diff --git a/android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java b/android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java index 274471339..612b7e1b4 100644 --- a/android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java +++ b/android/src/main/java/io/ably/flutter/plugin/AblyMessageCodec.java @@ -40,6 +40,7 @@ import io.ably.lib.types.MessageExtras; import io.ably.lib.types.Param; import io.ably.lib.types.PresenceMessage; +import io.ably.lib.types.Stats; import io.ably.lib.util.Crypto; import io.ably.lib.util.Log; import io.flutter.plugin.common.StandardMessageCodec; @@ -112,6 +113,8 @@ public AblyMessageCodec(CipherParamsStorage cipherParamsStorage) { new CodecPair<>(null, self::decodeRestHistoryParams)); put(PlatformConstants.CodecTypes.realtimeHistoryParams, new CodecPair<>(null, self::decodeRealtimeHistoryParams)); + put(PlatformConstants.CodecTypes.stats, + new CodecPair<>(self::encodeStats, null)); put(PlatformConstants.CodecTypes.restPresenceParams, new CodecPair<>(null, self::decodeRestPresenceParams)); put(PlatformConstants.CodecTypes.realtimePresenceParams, @@ -144,6 +147,81 @@ public AblyMessageCodec(CipherParamsStorage cipherParamsStorage) { }; } + private Map encodeStats(Stats stats) { + if (stats == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxStats.all, encodeStatsMessageTypes(stats.all)); + writeValueToJson(jsonMap, PlatformConstants.TxStats.apiRequests, encodeStatsRequestCount(stats.apiRequests)); + writeValueToJson(jsonMap, PlatformConstants.TxStats.channels, encodeStatsResourceCount(stats.channels)); + writeValueToJson(jsonMap, PlatformConstants.TxStats.connections, encodeStatsConnectionTypes(stats.connections)); + writeValueToJson(jsonMap, PlatformConstants.TxStats.inbound, encodeStatsMessageTraffic(stats.inbound)); + writeValueToJson(jsonMap, PlatformConstants.TxStats.intervalId, stats.intervalId); + writeValueToJson(jsonMap, PlatformConstants.TxStats.outbound, encodeStatsMessageTraffic(stats.outbound)); + writeValueToJson(jsonMap, PlatformConstants.TxStats.persisted, encodeStatsMessageTypes(stats.persisted)); + writeValueToJson(jsonMap, PlatformConstants.TxStats.tokenRequests, encodeStatsRequestCount(stats.tokenRequests)); + return jsonMap; + } + + private Map encodeStatsMessageTraffic(Stats.MessageTraffic messageTraffic) { + if (messageTraffic == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageTraffic.all, encodeStatsMessageTypes(messageTraffic.all)); + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageTraffic.realtime, encodeStatsMessageTypes(messageTraffic.realtime)); + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageTraffic.rest, encodeStatsMessageTypes(messageTraffic.rest)); + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageTraffic.webhook, encodeStatsMessageTypes(messageTraffic.webhook)); + return jsonMap; + } + + private Map encodeStatsConnectionTypes(Stats.ConnectionTypes connectionTypes) { + if (connectionTypes == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxStatsConnectionTypes.all, encodeStatsResourceCount(connectionTypes.all)); + writeValueToJson(jsonMap, PlatformConstants.TxStatsConnectionTypes.plain, encodeStatsResourceCount(connectionTypes.plain)); + writeValueToJson(jsonMap, PlatformConstants.TxStatsConnectionTypes.tls, encodeStatsResourceCount(connectionTypes.tls)); + return jsonMap; + } + + private Map encodeStatsResourceCount(Stats.ResourceCount resourceCount) { + if (resourceCount == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxStatsResourceCount.mean, resourceCount.mean); + writeValueToJson(jsonMap, PlatformConstants.TxStatsResourceCount.min, resourceCount.min); + writeValueToJson(jsonMap, PlatformConstants.TxStatsResourceCount.opened, resourceCount.opened); + writeValueToJson(jsonMap, PlatformConstants.TxStatsResourceCount.peak, resourceCount.peak); + writeValueToJson(jsonMap, PlatformConstants.TxStatsResourceCount.refused, resourceCount.refused); + return jsonMap; + } + + private Map encodeStatsRequestCount(Stats.RequestCount apiRequests) { + if (apiRequests == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxStatsRequestCount.succeeded, apiRequests.succeeded); + writeValueToJson(jsonMap, PlatformConstants.TxStatsRequestCount.failed, apiRequests.failed); + writeValueToJson(jsonMap, PlatformConstants.TxStatsRequestCount.refused, apiRequests.refused); + return jsonMap; + } + + private Map encodeStatsMessageTypes(Stats.MessageTypes messageTypes) { + if (messageTypes == null) return null; + final HashMap jsonMap = new HashMap<>(); + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageTypes.all, encodeStatsMessageCategory(messageTypes.all)); + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageTypes.messages, encodeStatsMessageCategory(messageTypes.messages)); + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageTypes.presence, encodeStatsMessageCategory(messageTypes.presence)); + return jsonMap; + } + + // This is different in other platform and will correspond to StatsMessageCount for other + // libraries + private Map encodeStatsMessageCategory(Stats.MessageCategory category) { + if (category == null) return null; + final HashMap jsonMap = new HashMap<>(); + + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageCount.count, category.count); + writeValueToJson(jsonMap, PlatformConstants.TxStatsMessageCount.data, category.data); + + return jsonMap; + } + @Override protected Object readValueOfType(final byte type, final ByteBuffer buffer) { CodecPair pair = codecMap.get(type); @@ -199,6 +277,8 @@ private Byte getType(Object value) { } else if (value instanceof ChannelOptions) { // Encoding it into a RealtimeChannelOptions instance, because it extends RestChannelOptions return PlatformConstants.CodecTypes.realtimeChannelOptions; + } else if (value instanceof Stats) { + return PlatformConstants.CodecTypes.stats; } return null; } @@ -303,9 +383,13 @@ private PlatformClientOptions decodeClientOptions(Map jsonMap) { readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.realtimeHost, v -> o.realtimeHost = (String) v); readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.port, v -> o.port = (Integer) v); readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.tlsPort, v -> o.tlsPort = (Integer) v); - o.autoConnect = false; // Always avoid auto-connect, to allow handle to be returned back to Dart side before authCallback is called. - // If the user specifies autoConnect, we call connect once we get the handle back to the dart side - // In other words, Ably Flutter internally manually connects, but to the SDK user this looks like autoConnect. + /* Always avoid auto-connect, to allow handle to be returned back to Dart side before + * authCallback is called. + * If the user specifies autoConnect, we call connect once we get the handle back to the dart side + * In other words, Ably Flutter internally manually connects, but to the SDK user this looks + * like autoConnect. + */ + o.autoConnect = false; readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.useBinaryProtocol, v -> o.useBinaryProtocol = (Boolean) v); readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.queueMessages, v -> o.queueMessages = (Boolean) v); readValueFromJson(jsonMap, PlatformConstants.TxClientOptions.echoMessages, v -> o.echoMessages = (Boolean) v); diff --git a/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java b/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java index 04bc36760..924ad0d7e 100644 --- a/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java +++ b/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java @@ -25,6 +25,7 @@ import io.ably.lib.realtime.Channel; import io.ably.lib.realtime.CompletionListener; import io.ably.lib.realtime.Presence; +import io.ably.lib.rest.AblyBase; import io.ably.lib.rest.AblyRest; import io.ably.lib.rest.Auth; import io.ably.lib.transport.Defaults; @@ -87,6 +88,9 @@ public AblyMethodCallHandler(final MethodChannel methodChannel, _map.put(PlatformConstants.PlatformMethod.realtimePresenceUpdate, this::updateRealtimePresence); _map.put(PlatformConstants.PlatformMethod.realtimePresenceLeave, this::leaveRealtimePresence); _map.put(PlatformConstants.PlatformMethod.releaseRealtimeChannel, this::releaseRealtimeChannel); + _map.put(PlatformConstants.PlatformMethod.realtimeTime, this::realtimeTime); + _map.put(PlatformConstants.PlatformMethod.restTime, this::restTime); + _map.put(PlatformConstants.PlatformMethod.stats, this::stats); // Push Notifications _map.put(PlatformConstants.PlatformMethod.pushActivate, this::pushActivate); @@ -304,6 +308,26 @@ private void getRestHistory(@NonNull MethodCall call, @NonNull MethodChannel.Res }); } + private void stats(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; + final AblyFlutterMessage> paramsMessage = + (AblyFlutterMessage>) message.message; + final Map map = paramsMessage.message; + final HashMap paramsMap = + (HashMap) map.get(PlatformConstants.TxTransportKeys.params); + Param[] params = new Param[paramsMap != null ? paramsMap.size() : 0]; + if (paramsMap != null) { + int i = 0; + for (String paramKey : paramsMap.keySet()) { + final Param param = new Param(paramKey, paramsMap.get(paramKey)); + params[i++] = param; + } + } + instanceStore + .getRest(paramsMessage.handle) + .statsAsync(params, this.paginatedResponseHandler(result, null)); + } + private void getRestPresence(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; this.>>ablyDo(message, (ablyLibrary, messageData) -> { @@ -600,7 +624,33 @@ private void releaseRealtimeChannel(@NonNull MethodCall call, @NonNull MethodCha }); } - private void getRealtimeHistory(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + private void realtimeTime(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) methodCall.arguments; + time(result, instanceStore.getRealtime((int) message.message)); + } + + private void restTime(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { + final AblyFlutterMessage message = (AblyFlutterMessage) methodCall.arguments; + time(result, instanceStore.getRest((int) message.message)); + } + + private void time(@NonNull MethodChannel.Result result, AblyBase client) { + Callback callback = new Callback() { + @Override + public void onSuccess(Long timeResult) { + result.success(timeResult); + } + + @Override + public void onError(ErrorInfo reason) { + result.error("40000", reason.message, reason); + } + }; + client.timeAsync(callback); + } + + + private void getRealtimeHistory(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { final AblyFlutterMessage message = (AblyFlutterMessage) call.arguments; this.>>ablyDo(message, (ablyLibrary, messageData) -> { final Map map = messageData.message; diff --git a/android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java b/android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java index 4ac2433b3..3339fb6d1 100644 --- a/android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java +++ b/android/src/main/java/io/ably/flutter/plugin/generated/PlatformConstants.java @@ -26,16 +26,17 @@ static final public class CodecTypes { public static final byte restPresenceParams = (byte) 142; public static final byte presenceMessage = (byte) 143; public static final byte realtimePresenceParams = (byte) 144; - public static final byte deviceDetails = (byte) 145; - public static final byte localDevice = (byte) 146; - public static final byte pushChannelSubscription = (byte) 147; - public static final byte unNotificationSettings = (byte) 148; - public static final byte remoteMessage = (byte) 149; - public static final byte errorInfo = (byte) 150; - public static final byte logLevel = (byte) 151; - public static final byte connectionStateChange = (byte) 152; - public static final byte channelStateChange = (byte) 153; - public static final byte cipherParams = (byte) 154; + public static final byte stats = (byte) 145; + public static final byte deviceDetails = (byte) 146; + public static final byte localDevice = (byte) 147; + public static final byte pushChannelSubscription = (byte) 148; + public static final byte unNotificationSettings = (byte) 149; + public static final byte remoteMessage = (byte) 150; + public static final byte errorInfo = (byte) 151; + public static final byte logLevel = (byte) 152; + public static final byte connectionStateChange = (byte) 153; + public static final byte channelStateChange = (byte) 154; + public static final byte cipherParams = (byte) 155; } static final public class PlatformMethod { @@ -66,6 +67,9 @@ static final public class PlatformMethod { public static final String publishRealtimeChannelMessage = "publishRealtimeChannelMessage"; public static final String releaseRealtimeChannel = "releaseRealtimeChannel"; public static final String realtimeHistory = "realtimeHistory"; + public static final String realtimeTime = "realtimeTime"; + public static final String restTime = "restTime"; + public static final String stats = "stats"; public static final String pushActivate = "pushActivate"; public static final String pushDeactivate = "pushDeactivate"; public static final String pushSubscribeDevice = "pushSubscribeDevice"; @@ -444,4 +448,54 @@ static final public class TxCryptoGenerateRandomKey { public static final String keyLength = "keyLength"; } + static final public class TxStats { + public static final String all = "all"; + public static final String apiRequests = "apiRequests"; + public static final String channels = "channels"; + public static final String connections = "connections"; + public static final String inbound = "inbound"; + public static final String intervalId = "intervalId"; + public static final String outbound = "outbound"; + public static final String persisted = "persisted"; + public static final String tokenRequests = "tokenRequests"; + } + + static final public class TxStatsMessageTypes { + public static final String all = "all"; + public static final String messages = "messages"; + public static final String presence = "presence"; + } + + static final public class TxStatsMessageCount { + public static final String count = "count"; + public static final String data = "data"; + } + + static final public class TxStatsRequestCount { + public static final String failed = "failed"; + public static final String refused = "refused"; + public static final String succeeded = "succeeded"; + } + + static final public class TxStatsResourceCount { + public static final String mean = "mean"; + public static final String min = "min"; + public static final String opened = "opened"; + public static final String peak = "peak"; + public static final String refused = "refused"; + } + + static final public class TxStatsConnectionTypes { + public static final String all = "all"; + public static final String plain = "plain"; + public static final String tls = "tls"; + } + + static final public class TxStatsMessageTraffic { + public static final String all = "all"; + public static final String realtime = "realtime"; + public static final String rest = "rest"; + public static final String webhook = "webhook"; + } + } diff --git a/bin/codegen_context.dart b/bin/codegen_context.dart index c77988a3b..57c06cf7a 100644 --- a/bin/codegen_context.dart +++ b/bin/codegen_context.dart @@ -21,6 +21,7 @@ Iterable> get _types sync* { 'restPresenceParams', 'presenceMessage', 'realtimePresenceParams', + 'stats', // Push Notifications 'deviceDetails', @@ -94,6 +95,9 @@ const List> _platformMethods = [ }, {'name': 'releaseRealtimeChannel', 'value': 'releaseRealtimeChannel'}, {'name': 'realtimeHistory', 'value': 'realtimeHistory'}, + {'name': 'realtimeTime', 'value': 'realtimeTime'}, + {'name': 'restTime', 'value': 'restTime'}, + {'name': 'stats', 'value': 'stats'}, // Push Notifications {'name': 'pushActivate', 'value': 'pushActivate'}, @@ -507,6 +511,44 @@ const List> _objects = [ { 'name': 'CryptoGenerateRandomKey', 'properties': ['keyLength'] + }, + { + 'name': 'Stats', + 'properties': [ + 'all', + 'apiRequests', + 'channels', + 'connections', + 'inbound', + 'intervalId', + 'outbound', + 'persisted', + 'tokenRequests' + ] + }, + { + 'name': 'StatsMessageTypes', + 'properties': ['all', 'messages', 'presence'] + }, + { + 'name': 'StatsMessageCount', + 'properties': ['count', 'data'] + }, + { + 'name': 'StatsRequestCount', + 'properties': ['failed', 'refused', 'succeeded'] + }, + { + 'name': 'StatsResourceCount', + 'properties': ['mean', 'min', 'opened', 'peak', 'refused'] + }, + { + 'name': 'StatsConnectionTypes', + 'properties': ['all', 'plain', 'tls'] + }, + { + 'name': 'StatsMessageTraffic', + 'properties': ['all', 'realtime', 'rest', 'webhook'] } ]; diff --git a/example/lib/ui/realtime_sliver.dart b/example/lib/ui/realtime_sliver.dart index 5e2666049..f9f0085d4 100644 --- a/example/lib/ui/realtime_sliver.dart +++ b/example/lib/ui/realtime_sliver.dart @@ -153,8 +153,10 @@ class RealtimeSliver extends HookWidget { final latestMessage = useState(null); final channelSubscription = useState?>(null); + final realtimeTime = useState(null); useEffect(() { + realtime.time().then((value) => realtimeTime.value = value); setupListeners(connectionState, channelState); return dispose; }, []); @@ -166,6 +168,7 @@ class RealtimeSliver extends HookWidget { 'Realtime', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), + Text('Realtime time: ${realtimeTime.value}'), Text('Connection State: ${connectionState.value}'), Row( children: [ diff --git a/example/lib/ui/rest_sliver.dart b/example/lib/ui/rest_sliver.dart index 62f1ea224..7a7dc1de5 100644 --- a/example/lib/ui/rest_sliver.dart +++ b/example/lib/ui/rest_sliver.dart @@ -38,7 +38,9 @@ class RestSliver extends HookWidget { Widget build(BuildContext context) { final messageCount = useState(1); final messageName = useState('Message ${messageCount.value}'); + final restTime = useState(null); useEffect(() { + rest.time().then((value) => restTime.value = value); messageName.value = 'Message ${messageCount.value}'; }, [messageCount]); @@ -49,6 +51,7 @@ class RestSliver extends HookWidget { 'Rest', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), + Text('Rest time: ${restTime.value}'), Row( children: [ Expanded( @@ -88,6 +91,16 @@ class RestSliver extends HookWidget { channel.presence.history(ably.RestHistoryParams(limit: 10)), builder: (context, message, _) => TextRow('Message name', '${message.id}:${message.clientId}:${message.data}')), + PaginatedResultViewer( + title: 'Stats', + subtitle: TextRow('What is it?', 'Realtime stats'), + query: () => rest.stats({"limit": 10, "end": "now"}), + builder: (context, stat, _) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextRow('Statistics ', stat.toString()), + ], + )), ], ); } diff --git a/ios/Classes/AblyFlutter.m b/ios/Classes/AblyFlutter.m index eec9428ae..f5b296658 100644 --- a/ios/Classes/AblyFlutter.m +++ b/ios/Classes/AblyFlutter.m @@ -2,6 +2,7 @@ #import "AblyFlutter.h" #import +#import #import "codec/AblyFlutterReaderWriter.h" #import "AblyFlutterMessage.h" @@ -9,6 +10,7 @@ #import "AblyFlutterStreamHandler.h" #import "AblyStreamsChannel.h" #import "codec/AblyPlatformConstants.h" +#import "ARTStatsQuery+ParamBuilder.h" #define LOG(fmt, ...) NSLog((@"%@:%d " fmt), [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, ##__VA_ARGS__) @@ -549,6 +551,57 @@ -(void)reset; result(nil); }; +static const FlutterHandler _realtimeTime = ^void(AblyFlutter *const ably, FlutterMethodCall *const call, const FlutterResult result) { + AblyFlutterMessage *const message = call.arguments; + AblyInstanceStore *const instanceStore = [ably instanceStore]; + + ARTRealtime *const realtime = [instanceStore realtimeFrom:message.message]; + [realtime time:^(NSDate * _Nullable dateTimeResult, NSError * _Nullable error) { + if(error){ + result(error); + }else{ + result(@([@(dateTimeResult.timeIntervalSince1970 *1000) longValue])); + } + }]; +}; + +static const FlutterHandler _restTime = ^void(AblyFlutter *const ably, FlutterMethodCall *const call, const FlutterResult result) { + AblyFlutterMessage *const message = call.arguments; + AblyInstanceStore *const instanceStore = [ably instanceStore]; + NSNumber *const handle = message.message; + ARTRest *const rest = [instanceStore restFrom:handle]; + [rest time:^(NSDate * _Nullable dateTimeResult, NSError * _Nullable error) { + if(error){ + result(error); + }else{ + result(@([@(dateTimeResult.timeIntervalSince1970 *1000) longValue])); + } + }]; +}; + +static const FlutterHandler _stats = ^void(AblyFlutter *const ably, FlutterMethodCall *const call, const FlutterResult result) { + AblyFlutterMessage *const arguments = call.arguments; + AblyInstanceStore *const instanceStore = [ably instanceStore]; + AblyFlutterMessage *message = arguments.message; + ARTRest *const rest = [instanceStore restFrom:message.handle]; + NSDictionary *paramsDict = message.message; + ARTStatsQuery *query; + if(paramsDict[@"params"]){ + NSDictionary *statsParams = paramsDict[@"params"]; + query = [ARTStatsQuery fromParams:statsParams]; + } + + NSError *statsError; + [rest stats:query callback:^(ARTPaginatedResult *statsResult, ARTErrorInfo *error) { + if(error){ + result(error); + }else{ + result(statsResult); + } + } error:&statsError]; + +}; + static const FlutterHandler _getNextPage = ^void(AblyFlutter *const ably, FlutterMethodCall *const call, const FlutterResult result) { AblyFlutterMessage *const message = call.arguments; AblyInstanceStore *const instanceStore = [ably instanceStore]; @@ -665,6 +718,9 @@ -(instancetype)initWithChannel:(FlutterMethodChannel *const)channel AblyPlatformMethod_realtimePresenceUpdate: _updateRealtimePresence, AblyPlatformMethod_realtimePresenceLeave: _leaveRealtimePresence, AblyPlatformMethod_releaseRealtimeChannel: _releaseRealtimeChannel, + AblyPlatformMethod_realtimeTime:_realtimeTime, + AblyPlatformMethod_restTime:_restTime, + AblyPlatformMethod_stats:_stats, // Push Notifications AblyPlatformMethod_pushActivate: PushHandlers.activate, AblyPlatformMethod_pushRequestPermission: PushHandlers.requestPermission, diff --git a/ios/Classes/codec/ARTStatsQuery+ParamBuilder.h b/ios/Classes/codec/ARTStatsQuery+ParamBuilder.h new file mode 100644 index 000000000..33e472172 --- /dev/null +++ b/ios/Classes/codec/ARTStatsQuery+ParamBuilder.h @@ -0,0 +1,12 @@ +// +// Created by Ikbal Kaya on 23/12/2021. +// + +#import +#import "ARTStats.h" + +@interface ARTStatsQuery (ParamBuilder) + ++(ARTStatsQuery *)fromParams:(NSDictionary *)params; + +@end \ No newline at end of file diff --git a/ios/Classes/codec/ARTStatsQuery+ParamBuilder.m b/ios/Classes/codec/ARTStatsQuery+ParamBuilder.m new file mode 100644 index 000000000..1e191cdd5 --- /dev/null +++ b/ios/Classes/codec/ARTStatsQuery+ParamBuilder.m @@ -0,0 +1,47 @@ +// +// Created by Ikbal Kaya on 23/12/2021. +// + +#import "ARTStatsQuery+ParamBuilder.h" +#import "ARTNSDate+ARTUtil.h" + + +@implementation ARTStatsQuery (ParamBuilder) ++ (ARTStatsQuery *)fromParams:(NSDictionary *)params { + ARTStatsQuery *query = [[ARTStatsQuery alloc] init]; + id start = params[@"start"]; + if(start && [start isKindOfClass:[NSNumber class]]){ + query.start = [NSDate artDateFromIntegerMs:[start longLongValue]]; + } + id end = params[@"end"]; + if(end && [end isKindOfClass:[NSNumber class]]){ + query.end = [NSDate artDateFromIntegerMs:[end longLongValue]]; + } + id direction = params[@"direction"]; + if(direction && [direction isKindOfClass:[NSString class]]){ + NSString *directionString = [direction stringValue]; + query.direction = [directionString isEqualToString:@"backwards"] ? ARTQueryDirectionBackwards : ARTQueryDirectionForwards; + } + id limit = params[@"limit"]; + if(limit && [limit isKindOfClass:[NSNumber class]]){ + query.limit = (uint16_t) [limit unsignedIntValue]; + } + + id unit = params[@"unit"]; + if(unit && [unit isKindOfClass:[NSString class]]){ + NSString *unitVal = [unit stringValue]; + // minute, hour, day or month, + if ([unitVal isEqualToString:@"minute"]){ + query.unit = ARTStatsGranularityMinute; + }else if ([unitVal isEqualToString:@"hour"]){ + query.unit = ARTStatsGranularityHour; + }else if ([unitVal isEqualToString:@"day"]){ + query.unit = ARTStatsGranularityDay; + }else if ([unitVal isEqualToString:@"month"]){ + query.unit = ARTStatsGranularityMonth; + } + } + return query; +} + +@end \ No newline at end of file diff --git a/ios/Classes/codec/AblyFlutterWriter.m b/ios/Classes/codec/AblyFlutterWriter.m index dfd2c9596..c542d16a0 100644 --- a/ios/Classes/codec/AblyFlutterWriter.m +++ b/ios/Classes/codec/AblyFlutterWriter.m @@ -51,6 +51,8 @@ + (UInt8) getType:(id)value{ return CodecTypeRestChannelOptions; } else if ([value isKindOfClass:[ARTCipherParams class]]) { return CodecTypeCipherParams; + } else if([value isKindOfClass:[ARTStats class]]){ + return CodecTypeStats; } return 0; } @@ -71,6 +73,7 @@ + (AblyCodecEncoder) getEncoder:(const NSString*)type { [NSString stringWithFormat:@"%d", CodecTypeUnNotificationSettings]: PushNotificationEncoders.encodeUNNotificationSettings, [NSString stringWithFormat:@"%d", CodecTypeRemoteMessage]: PushNotificationEncoders.encodeRemoteMessage, [NSString stringWithFormat:@"%d", CodecTypeCipherParams]: CryptoCodec.encodeCipherParams, + [NSString stringWithFormat:@"%d", CodecTypeStats]: StatsEncoders.encodeStats, }; return [_handlers objectForKey:[NSString stringWithFormat:@"%@", type]]; } @@ -317,4 +320,5 @@ +(NSString *) encodePresenceAction: (ARTPresenceAction) action { return dictionary; }; + @end diff --git a/ios/Classes/codec/AblyPlatformConstants.h b/ios/Classes/codec/AblyPlatformConstants.h index 5aecd2570..24dac1ba8 100644 --- a/ios/Classes/codec/AblyPlatformConstants.h +++ b/ios/Classes/codec/AblyPlatformConstants.h @@ -23,16 +23,17 @@ typedef NS_ENUM(UInt8, CodecType) { CodecTypeRestPresenceParams = 142, CodecTypePresenceMessage = 143, CodecTypeRealtimePresenceParams = 144, - CodecTypeDeviceDetails = 145, - CodecTypeLocalDevice = 146, - CodecTypePushChannelSubscription = 147, - CodecTypeUnNotificationSettings = 148, - CodecTypeRemoteMessage = 149, - CodecTypeErrorInfo = 150, - CodecTypeLogLevel = 151, - CodecTypeConnectionStateChange = 152, - CodecTypeChannelStateChange = 153, - CodecTypeCipherParams = 154, + CodecTypeStats = 145, + CodecTypeDeviceDetails = 146, + CodecTypeLocalDevice = 147, + CodecTypePushChannelSubscription = 148, + CodecTypeUnNotificationSettings = 149, + CodecTypeRemoteMessage = 150, + CodecTypeErrorInfo = 151, + CodecTypeLogLevel = 152, + CodecTypeConnectionStateChange = 153, + CodecTypeChannelStateChange = 154, + CodecTypeCipherParams = 155, }; @@ -64,6 +65,9 @@ extern NSString *const AblyPlatformMethod_onRealtimePresenceMessage; extern NSString *const AblyPlatformMethod_publishRealtimeChannelMessage; extern NSString *const AblyPlatformMethod_releaseRealtimeChannel; extern NSString *const AblyPlatformMethod_realtimeHistory; +extern NSString *const AblyPlatformMethod_realtimeTime; +extern NSString *const AblyPlatformMethod_restTime; +extern NSString *const AblyPlatformMethod_stats; extern NSString *const AblyPlatformMethod_pushActivate; extern NSString *const AblyPlatformMethod_pushDeactivate; extern NSString *const AblyPlatformMethod_pushSubscribeDevice; @@ -398,3 +402,46 @@ extern NSString *const TxCryptoGetParams_key; // key constants for CryptoGenerateRandomKey extern NSString *const TxCryptoGenerateRandomKey_keyLength; + +// key constants for Stats +extern NSString *const TxStats_all; +extern NSString *const TxStats_apiRequests; +extern NSString *const TxStats_channels; +extern NSString *const TxStats_connections; +extern NSString *const TxStats_inbound; +extern NSString *const TxStats_intervalId; +extern NSString *const TxStats_outbound; +extern NSString *const TxStats_persisted; +extern NSString *const TxStats_tokenRequests; + +// key constants for StatsMessageTypes +extern NSString *const TxStatsMessageTypes_all; +extern NSString *const TxStatsMessageTypes_messages; +extern NSString *const TxStatsMessageTypes_presence; + +// key constants for StatsMessageCount +extern NSString *const TxStatsMessageCount_count; +extern NSString *const TxStatsMessageCount_data; + +// key constants for StatsRequestCount +extern NSString *const TxStatsRequestCount_failed; +extern NSString *const TxStatsRequestCount_refused; +extern NSString *const TxStatsRequestCount_succeeded; + +// key constants for StatsResourceCount +extern NSString *const TxStatsResourceCount_mean; +extern NSString *const TxStatsResourceCount_min; +extern NSString *const TxStatsResourceCount_opened; +extern NSString *const TxStatsResourceCount_peak; +extern NSString *const TxStatsResourceCount_refused; + +// key constants for StatsConnectionTypes +extern NSString *const TxStatsConnectionTypes_all; +extern NSString *const TxStatsConnectionTypes_plain; +extern NSString *const TxStatsConnectionTypes_tls; + +// key constants for StatsMessageTraffic +extern NSString *const TxStatsMessageTraffic_all; +extern NSString *const TxStatsMessageTraffic_realtime; +extern NSString *const TxStatsMessageTraffic_rest; +extern NSString *const TxStatsMessageTraffic_webhook; diff --git a/ios/Classes/codec/AblyPlatformConstants.m b/ios/Classes/codec/AblyPlatformConstants.m index 1bc8cd010..ead5db17b 100644 --- a/ios/Classes/codec/AblyPlatformConstants.m +++ b/ios/Classes/codec/AblyPlatformConstants.m @@ -34,6 +34,9 @@ NSString *const AblyPlatformMethod_publishRealtimeChannelMessage= @"publishRealtimeChannelMessage"; NSString *const AblyPlatformMethod_releaseRealtimeChannel= @"releaseRealtimeChannel"; NSString *const AblyPlatformMethod_realtimeHistory= @"realtimeHistory"; +NSString *const AblyPlatformMethod_realtimeTime= @"realtimeTime"; +NSString *const AblyPlatformMethod_restTime= @"restTime"; +NSString *const AblyPlatformMethod_stats= @"stats"; NSString *const AblyPlatformMethod_pushActivate= @"pushActivate"; NSString *const AblyPlatformMethod_pushDeactivate= @"pushDeactivate"; NSString *const AblyPlatformMethod_pushSubscribeDevice= @"pushSubscribeDevice"; @@ -368,3 +371,46 @@ // key constants for CryptoGenerateRandomKey NSString *const TxCryptoGenerateRandomKey_keyLength = @"keyLength"; + +// key constants for Stats +NSString *const TxStats_all = @"all"; +NSString *const TxStats_apiRequests = @"apiRequests"; +NSString *const TxStats_channels = @"channels"; +NSString *const TxStats_connections = @"connections"; +NSString *const TxStats_inbound = @"inbound"; +NSString *const TxStats_intervalId = @"intervalId"; +NSString *const TxStats_outbound = @"outbound"; +NSString *const TxStats_persisted = @"persisted"; +NSString *const TxStats_tokenRequests = @"tokenRequests"; + +// key constants for StatsMessageTypes +NSString *const TxStatsMessageTypes_all = @"all"; +NSString *const TxStatsMessageTypes_messages = @"messages"; +NSString *const TxStatsMessageTypes_presence = @"presence"; + +// key constants for StatsMessageCount +NSString *const TxStatsMessageCount_count = @"count"; +NSString *const TxStatsMessageCount_data = @"data"; + +// key constants for StatsRequestCount +NSString *const TxStatsRequestCount_failed = @"failed"; +NSString *const TxStatsRequestCount_refused = @"refused"; +NSString *const TxStatsRequestCount_succeeded = @"succeeded"; + +// key constants for StatsResourceCount +NSString *const TxStatsResourceCount_mean = @"mean"; +NSString *const TxStatsResourceCount_min = @"min"; +NSString *const TxStatsResourceCount_opened = @"opened"; +NSString *const TxStatsResourceCount_peak = @"peak"; +NSString *const TxStatsResourceCount_refused = @"refused"; + +// key constants for StatsConnectionTypes +NSString *const TxStatsConnectionTypes_all = @"all"; +NSString *const TxStatsConnectionTypes_plain = @"plain"; +NSString *const TxStatsConnectionTypes_tls = @"tls"; + +// key constants for StatsMessageTraffic +NSString *const TxStatsMessageTraffic_all = @"all"; +NSString *const TxStatsMessageTraffic_realtime = @"realtime"; +NSString *const TxStatsMessageTraffic_rest = @"rest"; +NSString *const TxStatsMessageTraffic_webhook = @"webhook"; diff --git a/ios/Classes/codec/encoders/StatsEncoders.swift b/ios/Classes/codec/encoders/StatsEncoders.swift new file mode 100644 index 000000000..fd407b8c8 --- /dev/null +++ b/ios/Classes/codec/encoders/StatsEncoders.swift @@ -0,0 +1,72 @@ +// +// Created by Ikbal Kaya on 21/12/2021. +// + +import Foundation + +public class StatsEncoders: NSObject { + @objc + public static let encodeStats: (ARTStats) -> [String: Any] = { stats in + [ + TxStats_all: encodeStatsMessageTypes(stats.all), + TxStats_apiRequests: encodeStatsRequestCount(stats.apiRequests), + TxStats_channels: encodeStatsResourceCount(stats.channels), + TxStats_connections: encodeStatsConnectionTypes(stats.connections), + TxStats_inbound: encodeStatsMessageTraffic(stats.inbound), + TxStats_intervalId: stats.intervalId, + TxStats_outbound: encodeStatsMessageTraffic(stats.outbound), + TxStats_persisted: encodeStatsMessageTypes(stats.persisted), + TxStats_tokenRequests: encodeStatsRequestCount(stats.tokenRequests), + ] + } + + static let encodeStatsMessageTypes: (ARTStatsMessageTypes) -> [String: Any] = { types in + [ + TxStatsMessageTypes_all: encodeStatsMessageCount(types.all), + TxStatsMessageTypes_messages: encodeStatsMessageCount(types.messages), + TxStatsMessageTypes_presence: encodeStatsMessageCount(types.presence), + ] + } + + static let encodeStatsMessageCount: (ARTStatsMessageCount) -> [String: Any] = { count in + [ + TxStatsMessageCount_count: count.count, + TxStatsMessageCount_data: count.data, + ] + } + + static let encodeStatsRequestCount: (ARTStatsRequestCount) -> [String: Any] = { count in + [ + TxStatsRequestCount_failed: count.failed, + TxStatsRequestCount_refused: count.refused, + TxStatsRequestCount_succeeded: count.succeeded, + ] + } + + static let encodeStatsResourceCount: (ARTStatsResourceCount) -> [String: Any] = { count in + [ + TxStatsResourceCount_mean: count.mean, + TxStatsResourceCount_min: count.min, + TxStatsResourceCount_opened: count.opened, + TxStatsResourceCount_peak: count.peak, + TxStatsResourceCount_refused: count.refused, + ] + } + + static let encodeStatsConnectionTypes: (ARTStatsConnectionTypes) -> [String: Any] = { types in + [ + TxStatsConnectionTypes_all: encodeStatsResourceCount(types.all), + TxStatsConnectionTypes_plain: encodeStatsResourceCount(types.plain), + TxStatsConnectionTypes_tls: encodeStatsResourceCount(types.tls), + ] + } + + static let encodeStatsMessageTraffic: (ARTStatsMessageTraffic) -> [String: Any] = { types in + [ + TxStatsMessageTraffic_all: encodeStatsMessageTypes(types.all), + TxStatsMessageTraffic_realtime: encodeStatsMessageTypes(types.realtime), + TxStatsMessageTraffic_rest: encodeStatsMessageTypes(types.rest), + TxStatsMessageTraffic_webhook: encodeStatsMessageTypes(types.webhook), + ] + } +} diff --git a/lib/src/generated/platform_constants.dart b/lib/src/generated/platform_constants.dart index 2da94d152..92a534e89 100644 --- a/lib/src/generated/platform_constants.dart +++ b/lib/src/generated/platform_constants.dart @@ -23,16 +23,17 @@ class CodecTypes { static const int restPresenceParams = 142; static const int presenceMessage = 143; static const int realtimePresenceParams = 144; - static const int deviceDetails = 145; - static const int localDevice = 146; - static const int pushChannelSubscription = 147; - static const int unNotificationSettings = 148; - static const int remoteMessage = 149; - static const int errorInfo = 150; - static const int logLevel = 151; - static const int connectionStateChange = 152; - static const int channelStateChange = 153; - static const int cipherParams = 154; + static const int stats = 145; + static const int deviceDetails = 146; + static const int localDevice = 147; + static const int pushChannelSubscription = 148; + static const int unNotificationSettings = 149; + static const int remoteMessage = 150; + static const int errorInfo = 151; + static const int logLevel = 152; + static const int connectionStateChange = 153; + static const int channelStateChange = 154; + static const int cipherParams = 155; } class PlatformMethod { @@ -64,6 +65,9 @@ class PlatformMethod { 'publishRealtimeChannelMessage'; static const String releaseRealtimeChannel = 'releaseRealtimeChannel'; static const String realtimeHistory = 'realtimeHistory'; + static const String realtimeTime = 'realtimeTime'; + static const String restTime = 'restTime'; + static const String stats = 'stats'; static const String pushActivate = 'pushActivate'; static const String pushDeactivate = 'pushDeactivate'; static const String pushSubscribeDevice = 'pushSubscribeDevice'; @@ -449,3 +453,53 @@ class TxCryptoGetParams { class TxCryptoGenerateRandomKey { static const String keyLength = 'keyLength'; } + +class TxStats { + static const String all = 'all'; + static const String apiRequests = 'apiRequests'; + static const String channels = 'channels'; + static const String connections = 'connections'; + static const String inbound = 'inbound'; + static const String intervalId = 'intervalId'; + static const String outbound = 'outbound'; + static const String persisted = 'persisted'; + static const String tokenRequests = 'tokenRequests'; +} + +class TxStatsMessageTypes { + static const String all = 'all'; + static const String messages = 'messages'; + static const String presence = 'presence'; +} + +class TxStatsMessageCount { + static const String count = 'count'; + static const String data = 'data'; +} + +class TxStatsRequestCount { + static const String failed = 'failed'; + static const String refused = 'refused'; + static const String succeeded = 'succeeded'; +} + +class TxStatsResourceCount { + static const String mean = 'mean'; + static const String min = 'min'; + static const String opened = 'opened'; + static const String peak = 'peak'; + static const String refused = 'refused'; +} + +class TxStatsConnectionTypes { + static const String all = 'all'; + static const String plain = 'plain'; + static const String tls = 'tls'; +} + +class TxStatsMessageTraffic { + static const String all = 'all'; + static const String realtime = 'realtime'; + static const String rest = 'rest'; + static const String webhook = 'webhook'; +} diff --git a/lib/src/platform/src/codec.dart b/lib/src/platform/src/codec.dart index 041e80736..ffeb0a554 100644 --- a/lib/src/platform/src/codec.dart +++ b/lib/src/platform/src/codec.dart @@ -84,7 +84,7 @@ class Codec extends StandardMessageCodec { _encodeRealtimePresenceParams, null, ), - + CodecTypes.stats: _CodecPair(null, _decodeStats), // Push Notifications CodecTypes.deviceDetails: _CodecPair(null, _decodeDeviceDetails), @@ -1056,6 +1056,172 @@ class Codec extends StandardMessageCodec { ); } + /// Decodes value [jsonMap] to [Stats] + /// returns null if [jsonMap] is null + /// FIXME: Decoding crashes on iOS because of different implementations of SDK + /// See: https://github.com/ably/ably-cocoa/issues/1284 + Stats _decodeStats(Map jsonMap) { + final allJson = toJsonMap(_readFromJson( + jsonMap, + TxStats.all, + )); + final apiRequestsJson = toJsonMap(_readFromJson( + jsonMap, + TxStats.apiRequests, + )); + final channelsJson = toJsonMap(_readFromJson( + jsonMap, + TxStats.channels, + )); + final connectionsJson = toJsonMap(_readFromJson( + jsonMap, + TxStats.connections, + )); + final inboundJson = toJsonMap(_readFromJson( + jsonMap, + TxStats.inbound, + )); + final outboundJson = toJsonMap(_readFromJson( + jsonMap, + TxStats.outbound, + )); + final persistedJson = toJsonMap(_readFromJson( + jsonMap, + TxStats.persisted, + )); + final tokenRequestsJson = toJsonMap(_readFromJson( + jsonMap, + TxStats.tokenRequests, + )); + return Stats( + all: (allJson != null) ? _decodeStatsMessageTypes(allJson) : null, + apiRequests: (apiRequestsJson != null) + ? _decodeStatsRequestCount(apiRequestsJson) + : null, + channels: (channelsJson != null) + ? _decodeStatsResourceCount(channelsJson) + : null, + connections: (connectionsJson != null) + ? _decodeStatsConnectionTypes(connectionsJson) + : null, + inbound: (inboundJson != null) + ? _decodeStatsMessageTraffic(inboundJson) + : null, + intervalId: _readFromJson(jsonMap, TxStats.intervalId), + outbound: (outboundJson != null) + ? _decodeStatsMessageTraffic(outboundJson) + : null, + persisted: (persistedJson != null) + ? _decodeStatsMessageTypes(persistedJson) + : null, + tokenRequests: (tokenRequestsJson != null) + ? _decodeStatsRequestCount(tokenRequestsJson) + : null, + ); + } + + StatsMessageTypes _decodeStatsMessageTypes(Map jsonMap) { + final allJson = toJsonMap(_readFromJson( + jsonMap, + TxStatsMessageTypes.all, + )); + final messagesJson = toJsonMap(_readFromJson( + jsonMap, + TxStatsMessageTypes.messages, + )); + final presenceJson = toJsonMap(_readFromJson( + jsonMap, + TxStatsMessageTypes.presence, + )); + + return StatsMessageTypes( + all: (allJson != null) ? _decodeStatsMessageCount(allJson) : null, + messages: (messagesJson != null) + ? _decodeStatsMessageCount(messagesJson) + : null, + presence: (presenceJson != null) + ? _decodeStatsMessageCount(presenceJson) + : null, + ); + } + + StatsMessageCount _decodeStatsMessageCount(Map jsonMap) => + StatsMessageCount( + count: _readFromJson(jsonMap, TxStatsMessageCount.count), + data: _readFromJson(jsonMap, TxStatsMessageCount.data)); + + StatsResourceCount _decodeStatsResourceCount(Map jsonMap) => + StatsResourceCount( + mean: _readFromJson(jsonMap, TxStatsResourceCount.mean), + min: _readFromJson(jsonMap, TxStatsResourceCount.min), + opened: _readFromJson(jsonMap, TxStatsResourceCount.opened), + peak: _readFromJson(jsonMap, TxStatsResourceCount.peak), + refused: _readFromJson( + jsonMap, + TxStatsResourceCount.refused, + )); + + StatsConnectionTypes _decodeStatsConnectionTypes( + Map jsonMap) { + final allJson = toJsonMap(_readFromJson( + jsonMap, + TxStatsConnectionTypes.all, + )); + final plainJson = toJsonMap(_readFromJson( + jsonMap, + TxStatsConnectionTypes.plain, + )); + final tlsJson = toJsonMap(_readFromJson( + jsonMap, + TxStatsConnectionTypes.tls, + )); + + return StatsConnectionTypes( + all: (allJson != null) ? _decodeStatsResourceCount(allJson) : null, + plain: (plainJson != null) ? _decodeStatsResourceCount(plainJson) : null, + tls: (tlsJson != null) ? _decodeStatsResourceCount(tlsJson) : null, + ); + } + + StatsMessageTraffic _decodeStatsMessageTraffic(Map jsonMap) { + final allJson = toJsonMap(_readFromJson( + jsonMap, + TxStatsMessageTraffic.all, + )); + final realtimeJson = toJsonMap(_readFromJson( + jsonMap, + TxStatsMessageTraffic.realtime, + )); + final restJson = toJsonMap(_readFromJson( + jsonMap, + TxStatsMessageTraffic.rest, + )); + final webhookJson = toJsonMap(_readFromJson( + jsonMap, + TxStatsMessageTraffic.webhook, + )); + + return StatsMessageTraffic( + all: (allJson != null) ? _decodeStatsMessageTypes(allJson) : null, + realtime: (realtimeJson != null) + ? _decodeStatsMessageTypes(realtimeJson) + : null, + rest: (restJson != null) ? _decodeStatsMessageTypes(restJson) : null, + webhook: (webhookJson != null) + ? _decodeStatsMessageTypes(webhookJson) + : null); + } + + StatsRequestCount _decodeStatsRequestCount(Map jsonMap) => + StatsRequestCount( + failed: _readFromJson(jsonMap, TxStatsRequestCount.failed), + succeeded: + _readFromJson(jsonMap, TxStatsRequestCount.succeeded), + refused: _readFromJson( + jsonMap, + TxStatsRequestCount.refused, + )); + /// Decodes [action] to [PresenceAction] enum if not null PresenceAction? _decodePresenceAction(String? action) { switch (action) { diff --git a/lib/src/platform/src/platform_object.dart b/lib/src/platform/src/platform_object.dart index 05310a009..921054b87 100644 --- a/lib/src/platform/src/platform_object.dart +++ b/lib/src/platform/src/platform_object.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:ably_flutter/ably_flutter.dart'; -import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; /// A representation of a Platform side instance (Android, iOS). @@ -59,6 +58,29 @@ abstract class PlatformObject { } /// invoke platform method channel with AblyMessage encapsulation + /* FIXME(ikurek): This seems wrong - the structure is either + { + message: { + message: argument, + handle: handle, + type: null + }, + handle: null, + type: null + } + + or + + { + message: handle, + handle: null, + type: null + } + + Which means that the structure is wrong every time, but I can't + understand why and it seems that iOS/Android code reads handle from + message field, so I don't want to touch it now + */ Future invoke(final String method, [final Object? argument]) async { final _handle = await handle; final message = (null != argument) diff --git a/lib/src/platform/src/realtime/realtime.dart b/lib/src/platform/src/realtime/realtime.dart index 63b51b533..9912f6077 100644 --- a/lib/src/platform/src/realtime/realtime.dart +++ b/lib/src/platform/src/realtime/realtime.dart @@ -100,19 +100,13 @@ class Realtime extends PlatformObject { // throw UnimplementedError(); // } - /// gets stats based on params as a [PaginatedResult] - /// - /// https://docs.ably.com/client-lib-development-guide/features/#RSC6 - // Future> stats([Map? params]) { - // throw UnimplementedError(); - // } - /// returns server time /// /// https://docs.ably.com/client-lib-development-guide/features/#RSC16 - // Future time() { - // throw UnimplementedError(); - // } + Future time() async { + final time = await invokeRequest(PlatformMethod.realtimeTime); + return DateTime.fromMillisecondsSinceEpoch(time); + } /// represents the current state of the device in respect of it being a /// target for push notifications. diff --git a/lib/src/platform/src/rest/rest.dart b/lib/src/platform/src/rest/rest.dart index 6235f5654..33c5690f5 100644 --- a/lib/src/platform/src/rest/rest.dart +++ b/lib/src/platform/src/rest/rest.dart @@ -58,16 +58,21 @@ class Rest extends PlatformObject { /// gets stats based on params as a [PaginatedResult] /// /// https://docs.ably.com/client-lib-development-guide/features/#RSC6 - // Future> stats([Map? params]) { - // throw UnimplementedError(); - // } + Future> stats([Map? params]) async { + final message = await invokeRequest( + PlatformMethod.stats, {TxTransportKeys.params: params}); + return PaginatedResult.fromAblyMessage( + AblyMessage.castFrom(message), + ); + } /// returns server time /// /// https://docs.ably.com/client-lib-development-guide/features/#RSC16 - // Future time() { - // throw UnimplementedError(); - // } + Future time() async { + final time = await invokeRequest(PlatformMethod.restTime); + return DateTime.fromMillisecondsSinceEpoch(time); + } /// a push object interacting with Push API, such as /// subscribing for push notifications by clientId. diff --git a/lib/src/stats/src/stats.dart b/lib/src/stats/src/stats.dart index 9f0016c1c..ba233877b 100644 --- a/lib/src/stats/src/stats.dart +++ b/lib/src/stats/src/stats.dart @@ -4,6 +4,18 @@ import 'package:ably_flutter/ably_flutter.dart'; /// /// https://docs.ably.com/client-lib-development-guide/features/#TS1 class Stats { + /// Creates a stats instance + Stats( + {this.all, + this.apiRequests, + this.channels, + this.connections, + this.inbound, + this.intervalId, + this.outbound, + this.persisted, + this.tokenRequests}); + /// Aggregates inbound and outbound messages. /// /// https://docs.ably.com/client-lib-development-guide/features/#TS12e diff --git a/lib/src/stats/src/stats_connection_types.dart b/lib/src/stats/src/stats_connection_types.dart index 509a3ffba..fe40786a4 100644 --- a/lib/src/stats/src/stats_connection_types.dart +++ b/lib/src/stats/src/stats_connection_types.dart @@ -4,7 +4,10 @@ import 'package:ably_flutter/ably_flutter.dart'; /// for different (TLS vs non-TLS) connection types /// /// https://docs.ably.com/client-lib-development-guide/features/#TS4 -abstract class StatsConnectionTypes { +class StatsConnectionTypes { + /// Creates instance of [StatsConnectionTypes] + StatsConnectionTypes({this.all, this.plain, this.tls}); + /// All connection count (includes both TLS & non-TLS connections). StatsResourceCount? all; diff --git a/lib/src/stats/src/stats_message_count.dart b/lib/src/stats/src/stats_message_count.dart index cdbe89d3d..019c2262b 100644 --- a/lib/src/stats/src/stats_message_count.dart +++ b/lib/src/stats/src/stats_message_count.dart @@ -1,10 +1,13 @@ /// MessageCount contains aggregate counts for messages and data transferred /// /// https://docs.ably.com/client-lib-development-guide/features/#TS5 -abstract class StatsMessageCount { +class StatsMessageCount { + ///Creates instance of [StatsMessageCount] + StatsMessageCount({this.count, this.data}); + /// Count of all messages. - int? count; + double? count; /// Total data transferred for all messages in bytes. - int? data; + double? data; } diff --git a/lib/src/stats/src/stats_message_traffic.dart b/lib/src/stats/src/stats_message_traffic.dart index 8feef9ca6..e50eb9287 100644 --- a/lib/src/stats/src/stats_message_traffic.dart +++ b/lib/src/stats/src/stats_message_traffic.dart @@ -4,7 +4,10 @@ import 'package:ably_flutter/ably_flutter.dart'; /// for traffic over various transport types /// /// https://docs.ably.com/client-lib-development-guide/features/#TS7 -abstract class StatsMessageTraffic { +class StatsMessageTraffic { + /// Creates instance of [StatsMessageTraffic] + StatsMessageTraffic({this.all, this.realtime, this.rest, this.webhook}); + /// All messages count (includes realtime, rest and webhook messages). StatsMessageTypes? all; diff --git a/lib/src/stats/src/stats_message_types.dart b/lib/src/stats/src/stats_message_types.dart index caaa362e8..9f1f4ce3e 100644 --- a/lib/src/stats/src/stats_message_types.dart +++ b/lib/src/stats/src/stats_message_types.dart @@ -4,7 +4,10 @@ import 'package:ably_flutter/ably_flutter.dart'; /// for different (message vs presence) message types /// /// https://docs.ably.com/client-lib-development-guide/features/#TS6 -abstract class StatsMessageTypes { +class StatsMessageTypes { + /// Creates instance of [StatsMessageTypes] + StatsMessageTypes({this.all, this.messages, this.presence}); + /// All messages count (includes both presence & messages). StatsMessageCount? all; diff --git a/lib/src/stats/src/stats_request_count.dart b/lib/src/stats/src/stats_request_count.dart index 5bd0d8a1e..509bad5d3 100644 --- a/lib/src/stats/src/stats_request_count.dart +++ b/lib/src/stats/src/stats_request_count.dart @@ -1,14 +1,17 @@ /// RequestCount contains aggregate counts for requests made /// /// https://docs.ably.com/client-lib-development-guide/features/#TS8 -abstract class StatsRequestCount { +class StatsRequestCount { + /// Creates instance of [StatsRequestCount] + StatsRequestCount({this.failed, this.refused, this.succeeded}); + /// Requests failed. - int? failed; + double? failed; /// Requests refused typically as a result of permissions /// or a limit being exceeded. - int? refused; + double? refused; /// Requests succeeded. - int? succeeded; + double? succeeded; } diff --git a/lib/src/stats/src/stats_resource_count.dart b/lib/src/stats/src/stats_resource_count.dart index 89781023f..922e30ddd 100644 --- a/lib/src/stats/src/stats_resource_count.dart +++ b/lib/src/stats/src/stats_resource_count.dart @@ -2,19 +2,23 @@ /// in a specific scope /// /// https://docs.ably.com/client-lib-development-guide/features/#TS9 -abstract class StatsResourceCount { +class StatsResourceCount { + /// Creates instance of [StatsResourceCount] + StatsResourceCount( + {this.mean, this.min, this.opened, this.peak, this.refused}); + /// Average resources of this type used for this period. - int? mean; + double? mean; /// Minimum total resources of this type used for this period. - int? min; + double? min; /// Total resources of this type opened. - int? opened; + double? opened; /// Peak resources of this type used for this period. - int? peak; + double? peak; /// Resource requests refused within this period. - int? refused; + double? refused; }