diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 763a01f10..cef8b0d08 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -5,7 +5,28 @@ on: - main jobs: - check: + swift: + name: "Swift formatting checks" + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: 'Install SwiftFormat' + run: 'brew install swiftformat' + + - name: Run SwiftFormat + run: swiftformat --lint --swiftversion 5.5 . + java: + name: "Java formatting checks" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: axel-op/googlejavaformat-action@v3 + with: + args: "--set-exit-if-changed --dry-run" + dart: + name: "Dart formatting and analyzer checks" runs-on: ubuntu-latest steps: @@ -20,7 +41,7 @@ jobs: cd test_integration && flutter pub get && cd .. cd example && flutter pub get && cd .. - - name: 'Check formatting issues in the code' + - name: 'Check Dart formatting issues in the code' run: flutter format --set-exit-if-changed . - name: 'Flutter analyze help' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 397750656..f9f553d57 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,20 +2,43 @@ ## Getting Started -The code in this repository has been constructed to be -[built as a Flutter Plugin](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin). -It is not yet constructed as a federated plugin but this is in our backlog as -[issue 118](https://github.com/ably/ably-flutter/issues/118). - -After checking out this repository, run the following command: - - flutter pub get +- Clone the repository +- Install [Flutter](https://docs.flutter.dev/get-started/install) +- Install Android Studio, and then in Android Studio, install the [Flutter plugin](https://plugins.jetbrains.com/plugin/9212-flutter). + - Though you can debug Dart code from Visual Studio Code, you cannot debug the Android code. +- Download the dependencies listed in `pubspec.yaml` by running `flutter pub get` + - Files have a yellow background in Android Studio's project [tool window](https://developer.android.com/studio/intro) because they are marked as a library. This is intended as per [this IntelliJ Flutter plugin issue](https://github.com/flutter/flutter-intellij/issues/5596), but may be confusing or potentially limiting. You can delete the `.packages` file every time `flutter pub get` is run. This is optional. It is a deprecated autogenerated file which confuses Android Studio into thinking all files are build outputs instead of source files. +- Run `flutter doctor` to confirm your environment is setup correctly. + +## Editing and writing platform (Android/iOS) code + +- You can edit both example app and package code in the same IDE (Xcode, Android Studio or AppCode) window. +- **Android:** You can edit Android code by opening the example app with another Android Studio window. This example project shows all gradle modules in the repository, including the example app, the Ably Flutter package, but also all other packages installed in your `pubspec.yaml` which provide gradle modules. +- **iOS:** You can edit iOS code by opening the example app with [AppCode](https://www.jetbrains.com/objc/) or [Xcode](https://developer.apple.com/xcode/). The example project has access to the main plugin iOS code via symlinks. + - Recommended usage of Xcode and AppCode: + - Xcode: For modifying configuration (e.g. signing and capabilities, `Info.plist`) or other features not supported by AppCode + - AppCode: For writing code, refactoring or debugging. + +## Runing and debugging (Android/iOS) code + +- You cannot run packages independently, so Flutter package plugins include an example app to use the package. +- **Android:** In the Flutter project window, launch the application in debug mode in Android Studio. Then, in the Android project window, attach the debugger to the Android process. You can also do this with IntelliJ Idea. +- **iOS:** Both AppCode and Xcode can build and debug your flutter application. To use breakpoints, set them in AppCode or Xcode, but not Android Studio. **Warning:** `flutter run` will automatically run `pod install` in the `example/ios` folder, but if you don't use `flutter run` and use Xcode or AppCode, you may face Cocoapods errors if any dependencies are added or removed, and will need to run `pod install`. + +## Making changes + +We use Github actions to check code is formatted correctly. You should format your code using the same style to avoid Github CI job failures, since we do not automatically commit and format code. +- To format files, run `./format-files.sh`. It requires a few tools to be installed (e.g. `flutter`, `swiftformat` and `google-java-format`). Try to run `./format-files.sh` or read `format-files.sh` to discover and install the dependencies. +- To run this command successfully, you need to setup your environment. Read `format-files.sh` for more information. +- To automatically check formatting before committing, you can use the git hook shared in this repo. + - Run `git config core.hooksPath hooks` + - Git hooks don't work well in SourceTree, as per [SourceTree : Hook failing because paths don't seem to be set correctly](https://community.atlassian.com/t5/Bitbucket-questions/SourceTree-Hook-failing-because-paths-don-t-seem-to-be-set/qaq-p/274792). You will need to open SourceTree using `open /Applications/SourceTree.app/Contents/MacOS/SourceTree`. +- To autoformat Java code in Android Studio, follow the documentation: [IntelliJ, Android Studio, and other JetBrains IDEs](https://github.com/google/google-java-format#intellij-android-studio-and-other-jetbrains-ides). +## High level architecture -You may also find it insightful to run the following command, as it can reveal issues with your development environment: - - flutter doctor - -If using Android Studio, delete the `.packages` file. It is a deprecated autogenerated file which confuses Android Studio into thinking all files are build outputs instead of source files. +The code in this repository has been constructed to be +[built as a Flutter Plugin](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin). +It invokes methods in other Ably SDKs (Ably Android and Ably Cocoa) through `MethodChannels` and `EventChannels`. This means Android and iOS code provided in the plugin runs when the user's application is launched, even without them calling any methods in Ably directly. ## Implementation Notes @@ -26,7 +49,7 @@ There are 3 types of "refreshes" you might see in a Flutter app: - **Hot restart:** The Flutter application is restarted, but the host application (Android and iOS apps which host the Flutter application) does not. - State of the flutter application is reset. This means fields are all reset to their default values (or null). - This also means we must remember to clear the state in the host application when the app hot restarts. We do this by calling `await methodChannel.invokeMethod(PlatformMethod.resetAblyClients);`. - - From Flutter documentation: + - From [Flutter documentation](https://docs.flutter.dev/development/tools/hot-reload): > With a hot restart, the program starts from the beginning, executes the new version of main(), and builds a widget tree that displays the text Hello. - **App restart:** The entire application is restarted, clearing the state of both the Flutter application and the host application (Android and iOS apps). @@ -42,27 +65,48 @@ appropriate for programmer's failures on the Dart side of things. But time will ### Push Notifications -#### Push Notifications activation and deactivation +Read [Push Notification documentation](https://ably.com/documentation/general/push) for more information about what these features do. This document focuses on implementation. + +#### Device activation + +- **Android:** The result (success or error) are sent via Intents which users should register for at runtime. In the case of activation, users listen for an intent with the `io.ably.broadcast.PUSH_ACTIVATE` action. +- **iOS:** Errors are returned through ``ARTPushRegistererDelegate#didActivateAblyPush`` methods. However, in both SDKs, this error does not always return quickly. For example, if there was no internet connection, then `Push.activate()` will not throw an error, it will just block forever, because errors are not provided by the SDKs. Once an internet connection is made, the Intent will be sent and delegate methods will be called. + +Ably Flutter does this by passing a reference to the `FlutterResult` used to pass back the result to the Dart side, when the activation or deactivation completes. This makes it convenient for users: they can `await push.activate()`. However, users should not rely on this Future completing, in the case of network issues. + +#### Device deactivation + +- **Android:**: The implementation is similar to `activate`, but the result is provided via an intent with `io.ably.broadcast.PUSH_DEACTIVATE` action. +- **iOS:** The implementation is similar to `deactivate`, but the result is provided in the `ARTPushRegistererDelegate#didDeactivateAblyPush` method. -The platform SDKs ([ably-android](https://github.com/ably/ably-java) and [ably-cocoa](https://github.com/ably/ably-cocoa)) enable users to check if device activation, deactivation, or registration update fails. On Android, these errors are sent in Intents which users should register for at runtime. In Cocoa, errors are returned through `ARTPushRegistererDelegate` methods. However, in both SDKs, this error does not always return quickly. For example, if there was no internet connection, then `Push.activate()` will not throw an error, it will just block forever, because errors are not provided by the SDKs. Once an internet connection is made, the Intent will be sent and delegate methods will be called. +#### Device registration update -Ably Flutter does this by passing a reference to the `FlutterResult` used to pass back the result to the Dart side, when the activation or deactivation completes. This makes it convenient for users: they can `await push.activate()`. However, users should not rely on this Future completing, in the case of network issues. +The users application (and therefore Ably Flutter) may be provided with a new device token when one expires. The SDK would then update Ably servers with the device token. However, this might fail. Users will only be able be told if the update fails, but not if it succeeds. However, because this method is not invoked by the user, but instead the device token is provided by the OS method, the only way a user can find out about an error is if they listen to the `Push#onUpdateFailed` stream. #### Notification tap handling -Android's Firebase Messaging library enables users to select the Intent action used when the automatically-generated notification is tapped by the user. Users can do this by setting `fcm.notification.click_action` in Ably's push payload. However, for this to work, users would need to declare the intent action within their `AndroidManifest.xml`. Therefore, we don't really tell users they can modify `click_action` and configure it. However, they can do so if they wish. +Android's Firebase Messaging library enables users to select the Intent action used when the automatically-generated notification is tapped by the user. Users can do this by setting `fcm.notification.click_action` in Ably's push payload. However, for this to work, users would need to declare the intent action within their `AndroidManifest.xml`. Ably Flutter does not handle these click actions or implement any code to allow users to know which intent was launched on the Android side. Therefore, we don't really tell users they can modify `click_action` and configure it. However, users may attempt to do this by configuring their Android app. -#### Notifications generation for Foreground Apps +#### Displaying notifications in foregrounded apps -iOS enables users to show the notification received remotely even if the app is in the foreground, by calling a delegate method ([`userNotificationCenter(_:willPresent:withCompletionHandler:)`](https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter)) which the user can choose to show the message to the user, based on the notification content. FCM does not provide this functionality. Users can only configure this behaviour for iOS, by using `PushNotificationEvents#setOnShowNotificationInForeground`. +iOS enables users to show the notification received remotely even if the app is in the foreground, by calling a delegate method ([`userNotificationCenter(_:willPresent:withCompletionHandler:)`](https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter)) which the user can choose to show the message to the user, based on the notification content. Android / FCM does not provide this functionality. Users can only configure this behaviour for iOS, by using `PushNotificationEvents#setOnShowNotificationInForeground`. -#### Push Notifications Background Message Handling +#### Why Ably Flutter doesn't use or support [Firebase Messaging](https://pub.dev/packages/firebase_messaging) -Background processing in Flutter is a complicated subject that has not been explored publicly in detail. It involves creating Dart isolates manually (for Android), passing messages back and forth, etc. +- Firebase Messaging ignores any messages received on iOS devices which do not originate from FCM, and so will not allow users to handle push notifications on iOS sent from Ably. This rules out using Firebase Messaging on iOS. +- Because of point 1, we cannot even use the Firebase Messaging flutter package. This is because when a package is added as a dependency to `pubspec.yaml`, code is automatically run on the iOS and Android side regardless of any code being called in the Flutter application. This code includes method swizzling, and unfortunately seem to break any push notification handlers implemented by our package. +- Therefore, we needed a way to use Firebase Messaging without using it on iOS. This ruled out [`flutter_apns`](https://github.com/mwaylabs/flutter-apns) and [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) packages. +- Instead, Ably Flutter depends directly on the Firebase Messaging Android library. -Differences between Ably Flutter and Firebase Messaging implementation (Android only): +#### Background message handling + +It is much easier to handle push notifications on iOS than Android. This is because the user's Flutter application is launched automatically in `FlutterViewController` (by being defined in the `Main.storyboard`). This `ViewController` is launched even when the app is just processing a push notification when in the background. This is not the case on Android. The user's Flutter application is only launched when an activity is created, but activities are not created in response to a push notification being received. Therefore, we manually launch the users Flutter application in this case, to allow the user to handle the push notification with their a callback written in Dart in their Flutter application. + +A good starting point to understanding background execution of a Flutter application is explained in [Executing Dart in the Background with Flutter Plugins and Geofencing](https://medium.com/flutter/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124), or by reading the [implemnentation of `firebase_messaging`](https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging). An understanding of that article and android components will provide a solid understanding of the motivations of the architecture taken by Ably Flutter to handle push notifications. It is simpler whilst using up-to-date Android components, whilst also gives more flexibility to the user. + +Differences between Ably Flutter and [Firebase Messaging](https://pub.dev/packages/firebase_messaging) flutter package implementation (on Android only): - **Isolate code:** Firebase Messaging explicitly defines a callback which is launched in a custom isolate. Ably Flutter does not launch a custom dart entrypoint, but instead re-uses the user's default entrypoint (their Flutter application), by using `DartExecutor.DartEntrypoint.createDefault()`. Therefore, Ably Flutter provides the same environment for message handling on both Android and iOS: users application **is running** when we handle the message. -- **Resource consumption tradeoffs:** Firebase launches an isolate capable of only handling messages at app launch, even if users' application isn't handling remote messages. Firebase Messaging keeps this isolate running throughout the app, and have a queue process to queue messages by maintaining an Android Service. This allows 10 minutes of execution time, where as on iOS, Firebase Messaging only has 30 seconds of execution time. Instead, Ably Flutter launches a new isolate on every message if the application is not yet running and avoids creating a service and queueing work. A new message will spawn a new engine. +- **Resource consumption tradeoffs:** Firebase launches an isolate capable of only handling messages at app launch, even if users' application isn't handling remote messages. Firebase Messaging keeps this isolate running throughout the app, and have a queue process to queue messages by maintaining an Android Service. This allows 10 minutes of execution time, where as on iOS, Firebase Messaging only has 30 seconds of execution time. Instead, Ably Flutter launches a new isolate on every message if the application is not yet running and avoids creating a service and queueing work. A new message will spawn a new engine. It also uses a deprecated class, `JobIntentService`. - **Execution time:** Ably Flutter provides users with an execution time of 30 seconds on both Android and iOS to handle each message. On Android, Firebase messaging launches a Service which has [approximately 10 minutes](https://stackoverflow.com/questions/48630606/how-long-is-the-jobservice-execution-time-limit-mentioned-in-androids-jobinte) of execution time from it's launch to handle all messages received before Android stops the service. It's unclear if the Service will be automatically launched by iOS immediately, or if it will only be launched in the future. On iOS, each message has 30 seconds of execution time. This seems to be a bug in the design of firebase_messaging. Users can extend their execution time by using [package:workmanager](https://pub.dev/packages/workmanager). Because of this architectural simplicity, Ably Flutter does not need to use [`PluginUtilities`](https://stackoverflow.com/questions/69208164/what-does-pluginutilities-do-in-flutters-dartui-library-and-how-do-i-use/69208165#69208165), pass references of two methods between Dart and host platform, or save and load these methods in `SharedPreferences`. Ably Flutter avoids conflicts between the default `FlutterEngine` launched with a `FlutterActivity` and the one manually launched in the `BroadcastReceiver`, by using method channels with unique channel names. @@ -88,12 +132,6 @@ got both iOS and Android emulators open: flutter run -d all -## Debugging notes (Android Studio) - -To debug both platform and Dart code simultaneously: -- In Android: in the Flutter project window, launch the application in debug mode in Android Studio. Then, in the Android project window, attach the debugger to the Android process. -- In iOS: To debug iOS code, you must set breakpoints in Xcode. In Android Studio or command line, run the flutter run --dart-define` command you would usually run. This ensures when you build with Xcode, the environment variables are available to the app. Then, re-run the application using Xcode. Then, in Android Studio, click `Run` > `Flutter Attach`, or click the `Flutter Attach` button. - ## Testing changes in dependencies After making changes to `ably-java` or `ably-cocoa`, you can test changes without releasing those dependencies to users. To do this, you need a local copy of the repo with the changes you want to test. @@ -118,17 +156,40 @@ being a specialized package that includes platform-specific implementation code - Flutter [documentation](https://flutter.dev/docs), offering tutorials, samples, guidance on mobile development, and a full API reference. + +## Serialization + +Data is JSON-serialized back and forth from Dart to Android/iOS, where a dictionary/hashmap containing string constants as key (e.g. "name"), and the data as the value. Files containing constants (strings, integers) shared across Dart, Android and iOS are generated from the contents of `codegen_context.dart`. The output of this code generation is `platform_constants.dart`, +You'll notice 3 categories of constants: +- CodecTypes: These constants help identify which type has just been received from the Dart or platform so that we can deserialize it. +- PlatformMethod: Method invocations called from Dart to platform side, or vice-versa. +- JSON-serialization property names. These are strings used to identify values between Dart and platform. + - For the case of enums: e.g. an enum from Java is converted to a String and converted back into the correct enum, based on the string value. + - To send dictionaries/maps containing data, we need to identify each one. For example, to send a `ChannelOptions` type which contains `params`, `modes` and `cipherParams`, we would send a Map containing these 3 keys, and their values contain the data. The receiving side can immediately get the `modes` from the dictionary. -## Generating platform constants +In the future, this serialization code can be [significantly simplified using Pigeon](https://github.com/ably/ably-flutter/issues/28), but as we could also work on implementing the features in pure Dart SDK and avoid serialization altogether, this work has not yet been started. -Some files in the project are generated to maintain sync between - platform constants on both native and dart side. - Generated file paths are configured as values in `bin/codegen.dart` for `toGenerate` Map +In cases where instances cannot be fully serialized between Dart and the platform, we instead store these instances and pass a handle instead. For example, a Realtime client is stored in a Map on the Platform side (e.g. `AblyClientStore.java`), and a handle is passed to the Dart side. + +### Updating serialization -[Read about generation of platform specific constant files](bin/README.md) +You'll need to re-generate the platform constants whenever you: +- Change the serialization: + - A new codec type - required when a new top level serializable object is required (ex: `ErrorInfo` and `ClientOptions`) + - Platform and event names - when implementing a new method in `MethodChannel` or new event in `EventChannel` + - A new type needs to be serialized (either top-level, or nested) +- Change the templates: To change the structure of files being generated, you can modify the templates: + - `lib/src/generated/platformconstants.dart` for use in Flutter/Dart + - `android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java` for use in Android/Java + - `ios/Classes/codec/AblyPlatformConstants.h` for use in iOS/Objective-C + - `ios/Classes/codec/AblyPlatformConstants.m` for use in iOS/Objective-C -## Implementing new codec types +- To add or modify existing platform constants, modify: `codegen_context.dart` +- Then, to regenerate the platform constants using the latest "context", run `cd bin` and `dart codegen.dart` +- Once you have updated these constants, you have to now use these in the implementation. + +### Implementing new codec types 1. Add new type along with value in `_types` list at [bin/codegen_context.dart](bin/codegen_context.dart) 2. Add an object definition with object name and its properties to `objects` list at [bin/codegen_context.dart](bin/codegen_context.dart) @@ -147,7 +208,7 @@ Generate platform constants and continue 8. add new codec encoder method in [ios.classes.codec.AblyFlutterReader](ios/Classes/codec/AblyFlutterReader.m) and update `getDecoder` so that new codec decoder is called -## Implementing new platform methods +### Implementing new platform methods 1. Add new method name in `_platformMethods` list at [bin/codegen_context.dart](bin/codegen_context.dart) @@ -158,14 +219,13 @@ Generate platform constants and use wherever required The new flutter analyzer does a great job at analyzing complete flutter package. Running `flutter analyze` in project root will analyze dart files in complete project, - i.e., plugin code and example code +i.e., plugin code and example code Or, use the good old dart analyzer ```bash dartanalyzer --fatal-warnings lib/**/*.dart - dartanalyzer --fatal-warnings example/lib/**/*.dart ``` diff --git a/PushNotifications.md b/PushNotifications.md index bf2be484a..f3ed1ab0f 100644 --- a/PushNotifications.md +++ b/PushNotifications.md @@ -358,9 +358,11 @@ Take a look at the example app platform specific code to handle messages. For iO **iOS:** Some iOS dependencies perform techniques such as [method swizzling](https://nshipster.com/method-swizzling/) which break other iOS dependencies and Flutter Plugin packages. -### Deactivating the device +## Device deactivation -Do this only if you do not want the device to receive push notifications at all. You usually do not need to run this at all. +Do this only if you do not want the device to receive push notifications at all. You usually do not need to run this at all, and especially don't need to deactivate and re-activate on every launch. + +Like device activation, the `Future` returned by `activate` is not guaranteed to complete quickly. ```dart try { 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 c73a318c4..fd72cce0c 100644 --- a/android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java +++ b/android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java @@ -32,6 +32,7 @@ import io.ably.lib.types.MessageExtras; import io.ably.lib.types.Param; import io.ably.lib.types.PresenceMessage; +import io.ably.lib.types.Stats; import io.ably.lib.util.Crypto; import io.ably.lib.util.Log; import io.flutter.plugin.common.StandardMessageCodec; @@ -121,6 +122,7 @@ public AblyMessageCodec(CipherParamsStorage cipherParamsStorage) { put( PlatformConstants.CodecTypes.realtimeHistoryParams, new CodecPair<>(null, self::decodeRealtimeHistoryParams)); + put(PlatformConstants.CodecTypes.stats, new CodecPair<>(self::encodeStats, null)); put( PlatformConstants.CodecTypes.restPresenceParams, new CodecPair<>(null, self::decodeRestPresenceParams)); @@ -168,6 +170,125 @@ public AblyMessageCodec(CipherParamsStorage cipherParamsStorage) { }; } + private Map encodeStats(Stats stats) { + if (stats == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxStats.all, encodeStatsMessageTypes(stats.all)); + writeValueToJson( + jsonMap, PlatformConstants.TxStats.apiRequests, encodeStatsRequestCount(stats.apiRequests)); + writeValueToJson( + jsonMap, PlatformConstants.TxStats.channels, encodeStatsResourceCount(stats.channels)); + writeValueToJson( + jsonMap, + PlatformConstants.TxStats.connections, + encodeStatsConnectionTypes(stats.connections)); + writeValueToJson( + jsonMap, PlatformConstants.TxStats.inbound, encodeStatsMessageTraffic(stats.inbound)); + writeValueToJson(jsonMap, PlatformConstants.TxStats.intervalId, stats.intervalId); + writeValueToJson( + jsonMap, PlatformConstants.TxStats.outbound, encodeStatsMessageTraffic(stats.outbound)); + writeValueToJson( + jsonMap, PlatformConstants.TxStats.persisted, encodeStatsMessageTypes(stats.persisted)); + writeValueToJson( + jsonMap, + PlatformConstants.TxStats.tokenRequests, + encodeStatsRequestCount(stats.tokenRequests)); + return jsonMap; + } + + private Map encodeStatsMessageTraffic(Stats.MessageTraffic messageTraffic) { + if (messageTraffic == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson( + jsonMap, + PlatformConstants.TxStatsMessageTraffic.all, + encodeStatsMessageTypes(messageTraffic.all)); + writeValueToJson( + jsonMap, + PlatformConstants.TxStatsMessageTraffic.realtime, + encodeStatsMessageTypes(messageTraffic.realtime)); + writeValueToJson( + jsonMap, + PlatformConstants.TxStatsMessageTraffic.rest, + encodeStatsMessageTypes(messageTraffic.rest)); + writeValueToJson( + jsonMap, + PlatformConstants.TxStatsMessageTraffic.webhook, + encodeStatsMessageTypes(messageTraffic.webhook)); + return jsonMap; + } + + private Map encodeStatsConnectionTypes(Stats.ConnectionTypes connectionTypes) { + if (connectionTypes == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson( + jsonMap, + PlatformConstants.TxStatsConnectionTypes.all, + encodeStatsResourceCount(connectionTypes.all)); + writeValueToJson( + jsonMap, + PlatformConstants.TxStatsConnectionTypes.plain, + encodeStatsResourceCount(connectionTypes.plain)); + writeValueToJson( + jsonMap, + PlatformConstants.TxStatsConnectionTypes.tls, + encodeStatsResourceCount(connectionTypes.tls)); + return jsonMap; + } + + private Map encodeStatsResourceCount(Stats.ResourceCount resourceCount) { + if (resourceCount == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxStatsResourceCount.mean, resourceCount.mean); + writeValueToJson(jsonMap, PlatformConstants.TxStatsResourceCount.min, resourceCount.min); + writeValueToJson(jsonMap, PlatformConstants.TxStatsResourceCount.opened, resourceCount.opened); + writeValueToJson(jsonMap, PlatformConstants.TxStatsResourceCount.peak, resourceCount.peak); + writeValueToJson( + jsonMap, PlatformConstants.TxStatsResourceCount.refused, resourceCount.refused); + return jsonMap; + } + + private Map encodeStatsRequestCount(Stats.RequestCount apiRequests) { + if (apiRequests == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson( + jsonMap, PlatformConstants.TxStatsRequestCount.succeeded, apiRequests.succeeded); + writeValueToJson(jsonMap, PlatformConstants.TxStatsRequestCount.failed, apiRequests.failed); + writeValueToJson(jsonMap, PlatformConstants.TxStatsRequestCount.refused, apiRequests.refused); + return jsonMap; + } + + private Map encodeStatsMessageTypes(Stats.MessageTypes messageTypes) { + if (messageTypes == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson( + jsonMap, + PlatformConstants.TxStatsMessageTypes.all, + encodeMessageCategory(messageTypes.all)); + writeValueToJson( + jsonMap, + PlatformConstants.TxStatsMessageTypes.messages, + encodeMessageCategory(messageTypes.messages)); + writeValueToJson( + jsonMap, + PlatformConstants.TxStatsMessageTypes.presence, + encodeMessageCategory(messageTypes.presence)); + return jsonMap; + } + + // This is different in other platform and will correspond to StatsMessageCount for other + // libraries + private Map encodeMessageCategory(Stats.MessageCategory category) { + if (category == null) return null; + if (category.category == null) return null; + final HashMap jsonMap = new HashMap<>(); + + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageCount.count, category.count); + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageCount.data, category.data); + + return jsonMap; + } + @Override protected Object readValueOfType(final byte type, final ByteBuffer buffer) { CodecPair pair = codecMap.get(type); 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 efa02535b..c00f12461 100644 --- a/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java +++ b/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java @@ -3,12 +3,9 @@ import android.content.Context; import android.os.Handler; import android.os.Looper; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.firebase.messaging.RemoteMessage; - import io.ably.flutter.plugin.generated.PlatformConstants; import io.ably.flutter.plugin.push.PushActivationEventHandlers; import io.ably.flutter.plugin.types.PlatformClientOptions; @@ -33,7 +30,6 @@ import io.ably.lib.util.Log; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; - import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; @@ -42,943 +38,964 @@ import java.util.concurrent.CountDownLatch; public class AblyMethodCallHandler implements MethodChannel.MethodCallHandler { - private static final String TAG = AblyMethodCallHandler.class.getName(); - private final Context applicationContext; - private final MethodChannel methodChannel; - private final StreamsChannel streamsChannel; - private final Map> _map; - private final AblyInstanceStore instanceStore; - @Nullable - private RemoteMessage remoteMessageFromUserTapLaunchesApp; - - public AblyMethodCallHandler( - final MethodChannel methodChannel, - final StreamsChannel streamsChannel, - final Context applicationContext) { - this.methodChannel = methodChannel; - this.streamsChannel = streamsChannel; - this.applicationContext = applicationContext; - this.instanceStore = AblyInstanceStore.getInstance(); - _map = new HashMap<>(); - _map.put(PlatformConstants.PlatformMethod.getPlatformVersion, this::getPlatformVersion); - _map.put(PlatformConstants.PlatformMethod.getVersion, this::getVersion); - _map.put(PlatformConstants.PlatformMethod.resetAblyClients, this::resetAblyClients); - - // Rest - _map.put(PlatformConstants.PlatformMethod.createRest, this::createRest); - _map.put(PlatformConstants.PlatformMethod.setRestChannelOptions, this::setRestChannelOptions); - _map.put(PlatformConstants.PlatformMethod.publish, this::publishRestMessage); - _map.put(PlatformConstants.PlatformMethod.restHistory, this::getRestHistory); - _map.put(PlatformConstants.PlatformMethod.restPresenceGet, this::getRestPresence); - _map.put(PlatformConstants.PlatformMethod.restPresenceHistory, this::getRestPresenceHistory); - _map.put(PlatformConstants.PlatformMethod.releaseRestChannel, this::releaseRestChannel); - - // Realtime - _map.put(PlatformConstants.PlatformMethod.createRealtime, this::createRealtime); - _map.put(PlatformConstants.PlatformMethod.connectRealtime, this::connectRealtime); - _map.put(PlatformConstants.PlatformMethod.closeRealtime, this::closeRealtime); - _map.put(PlatformConstants.PlatformMethod.attachRealtimeChannel, this::attachRealtimeChannel); - _map.put(PlatformConstants.PlatformMethod.detachRealtimeChannel, this::detachRealtimeChannel); - _map.put( - PlatformConstants.PlatformMethod.setRealtimeChannelOptions, - this::setRealtimeChannelOptions); - _map.put( - PlatformConstants.PlatformMethod.publishRealtimeChannelMessage, - this::publishRealtimeChannelMessage); - _map.put(PlatformConstants.PlatformMethod.realtimeHistory, this::getRealtimeHistory); - _map.put(PlatformConstants.PlatformMethod.realtimePresenceGet, this::getRealtimePresence); - _map.put( - PlatformConstants.PlatformMethod.realtimePresenceHistory, this::getRealtimePresenceHistory); - _map.put(PlatformConstants.PlatformMethod.realtimePresenceEnter, this::enterRealtimePresence); - _map.put(PlatformConstants.PlatformMethod.realtimePresenceUpdate, this::updateRealtimePresence); - _map.put(PlatformConstants.PlatformMethod.realtimePresenceLeave, this::leaveRealtimePresence); - _map.put(PlatformConstants.PlatformMethod.releaseRealtimeChannel, this::releaseRealtimeChannel); - _map.put(PlatformConstants.PlatformMethod.realtimeTime, this::realtimeTime); - _map.put(PlatformConstants.PlatformMethod.restTime, this::restTime); - - // Push Notifications - _map.put(PlatformConstants.PlatformMethod.pushActivate, this::pushActivate); - _map.put(PlatformConstants.PlatformMethod.pushDeactivate, this::pushDeactivate); - _map.put(PlatformConstants.PlatformMethod.pushSubscribeDevice, this::pushSubscribeDevice); - _map.put(PlatformConstants.PlatformMethod.pushUnsubscribeDevice, this::pushUnsubscribeDevice); - _map.put(PlatformConstants.PlatformMethod.pushSubscribeClient, this::pushSubscribeClient); - _map.put(PlatformConstants.PlatformMethod.pushUnsubscribeClient, this::pushUnsubscribeClient); - _map.put(PlatformConstants.PlatformMethod.pushListSubscriptions, this::pushListSubscriptions); - _map.put(PlatformConstants.PlatformMethod.pushDevice, this::pushDevice); - _map.put( - PlatformConstants.PlatformMethod.pushNotificationTapLaunchedAppFromTerminated, - this::pushNotificationTapLaunchedAppFromTerminated); - - // paginated results - _map.put(PlatformConstants.PlatformMethod.nextPage, this::getNextPage); - _map.put(PlatformConstants.PlatformMethod.firstPage, this::getFirstPage); - - // Encryption - _map.put(PlatformConstants.PlatformMethod.cryptoGetParams, this::cryptoGetParams); - _map.put( - PlatformConstants.PlatformMethod.cryptoGenerateRandomKey, this::cryptoGenerateRandomKey); - } - - // MethodChannel.Result wrapper that responds on the platform thread. - // - // Plugins crash with "Methods marked with @UiThread must be executed on the main thread." - // This happens while making network calls in thread other than main thread - // - // https://github.com/flutter/flutter/issues/34993#issue-459900986 - // https://github.com/aloisdeniel/flutter_geocoder/commit/bc34cfe473bfd1934fe098bb7053248b75200241 - private static class MethodResultWrapper implements MethodChannel.Result { - private final MethodChannel.Result methodResult; - private final Handler handler; - - MethodResultWrapper(MethodChannel.Result result) { - methodResult = result; - handler = new Handler(Looper.getMainLooper()); - } - - @Override - public void success(final Object result) { - handler.post(() -> methodResult.success(result)); - } - - @Override - public void error( - final String errorCode, final String errorMessage, final Object errorDetails) { - handler.post(() -> methodResult.error(errorCode, errorMessage, errorDetails)); - } - - @Override - public void notImplemented() { - handler.post(methodResult::notImplemented); - } - } - - private void handleAblyException(@NonNull MethodChannel.Result result, @NonNull AblyException e) { - result.error(Integer.toString(e.errorInfo.code), e.getMessage(), e.errorInfo); + private static final String TAG = AblyMethodCallHandler.class.getName(); + private final Context applicationContext; + private final MethodChannel methodChannel; + private final StreamsChannel streamsChannel; + private final Map> _map; + private final AblyInstanceStore instanceStore; + @Nullable private RemoteMessage remoteMessageFromUserTapLaunchesApp; + + public AblyMethodCallHandler( + final MethodChannel methodChannel, + final StreamsChannel streamsChannel, + final Context applicationContext) { + this.methodChannel = methodChannel; + this.streamsChannel = streamsChannel; + this.applicationContext = applicationContext; + this.instanceStore = AblyInstanceStore.getInstance(); + _map = new HashMap<>(); + _map.put(PlatformConstants.PlatformMethod.getPlatformVersion, this::getPlatformVersion); + _map.put(PlatformConstants.PlatformMethod.getVersion, this::getVersion); + _map.put(PlatformConstants.PlatformMethod.resetAblyClients, this::resetAblyClients); + + // Rest + _map.put(PlatformConstants.PlatformMethod.createRest, this::createRest); + _map.put(PlatformConstants.PlatformMethod.setRestChannelOptions, this::setRestChannelOptions); + _map.put(PlatformConstants.PlatformMethod.publish, this::publishRestMessage); + _map.put(PlatformConstants.PlatformMethod.restHistory, this::getRestHistory); + _map.put(PlatformConstants.PlatformMethod.restPresenceGet, this::getRestPresence); + _map.put(PlatformConstants.PlatformMethod.restPresenceHistory, this::getRestPresenceHistory); + _map.put(PlatformConstants.PlatformMethod.releaseRestChannel, this::releaseRestChannel); + + // Realtime + _map.put(PlatformConstants.PlatformMethod.createRealtime, this::createRealtime); + _map.put(PlatformConstants.PlatformMethod.connectRealtime, this::connectRealtime); + _map.put(PlatformConstants.PlatformMethod.closeRealtime, this::closeRealtime); + _map.put(PlatformConstants.PlatformMethod.attachRealtimeChannel, this::attachRealtimeChannel); + _map.put(PlatformConstants.PlatformMethod.detachRealtimeChannel, this::detachRealtimeChannel); + _map.put( + PlatformConstants.PlatformMethod.setRealtimeChannelOptions, + this::setRealtimeChannelOptions); + _map.put( + PlatformConstants.PlatformMethod.publishRealtimeChannelMessage, + this::publishRealtimeChannelMessage); + _map.put(PlatformConstants.PlatformMethod.realtimeHistory, this::getRealtimeHistory); + _map.put(PlatformConstants.PlatformMethod.realtimePresenceGet, this::getRealtimePresence); + _map.put( + PlatformConstants.PlatformMethod.realtimePresenceHistory, this::getRealtimePresenceHistory); + _map.put(PlatformConstants.PlatformMethod.realtimePresenceEnter, this::enterRealtimePresence); + _map.put(PlatformConstants.PlatformMethod.realtimePresenceUpdate, this::updateRealtimePresence); + _map.put(PlatformConstants.PlatformMethod.realtimePresenceLeave, this::leaveRealtimePresence); + _map.put(PlatformConstants.PlatformMethod.releaseRealtimeChannel, this::releaseRealtimeChannel); + _map.put(PlatformConstants.PlatformMethod.realtimeTime, this::realtimeTime); + _map.put(PlatformConstants.PlatformMethod.restTime, this::restTime); + _map.put(PlatformConstants.PlatformMethod.stats, this::stats); + + // Push Notifications + _map.put(PlatformConstants.PlatformMethod.pushActivate, this::pushActivate); + _map.put(PlatformConstants.PlatformMethod.pushDeactivate, this::pushDeactivate); + _map.put(PlatformConstants.PlatformMethod.pushSubscribeDevice, this::pushSubscribeDevice); + _map.put(PlatformConstants.PlatformMethod.pushUnsubscribeDevice, this::pushUnsubscribeDevice); + _map.put(PlatformConstants.PlatformMethod.pushSubscribeClient, this::pushSubscribeClient); + _map.put(PlatformConstants.PlatformMethod.pushUnsubscribeClient, this::pushUnsubscribeClient); + _map.put(PlatformConstants.PlatformMethod.pushListSubscriptions, this::pushListSubscriptions); + _map.put(PlatformConstants.PlatformMethod.pushDevice, this::pushDevice); + _map.put( + PlatformConstants.PlatformMethod.pushNotificationTapLaunchedAppFromTerminated, + this::pushNotificationTapLaunchedAppFromTerminated); + + // paginated results + _map.put(PlatformConstants.PlatformMethod.nextPage, this::getNextPage); + _map.put(PlatformConstants.PlatformMethod.firstPage, this::getFirstPage); + + // Encryption + _map.put(PlatformConstants.PlatformMethod.cryptoGetParams, this::cryptoGetParams); + _map.put( + PlatformConstants.PlatformMethod.cryptoGenerateRandomKey, this::cryptoGenerateRandomKey); + } + + // MethodChannel.Result wrapper that responds on the platform thread. + // + // Plugins crash with "Methods marked with @UiThread must be executed on the main thread." + // This happens while making network calls in thread other than main thread + // + // https://github.com/flutter/flutter/issues/34993#issue-459900986 + // https://github.com/aloisdeniel/flutter_geocoder/commit/bc34cfe473bfd1934fe098bb7053248b75200241 + private static class MethodResultWrapper implements MethodChannel.Result { + private final MethodChannel.Result methodResult; + private final Handler handler; + + MethodResultWrapper(MethodChannel.Result result) { + methodResult = result; + handler = new Handler(Looper.getMainLooper()); } @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result rawResult) { - final MethodChannel.Result result = new MethodResultWrapper(rawResult); - Log.v( - TAG, - String.format("onMethodCall: Ably Flutter platform method \"%s\" invoked.", call.method)); - final BiConsumer handler = _map.get(call.method); - if (null == handler) { - // We don't have a handler for a method with this name so tell the caller. - result.notImplemented(); - } else { - // We have a handler for a method with this name so delegate to it. - handler.accept(call, result); - } + public void success(final Object result) { + handler.post(() -> methodResult.success(result)); } - private CompletionListener handleCompletionWithListener(@NonNull MethodChannel.Result result) { - return new CompletionListener() { - @Override - public void onSuccess() { - result.success(null); - } - - @Override - public void onError(ErrorInfo reason) { - handleAblyException(result, AblyException.fromErrorInfo(reason)); - } - }; + @Override + public void error( + final String errorCode, final String errorMessage, final Object errorDetails) { + handler.post(() -> methodResult.error(errorCode, errorMessage, errorDetails)); } - private void resetAblyClients(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - streamsChannel.reset(); - instanceStore.reset(); + @Override + public void notImplemented() { + handler.post(methodResult::notImplemented); + } + } + + private void handleAblyException(@NonNull MethodChannel.Result result, @NonNull AblyException e) { + result.error(Integer.toString(e.errorInfo.code), e.getMessage(), e.errorInfo); + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result rawResult) { + final MethodChannel.Result result = new MethodResultWrapper(rawResult); + Log.v( + TAG, + String.format("onMethodCall: Ably Flutter platform method \"%s\" invoked.", call.method)); + final BiConsumer handler = _map.get(call.method); + if (null == handler) { + // We don't have a handler for a method with this name so tell the caller. + result.notImplemented(); + } else { + // We have a handler for a method with this name so delegate to it. + handler.accept(call, result); + } + } + + private CompletionListener handleCompletionWithListener(@NonNull MethodChannel.Result result) { + return new CompletionListener() { + @Override + public void onSuccess() { result.success(null); - } - - private void createRest(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = - (AblyFlutterMessage) call.arguments; - this.ablyDo( - message, - (ablyLibrary, clientOptions) -> { - try { - final long handle = ablyLibrary.getHandleForNextClient(); - if (clientOptions.hasAuthCallback) { - clientOptions.options.authCallback = - (Auth.TokenParams params) -> { - final Object[] token = {null}; - final CountDownLatch latch = new CountDownLatch(1); - new Handler(Looper.getMainLooper()) - .post( - () -> { - AblyFlutterMessage channelMessage = - new AblyFlutterMessage<>(params, handle); - methodChannel.invokeMethod( - PlatformConstants.PlatformMethod.authCallback, - channelMessage, - new MethodChannel.Result() { - @Override - public void success(@Nullable Object result) { - token[0] = result; - latch.countDown(); - } - - @Override - public void error( - String errorCode, - @Nullable String errorMessage, - @Nullable Object errorDetails) { - Log.w( - TAG, - String.format( - "\"%s\" platform method received error from Dart" - + " side: %s", - PlatformConstants.PlatformMethod.authCallback, - errorMessage)); - latch.countDown(); - } - - @Override - public void notImplemented() { - Log.w( - TAG, - String.format( - "\"%s\" platform method not implemented on Dart side:" - + " %s", - PlatformConstants.PlatformMethod.authCallback)); - latch.countDown(); - } - }); - }); - - try { - latch.await(); - } catch (InterruptedException e) { - throw AblyException.fromErrorInfo( - e, - new ErrorInfo( - "Exception while waiting for authCallback to return", 400, 40000)); - } - - return token[0]; - }; - } - result.success(ablyLibrary.createRest(clientOptions.options, applicationContext)); - } catch (final AblyException e) { - handleAblyException(result, e); - } - }); - } + } + + @Override + public void onError(ErrorInfo reason) { + handleAblyException(result, AblyException.fromErrorInfo(reason)); + } + }; + } + + private void resetAblyClients(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + streamsChannel.reset(); + instanceStore.reset(); + result.success(null); + } + + private void createRest(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = + (AblyFlutterMessage) call.arguments; + this.ablyDo( + message, + (ablyLibrary, clientOptions) -> { + try { + final long handle = ablyLibrary.getHandleForNextClient(); + if (clientOptions.hasAuthCallback) { + clientOptions.options.authCallback = + (Auth.TokenParams params) -> { + final Object[] token = {null}; + final CountDownLatch latch = new CountDownLatch(1); + new Handler(Looper.getMainLooper()) + .post( + () -> { + AblyFlutterMessage channelMessage = + new AblyFlutterMessage<>(params, handle); + methodChannel.invokeMethod( + PlatformConstants.PlatformMethod.authCallback, + channelMessage, + new MethodChannel.Result() { + @Override + public void success(@Nullable Object result) { + token[0] = result; + latch.countDown(); + } + + @Override + public void error( + String errorCode, + @Nullable String errorMessage, + @Nullable Object errorDetails) { + Log.w( + TAG, + String.format( + "\"%s\" platform method received error from Dart" + + " side: %s", + PlatformConstants.PlatformMethod.authCallback, + errorMessage)); + latch.countDown(); + } + + @Override + public void notImplemented() { + Log.w( + TAG, + String.format( + "\"%s\" platform method not implemented on Dart side:" + + " %s", + PlatformConstants.PlatformMethod.authCallback)); + latch.countDown(); + } + }); + }); - private void setRestChannelOptions( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - // setOptions is not supported on a rest instance directly - // Track @ https://github.com/ably/ably-flutter/issues/14 - // An alternative is to use the side effect of get channel - // with options which updates passed channel options. - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - final ChannelOptions channelOptions = - (ChannelOptions) map.get(PlatformConstants.TxTransportKeys.options); try { - ablyLibrary.getRest(messageData.handle).channels.get(channelName, channelOptions); - result.success(null); - } catch (AblyException ae) { - handleAblyException(result, ae); + latch.await(); + } catch (InterruptedException e) { + throw AblyException.fromErrorInfo( + e, + new ErrorInfo( + "Exception while waiting for authCallback to return", 400, 40000)); } - }); - } - private void publishRestMessage(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - final ArrayList channelMessages = - (ArrayList) map.get(PlatformConstants.TxTransportKeys.messages); - if (channelMessages == null) { - result.error("Messages cannot be null", null, null); - return; - } - Message[] messages = new Message[channelMessages.size()]; - messages = channelMessages.toArray(messages); + return token[0]; + }; + } + result.success(ablyLibrary.createRest(clientOptions.options, applicationContext)); + } catch (final AblyException e) { + handleAblyException(result, e); + } + }); + } + + private void setRestChannelOptions( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + // setOptions is not supported on a rest instance directly + // Track @ https://github.com/ably/ably-flutter/issues/14 + // An alternative is to use the side effect of get channel + // with options which updates passed channel options. + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + final ChannelOptions channelOptions = + (ChannelOptions) map.get(PlatformConstants.TxTransportKeys.options); + try { + ablyLibrary.getRest(messageData.handle).channels.get(channelName, channelOptions); + result.success(null); + } catch (AblyException ae) { + handleAblyException(result, ae); + } + }); + } + + private void publishRestMessage(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + final ArrayList channelMessages = + (ArrayList) map.get(PlatformConstants.TxTransportKeys.messages); + if (channelMessages == null) { + result.error("Messages cannot be null", null, null); + return; + } + Message[] messages = new Message[channelMessages.size()]; + messages = channelMessages.toArray(messages); + ablyLibrary + .getRest(messageData.handle) + .channels + .get(channelName) + .publishAsync(messages, handleCompletionWithListener(result)); + }); + } + + private void releaseRestChannel(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>ablyDo( + message, + (ablyLibrary, messageData) -> { + final String channelName = messageData.message; + ablyLibrary.getRest(messageData.handle).channels.release(channelName); + result.success(null); + }); + } + + private Callback> paginatedResponseHandler( + @NonNull MethodChannel.Result result, Integer handle) { + return new Callback>() { + @Override + public void onSuccess(AsyncPaginatedResult paginatedResult) { + long paginatedResultHandle = instanceStore.setPaginatedResult(paginatedResult, handle); + result.success(new AblyFlutterMessage<>(paginatedResult, paginatedResultHandle)); + } + + @Override + public void onError(ErrorInfo reason) { + handleAblyException(result, AblyException.fromErrorInfo(reason)); + } + }; + } + + private void getRestHistory(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); + if (params == null) { + params = new Param[0]; + } + ablyLibrary + .getRest(messageData.handle) + .channels + .get(channelName) + .historyAsync(params, this.paginatedResponseHandler(result, null)); + }); + } + + private void stats(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + final AblyFlutterMessage> paramsMessage = + (AblyFlutterMessage>) message.message; + final Map map = paramsMessage.message; + final HashMap paramsMap = + (HashMap) map.get(PlatformConstants.TxTransportKeys.params); + Param[] params = new Param[paramsMap != null ? paramsMap.size() : 0]; + if (paramsMap != null) { + int i = 0; + for (String paramKey : paramsMap.keySet()) { + final Param param = new Param(paramKey, paramsMap.get(paramKey)); + params[i++] = param; + } + } + instanceStore + .getRest(paramsMessage.handle) + .statsAsync(params, this.paginatedResponseHandler(result, null)); + } + + private void getRestPresence(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); + if (params == null) { + params = new Param[0]; + } + ablyLibrary + .getRest(messageData.handle) + .channels + .get(channelName) + .presence + .getAsync(params, this.paginatedResponseHandler(result, null)); + }); + } + + private void getRestPresenceHistory( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); + if (params == null) { + params = new Param[0]; + } + ablyLibrary + .getRest(messageData.handle) + .channels + .get(channelName) + .presence + .historyAsync(params, this.paginatedResponseHandler(result, null)); + }); + } + + private void getRealtimePresence(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); + if (params == null) { + params = new Param[0]; + } + try { + result.success( + Arrays.asList( ablyLibrary - .getRest(messageData.handle) - .channels - .get(channelName) - .publishAsync(messages, handleCompletionWithListener(result)); - }); - } - - private void releaseRestChannel(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>ablyDo( - message, - (ablyLibrary, messageData) -> { - final String channelName = messageData.message; - ablyLibrary.getRest(messageData.handle).channels.release(channelName); - result.success(null); - }); - } - - private Callback> paginatedResponseHandler( - @NonNull MethodChannel.Result result, Integer handle) { - return new Callback>() { - @Override - public void onSuccess(AsyncPaginatedResult paginatedResult) { - long paginatedResultHandle = instanceStore.setPaginatedResult(paginatedResult, handle); - result.success(new AblyFlutterMessage<>(paginatedResult, paginatedResultHandle)); + .getRealtime(messageData.handle) + .channels + .get(channelName) + .presence + .get(params))); + } catch (AblyException e) { + handleAblyException(result, e); + } + }); + } + + private void getRealtimePresenceHistory( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); + if (params == null) { + params = new Param[0]; + } + ablyLibrary + .getRealtime(messageData.handle) + .channels + .get(channelName) + .presence + .historyAsync(params, this.paginatedResponseHandler(result, null)); + }); + } + + private void enterRealtimePresence( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + final String clientId = (String) map.get(PlatformConstants.TxTransportKeys.clientId); + final Object data = map.get(PlatformConstants.TxTransportKeys.data); + final Presence presence = + ablyLibrary.getRealtime(messageData.handle).channels.get(channelName).presence; + try { + presence.enterClient(clientId, data, handleCompletionWithListener(result)); + } catch (AblyException e) { + handleAblyException(result, e); + } + }); + } + + private void updateRealtimePresence( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + final String clientId = (String) map.get(PlatformConstants.TxTransportKeys.clientId); + final Object data = map.get(PlatformConstants.TxTransportKeys.data); + final Presence presence = + ablyLibrary.getRealtime(messageData.handle).channels.get(channelName).presence; + try { + if (clientId != null) { + presence.updateClient(clientId, data, handleCompletionWithListener(result)); + } else { + presence.update(data, handleCompletionWithListener(result)); } - - @Override - public void onError(ErrorInfo reason) { - handleAblyException(result, AblyException.fromErrorInfo(reason)); + } catch (AblyException e) { + handleAblyException(result, e); + } + }); + } + + private void leaveRealtimePresence( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + final String clientId = (String) map.get(PlatformConstants.TxTransportKeys.clientId); + final Object data = map.get(PlatformConstants.TxTransportKeys.data); + final Presence presence = + ablyLibrary.getRealtime(messageData.handle).channels.get(channelName).presence; + try { + if (clientId != null) { + presence.leaveClient(clientId, data, handleCompletionWithListener(result)); + } else { + presence.leave(data, handleCompletionWithListener(result)); } - }; - } - - private void getRestHistory(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); - if (params == null) { - params = new Param[0]; - } - ablyLibrary - .getRest(messageData.handle) - .channels - .get(channelName) - .historyAsync(params, this.paginatedResponseHandler(result, null)); - }); - } - - private void getRestPresence(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); - if (params == null) { - params = new Param[0]; - } - ablyLibrary - .getRest(messageData.handle) - .channels - .get(channelName) - .presence - .getAsync(params, this.paginatedResponseHandler(result, null)); - }); - } - - private void getRestPresenceHistory( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); - if (params == null) { - params = new Param[0]; - } - ablyLibrary - .getRest(messageData.handle) - .channels - .get(channelName) - .presence - .historyAsync(params, this.paginatedResponseHandler(result, null)); - }); - } - - private void getRealtimePresence(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); - if (params == null) { - params = new Param[0]; - } - try { - result.success( - Arrays.asList( - ablyLibrary - .getRealtime(messageData.handle) - .channels - .get(channelName) - .presence - .get(params))); - } catch (AblyException e) { - handleAblyException(result, e); - } - }); - } - - private void getRealtimePresenceHistory( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); - if (params == null) { - params = new Param[0]; - } - ablyLibrary - .getRealtime(messageData.handle) - .channels - .get(channelName) - .presence - .historyAsync(params, this.paginatedResponseHandler(result, null)); - }); - } - - private void enterRealtimePresence( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - final String clientId = (String) map.get(PlatformConstants.TxTransportKeys.clientId); - final Object data = map.get(PlatformConstants.TxTransportKeys.data); - final Presence presence = - ablyLibrary.getRealtime(messageData.handle).channels.get(channelName).presence; - try { - presence.enterClient(clientId, data, handleCompletionWithListener(result)); - } catch (AblyException e) { - handleAblyException(result, e); - } - }); - } - - private void updateRealtimePresence( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - final String clientId = (String) map.get(PlatformConstants.TxTransportKeys.clientId); - final Object data = map.get(PlatformConstants.TxTransportKeys.data); - final Presence presence = - ablyLibrary.getRealtime(messageData.handle).channels.get(channelName).presence; - try { - if (clientId != null) { - presence.updateClient(clientId, data, handleCompletionWithListener(result)); - } else { - presence.update(data, handleCompletionWithListener(result)); - } - } catch (AblyException e) { - handleAblyException(result, e); - } - }); - } - - private void leaveRealtimePresence( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - final String clientId = (String) map.get(PlatformConstants.TxTransportKeys.clientId); - final Object data = map.get(PlatformConstants.TxTransportKeys.data); - final Presence presence = - ablyLibrary.getRealtime(messageData.handle).channels.get(channelName).presence; - try { - if (clientId != null) { - presence.leaveClient(clientId, data, handleCompletionWithListener(result)); - } else { - presence.leave(data, handleCompletionWithListener(result)); - } - } catch (AblyException e) { - handleAblyException(result, e); - } - }); - } - - private void createRealtime(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = - (AblyFlutterMessage) call.arguments; - this.ablyDo( - message, - (ablyLibrary, clientOptions) -> { - try { - final long handle = ablyLibrary.getHandleForNextClient(); - if (clientOptions.hasAuthCallback) { - clientOptions.options.authCallback = - (Auth.TokenParams params) -> { - final Object[] token = {null}; - final CountDownLatch latch = new CountDownLatch(1); - new Handler(Looper.getMainLooper()) - .post( - () -> { - AblyFlutterMessage channelMessage = - new AblyFlutterMessage<>(params, handle); - methodChannel.invokeMethod( - PlatformConstants.PlatformMethod.realtimeAuthCallback, - channelMessage, - new MethodChannel.Result() { - @Override - public void success(@Nullable Object result) { - if (result != null) { - token[0] = result; - latch.countDown(); - } - } - - @Override - public void error( - String errorCode, - @Nullable String errorMessage, - @Nullable Object errorDetails) { - Log.w( - TAG, - String.format( - "\"%s\" platform method received error from Dart" - + " side: %s", - PlatformConstants.PlatformMethod.realtimeAuthCallback, - errorMessage)); - latch.countDown(); - } - - @Override - public void notImplemented() { - Log.w( - TAG, - String.format( - "\"%s\" platform method not implemented on Dart side:" - + " %s", - PlatformConstants.PlatformMethod - .realtimeAuthCallback)); - latch.countDown(); - } - }); - }); - - try { - latch.await(); - } catch (InterruptedException e) { - throw AblyException.fromErrorInfo( - e, - new ErrorInfo( - "Exception while waiting for authCallback to return", 400, 40000)); - } - - return token[0]; - }; - } - result.success(ablyLibrary.createRealtime(clientOptions.options, applicationContext)); - } catch (final AblyException e) { - handleAblyException(result, e); - } - }); - } - - private void connectRealtime(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - if (call.arguments instanceof Integer) { - final Integer realtimeHandle = (Integer) call.arguments; - instanceStore.getRealtime(realtimeHandle.longValue()).connect(); - } else { - // Using Number (the superclass of both Long and Integer) because Flutter could send us - // either depending on how big the value is. - // See: https://flutter.dev/docs/development/platform-integration/platform-channels#codec - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - final Integer realtimeHandle = message.message; - instanceStore.getRealtime(realtimeHandle.longValue()).connect(); - } - result.success(null); - } - - private void attachRealtimeChannel( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, ablyMessage) -> { - try { - final String channelName = - (String) ablyMessage.message.get(PlatformConstants.TxTransportKeys.channelName); - ablyLibrary - .getRealtime(ablyMessage.handle) - .channels - .get(channelName) - .attach(handleCompletionWithListener(result)); - } catch (AblyException e) { - handleAblyException(result, e); - } - }); - } - - private void detachRealtimeChannel( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, ablyMessage) -> { - try { - final String channelName = - (String) ablyMessage.message.get(PlatformConstants.TxTransportKeys.channelName); - ablyLibrary - .getRealtime(ablyMessage.handle) - .channels - .get(channelName) - .detach(handleCompletionWithListener(result)); - } catch (AblyException e) { - handleAblyException(result, e); - } - }); - } - - private void setRealtimeChannelOptions( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, ablyMessage) -> { - try { - final String channelName = - (String) ablyMessage.message.get(PlatformConstants.TxTransportKeys.channelName); - final ChannelOptions channelOptions = - (ChannelOptions) ablyMessage.message.get(PlatformConstants.TxTransportKeys.options); - ablyLibrary - .getRealtime(ablyMessage.handle) - .channels - .get(channelName) - .setOptions(channelOptions); - result.success(null); - } catch (AblyException e) { - handleAblyException(result, e); - } - }); - } + } catch (AblyException e) { + handleAblyException(result, e); + } + }); + } + + private void createRealtime(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = + (AblyFlutterMessage) call.arguments; + this.ablyDo( + message, + (ablyLibrary, clientOptions) -> { + try { + final long handle = ablyLibrary.getHandleForNextClient(); + if (clientOptions.hasAuthCallback) { + clientOptions.options.authCallback = + (Auth.TokenParams params) -> { + final Object[] token = {null}; + final CountDownLatch latch = new CountDownLatch(1); + new Handler(Looper.getMainLooper()) + .post( + () -> { + AblyFlutterMessage channelMessage = + new AblyFlutterMessage<>(params, handle); + methodChannel.invokeMethod( + PlatformConstants.PlatformMethod.realtimeAuthCallback, + channelMessage, + new MethodChannel.Result() { + @Override + public void success(@Nullable Object result) { + if (result != null) { + token[0] = result; + latch.countDown(); + } + } + + @Override + public void error( + String errorCode, + @Nullable String errorMessage, + @Nullable Object errorDetails) { + Log.w( + TAG, + String.format( + "\"%s\" platform method received error from Dart" + + " side: %s", + PlatformConstants.PlatformMethod.realtimeAuthCallback, + errorMessage)); + latch.countDown(); + } + + @Override + public void notImplemented() { + Log.w( + TAG, + String.format( + "\"%s\" platform method not implemented on Dart side:" + + " %s", + PlatformConstants.PlatformMethod + .realtimeAuthCallback)); + latch.countDown(); + } + }); + }); - private void publishRealtimeChannelMessage( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, ablyMessage) -> { try { - final String channelName = - (String) ablyMessage.message.get(PlatformConstants.TxTransportKeys.channelName); - final Channel channel = - ablyLibrary.getRealtime(ablyMessage.handle).channels.get(channelName); - - final ArrayList channelMessages = - (ArrayList) - ablyMessage.message.get(PlatformConstants.TxTransportKeys.messages); - if (channelMessages == null) { - result.error("Messages cannot be null", null, null); - return; - } - Message[] messages = new Message[channelMessages.size()]; - messages = channelMessages.toArray(messages); - channel.publish(messages, handleCompletionWithListener(result)); - } catch (AblyException e) { - handleAblyException(result, e); + latch.await(); + } catch (InterruptedException e) { + throw AblyException.fromErrorInfo( + e, + new ErrorInfo( + "Exception while waiting for authCallback to return", 400, 40000)); } - }); - } - - private void releaseRealtimeChannel( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>ablyDo( - message, - (ablyLibrary, messageData) -> { - final String channelName = messageData.message; - ablyLibrary.getRealtime(messageData.handle).channels.release(channelName); - result.success(null); - }); - } - - private void realtimeTime(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) methodCall.arguments; - time(methodCall, result, instanceStore.getRealtime((int) message.message)); - } - - private void restTime(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) methodCall.arguments; - time(methodCall, result, instanceStore.getRest((int) message.message)); - } - - private void time(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result, AblyBase client) { - final AblyFlutterMessage message = (AblyFlutterMessage) methodCall.arguments; - Callback callback = new Callback() { - @Override - public void onSuccess(Long timeResult) { - result.success(timeResult); - } - @Override - public void onError(ErrorInfo reason) { - result.error("40000", reason.message, reason); + return token[0]; + }; } - }; - client.timeAsync(callback); - } - - - private void getRealtimeHistory(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.>>ablyDo( - message, - (ablyLibrary, messageData) -> { - final Map map = messageData.message; - final String channelName = - (String) map.get(PlatformConstants.TxTransportKeys.channelName); - Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); - if (params == null) { - params = new Param[0]; - } - ablyLibrary - .getRealtime(messageData.handle) - .channels - .get(channelName) - .historyAsync(params, this.paginatedResponseHandler(result, null)); - }); - } - - private void closeRealtime(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.ablyDo( - message, - (ablyLibrary, realtimeHandle) -> { - ablyLibrary.getRealtime(realtimeHandle.longValue()).close(); - result.success(null); - }); - } - - private void pushActivate(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - try { - Integer handle = (Integer) message.message; - PushActivationEventHandlers.setResultForActivate(result); - instanceStore.getPush(handle).activate(); - } catch (AblyException e) { + result.success(ablyLibrary.createRealtime(clientOptions.options, applicationContext)); + } catch (final AblyException e) { handleAblyException(result, e); - } - } - - private void pushDeactivate(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - try { - Integer handle = (Integer) message.message; - PushActivationEventHandlers.setResultForDeactivate(result); - instanceStore.getPush(handle).deactivate(); - } catch (AblyException e) { + } + }); + } + + private void connectRealtime(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + if (call.arguments instanceof Integer) { + final Integer realtimeHandle = (Integer) call.arguments; + instanceStore.getRealtime(realtimeHandle.longValue()).connect(); + } else { + // Using Number (the superclass of both Long and Integer) because Flutter could send us + // either depending on how big the value is. + // See: https://flutter.dev/docs/development/platform-integration/platform-channels#codec + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + final Integer realtimeHandle = message.message; + instanceStore.getRealtime(realtimeHandle.longValue()).connect(); + } + result.success(null); + } + + private void attachRealtimeChannel( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, ablyMessage) -> { + try { + final String channelName = + (String) ablyMessage.message.get(PlatformConstants.TxTransportKeys.channelName); + ablyLibrary + .getRealtime(ablyMessage.handle) + .channels + .get(channelName) + .attach(handleCompletionWithListener(result)); + } catch (AblyException e) { handleAblyException(result, e); - } - } - - private void pushSubscribeDevice(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage>> message = - (AblyFlutterMessage) call.arguments; - AblyFlutterMessage> nestedMessage = message.message; - final String channelName = - (String) nestedMessage.message.get(PlatformConstants.TxTransportKeys.channelName); - instanceStore - .getPushChannel(nestedMessage.handle, channelName) - .subscribeDeviceAsync(handleCompletionWithListener(result)); - } - - private void pushUnsubscribeDevice( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage>> message = - (AblyFlutterMessage) call.arguments; - AblyFlutterMessage> nestedMessage = message.message; - final String channelName = - (String) nestedMessage.message.get(PlatformConstants.TxTransportKeys.channelName); - instanceStore - .getPushChannel(nestedMessage.handle, channelName) - .unsubscribeDeviceAsync(handleCompletionWithListener(result)); - } - - private void pushSubscribeClient(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage>> message = - (AblyFlutterMessage) call.arguments; - AblyFlutterMessage> nestedMessage = message.message; - final String channelName = - (String) nestedMessage.message.get(PlatformConstants.TxTransportKeys.channelName); - instanceStore - .getPushChannel(nestedMessage.handle, channelName) - .subscribeClientAsync(handleCompletionWithListener(result)); - } - - private void pushUnsubscribeClient( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage>> message = - (AblyFlutterMessage) call.arguments; - AblyFlutterMessage> nestedMessage = message.message; - final String channelName = - (String) nestedMessage.message.get(PlatformConstants.TxTransportKeys.channelName); - instanceStore - .getPushChannel(nestedMessage.handle, channelName) - .unsubscribeClientAsync(handleCompletionWithListener(result)); - } - - private void pushListSubscriptions( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage>> message = - (AblyFlutterMessage>>) call.arguments; - AblyFlutterMessage> nestedMessage = message.message; - final String channelName = - (String) nestedMessage.message.get(PlatformConstants.TxTransportKeys.channelName); - final Map paramsMap = - (Map) nestedMessage.message.get(PlatformConstants.TxTransportKeys.params); - instanceStore - .getPushChannel(nestedMessage.handle, channelName) - .listSubscriptionsAsync( - createParamsArrayFromMap(paramsMap), this.paginatedResponseHandler(result, null)); - } - - private Param[] createParamsArrayFromMap(@Nullable Map paramsMap) { - if (paramsMap == null) { - return new Param[0]; - } - Param[] params = new Param[paramsMap.size()]; - int paramsSize = 0; - for (Map.Entry entry : paramsMap.entrySet()) { - Param param = new Param(entry.getKey(), entry.getValue()); - params[paramsSize] = param; - paramsSize += 1; - } - return params; - } - - private void pushDevice(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - long handle = message.message; - try { - AblyRealtime realtime = instanceStore.getRealtime(handle); - if (realtime != null) { - result.success(realtime.device()); - return; - } - - AblyRest rest = instanceStore.getRest(handle); - if (rest != null) { - result.success(rest.device()); - return; + } + }); + } + + private void detachRealtimeChannel( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, ablyMessage) -> { + try { + final String channelName = + (String) ablyMessage.message.get(PlatformConstants.TxTransportKeys.channelName); + ablyLibrary + .getRealtime(ablyMessage.handle) + .channels + .get(channelName) + .detach(handleCompletionWithListener(result)); + } catch (AblyException e) { + handleAblyException(result, e); + } + }); + } + + private void setRealtimeChannelOptions( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, ablyMessage) -> { + try { + final String channelName = + (String) ablyMessage.message.get(PlatformConstants.TxTransportKeys.channelName); + final ChannelOptions channelOptions = + (ChannelOptions) ablyMessage.message.get(PlatformConstants.TxTransportKeys.options); + ablyLibrary + .getRealtime(ablyMessage.handle) + .channels + .get(channelName) + .setOptions(channelOptions); + result.success(null); + } catch (AblyException e) { + handleAblyException(result, e); + } + }); + } + + private void publishRealtimeChannelMessage( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, ablyMessage) -> { + try { + final String channelName = + (String) ablyMessage.message.get(PlatformConstants.TxTransportKeys.channelName); + final Channel channel = + ablyLibrary.getRealtime(ablyMessage.handle).channels.get(channelName); + + final ArrayList channelMessages = + (ArrayList) + ablyMessage.message.get(PlatformConstants.TxTransportKeys.messages); + if (channelMessages == null) { + result.error("Messages cannot be null", null, null); + return; } - - throw AblyException.fromErrorInfo(new ErrorInfo("No Ably client exists", 400, 40000)); - } catch (AblyException e) { + Message[] messages = new Message[channelMessages.size()]; + messages = channelMessages.toArray(messages); + channel.publish(messages, handleCompletionWithListener(result)); + } catch (AblyException e) { handleAblyException(result, e); - } - } - - public void setRemoteMessageFromUserTapLaunchesApp(@Nullable RemoteMessage message) { - remoteMessageFromUserTapLaunchesApp = message; - } - - private void pushNotificationTapLaunchedAppFromTerminated( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - result.success(remoteMessageFromUserTapLaunchesApp); - remoteMessageFromUserTapLaunchesApp = null; - } - - private void getNextPage(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; - this.ablyDo( - message, - (ablyLibrary, pageHandle) -> - ablyLibrary - .getPaginatedResult(pageHandle) - .next(this.paginatedResponseHandler(result, pageHandle))); - } - - private void getFirstPage(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final AblyFlutterMessage> message = - (AblyFlutterMessage>) call.arguments; - Integer pageHandle = message.message.message; - instanceStore + } + }); + } + + private void releaseRealtimeChannel( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>ablyDo( + message, + (ablyLibrary, messageData) -> { + final String channelName = messageData.message; + ablyLibrary.getRealtime(messageData.handle).channels.release(channelName); + result.success(null); + }); + } + + private void realtimeTime(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) methodCall.arguments; + time(methodCall, result, instanceStore.getRealtime((int) message.message)); + } + + private void restTime(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) methodCall.arguments; + time(methodCall, result, instanceStore.getRest((int) message.message)); + } + + private void time( + @NonNull MethodCall methodCall, @NonNull MethodChannel.Result result, AblyBase client) { + final AblyFlutterMessage message = (AblyFlutterMessage) methodCall.arguments; + Callback callback = + new Callback() { + @Override + public void onSuccess(Long timeResult) { + result.success(timeResult); + } + + @Override + public void onError(ErrorInfo reason) { + result.error("40000", reason.message, reason); + } + }; + client.timeAsync(callback); + } + + private void getRealtimeHistory(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.>>ablyDo( + message, + (ablyLibrary, messageData) -> { + final Map map = messageData.message; + final String channelName = + (String) map.get(PlatformConstants.TxTransportKeys.channelName); + Param[] params = (Param[]) map.get(PlatformConstants.TxTransportKeys.params); + if (params == null) { + params = new Param[0]; + } + ablyLibrary + .getRealtime(messageData.handle) + .channels + .get(channelName) + .historyAsync(params, this.paginatedResponseHandler(result, null)); + }); + } + + private void closeRealtime(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.ablyDo( + message, + (ablyLibrary, realtimeHandle) -> { + ablyLibrary.getRealtime(realtimeHandle.longValue()).close(); + result.success(null); + }); + } + + private void pushActivate(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + try { + Integer handle = (Integer) message.message; + PushActivationEventHandlers.setResultForActivate(result); + instanceStore.getPush(handle).activate(); + } catch (AblyException e) { + handleAblyException(result, e); + } + } + + private void pushDeactivate(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + try { + Integer handle = (Integer) message.message; + PushActivationEventHandlers.setResultForDeactivate(result); + instanceStore.getPush(handle).deactivate(); + } catch (AblyException e) { + handleAblyException(result, e); + } + } + + private void pushSubscribeDevice(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage>> message = + (AblyFlutterMessage) call.arguments; + AblyFlutterMessage> nestedMessage = message.message; + final String channelName = + (String) nestedMessage.message.get(PlatformConstants.TxTransportKeys.channelName); + instanceStore + .getPushChannel(nestedMessage.handle, channelName) + .subscribeDeviceAsync(handleCompletionWithListener(result)); + } + + private void pushUnsubscribeDevice( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage>> message = + (AblyFlutterMessage) call.arguments; + AblyFlutterMessage> nestedMessage = message.message; + final String channelName = + (String) nestedMessage.message.get(PlatformConstants.TxTransportKeys.channelName); + instanceStore + .getPushChannel(nestedMessage.handle, channelName) + .unsubscribeDeviceAsync(handleCompletionWithListener(result)); + } + + private void pushSubscribeClient(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage>> message = + (AblyFlutterMessage) call.arguments; + AblyFlutterMessage> nestedMessage = message.message; + final String channelName = + (String) nestedMessage.message.get(PlatformConstants.TxTransportKeys.channelName); + instanceStore + .getPushChannel(nestedMessage.handle, channelName) + .subscribeClientAsync(handleCompletionWithListener(result)); + } + + private void pushUnsubscribeClient( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage>> message = + (AblyFlutterMessage) call.arguments; + AblyFlutterMessage> nestedMessage = message.message; + final String channelName = + (String) nestedMessage.message.get(PlatformConstants.TxTransportKeys.channelName); + instanceStore + .getPushChannel(nestedMessage.handle, channelName) + .unsubscribeClientAsync(handleCompletionWithListener(result)); + } + + private void pushListSubscriptions( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage>> message = + (AblyFlutterMessage>>) call.arguments; + AblyFlutterMessage> nestedMessage = message.message; + final String channelName = + (String) nestedMessage.message.get(PlatformConstants.TxTransportKeys.channelName); + final Map paramsMap = + (Map) nestedMessage.message.get(PlatformConstants.TxTransportKeys.params); + instanceStore + .getPushChannel(nestedMessage.handle, channelName) + .listSubscriptionsAsync( + createParamsArrayFromMap(paramsMap), this.paginatedResponseHandler(result, null)); + } + + private Param[] createParamsArrayFromMap(@Nullable Map paramsMap) { + if (paramsMap == null) { + return new Param[0]; + } + Param[] params = new Param[paramsMap.size()]; + int paramsSize = 0; + for (Map.Entry entry : paramsMap.entrySet()) { + Param param = new Param(entry.getKey(), entry.getValue()); + params[paramsSize] = param; + paramsSize += 1; + } + return params; + } + + private void pushDevice(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + long handle = message.message; + try { + AblyRealtime realtime = instanceStore.getRealtime(handle); + if (realtime != null) { + result.success(realtime.device()); + return; + } + + AblyRest rest = instanceStore.getRest(handle); + if (rest != null) { + result.success(rest.device()); + return; + } + + throw AblyException.fromErrorInfo(new ErrorInfo("No Ably client exists", 400, 40000)); + } catch (AblyException e) { + handleAblyException(result, e); + } + } + + public void setRemoteMessageFromUserTapLaunchesApp(@Nullable RemoteMessage message) { + remoteMessageFromUserTapLaunchesApp = message; + } + + private void pushNotificationTapLaunchedAppFromTerminated( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + result.success(remoteMessageFromUserTapLaunchesApp); + remoteMessageFromUserTapLaunchesApp = null; + } + + private void getNextPage(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + this.ablyDo( + message, + (ablyLibrary, pageHandle) -> + ablyLibrary .getPaginatedResult(pageHandle) - .first(this.paginatedResponseHandler(result, pageHandle)); - } - - private void cryptoGetParams(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final Map message = (Map) call.arguments; - final String algorithm = (String) message.get(PlatformConstants.TxCryptoGetParams.algorithm); - final byte[] keyData = getKeyData(message.get(PlatformConstants.TxCryptoGetParams.key)); - if (keyData == null) { - result.error( - "40000", - "A key must be set for encryption, being either a base64 encoded key, or a byte array.", - null); - return; - } - - try { - result.success(Crypto.getParams(algorithm, keyData)); - } catch (NoSuchAlgorithmException e) { - result.error("40000", "cryptoGetParams: No algorithm found. " + e, e); - } - } - - private byte[] getKeyData(Object key) { - if (key == null) { - return null; - } - if (key instanceof String) { - return Base64Coder.decode((String) key); - } else if (key instanceof byte[]) { - return (byte[]) key; - } else { - return null; - } - } - - private void cryptoGenerateRandomKey( - @NonNull MethodCall call, @NonNull MethodChannel.Result result) { - final Integer keyLength = (Integer) call.arguments; - result.success(Crypto.generateRandomKey(keyLength)); - } - - // Extracts the message from an AblyFlutterMessage. - // - // It also passed the ablyLibrary argument, which you can just get with _ably without using this - // method - // The benefit of using this method is questionable: it allows you to reduce manually casting. - void ablyDo( - final AblyFlutterMessage message, final BiConsumer consumer) { - consumer.accept(instanceStore, (T) message.message); - } - - private void getPlatformVersion(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - result.success("Android " + android.os.Build.VERSION.RELEASE); - } - - private void getVersion(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - result.success(Defaults.ABLY_AGENT_VERSION); - } + .next(this.paginatedResponseHandler(result, pageHandle))); + } + + private void getFirstPage(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage> message = + (AblyFlutterMessage>) call.arguments; + Integer pageHandle = message.message.message; + instanceStore + .getPaginatedResult(pageHandle) + .first(this.paginatedResponseHandler(result, pageHandle)); + } + + private void cryptoGetParams(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final Map message = (Map) call.arguments; + final String algorithm = (String) message.get(PlatformConstants.TxCryptoGetParams.algorithm); + final byte[] keyData = getKeyData(message.get(PlatformConstants.TxCryptoGetParams.key)); + if (keyData == null) { + result.error( + "40000", + "A key must be set for encryption, being either a base64 encoded key, or a byte array.", + null); + return; + } + + try { + result.success(Crypto.getParams(algorithm, keyData)); + } catch (NoSuchAlgorithmException e) { + result.error("40000", "cryptoGetParams: No algorithm found. " + e, e); + } + } + + private byte[] getKeyData(Object key) { + if (key == null) { + return null; + } + if (key instanceof String) { + return Base64Coder.decode((String) key); + } else if (key instanceof byte[]) { + return (byte[]) key; + } else { + return null; + } + } + + private void cryptoGenerateRandomKey( + @NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final Integer keyLength = (Integer) call.arguments; + result.success(Crypto.generateRandomKey(keyLength)); + } + + // Extracts the message from an AblyFlutterMessage. + // + // It also passed the ablyLibrary argument, which you can just get with _ably without using this + // method + // The benefit of using this method is questionable: it allows you to reduce manually casting. + void ablyDo( + final AblyFlutterMessage message, final BiConsumer consumer) { + consumer.accept(instanceStore, (T) message.message); + } + + private void getPlatformVersion(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + result.success("Android " + android.os.Build.VERSION.RELEASE); + } + + private void getVersion(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + result.success(Defaults.ABLY_AGENT_VERSION); + } } 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 e4c295ca8..dfb539153 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 @@ -5,445 +5,499 @@ package io.ably.flutter.plugin.generated; - -final public class PlatformConstants { - - 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; - } - - static final public class PlatformMethod { - public static final String getPlatformVersion = "getPlatformVersion"; - public static final String getVersion = "getVersion"; - public static final String resetAblyClients = "resetAblyClients"; - public static final String authCallback = "authCallback"; - public static final String realtimeAuthCallback = "realtimeAuthCallback"; - public static final String createRest = "createRest"; - public static final String setRestChannelOptions = "setRestChannelOptions"; - public static final String publish = "publish"; - public static final String restHistory = "restHistory"; - public static final String restPresenceGet = "restPresenceGet"; - public static final String restPresenceHistory = "restPresenceHistory"; - public static final String releaseRestChannel = "releaseRestChannel"; - public static final String createRealtime = "createRealtime"; - public static final String connectRealtime = "connectRealtime"; - public static final String closeRealtime = "closeRealtime"; - public static final String attachRealtimeChannel = "attachRealtimeChannel"; - public static final String detachRealtimeChannel = "detachRealtimeChannel"; - public static final String setRealtimeChannelOptions = "setRealtimeChannelOptions"; - public static final String realtimePresenceGet = "realtimePresenceGet"; - public static final String realtimePresenceHistory = "realtimePresenceHistory"; - public static final String realtimePresenceEnter = "realtimePresenceEnter"; - public static final String realtimePresenceUpdate = "realtimePresenceUpdate"; - public static final String realtimePresenceLeave = "realtimePresenceLeave"; - public static final String onRealtimePresenceMessage = "onRealtimePresenceMessage"; - public static final String publishRealtimeChannelMessage = "publishRealtimeChannelMessage"; - public static final String releaseRealtimeChannel = "releaseRealtimeChannel"; - public static final String realtimeHistory = "realtimeHistory"; - public static final String realtimeTime = "realtimeTime"; - public static final String restTime = "restTime"; - public static final String pushActivate = "pushActivate"; - public static final String pushDeactivate = "pushDeactivate"; - public static final String pushSubscribeDevice = "pushSubscribeDevice"; - public static final String pushUnsubscribeDevice = "pushUnsubscribeDevice"; - public static final String pushSubscribeClient = "pushSubscribeClient"; - public static final String pushUnsubscribeClient = "pushUnsubscribeClient"; - public static final String pushListSubscriptions = "pushListSubscriptions"; - public static final String pushDevice = "pushDevice"; - public static final String pushRequestPermission = "pushRequestPermission"; - public static final String pushGetNotificationSettings = "pushGetNotificationSettings"; - public static final String pushOpenSettingsFor = "pushOpenSettingsFor"; - public static final String pushOnActivate = "pushOnActivate"; - public static final String pushOnDeactivate = "pushOnDeactivate"; - public static final String pushOnUpdateFailed = "pushOnUpdateFailed"; - public static final String pushNotificationTapLaunchedAppFromTerminated = "pushNotificationTapLaunchedAppFromTerminated"; - public static final String pushOnShowNotificationInForeground = "pushOnShowNotificationInForeground"; - public static final String pushOnMessage = "pushOnMessage"; - public static final String pushOnBackgroundMessage = "pushOnBackgroundMessage"; - public static final String pushOnNotificationTap = "pushOnNotificationTap"; - public static final String pushBackgroundFlutterApplicationReadyOnAndroid = "pushBackgroundFlutterApplicationReadyOnAndroid"; - public static final String onRealtimeConnectionStateChanged = "onRealtimeConnectionStateChanged"; - public static final String onRealtimeChannelStateChanged = "onRealtimeChannelStateChanged"; - public static final String onRealtimeChannelMessage = "onRealtimeChannelMessage"; - public static final String nextPage = "nextPage"; - public static final String firstPage = "firstPage"; - public static final String cryptoGetParams = "cryptoGetParams"; - public static final String cryptoGenerateRandomKey = "cryptoGenerateRandomKey"; - } - - static final public class TxTransportKeys { - public static final String channelName = "channelName"; - public static final String params = "params"; - public static final String data = "data"; - public static final String clientId = "clientId"; - public static final String options = "options"; - public static final String messages = "messages"; - } - - static final public class TxAblyMessage { - public static final String registrationHandle = "registrationHandle"; - public static final String type = "type"; - public static final String message = "message"; - } - - static final public class TxAblyEventMessage { - public static final String eventName = "eventName"; - public static final String type = "type"; - public static final String message = "message"; - } - - static final public class TxErrorInfo { - public static final String code = "code"; - public static final String message = "message"; - public static final String statusCode = "statusCode"; - public static final String href = "href"; - public static final String requestId = "requestId"; - public static final String cause = "cause"; - } - - static final public class TxMessageData { - public static final String data = "data"; - public static final String type = "type"; - } - - static final public class TxDeltaExtras { - public static final String format = "format"; - public static final String from = "from"; - } - - static final public class TxMessageExtras { - public static final String extras = "extras"; - public static final String delta = "delta"; - } - - static final public class TxClientOptions { - public static final String authUrl = "authUrl"; - public static final String authMethod = "authMethod"; - public static final String key = "key"; - 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"; - public static final String hasAuthCallback = "hasAuthCallback"; - public static final String clientId = "clientId"; - public static final String logLevel = "logLevel"; - public static final String tls = "tls"; - public static final String restHost = "restHost"; - public static final String realtimeHost = "realtimeHost"; - public static final String port = "port"; - public static final String tlsPort = "tlsPort"; - public static final String autoConnect = "autoConnect"; - public static final String useBinaryProtocol = "useBinaryProtocol"; - public static final String queueMessages = "queueMessages"; - public static final String echoMessages = "echoMessages"; - public static final String recover = "recover"; - public static final String environment = "environment"; - public static final String idempotentRestPublishing = "idempotentRestPublishing"; - public static final String httpOpenTimeout = "httpOpenTimeout"; - public static final String httpRequestTimeout = "httpRequestTimeout"; - public static final String httpMaxRetryCount = "httpMaxRetryCount"; - public static final String realtimeRequestTimeout = "realtimeRequestTimeout"; - public static final String fallbackHosts = "fallbackHosts"; - public static final String fallbackHostsUseDefault = "fallbackHostsUseDefault"; - public static final String fallbackRetryTimeout = "fallbackRetryTimeout"; - public static final String defaultTokenParams = "defaultTokenParams"; - public static final String channelRetryTimeout = "channelRetryTimeout"; - public static final String transportParams = "transportParams"; - } - - static final public class TxRestChannelOptions { - public static final String cipherParams = "cipherParams"; - } - - static final public class TxRealtimeChannelOptions { - public static final String params = "params"; - public static final String modes = "modes"; - public static final String cipherParams = "cipherParams"; - } - - static final public class TxCipherParams { - public static final String androidHandle = "androidHandle"; - public static final String iosAlgorithm = "iosAlgorithm"; - public static final String iosKey = "iosKey"; - } - - static final public class TxTokenDetails { - public static final String token = "token"; - public static final String expires = "expires"; - public static final String issued = "issued"; - public static final String capability = "capability"; - public static final String clientId = "clientId"; - } - - static final public class TxTokenParams { - public static final String capability = "capability"; - public static final String clientId = "clientId"; - public static final String nonce = "nonce"; - public static final String timestamp = "timestamp"; - public static final String ttl = "ttl"; - } - - static final public class TxTokenRequest { - public static final String capability = "capability"; - public static final String clientId = "clientId"; - public static final String keyName = "keyName"; - public static final String mac = "mac"; - public static final String nonce = "nonce"; - public static final String timestamp = "timestamp"; - public static final String ttl = "ttl"; - } - - static final public class TxEnumConstants { - public static final String initialized = "initialized"; - public static final String connecting = "connecting"; - public static final String connected = "connected"; - public static final String disconnected = "disconnected"; - public static final String attaching = "attaching"; - public static final String attached = "attached"; - public static final String detaching = "detaching"; - public static final String detached = "detached"; - public static final String suspended = "suspended"; - public static final String closing = "closing"; - public static final String closed = "closed"; - public static final String failed = "failed"; - public static final String absent = "absent"; - public static final String leave = "leave"; - public static final String enter = "enter"; - public static final String present = "present"; - public static final String update = "update"; - public static final String presence = "presence"; - public static final String publish = "publish"; - public static final String subscribe = "subscribe"; - public static final String presenceSubscribe = "presenceSubscribe"; - } - - static final public class TxFormFactorEnum { - public static final String phone = "phone"; - public static final String tablet = "tablet"; - public static final String desktop = "desktop"; - public static final String tv = "tv"; - public static final String watch = "watch"; - public static final String car = "car"; - public static final String embedded = "embedded"; - public static final String other = "other"; - } - - static final public class TxLogLevelEnum { - public static final String none = "none"; - public static final String verbose = "verbose"; - public static final String debug = "debug"; - public static final String info = "info"; - public static final String warn = "warn"; - public static final String error = "error"; - } - - static final public class TxDevicePlatformEnum { - public static final String ios = "ios"; - public static final String android = "android"; - public static final String browser = "browser"; - } - - static final public class TxDevicePushStateEnum { - public static final String active = "active"; - public static final String failing = "failing"; - public static final String failed = "failed"; - } - - static final public class TxConnectionStateChange { - public static final String current = "current"; - public static final String previous = "previous"; - public static final String event = "event"; - public static final String retryIn = "retryIn"; - public static final String reason = "reason"; - } - - static final public class TxChannelStateChange { - public static final String current = "current"; - public static final String previous = "previous"; - public static final String event = "event"; - public static final String resumed = "resumed"; - public static final String reason = "reason"; - } - - static final public class TxMessage { - public static final String id = "id"; - public static final String timestamp = "timestamp"; - public static final String clientId = "clientId"; - public static final String connectionId = "connectionId"; - public static final String encoding = "encoding"; - public static final String data = "data"; - public static final String name = "name"; - public static final String extras = "extras"; - } - - static final public class TxPresenceMessage { - public static final String id = "id"; - public static final String action = "action"; - public static final String clientId = "clientId"; - public static final String connectionId = "connectionId"; - public static final String data = "data"; - public static final String encoding = "encoding"; - public static final String extras = "extras"; - public static final String timestamp = "timestamp"; - } - - static final public class TxPaginatedResult { - public static final String items = "items"; - public static final String type = "type"; - public static final String hasNext = "hasNext"; - } - - static final public class TxRestHistoryParams { - public static final String start = "start"; - public static final String end = "end"; - public static final String direction = "direction"; - public static final String limit = "limit"; - } - - static final public class TxRealtimeHistoryParams { - public static final String start = "start"; - public static final String end = "end"; - public static final String direction = "direction"; - public static final String limit = "limit"; - public static final String untilAttach = "untilAttach"; - } - - static final public class TxRestPresenceParams { - public static final String limit = "limit"; - public static final String clientId = "clientId"; - public static final String connectionId = "connectionId"; - } - - static final public class TxRealtimePresenceParams { - public static final String waitForSync = "waitForSync"; - public static final String clientId = "clientId"; - public static final String connectionId = "connectionId"; - } - - static final public class TxDeviceDetails { - public static final String id = "id"; - public static final String clientId = "clientId"; - public static final String platform = "platform"; - public static final String formFactor = "formFactor"; - public static final String metadata = "metadata"; - public static final String devicePushDetails = "devicePushDetails"; - } - - static final public class TxDevicePushDetails { - public static final String recipient = "recipient"; - public static final String state = "state"; - public static final String errorReason = "errorReason"; - } - - static final public class TxLocalDevice { - public static final String deviceSecret = "deviceSecret"; - public static final String deviceIdentityToken = "deviceIdentityToken"; - } - - static final public class TxPushChannelSubscription { - public static final String channel = "channel"; - public static final String deviceId = "deviceId"; - public static final String clientId = "clientId"; - } - - static final public class TxPushRequestPermission { - public static final String badge = "badge"; - public static final String sound = "sound"; - public static final String alert = "alert"; - public static final String carPlay = "carPlay"; - public static final String criticalAlert = "criticalAlert"; - public static final String providesAppNotificationSettings = "providesAppNotificationSettings"; - public static final String provisional = "provisional"; - public static final String announcement = "announcement"; - } - - static final public class TxUNNotificationSettings { - public static final String authorizationStatus = "authorizationStatus"; - public static final String soundSetting = "soundSetting"; - public static final String badgeSetting = "badgeSetting"; - public static final String alertSetting = "alertSetting"; - public static final String notificationCenterSetting = "notificationCenterSetting"; - public static final String lockScreenSetting = "lockScreenSetting"; - public static final String carPlaySetting = "carPlaySetting"; - public static final String alertStyle = "alertStyle"; - public static final String showPreviewsSetting = "showPreviewsSetting"; - public static final String criticalAlertSetting = "criticalAlertSetting"; - public static final String providesAppNotificationSettings = "providesAppNotificationSettings"; - public static final String announcementSetting = "announcementSetting"; - public static final String scheduledDeliverySetting = "scheduledDeliverySetting"; - public static final String timeSensitiveSetting = "timeSensitiveSetting"; - } - - static final public class TxUNNotificationSettingEnum { - public static final String notSupported = "notSupported"; - public static final String disabled = "disabled"; - public static final String enabled = "enabled"; - } - - static final public class TxUNAlertStyleEnum { - public static final String none = "none"; - public static final String banner = "banner"; - public static final String alert = "alert"; - } - - static final public class TxUNAuthorizationStatusEnum { - public static final String notDetermined = "notDetermined"; - public static final String denied = "denied"; - public static final String authorized = "authorized"; - public static final String provisional = "provisional"; - public static final String ephemeral = "ephemeral"; - } - - static final public class TxUNShowPreviewsSettingEnum { - public static final String always = "always"; - public static final String whenAuthenticated = "whenAuthenticated"; - public static final String never = "never"; - } - - static final public class TxRemoteMessage { - public static final String data = "data"; - public static final String notification = "notification"; - } - - static final public class TxNotification { - public static final String title = "title"; - public static final String body = "body"; - } - - static final public class TxCryptoGetParams { - public static final String algorithm = "algorithm"; - public static final String key = "key"; - } - - static final public class TxCryptoGenerateRandomKey { - public static final String keyLength = "keyLength"; - } - +public final class PlatformConstants { + + public static final 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 stats = (byte) 155; + } + + public static final class PlatformMethod { + public static final String getPlatformVersion = "getPlatformVersion"; + public static final String getVersion = "getVersion"; + public static final String resetAblyClients = "resetAblyClients"; + public static final String authCallback = "authCallback"; + public static final String realtimeAuthCallback = "realtimeAuthCallback"; + public static final String createRest = "createRest"; + public static final String setRestChannelOptions = "setRestChannelOptions"; + public static final String publish = "publish"; + public static final String restHistory = "restHistory"; + public static final String restPresenceGet = "restPresenceGet"; + public static final String restPresenceHistory = "restPresenceHistory"; + public static final String releaseRestChannel = "releaseRestChannel"; + public static final String createRealtime = "createRealtime"; + public static final String connectRealtime = "connectRealtime"; + public static final String closeRealtime = "closeRealtime"; + public static final String attachRealtimeChannel = "attachRealtimeChannel"; + public static final String detachRealtimeChannel = "detachRealtimeChannel"; + public static final String setRealtimeChannelOptions = "setRealtimeChannelOptions"; + public static final String realtimePresenceGet = "realtimePresenceGet"; + public static final String realtimePresenceHistory = "realtimePresenceHistory"; + public static final String realtimePresenceEnter = "realtimePresenceEnter"; + public static final String realtimePresenceUpdate = "realtimePresenceUpdate"; + public static final String realtimePresenceLeave = "realtimePresenceLeave"; + public static final String onRealtimePresenceMessage = "onRealtimePresenceMessage"; + public static final String publishRealtimeChannelMessage = "publishRealtimeChannelMessage"; + public static final String releaseRealtimeChannel = "releaseRealtimeChannel"; + public static final String realtimeHistory = "realtimeHistory"; + public static final String realtimeTime = "realtimeTime"; + public static final String restTime = "restTime"; + public static final String stats = "stats"; + public static final String pushActivate = "pushActivate"; + public static final String pushDeactivate = "pushDeactivate"; + public static final String pushSubscribeDevice = "pushSubscribeDevice"; + public static final String pushUnsubscribeDevice = "pushUnsubscribeDevice"; + public static final String pushSubscribeClient = "pushSubscribeClient"; + public static final String pushUnsubscribeClient = "pushUnsubscribeClient"; + public static final String pushListSubscriptions = "pushListSubscriptions"; + public static final String pushDevice = "pushDevice"; + public static final String pushRequestPermission = "pushRequestPermission"; + public static final String pushGetNotificationSettings = "pushGetNotificationSettings"; + public static final String pushOpenSettingsFor = "pushOpenSettingsFor"; + public static final String pushOnActivate = "pushOnActivate"; + public static final String pushOnDeactivate = "pushOnDeactivate"; + public static final String pushOnUpdateFailed = "pushOnUpdateFailed"; + public static final String pushNotificationTapLaunchedAppFromTerminated = + "pushNotificationTapLaunchedAppFromTerminated"; + public static final String pushOnShowNotificationInForeground = + "pushOnShowNotificationInForeground"; + public static final String pushOnMessage = "pushOnMessage"; + public static final String pushOnBackgroundMessage = "pushOnBackgroundMessage"; + public static final String pushOnNotificationTap = "pushOnNotificationTap"; + public static final String pushBackgroundFlutterApplicationReadyOnAndroid = + "pushBackgroundFlutterApplicationReadyOnAndroid"; + public static final String onRealtimeConnectionStateChanged = + "onRealtimeConnectionStateChanged"; + public static final String onRealtimeChannelStateChanged = "onRealtimeChannelStateChanged"; + public static final String onRealtimeChannelMessage = "onRealtimeChannelMessage"; + public static final String nextPage = "nextPage"; + public static final String firstPage = "firstPage"; + public static final String cryptoGetParams = "cryptoGetParams"; + public static final String cryptoGenerateRandomKey = "cryptoGenerateRandomKey"; + } + + public static final class TxTransportKeys { + public static final String channelName = "channelName"; + public static final String params = "params"; + public static final String data = "data"; + public static final String clientId = "clientId"; + public static final String options = "options"; + public static final String messages = "messages"; + } + + public static final class TxAblyMessage { + public static final String registrationHandle = "registrationHandle"; + public static final String type = "type"; + public static final String message = "message"; + } + + public static final class TxAblyEventMessage { + public static final String eventName = "eventName"; + public static final String type = "type"; + public static final String message = "message"; + } + + public static final class TxErrorInfo { + public static final String code = "code"; + public static final String message = "message"; + public static final String statusCode = "statusCode"; + public static final String href = "href"; + public static final String requestId = "requestId"; + public static final String cause = "cause"; + } + + public static final class TxMessageData { + public static final String data = "data"; + public static final String type = "type"; + } + + public static final class TxDeltaExtras { + public static final String format = "format"; + public static final String from = "from"; + } + + public static final class TxMessageExtras { + public static final String extras = "extras"; + public static final String delta = "delta"; + } + + public static final class TxClientOptions { + public static final String authUrl = "authUrl"; + public static final String authMethod = "authMethod"; + public static final String key = "key"; + 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"; + public static final String hasAuthCallback = "hasAuthCallback"; + public static final String clientId = "clientId"; + public static final String logLevel = "logLevel"; + public static final String tls = "tls"; + public static final String restHost = "restHost"; + public static final String realtimeHost = "realtimeHost"; + public static final String port = "port"; + public static final String tlsPort = "tlsPort"; + public static final String autoConnect = "autoConnect"; + public static final String useBinaryProtocol = "useBinaryProtocol"; + public static final String queueMessages = "queueMessages"; + public static final String echoMessages = "echoMessages"; + public static final String recover = "recover"; + public static final String environment = "environment"; + public static final String idempotentRestPublishing = "idempotentRestPublishing"; + public static final String httpOpenTimeout = "httpOpenTimeout"; + public static final String httpRequestTimeout = "httpRequestTimeout"; + public static final String httpMaxRetryCount = "httpMaxRetryCount"; + public static final String realtimeRequestTimeout = "realtimeRequestTimeout"; + public static final String fallbackHosts = "fallbackHosts"; + public static final String fallbackHostsUseDefault = "fallbackHostsUseDefault"; + public static final String fallbackRetryTimeout = "fallbackRetryTimeout"; + public static final String defaultTokenParams = "defaultTokenParams"; + public static final String channelRetryTimeout = "channelRetryTimeout"; + public static final String transportParams = "transportParams"; + } + + public static final class TxRestChannelOptions { + public static final String cipherParams = "cipherParams"; + } + + public static final class TxRealtimeChannelOptions { + public static final String params = "params"; + public static final String modes = "modes"; + public static final String cipherParams = "cipherParams"; + } + + public static final class TxCipherParams { + public static final String androidHandle = "androidHandle"; + public static final String iosAlgorithm = "iosAlgorithm"; + public static final String iosKey = "iosKey"; + } + + public static final class TxTokenDetails { + public static final String token = "token"; + public static final String expires = "expires"; + public static final String issued = "issued"; + public static final String capability = "capability"; + public static final String clientId = "clientId"; + } + + public static final class TxTokenParams { + public static final String capability = "capability"; + public static final String clientId = "clientId"; + public static final String nonce = "nonce"; + public static final String timestamp = "timestamp"; + public static final String ttl = "ttl"; + } + + public static final class TxTokenRequest { + public static final String capability = "capability"; + public static final String clientId = "clientId"; + public static final String keyName = "keyName"; + public static final String mac = "mac"; + public static final String nonce = "nonce"; + public static final String timestamp = "timestamp"; + public static final String ttl = "ttl"; + } + + public static final class TxEnumConstants { + public static final String initialized = "initialized"; + public static final String connecting = "connecting"; + public static final String connected = "connected"; + public static final String disconnected = "disconnected"; + public static final String attaching = "attaching"; + public static final String attached = "attached"; + public static final String detaching = "detaching"; + public static final String detached = "detached"; + public static final String suspended = "suspended"; + public static final String closing = "closing"; + public static final String closed = "closed"; + public static final String failed = "failed"; + public static final String absent = "absent"; + public static final String leave = "leave"; + public static final String enter = "enter"; + public static final String present = "present"; + public static final String update = "update"; + public static final String presence = "presence"; + public static final String publish = "publish"; + public static final String subscribe = "subscribe"; + public static final String presenceSubscribe = "presenceSubscribe"; + } + + public static final class TxFormFactorEnum { + public static final String phone = "phone"; + public static final String tablet = "tablet"; + public static final String desktop = "desktop"; + public static final String tv = "tv"; + public static final String watch = "watch"; + public static final String car = "car"; + public static final String embedded = "embedded"; + public static final String other = "other"; + } + + public static final class TxLogLevelEnum { + public static final String none = "none"; + public static final String verbose = "verbose"; + public static final String debug = "debug"; + public static final String info = "info"; + public static final String warn = "warn"; + public static final String error = "error"; + } + + public static final class TxDevicePlatformEnum { + public static final String ios = "ios"; + public static final String android = "android"; + public static final String browser = "browser"; + } + + public static final class TxDevicePushStateEnum { + public static final String active = "active"; + public static final String failing = "failing"; + public static final String failed = "failed"; + } + + public static final class TxConnectionStateChange { + public static final String current = "current"; + public static final String previous = "previous"; + public static final String event = "event"; + public static final String retryIn = "retryIn"; + public static final String reason = "reason"; + } + + public static final class TxChannelStateChange { + public static final String current = "current"; + public static final String previous = "previous"; + public static final String event = "event"; + public static final String resumed = "resumed"; + public static final String reason = "reason"; + } + + public static final class TxMessage { + public static final String id = "id"; + public static final String timestamp = "timestamp"; + public static final String clientId = "clientId"; + public static final String connectionId = "connectionId"; + public static final String encoding = "encoding"; + public static final String data = "data"; + public static final String name = "name"; + public static final String extras = "extras"; + } + + public static final class TxPresenceMessage { + public static final String id = "id"; + public static final String action = "action"; + public static final String clientId = "clientId"; + public static final String connectionId = "connectionId"; + public static final String data = "data"; + public static final String encoding = "encoding"; + public static final String extras = "extras"; + public static final String timestamp = "timestamp"; + } + + public static final class TxPaginatedResult { + public static final String items = "items"; + public static final String type = "type"; + public static final String hasNext = "hasNext"; + } + + public static final class TxRestHistoryParams { + public static final String start = "start"; + public static final String end = "end"; + public static final String direction = "direction"; + public static final String limit = "limit"; + } + + public static final class TxRealtimeHistoryParams { + public static final String start = "start"; + public static final String end = "end"; + public static final String direction = "direction"; + public static final String limit = "limit"; + public static final String untilAttach = "untilAttach"; + } + + public static final class TxRestPresenceParams { + public static final String limit = "limit"; + public static final String clientId = "clientId"; + public static final String connectionId = "connectionId"; + } + + public static final class TxRealtimePresenceParams { + public static final String waitForSync = "waitForSync"; + public static final String clientId = "clientId"; + public static final String connectionId = "connectionId"; + } + + public static final class TxDeviceDetails { + public static final String id = "id"; + public static final String clientId = "clientId"; + public static final String platform = "platform"; + public static final String formFactor = "formFactor"; + public static final String metadata = "metadata"; + public static final String devicePushDetails = "devicePushDetails"; + } + + public static final class TxDevicePushDetails { + public static final String recipient = "recipient"; + public static final String state = "state"; + public static final String errorReason = "errorReason"; + } + + public static final class TxLocalDevice { + public static final String deviceSecret = "deviceSecret"; + public static final String deviceIdentityToken = "deviceIdentityToken"; + } + + public static final class TxPushChannelSubscription { + public static final String channel = "channel"; + public static final String deviceId = "deviceId"; + public static final String clientId = "clientId"; + } + + public static final class TxPushRequestPermission { + public static final String badge = "badge"; + public static final String sound = "sound"; + public static final String alert = "alert"; + public static final String carPlay = "carPlay"; + public static final String criticalAlert = "criticalAlert"; + public static final String providesAppNotificationSettings = "providesAppNotificationSettings"; + public static final String provisional = "provisional"; + public static final String announcement = "announcement"; + } + + public static final class TxUNNotificationSettings { + public static final String authorizationStatus = "authorizationStatus"; + public static final String soundSetting = "soundSetting"; + public static final String badgeSetting = "badgeSetting"; + public static final String alertSetting = "alertSetting"; + public static final String notificationCenterSetting = "notificationCenterSetting"; + public static final String lockScreenSetting = "lockScreenSetting"; + public static final String carPlaySetting = "carPlaySetting"; + public static final String alertStyle = "alertStyle"; + public static final String showPreviewsSetting = "showPreviewsSetting"; + public static final String criticalAlertSetting = "criticalAlertSetting"; + public static final String providesAppNotificationSettings = "providesAppNotificationSettings"; + public static final String announcementSetting = "announcementSetting"; + public static final String scheduledDeliverySetting = "scheduledDeliverySetting"; + public static final String timeSensitiveSetting = "timeSensitiveSetting"; + } + + public static final class TxUNNotificationSettingEnum { + public static final String notSupported = "notSupported"; + public static final String disabled = "disabled"; + public static final String enabled = "enabled"; + } + + public static final class TxUNAlertStyleEnum { + public static final String none = "none"; + public static final String banner = "banner"; + public static final String alert = "alert"; + } + + public static final class TxUNAuthorizationStatusEnum { + public static final String notDetermined = "notDetermined"; + public static final String denied = "denied"; + public static final String authorized = "authorized"; + public static final String provisional = "provisional"; + public static final String ephemeral = "ephemeral"; + } + + public static final class TxUNShowPreviewsSettingEnum { + public static final String always = "always"; + public static final String whenAuthenticated = "whenAuthenticated"; + public static final String never = "never"; + } + + public static final class TxRemoteMessage { + public static final String data = "data"; + public static final String notification = "notification"; + } + + public static final class TxNotification { + public static final String title = "title"; + public static final String body = "body"; + } + + public static final class TxCryptoGetParams { + public static final String algorithm = "algorithm"; + public static final String key = "key"; + } + + public static final class TxCryptoGenerateRandomKey { + public static final String keyLength = "keyLength"; + } + + public static final class TxStats { + public static final String all = "all"; + public static final String apiRequests = "apiRequests"; + public static final String channels = "channels"; + public static final String connections = "connections"; + public static final String inbound = "inbound"; + public static final String intervalId = "intervalId"; + public static final String outbound = "outbound"; + public static final String persisted = "persisted"; + public static final String tokenRequests = "tokenRequests"; + } + + public static final class TxStatsMessageTypes { + public static final String all = "all"; + public static final String messages = "messages"; + public static final String presence = "presence"; + } + + public static final class TxStatsMessageCount { + public static final String count = "count"; + public static final String data = "data"; + } + + public static final class TxStatsRequestCount { + public static final String failed = "failed"; + public static final String refused = "refused"; + public static final String succeeded = "succeeded"; + } + + public static final class TxStatsResourceCount { + public static final String mean = "mean"; + public static final String min = "min"; + public static final String opened = "opened"; + public static final String peak = "peak"; + public static final String refused = "refused"; + } + + public static final class TxStatsConnectionTypes { + public static final String all = "all"; + public static final String plain = "plain"; + public static final String tls = "tls"; + } + + public static final class TxStatsMessageTraffic { + public static final String all = "all"; + public static final String realtime = "realtime"; + public static final String rest = "rest"; + public static final String webhook = "webhook"; + } } diff --git a/bin/README.md b/bin/README.md deleted file mode 100644 index 0e8fcbe05..000000000 --- a/bin/README.md +++ /dev/null @@ -1,47 +0,0 @@ -## Code generation to keep platform constants in sync - -There are many platform constants that need to be sync on dart side and platform side. -Following are the constants that are being generated: -1. codec types -2. platform method and event names -3. serializable property names for serialization and de-serialization - -#### Generating files - -```bash -cd bin -dart codegen.dart -``` - -#### Template format - -A straight forward templates creating using dart string interpolation: - -#### Template and Context files - -source template files are available in `bin/templates` - and source context data in `bin/codegencontext.dart`. - - -#### Generated files - -These files are generated/modified upon code generation - -1. `lib/src/generated/platformconstants.dart` for use in Flutter/Dart -2. `android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java` for use in Android/Java -3. `ios/Classes/codec/AblyPlatformConstants.h` for use in iOS/Objective-C -4. `ios/Classes/codec/AblyPlatformConstants.m` for use in iOS/Objective-C - -#### When would I need to run code generation? - -When any of the below need to be added/updated -1. A new codec type - required when a new top level serializable object is required (ex: `ErrorInfo` and `ClientOptions`) -2. Platform and event names - when implementing a new method in `MethodChannel` or new event in `EventChannel` -3. A new object needs to be serialized (either top-level, or nested) - - -#### What should I do after running code generation? - -1. Test that everything still works -2. Commit changes to the template(s) -3. Commit changes to the generate files diff --git a/bin/codegen_context.dart b/bin/codegen_context.dart index e360ac433..cd948bcd8 100644 --- a/bin/codegen_context.dart +++ b/bin/codegen_context.dart @@ -38,6 +38,7 @@ Iterable> get _types sync* { // Encryption 'cipherParams', + 'stats', ]; // Custom type values must be over 127. At the time of writing @@ -96,6 +97,7 @@ const List> _platformMethods = [ {'name': 'realtimeHistory', 'value': 'realtimeHistory'}, {'name': 'realtimeTime', 'value': 'realtimeTime'}, {'name': 'restTime', 'value': 'restTime'}, + {'name': 'stats', 'value': 'stats'}, // Push Notifications {'name': 'pushActivate', 'value': 'pushActivate'}, @@ -509,6 +511,44 @@ const List> _objects = [ { 'name': 'CryptoGenerateRandomKey', 'properties': ['keyLength'] + }, + { + 'name': 'Stats', + 'properties': [ + 'all', + 'apiRequests', + 'channels', + 'connections', + 'inbound', + 'intervalId', + 'outbound', + 'persisted', + 'tokenRequests' + ] + }, + { + 'name': 'StatsMessageTypes', + 'properties': ['all', 'messages', 'presence'] + }, + { + 'name': 'StatsMessageCount', + 'properties': ['count', 'data'] + }, + { + 'name': 'StatsRequestCount', + 'properties': ['failed', 'refused', 'succeeded'] + }, + { + 'name': 'StatsResourceCount', + 'properties': ['mean', 'min', 'opened', 'peak', 'refused'] + }, + { + 'name': 'StatsConnectionTypes', + 'properties': ['all', 'plain', 'tls'] + }, + { + 'name': 'StatsMessageTraffic', + 'properties': ['all', 'realtime', 'rest', 'webhook'] } ]; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 06942f913..f530dd118 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2,7 +2,7 @@ PODS: - Ably (1.2.7): - AblyDeltaCodec (= 1.3.2) - msgpack (= 0.4.0) - - ably_flutter (1.2.5): + - ably_flutter (1.2.6): - Ably (= 1.2.7) - Flutter - AblyDeltaCodec (1.3.2) @@ -45,7 +45,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Ably: f627c0c2e4489785b74d33d2c1287edeb9c6a7be - ably_flutter: 43b5908539837dd5020a07a3ed7a6133a3275fa2 + ably_flutter: 7cf00f53f2fbafa97409987fb93075aa254624b3 AblyDeltaCodec: 783d017270de70bbbc0a84e4235297b225d33636 device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a diff --git a/example/lib/ui/rest_sliver.dart b/example/lib/ui/rest_sliver.dart index 78195594a..b1b9caa46 100644 --- a/example/lib/ui/rest_sliver.dart +++ b/example/lib/ui/rest_sliver.dart @@ -91,6 +91,16 @@ class RestSliver extends HookWidget { channel.presence.history(ably.RestHistoryParams(limit: 10)), builder: (context, message, _) => TextRow('Message name', '${message.id}:${message.clientId}:${message.data}')), + PaginatedResultViewer( + title: 'Stats', + subtitle: TextRow('What is it?', 'Realtime stats'), + query: () => rest.stats({"limit": 100, "end": "now"}), + builder: (context, stat, _) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextRow('Statistics ', stat.toString()), + ], + )), ], ); } diff --git a/format-files-dependencies.sh b/format-files-dependencies.sh new file mode 100755 index 000000000..67502f699 --- /dev/null +++ b/format-files-dependencies.sh @@ -0,0 +1,22 @@ + +echo "❓ Checking file formatting dependencies are installed..." + +# 1. You should have Flutter installed (e.g. in `/opt/flutter`) and on your PATH. This provides the Dart formatting tool, `flutter format`. +if ! [ -x "$(command -v flutter)" ]; then + echo "🚨 FAILURE: Missing dependency, you must install Flutter, download it from https://docs.flutter.dev/get-started/install/macos" >&2 + exit 1 +fi + +# 2. Download the release from https://github.com/google/google-java-format/releases, and update your environment variable. This provides the Java formatting tool. +if [ "$GOOGLE_JAVA_FORMAT_PATH" = "" ]; then + echo "🚨 FAILURE: You must download the latest 'all-deps.jar' from https://github.com/google/google-java-format/releases and set GOOGLE_JAVA_FORMAT_PATH to the jar you downloaded, e.g. add the following to ~/.zshrc: 'export GOOGLE_JAVA_FORMAT_PATH=path/to/google_java_format_path.jar'"; + exit 1; +fi + +# 3. Install using brew by running `brew install swiftformat`. This provides the Swift formatting tool. +if ! [ -x "$(command -v swiftformat)" ]; then + echo "🚨 FAILURE: Missing dependency, you must install swiftformat, e.g. run 'brew install swiftformat'" >&2 + exit 1 +fi + +echo "✅ File formatting dependencies exist.\n" \ No newline at end of file diff --git a/format-files.sh b/format-files.sh new file mode 100755 index 000000000..2fc3529f9 --- /dev/null +++ b/format-files.sh @@ -0,0 +1,28 @@ +#!/bin/zsh + +set -e + +MY_PATH=$(dirname "$0") + +source $MY_PATH/format-files-dependencies.sh + +echo "❓ Formatting Dart files..." +flutter format $MY_PATH +echo "✅ Formatted Dart files.\n" + +echo "❓ Formatting Java files..." +JAVA_FILES=$(find $MY_PATH/android -name "*java" -type f -exec ls {} \;) +echo $JAVA_FILES | xargs java \ + --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ + -jar $GOOGLE_JAVA_FORMAT_PATH --replace +echo "✅ Formatted Java files.\n" + +echo "❓ Formatting Swift files..." +SWIFT_VERSION=5.5 +swiftformat --swiftversion $SWIFT_VERSION $MY_PATH/ios +swiftformat --swiftversion $SWIFT_VERSION $MY_PATH/example/ios/Runner +echo "✅ Formatted Swift files.\n" diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 000000000..1c5db5cd9 --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,35 @@ +#!/bin/sh + +set -e +set -o pipefail + +MY_PATH=$(pwd) + +echo "Checking formatting of Dart, Swift and Java files, without modifying them." +echo "To format files, run '. format-files.sh'\n" # Objective-C files are not checked. + +# Redirect output to stderr. +exec 1>&2 + +source $MY_PATH/format-files-dependencies.sh + +echo "❓ Dart file formatting being checked..." +/opt/flutter/bin/flutter format --set-exit-if-changed $MY_PATH +echo "✅ Dart file formatting checked. \n" + +echo "❓ Java file formatting being checked..." +JAVA_FILES=$(find $MY_PATH/android -name "*java" -type f -exec ls {} \;) +echo $JAVA_FILES | xargs java \ + --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ + -jar $GOOGLE_JAVA_FORMAT_PATH --set-exit-if-changed --dry-run +echo "✅ Java file formatting checked. \n" + +echo "❓ Swift file formatting being checked..." +SWIFT_VERSION=5.5 +swiftformat --lint --swiftversion $SWIFT_VERSION $MY_PATH/ios +swiftformat --lint --swiftversion $SWIFT_VERSION $MY_PATH/example/ios/Runner +echo "✅ Swift file formatting checked. \n" diff --git a/ios/Classes/AblyFlutter.m b/ios/Classes/AblyFlutter.m index c857fca38..7431bc42a 100644 --- a/ios/Classes/AblyFlutter.m +++ b/ios/Classes/AblyFlutter.m @@ -2,6 +2,7 @@ #import "AblyFlutter.h" #import +#import #import "codec/AblyFlutterReaderWriter.h" #import "AblyFlutterMessage.h" @@ -9,6 +10,7 @@ #import "AblyFlutterStreamHandler.h" #import "AblyStreamsChannel.h" #import "codec/AblyPlatformConstants.h" +#import "ARTStatsQuery+ParamBuilder.h" #define LOG(fmt, ...) NSLog((@"%@:%d " fmt), [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, ##__VA_ARGS__) @@ -573,6 +575,29 @@ -(void)reset; }]; }; +static const FlutterHandler _stats = ^void(AblyFlutter *const ably, FlutterMethodCall *const call, const FlutterResult result) { + AblyFlutterMessage *const arguments = call.arguments; + AblyInstanceStore *const instanceStore = [ably instanceStore]; + ARTRest *const rest = [instanceStore restFrom:arguments.message]; + AblyFlutterMessage *message = arguments.message; + NSDictionary *paramsDict = message.message; + ARTStatsQuery *query; + if(paramsDict[@"params"]){ + NSDictionary *statsParams = paramsDict[@"params"]; + query = [ARTStatsQuery fromParams:statsParams]; + } + + NSError *statsError; + [rest stats:query callback:^(ARTPaginatedResult *statsResult, ARTErrorInfo *error) { + if(error){ + result(error); + }else{ + result(statsResult); + } + } error:&statsError]; + +}; + static const FlutterHandler _getNextPage = ^void(AblyFlutter *const ably, FlutterMethodCall *const call, const FlutterResult result) { AblyFlutterMessage *const message = call.arguments; AblyInstanceStore *const instanceStore = [ably instanceStore]; @@ -691,6 +716,7 @@ -(instancetype)initWithChannel:(FlutterMethodChannel *const)channel AblyPlatformMethod_releaseRealtimeChannel: _releaseRealtimeChannel, AblyPlatformMethod_realtimeTime:_realtimeTime, AblyPlatformMethod_realtimeTime:_restTime, + AblyPlatformMethod_stats:_stats, // Push Notifications AblyPlatformMethod_pushActivate: PushHandlers.activate, AblyPlatformMethod_pushRequestPermission: PushHandlers.requestPermission, diff --git a/ios/Classes/codec/ARTStatsQuery+ParamBuilder.h b/ios/Classes/codec/ARTStatsQuery+ParamBuilder.h new file mode 100644 index 000000000..33e472172 --- /dev/null +++ b/ios/Classes/codec/ARTStatsQuery+ParamBuilder.h @@ -0,0 +1,12 @@ +// +// Created by Ikbal Kaya on 23/12/2021. +// + +#import +#import "ARTStats.h" + +@interface ARTStatsQuery (ParamBuilder) + ++(ARTStatsQuery *)fromParams:(NSDictionary *)params; + +@end \ No newline at end of file diff --git a/ios/Classes/codec/ARTStatsQuery+ParamBuilder.m b/ios/Classes/codec/ARTStatsQuery+ParamBuilder.m new file mode 100644 index 000000000..1e191cdd5 --- /dev/null +++ b/ios/Classes/codec/ARTStatsQuery+ParamBuilder.m @@ -0,0 +1,47 @@ +// +// Created by Ikbal Kaya on 23/12/2021. +// + +#import "ARTStatsQuery+ParamBuilder.h" +#import "ARTNSDate+ARTUtil.h" + + +@implementation ARTStatsQuery (ParamBuilder) ++ (ARTStatsQuery *)fromParams:(NSDictionary *)params { + ARTStatsQuery *query = [[ARTStatsQuery alloc] init]; + id start = params[@"start"]; + if(start && [start isKindOfClass:[NSNumber class]]){ + query.start = [NSDate artDateFromIntegerMs:[start longLongValue]]; + } + id end = params[@"end"]; + if(end && [end isKindOfClass:[NSNumber class]]){ + query.end = [NSDate artDateFromIntegerMs:[end longLongValue]]; + } + id direction = params[@"direction"]; + if(direction && [direction isKindOfClass:[NSString class]]){ + NSString *directionString = [direction stringValue]; + query.direction = [directionString isEqualToString:@"backwards"] ? ARTQueryDirectionBackwards : ARTQueryDirectionForwards; + } + id limit = params[@"limit"]; + if(limit && [limit isKindOfClass:[NSNumber class]]){ + query.limit = (uint16_t) [limit unsignedIntValue]; + } + + id unit = params[@"unit"]; + if(unit && [unit isKindOfClass:[NSString class]]){ + NSString *unitVal = [unit stringValue]; + // minute, hour, day or month, + if ([unitVal isEqualToString:@"minute"]){ + query.unit = ARTStatsGranularityMinute; + }else if ([unitVal isEqualToString:@"hour"]){ + query.unit = ARTStatsGranularityHour; + }else if ([unitVal isEqualToString:@"day"]){ + query.unit = ARTStatsGranularityDay; + }else if ([unitVal isEqualToString:@"month"]){ + query.unit = ARTStatsGranularityMonth; + } + } + return query; +} + +@end \ No newline at end of file diff --git a/ios/Classes/codec/AblyFlutterWriter.m b/ios/Classes/codec/AblyFlutterWriter.m index 6d7a6126d..b077fd94b 100644 --- a/ios/Classes/codec/AblyFlutterWriter.m +++ b/ios/Classes/codec/AblyFlutterWriter.m @@ -51,6 +51,8 @@ + (UInt8) getType:(id)value{ return CodecTypeRestChannelOptions; } else if ([value isKindOfClass:[ARTCipherParams class]]) { return CodecTypeCipherParams; + } else if([value isKindOfClass:[ARTStats class]]){ + return CodecTypeStats; } return 0; } @@ -71,6 +73,7 @@ + (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", CodecTypeStats]: StatsEncoders.encodeStats, }; return [_handlers objectForKey:[NSString stringWithFormat:@"%@", type]]; } @@ -317,4 +320,5 @@ +(NSString *) encodePresenceAction: (ARTPresenceAction) action { return dictionary; }; + @end diff --git a/ios/Classes/codec/AblyPlatformConstants.h b/ios/Classes/codec/AblyPlatformConstants.h index 3e70044cc..193c4160b 100644 --- a/ios/Classes/codec/AblyPlatformConstants.h +++ b/ios/Classes/codec/AblyPlatformConstants.h @@ -33,6 +33,7 @@ typedef NS_ENUM(UInt8, CodecType) { CodecTypeConnectionStateChange = 152, CodecTypeChannelStateChange = 153, CodecTypeCipherParams = 154, + CodecTypeStats = 155, }; @@ -66,6 +67,7 @@ extern NSString *const AblyPlatformMethod_releaseRealtimeChannel; extern NSString *const AblyPlatformMethod_realtimeHistory; extern NSString *const AblyPlatformMethod_realtimeTime; extern NSString *const AblyPlatformMethod_restTime; +extern NSString *const AblyPlatformMethod_stats; extern NSString *const AblyPlatformMethod_pushActivate; extern NSString *const AblyPlatformMethod_pushDeactivate; extern NSString *const AblyPlatformMethod_pushSubscribeDevice; @@ -400,3 +402,46 @@ extern NSString *const TxCryptoGetParams_key; // key constants for CryptoGenerateRandomKey extern NSString *const TxCryptoGenerateRandomKey_keyLength; + +// key constants for Stats +extern NSString *const TxStats_all; +extern NSString *const TxStats_apiRequests; +extern NSString *const TxStats_channels; +extern NSString *const TxStats_connections; +extern NSString *const TxStats_inbound; +extern NSString *const TxStats_intervalId; +extern NSString *const TxStats_outbound; +extern NSString *const TxStats_persisted; +extern NSString *const TxStats_tokenRequests; + +// key constants for StatsMessageTypes +extern NSString *const TxStatsMessageTypes_all; +extern NSString *const TxStatsMessageTypes_messages; +extern NSString *const TxStatsMessageTypes_presence; + +// key constants for StatsMessageCount +extern NSString *const TxStatsMessageCount_count; +extern NSString *const TxStatsMessageCount_data; + +// key constants for StatsRequestCount +extern NSString *const TxStatsRequestCount_failed; +extern NSString *const TxStatsRequestCount_refused; +extern NSString *const TxStatsRequestCount_succeeded; + +// key constants for StatsResourceCount +extern NSString *const TxStatsResourceCount_mean; +extern NSString *const TxStatsResourceCount_min; +extern NSString *const TxStatsResourceCount_opened; +extern NSString *const TxStatsResourceCount_peak; +extern NSString *const TxStatsResourceCount_refused; + +// key constants for StatsConnectionTypes +extern NSString *const TxStatsConnectionTypes_all; +extern NSString *const TxStatsConnectionTypes_plain; +extern NSString *const TxStatsConnectionTypes_tls; + +// key constants for StatsMessageTraffic +extern NSString *const TxStatsMessageTraffic_all; +extern NSString *const TxStatsMessageTraffic_realtime; +extern NSString *const TxStatsMessageTraffic_rest; +extern NSString *const TxStatsMessageTraffic_webhook; diff --git a/ios/Classes/codec/AblyPlatformConstants.m b/ios/Classes/codec/AblyPlatformConstants.m index 7f3f0acf3..ead5db17b 100644 --- a/ios/Classes/codec/AblyPlatformConstants.m +++ b/ios/Classes/codec/AblyPlatformConstants.m @@ -36,6 +36,7 @@ NSString *const AblyPlatformMethod_realtimeHistory= @"realtimeHistory"; NSString *const AblyPlatformMethod_realtimeTime= @"realtimeTime"; NSString *const AblyPlatformMethod_restTime= @"restTime"; +NSString *const AblyPlatformMethod_stats= @"stats"; NSString *const AblyPlatformMethod_pushActivate= @"pushActivate"; NSString *const AblyPlatformMethod_pushDeactivate= @"pushDeactivate"; NSString *const AblyPlatformMethod_pushSubscribeDevice= @"pushSubscribeDevice"; @@ -370,3 +371,46 @@ // key constants for CryptoGenerateRandomKey NSString *const TxCryptoGenerateRandomKey_keyLength = @"keyLength"; + +// key constants for Stats +NSString *const TxStats_all = @"all"; +NSString *const TxStats_apiRequests = @"apiRequests"; +NSString *const TxStats_channels = @"channels"; +NSString *const TxStats_connections = @"connections"; +NSString *const TxStats_inbound = @"inbound"; +NSString *const TxStats_intervalId = @"intervalId"; +NSString *const TxStats_outbound = @"outbound"; +NSString *const TxStats_persisted = @"persisted"; +NSString *const TxStats_tokenRequests = @"tokenRequests"; + +// key constants for StatsMessageTypes +NSString *const TxStatsMessageTypes_all = @"all"; +NSString *const TxStatsMessageTypes_messages = @"messages"; +NSString *const TxStatsMessageTypes_presence = @"presence"; + +// key constants for StatsMessageCount +NSString *const TxStatsMessageCount_count = @"count"; +NSString *const TxStatsMessageCount_data = @"data"; + +// key constants for StatsRequestCount +NSString *const TxStatsRequestCount_failed = @"failed"; +NSString *const TxStatsRequestCount_refused = @"refused"; +NSString *const TxStatsRequestCount_succeeded = @"succeeded"; + +// key constants for StatsResourceCount +NSString *const TxStatsResourceCount_mean = @"mean"; +NSString *const TxStatsResourceCount_min = @"min"; +NSString *const TxStatsResourceCount_opened = @"opened"; +NSString *const TxStatsResourceCount_peak = @"peak"; +NSString *const TxStatsResourceCount_refused = @"refused"; + +// key constants for StatsConnectionTypes +NSString *const TxStatsConnectionTypes_all = @"all"; +NSString *const TxStatsConnectionTypes_plain = @"plain"; +NSString *const TxStatsConnectionTypes_tls = @"tls"; + +// key constants for StatsMessageTraffic +NSString *const TxStatsMessageTraffic_all = @"all"; +NSString *const TxStatsMessageTraffic_realtime = @"realtime"; +NSString *const TxStatsMessageTraffic_rest = @"rest"; +NSString *const TxStatsMessageTraffic_webhook = @"webhook"; diff --git a/ios/Classes/codec/encoders/StatsEncoders.swift b/ios/Classes/codec/encoders/StatsEncoders.swift new file mode 100644 index 000000000..fd407b8c8 --- /dev/null +++ b/ios/Classes/codec/encoders/StatsEncoders.swift @@ -0,0 +1,72 @@ +// +// Created by Ikbal Kaya on 21/12/2021. +// + +import Foundation + +public class StatsEncoders: NSObject { + @objc + public static let encodeStats: (ARTStats) -> [String: Any] = { stats in + [ + TxStats_all: encodeStatsMessageTypes(stats.all), + TxStats_apiRequests: encodeStatsRequestCount(stats.apiRequests), + TxStats_channels: encodeStatsResourceCount(stats.channels), + TxStats_connections: encodeStatsConnectionTypes(stats.connections), + TxStats_inbound: encodeStatsMessageTraffic(stats.inbound), + TxStats_intervalId: stats.intervalId, + TxStats_outbound: encodeStatsMessageTraffic(stats.outbound), + TxStats_persisted: encodeStatsMessageTypes(stats.persisted), + TxStats_tokenRequests: encodeStatsRequestCount(stats.tokenRequests), + ] + } + + static let encodeStatsMessageTypes: (ARTStatsMessageTypes) -> [String: Any] = { types in + [ + TxStatsMessageTypes_all: encodeStatsMessageCount(types.all), + TxStatsMessageTypes_messages: encodeStatsMessageCount(types.messages), + TxStatsMessageTypes_presence: encodeStatsMessageCount(types.presence), + ] + } + + static let encodeStatsMessageCount: (ARTStatsMessageCount) -> [String: Any] = { count in + [ + TxStatsMessageCount_count: count.count, + TxStatsMessageCount_data: count.data, + ] + } + + static let encodeStatsRequestCount: (ARTStatsRequestCount) -> [String: Any] = { count in + [ + TxStatsRequestCount_failed: count.failed, + TxStatsRequestCount_refused: count.refused, + TxStatsRequestCount_succeeded: count.succeeded, + ] + } + + static let encodeStatsResourceCount: (ARTStatsResourceCount) -> [String: Any] = { count in + [ + TxStatsResourceCount_mean: count.mean, + TxStatsResourceCount_min: count.min, + TxStatsResourceCount_opened: count.opened, + TxStatsResourceCount_peak: count.peak, + TxStatsResourceCount_refused: count.refused, + ] + } + + static let encodeStatsConnectionTypes: (ARTStatsConnectionTypes) -> [String: Any] = { types in + [ + TxStatsConnectionTypes_all: encodeStatsResourceCount(types.all), + TxStatsConnectionTypes_plain: encodeStatsResourceCount(types.plain), + TxStatsConnectionTypes_tls: encodeStatsResourceCount(types.tls), + ] + } + + static let encodeStatsMessageTraffic: (ARTStatsMessageTraffic) -> [String: Any] = { types in + [ + TxStatsMessageTraffic_all: encodeStatsMessageTypes(types.all), + TxStatsMessageTraffic_realtime: encodeStatsMessageTypes(types.realtime), + TxStatsMessageTraffic_rest: encodeStatsMessageTypes(types.rest), + TxStatsMessageTraffic_webhook: encodeStatsMessageTypes(types.webhook), + ] + } +} diff --git a/lib/src/generated/platform_constants.dart b/lib/src/generated/platform_constants.dart index a744c4f39..012075f58 100644 --- a/lib/src/generated/platform_constants.dart +++ b/lib/src/generated/platform_constants.dart @@ -33,6 +33,7 @@ class CodecTypes { static const int connectionStateChange = 152; static const int channelStateChange = 153; static const int cipherParams = 154; + static const int stats = 155; } class PlatformMethod { @@ -66,6 +67,7 @@ class PlatformMethod { static const String realtimeHistory = 'realtimeHistory'; static const String realtimeTime = 'realtimeTime'; static const String restTime = 'restTime'; + static const String stats = 'stats'; static const String pushActivate = 'pushActivate'; static const String pushDeactivate = 'pushDeactivate'; static const String pushSubscribeDevice = 'pushSubscribeDevice'; @@ -451,3 +453,53 @@ class TxCryptoGetParams { class TxCryptoGenerateRandomKey { static const String keyLength = 'keyLength'; } + +class TxStats { + static const String all = 'all'; + static const String apiRequests = 'apiRequests'; + static const String channels = 'channels'; + static const String connections = 'connections'; + static const String inbound = 'inbound'; + static const String intervalId = 'intervalId'; + static const String outbound = 'outbound'; + static const String persisted = 'persisted'; + static const String tokenRequests = 'tokenRequests'; +} + +class TxStatsMessageTypes { + static const String all = 'all'; + static const String messages = 'messages'; + static const String presence = 'presence'; +} + +class TxStatsMessageCount { + static const String count = 'count'; + static const String data = 'data'; +} + +class TxStatsRequestCount { + static const String failed = 'failed'; + static const String refused = 'refused'; + static const String succeeded = 'succeeded'; +} + +class TxStatsResourceCount { + static const String mean = 'mean'; + static const String min = 'min'; + static const String opened = 'opened'; + static const String peak = 'peak'; + static const String refused = 'refused'; +} + +class TxStatsConnectionTypes { + static const String all = 'all'; + static const String plain = 'plain'; + static const String tls = 'tls'; +} + +class TxStatsMessageTraffic { + static const String all = 'all'; + static const String realtime = 'realtime'; + static const String rest = 'rest'; + static const String webhook = 'webhook'; +} diff --git a/lib/src/platform/src/realtime/realtime.dart b/lib/src/platform/src/realtime/realtime.dart index 695d6a3e0..9912f6077 100644 --- a/lib/src/platform/src/realtime/realtime.dart +++ b/lib/src/platform/src/realtime/realtime.dart @@ -100,13 +100,6 @@ class Realtime extends PlatformObject { // throw UnimplementedError(); // } - /// gets stats based on params as a [PaginatedResult] - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSC6 - // Future> stats([Map? params]) { - // throw UnimplementedError(); - // } - /// returns server time /// /// https://docs.ably.com/client-lib-development-guide/features/#RSC16 diff --git a/lib/src/platform/src/rest/rest.dart b/lib/src/platform/src/rest/rest.dart index 8c2e8e4a5..33c5690f5 100644 --- a/lib/src/platform/src/rest/rest.dart +++ b/lib/src/platform/src/rest/rest.dart @@ -58,9 +58,13 @@ class Rest extends PlatformObject { /// gets stats based on params as a [PaginatedResult] /// /// https://docs.ably.com/client-lib-development-guide/features/#RSC6 - // Future> stats([Map? params]) { - // throw UnimplementedError(); - // } + Future> stats([Map? params]) async { + final message = await invokeRequest( + PlatformMethod.stats, {TxTransportKeys.params: params}); + return PaginatedResult.fromAblyMessage( + AblyMessage.castFrom(message), + ); + } /// returns server time ///