diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17dae29cc..eda645d8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,14 +75,14 @@ Some files in the project are generated to maintain sync between ## Implementing new codec types -1. Add new type along with value in `_types` list at [bin/codegencontext.dart](bin/codegencontext.dart) -2. Add an object definition with object name and its properties to `objects` list at [bin/codegencontext.dart](bin/codegencontext.dart) +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) This will create `Tx` under which all properties are accessible. Generate platform constants and continue -3. update `getCodecType` in [lib.src.codec.Codec](lib/src/codec.dart) so new codec type is returned based on runtime type -4. update `codecPair` in [lib.src.codec.Codec](lib/src/codec.dart) so new encoder/decoder is assigned for new type +3. update `getCodecType` in [Codec.dart](lib/src/platform/src/codec.dart) so new codec type is returned based on runtime type +4. update `codecPair` in [Codec.dart](lib/src/platform/src/codec.dart) so new encoder/decoder is assigned for new type 5. update `writeValue` in [android.src.main.java.io.ably.flutter.plugin.AblyMessageCodec](android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java) so new codec type is obtained from runtime type 6. update `codecMap` in [android.src.main.java.io.ably.flutter.plugin.AblyMessageCodec](android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java) @@ -94,7 +94,7 @@ Generate platform constants and continue ## Implementing new platform methods -1. Add new method name in `_platformMethods` list at [bin/codegencontext.dart](bin/codegencontext.dart) +1. Add new method name in `_platformMethods` list at [bin/codegen_context.dart](bin/codegen_context.dart) Generate platform constants and use wherever required diff --git a/android/src/main/java/io/ably/flutter/plugin/AblyLibrary.java b/android/src/main/java/io/ably/flutter/plugin/AblyLibrary.java index 7729a2ab9..1d7f99509 100644 --- a/android/src/main/java/io/ably/flutter/plugin/AblyLibrary.java +++ b/android/src/main/java/io/ably/flutter/plugin/AblyLibrary.java @@ -7,7 +7,6 @@ import io.ably.lib.types.AblyException; import io.ably.lib.types.AsyncPaginatedResult; import io.ably.lib.types.ClientOptions; -import io.ably.lib.types.PaginatedResult; class AblyLibrary { diff --git a/bin/codegen.dart b/bin/codegen.dart index 1324934d5..d58448ddb 100644 --- a/bin/codegen.dart +++ b/bin/codegen.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'codegencontext.dart' show context; +import 'codegen_context.dart' show context; import 'templates/platformconstants.dart.dart' as dart_template; import 'templates/platformconstants.h.dart' as objc_header_template; import 'templates/platformconstants.java.dart' as java_template; @@ -12,7 +12,7 @@ const String projectRoot = '../'; Map toGenerate = { // input template method vs output file path - dart_template.$: '${projectRoot}lib/src/generated/platformconstants.dart', + dart_template.$: '${projectRoot}lib/src/generated/platform_constants.dart', java_template.$: '${projectRoot}android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java', objc_header_template.$: diff --git a/bin/codegencontext.dart b/bin/codegen_context.dart similarity index 100% rename from bin/codegencontext.dart rename to bin/codegen_context.dart diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 7afcfe760..6ee4539f9 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -33,7 +33,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Ably: 863d35bb6a6aa5fc537304ef19f85cfa51d1bbde - ably_flutter: b37439c7b63f4b3e44f4674c5bfb56452d193ad0 + ably_flutter: 31894faaa659febdd3a57cdc03c1fc2c218d2c95 AblyDeltaCodec: 6123f31df5b04a0f5452968505a46ba16a9eb689 Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c msgpack: a14de9216d29cfd0a7aff5af5150601a27e899a4 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 0038c0985..8018149b1 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -171,7 +171,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = S38X8U35U5; + DevelopmentTeam = XXY98AVDR6; }; }; }; @@ -377,7 +377,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = S38X8U35U5; + DEVELOPMENT_TEAM = XXY98AVDR6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -506,7 +506,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = S38X8U35U5; + DEVELOPMENT_TEAM = XXY98AVDR6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -530,7 +530,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = S38X8U35U5; + DEVELOPMENT_TEAM = XXY98AVDR6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/lib/ably_flutter.dart b/lib/ably_flutter.dart index 235ef6b4c..17b49b666 100644 --- a/lib/ably_flutter.dart +++ b/lib/ably_flutter.dart @@ -1,9 +1,12 @@ -export 'src/generated/platformconstants.dart'; -export 'src/impl/paginated_result.dart'; -export 'src/impl/realtime/channels.dart'; -export 'src/impl/realtime/connection.dart'; -export 'src/impl/realtime/realtime.dart'; -export 'src/impl/rest/channels.dart'; -export 'src/impl/rest/rest.dart'; -export 'src/info.dart'; -export 'src/spec/spec.dart'; +library ably_flutter; + +export 'src/authentication/authentication.dart'; +export 'src/common/common.dart'; +export 'src/error/error.dart'; +export 'src/logging/logging.dart'; +export 'src/message/message.dart'; +export 'src/platform/platform.dart'; +export 'src/push_notifications/push_notifications.dart'; +export 'src/realtime/realtime.dart'; +export 'src/rest/rest.dart'; +export 'src/stats/stats.dart'; diff --git a/lib/src/authentication/authentication.dart b/lib/src/authentication/authentication.dart new file mode 100644 index 000000000..692a004d2 --- /dev/null +++ b/lib/src/authentication/authentication.dart @@ -0,0 +1,8 @@ +export 'src/auth.dart'; +export 'src/auth_options.dart'; +export 'src/cipher_params.dart'; +export 'src/client_options.dart'; +export 'src/http_auth_type.dart'; +export 'src/token_details.dart'; +export 'src/token_params.dart'; +export 'src/token_request.dart'; diff --git a/lib/src/spec/auth.dart b/lib/src/authentication/src/auth.dart similarity index 96% rename from lib/src/spec/auth.dart rename to lib/src/authentication/src/auth.dart index d0f516173..7ba5ab098 100644 --- a/lib/src/spec/auth.dart +++ b/lib/src/authentication/src/auth.dart @@ -1,5 +1,4 @@ -import 'common.dart'; -import 'rest/options.dart'; +import '../authentication.dart'; /// [Auth] object provides a way to create [TokenRequest] objects /// with [createTokenRequest] method or create Ably Tokens with diff --git a/lib/src/authentication/src/auth_options.dart b/lib/src/authentication/src/auth_options.dart new file mode 100644 index 000000000..396ca2328 --- /dev/null +++ b/lib/src/authentication/src/auth_options.dart @@ -0,0 +1,93 @@ +import '../authentication.dart'; + +/// A class providing configurable authentication options used when +/// authenticating or issuing tokens explicitly. +/// +/// These options are used when invoking Auth#authorize, Auth#requestToken, +/// Auth#createTokenRequest and Auth#authorize. +/// +/// https://docs.ably.com/client-lib-development-guide/features/#AO1 +abstract class AuthOptions { + /// initializes an instance without any defaults + AuthOptions(); + + /// Convenience constructor, to create an AuthOptions based + /// on the key string obtained from the application dashboard. + /// param [key]: the full key string as obtained from the dashboard + AuthOptions.fromKey(String key) { + if (key.contains(':')) { + this.key = key; + } else { + tokenDetails = TokenDetails(key); + } + } + + /// A function which is called when a new token is required. + /// + /// The role of the callback is to either generate a signed [TokenRequest] + /// which may then be submitted automatically by the library to + /// the Ably REST API requestToken; or to provide a valid token + /// as a [TokenDetails] object. + /// https://docs.ably.com/client-lib-development-guide/features/#AO2b + AuthCallback? authCallback; + + /// A URL that the library may use to obtain + /// a token String (in plain text format), + /// or a signed [TokenRequest] or [TokenDetails] (in JSON format). + /// + /// https://docs.ably.com/client-lib-development-guide/features/#AO2c + String? authUrl; + + /// HTTP Method used when a request is made using authURL + /// + /// defaults to 'GET', supports 'GET' and 'POST' + /// https://docs.ably.com/client-lib-development-guide/features/#AO2d + String? authMethod; + + /// Full Ably key string, as obtained from dashboard, + /// used when signing token requests locally + /// + /// https://docs.ably.com/client-lib-development-guide/features/#AO2a + String? key; + + /// An authentication token issued for this application + /// + /// https://docs.ably.com/client-lib-development-guide/features/#AO2i + TokenDetails? tokenDetails; + + /// Headers to be included in any request made to the [authUrl] + /// + /// https://docs.ably.com/client-lib-development-guide/features/#AO2e + Map? authHeaders; + + /// Additional params to be included in any request made to the [authUrl] + /// + /// As query params in the case of GET + /// and as form-encoded in the body in the case of POST + /// https://docs.ably.com/client-lib-development-guide/features/#AO2f + Map? authParams; + + /// If true, the library will when issuing a token request query + /// the Ably system for the current time instead of relying on a + /// locally-available time of day. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#AO2g + bool? queryTime; + + /// Token Auth is used if useTokenAuth is set to true + /// + /// or if useTokenAuth is unspecified and any one of + /// [authUrl], [authCallback], token, or [TokenDetails] is provided + /// https://docs.ably.com/client-lib-development-guide/features/#RSA4 + bool? useTokenAuth; + +// TODO(tiholic) missing token attribute here +// see: https://docs.ably.com/client-lib-development-guide/features/#AO2h +} + +/// Function-type alias implemented by a function that provides either tokens, +/// or signed token requests, in response to a request with given token params. +/// +/// Java: io.ably.lib.rest.Auth.TokenCallback.getTokenRequest(TokenParams) +/// returns either a [String] token or [TokenDetails] or [TokenRequest] +typedef AuthCallback = Future Function(TokenParams params); diff --git a/lib/src/authentication/src/cipher_params.dart b/lib/src/authentication/src/cipher_params.dart new file mode 100644 index 000000000..da3bb60fe --- /dev/null +++ b/lib/src/authentication/src/cipher_params.dart @@ -0,0 +1,26 @@ +/// params to configure encryption for a channel +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TZ1 +abstract class CipherParams { + /// Specifies the algorithm to use for encryption + /// + /// Default is AES. Currently only AES is supported. + /// https://docs.ably.com/client-lib-development-guide/features/#TZ2a + String? algorithm; + + /// private key used to encrypt and decrypt payloads + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TZ2d + dynamic key; + + /// the length in bits of the key + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TZ2b + int? keyLength; + + /// Specify cipher mode + /// + /// Default is CBC. Currently only CBC is supported + /// https://docs.ably.com/client-lib-development-guide/features/#TZ2c + String? mode; +} diff --git a/lib/src/spec/rest/options.dart b/lib/src/authentication/src/client_options.dart similarity index 65% rename from lib/src/spec/rest/options.dart rename to lib/src/authentication/src/client_options.dart index 6af7ab943..968695ed4 100644 --- a/lib/src/spec/rest/options.dart +++ b/lib/src/authentication/src/client_options.dart @@ -1,105 +1,6 @@ -import '../../../ably_flutter.dart'; -import '../common.dart'; - -/// Function-type alias implemented by a function that provides either tokens, -/// or signed token requests, in response to a request with given token params. -/// -/// Java: io.ably.lib.rest.Auth.TokenCallback.getTokenRequest(TokenParams) -/// returns either a [String] token or [TokenDetails] or [TokenRequest] -typedef AuthCallback = Future Function(TokenParams params); - -/// A class providing configurable authentication options used when -/// authenticating or issuing tokens explicitly. -/// -/// These options are used when invoking Auth#authorize, Auth#requestToken, -/// Auth#createTokenRequest and Auth#authorize. -/// -/// https://docs.ably.com/client-lib-development-guide/features/#AO1 -abstract class AuthOptions { - /// initializes an instance without any defaults - AuthOptions(); - - /// Convenience constructor, to create an AuthOptions based - /// on the key string obtained from the application dashboard. - /// param [key]: the full key string as obtained from the dashboard - AuthOptions.fromKey(String key) { - if (key.contains(':')) { - this.key = key; - } else { - tokenDetails = TokenDetails(key); - } - } - - /// A function which is called when a new token is required. - /// - /// The role of the callback is to either generate a signed [TokenRequest] - /// which may then be submitted automatically by the library to - /// the Ably REST API requestToken; or to provide a valid token - /// as a [TokenDetails] object. - /// https://docs.ably.com/client-lib-development-guide/features/#AO2b - AuthCallback? authCallback; - - /// A URL that the library may use to obtain - /// a token String (in plain text format), - /// or a signed [TokenRequest] or [TokenDetails] (in JSON format). - /// - /// https://docs.ably.com/client-lib-development-guide/features/#AO2c - String? authUrl; - - /// HTTP Method used when a request is made using authURL - /// - /// defaults to 'GET', supports 'GET' and 'POST' - /// https://docs.ably.com/client-lib-development-guide/features/#AO2d - String? authMethod; - - /// Full Ably key string, as obtained from dashboard, - /// used when signing token requests locally - /// - /// https://docs.ably.com/client-lib-development-guide/features/#AO2a - String? key; - - /// An authentication token issued for this application - /// - /// https://docs.ably.com/client-lib-development-guide/features/#AO2i - TokenDetails? tokenDetails; - - /// Headers to be included in any request made to the [authUrl] - /// - /// https://docs.ably.com/client-lib-development-guide/features/#AO2e - Map? authHeaders; - - /// Additional params to be included in any request made to the [authUrl] - /// - /// As query params in the case of GET - /// and as form-encoded in the body in the case of POST - /// https://docs.ably.com/client-lib-development-guide/features/#AO2f - Map? authParams; - - /// If true, the library will when issuing a token request query - /// the Ably system for the current time instead of relying on a - /// locally-available time of day. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#AO2g - bool? queryTime; - - /// Token Auth is used if useTokenAuth is set to true - /// - /// or if useTokenAuth is unspecified and any one of - /// [authUrl], [authCallback], token, or [TokenDetails] is provided - /// https://docs.ably.com/client-lib-development-guide/features/#RSA4 - bool? useTokenAuth; - -// TODO(tiholic) missing token attribute here -// see: https://docs.ably.com/client-lib-development-guide/features/#AO2h -} - -/// Custom handler to handle SDK log messages -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TO3c -typedef LogHandler = void Function({ - String? msg, - AblyException? exception, -}); +import '../../logging/logging.dart'; +import '../../realtime/realtime.dart'; +import '../authentication.dart'; /// Ably library options used when instancing a REST or Realtime client library /// diff --git a/lib/src/authentication/src/http_auth_type.dart b/lib/src/authentication/src/http_auth_type.dart new file mode 100644 index 000000000..47ca033de --- /dev/null +++ b/lib/src/authentication/src/http_auth_type.dart @@ -0,0 +1,11 @@ +/// Java: io.ably.lib.http.HttpAuth.Type +enum HttpAuthType { + /// indicates basic authentication + basic, + + /// digest authentication + digest, + + /// Token auth + xAblyToken, +} diff --git a/lib/src/authentication/src/token_details.dart b/lib/src/authentication/src/token_details.dart new file mode 100644 index 000000000..ca901b857 --- /dev/null +++ b/lib/src/authentication/src/token_details.dart @@ -0,0 +1,53 @@ +/// Response to a `requestToken` request +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TD1 +class TokenDetails { + /// https://docs.ably.com/client-lib-development-guide/features/#TD2 + String? token; + + /// Token expiry time in milliseconds + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TD3 + int? expires; + + /// the time the token was issued in milliseconds + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TD4 + int? issued; + + /// stringified capabilities JSON + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TD5 + String? capability; + + /// Client ID assigned to the token. + /// + /// If [clientId] is not set (i.e. null), then the token is prohibited + /// from assuming a clientId in any operations, however if clientId + /// is a wildcard string '*', then the token is permitted to assume + /// any clientId. Any other string value for clientId implies that the + /// clientId is both enforced and assumed for all operations for this token + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TD6 + String? clientId; + + /// instantiates a [TokenDetails] with provided values + TokenDetails( + this.token, { + this.expires, + this.issued, + this.capability, + this.clientId, + }); + + /// Creates an instance from the map + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TD7 + TokenDetails.fromMap(Map map) { + token = map['token'] as String?; + expires = map['expires'] as int?; + issued = map['issued'] as int?; + capability = map['capability'] as String?; + clientId = map['clientId'] as String?; + } +} diff --git a/lib/src/authentication/src/token_params.dart b/lib/src/authentication/src/token_params.dart new file mode 100644 index 000000000..9929a9abc --- /dev/null +++ b/lib/src/authentication/src/token_params.dart @@ -0,0 +1,63 @@ +import '../authentication.dart'; + +/// A class providing parameters of a token request. +/// +/// Parameters for a token request +/// +/// [Auth.authorize], [Auth.requestToken] and [Auth.createTokenRequest] +/// accept an instance of TokenParams as a parameter +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TK1 +class TokenParams { + /// Capability of the token. + /// + /// If the token request is successful, the capability of the + /// returned token will be the intersection of this [capability] + /// with the capability of the issuing key. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TK2b + String? capability; + + /// A clientId to associate with this token. + /// + /// The generated token may be used to authenticate as this clientId. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TK2c + String? clientId; + + /// An opaque nonce string of at least 16 characters to ensure uniqueness. + /// + /// Timestamps, in conjunction with the nonce, + /// are used to prevent requests from being replayed + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TK2d + String? nonce; + + /// The timestamp (in millis since the epoch) of this request. + /// + /// Timestamps, in conjunction with the nonce, are used to prevent + /// token requests from being replayed. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TK2d + DateTime? timestamp; + + /// Requested time to live for the token. + /// + /// If the token request is successful, the TTL of the returned + /// token will be less than or equal to this value depending on + /// application settings and the attributes of the issuing key. + /// + /// 0 means Ably will set it to the default value + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TK2a + int? ttl; + + /// instantiates a [TokenParams] with provided values + TokenParams({ + this.capability, + this.clientId, + this.nonce, + this.timestamp, + this.ttl, + }); +} diff --git a/lib/src/authentication/src/token_request.dart b/lib/src/authentication/src/token_request.dart new file mode 100644 index 000000000..f87c5bece --- /dev/null +++ b/lib/src/authentication/src/token_request.dart @@ -0,0 +1,71 @@ +/// spec: https://docs.ably.com/client-lib-development-guide/features/#TE1 +class TokenRequest { + /// [keyName] is the first part of Ably API Key. + /// + /// provided keyName will be used to authorize requests made to Ably. + /// spec: https://docs.ably.com/client-lib-development-guide/features/#TE2 + /// + /// More details about Ably API Key: + /// https://docs.ably.com/client-lib-development-guide/features/#RSA11 + String? keyName; + + /// An opaque nonce string of at least 16 characters to ensure + /// uniqueness of this request. Any subsequent request using the + /// same nonce will be rejected. + /// + /// spec: + /// https://docs.ably.com/client-lib-development-guide/features/#TE2 + /// https://docs.ably.com/client-lib-development-guide/features/#TE5 + String? nonce; + + /// The "Message Authentication Code" for this request. + /// + /// See the Ably Authentication documentation for more details. + /// spec: https://docs.ably.com/client-lib-development-guide/features/#TE2 + String? mac; + + /// stringified capabilities JSON + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TE3 + String? capability; + + /// Client ID assigned to the tokenRequest. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TE2 + String? clientId; + + /// timestamp long – The timestamp (in milliseconds since the epoch) + /// of this request. Timestamps, in conjunction with the nonce, + /// are used to prevent requests from being replayed + /// + /// spec: https://docs.ably.com/client-lib-development-guide/features/#TE5 + DateTime? timestamp; + + /// ttl attribute represents time to live (expiry) + /// of this token in milliseconds + /// + /// spec: https://docs.ably.com/client-lib-development-guide/features/#TE4 + int? ttl; + + /// instantiates a [TokenRequest] with provided values + TokenRequest({ + this.keyName, + this.nonce, + this.clientId, + this.mac, + this.capability, + this.timestamp, + this.ttl, + }); + + /// spec: https://docs.ably.com/client-lib-development-guide/features/#TE7 + TokenRequest.fromMap(Map map) { + keyName = map['keyName'] as String?; + nonce = map['nonce'] as String?; + mac = map['mac'] as String?; + capability = map['capability'] as String?; + clientId = map['clientId'] as String?; + timestamp = DateTime.fromMillisecondsSinceEpoch(map['timestamp'] as int); + ttl = map['ttl'] as int?; + } +} diff --git a/lib/src/common/common.dart b/lib/src/common/common.dart new file mode 100644 index 000000000..28a3801cf --- /dev/null +++ b/lib/src/common/common.dart @@ -0,0 +1,6 @@ +export 'src/ably_base.dart'; +export 'src/channels.dart'; +export 'src/event_emitter.dart'; +export 'src/event_listener.dart'; +export 'src/http_paginated_response.dart'; +export 'src/paginated_result.dart'; diff --git a/lib/src/spec/rest/ably_base.dart b/lib/src/common/src/ably_base.dart similarity index 89% rename from lib/src/spec/rest/ably_base.dart rename to lib/src/common/src/ably_base.dart index d8dc85486..6a931d818 100644 --- a/lib/src/spec/rest/ably_base.dart +++ b/lib/src/common/src/ably_base.dart @@ -1,8 +1,8 @@ -import '../../../ably_flutter.dart'; -import '../auth.dart'; +import '../../authentication/authentication.dart'; +import '../../platform/platform.dart'; +import '../../push_notifications/push_notifications.dart'; +import '../../stats/stats.dart'; import '../common.dart'; -import '../push/push.dart'; -import 'options.dart'; /// A base class for [Rest] and [Realtime] abstract class AblyBase { diff --git a/lib/src/common/src/channels.dart b/lib/src/common/src/channels.dart new file mode 100644 index 000000000..fe1e4b22a --- /dev/null +++ b/lib/src/common/src/channels.dart @@ -0,0 +1,69 @@ +import 'package:meta/meta.dart'; + +import '../../platform/platform.dart'; + +/// A collection of Channel objects accessible +/// through [Rest.channels] or [Realtime.channels] +abstract class Channels extends Iterable { + /// stores channel name vs instance of [ChannelType] + final _channels = {}; + + /// creates a channel with provided name and options + /// + /// This is a private method to be overridden by implementation classes + @protected + ChannelType createChannel(String name); + + /// creates a channel with [name]. + /// + /// Doesn't create a channel instance on platform side yet. + ChannelType get(String name) { + if (_channels[name] == null) { + _channels[name] = createChannel(name); + } + return _channels[name]!; + } + + /// returns true if a channel exists [name] + bool exists(String name) => _channels[name] != null; + + /// Same as [get]. + ChannelType operator [](String name) => get(name); + + @override + Iterator get iterator => + _ChannelIterator(_channels.values.toList()); + + /// releases channel with [name] + void release(String name) { + _channels.remove(name); + } +} + +/// Iterator class for [Channels.iterator] +class _ChannelIterator implements Iterator { + _ChannelIterator(this._channels); + + final List _channels; + + int _currentIndex = 0; + + T? _currentChannel; + + @override + T get current { + if (_currentChannel == null) { + throw StateError('Not iterating'); + } + return _currentChannel!; + } + + @override + bool moveNext() { + if (_currentIndex == _channels.length) { + return false; + } + _currentChannel = _channels[_currentIndex++]; + return true; + } +} diff --git a/lib/src/common/src/event_emitter.dart b/lib/src/common/src/event_emitter.dart new file mode 100644 index 000000000..215176765 --- /dev/null +++ b/lib/src/common/src/event_emitter.dart @@ -0,0 +1,15 @@ +import 'dart:async'; + +/// Interface implemented by Ably classes that can emit events, +/// offering the capability to create listeners for those events. +/// [E] is type of event to listen for +/// [G] is the instance which will be passed back in streams. +/// +/// +/// There is no `off` API as in other Ably client libraries as on returns a +/// [Stream] which can be subscribed for, and that subscription can be cancelled +/// using [StreamSubscription.cancel] API +abstract class EventEmitter { + /// Create a listener, with which registrations may be made. + Stream on([E? event]); +} diff --git a/lib/src/common/src/event_listener.dart b/lib/src/common/src/event_listener.dart new file mode 100644 index 000000000..53b091fe6 --- /dev/null +++ b/lib/src/common/src/event_listener.dart @@ -0,0 +1,12 @@ +/// Interface implemented by event listeners, returned by event emitters. +abstract class EventListener { + /// Register for all events (no parameter), or a specific event. + Stream on([E? event]); + + /// Register for a single occurrence of any event (no parameter), + /// or a specific event. + Future once([E? event]); + + /// Remove registrations for this listener, irrespective of type. + Future off(); +} diff --git a/lib/src/common/src/http_paginated_response.dart b/lib/src/common/src/http_paginated_response.dart new file mode 100644 index 000000000..307c5e685 --- /dev/null +++ b/lib/src/common/src/http_paginated_response.dart @@ -0,0 +1,50 @@ +import '../common.dart'; + +/// The response from an HTTP request containing an empty or +/// JSON-encodable object response +/// +/// [T] can be a [Map] or [List] +/// +/// https://docs.ably.com/client-lib-development-guide/features/#HP1 +abstract class HttpPaginatedResponse extends PaginatedResultInterface { + /// HTTP status code for the response + /// + /// https://docs.ably.com/client-lib-development-guide/features/#HP4 + int? statusCode; + + /// indicates whether the request is successful + /// + /// true when 200 <= [statusCode] < 300 + /// + /// https://docs.ably.com/client-lib-development-guide/features/#HP5 + bool? success; + + /// Value from X-Ably-Errorcode HTTP header, if available in response + /// + /// https://docs.ably.com/client-lib-development-guide/features/#HP6 + int? errorCode; + + /// Value from X-Ably-Errormessage HTTP header, if available in response + /// + /// https://docs.ably.com/client-lib-development-guide/features/#HP7 + String? errorMessage; + + /// Array of key value pairs of each response header + /// + /// https://docs.ably.com/client-lib-development-guide/features/#HP8 + List>? headers; + + /// returns a new HttpPaginatedResponse loaded with the next page of results. + /// + /// If there are no further pages, then null is returned. + /// https://docs.ably.com/client-lib-development-guide/features/#HP2 + @override + Future> next(); + + /// returns a new HttpPaginatedResponse with the first page of results + /// + /// If there are no further pages, then null is returned. + /// https://docs.ably.com/client-lib-development-guide/features/#HP2 + @override + Future> first(); +} diff --git a/lib/src/common/src/paginated_result.dart b/lib/src/common/src/paginated_result.dart new file mode 100644 index 000000000..409249a22 --- /dev/null +++ b/lib/src/common/src/paginated_result.dart @@ -0,0 +1,33 @@ +/// PaginatedResult [TG1](https://docs.ably.com/client-lib-development-guide/features/#TG1) +/// +/// A type that represents page results from a paginated query. +/// The response is accompanied by metadata that indicates the +/// relative queries available. +abstract class PaginatedResultInterface { + /// items contain page of results + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TG3 + List get items; + + /// returns a new PaginatedResult loaded with the next page of results. + /// + /// If there are no further pages, then null is returned. + /// https://docs.ably.com/client-lib-development-guide/features/#TG4 + Future> next(); + + /// returns a new PaginatedResult with the first page of results + /// + /// If there are no further pages, then null is returned. + /// https://docs.ably.com/client-lib-development-guide/features/#TG5 + Future> first(); + + /// returns true if there are further pages + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TG6 + bool hasNext(); + + /// returns true if this page is the last page + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TG7 + bool isLast(); +} diff --git a/lib/src/error/error.dart b/lib/src/error/error.dart new file mode 100644 index 000000000..773a066bb --- /dev/null +++ b/lib/src/error/error.dart @@ -0,0 +1,4 @@ +export 'src/ably_exception.dart'; +export 'src/error_codes.dart'; +export 'src/error_info.dart'; +export 'src/timeouts.dart'; diff --git a/lib/src/error/src/ably_exception.dart b/lib/src/error/src/ably_exception.dart new file mode 100644 index 000000000..3cf0be95f --- /dev/null +++ b/lib/src/error/src/ably_exception.dart @@ -0,0 +1,40 @@ +import 'package:flutter/services.dart'; + +import 'error_info.dart'; + +/// An exception generated by the native client library called by this plugin +class AblyException implements Exception { + /// platform error code + /// + /// Mostly used for storing [PlatformException.code] + final String? code; + + /// platform error message + /// + /// Mostly used for storing [PlatformException.message] + final String? message; + + /// error message from ably native sdk + final ErrorInfo? errorInfo; + + /// initializes with no defaults + AblyException([ + this.code, + this.message, + this.errorInfo, + ]); + + /// create AblyException from [PlatformException] + AblyException.fromPlatformException(PlatformException exception) + : code = exception.code, + message = exception.message, + errorInfo = exception.details as ErrorInfo?; + + @override + String toString() { + if (message == null) { + return 'AblyException (${(code == null) ? "" : '$code '})'; + } + return 'AblyException: $message (${(code == null) ? "" : '$code '})'; + } +} diff --git a/lib/src/error/src/error_codes.dart b/lib/src/error/src/error_codes.dart new file mode 100644 index 000000000..7f5eaf63f --- /dev/null +++ b/lib/src/error/src/error_codes.dart @@ -0,0 +1,9 @@ +/// Static error codes used inside the SDK +class ErrorCodes { + /// error code sent from platform in case if response from + /// authCallback is not of valid type. + /// When this is encountered, flutter side will re-try the + /// method call after responding to authCallback method channel + /// call triggered from platform side + static const int authCallbackFailure = 80019; +} diff --git a/lib/src/error/src/error_info.dart b/lib/src/error/src/error_info.dart new file mode 100644 index 000000000..b9971b793 --- /dev/null +++ b/lib/src/error/src/error_info.dart @@ -0,0 +1,45 @@ +import '../error.dart'; + +/// An [AblyException] encapsulates [ErrorInfo] which carries details +/// about information related to Ably-specific error [code], +/// generic [statusCode], error [message], +/// link to error related documentation as [href], +/// [requestId] and [cause] of this exception +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TI1 +class ErrorInfo { + /// ably specific error code + final int? code; + + /// link to error related documentation as + final String? href; + + /// error message + final String? message; + + /// cause for the error + final ErrorInfo? cause; + + /// generic status code + final int? statusCode; + + /// request id which triggered this exception + final String? requestId; + + /// instantiates a [ErrorInfo] with provided values + ErrorInfo({ + this.code, + this.href, + this.message, + this.cause, + this.statusCode, + this.requestId, + }); + + @override + String toString() => 'ErrorInfo' + ' message=$message' + ' code=$code' + ' statusCode=$statusCode' + ' href=$href'; +} diff --git a/lib/src/error/src/timeouts.dart b/lib/src/error/src/timeouts.dart new file mode 100644 index 000000000..18eedd063 --- /dev/null +++ b/lib/src/error/src/timeouts.dart @@ -0,0 +1,14 @@ +/// Static timeouts used inside the SDK +class Timeouts { + /// max time allowed for retrying an operation for auth failure + /// in case of usage of authCallback + static const retryOperationOnAuthFailure = Duration(seconds: 30); + + /// max time dart side will wait for platform side to respond with a + /// platform handle + static const acquireHandleTimeout = Duration(seconds: 5); + + /// max time dart side will wait for platform side to respond after + /// initializing an Ably instance on platform side + static const initializeTimeout = Duration(seconds: 5); +} diff --git a/lib/src/generated/platformconstants.dart b/lib/src/generated/platform_constants.dart similarity index 100% rename from lib/src/generated/platformconstants.dart rename to lib/src/generated/platform_constants.dart diff --git a/lib/src/info.dart b/lib/src/info.dart deleted file mode 100644 index 87e65d415..000000000 --- a/lib/src/info.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'generated/platformconstants.dart' show PlatformMethod; -import 'platform.dart' show invokePlatformMethod; - -/// Get android/iOS platform version -Future platformVersion() async => - (await invokePlatformMethod(PlatformMethod.getPlatformVersion))!; - -/// Get ably library version -Future version() async => - (await invokePlatformMethod(PlatformMethod.getVersion))!; diff --git a/lib/src/logging/logging.dart b/lib/src/logging/logging.dart new file mode 100644 index 000000000..17777c582 --- /dev/null +++ b/lib/src/logging/logging.dart @@ -0,0 +1,2 @@ +export 'src/log_handler.dart'; +export 'src/log_level.dart'; diff --git a/lib/src/logging/src/log_handler.dart b/lib/src/logging/src/log_handler.dart new file mode 100644 index 000000000..3150a875b --- /dev/null +++ b/lib/src/logging/src/log_handler.dart @@ -0,0 +1,9 @@ +import '../../error/src/ably_exception.dart'; + +/// Custom handler to handle SDK log messages +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TO3c +typedef LogHandler = void Function({ + String? msg, + AblyException? exception, +}); diff --git a/lib/src/logging/src/log_level.dart b/lib/src/logging/src/log_level.dart new file mode 100644 index 000000000..f21dde844 --- /dev/null +++ b/lib/src/logging/src/log_level.dart @@ -0,0 +1,29 @@ +import '../../authentication/authentication.dart'; + +/// Log levels - control verbosity of log messages +/// +/// Can be used for [ClientOptions.logLevel] +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TO3b +/// +/// TODO(tiholic) convert [LogLevel] to enum and update encoder to pass +/// right numeric values to platform methods +class LogLevel { + /// No logging + static const int none = 99; + + /// Verbose logs + static const int verbose = 2; + + /// debug logs + static const int debug = 3; + + /// info logs + static const int info = 4; + + /// warning logs + static const int warn = 5; + + /// error logs + static const int error = 6; +} diff --git a/lib/src/message/message.dart b/lib/src/message/message.dart new file mode 100644 index 000000000..0415a6866 --- /dev/null +++ b/lib/src/message/message.dart @@ -0,0 +1,6 @@ +export 'src/delta_extras.dart'; +export 'src/message.dart'; +export 'src/message_data.dart'; +export 'src/message_extras.dart'; +export 'src/presence_action.dart'; +export 'src/presence_message.dart'; diff --git a/lib/src/message/src/delta_extras.dart b/lib/src/message/src/delta_extras.dart new file mode 100644 index 000000000..40076971f --- /dev/null +++ b/lib/src/message/src/delta_extras.dart @@ -0,0 +1,27 @@ +import 'package:meta/meta.dart'; + +import '../../generated/platform_constants.dart'; +import '../message.dart'; + +/// Delta extension configuration for [MessageExtras] +@immutable +class DeltaExtras { + /// the id of the message the delta was generated from + final String? from; + + /// the delta format. Only "vcdiff" is supported currently + final String? format; + + /// create instance from a map + @protected + DeltaExtras.fromMap(Map value) + : from = value[TxDeltaExtras.from] as String?, + format = value[TxDeltaExtras.format] as String?; + + @override + bool operator ==(Object other) => + other is DeltaExtras && other.from == from && other.format == format; + + @override + int get hashCode => '$from:$format'.hashCode; +} diff --git a/lib/src/message/src/message.dart b/lib/src/message/src/message.dart new file mode 100644 index 000000000..e440169c6 --- /dev/null +++ b/lib/src/message/src/message.dart @@ -0,0 +1,130 @@ +import 'package:meta/meta.dart'; + +import '../../rest/rest.dart'; +import '../message.dart'; + +/// An individual message to be sent/received by Ably +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TM1 +@immutable +class Message { + /// A unique ID for this message + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TM2a + final String? id; + + /// The timestamp for this message + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TM2f + final DateTime? timestamp; + + /// The id of the publisher of this message + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TM2b + final String? clientId; + + /// The connection id of the publisher of this message + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TM2c + final String? connectionId; + + /// Any transformation applied to the data for this message + final String? encoding; + + final MessageData? _data; + + /// Message payload + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TM2d + Object? get data => _data?.data; + + /// Name of the message + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TM2g + final String? name; + + /// Message extras that may contain message metadata + /// and/or ancillary payloads + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TM2i + final MessageExtras? extras; + + /// Creates a message instance with [name], [data] and [clientId] + Message({ + this.id, + this.name, + Object? data, + this.clientId, + this.connectionId, + this.timestamp, + this.encoding, + this.extras, + }) : _data = MessageData.fromValue(data); + + @override + bool operator ==(Object other) => + other is Message && + other.id == id && + other.name == name && + other.data == data && + other.extras == extras && + other.encoding == encoding && + other.clientId == clientId && + other.timestamp == timestamp && + other.connectionId == connectionId; + + @override + int get hashCode => '$id:' + '$name:' + '$encoding:' + '$clientId:' + '$timestamp:' + '$connectionId:' + '${data?.hashCode}:' + '${extras?.hashCode}:' + .hashCode; + + /// https://docs.ably.com/client-lib-development-guide/features/#TM3 + /// + /// TODO(tiholic): decoding and decryption is not implemented as per + /// RSL6 and RLS6b as mentioned in TM3 + Message.fromEncoded( + Map jsonObject, [ + ChannelOptions? channelOptions, + ]) : id = jsonObject['id'] as String?, + name = jsonObject['name'] as String?, + clientId = jsonObject['clientId'] as String?, + connectionId = jsonObject['connectionId'] as String?, + _data = MessageData.fromValue(jsonObject['data']), + encoding = jsonObject['encoding'] as String?, + extras = MessageExtras.fromMap( + Map.castFrom( + jsonObject['extras'] as Map, + ), + ), + timestamp = jsonObject['timestamp'] != null + ? DateTime.fromMillisecondsSinceEpoch( + jsonObject['timestamp'] as int, + ) + : null; + + /// https://docs.ably.com/client-lib-development-guide/features/#TM3 + static List fromEncodedArray( + List> jsonArray, [ + ChannelOptions? channelOptions, + ]) => + jsonArray.map((e) => Message.fromEncoded(e, channelOptions)).toList(); + + @override + String toString() => 'Message' + ' id=$id' + ' name=$name' + ' data=$data' + ' extras=$extras' + ' encoding=$encoding' + ' clientId=$clientId' + ' timestamp=$timestamp' + ' connectionId=$connectionId'; + +// TODO(tiholic) add support for fromEncoded and fromEncodedArray (TM3) +} diff --git a/lib/src/message/src/message_data.dart b/lib/src/message/src/message_data.dart new file mode 100644 index 000000000..f978fd5cd --- /dev/null +++ b/lib/src/message/src/message_data.dart @@ -0,0 +1,45 @@ +import 'dart:typed_data'; + +/// Handles supported message data types, their encoding and decoding +class MessageData { + final T _data; + + /// Only Map, List, string and Buffer types are supported + MessageData(this._data) + : assert(T == Map || T == List || T == String || T == Uint8List); + + /// retrieve data + T get data => _data; + + /// initializes [MessageData] with given value and asserts from input type + static MessageData? fromValue(Object? value) { + if (value == null) { + return null; + } + assert( + value is MessageData || + value is Map || + value is List || + value is String || + value is Uint8List, + 'Message data must be either `Map`, `List`, `String` or `Uint8List`.' + ' Does not support $value ("${value.runtimeType}")', + ); + if (value is MessageData) { + return value; + } else if (value is Map) { + return MessageData(value); + } else if (value is Uint8List) { + return MessageData(value); + } else if (value is List) { + return MessageData(value); + } else if (value is String) { + return MessageData(value); + } else { + throw AssertionError( + 'Message data must be either `Map`, `List`, `String` or `Uint8List`.' + ' Does not support $value ("${value.runtimeType}")', + ); + } + } +} diff --git a/lib/src/message/src/message_extras.dart b/lib/src/message/src/message_extras.dart new file mode 100644 index 000000000..400274bb2 --- /dev/null +++ b/lib/src/message/src/message_extras.dart @@ -0,0 +1,52 @@ +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; + +import '../../generated/platform_constants.dart'; +import 'delta_extras.dart'; + +/// Handles supported message extras types, their encoding and decoding +@immutable +class MessageExtras { + /// json-encodable map of extras + final Map? map; + + /// configuration for delta compression extension + final DeltaExtras? _delta; + + /// delta configuration received from channel message + DeltaExtras? get delta => _delta; + + /// Creates an instance from given extras map + const MessageExtras(this.map) : _delta = null; + + /// Creates an instance from given extras map and an instance of DeltaExtras + const MessageExtras._withDelta(this.map, this._delta); + + /// initializes [MessageExtras] with given value and validates + /// the data type, runtime + static MessageExtras? fromMap(Map? extrasMap) { + if (extrasMap == null) return null; + extrasMap = Map.castFrom( + json.decode(json.encode(extrasMap)) as Map, + ); + final deltaMap = extrasMap.remove(TxMessageExtras.delta) as Map?; + return MessageExtras._withDelta( + extrasMap, + (deltaMap == null) ? null : DeltaExtras.fromMap(deltaMap), + ); + } + + @override + String toString() => {'extras': map, 'delta': delta}.toString(); + + @override + bool operator ==(Object other) => + other is MessageExtras && + const MapEquality().equals(other.map, map) && + other.delta == delta; + + @override + int get hashCode => '${map.hashCode}:${delta.hashCode}'.hashCode; +} diff --git a/lib/src/message/src/presence_action.dart b/lib/src/message/src/presence_action.dart new file mode 100644 index 000000000..2ac18be73 --- /dev/null +++ b/lib/src/message/src/presence_action.dart @@ -0,0 +1,24 @@ +import '../message.dart'; + +/// Status on a presence message +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TP2 +enum PresenceAction { + /// indicates that a client is absent for incoming [PresenceMessage] + absent, + + /// indicates that a client is present for incoming [PresenceMessage] + present, + + /// indicates that a client wants to enter a channel presence via + /// outgoing [PresenceMessage] + enter, + + /// indicates that a client wants to leave a channel presence via + /// outgoing [PresenceMessage] + leave, + + /// indicates that presence status of a client in presence member map + /// needs to be updated + update, +} diff --git a/lib/src/message/src/presence_message.dart b/lib/src/message/src/presence_message.dart new file mode 100644 index 000000000..afea8cfc1 --- /dev/null +++ b/lib/src/message/src/presence_message.dart @@ -0,0 +1,136 @@ +import 'package:meta/meta.dart'; + +import '../../rest/rest.dart'; +import 'message_data.dart'; +import 'message_extras.dart'; +import 'presence_action.dart'; + +/// An individual presence message sent or received via realtime +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TP1 +@immutable +class PresenceMessage { + /// unique ID for this presence message + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TP3a + final String? id; + + /// presence action - to update presence status of current client, + /// or to understand presence state of another client + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TP3b + final PresenceAction? action; + + /// https://docs.ably.com/client-lib-development-guide/features/#TP3c + final String? clientId; + + /// connection id of the source of this message + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TP3d + final String? connectionId; + + final MessageData? _data; + + /// Message payload + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TP3e + Object? get data => _data?.data; + + /// https://docs.ably.com/client-lib-development-guide/features/#TP3f + final String? encoding; + + /// Message extras that may contain message metadata + /// and/or ancillary payloads + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TP3i + final MessageExtras? extras; + + /// https://docs.ably.com/client-lib-development-guide/features/#TP3g + final DateTime? timestamp; + + /// https://docs.ably.com/client-lib-development-guide/features/#TP3h + String get memberKey => '$connectionId:$clientId'; + + /// instantiates presence message with + PresenceMessage({ + this.id, + this.action, + this.clientId, + this.connectionId, + Object? data, + this.encoding, + this.extras, + this.timestamp, + }) : _data = MessageData.fromValue(data); + + @override + bool operator ==(Object other) => + other is PresenceMessage && + other.id == id && + other.action == action && + other.clientId == clientId && + other.connectionId == connectionId && + other.data == data && + other.encoding == encoding && + other.extras == extras && + other.timestamp == timestamp; + + @override + int get hashCode => '$id:' + '$encoding:' + '$clientId:' + '$timestamp:' + '$connectionId:' + '${data?.toString()}:' + '${action.toString()}:' + '${extras?.toString()}:' + .hashCode; + + /// https://docs.ably.com/client-lib-development-guide/features/#TP4 + /// + /// TODO(tiholic): decoding and decryption is not implemented as per + /// RSL6 and RLS6b as mentioned in TP4 + PresenceMessage.fromEncoded( + Map jsonObject, [ + ChannelOptions? channelOptions, + ]) : id = jsonObject['id'] as String?, + action = PresenceAction.values.firstWhere((e) => + e.toString().split('.')[1] == jsonObject['action'] as String?), + clientId = jsonObject['clientId'] as String?, + connectionId = jsonObject['connectionId'] as String?, + _data = MessageData.fromValue(jsonObject['data']), + encoding = jsonObject['encoding'] as String?, + extras = MessageExtras.fromMap( + Map.castFrom( + jsonObject['extras'] as Map, + ), + ), + timestamp = jsonObject['timestamp'] != null + ? DateTime.fromMillisecondsSinceEpoch( + jsonObject['timestamp'] as int, + ) + : null; + + /// https://docs.ably.com/client-lib-development-guide/features/#TP4 + static List fromEncodedArray( + List> jsonArray, [ + ChannelOptions? channelOptions, + ]) => + jsonArray + .map((jsonObject) => PresenceMessage.fromEncoded( + jsonObject, + channelOptions, + )) + .toList(); + + @override + String toString() => 'PresenceMessage' + ' id=$id' + ' data=$data' + ' action=$action' + ' extras=$extras' + ' encoding=$encoding' + ' clientId=$clientId' + ' timestamp=$timestamp' + ' connectionId=$connectionId'; +} diff --git a/lib/src/platform.dart b/lib/src/platform.dart deleted file mode 100644 index caddf2bdf..000000000 --- a/lib/src/platform.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/services.dart'; - -import 'codec.dart'; -import 'generated/platformconstants.dart' show PlatformMethod; -import 'impl/streams_channel.dart'; -import 'method_call_handler.dart'; -import 'spec/common.dart' show ErrorInfo, AblyException; -import 'spec/constants.dart'; - -/// instance of [StandardMethodCodec] with custom [MessageCodec] for -/// exchanging Ably types with platform via platform channels -/// viz., [MethodChannel] and [StreamsChannel] -StandardMethodCodec codec = StandardMethodCodec(Codec()); - -/// instance of method channel to interact with android/ios code -final MethodChannel methodChannel = - MethodChannel('io.ably.flutter.plugin', codec); - -/// instance of method channel to listen to android/ios events -final StreamsChannel streamsChannel = - StreamsChannel('io.ably.flutter.stream', codec); - -/// Initializing ably on platform side by invoking `register` platform method. -/// Register will clear any stale instances on platform. -Future? _initializer; - -Future _initialize() async { - if (_initializer == null) { - AblyMethodCallHandler(methodChannel); - _initializer = methodChannel - .invokeMethod(PlatformMethod.registerAbly) - .timeout(Timeouts.initializeTimeout, onTimeout: () { - _initializer = null; - throw TimeoutException( - 'Initialization timed out.', - Timeouts.initializeTimeout, - ); - }); - } - return _initializer; -} - -/// invokes a platform [method] with [arguments] -/// -/// calls an [_initialize] method before invoking any method so as to handle -/// any cleanup tasks that are especially required while performing hot-restart -/// (as hot-restart is known to not clear any objects on platform side) -Future invokePlatformMethod(String method, [Object? arguments]) async { - await _initialize(); - try { - return await methodChannel.invokeMethod(method, arguments); - } on PlatformException catch (pe) { - if (pe.details is ErrorInfo) { - throw AblyException.fromPlatformException(pe); - } else { - rethrow; - } - } -} diff --git a/lib/src/platform/platform.dart b/lib/src/platform/platform.dart new file mode 100644 index 000000000..72266face --- /dev/null +++ b/lib/src/platform/platform.dart @@ -0,0 +1,16 @@ +export 'src/ably_event_message.dart'; +export 'src/ably_message.dart'; +export 'src/codec.dart'; +export 'src/info.dart'; +export 'src/method_call_handler.dart'; +export 'src/paginated_result.dart'; +export 'src/platform.dart'; +export 'src/platform_object.dart'; +export 'src/realtime/connection.dart'; +export 'src/realtime/presence.dart'; +export 'src/realtime/realtime.dart'; +export 'src/realtime/realtime_channel.dart'; +export 'src/rest/rest.dart'; +export 'src/rest/rest_channels.dart'; +export 'src/rest/rest_presence.dart'; +export 'src/streams_channel.dart'; diff --git a/lib/src/platform/src/ably_event_message.dart b/lib/src/platform/src/ably_event_message.dart new file mode 100644 index 000000000..41cb7f780 --- /dev/null +++ b/lib/src/platform/src/ably_event_message.dart @@ -0,0 +1,15 @@ +/// An encapsulating object used to pass data to platform for registering events +class AblyEventMessage { + /// name of the event to register a listener for + final String eventName; + + /// data to be passed for starting a listener + final Object? message; + + /// creates an instance with non-nul [eventName] + /// + /// [message] is optional + /// + /// Raises [AssertionError] if [eventName] is null + AblyEventMessage(this.eventName, [this.message]); +} diff --git a/lib/src/impl/message.dart b/lib/src/platform/src/ably_message.dart similarity index 61% rename from lib/src/impl/message.dart rename to lib/src/platform/src/ably_message.dart index 52035a934..e668ea65e 100644 --- a/lib/src/impl/message.dart +++ b/lib/src/platform/src/ably_message.dart @@ -1,4 +1,4 @@ -import '../../ably_flutter.dart'; +import '../../generated/platform_constants.dart'; /// An encapsulating object used to pass data to/from platform for method calls class AblyMessage { @@ -29,19 +29,3 @@ class AblyMessage { type: source.type, ); } - -/// An encapsulating object used to pass data to platform for registering events -class AblyEventMessage { - /// name of the event to register a listener for - final String eventName; - - /// data to be passed for starting a listener - final Object? message; - - /// creates an instance with non-nul [eventName] - /// - /// [message] is optional - /// - /// Raises [AssertionError] if [eventName] is null - AblyEventMessage(this.eventName, [this.message]); -} diff --git a/lib/src/codec.dart b/lib/src/platform/src/codec.dart similarity index 99% rename from lib/src/codec.dart rename to lib/src/platform/src/codec.dart index 5a2ce87ca..1895f9e7c 100644 --- a/lib/src/codec.dart +++ b/lib/src/platform/src/codec.dart @@ -1,11 +1,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import '../ably_flutter.dart'; -import '../src/generated/platformconstants.dart'; -import '../src/impl/message.dart'; -import '../src/spec/realtime/channels.dart'; -import '../src/spec/rest/channels.dart'; +import '../../authentication/authentication.dart'; +import '../../error/error.dart'; +import '../../generated/platform_constants.dart'; +import '../../message/message.dart'; +import '../../realtime/realtime.dart'; +import '../../rest/rest.dart'; +import '../platform.dart'; /// a [_Encoder] encodes custom type and converts it to a Map which will /// be passed on to platform side diff --git a/lib/src/platform/src/info.dart b/lib/src/platform/src/info.dart new file mode 100644 index 000000000..eec742171 --- /dev/null +++ b/lib/src/platform/src/info.dart @@ -0,0 +1,11 @@ +import '../../generated/platform_constants.dart'; +import '../platform.dart'; + +/// Get android/iOS platform version +Future platformVersion() async => + (await Platform.invokePlatformMethod( + PlatformMethod.getPlatformVersion))!; + +/// Get ably library version +Future version() async => + (await Platform.invokePlatformMethod(PlatformMethod.getVersion))!; diff --git a/lib/src/method_call_handler.dart b/lib/src/platform/src/method_call_handler.dart similarity index 74% rename from lib/src/method_call_handler.dart rename to lib/src/platform/src/method_call_handler.dart index 6bec286fb..7d4d32a29 100644 --- a/lib/src/method_call_handler.dart +++ b/lib/src/platform/src/method_call_handler.dart @@ -1,8 +1,10 @@ import 'package:flutter/services.dart'; -import '../ably_flutter.dart' as ably; -import 'generated/platformconstants.dart'; -import 'impl/message.dart'; +import '../../authentication/authentication.dart'; +import '../../error/error.dart'; +import '../../generated/platform_constants.dart'; +import '../platform.dart'; +import 'ably_message.dart'; /// Handles method calls invoked from platform side to dart side class AblyMethodCallHandler { @@ -24,10 +26,10 @@ class AblyMethodCallHandler { /// handles auth callback for rest instances Future onAuthCallback(AblyMessage message) async { - final tokenParams = message.message as ably.TokenParams; - final rest = ably.restInstances[message.handle]; + final tokenParams = message.message as TokenParams; + final rest = restInstances[message.handle]; if (rest == null) { - throw ably.AblyException('invalid message handle ${message.handle}'); + throw AblyException('invalid message handle ${message.handle}'); } final callbackResponse = await rest.options.authCallback!(tokenParams); Future.delayed(Duration.zero, rest.authUpdateComplete); @@ -42,10 +44,10 @@ class AblyMethodCallHandler { return null; } _realtimeAuthInProgress = true; - final tokenParams = message!.message as ably.TokenParams; - final realtime = ably.realtimeInstances[message.handle]; + final tokenParams = message!.message as TokenParams; + final realtime = realtimeInstances[message.handle]; if (realtime == null) { - throw ably.AblyException('invalid message handle ${message.handle}'); + throw AblyException('invalid message handle ${message.handle}'); } final callbackResponse = await realtime.options.authCallback!(tokenParams); Future.delayed(Duration.zero, realtime.authUpdateComplete); diff --git a/lib/src/impl/paginated_result.dart b/lib/src/platform/src/paginated_result.dart similarity index 93% rename from lib/src/impl/paginated_result.dart rename to lib/src/platform/src/paginated_result.dart index 9ba74859e..518a0659d 100644 --- a/lib/src/impl/paginated_result.dart +++ b/lib/src/platform/src/paginated_result.dart @@ -1,7 +1,6 @@ -import '../../ably_flutter.dart'; -import '../spec/spec.dart' as spec; -import 'message.dart'; -import 'platform_object.dart'; +import '../../common/common.dart'; +import '../../generated/platform_constants.dart'; +import '../platform.dart'; /// PaginatedResult [TG1](https://docs.ably.com/client-lib-development-guide/features/#TG1) /// @@ -9,7 +8,7 @@ import 'platform_object.dart'; /// The response is accompanied by metadata that indicates the /// relative queries available. class PaginatedResult extends PlatformObject - implements spec.PaginatedResultInterface { + implements PaginatedResultInterface { /// stores page handle created by platform APIs /// /// handle is updated after creating an instance as they are received diff --git a/lib/src/platform/src/platform.dart b/lib/src/platform/src/platform.dart new file mode 100644 index 000000000..feb0ea1ba --- /dev/null +++ b/lib/src/platform/src/platform.dart @@ -0,0 +1,61 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; + +import '../../error/error.dart'; +import '../../generated/platform_constants.dart'; +import '../platform.dart'; + +class Platform { + /// instance of [StandardMethodCodec] with custom [MessageCodec] for + /// exchanging Ably types with platform via platform channels + /// viz., [MethodChannel] and [StreamsChannel] + static StandardMethodCodec codec = StandardMethodCodec(Codec()); + + /// instance of method channel to interact with android/ios code + static final MethodChannel methodChannel = + MethodChannel('io.ably.flutter.plugin', codec); + + /// instance of method channel to listen to android/ios events + static final StreamsChannel streamsChannel = + StreamsChannel('io.ably.flutter.stream', codec); + + /// Initializing ably on platform side by invoking `register` platform method. + /// Register will clear any stale instances on platform. + static Future? _initializer; + + static Future _initialize() async { + if (_initializer == null) { + AblyMethodCallHandler(methodChannel); + _initializer = methodChannel + .invokeMethod(PlatformMethod.registerAbly) + .timeout(Timeouts.initializeTimeout, onTimeout: () { + _initializer = null; + throw TimeoutException( + 'Initialization timed out.', + Timeouts.initializeTimeout, + ); + }); + } + return _initializer; + } + + /// invokes a platform [method] with [arguments] + /// + /// calls an [_initialize] method before invoking any method to handle any + /// cleanup tasks that are especially required while performing hot-restart + /// (as hot-restart is known to not clear any objects on platform side) + static Future invokePlatformMethod(String method, + [Object? arguments]) async { + await _initialize(); + try { + return await methodChannel.invokeMethod(method, arguments); + } on PlatformException catch (pe) { + if (pe.details is ErrorInfo) { + throw AblyException.fromPlatformException(pe); + } else { + rethrow; + } + } + } +} diff --git a/lib/src/impl/platform_object.dart b/lib/src/platform/src/platform_object.dart similarity index 90% rename from lib/src/impl/platform_object.dart rename to lib/src/platform/src/platform_object.dart index 4973f505d..a4385e388 100644 --- a/lib/src/impl/platform_object.dart +++ b/lib/src/platform/src/platform_object.dart @@ -3,11 +3,9 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; -import '../platform.dart' as platform; -import '../spec/common.dart'; -import '../spec/constants.dart'; -import 'message.dart'; -import 'streams_channel.dart'; +import '../../error/error.dart'; +import '../platform.dart'; +import 'ably_event_message.dart'; /// An object which has a live counterpart in the Platform client library SDK, /// where that live counterpart is held as a strong reference by the plugin @@ -50,10 +48,10 @@ abstract class PlatformObject { ).then((value) => (_handleValue = value)!); /// [MethodChannel] to make method calls to platform side - MethodChannel get methodChannel => platform.methodChannel; + MethodChannel get methodChannel => Platform.methodChannel; /// [EventChannel] to register events on platform side - StreamsChannel get eventChannel => platform.streamsChannel; + StreamsChannel get eventChannel => Platform.streamsChannel; /// invoke platform method channel without AblyMessage encapsulation @protected @@ -61,7 +59,7 @@ abstract class PlatformObject { final String method, [ final Object? arguments, ]) async => - platform.invokePlatformMethod(method, arguments); + Platform.invokePlatformMethod(method, arguments); /// invoke platform method channel with AblyMessage encapsulation /// diff --git a/lib/src/impl/realtime/connection.dart b/lib/src/platform/src/realtime/connection.dart similarity index 89% rename from lib/src/impl/realtime/connection.dart rename to lib/src/platform/src/realtime/connection.dart index 58bbc7520..1147a64ad 100644 --- a/lib/src/impl/realtime/connection.dart +++ b/lib/src/platform/src/realtime/connection.dart @@ -1,10 +1,9 @@ import 'dart:async'; -import '../../../ably_flutter.dart'; -import '../../spec/spec.dart' - show ConnectionInterface, ConnectionState, ErrorInfo; -import '../platform_object.dart'; -import 'realtime.dart'; +import '../../../error/error.dart'; +import '../../../generated/platform_constants.dart'; +import '../../../realtime/realtime.dart'; +import '../../platform.dart'; /// connects to Ably service class Connection extends PlatformObject implements ConnectionInterface { diff --git a/lib/src/impl/realtime/presence.dart b/lib/src/platform/src/realtime/presence.dart similarity index 92% rename from lib/src/impl/realtime/presence.dart rename to lib/src/platform/src/realtime/presence.dart index aaedf6271..42fd2d490 100644 --- a/lib/src/impl/realtime/presence.dart +++ b/lib/src/platform/src/realtime/presence.dart @@ -1,12 +1,9 @@ -import '../../generated/platformconstants.dart'; -import '../../spec/spec.dart'; -import '../message.dart'; -import '../paginated_result.dart'; -import '../platform_object.dart'; -import 'channels.dart'; -import 'realtime.dart'; +import '../../../generated/platform_constants.dart'; +import '../../../message/message.dart'; +import '../../../realtime/realtime.dart'; +import '../../platform.dart'; -/// Plugin based implementation of [RestPresenceInterface] +/// Plugin based implementation of [RestPresence] class RealtimePresence extends PlatformObject implements RealtimePresenceInterface { final RealtimeChannel _channel; diff --git a/lib/src/impl/realtime/realtime.dart b/lib/src/platform/src/realtime/realtime.dart similarity index 92% rename from lib/src/impl/realtime/realtime.dart rename to lib/src/platform/src/realtime/realtime.dart index 3612413c4..fd5ffcd84 100644 --- a/lib/src/impl/realtime/realtime.dart +++ b/lib/src/platform/src/realtime/realtime.dart @@ -3,12 +3,14 @@ import 'dart:collection'; import 'package:pedantic/pedantic.dart'; -import '../../../ably_flutter.dart'; -import '../../spec/spec.dart' as spec; -import '../message.dart'; -import '../platform_object.dart'; -import 'channels.dart'; -import 'connection.dart'; +import '../../../authentication/authentication.dart'; +import '../../../common/common.dart'; +import '../../../error/error.dart'; +import '../../../generated/platform_constants.dart'; +import '../../../push_notifications/push_notifications.dart'; +import '../../../realtime/realtime.dart'; +import '../../../stats/stats.dart'; +import '../../platform.dart'; Map _realtimeInstances = {}; Map? _realtimeInstancesUnmodifiableView; @@ -20,7 +22,7 @@ Map get realtimeInstances => /// Ably's Realtime client class Realtime extends PlatformObject - implements spec.RealtimeInterface { + implements RealtimeInterface { /// instantiates with [ClientOptions] and a String [key] /// /// creates client options from key if [key] is provided diff --git a/lib/src/impl/realtime/channels.dart b/lib/src/platform/src/realtime/realtime_channel.dart similarity index 95% rename from lib/src/impl/realtime/channels.dart rename to lib/src/platform/src/realtime/realtime_channel.dart index c956e7c4d..3a9aa8c08 100644 --- a/lib/src/impl/realtime/channels.dart +++ b/lib/src/platform/src/realtime/realtime_channel.dart @@ -5,12 +5,12 @@ import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; import 'package:pedantic/pedantic.dart'; -import '../../../ably_flutter.dart'; -import '../../spec/push/channels.dart'; -import '../message.dart'; -import '../platform_object.dart'; -import 'presence.dart'; -import 'realtime.dart'; +import '../../../error/error.dart'; +import '../../../generated/platform_constants.dart'; +import '../../../message/message.dart'; +import '../../../push_notifications/push_notifications.dart'; +import '../../../realtime/realtime.dart'; +import '../../platform.dart'; /// Plugin based implementation of Realtime channel class RealtimeChannel extends PlatformObject @@ -211,7 +211,8 @@ class RealtimeChannel extends PlatformObject /// A collection of realtime channel objects /// /// https://docs.ably.com/client-lib-development-guide/features/#RTS1 -class RealtimePlatformChannels extends RealtimeChannels { +class RealtimePlatformChannels + extends RealtimeChannelsInterface { /// instantiates with the ably [Realtime] instance RealtimePlatformChannels(Realtime realtime) : super(realtime); diff --git a/lib/src/impl/rest/rest.dart b/lib/src/platform/src/rest/rest.dart similarity index 80% rename from lib/src/impl/rest/rest.dart rename to lib/src/platform/src/rest/rest.dart index 4ac871163..423cc5b3e 100644 --- a/lib/src/impl/rest/rest.dart +++ b/lib/src/platform/src/rest/rest.dart @@ -1,11 +1,13 @@ import 'dart:async'; import 'dart:collection'; -import '../../../ably_flutter.dart'; -import '../../spec/spec.dart' as spec; -import '../message.dart'; -import '../platform_object.dart'; -import 'channels.dart'; +import '../../../authentication/authentication.dart'; +import '../../../common/common.dart'; +import '../../../generated/platform_constants.dart'; +import '../../../push_notifications/push_notifications.dart'; +import '../../../rest/rest.dart'; +import '../../../stats/stats.dart'; +import '../../platform.dart'; Map _restInstances = {}; Map? _restInstancesUnmodifiableView; @@ -15,8 +17,7 @@ Map get restInstances => _restInstancesUnmodifiableView ??= UnmodifiableMapView(_restInstances); /// Ably's Rest client -class Rest extends PlatformObject - implements spec.RestInterface { +class Rest extends PlatformObject implements RestInterface { /// instantiates with [ClientOptions] and a String [key] /// /// creates client options from key if [key] is provided @@ -28,7 +29,7 @@ class Rest extends PlatformObject }) : assert(options != null || key != null), options = options ?? ClientOptions.fromKey(key!), super() { - channels = RestPlatformChannels(this); + channels = RestChannels(this); } @override @@ -84,5 +85,5 @@ class Rest extends PlatformObject Push? push; @override - late RestPlatformChannels channels; + late RestChannels channels; } diff --git a/lib/src/impl/rest/channels.dart b/lib/src/platform/src/rest/rest_channels.dart similarity index 94% rename from lib/src/impl/rest/channels.dart rename to lib/src/platform/src/rest/rest_channels.dart index 1e8c1000d..dc86ec5e3 100644 --- a/lib/src/impl/rest/channels.dart +++ b/lib/src/platform/src/rest/rest_channels.dart @@ -5,11 +5,11 @@ import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; import 'package:pedantic/pedantic.dart'; -import '../../../ably_flutter.dart'; -import '../message.dart'; -import '../platform_object.dart'; -import 'presence.dart'; -import 'rest.dart'; +import '../../../error/error.dart'; +import '../../../generated/platform_constants.dart'; +import '../../../message/message.dart'; +import '../../../rest/rest.dart'; +import '../../platform.dart'; /// Plugin based implementation of Rest channel class RestChannel extends PlatformObject implements RestChannelInterface { @@ -163,9 +163,9 @@ class RestChannel extends PlatformObject implements RestChannelInterface { /// A collection of rest channel objects /// /// https://docs.ably.io/client-lib-development-guide/features/#RSN1 -class RestPlatformChannels extends RestChannels { +class RestChannels extends RestChannelsInterface { /// instantiates with the ably [Rest] instance - RestPlatformChannels(Rest rest) : super(rest); + RestChannels(Rest rest) : super(rest); @override @protected diff --git a/lib/src/impl/rest/presence.dart b/lib/src/platform/src/rest/rest_presence.dart similarity index 87% rename from lib/src/impl/rest/presence.dart rename to lib/src/platform/src/rest/rest_presence.dart index 312b89e43..965848325 100644 --- a/lib/src/impl/rest/presence.dart +++ b/lib/src/platform/src/rest/rest_presence.dart @@ -1,10 +1,10 @@ -import '../../generated/platformconstants.dart'; -import '../../spec/spec.dart'; -import '../message.dart'; +import '../../../generated/platform_constants.dart'; +import '../../../message/message.dart'; +import '../../../rest/rest.dart'; +import '../../platform.dart'; import '../paginated_result.dart'; -import '../platform_object.dart'; -import 'channels.dart'; import 'rest.dart'; +import 'rest_channels.dart'; /// Plugin based implementation of [RestPresenceInterface] class RestPresence extends PlatformObject implements RestPresenceInterface { diff --git a/lib/src/impl/streams_channel.dart b/lib/src/platform/src/streams_channel.dart similarity index 98% rename from lib/src/impl/streams_channel.dart rename to lib/src/platform/src/streams_channel.dart index 3e1041e12..41096fefa 100644 --- a/lib/src/impl/streams_channel.dart +++ b/lib/src/platform/src/streams_channel.dart @@ -23,7 +23,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import '../spec/common.dart'; +import '../../error/error.dart'; /// Manages multiple event listeners which would otherwise require verbose code /// on platform side diff --git a/lib/src/push_notifications/push_notifications.dart b/lib/src/push_notifications/push_notifications.dart new file mode 100644 index 000000000..df3c48edd --- /dev/null +++ b/lib/src/push_notifications/push_notifications.dart @@ -0,0 +1,14 @@ +export 'src/admin/push_admin.dart'; +export 'src/admin/push_device_registrations.dart'; +export 'src/device_details.dart'; +export 'src/device_platform.dart'; +export 'src/device_push_details.dart'; +export 'src/device_push_state.dart'; +export 'src/device_registration_params.dart'; +export 'src/form_factor.dart'; +export 'src/push.dart'; +export 'src/push_channel.dart'; +export 'src/push_channel_params.dart'; +export 'src/push_channel_subscription.dart'; +export 'src/push_channel_subscription_params.dart'; +export 'src/push_channel_subscriptions.dart'; diff --git a/lib/src/push_notifications/src/admin/push_admin.dart b/lib/src/push_notifications/src/admin/push_admin.dart new file mode 100644 index 000000000..842054432 --- /dev/null +++ b/lib/src/push_notifications/src/admin/push_admin.dart @@ -0,0 +1,21 @@ +import '../push_channel_subscriptions.dart'; +import 'push_device_registrations.dart'; + +/// Class providing push notification administrative functionality +/// for registering devices and attaching to channels etc. +/// +/// https://docs.ably.com/client-lib-development-guide/features/#RSH1 +abstract class PushAdmin { + /// Manage device registrations. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b + PushDeviceRegistrations? deviceRegistrations; + + /// Manage channel subscriptions for devices or clients. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c + PushChannelSubscriptions? channelSubscriptions; + + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1a + Future publish(Map recipient, Map payload); +} diff --git a/lib/src/push_notifications/src/admin/push_device_registrations.dart b/lib/src/push_notifications/src/admin/push_device_registrations.dart new file mode 100644 index 000000000..2dfed00ab --- /dev/null +++ b/lib/src/push_notifications/src/admin/push_device_registrations.dart @@ -0,0 +1,40 @@ +import '../../../common/common.dart'; +import '../../push_notifications.dart'; + +/// Manage device registrations for push notifications +/// +/// https://ably.com/documentation/general/push/admin#device-registrations +abstract class PushDeviceRegistrations { + /// Get registered device by device ID. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b1 + Future get({ + DeviceDetails? deviceDetails, + String? deviceId, + }); + + /// List registered devices filtered by optional params. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b2 + Future> list( + DeviceRegistrationParams params, + ); + + /// Save and register device. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b3 + Future save(DeviceDetails deviceDetails); + + /// Remove device. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b4 + Future remove({ + DeviceDetails? deviceDetails, + String? deviceId, + }); + + /// Remove device matching where params. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b5 + Future removeWhere(DeviceRegistrationParams params); +} diff --git a/lib/src/push_notifications/src/device_details.dart b/lib/src/push_notifications/src/device_details.dart new file mode 100644 index 000000000..ca9387763 --- /dev/null +++ b/lib/src/push_notifications/src/device_details.dart @@ -0,0 +1,44 @@ +import '../push_notifications.dart'; +import 'device_push_details.dart'; +import 'form_factor.dart'; + +/// Details of a registered device. +/// +/// https://docs.ably.com/client-lib-development-guide/features/#PCD1 +abstract class DeviceDetails { + /// The id of the device registration. + /// + /// Generated locally if not available + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCD2 + String? id; + + /// populated for device registrations associated with a clientId (optional) + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCD3 + String? clientId; + + /// The device platform. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCD6 + DevicePlatform? platform; + + /// the device form factor. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCD4 + FormFactor? formFactor; + + /// a map of string key/value pairs containing any other registered + /// metadata associated with the device registration + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCD5 + Map? metadata; + + /// Device token. Generated locally, if not available. + String? deviceSecret; + + /// Details of the push registration for this device. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCD7 + DevicePushDetails? push; +} diff --git a/lib/src/push_notifications/src/device_platform.dart b/lib/src/push_notifications/src/device_platform.dart new file mode 100644 index 000000000..a0cb9f317 --- /dev/null +++ b/lib/src/push_notifications/src/device_platform.dart @@ -0,0 +1,14 @@ +import '../push_notifications.dart'; + +/// To indicate the operating system -or- platform of the client using SDK +/// in [DeviceDetails] while registering +enum DevicePlatform { + /// indicates an android device + android, + + /// indicates an iOS device + ios, + + /// indicates a browser + browser, +} diff --git a/lib/src/push_notifications/src/device_push_details.dart b/lib/src/push_notifications/src/device_push_details.dart new file mode 100644 index 000000000..97e1e93d8 --- /dev/null +++ b/lib/src/push_notifications/src/device_push_details.dart @@ -0,0 +1,24 @@ +import '../../error/src/error_info.dart'; + +import 'device_push_state.dart'; + +/// Details of the push registration for a given device +/// +/// https://docs.ably.com/client-lib-development-guide/features/#PCP1 +abstract class DevicePushDetails { + /// A map of string key/value pairs containing details of the push transport + /// and address. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCP3 + Map? recipient; + + /// The state of the push registration. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCP4 + DevicePushState? state; + + /// Any error information associated with the registration. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCP2 + ErrorInfo? errorReason; +} diff --git a/lib/src/push_notifications/src/device_push_state.dart b/lib/src/push_notifications/src/device_push_state.dart new file mode 100644 index 000000000..ec0db93ac --- /dev/null +++ b/lib/src/push_notifications/src/device_push_state.dart @@ -0,0 +1,14 @@ +import '../push_notifications.dart'; + +/// To indicate Push State of a device in [DeviceDetails] via [DevicePushState] +/// while registering +enum DevicePushState { + /// indicates active push state of the device + active, + + /// indicates that push state is failing + failing, + + /// indicates the device push state failed + failed, +} diff --git a/lib/src/push_notifications/src/device_registration_params.dart b/lib/src/push_notifications/src/device_registration_params.dart new file mode 100644 index 000000000..062ee80f1 --- /dev/null +++ b/lib/src/push_notifications/src/device_registration_params.dart @@ -0,0 +1,20 @@ +import '../push_notifications.dart'; +import 'device_push_state.dart'; + +/// Params to filter push device registrations. +/// +/// see: [PushDeviceRegistrations.list], [PushDeviceRegistrations.removeWhere] +/// https://docs.ably.com/client-lib-development-guide/features/#RSH1b2 +abstract class DeviceRegistrationParams { + /// filter by client id + String? clientId; + + /// filter by device id + String? deviceId; + + /// limit results for each page + int? limit; + + /// filter by device state + DevicePushState? state; +} diff --git a/lib/src/push_notifications/src/form_factor.dart b/lib/src/push_notifications/src/form_factor.dart new file mode 100644 index 000000000..abec508e8 --- /dev/null +++ b/lib/src/push_notifications/src/form_factor.dart @@ -0,0 +1,30 @@ +import '../push_notifications.dart'; + +/// To indicate the type of device in [DeviceDetails] while registering +/// +/// https://docs.ably.com/client-lib-development-guide/features/#PCD4 +enum FormFactor { + /// indicates the device is a mobile phone + phone, + + /// indicates the device is a tablet + tablet, + + /// indicates the device is a desktop + desktop, + + /// indicates the device is a television + tv, + + /// indicates the device is a smart watch + watch, + + /// indicates the device is an automobile + car, + + /// indicates the device is an embedded system / iOT device + embedded, + + /// indicates the device belong to categories other than mentioned above + other, +} diff --git a/lib/src/push_notifications/src/push.dart b/lib/src/push_notifications/src/push.dart new file mode 100644 index 000000000..f15e70c41 --- /dev/null +++ b/lib/src/push_notifications/src/push.dart @@ -0,0 +1,27 @@ +import '../push_notifications.dart'; +import 'admin/push_admin.dart'; + +/// Class providing push notification functionality +/// +/// https://docs.ably.com/client-lib-development-guide/features/#RSH1 +abstract class Push { + /// Admin features for push notifications like managing devices + /// and channel subscriptions. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1 + PushAdmin? admin; + + /// Activate this device for push notifications by registering + /// with the push transport such as GCM/APNS. + /// + /// returns DeviceDetails + /// https://docs.ably.com/client-lib-development-guide/features/#RSH2a + Future activate(); + + /// Deactivate this device for push notifications by removing + /// the registration with the push transport such as GCM/APNS. + /// + /// returns deviceId + /// https://docs.ably.com/client-lib-development-guide/features/#RSH2b + Future deactivate(); +} diff --git a/lib/src/spec/push/channels.dart b/lib/src/push_notifications/src/push_channel.dart similarity index 94% rename from lib/src/spec/push/channels.dart rename to lib/src/push_notifications/src/push_channel.dart index 5516f9024..06b010804 100644 --- a/lib/src/spec/push/channels.dart +++ b/lib/src/push_notifications/src/push_channel.dart @@ -1,4 +1,5 @@ -import '../common.dart'; +import '../../common/common.dart'; +import 'push_channel_subscription.dart'; /// Channel to receive push notifications on /// diff --git a/lib/src/push_notifications/src/push_channel_params.dart b/lib/src/push_notifications/src/push_channel_params.dart new file mode 100644 index 000000000..2a05bd638 --- /dev/null +++ b/lib/src/push_notifications/src/push_channel_params.dart @@ -0,0 +1,9 @@ +import '../push_notifications.dart'; + +/// params to filter channels on a [PushChannelSubscriptions] +/// +/// https://docs.ably.com/client-lib-development-guide/features/#RSH1c2 +abstract class PushChannelsParams { + /// limit results for each page + int? limit; +} diff --git a/lib/src/push_notifications/src/push_channel_subscription.dart b/lib/src/push_notifications/src/push_channel_subscription.dart new file mode 100644 index 000000000..1f8fb8eac --- /dev/null +++ b/lib/src/push_notifications/src/push_channel_subscription.dart @@ -0,0 +1,20 @@ +/// Details of a push subscription to a channel. +/// +/// https://docs.ably.com/client-lib-development-guide/features/#PCS1 +abstract class PushChannelSubscription { + /// the channel name associated with this subscription + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCS4 + String? channel; + + /// populated for subscriptions made for a specific device registration + /// (optional) + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCS2 + String? deviceId; + + /// populated for subscriptions made for a specific clientId (optional) + /// + /// https://docs.ably.com/client-lib-development-guide/features/#PCS3 + String? clientId; +} diff --git a/lib/src/push_notifications/src/push_channel_subscription_params.dart b/lib/src/push_notifications/src/push_channel_subscription_params.dart new file mode 100644 index 000000000..01ac7dc26 --- /dev/null +++ b/lib/src/push_notifications/src/push_channel_subscription_params.dart @@ -0,0 +1,19 @@ +import '../push_notifications.dart'; + +/// Params to filter push channel subscriptions. +/// +/// See [PushChannelSubscriptions.list], [PushChannelSubscriptions.removeWhere] +/// https://docs.ably.com/client-lib-development-guide/features/#RSH1c1 +abstract class PushChannelSubscriptionParams { + /// filter by channel + String? channel; + + /// filter by clientId + String? clientId; + + /// filter by deviceId + String? deviceId; + + /// limit results for each page + int? limit; +} diff --git a/lib/src/push_notifications/src/push_channel_subscriptions.dart b/lib/src/push_notifications/src/push_channel_subscriptions.dart new file mode 100644 index 000000000..3085a087e --- /dev/null +++ b/lib/src/push_notifications/src/push_channel_subscriptions.dart @@ -0,0 +1,34 @@ +import '../../common/common.dart'; +import '../push_notifications.dart'; + +/// Manage push notification channel subscriptions for devices or clients +abstract class PushChannelSubscriptions { + /// List channel subscriptions filtered by optional params. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c1 + Future> list( + PushChannelSubscriptionParams params, + ); + + /// List channels with at least one subscribed device. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c2 + Future> listChannels( + PushChannelsParams params, + ); + + /// Save push channel subscription for a device or client ID. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c3 + Future save(PushChannelSubscription subscription); + + /// Remove a push channel subscription. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c4 + Future remove(PushChannelSubscription subscription); + + /// Remove all matching push channel subscriptions. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c5 + Future removeWhere(PushChannelSubscriptionParams params); +} diff --git a/lib/src/realtime/realtime.dart b/lib/src/realtime/realtime.dart new file mode 100644 index 000000000..295e19720 --- /dev/null +++ b/lib/src/realtime/realtime.dart @@ -0,0 +1,13 @@ +export 'src/channel_event.dart'; +export 'src/channel_mode.dart'; +export 'src/channel_state.dart'; +export 'src/channel_state_event.dart'; +export 'src/channels.dart'; +export 'src/connection.dart'; +export 'src/connection_event.dart'; +export 'src/connection_state.dart'; +export 'src/connection_state_change.dart'; +export 'src/presence.dart'; +export 'src/realtime.dart'; +export 'src/realtime_history_params.dart'; +export 'src/realtime_presence_params.dart'; diff --git a/lib/src/realtime/src/channel_event.dart b/lib/src/realtime/src/channel_event.dart new file mode 100644 index 000000000..321c97988 --- /dev/null +++ b/lib/src/realtime/src/channel_event.dart @@ -0,0 +1,27 @@ +/// See Ably Realtime API documentation for more details. +enum ChannelEvent { + /// represents that a channel is initialized and no action was taken + /// i.e., even auto connect was not triggered - if enabled + initialized, + + /// channel is attaching + attaching, + + /// channel is attached + attached, + + /// channel is detaching + detaching, + + /// channel is detached + detached, + + /// channel is suspended + suspended, + + /// channel failed to connect + failed, + + /// specifies that a channel state is updated + update, +} diff --git a/lib/src/realtime/src/channel_mode.dart b/lib/src/realtime/src/channel_mode.dart new file mode 100644 index 000000000..2ca61ff48 --- /dev/null +++ b/lib/src/realtime/src/channel_mode.dart @@ -0,0 +1,18 @@ +/// Set of flags that represent the capabilities of a channel for current client +/// +/// See: +/// https://docs.ably.com/client-lib-development-guide/features/#TB2d +/// https://docs.ably.com/client-lib-development-guide/features/#RTL4m +enum ChannelMode { + /// specifies that channel can check for presence + presence, + + /// specifies that channel can publish + publish, + + /// specifies that channel can subscribe to messages + subscribe, + + /// specifies that channel can subscribe to presence events + presenceSubscribe, +} diff --git a/lib/src/realtime/src/channel_state.dart b/lib/src/realtime/src/channel_state.dart new file mode 100644 index 000000000..9eaf29d8c --- /dev/null +++ b/lib/src/realtime/src/channel_state.dart @@ -0,0 +1,25 @@ +/// See Ably Realtime API documentation for more details. +/// https://docs.ably.com/client-lib-development-guide/features/#channel-states-operations +enum ChannelState { + /// represents that a channel is initialized and no action was taken + /// i.e., even auto connect was not triggered - if enabled + initialized, + + /// channel is attaching + attaching, + + /// channel is attached + attached, + + /// channel is detaching + detaching, + + /// channel is detached + detached, + + /// channel is suspended + suspended, + + /// channel failed to connect + failed, +} diff --git a/lib/src/realtime/src/channel_state_event.dart b/lib/src/realtime/src/channel_state_event.dart new file mode 100644 index 000000000..abda245ad --- /dev/null +++ b/lib/src/realtime/src/channel_state_event.dart @@ -0,0 +1,46 @@ +import '../../error/src/error_info.dart'; + +import 'channel_event.dart'; +import 'channel_state.dart'; + +/// Whenever the channel state changes, a ChannelStateChange object +/// is emitted on the Channel object +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TH1 +class ChannelStateChange { + /// the event that generated the channel state change + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TH5 + final ChannelEvent event; + + /// current state of the channel + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TH2 + final ChannelState current; + + /// previous state of the channel + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TH2 + final ChannelState previous; + + /// reason for failure, in case of a failed state + /// + /// If the channel state change includes error information, + /// then the reason attribute will contain an ErrorInfo + /// object describing the reason for the error + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TH3 + ErrorInfo? reason; + + /// https://docs.ably.com/client-lib-development-guide/features/#TH4 + final bool? resumed; + + /// initializes with [resumed] set to false + ChannelStateChange( + this.current, + this.previous, + this.event, { + this.reason, + this.resumed = false, + }); +} diff --git a/lib/src/spec/realtime/channels.dart b/lib/src/realtime/src/channels.dart similarity index 86% rename from lib/src/spec/realtime/channels.dart rename to lib/src/realtime/src/channels.dart index ea35a553f..49e9317fa 100644 --- a/lib/src/spec/realtime/channels.dart +++ b/lib/src/realtime/src/channels.dart @@ -1,11 +1,17 @@ import 'dart:async'; -import '../../../ably_flutter.dart'; -import '../common.dart'; -import '../enums.dart'; -import '../message.dart'; -import '../push/channels.dart'; -import '../rest/channels.dart'; +import '../../authentication/authentication.dart'; +import '../../common/common.dart'; +import '../../common/src/channels.dart'; +import '../../common/src/event_emitter.dart'; +import '../../error/src/error_info.dart'; +import '../../message/src/message.dart'; +import '../../push_notifications/src/push_channel.dart'; +import '../../rest/src/channel_options.dart'; +import '../realtime.dart'; +import 'channel_event.dart'; +import 'channel_state.dart'; +import 'channel_state_event.dart'; import 'presence.dart'; import 'realtime.dart'; @@ -115,11 +121,11 @@ abstract class RealtimeChannelInterface /// A collection of realtime channel objects /// /// https://docs.ably.com/client-lib-development-guide/features/#RTS1 -abstract class RealtimeChannels +abstract class RealtimeChannelsInterface extends Channels { /// instance of ably realtime client RealtimeInterface realtime; /// instantiates with the ably [RealtimeInterface] instance - RealtimeChannels(this.realtime); + RealtimeChannelsInterface(this.realtime); } diff --git a/lib/src/spec/connection.dart b/lib/src/realtime/src/connection.dart similarity index 95% rename from lib/src/spec/connection.dart rename to lib/src/realtime/src/connection.dart index 1d143bc90..534db2fdb 100644 --- a/lib/src/spec/connection.dart +++ b/lib/src/realtime/src/connection.dart @@ -1,5 +1,6 @@ -import 'common.dart'; -import 'enums.dart'; +import '../../common/common.dart'; +import '../../error/error.dart'; +import '../realtime.dart'; /// connects to Ably service using a [web-socket](https://www.ably.com/topic/websockets) connection /// diff --git a/lib/src/realtime/src/connection_event.dart b/lib/src/realtime/src/connection_event.dart new file mode 100644 index 000000000..08358201c --- /dev/null +++ b/lib/src/realtime/src/connection_event.dart @@ -0,0 +1,34 @@ +import '../realtime.dart'; + +/// Connection event is same as [ConnectionState] except that it also handles +/// update operations on a connection +/// +/// See Ably Realtime API documentation for more details. +enum ConnectionEvent { + /// specifies that a connection is initialized + initialized, + + /// specifies that a connection to ably is being established + connecting, + + /// specifies that a connection to ably is established + connected, + + /// specifies that a connection to ably is disconnected + disconnected, + + /// specifies that a connection to ably is suspended + suspended, + + /// specifies that a connection to ably is closing + closing, + + /// specifies that a connection to ably is closed + closed, + + /// specifies that a connection to ably is failed + failed, + + /// specifies that a connection is updated + update, +} diff --git a/lib/src/realtime/src/connection_state.dart b/lib/src/realtime/src/connection_state.dart new file mode 100644 index 000000000..d5d78b8ae --- /dev/null +++ b/lib/src/realtime/src/connection_state.dart @@ -0,0 +1,27 @@ +/// See Ably Realtime API documentation for more details. +/// https://docs.ably.com/client-lib-development-guide/features/#connection-states-operations +enum ConnectionState { + /// specifies that a connection is initialized + initialized, + + /// specifies that a connection to ably is being established + connecting, + + /// specifies that a connection to ably is established + connected, + + /// specifies that a connection to ably is disconnected + disconnected, + + /// specifies that a connection to ably is suspended + suspended, + + /// specifies that a connection to ably is closing + closing, + + /// specifies that a connection to ably is closed + closed, + + /// specifies that a connection to ably is failed + failed, +} diff --git a/lib/src/realtime/src/connection_state_change.dart b/lib/src/realtime/src/connection_state_change.dart new file mode 100644 index 000000000..065b7ce3d --- /dev/null +++ b/lib/src/realtime/src/connection_state_change.dart @@ -0,0 +1,48 @@ +import '../../error/error.dart'; +import '../../platform/platform.dart'; +import 'connection_event.dart'; +import 'connection_state.dart'; + +/// Whenever the connection state changes, +/// a ConnectionStateChange object is emitted on the [Connection] object +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TA1 +class ConnectionStateChange { + /// https://docs.ably.com/client-lib-development-guide/features/#TA2 + final ConnectionEvent event; + + /// current state of the channel + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TA2 + final ConnectionState current; + + /// previous state of the channel + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TA2 + final ConnectionState previous; + + /// reason for failure, in case of a failed state + /// + /// If the channel state change includes error information, + /// then the reason attribute will contain an ErrorInfo + /// object describing the reason for the error + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TA3 + ErrorInfo? reason; + + /// when the client is not connected, a connection attempt will be made + /// automatically by the library after the number of milliseconds + /// specified by [retryIn] + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TA2 + int? retryIn; + + /// initializes without any defaults + ConnectionStateChange( + this.current, + this.previous, + this.event, { + this.reason, + this.retryIn, + }); +} diff --git a/lib/src/spec/realtime/presence.dart b/lib/src/realtime/src/presence.dart similarity index 92% rename from lib/src/spec/realtime/presence.dart rename to lib/src/realtime/src/presence.dart index d72eb089b..a2870dbf3 100644 --- a/lib/src/spec/realtime/presence.dart +++ b/lib/src/realtime/src/presence.dart @@ -1,8 +1,9 @@ import 'dart:async'; -import '../common.dart'; -import '../enums.dart'; -import '../message.dart'; +import '../../message/src/presence_action.dart'; +import '../../message/src/presence_message.dart'; +import '../../platform/platform.dart'; +import '../realtime.dart'; import 'channels.dart'; /// Presence object on a [RealtimeChannelInterface] helps to query @@ -28,7 +29,7 @@ abstract class RealtimePresenceInterface { /// filters the results if [params] are passed /// /// https://docs.ably.com/client-lib-development-guide/features/#RTP12 - Future> history([ + Future> history([ RealtimeHistoryParams? params, ]); diff --git a/lib/src/spec/realtime/realtime.dart b/lib/src/realtime/src/realtime.dart similarity index 78% rename from lib/src/spec/realtime/realtime.dart rename to lib/src/realtime/src/realtime.dart index 78dfaf720..e3f4ed9bf 100644 --- a/lib/src/spec/realtime/realtime.dart +++ b/lib/src/realtime/src/realtime.dart @@ -1,12 +1,12 @@ -import '../connection.dart'; -import '../rest/ably_base.dart'; -import '../rest/options.dart'; -import 'channels.dart'; +import '../../authentication/authentication.dart'; +import '../../common/common.dart'; +import '../realtime.dart'; /// an abstract class for Ably's Realtime client /// /// https://docs.ably.com/client-lib-development-guide/features/#RTC1 -abstract class RealtimeInterface extends AblyBase { +abstract class RealtimeInterface + extends AblyBase { /// https://docs.ably.com/client-lib-development-guide/features/#RTC1 RealtimeInterface({ ClientOptions? options, diff --git a/lib/src/realtime/src/realtime_history_params.dart b/lib/src/realtime/src/realtime_history_params.dart new file mode 100644 index 000000000..11f5ff4d5 --- /dev/null +++ b/lib/src/realtime/src/realtime_history_params.dart @@ -0,0 +1,29 @@ +// TODO stop extending RestHistoryParams: +// RealtimeHistoryParams is not a RestHistoryParams +import '../../rest/rest.dart'; + +/// https://docs.ably.com/client-lib-development-guide/features/#RTL10 +class RealtimeHistoryParams extends RestHistoryParams { + /// Decides whether to retrieve messages from earlier session. + /// + /// if true, will only retrieve messages prior to the moment that the channel + /// was attached or emitted an UPDATE indicating loss of continuity. + bool? untilAttach; + + /// instantiates with [direction] set to "backwards", [limit] to 100 + /// [start] to epoch and end to current time + /// + /// Raises [AssertionError] if [direction] is not "backwards" or "forwards" + RealtimeHistoryParams({ + DateTime? start, + DateTime? end, + String direction = 'backwards', + int limit = 100, + this.untilAttach, + }) : super( + start: start, + end: end, + direction: direction, + limit: limit, + ); +} diff --git a/lib/src/realtime/src/realtime_presence_params.dart b/lib/src/realtime/src/realtime_presence_params.dart new file mode 100644 index 000000000..7ccc13d95 --- /dev/null +++ b/lib/src/realtime/src/realtime_presence_params.dart @@ -0,0 +1,29 @@ +import '../../platform/platform.dart'; + +/// Params used as a filter for querying presence on a channel +/// +/// https://docs.ably.com/client-lib-development-guide/features/#RTP11c +class RealtimePresenceParams { + /// When true, [RealtimePresence.get] will wait until SYNC is complete + /// before returning a list of members + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RTP11c1 + final bool waitForSync; + + /// filters members by the provided clientId + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RTP11c2 + final String? clientId; + + /// filters members by the provided connectionId + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RTP11c3 + final String? connectionId; + + /// initializes with [waitForSync] set to true by default + RealtimePresenceParams({ + this.waitForSync = true, + this.clientId, + this.connectionId, + }); +} diff --git a/lib/src/rest/rest.dart b/lib/src/rest/rest.dart new file mode 100644 index 000000000..9e46d5d52 --- /dev/null +++ b/lib/src/rest/rest.dart @@ -0,0 +1,6 @@ +export 'src/channel_options.dart'; +export 'src/channels.dart'; +export 'src/presence.dart'; +export 'src/rest.dart'; +export 'src/rest_history_params.dart'; +export 'src/rest_presence_params.dart'; diff --git a/lib/src/rest/src/channel_options.dart b/lib/src/rest/src/channel_options.dart new file mode 100644 index 000000000..3aa810e6c --- /dev/null +++ b/lib/src/rest/src/channel_options.dart @@ -0,0 +1,10 @@ +/// options provided when instantiating a channel +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TB1 +class ChannelOptions { + /// https://docs.ably.com/client-lib-development-guide/features/#TB2b + final Object cipher; + + /// create channel options with a cipher + ChannelOptions(this.cipher); +} diff --git a/lib/src/spec/rest/channels.dart b/lib/src/rest/src/channels.dart similarity index 75% rename from lib/src/spec/rest/channels.dart rename to lib/src/rest/src/channels.dart index e3f4c891b..c4065274f 100644 --- a/lib/src/spec/rest/channels.dart +++ b/lib/src/rest/src/channels.dart @@ -1,18 +1,9 @@ -import '../../../ably_flutter.dart'; -import '../common.dart'; -import '../message.dart'; -import 'presence.dart'; - -/// options provided when instantiating a channel -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TB1 -class ChannelOptions { - /// https://docs.ably.com/client-lib-development-guide/features/#TB2b - final Object cipher; - - /// create channel options with a cipher - ChannelOptions(this.cipher); -} +import '../../common/common.dart'; +import '../../common/src/channels.dart'; +import '../../message/src/message.dart'; +import '../rest.dart'; +import 'channel_options.dart'; +import 'rest.dart'; /// A named channel through with rest client can interact with ably service. /// @@ -62,11 +53,11 @@ abstract class RestChannelInterface { /// A collection of rest channel objects /// /// https://docs.ably.com/client-lib-development-guide/features/#RSN1 -abstract class RestChannels +abstract class RestChannelsInterface extends Channels { /// instance of a rest client RestInterface rest; /// instantiates with the ably [RestInterface] instance - RestChannels(this.rest); + RestChannelsInterface(this.rest); } diff --git a/lib/src/spec/rest/presence.dart b/lib/src/rest/src/presence.dart similarity index 70% rename from lib/src/spec/rest/presence.dart rename to lib/src/rest/src/presence.dart index 6bd11dc3c..eed737a85 100644 --- a/lib/src/spec/rest/presence.dart +++ b/lib/src/rest/src/presence.dart @@ -1,8 +1,10 @@ -import '../common.dart'; -import '../message.dart'; -import 'channels.dart'; +import '../../common/common.dart'; +import '../../message/message.dart'; +import '../../platform/platform.dart'; +import 'rest_history_params.dart'; +import 'rest_presence_params.dart'; -/// Presence object on a [RestChannelInterface] helps to query Presence members +/// Presence object on a [RestChannel] helps to query Presence members /// and presence history /// /// https://docs.ably.com/client-lib-development-guide/features/#RSP1 diff --git a/lib/src/spec/rest/rest.dart b/lib/src/rest/src/rest.dart similarity index 70% rename from lib/src/spec/rest/rest.dart rename to lib/src/rest/src/rest.dart index 9624d96f0..7672b6e78 100644 --- a/lib/src/spec/rest/rest.dart +++ b/lib/src/rest/src/rest.dart @@ -1,17 +1,15 @@ -import '../rest/ably_base.dart'; -import '../rest/options.dart'; -import 'ably_base.dart'; +import '../../authentication/authentication.dart'; +import '../../common/common.dart'; import 'channels.dart'; -import 'options.dart'; /// an abstract class for Ably's Rest client /// /// https://docs.ably.com/client-lib-development-guide/features/#RSC1 -abstract class RestInterface extends AblyBase { +abstract class RestInterface extends AblyBase { /// collection of [RestChannelInterface] objects /// /// https://docs.ably.com/client-lib-development-guide/features/#RSN1 - late C channels; + late T channels; /// https://docs.ably.com/client-lib-development-guide/features/#RSC1 RestInterface({ diff --git a/lib/src/rest/src/rest_history_params.dart b/lib/src/rest/src/rest_history_params.dart new file mode 100644 index 000000000..39fe1a313 --- /dev/null +++ b/lib/src/rest/src/rest_history_params.dart @@ -0,0 +1,49 @@ +/// Params for rest history +/// +/// https://docs.ably.com/client-lib-development-guide/features/#RSL2b +class RestHistoryParams { + /// [start] must be equal to or less than end and is unaffected + /// by the request direction + /// + /// RLS2b1 + final DateTime start; + + /// [end] must be equal to or greater than start and is unaffected + /// by the request direction + /// + /// RLS2b1 + final DateTime end; + + /// Sorting history backwards or forwards + /// + /// if omitted the direction defaults to the REST API default (backwards) + /// RLS2b2 + final String direction; + + /// Number of items returned in one page + /// + /// [limit] supports up to 1,000 items. + /// if omitted the direction defaults to the REST API default (100) + /// RLS2b3 + final int limit; + + /// instantiates with [direction] set to "backwards", [limit] to 100 + /// [start] to epoch and end to current time + /// + /// Raises [AssertionError] if [direction] is not "backwards" or "forwards" + RestHistoryParams({ + DateTime? start, + DateTime? end, + this.direction = 'backwards', + this.limit = 100, + }) : assert(direction == 'backwards' || direction == 'forwards'), + start = start ?? DateTime.fromMillisecondsSinceEpoch(0), + end = end ?? DateTime.now(); + + @override + String toString() => 'RestHistoryParams:' + ' start=$start' + ' end=$end' + ' direction=$direction' + ' limit=$limit'; +} diff --git a/lib/src/rest/src/rest_presence_params.dart b/lib/src/rest/src/rest_presence_params.dart new file mode 100644 index 000000000..56c625703 --- /dev/null +++ b/lib/src/rest/src/rest_presence_params.dart @@ -0,0 +1,26 @@ +/// Params used as a filter for querying presence on a channel +/// +/// https://docs.ably.com/client-lib-development-guide/features/#RSP3a +class RestPresenceParams { + /// number of records to fetch per page + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSP3a1 + int limit; + + /// filters members by the provided clientId + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSP3a2 + String? clientId; + + /// filters members by the provided connectionId + /// + /// https://docs.ably.com/client-lib-development-guide/features/#RSP3a3 + String? connectionId; + + /// initializes with default [limit] set to 100 + RestPresenceParams({ + this.limit = 100, + this.clientId, + this.connectionId, + }); +} diff --git a/lib/src/spec/common.dart b/lib/src/spec/common.dart deleted file mode 100644 index 635b1f68b..000000000 --- a/lib/src/spec/common.dart +++ /dev/null @@ -1,975 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/services.dart'; -import 'package:meta/meta.dart'; - -import '../../ably_flutter.dart'; -import '../impl/realtime/connection.dart'; -import '../impl/realtime/presence.dart'; -import 'auth.dart'; -import 'enums.dart'; -import 'spec.dart'; - -/// params to configure encryption for a channel -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TZ1 -abstract class CipherParams { - /// Specifies the algorithm to use for encryption - /// - /// Default is AES. Currently only AES is supported. - /// https://docs.ably.com/client-lib-development-guide/features/#TZ2a - String? algorithm; - - /// private key used to encrypt and decrypt payloads - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TZ2d - dynamic key; - - /// the length in bits of the key - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TZ2b - int? keyLength; - - /// Specify cipher mode - /// - /// Default is CBC. Currently only CBC is supported - /// https://docs.ably.com/client-lib-development-guide/features/#TZ2c - String? mode; -} - -/// An [AblyException] encapsulates [ErrorInfo] which carries details -/// about information related to Ably-specific error [code], -/// generic [statusCode], error [message], -/// link to error related documentation as [href], -/// [requestId] and [cause] of this exception -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TI1 -class ErrorInfo { - /// ably specific error code - final int? code; - - /// link to error related documentation as - final String? href; - - /// error message - final String? message; - - /// cause for the error - final ErrorInfo? cause; - - /// generic status code - final int? statusCode; - - /// request id which triggered this exception - final String? requestId; - - /// instantiates a [ErrorInfo] with provided values - ErrorInfo({ - this.code, - this.href, - this.message, - this.cause, - this.statusCode, - this.requestId, - }); - - @override - String toString() => 'ErrorInfo' - ' message=$message' - ' code=$code' - ' statusCode=$statusCode' - ' href=$href'; -} - -/// MessageCount contains aggregate counts for messages and data transferred -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TS5 -abstract class StatsMessageCount { - /// Count of all messages. - int? count; - - /// Total data transferred for all messages in bytes. - int? data; -} - -/// MessageTypes contains a breakdown of summary stats data -/// for different (message vs presence) message types -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TS6 -abstract class StatsMessageTypes { - /// All messages count (includes both presence & messages). - StatsMessageCount? all; - - /// Count of channel messages. - StatsMessageCount? messages; - - /// Count of presence messages. - StatsMessageCount? presence; -} - -/// RequestCount contains aggregate counts for requests made -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TS8 -abstract class StatsRequestCount { - /// Requests failed. - int? failed; - - /// Requests refused typically as a result of permissions - /// or a limit being exceeded. - int? refused; - - /// Requests succeeded. - int? succeeded; -} - -/// ResourceCount contains aggregate data for usage of a resource -/// in a specific scope -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TS9 -abstract class StatsResourceCount { - /// Average resources of this type used for this period. - int? mean; - - /// Minimum total resources of this type used for this period. - int? min; - - /// Total resources of this type opened. - int? opened; - - /// Peak resources of this type used for this period. - int? peak; - - /// Resource requests refused within this period. - int? refused; -} - -/// ConnectionTypes contains a breakdown of summary stats data -/// for different (TLS vs non-TLS) connection types -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TS4 -abstract class StatsConnectionTypes { - /// All connection count (includes both TLS & non-TLS connections). - StatsResourceCount? all; - - /// Non-TLS connection count (unencrypted). - StatsResourceCount? plain; - - /// TLS connection count. - StatsResourceCount? tls; -} - -/// MessageTraffic contains a breakdown of summary stats data -/// for traffic over various transport types -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TS7 -abstract class StatsMessageTraffic { - /// All messages count (includes realtime, rest and webhook messages). - StatsMessageTypes? all; - - /// Count of messages transferred over a realtime transport - /// such as WebSockets. - StatsMessageTypes? realtime; - - /// Count of messages transferred using REST. - StatsMessageTypes? rest; - - /// Count of messages delivered using WebHooks. - StatsMessageTypes? webhook; -} - -/// A class providing parameters of a token request. -/// -/// Parameters for a token request -/// -/// [Auth.authorize], [Auth.requestToken] and [Auth.createTokenRequest] -/// accept an instance of TokenParams as a parameter -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TK1 -class TokenParams { - /// Capability of the token. - /// - /// If the token request is successful, the capability of the - /// returned token will be the intersection of this [capability] - /// with the capability of the issuing key. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TK2b - String? capability; - - /// A clientId to associate with this token. - /// - /// The generated token may be used to authenticate as this clientId. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TK2c - String? clientId; - - /// An opaque nonce string of at least 16 characters to ensure uniqueness. - /// - /// Timestamps, in conjunction with the nonce, - /// are used to prevent requests from being replayed - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TK2d - String? nonce; - - /// The timestamp (in millis since the epoch) of this request. - /// - /// Timestamps, in conjunction with the nonce, are used to prevent - /// token requests from being replayed. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TK2d - DateTime? timestamp; - - /// Requested time to live for the token. - /// - /// If the token request is successful, the TTL of the returned - /// token will be less than or equal to this value depending on - /// application settings and the attributes of the issuing key. - /// - /// 0 means Ably will set it to the default value - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TK2a - int? ttl; - - /// instantiates a [TokenParams] with provided values - TokenParams({ - this.capability, - this.clientId, - this.nonce, - this.timestamp, - this.ttl, - }); -} - -/// Response to a `requestToken` request -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TD1 -class TokenDetails { - /// https://docs.ably.com/client-lib-development-guide/features/#TD2 - String? token; - - /// Token expiry time in milliseconds - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TD3 - int? expires; - - /// the time the token was issued in milliseconds - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TD4 - int? issued; - - /// stringified capabilities JSON - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TD5 - String? capability; - - /// Client ID assigned to the token. - /// - /// If [clientId] is not set (i.e. null), then the token is prohibited - /// from assuming a clientId in any operations, however if clientId - /// is a wildcard string '*', then the token is permitted to assume - /// any clientId. Any other string value for clientId implies that the - /// clientId is both enforced and assumed for all operations for this token - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TD6 - String? clientId; - - /// instantiates a [TokenDetails] with provided values - TokenDetails( - this.token, { - this.expires, - this.issued, - this.capability, - this.clientId, - }); - - /// Creates an instance from the map - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TD7 - TokenDetails.fromMap(Map map) { - token = map['token'] as String?; - expires = map['expires'] as int?; - issued = map['issued'] as int?; - capability = map['capability'] as String?; - clientId = map['clientId'] as String?; - } -} - -/// spec: https://docs.ably.com/client-lib-development-guide/features/#TE1 -class TokenRequest { - /// [keyName] is the first part of Ably API Key. - /// - /// provided keyName will be used to authorize requests made to Ably. - /// spec: https://docs.ably.com/client-lib-development-guide/features/#TE2 - /// - /// More details about Ably API Key: - /// https://docs.ably.com/client-lib-development-guide/features/#RSA11 - String? keyName; - - /// An opaque nonce string of at least 16 characters to ensure - /// uniqueness of this request. Any subsequent request using the - /// same nonce will be rejected. - /// - /// spec: - /// https://docs.ably.com/client-lib-development-guide/features/#TE2 - /// https://docs.ably.com/client-lib-development-guide/features/#TE5 - String? nonce; - - /// The "Message Authentication Code" for this request. - /// - /// See the Ably Authentication documentation for more details. - /// spec: https://docs.ably.com/client-lib-development-guide/features/#TE2 - String? mac; - - /// stringified capabilities JSON - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TE3 - String? capability; - - /// Client ID assigned to the tokenRequest. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TE2 - String? clientId; - - /// timestamp long – The timestamp (in milliseconds since the epoch) - /// of this request. Timestamps, in conjunction with the nonce, - /// are used to prevent requests from being replayed - /// - /// spec: https://docs.ably.com/client-lib-development-guide/features/#TE5 - DateTime? timestamp; - - /// ttl attribute represents time to live (expiry) - /// of this token in milliseconds - /// - /// spec: https://docs.ably.com/client-lib-development-guide/features/#TE4 - int? ttl; - - /// instantiates a [TokenRequest] with provided values - TokenRequest({ - this.keyName, - this.nonce, - this.clientId, - this.mac, - this.capability, - this.timestamp, - this.ttl, - }); - - /// spec: https://docs.ably.com/client-lib-development-guide/features/#TE7 - TokenRequest.fromMap(Map map) { - keyName = map['keyName'] as String?; - nonce = map['nonce'] as String?; - mac = map['mac'] as String?; - capability = map['capability'] as String?; - clientId = map['clientId'] as String?; - timestamp = DateTime.fromMillisecondsSinceEpoch(map['timestamp'] as int); - ttl = map['ttl'] as int?; - } -} - -/// Params for rest history -/// -/// https://docs.ably.com/client-lib-development-guide/features/#RSL2b -class RestHistoryParams { - /// [start] must be equal to or less than end and is unaffected - /// by the request direction - /// - /// RLS2b1 - final DateTime start; - - /// [end] must be equal to or greater than start and is unaffected - /// by the request direction - /// - /// RLS2b1 - final DateTime end; - - /// Sorting history backwards or forwards - /// - /// if omitted the direction defaults to the REST API default (backwards) - /// RLS2b2 - final String direction; - - /// Number of items returned in one page - /// - /// [limit] supports up to 1,000 items. - /// if omitted the direction defaults to the REST API default (100) - /// RLS2b3 - final int limit; - - /// instantiates with [direction] set to "backwards", [limit] to 100 - /// [start] to epoch and end to current time - /// - /// Raises [AssertionError] if [direction] is not "backwards" or "forwards" - RestHistoryParams({ - DateTime? start, - DateTime? end, - this.direction = 'backwards', - this.limit = 100, - }) : assert(direction == 'backwards' || direction == 'forwards'), - start = start ?? DateTime.fromMillisecondsSinceEpoch(0), - end = end ?? DateTime.now(); - - @override - String toString() => 'RestHistoryParams:' - ' start=$start' - ' end=$end' - ' direction=$direction' - ' limit=$limit'; -} - -/// https://docs.ably.com/client-lib-development-guide/features/#RTL10 -class RealtimeHistoryParams extends RestHistoryParams { - /// Decides whether to retrieve messages from earlier session. - /// - /// if true, will only retrieve messages prior to the moment that the channel - /// was attached or emitted an UPDATE indicating loss of continuity. - bool? untilAttach; - - /// instantiates with [direction] set to "backwards", [limit] to 100 - /// [start] to epoch and end to current time - /// - /// Raises [AssertionError] if [direction] is not "backwards" or "forwards" - RealtimeHistoryParams({ - DateTime? start, - DateTime? end, - String direction = 'backwards', - int limit = 100, - this.untilAttach, - }) : super( - start: start, - end: end, - direction: direction, - limit: limit, - ); -} - -/// Params used as a filter for querying presence on a channel -/// -/// https://docs.ably.com/client-lib-development-guide/features/#RSP3a -class RestPresenceParams { - /// number of records to fetch per page - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSP3a1 - int limit; - - /// filters members by the provided clientId - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSP3a2 - String? clientId; - - /// filters members by the provided connectionId - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSP3a3 - String? connectionId; - - /// initializes with default [limit] set to 100 - RestPresenceParams({ - this.limit = 100, - this.clientId, - this.connectionId, - }); -} - -/// Params used as a filter for querying presence on a channel -/// -/// https://docs.ably.com/client-lib-development-guide/features/#RTP11c -class RealtimePresenceParams { - /// When true, [RealtimePresence.get] will wait until SYNC is complete - /// before returning a list of members - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RTP11c1 - final bool waitForSync; - - /// filters members by the provided clientId - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RTP11c2 - final String? clientId; - - /// filters members by the provided connectionId - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RTP11c3 - final String? connectionId; - - /// initializes with [waitForSync] set to true by default - RealtimePresenceParams({ - this.waitForSync = true, - this.clientId, - this.connectionId, - }); -} - -/// An exception generated by the native client library called by this plugin -class AblyException implements Exception { - /// platform error code - /// - /// Mostly used for storing [PlatformException.code] - final String? code; - - /// platform error message - /// - /// Mostly used for storing [PlatformException.message] - final String? message; - - /// error message from ably native sdk - final ErrorInfo? errorInfo; - - /// initializes with no defaults - AblyException([ - this.code, - this.message, - this.errorInfo, - ]); - - /// create AblyException from [PlatformException] - AblyException.fromPlatformException(PlatformException pe) - : code = pe.code, - message = pe.message, - errorInfo = pe.details as ErrorInfo?; - - @override - String toString() { - if (message == null) { - return 'AblyException (${(code == null) ? "" : '$code '})'; - } - return 'AblyException: $message (${(code == null) ? "" : '$code '})'; - } -} - -/// Whenever the channel state changes, a ChannelStateChange object -/// is emitted on the Channel object -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TH1 -class ChannelStateChange { - /// the event that generated the channel state change - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TH5 - final ChannelEvent event; - - /// current state of the channel - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TH2 - final ChannelState current; - - /// previous state of the channel - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TH2 - final ChannelState previous; - - /// reason for failure, in case of a failed state - /// - /// If the channel state change includes error information, - /// then the reason attribute will contain an ErrorInfo - /// object describing the reason for the error - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TH3 - ErrorInfo? reason; - - /// https://docs.ably.com/client-lib-development-guide/features/#TH4 - final bool? resumed; - - /// initializes with [resumed] set to false - ChannelStateChange( - this.current, - this.previous, - this.event, { - this.reason, - this.resumed = false, - }); -} - -/// Whenever the connection state changes, -/// a ConnectionStateChange object is emitted on the [Connection] object -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TA1 -class ConnectionStateChange { - /// https://docs.ably.com/client-lib-development-guide/features/#TA2 - final ConnectionEvent event; - - /// current state of the channel - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TA2 - final ConnectionState current; - - /// previous state of the channel - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TA2 - final ConnectionState previous; - - /// reason for failure, in case of a failed state - /// - /// If the channel state change includes error information, - /// then the reason attribute will contain an ErrorInfo - /// object describing the reason for the error - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TA3 - ErrorInfo? reason; - - /// when the client is not connected, a connection attempt will be made - /// automatically by the library after the number of milliseconds - /// specified by [retryIn] - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TA2 - int? retryIn; - - /// initializes without any defaults - ConnectionStateChange( - this.current, - this.previous, - this.event, { - this.reason, - this.retryIn, - }); -} - -/// Details of the push registration for a given device -/// -/// https://docs.ably.com/client-lib-development-guide/features/#PCP1 -abstract class DevicePushDetails { - /// A map of string key/value pairs containing details of the push transport - /// and address. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCP3 - Map? recipient; - - /// The state of the push registration. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCP4 - DevicePushState? state; - - /// Any error information associated with the registration. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCP2 - ErrorInfo? errorReason; -} - -/// Details of a registered device. -/// -/// https://docs.ably.com/client-lib-development-guide/features/#PCD1 -abstract class DeviceDetails { - /// The id of the device registration. - /// - /// Generated locally if not available - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCD2 - String? id; - - /// populated for device registrations associated with a clientId (optional) - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCD3 - String? clientId; - - /// The device platform. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCD6 - DevicePlatform? platform; - - /// the device form factor. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCD4 - FormFactor? formFactor; - - /// a map of string key/value pairs containing any other registered - /// metadata associated with the device registration - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCD5 - Map? metadata; - - /// Device token. Generated locally, if not available. - String? deviceSecret; - - /// Details of the push registration for this device. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCD7 - DevicePushDetails? push; -} - -/// Details of a push subscription to a channel. -/// -/// https://docs.ably.com/client-lib-development-guide/features/#PCS1 -abstract class PushChannelSubscription { - /// the channel name associated with this subscription - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCS4 - String? channel; - - /// populated for subscriptions made for a specific device registration - /// (optional) - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCS2 - String? deviceId; - - /// populated for subscriptions made for a specific clientId (optional) - /// - /// https://docs.ably.com/client-lib-development-guide/features/#PCS3 - String? clientId; -} - -/// Params to filter push device registrations. -/// -/// see: [PushDeviceRegistrations.list], [PushDeviceRegistrations.removeWhere] -/// https://docs.ably.com/client-lib-development-guide/features/#RSH1b2 -abstract class DeviceRegistrationParams { - /// filter by client id - String? clientId; - - /// filter by device id - String? deviceId; - - /// limit results for each page - int? limit; - - /// filter by device state - DevicePushState? state; -} - -/// Params to filter push channel subscriptions. -/// -/// See [PushChannelSubscriptions.list], [PushChannelSubscriptions.removeWhere] -/// https://docs.ably.com/client-lib-development-guide/features/#RSH1c1 -abstract class PushChannelSubscriptionParams { - /// filter by channel - String? channel; - - /// filter by clientId - String? clientId; - - /// filter by deviceId - String? deviceId; - - /// limit results for each page - int? limit; -} - -/// params to filter channels on a [PushChannelSubscriptions] -/// -/// https://docs.ably.com/client-lib-development-guide/features/#RSH1c2 -abstract class PushChannelsParams { - /// limit results for each page - int? limit; -} - -/// Interface implemented by event listeners, returned by event emitters. -abstract class EventListener { - /// Register for all events (no parameter), or a specific event. - Stream on([E? event]); - - /// Register for a single occurrence of any event (no parameter), - /// or a specific event. - Future once([E? event]); - - /// Remove registrations for this listener, irrespective of type. - Future off(); -} - -/// Interface implemented by Ably classes that can emit events, -/// offering the capability to create listeners for those events. -/// [E] is type of event to listen for -/// [G] is the instance which will be passed back in streams. -/// -/// -/// There is no `off` API as in other Ably client libraries as on returns a -/// [Stream] which can be subscribed for, and that subscription can be cancelled -/// using [StreamSubscription.cancel] API -abstract class EventEmitter { - /// Create a listener, with which registrations may be made. - Stream on([E? event]); -} - -/// PaginatedResult [TG1](https://docs.ably.com/client-lib-development-guide/features/#TG1) -/// -/// A type that represents page results from a paginated query. -/// The response is accompanied by metadata that indicates the -/// relative queries available. -abstract class PaginatedResultInterface { - /// items contain page of results - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TG3 - List get items; - - /// returns a new PaginatedResult loaded with the next page of results. - /// - /// If there are no further pages, then null is returned. - /// https://docs.ably.com/client-lib-development-guide/features/#TG4 - Future> next(); - - /// returns a new PaginatedResult with the first page of results - /// - /// If there are no further pages, then null is returned. - /// https://docs.ably.com/client-lib-development-guide/features/#TG5 - Future> first(); - - /// returns true if there are further pages - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TG6 - bool hasNext(); - - /// returns true if this page is the last page - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TG7 - bool isLast(); -} - -/// The response from an HTTP request containing an empty or -/// JSON-encodable object response -/// -/// [T] can be a [Map] or [List] -/// -/// https://docs.ably.com/client-lib-development-guide/features/#HP1 -abstract class HttpPaginatedResponse extends PaginatedResultInterface { - /// HTTP status code for the response - /// - /// https://docs.ably.com/client-lib-development-guide/features/#HP4 - int? statusCode; - - /// indicates whether the request is successful - /// - /// true when 200 <= [statusCode] < 300 - /// - /// https://docs.ably.com/client-lib-development-guide/features/#HP5 - bool? success; - - /// Value from X-Ably-Errorcode HTTP header, if available in response - /// - /// https://docs.ably.com/client-lib-development-guide/features/#HP6 - int? errorCode; - - /// Value from X-Ably-Errormessage HTTP header, if available in response - /// - /// https://docs.ably.com/client-lib-development-guide/features/#HP7 - String? errorMessage; - - /// Array of key value pairs of each response header - /// - /// https://docs.ably.com/client-lib-development-guide/features/#HP8 - List>? headers; - - /// returns a new HttpPaginatedResponse loaded with the next page of results. - /// - /// If there are no further pages, then null is returned. - /// https://docs.ably.com/client-lib-development-guide/features/#HP2 - @override - Future> next(); - - /// returns a new HttpPaginatedResponse with the first page of results - /// - /// If there are no further pages, then null is returned. - /// https://docs.ably.com/client-lib-development-guide/features/#HP2 - @override - Future> first(); -} - -/// A class representing an individual statistic for a specified [intervalId] -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TS1 -class Stats { - /// Aggregates inbound and outbound messages. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TS12e - StatsMessageTypes? all; - - /// Breakdown of API requests received via the REST API. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TS12e - StatsRequestCount? apiRequests; - - /// Breakdown of channels stats. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TS12e - StatsResourceCount? channels; - - /// Breakdown of connection stats data for different (TLS vs non-TLS) - /// connection types. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TS12i - StatsConnectionTypes? connections; - - /// All inbound messages i.e. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TS12f - StatsMessageTraffic? inbound; - - /// The interval that this statistic applies to, - /// see GRANULARITY and INTERVAL_FORMAT_STRING. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TS12a - String? intervalId; - - /// All outbound messages i.e. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TS12g - StatsMessageTraffic? outbound; - - /// Messages persisted for later retrieval via the history API. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TS12h - StatsMessageTypes? persisted; - - /// Breakdown of Token requests received via the REST API. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TS12l - StatsRequestCount? tokenRequests; -} - -/// Iterator class for [Channels.iterator] -class _ChannelIterator implements Iterator { - _ChannelIterator(this._channels); - - final List _channels; - - int _currentIndex = 0; - - T? _currentChannel; - - @override - T get current { - if (_currentChannel == null) { - throw StateError('Not iterating'); - } - return _currentChannel!; - } - - @override - bool moveNext() { - if (_currentIndex == _channels.length) { - return false; - } - _currentChannel = _channels[_currentIndex++]; - return true; - } -} - -/// A collection of Channel objects accessible -/// through [Rest.channels] or [Realtime.channels] -abstract class Channels extends Iterable { - /// stores channel name vs instance of [ChannelType] - final _channels = {}; - - /// creates a channel with provided name and options - /// - /// This is a private method to be overridden by implementation classes - @protected - ChannelType createChannel(String name); - - /// creates a channel with [name]. - /// - /// Doesn't create a channel instance on platform side yet. - ChannelType get(String name) { - if (_channels[name] == null) { - _channels[name] = createChannel(name); - } - return _channels[name]!; - } - - /// returns true if a channel exists [name] - bool exists(String name) => _channels[name] != null; - - /// Same as [get]. - ChannelType operator [](String name) => get(name); - - @override - Iterator get iterator => - _ChannelIterator(_channels.values.toList()); - - /// releases channel with [name] - void release(String name) { - _channels.remove(name); - } -} diff --git a/lib/src/spec/constants.dart b/lib/src/spec/constants.dart deleted file mode 100644 index 39a9bc770..000000000 --- a/lib/src/spec/constants.dart +++ /dev/null @@ -1,54 +0,0 @@ -import '../../ably_flutter.dart'; - -/// Log levels - control verbosity of log messages -/// -/// Can be used for [ClientOptions.logLevel] -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TO3b -/// -/// TODO(tiholic) convert [LogLevel] to enum and update encoder to pass -/// right numeric values to platform methods -class LogLevel { - /// No logging - static const int none = 99; - - /// Verbose logs - static const int verbose = 2; - - /// debug logs - static const int debug = 3; - - /// info logs - static const int info = 4; - - /// warning logs - static const int warn = 5; - - /// error logs - static const int error = 6; -} - -/// Static error codes used inside the SDK -class ErrorCodes { - /// error code sent from platform in case if response from - /// authCallback is not of valid type. - /// When this is encountered, flutter side will re-try the - /// method call after responding to authCallback method channel - /// call triggered from platform side - static const int authCallbackFailure = 80019; -} - -/// Static timeouts used inside the SDK -class Timeouts { - /// max time allowed for retrying an operation for auth failure - /// in case of usage of authCallback - static const retryOperationOnAuthFailure = Duration(seconds: 30); - - /// max time dart side will wait for platform side to respond with a - /// platform handle - static const acquireHandleTimeout = Duration(seconds: 5); - - /// max time dart side will wait for platform side to respond after - /// initializing an Ably instance on platform side - static const initializeTimeout = Duration(seconds: 5); -} diff --git a/lib/src/spec/enums.dart b/lib/src/spec/enums.dart deleted file mode 100644 index f2177a7b2..000000000 --- a/lib/src/spec/enums.dart +++ /dev/null @@ -1,246 +0,0 @@ -import '../../ably_flutter.dart'; - -/// Set of flags that represent the capabilities of a channel for current client -/// -/// See: -/// https://docs.ably.com/client-lib-development-guide/features/#TB2d -/// https://docs.ably.com/client-lib-development-guide/features/#RTL4m -enum ChannelMode { - /// specifies that channel can check for presence - presence, - - /// specifies that channel can publish - publish, - - /// specifies that channel can subscribe to messages - subscribe, - - /// specifies that channel can subscribe to presence events - presenceSubscribe, -} - -/// Connection state -/// -/// See Ably Realtime API documentation for more details. -/// https://docs.ably.com/client-lib-development-guide/features/#connection-states-operations -enum ConnectionState { - /// specifies that a connection is initialized - initialized, - - /// specifies that a connection to ably is being established - connecting, - - /// specifies that a connection to ably is established - connected, - - /// specifies that a connection to ably is disconnected - disconnected, - - /// specifies that a connection to ably is suspended - suspended, - - /// specifies that a connection to ably is closing - closing, - - /// specifies that a connection to ably is closed - closed, - - /// specifies that a connection to ably is failed - failed, -} - -/// Connection event is same as [ConnectionState] except that it also handles -/// update operations on a connection -/// -/// See Ably Realtime API documentation for more details. -enum ConnectionEvent { - /// specifies that a connection is initialized - initialized, - - /// specifies that a connection to ably is being established - connecting, - - /// specifies that a connection to ably is established - connected, - - /// specifies that a connection to ably is disconnected - disconnected, - - /// specifies that a connection to ably is suspended - suspended, - - /// specifies that a connection to ably is closing - closing, - - /// specifies that a connection to ably is closed - closed, - - /// specifies that a connection to ably is failed - failed, - - /// specifies that a connection is updated - update, -} - -/// Channel states -/// -/// See Ably Realtime API documentation for more details. -/// https://docs.ably.com/client-lib-development-guide/features/#channel-states-operations -enum ChannelState { - /// represents that a channel is initialized and no action was taken - /// i.e., even auto connect was not triggered - if enabled - initialized, - - /// channel is attaching - attaching, - - /// channel is attached - attached, - - /// channel is detaching - detaching, - - /// channel is detached - detached, - - /// channel is suspended - suspended, - - /// channel failed to connect - failed, -} - -/// Channel events -/// -/// See Ably Realtime API documentation for more details. -enum ChannelEvent { - /// represents that a channel is initialized and no action was taken - /// i.e., even auto connect was not triggered - if enabled - initialized, - - /// channel is attaching - attaching, - - /// channel is attached - attached, - - /// channel is detaching - detaching, - - /// channel is detached - detached, - - /// channel is suspended - suspended, - - /// channel failed to connect - failed, - - /// specifies that a channel state is updated - update, -} - -/// Status on a presence message -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TP2 -enum PresenceAction { - /// indicates that a client is absent for incoming [PresenceMessage] - absent, - - /// indicates that a client is present for incoming [PresenceMessage] - present, - - /// indicates that a client wants to enter a channel presence via - /// outgoing [PresenceMessage] - enter, - - /// indicates that a client wants to leave a channel presence via - /// outgoing [PresenceMessage] - leave, - - /// indicates that presence status of a client in presence member map - /// needs to be updated - update, -} - -/// https://docs.ably.com/client-lib-development-guide/features/#TS12c -enum StatsIntervalGranularity { - /// indicates units in minutes - minute, - - /// indicates units in hours - hour, - - /// indicates units in days - day, - - /// indicates units in months - month, -} - -/// Java: io.ably.lib.http.HttpAuth.Type -enum HttpAuthType { - /// indicates basic authentication - basic, - - /// digest authentication - digest, - - /// Token auth - xAblyToken, -} - -/// To indicate Push State of a device in [DeviceDetails] via [DevicePushState] -/// while registering -enum DevicePushState { - /// indicates active push state of the device - active, - - /// indicates that push state is failing - failing, - - /// indicates the device push state failed - failed, -} - -/// To indicate the operating system -or- platform of the client using SDK -/// in [DeviceDetails] while registering -enum DevicePlatform { - /// indicates an android device - android, - - /// indicates an iOS device - ios, - - /// indicates a browser - browser, -} - -/// To indicate the type of device in [DeviceDetails] while registering -/// -/// https://docs.ably.com/client-lib-development-guide/features/#PCD4 -enum FormFactor { - /// indicates the device is a mobile phone - phone, - - /// indicates the device is a tablet - tablet, - - /// indicates the device is a desktop - desktop, - - /// indicates the device is a television - tv, - - /// indicates the device is a smart watch - watch, - - /// indicates the device is an automobile - car, - - /// indicates the device is an embedded system / iOT device - embedded, - - /// indicates the device belong to categories other than mentioned above - other, -} diff --git a/lib/src/spec/message.dart b/lib/src/spec/message.dart deleted file mode 100644 index 350ef5fe9..000000000 --- a/lib/src/spec/message.dart +++ /dev/null @@ -1,376 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:collection/collection.dart'; -import 'package:meta/meta.dart'; - -import '../generated/platformconstants.dart'; -import 'enums.dart'; -import 'rest/channels.dart'; - -/// Handles supported message data types, their encoding and decoding -class MessageData { - final T _data; - - /// Only Map, List, string and Buffer types are supported - MessageData(this._data) - : assert(T == Map || T == List || T == String || T == Uint8List); - - /// retrieve data - T get data => _data; - - /// initializes [MessageData] with given value and asserts from input type - static MessageData? fromValue(Object? value) { - if (value == null) { - return null; - } - assert( - value is MessageData || - value is Map || - value is List || - value is String || - value is Uint8List, - 'Message data must be either `Map`, `List`, `String` or `Uint8List`.' - ' Does not support $value ("${value.runtimeType}")', - ); - if (value is MessageData) { - return value; - } else if (value is Map) { - return MessageData(value); - } else if (value is Uint8List) { - return MessageData(value); - } else if (value is List) { - return MessageData(value); - } else if (value is String) { - return MessageData(value); - } else { - throw AssertionError( - 'Message data must be either `Map`, `List`, `String` or `Uint8List`.' - ' Does not support $value ("${value.runtimeType}")', - ); - } - } -} - -/// Delta extension configuration for [MessageExtras] -@immutable -class DeltaExtras { - /// the id of the message the delta was generated from - final String? from; - - /// the delta format. Only "vcdiff" is supported currently - final String? format; - - /// create instance from a map - DeltaExtras._fromMap(Map value) - : from = value[TxDeltaExtras.from] as String?, - format = value[TxDeltaExtras.format] as String?; - - @override - bool operator ==(Object other) => - other is DeltaExtras && other.from == from && other.format == format; - - @override - int get hashCode => '$from:$format'.hashCode; -} - -/// Handles supported message extras types, their encoding and decoding -@immutable -class MessageExtras { - /// json-encodable map of extras - final Map? map; - - /// configuration for delta compression extension - final DeltaExtras? _delta; - - /// delta configuration received from channel message - DeltaExtras? get delta => _delta; - - /// Creates an instance from given extras map - const MessageExtras(this.map) : _delta = null; - - /// Creates an instance from given extras map and an instance of DeltaExtras - const MessageExtras._withDelta(this.map, this._delta); - - /// initializes [MessageExtras] with given value and validates - /// the data type, runtime - static MessageExtras? fromMap(Map? extrasMap) { - if (extrasMap == null) return null; - extrasMap = Map.castFrom( - json.decode(json.encode(extrasMap)) as Map, - ); - final deltaMap = extrasMap.remove(TxMessageExtras.delta) as Map?; - return MessageExtras._withDelta( - extrasMap, - (deltaMap == null) ? null : DeltaExtras._fromMap(deltaMap), - ); - } - - @override - String toString() => {'extras': map, 'delta': delta}.toString(); - - @override - bool operator ==(Object other) => - other is MessageExtras && - const MapEquality().equals(other.map, map) && - other.delta == delta; - - @override - int get hashCode => '${map.hashCode}:${delta.hashCode}'.hashCode; -} - -/// An individual message to be sent/received by Ably -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TM1 -@immutable -class Message { - /// A unique ID for this message - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TM2a - final String? id; - - /// The timestamp for this message - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TM2f - final DateTime? timestamp; - - /// The id of the publisher of this message - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TM2b - final String? clientId; - - /// The connection id of the publisher of this message - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TM2c - final String? connectionId; - - /// Any transformation applied to the data for this message - final String? encoding; - - final MessageData? _data; - - /// Message payload - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TM2d - Object? get data => _data?.data; - - /// Name of the message - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TM2g - final String? name; - - /// Message extras that may contain message metadata - /// and/or ancillary payloads - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TM2i - final MessageExtras? extras; - - /// Creates a message instance with [name], [data] and [clientId] - Message({ - this.id, - this.name, - Object? data, - this.clientId, - this.connectionId, - this.timestamp, - this.encoding, - this.extras, - }) : _data = MessageData.fromValue(data); - - @override - bool operator ==(Object other) => - other is Message && - other.id == id && - other.name == name && - other.data == data && - other.extras == extras && - other.encoding == encoding && - other.clientId == clientId && - other.timestamp == timestamp && - other.connectionId == connectionId; - - @override - int get hashCode => '$id:' - '$name:' - '$encoding:' - '$clientId:' - '$timestamp:' - '$connectionId:' - '${data?.hashCode}:' - '${extras?.hashCode}:' - .hashCode; - - /// https://docs.ably.com/client-lib-development-guide/features/#TM3 - /// - /// TODO(tiholic): decoding and decryption is not implemented as per - /// RSL6 and RLS6b as mentioned in TM3 - Message.fromEncoded( - Map jsonObject, [ - ChannelOptions? channelOptions, - ]) : id = jsonObject['id'] as String?, - name = jsonObject['name'] as String?, - clientId = jsonObject['clientId'] as String?, - connectionId = jsonObject['connectionId'] as String?, - _data = MessageData.fromValue(jsonObject['data']), - encoding = jsonObject['encoding'] as String?, - extras = MessageExtras.fromMap( - Map.castFrom( - jsonObject['extras'] as Map, - ), - ), - timestamp = jsonObject['timestamp'] != null - ? DateTime.fromMillisecondsSinceEpoch( - jsonObject['timestamp'] as int, - ) - : null; - - /// https://docs.ably.com/client-lib-development-guide/features/#TM3 - static List fromEncodedArray( - List> jsonArray, [ - ChannelOptions? channelOptions, - ]) => - jsonArray.map((e) => Message.fromEncoded(e, channelOptions)).toList(); - - @override - String toString() => 'Message' - ' id=$id' - ' name=$name' - ' data=$data' - ' extras=$extras' - ' encoding=$encoding' - ' clientId=$clientId' - ' timestamp=$timestamp' - ' connectionId=$connectionId'; - -// TODO(tiholic) add support for fromEncoded and fromEncodedArray (TM3) -} - -/// An individual presence message sent or received via realtime -/// -/// https://docs.ably.com/client-lib-development-guide/features/#TP1 -@immutable -class PresenceMessage { - /// unique ID for this presence message - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TP3a - final String? id; - - /// presence action - to update presence status of current client, - /// or to understand presence state of another client - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TP3b - final PresenceAction? action; - - /// https://docs.ably.com/client-lib-development-guide/features/#TP3c - final String? clientId; - - /// connection id of the source of this message - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TP3d - final String? connectionId; - - final MessageData? _data; - - /// Message payload - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TP3e - Object? get data => _data?.data; - - /// https://docs.ably.com/client-lib-development-guide/features/#TP3f - final String? encoding; - - /// Message extras that may contain message metadata - /// and/or ancillary payloads - /// - /// https://docs.ably.com/client-lib-development-guide/features/#TP3i - final MessageExtras? extras; - - /// https://docs.ably.com/client-lib-development-guide/features/#TP3g - final DateTime? timestamp; - - /// https://docs.ably.com/client-lib-development-guide/features/#TP3h - String get memberKey => '$connectionId:$clientId'; - - /// instantiates presence message with - PresenceMessage({ - this.id, - this.action, - this.clientId, - this.connectionId, - Object? data, - this.encoding, - this.extras, - this.timestamp, - }) : _data = MessageData.fromValue(data); - - @override - bool operator ==(Object other) => - other is PresenceMessage && - other.id == id && - other.action == action && - other.clientId == clientId && - other.connectionId == connectionId && - other.data == data && - other.encoding == encoding && - other.extras == extras && - other.timestamp == timestamp; - - @override - int get hashCode => '$id:' - '$encoding:' - '$clientId:' - '$timestamp:' - '$connectionId:' - '${data?.toString()}:' - '${action.toString()}:' - '${extras?.toString()}:' - .hashCode; - - /// https://docs.ably.com/client-lib-development-guide/features/#TP4 - /// - /// TODO(tiholic): decoding and decryption is not implemented as per - /// RSL6 and RLS6b as mentioned in TP4 - PresenceMessage.fromEncoded( - Map jsonObject, [ - ChannelOptions? channelOptions, - ]) : id = jsonObject['id'] as String?, - action = PresenceAction.values.firstWhere((e) => - e.toString().split('.')[1] == jsonObject['action'] as String?), - clientId = jsonObject['clientId'] as String?, - connectionId = jsonObject['connectionId'] as String?, - _data = MessageData.fromValue(jsonObject['data']), - encoding = jsonObject['encoding'] as String?, - extras = MessageExtras.fromMap( - Map.castFrom( - jsonObject['extras'] as Map, - ), - ), - timestamp = jsonObject['timestamp'] != null - ? DateTime.fromMillisecondsSinceEpoch( - jsonObject['timestamp'] as int, - ) - : null; - - /// https://docs.ably.com/client-lib-development-guide/features/#TP4 - static List fromEncodedArray( - List> jsonArray, [ - ChannelOptions? channelOptions, - ]) => - jsonArray - .map((jsonObject) => PresenceMessage.fromEncoded( - jsonObject, - channelOptions, - )) - .toList(); - - @override - String toString() => 'PresenceMessage' - ' id=$id' - ' data=$data' - ' action=$action' - ' extras=$extras' - ' encoding=$encoding' - ' clientId=$clientId' - ' timestamp=$timestamp' - ' connectionId=$connectionId'; -} diff --git a/lib/src/spec/push/push.dart b/lib/src/spec/push/push.dart deleted file mode 100644 index 8dd035560..000000000 --- a/lib/src/spec/push/push.dart +++ /dev/null @@ -1,113 +0,0 @@ -import '../common.dart'; - -/// Manage push notification channel subscriptions for devices or clients -abstract class PushChannelSubscriptions { - /// List channel subscriptions filtered by optional params. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c1 - Future> list( - PushChannelSubscriptionParams params, - ); - - /// List channels with at least one subscribed device. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c2 - Future> listChannels( - PushChannelsParams params, - ); - - /// Save push channel subscription for a device or client ID. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c3 - Future save(PushChannelSubscription subscription); - - /// Remove a push channel subscription. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c4 - Future remove(PushChannelSubscription subscription); - - /// Remove all matching push channel subscriptions. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c5 - Future removeWhere(PushChannelSubscriptionParams params); -} - -/// Manage device registrations for push notifications -abstract class PushDeviceRegistrations { - /// Get registered device by device ID. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b1 - Future get({ - DeviceDetails? deviceDetails, - String? deviceId, - }); - - /// List registered devices filtered by optional params. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b2 - Future> list( - DeviceRegistrationParams params, - ); - - /// Save and register device. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b3 - Future save(DeviceDetails deviceDetails); - - /// Remove device. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b4 - Future remove({ - DeviceDetails? deviceDetails, - String? deviceId, - }); - - /// Remove device matching where params. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b5 - Future removeWhere(DeviceRegistrationParams params); -} - -/// Class providing push notification administrative functionality -/// for registering devices and attaching to channels etc. -/// -/// https://docs.ably.com/client-lib-development-guide/features/#RSH1 -abstract class PushAdmin { - /// Manage device registrations. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1b - PushDeviceRegistrations? deviceRegistrations; - - /// Manage channel subscriptions for devices or clients. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1c - PushChannelSubscriptions? channelSubscriptions; - - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1a - Future publish(Map recipient, Map payload); -} - -/// Class providing push notification functionality -/// -/// https://docs.ably.com/client-lib-development-guide/features/#RSH1 -abstract class Push { - /// Admin features for push notifications like managing devices - /// and channel subscriptions. - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSH1 - PushAdmin? admin; - - /// Activate this device for push notifications by registering - /// with the push transport such as GCM/APNS. - /// - /// returns DeviceDetails - /// https://docs.ably.com/client-lib-development-guide/features/#RSH2a - Future activate(); - - /// Deactivate this device for push notifications by removing - /// the registration with the push transport such as GCM/APNS. - /// - /// returns deviceId - /// https://docs.ably.com/client-lib-development-guide/features/#RSH2b - Future deactivate(); -} diff --git a/lib/src/spec/spec.dart b/lib/src/spec/spec.dart deleted file mode 100644 index c27687597..000000000 --- a/lib/src/spec/spec.dart +++ /dev/null @@ -1,16 +0,0 @@ -export 'auth.dart'; -export 'common.dart'; -export 'common.dart'; -export 'connection.dart'; -export 'constants.dart'; -export 'enums.dart'; -export 'message.dart'; -export 'push/push.dart'; -export 'realtime/channels.dart'; -export 'realtime/presence.dart'; -export 'realtime/realtime.dart'; -export 'rest/ably_base.dart'; -export 'rest/channels.dart'; -export 'rest/options.dart'; -export 'rest/presence.dart'; -export 'rest/rest.dart'; diff --git a/lib/src/stats/stats.dart b/lib/src/stats/stats.dart new file mode 100644 index 000000000..03b4d9bb4 --- /dev/null +++ b/lib/src/stats/stats.dart @@ -0,0 +1,57 @@ +import 'stats_connection_types.dart'; +import 'stats_message_traffic.dart'; +import 'stats_message_types.dart'; +import 'stats_request_count.dart'; +import 'stats_resource_count.dart'; + +/// A class representing an individual statistic for a specified [intervalId] +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TS1 +class Stats { + /// Aggregates inbound and outbound messages. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TS12e + StatsMessageTypes? all; + + /// Breakdown of API requests received via the REST API. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TS12e + StatsRequestCount? apiRequests; + + /// Breakdown of channels stats. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TS12e + StatsResourceCount? channels; + + /// Breakdown of connection stats data for different (TLS vs non-TLS) + /// connection types. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TS12i + StatsConnectionTypes? connections; + + /// All inbound messages i.e. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TS12f + StatsMessageTraffic? inbound; + + /// The interval that this statistic applies to, + /// see GRANULARITY and INTERVAL_FORMAT_STRING. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TS12a + String? intervalId; + + /// All outbound messages i.e. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TS12g + StatsMessageTraffic? outbound; + + /// Messages persisted for later retrieval via the history API. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TS12h + StatsMessageTypes? persisted; + + /// Breakdown of Token requests received via the REST API. + /// + /// https://docs.ably.com/client-lib-development-guide/features/#TS12l + StatsRequestCount? tokenRequests; +} diff --git a/lib/src/stats/stats_connection_types.dart b/lib/src/stats/stats_connection_types.dart new file mode 100644 index 000000000..3589cd12f --- /dev/null +++ b/lib/src/stats/stats_connection_types.dart @@ -0,0 +1,16 @@ +import 'stats_resource_count.dart'; + +/// ConnectionTypes contains a breakdown of summary stats data +/// for different (TLS vs non-TLS) connection types +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TS4 +abstract class StatsConnectionTypes { + /// All connection count (includes both TLS & non-TLS connections). + StatsResourceCount? all; + + /// Non-TLS connection count (unencrypted). + StatsResourceCount? plain; + + /// TLS connection count. + StatsResourceCount? tls; +} diff --git a/lib/src/stats/stats_interval_granularity.dart b/lib/src/stats/stats_interval_granularity.dart new file mode 100644 index 000000000..81918c688 --- /dev/null +++ b/lib/src/stats/stats_interval_granularity.dart @@ -0,0 +1,14 @@ +/// https://docs.ably.com/client-lib-development-guide/features/#TS12c +enum StatsIntervalGranularity { + /// indicates units in minutes + minute, + + /// indicates units in hours + hour, + + /// indicates units in days + day, + + /// indicates units in months + month, +} diff --git a/lib/src/stats/stats_message_count.dart b/lib/src/stats/stats_message_count.dart new file mode 100644 index 000000000..cdbe89d3d --- /dev/null +++ b/lib/src/stats/stats_message_count.dart @@ -0,0 +1,10 @@ +/// MessageCount contains aggregate counts for messages and data transferred +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TS5 +abstract class StatsMessageCount { + /// Count of all messages. + int? count; + + /// Total data transferred for all messages in bytes. + int? data; +} diff --git a/lib/src/stats/stats_message_traffic.dart b/lib/src/stats/stats_message_traffic.dart new file mode 100644 index 000000000..6ff5478ea --- /dev/null +++ b/lib/src/stats/stats_message_traffic.dart @@ -0,0 +1,20 @@ +import 'stats_message_types.dart'; + +/// MessageTraffic contains a breakdown of summary stats data +/// for traffic over various transport types +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TS7 +abstract class StatsMessageTraffic { + /// All messages count (includes realtime, rest and webhook messages). + StatsMessageTypes? all; + + /// Count of messages transferred over a realtime transport + /// such as WebSockets. + StatsMessageTypes? realtime; + + /// Count of messages transferred using REST. + StatsMessageTypes? rest; + + /// Count of messages delivered using WebHooks. + StatsMessageTypes? webhook; +} diff --git a/lib/src/stats/stats_message_types.dart b/lib/src/stats/stats_message_types.dart new file mode 100644 index 000000000..9348467d1 --- /dev/null +++ b/lib/src/stats/stats_message_types.dart @@ -0,0 +1,16 @@ +import 'stats_message_count.dart'; + +/// MessageTypes contains a breakdown of summary stats data +/// for different (message vs presence) message types +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TS6 +abstract class StatsMessageTypes { + /// All messages count (includes both presence & messages). + StatsMessageCount? all; + + /// Count of channel messages. + StatsMessageCount? messages; + + /// Count of presence messages. + StatsMessageCount? presence; +} diff --git a/lib/src/stats/stats_request_count.dart b/lib/src/stats/stats_request_count.dart new file mode 100644 index 000000000..5bd0d8a1e --- /dev/null +++ b/lib/src/stats/stats_request_count.dart @@ -0,0 +1,14 @@ +/// RequestCount contains aggregate counts for requests made +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TS8 +abstract class StatsRequestCount { + /// Requests failed. + int? failed; + + /// Requests refused typically as a result of permissions + /// or a limit being exceeded. + int? refused; + + /// Requests succeeded. + int? succeeded; +} diff --git a/lib/src/stats/stats_resource_count.dart b/lib/src/stats/stats_resource_count.dart new file mode 100644 index 000000000..89781023f --- /dev/null +++ b/lib/src/stats/stats_resource_count.dart @@ -0,0 +1,20 @@ +/// ResourceCount contains aggregate data for usage of a resource +/// in a specific scope +/// +/// https://docs.ably.com/client-lib-development-guide/features/#TS9 +abstract class StatsResourceCount { + /// Average resources of this type used for this period. + int? mean; + + /// Minimum total resources of this type used for this period. + int? min; + + /// Total resources of this type opened. + int? opened; + + /// Peak resources of this type used for this period. + int? peak; + + /// Resource requests refused within this period. + int? refused; +} diff --git a/test/ably_flutter_plugin_test.dart b/test/ably_flutter_plugin_test.dart index 73f1a00ca..8a21a8713 100644 --- a/test/ably_flutter_plugin_test.dart +++ b/test/ably_flutter_plugin_test.dart @@ -1,11 +1,9 @@ import 'package:ably_flutter/ably_flutter.dart'; -import 'package:ably_flutter/src/impl/rest/rest.dart'; -import 'package:ably_flutter/src/info.dart'; -import 'package:ably_flutter/src/platform.dart' as platform; +import 'package:ably_flutter/src/generated/platform_constants.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - final channel = platform.methodChannel; + final channel = Platform.methodChannel; TestWidgetsFlutterBinding.ensureInitialized(); var counter = 0; diff --git a/test/utils.dart b/test/mock_method_call_manager.dart similarity index 83% rename from test/utils.dart rename to test/mock_method_call_manager.dart index 913384139..f88d7ce8b 100644 --- a/test/utils.dart +++ b/test/mock_method_call_manager.dart @@ -1,7 +1,5 @@ import 'package:ably_flutter/ably_flutter.dart'; -import 'package:ably_flutter/src/impl/message.dart'; -import 'package:ably_flutter/src/method_call_handler.dart'; -import 'package:ably_flutter/src/platform.dart' as platform; +import 'package:ably_flutter/src/generated/platform_constants.dart'; import 'package:flutter/services.dart'; typedef MethodCallHandler = Future Function(MethodCall); @@ -11,17 +9,16 @@ class MockMethodCallManager { bool isAuthenticated = false; final channels = {}; final publishedMessages = []; - final methodChannel = platform.methodChannel; MockMethodCallManager() { - methodChannel.setMockMethodCallHandler(handler); + Platform.methodChannel.setMockMethodCallHandler(handler); } void reset() { channels.clear(); publishedMessages.clear(); handleCounter = 0; - methodChannel.setMockMethodCallHandler(null); + Platform.methodChannel.setMockMethodCallHandler(null); } Future handler(MethodCall methodCall) async { @@ -45,7 +42,7 @@ class MockMethodCallManager { // because function references (in `authCallback`) get dropped by the // PlatformChannel. if (!isAuthenticated && clientOptions.authUrl == 'hasAuthCallback') { - await AblyMethodCallHandler(methodChannel).onAuthCallback( + await AblyMethodCallHandler(Platform.methodChannel).onAuthCallback( AblyMessage( TokenParams(timestamp: DateTime.now()), handle: handle, @@ -70,9 +67,10 @@ class MockMethodCallManager { // because function references (in `authCallback`) get dropped by the // PlatformChannel. if (!isAuthenticated && clientOptions.authUrl == 'hasAuthCallback') { - await AblyMethodCallHandler(methodChannel).onRealtimeAuthCallback( - AblyMessage(TokenParams(timestamp: DateTime.now()), - handle: handle)); + await AblyMethodCallHandler(Platform.methodChannel) + .onRealtimeAuthCallback( + AblyMessage(TokenParams(timestamp: DateTime.now()), handle: handle), + ); isAuthenticated = true; throw PlatformException( code: ErrorCodes.authCallbackFailure.toString(), diff --git a/test/models/client_options.dart b/test/models/client_options.dart index b87cf0f36..592d36241 100644 --- a/test/models/client_options.dart +++ b/test/models/client_options.dart @@ -1,5 +1,5 @@ -import 'package:test/test.dart'; import 'package:ably_flutter/ably_flutter.dart'; +import 'package:test/test.dart'; void main() { /// We are leaving it up to the platform client library SDK to supply defaults diff --git a/test/realtime/channel_test.dart b/test/realtime/channel_test.dart index 4bc20e357..7f9979adc 100644 --- a/test/realtime/channel_test.dart +++ b/test/realtime/channel_test.dart @@ -1,12 +1,12 @@ import 'dart:async'; import 'package:ably_flutter/ably_flutter.dart'; -import 'package:ably_flutter/src/impl/message.dart'; +import 'package:ably_flutter/src/generated/platform_constants.dart'; import 'package:fake_async/fake_async.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pedantic/pedantic.dart'; -import '../utils.dart'; +import '../mock_method_call_manager.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -15,11 +15,10 @@ void main() { setUp(() { manager = MockMethodCallManager(); - manager.methodChannel.setMockMethodCallHandler(manager.handler); }); tearDown(() { - manager.methodChannel.setMockMethodCallHandler(null); + manager.reset(); }); group('realtime#channels#channel', () { diff --git a/test/rest/channel_test.dart b/test/rest/channel_test.dart index eed7d01cd..01b191825 100644 --- a/test/rest/channel_test.dart +++ b/test/rest/channel_test.dart @@ -1,12 +1,12 @@ import 'dart:async'; import 'package:ably_flutter/ably_flutter.dart'; -import 'package:ably_flutter/src/impl/message.dart'; +import 'package:ably_flutter/src/generated/platform_constants.dart'; import 'package:fake_async/fake_async.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pedantic/pedantic.dart'; -import '../utils.dart'; +import '../mock_method_call_manager.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -15,11 +15,10 @@ void main() { setUp(() { manager = MockMethodCallManager(); - manager.methodChannel.setMockMethodCallHandler(manager.handler); }); tearDown(() { - manager.methodChannel.setMockMethodCallHandler(null); + manager.reset(); }); group('rest#channels#channel', () { diff --git a/test/rest/channels_test.dart b/test/rest/channels_test.dart index 0aa4b527f..ecfead855 100644 --- a/test/rest/channels_test.dart +++ b/test/rest/channels_test.dart @@ -1,13 +1,13 @@ import 'package:ably_flutter/ably_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../utils.dart'; +import '../mock_method_call_manager.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late MockMethodCallManager manager; - late RestPlatformChannels channels; + late RestChannels channels; setUp(() { manager = MockMethodCallManager(); @@ -38,8 +38,8 @@ void main() { test('creates/returns channel with list accessor #[]', () { final channel2 = channels.get('channel-2'); - final chanel2WithSyntacticSugar = channels['channel-2']; - expect(chanel2WithSyntacticSugar, channel2); + final channel2WithSyntacticSugar = channels['channel-2']; + expect(channel2WithSyntacticSugar, channel2); final channel3 = channels['channel-3']; expect(channel3.name, 'channel-3');