diff --git a/android/build.gradle b/android/build.gradle index da4723c0c..6aa43c528 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -23,7 +23,7 @@ apply plugin: 'com.android.library' dependencies { // https://github.com/ably/ably-java/ - implementation 'io.ably:ably-android:1.2.15' + implementation 'io.ably:ably-android:1.2.27' // https://firebase.google.com/docs/cloud-messaging/android/client implementation 'com.google.firebase:firebase-messaging:23.0.4' diff --git a/android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java b/android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java index 879df8fa3..c2067bc61 100644 --- a/android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java +++ b/android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java @@ -1,5 +1,6 @@ package io.ably.flutter.plugin; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.firebase.messaging.RemoteMessage; @@ -7,7 +8,6 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.ToNumberPolicy; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; @@ -48,869 +48,934 @@ public class AblyMessageCodec extends StandardMessageCodec { - interface CodecEncoder { - Map encode(T value); - } - - interface CodecDecoder { - T decode(Map jsonMap); - } - - private static class CodecPair { - final CodecEncoder encoder; - final CodecDecoder decoder; - - CodecPair(CodecEncoder encoder, CodecDecoder decoder) { - this.encoder = encoder; - this.decoder = decoder; - } - - Map encode(final Object value) { - if (this.encoder == null) { - throw SerializationException.forEncoder(value.getClass()); - } - return this.encoder.encode((T) value); - } - - T decode(Map jsonMap) { - if (this.decoder == null) { - throw SerializationException.forDecoder(jsonMap); - } - return this.decoder.decode(jsonMap); - } - } - - private Map codecMap; - - private static final Gson gson = new Gson() - .newBuilder() - .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) - .create(); - - private final CipherParamsStorage cipherParamsStorage; - - public AblyMessageCodec(CipherParamsStorage cipherParamsStorage) { - final AblyMessageCodec self = this; - this.cipherParamsStorage = cipherParamsStorage; - codecMap = new HashMap() { - { - put(PlatformConstants.CodecTypes.ablyMessage, - new CodecPair<>(self::encodeAblyFlutterMessage, self::decodeAblyFlutterMessage)); - put(PlatformConstants.CodecTypes.ablyEventMessage, - new CodecPair<>(null, self::decodeAblyFlutterEventMessage)); - put(PlatformConstants.CodecTypes.clientOptions, - new CodecPair<>(null, self::decodeClientOptions)); - put(PlatformConstants.CodecTypes.tokenParams, - new CodecPair<>(self::encodeTokenParams, null)); - put(PlatformConstants.CodecTypes.tokenDetails, - new CodecPair<>(null, self::decodeTokenDetails)); - put(PlatformConstants.CodecTypes.tokenRequest, - new CodecPair<>(null, self::decodeTokenRequest)); - put(PlatformConstants.CodecTypes.restChannelOptions, - new CodecPair<>(null, self::decodeRestChannelOptions)); - put(PlatformConstants.CodecTypes.realtimeChannelOptions, - new CodecPair<>(null, self::decodeRealtimeChannelOptions)); - put(PlatformConstants.CodecTypes.paginatedResult, - new CodecPair<>(self::encodePaginatedResult, null)); - put(PlatformConstants.CodecTypes.restHistoryParams, - new CodecPair<>(null, self::decodeRestHistoryParams)); - put(PlatformConstants.CodecTypes.realtimeHistoryParams, - new CodecPair<>(null, self::decodeRealtimeHistoryParams)); - put(PlatformConstants.CodecTypes.restPresenceParams, - new CodecPair<>(null, self::decodeRestPresenceParams)); - put(PlatformConstants.CodecTypes.realtimePresenceParams, - new CodecPair<>(null, self::decodeRealtimePresenceParams)); - put(PlatformConstants.CodecTypes.errorInfo, - new CodecPair<>(self::encodeErrorInfo, null)); - put(PlatformConstants.CodecTypes.messageData, - new CodecPair<>(null, self::decodeChannelMessageData)); - put(PlatformConstants.CodecTypes.messageExtras, - new CodecPair<>(self::encodeChannelMessageExtras, self::decodeChannelMessageExtras)); - put(PlatformConstants.CodecTypes.message, - new CodecPair<>(self::encodeChannelMessage, self::decodeChannelMessage)); - put(PlatformConstants.CodecTypes.presenceMessage, - new CodecPair<>(self::encodePresenceMessage, null)); - put(PlatformConstants.CodecTypes.connectionStateChange, - new CodecPair<>(self::encodeConnectionStateChange, null)); - put(PlatformConstants.CodecTypes.channelStateChange, - new CodecPair<>(self::encodeChannelStateChange, null)); - put(PlatformConstants.CodecTypes.deviceDetails, - new CodecPair<>(self::encodeDeviceDetails, null)); - put(PlatformConstants.CodecTypes.localDevice, - new CodecPair<>(self::encodeLocalDevice, null)); - put(PlatformConstants.CodecTypes.pushChannelSubscription, - new CodecPair<>(self::encodePushChannelSubscription, null)); - put(PlatformConstants.CodecTypes.remoteMessage, - new CodecPair<>(self::encodeRemoteMessage, null)); - put(PlatformConstants.CodecTypes.cipherParams, - new CodecPair<>(self::encodeCipherParams, self::decodeCipherParams)); - } - }; - } - - @Override - protected Object readValueOfType(final byte type, final ByteBuffer buffer) { - CodecPair pair = codecMap.get(type); - if (pair != null) { - Map jsonMap = (Map) readValue(buffer); - return pair.decode(jsonMap); - } - return super.readValueOfType(type, buffer); - } - - private void readValueFromJson(Map jsonMap, String key, final Consumer consumer) { - final Object object = jsonMap.get(key); - if (null != object) { - consumer.accept(object); - } - } - - private void writeValueToJson(Map jsonMap, String key, Object value) { - if (null != value) { - jsonMap.put(key, value); - } - } - - private Byte getType(Object value) { - if (value instanceof AblyFlutterMessage) { - return PlatformConstants.CodecTypes.ablyMessage; - } else if (value instanceof ErrorInfo) { - return PlatformConstants.CodecTypes.errorInfo; - } else if (value instanceof Auth.TokenParams) { - return PlatformConstants.CodecTypes.tokenParams; - } else if (value instanceof AsyncPaginatedResult) { - return PlatformConstants.CodecTypes.paginatedResult; - } else if (value instanceof ConnectionStateListener.ConnectionStateChange) { - return PlatformConstants.CodecTypes.connectionStateChange; - } else if (value instanceof ChannelStateListener.ChannelStateChange) { - return PlatformConstants.CodecTypes.channelStateChange; - } else if (value instanceof PresenceMessage) { - return PlatformConstants.CodecTypes.presenceMessage; - } else if (value instanceof MessageExtras) { - return PlatformConstants.CodecTypes.messageExtras; - } else if (value instanceof Message) { - return PlatformConstants.CodecTypes.message; - } else if (value instanceof LocalDevice) { - return PlatformConstants.CodecTypes.localDevice; - } else if (value instanceof DeviceDetails) { - return PlatformConstants.CodecTypes.deviceDetails; - } else if (value instanceof Push.ChannelSubscription) { - return PlatformConstants.CodecTypes.pushChannelSubscription; - } else if (value instanceof RemoteMessage) { - return PlatformConstants.CodecTypes.remoteMessage; - } else if (value instanceof Crypto.CipherParams) { - return PlatformConstants.CodecTypes.cipherParams; - } else if (value instanceof ChannelOptions) { - // Encoding it into a RealtimeChannelOptions instance, because it extends RestChannelOptions - return PlatformConstants.CodecTypes.realtimeChannelOptions; - } - return null; - } - - @Override - protected void writeValue(ByteArrayOutputStream stream, Object value) { - Byte type = getType(value); - if (type != null) { - CodecPair pair = codecMap.get(type); - if (pair != null) { - stream.write(type); - writeValue(stream, pair.encode(value)); - return; - } - } - if (value instanceof JsonElement) { - WriteJsonElement(stream, (JsonElement) value); - return; - } - super.writeValue(stream, value); - } - - private void WriteJsonElement(ByteArrayOutputStream stream, JsonElement value) { - if (value instanceof JsonObject) { - super.writeValue(stream, gson.fromJson(value, Map.class)); - } else if (value instanceof JsonArray) { - super.writeValue(stream, gson.fromJson(value, ArrayList.class)); - } - } - - /** - * Converts Map to JsonObject, ArrayList to JsonArray and - * returns null if these 2 types are a no-match - */ - static JsonElement readValueAsJsonElement(final Object object) { - if (object instanceof Map) { - return gson.fromJson(gson.toJson(object, Map.class), JsonObject.class); - } else if (object instanceof ArrayList) { - return gson.fromJson(gson.toJson(object, ArrayList.class), JsonArray.class); - } - return null; - } - - /** - * Dart int types get delivered to Java as Integer, unless '32 bits not enough' in which case - * they are delivered as Long. - * See: https://flutter.dev/docs/development/platform-integration/platform-channels#codec - */ - private Long readValueAsLong(final Object object) { - if (null == object) { - return null; - } - if (object instanceof Integer) { - return ((Integer) object).longValue(); - } - return (Long) object; // will java.lang.ClassCastException if object is not a Long - } - - private AblyFlutterMessage decodeAblyFlutterMessage(Map jsonMap) { - if (jsonMap == null) return null; - final Long handle = readValueAsLong(jsonMap.get(PlatformConstants.TxAblyMessage.registrationHandle)); - final Object messageType = jsonMap.get(PlatformConstants.TxAblyMessage.type); - final Integer type = (messageType == null) ? null : Integer.parseInt(messageType.toString()); - Object message = jsonMap.get(PlatformConstants.TxAblyMessage.message); - if (type != null) { - message = codecMap.get((byte) (int) type).decode((Map) message); - } - return new AblyFlutterMessage<>(message, handle); - } - - private AblyEventMessage decodeAblyFlutterEventMessage(Map jsonMap) { - if (jsonMap == null) return null; - final String eventName = (String) jsonMap.get(PlatformConstants.TxAblyEventMessage.eventName); - final Object messageType = jsonMap.get(PlatformConstants.TxAblyEventMessage.type); - final Integer type = (messageType == null) ? null : Integer.parseInt(messageType.toString()); - Object message = jsonMap.get(PlatformConstants.TxAblyEventMessage.message); - if (type != null) { - message = codecMap.get((byte) (int) type).decode((Map) message); - } - return new AblyEventMessage<>(eventName, message); - } - - private PlatformClientOptions decodeClientOptions(Map jsonMap) { - if (jsonMap == null) return null; - final ClientOptions o = new ClientOptions(); - - // AuthOptions (super class of ClientOptions) - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authUrl, v -> o.authUrl = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authMethod, v -> o.authMethod = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.key, v -> o.key = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.tokenDetails, v -> o.tokenDetails = decodeTokenDetails((Map) v)); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authHeaders, v -> o.authHeaders = (Param[]) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authParams, v -> o.authParams = (Param[]) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.queryTime, v -> o.queryTime = (Boolean) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.useTokenAuth, v -> o.useTokenAuth = (Boolean) v); - - // ClientOptions - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.clientId, v -> o.clientId = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.logLevel, v -> o.logLevel = decodeLogLevel((String) v)); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.tls, v -> o.tls = (Boolean) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.restHost, v -> o.restHost = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.realtimeHost, v -> o.realtimeHost = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.port, v -> o.port = (Integer) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.tlsPort, v -> o.tlsPort = (Integer) v); - o.autoConnect = false; // Always avoid auto-connect, to allow handle to be returned back to Dart side before authCallback is called. - // If the user specifies autoConnect, we call connect once we get the handle back to the dart side - // In other words, Ably Flutter internally manually connects, but to the SDK user this looks like autoConnect. - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.useBinaryProtocol, v -> o.useBinaryProtocol = (Boolean) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.queueMessages, v -> o.queueMessages = (Boolean) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.echoMessages, v -> o.echoMessages = (Boolean) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.recover, v -> o.recover = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.environment, v -> o.environment = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.idempotentRestPublishing, v -> o.idempotentRestPublishing = (Boolean) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.httpOpenTimeout, v -> o.httpOpenTimeout = (Integer) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.httpRequestTimeout, v -> o.httpRequestTimeout = (Integer) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.httpMaxRetryCount, v -> o.httpMaxRetryCount = (Integer) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.realtimeRequestTimeout, v -> o.realtimeRequestTimeout = readValueAsLong(v)); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.fallbackHosts, v -> o.fallbackHosts = (String[]) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.fallbackHostsUseDefault, v -> o.fallbackHostsUseDefault = (Boolean) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.fallbackRetryTimeout, v -> o.fallbackRetryTimeout = readValueAsLong(v)); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.defaultTokenParams, v -> o.defaultTokenParams = decodeTokenParams((Map) v)); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.channelRetryTimeout, v -> o.channelRetryTimeout = (Integer) v); - readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.transportParams, v -> o.transportParams = decodeTransportParams((Map) v)); - - o.agents = new HashMap() {{ - put("ably-flutter", BuildConfig.FLUTTER_PACKAGE_PLUGIN_VERSION); - put("dart", (String) jsonMap.get(PlatformConstants.TxClientOptions.dartVersion)); - }}; - - - return new PlatformClientOptions(o, jsonMap.containsKey(PlatformConstants.TxClientOptions.hasAuthCallback) ? ((boolean) jsonMap.get(PlatformConstants.TxClientOptions.hasAuthCallback)) : false); - } - - private int decodeLogLevel(String logLevelString) { - if (logLevelString == null) return Log.WARN; - switch (logLevelString) { - case PlatformConstants.TxLogLevelEnum.none: - return Log.NONE; - case PlatformConstants.TxLogLevelEnum.verbose: - return Log.VERBOSE; - case PlatformConstants.TxLogLevelEnum.debug: - return Log.DEBUG; - case PlatformConstants.TxLogLevelEnum.info: - return Log.INFO; - case PlatformConstants.TxLogLevelEnum.error: - return Log.ERROR; - default: - throw SerializationException.forEnum(logLevelString, Log.class); - } - } - - private TokenDetails decodeTokenDetails(Map jsonMap) { - if (jsonMap == null) return null; - final TokenDetails o = new TokenDetails(); - readValueFromJson(jsonMap, PlatformConstants.TxTokenDetails.token, v -> o.token = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenDetails.expires, v -> o.expires = (long) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenDetails.issued, v -> o.issued = (long) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenDetails.capability, v -> o.capability = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenDetails.clientId, v -> o.clientId = (String) v); - - return o; - } - - private Auth.TokenParams decodeTokenParams(Map jsonMap) { - if (jsonMap == null) return null; - final Auth.TokenParams o = new Auth.TokenParams(); - readValueFromJson(jsonMap, PlatformConstants.TxTokenParams.capability, v -> o.capability = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenParams.clientId, v -> o.clientId = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenParams.timestamp, v -> o.timestamp = readValueAsLong(v)); - readValueFromJson(jsonMap, PlatformConstants.TxTokenParams.ttl, v -> o.ttl = readValueAsLong(v)); - // nonce is not supported in ably-java - // Track @ https://github.com/ably/ably-flutter/issues/14 - return o; - } - - private Param[] decodeTransportParams(Map jsonMap) { - if (jsonMap == null) return null; - // It's not possible to initialize the array here, because that way, - // Params will have a (null, null) entry, so we need to initialize it later with `Params.set` - Param[] transportParams = null; - for (String key: jsonMap.keySet()) { - // Params.set() creates new parms instance if o is null - transportParams = Param.set(transportParams, key, jsonMap.get(key)); - } - return transportParams; - } - - private Auth.TokenRequest decodeTokenRequest(Map jsonMap) { - if (jsonMap == null) return null; - final Auth.TokenRequest o = new Auth.TokenRequest(); - readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.keyName, v -> o.keyName = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.nonce, v -> o.nonce = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.mac, v -> o.mac = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.capability, v -> o.capability = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.clientId, v -> o.clientId = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.timestamp, v -> o.timestamp = readValueAsLong(v)); - readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.ttl, v -> o.ttl = readValueAsLong(v)); - return o; - } - - private ChannelOptions decodeRestChannelOptions(Map jsonMap) { - if (jsonMap == null) return null; - ChannelOptions options = new ChannelOptions(); - options.cipherParams = decodeCipherParams((Map) jsonMap.get(PlatformConstants.TxRestChannelOptions.cipherParams)); - if (options.cipherParams != null) { - options.encrypted = true; - } - return options; - } - - private ChannelOptions decodeRealtimeChannelOptions(Map jsonMap) { - if (jsonMap == null) return null; - ChannelOptions options = new ChannelOptions(); - options.cipherParams = decodeCipherParams((Map) jsonMap.get(PlatformConstants.TxRealtimeChannelOptions.cipherParams)); - if (options.cipherParams != null) { - options.encrypted = true; - } - options.params = (Map) jsonMap.get(PlatformConstants.TxRealtimeChannelOptions.params); - final ArrayList modes = (ArrayList) jsonMap.get(PlatformConstants.TxRealtimeChannelOptions.modes); - if (modes != null && modes.size() > 0) { - options.modes = createChannelModesArray(modes); - } - - return options; - } - - private Map encodeCipherParams(Crypto.CipherParams cipherParams) { - if (cipherParams == null) return null; - final Integer handle = cipherParamsStorage.getHandle(cipherParams); - HashMap jsonMap = new HashMap<>(); - - // All other properties in CipherParams are package private, so cannot be exposed to Dart side. - jsonMap.put(PlatformConstants.TxCipherParams.androidHandle, handle); - - return jsonMap; - } - - private Crypto.CipherParams decodeCipherParams(@Nullable Map cipherParamsDictionary) { - if (cipherParamsDictionary == null) return null; - final Integer cipherParamsHandle = (Integer) cipherParamsDictionary.get(PlatformConstants.TxCipherParams.androidHandle); - return cipherParamsStorage.from(cipherParamsHandle); - } - - private ChannelMode[] createChannelModesArray(ArrayList modesString) { - ChannelMode[] modes = new ChannelMode[modesString.size()]; - for (int i = 0; i < modesString.size(); i++) { - modes[i] = decodeChannelOptionsMode(modesString.get(i)); - } - return modes; - } - - private ChannelMode decodeChannelOptionsMode(String mode) { - switch (mode) { - case PlatformConstants.TxEnumConstants.presence: - return ChannelMode.presence; - case PlatformConstants.TxEnumConstants.publish: - return ChannelMode.publish; - case PlatformConstants.TxEnumConstants.subscribe: - return ChannelMode.subscribe; - case PlatformConstants.TxEnumConstants.presenceSubscribe: - return ChannelMode.presence_subscribe; - default: - throw SerializationException.forEnum(mode, ChannelMode.class); - } - } - - private Param[] decodeRestHistoryParams(Map jsonMap) { - if (jsonMap == null) return null; - Param[] params = new Param[jsonMap.size()]; - int index = 0; - final Object start = jsonMap.get(PlatformConstants.TxRestHistoryParams.start); - final Object end = jsonMap.get(PlatformConstants.TxRestHistoryParams.end); - final Object limit = jsonMap.get(PlatformConstants.TxRestHistoryParams.limit); - final Object direction = jsonMap.get(PlatformConstants.TxRestHistoryParams.direction); - if (start != null) { - params[index++] = new Param(PlatformConstants.TxRestHistoryParams.start, readValueAsLong(start)); - } - if (end != null) { - params[index++] = new Param(PlatformConstants.TxRestHistoryParams.end, readValueAsLong(end)); - } - if (limit != null) { - params[index++] = new Param(PlatformConstants.TxRestHistoryParams.limit, (Integer) limit); - } - if (direction != null) { - params[index] = new Param(PlatformConstants.TxRestHistoryParams.direction, (String) direction); - } - return params; - } - - private Param[] decodeRealtimeHistoryParams(Map jsonMap) { - if (jsonMap == null) return null; - Param[] params = new Param[jsonMap.size()]; - int index = 0; - final Object start = jsonMap.get(PlatformConstants.TxRealtimeHistoryParams.start); - final Object end = jsonMap.get(PlatformConstants.TxRealtimeHistoryParams.end); - final Object limit = jsonMap.get(PlatformConstants.TxRealtimeHistoryParams.limit); - final Object direction = jsonMap.get(PlatformConstants.TxRealtimeHistoryParams.direction); - final Object untilAttach = jsonMap.get(PlatformConstants.TxRealtimeHistoryParams.untilAttach); - if (start != null) { - params[index++] = new Param(PlatformConstants.TxRealtimeHistoryParams.start, readValueAsLong(start)); - } - if (end != null) { - params[index++] = new Param(PlatformConstants.TxRealtimeHistoryParams.end, readValueAsLong(end)); - } - if (limit != null) { - params[index++] = new Param(PlatformConstants.TxRealtimeHistoryParams.limit, (Integer) limit); - } - if (direction != null) { - params[index++] = new Param(PlatformConstants.TxRealtimeHistoryParams.direction, (String) direction); - } - if (untilAttach != null) { - params[index] = new Param(PlatformConstants.TxRealtimeHistoryParams.untilAttach, (boolean) untilAttach); - } - return params; - } - - private Param[] decodeRestPresenceParams(Map jsonMap) { - if (jsonMap == null) return null; - Param[] params = new Param[jsonMap.size()]; - int index = 0; - final Object limit = jsonMap.get(PlatformConstants.TxRestPresenceParams.limit); - final Object clientId = jsonMap.get(PlatformConstants.TxRestPresenceParams.clientId); - final Object connectionId = jsonMap.get(PlatformConstants.TxRestPresenceParams.connectionId); - if (limit != null) { - params[index++] = new Param(PlatformConstants.TxRestPresenceParams.limit, (Integer) limit); - } - if (clientId != null) { - params[index++] = new Param(PlatformConstants.TxRestPresenceParams.clientId, (String) clientId); - } - if (connectionId != null) { - params[index] = new Param(PlatformConstants.TxRestPresenceParams.connectionId, (String) connectionId); - } - return params; - } - - private Param[] decodeRealtimePresenceParams(Map jsonMap) { - if (jsonMap == null) return null; - Param[] params = new Param[jsonMap.size()]; - int index = 0; - final Object waitForSync = jsonMap.get(PlatformConstants.TxRealtimePresenceParams.waitForSync); - final Object clientId = jsonMap.get(PlatformConstants.TxRealtimePresenceParams.clientId); - final Object connectionId = jsonMap.get(PlatformConstants.TxRealtimePresenceParams.connectionId); - if (waitForSync != null) { - params[index++] = new Param(PlatformConstants.TxRealtimePresenceParams.waitForSync, (Boolean) waitForSync); - } - if (clientId != null) { - params[index++] = new Param(PlatformConstants.TxRealtimePresenceParams.clientId, (String) clientId); - } - if (connectionId != null) { - params[index] = new Param(PlatformConstants.TxRealtimePresenceParams.connectionId, (String) connectionId); - } - return params; - } - - private Object decodeMessageData(Object messageData) { - final JsonElement json = readValueAsJsonElement(messageData); - return (json == null) ? messageData : json; - } - - private Object decodeChannelMessageData(Map jsonMap) { - if (jsonMap == null) return null; - return decodeMessageData(jsonMap.get(PlatformConstants.TxMessage.data)); - } - - private MessageExtras decodeChannelMessageExtras(Map jsonMap) { - if (jsonMap == null) return null; - Object extras = jsonMap.get(PlatformConstants.TxMessageExtras.extras); - // extras received from dart side could be a nested map with dynamic value types - // So, converting to a json string and then using that to create a JSONObject - String extrasJson = gson.toJson(extras, Map.class); - return new MessageExtras(gson.fromJson(extrasJson, JsonObject.class)); - } - - private Message decodeChannelMessage(Map jsonMap) { - if (jsonMap == null) return null; - final Message o = new Message(); - readValueFromJson(jsonMap, PlatformConstants.TxMessage.id, v -> o.id = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxMessage.clientId, v -> o.clientId = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxMessage.name, v -> o.name = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxMessage.data, v -> o.data = decodeMessageData(v)); - readValueFromJson(jsonMap, PlatformConstants.TxMessage.encoding, v -> o.encoding = (String) v); - readValueFromJson(jsonMap, PlatformConstants.TxMessage.extras, v -> o.extras = (MessageExtras) v); - return o; - } + interface CodecEncoder { + Map encode(T value); + } + + interface CodecDecoder { + T decode(Map jsonMap); + } + + private static class CodecPair { + final CodecEncoder encoder; + final CodecDecoder decoder; + + CodecPair(CodecEncoder encoder, CodecDecoder decoder) { + this.encoder = encoder; + this.decoder = decoder; + } + + Map encode(final Object value) { + if (this.encoder == null) { + throw SerializationException.forEncoder(value.getClass()); + } + return this.encoder.encode((T) value); + } + + T decode(Map jsonMap) { + if (this.decoder == null) { + throw SerializationException.forDecoder(jsonMap); + } + return this.decoder.decode(jsonMap); + } + } + + private Map codecMap; + private static final Gson gson = new Gson(); + private final CipherParamsStorage cipherParamsStorage; + + + public AblyMessageCodec(CipherParamsStorage cipherParamsStorage) { + final AblyMessageCodec self = this; + this.cipherParamsStorage = cipherParamsStorage; + codecMap = new HashMap() { + { + put(PlatformConstants.CodecTypes.ablyMessage, + new CodecPair<>(self::encodeAblyFlutterMessage, self::decodeAblyFlutterMessage)); + put(PlatformConstants.CodecTypes.ablyEventMessage, + new CodecPair<>(null, self::decodeAblyFlutterEventMessage)); + put(PlatformConstants.CodecTypes.clientOptions, + new CodecPair<>(null, self::decodeClientOptions)); + put(PlatformConstants.CodecTypes.authOptions, + new CodecPair<>(null, self::decodeAuthOptions)); + put(PlatformConstants.CodecTypes.tokenParams, + new CodecPair<>(self::encodeTokenParams, self::decodeTokenParams)); + put(PlatformConstants.CodecTypes.tokenDetails, + new CodecPair<>(self::encodeTokenDetails, self::decodeTokenDetails)); + put(PlatformConstants.CodecTypes.tokenRequest, + new CodecPair<>(self::encodeTokenRequest, self::decodeTokenRequest)); + put(PlatformConstants.CodecTypes.restChannelOptions, + new CodecPair<>(null, self::decodeRestChannelOptions)); + put(PlatformConstants.CodecTypes.realtimeChannelOptions, + new CodecPair<>(null, self::decodeRealtimeChannelOptions)); + put(PlatformConstants.CodecTypes.paginatedResult, + new CodecPair<>(self::encodePaginatedResult, null)); + put(PlatformConstants.CodecTypes.restHistoryParams, + new CodecPair<>(null, self::decodeRestHistoryParams)); + put(PlatformConstants.CodecTypes.realtimeHistoryParams, + new CodecPair<>(null, self::decodeRealtimeHistoryParams)); + put(PlatformConstants.CodecTypes.restPresenceParams, + new CodecPair<>(null, self::decodeRestPresenceParams)); + put(PlatformConstants.CodecTypes.realtimePresenceParams, + new CodecPair<>(null, self::decodeRealtimePresenceParams)); + put(PlatformConstants.CodecTypes.errorInfo, + new CodecPair<>(self::encodeErrorInfo, null)); + put(PlatformConstants.CodecTypes.messageData, + new CodecPair<>(null, self::decodeChannelMessageData)); + put(PlatformConstants.CodecTypes.messageExtras, + new CodecPair<>(self::encodeChannelMessageExtras, self::decodeChannelMessageExtras)); + put(PlatformConstants.CodecTypes.message, + new CodecPair<>(self::encodeChannelMessage, self::decodeChannelMessage)); + put(PlatformConstants.CodecTypes.presenceMessage, + new CodecPair<>(self::encodePresenceMessage, null)); + put(PlatformConstants.CodecTypes.connectionStateChange, + new CodecPair<>(self::encodeConnectionStateChange, null)); + put(PlatformConstants.CodecTypes.channelStateChange, + new CodecPair<>(self::encodeChannelStateChange, null)); + put(PlatformConstants.CodecTypes.deviceDetails, + new CodecPair<>(self::encodeDeviceDetails, null)); + put(PlatformConstants.CodecTypes.localDevice, + new CodecPair<>(self::encodeLocalDevice, null)); + put(PlatformConstants.CodecTypes.pushChannelSubscription, + new CodecPair<>(self::encodePushChannelSubscription, null)); + put(PlatformConstants.CodecTypes.remoteMessage, + new CodecPair<>(self::encodeRemoteMessage, null)); + put(PlatformConstants.CodecTypes.cipherParams, + new CodecPair<>(self::encodeCipherParams, self::decodeCipherParams)); + } + }; + } + + @Override + protected Object readValueOfType(final byte type, final ByteBuffer buffer) { + CodecPair pair = codecMap.get(type); + if (pair != null) { + Map jsonMap = (Map) readValue(buffer); + return pair.decode(jsonMap); + } + return super.readValueOfType(type, buffer); + } + + private void readValueFromJson(Map jsonMap, String key, final Consumer consumer) { + final Object object = jsonMap.get(key); + if (null != object) { + consumer.accept(object); + } + } + + private void writeValueToJson(Map jsonMap, String key, Object value) { + if (null != value) { + jsonMap.put(key, value); + } + } + + private Byte getType(Object value) { + if (value instanceof AblyFlutterMessage) { + return PlatformConstants.CodecTypes.ablyMessage; + } else if (value instanceof ErrorInfo) { + return PlatformConstants.CodecTypes.errorInfo; + } else if(value instanceof Auth.TokenRequest){ + return PlatformConstants.CodecTypes.tokenRequest; + }else if (value instanceof Auth.TokenParams) { + return PlatformConstants.CodecTypes.tokenParams; + } else if (value instanceof AsyncPaginatedResult) { + return PlatformConstants.CodecTypes.paginatedResult; + } else if (value instanceof ConnectionStateListener.ConnectionStateChange) { + return PlatformConstants.CodecTypes.connectionStateChange; + } else if (value instanceof ChannelStateListener.ChannelStateChange) { + return PlatformConstants.CodecTypes.channelStateChange; + } else if (value instanceof PresenceMessage) { + return PlatformConstants.CodecTypes.presenceMessage; + } else if (value instanceof MessageExtras) { + return PlatformConstants.CodecTypes.messageExtras; + } else if (value instanceof Message) { + return PlatformConstants.CodecTypes.message; + } else if (value instanceof LocalDevice) { + return PlatformConstants.CodecTypes.localDevice; + } else if (value instanceof DeviceDetails) { + return PlatformConstants.CodecTypes.deviceDetails; + } else if (value instanceof Push.ChannelSubscription) { + return PlatformConstants.CodecTypes.pushChannelSubscription; + } else if (value instanceof RemoteMessage) { + return PlatformConstants.CodecTypes.remoteMessage; + } else if (value instanceof Crypto.CipherParams) { + return PlatformConstants.CodecTypes.cipherParams; + } else if (value instanceof ChannelOptions) { + // Encoding it into a RealtimeChannelOptions instance, because it extends RestChannelOptions + return PlatformConstants.CodecTypes.realtimeChannelOptions; + } else if (value instanceof TokenDetails) { + return PlatformConstants.CodecTypes.tokenDetails; + } + return null; + } + + @Override + protected void writeValue(ByteArrayOutputStream stream, Object value) { + Byte type = getType(value); + if (type != null) { + CodecPair pair = codecMap.get(type); + if (pair != null) { + stream.write(type); + writeValue(stream, pair.encode(value)); + return; + } + } + if (value instanceof JsonElement) { + WriteJsonElement(stream, (JsonElement) value); + return; + } + super.writeValue(stream, value); + } + + private void WriteJsonElement(ByteArrayOutputStream stream, JsonElement value) { + if (value instanceof JsonObject) { + super.writeValue(stream, gson.fromJson(value, Map.class)); + } else if (value instanceof JsonArray) { + super.writeValue(stream, gson.fromJson(value, ArrayList.class)); + } + } + + /** + * Converts Map to JsonObject, ArrayList to JsonArray and + * returns null if these 2 types are a no-match + */ + static JsonElement readValueAsJsonElement(final Object object) { + if (object instanceof Map) { + return gson.fromJson(gson.toJson(object, Map.class), JsonObject.class); + } else if (object instanceof ArrayList) { + return gson.fromJson(gson.toJson(object, ArrayList.class), JsonArray.class); + } + return null; + } + + /** + * Dart int types get delivered to Java as Integer, unless '32 bits not enough' in which case + * they are delivered as Long. + * See: https://flutter.dev/docs/development/platform-integration/platform-channels#codec + */ + private Long readValueAsLong(final Object object) { + if (null == object) { + return null; + } + if (object instanceof Integer) { + return ((Integer) object).longValue(); + } + return (Long) object; // will java.lang.ClassCastException if object is not a Long + } + + private AblyFlutterMessage decodeAblyFlutterMessage(Map jsonMap) { + if (jsonMap == null) return null; + final Long handle = readValueAsLong(jsonMap.get(PlatformConstants.TxAblyMessage.registrationHandle)); + final Object messageType = jsonMap.get(PlatformConstants.TxAblyMessage.type); + final Integer type = (messageType == null) ? null : Integer.parseInt(messageType.toString()); + Object message = jsonMap.get(PlatformConstants.TxAblyMessage.message); + if (type != null) { + message = codecMap.get((byte) (int) type).decode((Map) message); + } + return new AblyFlutterMessage<>(message, handle); + } + + private AblyEventMessage decodeAblyFlutterEventMessage(Map jsonMap) { + if (jsonMap == null) return null; + final String eventName = (String) jsonMap.get(PlatformConstants.TxAblyEventMessage.eventName); + final Object messageType = jsonMap.get(PlatformConstants.TxAblyEventMessage.type); + final Integer type = (messageType == null) ? null : Integer.parseInt(messageType.toString()); + Object message = jsonMap.get(PlatformConstants.TxAblyEventMessage.message); + if (type != null) { + message = codecMap.get((byte) (int) type).decode((Map) message); + } + return new AblyEventMessage<>(eventName, message); + } + + + private Auth.AuthOptions decodeAuthOptions(Map jsonMap) { + if (jsonMap == null) return null; + final Auth.AuthOptions authOptions = new Auth.AuthOptions(); + + // AuthOptions + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authUrl, value -> authOptions.authUrl = (String) value); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authMethod, v -> authOptions.authMethod = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.key, v -> authOptions.key = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.tokenDetails, v -> authOptions.tokenDetails = decodeTokenDetails((Map) v)); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authHeaders, value -> { + authOptions.authHeaders = mapToParams((HashMap) value); + }); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authParams, value -> { + authOptions.authParams = mapToParams((HashMap) value); + }); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.queryTime, v -> authOptions.queryTime = (Boolean) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.useTokenAuth, v -> authOptions.useTokenAuth = (Boolean) v); + + return authOptions; + } + + private PlatformClientOptions decodeClientOptions(Map jsonMap) { + if (jsonMap == null) return null; + final ClientOptions clientOptions = new ClientOptions(); + + // AuthOptions (super class of ClientOptions) + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authUrl, value -> clientOptions.authUrl = (String) value); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authMethod, v -> clientOptions.authMethod = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.key, v -> clientOptions.key = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.tokenDetails, v -> clientOptions.tokenDetails = decodeTokenDetails((Map) v)); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authHeaders, value -> { + clientOptions.authHeaders = mapToParams((HashMap) value); + }); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.authParams, value -> { + clientOptions.authParams = mapToParams((HashMap) value); + }); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.queryTime, v -> clientOptions.queryTime = (Boolean) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.useTokenAuth, v -> clientOptions.useTokenAuth = (Boolean) v); + + // ClientOptions + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.clientId, v -> clientOptions.clientId = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.logLevel, v -> clientOptions.logLevel = decodeLogLevel((String) v)); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.tls, v -> clientOptions.tls = (Boolean) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.restHost, v -> clientOptions.restHost = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.realtimeHost, v -> clientOptions.realtimeHost = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.port, v -> clientOptions.port = (Integer) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.tlsPort, v -> clientOptions.tlsPort = (Integer) v); + clientOptions.autoConnect = false; // Always avoid auto-connect, to allow handle to be returned back to Dart side before authCallback is called. + // If the user specifies autoConnect, we call connect once we get the handle back to the dart side + // In other words, Ably Flutter internally manually connects, but to the SDK user this looks like autoConnect. + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.useBinaryProtocol, v -> clientOptions.useBinaryProtocol = (Boolean) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.queueMessages, v -> clientOptions.queueMessages = (Boolean) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.echoMessages, v -> clientOptions.echoMessages = (Boolean) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.recover, v -> clientOptions.recover = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.environment, v -> clientOptions.environment = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.idempotentRestPublishing, v -> clientOptions.idempotentRestPublishing = (Boolean) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.httpOpenTimeout, v -> clientOptions.httpOpenTimeout = (Integer) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.httpRequestTimeout, v -> clientOptions.httpRequestTimeout = (Integer) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.httpMaxRetryCount, v -> clientOptions.httpMaxRetryCount = (Integer) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.realtimeRequestTimeout, v -> clientOptions.realtimeRequestTimeout = readValueAsLong(v)); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.fallbackHosts, v -> clientOptions.fallbackHosts = (String[]) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.fallbackHostsUseDefault, v -> clientOptions.fallbackHostsUseDefault = (Boolean) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.fallbackRetryTimeout, v -> clientOptions.fallbackRetryTimeout = readValueAsLong(v)); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.defaultTokenParams, v -> clientOptions.defaultTokenParams = decodeTokenParams((Map) v)); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.channelRetryTimeout, v -> clientOptions.channelRetryTimeout = (Integer) v); + readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.transportParams, v -> clientOptions.transportParams = decodeTransportParams((Map) v)); + + clientOptions.agents = new HashMap() {{ + put("ably-flutter", BuildConfig.FLUTTER_PACKAGE_PLUGIN_VERSION); + put("dart", (String) jsonMap.get(PlatformConstants.TxClientOptions.dartVersion)); + }}; + + + return new PlatformClientOptions(clientOptions, jsonMap.containsKey(PlatformConstants.TxClientOptions.hasAuthCallback) ? ((boolean) jsonMap.get(PlatformConstants.TxClientOptions.hasAuthCallback)) : false); + } + + @NonNull + private Param[] mapToParams(HashMap value) { + final HashMap paramMap = value; + final Param[] params = new Param[paramMap.size()]; + + int index = 0; + for (final String key : paramMap.keySet()) { + params[index++] = new Param(key, paramMap.get(key)); + } + return params; + } + + private int decodeLogLevel(String logLevelString) { + if (logLevelString == null) return Log.WARN; + switch (logLevelString) { + case PlatformConstants.TxLogLevelEnum.none: + return Log.NONE; + case PlatformConstants.TxLogLevelEnum.verbose: + return Log.VERBOSE; + case PlatformConstants.TxLogLevelEnum.debug: + return Log.DEBUG; + case PlatformConstants.TxLogLevelEnum.info: + return Log.INFO; + case PlatformConstants.TxLogLevelEnum.error: + return Log.ERROR; + default: + throw SerializationException.forEnum(logLevelString, Log.class); + } + } + + private TokenDetails decodeTokenDetails(Map jsonMap) { + if (jsonMap == null) return null; + final TokenDetails o = new TokenDetails(); + readValueFromJson(jsonMap, PlatformConstants.TxTokenDetails.token, v -> o.token = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenDetails.expires, v -> o.expires = (long) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenDetails.issued, v -> o.issued = (long) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenDetails.capability, v -> o.capability = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenDetails.clientId, v -> o.clientId = (String) v); + + return o; + } + + private Auth.TokenParams decodeTokenParams(Map jsonMap) { + if (jsonMap == null) return null; + final Auth.TokenParams o = new Auth.TokenParams(); + readValueFromJson(jsonMap, PlatformConstants.TxTokenParams.capability, v -> o.capability = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenParams.clientId, v -> o.clientId = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenParams.timestamp, v -> o.timestamp = readValueAsLong(v)); + readValueFromJson(jsonMap, PlatformConstants.TxTokenParams.ttl, v -> o.ttl = readValueAsLong(v)); + // nonce is not supported in ably-java + // Track @ https://github.com/ably/ably-flutter/issues/14 + return o; + } + + private Param[] decodeTransportParams(Map jsonMap) { + if (jsonMap == null) return null; + // It's not possible to initialize the array here, because that way, + // Params will have a (null, null) entry, so we need to initialize it later with `Params.set` + Param[] transportParams = null; + for (String key : jsonMap.keySet()) { + // Params.set() creates new parms instance if o is null + transportParams = Param.set(transportParams, key, jsonMap.get(key)); + } + return transportParams; + } + + private Auth.TokenRequest decodeTokenRequest(Map jsonMap) { + if (jsonMap == null) return null; + final Auth.TokenRequest o = new Auth.TokenRequest(); + readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.keyName, v -> o.keyName = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.nonce, v -> o.nonce = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.mac, v -> o.mac = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.capability, v -> o.capability = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.clientId, v -> o.clientId = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.timestamp, v -> o.timestamp = readValueAsLong(v)); + readValueFromJson(jsonMap, PlatformConstants.TxTokenRequest.ttl, v -> o.ttl = readValueAsLong(v)); + return o; + } + + private ChannelOptions decodeRestChannelOptions(Map jsonMap) { + if (jsonMap == null) return null; + ChannelOptions options = new ChannelOptions(); + options.cipherParams = decodeCipherParams((Map) jsonMap.get(PlatformConstants.TxRestChannelOptions.cipherParams)); + if (options.cipherParams != null) { + options.encrypted = true; + } + return options; + } + + private ChannelOptions decodeRealtimeChannelOptions(Map jsonMap) { + if (jsonMap == null) return null; + ChannelOptions options = new ChannelOptions(); + options.cipherParams = decodeCipherParams((Map) jsonMap.get(PlatformConstants.TxRealtimeChannelOptions.cipherParams)); + if (options.cipherParams != null) { + options.encrypted = true; + } + options.params = (Map) jsonMap.get(PlatformConstants.TxRealtimeChannelOptions.params); + final ArrayList modes = (ArrayList) jsonMap.get(PlatformConstants.TxRealtimeChannelOptions.modes); + if (modes != null && modes.size() > 0) { + options.modes = createChannelModesArray(modes); + } + + return options; + } + + private Map encodeCipherParams(Crypto.CipherParams cipherParams) { + if (cipherParams == null) return null; + final Integer handle = cipherParamsStorage.getHandle(cipherParams); + HashMap jsonMap = new HashMap<>(); + + // All other properties in CipherParams are package private, so cannot be exposed to Dart side. + jsonMap.put(PlatformConstants.TxCipherParams.androidHandle, handle); + + return jsonMap; + } + + private Crypto.CipherParams decodeCipherParams(@Nullable Map cipherParamsDictionary) { + if (cipherParamsDictionary == null) return null; + final Integer cipherParamsHandle = (Integer) cipherParamsDictionary.get(PlatformConstants.TxCipherParams.androidHandle); + return cipherParamsStorage.from(cipherParamsHandle); + } + + private ChannelMode[] createChannelModesArray(ArrayList modesString) { + ChannelMode[] modes = new ChannelMode[modesString.size()]; + for (int i = 0; i < modesString.size(); i++) { + modes[i] = decodeChannelOptionsMode(modesString.get(i)); + } + return modes; + } + + private ChannelMode decodeChannelOptionsMode(String mode) { + switch (mode) { + case PlatformConstants.TxEnumConstants.presence: + return ChannelMode.presence; + case PlatformConstants.TxEnumConstants.publish: + return ChannelMode.publish; + case PlatformConstants.TxEnumConstants.subscribe: + return ChannelMode.subscribe; + case PlatformConstants.TxEnumConstants.presenceSubscribe: + return ChannelMode.presence_subscribe; + default: + throw SerializationException.forEnum(mode, ChannelMode.class); + } + } + + private Param[] decodeRestHistoryParams(Map jsonMap) { + if (jsonMap == null) return null; + Param[] params = new Param[jsonMap.size()]; + int index = 0; + final Object start = jsonMap.get(PlatformConstants.TxRestHistoryParams.start); + final Object end = jsonMap.get(PlatformConstants.TxRestHistoryParams.end); + final Object limit = jsonMap.get(PlatformConstants.TxRestHistoryParams.limit); + final Object direction = jsonMap.get(PlatformConstants.TxRestHistoryParams.direction); + if (start != null) { + params[index++] = new Param(PlatformConstants.TxRestHistoryParams.start, readValueAsLong(start)); + } + if (end != null) { + params[index++] = new Param(PlatformConstants.TxRestHistoryParams.end, readValueAsLong(end)); + } + if (limit != null) { + params[index++] = new Param(PlatformConstants.TxRestHistoryParams.limit, (Integer) limit); + } + if (direction != null) { + params[index] = new Param(PlatformConstants.TxRestHistoryParams.direction, (String) direction); + } + return params; + } + + private Param[] decodeRealtimeHistoryParams(Map jsonMap) { + if (jsonMap == null) return null; + Param[] params = new Param[jsonMap.size()]; + int index = 0; + final Object start = jsonMap.get(PlatformConstants.TxRealtimeHistoryParams.start); + final Object end = jsonMap.get(PlatformConstants.TxRealtimeHistoryParams.end); + final Object limit = jsonMap.get(PlatformConstants.TxRealtimeHistoryParams.limit); + final Object direction = jsonMap.get(PlatformConstants.TxRealtimeHistoryParams.direction); + final Object untilAttach = jsonMap.get(PlatformConstants.TxRealtimeHistoryParams.untilAttach); + if (start != null) { + params[index++] = new Param(PlatformConstants.TxRealtimeHistoryParams.start, readValueAsLong(start)); + } + if (end != null) { + params[index++] = new Param(PlatformConstants.TxRealtimeHistoryParams.end, readValueAsLong(end)); + } + if (limit != null) { + params[index++] = new Param(PlatformConstants.TxRealtimeHistoryParams.limit, (Integer) limit); + } + if (direction != null) { + params[index++] = new Param(PlatformConstants.TxRealtimeHistoryParams.direction, (String) direction); + } + if (untilAttach != null) { + params[index] = new Param(PlatformConstants.TxRealtimeHistoryParams.untilAttach, (boolean) untilAttach); + } + return params; + } + + private Param[] decodeRestPresenceParams(Map jsonMap) { + if (jsonMap == null) return null; + Param[] params = new Param[jsonMap.size()]; + int index = 0; + final Object limit = jsonMap.get(PlatformConstants.TxRestPresenceParams.limit); + final Object clientId = jsonMap.get(PlatformConstants.TxRestPresenceParams.clientId); + final Object connectionId = jsonMap.get(PlatformConstants.TxRestPresenceParams.connectionId); + if (limit != null) { + params[index++] = new Param(PlatformConstants.TxRestPresenceParams.limit, (Integer) limit); + } + if (clientId != null) { + params[index++] = new Param(PlatformConstants.TxRestPresenceParams.clientId, (String) clientId); + } + if (connectionId != null) { + params[index] = new Param(PlatformConstants.TxRestPresenceParams.connectionId, (String) connectionId); + } + return params; + } + + private Param[] decodeRealtimePresenceParams(Map jsonMap) { + if (jsonMap == null) return null; + Param[] params = new Param[jsonMap.size()]; + int index = 0; + final Object waitForSync = jsonMap.get(PlatformConstants.TxRealtimePresenceParams.waitForSync); + final Object clientId = jsonMap.get(PlatformConstants.TxRealtimePresenceParams.clientId); + final Object connectionId = jsonMap.get(PlatformConstants.TxRealtimePresenceParams.connectionId); + if (waitForSync != null) { + params[index++] = new Param(PlatformConstants.TxRealtimePresenceParams.waitForSync, (Boolean) waitForSync); + } + if (clientId != null) { + params[index++] = new Param(PlatformConstants.TxRealtimePresenceParams.clientId, (String) clientId); + } + if (connectionId != null) { + params[index] = new Param(PlatformConstants.TxRealtimePresenceParams.connectionId, (String) connectionId); + } + return params; + } + + private Object decodeMessageData(Object messageData) { + final JsonElement json = readValueAsJsonElement(messageData); + return (json == null) ? messageData : json; + } + + private Object decodeChannelMessageData(Map jsonMap) { + if (jsonMap == null) return null; + return decodeMessageData(jsonMap.get(PlatformConstants.TxMessage.data)); + } + + private MessageExtras decodeChannelMessageExtras(Map jsonMap) { + if (jsonMap == null) return null; + Object extras = jsonMap.get(PlatformConstants.TxMessageExtras.extras); + // extras received from dart side could be a nested map with dynamic value types + // So, converting to a json string and then using that to create a JSONObject + String extrasJson = gson.toJson(extras, Map.class); + return new MessageExtras(gson.fromJson(extrasJson, JsonObject.class)); + } + + private Message decodeChannelMessage(Map jsonMap) { + if (jsonMap == null) return null; + final Message o = new Message(); + readValueFromJson(jsonMap, PlatformConstants.TxMessage.id, v -> o.id = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxMessage.clientId, v -> o.clientId = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxMessage.name, v -> o.name = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxMessage.data, v -> o.data = decodeMessageData(v)); + readValueFromJson(jsonMap, PlatformConstants.TxMessage.encoding, v -> o.encoding = (String) v); + readValueFromJson(jsonMap, PlatformConstants.TxMessage.extras, v -> o.extras = (MessageExtras) v); + return o; + } //=============================================================== //=====================HANDLING WRITE============================ //=============================================================== - private Map encodeAblyFlutterMessage(AblyFlutterMessage c) { - if (c == null) return null; - HashMap jsonMap = new HashMap<>(); - writeValueToJson(jsonMap, PlatformConstants.TxAblyMessage.registrationHandle, c.handle); - Byte type = getType(c.message); - CodecPair pair = codecMap.get(type); - if (type != null && pair != null) { - writeValueToJson(jsonMap, PlatformConstants.TxAblyMessage.type, type & 0xff); - writeValueToJson(jsonMap, PlatformConstants.TxAblyMessage.message, pair.encode(c.message)); - } - return jsonMap; - } - - private Map encodeErrorInfo(ErrorInfo c) { - if (c == null) return null; - HashMap jsonMap = new HashMap<>(); - writeValueToJson(jsonMap, PlatformConstants.TxErrorInfo.code, c.code); - writeValueToJson(jsonMap, PlatformConstants.TxErrorInfo.message, c.message); - writeValueToJson(jsonMap, PlatformConstants.TxErrorInfo.statusCode, c.statusCode); - writeValueToJson(jsonMap, PlatformConstants.TxErrorInfo.href, c.href); - // requestId and cause - not available in ably-java - // track @ https://github.com/ably/ably-flutter/issues/14 - return jsonMap; - } - - private Map encodeTokenParams(Auth.TokenParams c) { - if (c == null) return null; - HashMap jsonMap = new HashMap<>(); - writeValueToJson(jsonMap, PlatformConstants.TxTokenParams.capability, c.capability); - writeValueToJson(jsonMap, PlatformConstants.TxTokenParams.clientId, c.clientId); - writeValueToJson(jsonMap, PlatformConstants.TxTokenParams.timestamp, c.timestamp); - writeValueToJson(jsonMap, PlatformConstants.TxTokenParams.ttl, c.ttl); - // nonce is not supported in ably-java - // Track @ https://github.com/ably/ably-flutter/issues/14 - return jsonMap; - } - - private String encodeConnectionState(ConnectionState state) { - switch (state) { - case initialized: - return PlatformConstants.TxEnumConstants.initialized; - case connecting: - return PlatformConstants.TxEnumConstants.connecting; - case connected: - return PlatformConstants.TxEnumConstants.connected; - case disconnected: - return PlatformConstants.TxEnumConstants.disconnected; - case suspended: - return PlatformConstants.TxEnumConstants.suspended; - case closing: - return PlatformConstants.TxEnumConstants.closing; - case closed: - return PlatformConstants.TxEnumConstants.closed; - case failed: - return PlatformConstants.TxEnumConstants.failed; - default: - throw SerializationException.forEnum(state, String.class); - } - } - - private String encodeConnectionEvent(ConnectionEvent event) { - switch (event) { - case initialized: - return PlatformConstants.TxEnumConstants.initialized; - case connecting: - return PlatformConstants.TxEnumConstants.connecting; - case connected: - return PlatformConstants.TxEnumConstants.connected; - case disconnected: - return PlatformConstants.TxEnumConstants.disconnected; - case suspended: - return PlatformConstants.TxEnumConstants.suspended; - case closing: - return PlatformConstants.TxEnumConstants.closing; - case closed: - return PlatformConstants.TxEnumConstants.closed; - case failed: - return PlatformConstants.TxEnumConstants.failed; - case update: - return PlatformConstants.TxEnumConstants.update; - default: - throw SerializationException.forEnum(event, String.class); - } - } - - private String encodeChannelState(ChannelState state) { - switch (state) { - case initialized: - return PlatformConstants.TxEnumConstants.initialized; - case attaching: - return PlatformConstants.TxEnumConstants.attaching; - case attached: - return PlatformConstants.TxEnumConstants.attached; - case detaching: - return PlatformConstants.TxEnumConstants.detaching; - case detached: - return PlatformConstants.TxEnumConstants.detached; - case failed: - return PlatformConstants.TxEnumConstants.failed; - case suspended: - return PlatformConstants.TxEnumConstants.suspended; - default: - throw SerializationException.forEnum(state, String.class); - } - } - - private String encodeChannelEvent(ChannelEvent event) { - switch (event) { - case initialized: - return PlatformConstants.TxEnumConstants.initialized; - case attaching: - return PlatformConstants.TxEnumConstants.attaching; - case attached: - return PlatformConstants.TxEnumConstants.attached; - case detaching: - return PlatformConstants.TxEnumConstants.detaching; - case detached: - return PlatformConstants.TxEnumConstants.detached; - case failed: - return PlatformConstants.TxEnumConstants.failed; - case suspended: - return PlatformConstants.TxEnumConstants.suspended; - case update: - return PlatformConstants.TxEnumConstants.update; - default: - throw SerializationException.forEnum(event, String.class); - } - } - - private Map encodePaginatedResult(AsyncPaginatedResult c) { - if (c == null) return null; - HashMap jsonMap = new HashMap<>(); - Object[] items = c.items(); - if (items.length > 0) { - Byte type = getType(items[0]); - CodecPair pair = codecMap.get(type); - if (type != null && pair != null) { - ArrayList> list = new ArrayList<>(items.length); - for (Object item : items) { - list.add((Map) pair.encode(item)); - } - writeValueToJson( - jsonMap, - PlatformConstants.TxPaginatedResult.items, - list - ); - writeValueToJson(jsonMap, PlatformConstants.TxPaginatedResult.type, type & 0xff); - } - } else { - writeValueToJson(jsonMap, PlatformConstants.TxPaginatedResult.items, null); - } - writeValueToJson(jsonMap, PlatformConstants.TxPaginatedResult.hasNext, c.hasNext()); - return jsonMap; - } - - private Map encodeConnectionStateChange(ConnectionStateListener.ConnectionStateChange c) { - if (c == null) return null; - final HashMap jsonMap = new HashMap<>(); - writeValueToJson(jsonMap, PlatformConstants.TxConnectionStateChange.current, encodeConnectionState(c.current)); - writeValueToJson(jsonMap, PlatformConstants.TxConnectionStateChange.previous, encodeConnectionState(c.previous)); - writeValueToJson(jsonMap, PlatformConstants.TxConnectionStateChange.event, encodeConnectionEvent(c.event)); - writeValueToJson(jsonMap, PlatformConstants.TxConnectionStateChange.retryIn, c.retryIn); - writeValueToJson(jsonMap, PlatformConstants.TxConnectionStateChange.reason, encodeErrorInfo(c.reason)); - return jsonMap; - } - - private Map encodeChannelStateChange(ChannelStateListener.ChannelStateChange c) { - if (c == null) return null; - final HashMap jsonMap = new HashMap<>(); - writeValueToJson(jsonMap, PlatformConstants.TxChannelStateChange.current, encodeChannelState(c.current)); - writeValueToJson(jsonMap, PlatformConstants.TxChannelStateChange.previous, encodeChannelState(c.previous)); - writeValueToJson(jsonMap, PlatformConstants.TxChannelStateChange.event, encodeChannelEvent(c.event)); - writeValueToJson(jsonMap, PlatformConstants.TxChannelStateChange.resumed, c.resumed); - writeValueToJson(jsonMap, PlatformConstants.TxChannelStateChange.reason, encodeErrorInfo(c.reason)); - return jsonMap; - } - - private Map encodeDeviceDetails(DeviceDetails c) { - if (c == null) return null; - final HashMap jsonMap = new HashMap<>(); - - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.id, c.id); - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.clientId, c.clientId); - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.platform, c.platform); - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.formFactor, c.formFactor); - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.metadata, c.metadata); - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.devicePushDetails, encodeDevicePushDetails(c.push)); - - return jsonMap; - } - - private Map encodeDevicePushDetails(DeviceDetails.Push c) { - if (c == null) return null; - final HashMap jsonMap = new HashMap<>(); - - writeValueToJson(jsonMap, PlatformConstants.TxDevicePushDetails.recipient, c.recipient); - writeValueToJson(jsonMap, PlatformConstants.TxDevicePushDetails.state, encodeDevicePushDetailsState(c.state)); - writeValueToJson(jsonMap, PlatformConstants.TxDevicePushDetails.errorReason, encodeErrorInfo(c.errorReason)); - - return jsonMap; - } - - private String encodeDevicePushDetailsState(DeviceDetails.Push.State state) { - if (state == null) return null; - - switch (state) { - case ACTIVE: - return PlatformConstants.TxDevicePushStateEnum.active; - case FAILING: - return PlatformConstants.TxDevicePushStateEnum.failing; - case FAILED: - return PlatformConstants.TxDevicePushStateEnum.failed; - default: - throw SerializationException.forEnum(state, String.class); - } - } - - private Map encodeLocalDevice(LocalDevice c) { - if (c == null) return null; - final HashMap jsonMap = new HashMap<>(); - - writeValueToJson(jsonMap, PlatformConstants.TxLocalDevice.deviceSecret, c.deviceSecret); - writeValueToJson(jsonMap, PlatformConstants.TxLocalDevice.deviceIdentityToken, c.deviceIdentityToken); - - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.id, c.id); - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.clientId, c.clientId); - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.platform, c.platform); - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.formFactor, c.formFactor); - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.metadata, c.metadata); - writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.devicePushDetails, encodeDevicePushDetails(c.push)); - - return jsonMap; - } - - private Map encodePushChannelSubscription(PushBase.ChannelSubscription c) { - if (c == null) return null; - final HashMap jsonMap = new HashMap<>(); - - writeValueToJson(jsonMap, PlatformConstants.TxPushChannelSubscription.channel, c.channel); - writeValueToJson(jsonMap, PlatformConstants.TxPushChannelSubscription.clientId, c.clientId); - writeValueToJson(jsonMap, PlatformConstants.TxPushChannelSubscription.deviceId, c.deviceId); - - return jsonMap; - } - - private Map encodeChannelMessageExtras(MessageExtras c) { - if (c == null) return null; - final HashMap jsonMap = - gson.>fromJson(c.asJsonObject().toString(), HashMap.class); - DeltaExtras deltaExtras = c.getDelta(); - if (deltaExtras != null) { - final HashMap deltaJson = new HashMap<>(); - writeValueToJson(deltaJson, PlatformConstants.TxDeltaExtras.format, deltaExtras.getFormat()); - writeValueToJson(deltaJson, PlatformConstants.TxDeltaExtras.from, deltaExtras.getFrom()); - writeValueToJson(jsonMap, PlatformConstants.TxMessageExtras.delta, deltaJson); - } - return jsonMap; - } - - private Map encodeChannelMessage(Message c) { - if (c == null) return null; - final HashMap jsonMap = new HashMap<>(); - writeValueToJson(jsonMap, PlatformConstants.TxMessage.id, c.id); - writeValueToJson(jsonMap, PlatformConstants.TxMessage.clientId, c.clientId); - writeValueToJson(jsonMap, PlatformConstants.TxMessage.connectionId, c.connectionId); - writeValueToJson(jsonMap, PlatformConstants.TxMessage.timestamp, c.timestamp); - writeValueToJson(jsonMap, PlatformConstants.TxMessage.name, c.name); - writeValueToJson(jsonMap, PlatformConstants.TxMessage.data, c.data); - writeValueToJson(jsonMap, PlatformConstants.TxMessage.encoding, c.encoding); - writeValueToJson(jsonMap, PlatformConstants.TxMessage.extras, c.extras); - return jsonMap; - } - - private String encodePresenceAction(PresenceMessage.Action action) { - switch (action) { - case absent: - return PlatformConstants.TxEnumConstants.absent; - case leave: - return PlatformConstants.TxEnumConstants.leave; - case enter: - return PlatformConstants.TxEnumConstants.enter; - case present: - return PlatformConstants.TxEnumConstants.present; - case update: - return PlatformConstants.TxEnumConstants.update; - default: - throw SerializationException.forEnum(action, String.class); - } - } - - private Map encodePresenceMessage(PresenceMessage c) { - if (c == null) return null; - final HashMap jsonMap = new HashMap<>(); - writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.id, c.id); - writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.action, encodePresenceAction(c.action)); - writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.clientId, c.clientId); - writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.connectionId, c.connectionId); - writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.timestamp, c.timestamp); - writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.data, c.data); - writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.encoding, c.encoding); - // PresenceMessage#extras is not supported in ably-java - // Track @ https://github.com/ably/ably-flutter/issues/14 - return jsonMap; - } - - private Map encodeRemoteMessage(RemoteMessage message) { - if (message == null) return null; - final HashMap jsonMap = new HashMap<>(); - writeValueToJson(jsonMap, PlatformConstants.TxRemoteMessage.data, message.getData()); - writeValueToJson(jsonMap, PlatformConstants.TxRemoteMessage.notification, encodeNotification(message.getNotification())); - return jsonMap; - } - - private Map encodeNotification(RemoteMessage.Notification notification) { - if (notification == null) return null; - final HashMap jsonMap = new HashMap<>(); - writeValueToJson(jsonMap, PlatformConstants.TxNotification.title, notification.getTitle()); - writeValueToJson(jsonMap, PlatformConstants.TxNotification.body, notification.getBody()); - return jsonMap; - } + private Map encodeAblyFlutterMessage(AblyFlutterMessage c) { + if (c == null) return null; + HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxAblyMessage.registrationHandle, c.handle); + Byte type = getType(c.message); + CodecPair pair = codecMap.get(type); + if (type != null && pair != null) { + writeValueToJson(jsonMap, PlatformConstants.TxAblyMessage.type, type & 0xff); + writeValueToJson(jsonMap, PlatformConstants.TxAblyMessage.message, pair.encode(c.message)); + } + return jsonMap; + } + + private Map encodeErrorInfo(ErrorInfo c) { + if (c == null) return null; + HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxErrorInfo.code, c.code); + writeValueToJson(jsonMap, PlatformConstants.TxErrorInfo.message, c.message); + writeValueToJson(jsonMap, PlatformConstants.TxErrorInfo.statusCode, c.statusCode); + writeValueToJson(jsonMap, PlatformConstants.TxErrorInfo.href, c.href); + // requestId and cause - not available in ably-java + // track @ https://github.com/ably/ably-flutter/issues/14 + return jsonMap; + } + + private Map encodeTokenParams(Auth.TokenParams c) { + if (c == null) return null; + HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxTokenParams.capability, c.capability); + writeValueToJson(jsonMap, PlatformConstants.TxTokenParams.clientId, c.clientId); + writeValueToJson(jsonMap, PlatformConstants.TxTokenParams.timestamp, c.timestamp); + writeValueToJson(jsonMap, PlatformConstants.TxTokenParams.ttl, c.ttl); + // nonce is not supported in ably-java + // Track @ https://github.com/ably/ably-flutter/issues/14 + return jsonMap; + } + + private Map encodeTokenRequest(Auth.TokenRequest tokenRequest) { + if (tokenRequest == null) return null; + HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxTokenRequest.capability, tokenRequest.capability); + writeValueToJson(jsonMap, PlatformConstants.TxTokenRequest.clientId, tokenRequest.clientId); + writeValueToJson(jsonMap, PlatformConstants.TxTokenRequest.timestamp, tokenRequest.timestamp); + writeValueToJson(jsonMap, PlatformConstants.TxTokenRequest.ttl, tokenRequest.ttl); + writeValueToJson(jsonMap, PlatformConstants.TxTokenRequest.keyName, tokenRequest.keyName); + writeValueToJson(jsonMap, PlatformConstants.TxTokenRequest.mac, tokenRequest.mac); + writeValueToJson(jsonMap, PlatformConstants.TxTokenRequest.nonce, tokenRequest.nonce); + return jsonMap; + } + + private Map encodeTokenDetails(Auth.TokenDetails tokenDetails) { + if (tokenDetails == null) return null; + HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxTokenDetails.token, tokenDetails.token); + writeValueToJson(jsonMap, PlatformConstants.TxTokenDetails.clientId, tokenDetails.clientId); + writeValueToJson(jsonMap, PlatformConstants.TxTokenDetails.expires, tokenDetails.expires); + writeValueToJson(jsonMap, PlatformConstants.TxTokenDetails.issued, tokenDetails.issued); + writeValueToJson(jsonMap, PlatformConstants.TxTokenDetails.capability, tokenDetails.capability); + + return jsonMap; + } + + private String encodeConnectionState(ConnectionState state) { + switch (state) { + case initialized: + return PlatformConstants.TxEnumConstants.initialized; + case connecting: + return PlatformConstants.TxEnumConstants.connecting; + case connected: + return PlatformConstants.TxEnumConstants.connected; + case disconnected: + return PlatformConstants.TxEnumConstants.disconnected; + case suspended: + return PlatformConstants.TxEnumConstants.suspended; + case closing: + return PlatformConstants.TxEnumConstants.closing; + case closed: + return PlatformConstants.TxEnumConstants.closed; + case failed: + return PlatformConstants.TxEnumConstants.failed; + default: + throw SerializationException.forEnum(state, String.class); + } + } + + private String encodeConnectionEvent(ConnectionEvent event) { + switch (event) { + case initialized: + return PlatformConstants.TxEnumConstants.initialized; + case connecting: + return PlatformConstants.TxEnumConstants.connecting; + case connected: + return PlatformConstants.TxEnumConstants.connected; + case disconnected: + return PlatformConstants.TxEnumConstants.disconnected; + case suspended: + return PlatformConstants.TxEnumConstants.suspended; + case closing: + return PlatformConstants.TxEnumConstants.closing; + case closed: + return PlatformConstants.TxEnumConstants.closed; + case failed: + return PlatformConstants.TxEnumConstants.failed; + case update: + return PlatformConstants.TxEnumConstants.update; + default: + throw SerializationException.forEnum(event, String.class); + } + } + + private String encodeChannelState(ChannelState state) { + switch (state) { + case initialized: + return PlatformConstants.TxEnumConstants.initialized; + case attaching: + return PlatformConstants.TxEnumConstants.attaching; + case attached: + return PlatformConstants.TxEnumConstants.attached; + case detaching: + return PlatformConstants.TxEnumConstants.detaching; + case detached: + return PlatformConstants.TxEnumConstants.detached; + case failed: + return PlatformConstants.TxEnumConstants.failed; + case suspended: + return PlatformConstants.TxEnumConstants.suspended; + default: + throw SerializationException.forEnum(state, String.class); + } + } + + private String encodeChannelEvent(ChannelEvent event) { + switch (event) { + case initialized: + return PlatformConstants.TxEnumConstants.initialized; + case attaching: + return PlatformConstants.TxEnumConstants.attaching; + case attached: + return PlatformConstants.TxEnumConstants.attached; + case detaching: + return PlatformConstants.TxEnumConstants.detaching; + case detached: + return PlatformConstants.TxEnumConstants.detached; + case failed: + return PlatformConstants.TxEnumConstants.failed; + case suspended: + return PlatformConstants.TxEnumConstants.suspended; + case update: + return PlatformConstants.TxEnumConstants.update; + default: + throw SerializationException.forEnum(event, String.class); + } + } + + private Map encodePaginatedResult(AsyncPaginatedResult c) { + if (c == null) return null; + HashMap jsonMap = new HashMap<>(); + Object[] items = c.items(); + if (items.length > 0) { + Byte type = getType(items[0]); + CodecPair pair = codecMap.get(type); + if (type != null && pair != null) { + ArrayList> list = new ArrayList<>(items.length); + for (Object item : items) { + list.add((Map) pair.encode(item)); + } + writeValueToJson( + jsonMap, + PlatformConstants.TxPaginatedResult.items, + list + ); + writeValueToJson(jsonMap, PlatformConstants.TxPaginatedResult.type, type & 0xff); + } + } else { + writeValueToJson(jsonMap, PlatformConstants.TxPaginatedResult.items, null); + } + writeValueToJson(jsonMap, PlatformConstants.TxPaginatedResult.hasNext, c.hasNext()); + return jsonMap; + } + + private Map encodeConnectionStateChange(ConnectionStateListener.ConnectionStateChange c) { + if (c == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxConnectionStateChange.current, encodeConnectionState(c.current)); + writeValueToJson(jsonMap, PlatformConstants.TxConnectionStateChange.previous, encodeConnectionState(c.previous)); + writeValueToJson(jsonMap, PlatformConstants.TxConnectionStateChange.event, encodeConnectionEvent(c.event)); + writeValueToJson(jsonMap, PlatformConstants.TxConnectionStateChange.retryIn, c.retryIn); + writeValueToJson(jsonMap, PlatformConstants.TxConnectionStateChange.reason, encodeErrorInfo(c.reason)); + return jsonMap; + } + + private Map encodeChannelStateChange(ChannelStateListener.ChannelStateChange c) { + if (c == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxChannelStateChange.current, encodeChannelState(c.current)); + writeValueToJson(jsonMap, PlatformConstants.TxChannelStateChange.previous, encodeChannelState(c.previous)); + writeValueToJson(jsonMap, PlatformConstants.TxChannelStateChange.event, encodeChannelEvent(c.event)); + writeValueToJson(jsonMap, PlatformConstants.TxChannelStateChange.resumed, c.resumed); + writeValueToJson(jsonMap, PlatformConstants.TxChannelStateChange.reason, encodeErrorInfo(c.reason)); + return jsonMap; + } + + private Map encodeDeviceDetails(DeviceDetails c) { + if (c == null) return null; + final HashMap jsonMap = new HashMap<>(); + + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.id, c.id); + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.clientId, c.clientId); + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.platform, c.platform); + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.formFactor, c.formFactor); + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.metadata, c.metadata); + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.devicePushDetails, encodeDevicePushDetails(c.push)); + + return jsonMap; + } + + private Map encodeDevicePushDetails(DeviceDetails.Push c) { + if (c == null) return null; + final HashMap jsonMap = new HashMap<>(); + + writeValueToJson(jsonMap, PlatformConstants.TxDevicePushDetails.recipient, c.recipient); + writeValueToJson(jsonMap, PlatformConstants.TxDevicePushDetails.state, encodeDevicePushDetailsState(c.state)); + writeValueToJson(jsonMap, PlatformConstants.TxDevicePushDetails.errorReason, encodeErrorInfo(c.errorReason)); + + return jsonMap; + } + + private String encodeDevicePushDetailsState(DeviceDetails.Push.State state) { + if (state == null) return null; + + switch (state) { + case ACTIVE: + return PlatformConstants.TxDevicePushStateEnum.active; + case FAILING: + return PlatformConstants.TxDevicePushStateEnum.failing; + case FAILED: + return PlatformConstants.TxDevicePushStateEnum.failed; + default: + throw SerializationException.forEnum(state, String.class); + } + } + + private Map encodeLocalDevice(LocalDevice c) { + if (c == null) return null; + final HashMap jsonMap = new HashMap<>(); + + writeValueToJson(jsonMap, PlatformConstants.TxLocalDevice.deviceSecret, c.deviceSecret); + writeValueToJson(jsonMap, PlatformConstants.TxLocalDevice.deviceIdentityToken, c.deviceIdentityToken); + + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.id, c.id); + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.clientId, c.clientId); + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.platform, c.platform); + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.formFactor, c.formFactor); + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.metadata, c.metadata); + writeValueToJson(jsonMap, PlatformConstants.TxDeviceDetails.devicePushDetails, encodeDevicePushDetails(c.push)); + + return jsonMap; + } + + private Map encodePushChannelSubscription(PushBase.ChannelSubscription c) { + if (c == null) return null; + final HashMap jsonMap = new HashMap<>(); + + writeValueToJson(jsonMap, PlatformConstants.TxPushChannelSubscription.channel, c.channel); + writeValueToJson(jsonMap, PlatformConstants.TxPushChannelSubscription.clientId, c.clientId); + writeValueToJson(jsonMap, PlatformConstants.TxPushChannelSubscription.deviceId, c.deviceId); + + return jsonMap; + } + + private Map encodeChannelMessageExtras(MessageExtras c) { + if (c == null) return null; + final HashMap jsonMap = + gson.>fromJson(c.asJsonObject().toString(), HashMap.class); + DeltaExtras deltaExtras = c.getDelta(); + if (deltaExtras != null) { + final HashMap deltaJson = new HashMap<>(); + writeValueToJson(deltaJson, PlatformConstants.TxDeltaExtras.format, deltaExtras.getFormat()); + writeValueToJson(deltaJson, PlatformConstants.TxDeltaExtras.from, deltaExtras.getFrom()); + writeValueToJson(jsonMap, PlatformConstants.TxMessageExtras.delta, deltaJson); + } + return jsonMap; + } + + private Map encodeChannelMessage(Message c) { + if (c == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxMessage.id, c.id); + writeValueToJson(jsonMap, PlatformConstants.TxMessage.clientId, c.clientId); + writeValueToJson(jsonMap, PlatformConstants.TxMessage.connectionId, c.connectionId); + writeValueToJson(jsonMap, PlatformConstants.TxMessage.timestamp, c.timestamp); + writeValueToJson(jsonMap, PlatformConstants.TxMessage.name, c.name); + writeValueToJson(jsonMap, PlatformConstants.TxMessage.data, c.data); + writeValueToJson(jsonMap, PlatformConstants.TxMessage.encoding, c.encoding); + writeValueToJson(jsonMap, PlatformConstants.TxMessage.extras, c.extras); + return jsonMap; + } + + private String encodePresenceAction(PresenceMessage.Action action) { + switch (action) { + case absent: + return PlatformConstants.TxEnumConstants.absent; + case leave: + return PlatformConstants.TxEnumConstants.leave; + case enter: + return PlatformConstants.TxEnumConstants.enter; + case present: + return PlatformConstants.TxEnumConstants.present; + case update: + return PlatformConstants.TxEnumConstants.update; + default: + throw SerializationException.forEnum(action, String.class); + } + } + + private Map encodePresenceMessage(PresenceMessage c) { + if (c == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.id, c.id); + writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.action, encodePresenceAction(c.action)); + writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.clientId, c.clientId); + writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.connectionId, c.connectionId); + writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.timestamp, c.timestamp); + writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.data, c.data); + writeValueToJson(jsonMap, PlatformConstants.TxPresenceMessage.encoding, c.encoding); + // PresenceMessage#extras is not supported in ably-java + // Track @ https://github.com/ably/ably-flutter/issues/14 + return jsonMap; + } + + private Map encodeRemoteMessage(RemoteMessage message) { + if (message == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxRemoteMessage.data, message.getData()); + writeValueToJson(jsonMap, PlatformConstants.TxRemoteMessage.notification, encodeNotification(message.getNotification())); + return jsonMap; + } + + private Map encodeNotification(RemoteMessage.Notification notification) { + if (notification == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxNotification.title, notification.getTitle()); + writeValueToJson(jsonMap, PlatformConstants.TxNotification.body, notification.getBody()); + return jsonMap; + } } diff --git a/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java b/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java index 8a273bdbf..cfbc2e5f6 100644 --- a/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java +++ b/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java @@ -18,7 +18,6 @@ import io.ably.flutter.plugin.generated.PlatformConstants; import io.ably.flutter.plugin.push.PushActivationEventHandlers; -import io.ably.flutter.plugin.push.PushMessagingEventHandlers; import io.ably.flutter.plugin.types.PlatformClientOptions; import io.ably.flutter.plugin.util.BiConsumer; import io.ably.lib.realtime.AblyRealtime; @@ -49,6 +48,7 @@ public class AblyMethodCallHandler implements MethodChannel.MethodCallHandler { private final StreamsChannel streamsChannel; private final Map> _map; private final AblyInstanceStore instanceStore; + private final AuthMethodHandler authMethodHandler; @Nullable private RemoteMessage remoteMessageFromUserTapLaunchesApp; @@ -59,6 +59,7 @@ public AblyMethodCallHandler(final MethodChannel methodChannel, this.streamsChannel = streamsChannel; this.applicationContext = applicationContext; this.instanceStore = AblyInstanceStore.getInstance(); + authMethodHandler = new AuthMethodHandler(this.instanceStore); _map = new HashMap<>(); _map.put(PlatformConstants.PlatformMethod.getPlatformVersion, this::getPlatformVersion); _map.put(PlatformConstants.PlatformMethod.getVersion, this::getVersion); @@ -91,6 +92,26 @@ public AblyMethodCallHandler(final MethodChannel methodChannel, _map.put(PlatformConstants.PlatformMethod.realtimeTime, this::realtimeTime); _map.put(PlatformConstants.PlatformMethod.restTime, this::restTime); + //authorizations + _map.put(PlatformConstants.PlatformMethod.realtimeAuthAuthorize, + (methodCall, result) -> authMethodHandler.authorize(methodCall, result, AuthMethodHandler.Type.Realtime)); + _map.put(PlatformConstants.PlatformMethod.realtimeAuthRequestToken, + (methodCall, result) -> authMethodHandler.requestToken(methodCall, result, AuthMethodHandler.Type.Realtime)); + _map.put(PlatformConstants.PlatformMethod.realtimeAuthCreateTokenRequest, + (methodCall, result) -> authMethodHandler.createTokenRequest(methodCall, result, AuthMethodHandler.Type.Realtime)); + _map.put(PlatformConstants.PlatformMethod.realtimeAuthGetClientId, + (methodCall, result) -> authMethodHandler.clientId(methodCall, result, AuthMethodHandler.Type.Realtime)); + + _map.put(PlatformConstants.PlatformMethod.restAuthAuthorize, + (methodCall, result) -> authMethodHandler.authorize(methodCall, result, AuthMethodHandler.Type.Rest)); + _map.put(PlatformConstants.PlatformMethod.restAuthRequestToken, + (methodCall, result) -> authMethodHandler.requestToken(methodCall, result, AuthMethodHandler.Type.Rest)); + _map.put(PlatformConstants.PlatformMethod.restAuthCreateTokenRequest, + (methodCall, result) -> authMethodHandler.createTokenRequest(methodCall, result, + AuthMethodHandler.Type.Rest)); + _map.put(PlatformConstants.PlatformMethod.restAuthGetClientId, + (methodCall, result) -> authMethodHandler.clientId(methodCall, result, AuthMethodHandler.Type.Rest)); + // Push Notifications _map.put(PlatformConstants.PlatformMethod.pushActivate, this::pushActivate); _map.put(PlatformConstants.PlatformMethod.pushDeactivate, this::pushDeactivate); @@ -559,7 +580,10 @@ private void realtimeTime(@NonNull MethodCall methodCall, @NonNull MethodChannel time(result, instanceStore.getRealtime(ablyMessage.handle)); } - private void restTime(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { + + + + private void restTime(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { final AblyFlutterMessage ablyMessage = (AblyFlutterMessage) methodCall.arguments; time(result, instanceStore.getRest(ablyMessage.handle)); } diff --git a/android/src/main/java/io/ably/flutter/plugin/AuthMethodHandler.java b/android/src/main/java/io/ably/flutter/plugin/AuthMethodHandler.java new file mode 100644 index 000000000..6ce5c0135 --- /dev/null +++ b/android/src/main/java/io/ably/flutter/plugin/AuthMethodHandler.java @@ -0,0 +1,98 @@ +package io.ably.flutter.plugin; + +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; + +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import io.ably.flutter.plugin.generated.PlatformConstants; +import io.ably.lib.rest.Auth; +import io.ably.lib.types.AblyException; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +class AuthMethodHandler { + enum Type{Realtime,Rest} + + private final AblyInstanceStore instanceStore; + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + + public AuthMethodHandler(AblyInstanceStore instanceStore) { + this.instanceStore = instanceStore; + + } + + void authorize(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result, Type type) { + final AblyFlutterMessage> ablyMessage = (AblyFlutterMessage) methodCall.arguments; + final Auth.TokenParams tokenParams = + (Auth.TokenParams) ablyMessage.message.get(PlatformConstants.TxTransportKeys.params); + + final Auth.AuthOptions options = + (Auth.AuthOptions) ablyMessage.message.get(PlatformConstants.TxTransportKeys.options); + + executor.execute(() -> { + try { + final Auth.TokenDetails tokenDetails = getAuth(ablyMessage, type) + .authorize(tokenParams, options); + result.success(tokenDetails); + } catch (AblyException e) { + result.error(String.valueOf(e.errorInfo.code), e.errorInfo.message, e); + } + }); + + } + + private Auth getAuth(AblyFlutterMessage> ablyMessage, Type type) { + if (type == Type.Realtime) { + return instanceStore.getRealtime(ablyMessage.handle).auth; + } + return instanceStore.getRest(ablyMessage.handle).auth; + } + + void requestToken(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result, Type type) { + final AblyFlutterMessage> ablyMessage = (AblyFlutterMessage) methodCall.arguments; + final Auth.TokenParams tokenParams = + (Auth.TokenParams) ablyMessage.message.get(PlatformConstants.TxTransportKeys.params); + + final Auth.AuthOptions options = + (Auth.AuthOptions) ablyMessage.message.get(PlatformConstants.TxTransportKeys.options); + + executor.execute(() -> { + try { + final Auth.TokenDetails tokenDetails = getAuth(ablyMessage, type) + .requestToken(tokenParams, options); + result.success(tokenDetails); + } catch (AblyException e) { + result.error(String.valueOf(e.errorInfo.code), e.errorInfo.message, e); + } + }); + } + + void createTokenRequest(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result, Type type) { + final AblyFlutterMessage> ablyMessage = (AblyFlutterMessage) methodCall.arguments; + final Auth.TokenParams tokenParams = + (Auth.TokenParams) ablyMessage.message.get(PlatformConstants.TxTransportKeys.params); + + final Auth.AuthOptions options = + (Auth.AuthOptions) ablyMessage.message.get(PlatformConstants.TxTransportKeys.options); + + executor.execute(() -> { + try { + final Auth.TokenRequest tokenRequest = getAuth(ablyMessage, type) + .createTokenRequest(tokenParams, options); + result.success(tokenRequest); + } catch (AblyException e) { + result.error(String.valueOf(e.errorInfo.code), e.errorInfo.message, e); + } + }); + } + + public void clientId(MethodCall methodCall, MethodChannel.Result result, Type type) { + String clientId = getAuth((AblyFlutterMessage) methodCall.arguments, type).clientId; + result.success(clientId); + } +} diff --git a/android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java b/android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java index cf4e7a69f..a15342698 100644 --- a/android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java +++ b/android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java @@ -12,30 +12,31 @@ static final public class CodecTypes { public static final byte ablyMessage = (byte) 128; public static final byte ablyEventMessage = (byte) 129; public static final byte clientOptions = (byte) 130; - public static final byte messageData = (byte) 131; - public static final byte messageExtras = (byte) 132; - public static final byte message = (byte) 133; - public static final byte tokenParams = (byte) 134; - public static final byte tokenDetails = (byte) 135; - public static final byte tokenRequest = (byte) 136; - public static final byte restChannelOptions = (byte) 137; - public static final byte realtimeChannelOptions = (byte) 138; - public static final byte paginatedResult = (byte) 139; - public static final byte restHistoryParams = (byte) 140; - public static final byte realtimeHistoryParams = (byte) 141; - public static final byte restPresenceParams = (byte) 142; - public static final byte presenceMessage = (byte) 143; - public static final byte realtimePresenceParams = (byte) 144; - public static final byte deviceDetails = (byte) 145; - public static final byte localDevice = (byte) 146; - public static final byte pushChannelSubscription = (byte) 147; - public static final byte unNotificationSettings = (byte) 148; - public static final byte remoteMessage = (byte) 149; - public static final byte errorInfo = (byte) 150; - public static final byte logLevel = (byte) 151; - public static final byte connectionStateChange = (byte) 152; - public static final byte channelStateChange = (byte) 153; - public static final byte cipherParams = (byte) 154; + public static final byte authOptions = (byte) 131; + public static final byte messageData = (byte) 132; + public static final byte messageExtras = (byte) 133; + public static final byte message = (byte) 134; + public static final byte tokenParams = (byte) 135; + public static final byte tokenDetails = (byte) 136; + public static final byte tokenRequest = (byte) 137; + public static final byte restChannelOptions = (byte) 138; + public static final byte realtimeChannelOptions = (byte) 139; + public static final byte paginatedResult = (byte) 140; + public static final byte restHistoryParams = (byte) 141; + public static final byte realtimeHistoryParams = (byte) 142; + public static final byte restPresenceParams = (byte) 143; + public static final byte presenceMessage = (byte) 144; + public static final byte realtimePresenceParams = (byte) 145; + public static final byte deviceDetails = (byte) 146; + public static final byte localDevice = (byte) 147; + public static final byte pushChannelSubscription = (byte) 148; + public static final byte unNotificationSettings = (byte) 149; + public static final byte remoteMessage = (byte) 150; + public static final byte errorInfo = (byte) 151; + public static final byte logLevel = (byte) 152; + public static final byte connectionStateChange = (byte) 153; + public static final byte channelStateChange = (byte) 154; + public static final byte cipherParams = (byte) 155; } static final public class PlatformMethod { @@ -51,6 +52,10 @@ static final public class PlatformMethod { public static final String restPresenceGet = "restPresenceGet"; public static final String restPresenceHistory = "restPresenceHistory"; public static final String releaseRestChannel = "releaseRestChannel"; + public static final String restAuthAuthorize = "restAuthAuthorize"; + public static final String restAuthCreateTokenRequest = "restAuthCreateTokenRequest"; + public static final String restAuthRequestToken = "restAuthRequestToken"; + public static final String restAuthGetClientId = "restAuthGetClientId"; public static final String createRealtime = "createRealtime"; public static final String connectRealtime = "connectRealtime"; public static final String closeRealtime = "closeRealtime"; @@ -68,6 +73,10 @@ static final public class PlatformMethod { public static final String realtimeHistory = "realtimeHistory"; public static final String realtimeTime = "realtimeTime"; public static final String restTime = "restTime"; + public static final String realtimeAuthAuthorize = "realtimeAuthAuthorize"; + public static final String realtimeAuthCreateTokenRequest = "realtimeAuthCreateTokenRequest"; + public static final String realtimeAuthRequestToken = "realtimeAuthRequestToken"; + public static final String realtimeAuthGetClientId = "realtimeAuthGetClientId"; public static final String pushActivate = "pushActivate"; public static final String pushDeactivate = "pushDeactivate"; public static final String pushReset = "pushReset"; @@ -204,6 +213,19 @@ static final public class TxTokenDetails { public static final String clientId = "clientId"; } + static final public class TxAuthOptions { + public static final String authCallback = "authCallback"; + public static final String authUrl = "authUrl"; + public static final String authMethod = "authMethod"; + public static final String key = "key"; + public static final String token = "token"; + public static final String tokenDetails = "tokenDetails"; + public static final String authHeaders = "authHeaders"; + public static final String authParams = "authParams"; + public static final String queryTime = "queryTime"; + public static final String useTokenAuth = "useTokenAuth"; + } + static final public class TxTokenParams { public static final String capability = "capability"; public static final String clientId = "clientId"; diff --git a/bin/codegen_context.dart b/bin/codegen_context.dart index 0a114b90b..3b91e3079 100644 --- a/bin/codegen_context.dart +++ b/bin/codegen_context.dart @@ -7,6 +7,7 @@ Iterable> get _types sync* { //Other ably objects 'clientOptions', + 'authOptions', 'messageData', 'messageExtras', 'message', @@ -74,6 +75,10 @@ const List> _platformMethods = [ {'name': 'restPresenceGet', 'value': 'restPresenceGet'}, {'name': 'restPresenceHistory', 'value': 'restPresenceHistory'}, {'name': 'releaseRestChannel', 'value': 'releaseRestChannel'}, + {'name': 'restAuthAuthorize', 'value': 'restAuthAuthorize'}, + {'name': 'restAuthCreateTokenRequest', 'value': 'restAuthCreateTokenRequest'}, + {'name': 'restAuthRequestToken', 'value': 'restAuthRequestToken'}, + {'name': 'restAuthGetClientId', 'value': 'restAuthGetClientId'}, // Realtime {'name': 'createRealtime', 'value': 'createRealtime'}, @@ -96,6 +101,13 @@ const List> _platformMethods = [ {'name': 'realtimeHistory', 'value': 'realtimeHistory'}, {'name': 'realtimeTime', 'value': 'realtimeTime'}, {'name': 'restTime', 'value': 'restTime'}, + {'name': 'realtimeAuthAuthorize', 'value': 'realtimeAuthAuthorize'}, + { + 'name': 'realtimeAuthCreateTokenRequest', + 'value': 'realtimeAuthCreateTokenRequest' + }, + {'name': 'realtimeAuthRequestToken', 'value': 'realtimeAuthRequestToken'}, + {'name': 'realtimeAuthGetClientId', 'value': 'realtimeAuthGetClientId'}, // Push Notifications {'name': 'pushActivate', 'value': 'pushActivate'}, @@ -267,6 +279,21 @@ const List> _objects = [ 'clientId' ] }, + { + 'name': 'AuthOptions', + 'properties': [ + 'authCallback', + 'authUrl', + 'authMethod', + 'key', + 'token', + 'tokenDetails', + 'authHeaders', + 'authParams', + 'queryTime', + 'useTokenAuth' + ] + }, { 'name': 'TokenParams', 'properties': [ diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 89691b90d..6be1b15e2 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -48,7 +48,7 @@ SPEC CHECKSUMS: ably_flutter: bba4fea46a2a3269fae98b67bd5c0c75cf3f0132 AblyDeltaCodec: 783d017270de70bbbc0a84e4235297b225d33636 device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58 msgpack: c85f6251873059738472ae136951cec5f30f3251 @@ -56,4 +56,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/example/pubspec.lock b/example/pubspec.lock index 23885748f..85c0fb13c 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -12,119 +12,136 @@ packages: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + sha256: "0bd9a99b6eb96f07af141f0eb53eace8983e8e5aa5de59777aca31684680ef22" + url: "https://pub.dev" source: hosted version: "2.3.0" async: dependency: "direct main" description: name: async - url: "https://pub.dartlang.org" + sha256: db4766341bd8ecb66556f31ab891a5d596ef829221993531bd64a8e6342f0cda + url: "https://pub.dev" source: hosted version: "2.8.2" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" source: hosted version: "1.3.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: "6021e0172ab6e6eaa1d391afed0a99353921f00c54385c574dc53e55d67c092c" + url: "https://pub.dev" source: hosted version: "1.1.0" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.17.0" crypto: dependency: "direct main" description: name: crypto - url: "https://pub.dartlang.org" + sha256: cf75650c66c0316274e21d7c43d3dea246273af5955bd94e8184837cd577575c + url: "https://pub.dev" source: hosted version: "3.0.1" dbus: dependency: transitive description: name: dbus - url: "https://pub.dartlang.org" + sha256: "83fb17f96be368b3b991b56929c817876cea4a689ba235ef2c359970ed7de049" + url: "https://pub.dev" source: hosted version: "0.6.8" device_info_plus: dependency: "direct main" description: name: device_info_plus - url: "https://pub.dartlang.org" + sha256: cebe8e3c85a0049120909a9ce5993361a389f998e06bac4d65305e27388106fe + url: "https://pub.dev" source: hosted version: "3.2.1" device_info_plus_linux: dependency: transitive description: name: device_info_plus_linux - url: "https://pub.dartlang.org" + sha256: e4eb5db4704f5534e872148a21cfcd39581022b63df556da6720d88f7c9f91a9 + url: "https://pub.dev" source: hosted version: "2.1.1" device_info_plus_macos: dependency: transitive description: name: device_info_plus_macos - url: "https://pub.dartlang.org" + sha256: "6b7bf87119739f6a08b74129f9d9d4232e1cd293c431b0ebd14a220c62a0ae14" + url: "https://pub.dev" source: hosted version: "2.2.2" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - url: "https://pub.dartlang.org" + sha256: "7b2519986b1d805c0d123fcfb42a1be73f9ec2f04f45b15e693b2cb355305b86" + url: "https://pub.dev" source: hosted version: "2.3.0+1" device_info_plus_web: dependency: transitive description: name: device_info_plus_web - url: "https://pub.dartlang.org" + sha256: "38715ad1ef3bee8915dd7bee08a9ac9ab54472a8df425c887062a3046209f663" + url: "https://pub.dev" source: hosted version: "2.1.0" device_info_plus_windows: dependency: transitive description: name: device_info_plus_windows - url: "https://pub.dartlang.org" + sha256: "8fb1403fc94636d6ab48aeebb5f9379f2ca51cde3b337167ec6f39db09234492" + url: "https://pub.dev" source: hosted version: "2.1.1" enum_to_string: dependency: "direct main" description: name: enum_to_string - url: "https://pub.dartlang.org" + sha256: bd9e83a33b754cb43a75b36a9af2a0b92a757bfd9847d2621ca0b1bed45f8e7a + url: "https://pub.dev" source: hosted version: "2.0.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: "35d0f481d939de0d640b3db9a7aa36a52cd22054a798a73b4f50bdad5ce12678" + url: "https://pub.dev" source: hosted version: "1.1.2" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: b69516f2c26a5bcac4eee2e32512e1a5205ab312b3536c1c1227b2b942b5f9ad + url: "https://pub.dev" source: hosted version: "6.1.2" flutter: @@ -136,35 +153,40 @@ packages: dependency: "direct main" description: name: flutter_hooks - url: "https://pub.dartlang.org" + sha256: c32436445d490ed6c6c94099dd835ecdb8e6fed32d6dc0bc1c3c646b54257e25 + url: "https://pub.dev" source: hosted version: "0.18.2" flutter_lints: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 + url: "https://pub.dev" source: hosted version: "1.0.4" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications - url: "https://pub.dartlang.org" + sha256: "13e68f60b920d9b7699d9f305ccafa92b23659bb8b9594f097a60704281166b0" + url: "https://pub.dev" source: hosted version: "9.2.0" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - url: "https://pub.dartlang.org" + sha256: "64a5f687a87c2a3b87df81484cd9d797082a2cc1144a74345fc5f2c8223fc4e6" + url: "https://pub.dev" source: hosted version: "0.4.1+1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - url: "https://pub.dartlang.org" + sha256: "21bceee103a66a53b30ea9daf677f990e5b9e89b62f222e60dd241cd08d63d3a" + url: "https://pub.dev" source: hosted version: "5.0.0" flutter_web_plugins: @@ -176,98 +198,112 @@ packages: dependency: "direct main" description: name: fluttertoast - url: "https://pub.dartlang.org" + sha256: b3ae793108ad2a7e8f2fea91ca5bb2ba7ef03621c4d9ed7a92fe3391da64c000 + url: "https://pub.dev" source: hosted version: "8.0.8" http: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + sha256: "2ed163531e071c2c6b7c659635112f24cb64ecbebf6af46b550d536c0b1aa112" + url: "https://pub.dev" source: hosted version: "0.13.4" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: e362d639ba3bc07d5a71faebb98cde68c05bfbcfbbb444b60b6f60bb67719185 + url: "https://pub.dev" source: hosted version: "4.0.0" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted - version: "0.6.3" + version: "0.6.5" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + url: "https://pub.dev" source: hosted version: "1.0.1" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb" + url: "https://pub.dev" source: hosted version: "1.8.0" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "1a914995d4ef10c94ff183528c120d35ed43b5eaa8713fc6766a9be4570782e2" + url: "https://pub.dev" source: hosted version: "4.4.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: "075f927ebbab4262ace8d0b283929ac5410c0ac4e7fc123c76429564facfb757" + url: "https://pub.dev" source: hosted version: "2.1.2" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" source: hosted version: "4.2.4" retry: dependency: "direct main" description: name: retry - url: "https://pub.dartlang.org" + sha256: "45dfeebaf095b606fdb9dbfb4c114cc204449bc274783b452658365e03afdbab" + url: "https://pub.dev" source: hosted version: "3.1.0" rxdart: dependency: "direct main" description: name: rxdart - url: "https://pub.dartlang.org" + sha256: bc2d2b17b87fab32e2dca53ca3066d3147de6f96c74d76cfe1a379a24239c46d + url: "https://pub.dev" source: hosted version: "0.27.3" sky_engine: @@ -279,72 +315,82 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: d5f89a9e52b36240a80282b3dc0667dd36e53459717bb17b8fb102d30496606a + url: "https://pub.dev" source: hosted version: "1.8.1" stream_transform: dependency: "direct main" description: name: stream_transform - url: "https://pub.dartlang.org" + sha256: ed464977cb26a1f41537e177e190c67223dbd9f4f683489b6ab2e5d211ec564e + url: "https://pub.dev" source: hosted version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: dd11571b8a03f7cadcf91ec26a77e02bfbd6bbba2a512924d3116646b4198fc4 + url: "https://pub.dev" source: hosted version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a88162591b02c1f3a3db3af8ce1ea2b374bd75a7bb8d5e353bcfbdc79d719830 + url: "https://pub.dev" source: hosted version: "1.2.0" timezone: dependency: transitive description: name: timezone - url: "https://pub.dartlang.org" + sha256: "57b35f6e8ef731f18529695bffc62f92c6189fac2e52c12d478dec1931afb66e" + url: "https://pub.dev" source: hosted version: "0.8.0" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "53bdf7e979cfbf3e28987552fd72f637e63f3c8724c9e56d9246942dc2fa36ee" + url: "https://pub.dev" source: hosted version: "1.3.0" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: "9273b3769064f82a3b330d95123d7d6cabc5ecbc59497abc7191e0a43920225b" + url: "https://pub.dev" source: hosted version: "2.3.10" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: "060b6e1c891d956f72b5ac9463466c37cce3fa962a921532fc001e86fe93438e" + url: "https://pub.dev" source: hosted version: "0.2.0+1" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: baa23bcba1ba4ce4b22c0c7a1d9c861e7015cb5169512676da0b85138e72840c + url: "https://pub.dev" source: hosted version: "5.3.1" sdks: - dart: ">=2.15.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=2.8.0" diff --git a/ios/Classes/AblyFlutter.m b/ios/Classes/AblyFlutter.m index ca3203ac7..e465407d8 100644 --- a/ios/Classes/AblyFlutter.m +++ b/ios/Classes/AblyFlutter.m @@ -641,6 +641,30 @@ -(void)reset; }]; }; +static const FlutterHandler _realtimeAuthCreateTokenRequest = ^void(AblyFlutter *const ably, FlutterMethodCall *const call, const FlutterResult result) { + AblyFlutterMessage *const ablyMessage = call.arguments; + NSDictionary *const message = ablyMessage.message; + NSString *const channelName = (NSString*) message[TxTransportKeys_channelName]; + ARTTokenParams *const tokenParams = (ARTTokenParams *) message[TxTransportKeys_params]; + ARTAuthOptions *const authOptions = (ARTAuthOptions *) message[TxTransportKeys_options]; + + AblyInstanceStore *const instanceStore = [ably instanceStore]; + ARTRealtime *const realtime = [instanceStore realtimeFrom:ablyMessage.handle]; + [realtime.auth requestToken:tokenParams withOptions:authOptions callback:^(ARTTokenDetails * _Nullable tokenDetails, NSError * _Nullable error) { + if (error) { + result([ + FlutterError + errorWithCode:[NSString stringWithFormat: @"%ld", (long)error.code] + message:[NSString stringWithFormat:@"Error creating token request = %@", error] + details:error + ]); + } else { + result(tokenDetails); + } + + }]; +}; + @implementation AblyFlutter { NSDictionary* _handlers; AblyStreamsChannel* _streamsChannel; @@ -732,6 +756,15 @@ -(instancetype)initWithChannel:(FlutterMethodChannel *const)channel // Encryption AblyPlatformMethod_cryptoGetParams: CryptoHandlers.getParams, AblyPlatformMethod_cryptoGenerateRandomKey: CryptoHandlers.generateRandomKey, + //Authorize + AblyPlatformMethod_realtimeAuthAuthorize: AuthHandlers.realtimeAuthorize, + AblyPlatformMethod_realtimeAuthCreateTokenRequest: AuthHandlers.realtimeCreateTokenRequest, + AblyPlatformMethod_realtimeAuthRequestToken: AuthHandlers.realtimeRequestToken, + AblyPlatformMethod_realtimeAuthGetClientId: AuthHandlers.realtimeAuthClientId, + AblyPlatformMethod_restAuthAuthorize: AuthHandlers.restAuthorize, + AblyPlatformMethod_restAuthCreateTokenRequest: AuthHandlers.restCreateTokenRequest, + AblyPlatformMethod_restAuthRequestToken: AuthHandlers.restRequestToken, + AblyPlatformMethod_restAuthGetClientId: AuthHandlers.restAuthClientId }; [registrar addApplicationDelegate:self]; diff --git a/ios/Classes/codec/AblyFlutterReader.m b/ios/Classes/codec/AblyFlutterReader.m index 4f1c81aeb..fe6c452d5 100644 --- a/ios/Classes/codec/AblyFlutterReader.m +++ b/ios/Classes/codec/AblyFlutterReader.m @@ -40,6 +40,8 @@ + (AblyCodecDecoder) getDecoder:(const NSString*)type { [NSString stringWithFormat:@"%d", CodecTypeRealtimePresenceParams]: readRealtimePresenceParams, [NSString stringWithFormat:@"%d", CodecTypeMessageData]: readMessageData, [NSString stringWithFormat:@"%d", CodecTypeCipherParams]: CryptoCodec.readCipherParams, + [NSString stringWithFormat:@"%d", CodecTypeAuthOptions]: readAuthOptions, + [NSString stringWithFormat:@"%d", CodecTypeTokenParams]: readTokenParams, }; return _handlers[[NSString stringWithFormat:@"%@", type]]; } @@ -91,20 +93,20 @@ use an explicitly received null from Dart (manifesting this side as a nil id) */ #define ON_VALUE(BLOCK, DICTIONARY, JSON_KEY) { \ const id value = [DICTIONARY objectForKey: JSON_KEY]; \ -if (value) { \ +if (value && !([value isKindOfClass:[NSNull class]])) { \ BLOCK(value); \ } \ } #define READ_VALUE(OBJECT, PROPERTY, DICTIONARY, JSON_KEY) { \ -ON_VALUE(^(const id value) { OBJECT.PROPERTY = value; }, DICTIONARY, JSON_KEY); \ +ON_VALUE(^(const id value) {if(!([value isKindOfClass:[NSNull class]])) OBJECT.PROPERTY = value; }, DICTIONARY, JSON_KEY); \ } /** Where an NSNumber has been decoded and the property to be set is BOOL. */ #define READ_BOOL(OBJECT, PROPERTY, DICTIONARY, JSON_KEY) { \ -ON_VALUE(^(const id number) { OBJECT.PROPERTY = [number boolValue]; }, DICTIONARY, JSON_KEY); \ +ON_VALUE(^(const id number) {if(!([value isKindOfClass:[NSNull class]])) OBJECT.PROPERTY = [number boolValue]; }, DICTIONARY, JSON_KEY); \ } static AblyCodecDecoder readClientOptions = ^AblyFlutterClientOptions*(NSDictionary *const dictionary) { @@ -155,6 +157,38 @@ use an explicitly received null from Dart (manifesting this side as a nil id) hasAuthCallback:dictionary[TxClientOptions_hasAuthCallback]]; }; +static AblyCodecDecoder readAuthOptions = ^ARTAuthOptions*(NSDictionary *const dictionary) { + ARTAuthOptions *const authOptions = [ARTAuthOptions new]; + READ_VALUE(authOptions, authUrl, dictionary, TxAuthOptions_authUrl); + + READ_VALUE(authOptions, authMethod, dictionary, TxAuthOptions_authMethod) + + ON_VALUE(^(const id value) { authOptions.tokenDetails = [AblyFlutterReader tokenDetailsFromDictionary: value]; }, dictionary, TxAuthOptions_tokenDetails); + ; + READ_VALUE(authOptions, key, dictionary, TxAuthOptions_key); + + READ_VALUE(authOptions, authHeaders, dictionary, TxAuthOptions_authHeaders); + + READ_VALUE(authOptions, authParams, dictionary, TxAuthOptions_authParams); + + READ_VALUE(authOptions, queryTime, dictionary, TxAuthOptions_queryTime); + + + return authOptions; +}; + +static AblyCodecDecoder readTokenParams = ^ARTTokenParams*(NSDictionary *const dictionary) { + ARTTokenParams *const tokenParams = [ARTTokenParams new]; + + READ_VALUE(tokenParams, capability, dictionary, TxTokenParams_capability); + READ_VALUE(tokenParams, clientId, dictionary, TxTokenParams_clientId) + READ_VALUE(tokenParams, timestamp, dictionary, TxTokenParams_timestamp); + READ_VALUE(tokenParams, ttl, dictionary, TxTokenParams_ttl); + + return tokenParams; +}; + + +(ARTTokenDetails *)tokenDetailsFromDictionary: (NSDictionary *) dictionary { NSString *token = nil; NSDate *expires = nil; diff --git a/ios/Classes/codec/AblyFlutterWriter.m b/ios/Classes/codec/AblyFlutterWriter.m index dfd2c9596..b5caf9c56 100644 --- a/ios/Classes/codec/AblyFlutterWriter.m +++ b/ios/Classes/codec/AblyFlutterWriter.m @@ -29,6 +29,8 @@ + (UInt8) getType:(id)value{ return CodecTypeMessage; }else if([value isKindOfClass:[ARTPresenceMessage class]]){ return CodecTypePresenceMessage; + } else if ([value isKindOfClass:[ARTTokenRequest class]]){ + return CodecTypeTokenRequest; }else if([value isKindOfClass:[ARTTokenParams class]]){ return CodecTypeTokenParams; }else if([value isKindOfClass:[ARTPaginatedResult class]]){ @@ -51,6 +53,8 @@ + (UInt8) getType:(id)value{ return CodecTypeRestChannelOptions; } else if ([value isKindOfClass:[ARTCipherParams class]]) { return CodecTypeCipherParams; + } else if ([value isKindOfClass:[ARTTokenDetails class]]) { + return CodecTypeTokenDetails; } return 0; } @@ -71,6 +75,8 @@ + (AblyCodecEncoder) getEncoder:(const NSString*)type { [NSString stringWithFormat:@"%d", CodecTypeUnNotificationSettings]: PushNotificationEncoders.encodeUNNotificationSettings, [NSString stringWithFormat:@"%d", CodecTypeRemoteMessage]: PushNotificationEncoders.encodeRemoteMessage, [NSString stringWithFormat:@"%d", CodecTypeCipherParams]: CryptoCodec.encodeCipherParams, + [NSString stringWithFormat:@"%d", CodecTypeTokenDetails]: encodeTokenDetails, + [NSString stringWithFormat:@"%d", CodecTypeTokenRequest]: encodeTokenRequest, }; return [_handlers objectForKey:[NSString stringWithFormat:@"%@", type]]; } @@ -80,7 +86,8 @@ - (void)writeValue:(id)value { if(type != 0){ [self writeByte: type]; AblyCodecEncoder encoder = [AblyFlutterWriter getEncoder: [NSString stringWithFormat:@"%d", type]]; - [self writeValue: encoder(value)]; + id encoded = encoder(value); + [self writeValue: encoded]; return; } [super writeValue:value]; @@ -297,6 +304,34 @@ +(NSString *) encodePresenceAction: (ARTPresenceAction) action { return dictionary; }; +static AblyCodecEncoder encodeTokenRequest = ^NSMutableDictionary*(ARTTokenRequest *const tokenRequest) { + NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init]; + + WRITE_VALUE(dictionary, TxTokenRequest_ttl, [tokenRequest ttl]); + WRITE_VALUE(dictionary, TxTokenRequest_nonce, [tokenRequest nonce]); + WRITE_VALUE(dictionary, TxTokenRequest_clientId, [tokenRequest clientId]); + WRITE_VALUE(dictionary, TxTokenRequest_timestamp, + [tokenRequest timestamp]?@((long)([[tokenRequest timestamp] timeIntervalSince1970]*1000)):nil); + WRITE_VALUE(dictionary, TxTokenRequest_capability, [tokenRequest capability]); + WRITE_VALUE(dictionary, TxTokenRequest_mac, [tokenRequest mac]); + WRITE_VALUE(dictionary, TxTokenRequest_keyName, [tokenRequest keyName]); + + return dictionary; +}; + +static AblyCodecEncoder encodeTokenDetails = ^NSMutableDictionary*(ARTTokenDetails *const details) { + NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init]; + + WRITE_VALUE(dictionary, TxTokenDetails_token, [details token]); + + WRITE_VALUE(dictionary, TxTokenDetails_issued, [details issued]?@((long)([[details issued] timeIntervalSince1970]*1000)):nil); + WRITE_VALUE(dictionary, TxTokenDetails_expires, [details expires]?@((long)([[details expires] timeIntervalSince1970]*1000)):nil); + WRITE_VALUE(dictionary, TxTokenDetails_clientId, [details clientId]); + WRITE_VALUE(dictionary, TxTokenDetails_capability, [details capability]); + + return dictionary; +}; + static AblyCodecEncoder encodePaginatedResult = ^NSMutableDictionary*(ARTPaginatedResult *const result) { NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init]; NSArray* items = [result items]; diff --git a/ios/Classes/codec/AblyPlatformConstants.h b/ios/Classes/codec/AblyPlatformConstants.h index fecd39be3..ba6253f10 100644 --- a/ios/Classes/codec/AblyPlatformConstants.h +++ b/ios/Classes/codec/AblyPlatformConstants.h @@ -9,30 +9,31 @@ typedef NS_ENUM(UInt8, CodecType) { CodecTypeAblyMessage = 128, CodecTypeAblyEventMessage = 129, CodecTypeClientOptions = 130, - CodecTypeMessageData = 131, - CodecTypeMessageExtras = 132, - CodecTypeMessage = 133, - CodecTypeTokenParams = 134, - CodecTypeTokenDetails = 135, - CodecTypeTokenRequest = 136, - CodecTypeRestChannelOptions = 137, - CodecTypeRealtimeChannelOptions = 138, - CodecTypePaginatedResult = 139, - CodecTypeRestHistoryParams = 140, - CodecTypeRealtimeHistoryParams = 141, - CodecTypeRestPresenceParams = 142, - CodecTypePresenceMessage = 143, - CodecTypeRealtimePresenceParams = 144, - CodecTypeDeviceDetails = 145, - CodecTypeLocalDevice = 146, - CodecTypePushChannelSubscription = 147, - CodecTypeUnNotificationSettings = 148, - CodecTypeRemoteMessage = 149, - CodecTypeErrorInfo = 150, - CodecTypeLogLevel = 151, - CodecTypeConnectionStateChange = 152, - CodecTypeChannelStateChange = 153, - CodecTypeCipherParams = 154, + CodecTypeAuthOptions = 131, + CodecTypeMessageData = 132, + CodecTypeMessageExtras = 133, + CodecTypeMessage = 134, + CodecTypeTokenParams = 135, + CodecTypeTokenDetails = 136, + CodecTypeTokenRequest = 137, + CodecTypeRestChannelOptions = 138, + CodecTypeRealtimeChannelOptions = 139, + CodecTypePaginatedResult = 140, + CodecTypeRestHistoryParams = 141, + CodecTypeRealtimeHistoryParams = 142, + CodecTypeRestPresenceParams = 143, + CodecTypePresenceMessage = 144, + CodecTypeRealtimePresenceParams = 145, + CodecTypeDeviceDetails = 146, + CodecTypeLocalDevice = 147, + CodecTypePushChannelSubscription = 148, + CodecTypeUnNotificationSettings = 149, + CodecTypeRemoteMessage = 150, + CodecTypeErrorInfo = 151, + CodecTypeLogLevel = 152, + CodecTypeConnectionStateChange = 153, + CodecTypeChannelStateChange = 154, + CodecTypeCipherParams = 155, }; @@ -49,6 +50,10 @@ extern NSString *const AblyPlatformMethod_restHistory; extern NSString *const AblyPlatformMethod_restPresenceGet; extern NSString *const AblyPlatformMethod_restPresenceHistory; extern NSString *const AblyPlatformMethod_releaseRestChannel; +extern NSString *const AblyPlatformMethod_restAuthAuthorize; +extern NSString *const AblyPlatformMethod_restAuthCreateTokenRequest; +extern NSString *const AblyPlatformMethod_restAuthRequestToken; +extern NSString *const AblyPlatformMethod_restAuthGetClientId; extern NSString *const AblyPlatformMethod_createRealtime; extern NSString *const AblyPlatformMethod_connectRealtime; extern NSString *const AblyPlatformMethod_closeRealtime; @@ -66,6 +71,10 @@ extern NSString *const AblyPlatformMethod_releaseRealtimeChannel; extern NSString *const AblyPlatformMethod_realtimeHistory; extern NSString *const AblyPlatformMethod_realtimeTime; extern NSString *const AblyPlatformMethod_restTime; +extern NSString *const AblyPlatformMethod_realtimeAuthAuthorize; +extern NSString *const AblyPlatformMethod_realtimeAuthCreateTokenRequest; +extern NSString *const AblyPlatformMethod_realtimeAuthRequestToken; +extern NSString *const AblyPlatformMethod_realtimeAuthGetClientId; extern NSString *const AblyPlatformMethod_pushActivate; extern NSString *const AblyPlatformMethod_pushDeactivate; extern NSString *const AblyPlatformMethod_pushReset; @@ -189,6 +198,18 @@ extern NSString *const TxTokenDetails_issued; extern NSString *const TxTokenDetails_capability; extern NSString *const TxTokenDetails_clientId; +// key constants for AuthOptions +extern NSString *const TxAuthOptions_authCallback; +extern NSString *const TxAuthOptions_authUrl; +extern NSString *const TxAuthOptions_authMethod; +extern NSString *const TxAuthOptions_key; +extern NSString *const TxAuthOptions_token; +extern NSString *const TxAuthOptions_tokenDetails; +extern NSString *const TxAuthOptions_authHeaders; +extern NSString *const TxAuthOptions_authParams; +extern NSString *const TxAuthOptions_queryTime; +extern NSString *const TxAuthOptions_useTokenAuth; + // key constants for TokenParams extern NSString *const TxTokenParams_capability; extern NSString *const TxTokenParams_clientId; diff --git a/ios/Classes/codec/AblyPlatformConstants.m b/ios/Classes/codec/AblyPlatformConstants.m index cc3ad48f6..9f4761111 100644 --- a/ios/Classes/codec/AblyPlatformConstants.m +++ b/ios/Classes/codec/AblyPlatformConstants.m @@ -19,6 +19,10 @@ NSString *const AblyPlatformMethod_restPresenceGet= @"restPresenceGet"; NSString *const AblyPlatformMethod_restPresenceHistory= @"restPresenceHistory"; NSString *const AblyPlatformMethod_releaseRestChannel= @"releaseRestChannel"; +NSString *const AblyPlatformMethod_restAuthAuthorize= @"restAuthAuthorize"; +NSString *const AblyPlatformMethod_restAuthCreateTokenRequest= @"restAuthCreateTokenRequest"; +NSString *const AblyPlatformMethod_restAuthRequestToken= @"restAuthRequestToken"; +NSString *const AblyPlatformMethod_restAuthGetClientId= @"restAuthGetClientId"; NSString *const AblyPlatformMethod_createRealtime= @"createRealtime"; NSString *const AblyPlatformMethod_connectRealtime= @"connectRealtime"; NSString *const AblyPlatformMethod_closeRealtime= @"closeRealtime"; @@ -36,6 +40,10 @@ NSString *const AblyPlatformMethod_realtimeHistory= @"realtimeHistory"; NSString *const AblyPlatformMethod_realtimeTime= @"realtimeTime"; NSString *const AblyPlatformMethod_restTime= @"restTime"; +NSString *const AblyPlatformMethod_realtimeAuthAuthorize= @"realtimeAuthAuthorize"; +NSString *const AblyPlatformMethod_realtimeAuthCreateTokenRequest= @"realtimeAuthCreateTokenRequest"; +NSString *const AblyPlatformMethod_realtimeAuthRequestToken= @"realtimeAuthRequestToken"; +NSString *const AblyPlatformMethod_realtimeAuthGetClientId= @"realtimeAuthGetClientId"; NSString *const AblyPlatformMethod_pushActivate= @"pushActivate"; NSString *const AblyPlatformMethod_pushDeactivate= @"pushDeactivate"; NSString *const AblyPlatformMethod_pushReset= @"pushReset"; @@ -159,6 +167,18 @@ NSString *const TxTokenDetails_capability = @"capability"; NSString *const TxTokenDetails_clientId = @"clientId"; +// key constants for AuthOptions +NSString *const TxAuthOptions_authCallback = @"authCallback"; +NSString *const TxAuthOptions_authUrl = @"authUrl"; +NSString *const TxAuthOptions_authMethod = @"authMethod"; +NSString *const TxAuthOptions_key = @"key"; +NSString *const TxAuthOptions_token = @"token"; +NSString *const TxAuthOptions_tokenDetails = @"tokenDetails"; +NSString *const TxAuthOptions_authHeaders = @"authHeaders"; +NSString *const TxAuthOptions_authParams = @"authParams"; +NSString *const TxAuthOptions_queryTime = @"queryTime"; +NSString *const TxAuthOptions_useTokenAuth = @"useTokenAuth"; + // key constants for TokenParams NSString *const TxTokenParams_capability = @"capability"; NSString *const TxTokenParams_clientId = @"clientId"; diff --git a/ios/Classes/handlers/AuthHandlers.swift b/ios/Classes/handlers/AuthHandlers.swift new file mode 100644 index 000000000..fdef4657a --- /dev/null +++ b/ios/Classes/handlers/AuthHandlers.swift @@ -0,0 +1,162 @@ +// +// AuthHandlers.swift +// ably_flutter +// +// Created by Ikbal Kaya on 17/03/2023. +// + +import Foundation + +public class AuthHandlers: NSObject { + + + @objc + public static let realtimeCreateTokenRequest: FlutterHandler = { ably, call, result in + let ablyMessage = call.arguments as! AblyFlutterMessage + let realtime = ably.instanceStore.realtime(from: ablyMessage.handle) + let dataMap = ablyMessage.message as! Dictionary + var authOptions: ARTAuthOptions? + var tokenParams: ARTTokenParams? + + if let dataMap = ablyMessage.message as? Dictionary { + authOptions = dataMap[TxTransportKeys_options] as? ARTAuthOptions + tokenParams = dataMap[TxTransportKeys_params] as? ARTTokenParams + } + realtime?.auth.createTokenRequest(tokenParams, options: authOptions, callback: { (tokenRequest: ARTTokenRequest?, error: Error?) in + if let error = error { + result(error) + return + } + result(tokenRequest) + }) + } + + @objc + public static let realtimeAuthClientId: FlutterHandler = { ably, call, result in + let ablyMessage = call.arguments as! AblyFlutterMessage + let realtime = ably.instanceStore.realtime(from: ablyMessage.handle) + let clientId = realtime?.auth.clientId + result(clientId) + } + + @objc + public static let realtimeAuthorize: FlutterHandler = { ably, call, result in + let ablyMessage = call.arguments as! AblyFlutterMessage + let realtime = ably.instanceStore.realtime(from: ablyMessage.handle) + let dataMap = ablyMessage.message as! Dictionary + var authOptions: ARTAuthOptions? + var tokenParams: ARTTokenParams? + + if let dataMap = ablyMessage.message as? Dictionary { + authOptions = dataMap[TxTransportKeys_options] as? ARTAuthOptions + tokenParams = dataMap[TxTransportKeys_params] as? ARTTokenParams + } + + realtime?.auth.authorize(tokenParams, options: authOptions, callback: { (details:ARTTokenDetails?, error: Error?) in + if let error = error { + result(error) + return + } + result(details) + + }); + } + + @objc + public static let realtimeRequestToken: FlutterHandler = { ably, call, result in + let ablyMessage = call.arguments as! AblyFlutterMessage + let realtime = ably.instanceStore.realtime(from: ablyMessage.handle) + let dataMap = ablyMessage.message as! Dictionary + var authOptions: ARTAuthOptions? + var tokenParams: ARTTokenParams? + + if let dataMap = ablyMessage.message as? Dictionary { + authOptions = dataMap[TxTransportKeys_options] as? ARTAuthOptions + tokenParams = dataMap[TxTransportKeys_params] as? ARTTokenParams + } + realtime?.auth.requestToken(tokenParams, with: authOptions, callback: { (details: ARTTokenDetails?, error: Error?) in + if let error = error { + result(error) + return + } + result(details) + }) + + } + + + @objc + public static let restCreateTokenRequest: FlutterHandler = { ably, call, result in + let ablyMessage = call.arguments as! AblyFlutterMessage + let rest = ably.instanceStore.rest(from: ablyMessage.handle) + let dataMap = ablyMessage.message as! Dictionary + var authOptions: ARTAuthOptions? + var tokenParams: ARTTokenParams? + + if let dataMap = ablyMessage.message as? Dictionary { + authOptions = dataMap[TxTransportKeys_options] as? ARTAuthOptions + tokenParams = dataMap[TxTransportKeys_params] as? ARTTokenParams + } + rest?.auth.createTokenRequest(tokenParams, options: authOptions, callback: { (tokenRequest: ARTTokenRequest?, error: Error?) in + if let error = error { + result(error) + return + } + result(tokenRequest) + }) + } + + + @objc + public static let restAuthorize: FlutterHandler = { ably, call, result in + let ablyMessage = call.arguments as! AblyFlutterMessage + let rest = ably.instanceStore.rest(from: ablyMessage.handle) + let dataMap = ablyMessage.message as! Dictionary + var authOptions: ARTAuthOptions? + var tokenParams: ARTTokenParams? + + if let dataMap = ablyMessage.message as? Dictionary { + authOptions = dataMap[TxTransportKeys_options] as? ARTAuthOptions + tokenParams = dataMap[TxTransportKeys_params] as? ARTTokenParams + } + + rest?.auth.authorize(tokenParams, options: authOptions, callback: { (details:ARTTokenDetails?, error: Error?) in + if let error = error { + result(error) + return + } + result(details) + + }); + } + + @objc + public static let restRequestToken: FlutterHandler = { ably, call, result in + let ablyMessage = call.arguments as! AblyFlutterMessage + let rest = ably.instanceStore.rest(from: ablyMessage.handle) + let dataMap = ablyMessage.message as! Dictionary + var authOptions: ARTAuthOptions? + var tokenParams: ARTTokenParams? + + if let dataMap = ablyMessage.message as? Dictionary { + authOptions = dataMap[TxTransportKeys_options] as? ARTAuthOptions + tokenParams = dataMap[TxTransportKeys_params] as? ARTTokenParams + } + rest?.auth.requestToken(tokenParams, with: authOptions, callback: { (details: ARTTokenDetails?, error: Error?) in + if let error = error { + result(error) + return + } + result(details) + }) + + } + + @objc + public static let restAuthClientId: FlutterHandler = { ably, call, result in + let ablyMessage = call.arguments as! AblyFlutterMessage + let rest = ably.instanceStore.rest(from: ablyMessage.handle) + let clientId = rest?.auth.clientId + result(clientId) + } +} diff --git a/lib/src/authentication/src/auth.dart b/lib/src/authentication/src/auth.dart index f8da0a46e..2bbf9884a 100644 --- a/lib/src/authentication/src/auth.dart +++ b/lib/src/authentication/src/auth.dart @@ -12,7 +12,7 @@ abstract class Auth { /// implicit in a token used to instantiate the library. An error is raised if /// a `clientId` specified here conflicts with the `clientId` implicit in the /// token. Find out more about [identified clients](https://ably.com/docs/core-features/authentication#identified-clients). - String get clientId; + Future get clientId; /// Instructs the library to get a new token immediately using [tokenParams] /// and [authOptions] parameters. diff --git a/lib/src/authentication/src/auth_options.dart b/lib/src/authentication/src/auth_options.dart index 220d2d5a7..feb3bce91 100644 --- a/lib/src/authentication/src/auth_options.dart +++ b/lib/src/authentication/src/auth_options.dart @@ -6,7 +6,7 @@ import 'package:ably_flutter/ably_flutter.dart'; /// Properties set using `AuthOptions` are used instead of the default /// values set when the client library is instantiated, as opposed to being /// merged with them. -abstract class AuthOptions { +class AuthOptions { /// Called when a new token is required. /// /// The role of the callback is to obtain a fresh token, one of: an Ably Token diff --git a/lib/src/generated/platform_constants.dart b/lib/src/generated/platform_constants.dart index 8458ce6a4..e8f113192 100644 --- a/lib/src/generated/platform_constants.dart +++ b/lib/src/generated/platform_constants.dart @@ -9,30 +9,31 @@ class CodecTypes { static const int ablyMessage = 128; static const int ablyEventMessage = 129; static const int clientOptions = 130; - static const int messageData = 131; - static const int messageExtras = 132; - static const int message = 133; - static const int tokenParams = 134; - static const int tokenDetails = 135; - static const int tokenRequest = 136; - static const int restChannelOptions = 137; - static const int realtimeChannelOptions = 138; - static const int paginatedResult = 139; - static const int restHistoryParams = 140; - static const int realtimeHistoryParams = 141; - static const int restPresenceParams = 142; - static const int presenceMessage = 143; - static const int realtimePresenceParams = 144; - static const int deviceDetails = 145; - static const int localDevice = 146; - static const int pushChannelSubscription = 147; - static const int unNotificationSettings = 148; - static const int remoteMessage = 149; - static const int errorInfo = 150; - static const int logLevel = 151; - static const int connectionStateChange = 152; - static const int channelStateChange = 153; - static const int cipherParams = 154; + static const int authOptions = 131; + static const int messageData = 132; + static const int messageExtras = 133; + static const int message = 134; + static const int tokenParams = 135; + static const int tokenDetails = 136; + static const int tokenRequest = 137; + static const int restChannelOptions = 138; + static const int realtimeChannelOptions = 139; + static const int paginatedResult = 140; + static const int restHistoryParams = 141; + static const int realtimeHistoryParams = 142; + static const int restPresenceParams = 143; + static const int presenceMessage = 144; + static const int realtimePresenceParams = 145; + static const int deviceDetails = 146; + static const int localDevice = 147; + static const int pushChannelSubscription = 148; + static const int unNotificationSettings = 149; + static const int remoteMessage = 150; + static const int errorInfo = 151; + static const int logLevel = 152; + static const int connectionStateChange = 153; + static const int channelStateChange = 154; + static const int cipherParams = 155; } class PlatformMethod { @@ -48,6 +49,10 @@ class PlatformMethod { static const String restPresenceGet = 'restPresenceGet'; static const String restPresenceHistory = 'restPresenceHistory'; static const String releaseRestChannel = 'releaseRestChannel'; + static const String restAuthAuthorize = 'restAuthAuthorize'; + static const String restAuthCreateTokenRequest = 'restAuthCreateTokenRequest'; + static const String restAuthRequestToken = 'restAuthRequestToken'; + static const String restAuthGetClientId = 'restAuthGetClientId'; static const String createRealtime = 'createRealtime'; static const String connectRealtime = 'connectRealtime'; static const String closeRealtime = 'closeRealtime'; @@ -66,6 +71,11 @@ class PlatformMethod { static const String realtimeHistory = 'realtimeHistory'; static const String realtimeTime = 'realtimeTime'; static const String restTime = 'restTime'; + static const String realtimeAuthAuthorize = 'realtimeAuthAuthorize'; + static const String realtimeAuthCreateTokenRequest = + 'realtimeAuthCreateTokenRequest'; + static const String realtimeAuthRequestToken = 'realtimeAuthRequestToken'; + static const String realtimeAuthGetClientId = 'realtimeAuthGetClientId'; static const String pushActivate = 'pushActivate'; static const String pushDeactivate = 'pushDeactivate'; static const String pushReset = 'pushReset'; @@ -208,6 +218,19 @@ class TxTokenDetails { static const String clientId = 'clientId'; } +class TxAuthOptions { + static const String authCallback = 'authCallback'; + static const String authUrl = 'authUrl'; + static const String authMethod = 'authMethod'; + static const String key = 'key'; + static const String token = 'token'; + static const String tokenDetails = 'tokenDetails'; + static const String authHeaders = 'authHeaders'; + static const String authParams = 'authParams'; + static const String queryTime = 'queryTime'; + static const String useTokenAuth = 'useTokenAuth'; +} + class TxTokenParams { static const String capability = 'capability'; static const String clientId = 'clientId'; diff --git a/lib/src/platform/src/codec.dart b/lib/src/platform/src/codec.dart index 18b15e705..30f4b67ce 100644 --- a/lib/src/platform/src/codec.dart +++ b/lib/src/platform/src/codec.dart @@ -1,6 +1,5 @@ import 'dart:io' as io show Platform; import 'dart:typed_data'; - import 'package:ably_flutter/ably_flutter.dart'; import 'package:ably_flutter/src/platform/platform_internal.dart'; import 'package:flutter/foundation.dart'; @@ -81,13 +80,14 @@ class Codec extends StandardMessageCodec { CodecTypes.tokenDetails: _CodecPair(_encodeTokenDetails, _decodeTokenDetails), CodecTypes.tokenRequest: - _CodecPair(_encodeTokenRequest, null), + _CodecPair(_encodeTokenRequest, _decodeTokenRequest), CodecTypes.restChannelOptions: _CodecPair(_encodeRestChannelOptions, null), CodecTypes.realtimeChannelOptions: _CodecPair( _encodeRealtimeChannelOptions, null, ), + CodecTypes.authOptions: _CodecPair(_encodeAuthOptions, null), CodecTypes.paginatedResult: _CodecPair>(null, _decodePaginatedResult), CodecTypes.realtimeHistoryParams: @@ -194,6 +194,8 @@ class Codec extends StandardMessageCodec { return CodecTypes.ablyMessage; } else if (value is AblyEventMessage) { return CodecTypes.ablyEventMessage; + } else if (value is AuthOptions) { + return CodecTypes.authOptions; } // ignore: avoid_returning_null return null; @@ -384,6 +386,23 @@ class Codec extends StandardMessageCodec { return jsonMap; } + Map _encodeAuthOptions(final AuthOptions authOptions) { + final jsonMap = {}; + jsonMap[TxAuthOptions.tokenDetails] = + _encodeTokenDetails(authOptions.tokenDetails); + jsonMap[TxAuthOptions.authUrl] = authOptions.authUrl; + jsonMap[TxAuthOptions.authMethod] = authOptions.authMethod; + jsonMap[TxAuthOptions.key] = authOptions.key; + //For authOptions.authCallback happens check method_call_handler.dart + jsonMap[TxAuthOptions.token] = authOptions.tokenDetails?.token; + jsonMap[TxAuthOptions.authHeaders] = authOptions.authHeaders; + jsonMap[TxAuthOptions.authParams] = authOptions.authParams; + jsonMap[TxAuthOptions.queryTime] = authOptions.queryTime; + jsonMap[TxAuthOptions.useTokenAuth] = authOptions.useTokenAuth; + + return jsonMap; + } + Map? _encodeCipherParams(final CipherParams? params) { if (params == null) { return null; @@ -693,6 +712,24 @@ class Codec extends StandardMessageCodec { clientId: _readFromJson(jsonMap, TxTokenDetails.clientId), ); + /// @nodoc + /// Decodes value [jsonMap] to [TokenRequest] + /// returns null if [jsonMap] is null + TokenRequest _decodeTokenRequest(Map jsonMap) { + final timestamp = _readFromJson(jsonMap, TxTokenRequest.timestamp); + return TokenRequest( + capability: _readFromJson(jsonMap, TxTokenRequest.capability), + clientId: _readFromJson(jsonMap, TxTokenRequest.clientId), + keyName: _readFromJson(jsonMap, TxTokenRequest.keyName), + mac: _readFromJson(jsonMap, TxTokenRequest.mac), + nonce: _readFromJson(jsonMap, TxTokenRequest.nonce), + timestamp: (timestamp != null) + ? DateTime.fromMillisecondsSinceEpoch(timestamp) + : null, + ttl: _readFromJson(jsonMap, TxTokenRequest.ttl), + ); + } + /// @nodoc /// Decodes value [jsonMap] to [TokenParams] /// returns null if [jsonMap] is null diff --git a/lib/src/platform/src/realtime/realtime.dart b/lib/src/platform/src/realtime/realtime.dart index f862ce1f4..d186dbdc7 100644 --- a/lib/src/platform/src/realtime/realtime.dart +++ b/lib/src/platform/src/realtime/realtime.dart @@ -4,6 +4,7 @@ import 'dart:io' as io show Platform; import 'package:ably_flutter/ably_flutter.dart'; import 'package:ably_flutter/src/platform/platform_internal.dart'; +import 'package:ably_flutter/src/realtime/src/realtime_auth.dart'; /// A client that extends functionality of the [Rest] and provides /// additional realtime-specific features. @@ -19,6 +20,7 @@ class Realtime extends PlatformObject { _connection = Connection(this); _channels = RealtimeChannels(this); push = Push(realtime: this); + auth = RealtimeAuth(this); } /// Constructs a `Realtime` object using an Ably API [key] or token string @@ -59,7 +61,7 @@ class Realtime extends PlatformObject { Connection get connection => _connection; /// An [Auth] object. - // Auth? auth; + late Auth auth; /// @nodoc /// A [ClientOptions] object used to configure the client connection to Ably. diff --git a/lib/src/platform/src/rest/rest.dart b/lib/src/platform/src/rest/rest.dart index 5018e2b33..2947173fb 100644 --- a/lib/src/platform/src/rest/rest.dart +++ b/lib/src/platform/src/rest/rest.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'package:ably_flutter/ably_flutter.dart'; import 'package:ably_flutter/src/platform/platform_internal.dart'; +import 'package:ably_flutter/src/rest/src/rest_auth.dart'; Map _restInstances = {}; @@ -17,6 +18,7 @@ class Rest extends PlatformObject { Rest({required this.options}) : super() { channels = RestChannels(this); push = Push(rest: this); + auth = RestAuth(this); } /// Constructs a `Rest` object using an Ably API [key] or token string @@ -34,7 +36,7 @@ class Rest extends PlatformObject { } /// An [Auth] object. - // Auth? auth; + late Auth auth; /// An object that contains additional client-specific properties late ClientOptions options; diff --git a/lib/src/realtime/src/realtime_auth.dart b/lib/src/realtime/src/realtime_auth.dart new file mode 100644 index 000000000..c6366d27e --- /dev/null +++ b/lib/src/realtime/src/realtime_auth.dart @@ -0,0 +1,41 @@ +import 'package:ably_flutter/ably_flutter.dart'; +import 'package:ably_flutter/src/platform/platform_internal.dart'; + +/// See [Auth] for more information. +class RealtimeAuth extends Auth { + final Realtime _realtime; + + /// Constructor for RealtimeAuth + RealtimeAuth(this._realtime); + + @override + Future authorize( + {AuthOptions? authOptions, TokenParams? tokenParams}) => + _realtime.invokeRequest( + PlatformMethod.realtimeAuthAuthorize, { + TxTransportKeys.options: authOptions, + TxTransportKeys.params: tokenParams + }); + + @override + Future get clientId async => + _realtime.invokeRequest(PlatformMethod.realtimeAuthGetClientId); + + @override + Future createTokenRequest( + {AuthOptions? authOptions, TokenParams? tokenParams}) => + _realtime.invokeRequest( + PlatformMethod.realtimeAuthCreateTokenRequest, { + TxTransportKeys.options: authOptions, + TxTransportKeys.params: tokenParams + }); + + @override + Future requestToken( + {AuthOptions? authOptions, TokenParams? tokenParams}) => + _realtime.invokeRequest( + PlatformMethod.realtimeAuthRequestToken, { + TxTransportKeys.options: authOptions, + TxTransportKeys.params: tokenParams + }); +} diff --git a/lib/src/rest/src/rest_auth.dart b/lib/src/rest/src/rest_auth.dart new file mode 100644 index 000000000..d9d2897d1 --- /dev/null +++ b/lib/src/rest/src/rest_auth.dart @@ -0,0 +1,39 @@ +import 'package:ably_flutter/ably_flutter.dart'; +import 'package:ably_flutter/src/platform/platform_internal.dart'; + +/// See [Auth] for more information. +class RestAuth extends Auth { + final Rest _rest; + + /// Constructor for RestAuth + RestAuth(this._rest); + + @override + Future authorize( + {AuthOptions? authOptions, TokenParams? tokenParams}) => + _rest.invokeRequest(PlatformMethod.restAuthAuthorize, { + TxTransportKeys.options: authOptions, + TxTransportKeys.params: tokenParams + }); + + @override + Future get clientId async => + _rest.invoke(PlatformMethod.restAuthGetClientId); + + @override + Future createTokenRequest( + {AuthOptions? authOptions, TokenParams? tokenParams}) => + _rest.invokeRequest( + PlatformMethod.restAuthCreateTokenRequest, { + TxTransportKeys.options: authOptions, + TxTransportKeys.params: tokenParams + }); + + @override + Future requestToken( + {AuthOptions? authOptions, TokenParams? tokenParams}) => + _rest.invokeRequest(PlatformMethod.restAuthRequestToken, { + TxTransportKeys.options: authOptions, + TxTransportKeys.params: tokenParams + }); +} diff --git a/pubspec.lock b/pubspec.lock index 48915d91e..1a519606b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -21,21 +21,14 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: "direct main" description: @@ -49,7 +42,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -80,28 +73,28 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -113,7 +106,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -134,21 +127,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" vector_math: dependency: transitive description: diff --git a/test_integration/ios/Podfile.lock b/test_integration/ios/Podfile.lock index 041d63dd3..0f4cc7397 100644 --- a/test_integration/ios/Podfile.lock +++ b/test_integration/ios/Podfile.lock @@ -29,7 +29,7 @@ SPEC CHECKSUMS: Ably: 80be962e2e87bac69727a26ba72b868c8e86288e ably_flutter: bba4fea46a2a3269fae98b67bd5c0c75cf3f0132 AblyDeltaCodec: 783d017270de70bbbc0a84e4235297b225d33636 - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 msgpack: c85f6251873059738472ae136951cec5f30f3251 PODFILE CHECKSUM: f2c6cc511583caab2c65ac3f5766428391d93d34 diff --git a/test_integration/lib/config/test_factory.dart b/test_integration/lib/config/test_factory.dart index 76e597d7e..48eb263ff 100644 --- a/test_integration/lib/config/test_factory.dart +++ b/test_integration/lib/config/test_factory.dart @@ -5,6 +5,10 @@ import 'package:ably_flutter_integration_test/test/crypto/crypto_ensure_supporte import 'package:ably_flutter_integration_test/test/crypto/crypto_generate_random_key_test.dart'; import 'package:ably_flutter_integration_test/test/crypto/crypto_get_default_params.dart'; import 'package:ably_flutter_integration_test/test/helpers_test.dart'; +import 'package:ably_flutter_integration_test/test/realtime/realtime_auth_client_id_test.dart'; +import 'package:ably_flutter_integration_test/test/realtime' + '/realtime_authorize_test' + '.dart'; import 'package:ably_flutter_integration_test/test/realtime/realtime_encrypted_publish_test.dart'; import 'package:ably_flutter_integration_test/test/realtime/realtime_events_test.dart'; import 'package:ably_flutter_integration_test/test/realtime/realtime_history_test.dart'; @@ -17,7 +21,10 @@ import 'package:ably_flutter_integration_test/test/realtime/realtime_publish_tes import 'package:ably_flutter_integration_test/test/realtime/realtime_publish_with_auth_callback_test.dart'; import 'package:ably_flutter_integration_test/test/realtime/realtime_subscribe.dart'; import 'package:ably_flutter_integration_test/test/realtime/realtime_time_test.dart'; +import 'package:ably_flutter_integration_test/test/rest/rest_auth_client_id_test.dart'; +import 'package:ably_flutter_integration_test/test/rest/rest_authorize_test.dart'; import 'package:ably_flutter_integration_test/test/rest/rest_capability_test.dart'; +import 'package:ably_flutter_integration_test/test/rest/rest_create_token_request_test.dart'; import 'package:ably_flutter_integration_test/test/rest/rest_encrypted_publish_test.dart'; import 'package:ably_flutter_integration_test/test/rest/rest_history_test.dart'; import 'package:ably_flutter_integration_test/test/rest/rest_history_with_auth_callback_test.dart'; @@ -25,6 +32,7 @@ import 'package:ably_flutter_integration_test/test/rest/rest_presence_get_test.d import 'package:ably_flutter_integration_test/test/rest/rest_presence_history_test.dart'; import 'package:ably_flutter_integration_test/test/rest/rest_publish_test.dart'; import 'package:ably_flutter_integration_test/test/rest/rest_publish_with_auth_callback_test.dart'; +import 'package:ably_flutter_integration_test/test/rest/rest_request_token_test.dart'; import 'package:ably_flutter_integration_test/test/rest/rest_time_test.dart'; typedef TestFactory = Future> Function({ @@ -58,6 +66,8 @@ final testFactory = { TestName.realtimePublishWithAuthCallback: testRealtimePublishWithAuthCallback, TestName.realtimeSubscribe: testRealtimeSubscribe, TestName.realtimeTime: testRealtimeTime, + TestName.realtimeAuthAuthorize: testRealtimeAuthroize, + TestName.realtimeAuthClientId: testRealtimeAuthClientId, // rest tests TestName.restCapabilities: testRestCapabilities, @@ -71,6 +81,10 @@ final testFactory = { TestName.restPublishSpec: testRestPublishSpec, TestName.restPublishWithAuthCallback: testRestPublishWithAuthCallback, TestName.restTime: testRestTime, + TestName.restAuthAuthorize: testRestAuthorize, + TestName.restRequestToken: testRestRequestToken, + TestName.restCreateTokenRequest: testRestCreateTokenRequest, + TestName.restAuthClientId: testRestAuthClientId, // helper tests TestName.testHelperUnhandledExceptionTest: testHelperUnhandledException, diff --git a/test_integration/lib/config/test_names.dart b/test_integration/lib/config/test_names.dart index 2d8890b5f..0ad5e1198 100644 --- a/test_integration/lib/config/test_names.dart +++ b/test_integration/lib/config/test_names.dart @@ -32,6 +32,10 @@ class TestName { 'realtimePublishWithAuthCallback'; static const String realtimeSubscribe = 'realtimeSubscribe'; static const String realtimeTime = 'realtimeTime'; + static const String realtimeAuthAuthorize = 'realtimeAuthorize'; + static const String realtimeCreateTokenRequest = 'realtimeCreateTokenRequest'; + static const String realtimeRequestToken = 'realtimeRequestToken'; + static const String realtimeAuthClientId = 'realtimeAuthClientId'; // rest static const String restCapabilities = 'restCapabilities'; @@ -48,6 +52,11 @@ class TestName { 'restPublishWithAuthCallback'; static const String restTime = 'restTime'; + static const String restAuthAuthorize = 'restAuthorize'; + static const String restCreateTokenRequest = 'restCreateTokenRequest'; + static const String restRequestToken = 'restRequestToken'; + static const String restAuthClientId = 'restAuthClientId'; + // helpers static const String testHelperFlutterErrorTest = 'testHelperFlutterErrorTest'; static const String testHelperUnhandledExceptionTest = diff --git a/test_integration/lib/main.dart b/test_integration/lib/main.dart index 97412a43e..f547948ff 100644 --- a/test_integration/lib/main.dart +++ b/test_integration/lib/main.dart @@ -8,12 +8,12 @@ import 'package:flutter_driver/driver_extension.dart'; void main() { final testDispatcherController = DispatcherController(); - // track FlutterError's - FlutterError.onError = testDispatcherController.logFlutterErrors; - // enable driver extension enableFlutterDriverExtension(handler: testDispatcherController.driveHandler); + // track FlutterError's + FlutterError.onError = testDispatcherController.logFlutterErrors; + runZoned( () => runApp( TestDispatcher( diff --git a/test_integration/lib/test/realtime/realtime_auth_client_id_test.dart b/test_integration/lib/test/realtime/realtime_auth_client_id_test.dart new file mode 100644 index 000000000..e94781380 --- /dev/null +++ b/test_integration/lib/test/realtime/realtime_auth_client_id_test.dart @@ -0,0 +1,40 @@ +import 'package:ably_flutter/ably_flutter.dart'; +import 'package:ably_flutter_integration_test/app_provisioning.dart'; +import 'package:ably_flutter_integration_test/factory/reporter.dart'; + +Future> testRealtimeAuthClientId({ + required Reporter reporter, + Map? payload, +}) async { + reporter.reportLog('init start'); + final appKey = await AppProvisioning().provisionApp(); + + //init ably for token + final clientOptionsForToken = ClientOptions( + key: appKey, + environment: 'sandbox', + logLevel: LogLevel.verbose, + ); + + final ablyForToken = Rest( + options: clientOptionsForToken, + ); + /* get first token */ + final firstToken = await ablyForToken.auth.requestToken(); + + final clientOptions = ClientOptions( + key: appKey, + environment: 'sandbox', + useTokenAuth: true, + autoConnect: false, + clientId: 'testClientId', + tokenDetails: firstToken); + final realtime = Realtime(options: clientOptions); + await realtime.connect(); + + final clientId = await realtime.auth.clientId; + if (clientId != 'testClientId') { + throw Error(); + } + return {'handle': await realtime.handle}; +} diff --git a/test_integration/lib/test/realtime/realtime_authorize_test.dart b/test_integration/lib/test/realtime/realtime_authorize_test.dart new file mode 100644 index 000000000..b85136b0a --- /dev/null +++ b/test_integration/lib/test/realtime/realtime_authorize_test.dart @@ -0,0 +1,50 @@ +import 'package:ably_flutter/ably_flutter.dart'; +import 'package:ably_flutter_integration_test/app_provisioning.dart'; +import 'package:ably_flutter_integration_test/factory/reporter.dart'; + +Future> testRealtimeAuthroize({ + required Reporter reporter, + Map? payload, +}) async { + reporter.reportLog('init start'); + final appKey = await AppProvisioning().provisionApp(); + + //init ably for token + final clientOptionsForToken = ClientOptions( + key: appKey, + environment: 'sandbox', + logLevel: LogLevel.verbose, + ); + + final ablyForToken = Rest( + options: clientOptionsForToken, + ); + /* get first token */ + final firstToken = await ablyForToken.auth.requestToken(); + + final clientOptions = ClientOptions( + key: appKey, + environment: 'sandbox', + useTokenAuth: true, + autoConnect: false, + tokenDetails: firstToken); + final realtime = Realtime(options: clientOptions); + await realtime.connect(); + + const tokenParams = TokenParams(ttl: 20000); + Future authCallback(params) async => TokenRequest.fromMap( + await AppProvisioning().getTokenRequest(), + ); + final authOptions = AuthOptions( + key: appKey, + useTokenAuth: true, + authCallback: authCallback, + authMethod: 'GET'); + final refreshedToken = await realtime.auth + .authorize(authOptions: authOptions, tokenParams: tokenParams); + //tokens must be different + if (refreshedToken == firstToken) { + throw Error(); + } + return {'handle': await realtime.handle}; +} diff --git a/test_integration/lib/test/rest/rest_auth_client_id_test.dart b/test_integration/lib/test/rest/rest_auth_client_id_test.dart new file mode 100644 index 000000000..32eefa261 --- /dev/null +++ b/test_integration/lib/test/rest/rest_auth_client_id_test.dart @@ -0,0 +1,39 @@ +import 'package:ably_flutter/ably_flutter.dart'; +import 'package:ably_flutter_integration_test/app_provisioning.dart'; +import 'package:ably_flutter_integration_test/factory/reporter.dart'; + +Future> testRestAuthClientId({ + required Reporter reporter, + Map? payload, +}) async { + reporter.reportLog('init start'); + final appKey = await AppProvisioning().provisionApp(); + + //init ably for token + final clientOptionsForToken = ClientOptions( + key: appKey, + environment: 'sandbox', + logLevel: LogLevel.verbose, + ); + + final ablyForToken = Rest( + options: clientOptionsForToken, + ); + /* get first token */ + final firstToken = await ablyForToken.auth.requestToken(); + + final clientOptions = ClientOptions( + key: appKey, + environment: 'sandbox', + useTokenAuth: true, + autoConnect: false, + clientId: 'testClientId', + tokenDetails: firstToken); + final rest = Rest(options: clientOptions); + + final clientId = await rest.auth.clientId; + if (clientId != 'testClientId') { + throw Error(); + } + return {'handle': await rest.handle}; +} diff --git a/test_integration/lib/test/rest/rest_authorize_test.dart b/test_integration/lib/test/rest/rest_authorize_test.dart new file mode 100644 index 000000000..c0f636326 --- /dev/null +++ b/test_integration/lib/test/rest/rest_authorize_test.dart @@ -0,0 +1,49 @@ +import 'package:ably_flutter/ably_flutter.dart'; +import 'package:ably_flutter_integration_test/app_provisioning.dart'; +import 'package:ably_flutter_integration_test/factory/reporter.dart'; + +Future> testRestAuthorize({ + required Reporter reporter, + Map? payload, +}) async { + reporter.reportLog('init start'); + final appKey = await AppProvisioning().provisionApp(); + + //init ably for token + final clientOptionsForToken = ClientOptions( + key: appKey, + environment: 'sandbox', + logLevel: LogLevel.verbose, + ); + + final ablyForToken = Rest( + options: clientOptionsForToken, + ); + /* get first token */ + final firstToken = await ablyForToken.auth.requestToken(); + + final clientOptions = ClientOptions( + key: appKey, + environment: 'sandbox', + useTokenAuth: true, + autoConnect: false, + tokenDetails: firstToken); + final rest = Rest(options: clientOptions); + + const tokenParams = TokenParams(ttl: 20000); + Future authCallback(params) async => TokenRequest.fromMap( + await AppProvisioning().getTokenRequest(), + ); + final authOptions = AuthOptions( + key: appKey, + useTokenAuth: true, + authCallback: authCallback, + authMethod: 'GET'); + final refreshedToken = await rest.auth + .authorize(authOptions: authOptions, tokenParams: tokenParams); + //tokens must be different + if (refreshedToken == firstToken) { + throw Error(); + } + return {'handle': await rest.handle}; +} diff --git a/test_integration/lib/test/rest/rest_create_token_request_test.dart b/test_integration/lib/test/rest/rest_create_token_request_test.dart new file mode 100644 index 000000000..a94f5bcd4 --- /dev/null +++ b/test_integration/lib/test/rest/rest_create_token_request_test.dart @@ -0,0 +1,44 @@ +import 'package:ably_flutter/ably_flutter.dart'; +import 'package:ably_flutter_integration_test/app_provisioning.dart'; +import 'package:ably_flutter_integration_test/factory/reporter.dart'; + +Future> testRestCreateTokenRequest({ + required Reporter reporter, + Map? payload, +}) async { + reporter.reportLog('init start'); + final appKey = await AppProvisioning().provisionApp(); + + final clientOptionsForToken = ClientOptions( + key: appKey, + environment: 'sandbox', + logLevel: LogLevel.verbose, + ); + + final rest = Rest( + options: clientOptionsForToken, + ); + const tokenParams = TokenParams( + ttl: 20000, + capability: '{"testchannel":["publish"]}', + clientId: 'testclient'); + + final authOptions = AuthOptions( + key: appKey, + useTokenAuth: true, + authMethod: 'GET', + authHeaders: {'heardkey': 'headervalue'}, + authUrl: 'bogus', + authParams: {'paramkey': 'paramvalue'}); + final request = await rest.auth + .createTokenRequest(authOptions: authOptions, tokenParams: tokenParams); + if (request.ttl != tokenParams.ttl || + request.clientId != tokenParams.clientId || + request.capability != tokenParams.capability) { + throw Error(); + } + + return { + 'handle': await rest.handle, + }; +} diff --git a/test_integration/lib/test/rest/rest_request_token_test.dart b/test_integration/lib/test/rest/rest_request_token_test.dart new file mode 100644 index 000000000..b1b179fb1 --- /dev/null +++ b/test_integration/lib/test/rest/rest_request_token_test.dart @@ -0,0 +1,33 @@ +import 'package:ably_flutter/ably_flutter.dart'; +import 'package:ably_flutter_integration_test/app_provisioning.dart'; +import 'package:ably_flutter_integration_test/factory/reporter.dart'; +import 'package:ably_flutter_integration_test/utils/rest.dart'; + +Future> testRestRequestToken({ + required Reporter reporter, + Map? payload, +}) async { + reporter.reportLog('init start'); + final appKey = await AppProvisioning().provisionApp(); + + //init ably for token + final clientOptionsForToken = ClientOptions( + key: appKey, + environment: 'sandbox', + logLevel: LogLevel.verbose, + ); + + final restForToken = Rest( + options: clientOptionsForToken, + ); + + final token = await restForToken.auth.requestToken(); + + final clientOptions = ClientOptions( + tokenDetails: token, environment: 'sandbox', logLevel: LogLevel.verbose); + final tokenedRest = Rest(options: clientOptions); + + await publishMessages(tokenedRest.channels.get('test')); + + return {'handle': await tokenedRest.handle}; +} diff --git a/test_integration/lib/test_dispatcher.dart b/test_integration/lib/test_dispatcher.dart index fb12bcf76..c7f6f6c0b 100644 --- a/test_integration/lib/test_dispatcher.dart +++ b/test_integration/lib/test_dispatcher.dart @@ -106,13 +106,14 @@ class _TestDispatcherState extends State { case _TestStatus.progress: return Colors.blue; case null: - return Colors.grey; + return Colors.white; } } Widget _getAction(String testName) { final playIcon = IconButton( icon: const Icon(Icons.play_arrow), + color: Colors.white, onPressed: () { handleDriverMessage(TestControlMessage(testName)).then((_) { setState(() {}); @@ -154,13 +155,14 @@ class _TestDispatcherState extends State { Expanded( child: Text( testName, - style: TextStyle(color: _getColor(testName)), + style: TextStyle(color: _getColor(testName), fontSize: 16), ), ), _getAction(testName), _getStatus(testName), IconButton( icon: const Icon(Icons.remove_red_eye), + color: Colors.white, onPressed: () { showDialog( context: context, @@ -181,7 +183,8 @@ class _TestDispatcherState extends State { @override Widget build(BuildContext context) => MaterialApp( - home: Scaffold( + theme: ThemeData(scaffoldBackgroundColor: Colors.black), + home: Scaffold( appBar: AppBar( title: const Text('Test dispatcher'), ), @@ -191,11 +194,11 @@ class _TestDispatcherState extends State { children: [ Center( child: Text( - _reporters.isEmpty - ? '-' - : 'running ${_reporters.length}' - ' (${_reporters.keys.toList().toString()}) tests', - ), + _reporters.isEmpty + ? '-' + : 'running ${_reporters.length}' + ' (${_reporters.keys.toList().toString()}) tests', + style: const TextStyle(color: Colors.white)), ), Expanded( child: ListView.builder( diff --git a/test_integration/pubspec.lock b/test_integration/pubspec.lock index cfa2b822a..d75c0d7eb 100644 --- a/test_integration/pubspec.lock +++ b/test_integration/pubspec.lock @@ -28,7 +28,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.1.6" + version: "3.3.0" args: dependency: "direct main" description: @@ -42,7 +42,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -56,7 +56,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -70,14 +70,14 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: @@ -91,14 +91,14 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.5.0" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" enum_to_string: dependency: "direct main" description: @@ -112,7 +112,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" file: dependency: transitive description: @@ -138,7 +138,7 @@ packages: source: hosted version: "1.0.4" flutter_test: - dependency: "direct dev" + dependency: transitive description: flutter source: sdk version: "0.0.0" @@ -211,26 +211,26 @@ packages: source: hosted version: "1.0.2" matcher: - dependency: "direct overridden" + dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -258,7 +258,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" platform: dependency: transitive description: @@ -340,7 +340,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -361,63 +361,63 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" sync_http: dependency: transitive description: name: sync_http url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.3.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test: dependency: "direct dev" description: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.21.1" + version: "1.21.4" test_api: - dependency: "direct overridden" + dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.13" + version: "0.4.16" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" vm_service: dependency: transitive description: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "7.5.0" + version: "9.0.0" watcher: dependency: transitive description: @@ -454,5 +454,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=2.5.0" diff --git a/test_integration/test_driver/test_implementation/rest_tests.dart b/test_integration/test_driver/test_implementation/rest_tests.dart index 5c623c860..0e740c30e 100644 --- a/test_integration/test_driver/test_implementation/rest_tests.dart +++ b/test_integration/test_driver/test_implementation/rest_tests.dart @@ -18,6 +18,34 @@ void testRestPublish(FlutterDriver Function() getDriver) { }); } +void testRestRequestTokenPublish(FlutterDriver Function() getDriver) { + const message = TestControlMessage(TestName.restRequestToken); + late TestControlResponseMessage response; + setUpAll( + () async => response = await requestDataForTest(getDriver(), message)); + + test( + 'rest instance with token has a valid handle and successfully' + ' publishes messages', () { + expect(response.payload['handle'], isA()); + expect(response.payload['handle'], greaterThan(0)); + }); +} + +void testRestCreateTokenRequestPublish(FlutterDriver Function() getDriver) { + const message = TestControlMessage(TestName.restCreateTokenRequest); + late TestControlResponseMessage response; + setUpAll( + () async => response = await requestDataForTest(getDriver(), message)); + + test( + 'rest instance with token has a valid handle and successfully ' + 'publishes messages', () { + expect(response.payload['handle'], isA()); + expect(response.payload['log'], greaterThan(0)); + }); +} + void testRestEncryptedPublish(FlutterDriver Function() getDriver) { const message = TestControlMessage(TestName.restEncryptedPublish); late TestControlResponseMessage response; diff --git a/test_integration/test_driver/tests_config.dart b/test_integration/test_driver/tests_config.dart index 29eed81ee..ecf9a293a 100644 --- a/test_integration/test_driver/tests_config.dart +++ b/test_integration/test_driver/tests_config.dart @@ -1,5 +1,4 @@ import 'package:flutter_driver/flutter_driver.dart'; - import 'test_implementation/basic_platform_tests.dart'; import 'test_implementation/crypto_tests.dart'; import 'test_implementation/helper_tests.dart'; @@ -19,6 +18,7 @@ final _tests = }, TestModules.rest: { 'should publish': testRestPublish, + 'should publish with tokenRequest': testRestRequestTokenPublish, 'should publish encrypted': testRestEncryptedPublish, 'should retrieve history': testRestHistory, 'should retrieve history with auth callback':