From fbbdbfd71f9ea4dde6db303846165cc01cc3ffed Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 7 Feb 2017 21:50:43 +0000 Subject: [PATCH 01/46] Initial Push types --- Ably.xcodeproj/project.pbxproj | 32 +++++++++++++++++++++++++++++++ Source/ARTDeviceDetails.h | 29 ++++++++++++++++++++++++++++ Source/ARTDeviceDetails.m | 29 ++++++++++++++++++++++++++++ Source/ARTPush.h | 35 ++++++++++++++++++++++++++++++++++ Source/ARTPush.m | 25 ++++++++++++++++++++++++ Source/ARTPushChannel.h | 19 ++++++++++++++++++ Source/ARTPushChannel.m | 29 ++++++++++++++++++++++++++++ Source/Ably.h | 3 +++ 8 files changed, 201 insertions(+) create mode 100644 Source/ARTDeviceDetails.h create mode 100644 Source/ARTDeviceDetails.m create mode 100644 Source/ARTPush.h create mode 100644 Source/ARTPush.m create mode 100644 Source/ARTPushChannel.h create mode 100644 Source/ARTPushChannel.m diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 05e199b8d..4fa456053 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -133,6 +133,12 @@ D79FF2751D901CD50067FA6A /* ARTRealtime+TestSuite.m in Sources */ = {isa = PBXBuildFile; fileRef = D79FF2741D901CD50067FA6A /* ARTRealtime+TestSuite.m */; }; D7B17EE31C07208B00A6958E /* ARTConnectionDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B17EE11C07208B00A6958E /* ARTConnectionDetails.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7B17EE41C07208B00A6958E /* ARTConnectionDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D7B17EE21C07208B00A6958E /* ARTConnectionDetails.m */; }; + D7B621901E4A6E0200684474 /* ARTPush.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B6218E1E4A6E0200684474 /* ARTPush.h */; }; + D7B621911E4A6E0200684474 /* ARTPush.m in Sources */ = {isa = PBXBuildFile; fileRef = D7B6218F1E4A6E0200684474 /* ARTPush.m */; }; + D7B621941E4A6FE600684474 /* ARTDeviceDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */; }; + D7B621951E4A6FE600684474 /* ARTDeviceDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */; }; + D7B621981E4A762A00684474 /* ARTPushChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B621961E4A762A00684474 /* ARTPushChannel.h */; }; + D7B621991E4A762A00684474 /* ARTPushChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D7B621971E4A762A00684474 /* ARTPushChannel.m */; }; D7C1B8771BBEA81A0087B55F /* Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C1B8761BBEA81A0087B55F /* Auth.swift */; }; D7C1B8791BBF5F810087B55F /* ARTAuth+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C1B8781BBF5F460087B55F /* ARTAuth+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; D7CEF12D1C8D821D004FB242 /* ARTRealtimeChannels+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D7CEF12C1C8D821D004FB242 /* ARTRealtimeChannels+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -370,6 +376,12 @@ D79FF2741D901CD50067FA6A /* ARTRealtime+TestSuite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ARTRealtime+TestSuite.m"; sourceTree = ""; }; D7B17EE11C07208B00A6958E /* ARTConnectionDetails.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTConnectionDetails.h; sourceTree = ""; }; D7B17EE21C07208B00A6958E /* ARTConnectionDetails.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTConnectionDetails.m; sourceTree = ""; }; + D7B6218E1E4A6E0200684474 /* ARTPush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPush.h; sourceTree = ""; }; + D7B6218F1E4A6E0200684474 /* ARTPush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPush.m; sourceTree = ""; }; + D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTDeviceDetails.h; sourceTree = ""; }; + D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTDeviceDetails.m; sourceTree = ""; }; + D7B621961E4A762A00684474 /* ARTPushChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPushChannel.h; sourceTree = ""; }; + D7B621971E4A762A00684474 /* ARTPushChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushChannel.m; sourceTree = ""; }; D7C1B8761BBEA81A0087B55F /* Auth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Auth.swift; sourceTree = ""; }; D7C1B8781BBF5F460087B55F /* ARTAuth+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ARTAuth+Private.h"; sourceTree = ""; }; D7CEF12C1C8D821D004FB242 /* ARTRealtimeChannels+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARTRealtimeChannels+Private.h"; sourceTree = ""; }; @@ -562,6 +574,7 @@ 1CD8DC9E1B1C7315007EAF36 /* ARTDefault.m */, D746AE301BBC299D003ECEF8 /* Rest */, D746AE311BBC29B2003ECEF8 /* Realtime */, + D7B6218D1E4A6DC000684474 /* Push */, D746AE331BBC29FF003ECEF8 /* Types */, D746AE351BBC2BF9003ECEF8 /* HTTP */, D746AE341BBC2B60003ECEF8 /* Utilities */, @@ -768,6 +781,19 @@ name = HTTP; sourceTree = ""; }; + D7B6218D1E4A6DC000684474 /* Push */ = { + isa = PBXGroup; + children = ( + D7B6218E1E4A6E0200684474 /* ARTPush.h */, + D7B6218F1E4A6E0200684474 /* ARTPush.m */, + D7B621961E4A762A00684474 /* ARTPushChannel.h */, + D7B621971E4A762A00684474 /* ARTPushChannel.m */, + D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */, + D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */, + ); + name = Push; + sourceTree = ""; + }; FAFEC5BB8312EB69E84C28D4 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -822,6 +848,7 @@ 1CD8DC9F1B1C7315007EAF36 /* ARTDefault.h in Headers */, D7D8F8211BC2BE16009718F2 /* ARTAuthOptions.h in Headers */, EB82F8511C59D29B00661917 /* ARTDataEncoder.h in Headers */, + D7B621981E4A762A00684474 /* ARTPushChannel.h in Headers */, D746AE401BBC5B14003ECEF8 /* ARTEventEmitter.h in Headers */, D746AE531BBD85C5003ECEF8 /* ARTChannels.h in Headers */, D7D8F82D1BC2C706009718F2 /* ARTTokenParams.h in Headers */, @@ -832,12 +859,14 @@ 1C578E1F1B3435CA00EF46EC /* ARTFallback.h in Headers */, EB7617721CB6CBFF00D0981E /* ARTRealtimePresence+Private.h in Headers */, D7D5A69C1CA40C350071BD6D /* ARTConnectionDetails+Private.h in Headers */, + D7B621901E4A6E0200684474 /* ARTPush.h in Headers */, 96E408471A3895E800087F77 /* ARTWebSocketTransport.h in Headers */, EB503C8A1C7F1FE40053AF00 /* ARTLog+Private.h in Headers */, 96E4083F1A3892C700087F77 /* ARTRealtimeTransport.h in Headers */, D746AE4F1BBD84E7003ECEF8 /* ARTChannelOptions.h in Headers */, D7588AF31BFF91B800BB8279 /* ARTURLSessionServerTrust.h in Headers */, D746AE3C1BBC5AE1003ECEF8 /* ARTRealtimeChannel.h in Headers */, + D7B621941E4A6FE600684474 /* ARTDeviceDetails.h in Headers */, 96BF61701A35FB7C004CF2B3 /* ARTAuth.h in Headers */, 96A507A11A377AA50077CDF8 /* ARTPresenceMessage.h in Headers */, 850BFB4C1B79323C009D0ADD /* ARTPaginatedResult.h in Headers */, @@ -1159,6 +1188,7 @@ EB82F8521C59D30500661917 /* ARTDataEncoder.m in Sources */, EB2D5A911CC941A700AD1A67 /* ARTRealtimeTransport.m in Sources */, D746AE391BBC3201003ECEF8 /* ARTMessage.m in Sources */, + D7B621991E4A762A00684474 /* ARTPushChannel.m in Sources */, EB89D40B1C61C6EA007FA5B7 /* ARTRealtimeChannels.m in Sources */, D746AE231BBB60EE003ECEF8 /* ARTChannel.m in Sources */, EB2D84FD1CD769B800F23CDA /* ARTOSReachability.m in Sources */, @@ -1195,7 +1225,9 @@ EB9121401CA0AD8200BA0A40 /* ARTMsgPackEncoder.m in Sources */, 96BF61651A35CDE1004CF2B3 /* ARTBaseMessage.m in Sources */, D7F1D3781BF4DE72001A4B5E /* ARTRealtimePresence.m in Sources */, + D7B621911E4A6E0200684474 /* ARTPush.m in Sources */, 1CD8DCA01B1C7315007EAF36 /* ARTDefault.m in Sources */, + D7B621951E4A6FE600684474 /* ARTDeviceDetails.m in Sources */, D7588AF41BFF91B800BB8279 /* ARTURLSessionServerTrust.m in Sources */, D746AE501BBD84E7003ECEF8 /* ARTChannelOptions.m in Sources */, EB89D4051C61C1A4007FA5B7 /* ARTRestChannels.m in Sources */, diff --git a/Source/ARTDeviceDetails.h b/Source/ARTDeviceDetails.h new file mode 100644 index 000000000..c90121f89 --- /dev/null +++ b/Source/ARTDeviceDetails.h @@ -0,0 +1,29 @@ +// +// ARTDeviceDetails.h +// Ably +// +// Created by Ricardo Pereira on 07/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const ARTDevicePlatform; +extern NSString *const ARTDevicePushTransportType; + +typedef NS_ENUM(NSUInteger, ARTDeviceFormFactor) { + ARTDeviceFormFactorMobile, + ARTDeviceFormFactorTablet, + ARTDeviceFormFactorDesktop, + ARTDeviceFormFactorEmbedded +}; + +NSString *ARTDeviceFormFactorToStr(ARTDeviceFormFactor formFactor); + +@interface ARTDeviceDetails : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m new file mode 100644 index 000000000..1f8e420d2 --- /dev/null +++ b/Source/ARTDeviceDetails.m @@ -0,0 +1,29 @@ +// +// ARTDeviceDetails.m +// Ably +// +// Created by Ricardo Pereira on 07/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTDeviceDetails.h" + +NSString *const ARTDevicePlatform = @"ios"; +NSString *const ARTDevicePushTransportType = @"apns"; + +NSString *ARTDeviceFormFactorToStr(ARTDeviceFormFactor formFactor) { + switch (formFactor) { + case ARTDeviceFormFactorMobile: + return @"mobile"; //0 + case ARTDeviceFormFactorTablet: + return @"tablet"; //1 + case ARTDeviceFormFactorDesktop: + return @"desktop"; //2 + case ARTDeviceFormFactorEmbedded: + return @"embedded"; //3 + } +} + +@implementation ARTDeviceDetails + +@end diff --git a/Source/ARTPush.h b/Source/ARTPush.h new file mode 100644 index 000000000..c84d6ea13 --- /dev/null +++ b/Source/ARTPush.h @@ -0,0 +1,35 @@ +// +// ARTPush.h +// Ably +// +// Created by Ricardo Pereira on 07/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import +#import "ARTDeviceDetails.h" +#import "ARTTypes.h" + +@interface ARTJsonObject : NSDictionary +@end + +@interface ARTDeviceId : NSString +@end + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTPush : NSObject + +/// Publish a push notification. +- (void)publish:(NSDictionary *)params jsonObject:(ARTJsonObject *)jsonObject; + +#ifdef TARGET_OS_IPHONE +/// Register a device, including the information necessary to deliver push notifications to it. +- (void)activate:(ARTDeviceDetails *)deviceDetails callback:(void (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable))callback; +/// Unregister a device. +- (void)deactivate:(ARTDeviceId *)deviceId callback:(void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))callback; +#endif + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPush.m b/Source/ARTPush.m new file mode 100644 index 000000000..b17d19073 --- /dev/null +++ b/Source/ARTPush.m @@ -0,0 +1,25 @@ +// +// ARTPush.m +// Ably +// +// Created by Ricardo Pereira on 07/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTPush.h" + +@implementation ARTPush + +- (void)publish:(NSDictionary *)params jsonObject:(ARTJsonObject *)jsonObject { + +} + +- (void)activate:(ARTDeviceDetails *)deviceDetails callback:(void (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable))callback { + +} + +- (void)deactivate:(ARTDeviceId *)deviceId callback:(void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))callback { + +} + +@end diff --git a/Source/ARTPushChannel.h b/Source/ARTPushChannel.h new file mode 100644 index 000000000..28d5e2ae5 --- /dev/null +++ b/Source/ARTPushChannel.h @@ -0,0 +1,19 @@ +// +// ARTPushChannel.h +// Ably +// +// Created by Ricardo Pereira on 07/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import + +@interface ARTPushChannel : NSObject + +- (void)subscribeForDevice:(NSString *)device; +- (void)subscribeForClientId:(NSString *)clientId; + +- (void)unsubscribeForDevice:(NSString *)device; +- (void)unsubscribeForClientId:(NSString *)clientId; + +@end diff --git a/Source/ARTPushChannel.m b/Source/ARTPushChannel.m new file mode 100644 index 000000000..cf4a047e3 --- /dev/null +++ b/Source/ARTPushChannel.m @@ -0,0 +1,29 @@ +// +// ARTPushChannel.m +// Ably +// +// Created by Ricardo Pereira on 07/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTPushChannel.h" + +@implementation ARTPushChannel + +- (void)subscribeForDevice:(NSString *)device { + +} + +- (void)subscribeForClientId:(NSString *)clientId { + +} + +- (void)unsubscribeForDevice:(NSString *)device { + +} + +- (void)unsubscribeForClientId:(NSString *)clientId { + +} + +@end diff --git a/Source/Ably.h b/Source/Ably.h index ec9c3a8d9..f6eb32083 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -57,6 +57,9 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import "ARTReachability.h" #import "ARTOSReachability.h" #import "ARTGCD.h" +#import "ARTPush.h" +#import "ARTPushChannel.h" +#import "ARTDeviceDetails.h" #import "ARTNSDictionary+ARTDictionaryUtil.h" #import "ARTNSDate+ARTUtil.h" From 16af68127d870bf4885252f463357bcdb5e85856 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 8 Feb 2017 11:33:02 +0000 Subject: [PATCH 02/46] Update project --- Ably.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 4fa456053..723b51362 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -1105,7 +1105,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run \'pod install\' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; E372156FD08054912A7AEF3A /* [CP] Embed Pods Frameworks */ = { @@ -1135,7 +1135,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run \'pod install\' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; F7782A5646A3F6417E256264 /* [CP] Check Pods Manifest.lock */ = { @@ -1150,7 +1150,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run \'pod install\' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ From c75173f9f9afb32e70eda299bc7b1dc53d4215dc Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 8 Feb 2017 11:33:58 +0000 Subject: [PATCH 03/46] ARTPushNotifications interface --- Source/ARTPush.h | 17 +++++++++++++++++ Source/ARTPush.m | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/Source/ARTPush.h b/Source/ARTPush.h index c84d6ea13..f341ce5db 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -16,9 +16,26 @@ @interface ARTDeviceId : NSString @end + +#pragma mark ARTPushNotifications interface + +#ifdef TARGET_OS_IPHONE +@protocol ARTPushNotifications +- (void)didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken; +- (void)didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error; +@end +#endif + + +#pragma mark ARTPush type + NS_ASSUME_NONNULL_BEGIN +#ifdef TARGET_OS_IPHONE +@interface ARTPush : NSObject +#else @interface ARTPush : NSObject +#endif /// Publish a push notification. - (void)publish:(NSDictionary *)params jsonObject:(ARTJsonObject *)jsonObject; diff --git a/Source/ARTPush.m b/Source/ARTPush.m index b17d19073..5354c4904 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -10,6 +10,16 @@ @implementation ARTPush +#ifdef TARGET_OS_IPHONE +- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + NSLog(@"ARTPush %p %s: %@", self, __FUNCTION__, deviceToken); +} + +- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + NSLog(@"ARTPush %p %s: %@", self, __FUNCTION__, error); +} +#endif + - (void)publish:(NSDictionary *)params jsonObject:(ARTJsonObject *)jsonObject { } From 015abbad12036e5feb69afbfe9678a7b63e07af2 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 8 Feb 2017 11:40:24 +0000 Subject: [PATCH 04/46] Realtime: integrate Push type --- Source/ARTRealtime.h | 19 ++++++++++++------- Source/ARTRealtime.m | 35 ++++++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/Source/ARTRealtime.h b/Source/ARTRealtime.h index e6559534a..15300ce38 100644 --- a/Source/ARTRealtime.h +++ b/Source/ARTRealtime.h @@ -26,6 +26,7 @@ @class ARTEventEmitter; @class ARTRealtimeChannel; @class ARTAuth; +@class ARTPush; @class ARTProtocolMessage; @class ARTRealtimeChannels; @@ -39,24 +40,28 @@ ART_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) ARTConnection *connection; @property (nonatomic, strong, readonly) ARTRealtimeChannels *channels; -@property (readonly, getter=getAuth) ARTAuth *auth; +@property (readonly) ARTAuth *auth; +@property (readonly) ARTPush *push; @property (readonly, art_nullable, getter=getClientId) NSString *clientId; - (instancetype)init UNAVAILABLE_ATTRIBUTE; +/** + Instance the Ably library with the given options. + :param options: see ARTClientOptions for options + */ +- (instancetype)initWithOptions:(ARTClientOptions *)options; + /** Instance the Ably library using a key only. This is simply a convenience constructor for the simplest case of instancing the library with a key for basic authentication and no other options. :param key; String key (obtained from application dashboard) */ - (instancetype)initWithKey:(NSString *)key; - - (instancetype)initWithToken:(NSString *)token; -/** -Instance the Ably library with the given options. -:param options: see ARTClientOptions for options -*/ -- (instancetype)initWithOptions:(ARTClientOptions *)options; ++ (instancetype)createWithOptions:(ARTClientOptions *)options NS_SWIFT_UNAVAILABLE("Use instance initializer instead"); ++ (instancetype)createWithKey:(NSString *)key NS_SWIFT_UNAVAILABLE("Use instance initializer instead"); ++ (instancetype)createWithToken:(NSString *)tokenId NS_SWIFT_UNAVAILABLE("Use instance initializer instead"); - (void)time:(void (^)(NSDate *__art_nullable, NSError *__art_nullable))cb; - (void)ping:(void (^)(ARTErrorInfo *__art_nullable))cb; diff --git a/Source/ARTRealtime.m b/Source/ARTRealtime.m index 7b452fb26..838e9f042 100644 --- a/Source/ARTRealtime.m +++ b/Source/ARTRealtime.m @@ -31,6 +31,7 @@ #import "ARTStats.h" #import "ARTRealtimeTransport.h" #import "ARTFallback.h" +#import "ARTPush.h" @interface ARTConnectionStateChange () @@ -53,14 +54,6 @@ @implementation ARTRealtime { _Nonnull dispatch_queue_t _eventQueue; } -- (instancetype)initWithKey:(NSString *)key { - return [self initWithOptions:[[ARTClientOptions alloc] initWithKey:key]]; -} - -- (instancetype)initWithToken:(NSString *)token { - return [self initWithOptions:[[ARTClientOptions alloc] initWithToken:token]]; -} - - (instancetype)initWithOptions:(ARTClientOptions *)options { self = [super init]; if (self) { @@ -94,6 +87,26 @@ - (instancetype)initWithOptions:(ARTClientOptions *)options { return self; } +- (instancetype)initWithKey:(NSString *)key { + return [self initWithOptions:[[ARTClientOptions alloc] initWithKey:key]]; +} + +- (instancetype)initWithToken:(NSString *)token { + return [self initWithOptions:[[ARTClientOptions alloc] initWithToken:token]]; +} + ++ (instancetype)createWithOptions:(ARTClientOptions *)options { + return [[ARTRealtime alloc] initWithOptions:options]; +} + ++ (instancetype)createWithKey:(NSString *)key { + return [[ARTRealtime alloc] initWithKey:key]; +} + ++ (instancetype)createWithToken:(NSString *)tokenId { + return [[ARTRealtime alloc] initWithToken:tokenId]; +} + - (id)getTransport { return _transport; } @@ -114,10 +127,14 @@ - (NSString *)description { return [NSString stringWithFormat:@"Realtime: %@", self.clientId]; } -- (ARTAuth *)getAuth { +- (ARTAuth *)auth { return self.rest.auth; } +- (ARTPush *)push { + return self.rest.push; +} + - (void)dealloc { [self.logger debug:__FILE__ line:__LINE__ message:@"R:%p dealloc", self]; From d337aa9163e163eaead0054cdcacb5bdea2e2482 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 8 Feb 2017 11:40:31 +0000 Subject: [PATCH 05/46] Rest: integrate Push type --- Source/ARTRest.h | 16 ++++++++++++++++ Source/ARTRest.m | 16 +++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Source/ARTRest.h b/Source/ARTRest.h index 6741a5d95..904318649 100644 --- a/Source/ARTRest.h +++ b/Source/ARTRest.h @@ -16,6 +16,7 @@ @class ARTRestChannels; @class ARTClientOptions; @class ARTAuth; +@class ARTPush; @class ARTCancellable; @class ARTStatsQuery; @@ -24,10 +25,24 @@ ART_ASSUME_NONNULL_BEGIN @interface ARTRest : NSObject - (instancetype)init UNAVAILABLE_ATTRIBUTE; + +/** + Instance the Ably library with the given options. + :param options: see ARTClientOptions for options + */ - (instancetype)initWithOptions:(ARTClientOptions *)options; + +/** + Instance the Ably library using a key only. This is simply a convenience constructor for the simplest case of instancing the library with a key for basic authentication and no other options. + :param key; String key (obtained from application dashboard) + */ - (instancetype)initWithKey:(NSString *)key; - (instancetype)initWithToken:(NSString *)tokenId; ++ (instancetype)createWithOptions:(ARTClientOptions *)options NS_SWIFT_UNAVAILABLE("Use instance initializer instead"); ++ (instancetype)createWithKey:(NSString *)key NS_SWIFT_UNAVAILABLE("Use instance initializer instead"); ++ (instancetype)createWithToken:(NSString *)tokenId NS_SWIFT_UNAVAILABLE("Use instance initializer instead"); + - (void)time:(void (^)(NSDate *__art_nullable, NSError *__art_nullable))callback; - (BOOL)stats:(void (^)(__GENERIC(ARTPaginatedResult, ARTStats *) *__art_nullable, ARTErrorInfo *__art_nullable))callback; @@ -35,6 +50,7 @@ ART_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) ARTRestChannels *channels; @property (nonatomic, strong, readonly) ARTAuth *auth; +@property (nonatomic, strong, readonly) ARTPush *push; @end diff --git a/Source/ARTRest.m b/Source/ARTRest.m index 0c7bd4dbf..5f4570f20 100644 --- a/Source/ARTRest.m +++ b/Source/ARTRest.m @@ -34,6 +34,7 @@ #import "ARTDefault.h" #import "ARTFallback.h" #import "ARTGCD.h" +#import "ARTPush.h" @implementation ARTRest @@ -71,6 +72,7 @@ - (instancetype)initWithOptions:(ARTClientOptions *)options { _fallbackCount = 0; _auth = [[ARTAuth alloc] init:self withOptions:_options]; + _push = [[ARTPush alloc] init]; _channels = [[ARTRestChannels alloc] initWithRest:self]; [self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p initialized", self]; @@ -86,6 +88,18 @@ - (instancetype)initWithToken:(NSString *)token { return [self initWithOptions:[[ARTClientOptions alloc] initWithToken:token]]; } ++ (instancetype)createWithOptions:(ARTClientOptions *)options { + return [[ARTRest alloc] initWithOptions:options]; +} + ++ (instancetype)createWithKey:(NSString *)key { + return [[ARTRest alloc] initWithKey:key]; +} + ++ (instancetype)createWithToken:(NSString *)tokenId { + return [[ARTRest alloc] initWithToken:tokenId]; +} + - (void)dealloc { [self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p dealloc", self]; } @@ -300,4 +314,4 @@ - (NSURL *)getBaseUrl { return components.URL; } -@end \ No newline at end of file +@end From ecd193d64b24b7f2d3a32772291d49ac2e219b2e Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 8 Feb 2017 17:28:35 +0000 Subject: [PATCH 06/46] Push.activate --- Ably.xcodeproj/project.pbxproj | 14 ++++-- Source/ARTDeviceDetails.h | 14 +++++- Source/ARTDeviceDetails.m | 20 +++++++- Source/ARTDevicePushDetails.h | 33 ++++++++++++ Source/ARTDevicePushDetails.m | 19 +++++++ Source/ARTEncoder.h | 31 +++++++++--- Source/ARTJsonLikeEncoder.h | 5 +- Source/ARTJsonLikeEncoder.m | 91 ++++++++++++++++++++++------------ Source/ARTPush.h | 5 ++ Source/ARTPush.m | 38 ++++++++++++-- Source/ARTRealtime.h | 2 +- Source/ARTRest.h | 2 +- Source/ARTRest.m | 2 +- Source/Ably.h | 1 + Spec/Auth.swift | 4 -- Spec/Stats.swift | 1 - 16 files changed, 226 insertions(+), 56 deletions(-) create mode 100644 Source/ARTDevicePushDetails.h create mode 100644 Source/ARTDevicePushDetails.m diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 723b51362..0b67b2bc6 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -128,16 +128,18 @@ D7588AF41BFF91B800BB8279 /* ARTURLSessionServerTrust.m in Sources */ = {isa = PBXBuildFile; fileRef = D7588AF21BFF91B800BB8279 /* ARTURLSessionServerTrust.m */; }; D75A3F1B1DDE5B62002A4AAD /* ARTGCD.h in Headers */ = {isa = PBXBuildFile; fileRef = D75A3F191DDE5B62002A4AAD /* ARTGCD.h */; settings = {ATTRIBUTES = (Public, ); }; }; D75A3F1C1DDE5B62002A4AAD /* ARTGCD.m in Sources */ = {isa = PBXBuildFile; fileRef = D75A3F1A1DDE5B62002A4AAD /* ARTGCD.m */; }; + D768C6AC1E4B5B0200436011 /* ARTDevicePushDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D768C6AD1E4B5B0200436011 /* ARTDevicePushDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */; }; D77394031C6F6FFE00F5478F /* ARTProtocolMessage+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D77394021C6F6FFE00F5478F /* ARTProtocolMessage+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; D780846E1C68B3E50083009D /* NSObject+TestSuite.m in Sources */ = {isa = PBXBuildFile; fileRef = D780846D1C68B3E50083009D /* NSObject+TestSuite.m */; }; D79FF2751D901CD50067FA6A /* ARTRealtime+TestSuite.m in Sources */ = {isa = PBXBuildFile; fileRef = D79FF2741D901CD50067FA6A /* ARTRealtime+TestSuite.m */; }; D7B17EE31C07208B00A6958E /* ARTConnectionDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B17EE11C07208B00A6958E /* ARTConnectionDetails.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7B17EE41C07208B00A6958E /* ARTConnectionDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D7B17EE21C07208B00A6958E /* ARTConnectionDetails.m */; }; - D7B621901E4A6E0200684474 /* ARTPush.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B6218E1E4A6E0200684474 /* ARTPush.h */; }; + D7B621901E4A6E0200684474 /* ARTPush.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B6218E1E4A6E0200684474 /* ARTPush.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7B621911E4A6E0200684474 /* ARTPush.m in Sources */ = {isa = PBXBuildFile; fileRef = D7B6218F1E4A6E0200684474 /* ARTPush.m */; }; - D7B621941E4A6FE600684474 /* ARTDeviceDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */; }; + D7B621941E4A6FE600684474 /* ARTDeviceDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7B621951E4A6FE600684474 /* ARTDeviceDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */; }; - D7B621981E4A762A00684474 /* ARTPushChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B621961E4A762A00684474 /* ARTPushChannel.h */; }; + D7B621981E4A762A00684474 /* ARTPushChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B621961E4A762A00684474 /* ARTPushChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7B621991E4A762A00684474 /* ARTPushChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D7B621971E4A762A00684474 /* ARTPushChannel.m */; }; D7C1B8771BBEA81A0087B55F /* Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C1B8761BBEA81A0087B55F /* Auth.swift */; }; D7C1B8791BBF5F810087B55F /* ARTAuth+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C1B8781BBF5F460087B55F /* ARTAuth+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -369,6 +371,8 @@ D7588AF21BFF91B800BB8279 /* ARTURLSessionServerTrust.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTURLSessionServerTrust.m; sourceTree = ""; }; D75A3F191DDE5B62002A4AAD /* ARTGCD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTGCD.h; sourceTree = ""; }; D75A3F1A1DDE5B62002A4AAD /* ARTGCD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTGCD.m; sourceTree = ""; }; + D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTDevicePushDetails.h; sourceTree = ""; }; + D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTDevicePushDetails.m; sourceTree = ""; }; D77394021C6F6FFE00F5478F /* ARTProtocolMessage+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARTProtocolMessage+Private.h"; sourceTree = ""; }; D780846C1C68B3E50083009D /* NSObject+TestSuite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+TestSuite.h"; sourceTree = ""; }; D780846D1C68B3E50083009D /* NSObject+TestSuite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+TestSuite.m"; sourceTree = ""; }; @@ -790,6 +794,8 @@ D7B621971E4A762A00684474 /* ARTPushChannel.m */, D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */, D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */, + D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */, + D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */, ); name = Push; sourceTree = ""; @@ -849,6 +855,7 @@ D7D8F8211BC2BE16009718F2 /* ARTAuthOptions.h in Headers */, EB82F8511C59D29B00661917 /* ARTDataEncoder.h in Headers */, D7B621981E4A762A00684474 /* ARTPushChannel.h in Headers */, + D768C6AC1E4B5B0200436011 /* ARTDevicePushDetails.h in Headers */, D746AE401BBC5B14003ECEF8 /* ARTEventEmitter.h in Headers */, D746AE531BBD85C5003ECEF8 /* ARTChannels.h in Headers */, D7D8F82D1BC2C706009718F2 /* ARTTokenParams.h in Headers */, @@ -1231,6 +1238,7 @@ D7588AF41BFF91B800BB8279 /* ARTURLSessionServerTrust.m in Sources */, D746AE501BBD84E7003ECEF8 /* ARTChannelOptions.m in Sources */, EB89D4051C61C1A4007FA5B7 /* ARTRestChannels.m in Sources */, + D768C6AD1E4B5B0200436011 /* ARTDevicePushDetails.m in Sources */, 1C6C18A41ADFDAB100AB79E4 /* ARTLog.m in Sources */, D70EAAEE1BC3376200CD8B9E /* ARTRestChannel.m in Sources */, D746AE291BBB61C9003ECEF8 /* ARTPresence.m in Sources */, diff --git a/Source/ARTDeviceDetails.h b/Source/ARTDeviceDetails.h index c90121f89..5383f55a1 100644 --- a/Source/ARTDeviceDetails.h +++ b/Source/ARTDeviceDetails.h @@ -8,10 +8,11 @@ #import +@class ARTDevicePushDetails; + NS_ASSUME_NONNULL_BEGIN extern NSString *const ARTDevicePlatform; -extern NSString *const ARTDevicePushTransportType; typedef NS_ENUM(NSUInteger, ARTDeviceFormFactor) { ARTDeviceFormFactorMobile, @@ -24,6 +25,17 @@ NSString *ARTDeviceFormFactorToStr(ARTDeviceFormFactor formFactor); @interface ARTDeviceDetails : NSObject +@property (nonatomic, readonly) NSString *id; +@property (nullable, nonatomic) NSString *clientId; +@property (nonatomic, readonly) NSString *platform; +@property (nonatomic, readonly) ARTDeviceFormFactor formFactor; +@property (nullable, nonatomic) NSDictionary *metadata; +@property (nonatomic, readonly) ARTDevicePushDetails *push; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithToken:(NSString *)deviceToken; ++ (instancetype)fromLocalDevice:(NSString *)deviceToken; + @end NS_ASSUME_NONNULL_END diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index 1f8e420d2..4715879b4 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -7,9 +7,9 @@ // #import "ARTDeviceDetails.h" +#import "ARTDevicePushDetails.h" NSString *const ARTDevicePlatform = @"ios"; -NSString *const ARTDevicePushTransportType = @"apns"; NSString *ARTDeviceFormFactorToStr(ARTDeviceFormFactor formFactor) { switch (formFactor) { @@ -26,4 +26,22 @@ @implementation ARTDeviceDetails ++ (instancetype)fromLocalDevice:(NSString *)deviceToken { + return [[ARTDeviceDetails alloc] initWithToken:deviceToken]; +} + +- (instancetype)initWithToken:(NSString *)deviceToken { + if (self = [super init]) { + _id = [[NSUUID new] UUIDString]; + _formFactor = ARTDeviceFormFactorMobile; + _push = [[ARTDevicePushDetails alloc] init]; + _push.deviceToken = deviceToken; + } + return self; +} + +- (NSString *)platform { + return ARTDevicePlatform; +} + @end diff --git a/Source/ARTDevicePushDetails.h b/Source/ARTDevicePushDetails.h new file mode 100644 index 000000000..4bb68e385 --- /dev/null +++ b/Source/ARTDevicePushDetails.h @@ -0,0 +1,33 @@ +// +// ARTDevicePushDetails.h +// Ably +// +// Created by Ricardo Pereira on 08/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import + +@class ARTErrorInfo; + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const ARTDevicePushTransportType; + +typedef NS_ENUM(NSUInteger, ARTDevicePushState) { + ARTDevicePushStateInitialized, + ARTDevicePushStateActive, + ARTDevicePushStateFailing, + ARTDevicePushStateFailed +}; + +@interface ARTDevicePushDetails : NSObject + +@property (nonatomic, readonly) NSString *transportType; +@property (nonatomic) NSString *deviceToken; +@property (nonatomic, assign) ARTDevicePushState state; +@property (nullable, nonatomic) ARTErrorInfo *errorReason; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTDevicePushDetails.m b/Source/ARTDevicePushDetails.m new file mode 100644 index 000000000..37bc646c0 --- /dev/null +++ b/Source/ARTDevicePushDetails.m @@ -0,0 +1,19 @@ +// +// ARTDevicePushDetails.m +// Ably +// +// Created by Ricardo Pereira on 08/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTDevicePushDetails.h" + +NSString *const ARTDevicePushTransportType = @"apns"; + +@implementation ARTDevicePushDetails + +- (NSString *)transportType { + return ARTDevicePushTransportType; +} + +@end diff --git a/Source/ARTEncoder.h b/Source/ARTEncoder.h index 7589d36f3..346954092 100644 --- a/Source/ARTEncoder.h +++ b/Source/ARTEncoder.h @@ -14,6 +14,8 @@ @class ARTProtocolMessage; @class ARTTokenDetails; @class ARTTokenRequest; +@class ARTDeviceDetails; +@class ARTDevicePushDetails; typedef NS_ENUM(NSUInteger, ARTEncoderFormat) { ARTEncoderFormatJson, @@ -28,24 +30,41 @@ ART_ASSUME_NONNULL_BEGIN - (ARTEncoderFormat)format; - (NSString *)formatAsString; +// TokenRequest - (art_nullable NSData *)encodeTokenRequest:(ARTTokenRequest *)request; -- (art_nullable NSData *)encodeTokenDetails:(ARTTokenDetails *)tokenDetails; +- (art_nullable ARTTokenRequest *)decodeTokenRequest:(NSData *)data error:(NSError * __autoreleasing *)error; +// TokenDetails +- (art_nullable NSData *)encodeTokenDetails:(ARTTokenDetails *)tokenDetails; - (art_nullable ARTTokenDetails *)decodeTokenDetails:(NSData *)data error:(NSError * __autoreleasing *)error; -- (art_nullable ARTTokenRequest *)decodeTokenRequest:(NSData *)data error:(NSError * __autoreleasing *)error; -- (art_nullable ARTMessage *)decodeMessage:(NSData *)data; -- (art_nullable NSArray *)decodeMessages:(NSData *)data; + +// Message - (art_nullable NSData *)encodeMessage:(ARTMessage *)message; +- (art_nullable ARTMessage *)decodeMessage:(NSData *)data; + +// Message list - (art_nullable NSData *)encodeMessages:(NSArray *)messages; +- (art_nullable NSArray *)decodeMessages:(NSData *)data; -- (art_nullable ARTPresenceMessage *)decodePresenceMessage:(NSData *)data; -- (art_nullable NSArray *)decodePresenceMessages:(NSData *)data; +// PresenceMessage - (art_nullable NSData *)encodePresenceMessage:(ARTPresenceMessage *)message; +- (art_nullable ARTPresenceMessage *)decodePresenceMessage:(NSData *)data; + +// PresenceMessage list - (art_nullable NSData *)encodePresenceMessages:(NSArray *)messages; +- (art_nullable NSArray *)decodePresenceMessages:(NSData *)data; +// ProtocolMessage - (art_nullable NSData *)encodeProtocolMessage:(ARTProtocolMessage *)message; - (art_nullable ARTProtocolMessage *)decodeProtocolMessage:(NSData *)data; +// DeviceDetails +- (art_nullable NSData *)encodeDeviceDetails:(ARTDeviceDetails *)deviceDetails; + +// DevicePushDetails +- (art_nullable NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails; + +// Others - (art_nullable NSDate *)decodeTime:(NSData *)data; - (art_nullable NSError *)decodeError:(NSData *)error; - (art_nullable NSArray *)decodeStats:(NSData *)data; diff --git a/Source/ARTJsonLikeEncoder.h b/Source/ARTJsonLikeEncoder.h index 7b65d4477..ee20e7b85 100644 --- a/Source/ARTJsonLikeEncoder.h +++ b/Source/ARTJsonLikeEncoder.h @@ -28,10 +28,11 @@ ART_ASSUME_NONNULL_BEGIN @interface ARTJsonLikeEncoder : NSObject -@property (nonatomic, weak) ARTRest *rest; @property (nonatomic, strong, art_nullable) id delegate; -- (instancetype)initWithRest:(ARTRest *)rest delegate:(id __art_nullable)delegate; +- (instancetype)initWithDelegate:(id)delegate; +- (instancetype)initWithLogger:(ARTLog *)logger delegate:(nullable id)delegate; +- (instancetype)initWithRest:(ARTRest *)rest delegate:(nullable id)delegate; @end diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index 8d75191d8..dd3be07e6 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -23,46 +23,41 @@ #import "ARTStatus.h" #import "ARTTokenDetails.h" #import "ARTTokenRequest.h" +#import "ARTDeviceDetails.h" +#import "ARTDevicePushDetails.h" #import "ARTConnectionDetails.h" #import "ARTRest+Private.h" +#import "ARTJsonEncoder.h" @interface ARTJsonLikeEncoder () -- (ARTMessage *)messageFromDictionary:(NSDictionary *)input; -- (NSArray *)messagesFromArray:(NSArray *)input; - -- (ARTPresenceMessage *)presenceMessageFromDictionary:(NSDictionary *)input; -- (NSArray *)presenceMessagesFromArray:(NSArray *)input; - -- (NSDictionary *)messageToDictionary:(ARTMessage *)message; -- (NSArray *)messagesToArray:(NSArray *)messages; - -- (NSDictionary *)presenceMessageToDictionary:(ARTPresenceMessage *)message; -- (NSArray *)presenceMessagesToArray:(NSArray *)messages; - -- (NSDictionary *)protocolMessageToDictionary:(ARTProtocolMessage *)message; -- (ARTProtocolMessage *)protocolMessageFromDictionary:(NSDictionary *)input; - -- (NSDictionary *)tokenRequestToDictionary:(ARTTokenRequest *)tokenRequest; - -- (NSArray *)statsFromArray:(NSArray *)input; -- (ARTStats *)statsFromDictionary:(NSDictionary *)input; -- (ARTStatsMessageTypes *)statsMessageTypesFromDictionary:(NSDictionary *)input; -- (ARTStatsMessageCount *)statsMessageCountFromDictionary:(NSDictionary *)input; -- (ARTStatsMessageTraffic *)statsMessageTrafficFromDictionary:(NSDictionary *)input; -- (ARTStatsConnectionTypes *)statsConnectionTypesFromDictionary:(NSDictionary *)input; -- (ARTStatsResourceCount *)statsResourceCountFromDictionary:(NSDictionary *)input; -- (ARTStatsRequestCount *)statsRequestCountFromDictionary:(NSDictionary *)input; +@end -- (void)writeData:(id)data encoding:(NSString *)encoding toDictionary:(NSMutableDictionary *)output; +@implementation ARTJsonLikeEncoder { + __weak ARTRest *_rest; + __weak ARTLog *_logger; +} -- (NSDictionary *)decodeDictionary:(NSData *)data; -- (NSArray *)decodeArray:(NSData *)data; +- (instancetype)init { + return [self initWithDelegate:[[ARTJsonEncoder alloc] init]]; +} -@end +- (instancetype)initWithDelegate:(id)delegate { + if (self = [super init]) { + _rest = nil; + _logger = nil; + _delegate = delegate; + } + return self; +} -@implementation ARTJsonLikeEncoder { - ARTLog *_logger; +- (instancetype)initWithLogger:(ARTLog *)logger delegate:(id)delegate { + if (self = [super init]) { + _rest = nil; + _logger = logger; + _delegate = delegate; + } + return self; } - (instancetype)initWithRest:(ARTRest *)rest delegate:(id)delegate { @@ -142,6 +137,14 @@ - (NSData *)encodeTokenDetails:(ARTTokenDetails *)tokenDetails { return [self encode:[self tokenDetailsToDictionary:tokenDetails]]; } +- (NSData *)encodeDeviceDetails:(ARTDeviceDetails *)deviceDetails { + return [self encode:[self deviceDetailsToDictionary:deviceDetails]]; +} + +- (NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails { + return [self encode:[self devicePushDetailsToDictionary:devicePushDetails]]; +} + - (NSDate *)decodeTime:(NSData *)data { NSArray *resp = [self decodeArray:data]; [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: decodeTime %@", _rest, [_delegate formatAsString], resp]; @@ -479,6 +482,32 @@ - (NSDictionary *)tokenDetailsToDictionary:(ARTTokenDetails *)tokenDetails { return dictionary; } +- (NSDictionary *)deviceDetailsToDictionary:(ARTDeviceDetails *)deviceDetails { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + + dictionary[@"id"] = deviceDetails.id; + dictionary[@"platform"] = deviceDetails.platform; + dictionary[@"formFactor"] = ARTDeviceFormFactorToStr(deviceDetails.formFactor); + + if (deviceDetails.clientId) { + dictionary[@"cliendId"] = deviceDetails.clientId; + } + + dictionary[@"push"] = [self devicePushDetailsToDictionary:deviceDetails.push]; + + return dictionary; +} + +- (NSDictionary *)devicePushDetailsToDictionary:(ARTDevicePushDetails *)devicePushDetails { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + + dictionary[@"transportType"] = devicePushDetails.transportType; + + dictionary[@"metadata"] = @{ @"deviceToken": devicePushDetails.deviceToken, }; + + return dictionary; +} + - (ARTProtocolMessage *)protocolMessageFromDictionary:(NSDictionary *)input { [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: protocolMessageFromDictionary %@", _rest, [_delegate formatAsString], input]; if (![input isKindOfClass:[NSDictionary class]]) { diff --git a/Source/ARTPush.h b/Source/ARTPush.h index f341ce5db..572b3cc54 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -10,6 +10,8 @@ #import "ARTDeviceDetails.h" #import "ARTTypes.h" +@class ARTRest; + @interface ARTJsonObject : NSDictionary @end @@ -37,6 +39,9 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPush : NSObject #endif +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)init:(ARTRest *)rest; + /// Publish a push notification. - (void)publish:(NSDictionary *)params jsonObject:(ARTJsonObject *)jsonObject; diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 5354c4904..7ec440cf0 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -7,25 +7,55 @@ // #import "ARTPush.h" +#import "ARTRest+Private.h" +#import "ARTJsonEncoder.h" +#import "ARTJsonLikeEncoder.h" -@implementation ARTPush +@interface ARTPush () + +@end + +@implementation ARTPush { + __weak ARTRest *_rest; +} + +- (instancetype)init:(ARTRest *)rest { + if (self = [super init]) { + _rest = rest; + } + return self; +} -#ifdef TARGET_OS_IPHONE - (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - NSLog(@"ARTPush %p %s: %@", self, __FUNCTION__, deviceToken); + // Normalizing token by removing symbols and spaces + NSString *token = [[[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""]; + + ARTDeviceDetails *localDeviceDetails = [ARTDeviceDetails fromLocalDevice:token]; + + [self activate:localDeviceDetails callback:^(ARTDeviceDetails *deviceDetails, ARTErrorInfo *error) { + + }]; } - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { NSLog(@"ARTPush %p %s: %@", self, __FUNCTION__, error); } -#endif - (void)publish:(NSDictionary *)params jsonObject:(ARTJsonObject *)jsonObject { } - (void)activate:(ARTDeviceDetails *)deviceDetails callback:(void (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable))callback { + id jsonEncoder = [[ARTJsonLikeEncoder alloc] init]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"]]; + request.HTTPMethod = @"POST"; + request.HTTPBody = [jsonEncoder encodeDeviceDetails:deviceDetails]; + [request setValue:[jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + + [_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + + }]; } - (void)deactivate:(ARTDeviceId *)deviceId callback:(void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))callback { diff --git a/Source/ARTRealtime.h b/Source/ARTRealtime.h index 15300ce38..efe0259fe 100644 --- a/Source/ARTRealtime.h +++ b/Source/ARTRealtime.h @@ -44,7 +44,7 @@ ART_ASSUME_NONNULL_BEGIN @property (readonly) ARTPush *push; @property (readonly, art_nullable, getter=getClientId) NSString *clientId; -- (instancetype)init UNAVAILABLE_ATTRIBUTE; +- (instancetype)init NS_UNAVAILABLE; /** Instance the Ably library with the given options. diff --git a/Source/ARTRest.h b/Source/ARTRest.h index 904318649..4d20be63b 100644 --- a/Source/ARTRest.h +++ b/Source/ARTRest.h @@ -24,7 +24,7 @@ ART_ASSUME_NONNULL_BEGIN @interface ARTRest : NSObject -- (instancetype)init UNAVAILABLE_ATTRIBUTE; +- (instancetype)init NS_UNAVAILABLE; /** Instance the Ably library with the given options. diff --git a/Source/ARTRest.m b/Source/ARTRest.m index 5f4570f20..a07e23468 100644 --- a/Source/ARTRest.m +++ b/Source/ARTRest.m @@ -72,7 +72,7 @@ - (instancetype)initWithOptions:(ARTClientOptions *)options { _fallbackCount = 0; _auth = [[ARTAuth alloc] init:self withOptions:_options]; - _push = [[ARTPush alloc] init]; + _push = [[ARTPush alloc] init:self]; _channels = [[ARTRestChannels alloc] initWithRest:self]; [self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p initialized", self]; diff --git a/Source/Ably.h b/Source/Ably.h index f6eb32083..abd192ecf 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -60,6 +60,7 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import "ARTPush.h" #import "ARTPushChannel.h" #import "ARTDeviceDetails.h" +#import "ARTDevicePushDetails.h" #import "ARTNSDictionary+ARTDictionaryUtil.h" #import "ARTNSDate+ARTUtil.h" diff --git a/Spec/Auth.swift b/Spec/Auth.swift index 8a02b807f..d0cc380a9 100644 --- a/Spec/Auth.swift +++ b/Spec/Auth.swift @@ -700,7 +700,6 @@ class Auth : QuickSpec { } let encoder = ARTJsonLikeEncoder() - encoder.delegate = ARTJsonEncoder() guard let jsonTokenDetails = encoder.encodeTokenDetails(testTokenDetails) else { fail("Invalid TokenDetails") return @@ -763,7 +762,6 @@ class Auth : QuickSpec { } let encoder = ARTJsonLikeEncoder() - encoder.delegate = ARTJsonEncoder() guard let jsonTokenRequest = encoder.encodeTokenRequest(testTokenRequest) else { fail("Invalid TokenRequest") return @@ -2033,7 +2031,6 @@ class Auth : QuickSpec { } let encoder = ARTJsonLikeEncoder() - encoder.delegate = ARTJsonEncoder() guard let tokenDetailsJSON = NSString(data: encoder.encodeTokenDetails(tokenDetails) ?? NSData(), encoding: NSUTF8StringEncoding) else { XCTFail("JSON TokenDetails is empty") return @@ -2162,7 +2159,6 @@ class Auth : QuickSpec { let testTokenDetails = getTestTokenDetails(ttl: 0.1) let encoder = ARTJsonLikeEncoder() - encoder.delegate = ARTJsonEncoder() guard let currentTokenDetails = testTokenDetails, jsonTokenDetails = encoder.encodeTokenDetails(currentTokenDetails) else { fail("Invalid TokenDetails") return diff --git a/Spec/Stats.swift b/Spec/Stats.swift index 6f520188e..df7763074 100644 --- a/Spec/Stats.swift +++ b/Spec/Stats.swift @@ -16,7 +16,6 @@ class Stats: QuickSpec { override func spec() { describe("Stats") { let encoder = ARTJsonLikeEncoder() - encoder.delegate = ARTJsonEncoder() // TS6 for attribute in ["all", "persisted"] { From 0ebc76b5fee9132a6a3df9e57a38d6a0d3e6441a Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Thu, 9 Feb 2017 00:07:11 +0000 Subject: [PATCH 07/46] fixup! Push.activate --- Source/ARTDeviceDetails.h | 8 +-- Source/ARTDeviceDetails.m | 13 +++-- Source/ARTDevicePushDetails.h | 4 +- Source/ARTDevicePushDetails.m | 12 +++++ Source/ARTEncoder.h | 1 + Source/ARTJsonLikeEncoder.m | 24 ++++++++- Source/ARTPush.h | 16 ++++-- Source/ARTPush.m | 97 +++++++++++++++++++++++++++++------ 8 files changed, 147 insertions(+), 28 deletions(-) diff --git a/Source/ARTDeviceDetails.h b/Source/ARTDeviceDetails.h index 5383f55a1..d60ce1fc9 100644 --- a/Source/ARTDeviceDetails.h +++ b/Source/ARTDeviceDetails.h @@ -7,6 +7,7 @@ // #import +#import "ARTPush.h" @class ARTDevicePushDetails; @@ -31,10 +32,11 @@ NSString *ARTDeviceFormFactorToStr(ARTDeviceFormFactor formFactor); @property (nonatomic, readonly) ARTDeviceFormFactor formFactor; @property (nullable, nonatomic) NSDictionary *metadata; @property (nonatomic, readonly) ARTDevicePushDetails *push; +@property (nullable, nonatomic) ARTUpdateToken *updateToken; -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithToken:(NSString *)deviceToken; -+ (instancetype)fromLocalDevice:(NSString *)deviceToken; +- (instancetype)initWithId:(NSString *)id; + ++ (instancetype)fromLocalDevice; @end diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index 4715879b4..ceeb80d98 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -26,16 +26,19 @@ @implementation ARTDeviceDetails -+ (instancetype)fromLocalDevice:(NSString *)deviceToken { - return [[ARTDeviceDetails alloc] initWithToken:deviceToken]; ++ (instancetype)fromLocalDevice { + return [[ARTDeviceDetails alloc] init]; } -- (instancetype)initWithToken:(NSString *)deviceToken { +- (instancetype)init { + return [self initWithId:[[NSUUID new] UUIDString]]; +} + +- (instancetype)initWithId:(NSString *)id { if (self = [super init]) { - _id = [[NSUUID new] UUIDString]; + _id = id; _formFactor = ARTDeviceFormFactorMobile; _push = [[ARTDevicePushDetails alloc] init]; - _push.deviceToken = deviceToken; } return self; } diff --git a/Source/ARTDevicePushDetails.h b/Source/ARTDevicePushDetails.h index 4bb68e385..bd3873d09 100644 --- a/Source/ARTDevicePushDetails.h +++ b/Source/ARTDevicePushDetails.h @@ -21,10 +21,12 @@ typedef NS_ENUM(NSUInteger, ARTDevicePushState) { ARTDevicePushStateFailed }; +ARTDevicePushState ARTDevicePushStateFromStr(NSString *value); + @interface ARTDevicePushDetails : NSObject @property (nonatomic, readonly) NSString *transportType; -@property (nonatomic) NSString *deviceToken; +@property (nonatomic) NSData *deviceToken; @property (nonatomic, assign) ARTDevicePushState state; @property (nullable, nonatomic) ARTErrorInfo *errorReason; diff --git a/Source/ARTDevicePushDetails.m b/Source/ARTDevicePushDetails.m index 37bc646c0..8360d4085 100644 --- a/Source/ARTDevicePushDetails.m +++ b/Source/ARTDevicePushDetails.m @@ -10,6 +10,18 @@ NSString *const ARTDevicePushTransportType = @"apns"; +ARTDevicePushState ARTDevicePushStateFromStr(NSString *value) { + if ([[value lowercaseString] isEqualToString:@"active"]) { + return ARTDevicePushStateActive; + } + else if ([[value lowercaseString] isEqualToString:@"failing"]) { + return ARTDevicePushStateFailing; + } + else { + return ARTDevicePushStateFailed; + } +} + @implementation ARTDevicePushDetails - (NSString *)transportType { diff --git a/Source/ARTEncoder.h b/Source/ARTEncoder.h index 346954092..5419e5ea2 100644 --- a/Source/ARTEncoder.h +++ b/Source/ARTEncoder.h @@ -60,6 +60,7 @@ ART_ASSUME_NONNULL_BEGIN // DeviceDetails - (art_nullable NSData *)encodeDeviceDetails:(ARTDeviceDetails *)deviceDetails; +- (art_nullable ARTDeviceDetails *)decodeDeviceDetails:(NSData *)data; // DevicePushDetails - (art_nullable NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails; diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index dd3be07e6..adff5dd1d 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -141,6 +141,10 @@ - (NSData *)encodeDeviceDetails:(ARTDeviceDetails *)deviceDetails { return [self encode:[self deviceDetailsToDictionary:deviceDetails]]; } +- (ARTDeviceDetails *)decodeDeviceDetails:(NSData *)data { + return [self deviceDetailsFromDictionary:[self decodeDictionary:data] error:nil]; +} + - (NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails { return [self encode:[self devicePushDetailsToDictionary:devicePushDetails]]; } @@ -498,12 +502,30 @@ - (NSDictionary *)deviceDetailsToDictionary:(ARTDeviceDetails *)deviceDetails { return dictionary; } +- (ARTDeviceDetails *)deviceDetailsFromDictionary:(NSDictionary *)input error:(NSError * __autoreleasing *)error { + [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: deviceDetailsFromDictionary %@", _rest, [_delegate formatAsString], input]; + + if (![input isKindOfClass:[NSDictionary class]]) { + return nil; + } + + ARTDeviceDetails *deviceDetails = [[ARTDeviceDetails alloc] initWithId:[input artString:@"id"]]; + deviceDetails.updateToken = (ARTUpdateToken *)[input artString:@"updateToken"]; + deviceDetails.push.state = ARTDevicePushStateFromStr([input artString:@"state"]); + + return deviceDetails; +} + - (NSDictionary *)devicePushDetailsToDictionary:(ARTDevicePushDetails *)devicePushDetails { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; dictionary[@"transportType"] = devicePushDetails.transportType; - dictionary[@"metadata"] = @{ @"deviceToken": devicePushDetails.deviceToken, }; + if (devicePushDetails.deviceToken) { + // Normalizing token by removing symbols and spaces + NSString *token = [[[devicePushDetails.deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""]; + dictionary[@"metadata"] = @{ @"deviceToken": token, }; + } return dictionary; } diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 572b3cc54..3eae6ee76 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -7,10 +7,10 @@ // #import -#import "ARTDeviceDetails.h" #import "ARTTypes.h" @class ARTRest; +@class ARTDeviceDetails; @interface ARTJsonObject : NSDictionary @end @@ -18,6 +18,11 @@ @interface ARTDeviceId : NSString @end +@interface ARTDeviceToken : NSData +@end + +@interface ARTUpdateToken : NSString +@end #pragma mark ARTPushNotifications interface @@ -39,6 +44,8 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPush : NSObject #endif +@property (nonatomic, readonly) ARTDeviceDetails *device; + - (instancetype)init NS_UNAVAILABLE; - (instancetype)init:(ARTRest *)rest; @@ -47,9 +54,12 @@ NS_ASSUME_NONNULL_BEGIN #ifdef TARGET_OS_IPHONE /// Register a device, including the information necessary to deliver push notifications to it. -- (void)activate:(ARTDeviceDetails *)deviceDetails callback:(void (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable))callback; +- (void)activate; +- (void)activate:(ARTDeviceDetails *)deviceDetails; +- (void)activate:(ARTDeviceDetails *)deviceDetails registerCallback:(nullable ARTUpdateToken* (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable))registerCallback; /// Unregister a device. -- (void)deactivate:(ARTDeviceId *)deviceId callback:(void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))callback; +- (void)deactivate:(ARTDeviceId *)deviceId; +- (void)deactivate:(ARTDeviceId *)deviceId deregisterCallback:(nullable void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))deregisterCallback; #endif @end diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 7ec440cf0..184444911 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -7,58 +7,125 @@ // #import "ARTPush.h" +#import "ARTDeviceDetails.h" #import "ARTRest+Private.h" +#import "ARTLog.h" #import "ARTJsonEncoder.h" #import "ARTJsonLikeEncoder.h" +#import "ARTEventEmitter.h" + +NSString *const ARTDeviceTokenKey = @"DeviceToken"; + +typedef NS_ENUM(NSUInteger, ARTPushState) { + ARTPushStateDeactivated, + ARTPushStateActivated, +}; @interface ARTPush () +@property (nonatomic, readonly) ARTPushState state; + @end @implementation ARTPush { __weak ARTRest *_rest; + __weak ARTLog *_logger; + id _jsonEncoder; + ARTEventEmitter *_deviceTokenEmitter; } - (instancetype)init:(ARTRest *)rest { if (self = [super init]) { _rest = rest; + _logger = rest.logger; + _device = [ARTDeviceDetails fromLocalDevice]; + _state = ARTPushStateDeactivated; + _deviceTokenEmitter = [[ARTEventEmitter alloc] init]; + _jsonEncoder = [[ARTJsonLikeEncoder alloc] init]; } return self; } - (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - // Normalizing token by removing symbols and spaces - NSString *token = [[[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""]; + [_logger info:@"ARTPush: device token received and stored"]; + [[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:ARTDeviceTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [_deviceTokenEmitter emit:[NSNull null] with:(ARTDeviceToken *)deviceToken]; +} - ARTDeviceDetails *localDeviceDetails = [ARTDeviceDetails fromLocalDevice:token]; +- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + [_logger error:@"ARTPush: device token not received (%@)", [error localizedDescription]]; +} - [self activate:localDeviceDetails callback:^(ARTDeviceDetails *deviceDetails, ARTErrorInfo *error) { +- (void)publish:(NSDictionary *)params jsonObject:(ARTJsonObject *)jsonObject { - }]; } -- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - NSLog(@"ARTPush %p %s: %@", self, __FUNCTION__, error); +- (void)activate { + [self activate:self.device registerCallback:nil]; } -- (void)publish:(NSDictionary *)params jsonObject:(ARTJsonObject *)jsonObject { +- (void)activate:(ARTDeviceDetails *)deviceDetails { + [self activate:deviceDetails registerCallback:nil]; +} + +- (void)activate:(ARTDeviceDetails *)deviceDetails registerCallback:(ARTUpdateToken* (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable))registerCallback { + if (self.state == ARTPushStateActivated) { + return; + } + NSData *deviceToken = [[NSUserDefaults standardUserDefaults] dataForKey:ARTDeviceTokenKey]; + if (!deviceToken) { + // Waiting for device token + [_deviceTokenEmitter once:^(ARTDeviceToken *deviceToken) { + [self activate:deviceDetails registerCallback:registerCallback]; + }]; + return; + } + + if (registerCallback) { + self.device.updateToken = registerCallback(deviceDetails, nil); + return; + } + + if (self.device.updateToken) { + [self updateDevice:deviceDetails]; + } + else { + [self newDevice:deviceDetails]; + } } -- (void)activate:(ARTDeviceDetails *)deviceDetails callback:(void (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable))callback { - id jsonEncoder = [[ARTJsonLikeEncoder alloc] init]; +- (void)deactivate:(ARTDeviceId *)deviceId { + [self deactivate:deviceId deregisterCallback:nil]; +} + +- (void)deactivate:(ARTDeviceId *)deviceId deregisterCallback:(void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))deregisterCallback { + +} + +- (void)newDevice:(ARTDeviceDetails *)deviceDetails { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"]]; request.HTTPMethod = @"POST"; - request.HTTPBody = [jsonEncoder encodeDeviceDetails:deviceDetails]; - [request setValue:[jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + request.HTTPBody = [_jsonEncoder encodeDeviceDetails:deviceDetails]; + [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + [_logger debug:__FILE__ line:__LINE__ message:@"ARTPush: device registration with request %@", request]; [_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - - + if (response.statusCode == 201 /*Created*/) { + ARTDeviceDetails *deviceDetails = [_jsonEncoder decodeDeviceDetails:data]; + self.device.updateToken = deviceDetails.updateToken; + } + else if (error) { + [_logger error:@"ARTPush: device registration failed (%@)", error.localizedDescription]; + } + else { + [_logger error:@"ARTPush: device registration failed with unknown error"]; + } }]; } -- (void)deactivate:(ARTDeviceId *)deviceId callback:(void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))callback { +- (void)updateDevice:(ARTDeviceDetails *)deviceDetails { } From bb829d87c89d074a5db48e148ec47a46de5599b0 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Mon, 13 Feb 2017 11:52:27 +0000 Subject: [PATCH 08/46] Push.publish - add PushRecipient protocol --- Ably.xcodeproj/project.pbxproj | 8 ++++ Source/ARTEncoder.h | 6 +++ Source/ARTJsonLikeEncoder.m | 10 +++++ Source/ARTPush.h | 3 +- Source/ARTPush.m | 20 ++++++++- Source/ARTPushRecipient.h | 47 +++++++++++++++++++++ Source/ARTPushRecipient.m | 76 ++++++++++++++++++++++++++++++++++ 7 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 Source/ARTPushRecipient.h create mode 100644 Source/ARTPushRecipient.m diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 0b67b2bc6..2ae54af2b 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -128,6 +128,8 @@ D7588AF41BFF91B800BB8279 /* ARTURLSessionServerTrust.m in Sources */ = {isa = PBXBuildFile; fileRef = D7588AF21BFF91B800BB8279 /* ARTURLSessionServerTrust.m */; }; D75A3F1B1DDE5B62002A4AAD /* ARTGCD.h in Headers */ = {isa = PBXBuildFile; fileRef = D75A3F191DDE5B62002A4AAD /* ARTGCD.h */; settings = {ATTRIBUTES = (Public, ); }; }; D75A3F1C1DDE5B62002A4AAD /* ARTGCD.m in Sources */ = {isa = PBXBuildFile; fileRef = D75A3F1A1DDE5B62002A4AAD /* ARTGCD.m */; }; + D7684CFA1E51D20100F3B07F /* ARTPushRecipient.h in Headers */ = {isa = PBXBuildFile; fileRef = D7684CF81E51D20100F3B07F /* ARTPushRecipient.h */; }; + D7684CFB1E51D20100F3B07F /* ARTPushRecipient.m in Sources */ = {isa = PBXBuildFile; fileRef = D7684CF91E51D20100F3B07F /* ARTPushRecipient.m */; }; D768C6AC1E4B5B0200436011 /* ARTDevicePushDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */; settings = {ATTRIBUTES = (Public, ); }; }; D768C6AD1E4B5B0200436011 /* ARTDevicePushDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */; }; D77394031C6F6FFE00F5478F /* ARTProtocolMessage+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D77394021C6F6FFE00F5478F /* ARTProtocolMessage+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -371,6 +373,8 @@ D7588AF21BFF91B800BB8279 /* ARTURLSessionServerTrust.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTURLSessionServerTrust.m; sourceTree = ""; }; D75A3F191DDE5B62002A4AAD /* ARTGCD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTGCD.h; sourceTree = ""; }; D75A3F1A1DDE5B62002A4AAD /* ARTGCD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTGCD.m; sourceTree = ""; }; + D7684CF81E51D20100F3B07F /* ARTPushRecipient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPushRecipient.h; sourceTree = ""; }; + D7684CF91E51D20100F3B07F /* ARTPushRecipient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushRecipient.m; sourceTree = ""; }; D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTDevicePushDetails.h; sourceTree = ""; }; D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTDevicePushDetails.m; sourceTree = ""; }; D77394021C6F6FFE00F5478F /* ARTProtocolMessage+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARTProtocolMessage+Private.h"; sourceTree = ""; }; @@ -790,6 +794,8 @@ children = ( D7B6218E1E4A6E0200684474 /* ARTPush.h */, D7B6218F1E4A6E0200684474 /* ARTPush.m */, + D7684CF81E51D20100F3B07F /* ARTPushRecipient.h */, + D7684CF91E51D20100F3B07F /* ARTPushRecipient.m */, D7B621961E4A762A00684474 /* ARTPushChannel.h */, D7B621971E4A762A00684474 /* ARTPushChannel.m */, D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */, @@ -879,6 +885,7 @@ 850BFB4C1B79323C009D0ADD /* ARTPaginatedResult.h in Headers */, D746AE1E1BBB5207003ECEF8 /* ARTDataQuery+Private.h in Headers */, D77394031C6F6FFE00F5478F /* ARTProtocolMessage+Private.h in Headers */, + D7684CFA1E51D20100F3B07F /* ARTPushRecipient.h in Headers */, D746AE2F1BBBE7D7003ECEF8 /* ARTPaginatedResult+Private.h in Headers */, D746AE431BBC5CD0003ECEF8 /* ARTRealtimeChannel+Private.h in Headers */, D746AE251BBB611C003ECEF8 /* ARTChannel+Private.h in Headers */, @@ -1219,6 +1226,7 @@ 850BFB4D1B79323C009D0ADD /* ARTPaginatedResult.m in Sources */, EB9C530D1CD7BFF300.8.557 /* ARTJsonLikeEncoder.m in Sources */, D746AE1F1BBB5207003ECEF8 /* ARTDataQuery.m in Sources */, + D7684CFB1E51D20100F3B07F /* ARTPushRecipient.m in Sources */, 961343D91A42E0B7006DC822 /* ARTClientOptions.m in Sources */, 96BF615F1A35C1C8004CF2B3 /* ARTTypes.m in Sources */, D7D8F82E1BC2C706009718F2 /* ARTTokenParams.m in Sources */, diff --git a/Source/ARTEncoder.h b/Source/ARTEncoder.h index 5419e5ea2..16c04f3c1 100644 --- a/Source/ARTEncoder.h +++ b/Source/ARTEncoder.h @@ -16,6 +16,9 @@ @class ARTTokenRequest; @class ARTDeviceDetails; @class ARTDevicePushDetails; +@class ARTJsonObject; + +@protocol ARTPushRecipient; typedef NS_ENUM(NSUInteger, ARTEncoderFormat) { ARTEncoderFormatJson, @@ -65,6 +68,9 @@ ART_ASSUME_NONNULL_BEGIN // DevicePushDetails - (art_nullable NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails; +// PushRecipient +- (art_nullable NSData *)encodePushRecipient:(id)recipient withJsonObject:(ARTJsonObject *)jsonObject; + // Others - (art_nullable NSDate *)decodeTime:(NSData *)data; - (art_nullable NSError *)decodeError:(NSData *)error; diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index adff5dd1d..9bad83482 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -23,6 +23,8 @@ #import "ARTStatus.h" #import "ARTTokenDetails.h" #import "ARTTokenRequest.h" +#import "ARTPush.h" +#import "ARTPushRecipient.h" #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" #import "ARTConnectionDetails.h" @@ -149,6 +151,14 @@ - (NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails { return [self encode:[self devicePushDetailsToDictionary:devicePushDetails]]; } +- (NSData *)encodePushRecipient:(id)pushRecipient withJsonObject:(ARTJsonObject *)jsonObject { + NSDictionary *object = @{ + @"recipient": pushRecipient.recipient, + @"push": jsonObject, + }; + return [self encode:object]; +} + - (NSDate *)decodeTime:(NSData *)data { NSArray *resp = [self decodeArray:data]; [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: decodeTime %@", _rest, [_delegate formatAsString], resp]; diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 3eae6ee76..2639ef822 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -24,6 +24,7 @@ @interface ARTUpdateToken : NSString @end + #pragma mark ARTPushNotifications interface #ifdef TARGET_OS_IPHONE @@ -50,7 +51,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init:(ARTRest *)rest; /// Publish a push notification. -- (void)publish:(NSDictionary *)params jsonObject:(ARTJsonObject *)jsonObject; +- (void)publish:(id)recipient jsonObject:(ARTJsonObject *)jsonObject; #ifdef TARGET_OS_IPHONE /// Register a device, including the information necessary to deliver push notifications to it. diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 184444911..1d4f07a51 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -57,8 +57,24 @@ - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [_logger error:@"ARTPush: device token not received (%@)", [error localizedDescription]]; } -- (void)publish:(NSDictionary *)params jsonObject:(ARTJsonObject *)jsonObject { +- (void)publish:(id)recipient jsonObject:(ARTJsonObject *)jsonObject { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/publish"]]; + request.HTTPMethod = @"POST"; + request.HTTPBody = [_jsonEncoder encodePushRecipient:recipient withJsonObject:jsonObject]; + [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + [_logger debug:__FILE__ line:__LINE__ message:@"ARTPush: push notification to a single device %@", request]; + [_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + return; + } + if (error) { + [_logger error:@"ARTPush: push notification to a single device failed (%@)", error.localizedDescription]; + } + else { + [_logger error:@"ARTPush: push notification to a single device failed with unknown error"]; + } + }]; } - (void)activate { @@ -101,7 +117,7 @@ - (void)deactivate:(ARTDeviceId *)deviceId { } - (void)deactivate:(ARTDeviceId *)deviceId deregisterCallback:(void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))deregisterCallback { - + // TODO } - (void)newDevice:(ARTDeviceDetails *)deviceDetails { diff --git a/Source/ARTPushRecipient.h b/Source/ARTPushRecipient.h new file mode 100644 index 000000000..93ebda984 --- /dev/null +++ b/Source/ARTPushRecipient.h @@ -0,0 +1,47 @@ +// +// ARTPushRecipient.h +// Ably +// +// Created by Ricardo Pereira on 13/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol ARTPushRecipient +@property (nonatomic, readonly) NSDictionary *recipient; +@end + +// ClientId +@interface ARTPushRecipientClientId : NSObject +@property (nonatomic) NSString *clientId; +@end + +// DeviceId +@interface ARTPushRecipientDeviceId : NSObject +@property (nonatomic) NSString *deviceId; +@end + +// APNs +@interface ARTPushRecipientAPNDevice : NSObject +@property (nonatomic) NSString *deviceToken; +@end + +// GCM +@interface ARTPushRecipientGCMDevice : NSObject +@property (nonatomic) NSString *registrationToken; +@end + +// FCM +@interface ARTPushRecipientFCMDevice : ARTPushRecipientGCMDevice +@end + +// Web +@interface ARTPushRecipientWebDevice : NSObject +@property (nonatomic) NSString *targetURL; +@property (nonatomic) NSString *encryptionKey; +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushRecipient.m b/Source/ARTPushRecipient.m new file mode 100644 index 000000000..ff29e9bd4 --- /dev/null +++ b/Source/ARTPushRecipient.m @@ -0,0 +1,76 @@ +// +// ARTPushRecipient.m +// Ably +// +// Created by Ricardo Pereira on 13/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTPushRecipient.h" + +#pragma mark ClientId +@implementation ARTPushRecipientClientId + +- (NSDictionary *)recipient { + return @{ @"clientId": self.clientId }; +} + +@end + +#pragma mark DeviceId +@implementation ARTPushRecipientDeviceId + +- (NSDictionary *)recipient { + return @{ @"deviceId": self.deviceId }; +} + +@end + +#pragma mark APNs +@implementation ARTPushRecipientAPNDevice + +- (NSDictionary *)recipient { + return @{ + @"transportType": @"apns", + @"deviceToken": self.deviceToken, + }; +} + +@end + +#pragma mark GCM +@implementation ARTPushRecipientGCMDevice + +- (NSDictionary *)recipient { + return @{ + @"transportType": @"gcm", + @"registrationToken": self.registrationToken, + }; +} + +@end + +#pragma mark FCM +@implementation ARTPushRecipientFCMDevice + +- (NSDictionary *)recipient { + return @{ + @"transportType": @"fcm", + @"registrationToken": self.registrationToken, + }; +} + +@end + +#pragma mark Web +@implementation ARTPushRecipientWebDevice + +- (NSDictionary *)recipient { + return @{ + @"transportType": @"web", + @"targetUrl": self.targetURL, + @"encryptionKey": self.encryptionKey, + }; +} + +@end From c956ba97fa742ea5e37182524b64fce0d917f9ee Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Mon, 13 Feb 2017 14:42:20 +0000 Subject: [PATCH 09/46] fixup! Push.publish --- Ably.xcodeproj/project.pbxproj | 2 +- Source/ARTPush.h | 2 ++ Source/Ably.h | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 2ae54af2b..101676f77 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -128,7 +128,7 @@ D7588AF41BFF91B800BB8279 /* ARTURLSessionServerTrust.m in Sources */ = {isa = PBXBuildFile; fileRef = D7588AF21BFF91B800BB8279 /* ARTURLSessionServerTrust.m */; }; D75A3F1B1DDE5B62002A4AAD /* ARTGCD.h in Headers */ = {isa = PBXBuildFile; fileRef = D75A3F191DDE5B62002A4AAD /* ARTGCD.h */; settings = {ATTRIBUTES = (Public, ); }; }; D75A3F1C1DDE5B62002A4AAD /* ARTGCD.m in Sources */ = {isa = PBXBuildFile; fileRef = D75A3F1A1DDE5B62002A4AAD /* ARTGCD.m */; }; - D7684CFA1E51D20100F3B07F /* ARTPushRecipient.h in Headers */ = {isa = PBXBuildFile; fileRef = D7684CF81E51D20100F3B07F /* ARTPushRecipient.h */; }; + D7684CFA1E51D20100F3B07F /* ARTPushRecipient.h in Headers */ = {isa = PBXBuildFile; fileRef = D7684CF81E51D20100F3B07F /* ARTPushRecipient.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7684CFB1E51D20100F3B07F /* ARTPushRecipient.m in Sources */ = {isa = PBXBuildFile; fileRef = D7684CF91E51D20100F3B07F /* ARTPushRecipient.m */; }; D768C6AC1E4B5B0200436011 /* ARTDevicePushDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */; settings = {ATTRIBUTES = (Public, ); }; }; D768C6AD1E4B5B0200436011 /* ARTDevicePushDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */; }; diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 2639ef822..0b47690f6 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -12,6 +12,8 @@ @class ARTRest; @class ARTDeviceDetails; +@protocol ARTPushRecipient; + @interface ARTJsonObject : NSDictionary @end diff --git a/Source/Ably.h b/Source/Ably.h index abd192ecf..d8d9ea473 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -59,6 +59,7 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import "ARTGCD.h" #import "ARTPush.h" #import "ARTPushChannel.h" +#import "ARTPushRecipient.h" #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" From 581e76f4d704b6fc69eaeb1ab788d3cccaf15f18 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Mon, 13 Feb 2017 17:08:36 +0000 Subject: [PATCH 10/46] PushChannel subscribe and unsubscribe --- Source/ARTEncoder.h | 6 ++- Source/ARTHttp.h | 16 +++--- Source/ARTHttp.m | 66 +++-------------------- Source/ARTJsonLikeEncoder.m | 2 +- Source/ARTPush.h | 18 +++---- Source/ARTPush.m | 26 +++++----- Source/ARTPushChannel.h | 16 +++++- Source/ARTPushChannel.m | 101 ++++++++++++++++++++++++++++++++++-- Source/ARTRealtimeChannel.h | 5 +- Source/ARTRealtimeChannel.m | 11 +++- Source/ARTRest+Private.h | 11 +--- Source/ARTRest.m | 6 ++- Source/ARTRestChannel.h | 4 +- Source/ARTRestChannel.m | 19 +++++-- Source/ARTTypes.h | 2 + Spec/TestUtilities.swift | 2 +- 16 files changed, 191 insertions(+), 120 deletions(-) diff --git a/Source/ARTEncoder.h b/Source/ARTEncoder.h index 16c04f3c1..5b918e413 100644 --- a/Source/ARTEncoder.h +++ b/Source/ARTEncoder.h @@ -8,6 +8,7 @@ #import #import "CompatibilityMacros.h" +#import "ARTTypes.h" @class ARTMessage; @class ARTPresenceMessage; @@ -16,7 +17,6 @@ @class ARTTokenRequest; @class ARTDeviceDetails; @class ARTDevicePushDetails; -@class ARTJsonObject; @protocol ARTPushRecipient; @@ -33,6 +33,10 @@ ART_ASSUME_NONNULL_BEGIN - (ARTEncoderFormat)format; - (NSString *)formatAsString; +- (id)decode:(NSData *)data; +- (NSData *)encode:(id)obj; + + // TokenRequest - (art_nullable NSData *)encodeTokenRequest:(ARTTokenRequest *)request; - (art_nullable ARTTokenRequest *)decodeTokenRequest:(NSData *)data error:(NSError * __autoreleasing *)error; diff --git a/Source/ARTHttp.h b/Source/ARTHttp.h index a87dfea67..05552d6ac 100644 --- a/Source/ARTHttp.h +++ b/Source/ARTHttp.h @@ -16,12 +16,19 @@ ART_ASSUME_NONNULL_BEGIN @protocol ARTHTTPExecutor -@property (nonatomic, weak) ARTLog *logger; +@property (nonatomic) ARTLog *logger; - (void)executeRequest:(NSMutableURLRequest *)request completion:(art_nullable void (^)(NSHTTPURLResponse *__art_nullable, NSData *__art_nullable, NSError *__art_nullable))callback; @end +@protocol ARTHTTPAuthenticatedExecutor + +- (void)executeRequest:(NSMutableURLRequest *)request withAuthOption:(ARTAuthentication)authOption completion:(void (^)(NSHTTPURLResponse *__art_nullable, NSData *__art_nullable, NSError *__art_nullable))callback; + +@end + + @interface ARTHttpRequest : NSObject @property (readonly, strong, nonatomic) NSString *method; @@ -55,13 +62,6 @@ ART_ASSUME_NONNULL_BEGIN @interface ARTHttp : NSObject -@property (nonatomic, weak) ARTLog *logger; - -- (instancetype)init; - -- (id)makeRequestWithMethod:(NSString *)method url:(NSURL *)url headers:(art_nullable NSDictionary *)headers body:(art_nullable NSData *)body callback:(void (^)(ARTHttpResponse *))cb; - - @end ART_ASSUME_NONNULL_END diff --git a/Source/ARTHttp.m b/Source/ARTHttp.m index 4c0700c8d..ad2846f9c 100644 --- a/Source/ARTHttp.m +++ b/Source/ARTHttp.m @@ -16,6 +16,9 @@ @interface ARTHttp () @end + +#pragma mark - ARTHttpRequestHandle + @interface ARTHttpRequestHandle : NSObject @property (readonly, nonatomic, strong) NSURLSessionDataTask *dataTask; @@ -150,9 +153,12 @@ - (void)cancel { #pragma mark - ARTHttp @implementation ARTHttp { + ARTLog *_logger; _Nullable dispatch_queue_t _queue; } +@synthesize logger = _logger; + - (instancetype)init { self = [super init]; if (self) { @@ -194,64 +200,4 @@ - (void)executeRequest:(NSMutableURLRequest *)request completion:(void (^)(NSHTT }]; } -- (id)makeRequestWithMethod:(NSString *)method url:(NSURL *)url headers:(NSDictionary *)headers body:(NSData *)body callback:(void (^)(ARTHttpResponse *))cb { - return [self makeRequest:[[ARTHttpRequest alloc] initWithMethod:method url:url headers:headers body:body] callback:cb]; -} - -- (id)makeRequest:(ARTHttpRequest *)artRequest callback:(void (^)(ARTHttpResponse *))cb { - - if(![artRequest.method isEqualToString:@"GET"] && ![artRequest.method isEqualToString:@"POST"]){ - [NSException raise:@"Http method must be GET or POST" format:@""]; - } - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:artRequest.url]; - request.HTTPMethod = artRequest.method; - [self.logger verbose:@"ARTHttp request URL is %@", artRequest.url]; - - for (NSString *headerName in artRequest.headers) { - NSString *headerValue = [artRequest.headers objectForKey:headerName]; - [request setValue:headerValue forHTTPHeaderField:headerName]; - } - - request.HTTPBody = artRequest.body; - [self.logger debug:@"ARTHttp: makeRequest %@", [request allHTTPHeaderFields]]; - - [self.urlSession get:request completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - [self.logger verbose:@"ARTHttp: Got response %@, err %@", response, error]; - - if(error) { - [self.logger error:@"ARTHttp receieved error: %@", error]; - cb([ARTHttpResponse responseWithStatus:500 headers:nil body:nil]); - } - else { - if (httpResponse) { - int status = (int)httpResponse.statusCode; - [self.logger debug:@"ARTHttp response status is %d", status]; - [self.logger verbose:@"ARTHttp received response %@",[NSJSONSerialization JSONObjectWithData:data options:0 error:nil]]; - - dispatch_async(_queue, ^{ - cb([ARTHttpResponse responseWithStatus:status headers:httpResponse.allHeaderFields body:data]); - }); - } else { - dispatch_async(_queue, ^{ - cb([ARTHttpResponse response]); - }); - } - } - }]; - return nil; -} - -+ (NSDictionary *)getErrorDictionary { - NSString * path =[[[[NSBundle bundleForClass: [self class]] resourcePath] stringByAppendingString:@"/"] stringByAppendingString:@"ably-common/protocol/errors.json"]; - NSString * errorsString =[NSString stringWithContentsOfFile:path - encoding:NSUTF8StringEncoding - error:NULL]; - NSData *data = [errorsString dataUsingEncoding:NSUTF8StringEncoding]; - NSError * error; - NSDictionary * topLevel =[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]; - return topLevel; -} - @end diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index 9bad83482..08b40d38a 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -520,7 +520,7 @@ - (ARTDeviceDetails *)deviceDetailsFromDictionary:(NSDictionary *)input error:(N } ARTDeviceDetails *deviceDetails = [[ARTDeviceDetails alloc] initWithId:[input artString:@"id"]]; - deviceDetails.updateToken = (ARTUpdateToken *)[input artString:@"updateToken"]; + deviceDetails.updateToken = [input artString:@"updateToken"]; deviceDetails.push.state = ARTDevicePushStateFromStr([input artString:@"state"]); return deviceDetails; diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 0b47690f6..8963d8e42 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -12,19 +12,13 @@ @class ARTRest; @class ARTDeviceDetails; +@protocol ARTHTTPAuthenticatedExecutor; @protocol ARTPushRecipient; -@interface ARTJsonObject : NSDictionary -@end - -@interface ARTDeviceId : NSString -@end - -@interface ARTDeviceToken : NSData -@end - -@interface ARTUpdateToken : NSString -@end +// More context +typedef NSString ARTDeviceId; +typedef NSData ARTDeviceToken; +typedef NSString ARTUpdateToken; #pragma mark ARTPushNotifications interface @@ -50,7 +44,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) ARTDeviceDetails *device; - (instancetype)init NS_UNAVAILABLE; -- (instancetype)init:(ARTRest *)rest; +- (instancetype)init:(id)httpExecutor; /// Publish a push notification. - (void)publish:(id)recipient jsonObject:(ARTJsonObject *)jsonObject; diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 1d4f07a51..12aace899 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -28,16 +28,16 @@ @interface ARTPush () @end @implementation ARTPush { - __weak ARTRest *_rest; + id _httpExecutor; __weak ARTLog *_logger; id _jsonEncoder; ARTEventEmitter *_deviceTokenEmitter; } -- (instancetype)init:(ARTRest *)rest { +- (instancetype)init:(id)httpExecutor { if (self = [super init]) { - _rest = rest; - _logger = rest.logger; + _httpExecutor = httpExecutor; + _logger = [httpExecutor logger]; _device = [ARTDeviceDetails fromLocalDevice]; _state = ARTPushStateDeactivated; _deviceTokenEmitter = [[ARTEventEmitter alloc] init]; @@ -50,7 +50,7 @@ - (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [_logger info:@"ARTPush: device token received and stored"]; [[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:ARTDeviceTokenKey]; [[NSUserDefaults standardUserDefaults] synchronize]; - [_deviceTokenEmitter emit:[NSNull null] with:(ARTDeviceToken *)deviceToken]; + [_deviceTokenEmitter emit:[NSNull null] with:deviceToken]; } - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { @@ -63,16 +63,16 @@ - (void)publish:(id)recipient jsonObject:(ARTJsonObject *)json request.HTTPBody = [_jsonEncoder encodePushRecipient:recipient withJsonObject:jsonObject]; [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; - [_logger debug:__FILE__ line:__LINE__ message:@"ARTPush: push notification to a single device %@", request]; - [_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + [_logger debug:__FILE__ line:__LINE__ message:@"push notification to a single device %@", request]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (response.statusCode == 200 /*OK*/) { return; } if (error) { - [_logger error:@"ARTPush: push notification to a single device failed (%@)", error.localizedDescription]; + [_logger error:@"%@: push notification to a single device failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; } else { - [_logger error:@"ARTPush: push notification to a single device failed with unknown error"]; + [_logger error:@"%@: push notification to a single device failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; } }]; } @@ -126,17 +126,17 @@ - (void)newDevice:(ARTDeviceDetails *)deviceDetails { request.HTTPBody = [_jsonEncoder encodeDeviceDetails:deviceDetails]; [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; - [_logger debug:__FILE__ line:__LINE__ message:@"ARTPush: device registration with request %@", request]; - [_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + [_logger debug:__FILE__ line:__LINE__ message:@"device registration with request %@", request]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (response.statusCode == 201 /*Created*/) { ARTDeviceDetails *deviceDetails = [_jsonEncoder decodeDeviceDetails:data]; self.device.updateToken = deviceDetails.updateToken; } else if (error) { - [_logger error:@"ARTPush: device registration failed (%@)", error.localizedDescription]; + [_logger error:@"%@: device registration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; } else { - [_logger error:@"ARTPush: device registration failed with unknown error"]; + [_logger error:@"%@: device registration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; } }]; } diff --git a/Source/ARTPushChannel.h b/Source/ARTPushChannel.h index 28d5e2ae5..ef6ffbf31 100644 --- a/Source/ARTPushChannel.h +++ b/Source/ARTPushChannel.h @@ -7,13 +7,25 @@ // #import +#import "ARTPush.h" + +@class ARTChannel; + +@protocol ARTHTTPAuthenticatedExecutor; + +NS_ASSUME_NONNULL_BEGIN @interface ARTPushChannel : NSObject -- (void)subscribeForDevice:(NSString *)device; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)init:(id)httpExecutor withChannel:(ARTChannel *)channel; + +- (void)subscribeForDevice:(ARTDeviceId *)deviceId; - (void)subscribeForClientId:(NSString *)clientId; -- (void)unsubscribeForDevice:(NSString *)device; +- (void)unsubscribeForDevice:(ARTDeviceId *)deviceId; - (void)unsubscribeForClientId:(NSString *)clientId; @end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushChannel.m b/Source/ARTPushChannel.m index cf4a047e3..454b4ff3f 100644 --- a/Source/ARTPushChannel.m +++ b/Source/ARTPushChannel.m @@ -7,23 +7,118 @@ // #import "ARTPushChannel.h" +#import "ARTHttp.h" +#import "ARTLog.h" +#import "ARTChannel.h" +#import "ARTJsonLikeEncoder.h" -@implementation ARTPushChannel +@implementation ARTPushChannel { + id _httpExecutor; + __weak ARTLog *_logger; + __weak ARTChannel *_channel; + id _jsonEncoder; +} + +- (instancetype)init:(id)httpExecutor withChannel:(ARTChannel *)channel { + if (self == [super self]) { + _httpExecutor = httpExecutor; + _logger = [httpExecutor logger]; + _channel = channel; + _jsonEncoder = [[ARTJsonLikeEncoder alloc] init]; + } + return self; +} -- (void)subscribeForDevice:(NSString *)device { +- (void)subscribeForDevice:(ARTDeviceId *)deviceId { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; + request.HTTPMethod = @"POST"; + request.HTTPBody = [_jsonEncoder encode:@{ + @"deviceId": deviceId, + @"channel": _channel.name, + }]; + [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + [_logger debug:__FILE__ line:__LINE__ message:@"subscribe notifications for device %@ in channel %@", deviceId, _channel.name]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + return; + } + if (error) { + [_logger error:@"%@: subscribe notifications for device %@ in channel %@ failed (%@)", NSStringFromClass(self.class), deviceId, _channel.name, error.localizedDescription]; + } + else { + [_logger error:@"%@: subscribe notifications for device %@ in channel %@ failed with status code %ld", NSStringFromClass(self.class), deviceId, _channel.name, (long)response.statusCode]; + } + }]; } - (void)subscribeForClientId:(NSString *)clientId { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; + request.HTTPMethod = @"POST"; + request.HTTPBody = [_jsonEncoder encode:@{ + @"clientId": clientId, + @"channel": _channel.name, + }]; + [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + [_logger debug:__FILE__ line:__LINE__ message:@"subscribe notifications for clientId %@ in channel %@", clientId, _channel.name]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + return; + } + if (error) { + [_logger error:@"%@: subscribe notifications for clientId %@ in channel %@ failed (%@)", NSStringFromClass(self.class), clientId, _channel.name, error.localizedDescription]; + } + else { + [_logger error:@"%@: subscribe notifications for clientId %@ in channel %@ failed with status code %ld", NSStringFromClass(self.class), clientId, _channel.name, (long)response.statusCode]; + } + }]; } -- (void)unsubscribeForDevice:(NSString *)device { +- (void)unsubscribeForDevice:(NSString *)deviceId { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; + request.HTTPMethod = @"DELETE"; + request.HTTPBody = [_jsonEncoder encode:@{ + @"deviceId": deviceId, + @"channel": _channel.name, + }]; + [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + [_logger debug:__FILE__ line:__LINE__ message:@"unsubscribe notifications for device %@ in channel %@", deviceId, _channel.name]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + return; + } + if (error) { + [_logger error:@"%@: unsubscribe notifications for device %@ in channel %@ failed (%@)", NSStringFromClass(self.class), deviceId, _channel.name, error.localizedDescription]; + } + else { + [_logger error:@"%@: unsubscribe notifications for device %@ in channel %@ failed with status code %ld", NSStringFromClass(self.class), deviceId, _channel.name, (long)response.statusCode]; + } + }]; } - (void)unsubscribeForClientId:(NSString *)clientId { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; + request.HTTPMethod = @"DELETE"; + request.HTTPBody = [_jsonEncoder encode:@{ + @"clientId": clientId, + @"channel": _channel.name, + }]; + [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + [_logger debug:__FILE__ line:__LINE__ message:@"unsubscribe notifications for clientId %@ in channel %@", clientId, _channel.name]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + return; + } + if (error) { + [_logger error:@"%@: unsubscribe notifications for clientId %@ in channel %@ failed (%@)", NSStringFromClass(self.class), clientId, _channel.name, error.localizedDescription]; + } + else { + [_logger error:@"%@: unsubscribe notifications for clientId %@ in channel %@ failed with status code %ld", NSStringFromClass(self.class), clientId, _channel.name, (long)response.statusCode]; + } + }]; } @end diff --git a/Source/ARTRealtimeChannel.h b/Source/ARTRealtimeChannel.h index 2b20f4446..ffac9631b 100644 --- a/Source/ARTRealtimeChannel.h +++ b/Source/ARTRealtimeChannel.h @@ -18,12 +18,15 @@ ART_ASSUME_NONNULL_BEGIN @class ARTRealtimePresence; +@class ARTPushChannel; @interface ARTRealtimeChannel : ARTChannel @property (readwrite, assign, nonatomic) ARTRealtimeChannelState state; @property (readonly, strong, nonatomic, art_nullable) ARTErrorInfo *errorReason; -@property (readonly, getter=getPresence) ARTRealtimePresence *presence; + +@property (readonly) ARTRealtimePresence *presence; +@property (readonly) ARTPushChannel *push; - (void)attach; - (void)attach:(art_nullable void (^)(ARTErrorInfo *__art_nullable))callback; diff --git a/Source/ARTRealtimeChannel.m b/Source/ARTRealtimeChannel.m index 256e87e82..71d69184e 100644 --- a/Source/ARTRealtimeChannel.m +++ b/Source/ARTRealtimeChannel.m @@ -26,9 +26,11 @@ #import "ARTDefault.h" #import "ARTRest.h" #import "ARTClientOptions.h" +#import "ARTPushChannel.h" @interface ARTRealtimeChannel () { ARTRealtimePresence *_realtimePresence; + ARTPushChannel *_pushChannel; CFRunLoopTimerRef _attachTimer; CFRunLoopTimerRef _detachTimer; __GENERIC(ARTEventEmitter, NSNull *, ARTErrorInfo *) *_attachedEventEmitter; @@ -65,13 +67,20 @@ + (instancetype)channelWithRealtime:(ARTRealtime *)realtime andName:(NSString *) return [[ARTRealtimeChannel alloc] initWithRealtime:realtime andName:name withOptions:options]; } -- (ARTRealtimePresence *)getPresence { +- (ARTRealtimePresence *)presence { if (!_realtimePresence) { _realtimePresence = [[ARTRealtimePresence alloc] initWithChannel:self]; } return _realtimePresence; } +- (ARTPushChannel *)push { + if (!_pushChannel) { + _pushChannel = [[ARTPushChannel alloc] init:self.realtime.rest withChannel:self]; + } + return _pushChannel; +} + - (void)internalPostMessages:(id)data callback:(void (^)(ARTErrorInfo *__art_nullable error))callback { ARTProtocolMessage *msg = [[ARTProtocolMessage alloc] init]; msg.action = ARTProtocolMessageMessage; diff --git a/Source/ARTRest+Private.h b/Source/ARTRest+Private.h index be2f284ab..89403128d 100644 --- a/Source/ARTRest+Private.h +++ b/Source/ARTRest+Private.h @@ -15,7 +15,7 @@ ART_ASSUME_NONNULL_BEGIN /// ARTRest private methods that are used internally and for whitebox testing -@interface ARTRest () +@interface ARTRest () @property (nonatomic, strong, readonly) ARTClientOptions *options; @property (readonly, strong, nonatomic) __GENERIC(id, ARTEncoder) defaultEncoder; @@ -27,21 +27,12 @@ ART_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, getter=getBaseUrl) NSURL *baseUrl; -@property (nonatomic, strong, readonly) ARTLog *logger; - -// MARK: Not accessible by tests @property (readonly, strong, nonatomic) ARTHttp *http; @property (strong, nonatomic) ARTAuth *auth; @property (readwrite, assign, nonatomic) int fallbackCount; -// MARK: ARTHTTPExecutor - -- (void)executeRequest:(NSMutableURLRequest *)request completion:(void (^)(NSHTTPURLResponse *__art_nullable, NSData *__art_nullable, NSError *__art_nullable))callback; - // MARK: Internal -- (void)executeRequest:(NSMutableURLRequest *)request withAuthOption:(ARTAuthentication)authOption completion:(void (^)(NSHTTPURLResponse *__art_nullable, NSData *__art_nullable, NSError *__art_nullable))callback; - - (void)prepareAuthorisationHeader:(ARTAuthMethod)method completion:(void (^)(NSString *__art_nonnull authorization, NSError *__art_nullable error))callback; - (id)internetIsUp:(void (^)(BOOL isUp))cb; diff --git a/Source/ARTRest.m b/Source/ARTRest.m index a07e23468..2df3e0430 100644 --- a/Source/ARTRest.m +++ b/Source/ARTRest.m @@ -36,7 +36,11 @@ #import "ARTGCD.h" #import "ARTPush.h" -@implementation ARTRest +@implementation ARTRest { + ARTLog *_logger; +} + +@synthesize logger = _logger; - (instancetype)initWithOptions:(ARTClientOptions *)options { self = [super init]; diff --git a/Source/ARTRestChannel.h b/Source/ARTRestChannel.h index 713a45a4f..0c5cadf4c 100644 --- a/Source/ARTRestChannel.h +++ b/Source/ARTRestChannel.h @@ -12,12 +12,14 @@ @class ARTRest; @class ARTRestPresence; +@class ARTPushChannel; ART_ASSUME_NONNULL_BEGIN @interface ARTRestChannel : ARTChannel -@property (readonly, getter=getPresence) ARTRestPresence *presence; +@property (readonly) ARTRestPresence *presence; +@property (readonly) ARTPushChannel *push; - (BOOL)history:(art_nullable ARTDataQuery *)query callback:(void(^)(__GENERIC(ARTPaginatedResult, ARTMessage *) *__art_nullable result, ARTErrorInfo *__art_nullable error))callback error:(NSError *__art_nullable *__art_nullable)errorPtr; diff --git a/Source/ARTRestChannel.m b/Source/ARTRestChannel.m index a4d67b66a..a6fb7371e 100644 --- a/Source/ARTRestChannel.m +++ b/Source/ARTRestChannel.m @@ -20,10 +20,12 @@ #import "ARTAuth.h" #import "ARTTokenDetails.h" #import "ARTNSArray+ARTFunctional.h" +#import "ARTPushChannel.h" @implementation ARTRestChannel { @private - ARTRestPresence *_restPresence; + ARTRestPresence *_presence; + ARTPushChannel *_pushChannel; @public NSString *_basePath; } @@ -45,11 +47,18 @@ - (NSString *)getBasePath { return _basePath; } -- (ARTRestPresence *)getPresence { - if (!_restPresence) { - _restPresence = [[ARTRestPresence alloc] initWithChannel:self]; +- (ARTRestPresence *)presence { + if (!_presence) { + _presence = [[ARTRestPresence alloc] initWithChannel:self]; } - return _restPresence; + return _presence; +} + +- (ARTPushChannel *)push { + if (!_pushChannel) { + _pushChannel = [[ARTPushChannel alloc] init:self.rest withChannel:self]; + } + return _pushChannel; } - (void)history:(void (^)(__GENERIC(ARTPaginatedResult, ARTMessage *) *, ARTErrorInfo *))callback { diff --git a/Source/ARTTypes.h b/Source/ARTTypes.h index e416d4086..f0c19e835 100644 --- a/Source/ARTTypes.h +++ b/Source/ARTTypes.h @@ -21,6 +21,8 @@ @class __GENERIC(ARTPaginatedResult, ItemType); @class ARTStats; +typedef NSDictionary ARTJsonObject; + typedef NS_ENUM(NSUInteger, ARTAuthentication) { ARTAuthenticationOff, ARTAuthenticationOn, diff --git a/Spec/TestUtilities.swift b/Spec/TestUtilities.swift index b2b85d309..53fea224b 100644 --- a/Spec/TestUtilities.swift +++ b/Spec/TestUtilities.swift @@ -578,7 +578,7 @@ class MockHTTP: ARTHttp { class TestProxyHTTPExecutor: NSObject, ARTHTTPExecutor { var http: ARTHttp? = ARTHttp() - var logger: ARTLog? + var logger: ARTLog! var requests: [NSMutableURLRequest] = [] var responses: [NSHTTPURLResponse] = [] From 08f9d0186e3de39e3c8aa598dcfb7c941c2c6330 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 14 Feb 2017 10:16:39 +0000 Subject: [PATCH 11/46] Fix registerCallback --- Source/ARTPush.h | 3 +-- Source/ARTPush.m | 15 +++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 8963d8e42..f79adf51d 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -52,8 +52,7 @@ NS_ASSUME_NONNULL_BEGIN #ifdef TARGET_OS_IPHONE /// Register a device, including the information necessary to deliver push notifications to it. - (void)activate; -- (void)activate:(ARTDeviceDetails *)deviceDetails; -- (void)activate:(ARTDeviceDetails *)deviceDetails registerCallback:(nullable ARTUpdateToken* (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable))registerCallback; +- (void)activateWithRegisterCallback:(void (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable, void (^ _Nullable)(ARTUpdateToken * _Nullable, ARTErrorInfo * _Nullable)))registerCallback; /// Unregister a device. - (void)deactivate:(ARTDeviceId *)deviceId; - (void)deactivate:(ARTDeviceId *)deviceId deregisterCallback:(nullable void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))deregisterCallback; diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 12aace899..91e178f0c 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -81,11 +81,11 @@ - (void)activate { [self activate:self.device registerCallback:nil]; } -- (void)activate:(ARTDeviceDetails *)deviceDetails { - [self activate:deviceDetails registerCallback:nil]; +- (void)activateWithRegisterCallback:(void (^)(ARTDeviceDetails *, ARTErrorInfo *, void (^)(ARTUpdateToken *, ARTErrorInfo *)))registerCallback { + [self activate:self.device registerCallback:registerCallback]; } -- (void)activate:(ARTDeviceDetails *)deviceDetails registerCallback:(ARTUpdateToken* (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable))registerCallback { +- (void)activate:(ARTDeviceDetails *)deviceDetails registerCallback:(void (^)(ARTDeviceDetails *, ARTErrorInfo *, void (^)(ARTUpdateToken *, ARTErrorInfo *)))registerCallback { if (self.state == ARTPushStateActivated) { return; } @@ -100,7 +100,14 @@ - (void)activate:(ARTDeviceDetails *)deviceDetails registerCallback:(ARTUpdateTo } if (registerCallback) { - self.device.updateToken = registerCallback(deviceDetails, nil); + registerCallback(deviceDetails, nil, ^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { + if (updateToken) { + self.device.updateToken = updateToken; + } + if (error) { + [_logger error:@"%@: device registration using a `registerCallback` failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + } + }); return; } From 4d3b7720637f507490544554efbba8879808e1cb Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 14 Feb 2017 10:21:44 +0000 Subject: [PATCH 12/46] Remove warnings - unused methods --- Tests/ARTHttpTest.m | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/Tests/ARTHttpTest.m b/Tests/ARTHttpTest.m index b54b9216f..a0cd301da 100644 --- a/Tests/ARTHttpTest.m +++ b/Tests/ARTHttpTest.m @@ -30,25 +30,4 @@ - (void)tearDown { [super tearDown]; } -- (void)testPingGoogle { - __weak XCTestExpectation *expectation = [self expectationWithDescription:@"get"]; - - [self.http makeRequestWithMethod:@"GET" url:[NSURL URLWithString:@"http://www.google.com"] headers:nil body:nil callback:^(ARTHttpResponse *response) { - XCTAssertEqual(response.status, 200); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:10.0 handler:nil]; -} -- (void)testNonExistantPath { - __weak XCTestExpectation *expectation = [self expectationWithDescription:@"get"]; - - [self.http makeRequestWithMethod:@"GET" url:[NSURL URLWithString:@"http://rest.ably.io"] headers:nil body:nil callback:^(ARTHttpResponse *response) { - XCTAssertEqual(response.status, 404); - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:[ARTTestUtil timeout] handler:nil]; -} - @end From 64e6dab5c3aa455e55ff1f734390b58d33b50c33 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 14 Feb 2017 10:29:30 +0000 Subject: [PATCH 13/46] Fix FormFactor --- Source/ARTDeviceDetails.h | 12 ++---------- Source/ARTDeviceDetails.m | 19 +++++-------------- Source/ARTJsonLikeEncoder.m | 2 +- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/Source/ARTDeviceDetails.h b/Source/ARTDeviceDetails.h index d60ce1fc9..d4068a73b 100644 --- a/Source/ARTDeviceDetails.h +++ b/Source/ARTDeviceDetails.h @@ -14,22 +14,14 @@ NS_ASSUME_NONNULL_BEGIN extern NSString *const ARTDevicePlatform; - -typedef NS_ENUM(NSUInteger, ARTDeviceFormFactor) { - ARTDeviceFormFactorMobile, - ARTDeviceFormFactorTablet, - ARTDeviceFormFactorDesktop, - ARTDeviceFormFactorEmbedded -}; - -NSString *ARTDeviceFormFactorToStr(ARTDeviceFormFactor formFactor); +extern NSString *const ARTDeviceFormFactor; @interface ARTDeviceDetails : NSObject @property (nonatomic, readonly) NSString *id; @property (nullable, nonatomic) NSString *clientId; @property (nonatomic, readonly) NSString *platform; -@property (nonatomic, readonly) ARTDeviceFormFactor formFactor; +@property (nonatomic, readonly) NSString *formFactor; @property (nullable, nonatomic) NSDictionary *metadata; @property (nonatomic, readonly) ARTDevicePushDetails *push; @property (nullable, nonatomic) ARTUpdateToken *updateToken; diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index ceeb80d98..1d0e6b6e7 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -10,19 +10,7 @@ #import "ARTDevicePushDetails.h" NSString *const ARTDevicePlatform = @"ios"; - -NSString *ARTDeviceFormFactorToStr(ARTDeviceFormFactor formFactor) { - switch (formFactor) { - case ARTDeviceFormFactorMobile: - return @"mobile"; //0 - case ARTDeviceFormFactorTablet: - return @"tablet"; //1 - case ARTDeviceFormFactorDesktop: - return @"desktop"; //2 - case ARTDeviceFormFactorEmbedded: - return @"embedded"; //3 - } -} +NSString *const ARTDeviceFormFactor = @"mobile"; @implementation ARTDeviceDetails @@ -37,7 +25,6 @@ - (instancetype)init { - (instancetype)initWithId:(NSString *)id { if (self = [super init]) { _id = id; - _formFactor = ARTDeviceFormFactorMobile; _push = [[ARTDevicePushDetails alloc] init]; } return self; @@ -47,4 +34,8 @@ - (NSString *)platform { return ARTDevicePlatform; } +- (NSString *)formFactor { + return ARTDeviceFormFactor; +} + @end diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index 08b40d38a..3c6427019 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -501,7 +501,7 @@ - (NSDictionary *)deviceDetailsToDictionary:(ARTDeviceDetails *)deviceDetails { dictionary[@"id"] = deviceDetails.id; dictionary[@"platform"] = deviceDetails.platform; - dictionary[@"formFactor"] = ARTDeviceFormFactorToStr(deviceDetails.formFactor); + dictionary[@"formFactor"] = deviceDetails.formFactor; if (deviceDetails.clientId) { dictionary[@"cliendId"] = deviceDetails.clientId; From d67ba789275702e0479c12e6ee4e2f83dfabbe0c Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 14 Feb 2017 10:51:18 +0000 Subject: [PATCH 14/46] Fix: use device token as Hex string --- Source/ARTJsonLikeEncoder.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index 3c6427019..a2bd621b2 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -532,9 +532,9 @@ - (NSDictionary *)devicePushDetailsToDictionary:(ARTDevicePushDetails *)devicePu dictionary[@"transportType"] = devicePushDetails.transportType; if (devicePushDetails.deviceToken) { - // Normalizing token by removing symbols and spaces - NSString *token = [[[devicePushDetails.deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""]; - dictionary[@"metadata"] = @{ @"deviceToken": token, }; + dictionary[@"metadata"] = @{ + @"deviceToken": [devicePushDetails.deviceToken description], // HEX string + }; } return dictionary; From bd73596752d849c6d6d45fe475a1bd4698fd81a6 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 14 Feb 2017 11:03:29 +0000 Subject: [PATCH 15/46] Fix: should respect the default encoder - useBinaryProtocol --- Source/ARTHttp.h | 4 ++++ Source/ARTPush.m | 12 +++++------- Source/ARTPushChannel.m | 18 ++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Source/ARTHttp.h b/Source/ARTHttp.h index 05552d6ac..e94ef4d51 100644 --- a/Source/ARTHttp.h +++ b/Source/ARTHttp.h @@ -12,6 +12,8 @@ @class ARTErrorInfo; +@protocol ARTEncoder; + ART_ASSUME_NONNULL_BEGIN @protocol ARTHTTPExecutor @@ -24,6 +26,8 @@ ART_ASSUME_NONNULL_BEGIN @protocol ARTHTTPAuthenticatedExecutor +- (id)defaultEncoder; + - (void)executeRequest:(NSMutableURLRequest *)request withAuthOption:(ARTAuthentication)authOption completion:(void (^)(NSHTTPURLResponse *__art_nullable, NSData *__art_nullable, NSError *__art_nullable))callback; @end diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 91e178f0c..e4d8d8393 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -30,7 +30,6 @@ @interface ARTPush () @implementation ARTPush { id _httpExecutor; __weak ARTLog *_logger; - id _jsonEncoder; ARTEventEmitter *_deviceTokenEmitter; } @@ -41,7 +40,6 @@ - (instancetype)init:(id)httpExecutor { _device = [ARTDeviceDetails fromLocalDevice]; _state = ARTPushStateDeactivated; _deviceTokenEmitter = [[ARTEventEmitter alloc] init]; - _jsonEncoder = [[ARTJsonLikeEncoder alloc] init]; } return self; } @@ -60,8 +58,8 @@ - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - (void)publish:(id)recipient jsonObject:(ARTJsonObject *)jsonObject { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/publish"]]; request.HTTPMethod = @"POST"; - request.HTTPBody = [_jsonEncoder encodePushRecipient:recipient withJsonObject:jsonObject]; - [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + request.HTTPBody = [[_httpExecutor defaultEncoder] encodePushRecipient:recipient withJsonObject:jsonObject]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"push notification to a single device %@", request]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { @@ -130,13 +128,13 @@ - (void)deactivate:(ARTDeviceId *)deviceId deregisterCallback:(void (^)(ARTDevic - (void)newDevice:(ARTDeviceDetails *)deviceDetails { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"]]; request.HTTPMethod = @"POST"; - request.HTTPBody = [_jsonEncoder encodeDeviceDetails:deviceDetails]; - [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + request.HTTPBody = [[_httpExecutor defaultEncoder] encodeDeviceDetails:deviceDetails]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"device registration with request %@", request]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (response.statusCode == 201 /*Created*/) { - ARTDeviceDetails *deviceDetails = [_jsonEncoder decodeDeviceDetails:data]; + ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data]; self.device.updateToken = deviceDetails.updateToken; } else if (error) { diff --git a/Source/ARTPushChannel.m b/Source/ARTPushChannel.m index 454b4ff3f..af9764b13 100644 --- a/Source/ARTPushChannel.m +++ b/Source/ARTPushChannel.m @@ -16,7 +16,6 @@ @implementation ARTPushChannel { id _httpExecutor; __weak ARTLog *_logger; __weak ARTChannel *_channel; - id _jsonEncoder; } - (instancetype)init:(id)httpExecutor withChannel:(ARTChannel *)channel { @@ -24,7 +23,6 @@ - (instancetype)init:(id)httpExecutor withChannel: _httpExecutor = httpExecutor; _logger = [httpExecutor logger]; _channel = channel; - _jsonEncoder = [[ARTJsonLikeEncoder alloc] init]; } return self; } @@ -32,11 +30,11 @@ - (instancetype)init:(id)httpExecutor withChannel: - (void)subscribeForDevice:(ARTDeviceId *)deviceId { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; request.HTTPMethod = @"POST"; - request.HTTPBody = [_jsonEncoder encode:@{ + request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ @"deviceId": deviceId, @"channel": _channel.name, }]; - [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"subscribe notifications for device %@ in channel %@", deviceId, _channel.name]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { @@ -55,11 +53,11 @@ - (void)subscribeForDevice:(ARTDeviceId *)deviceId { - (void)subscribeForClientId:(NSString *)clientId { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; request.HTTPMethod = @"POST"; - request.HTTPBody = [_jsonEncoder encode:@{ + request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ @"clientId": clientId, @"channel": _channel.name, }]; - [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"subscribe notifications for clientId %@ in channel %@", clientId, _channel.name]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { @@ -78,11 +76,11 @@ - (void)subscribeForClientId:(NSString *)clientId { - (void)unsubscribeForDevice:(NSString *)deviceId { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; request.HTTPMethod = @"DELETE"; - request.HTTPBody = [_jsonEncoder encode:@{ + request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ @"deviceId": deviceId, @"channel": _channel.name, }]; - [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"unsubscribe notifications for device %@ in channel %@", deviceId, _channel.name]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { @@ -101,11 +99,11 @@ - (void)unsubscribeForDevice:(NSString *)deviceId { - (void)unsubscribeForClientId:(NSString *)clientId { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; request.HTTPMethod = @"DELETE"; - request.HTTPBody = [_jsonEncoder encode:@{ + request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ @"clientId": clientId, @"channel": _channel.name, }]; - [request setValue:[_jsonEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"unsubscribe notifications for clientId %@ in channel %@", clientId, _channel.name]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { From 58f7eab7c26b98d2a6d45272ee485704c44cac0c Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 14 Feb 2017 11:11:35 +0000 Subject: [PATCH 16/46] Remove ARTPushRecipient protocol and types --- Ably.xcodeproj/project.pbxproj | 8 -------- Source/ARTEncoder.h | 3 --- Source/ARTJsonLikeEncoder.m | 9 --------- Source/ARTPush.h | 4 ++-- Source/ARTPush.m | 7 +++++-- Source/Ably.h | 1 - 6 files changed, 7 insertions(+), 25 deletions(-) diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 101676f77..0b67b2bc6 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -128,8 +128,6 @@ D7588AF41BFF91B800BB8279 /* ARTURLSessionServerTrust.m in Sources */ = {isa = PBXBuildFile; fileRef = D7588AF21BFF91B800BB8279 /* ARTURLSessionServerTrust.m */; }; D75A3F1B1DDE5B62002A4AAD /* ARTGCD.h in Headers */ = {isa = PBXBuildFile; fileRef = D75A3F191DDE5B62002A4AAD /* ARTGCD.h */; settings = {ATTRIBUTES = (Public, ); }; }; D75A3F1C1DDE5B62002A4AAD /* ARTGCD.m in Sources */ = {isa = PBXBuildFile; fileRef = D75A3F1A1DDE5B62002A4AAD /* ARTGCD.m */; }; - D7684CFA1E51D20100F3B07F /* ARTPushRecipient.h in Headers */ = {isa = PBXBuildFile; fileRef = D7684CF81E51D20100F3B07F /* ARTPushRecipient.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D7684CFB1E51D20100F3B07F /* ARTPushRecipient.m in Sources */ = {isa = PBXBuildFile; fileRef = D7684CF91E51D20100F3B07F /* ARTPushRecipient.m */; }; D768C6AC1E4B5B0200436011 /* ARTDevicePushDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */; settings = {ATTRIBUTES = (Public, ); }; }; D768C6AD1E4B5B0200436011 /* ARTDevicePushDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */; }; D77394031C6F6FFE00F5478F /* ARTProtocolMessage+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D77394021C6F6FFE00F5478F /* ARTProtocolMessage+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -373,8 +371,6 @@ D7588AF21BFF91B800BB8279 /* ARTURLSessionServerTrust.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTURLSessionServerTrust.m; sourceTree = ""; }; D75A3F191DDE5B62002A4AAD /* ARTGCD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTGCD.h; sourceTree = ""; }; D75A3F1A1DDE5B62002A4AAD /* ARTGCD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTGCD.m; sourceTree = ""; }; - D7684CF81E51D20100F3B07F /* ARTPushRecipient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPushRecipient.h; sourceTree = ""; }; - D7684CF91E51D20100F3B07F /* ARTPushRecipient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushRecipient.m; sourceTree = ""; }; D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTDevicePushDetails.h; sourceTree = ""; }; D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTDevicePushDetails.m; sourceTree = ""; }; D77394021C6F6FFE00F5478F /* ARTProtocolMessage+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARTProtocolMessage+Private.h"; sourceTree = ""; }; @@ -794,8 +790,6 @@ children = ( D7B6218E1E4A6E0200684474 /* ARTPush.h */, D7B6218F1E4A6E0200684474 /* ARTPush.m */, - D7684CF81E51D20100F3B07F /* ARTPushRecipient.h */, - D7684CF91E51D20100F3B07F /* ARTPushRecipient.m */, D7B621961E4A762A00684474 /* ARTPushChannel.h */, D7B621971E4A762A00684474 /* ARTPushChannel.m */, D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */, @@ -885,7 +879,6 @@ 850BFB4C1B79323C009D0ADD /* ARTPaginatedResult.h in Headers */, D746AE1E1BBB5207003ECEF8 /* ARTDataQuery+Private.h in Headers */, D77394031C6F6FFE00F5478F /* ARTProtocolMessage+Private.h in Headers */, - D7684CFA1E51D20100F3B07F /* ARTPushRecipient.h in Headers */, D746AE2F1BBBE7D7003ECEF8 /* ARTPaginatedResult+Private.h in Headers */, D746AE431BBC5CD0003ECEF8 /* ARTRealtimeChannel+Private.h in Headers */, D746AE251BBB611C003ECEF8 /* ARTChannel+Private.h in Headers */, @@ -1226,7 +1219,6 @@ 850BFB4D1B79323C009D0ADD /* ARTPaginatedResult.m in Sources */, EB9C530D1CD7BFF300.8.557 /* ARTJsonLikeEncoder.m in Sources */, D746AE1F1BBB5207003ECEF8 /* ARTDataQuery.m in Sources */, - D7684CFB1E51D20100F3B07F /* ARTPushRecipient.m in Sources */, 961343D91A42E0B7006DC822 /* ARTClientOptions.m in Sources */, 96BF615F1A35C1C8004CF2B3 /* ARTTypes.m in Sources */, D7D8F82E1BC2C706009718F2 /* ARTTokenParams.m in Sources */, diff --git a/Source/ARTEncoder.h b/Source/ARTEncoder.h index 5b918e413..a61e65683 100644 --- a/Source/ARTEncoder.h +++ b/Source/ARTEncoder.h @@ -72,9 +72,6 @@ ART_ASSUME_NONNULL_BEGIN // DevicePushDetails - (art_nullable NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails; -// PushRecipient -- (art_nullable NSData *)encodePushRecipient:(id)recipient withJsonObject:(ARTJsonObject *)jsonObject; - // Others - (art_nullable NSDate *)decodeTime:(NSData *)data; - (art_nullable NSError *)decodeError:(NSData *)error; diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index a2bd621b2..88071084f 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -24,7 +24,6 @@ #import "ARTTokenDetails.h" #import "ARTTokenRequest.h" #import "ARTPush.h" -#import "ARTPushRecipient.h" #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" #import "ARTConnectionDetails.h" @@ -151,14 +150,6 @@ - (NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails { return [self encode:[self devicePushDetailsToDictionary:devicePushDetails]]; } -- (NSData *)encodePushRecipient:(id)pushRecipient withJsonObject:(ARTJsonObject *)jsonObject { - NSDictionary *object = @{ - @"recipient": pushRecipient.recipient, - @"push": jsonObject, - }; - return [self encode:object]; -} - - (NSDate *)decodeTime:(NSData *)data { NSArray *resp = [self decodeArray:data]; [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: decodeTime %@", _rest, [_delegate formatAsString], resp]; diff --git a/Source/ARTPush.h b/Source/ARTPush.h index f79adf51d..49c76ecd4 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -13,12 +13,12 @@ @class ARTDeviceDetails; @protocol ARTHTTPAuthenticatedExecutor; -@protocol ARTPushRecipient; // More context typedef NSString ARTDeviceId; typedef NSData ARTDeviceToken; typedef NSString ARTUpdateToken; +typedef ARTJsonObject ARTPushRecipient; #pragma mark ARTPushNotifications interface @@ -47,7 +47,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init:(id)httpExecutor; /// Publish a push notification. -- (void)publish:(id)recipient jsonObject:(ARTJsonObject *)jsonObject; +- (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject; #ifdef TARGET_OS_IPHONE /// Register a device, including the information necessary to deliver push notifications to it. diff --git a/Source/ARTPush.m b/Source/ARTPush.m index e4d8d8393..2eec8b8ae 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -55,10 +55,13 @@ - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [_logger error:@"ARTPush: device token not received (%@)", [error localizedDescription]]; } -- (void)publish:(id)recipient jsonObject:(ARTJsonObject *)jsonObject { +- (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/publish"]]; request.HTTPMethod = @"POST"; - request.HTTPBody = [[_httpExecutor defaultEncoder] encodePushRecipient:recipient withJsonObject:jsonObject]; + request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ + @"recipient": recipient, + @"push": jsonObject, + }]; [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"push notification to a single device %@", request]; diff --git a/Source/Ably.h b/Source/Ably.h index d8d9ea473..abd192ecf 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -59,7 +59,6 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import "ARTGCD.h" #import "ARTPush.h" #import "ARTPushChannel.h" -#import "ARTPushRecipient.h" #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" From e584d67839ebc6638b1dce6fe6d2edd90bd6f26e Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 15 Feb 2017 11:53:10 +0000 Subject: [PATCH 17/46] Add ULID pod --- Podfile | 1 + Podfile.lock | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Podfile b/Podfile index 46a9e0821..eefeafa62 100644 --- a/Podfile +++ b/Podfile @@ -5,6 +5,7 @@ podspec :path => 'Ably.podspec' def project_pods pod 'SocketRocket', '0.5.1' pod 'msgpack', '0.1.8' + pod 'ULID', :git => 'https://github.com/Whitesmith/ulid.git', :branch => 'master' end def test_pods diff --git a/Podfile.lock b/Podfile.lock index bb47b7e8e..235cba657 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -5,6 +5,7 @@ PODS: - Quick (0.9.3) - SocketRocket (0.5.1) - SwiftyJSON (2.4.0) + - ULID (0.1.0) DEPENDENCIES: - Aspects @@ -13,6 +14,17 @@ DEPENDENCIES: - Quick (= 0.9.3) - SocketRocket (= 0.5.1) - SwiftyJSON (= 2.4.0) + - ULID (from `https://github.com/Whitesmith/ulid.git`, branch `master`) + +EXTERNAL SOURCES: + ULID: + :branch: master + :git: https://github.com/Whitesmith/ulid.git + +CHECKOUT OPTIONS: + ULID: + :commit: c7b4dfec8bc98c2978787bd9ed32a44fcb1c5dbb + :git: https://github.com/Whitesmith/ulid.git SPEC CHECKSUMS: Aspects: 7595ba96a6727a58ebcbfc954497fc5d2fdde546 @@ -21,7 +33,8 @@ SPEC CHECKSUMS: Quick: 13a2a2b19a5d8e3ed4fd0c36ee46597fd77ebf71 SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531 SwiftyJSON: 96918c1bf505efa50c4f72957018dd3452090c9c + ULID: ea870a817f38f3d61355fe77850dba580af87b62 -PODFILE CHECKSUM: c59fef6e348f90fbaa529d13373c2958f7b9b642 +PODFILE CHECKSUM: d202fb38800e6408aace2b44dd5d42ef9c8b1a8e COCOAPODS: 1.1.1 From 2a015d9b40af4ced9e067e0b4e36c28002021c41 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 15 Feb 2017 11:53:48 +0000 Subject: [PATCH 18/46] Use ULID as DeviceId --- Source/ARTDeviceDetails.h | 4 ++-- Source/ARTDeviceDetails.m | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Source/ARTDeviceDetails.h b/Source/ARTDeviceDetails.h index d4068a73b..f8eb04f5e 100644 --- a/Source/ARTDeviceDetails.h +++ b/Source/ARTDeviceDetails.h @@ -18,7 +18,7 @@ extern NSString *const ARTDeviceFormFactor; @interface ARTDeviceDetails : NSObject -@property (nonatomic, readonly) NSString *id; +@property (nonatomic, readonly) ARTDeviceId *id; @property (nullable, nonatomic) NSString *clientId; @property (nonatomic, readonly) NSString *platform; @property (nonatomic, readonly) NSString *formFactor; @@ -26,7 +26,7 @@ extern NSString *const ARTDeviceFormFactor; @property (nonatomic, readonly) ARTDevicePushDetails *push; @property (nullable, nonatomic) ARTUpdateToken *updateToken; -- (instancetype)initWithId:(NSString *)id; +- (instancetype)initWithId:(ARTDeviceId *)deviceId; + (instancetype)fromLocalDevice; diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index 1d0e6b6e7..7a2184db2 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -8,6 +8,7 @@ #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" +#import NSString *const ARTDevicePlatform = @"ios"; NSString *const ARTDeviceFormFactor = @"mobile"; @@ -16,15 +17,16 @@ @implementation ARTDeviceDetails + (instancetype)fromLocalDevice { return [[ARTDeviceDetails alloc] init]; + // TODO } - (instancetype)init { - return [self initWithId:[[NSUUID new] UUIDString]]; + return [self initWithId:[[WSULID ulid] ULIDString]]; } -- (instancetype)initWithId:(NSString *)id { +- (instancetype)initWithId:(ARTDeviceId *)deviceId { if (self = [super init]) { - _id = id; + _id = deviceId; _push = [[ARTDevicePushDetails alloc] init]; } return self; From 9dd847277c3444bcf091286bcb2e09e7a0032057 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 15 Feb 2017 11:54:02 +0000 Subject: [PATCH 19/46] Push.deactivate --- Source/ARTPush.h | 10 +++++----- Source/ARTPush.m | 41 ++++++++++++++++++++++++++++++++++++++--- Source/ARTPushChannel.m | 26 ++++++++++++++------------ 3 files changed, 57 insertions(+), 20 deletions(-) diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 49c76ecd4..c2795fbf2 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -23,7 +23,7 @@ typedef ARTJsonObject ARTPushRecipient; #pragma mark ARTPushNotifications interface -#ifdef TARGET_OS_IPHONE +#ifdef TARGET_OS_IPHONE //iOS system @protocol ARTPushNotifications - (void)didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken; - (void)didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error; @@ -35,7 +35,7 @@ typedef ARTJsonObject ARTPushRecipient; NS_ASSUME_NONNULL_BEGIN -#ifdef TARGET_OS_IPHONE +#ifdef TARGET_OS_IPHONE //iOS system @interface ARTPush : NSObject #else @interface ARTPush : NSObject @@ -49,13 +49,13 @@ NS_ASSUME_NONNULL_BEGIN /// Publish a push notification. - (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject; -#ifdef TARGET_OS_IPHONE +#ifdef TARGET_OS_IPHONE //iOS system /// Register a device, including the information necessary to deliver push notifications to it. - (void)activate; - (void)activateWithRegisterCallback:(void (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable, void (^ _Nullable)(ARTUpdateToken * _Nullable, ARTErrorInfo * _Nullable)))registerCallback; /// Unregister a device. -- (void)deactivate:(ARTDeviceId *)deviceId; -- (void)deactivate:(ARTDeviceId *)deviceId deregisterCallback:(nullable void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))deregisterCallback; +- (void)deactivate; +- (void)deactivateWithDeregisterCallback:(void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable, void (^ _Nullable)(ARTErrorInfo * _Nullable)))deregisterCallback; #endif @end diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 2eec8b8ae..0516d54c1 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -120,12 +120,47 @@ - (void)activate:(ARTDeviceDetails *)deviceDetails registerCallback:(void (^)(AR } } -- (void)deactivate:(ARTDeviceId *)deviceId { +- (void)deactivate { + ARTDeviceId *deviceId = @""; // TODO [self deactivate:deviceId deregisterCallback:nil]; } -- (void)deactivate:(ARTDeviceId *)deviceId deregisterCallback:(void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable))deregisterCallback { - // TODO +- (void)deactivateWithDeregisterCallback:(void (^)(ARTDeviceId *, ARTErrorInfo *, void (^)(ARTErrorInfo *)))deregisterCallback { + ARTDeviceId *deviceId = @""; // TODO + [self deactivate:deviceId deregisterCallback:deregisterCallback]; +} + +- (void)deactivate:(ARTDeviceId *)deviceId deregisterCallback:(void (^)(ARTDeviceId *, ARTErrorInfo *, void (^)(ARTErrorInfo *)))deregisterCallback { + if (deregisterCallback) { + deregisterCallback(deviceId, nil, ^(ARTErrorInfo *error) { + if (error) { + [_logger error:@"%@: device deregistration using a `deregisterCallback` failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + } + }); + return; + } + + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"] resolvingAgainstBaseURL:NO]; + components.queryItems = @[ + [NSURLQueryItem queryItemWithName:@"deviceId" value:deviceId], + ]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; + request.HTTPMethod = @"DELETE"; + + [_logger debug:__FILE__ line:__LINE__ message:@"device deregistration with request %@", request]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + [_logger debug:__FILE__ line:__LINE__ message:@"successfully deactivate device"]; + self.device.updateToken = nil; + } + else if (error) { + [_logger error:@"%@: device deregistration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + } + else { + [_logger error:@"%@: device deregistration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + } + }]; } - (void)newDevice:(ARTDeviceDetails *)deviceDetails { diff --git a/Source/ARTPushChannel.m b/Source/ARTPushChannel.m index af9764b13..be44911de 100644 --- a/Source/ARTPushChannel.m +++ b/Source/ARTPushChannel.m @@ -74,13 +74,14 @@ - (void)subscribeForClientId:(NSString *)clientId { } - (void)unsubscribeForDevice:(NSString *)deviceId { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; + components.queryItems = @[ + [NSURLQueryItem queryItemWithName:@"deviceId" value:deviceId], + [NSURLQueryItem queryItemWithName:@"channel" value:_channel.name], + ]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; request.HTTPMethod = @"DELETE"; - request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ - @"deviceId": deviceId, - @"channel": _channel.name, - }]; - [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"unsubscribe notifications for device %@ in channel %@", deviceId, _channel.name]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { @@ -97,13 +98,14 @@ - (void)unsubscribeForDevice:(NSString *)deviceId { } - (void)unsubscribeForClientId:(NSString *)clientId { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; + components.queryItems = @[ + [NSURLQueryItem queryItemWithName:@"clientId" value:clientId], + [NSURLQueryItem queryItemWithName:@"channel" value:_channel.name], + ]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; request.HTTPMethod = @"DELETE"; - request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ - @"clientId": clientId, - @"channel": _channel.name, - }]; - [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"unsubscribe notifications for clientId %@ in channel %@", clientId, _channel.name]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { From ccf0e2bb527a4083b192d26730b59a216a33a0d4 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 15 Feb 2017 12:04:28 +0000 Subject: [PATCH 20/46] Load local device --- Source/ARTDeviceDetails.h | 1 + Source/ARTDeviceDetails.m | 15 +++++++++------ Source/ARTPush.m | 8 +++----- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Source/ARTDeviceDetails.h b/Source/ARTDeviceDetails.h index f8eb04f5e..413ef1ed5 100644 --- a/Source/ARTDeviceDetails.h +++ b/Source/ARTDeviceDetails.h @@ -26,6 +26,7 @@ extern NSString *const ARTDeviceFormFactor; @property (nonatomic, readonly) ARTDevicePushDetails *push; @property (nullable, nonatomic) ARTUpdateToken *updateToken; +- (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithId:(ARTDeviceId *)deviceId; + (instancetype)fromLocalDevice; diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index 7a2184db2..a3274e914 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -13,15 +13,18 @@ NSString *const ARTDevicePlatform = @"ios"; NSString *const ARTDeviceFormFactor = @"mobile"; +NSString *const ARTDeviceIdKey = @"ARTDeviceId"; + @implementation ARTDeviceDetails + (instancetype)fromLocalDevice { - return [[ARTDeviceDetails alloc] init]; - // TODO -} - -- (instancetype)init { - return [self initWithId:[[WSULID ulid] ULIDString]]; + NSString *deviceId = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceIdKey]; + if (!deviceId) { + deviceId = [[WSULID ulid] ULIDString]; + [[NSUserDefaults standardUserDefaults] setObject:deviceId forKey:ARTDeviceIdKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } + return [[ARTDeviceDetails alloc] initWithId:deviceId]; } - (instancetype)initWithId:(ARTDeviceId *)deviceId { diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 0516d54c1..ef20f4dc5 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -14,7 +14,7 @@ #import "ARTJsonLikeEncoder.h" #import "ARTEventEmitter.h" -NSString *const ARTDeviceTokenKey = @"DeviceToken"; +NSString *const ARTDeviceTokenKey = @"ARTDeviceToken"; typedef NS_ENUM(NSUInteger, ARTPushState) { ARTPushStateDeactivated, @@ -121,13 +121,11 @@ - (void)activate:(ARTDeviceDetails *)deviceDetails registerCallback:(void (^)(AR } - (void)deactivate { - ARTDeviceId *deviceId = @""; // TODO - [self deactivate:deviceId deregisterCallback:nil]; + [self deactivate:self.device.id deregisterCallback:nil]; } - (void)deactivateWithDeregisterCallback:(void (^)(ARTDeviceId *, ARTErrorInfo *, void (^)(ARTErrorInfo *)))deregisterCallback { - ARTDeviceId *deviceId = @""; // TODO - [self deactivate:deviceId deregisterCallback:deregisterCallback]; + [self deactivate:self.device.id deregisterCallback:deregisterCallback]; } - (void)deactivate:(ARTDeviceId *)deviceId deregisterCallback:(void (^)(ARTDeviceId *, ARTErrorInfo *, void (^)(ARTErrorInfo *)))deregisterCallback { From 8622a3017b51258e548148650319ca28a30c9d38 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 15 Feb 2017 14:40:54 +0000 Subject: [PATCH 21/46] Push: update device --- Source/ARTDevicePushDetails.h | 4 +++- Source/ARTDevicePushDetails.m | 7 +++++++ Source/ARTPush.m | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Source/ARTDevicePushDetails.h b/Source/ARTDevicePushDetails.h index bd3873d09..29dc46cee 100644 --- a/Source/ARTDevicePushDetails.h +++ b/Source/ARTDevicePushDetails.h @@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN +extern NSString *const ARTDeviceTokenKey; + extern NSString *const ARTDevicePushTransportType; typedef NS_ENUM(NSUInteger, ARTDevicePushState) { @@ -26,7 +28,7 @@ ARTDevicePushState ARTDevicePushStateFromStr(NSString *value); @interface ARTDevicePushDetails : NSObject @property (nonatomic, readonly) NSString *transportType; -@property (nonatomic) NSData *deviceToken; +@property (nonatomic, readonly) NSData *deviceToken; @property (nonatomic, assign) ARTDevicePushState state; @property (nullable, nonatomic) ARTErrorInfo *errorReason; diff --git a/Source/ARTDevicePushDetails.m b/Source/ARTDevicePushDetails.m index 8360d4085..d52a6e1ac 100644 --- a/Source/ARTDevicePushDetails.m +++ b/Source/ARTDevicePushDetails.m @@ -7,6 +7,9 @@ // #import "ARTDevicePushDetails.h" +#import "ARTPush.h" + +NSString *const ARTDeviceTokenKey = @"ARTDeviceToken"; NSString *const ARTDevicePushTransportType = @"apns"; @@ -28,4 +31,8 @@ - (NSString *)transportType { return ARTDevicePushTransportType; } +- (NSData *)deviceToken { + return [[NSUserDefaults standardUserDefaults] dataForKey:ARTDeviceTokenKey]; +} + @end diff --git a/Source/ARTPush.m b/Source/ARTPush.m index ef20f4dc5..b706c71cf 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -8,14 +8,13 @@ #import "ARTPush.h" #import "ARTDeviceDetails.h" +#import "ARTDevicePushDetails.h" #import "ARTRest+Private.h" #import "ARTLog.h" #import "ARTJsonEncoder.h" #import "ARTJsonLikeEncoder.h" #import "ARTEventEmitter.h" -NSString *const ARTDeviceTokenKey = @"ARTDeviceToken"; - typedef NS_ENUM(NSUInteger, ARTPushState) { ARTPushStateDeactivated, ARTPushStateActivated, @@ -183,7 +182,37 @@ - (void)newDevice:(ARTDeviceDetails *)deviceDetails { } - (void)updateDevice:(ARTDeviceDetails *)deviceDetails { + if (!deviceDetails.updateToken) { + [_logger error:@"%@: update token is missing", NSStringFromClass(self.class)]; + return; + } + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[[NSURL URLWithString:@"/push/deviceRegistrations"] URLByAppendingPathComponent:deviceDetails.id]]; + NSData *tokenData = [deviceDetails.updateToken dataUsingEncoding:NSUTF8StringEncoding]; + NSString *tokenBase64 = [tokenData base64EncodedStringWithOptions:0]; + [request setValue:[NSString stringWithFormat:@"Bearer %@", tokenBase64] forHTTPHeaderField:@"Authorization"]; + request.HTTPMethod = @"PUT"; + request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ + @"push": @{ + @"metadata": @{ + @"deviceToken": deviceDetails.push.deviceToken, + } + } + }]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + [_logger debug:__FILE__ line:__LINE__ message:@"update device with request %@", request]; + [_httpExecutor executeRequest:request completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data]; + self.device.updateToken = deviceDetails.updateToken; + } + else if (error) { + [_logger error:@"%@: update device failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + } + else { + [_logger error:@"%@: update device failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + } + }]; } @end From 2c68fa939836e27a1d416e3ace9a98f1f714ab1d Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 15 Feb 2017 17:22:26 +0000 Subject: [PATCH 22/46] PushChannel.subscriptions --- Ably.xcodeproj/project.pbxproj | 8 ++++ Source/ARTEncoder.h | 8 +++- Source/ARTHttp.h | 3 ++ Source/ARTJsonLikeEncoder.m | 50 +++++++++++++++++++++++++ Source/ARTPaginatedResult+Private.h | 4 +- Source/ARTPaginatedResult.m | 22 +++++------ Source/ARTPushChannel.h | 14 +++++-- Source/ARTPushChannel.m | 57 +++++++++++++++++++++++++++-- Source/ARTPushChannelSubscription.h | 25 +++++++++++++ Source/ARTPushChannelSubscription.m | 29 +++++++++++++++ Source/ARTStatus.h | 3 +- Source/Ably.h | 1 + 12 files changed, 201 insertions(+), 23 deletions(-) create mode 100644 Source/ARTPushChannelSubscription.h create mode 100644 Source/ARTPushChannelSubscription.m diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 0b67b2bc6..738efb70e 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -132,6 +132,8 @@ D768C6AD1E4B5B0200436011 /* ARTDevicePushDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */; }; D77394031C6F6FFE00F5478F /* ARTProtocolMessage+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D77394021C6F6FFE00F5478F /* ARTProtocolMessage+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; D780846E1C68B3E50083009D /* NSObject+TestSuite.m in Sources */ = {isa = PBXBuildFile; fileRef = D780846D1C68B3E50083009D /* NSObject+TestSuite.m */; }; + D785C4291E549E33008FEC05 /* ARTPushChannelSubscription.h in Headers */ = {isa = PBXBuildFile; fileRef = D785C4271E549E33008FEC05 /* ARTPushChannelSubscription.h */; }; + D785C42A1E549E33008FEC05 /* ARTPushChannelSubscription.m in Sources */ = {isa = PBXBuildFile; fileRef = D785C4281E549E33008FEC05 /* ARTPushChannelSubscription.m */; }; D79FF2751D901CD50067FA6A /* ARTRealtime+TestSuite.m in Sources */ = {isa = PBXBuildFile; fileRef = D79FF2741D901CD50067FA6A /* ARTRealtime+TestSuite.m */; }; D7B17EE31C07208B00A6958E /* ARTConnectionDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B17EE11C07208B00A6958E /* ARTConnectionDetails.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7B17EE41C07208B00A6958E /* ARTConnectionDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D7B17EE21C07208B00A6958E /* ARTConnectionDetails.m */; }; @@ -376,6 +378,8 @@ D77394021C6F6FFE00F5478F /* ARTProtocolMessage+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARTProtocolMessage+Private.h"; sourceTree = ""; }; D780846C1C68B3E50083009D /* NSObject+TestSuite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+TestSuite.h"; sourceTree = ""; }; D780846D1C68B3E50083009D /* NSObject+TestSuite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+TestSuite.m"; sourceTree = ""; }; + D785C4271E549E33008FEC05 /* ARTPushChannelSubscription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPushChannelSubscription.h; sourceTree = ""; }; + D785C4281E549E33008FEC05 /* ARTPushChannelSubscription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushChannelSubscription.m; sourceTree = ""; }; D79FF2731D901CD50067FA6A /* ARTRealtime+TestSuite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARTRealtime+TestSuite.h"; sourceTree = ""; }; D79FF2741D901CD50067FA6A /* ARTRealtime+TestSuite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ARTRealtime+TestSuite.m"; sourceTree = ""; }; D7B17EE11C07208B00A6958E /* ARTConnectionDetails.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTConnectionDetails.h; sourceTree = ""; }; @@ -792,6 +796,8 @@ D7B6218F1E4A6E0200684474 /* ARTPush.m */, D7B621961E4A762A00684474 /* ARTPushChannel.h */, D7B621971E4A762A00684474 /* ARTPushChannel.m */, + D785C4271E549E33008FEC05 /* ARTPushChannelSubscription.h */, + D785C4281E549E33008FEC05 /* ARTPushChannelSubscription.m */, D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */, D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */, D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */, @@ -882,6 +888,7 @@ D746AE2F1BBBE7D7003ECEF8 /* ARTPaginatedResult+Private.h in Headers */, D746AE431BBC5CD0003ECEF8 /* ARTRealtimeChannel+Private.h in Headers */, D746AE251BBB611C003ECEF8 /* ARTChannel+Private.h in Headers */, + D785C4291E549E33008FEC05 /* ARTPushChannelSubscription.h in Headers */, D7F1D3731BF4DE07001A4B5E /* ARTRestPresence.h in Headers */, D7B17EE31C07208B00A6958E /* ARTConnectionDetails.h in Headers */, EBFA366E1D58B05000B09AA7 /* ARTRestPresence+Private.h in Headers */, @@ -1212,6 +1219,7 @@ 960D07941A45F1D800ED8C8C /* ARTCrypto.m in Sources */, D7D8F8221BC2BE16009718F2 /* ARTAuthOptions.m in Sources */, 1C55427D1B148306003068DB /* ARTStatus.m in Sources */, + D785C42A1E549E33008FEC05 /* ARTPushChannelSubscription.m in Sources */, D7B17EE41C07208B00A6958E /* ARTConnectionDetails.m in Sources */, 96BF61591A35B52C004CF2B3 /* ARTHttp.m in Sources */, 1C578E201B3435CA00EF46EC /* ARTFallback.m in Sources */, diff --git a/Source/ARTEncoder.h b/Source/ARTEncoder.h index a61e65683..68252c29b 100644 --- a/Source/ARTEncoder.h +++ b/Source/ARTEncoder.h @@ -17,6 +17,7 @@ @class ARTTokenRequest; @class ARTDeviceDetails; @class ARTDevicePushDetails; +@class ARTPushChannelSubscription; @protocol ARTPushRecipient; @@ -51,7 +52,7 @@ ART_ASSUME_NONNULL_BEGIN // Message list - (art_nullable NSData *)encodeMessages:(NSArray *)messages; -- (art_nullable NSArray *)decodeMessages:(NSData *)data; +- (art_nullable NSArray *)decodeMessages:(NSData *)data; // PresenceMessage - (art_nullable NSData *)encodePresenceMessage:(ARTPresenceMessage *)message; @@ -59,7 +60,7 @@ ART_ASSUME_NONNULL_BEGIN // PresenceMessage list - (art_nullable NSData *)encodePresenceMessages:(NSArray *)messages; -- (art_nullable NSArray *)decodePresenceMessages:(NSData *)data; +- (art_nullable NSArray *)decodePresenceMessages:(NSData *)data; // ProtocolMessage - (art_nullable NSData *)encodeProtocolMessage:(ARTProtocolMessage *)message; @@ -72,6 +73,9 @@ ART_ASSUME_NONNULL_BEGIN // DevicePushDetails - (art_nullable NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails; +// Push Channel Subscriptions +- (art_nullable NSArray *)decodePushChannelSubscriptions:(NSData *)data error:(NSError * __autoreleasing *)error; + // Others - (art_nullable NSDate *)decodeTime:(NSData *)data; - (art_nullable NSError *)decodeError:(NSData *)error; diff --git a/Source/ARTHttp.h b/Source/ARTHttp.h index e94ef4d51..afa4f6209 100644 --- a/Source/ARTHttp.h +++ b/Source/ARTHttp.h @@ -11,6 +11,7 @@ #import "ARTLog.h" @class ARTErrorInfo; +@class ARTClientOptions; @protocol ARTEncoder; @@ -26,6 +27,8 @@ ART_ASSUME_NONNULL_BEGIN @protocol ARTHTTPAuthenticatedExecutor +- (ARTClientOptions *)options; + - (id)defaultEncoder; - (void)executeRequest:(NSMutableURLRequest *)request withAuthOption:(ARTAuthentication)authOption completion:(void (^)(NSHTTPURLResponse *__art_nullable, NSData *__art_nullable, NSError *__art_nullable))callback; diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index 88071084f..d246865b2 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -29,6 +29,7 @@ #import "ARTConnectionDetails.h" #import "ARTRest+Private.h" #import "ARTJsonEncoder.h" +#import "ARTPushChannelSubscription.h" @interface ARTJsonLikeEncoder () @@ -150,6 +151,55 @@ - (NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails { return [self encode:[self devicePushDetailsToDictionary:devicePushDetails]]; } +- (NSArray *)decodePushChannelSubscriptions:(NSData *)data error:(NSError * __autoreleasing *)error { + return [self pushChannelSubscriptionsFromArray:[self decodeArray:data] error:error]; +} + +- (NSArray *)pushChannelSubscriptionsFromArray:(NSArray *)input error:(NSError * __autoreleasing *)error { + NSMutableArray *output = [NSMutableArray array]; + for (NSDictionary *item in input) { + ARTPushChannelSubscription *subscription = [self pushChannelSubscriptionFromDictionary:item error:error]; + if (!subscription) { + return nil; + } + [output addObject:subscription]; + } + return output; +} + +- (ARTPushChannelSubscription *)pushChannelSubscriptionFromDictionary:(NSDictionary *)input error:(NSError * __autoreleasing *)error { + [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: pushChannelSubscriptionFromDictionary %@", _rest, [_delegate formatAsString], input]; + + if (![input isKindOfClass:[NSDictionary class]]) { + return nil; + } + + NSString *clientId = [input artString:@"clientId"]; + NSString *deviceId = [input artString:@"deviceId"]; + + if ((clientId && deviceId) || (!clientId && !deviceId)) { + [_logger error:@"ARTJsonLikeEncoder<%@>: clientId and deviceId are both present or both nil", [_delegate formatAsString]]; + if (error) { + *error = [NSError errorWithDomain:ARTAblyErrorDomain + code:ARTCodeErrorAPIInconsistency + userInfo:@{ NSLocalizedDescriptionKey: @"clientId and deviceId are both present or both nil"}]; + } + return nil; + } + + NSString *channelName = [input artString:@"channel"]; + + ARTPushChannelSubscription *channelSubscription; + if (clientId) { + channelSubscription = [[ARTPushChannelSubscription alloc] initWithClientId:clientId andChannel:channelName]; + } + else { + channelSubscription = [[ARTPushChannelSubscription alloc] initWithDeviceId:deviceId andChannel:channelName]; + } + + return channelSubscription; +} + - (NSDate *)decodeTime:(NSData *)data { NSArray *resp = [self decodeArray:data]; [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: decodeTime %@", _rest, [_delegate formatAsString], resp]; diff --git a/Source/ARTPaginatedResult+Private.h b/Source/ARTPaginatedResult+Private.h index 5a675c26e..e6aedf6ac 100644 --- a/Source/ARTPaginatedResult+Private.h +++ b/Source/ARTPaginatedResult+Private.h @@ -10,13 +10,15 @@ @class ARTRest; +@protocol ARTHTTPAuthenticatedExecutor; + ART_ASSUME_NONNULL_BEGIN @interface __GENERIC(ARTPaginatedResult, ItemType) () typedef __GENERIC(NSArray, ItemType) *__art_nullable(^ARTPaginatedResultResponseProcessor)(NSHTTPURLResponse *__art_nullable, NSData *__art_nullable); -+ (void)executePaginated:(ARTRest *)rest withRequest:(NSMutableURLRequest *)request ++ (void)executePaginated:(id)httpExecutor withRequest:(NSMutableURLRequest *)request andResponseProcessor:(ARTPaginatedResultResponseProcessor)responseProcessor callback:(void (^)(__GENERIC(ARTPaginatedResult, ItemType) *__art_nullable result, ARTErrorInfo *__art_nullable error))callback; diff --git a/Source/ARTPaginatedResult.m b/Source/ARTPaginatedResult.m index e03c2a0e3..46e3e53d2 100644 --- a/Source/ARTPaginatedResult.m +++ b/Source/ARTPaginatedResult.m @@ -13,7 +13,7 @@ #import "ARTRest+Private.h" @implementation ARTPaginatedResult { - __weak ARTRest *_rest; + id _httpExecutor; NSMutableURLRequest *_relFirst; NSMutableURLRequest *_relCurrent; NSMutableURLRequest *_relNext; @@ -21,7 +21,7 @@ @implementation ARTPaginatedResult { } - (instancetype)initWithItems:(NSArray *)items - rest:(ARTRest *)rest + httpExecutor:(id)httpExecutor relFirst:(NSMutableURLRequest *)relFirst relCurrent:(NSMutableURLRequest *)relCurrent relNext:(NSMutableURLRequest *)relNext @@ -37,7 +37,7 @@ - (instancetype)initWithItems:(NSArray *)items _hasNext = !!relNext; _isLast = !_hasNext; - _rest = rest; + _httpExecutor = httpExecutor; _responseProcessor = responseProcessor; } @@ -45,7 +45,7 @@ - (instancetype)initWithItems:(NSArray *)items } - (void)first:(void (^)(__GENERIC(ARTPaginatedResult, id) *__art_nullable result, ARTErrorInfo *__art_nullable error))callback { - [self.class executePaginated:_rest withRequest:_relFirst andResponseProcessor:_responseProcessor callback:callback]; + [self.class executePaginated:_httpExecutor withRequest:_relFirst andResponseProcessor:_responseProcessor callback:callback]; } - (void)next:(void (^)(__GENERIC(ARTPaginatedResult, id) *__art_nullable result, ARTErrorInfo *__art_nullable error))callback { @@ -56,7 +56,7 @@ - (void)next:(void (^)(__GENERIC(ARTPaginatedResult, id) *__art_nullable result, callback(nil, nil); return; } - [self.class executePaginated:_rest withRequest:_relNext andResponseProcessor:_responseProcessor callback:callback]; + [self.class executePaginated:_httpExecutor withRequest:_relNext andResponseProcessor:_responseProcessor callback:callback]; } static NSDictionary *extractLinks(NSHTTPURLResponse *response) { @@ -99,15 +99,15 @@ - (void)next:(void (^)(__GENERIC(ARTPaginatedResult, id) *__art_nullable result, return [NSMutableURLRequest requestWithURL:url]; } -+ (void)executePaginated:(ARTRest *)rest withRequest:(NSMutableURLRequest *)request andResponseProcessor:(ARTPaginatedResultResponseProcessor)responseProcessor callback:(void (^)(__GENERIC(ARTPaginatedResult, id) *__art_nullable result, ARTErrorInfo *__art_nullable error))callback { - [rest.logger debug:__FILE__ line:__LINE__ message:@"Paginated request: %@", request]; ++ (void)executePaginated:(id)httpExecutor withRequest:(NSMutableURLRequest *)request andResponseProcessor:(ARTPaginatedResultResponseProcessor)responseProcessor callback:(void (^)(__GENERIC(ARTPaginatedResult, id) *__art_nullable result, ARTErrorInfo *__art_nullable error))callback { + [[httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"Paginated request: %@", request]; - [rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + [httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (error) { callback(nil, [ARTErrorInfo createWithNSError:error]); } else { - [rest.logger debug:__FILE__ line:__LINE__ message:@"Paginated response: %@", response]; - [rest.logger debug:__FILE__ line:__LINE__ message:@"Paginated response data: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; + [[httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"Paginated response: %@", response]; + [[httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"Paginated response data: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; NSArray *items = responseProcessor(response, data); @@ -118,7 +118,7 @@ + (void)executePaginated:(ARTRest *)rest withRequest:(NSMutableURLRequest *)requ NSMutableURLRequest *nextRel = requestRelativeTo(request, links[@"next"]);; ARTPaginatedResult *result = [[ARTPaginatedResult alloc] initWithItems:items - rest:rest + httpExecutor:httpExecutor relFirst:firstRel relCurrent:currentRel relNext:nextRel diff --git a/Source/ARTPushChannel.h b/Source/ARTPushChannel.h index ef6ffbf31..ac8e628d9 100644 --- a/Source/ARTPushChannel.h +++ b/Source/ARTPushChannel.h @@ -10,6 +10,8 @@ #import "ARTPush.h" @class ARTChannel; +@class ARTPushChannelSubscription; +@class ARTPaginatedResult; @protocol ARTHTTPAuthenticatedExecutor; @@ -20,11 +22,15 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; - (instancetype)init:(id)httpExecutor withChannel:(ARTChannel *)channel; -- (void)subscribeForDevice:(ARTDeviceId *)deviceId; -- (void)subscribeForClientId:(NSString *)clientId; +- (void)subscribe; +- (void)subscribeDevice:(ARTDeviceId *)deviceId; +- (void)subscribeClient:(NSString *)clientId; -- (void)unsubscribeForDevice:(ARTDeviceId *)deviceId; -- (void)unsubscribeForClientId:(NSString *)clientId; +- (void)unsubscribe; +- (void)unsubscribeDevice:(ARTDeviceId *)deviceId; +- (void)unsubscribeClient:(NSString *)clientId; + +- (void)subscriptions:(void(^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; @end diff --git a/Source/ARTPushChannel.m b/Source/ARTPushChannel.m index be44911de..29da917ea 100644 --- a/Source/ARTPushChannel.m +++ b/Source/ARTPushChannel.m @@ -11,6 +11,11 @@ #import "ARTLog.h" #import "ARTChannel.h" #import "ARTJsonLikeEncoder.h" +#import "ARTClientOptions.h" +#import "ARTPaginatedResult+Private.h" +#import "ARTPushChannelSubscription.h" + +const NSUInteger ARTDefaultLimit = 100; @implementation ARTPushChannel { id _httpExecutor; @@ -27,7 +32,15 @@ - (instancetype)init:(id)httpExecutor withChannel: return self; } -- (void)subscribeForDevice:(ARTDeviceId *)deviceId { +- (NSString *)clientId { + return [self clientId]; +} + +- (void)subscribe { + [self subscribeClient:[self clientId]]; +} + +- (void)subscribeDevice:(ARTDeviceId *)deviceId { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; request.HTTPMethod = @"POST"; request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ @@ -50,7 +63,7 @@ - (void)subscribeForDevice:(ARTDeviceId *)deviceId { }]; } -- (void)subscribeForClientId:(NSString *)clientId { +- (void)subscribeClient:(NSString *)clientId { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; request.HTTPMethod = @"POST"; request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ @@ -73,7 +86,11 @@ - (void)subscribeForClientId:(NSString *)clientId { }]; } -- (void)unsubscribeForDevice:(NSString *)deviceId { +- (void)unsubscribe { + [self unsubscribeClient:[self clientId]]; +} + +- (void)unsubscribeDevice:(NSString *)deviceId { NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; components.queryItems = @[ [NSURLQueryItem queryItemWithName:@"deviceId" value:deviceId], @@ -97,7 +114,7 @@ - (void)unsubscribeForDevice:(NSString *)deviceId { }]; } -- (void)unsubscribeForClientId:(NSString *)clientId { +- (void)unsubscribeClient:(NSString *)clientId { NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; components.queryItems = @[ [NSURLQueryItem queryItemWithName:@"clientId" value:clientId], @@ -121,4 +138,36 @@ - (void)unsubscribeForClientId:(NSString *)clientId { }]; } +- (void)subscriptions:(void(^)(ARTPaginatedResult *result, ARTErrorInfo *error))callback { + [self subscriptionsClient:[self clientId] limit:ARTDefaultLimit callback:callback error:nil]; +} + +- (BOOL)subscriptionsClient:(NSString *)clientId limit:(NSUInteger)limit callback:(void(^)(ARTPaginatedResult *result, ARTErrorInfo *error))callback error:(NSError **)errorPtr { + if (limit > 1000) { + if (errorPtr) { + *errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain + code:ARTDataQueryErrorLimit + userInfo:@{NSLocalizedDescriptionKey:@"Limit supports up to 1000 results only"}]; + } + return NO; + } + + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; + components.queryItems = @[ + [NSURLQueryItem queryItemWithName:@"clientId" value:clientId], + [NSURLQueryItem queryItemWithName:@"limit" value:[[NSNumber numberWithUnsignedInteger:limit] stringValue]], + ]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; + request.HTTPMethod = @"GET"; + + ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data) { + ARTErrorInfo *error; + return [[_httpExecutor defaultEncoder] decodePushChannelSubscriptions:data error:&error]; + }; + + [ARTPaginatedResult executePaginated:_httpExecutor withRequest:request andResponseProcessor:responseProcessor callback:callback]; + return YES; +} + @end diff --git a/Source/ARTPushChannelSubscription.h b/Source/ARTPushChannelSubscription.h new file mode 100644 index 000000000..83297a257 --- /dev/null +++ b/Source/ARTPushChannelSubscription.h @@ -0,0 +1,25 @@ +// +// ARTPushChannelSubscription.h +// Ably +// +// Created by Ricardo Pereira on 15/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTPushChannelSubscription : NSObject + +@property (nonatomic, readonly) NSString *deviceId; +@property (nonatomic, readonly) NSString *clientId; +@property (nonatomic, readonly) NSString *channelName; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDeviceId:(NSString *)deviceId andChannel:(NSString *)channelName; +- (instancetype)initWithClientId:(NSString *)clientId andChannel:(NSString *)channelName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushChannelSubscription.m b/Source/ARTPushChannelSubscription.m new file mode 100644 index 000000000..192fa2601 --- /dev/null +++ b/Source/ARTPushChannelSubscription.m @@ -0,0 +1,29 @@ +// +// ARTPushChannelSubscription.m +// Ably +// +// Created by Ricardo Pereira on 15/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTPushChannelSubscription.h" + +@implementation ARTPushChannelSubscription + +- (instancetype)initWithDeviceId:(NSString *)deviceId andChannel:(NSString *)channelName { + if (self = [super init]) { + _deviceId = deviceId; + _channelName = channelName; + } + return self; +} + +- (instancetype)initWithClientId:(NSString *)clientId andChannel:(NSString *)channelName { + if (self = [super init]) { + _clientId = clientId; + _channelName = channelName; + } + return self; +} + +@end diff --git a/Source/ARTStatus.h b/Source/ARTStatus.h index 3f37407d9..dc4fd4798 100644 --- a/Source/ARTStatus.h +++ b/Source/ARTStatus.h @@ -38,7 +38,8 @@ typedef NS_ENUM(NSUInteger, ARTState) { */ typedef CF_ENUM(NSUInteger, ARTCodeError) { // FIXME: check hard coded errors - ARTCodeErrorAPIKeyMissing = 80001 + ARTCodeErrorAPIKeyMissing = 80001, + ARTCodeErrorAPIInconsistency = 80002 }; ART_ASSUME_NONNULL_BEGIN diff --git a/Source/Ably.h b/Source/Ably.h index abd192ecf..c7ba92cbb 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -59,6 +59,7 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import "ARTGCD.h" #import "ARTPush.h" #import "ARTPushChannel.h" +#import "ARTPushChannelSubscription.h" #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" From 4d2d68655e60c490a17d912b45c8fd8ad0f49a3f Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Mon, 20 Feb 2017 12:23:08 +0000 Subject: [PATCH 23/46] Update ULID pod --- Ably.podspec | 1 + Podfile | 2 +- Podfile.lock | 18 ++++-------------- Source/ARTDeviceDetails.m | 2 +- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/Ably.podspec b/Ably.podspec index 428ec26b9..f739011b3 100644 --- a/Ably.podspec +++ b/Ably.podspec @@ -18,4 +18,5 @@ Pod::Spec.new do |s| s.module_map = 'Source/Ably.modulemap' s.dependency 'SocketRocket', '0.5.1' s.dependency 'msgpack', '0.1.8' + s.dependency 'ULID', '1.0.1' end diff --git a/Podfile b/Podfile index eefeafa62..374df7bf7 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ podspec :path => 'Ably.podspec' def project_pods pod 'SocketRocket', '0.5.1' pod 'msgpack', '0.1.8' - pod 'ULID', :git => 'https://github.com/Whitesmith/ulid.git', :branch => 'master' + pod 'ULID', '1.0.1' end def test_pods diff --git a/Podfile.lock b/Podfile.lock index 235cba657..1285c372c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -5,7 +5,7 @@ PODS: - Quick (0.9.3) - SocketRocket (0.5.1) - SwiftyJSON (2.4.0) - - ULID (0.1.0) + - ULID (1.0.1) DEPENDENCIES: - Aspects @@ -14,17 +14,7 @@ DEPENDENCIES: - Quick (= 0.9.3) - SocketRocket (= 0.5.1) - SwiftyJSON (= 2.4.0) - - ULID (from `https://github.com/Whitesmith/ulid.git`, branch `master`) - -EXTERNAL SOURCES: - ULID: - :branch: master - :git: https://github.com/Whitesmith/ulid.git - -CHECKOUT OPTIONS: - ULID: - :commit: c7b4dfec8bc98c2978787bd9ed32a44fcb1c5dbb - :git: https://github.com/Whitesmith/ulid.git + - ULID (= 1.0.1) SPEC CHECKSUMS: Aspects: 7595ba96a6727a58ebcbfc954497fc5d2fdde546 @@ -33,8 +23,8 @@ SPEC CHECKSUMS: Quick: 13a2a2b19a5d8e3ed4fd0c36ee46597fd77ebf71 SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531 SwiftyJSON: 96918c1bf505efa50c4f72957018dd3452090c9c - ULID: ea870a817f38f3d61355fe77850dba580af87b62 + ULID: 3caaee729fb720b436bd0bff552db76efd7878b9 -PODFILE CHECKSUM: d202fb38800e6408aace2b44dd5d42ef9c8b1a8e +PODFILE CHECKSUM: 419141b5bf70d2352bf50b46b3d0b9b2cc9e7ccc COCOAPODS: 1.1.1 diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index a3274e914..b9bea7ae9 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -20,7 +20,7 @@ @implementation ARTDeviceDetails + (instancetype)fromLocalDevice { NSString *deviceId = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceIdKey]; if (!deviceId) { - deviceId = [[WSULID ulid] ULIDString]; + deviceId = [[ULID new] ulidString]; [[NSUserDefaults standardUserDefaults] setObject:deviceId forKey:ARTDeviceIdKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } From f46088191499867339bdae1f0717c87b73e6589f Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Mon, 20 Feb 2017 20:08:07 +0000 Subject: [PATCH 24/46] PushAdmin --- Ably.xcodeproj/project.pbxproj | 26 +++++++- Source/ARTEncoder.h | 8 ++- Source/ARTJsonLikeEncoder.m | 64 +++++++++++++++++- Source/ARTPush.m | 4 +- Source/ARTPushAdmin.h | 28 ++++++++ Source/ARTPushAdmin.m | 27 ++++++++ Source/ARTPushChannelSubscriptions.h | 34 ++++++++++ Source/ARTPushChannelSubscriptions.m | 96 +++++++++++++++++++++++++++ Source/ARTPushDeviceRegistrations.h | 32 +++++++++ Source/ARTPushDeviceRegistrations.m | 98 ++++++++++++++++++++++++++++ Source/ARTRealtimeChannel.m | 2 +- Source/ARTTypes.h | 4 ++ Source/ARTTypes.m | 15 +++++ Source/Ably.h | 3 + 14 files changed, 433 insertions(+), 8 deletions(-) create mode 100644 Source/ARTPushAdmin.h create mode 100644 Source/ARTPushAdmin.m create mode 100644 Source/ARTPushChannelSubscriptions.h create mode 100644 Source/ARTPushChannelSubscriptions.m create mode 100644 Source/ARTPushDeviceRegistrations.h create mode 100644 Source/ARTPushDeviceRegistrations.m diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 738efb70e..3cd03073f 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -135,6 +135,12 @@ D785C4291E549E33008FEC05 /* ARTPushChannelSubscription.h in Headers */ = {isa = PBXBuildFile; fileRef = D785C4271E549E33008FEC05 /* ARTPushChannelSubscription.h */; }; D785C42A1E549E33008FEC05 /* ARTPushChannelSubscription.m in Sources */ = {isa = PBXBuildFile; fileRef = D785C4281E549E33008FEC05 /* ARTPushChannelSubscription.m */; }; D79FF2751D901CD50067FA6A /* ARTRealtime+TestSuite.m in Sources */ = {isa = PBXBuildFile; fileRef = D79FF2741D901CD50067FA6A /* ARTRealtime+TestSuite.m */; }; + D7AE18C91E5B40C900478D82 /* ARTPushAdmin.h in Headers */ = {isa = PBXBuildFile; fileRef = D7AE18C71E5B40C900478D82 /* ARTPushAdmin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D7AE18CA1E5B40C900478D82 /* ARTPushAdmin.m in Sources */ = {isa = PBXBuildFile; fileRef = D7AE18C81E5B40C900478D82 /* ARTPushAdmin.m */; }; + D7AE18CE1E5B40FE00478D82 /* ARTPushDeviceRegistrations.h in Headers */ = {isa = PBXBuildFile; fileRef = D7AE18CC1E5B40FE00478D82 /* ARTPushDeviceRegistrations.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D7AE18CF1E5B40FE00478D82 /* ARTPushDeviceRegistrations.m in Sources */ = {isa = PBXBuildFile; fileRef = D7AE18CD1E5B40FE00478D82 /* ARTPushDeviceRegistrations.m */; }; + D7AE18D21E5B410F00478D82 /* ARTPushChannelSubscriptions.h in Headers */ = {isa = PBXBuildFile; fileRef = D7AE18D01E5B410F00478D82 /* ARTPushChannelSubscriptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D7AE18D31E5B410F00478D82 /* ARTPushChannelSubscriptions.m in Sources */ = {isa = PBXBuildFile; fileRef = D7AE18D11E5B410F00478D82 /* ARTPushChannelSubscriptions.m */; }; D7B17EE31C07208B00A6958E /* ARTConnectionDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B17EE11C07208B00A6958E /* ARTConnectionDetails.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7B17EE41C07208B00A6958E /* ARTConnectionDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D7B17EE21C07208B00A6958E /* ARTConnectionDetails.m */; }; D7B621901E4A6E0200684474 /* ARTPush.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B6218E1E4A6E0200684474 /* ARTPush.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -382,6 +388,12 @@ D785C4281E549E33008FEC05 /* ARTPushChannelSubscription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushChannelSubscription.m; sourceTree = ""; }; D79FF2731D901CD50067FA6A /* ARTRealtime+TestSuite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARTRealtime+TestSuite.h"; sourceTree = ""; }; D79FF2741D901CD50067FA6A /* ARTRealtime+TestSuite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ARTRealtime+TestSuite.m"; sourceTree = ""; }; + D7AE18C71E5B40C900478D82 /* ARTPushAdmin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPushAdmin.h; sourceTree = ""; }; + D7AE18C81E5B40C900478D82 /* ARTPushAdmin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushAdmin.m; sourceTree = ""; }; + D7AE18CC1E5B40FE00478D82 /* ARTPushDeviceRegistrations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPushDeviceRegistrations.h; sourceTree = ""; }; + D7AE18CD1E5B40FE00478D82 /* ARTPushDeviceRegistrations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushDeviceRegistrations.m; sourceTree = ""; }; + D7AE18D01E5B410F00478D82 /* ARTPushChannelSubscriptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPushChannelSubscriptions.h; sourceTree = ""; }; + D7AE18D11E5B410F00478D82 /* ARTPushChannelSubscriptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushChannelSubscriptions.m; sourceTree = ""; }; D7B17EE11C07208B00A6958E /* ARTConnectionDetails.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTConnectionDetails.h; sourceTree = ""; }; D7B17EE21C07208B00A6958E /* ARTConnectionDetails.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTConnectionDetails.m; sourceTree = ""; }; D7B6218E1E4A6E0200684474 /* ARTPush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPush.h; sourceTree = ""; }; @@ -701,6 +713,7 @@ D7D8F8231BC2C691009718F2 /* ARTTokenDetails.h */, D7D8F8241BC2C691009718F2 /* ARTTokenDetails.m */, 961343D61A42E0B7006DC822 /* ARTClientOptions.h */, + EB503C871C7E4A090053AF00 /* ARTClientOptions+Private.h */, 961343D71A42E0B7006DC822 /* ARTClientOptions.m */, D746AE201BBB60EE003ECEF8 /* ARTChannel.h */, D746AE241BBB611C003ECEF8 /* ARTChannel+Private.h */, @@ -731,7 +744,6 @@ 1C55427C1B148306003068DB /* ARTStatus.m */, 96BF615C1A35C1C8004CF2B3 /* ARTTypes.h */, 96BF615D1A35C1C8004CF2B3 /* ARTTypes.m */, - EB503C871C7E4A090053AF00 /* ARTClientOptions+Private.h */, ); name = Types; sourceTree = ""; @@ -800,6 +812,12 @@ D785C4281E549E33008FEC05 /* ARTPushChannelSubscription.m */, D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */, D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */, + D7AE18C71E5B40C900478D82 /* ARTPushAdmin.h */, + D7AE18C81E5B40C900478D82 /* ARTPushAdmin.m */, + D7AE18CC1E5B40FE00478D82 /* ARTPushDeviceRegistrations.h */, + D7AE18CD1E5B40FE00478D82 /* ARTPushDeviceRegistrations.m */, + D7AE18D01E5B410F00478D82 /* ARTPushChannelSubscriptions.h */, + D7AE18D11E5B410F00478D82 /* ARTPushChannelSubscriptions.m */, D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */, D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */, ); @@ -884,8 +902,10 @@ 96A507A11A377AA50077CDF8 /* ARTPresenceMessage.h in Headers */, 850BFB4C1B79323C009D0ADD /* ARTPaginatedResult.h in Headers */, D746AE1E1BBB5207003ECEF8 /* ARTDataQuery+Private.h in Headers */, + D7AE18C91E5B40C900478D82 /* ARTPushAdmin.h in Headers */, D77394031C6F6FFE00F5478F /* ARTProtocolMessage+Private.h in Headers */, D746AE2F1BBBE7D7003ECEF8 /* ARTPaginatedResult+Private.h in Headers */, + D7AE18CE1E5B40FE00478D82 /* ARTPushDeviceRegistrations.h in Headers */, D746AE431BBC5CD0003ECEF8 /* ARTRealtimeChannel+Private.h in Headers */, D746AE251BBB611C003ECEF8 /* ARTChannel+Private.h in Headers */, D785C4291E549E33008FEC05 /* ARTPushChannelSubscription.h in Headers */, @@ -903,6 +923,7 @@ 96A507AD1A3780F60077CDF8 /* ARTJsonEncoder.h in Headers */, EB89D4041C61C1A4007FA5B7 /* ARTRestChannels.h in Headers */, 96A507951A370F860077CDF8 /* ARTStats.h in Headers */, + D7AE18D21E5B410F00478D82 /* ARTPushChannelSubscriptions.h in Headers */, D7D8F8251BC2C691009718F2 /* ARTTokenDetails.h in Headers */, D7D8F82B1BC2C706009718F2 /* ARTTokenRequest.h in Headers */, EB1AE0CC1C5C1EB200D62250 /* ARTEventEmitter+Private.h in Headers */, @@ -1217,6 +1238,7 @@ D75A3F1C1DDE5B62002A4AAD /* ARTGCD.m in Sources */, 1C2B0FFE1B136A6D00E3633C /* ARTPresenceMap.m in Sources */, 960D07941A45F1D800ED8C8C /* ARTCrypto.m in Sources */, + D7AE18CA1E5B40C900478D82 /* ARTPushAdmin.m in Sources */, D7D8F8221BC2BE16009718F2 /* ARTAuthOptions.m in Sources */, 1C55427D1B148306003068DB /* ARTStatus.m in Sources */, D785C42A1E549E33008FEC05 /* ARTPushChannelSubscription.m in Sources */, @@ -1241,7 +1263,9 @@ 96BF61651A35CDE1004CF2B3 /* ARTBaseMessage.m in Sources */, D7F1D3781BF4DE72001A4B5E /* ARTRealtimePresence.m in Sources */, D7B621911E4A6E0200684474 /* ARTPush.m in Sources */, + D7AE18D31E5B410F00478D82 /* ARTPushChannelSubscriptions.m in Sources */, 1CD8DCA01B1C7315007EAF36 /* ARTDefault.m in Sources */, + D7AE18CF1E5B40FE00478D82 /* ARTPushDeviceRegistrations.m in Sources */, D7B621951E4A6FE600684474 /* ARTDeviceDetails.m in Sources */, D7588AF41BFF91B800BB8279 /* ARTURLSessionServerTrust.m in Sources */, D746AE501BBD84E7003ECEF8 /* ARTChannelOptions.m in Sources */, diff --git a/Source/ARTEncoder.h b/Source/ARTEncoder.h index 68252c29b..c4861c5ef 100644 --- a/Source/ARTEncoder.h +++ b/Source/ARTEncoder.h @@ -68,12 +68,16 @@ ART_ASSUME_NONNULL_BEGIN // DeviceDetails - (art_nullable NSData *)encodeDeviceDetails:(ARTDeviceDetails *)deviceDetails; -- (art_nullable ARTDeviceDetails *)decodeDeviceDetails:(NSData *)data; +- (art_nullable ARTDeviceDetails *)decodeDeviceDetails:(NSData *)data error:(NSError * __autoreleasing *)error; +- (art_nullable NSArray *)decodeDevicesDetails:(NSData *)data error:(NSError * __autoreleasing *)error; // DevicePushDetails - (art_nullable NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails; +- (art_nullable ARTDevicePushDetails *)decodeDevicePushDetails:(NSData *)data error:(NSError * __autoreleasing *)error; -// Push Channel Subscriptions +// Push Channel Subscription +- (art_nullable NSData *)encodePushChannelSubscription:(ARTPushChannelSubscription *)channelSubscription; +- (art_nullable ARTPushChannelSubscription *)decodePushChannelSubscription:(NSData *)data error:(NSError * __autoreleasing *)error; - (art_nullable NSArray *)decodePushChannelSubscriptions:(NSData *)data error:(NSError * __autoreleasing *)error; // Others diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index d246865b2..a456f209a 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -143,14 +143,42 @@ - (NSData *)encodeDeviceDetails:(ARTDeviceDetails *)deviceDetails { return [self encode:[self deviceDetailsToDictionary:deviceDetails]]; } -- (ARTDeviceDetails *)decodeDeviceDetails:(NSData *)data { - return [self deviceDetailsFromDictionary:[self decodeDictionary:data] error:nil]; +- (ARTDeviceDetails *)decodeDeviceDetails:(NSData *)data error:(NSError * __autoreleasing *)error { + return [self deviceDetailsFromDictionary:[self decodeDictionary:data] error:error]; +} + +- (NSArray *)decodeDevicesDetails:(NSData *)data error:(NSError * __autoreleasing *)error { + return [self devicesDetailsFromArray:[self decodeArray:data] error:error]; +} + +- (NSArray *)devicesDetailsFromArray:(NSArray *)input error:(NSError * __autoreleasing *)error { + NSMutableArray *output = [NSMutableArray array]; + for (NSDictionary *item in input) { + ARTDeviceDetails *deviceDetails = [self deviceDetailsFromDictionary:item error:error]; + if (!deviceDetails) { + return nil; + } + [output addObject:deviceDetails]; + } + return output; } - (NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails { return [self encode:[self devicePushDetailsToDictionary:devicePushDetails]]; } +- (ARTDevicePushDetails *)decodeDevicePushDetails:(NSData *)data error:(NSError * __autoreleasing *)error { + return [self devicePushDetailsFromDictionary:[self decode:data] error:error]; +} + +- (NSData *)encodePushChannelSubscription:(ARTPushChannelSubscription *)channelSubscription { + return [self encode:[self pushChannelSubscriptionToDictionary:channelSubscription]]; +} + +- (ARTPushChannelSubscription *)decodePushChannelSubscription:(NSData *)data error:(NSError * __autoreleasing *)error { + return [self pushChannelSubscriptionFromDictionary:[self decodeDictionary:data] error:error]; +} + - (NSArray *)decodePushChannelSubscriptions:(NSData *)data error:(NSError * __autoreleasing *)error { return [self pushChannelSubscriptionsFromArray:[self decodeArray:data] error:error]; } @@ -167,6 +195,25 @@ - (NSData *)encodeDevicePushDetails:(ARTDevicePushDetails *)devicePushDetails { return output; } +- (NSDictionary *)pushChannelSubscriptionToDictionary:(ARTPushChannelSubscription *)channelSubscription { + NSMutableDictionary *output = [NSMutableDictionary dictionary]; + + if (channelSubscription.channelName) { + [output setObject:channelSubscription.channelName forKey:@"channel"]; + } + + if (channelSubscription.clientId) { + [output setObject:channelSubscription.clientId forKey:@"clientId"]; + } + + if (channelSubscription.deviceId) { + [output setObject:channelSubscription.deviceId forKey:@"deviceId"]; + } + + [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: pushChannelSubscriptionToDictionary %@", _rest, [_delegate formatAsString], output]; + return output; +} + - (ARTPushChannelSubscription *)pushChannelSubscriptionFromDictionary:(NSDictionary *)input error:(NSError * __autoreleasing *)error { [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: pushChannelSubscriptionFromDictionary %@", _rest, [_delegate formatAsString], input]; @@ -581,6 +628,19 @@ - (NSDictionary *)devicePushDetailsToDictionary:(ARTDevicePushDetails *)devicePu return dictionary; } +- (ARTDevicePushDetails *)devicePushDetailsFromDictionary:(NSDictionary *)input error:(NSError * __autoreleasing *)error { + [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: devicePushDetailsFromDictionary %@", _rest, [_delegate formatAsString], input]; + + if (![input isKindOfClass:[NSDictionary class]]) { + return nil; + } + + ARTDevicePushDetails *devicePushDetails = [[ARTDevicePushDetails alloc] init]; + devicePushDetails.state = ARTDevicePushStateFromStr([input artString:@"state"]); + + return devicePushDetails; +} + - (ARTProtocolMessage *)protocolMessageFromDictionary:(NSDictionary *)input { [_logger verbose:@"RS:%p ARTJsonLikeEncoder<%@>: protocolMessageFromDictionary %@", _rest, [_delegate formatAsString], input]; if (![input isKindOfClass:[NSDictionary class]]) { diff --git a/Source/ARTPush.m b/Source/ARTPush.m index b706c71cf..5c05f3ceb 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -169,7 +169,7 @@ - (void)newDevice:(ARTDeviceDetails *)deviceDetails { [_logger debug:__FILE__ line:__LINE__ message:@"device registration with request %@", request]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (response.statusCode == 201 /*Created*/) { - ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data]; + ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; self.device.updateToken = deviceDetails.updateToken; } else if (error) { @@ -203,7 +203,7 @@ - (void)updateDevice:(ARTDeviceDetails *)deviceDetails { [_logger debug:__FILE__ line:__LINE__ message:@"update device with request %@", request]; [_httpExecutor executeRequest:request completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (response.statusCode == 200 /*OK*/) { - ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data]; + ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; self.device.updateToken = deviceDetails.updateToken; } else if (error) { diff --git a/Source/ARTPushAdmin.h b/Source/ARTPushAdmin.h new file mode 100644 index 000000000..6e8a34a2b --- /dev/null +++ b/Source/ARTPushAdmin.h @@ -0,0 +1,28 @@ +// +// ARTPushAdmin.h +// Ably +// +// Created by Ricardo Pereira on 20/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import + +@class ARTPushDeviceRegistrations; +@class ARTPushChannelSubscriptions; + +@protocol ARTHTTPAuthenticatedExecutor; + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTPushAdmin : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)init:(id)httpExecutor; + +@property (nonatomic, readonly) ARTPushDeviceRegistrations* deviceRegistrations; +@property (nonatomic, readonly) ARTPushChannelSubscriptions* channelSubscriptions; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushAdmin.m b/Source/ARTPushAdmin.m new file mode 100644 index 000000000..abb879bec --- /dev/null +++ b/Source/ARTPushAdmin.m @@ -0,0 +1,27 @@ +// +// ARTPushAdmin.m +// Ably +// +// Created by Ricardo Pereira on 20/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTPushAdmin.h" +#import "ARTHttp.h" +#import "ARTPushDeviceRegistrations.h" +#import "ARTPushChannelSubscriptions.h" + +@implementation ARTPushAdmin { + id _httpExecutor; +} + +- (instancetype)init:(id)httpExecutor { + if (self = [super init]) { + _httpExecutor = httpExecutor; + _deviceRegistrations = [[ARTPushDeviceRegistrations alloc] init:httpExecutor]; + _channelSubscriptions = [[ARTPushChannelSubscriptions alloc] init:httpExecutor]; + } + return self; +} + +@end diff --git a/Source/ARTPushChannelSubscriptions.h b/Source/ARTPushChannelSubscriptions.h new file mode 100644 index 000000000..f5cb11e40 --- /dev/null +++ b/Source/ARTPushChannelSubscriptions.h @@ -0,0 +1,34 @@ +// +// ARTPushChannelSubscriptions.h +// Ably +// +// Created by Ricardo Pereira on 20/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import +#import "ARTTypes.h" + +@class ARTPushChannelSubscription; +@class ARTPaginatedResult; + +@protocol ARTHTTPAuthenticatedExecutor; + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTPushChannelSubscriptions : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)init:(id)httpExecutor; + +- (void)save:(ARTPushChannelSubscription *)channelSubscription callback:(void (^)(ARTErrorInfo *_Nullable))callback; + +- (void)listChannels:(void (^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; + +- (void)get:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; + +- (void)remove:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *_Nullable))callback; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushChannelSubscriptions.m b/Source/ARTPushChannelSubscriptions.m new file mode 100644 index 000000000..21833fb8f --- /dev/null +++ b/Source/ARTPushChannelSubscriptions.m @@ -0,0 +1,96 @@ +// +// ARTPushChannelSubscriptions.m +// Ably +// +// Created by Ricardo Pereira on 20/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTPushChannelSubscriptions.h" +#import "ARTHttp.h" +#import "ARTLog.h" +#import "ARTPaginatedResult+Private.h" +#import "ARTPushChannelSubscription.h" +#import "ARTClientOptions.h" +#import "ARTEncoder.h" +#import "ARTNSArray+ARTFunctional.h" + +@implementation ARTPushChannelSubscriptions { + id _httpExecutor; + __weak ARTLog* _logger; +} + +- (instancetype)init:(id)httpExecutor { + if (self = [super init]) { + _httpExecutor = httpExecutor; + _logger = [httpExecutor logger]; + } + return self; +} + +- (void)save:(ARTPushChannelSubscription *)channelSubscription callback:(void (^)(ARTErrorInfo *error))callback { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; + request.HTTPMethod = @"PUT"; + request.HTTPBody = [[_httpExecutor defaultEncoder] encodePushChannelSubscription:channelSubscription]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + + [_logger debug:__FILE__ line:__LINE__ message:@"save channel subscription with request %@", request]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + [_logger debug:__FILE__ line:__LINE__ message:@"channel subscription saved successfully"]; + } + else if (error) { + [_logger error:@"%@: save channel subscription failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + } + else { + [_logger error:@"%@: save channel subscription failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + } + }]; +} + +- (void)listChannels:(void (^)(ARTPaginatedResult * _Nullable, ARTErrorInfo *error))callback { + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; + request.HTTPMethod = @"GET"; + + ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data) { + return [[[_httpExecutor defaultEncoder] decodePushChannelSubscriptions:data error:nil] artMap:^NSString *(ARTPushChannelSubscription *item) { + return [(ARTPushChannelSubscription *)item channelName]; + }]; + }; + [ARTPaginatedResult executePaginated:_httpExecutor withRequest:request andResponseProcessor:responseProcessor callback:callback]; +} + +- (void)get:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *result, ARTErrorInfo *error))callback { + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; + components.queryItems = [params asURLQueryItems]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; + request.HTTPMethod = @"GET"; + + ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data) { + return [[_httpExecutor defaultEncoder] decodePushChannelSubscriptions:data error:nil]; + }; + [ARTPaginatedResult executePaginated:_httpExecutor withRequest:request andResponseProcessor:responseProcessor callback:callback]; +} + +- (void)remove:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *error))callback { + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; + components.queryItems = [params asURLQueryItems]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; + request.HTTPMethod = @"DELETE"; + + [_logger debug:__FILE__ line:__LINE__ message:@"remove channel subscription with request %@", request]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + [_logger debug:__FILE__ line:__LINE__ message:@"%@: channel subscription removed successfully", NSStringFromClass(self.class)]; + } + else if (error) { + [_logger error:@"%@: remove channel subscription failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + } + else { + [_logger error:@"%@: remove channel subscription failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + } + }]; +} + +@end diff --git a/Source/ARTPushDeviceRegistrations.h b/Source/ARTPushDeviceRegistrations.h new file mode 100644 index 000000000..9aacbd380 --- /dev/null +++ b/Source/ARTPushDeviceRegistrations.h @@ -0,0 +1,32 @@ +// +// ARTPushDeviceRegistrations.h +// Ably +// +// Created by Ricardo Pereira on 20/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import +#import "ARTTypes.h" + +@class ARTDeviceDetails; +@class ARTPaginatedResult; + +@protocol ARTHTTPAuthenticatedExecutor; + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTPushDeviceRegistrations : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)init:(id)httpExecutor; + +- (void)save:(ARTDeviceDetails *)deviceDetails callback:(void (^)(ARTErrorInfo *_Nullable))callback; + +- (void)get:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; + +- (void)remove:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *_Nullable))callback; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushDeviceRegistrations.m b/Source/ARTPushDeviceRegistrations.m new file mode 100644 index 000000000..9b04106f1 --- /dev/null +++ b/Source/ARTPushDeviceRegistrations.m @@ -0,0 +1,98 @@ +// +// ARTPushDeviceRegistrations.m +// Ably +// +// Created by Ricardo Pereira on 20/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTPushDeviceRegistrations.h" +#import "ARTHttp.h" +#import "ARTLog.h" +#import "ARTPaginatedResult+Private.h" +#import "ARTDeviceDetails.h" +#import "ARTDevicePushDetails.h" +#import "ARTClientOptions.h" +#import "ARTEncoder.h" + +@implementation ARTPushDeviceRegistrations { + id _httpExecutor; + __weak ARTLog* _logger; +} + +- (instancetype)init:(id)httpExecutor { + if (self = [super init]) { + _httpExecutor = httpExecutor; + _logger = [httpExecutor logger]; + } + return self; +} + +- (void)save:(ARTDeviceDetails *)deviceDetails callback:(void (^)(ARTErrorInfo *error))callback { + if (!deviceDetails.updateToken) { + [_logger error:@"%@: update token is missing", NSStringFromClass(self.class)]; + return; + } + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[[NSURL URLWithString:@"/push/deviceRegistrations"] URLByAppendingPathComponent:deviceDetails.id]]; + NSData *tokenData = [deviceDetails.updateToken dataUsingEncoding:NSUTF8StringEncoding]; + NSString *tokenBase64 = [tokenData base64EncodedStringWithOptions:0]; + [request setValue:[NSString stringWithFormat:@"Bearer %@", tokenBase64] forHTTPHeaderField:@"Authorization"]; + request.HTTPMethod = @"PUT"; + request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ + @"push": @{ + @"metadata": @{ + @"deviceToken": deviceDetails.push.deviceToken, + } + } + }]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + + [_logger debug:__FILE__ line:__LINE__ message:@"save device with request %@", request]; + [_httpExecutor executeRequest:request completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + [_logger debug:__FILE__ line:__LINE__ message:@"%@: save device successfully", NSStringFromClass(self.class)]; + ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; + deviceDetails.updateToken = deviceDetails.updateToken; + } + else if (error) { + [_logger error:@"%@: save device failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + } + else { + [_logger error:@"%@: save device failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + } + }]; +} + +- (void)get:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *result, ARTErrorInfo *error))callback { + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"] resolvingAgainstBaseURL:NO]; + components.queryItems = [params asURLQueryItems]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; + request.HTTPMethod = @"GET"; + + ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data) { + return [[_httpExecutor defaultEncoder] decodeDevicesDetails:data error:nil]; + }; + [ARTPaginatedResult executePaginated:_httpExecutor withRequest:request andResponseProcessor:responseProcessor callback:callback]; +} + +- (void)remove:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *error))callback { + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"] resolvingAgainstBaseURL:NO]; + components.queryItems = [params asURLQueryItems]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; + request.HTTPMethod = @"DELETE"; + + [_logger debug:__FILE__ line:__LINE__ message:@"remove device with request %@", request]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + [_logger debug:__FILE__ line:__LINE__ message:@"%@: remove device successfully", NSStringFromClass(self.class)]; + } + else if (error) { + [_logger error:@"%@: remove device failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + } + else { + [_logger error:@"%@: remove device failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + } + }]; +} + +@end diff --git a/Source/ARTRealtimeChannel.m b/Source/ARTRealtimeChannel.m index 71d69184e..cef62ab15 100644 --- a/Source/ARTRealtimeChannel.m +++ b/Source/ARTRealtimeChannel.m @@ -24,7 +24,7 @@ #import "ARTNSArray+ARTFunctional.h" #import "ARTStatus.h" #import "ARTDefault.h" -#import "ARTRest.h" +#import "ARTRest+Private.h" #import "ARTClientOptions.h" #import "ARTPushChannel.h" diff --git a/Source/ARTTypes.h b/Source/ARTTypes.h index f0c19e835..86adc853d 100644 --- a/Source/ARTTypes.h +++ b/Source/ARTTypes.h @@ -126,4 +126,8 @@ NSString *generateNonce(); @interface NSDictionary (ARTJsonCompatible) @end +@interface NSDictionary (NSURLQueryItem) +@property (nonatomic, readonly) NSArray *asURLQueryItems; +@end + ART_ASSUME_NONNULL_END diff --git a/Source/ARTTypes.m b/Source/ARTTypes.m index d2a955b9d..ca237b306 100644 --- a/Source/ARTTypes.m +++ b/Source/ARTTypes.m @@ -128,3 +128,18 @@ - (NSDictionary *)toJSON:(NSError *__art_nullable *__art_nullable)error { } @end + +@implementation NSDictionary (NSURLQueryItem) + +- (NSArray *)asURLQueryItems { + NSMutableArray *items = [NSMutableArray new]; + for (id key in [self allKeys]) { + id value = [self valueForKey:key]; + if ([key isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) { + [items addObject:[NSURLQueryItem queryItemWithName:key value:value]]; + } + } + return items; +} + +@end diff --git a/Source/Ably.h b/Source/Ably.h index c7ba92cbb..cf454c801 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -60,6 +60,9 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import "ARTPush.h" #import "ARTPushChannel.h" #import "ARTPushChannelSubscription.h" +#import "ARTPushAdmin.h" +#import "ARTPushChannelSubscriptions.h" +#import "ARTPushDeviceRegistrations.h" #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" From 1e51f3e7112c669a141a4bc0fba65a9fc9517848 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Mon, 20 Feb 2017 20:18:40 +0000 Subject: [PATCH 25/46] Update ULID pod --- Ably.podspec | 2 +- Podfile | 2 +- Podfile.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Ably.podspec b/Ably.podspec index f739011b3..018ae52a8 100644 --- a/Ably.podspec +++ b/Ably.podspec @@ -18,5 +18,5 @@ Pod::Spec.new do |s| s.module_map = 'Source/Ably.modulemap' s.dependency 'SocketRocket', '0.5.1' s.dependency 'msgpack', '0.1.8' - s.dependency 'ULID', '1.0.1' + s.dependency 'ULID', '1.0.2' end diff --git a/Podfile b/Podfile index 374df7bf7..f9087189b 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ podspec :path => 'Ably.podspec' def project_pods pod 'SocketRocket', '0.5.1' pod 'msgpack', '0.1.8' - pod 'ULID', '1.0.1' + pod 'ULID', '1.0.2' end def test_pods diff --git a/Podfile.lock b/Podfile.lock index 1285c372c..61f3fc847 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -5,7 +5,7 @@ PODS: - Quick (0.9.3) - SocketRocket (0.5.1) - SwiftyJSON (2.4.0) - - ULID (1.0.1) + - ULID (1.0.2) DEPENDENCIES: - Aspects @@ -14,7 +14,7 @@ DEPENDENCIES: - Quick (= 0.9.3) - SocketRocket (= 0.5.1) - SwiftyJSON (= 2.4.0) - - ULID (= 1.0.1) + - ULID (= 1.0.2) SPEC CHECKSUMS: Aspects: 7595ba96a6727a58ebcbfc954497fc5d2fdde546 @@ -23,8 +23,8 @@ SPEC CHECKSUMS: Quick: 13a2a2b19a5d8e3ed4fd0c36ee46597fd77ebf71 SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531 SwiftyJSON: 96918c1bf505efa50c4f72957018dd3452090c9c - ULID: 3caaee729fb720b436bd0bff552db76efd7878b9 + ULID: fcabaa95746b670beb80c029beb3372da2f729bd -PODFILE CHECKSUM: 419141b5bf70d2352bf50b46b3d0b9b2cc9e7ccc +PODFILE CHECKSUM: 411dc0283578f56e1dca99bdd9846aa278632d93 COCOAPODS: 1.1.1 From 6a5b42f7add6f0186e89c9bd7bb9ce3cd78bfa70 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 22 Feb 2017 09:54:10 +0000 Subject: [PATCH 26/46] Remove warnings --- Source/ARTPushRecipient.h | 47 ------------------------ Source/ARTPushRecipient.m | 76 --------------------------------------- 2 files changed, 123 deletions(-) delete mode 100644 Source/ARTPushRecipient.h delete mode 100644 Source/ARTPushRecipient.m diff --git a/Source/ARTPushRecipient.h b/Source/ARTPushRecipient.h deleted file mode 100644 index 93ebda984..000000000 --- a/Source/ARTPushRecipient.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// ARTPushRecipient.h -// Ably -// -// Created by Ricardo Pereira on 13/02/2017. -// Copyright © 2017 Ably. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ARTPushRecipient -@property (nonatomic, readonly) NSDictionary *recipient; -@end - -// ClientId -@interface ARTPushRecipientClientId : NSObject -@property (nonatomic) NSString *clientId; -@end - -// DeviceId -@interface ARTPushRecipientDeviceId : NSObject -@property (nonatomic) NSString *deviceId; -@end - -// APNs -@interface ARTPushRecipientAPNDevice : NSObject -@property (nonatomic) NSString *deviceToken; -@end - -// GCM -@interface ARTPushRecipientGCMDevice : NSObject -@property (nonatomic) NSString *registrationToken; -@end - -// FCM -@interface ARTPushRecipientFCMDevice : ARTPushRecipientGCMDevice -@end - -// Web -@interface ARTPushRecipientWebDevice : NSObject -@property (nonatomic) NSString *targetURL; -@property (nonatomic) NSString *encryptionKey; -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushRecipient.m b/Source/ARTPushRecipient.m deleted file mode 100644 index ff29e9bd4..000000000 --- a/Source/ARTPushRecipient.m +++ /dev/null @@ -1,76 +0,0 @@ -// -// ARTPushRecipient.m -// Ably -// -// Created by Ricardo Pereira on 13/02/2017. -// Copyright © 2017 Ably. All rights reserved. -// - -#import "ARTPushRecipient.h" - -#pragma mark ClientId -@implementation ARTPushRecipientClientId - -- (NSDictionary *)recipient { - return @{ @"clientId": self.clientId }; -} - -@end - -#pragma mark DeviceId -@implementation ARTPushRecipientDeviceId - -- (NSDictionary *)recipient { - return @{ @"deviceId": self.deviceId }; -} - -@end - -#pragma mark APNs -@implementation ARTPushRecipientAPNDevice - -- (NSDictionary *)recipient { - return @{ - @"transportType": @"apns", - @"deviceToken": self.deviceToken, - }; -} - -@end - -#pragma mark GCM -@implementation ARTPushRecipientGCMDevice - -- (NSDictionary *)recipient { - return @{ - @"transportType": @"gcm", - @"registrationToken": self.registrationToken, - }; -} - -@end - -#pragma mark FCM -@implementation ARTPushRecipientFCMDevice - -- (NSDictionary *)recipient { - return @{ - @"transportType": @"fcm", - @"registrationToken": self.registrationToken, - }; -} - -@end - -#pragma mark Web -@implementation ARTPushRecipientWebDevice - -- (NSDictionary *)recipient { - return @{ - @"transportType": @"web", - @"targetUrl": self.targetURL, - @"encryptionKey": self.encryptionKey, - }; -} - -@end From 4ab1d74bae31ce71133f603e166d23d8801e7516 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 22 Feb 2017 10:00:38 +0000 Subject: [PATCH 27/46] JSON encoder: normalize deviceToken hex string --- Source/ARTJsonLikeEncoder.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index a456f209a..f4d671f04 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -620,8 +620,11 @@ - (NSDictionary *)devicePushDetailsToDictionary:(ARTDevicePushDetails *)devicePu dictionary[@"transportType"] = devicePushDetails.transportType; if (devicePushDetails.deviceToken) { + // HEX string, i.e.: <12ce7dda 8032c423 8f8bd40f 3484e5bb f4698da5 8b7fdf8d 5c55e0a2 XXXXXXXX> + // Normalizing token by removing symbols and spaces, i.e.: 12ce7dda8032c4238f8bd40f3484e5bbf4698da58b7fdf8d5c55e0a2XXXXXXXX + NSString *token = [[[devicePushDetails.deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""]; dictionary[@"metadata"] = @{ - @"deviceToken": [devicePushDetails.deviceToken description], // HEX string + @"deviceToken": token, }; } From 25a0510989aa0f079626c4ec16775e583604333b Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 22 Feb 2017 10:07:14 +0000 Subject: [PATCH 28/46] Fix: Push.publish request body --- Source/ARTPush.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 5c05f3ceb..4e4bb9799 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -57,10 +57,10 @@ - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/publish"]]; request.HTTPMethod = @"POST"; - request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ - @"recipient": recipient, - @"push": jsonObject, - }]; + NSMutableDictionary *body = [NSMutableDictionary dictionary]; + [body setObject:recipient forKey:@"recipient"]; + [body addEntriesFromDictionary:jsonObject]; + request.HTTPBody = [[_httpExecutor defaultEncoder] encode:body]; [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"push notification to a single device %@", request]; From 002c8823d5ae4f3946b0cb20ea77f73f92c5ac41 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 22 Feb 2017 10:37:40 +0000 Subject: [PATCH 29/46] Update device forms --- Source/ARTDeviceDetails.m | 25 +++++++++++++++++++++++-- Source/ARTPush.h | 6 +++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index b9bea7ae9..814a15f88 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -11,7 +11,21 @@ #import NSString *const ARTDevicePlatform = @"ios"; -NSString *const ARTDeviceFormFactor = @"mobile"; + +#if TARGET_OS_IOS +#import +NSString *const ARTDeviceFormFactor = @"phone"; +#elif TARGET_OS_TV +NSString *const ARTDeviceFormFactor = @"tv"; +#elif TARGET_OS_WATCH +NSString *const ARTDeviceFormFactor = @"watch"; +#elif TARGET_OS_SIMULATOR +NSString *const ARTDeviceFormFactor = @"simulator"; +#elif TARGET_OS_MAC +NSString *const ARTDeviceFormFactor = @"desktop"; +#else +NSString *const ARTDeviceFormFactor = @"embedded"; +#endif NSString *const ARTDeviceIdKey = @"ARTDeviceId"; @@ -40,7 +54,14 @@ - (NSString *)platform { } - (NSString *)formFactor { - return ARTDeviceFormFactor; + switch (UI_USER_INTERFACE_IDIOM()) { + case UIUserInterfaceIdiomPad: + return @"tablet"; + case UIUserInterfaceIdiomCarPlay: + return @"car"; + default: + return ARTDeviceFormFactor; + } } @end diff --git a/Source/ARTPush.h b/Source/ARTPush.h index c2795fbf2..1ab2fc81e 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -23,7 +23,7 @@ typedef ARTJsonObject ARTPushRecipient; #pragma mark ARTPushNotifications interface -#ifdef TARGET_OS_IPHONE //iOS system +#ifdef TARGET_OS_IOS @protocol ARTPushNotifications - (void)didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken; - (void)didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error; @@ -35,7 +35,7 @@ typedef ARTJsonObject ARTPushRecipient; NS_ASSUME_NONNULL_BEGIN -#ifdef TARGET_OS_IPHONE //iOS system +#ifdef TARGET_OS_IOS @interface ARTPush : NSObject #else @interface ARTPush : NSObject @@ -49,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN /// Publish a push notification. - (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject; -#ifdef TARGET_OS_IPHONE //iOS system +#ifdef TARGET_OS_IOS /// Register a device, including the information necessary to deliver push notifications to it. - (void)activate; - (void)activateWithRegisterCallback:(void (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable, void (^ _Nullable)(ARTUpdateToken * _Nullable, ARTErrorInfo * _Nullable)))registerCallback; From 0733ca8ca47cc0f04fddb867058d983c40641aae Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 22 Feb 2017 20:52:52 +0000 Subject: [PATCH 30/46] Activation State Machine: init --- Ably.xcodeproj/project.pbxproj | 52 ++++++++-- Source/ARTPush.m | 11 ++ Source/ARTPushActivationEvent.h | 40 ++++++++ Source/ARTPushActivationEvent.m | 56 ++++++++++ Source/ARTPushActivationState.h | 49 +++++++++ Source/ARTPushActivationState.m | 136 +++++++++++++++++++++++++ Source/ARTPushActivationStateMachine.h | 28 +++++ Source/ARTPushActivationStateMachine.m | 57 +++++++++++ Source/ARTTypes.h | 8 +- Source/ARTTypes.m | 21 +++- Source/Ably.h | 3 + 11 files changed, 453 insertions(+), 8 deletions(-) create mode 100644 Source/ARTPushActivationEvent.h create mode 100644 Source/ARTPushActivationEvent.m create mode 100644 Source/ARTPushActivationState.h create mode 100644 Source/ARTPushActivationState.m create mode 100644 Source/ARTPushActivationStateMachine.h create mode 100644 Source/ARTPushActivationStateMachine.m diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 3cd03073f..c47fb3b5b 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -95,6 +95,12 @@ D70EAAEE1BC3376200CD8B9E /* ARTRestChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D70EAAEC1BC3376200CD8B9E /* ARTRestChannel.m */; }; D714A63E1C74D4B2002F2CA0 /* NSObject+TestSuite.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714A63D1C74D4B2002F2CA0 /* NSObject+TestSuite.swift */; }; D714A6401C75F0C5002F2CA0 /* ARTWebSocketTransport+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D714A63F1C75F0C5002F2CA0 /* ARTWebSocketTransport+Private.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D71966E41E5DF360000974DD /* ARTPushActivationStateMachine.h in Headers */ = {isa = PBXBuildFile; fileRef = D71966E21E5DF360000974DD /* ARTPushActivationStateMachine.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D71966E51E5DF360000974DD /* ARTPushActivationStateMachine.m in Sources */ = {isa = PBXBuildFile; fileRef = D71966E31E5DF360000974DD /* ARTPushActivationStateMachine.m */; }; + D71966EA1E5E006E000974DD /* ARTPushActivationState.h in Headers */ = {isa = PBXBuildFile; fileRef = D71966E81E5E006E000974DD /* ARTPushActivationState.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D71966EB1E5E006E000974DD /* ARTPushActivationState.m in Sources */ = {isa = PBXBuildFile; fileRef = D71966E91E5E006E000974DD /* ARTPushActivationState.m */; }; + D71966EE1E5E0081000974DD /* ARTPushActivationEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = D71966EC1E5E0081000974DD /* ARTPushActivationEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D71966EF1E5E0081000974DD /* ARTPushActivationEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = D71966ED1E5E0081000974DD /* ARTPushActivationEvent.m */; }; D71D30041C5F7B2F002115B0 /* RealtimeClientChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71D30031C5F7B2F002115B0 /* RealtimeClientChannels.swift */; }; D72304701BB72CED00F1ABDA /* RealtimeClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723046F1BB72CED00F1ABDA /* RealtimeClient.swift */; }; D72768211C9C19040022F8B2 /* RestClientPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72768201C9C19040022F8B2 /* RestClientPresence.swift */; }; @@ -345,6 +351,12 @@ D70EAAEC1BC3376200CD8B9E /* ARTRestChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRestChannel.m; sourceTree = ""; }; D714A63D1C74D4B2002F2CA0 /* NSObject+TestSuite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+TestSuite.swift"; sourceTree = ""; }; D714A63F1C75F0C5002F2CA0 /* ARTWebSocketTransport+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARTWebSocketTransport+Private.h"; sourceTree = ""; }; + D71966E21E5DF360000974DD /* ARTPushActivationStateMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPushActivationStateMachine.h; sourceTree = ""; }; + D71966E31E5DF360000974DD /* ARTPushActivationStateMachine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushActivationStateMachine.m; sourceTree = ""; }; + D71966E81E5E006E000974DD /* ARTPushActivationState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPushActivationState.h; sourceTree = ""; }; + D71966E91E5E006E000974DD /* ARTPushActivationState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushActivationState.m; sourceTree = ""; }; + D71966EC1E5E0081000974DD /* ARTPushActivationEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPushActivationEvent.h; sourceTree = ""; }; + D71966ED1E5E0081000974DD /* ARTPushActivationEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPushActivationEvent.m; sourceTree = ""; }; D71D30031C5F7B2F002115B0 /* RealtimeClientChannels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClientChannels.swift; sourceTree = ""; }; D723046F1BB72CED00F1ABDA /* RealtimeClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClient.swift; sourceTree = ""; }; D72768201C9C19040022F8B2 /* RestClientPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestClientPresence.swift; sourceTree = ""; }; @@ -636,6 +648,32 @@ name = "Supporting Files"; sourceTree = ""; }; + D71966E61E5DFFB2000974DD /* Admin */ = { + isa = PBXGroup; + children = ( + D7AE18C71E5B40C900478D82 /* ARTPushAdmin.h */, + D7AE18C81E5B40C900478D82 /* ARTPushAdmin.m */, + D7AE18CC1E5B40FE00478D82 /* ARTPushDeviceRegistrations.h */, + D7AE18CD1E5B40FE00478D82 /* ARTPushDeviceRegistrations.m */, + D7AE18D01E5B410F00478D82 /* ARTPushChannelSubscriptions.h */, + D7AE18D11E5B410F00478D82 /* ARTPushChannelSubscriptions.m */, + ); + name = Admin; + sourceTree = ""; + }; + D71966E71E5DFFC6000974DD /* Activation State Machine */ = { + isa = PBXGroup; + children = ( + D71966E21E5DF360000974DD /* ARTPushActivationStateMachine.h */, + D71966E31E5DF360000974DD /* ARTPushActivationStateMachine.m */, + D71966E81E5E006E000974DD /* ARTPushActivationState.h */, + D71966E91E5E006E000974DD /* ARTPushActivationState.m */, + D71966EC1E5E0081000974DD /* ARTPushActivationEvent.h */, + D71966ED1E5E0081000974DD /* ARTPushActivationEvent.m */, + ); + name = "Activation State Machine"; + sourceTree = ""; + }; D746AE301BBC299D003ECEF8 /* Rest */ = { isa = PBXGroup; children = ( @@ -812,14 +850,10 @@ D785C4281E549E33008FEC05 /* ARTPushChannelSubscription.m */, D7B621921E4A6FE600684474 /* ARTDeviceDetails.h */, D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */, - D7AE18C71E5B40C900478D82 /* ARTPushAdmin.h */, - D7AE18C81E5B40C900478D82 /* ARTPushAdmin.m */, - D7AE18CC1E5B40FE00478D82 /* ARTPushDeviceRegistrations.h */, - D7AE18CD1E5B40FE00478D82 /* ARTPushDeviceRegistrations.m */, - D7AE18D01E5B410F00478D82 /* ARTPushChannelSubscriptions.h */, - D7AE18D11E5B410F00478D82 /* ARTPushChannelSubscriptions.m */, D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */, D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */, + D71966E71E5DFFC6000974DD /* Activation State Machine */, + D71966E61E5DFFB2000974DD /* Admin */, ); name = Push; sourceTree = ""; @@ -865,6 +899,8 @@ EB8AC6431C6515ED002ABA92 /* ARTTokenParams+Private.h in Headers */, D75A3F1B1DDE5B62002A4AAD /* ARTGCD.h in Headers */, D746AE1D1BBB5207003ECEF8 /* ARTDataQuery.h in Headers */, + D71966E41E5DF360000974DD /* ARTPushActivationStateMachine.h in Headers */, + D71966EA1E5E006E000974DD /* ARTPushActivationState.h in Headers */, EB89D40F1C62303E007FA5B7 /* ARTConnection+Private.h in Headers */, D746AE281BBB61C9003ECEF8 /* ARTPresence.h in Headers */, EB89D4091C61C5ED007FA5B7 /* ARTRealtimeChannels.h in Headers */, @@ -901,6 +937,7 @@ 96BF61701A35FB7C004CF2B3 /* ARTAuth.h in Headers */, 96A507A11A377AA50077CDF8 /* ARTPresenceMessage.h in Headers */, 850BFB4C1B79323C009D0ADD /* ARTPaginatedResult.h in Headers */, + D71966EE1E5E0081000974DD /* ARTPushActivationEvent.h in Headers */, D746AE1E1BBB5207003ECEF8 /* ARTDataQuery+Private.h in Headers */, D7AE18C91E5B40C900478D82 /* ARTPushAdmin.h in Headers */, D77394031C6F6FFE00F5478F /* ARTProtocolMessage+Private.h in Headers */, @@ -1244,6 +1281,7 @@ D785C42A1E549E33008FEC05 /* ARTPushChannelSubscription.m in Sources */, D7B17EE41C07208B00A6958E /* ARTConnectionDetails.m in Sources */, 96BF61591A35B52C004CF2B3 /* ARTHttp.m in Sources */, + D71966EB1E5E006E000974DD /* ARTPushActivationState.m in Sources */, 1C578E201B3435CA00EF46EC /* ARTFallback.m in Sources */, 96A507B61A37881C0077CDF8 /* ARTNSDate+ARTUtil.m in Sources */, 850BFB4D1B79323C009D0ADD /* ARTPaginatedResult.m in Sources */, @@ -1259,6 +1297,7 @@ D7D29B421BE3DEB300374295 /* ARTConnection.m in Sources */, 96BF61711A35FB7C004CF2B3 /* ARTAuth.m in Sources */, 96E408441A38939E00087F77 /* ARTProtocolMessage.m in Sources */, + D71966E51E5DF360000974DD /* ARTPushActivationStateMachine.m in Sources */, EB9121401CA0AD8200BA0A40 /* ARTMsgPackEncoder.m in Sources */, 96BF61651A35CDE1004CF2B3 /* ARTBaseMessage.m in Sources */, D7F1D3781BF4DE72001A4B5E /* ARTRealtimePresence.m in Sources */, @@ -1276,6 +1315,7 @@ D746AE291BBB61C9003ECEF8 /* ARTPresence.m in Sources */, 96A507BE1A3791490077CDF8 /* ARTRealtime.m in Sources */, 967A43221A39AEAF00E4CE23 /* ARTNSArray+ARTFunctional.m in Sources */, + D71966EF1E5E0081000974DD /* ARTPushActivationEvent.m in Sources */, 96A507A61A377DE90077CDF8 /* ARTNSDictionary+ARTDictionaryUtil.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 4e4bb9799..7c688086c 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -14,6 +14,7 @@ #import "ARTJsonEncoder.h" #import "ARTJsonLikeEncoder.h" #import "ARTEventEmitter.h" +#import "ARTPushActivationStateMachine.h" typedef NS_ENUM(NSUInteger, ARTPushState) { ARTPushStateDeactivated, @@ -54,6 +55,16 @@ - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [_logger error:@"ARTPush: device token not received (%@)", [error localizedDescription]]; } ++ (ARTPushActivationStateMachine *)activationMachine { + static dispatch_once_t once; + static id activationMachineInstance; + dispatch_once(&once, ^{ + // Error: ARTPush.m:62:81: Instance variable '_httpExecutor' accessed in class method + //activationMachineInstance = [[ARTPushActivationStateMachine alloc] init:_httpExecutor]; + }); + return activationMachineInstance; +} + - (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/publish"]]; request.HTTPMethod = @"POST"; diff --git a/Source/ARTPushActivationEvent.h b/Source/ARTPushActivationEvent.h new file mode 100644 index 000000000..a7cf19001 --- /dev/null +++ b/Source/ARTPushActivationEvent.h @@ -0,0 +1,40 @@ +// +// ARTPushActivationEvent.h +// Ably +// +// Created by Ricardo Pereira on 22/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import + +@interface ARTPushActivationEvent : NSObject + +@end + +@interface ARTPushActivationCalledActivateEvent : ARTPushActivationEvent +@end + +@interface ARTPushActivationCalledDeactivateEvent : ARTPushActivationEvent +@end + +@interface ARTPushActivationGotPushDeviceDetailsEvent : ARTPushActivationEvent +@end + +@interface ARTPushActivationGotUpdateTokenEvent : ARTPushActivationEvent +@end + +@interface ARTPushActivationGettingUpdateTokenFailedEvent : ARTPushActivationEvent +@end + +@interface ARTPushActivationRegistrationUpdatedEvent : ARTPushActivationEvent +@end + +@interface ARTPushActivationUpdatingRegistrationFailedEvent : ARTPushActivationEvent +@end + +@interface ARTPushActivationDeregisteredEvent : ARTPushActivationEvent +@end + +@interface ARTPushActivationDeregistrationFailedEvent : ARTPushActivationEvent +@end diff --git a/Source/ARTPushActivationEvent.m b/Source/ARTPushActivationEvent.m new file mode 100644 index 000000000..90076ef1f --- /dev/null +++ b/Source/ARTPushActivationEvent.m @@ -0,0 +1,56 @@ +// +// ARTPushActivationEvent.m +// Ably +// +// Created by Ricardo Pereira on 22/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTPushActivationEvent.h" + +@implementation ARTPushActivationEvent + +- (id)copyWithZone:(NSZone *)zone { + // Implement NSCopying by retaining the original instead of creating a new copy when the class and its contents are immutable. + return self; +} + +#pragma mark - NSCoding + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + // Just to persist the class info, no properties +} + +@end + +@implementation ARTPushActivationCalledActivateEvent +@end + +@implementation ARTPushActivationCalledDeactivateEvent +@end + +@implementation ARTPushActivationGotPushDeviceDetailsEvent +@end + +@implementation ARTPushActivationGotUpdateTokenEvent +@end + +@implementation ARTPushActivationGettingUpdateTokenFailedEvent +@end + +@implementation ARTPushActivationRegistrationUpdatedEvent +@end + +@implementation ARTPushActivationUpdatingRegistrationFailedEvent +@end + +@implementation ARTPushActivationDeregisteredEvent +@end + +@implementation ARTPushActivationDeregistrationFailedEvent +@end diff --git a/Source/ARTPushActivationState.h b/Source/ARTPushActivationState.h new file mode 100644 index 000000000..9e7b52a9a --- /dev/null +++ b/Source/ARTPushActivationState.h @@ -0,0 +1,49 @@ +// +// ARTPushActivationState.h +// Ably +// +// Created by Ricardo Pereira on 22/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import + +@class ARTPushActivationStateMachine; +@class ARTPushActivationEvent; + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTPushActivationState : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithMachine:(ARTPushActivationStateMachine *)machine; + +- (nullable ARTPushActivationState *)transition:(ARTPushActivationEvent *)event; + +@end + +@interface ARTPushActivationNotActivatedState : ARTPushActivationState +@end + +@interface ARTPushActivationCalledActivateState : ARTPushActivationState +@end + +@interface ARTPushActivationWaitingForUpdateTokenState : ARTPushActivationState +@end + +@interface ARTPushActivationWaitingForPushDeviceDetailsState : ARTPushActivationState +@end + +@interface ARTPushActivationWaitingForNewPushDeviceDetailsState : ARTPushActivationState +@end + +@interface ARTPushActivationWaitingForRegistrationUpdateState : ARTPushActivationState +@end + +@interface ARTPushActivationWaitingForDeregistrationState : ARTPushActivationState +@end + +@interface ARTPushActivationAfterRegistrationUpdateFailedState : ARTPushActivationState +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m new file mode 100644 index 000000000..778e6a0ce --- /dev/null +++ b/Source/ARTPushActivationState.m @@ -0,0 +1,136 @@ +// +// ARTPushActivationState.m +// Ably +// +// Created by Ricardo Pereira on 22/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTPushActivationState.h" +#import "ARTPushActivationStateMachine.h" +#import "ARTPushActivationEvent.h" +#import "ARTLog.h" + +@interface ARTPushActivationState () + +@property (atomic, readonly) ARTPushActivationStateMachine *machine; + +@end + +@implementation ARTPushActivationState + +- (instancetype)initWithMachine:(ARTPushActivationStateMachine *)machine { + if (self = [super init]) { + _machine = machine; + } + return self; +} + +- (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + NSAssert(false, @"-[%s:%d %s] should always be overriden.", __FILE__, __LINE__, __FUNCTION__); + return nil; +} + +- (void)logEventTransition:(ARTPushActivationEvent *)event file:(const char *)file line:(NSUInteger)line { + [[self.machine logger] debug:__FILE__ line:__LINE__ message:@"%@ state: transitioning to %@ event", NSStringFromClass(self.class), NSStringFromClass(event.class)]; +} + +- (id)copyWithZone:(NSZone *)zone { + // Implement NSCopying by retaining the original instead of creating a new copy when the class and its contents are immutable. + return self; +} + +#pragma mark - NSCoding + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + // Just to persist the class info, no properties +} + +@end + + +#pragma mark - Activation States + +@implementation ARTPushActivationNotActivatedState + +- (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + if ([event isKindOfClass:[ARTPushActivationCalledDeactivateEvent class]]) { + [self logEventTransition:event file:__FILE__ line:__LINE__]; + // TODO + } + else if ([event isKindOfClass:[ARTPushActivationCalledActivateEvent class]]) { + [self logEventTransition:event file:__FILE__ line:__LINE__]; + // TODO + } + return nil; +} + +@end + +@implementation ARTPushActivationCalledActivateState + +- (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + // TODO + return nil; +} + +@end + +@implementation ARTPushActivationWaitingForUpdateTokenState + +- (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + // TODO + return nil; +} + +@end + +@implementation ARTPushActivationWaitingForPushDeviceDetailsState + +- (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + // TODO + return nil; +} + +@end + +@implementation ARTPushActivationWaitingForNewPushDeviceDetailsState + +- (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + // TODO + return nil; +} + +@end + +@implementation ARTPushActivationWaitingForRegistrationUpdateState + +- (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + // TODO + return nil; +} + +@end + +@implementation ARTPushActivationWaitingForDeregistrationState + +- (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + // TODO + return nil; +} + +@end + +@implementation ARTPushActivationAfterRegistrationUpdateFailedState + +- (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + // TODO + return nil; +} + +@end diff --git a/Source/ARTPushActivationStateMachine.h b/Source/ARTPushActivationStateMachine.h new file mode 100644 index 000000000..44cce2e6a --- /dev/null +++ b/Source/ARTPushActivationStateMachine.h @@ -0,0 +1,28 @@ +// +// ARTPushActivationStateMachine.h +// Ably +// +// Created by Ricardo Pereira on 22/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import + +@class ARTLog; + +@protocol ARTHTTPAuthenticatedExecutor; + +NS_ASSUME_NONNULL_BEGIN + +// TODO: this should not be available to user's +@interface ARTPushActivationStateMachine : NSObject + +@property (nonatomic, readonly) id httpExecutor; +@property (nonatomic, readonly) ARTLog *logger; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)init:(id)httpExecutor; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m new file mode 100644 index 000000000..65188f3ce --- /dev/null +++ b/Source/ARTPushActivationStateMachine.m @@ -0,0 +1,57 @@ +// +// ARTPushActivationStateMachine.m +// Ably +// +// Created by Ricardo Pereira on 22/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTPushActivationStateMachine.h" +#import "ARTPushActivationEvent.h" +#import "ARTPushActivationState.h" +#import "ARTRest+Private.h" +#import "ARTLog.h" +#import "ARTJsonEncoder.h" +#import "ARTJsonLikeEncoder.h" +#import "ARTTypes.h" + +NSString *const ARTPushActivationCurrentStateKey = @"ARTPushActivationCurrentState"; +NSString *const ARTPushActivationPendingEventsKey = @"ARTPushActivationPendingEvents"; + +@implementation ARTPushActivationStateMachine { + ARTPushActivationState *_current; + NSMutableArray *_pendingEvents; +} + +- (instancetype)init:(id)httpExecutor { + if (self = [super init]) { + _httpExecutor = httpExecutor; + _logger = [_httpExecutor logger]; + // Unarquiving + NSData *stateData = [[NSUserDefaults standardUserDefaults] objectForKey:ARTPushActivationCurrentStateKey]; + _current = [NSKeyedUnarchiver unarchiveObjectWithData:stateData]; + if (!_current) { + _current = [ARTPushActivationNotActivatedState new]; + } + NSData *pendingEventsData = [[NSUserDefaults standardUserDefaults] objectForKey:ARTPushActivationPendingEventsKey]; + _pendingEvents = [NSKeyedUnarchiver unarchiveObjectWithData:pendingEventsData]; + if (!_pendingEvents) { + _pendingEvents = [NSMutableArray array]; + } + } + return self; +} + +- (void)handleEvent:(nonnull ARTPushActivationEvent *)event { + ARTPushActivationState *next = [_current transition:event]; + _current = next; + [self persist]; +} + +- (void)persist { + // Arquiving + [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_current] forKey:ARTPushActivationCurrentStateKey]; + [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_pendingEvents] forKey:ARTPushActivationPendingEventsKey]; +} + +@end diff --git a/Source/ARTTypes.h b/Source/ARTTypes.h index 86adc853d..621fce394 100644 --- a/Source/ARTTypes.h +++ b/Source/ARTTypes.h @@ -126,8 +126,14 @@ NSString *generateNonce(); @interface NSDictionary (ARTJsonCompatible) @end -@interface NSDictionary (NSURLQueryItem) +@interface NSDictionary (URLQueryItemAdditions) @property (nonatomic, readonly) NSArray *asURLQueryItems; @end +@interface NSMutableArray (QueueAdditions) +- (void)enqueue:(id)object; +- (id)dequeue; +- (id)peek; +@end + ART_ASSUME_NONNULL_END diff --git a/Source/ARTTypes.m b/Source/ARTTypes.m index ca237b306..91ae3428c 100644 --- a/Source/ARTTypes.m +++ b/Source/ARTTypes.m @@ -129,7 +129,7 @@ - (NSDictionary *)toJSON:(NSError *__art_nullable *__art_nullable)error { @end -@implementation NSDictionary (NSURLQueryItem) +@implementation NSDictionary (URLQueryItemAdditions) - (NSArray *)asURLQueryItems { NSMutableArray *items = [NSMutableArray new]; @@ -143,3 +143,22 @@ @implementation NSDictionary (NSURLQueryItem) } @end + +@implementation NSMutableArray (QueueAdditions) + +- (void)enqueue:(id)object { + [self addObject:object]; +} + +- (id)dequeue { + id item = [self firstObject]; + if (item) [self removeObjectAtIndex:0]; + return item; + +} + +- (id)peek { + return [self firstObject]; +} + +@end diff --git a/Source/Ably.h b/Source/Ably.h index cf454c801..020f9c6fb 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -60,6 +60,9 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import "ARTPush.h" #import "ARTPushChannel.h" #import "ARTPushChannelSubscription.h" +#import "ARTPushActivationStateMachine.h" +#import "ARTPushActivationEvent.h" +#import "ARTPushActivationState.h" #import "ARTPushAdmin.h" #import "ARTPushChannelSubscriptions.h" #import "ARTPushDeviceRegistrations.h" From 7663749fd19b39c05750d203d758c2955d818d04 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 28 Feb 2017 20:18:26 +0000 Subject: [PATCH 31/46] Activation State Machine: registration --- Ably.xcodeproj/project.pbxproj | 8 + Source/ARTDeviceDetails.h | 2 - Source/ARTDeviceDetails.m | 13 -- Source/ARTDevicePushDetails.h | 2 - Source/ARTDevicePushDetails.m | 2 - Source/ARTLocalDevice.h | 26 ++++ Source/ARTLocalDevice.m | 50 ++++++ Source/ARTPush.h | 40 +++-- Source/ARTPush.m | 178 +++------------------ Source/ARTPushActivationEvent.h | 33 ++-- Source/ARTPushActivationEvent.m | 35 +++-- Source/ARTPushActivationState.h | 23 ++- Source/ARTPushActivationState.m | 60 ++++++-- Source/ARTPushActivationStateMachine.h | 15 +- Source/ARTPushActivationStateMachine.m | 205 ++++++++++++++++++++++++- Source/Ably.h | 1 + 16 files changed, 448 insertions(+), 245 deletions(-) create mode 100644 Source/ARTLocalDevice.h create mode 100644 Source/ARTLocalDevice.m diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index c47fb3b5b..f00677878 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -171,6 +171,8 @@ D7D8F82D1BC2C706009718F2 /* ARTTokenParams.h in Headers */ = {isa = PBXBuildFile; fileRef = D7D8F8291BC2C706009718F2 /* ARTTokenParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7D8F82E1BC2C706009718F2 /* ARTTokenParams.m in Sources */ = {isa = PBXBuildFile; fileRef = D7D8F82A1BC2C706009718F2 /* ARTTokenParams.m */; }; D7DC8AF11C6AA0C8005AF165 /* ARTDefault+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D7DC8AF01C6A9FFC005AF165 /* ARTDefault+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + D7DEAFD11E65926D00D23F24 /* ARTLocalDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = D7DEAFCF1E65926D00D23F24 /* ARTLocalDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D7DEAFD21E65926D00D23F24 /* ARTLocalDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = D7DEAFD01E65926D00D23F24 /* ARTLocalDevice.m */; }; D7EBE5A31BE8391E0086E675 /* RealtimeClientConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EBE5A21BE8391E0086E675 /* RealtimeClientConnection.swift */; }; D7EBE5A41BE9F6900086E675 /* ARTConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D7D29B401BE3DD0600374295 /* ARTConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7F1D3731BF4DE07001A4B5E /* ARTRestPresence.h in Headers */ = {isa = PBXBuildFile; fileRef = D7F1D3711BF4DE07001A4B5E /* ARTRestPresence.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -431,6 +433,8 @@ D7D8F8291BC2C706009718F2 /* ARTTokenParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTTokenParams.h; sourceTree = ""; }; D7D8F82A1BC2C706009718F2 /* ARTTokenParams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTTokenParams.m; sourceTree = ""; }; D7DC8AF01C6A9FFC005AF165 /* ARTDefault+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ARTDefault+Private.h"; sourceTree = ""; }; + D7DEAFCF1E65926D00D23F24 /* ARTLocalDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTLocalDevice.h; sourceTree = ""; }; + D7DEAFD01E65926D00D23F24 /* ARTLocalDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTLocalDevice.m; sourceTree = ""; }; D7EBE5A21BE8391E0086E675 /* RealtimeClientConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClientConnection.swift; sourceTree = ""; }; D7F1D3711BF4DE07001A4B5E /* ARTRestPresence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRestPresence.h; sourceTree = ""; }; D7F1D3721BF4DE07001A4B5E /* ARTRestPresence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRestPresence.m; sourceTree = ""; }; @@ -852,6 +856,8 @@ D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */, D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */, D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */, + D7DEAFCF1E65926D00D23F24 /* ARTLocalDevice.h */, + D7DEAFD01E65926D00D23F24 /* ARTLocalDevice.m */, D71966E71E5DFFC6000974DD /* Activation State Machine */, D71966E61E5DFFB2000974DD /* Admin */, ); @@ -896,6 +902,7 @@ 967A43211A39AEAF00E4CE23 /* ARTNSArray+ARTFunctional.h in Headers */, D7C1B8791BBF5F810087B55F /* ARTAuth+Private.h in Headers */, 96A507A51A377DE90077CDF8 /* ARTNSDictionary+ARTDictionaryUtil.h in Headers */, + D7DEAFD11E65926D00D23F24 /* ARTLocalDevice.h in Headers */, EB8AC6431C6515ED002ABA92 /* ARTTokenParams+Private.h in Headers */, D75A3F1B1DDE5B62002A4AAD /* ARTGCD.h in Headers */, D746AE1D1BBB5207003ECEF8 /* ARTDataQuery.h in Headers */, @@ -1263,6 +1270,7 @@ D7B621991E4A762A00684474 /* ARTPushChannel.m in Sources */, EB89D40B1C61C6EA007FA5B7 /* ARTRealtimeChannels.m in Sources */, D746AE231BBB60EE003ECEF8 /* ARTChannel.m in Sources */, + D7DEAFD21E65926D00D23F24 /* ARTLocalDevice.m in Sources */, EB2D84FD1CD769B800F23CDA /* ARTOSReachability.m in Sources */, D746AE481BBD6FE9003ECEF8 /* ARTQueuedMessage.m in Sources */, D746AE3D1BBC5AE1003ECEF8 /* ARTRealtimeChannel.m in Sources */, diff --git a/Source/ARTDeviceDetails.h b/Source/ARTDeviceDetails.h index 413ef1ed5..f40c8511a 100644 --- a/Source/ARTDeviceDetails.h +++ b/Source/ARTDeviceDetails.h @@ -29,8 +29,6 @@ extern NSString *const ARTDeviceFormFactor; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithId:(ARTDeviceId *)deviceId; -+ (instancetype)fromLocalDevice; - @end NS_ASSUME_NONNULL_END diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index 814a15f88..bab269685 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -8,7 +8,6 @@ #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" -#import NSString *const ARTDevicePlatform = @"ios"; @@ -27,20 +26,8 @@ NSString *const ARTDeviceFormFactor = @"embedded"; #endif -NSString *const ARTDeviceIdKey = @"ARTDeviceId"; - @implementation ARTDeviceDetails -+ (instancetype)fromLocalDevice { - NSString *deviceId = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceIdKey]; - if (!deviceId) { - deviceId = [[ULID new] ulidString]; - [[NSUserDefaults standardUserDefaults] setObject:deviceId forKey:ARTDeviceIdKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; - } - return [[ARTDeviceDetails alloc] initWithId:deviceId]; -} - - (instancetype)initWithId:(ARTDeviceId *)deviceId { if (self = [super init]) { _id = deviceId; diff --git a/Source/ARTDevicePushDetails.h b/Source/ARTDevicePushDetails.h index 29dc46cee..5ed24bd47 100644 --- a/Source/ARTDevicePushDetails.h +++ b/Source/ARTDevicePushDetails.h @@ -12,8 +12,6 @@ NS_ASSUME_NONNULL_BEGIN -extern NSString *const ARTDeviceTokenKey; - extern NSString *const ARTDevicePushTransportType; typedef NS_ENUM(NSUInteger, ARTDevicePushState) { diff --git a/Source/ARTDevicePushDetails.m b/Source/ARTDevicePushDetails.m index d52a6e1ac..997302ab1 100644 --- a/Source/ARTDevicePushDetails.m +++ b/Source/ARTDevicePushDetails.m @@ -9,8 +9,6 @@ #import "ARTDevicePushDetails.h" #import "ARTPush.h" -NSString *const ARTDeviceTokenKey = @"ARTDeviceToken"; - NSString *const ARTDevicePushTransportType = @"apns"; ARTDevicePushState ARTDevicePushStateFromStr(NSString *value) { diff --git a/Source/ARTLocalDevice.h b/Source/ARTLocalDevice.h new file mode 100644 index 000000000..27ed937d8 --- /dev/null +++ b/Source/ARTLocalDevice.h @@ -0,0 +1,26 @@ +// +// ARTLocalDevice.h +// Ably +// +// Created by Ricardo Pereira on 28/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import +#import "ARTDeviceDetails.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTLocalDevice : ARTDeviceDetails + +@property (nonatomic, readonly) ARTDeviceToken *registrationToken; + +- (instancetype)init; ++ (ARTLocalDevice *)local; + +- (void)resetId; +- (void)resetUpdateToken:(void (^)(ARTErrorInfo * _Nullable))callback; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTLocalDevice.m b/Source/ARTLocalDevice.m new file mode 100644 index 000000000..06b24d882 --- /dev/null +++ b/Source/ARTLocalDevice.m @@ -0,0 +1,50 @@ +// +// ARTLocalDevice.m +// Ably +// +// Created by Ricardo Pereira on 28/02/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTLocalDevice.h" +#import "ARTDevicePushDetails.h" +#import "ARTPush.h" +#import + +@implementation ARTLocalDevice + +- (instancetype)init { + NSString *deviceId = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceIdKey]; + if (!deviceId) { + deviceId = [[ULID new] ulidString]; + [[NSUserDefaults standardUserDefaults] setObject:deviceId forKey:ARTDeviceIdKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } + if (self = [super initWithId:deviceId]) { + self.updateToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceUpdateTokenKey]; + } + return self; +} + ++ (ARTLocalDevice *)local { + static dispatch_once_t once; + static id localDevice; + dispatch_once(&once, ^{ + localDevice = [[ARTLocalDevice alloc] init]; + }); + return localDevice; +} + +- (ARTDeviceToken *)registrationToken { + return self.push.deviceToken; +} + +- (void)resetId { + +} + +- (void)resetUpdateToken:(void (^)(ARTErrorInfo *error))callback { + +} + +@end diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 1ab2fc81e..4a7488f90 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -9,7 +9,6 @@ #import #import "ARTTypes.h" -@class ARTRest; @class ARTDeviceDetails; @protocol ARTHTTPAuthenticatedExecutor; @@ -21,25 +20,37 @@ typedef NSString ARTUpdateToken; typedef ARTJsonObject ARTPushRecipient; -#pragma mark ARTPushNotifications interface +#pragma mark ARTPushRegisterer interface + +@protocol ARTPushRegistererDelegate + +- (void)ablyPushRegisterCallback:(nullable ARTErrorInfo *)error; +- (void)ablyPushDeregisterCallback:(nullable ARTErrorInfo *)error; + +@optional + +// Key with push-subscribe capability +- (nonnull NSString *)ablyPushAuthKey; + +// Token with push-subscribe capability (when registering with a client ID, the token must be associated with it) +- (nonnull NSString *)ablyPushAuthToken; +- (nullable NSString *)ablyPushClientId; + +- (void)ablyPushCustomRegister:(nullable ARTErrorInfo *)error deviceDetails:(nullable ARTDeviceDetails *)deviceDetails callback:(void (^ _Nonnull)(ARTUpdateToken * _Nonnull, ARTErrorInfo * _Nullable))callback; +- (void)ablyPushCustomDeregister:(nullable ARTErrorInfo *)error deviceId:(nullable ARTDeviceId *)deviceId callback:(void (^ _Nullable)(ARTErrorInfo * _Nullable))callback; -#ifdef TARGET_OS_IOS -@protocol ARTPushNotifications -- (void)didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken; -- (void)didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error; @end -#endif #pragma mark ARTPush type NS_ASSUME_NONNULL_BEGIN -#ifdef TARGET_OS_IOS -@interface ARTPush : NSObject -#else +extern NSString *const ARTDeviceIdKey; +extern NSString *const ARTDeviceUpdateTokenKey; +extern NSString *const ARTDeviceTokenKey; + @interface ARTPush : NSObject -#endif @property (nonatomic, readonly) ARTDeviceDetails *device; @@ -50,12 +61,15 @@ NS_ASSUME_NONNULL_BEGIN - (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject; #ifdef TARGET_OS_IOS +/// Push Registration token ++ (void)didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken; ++ (void)didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error; + /// Register a device, including the information necessary to deliver push notifications to it. - (void)activate; -- (void)activateWithRegisterCallback:(void (^)(ARTDeviceDetails * _Nullable, ARTErrorInfo * _Nullable, void (^ _Nullable)(ARTUpdateToken * _Nullable, ARTErrorInfo * _Nullable)))registerCallback; + /// Unregister a device. - (void)deactivate; -- (void)deactivateWithDeregisterCallback:(void (^)(ARTDeviceId * _Nullable, ARTErrorInfo * _Nullable, void (^ _Nullable)(ARTErrorInfo * _Nullable)))deregisterCallback; #endif @end diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 7c688086c..b9fd94611 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -15,56 +15,46 @@ #import "ARTJsonLikeEncoder.h" #import "ARTEventEmitter.h" #import "ARTPushActivationStateMachine.h" +#import "ARTPushActivationEvent.h" -typedef NS_ENUM(NSUInteger, ARTPushState) { - ARTPushStateDeactivated, - ARTPushStateActivated, -}; - -@interface ARTPush () - -@property (nonatomic, readonly) ARTPushState state; - -@end +NSString *const ARTDeviceIdKey = @"ARTDeviceId"; +NSString *const ARTDeviceUpdateTokenKey = @"ARTDeviceUpdateToken"; +NSString *const ARTDeviceTokenKey = @"ARTDeviceToken"; @implementation ARTPush { id _httpExecutor; __weak ARTLog *_logger; - ARTEventEmitter *_deviceTokenEmitter; } - (instancetype)init:(id)httpExecutor { if (self = [super init]) { _httpExecutor = httpExecutor; _logger = [httpExecutor logger]; - _device = [ARTDeviceDetails fromLocalDevice]; - _state = ARTPushStateDeactivated; - _deviceTokenEmitter = [[ARTEventEmitter alloc] init]; } return self; } -- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - [_logger info:@"ARTPush: device token received and stored"]; - [[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:ARTDeviceTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; - [_deviceTokenEmitter emit:[NSNull null] with:deviceToken]; -} - -- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - [_logger error:@"ARTPush: device token not received (%@)", [error localizedDescription]]; -} - + (ARTPushActivationStateMachine *)activationMachine { static dispatch_once_t once; static id activationMachineInstance; dispatch_once(&once, ^{ - // Error: ARTPush.m:62:81: Instance variable '_httpExecutor' accessed in class method - //activationMachineInstance = [[ARTPushActivationStateMachine alloc] init:_httpExecutor]; + activationMachineInstance = [[ARTPushActivationStateMachine alloc] init]; }); return activationMachineInstance; } ++ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + NSLog(@"ARTPush: device token received and stored"); + [[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:ARTDeviceTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventGotPushDeviceDetails new]]; +} + ++ (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + NSLog(@"ARTPush: device token not received (%@)", [error localizedDescription]); + [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; +} + - (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/publish"]]; request.HTTPMethod = @"POST"; @@ -89,141 +79,11 @@ - (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonOb } - (void)activate { - [self activate:self.device registerCallback:nil]; -} - -- (void)activateWithRegisterCallback:(void (^)(ARTDeviceDetails *, ARTErrorInfo *, void (^)(ARTUpdateToken *, ARTErrorInfo *)))registerCallback { - [self activate:self.device registerCallback:registerCallback]; -} - -- (void)activate:(ARTDeviceDetails *)deviceDetails registerCallback:(void (^)(ARTDeviceDetails *, ARTErrorInfo *, void (^)(ARTUpdateToken *, ARTErrorInfo *)))registerCallback { - if (self.state == ARTPushStateActivated) { - return; - } - - NSData *deviceToken = [[NSUserDefaults standardUserDefaults] dataForKey:ARTDeviceTokenKey]; - if (!deviceToken) { - // Waiting for device token - [_deviceTokenEmitter once:^(ARTDeviceToken *deviceToken) { - [self activate:deviceDetails registerCallback:registerCallback]; - }]; - return; - } - - if (registerCallback) { - registerCallback(deviceDetails, nil, ^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { - if (updateToken) { - self.device.updateToken = updateToken; - } - if (error) { - [_logger error:@"%@: device registration using a `registerCallback` failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; - } - }); - return; - } - - if (self.device.updateToken) { - [self updateDevice:deviceDetails]; - } - else { - [self newDevice:deviceDetails]; - } + [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledActivate new]]; } - (void)deactivate { - [self deactivate:self.device.id deregisterCallback:nil]; -} - -- (void)deactivateWithDeregisterCallback:(void (^)(ARTDeviceId *, ARTErrorInfo *, void (^)(ARTErrorInfo *)))deregisterCallback { - [self deactivate:self.device.id deregisterCallback:deregisterCallback]; -} - -- (void)deactivate:(ARTDeviceId *)deviceId deregisterCallback:(void (^)(ARTDeviceId *, ARTErrorInfo *, void (^)(ARTErrorInfo *)))deregisterCallback { - if (deregisterCallback) { - deregisterCallback(deviceId, nil, ^(ARTErrorInfo *error) { - if (error) { - [_logger error:@"%@: device deregistration using a `deregisterCallback` failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; - } - }); - return; - } - - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"] resolvingAgainstBaseURL:NO]; - components.queryItems = @[ - [NSURLQueryItem queryItemWithName:@"deviceId" value:deviceId], - ]; - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"DELETE"; - - [_logger debug:__FILE__ line:__LINE__ message:@"device deregistration with request %@", request]; - [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 200 /*OK*/) { - [_logger debug:__FILE__ line:__LINE__ message:@"successfully deactivate device"]; - self.device.updateToken = nil; - } - else if (error) { - [_logger error:@"%@: device deregistration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; - } - else { - [_logger error:@"%@: device deregistration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; - } - }]; -} - -- (void)newDevice:(ARTDeviceDetails *)deviceDetails { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"]]; - request.HTTPMethod = @"POST"; - request.HTTPBody = [[_httpExecutor defaultEncoder] encodeDeviceDetails:deviceDetails]; - [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; - - [_logger debug:__FILE__ line:__LINE__ message:@"device registration with request %@", request]; - [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 201 /*Created*/) { - ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; - self.device.updateToken = deviceDetails.updateToken; - } - else if (error) { - [_logger error:@"%@: device registration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; - } - else { - [_logger error:@"%@: device registration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; - } - }]; -} - -- (void)updateDevice:(ARTDeviceDetails *)deviceDetails { - if (!deviceDetails.updateToken) { - [_logger error:@"%@: update token is missing", NSStringFromClass(self.class)]; - return; - } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[[NSURL URLWithString:@"/push/deviceRegistrations"] URLByAppendingPathComponent:deviceDetails.id]]; - NSData *tokenData = [deviceDetails.updateToken dataUsingEncoding:NSUTF8StringEncoding]; - NSString *tokenBase64 = [tokenData base64EncodedStringWithOptions:0]; - [request setValue:[NSString stringWithFormat:@"Bearer %@", tokenBase64] forHTTPHeaderField:@"Authorization"]; - request.HTTPMethod = @"PUT"; - request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ - @"push": @{ - @"metadata": @{ - @"deviceToken": deviceDetails.push.deviceToken, - } - } - }]; - [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; - - [_logger debug:__FILE__ line:__LINE__ message:@"update device with request %@", request]; - [_httpExecutor executeRequest:request completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 200 /*OK*/) { - ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; - self.device.updateToken = deviceDetails.updateToken; - } - else if (error) { - [_logger error:@"%@: update device failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; - } - else { - [_logger error:@"%@: update device failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; - } - }]; + [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledDeactivate new]]; } @end diff --git a/Source/ARTPushActivationEvent.h b/Source/ARTPushActivationEvent.h index a7cf19001..dced401d3 100644 --- a/Source/ARTPushActivationEvent.h +++ b/Source/ARTPushActivationEvent.h @@ -8,33 +8,46 @@ #import -@interface ARTPushActivationEvent : NSObject +@class ARTErrorInfo; + +@interface ARTPushActivationEvent : NSObject @end -@interface ARTPushActivationCalledActivateEvent : ARTPushActivationEvent +@interface ARTPushActivationErrorEvent : ARTPushActivationEvent + +@property (nonatomic, readonly) ARTErrorInfo *error; + +- (instancetype)initWithError:(ARTErrorInfo *)error; ++ (instancetype)newWithError:(ARTErrorInfo *)error; + +@end + +#pragma mark - Events + +@interface ARTPushActivationEventCalledActivate : ARTPushActivationEvent @end -@interface ARTPushActivationCalledDeactivateEvent : ARTPushActivationEvent +@interface ARTPushActivationEventCalledDeactivate : ARTPushActivationEvent @end -@interface ARTPushActivationGotPushDeviceDetailsEvent : ARTPushActivationEvent +@interface ARTPushActivationEventGotPushDeviceDetails : ARTPushActivationEvent @end -@interface ARTPushActivationGotUpdateTokenEvent : ARTPushActivationEvent +@interface ARTPushActivationEventGotUpdateToken : ARTPushActivationEvent @end -@interface ARTPushActivationGettingUpdateTokenFailedEvent : ARTPushActivationEvent +@interface ARTPushActivationEventGettingUpdateTokenFailed : ARTPushActivationErrorEvent @end -@interface ARTPushActivationRegistrationUpdatedEvent : ARTPushActivationEvent +@interface ARTPushActivationEventRegistrationUpdated : ARTPushActivationEvent @end -@interface ARTPushActivationUpdatingRegistrationFailedEvent : ARTPushActivationEvent +@interface ARTPushActivationEventUpdatingRegistrationFailed : ARTPushActivationErrorEvent @end -@interface ARTPushActivationDeregisteredEvent : ARTPushActivationEvent +@interface ARTPushActivationEventDeregistered : ARTPushActivationEvent @end -@interface ARTPushActivationDeregistrationFailedEvent : ARTPushActivationEvent +@interface ARTPushActivationEventDeregistrationFailed : ARTPushActivationErrorEvent @end diff --git a/Source/ARTPushActivationEvent.m b/Source/ARTPushActivationEvent.m index 90076ef1f..bf5132831 100644 --- a/Source/ARTPushActivationEvent.m +++ b/Source/ARTPushActivationEvent.m @@ -7,6 +7,7 @@ // #import "ARTPushActivationEvent.h" +#import "ARTTypes.h" @implementation ARTPushActivationEvent @@ -28,29 +29,45 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { @end -@implementation ARTPushActivationCalledActivateEvent + +@implementation ARTPushActivationErrorEvent + +- (instancetype)initWithError:(ARTErrorInfo *)error { + if (self = [super init]) { + _error = error; + } + return self; +} + ++ (instancetype)newWithError:(ARTErrorInfo *)error { + return [[self alloc] initWithError:error]; +} + +@end + +@implementation ARTPushActivationEventCalledActivate @end -@implementation ARTPushActivationCalledDeactivateEvent +@implementation ARTPushActivationEventCalledDeactivate @end -@implementation ARTPushActivationGotPushDeviceDetailsEvent +@implementation ARTPushActivationEventGotPushDeviceDetails @end -@implementation ARTPushActivationGotUpdateTokenEvent +@implementation ARTPushActivationEventGotUpdateToken @end -@implementation ARTPushActivationGettingUpdateTokenFailedEvent +@implementation ARTPushActivationEventGettingUpdateTokenFailed @end -@implementation ARTPushActivationRegistrationUpdatedEvent +@implementation ARTPushActivationEventRegistrationUpdated @end -@implementation ARTPushActivationUpdatingRegistrationFailedEvent +@implementation ARTPushActivationEventUpdatingRegistrationFailed @end -@implementation ARTPushActivationDeregisteredEvent +@implementation ARTPushActivationEventDeregistered @end -@implementation ARTPushActivationDeregistrationFailedEvent +@implementation ARTPushActivationEventDeregistrationFailed @end diff --git a/Source/ARTPushActivationState.h b/Source/ARTPushActivationState.h index 9e7b52a9a..73e89f9ac 100644 --- a/Source/ARTPushActivationState.h +++ b/Source/ARTPushActivationState.h @@ -17,33 +17,40 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithMachine:(ARTPushActivationStateMachine *)machine; ++ (instancetype)new NS_UNAVAILABLE; ++ (instancetype)newWithMachine:(ARTPushActivationStateMachine *)machine; - (nullable ARTPushActivationState *)transition:(ARTPushActivationEvent *)event; @end -@interface ARTPushActivationNotActivatedState : ARTPushActivationState +@interface ARTPushActivationPersistentState : ARTPushActivationState @end -@interface ARTPushActivationCalledActivateState : ARTPushActivationState +#pragma mark - States + +@interface ARTPushActivationStateNotActivated : ARTPushActivationPersistentState +@end + +@interface ARTPushActivationStateCalledActivate : ARTPushActivationState @end -@interface ARTPushActivationWaitingForUpdateTokenState : ARTPushActivationState +@interface ARTPushActivationStateWaitingForUpdateToken : ARTPushActivationState @end -@interface ARTPushActivationWaitingForPushDeviceDetailsState : ARTPushActivationState +@interface ARTPushActivationStateWaitingForPushDeviceDetails : ARTPushActivationPersistentState @end -@interface ARTPushActivationWaitingForNewPushDeviceDetailsState : ARTPushActivationState +@interface ARTPushActivationStateWaitingForNewPushDeviceDetails : ARTPushActivationPersistentState @end -@interface ARTPushActivationWaitingForRegistrationUpdateState : ARTPushActivationState +@interface ARTPushActivationStateWaitingForRegistrationUpdate : ARTPushActivationState @end -@interface ARTPushActivationWaitingForDeregistrationState : ARTPushActivationState +@interface ARTPushActivationStateWaitingForDeregistration : ARTPushActivationState @end -@interface ARTPushActivationAfterRegistrationUpdateFailedState : ARTPushActivationState +@interface ARTPushActivationStateAfterRegistrationUpdateFailed : ARTPushActivationPersistentState @end NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m index 778e6a0ce..0b23b12c0 100644 --- a/Source/ARTPushActivationState.m +++ b/Source/ARTPushActivationState.m @@ -9,6 +9,8 @@ #import "ARTPushActivationState.h" #import "ARTPushActivationStateMachine.h" #import "ARTPushActivationEvent.h" +#import "ARTLocalDevice.h" +#import "ARTDevicePushDetails.h" #import "ARTLog.h" @interface ARTPushActivationState () @@ -26,13 +28,17 @@ - (instancetype)initWithMachine:(ARTPushActivationStateMachine *)machine { return self; } ++ (instancetype)newWithMachine:(ARTPushActivationStateMachine *)machine { + return [[ARTPushActivationState alloc] initWithMachine:machine]; +} + - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { NSAssert(false, @"-[%s:%d %s] should always be overriden.", __FILE__, __LINE__, __FUNCTION__); return nil; } - (void)logEventTransition:(ARTPushActivationEvent *)event file:(const char *)file line:(NSUInteger)line { - [[self.machine logger] debug:__FILE__ line:__LINE__ message:@"%@ state: transitioning to %@ event", NSStringFromClass(self.class), NSStringFromClass(event.class)]; + NSLog(@"%@ state: transitioning to %@ event", NSStringFromClass(self.class), NSStringFromClass(event.class)); } - (id)copyWithZone:(NSZone *)zone { @@ -53,26 +59,42 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { @end +#pragma mark - Persistent State + +@implementation ARTPushActivationPersistentState +@end #pragma mark - Activation States -@implementation ARTPushActivationNotActivatedState +@implementation ARTPushActivationStateNotActivated - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { - if ([event isKindOfClass:[ARTPushActivationCalledDeactivateEvent class]]) { + if ([event isKindOfClass:[ARTPushActivationEventCalledDeactivate class]]) { [self logEventTransition:event file:__FILE__ line:__LINE__]; - // TODO + [self.machine callDeactivatedCallback:nil]; + return self; } - else if ([event isKindOfClass:[ARTPushActivationCalledActivateEvent class]]) { + else if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { [self logEventTransition:event file:__FILE__ line:__LINE__]; - // TODO + ARTLocalDevice *local = [ARTLocalDevice local]; + + if (local.updateToken != nil) { + // Already registered. + return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithMachine:self.machine]; + } + + if (local.registrationToken == nil) { + [self.machine sendEvent:[ARTPushActivationEventGotPushDeviceDetails new]]; + } + + return [ARTPushActivationStateWaitingForPushDeviceDetails newWithMachine:self.machine]; } return nil; } @end -@implementation ARTPushActivationCalledActivateState +@implementation ARTPushActivationStateCalledActivate - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { // TODO @@ -81,7 +103,7 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @end -@implementation ARTPushActivationWaitingForUpdateTokenState +@implementation ARTPushActivationStateWaitingForUpdateToken - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { // TODO @@ -90,16 +112,26 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @end -@implementation ARTPushActivationWaitingForPushDeviceDetailsState +@implementation ARTPushActivationStateWaitingForPushDeviceDetails - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { - // TODO + if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { + return [ARTPushActivationStateCalledActivate newWithMachine:self.machine]; + } + else if ([event isKindOfClass:[ARTPushActivationEventCalledDeactivate class]]) { + [self.machine callDeactivatedCallback:nil]; + return [ARTPushActivationStateNotActivated newWithMachine:self.machine]; + } + else if ([event isKindOfClass:[ARTPushActivationEventGotPushDeviceDetails class]]) { + [self.machine deviceRegistration:nil]; + return [ARTPushActivationStateWaitingForUpdateToken newWithMachine:self.machine]; + } return nil; } @end -@implementation ARTPushActivationWaitingForNewPushDeviceDetailsState +@implementation ARTPushActivationStateWaitingForNewPushDeviceDetails - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { // TODO @@ -108,7 +140,7 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @end -@implementation ARTPushActivationWaitingForRegistrationUpdateState +@implementation ARTPushActivationStateWaitingForRegistrationUpdate - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { // TODO @@ -117,7 +149,7 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @end -@implementation ARTPushActivationWaitingForDeregistrationState +@implementation ARTPushActivationStateWaitingForDeregistration - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { // TODO @@ -126,7 +158,7 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @end -@implementation ARTPushActivationAfterRegistrationUpdateFailedState +@implementation ARTPushActivationStateAfterRegistrationUpdateFailed - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { // TODO diff --git a/Source/ARTPushActivationStateMachine.h b/Source/ARTPushActivationStateMachine.h index 44cce2e6a..e3e16fb49 100644 --- a/Source/ARTPushActivationStateMachine.h +++ b/Source/ARTPushActivationStateMachine.h @@ -8,21 +8,24 @@ #import -@class ARTLog; +@class ARTErrorInfo; +@class ARTPushActivationEvent; @protocol ARTHTTPAuthenticatedExecutor; NS_ASSUME_NONNULL_BEGIN -// TODO: this should not be available to user's @interface ARTPushActivationStateMachine : NSObject -@property (nonatomic, readonly) id httpExecutor; -@property (nonatomic, readonly) ARTLog *logger; +- (instancetype)init; -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)init:(id)httpExecutor; +- (void)sendEvent:(ARTPushActivationEvent *)event; @end +@interface ARTPushActivationStateMachine (Protected) +- (void)deviceRegistration:(nullable ARTErrorInfo *)error; +- (void)callDeactivatedCallback:(nullable ARTErrorInfo *)error; +@end + NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m index 65188f3ce..5def6c113 100644 --- a/Source/ARTPushActivationStateMachine.m +++ b/Source/ARTPushActivationStateMachine.m @@ -7,6 +7,7 @@ // #import "ARTPushActivationStateMachine.h" +#import "ARTPush.h" #import "ARTPushActivationEvent.h" #import "ARTPushActivationState.h" #import "ARTRest+Private.h" @@ -14,6 +15,11 @@ #import "ARTJsonEncoder.h" #import "ARTJsonLikeEncoder.h" #import "ARTTypes.h" +#import "ARTLocalDevice.h" + +#ifdef TARGET_OS_IOS +#import +#endif NSString *const ARTPushActivationCurrentStateKey = @"ARTPushActivationCurrentState"; NSString *const ARTPushActivationPendingEventsKey = @"ARTPushActivationPendingEvents"; @@ -23,15 +29,13 @@ @implementation ARTPushActivationStateMachine { NSMutableArray *_pendingEvents; } -- (instancetype)init:(id)httpExecutor { +- (instancetype)init { if (self = [super init]) { - _httpExecutor = httpExecutor; - _logger = [_httpExecutor logger]; // Unarquiving NSData *stateData = [[NSUserDefaults standardUserDefaults] objectForKey:ARTPushActivationCurrentStateKey]; _current = [NSKeyedUnarchiver unarchiveObjectWithData:stateData]; if (!_current) { - _current = [ARTPushActivationNotActivatedState new]; + _current = [ARTPushActivationStateNotActivated newWithMachine:self]; } NSData *pendingEventsData = [[NSUserDefaults standardUserDefaults] objectForKey:ARTPushActivationPendingEventsKey]; _pendingEvents = [NSKeyedUnarchiver unarchiveObjectWithData:pendingEventsData]; @@ -42,16 +46,203 @@ - (instancetype)init:(id)httpExecutor { return self; } +- (void)sendEvent:(ARTPushActivationEvent *)event { + [self handleEvent:event]; +} + - (void)handleEvent:(nonnull ARTPushActivationEvent *)event { - ARTPushActivationState *next = [_current transition:event]; - _current = next; + ARTPushActivationState *maybeNext = [_current transition:event]; + + if (maybeNext == nil) { + [_pendingEvents addObject:event]; + return; + } + + while (true) { + ARTPushActivationEvent *pending = [_pendingEvents peek]; + if (pending == nil) { + break; + } + maybeNext = [_current transition:pending]; + if (maybeNext == nil) { + break; + } + [_pendingEvents dequeue]; + + _current = maybeNext; + } + [self persist]; } - (void)persist { // Arquiving - [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_current] forKey:ARTPushActivationCurrentStateKey]; + if ([_current isKindOfClass:[ARTPushActivationPersistentState class]]) { + [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_current] forKey:ARTPushActivationCurrentStateKey]; + } [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_pendingEvents] forKey:ARTPushActivationPendingEventsKey]; } +- (void)deviceRegistration:(ARTErrorInfo *)error { + #ifdef TARGET_OS_IOS + ARTLocalDevice *local = [ARTLocalDevice local]; + + if (![[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { + [NSException raise:@"ARTPushRegistererDelegate must be implemented on AppDelegate" format:@""]; + } + + id delegate = [UIApplication sharedApplication].delegate; + + // Custom register + SEL customRegisterMethodSelector = @selector(ablyPushCustomRegister:deviceDetails:callback:); + if ([delegate respondsToSelector:customRegisterMethodSelector]) { + [delegate ablyPushCustomRegister:error deviceDetails:local callback:^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { + if (![delegate respondsToSelector:@selector(ablyPushRegisterCallback:)]) { + [NSException raise:@"ablyPushRegisterCallback: method is required" format:@""]; + } + if (error) { + // Failed + [delegate ablyPushRegisterCallback:error]; + [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:error]]; + } + else if (updateToken) { + // Success + [delegate ablyPushRegisterCallback:nil]; + [[NSUserDefaults standardUserDefaults] setObject:updateToken forKey:ARTDeviceUpdateTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self sendEvent:[ARTPushActivationEventGotUpdateToken new]]; + } + else { + ARTErrorInfo *missingUpdateTokenError = [ARTErrorInfo createWithCode:0 message:@"UpdateToken is expected"]; + [delegate ablyPushRegisterCallback:missingUpdateTokenError]; + [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:missingUpdateTokenError]]; + } + }]; + return; + } + + // Asynchronous HTTP request + id httpExecutor; + + if ([delegate respondsToSelector:@selector(ablyPushAuthKey)]) { + NSString *key = [delegate ablyPushAuthKey]; + httpExecutor = [ARTRest createWithKey:key]; + } + else if ([delegate respondsToSelector:@selector(ablyPushAuthToken)]) { + NSString *token = [delegate ablyPushAuthToken]; + httpExecutor = [ARTRest createWithToken:token]; + } + else { + [NSException raise:@"ARTPushRegistererDelegate: must have a key or token for authentication" format:@""]; + } + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"]]; + request.HTTPMethod = @"POST"; + request.HTTPBody = [[httpExecutor defaultEncoder] encodeDeviceDetails:local]; + [request setValue:[[httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + + [[httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"device registration with request %@", request]; + [httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 201 /*Created*/) { + ARTDeviceDetails *deviceDetails = [[httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; + [[NSUserDefaults standardUserDefaults] setObject:deviceDetails.updateToken forKey:ARTDeviceUpdateTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self sendEvent:[ARTPushActivationEventGotUpdateToken new]]; + } + else if (error) { + [[httpExecutor logger] error:@"%@: device registration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; + } + else { + [[httpExecutor logger] error:@"%@: device registration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:[ARTErrorInfo createWithCode:response.statusCode message:@"Device registration failed"]]]; + } + }]; + #endif +} + +- (void)deviceUnregistration:(ARTErrorInfo *)error { + #ifdef TARGET_OS_IOS + ARTLocalDevice *local = [ARTLocalDevice local]; + + if (![[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { + [NSException raise:@"ARTPushRegistererDelegate must be implemented on AppDelegate" format:@""]; + } + + id delegate = [UIApplication sharedApplication].delegate; + + // Custom register + SEL customDeregisterMethodSelector = @selector(ablyPushCustomDeregister:deviceId:callback:); + if ([delegate respondsToSelector:customDeregisterMethodSelector]) { + [delegate ablyPushCustomDeregister:error deviceId:local.id callback:^(ARTErrorInfo *error) { + if (![delegate respondsToSelector:@selector(ablyPushDeregisterCallback:)]) { + [NSException raise:@"ablyPushDeregisterCallback: method is required" format:@""]; + } + if (error) { + // Failed + [delegate ablyPushRegisterCallback:error]; + [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:error]]; + } + else { + // Success + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceUpdateTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [delegate ablyPushDeregisterCallback:nil]; + } + }]; + return; + } + + // Asynchronous HTTP request + id httpExecutor; + + if ([delegate respondsToSelector:@selector(ablyPushAuthKey)]) { + NSString *key = [delegate ablyPushAuthKey]; + httpExecutor = [ARTRest createWithKey:key]; + } + else if ([delegate respondsToSelector:@selector(ablyPushAuthToken)]) { + NSString *token = [delegate ablyPushAuthToken]; + httpExecutor = [ARTRest createWithToken:token]; + } + else { + [NSException raise:@"ARTPushRegistererDelegate: must have a key or token for authentication" format:@""]; + } + + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"] resolvingAgainstBaseURL:NO]; + components.queryItems = @[ + [NSURLQueryItem queryItemWithName:@"deviceId" value:local.id], + ]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; + request.HTTPMethod = @"DELETE"; + + [[httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"device deregistration with request %@", request]; + [httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + [[httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"successfully deactivate device"]; + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceUpdateTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } + else if (error) { + [[httpExecutor logger] error:@"%@: device deregistration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + } + else { + [[httpExecutor logger] error:@"%@: device deregistration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + } + }]; + #endif +} + +- (void)callDeactivatedCallback:(ARTErrorInfo *)error { + #ifdef TARGET_OS_IOS + if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { + id delegate = [UIApplication sharedApplication].delegate; + SEL deregisterCallbackMethodSelector = @selector(ablyPushDeregisterCallback:); + if ([delegate respondsToSelector:deregisterCallbackMethodSelector]) { + [delegate ablyPushDeregisterCallback:error]; + } + } + #endif +} + @end diff --git a/Source/Ably.h b/Source/Ably.h index 020f9c6fb..9a18bf098 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -68,6 +68,7 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import "ARTPushDeviceRegistrations.h" #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" +#import "ARTLocalDevice.h" #import "ARTNSDictionary+ARTDictionaryUtil.h" #import "ARTNSDate+ARTUtil.h" From e42b798641ab1d5a8bcde1a6e5e2b172b6d0cf1f Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 1 Mar 2017 10:16:56 +0000 Subject: [PATCH 32/46] Removed ablyPushAuthKey, ablyPushAuthToken and ablyPushClientId from push delegate --- Source/ARTPush.h | 7 --- Source/ARTPush.m | 15 ++++- Source/ARTPushActivationEvent.h | 25 ++++++++- Source/ARTPushActivationEvent.m | 61 ++++++++++++++++++++ Source/ARTPushActivationState.h | 24 +++++++- Source/ARTPushActivationState.m | 78 ++++++++++++++++++++++++-- Source/ARTPushActivationStateMachine.h | 3 +- Source/ARTPushActivationStateMachine.m | 32 +---------- 8 files changed, 197 insertions(+), 48 deletions(-) diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 4a7488f90..0b0aa6133 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -29,13 +29,6 @@ typedef ARTJsonObject ARTPushRecipient; @optional -// Key with push-subscribe capability -- (nonnull NSString *)ablyPushAuthKey; - -// Token with push-subscribe capability (when registering with a client ID, the token must be associated with it) -- (nonnull NSString *)ablyPushAuthToken; -- (nullable NSString *)ablyPushClientId; - - (void)ablyPushCustomRegister:(nullable ARTErrorInfo *)error deviceDetails:(nullable ARTDeviceDetails *)deviceDetails callback:(void (^ _Nonnull)(ARTUpdateToken * _Nonnull, ARTErrorInfo * _Nullable))callback; - (void)ablyPushCustomDeregister:(nullable ARTErrorInfo *)error deviceId:(nullable ARTDeviceId *)deviceId callback:(void (^ _Nullable)(ARTErrorInfo * _Nullable))callback; diff --git a/Source/ARTPush.m b/Source/ARTPush.m index b9fd94611..0349b1043 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -16,6 +16,7 @@ #import "ARTEventEmitter.h" #import "ARTPushActivationStateMachine.h" #import "ARTPushActivationEvent.h" +#import "ARTClientOptions+Private.h" NSString *const ARTDeviceIdKey = @"ARTDeviceId"; NSString *const ARTDeviceUpdateTokenKey = @"ARTDeviceUpdateToken"; @@ -79,11 +80,21 @@ - (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonOb } - (void)activate { - [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledActivate new]]; + if ([[_httpExecutor options] key]) { + [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledActivate newWithKey:[_httpExecutor options].key clientId:[_httpExecutor options].clientId]]; + } + else if ([[_httpExecutor options] token]) { + [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledActivate newWithToken:[_httpExecutor options].token clientId:[_httpExecutor options].clientId]]; + } } - (void)deactivate { - [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledDeactivate new]]; + if ([[_httpExecutor options] key]) { + [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledDeactivate newWithKey:[_httpExecutor options].key clientId:[_httpExecutor options].clientId]]; + } + else if ([[_httpExecutor options] token]) { + [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledDeactivate newWithToken:[_httpExecutor options].token clientId:[_httpExecutor options].clientId]]; + } } @end diff --git a/Source/ARTPushActivationEvent.h b/Source/ARTPushActivationEvent.h index dced401d3..69d5d7afd 100644 --- a/Source/ARTPushActivationEvent.h +++ b/Source/ARTPushActivationEvent.h @@ -10,10 +10,13 @@ @class ARTErrorInfo; +NS_ASSUME_NONNULL_BEGIN + @interface ARTPushActivationEvent : NSObject @end +/// Event with Error info @interface ARTPushActivationErrorEvent : ARTPushActivationEvent @property (nonatomic, readonly) ARTErrorInfo *error; @@ -23,12 +26,28 @@ @end +/// Event with Auth credentials +@interface ARTPushActivationAuthEvent : ARTPushActivationEvent + +@property (nonatomic, readonly) NSString *key; +@property (nonatomic, readonly) NSString *token; +@property (nonatomic, readonly) NSString *clientId; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithKey:(NSString *)key clientId:(nullable NSString *)clientId; ++ (instancetype)newWithKey:(NSString *)key clientId:(nullable NSString *)clientId; +- (instancetype)initWithToken:(NSString *)token clientId:(nullable NSString *)clientId; ++ (instancetype)newWithToken:(NSString *)token clientId:(nullable NSString *)clientId; + +@end + #pragma mark - Events -@interface ARTPushActivationEventCalledActivate : ARTPushActivationEvent +@interface ARTPushActivationEventCalledActivate : ARTPushActivationAuthEvent @end -@interface ARTPushActivationEventCalledDeactivate : ARTPushActivationEvent +@interface ARTPushActivationEventCalledDeactivate : ARTPushActivationAuthEvent @end @interface ARTPushActivationEventGotPushDeviceDetails : ARTPushActivationEvent @@ -51,3 +70,5 @@ @interface ARTPushActivationEventDeregistrationFailed : ARTPushActivationErrorEvent @end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushActivationEvent.m b/Source/ARTPushActivationEvent.m index bf5132831..672437218 100644 --- a/Source/ARTPushActivationEvent.m +++ b/Source/ARTPushActivationEvent.m @@ -29,6 +29,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { @end +#pragma mark - Event with Error info @implementation ARTPushActivationErrorEvent @@ -43,8 +44,68 @@ + (instancetype)newWithError:(ARTErrorInfo *)error { return [[self alloc] initWithError:error]; } +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + if (self = [super initWithCoder:aDecoder]) { + _error = [aDecoder decodeObjectForKey:@"error"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + [aCoder encodeObject:self.error forKey:@"error"]; +} + @end +#pragma mark - Event with Auth credentials + +@implementation ARTPushActivationAuthEvent + +- (instancetype)initWithKey:(NSString *)key clientId:(NSString *)clientId { + if (self = [super init]) { + _key = key; + _clientId = clientId; + } + return self; +} + ++ (instancetype)newWithKey:(NSString *)key clientId:(NSString *)clientId { + return [[self alloc] initWithKey:key clientId:clientId]; +} + +- (instancetype)initWithToken:(NSString *)token clientId:(NSString *)clientId { + if (self = [super init]) { + _token = token; + _clientId = clientId; + } + return self; +} + ++ (instancetype)newWithToken:(NSString *)token clientId:(NSString *)clientId { + return [[self alloc] initWithToken:token clientId:clientId]; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + if (self = [super initWithCoder:aDecoder]) { + _key = [aDecoder decodeObjectForKey:@"key"]; + _token = [aDecoder decodeObjectForKey:@"token"]; + _clientId = [aDecoder decodeObjectForKey:@"clientId"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + [aCoder encodeObject:self.key forKey:@"key"]; + [aCoder encodeObject:self.token forKey:@"token"]; + [aCoder encodeObject:self.clientId forKey:@"clientId"]; +} + +@end + +#pragma mark - Activation Events + @implementation ARTPushActivationEventCalledActivate @end diff --git a/Source/ARTPushActivationState.h b/Source/ARTPushActivationState.h index 73e89f9ac..0fedc396c 100644 --- a/Source/ARTPushActivationState.h +++ b/Source/ARTPushActivationState.h @@ -11,6 +11,8 @@ @class ARTPushActivationStateMachine; @class ARTPushActivationEvent; +@protocol ARTHTTPAuthenticatedExecutor; + NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationState : NSObject @@ -24,9 +26,27 @@ NS_ASSUME_NONNULL_BEGIN @end +/// Persistent State @interface ARTPushActivationPersistentState : ARTPushActivationState @end +/// Persistent State with Auth credentials +@interface ARTPushActivationAuthState : ARTPushActivationPersistentState + +@property (nonatomic, readonly) NSString *key; +@property (nonatomic, readonly) NSString *token; +@property (nonatomic, readonly) NSString *clientId; + +- (instancetype)initWithMachine:(ARTPushActivationStateMachine *)machine NS_UNAVAILABLE; ++ (instancetype)newWithMachine:(ARTPushActivationStateMachine *)machine NS_UNAVAILABLE; + +- (instancetype)initWithKey:(NSString *)key machine:(ARTPushActivationStateMachine *)machine clientId:(nullable NSString *)clientId; ++ (instancetype)newWithKey:(NSString *)key machine:(ARTPushActivationStateMachine *)machine clientId:(nullable NSString *)clientId; +- (instancetype)initWithToken:(NSString *)token machine:(ARTPushActivationStateMachine *)machine clientId:(nullable NSString *)clientId; ++ (instancetype)newWithToken:(NSString *)token machine:(ARTPushActivationStateMachine *)machine clientId:(nullable NSString *)clientId; + +@end + #pragma mark - States @interface ARTPushActivationStateNotActivated : ARTPushActivationPersistentState @@ -38,10 +58,10 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationStateWaitingForUpdateToken : ARTPushActivationState @end -@interface ARTPushActivationStateWaitingForPushDeviceDetails : ARTPushActivationPersistentState +@interface ARTPushActivationStateWaitingForPushDeviceDetails : ARTPushActivationAuthState @end -@interface ARTPushActivationStateWaitingForNewPushDeviceDetails : ARTPushActivationPersistentState +@interface ARTPushActivationStateWaitingForNewPushDeviceDetails : ARTPushActivationAuthState @end @interface ARTPushActivationStateWaitingForRegistrationUpdate : ARTPushActivationState diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m index 0b23b12c0..40ad8cd74 100644 --- a/Source/ARTPushActivationState.m +++ b/Source/ARTPushActivationState.m @@ -12,6 +12,8 @@ #import "ARTLocalDevice.h" #import "ARTDevicePushDetails.h" #import "ARTLog.h" +#import "ARTRest+Private.h" +#import "ARTHttp.h" @interface ARTPushActivationState () @@ -64,6 +66,52 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { @implementation ARTPushActivationPersistentState @end +#pragma mark - Persistent State with Auth credentials + +@implementation ARTPushActivationAuthState + +- (instancetype)initWithKey:(NSString *)key machine:(ARTPushActivationStateMachine *)machine clientId:(NSString *)clientId { + if (self = [super initWithMachine:machine]) { + _key = key; + _clientId = clientId; + } + return self; +} + ++ (instancetype)newWithKey:(NSString *)key machine:(ARTPushActivationStateMachine *)machine clientId:(NSString *)clientId { + return [[self alloc] initWithKey:key clientId:clientId]; +} + +- (instancetype)initWithToken:(NSString *)token machine:(ARTPushActivationStateMachine *)machine clientId:(NSString *)clientId { + if (self = [super initWithMachine:machine]) { + _token = token; + _clientId = clientId; + } + return self; +} + ++ (instancetype)newWithToken:(NSString *)token machine:(ARTPushActivationStateMachine *)machine clientId:(NSString *)clientId { + return [[self alloc] initWithToken:token clientId:clientId]; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + if (self = [super initWithCoder:aDecoder]) { + _key = [aDecoder decodeObjectForKey:@"key"]; + _token = [aDecoder decodeObjectForKey:@"token"]; + _clientId = [aDecoder decodeObjectForKey:@"clientId"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + [aCoder encodeObject:self.key forKey:@"key"]; + [aCoder encodeObject:self.token forKey:@"token"]; + [aCoder encodeObject:self.clientId forKey:@"clientId"]; +} + +@end + #pragma mark - Activation States @implementation ARTPushActivationStateNotActivated @@ -76,18 +124,30 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { } else if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { [self logEventTransition:event file:__FILE__ line:__LINE__]; + ARTPushActivationEventCalledActivate *activateEvent = (ARTPushActivationEventCalledActivate *)event; ARTLocalDevice *local = [ARTLocalDevice local]; if (local.updateToken != nil) { // Already registered. - return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithMachine:self.machine]; + if (activateEvent.key) { + return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithKey:activateEvent.key machine:self.machine clientId:activateEvent.clientId]; + } + if (activateEvent.token) { + return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithToken:activateEvent.token machine:self.machine clientId:activateEvent.clientId]; + } + return nil; } - if (local.registrationToken == nil) { + if (local.registrationToken != nil) { [self.machine sendEvent:[ARTPushActivationEventGotPushDeviceDetails new]]; } - return [ARTPushActivationStateWaitingForPushDeviceDetails newWithMachine:self.machine]; + if (activateEvent.key) { + return [ARTPushActivationStateWaitingForPushDeviceDetails newWithKey:activateEvent.key machine:self.machine clientId:activateEvent.clientId]; + } + if (activateEvent.token) { + return [ARTPushActivationStateWaitingForPushDeviceDetails newWithToken:activateEvent.token machine:self.machine clientId:activateEvent.clientId]; + } } return nil; } @@ -123,7 +183,17 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { return [ARTPushActivationStateNotActivated newWithMachine:self.machine]; } else if ([event isKindOfClass:[ARTPushActivationEventGotPushDeviceDetails class]]) { - [self.machine deviceRegistration:nil]; + id httpExecutor; + if (self.key) { + httpExecutor = [ARTRest createWithKey:self.key]; + } + else if (self.token) { + httpExecutor = [ARTRest createWithToken:self.token]; + } + else { + [NSException raise:@"ARTPushActivationStateWaitingForPushDeviceDetails: must have a key or token for authentication" format:@""]; + } + [self.machine deviceRegistration:httpExecutor error:nil]; return [ARTPushActivationStateWaitingForUpdateToken newWithMachine:self.machine]; } return nil; diff --git a/Source/ARTPushActivationStateMachine.h b/Source/ARTPushActivationStateMachine.h index e3e16fb49..1e5d1015b 100644 --- a/Source/ARTPushActivationStateMachine.h +++ b/Source/ARTPushActivationStateMachine.h @@ -24,7 +24,8 @@ NS_ASSUME_NONNULL_BEGIN @end @interface ARTPushActivationStateMachine (Protected) -- (void)deviceRegistration:(nullable ARTErrorInfo *)error; +- (void)deviceRegistration:(id)httpExecutor error:(nullable ARTErrorInfo *)error; +- (void)deviceUnregistration:(id)httpExecutor error:(nullable ARTErrorInfo *)error; - (void)callDeactivatedCallback:(nullable ARTErrorInfo *)error; @end diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m index 5def6c113..c0bce7e35 100644 --- a/Source/ARTPushActivationStateMachine.m +++ b/Source/ARTPushActivationStateMachine.m @@ -83,7 +83,7 @@ - (void)persist { [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_pendingEvents] forKey:ARTPushActivationPendingEventsKey]; } -- (void)deviceRegistration:(ARTErrorInfo *)error { +- (void)deviceRegistration:(id)httpExecutor error:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS ARTLocalDevice *local = [ARTLocalDevice local]; @@ -122,20 +122,6 @@ - (void)deviceRegistration:(ARTErrorInfo *)error { } // Asynchronous HTTP request - id httpExecutor; - - if ([delegate respondsToSelector:@selector(ablyPushAuthKey)]) { - NSString *key = [delegate ablyPushAuthKey]; - httpExecutor = [ARTRest createWithKey:key]; - } - else if ([delegate respondsToSelector:@selector(ablyPushAuthToken)]) { - NSString *token = [delegate ablyPushAuthToken]; - httpExecutor = [ARTRest createWithToken:token]; - } - else { - [NSException raise:@"ARTPushRegistererDelegate: must have a key or token for authentication" format:@""]; - } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"]]; request.HTTPMethod = @"POST"; request.HTTPBody = [[httpExecutor defaultEncoder] encodeDeviceDetails:local]; @@ -161,7 +147,7 @@ - (void)deviceRegistration:(ARTErrorInfo *)error { #endif } -- (void)deviceUnregistration:(ARTErrorInfo *)error { +- (void)deviceUnregistration:(id)httpExecutor error:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS ARTLocalDevice *local = [ARTLocalDevice local]; @@ -194,20 +180,6 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { } // Asynchronous HTTP request - id httpExecutor; - - if ([delegate respondsToSelector:@selector(ablyPushAuthKey)]) { - NSString *key = [delegate ablyPushAuthKey]; - httpExecutor = [ARTRest createWithKey:key]; - } - else if ([delegate respondsToSelector:@selector(ablyPushAuthToken)]) { - NSString *token = [delegate ablyPushAuthToken]; - httpExecutor = [ARTRest createWithToken:token]; - } - else { - [NSException raise:@"ARTPushRegistererDelegate: must have a key or token for authentication" format:@""]; - } - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"] resolvingAgainstBaseURL:NO]; components.queryItems = @[ [NSURLQueryItem queryItemWithName:@"deviceId" value:local.id], From 772b53da6d8553f75f923e378203a002240b6a05 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Thu, 2 Mar 2017 00:42:21 +0000 Subject: [PATCH 33/46] Add Rest argument on Push delegate --- Source/ARTPush.h | 11 +- Source/ARTPush.m | 34 +++--- Source/ARTPushActivationEvent.h | 20 +--- Source/ARTPushActivationEvent.m | 46 -------- Source/ARTPushActivationState.h | 25 +--- Source/ARTPushActivationState.m | 152 ++++++++++++------------ Source/ARTPushActivationStateMachine.h | 11 +- Source/ARTPushActivationStateMachine.m | 155 +++++++++++++++++++++---- 8 files changed, 238 insertions(+), 216 deletions(-) diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 0b0aa6133..023bfeb60 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -9,6 +9,7 @@ #import #import "ARTTypes.h" +@class ARTRest; @class ARTDeviceDetails; @protocol ARTHTTPAuthenticatedExecutor; @@ -24,11 +25,13 @@ typedef ARTJsonObject ARTPushRecipient; @protocol ARTPushRegistererDelegate -- (void)ablyPushRegisterCallback:(nullable ARTErrorInfo *)error; -- (void)ablyPushDeregisterCallback:(nullable ARTErrorInfo *)error; +- (void)ablyPushActivateCallback:(nullable ARTErrorInfo *)error; +- (void)ablyPushDeactivateCallback:(nullable ARTErrorInfo *)error; @optional +- (void)ablyPushUpdateFailedCallback:(nullable ARTErrorInfo *)error; + - (void)ablyPushCustomRegister:(nullable ARTErrorInfo *)error deviceDetails:(nullable ARTDeviceDetails *)deviceDetails callback:(void (^ _Nonnull)(ARTUpdateToken * _Nonnull, ARTErrorInfo * _Nullable))callback; - (void)ablyPushCustomDeregister:(nullable ARTErrorInfo *)error deviceId:(nullable ARTDeviceId *)deviceId callback:(void (^ _Nullable)(ARTErrorInfo * _Nullable))callback; @@ -55,8 +58,8 @@ extern NSString *const ARTDeviceTokenKey; #ifdef TARGET_OS_IOS /// Push Registration token -+ (void)didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken; -+ (void)didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error; ++ (void)didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken rest:(ARTRest *)rest; ++ (void)didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error rest:(ARTRest *)rest; /// Register a device, including the information necessary to deliver push notifications to it. - (void)activate; diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 0349b1043..eb6751813 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -35,25 +35,31 @@ - (instancetype)init:(id)httpExecutor { return self; } -+ (ARTPushActivationStateMachine *)activationMachine { +- (ARTPushActivationStateMachine *)activationMachine { static dispatch_once_t once; static id activationMachineInstance; dispatch_once(&once, ^{ - activationMachineInstance = [[ARTPushActivationStateMachine alloc] init]; + activationMachineInstance = [[ARTPushActivationStateMachine alloc] init:_httpExecutor]; }); return activationMachineInstance; } -+ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - NSLog(@"ARTPush: device token received and stored"); ++ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken rest:(ARTRest *)rest { + NSLog(@"ARTPush: device token received %@", deviceToken); + NSData *currentDeviceToken = [[NSUserDefaults standardUserDefaults] dataForKey:ARTDeviceTokenKey]; + if ([currentDeviceToken isEqualToData:deviceToken]) { + // Already stored. + return; + } [[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:ARTDeviceTokenKey]; [[NSUserDefaults standardUserDefaults] synchronize]; - [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventGotPushDeviceDetails new]]; + NSLog(@"ARTPush: device token stored"); + [[rest.push activationMachine] sendEvent:[ARTPushActivationEventGotPushDeviceDetails new]]; } -+ (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { ++ (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error rest:(ARTRest *)rest { NSLog(@"ARTPush: device token not received (%@)", [error localizedDescription]); - [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; + [[rest.push activationMachine] sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; } - (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject { @@ -80,21 +86,11 @@ - (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonOb } - (void)activate { - if ([[_httpExecutor options] key]) { - [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledActivate newWithKey:[_httpExecutor options].key clientId:[_httpExecutor options].clientId]]; - } - else if ([[_httpExecutor options] token]) { - [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledActivate newWithToken:[_httpExecutor options].token clientId:[_httpExecutor options].clientId]]; - } + [[self activationMachine] sendEvent:[ARTPushActivationEventCalledActivate new]]; } - (void)deactivate { - if ([[_httpExecutor options] key]) { - [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledDeactivate newWithKey:[_httpExecutor options].key clientId:[_httpExecutor options].clientId]]; - } - else if ([[_httpExecutor options] token]) { - [[ARTPush activationMachine] sendEvent:[ARTPushActivationEventCalledDeactivate newWithToken:[_httpExecutor options].token clientId:[_httpExecutor options].clientId]]; - } + [[self activationMachine] sendEvent:[ARTPushActivationEventCalledDeactivate new]]; } @end diff --git a/Source/ARTPushActivationEvent.h b/Source/ARTPushActivationEvent.h index 69d5d7afd..a5a78a8bc 100644 --- a/Source/ARTPushActivationEvent.h +++ b/Source/ARTPushActivationEvent.h @@ -26,28 +26,12 @@ NS_ASSUME_NONNULL_BEGIN @end -/// Event with Auth credentials -@interface ARTPushActivationAuthEvent : ARTPushActivationEvent - -@property (nonatomic, readonly) NSString *key; -@property (nonatomic, readonly) NSString *token; -@property (nonatomic, readonly) NSString *clientId; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; -- (instancetype)initWithKey:(NSString *)key clientId:(nullable NSString *)clientId; -+ (instancetype)newWithKey:(NSString *)key clientId:(nullable NSString *)clientId; -- (instancetype)initWithToken:(NSString *)token clientId:(nullable NSString *)clientId; -+ (instancetype)newWithToken:(NSString *)token clientId:(nullable NSString *)clientId; - -@end - #pragma mark - Events -@interface ARTPushActivationEventCalledActivate : ARTPushActivationAuthEvent +@interface ARTPushActivationEventCalledActivate : ARTPushActivationEvent @end -@interface ARTPushActivationEventCalledDeactivate : ARTPushActivationAuthEvent +@interface ARTPushActivationEventCalledDeactivate : ARTPushActivationEvent @end @interface ARTPushActivationEventGotPushDeviceDetails : ARTPushActivationEvent diff --git a/Source/ARTPushActivationEvent.m b/Source/ARTPushActivationEvent.m index 672437218..6e9a26fbe 100644 --- a/Source/ARTPushActivationEvent.m +++ b/Source/ARTPushActivationEvent.m @@ -58,52 +58,6 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { @end -#pragma mark - Event with Auth credentials - -@implementation ARTPushActivationAuthEvent - -- (instancetype)initWithKey:(NSString *)key clientId:(NSString *)clientId { - if (self = [super init]) { - _key = key; - _clientId = clientId; - } - return self; -} - -+ (instancetype)newWithKey:(NSString *)key clientId:(NSString *)clientId { - return [[self alloc] initWithKey:key clientId:clientId]; -} - -- (instancetype)initWithToken:(NSString *)token clientId:(NSString *)clientId { - if (self = [super init]) { - _token = token; - _clientId = clientId; - } - return self; -} - -+ (instancetype)newWithToken:(NSString *)token clientId:(NSString *)clientId { - return [[self alloc] initWithToken:token clientId:clientId]; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { - if (self = [super initWithCoder:aDecoder]) { - _key = [aDecoder decodeObjectForKey:@"key"]; - _token = [aDecoder decodeObjectForKey:@"token"]; - _clientId = [aDecoder decodeObjectForKey:@"clientId"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [super encodeWithCoder:aCoder]; - [aCoder encodeObject:self.key forKey:@"key"]; - [aCoder encodeObject:self.token forKey:@"token"]; - [aCoder encodeObject:self.clientId forKey:@"clientId"]; -} - -@end - #pragma mark - Activation Events @implementation ARTPushActivationEventCalledActivate diff --git a/Source/ARTPushActivationState.h b/Source/ARTPushActivationState.h index 0fedc396c..ca7bf3a97 100644 --- a/Source/ARTPushActivationState.h +++ b/Source/ARTPushActivationState.h @@ -30,23 +30,6 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationPersistentState : ARTPushActivationState @end -/// Persistent State with Auth credentials -@interface ARTPushActivationAuthState : ARTPushActivationPersistentState - -@property (nonatomic, readonly) NSString *key; -@property (nonatomic, readonly) NSString *token; -@property (nonatomic, readonly) NSString *clientId; - -- (instancetype)initWithMachine:(ARTPushActivationStateMachine *)machine NS_UNAVAILABLE; -+ (instancetype)newWithMachine:(ARTPushActivationStateMachine *)machine NS_UNAVAILABLE; - -- (instancetype)initWithKey:(NSString *)key machine:(ARTPushActivationStateMachine *)machine clientId:(nullable NSString *)clientId; -+ (instancetype)newWithKey:(NSString *)key machine:(ARTPushActivationStateMachine *)machine clientId:(nullable NSString *)clientId; -- (instancetype)initWithToken:(NSString *)token machine:(ARTPushActivationStateMachine *)machine clientId:(nullable NSString *)clientId; -+ (instancetype)newWithToken:(NSString *)token machine:(ARTPushActivationStateMachine *)machine clientId:(nullable NSString *)clientId; - -@end - #pragma mark - States @interface ARTPushActivationStateNotActivated : ARTPushActivationPersistentState @@ -58,19 +41,19 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationStateWaitingForUpdateToken : ARTPushActivationState @end -@interface ARTPushActivationStateWaitingForPushDeviceDetails : ARTPushActivationAuthState +@interface ARTPushActivationStateWaitingForPushDeviceDetails : ARTPushActivationPersistentState @end -@interface ARTPushActivationStateWaitingForNewPushDeviceDetails : ARTPushActivationAuthState +@interface ARTPushActivationStateWaitingForNewPushDeviceDetails : ARTPushActivationPersistentState @end @interface ARTPushActivationStateWaitingForRegistrationUpdate : ARTPushActivationState @end -@interface ARTPushActivationStateWaitingForDeregistration : ARTPushActivationState +@interface ARTPushActivationStateAfterRegistrationUpdateFailed : ARTPushActivationPersistentState @end -@interface ARTPushActivationStateAfterRegistrationUpdateFailed : ARTPushActivationPersistentState +@interface ARTPushActivationStateWaitingForDeregistration : ARTPushActivationState @end NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m index 40ad8cd74..50ae56edc 100644 --- a/Source/ARTPushActivationState.m +++ b/Source/ARTPushActivationState.m @@ -66,88 +66,29 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { @implementation ARTPushActivationPersistentState @end -#pragma mark - Persistent State with Auth credentials - -@implementation ARTPushActivationAuthState - -- (instancetype)initWithKey:(NSString *)key machine:(ARTPushActivationStateMachine *)machine clientId:(NSString *)clientId { - if (self = [super initWithMachine:machine]) { - _key = key; - _clientId = clientId; - } - return self; -} - -+ (instancetype)newWithKey:(NSString *)key machine:(ARTPushActivationStateMachine *)machine clientId:(NSString *)clientId { - return [[self alloc] initWithKey:key clientId:clientId]; -} - -- (instancetype)initWithToken:(NSString *)token machine:(ARTPushActivationStateMachine *)machine clientId:(NSString *)clientId { - if (self = [super initWithMachine:machine]) { - _token = token; - _clientId = clientId; - } - return self; -} - -+ (instancetype)newWithToken:(NSString *)token machine:(ARTPushActivationStateMachine *)machine clientId:(NSString *)clientId { - return [[self alloc] initWithToken:token clientId:clientId]; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { - if (self = [super initWithCoder:aDecoder]) { - _key = [aDecoder decodeObjectForKey:@"key"]; - _token = [aDecoder decodeObjectForKey:@"token"]; - _clientId = [aDecoder decodeObjectForKey:@"clientId"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [super encodeWithCoder:aCoder]; - [aCoder encodeObject:self.key forKey:@"key"]; - [aCoder encodeObject:self.token forKey:@"token"]; - [aCoder encodeObject:self.clientId forKey:@"clientId"]; -} - -@end - #pragma mark - Activation States @implementation ARTPushActivationStateNotActivated - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + [self logEventTransition:event file:__FILE__ line:__LINE__]; if ([event isKindOfClass:[ARTPushActivationEventCalledDeactivate class]]) { - [self logEventTransition:event file:__FILE__ line:__LINE__]; [self.machine callDeactivatedCallback:nil]; return self; } else if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { - [self logEventTransition:event file:__FILE__ line:__LINE__]; - ARTPushActivationEventCalledActivate *activateEvent = (ARTPushActivationEventCalledActivate *)event; ARTLocalDevice *local = [ARTLocalDevice local]; if (local.updateToken != nil) { // Already registered. - if (activateEvent.key) { - return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithKey:activateEvent.key machine:self.machine clientId:activateEvent.clientId]; - } - if (activateEvent.token) { - return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithToken:activateEvent.token machine:self.machine clientId:activateEvent.clientId]; - } - return nil; + return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithMachine:self.machine]; } if (local.registrationToken != nil) { [self.machine sendEvent:[ARTPushActivationEventGotPushDeviceDetails new]]; } - if (activateEvent.key) { - return [ARTPushActivationStateWaitingForPushDeviceDetails newWithKey:activateEvent.key machine:self.machine clientId:activateEvent.clientId]; - } - if (activateEvent.token) { - return [ARTPushActivationStateWaitingForPushDeviceDetails newWithToken:activateEvent.token machine:self.machine clientId:activateEvent.clientId]; - } + return [ARTPushActivationStateWaitingForPushDeviceDetails newWithMachine:self.machine]; } return nil; } @@ -166,7 +107,19 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @implementation ARTPushActivationStateWaitingForUpdateToken - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { - // TODO + [self logEventTransition:event file:__FILE__ line:__LINE__]; + if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { + return self; + } + else if ([event isKindOfClass:[ARTPushActivationEventGotUpdateToken class]]) { + [ARTLocalDevice local].updateToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceUpdateTokenKey]; + [self.machine callActivatedCallback:nil]; + return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithMachine:self.machine]; + } + else if ([event isKindOfClass:[ARTPushActivationEventGettingUpdateTokenFailed class]]) { + [self.machine callActivatedCallback:[(ARTPushActivationEventGettingUpdateTokenFailed *)event error]]; + return [ARTPushActivationStateNotActivated newWithMachine:self.machine]; + } return nil; } @@ -175,6 +128,7 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @implementation ARTPushActivationStateWaitingForPushDeviceDetails - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { + [self logEventTransition:event file:__FILE__ line:__LINE__]; if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { return [ARTPushActivationStateCalledActivate newWithMachine:self.machine]; } @@ -183,17 +137,7 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { return [ARTPushActivationStateNotActivated newWithMachine:self.machine]; } else if ([event isKindOfClass:[ARTPushActivationEventGotPushDeviceDetails class]]) { - id httpExecutor; - if (self.key) { - httpExecutor = [ARTRest createWithKey:self.key]; - } - else if (self.token) { - httpExecutor = [ARTRest createWithToken:self.token]; - } - else { - [NSException raise:@"ARTPushActivationStateWaitingForPushDeviceDetails: must have a key or token for authentication" format:@""]; - } - [self.machine deviceRegistration:httpExecutor error:nil]; + [self.machine deviceRegistration:nil]; return [ARTPushActivationStateWaitingForUpdateToken newWithMachine:self.machine]; } return nil; @@ -204,7 +148,19 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @implementation ARTPushActivationStateWaitingForNewPushDeviceDetails - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { - // TODO + [self logEventTransition:event file:__FILE__ line:__LINE__]; + if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { + [self.machine callActivatedCallback:nil]; + return self; + } + else if ([event isKindOfClass:[ARTPushActivationEventCalledDeactivate class]]) { + [self.machine deviceUnregistration:nil]; + return [ARTPushActivationStateWaitingForDeregistration newWithMachine:self.machine]; + } + else if ([event isKindOfClass:[ARTPushActivationEventGotPushDeviceDetails class]]) { + [self.machine deviceUpdateRegistration:nil]; + return [ARTPushActivationStateWaitingForRegistrationUpdate newWithMachine:self.machine]; + } return nil; } @@ -213,26 +169,62 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @implementation ARTPushActivationStateWaitingForRegistrationUpdate - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { - // TODO + [self logEventTransition:event file:__FILE__ line:__LINE__]; + if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { + [self.machine callActivatedCallback:nil]; + return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithMachine:self.machine]; + } + else if ([event isKindOfClass:[ARTPushActivationEventRegistrationUpdated class]]) { + return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithMachine:self.machine]; + } + else if ([event isKindOfClass:[ARTPushActivationEventUpdatingRegistrationFailed class]]) { + [self.machine callUpdateFailedCallback:[(ARTPushActivationEventUpdatingRegistrationFailed *)event error]]; + return [ARTPushActivationStateAfterRegistrationUpdateFailed newWithMachine:self.machine]; + } return nil; } @end -@implementation ARTPushActivationStateWaitingForDeregistration +@implementation ARTPushActivationStateAfterRegistrationUpdateFailed - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { - // TODO + [self logEventTransition:event file:__FILE__ line:__LINE__]; + if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]] || + [event isKindOfClass:[ARTPushActivationEventGotPushDeviceDetails class]]) { + [self.machine deviceRegistration:nil]; + return [ARTPushActivationStateWaitingForUpdateToken newWithMachine:self.machine]; + } + else if ([event isKindOfClass:[ARTPushActivationEventCalledDeactivate class]]) { + [self.machine callDeactivatedCallback:nil]; + return [ARTPushActivationStateNotActivated newWithMachine:self.machine]; + } return nil; } @end -@implementation ARTPushActivationStateAfterRegistrationUpdateFailed +@implementation ARTPushActivationStateWaitingForDeregistration - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { - // TODO + [self logEventTransition:event file:__FILE__ line:__LINE__]; + if ([event isKindOfClass:[ARTPushActivationEventCalledDeactivate class]]) { + return [ARTPushActivationStateWaitingForDeregistration newWithMachine:self.machine]; + } + else if ([event isKindOfClass:[ARTPushActivationEventDeregistered class]]) { + ARTLocalDevice *local = [ARTLocalDevice local]; + local.updateToken = nil; + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceUpdateTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self.machine callDeactivatedCallback:nil]; + return [ARTPushActivationStateNotActivated newWithMachine:self.machine]; + } + else if ([event isKindOfClass:[ARTPushActivationEventDeregistrationFailed class]]) { + [self.machine callDeactivatedCallback:[(ARTPushActivationEventDeregistrationFailed *)event error]]; + return [ARTPushActivationStateWaitingForDeregistration newWithMachine:self.machine]; + } return nil; + } @end diff --git a/Source/ARTPushActivationStateMachine.h b/Source/ARTPushActivationStateMachine.h index 1e5d1015b..6d7907926 100644 --- a/Source/ARTPushActivationStateMachine.h +++ b/Source/ARTPushActivationStateMachine.h @@ -17,16 +17,21 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationStateMachine : NSObject -- (instancetype)init; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init:(id)httpExecutor; - (void)sendEvent:(ARTPushActivationEvent *)event; @end @interface ARTPushActivationStateMachine (Protected) -- (void)deviceRegistration:(id)httpExecutor error:(nullable ARTErrorInfo *)error; -- (void)deviceUnregistration:(id)httpExecutor error:(nullable ARTErrorInfo *)error; +- (void)deviceRegistration:(nullable ARTErrorInfo *)error; +- (void)deviceUpdateRegistration:(nullable ARTErrorInfo *)error; +- (void)deviceUnregistration:(nullable ARTErrorInfo *)error; +- (void)callActivatedCallback:(nullable ARTErrorInfo *)error; - (void)callDeactivatedCallback:(nullable ARTErrorInfo *)error; +- (void)callUpdateFailedCallback:(nullable ARTErrorInfo *)error; @end NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m index c0bce7e35..bcb562b98 100644 --- a/Source/ARTPushActivationStateMachine.m +++ b/Source/ARTPushActivationStateMachine.m @@ -16,6 +16,7 @@ #import "ARTJsonLikeEncoder.h" #import "ARTTypes.h" #import "ARTLocalDevice.h" +#import "ARTDevicePushDetails.h" #ifdef TARGET_OS_IOS #import @@ -27,10 +28,13 @@ @implementation ARTPushActivationStateMachine { ARTPushActivationState *_current; NSMutableArray *_pendingEvents; + id _httpExecutor; + } -- (instancetype)init { +- (instancetype)init:(id)httpExecutor { if (self = [super init]) { + _httpExecutor = httpExecutor; // Unarquiving NSData *stateData = [[NSUserDefaults standardUserDefaults] objectForKey:ARTPushActivationCurrentStateKey]; _current = [NSKeyedUnarchiver unarchiveObjectWithData:stateData]; @@ -83,7 +87,7 @@ - (void)persist { [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_pendingEvents] forKey:ARTPushActivationPendingEventsKey]; } -- (void)deviceRegistration:(id)httpExecutor error:(ARTErrorInfo *)error { +- (void)deviceRegistration:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS ARTLocalDevice *local = [ARTLocalDevice local]; @@ -97,24 +101,24 @@ - (void)deviceRegistration:(id)httpExecutor error: SEL customRegisterMethodSelector = @selector(ablyPushCustomRegister:deviceDetails:callback:); if ([delegate respondsToSelector:customRegisterMethodSelector]) { [delegate ablyPushCustomRegister:error deviceDetails:local callback:^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { - if (![delegate respondsToSelector:@selector(ablyPushRegisterCallback:)]) { + if (![delegate respondsToSelector:@selector(ablyPushActivateCallback:)]) { [NSException raise:@"ablyPushRegisterCallback: method is required" format:@""]; } if (error) { // Failed - [delegate ablyPushRegisterCallback:error]; + [delegate ablyPushActivateCallback:error]; [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:error]]; } else if (updateToken) { // Success - [delegate ablyPushRegisterCallback:nil]; + [delegate ablyPushActivateCallback:nil]; [[NSUserDefaults standardUserDefaults] setObject:updateToken forKey:ARTDeviceUpdateTokenKey]; [[NSUserDefaults standardUserDefaults] synchronize]; [self sendEvent:[ARTPushActivationEventGotUpdateToken new]]; } else { ARTErrorInfo *missingUpdateTokenError = [ARTErrorInfo createWithCode:0 message:@"UpdateToken is expected"]; - [delegate ablyPushRegisterCallback:missingUpdateTokenError]; + [delegate ablyPushActivateCallback:missingUpdateTokenError]; [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:missingUpdateTokenError]]; } }]; @@ -124,30 +128,104 @@ - (void)deviceRegistration:(id)httpExecutor error: // Asynchronous HTTP request NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"]]; request.HTTPMethod = @"POST"; - request.HTTPBody = [[httpExecutor defaultEncoder] encodeDeviceDetails:local]; - [request setValue:[[httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + request.HTTPBody = [[_httpExecutor defaultEncoder] encodeDeviceDetails:local]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; - [[httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"device registration with request %@", request]; - [httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + [[_httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"device registration with request %@", request]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (response.statusCode == 201 /*Created*/) { - ARTDeviceDetails *deviceDetails = [[httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; + ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; [[NSUserDefaults standardUserDefaults] setObject:deviceDetails.updateToken forKey:ARTDeviceUpdateTokenKey]; [[NSUserDefaults standardUserDefaults] synchronize]; [self sendEvent:[ARTPushActivationEventGotUpdateToken new]]; } else if (error) { - [[httpExecutor logger] error:@"%@: device registration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + [[_httpExecutor logger] error:@"%@: device registration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; } else { - [[httpExecutor logger] error:@"%@: device registration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + [[_httpExecutor logger] error:@"%@: device registration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:[ARTErrorInfo createWithCode:response.statusCode message:@"Device registration failed"]]]; } }]; #endif } -- (void)deviceUnregistration:(id)httpExecutor error:(ARTErrorInfo *)error { +- (void)deviceUpdateRegistration:(ARTErrorInfo *)error { + #ifdef TARGET_OS_IOS + ARTLocalDevice *local = [ARTLocalDevice local]; + + if (![[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { + [NSException raise:@"ARTPushRegistererDelegate must be implemented on AppDelegate" format:@""]; + } + + id delegate = [UIApplication sharedApplication].delegate; + + // Custom register + SEL customRegisterMethodSelector = @selector(ablyPushCustomRegister:deviceDetails:callback:); + if ([delegate respondsToSelector:customRegisterMethodSelector]) { + [delegate ablyPushCustomRegister:error deviceDetails:local callback:^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { + if (![delegate respondsToSelector:@selector(ablyPushActivateCallback:)]) { + [NSException raise:@"ablyPushRegisterCallback: method is required" format:@""]; + } + if (error) { + // Failed + [delegate ablyPushActivateCallback:error]; + [self sendEvent:[ARTPushActivationEventUpdatingRegistrationFailed newWithError:error]]; + } + else if (updateToken) { + // Success + [delegate ablyPushActivateCallback:nil]; + [[NSUserDefaults standardUserDefaults] setObject:updateToken forKey:ARTDeviceUpdateTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + local.updateToken = updateToken; + [self sendEvent:[ARTPushActivationEventRegistrationUpdated new]]; + } + else { + ARTErrorInfo *missingUpdateTokenError = [ARTErrorInfo createWithCode:0 message:@"UpdateToken is expected"]; + [delegate ablyPushActivateCallback:missingUpdateTokenError]; + [self sendEvent:[ARTPushActivationEventUpdatingRegistrationFailed newWithError:missingUpdateTokenError]]; + } + }]; + return; + } + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[[NSURL URLWithString:@"/push/deviceRegistrations"] URLByAppendingPathComponent:local.id]]; + NSData *tokenData = [local.updateToken dataUsingEncoding:NSUTF8StringEncoding]; + NSString *tokenBase64 = [tokenData base64EncodedStringWithOptions:0]; + [request setValue:[NSString stringWithFormat:@"Bearer %@", tokenBase64] forHTTPHeaderField:@"Authorization"]; + request.HTTPMethod = @"PUT"; + request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ + @"push": @{ + @"metadata": @{ + @"deviceToken": local.push.deviceToken, + } + } + }]; + [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + + [[_httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"update device with request %@", request]; + [_httpExecutor executeRequest:request completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (response.statusCode == 200 /*OK*/) { + ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; + [[NSUserDefaults standardUserDefaults] setObject:deviceDetails.updateToken forKey:ARTDeviceUpdateTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + local.updateToken = deviceDetails.updateToken; + [self sendEvent:[ARTPushActivationEventRegistrationUpdated new]]; + } + else if (error) { + [[_httpExecutor logger] error:@"%@: update device failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + [self sendEvent:[ARTPushActivationEventUpdatingRegistrationFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; + } + else { + [[_httpExecutor logger] error:@"%@: update device failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + [self sendEvent:[ARTPushActivationEventUpdatingRegistrationFailed newWithError:[ARTErrorInfo createWithCode:response.statusCode message:@"Update device failed"]]]; + } + }]; + #endif +} + +- (void)deviceUnregistration:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS ARTLocalDevice *local = [ARTLocalDevice local]; @@ -161,19 +239,19 @@ - (void)deviceUnregistration:(id)httpExecutor erro SEL customDeregisterMethodSelector = @selector(ablyPushCustomDeregister:deviceId:callback:); if ([delegate respondsToSelector:customDeregisterMethodSelector]) { [delegate ablyPushCustomDeregister:error deviceId:local.id callback:^(ARTErrorInfo *error) { - if (![delegate respondsToSelector:@selector(ablyPushDeregisterCallback:)]) { + if (![delegate respondsToSelector:@selector(ablyPushDeactivateCallback:)]) { [NSException raise:@"ablyPushDeregisterCallback: method is required" format:@""]; } if (error) { // Failed - [delegate ablyPushRegisterCallback:error]; + [delegate ablyPushDeactivateCallback:error]; [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:error]]; } else { // Success [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceUpdateTokenKey]; [[NSUserDefaults standardUserDefaults] synchronize]; - [delegate ablyPushDeregisterCallback:nil]; + [delegate ablyPushDeactivateCallback:nil]; } }]; return; @@ -188,30 +266,57 @@ - (void)deviceUnregistration:(id)httpExecutor erro NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; request.HTTPMethod = @"DELETE"; - [[httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"device deregistration with request %@", request]; - [httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + [[_httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"device deregistration with request %@", request]; + [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (response.statusCode == 200 /*OK*/) { - [[httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"successfully deactivate device"]; + [[_httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"successfully deactivate device"]; [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceUpdateTokenKey]; [[NSUserDefaults standardUserDefaults] synchronize]; + [self sendEvent:[ARTPushActivationEventDeregistered new]]; } else if (error) { - [[httpExecutor logger] error:@"%@: device deregistration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + [[_httpExecutor logger] error:@"%@: device deregistration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; } else { - [[httpExecutor logger] error:@"%@: device deregistration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + [[_httpExecutor logger] error:@"%@: device deregistration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; + [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:[ARTErrorInfo createWithCode:response.statusCode message:@"Device registration failed"]]]; } }]; #endif } +- (void)callActivatedCallback:(ARTErrorInfo *)error { + #ifdef TARGET_OS_IOS + if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { + id delegate = [UIApplication sharedApplication].delegate; + SEL activateCallbackMethodSelector = @selector(ablyPushActivateCallback:); + if ([delegate respondsToSelector:activateCallbackMethodSelector]) { + [delegate ablyPushActivateCallback:error]; + } + } + #endif +} + - (void)callDeactivatedCallback:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { id delegate = [UIApplication sharedApplication].delegate; - SEL deregisterCallbackMethodSelector = @selector(ablyPushDeregisterCallback:); - if ([delegate respondsToSelector:deregisterCallbackMethodSelector]) { - [delegate ablyPushDeregisterCallback:error]; + SEL deactivateCallbackMethodSelector = @selector(ablyPushDeactivateCallback:); + if ([delegate respondsToSelector:deactivateCallbackMethodSelector]) { + [delegate ablyPushDeactivateCallback:error]; + } + } + #endif +} + +- (void)callUpdateFailedCallback:(nullable ARTErrorInfo *)error { + #ifdef TARGET_OS_IOS + if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { + id delegate = [UIApplication sharedApplication].delegate; + SEL updateFailedCallbackMethodSelector = @selector(ablyPushUpdateFailedCallback:); + if ([delegate respondsToSelector:updateFailedCallbackMethodSelector]) { + [delegate ablyPushUpdateFailedCallback:error]; } } #endif From 1283becc7dc19857a100eea8a6ac6fe2ed04568e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20C=C3=A1rdenas?= Date: Wed, 19 Apr 2017 00:28:08 +0200 Subject: [PATCH 34/46] tmp --- Ably.xcodeproj/project.pbxproj | 12 ++ Examples/Tests/Podfile | 2 +- Examples/Tests/Podfile.lock | 14 +- .../Tests/Tests.xcodeproj/project.pbxproj | 4 +- Source/ARTDeviceDetails.h | 13 +- Source/ARTDeviceDetails.m | 33 +--- Source/ARTDevicePushDetails.h | 19 +-- Source/ARTDevicePushDetails.m | 25 +-- Source/ARTJsonLikeEncoder.m | 24 +-- Source/ARTLocalDevice+Private.h | 30 ++++ Source/ARTLocalDevice.h | 7 +- Source/ARTLocalDevice.m | 108 ++++++++++--- Source/ARTPush+Private.h | 24 +++ Source/ARTPush.h | 27 ++-- Source/ARTPush.m | 60 +++---- Source/ARTPushActivationState.h | 3 - Source/ARTPushActivationState.m | 11 +- Source/ARTPushActivationStateMachine.h | 6 +- Source/ARTPushActivationStateMachine.m | 153 +++++++----------- Source/ARTPushAdmin+Private.h | 22 +++ Source/ARTPushAdmin.h | 1 - Source/ARTPushChannel.h | 20 ++- Source/ARTPushChannel.m | 1 - Source/ARTPushChannelSubscription.h | 10 +- Source/ARTPushChannelSubscription.m | 4 +- Source/ARTRealtime.h | 3 + Source/ARTRealtime.m | 7 +- Source/ARTRest.h | 4 + Source/ARTRest.m | 11 +- 29 files changed, 368 insertions(+), 290 deletions(-) create mode 100644 Source/ARTLocalDevice+Private.h create mode 100644 Source/ARTPush+Private.h create mode 100644 Source/ARTPushAdmin+Private.h diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index f00677878..e6de4c216 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -209,6 +209,9 @@ EBAB9A6F1C69702800AF036B /* ReadmeExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBAB9A6E1C69702800AF036B /* ReadmeExamples.swift */; }; EBAB9A711C69719900AF036B /* ARTReadmeExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = EBAB9A701C69719900AF036B /* ARTReadmeExamples.m */; }; EBFA366E1D58B05000B09AA7 /* ARTRestPresence+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = EB7617701CB6C18C00D0981E /* ARTRestPresence+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + EBFFAC191E97919C003E7326 /* ARTLocalDevice+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = EBFFAC181E97919C003E7326 /* ARTLocalDevice+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + EBFFAC1B1E97EF68003E7326 /* ARTPushAdmin+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = EBFFAC1A1E97EF5C003E7326 /* ARTPushAdmin+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + EBFFAC1D1E97FB76003E7326 /* ARTPush+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = EBFFAC1C1E97FB23003E7326 /* ARTPush+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ @@ -470,6 +473,9 @@ EB9C530C1CD7BFF300.8.557 /* ARTJsonLikeEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTJsonLikeEncoder.m; sourceTree = ""; }; EBAB9A6E1C69702800AF036B /* ReadmeExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeExamples.swift; sourceTree = ""; }; EBAB9A701C69719900AF036B /* ARTReadmeExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTReadmeExamples.m; sourceTree = ""; }; + EBFFAC181E97919C003E7326 /* ARTLocalDevice+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARTLocalDevice+Private.h"; sourceTree = ""; }; + EBFFAC1A1E97EF5C003E7326 /* ARTPushAdmin+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ARTPushAdmin+Private.h"; sourceTree = ""; }; + EBFFAC1C1E97FB23003E7326 /* ARTPush+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ARTPush+Private.h"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -656,6 +662,7 @@ isa = PBXGroup; children = ( D7AE18C71E5B40C900478D82 /* ARTPushAdmin.h */, + EBFFAC1A1E97EF5C003E7326 /* ARTPushAdmin+Private.h */, D7AE18C81E5B40C900478D82 /* ARTPushAdmin.m */, D7AE18CC1E5B40FE00478D82 /* ARTPushDeviceRegistrations.h */, D7AE18CD1E5B40FE00478D82 /* ARTPushDeviceRegistrations.m */, @@ -847,6 +854,7 @@ isa = PBXGroup; children = ( D7B6218E1E4A6E0200684474 /* ARTPush.h */, + EBFFAC1C1E97FB23003E7326 /* ARTPush+Private.h */, D7B6218F1E4A6E0200684474 /* ARTPush.m */, D7B621961E4A762A00684474 /* ARTPushChannel.h */, D7B621971E4A762A00684474 /* ARTPushChannel.m */, @@ -856,6 +864,7 @@ D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */, D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */, D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */, + EBFFAC181E97919C003E7326 /* ARTLocalDevice+Private.h */, D7DEAFCF1E65926D00D23F24 /* ARTLocalDevice.h */, D7DEAFD01E65926D00D23F24 /* ARTLocalDevice.m */, D71966E71E5DFFC6000974DD /* Activation State Machine */, @@ -932,11 +941,13 @@ D7F1D3771BF4DE72001A4B5E /* ARTRealtimePresence.h in Headers */, 1C578E1F1B3435CA00EF46EC /* ARTFallback.h in Headers */, EB7617721CB6CBFF00D0981E /* ARTRealtimePresence+Private.h in Headers */, + EBFFAC1B1E97EF68003E7326 /* ARTPushAdmin+Private.h in Headers */, D7D5A69C1CA40C350071BD6D /* ARTConnectionDetails+Private.h in Headers */, D7B621901E4A6E0200684474 /* ARTPush.h in Headers */, 96E408471A3895E800087F77 /* ARTWebSocketTransport.h in Headers */, EB503C8A1C7F1FE40053AF00 /* ARTLog+Private.h in Headers */, 96E4083F1A3892C700087F77 /* ARTRealtimeTransport.h in Headers */, + EBFFAC191E97919C003E7326 /* ARTLocalDevice+Private.h in Headers */, D746AE4F1BBD84E7003ECEF8 /* ARTChannelOptions.h in Headers */, D7588AF31BFF91B800BB8279 /* ARTURLSessionServerTrust.h in Headers */, D746AE3C1BBC5AE1003ECEF8 /* ARTRealtimeChannel.h in Headers */, @@ -956,6 +967,7 @@ D7F1D3731BF4DE07001A4B5E /* ARTRestPresence.h in Headers */, D7B17EE31C07208B00A6958E /* ARTConnectionDetails.h in Headers */, EBFA366E1D58B05000B09AA7 /* ARTRestPresence+Private.h in Headers */, + EBFFAC1D1E97FB76003E7326 /* ARTPush+Private.h in Headers */, EB2D85011CD769C800F23CDA /* ARTOSReachability.h in Headers */, 960D07971A46FFC300ED8C8C /* ARTRest+Private.h in Headers */, 1C05CF201AC1D7EB00687AC9 /* ARTRealtime+Private.h in Headers */, diff --git a/Examples/Tests/Podfile b/Examples/Tests/Podfile index 76c9436f2..61c44d484 100644 --- a/Examples/Tests/Podfile +++ b/Examples/Tests/Podfile @@ -1,6 +1,6 @@ use_frameworks! -pod 'Ably', :path => '../..' +pod 'Ably', '1.0.2' target 'Tests' do diff --git a/Examples/Tests/Podfile.lock b/Examples/Tests/Podfile.lock index 1aaf0078b..fb1dd6a46 100644 --- a/Examples/Tests/Podfile.lock +++ b/Examples/Tests/Podfile.lock @@ -1,22 +1,18 @@ PODS: - - Ably (0.8.9): + - Ably (1.0.2): - msgpack (= 0.1.8) - SocketRocket (= 0.5.1) - msgpack (0.1.8) - SocketRocket (0.5.1) DEPENDENCIES: - - Ably (from `../..`) - -EXTERNAL SOURCES: - Ably: - :path: "../.." + - Ably (= 1.0.2) SPEC CHECKSUMS: - Ably: faaabeef80b7023bb724829cfd35d658d016ec08 + Ably: 86f0d104df831301a4085c0fd0dc9dcad4cb3058 msgpack: 97491d2ea799408f4694f2c7d7fd79baf77853dd SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531 -PODFILE CHECKSUM: 6af34bf7f91045b23539816c1d0cfe253bec5ea5 +PODFILE CHECKSUM: d486557ee942e3a02734b807d295f067067c212e -COCOAPODS: 1.0.1 +COCOAPODS: 1.2.0 diff --git a/Examples/Tests/Tests.xcodeproj/project.pbxproj b/Examples/Tests/Tests.xcodeproj/project.pbxproj index 5f06f2e9a..b8f79b6bb 100644 --- a/Examples/Tests/Tests.xcodeproj/project.pbxproj +++ b/Examples/Tests/Tests.xcodeproj/project.pbxproj @@ -275,7 +275,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 80379784248687E10A66CCC2 /* [CP] Check Pods Manifest.lock */ = { @@ -290,7 +290,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; DD907CB481BED4C761C68F03 /* [CP] Embed Pods Frameworks */ = { diff --git a/Source/ARTDeviceDetails.h b/Source/ARTDeviceDetails.h index f40c8511a..adb8d670c 100644 --- a/Source/ARTDeviceDetails.h +++ b/Source/ARTDeviceDetails.h @@ -13,17 +13,14 @@ NS_ASSUME_NONNULL_BEGIN -extern NSString *const ARTDevicePlatform; -extern NSString *const ARTDeviceFormFactor; - @interface ARTDeviceDetails : NSObject -@property (nonatomic, readonly) ARTDeviceId *id; +@property (nonatomic) ARTDeviceId *id; @property (nullable, nonatomic) NSString *clientId; -@property (nonatomic, readonly) NSString *platform; -@property (nonatomic, readonly) NSString *formFactor; -@property (nullable, nonatomic) NSDictionary *metadata; -@property (nonatomic, readonly) ARTDevicePushDetails *push; +@property (nonatomic) NSString *platform; +@property (nonatomic) NSString *formFactor; +@property (nonatomic) NSDictionary *metadata; +@property (nonatomic) ARTDevicePushDetails *push; @property (nullable, nonatomic) ARTUpdateToken *updateToken; - (instancetype)init NS_UNAVAILABLE; diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index bab269685..c6e163857 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -9,46 +9,15 @@ #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" -NSString *const ARTDevicePlatform = @"ios"; - -#if TARGET_OS_IOS -#import -NSString *const ARTDeviceFormFactor = @"phone"; -#elif TARGET_OS_TV -NSString *const ARTDeviceFormFactor = @"tv"; -#elif TARGET_OS_WATCH -NSString *const ARTDeviceFormFactor = @"watch"; -#elif TARGET_OS_SIMULATOR -NSString *const ARTDeviceFormFactor = @"simulator"; -#elif TARGET_OS_MAC -NSString *const ARTDeviceFormFactor = @"desktop"; -#else -NSString *const ARTDeviceFormFactor = @"embedded"; -#endif - @implementation ARTDeviceDetails - (instancetype)initWithId:(ARTDeviceId *)deviceId { if (self = [super init]) { _id = deviceId; _push = [[ARTDevicePushDetails alloc] init]; + _metadata = [[NSDictionary alloc] init]; } return self; } -- (NSString *)platform { - return ARTDevicePlatform; -} - -- (NSString *)formFactor { - switch (UI_USER_INTERFACE_IDIOM()) { - case UIUserInterfaceIdiomPad: - return @"tablet"; - case UIUserInterfaceIdiomCarPlay: - return @"car"; - default: - return ARTDeviceFormFactor; - } -} - @end diff --git a/Source/ARTDevicePushDetails.h b/Source/ARTDevicePushDetails.h index 5ed24bd47..264859f41 100644 --- a/Source/ARTDevicePushDetails.h +++ b/Source/ARTDevicePushDetails.h @@ -12,23 +12,14 @@ NS_ASSUME_NONNULL_BEGIN -extern NSString *const ARTDevicePushTransportType; - -typedef NS_ENUM(NSUInteger, ARTDevicePushState) { - ARTDevicePushStateInitialized, - ARTDevicePushStateActive, - ARTDevicePushStateFailing, - ARTDevicePushStateFailed -}; - -ARTDevicePushState ARTDevicePushStateFromStr(NSString *value); - @interface ARTDevicePushDetails : NSObject -@property (nonatomic, readonly) NSString *transportType; -@property (nonatomic, readonly) NSData *deviceToken; -@property (nonatomic, assign) ARTDevicePushState state; +@property (nullable, nonatomic) NSString *transportType; +@property (nullable, nonatomic) NSString *state; @property (nullable, nonatomic) ARTErrorInfo *errorReason; +@property (nonatomic) NSDictionary *metadata; + +- (instancetype)init; @end diff --git a/Source/ARTDevicePushDetails.m b/Source/ARTDevicePushDetails.m index 997302ab1..21adba207 100644 --- a/Source/ARTDevicePushDetails.m +++ b/Source/ARTDevicePushDetails.m @@ -9,28 +9,13 @@ #import "ARTDevicePushDetails.h" #import "ARTPush.h" -NSString *const ARTDevicePushTransportType = @"apns"; - -ARTDevicePushState ARTDevicePushStateFromStr(NSString *value) { - if ([[value lowercaseString] isEqualToString:@"active"]) { - return ARTDevicePushStateActive; - } - else if ([[value lowercaseString] isEqualToString:@"failing"]) { - return ARTDevicePushStateFailing; - } - else { - return ARTDevicePushStateFailed; - } -} - @implementation ARTDevicePushDetails -- (NSString *)transportType { - return ARTDevicePushTransportType; -} - -- (NSData *)deviceToken { - return [[NSUserDefaults standardUserDefaults] dataForKey:ARTDeviceTokenKey]; +- (instancetype)init { + if (self = [super init]) { + _metadata = [[NSDictionary alloc] init]; + } + return self; } @end diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index f4d671f04..468d96205 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -608,8 +608,12 @@ - (ARTDeviceDetails *)deviceDetailsFromDictionary:(NSDictionary *)input error:(N } ARTDeviceDetails *deviceDetails = [[ARTDeviceDetails alloc] initWithId:[input artString:@"id"]]; + deviceDetails.clientId = [input artString:@"clientId"]; + deviceDetails.platform = [input artString:@"platform"]; + deviceDetails.formFactor = [input artString:@"formFactor"]; + deviceDetails.metadata = [input valueForKey:@"metadata"]; + deviceDetails.push = [self devicePushDetailsFromDictionary:input[@"push"] error:error]; deviceDetails.updateToken = [input artString:@"updateToken"]; - deviceDetails.push.state = ARTDevicePushStateFromStr([input artString:@"state"]); return deviceDetails; } @@ -618,15 +622,7 @@ - (NSDictionary *)devicePushDetailsToDictionary:(ARTDevicePushDetails *)devicePu NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; dictionary[@"transportType"] = devicePushDetails.transportType; - - if (devicePushDetails.deviceToken) { - // HEX string, i.e.: <12ce7dda 8032c423 8f8bd40f 3484e5bb f4698da5 8b7fdf8d 5c55e0a2 XXXXXXXX> - // Normalizing token by removing symbols and spaces, i.e.: 12ce7dda8032c4238f8bd40f3484e5bbf4698da58b7fdf8d5c55e0a2XXXXXXXX - NSString *token = [[[devicePushDetails.deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""]; - dictionary[@"metadata"] = @{ - @"deviceToken": token, - }; - } + dictionary[@"metadata"] = devicePushDetails.metadata; return dictionary; } @@ -639,7 +635,13 @@ - (ARTDevicePushDetails *)devicePushDetailsFromDictionary:(NSDictionary *)input } ARTDevicePushDetails *devicePushDetails = [[ARTDevicePushDetails alloc] init]; - devicePushDetails.state = ARTDevicePushStateFromStr([input artString:@"state"]); + devicePushDetails.transportType = [input artString:@"transportType"]; + devicePushDetails.state = [input artString:@"state"]; + NSDictionary *error = [input valueForKey:@"errorReason"]; + if (error) { + devicePushDetails.state.errorReason = [ARTErrorInfo createWithCode:[[error artNumber:@"code"] intValue] status:[[error artNumber:@"statusCode"] intValue] message:[error artString:@"message"]]; + } + devicePushDetails.metadata = [input valueForKey:@"metadata"]; return devicePushDetails; } diff --git a/Source/ARTLocalDevice+Private.h b/Source/ARTLocalDevice+Private.h new file mode 100644 index 000000000..e750f76cd --- /dev/null +++ b/Source/ARTLocalDevice+Private.h @@ -0,0 +1,30 @@ +// +// ARTLocalDevice+Private.h +// Ably +// +// Created by Toni Cárdenas on 07/04/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#ifndef ARTLocalDevice_Private_h +#define ARTLocalDevice_Private_h + +#import "ARTRest.h" + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const ARTDeviceIdKey; +extern NSString *const ARTDeviceUpdateTokenKey; +extern NSString *const ARTDeviceTokenKey; + +@interface ARTLocalDevice : ARTDeviceDetails + ++ (ARTLocalDevice *_Nonnull)load:(ARTRest *)rest; +- (void)setAndPersistDeviceToken:(NSString *_Nullable)token; +- (void)setAndPersistUpdateToken:(NSString *_Nullable)token; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* ARTLocalDevice_Private_h */ diff --git a/Source/ARTLocalDevice.h b/Source/ARTLocalDevice.h index 27ed937d8..f45241328 100644 --- a/Source/ARTLocalDevice.h +++ b/Source/ARTLocalDevice.h @@ -13,13 +13,10 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTLocalDevice : ARTDeviceDetails -@property (nonatomic, readonly) ARTDeviceToken *registrationToken; - -- (instancetype)init; -+ (ARTLocalDevice *)local; +- (instancetype)init NS_UNAVAILABLE; - (void)resetId; -- (void)resetUpdateToken:(void (^)(ARTErrorInfo * _Nullable))callback; +- (void)resetUpdateToken:(void (^_Nullable)(ARTErrorInfo * _Nullable))callback; @end diff --git a/Source/ARTLocalDevice.m b/Source/ARTLocalDevice.m index 06b24d882..b0533e494 100644 --- a/Source/ARTLocalDevice.m +++ b/Source/ARTLocalDevice.m @@ -6,45 +6,117 @@ // Copyright © 2017 Ably. All rights reserved. // -#import "ARTLocalDevice.h" +#import "ARTLocalDevice+Private.h" #import "ARTDevicePushDetails.h" #import "ARTPush.h" #import +NSString *const ARTDevicePlatform = @"ios"; + +#if TARGET_OS_IOS +#import +NSString *const ARTDeviceFormFactor = @"phone"; +#elif TARGET_OS_TV +NSString *const ARTDeviceFormFactor = @"tv"; +#elif TARGET_OS_WATCH +NSString *const ARTDeviceFormFactor = @"watch"; +#elif TARGET_OS_SIMULATOR +NSString *const ARTDeviceFormFactor = @"simulator"; +#elif TARGET_OS_MAC +NSString *const ARTDeviceFormFactor = @"desktop"; +#else +NSString *const ARTDeviceFormFactor = @"embedded"; +#endif + +NSString *const ARTDevicePushTransportType = @"apns"; + @implementation ARTLocalDevice -- (instancetype)init { +- (instancetype)initWithRest:(ARTRest *rest) { + if (self = [super init]) { + _rest = rest; + } + return self; +} + ++ (ARTLocalDevice *)load:(ARTRest *_Nonnull)rest { + ARTLocalDevice *device = [[ARTLocalDevice alloc] init]; + device.clientId = _rest.auth.clientId; + device.platform = ARTDevicePlatform; + switch (UI_USER_INTERFACE_IDIOM()) { + case UIUserInterfaceIdiomPad: + device.formFactor = @"tablet"; + case UIUserInterfaceIdiomCarPlay: + device.formFactor = @"car"; + default: + device.formFactor = ARTDeviceFormFactor; + } + device.push.transportType = ARTDevicePushTransportType; + NSString *deviceId = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceIdKey]; if (!deviceId) { deviceId = [[ULID new] ulidString]; [[NSUserDefaults standardUserDefaults] setObject:deviceId forKey:ARTDeviceIdKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } - if (self = [super initWithId:deviceId]) { - self.updateToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceUpdateTokenKey]; - } - return self; -} + device.id = deviceId; + device.updateToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceUpdateTokenKey]; -+ (ARTLocalDevice *)local { - static dispatch_once_t once; - static id localDevice; - dispatch_once(&once, ^{ - localDevice = [[ARTLocalDevice alloc] init]; - }); - return localDevice; -} + device.setDeviceToken([[NSUserDefaults standardUserDefaults] dataForKey:ARTDeviceTokenKey]); -- (ARTDeviceToken *)registrationToken { - return self.push.deviceToken; + return device; } - (void)resetId { - + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceIdKey]; + setAndPersistUpdateToken(nil); + NSString *deviceId = [[ULID new] ulidString]; + [[NSUserDefaults standardUserDefaults] setObject:deviceId forKey:ARTDeviceIdKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + device.id = deviceId; } - (void)resetUpdateToken:(void (^)(ARTErrorInfo *error))callback { + if (self.id == nil || self.updateToken == nil) { + if (callback) callback(nil); + return; + } + + NSString *path = @"/push/deviceDetails"; + path = [path stringByAppendingPathComponent:self.id]; + path = [path stringByAppendingPathComponent:@"resetUpdateToken"]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:path]]; + request.HTTPMethod = @"POST"; + + [self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p resetUpdateToken", _rest]; + [_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (!error && data) { + ARTDeviceDetails *updated = [self.rest.encoders[response.allHeaderFields[@"Content-Type"]] decodeDeviceDetails:data error:&error]; + if (!error) { + self.setAndPersistUpdateToken(updated.updateToken); + } + } + if (callback) { + ARTErrorInfo *errorInfo = error ? [ARTErrorInfo createWithNSError:error] : nil; + callback(errorInfo); + } + }]; +} + +- (void)setDeviceToken:(NSString *_Nonnull)token { + self.push.metadata[@"deviceToken"] = token; +} + +- (void)setAndPersistDeviceToken:(NSString *)token { + [[NSUserDefaults standardUserDefaults] setObject:token forKey:ARTDeviceTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self setDeviceToken:token]; +} +- (void)setAndPersistUpdateToken:(NSString *)token { + [[NSUserDefaults standardUserDefaults] setObject:token forKey:ARTDeviceUpdateTokenKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + self.updateToken = token; } @end diff --git a/Source/ARTPush+Private.h b/Source/ARTPush+Private.h new file mode 100644 index 000000000..32aab30a1 --- /dev/null +++ b/Source/ARTPush+Private.h @@ -0,0 +1,24 @@ +// +// ARTPush+Private.h +// Ably +// +// Created by Toni Cárdenas on 07/04/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#ifndef ARTPush_Private_h +#define ARTPush_Private_h + +#import "ARTPush.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTPush () + +- (instancetype)init:(ARTRest *)rest; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* ARTPush_Private_h */ diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 023bfeb60..2de117546 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -8,58 +8,55 @@ #import #import "ARTTypes.h" +#import "ARTPushAdmin.h" @class ARTRest; @class ARTDeviceDetails; -@protocol ARTHTTPAuthenticatedExecutor; - // More context typedef NSString ARTDeviceId; typedef NSData ARTDeviceToken; typedef NSString ARTUpdateToken; typedef ARTJsonObject ARTPushRecipient; - #pragma mark ARTPushRegisterer interface +#ifdef TARGET_OS_IOS + @protocol ARTPushRegistererDelegate -- (void)ablyPushActivateCallback:(nullable ARTErrorInfo *)error; -- (void)ablyPushDeactivateCallback:(nullable ARTErrorInfo *)error; +- (void)didActivateAblyPush:(nullable ARTErrorInfo *)error; +- (void)didDeactivateAblyPush:(nullable ARTErrorInfo *)error; @optional -- (void)ablyPushUpdateFailedCallback:(nullable ARTErrorInfo *)error; +- (void)didAblyPushRegistrationFail:(nullable ARTErrorInfo *)error; - (void)ablyPushCustomRegister:(nullable ARTErrorInfo *)error deviceDetails:(nullable ARTDeviceDetails *)deviceDetails callback:(void (^ _Nonnull)(ARTUpdateToken * _Nonnull, ARTErrorInfo * _Nullable))callback; - (void)ablyPushCustomDeregister:(nullable ARTErrorInfo *)error deviceId:(nullable ARTDeviceId *)deviceId callback:(void (^ _Nullable)(ARTErrorInfo * _Nullable))callback; @end +#endif + #pragma mark ARTPush type NS_ASSUME_NONNULL_BEGIN -extern NSString *const ARTDeviceIdKey; -extern NSString *const ARTDeviceUpdateTokenKey; -extern NSString *const ARTDeviceTokenKey; - @interface ARTPush : NSObject -@property (nonatomic, readonly) ARTDeviceDetails *device; +@property (nonatomic, strong, readonly) ARTPushAdmin *admin; - (instancetype)init NS_UNAVAILABLE; -- (instancetype)init:(id)httpExecutor; /// Publish a push notification. -- (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject; +- (void)publish:(ARTPushRecipient *)recipient notification:(ARTJsonObject *)notification callback:(art_nullable void (^)(ARTErrorInfo *__art_nullable error))callback; #ifdef TARGET_OS_IOS /// Push Registration token -+ (void)didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken rest:(ARTRest *)rest; -+ (void)didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error rest:(ARTRest *)rest; ++ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken rest:(ARTRest *)rest; ++ (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error rest:(ARTRest *)rest; /// Register a device, including the information necessary to deliver push notifications to it. - (void)activate; diff --git a/Source/ARTPush.m b/Source/ARTPush.m index eb6751813..6e2a0604c 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -14,36 +14,63 @@ #import "ARTJsonEncoder.h" #import "ARTJsonLikeEncoder.h" #import "ARTEventEmitter.h" +#ifdef TARGET_OS_IOS #import "ARTPushActivationStateMachine.h" +#endif #import "ARTPushActivationEvent.h" #import "ARTClientOptions+Private.h" +#import "ARTPushAdmin+Private.h" NSString *const ARTDeviceIdKey = @"ARTDeviceId"; NSString *const ARTDeviceUpdateTokenKey = @"ARTDeviceUpdateToken"; NSString *const ARTDeviceTokenKey = @"ARTDeviceToken"; @implementation ARTPush { - id _httpExecutor; + ARTRest *_rest; __weak ARTLog *_logger; } -- (instancetype)init:(id)httpExecutor { +- (instancetype)init:(ARTRest *)rest { if (self = [super init]) { - _httpExecutor = httpExecutor; + _rest = rest; _logger = [httpExecutor logger]; + _admin = [[ARTPushAdmin alloc] init:httpExecutor]; } return self; } +- (void)publish:(ARTPushRecipient *)recipient notification:(ARTJsonObject *)notification callback:(art_nullable void (^)(ARTErrorInfo *__art_nullable error))callback { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/publish"]]; + request.HTTPMethod = @"POST"; + NSMutableDictionary *body = [NSMutableDictionary dictionary]; + [body setObject:recipient forKey:@"recipient"]; + [body addEntriesFromDictionary:notification]; + request.HTTPBody = [[_rest defaultEncoder] encode:body]; + [request setValue:[[_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + + [_logger debug:__FILE__ line:__LINE__ message:@"push notification to a single device %@", request]; + [_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (error) { + [_logger error:@"%@: push notification to a single device failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + if (callback) callback([ARTErrorInfo createWithNSError:error]); + return; + } + if (callback) callback(nil); + }]; +} + +#ifdef TARGET_OS_IOS + - (ARTPushActivationStateMachine *)activationMachine { static dispatch_once_t once; static id activationMachineInstance; dispatch_once(&once, ^{ - activationMachineInstance = [[ARTPushActivationStateMachine alloc] init:_httpExecutor]; + activationMachineInstance = [[ARTPushActivationStateMachine alloc] init:_rest]; }); return activationMachineInstance; } + + (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken rest:(ARTRest *)rest { NSLog(@"ARTPush: device token received %@", deviceToken); NSData *currentDeviceToken = [[NSUserDefaults standardUserDefaults] dataForKey:ARTDeviceTokenKey]; @@ -62,29 +89,6 @@ + (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error rest:( [[rest.push activationMachine] sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; } -- (void)publish:(ARTPushRecipient *)recipient jsonObject:(ARTJsonObject *)jsonObject { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/publish"]]; - request.HTTPMethod = @"POST"; - NSMutableDictionary *body = [NSMutableDictionary dictionary]; - [body setObject:recipient forKey:@"recipient"]; - [body addEntriesFromDictionary:jsonObject]; - request.HTTPBody = [[_httpExecutor defaultEncoder] encode:body]; - [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; - - [_logger debug:__FILE__ line:__LINE__ message:@"push notification to a single device %@", request]; - [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 200 /*OK*/) { - return; - } - if (error) { - [_logger error:@"%@: push notification to a single device failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; - } - else { - [_logger error:@"%@: push notification to a single device failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; - } - }]; -} - - (void)activate { [[self activationMachine] sendEvent:[ARTPushActivationEventCalledActivate new]]; } @@ -93,4 +97,6 @@ - (void)deactivate { [[self activationMachine] sendEvent:[ARTPushActivationEventCalledDeactivate new]]; } +#endif + @end diff --git a/Source/ARTPushActivationState.h b/Source/ARTPushActivationState.h index ca7bf3a97..aeca68c62 100644 --- a/Source/ARTPushActivationState.h +++ b/Source/ARTPushActivationState.h @@ -35,9 +35,6 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationStateNotActivated : ARTPushActivationPersistentState @end -@interface ARTPushActivationStateCalledActivate : ARTPushActivationState -@end - @interface ARTPushActivationStateWaitingForUpdateToken : ARTPushActivationState @end diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m index 50ae56edc..4428ce963 100644 --- a/Source/ARTPushActivationState.m +++ b/Source/ARTPushActivationState.m @@ -95,15 +95,6 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @end -@implementation ARTPushActivationStateCalledActivate - -- (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { - // TODO - return nil; -} - -@end - @implementation ARTPushActivationStateWaitingForUpdateToken - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { @@ -130,7 +121,7 @@ @implementation ARTPushActivationStateWaitingForPushDeviceDetails - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { [self logEventTransition:event file:__FILE__ line:__LINE__]; if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { - return [ARTPushActivationStateCalledActivate newWithMachine:self.machine]; + return self; } else if ([event isKindOfClass:[ARTPushActivationEventCalledDeactivate class]]) { [self.machine callDeactivatedCallback:nil]; diff --git a/Source/ARTPushActivationStateMachine.h b/Source/ARTPushActivationStateMachine.h index 6d7907926..7b7e2fe6b 100644 --- a/Source/ARTPushActivationStateMachine.h +++ b/Source/ARTPushActivationStateMachine.h @@ -6,6 +6,8 @@ // Copyright © 2017 Ably. All rights reserved. // +#ifdef TARGET_OS_IOS + #import @class ARTErrorInfo; @@ -19,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; -- (instancetype)init:(id)httpExecutor; +- (instancetype)init:(ARTRest *)rest; - (void)sendEvent:(ARTPushActivationEvent *)event; @@ -35,3 +37,5 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END + +#endif diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m index bcb562b98..e13dc635d 100644 --- a/Source/ARTPushActivationStateMachine.m +++ b/Source/ARTPushActivationStateMachine.m @@ -15,12 +15,11 @@ #import "ARTJsonEncoder.h" #import "ARTJsonLikeEncoder.h" #import "ARTTypes.h" -#import "ARTLocalDevice.h" +#import "ARTLocalDevice+Private.h" #import "ARTDevicePushDetails.h" #ifdef TARGET_OS_IOS #import -#endif NSString *const ARTPushActivationCurrentStateKey = @"ARTPushActivationCurrentState"; NSString *const ARTPushActivationPendingEventsKey = @"ARTPushActivationPendingEvents"; @@ -28,13 +27,13 @@ @implementation ARTPushActivationStateMachine { ARTPushActivationState *_current; NSMutableArray *_pendingEvents; - id _httpExecutor; + ARTRest *_rest; } -- (instancetype)init:(id)httpExecutor { +- (instancetype)init:(ARTRest *)rest { if (self = [super init]) { - _httpExecutor = httpExecutor; + _rest = rest; // Unarquiving NSData *stateData = [[NSUserDefaults standardUserDefaults] objectForKey:ARTPushActivationCurrentStateKey]; _current = [NSKeyedUnarchiver unarchiveObjectWithData:stateData]; @@ -62,6 +61,8 @@ - (void)handleEvent:(nonnull ARTPushActivationEvent *)event { return; } + _current = maybeNext; + while (true) { ARTPushActivationEvent *pending = [_pendingEvents peek]; if (pending == nil) { @@ -80,7 +81,7 @@ - (void)handleEvent:(nonnull ARTPushActivationEvent *)event { } - (void)persist { - // Arquiving + // Archiving if ([_current isKindOfClass:[ARTPushActivationPersistentState class]]) { [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_current] forKey:ARTPushActivationCurrentStateKey]; } @@ -89,7 +90,7 @@ - (void)persist { - (void)deviceRegistration:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS - ARTLocalDevice *local = [ARTLocalDevice local]; + ARTLocalDevice *local = _rest.device; if (![[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { [NSException raise:@"ARTPushRegistererDelegate must be implemented on AppDelegate" format:@""]; @@ -101,24 +102,20 @@ - (void)deviceRegistration:(ARTErrorInfo *)error { SEL customRegisterMethodSelector = @selector(ablyPushCustomRegister:deviceDetails:callback:); if ([delegate respondsToSelector:customRegisterMethodSelector]) { [delegate ablyPushCustomRegister:error deviceDetails:local callback:^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { - if (![delegate respondsToSelector:@selector(ablyPushActivateCallback:)]) { - [NSException raise:@"ablyPushRegisterCallback: method is required" format:@""]; - } if (error) { // Failed - [delegate ablyPushActivateCallback:error]; + [delegate didActivateAblyPush:error]; [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:error]]; } else if (updateToken) { // Success - [delegate ablyPushActivateCallback:nil]; - [[NSUserDefaults standardUserDefaults] setObject:updateToken forKey:ARTDeviceUpdateTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [local setAndPersistUpdateToken:updateToken]; + [delegate didActivateAblyPush:nil]; [self sendEvent:[ARTPushActivationEventGotUpdateToken new]]; } else { ARTErrorInfo *missingUpdateTokenError = [ARTErrorInfo createWithCode:0 message:@"UpdateToken is expected"]; - [delegate ablyPushActivateCallback:missingUpdateTokenError]; + [delegate didActivateAblyPush:missingUpdateTokenError]; [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:missingUpdateTokenError]]; } }]; @@ -128,32 +125,26 @@ - (void)deviceRegistration:(ARTErrorInfo *)error { // Asynchronous HTTP request NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"]]; request.HTTPMethod = @"POST"; - request.HTTPBody = [[_httpExecutor defaultEncoder] encodeDeviceDetails:local]; - [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; - - [[_httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"device registration with request %@", request]; - [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 201 /*Created*/) { - ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; - [[NSUserDefaults standardUserDefaults] setObject:deviceDetails.updateToken forKey:ARTDeviceUpdateTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; - [self sendEvent:[ARTPushActivationEventGotUpdateToken new]]; - } - else if (error) { - [[_httpExecutor logger] error:@"%@: device registration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + request.HTTPBody = [[_rest defaultEncoder] encodeDeviceDetails:local]; + [request setValue:[[_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + + [[_rest logger] debug:__FILE__ line:__LINE__ message:@"device registration with request %@", request]; + [_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (error) { + [[_rest logger] error:@"%@: device registration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; + return; } - else { - [[_httpExecutor logger] error:@"%@: device registration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; - [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:[ARTErrorInfo createWithCode:response.statusCode message:@"Device registration failed"]]]; - } + ARTDeviceDetails *deviceDetails = [[_rest defaultEncoder] decodeDeviceDetails:data error:nil]; + [local setAndPersistUpdateToken:deviceDetails.updateToken]; + [self sendEvent:[ARTPushActivationEventGotUpdateToken new]]; }]; #endif } - (void)deviceUpdateRegistration:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS - ARTLocalDevice *local = [ARTLocalDevice local]; + ARTLocalDevice *local = _rest.device; if (![[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { [NSException raise:@"ARTPushRegistererDelegate must be implemented on AppDelegate" format:@""]; @@ -165,25 +156,20 @@ - (void)deviceUpdateRegistration:(ARTErrorInfo *)error { SEL customRegisterMethodSelector = @selector(ablyPushCustomRegister:deviceDetails:callback:); if ([delegate respondsToSelector:customRegisterMethodSelector]) { [delegate ablyPushCustomRegister:error deviceDetails:local callback:^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { - if (![delegate respondsToSelector:@selector(ablyPushActivateCallback:)]) { - [NSException raise:@"ablyPushRegisterCallback: method is required" format:@""]; - } if (error) { // Failed - [delegate ablyPushActivateCallback:error]; + [delegate didActivateAblyPush:error]; [self sendEvent:[ARTPushActivationEventUpdatingRegistrationFailed newWithError:error]]; } else if (updateToken) { // Success - [delegate ablyPushActivateCallback:nil]; - [[NSUserDefaults standardUserDefaults] setObject:updateToken forKey:ARTDeviceUpdateTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; - local.updateToken = updateToken; + [local setAndPersistUpdateToken:updateToken]; + [delegate didActivateAblyPush:nil]; [self sendEvent:[ARTPushActivationEventRegistrationUpdated new]]; } else { ARTErrorInfo *missingUpdateTokenError = [ARTErrorInfo createWithCode:0 message:@"UpdateToken is expected"]; - [delegate ablyPushActivateCallback:missingUpdateTokenError]; + [delegate didActivateAblyPush:missingUpdateTokenError]; [self sendEvent:[ARTPushActivationEventUpdatingRegistrationFailed newWithError:missingUpdateTokenError]]; } }]; @@ -195,43 +181,30 @@ - (void)deviceUpdateRegistration:(ARTErrorInfo *)error { NSString *tokenBase64 = [tokenData base64EncodedStringWithOptions:0]; [request setValue:[NSString stringWithFormat:@"Bearer %@", tokenBase64] forHTTPHeaderField:@"Authorization"]; request.HTTPMethod = @"PUT"; - request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ + request.HTTPBody = [[_rest defaultEncoder] encode:@{ @"push": @{ - @"metadata": @{ - @"deviceToken": local.push.deviceToken, - } + @"metadata": local.push.metadata } }]; - [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; - - [[_httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"update device with request %@", request]; - [_httpExecutor executeRequest:request completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 200 /*OK*/) { - ARTDeviceDetails *deviceDetails = [[_httpExecutor defaultEncoder] decodeDeviceDetails:data error:nil]; - [[NSUserDefaults standardUserDefaults] setObject:deviceDetails.updateToken forKey:ARTDeviceUpdateTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; - local.updateToken = deviceDetails.updateToken; - [self sendEvent:[ARTPushActivationEventRegistrationUpdated new]]; - } - else if (error) { - [[_httpExecutor logger] error:@"%@: update device failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + [request setValue:[[_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + + [[_rest logger] debug:__FILE__ line:__LINE__ message:@"update device with request %@", request]; + [_rest executeRequest:request completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (error) { + [[_rest logger] error:@"%@: update device failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; [self sendEvent:[ARTPushActivationEventUpdatingRegistrationFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; + return; } - else { - [[_httpExecutor logger] error:@"%@: update device failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; - [self sendEvent:[ARTPushActivationEventUpdatingRegistrationFailed newWithError:[ARTErrorInfo createWithCode:response.statusCode message:@"Update device failed"]]]; - } + ARTDeviceDetails *deviceDetails = [[_rest defaultEncoder] decodeDeviceDetails:data error:nil]; + [local setAndPersistUpdateToken:deviceDetails.updateToken]; + [self sendEvent:[ARTPushActivationEventRegistrationUpdated new]]; }]; #endif } - (void)deviceUnregistration:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS - ARTLocalDevice *local = [ARTLocalDevice local]; - - if (![[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { - [NSException raise:@"ARTPushRegistererDelegate must be implemented on AppDelegate" format:@""]; - } + ARTLocalDevice *local = _rest.device; id delegate = [UIApplication sharedApplication].delegate; @@ -239,19 +212,14 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { SEL customDeregisterMethodSelector = @selector(ablyPushCustomDeregister:deviceId:callback:); if ([delegate respondsToSelector:customDeregisterMethodSelector]) { [delegate ablyPushCustomDeregister:error deviceId:local.id callback:^(ARTErrorInfo *error) { - if (![delegate respondsToSelector:@selector(ablyPushDeactivateCallback:)]) { - [NSException raise:@"ablyPushDeregisterCallback: method is required" format:@""]; - } if (error) { // Failed - [delegate ablyPushDeactivateCallback:error]; + [delegate didDeactivateAblyPush:error]; [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:error]]; } else { // Success - [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceUpdateTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; - [delegate ablyPushDeactivateCallback:nil]; + [delegate didDeactivateAblyPush:nil]; } }]; return; @@ -266,22 +234,15 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; request.HTTPMethod = @"DELETE"; - [[_httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"device deregistration with request %@", request]; - [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 200 /*OK*/) { - [[_httpExecutor logger] debug:__FILE__ line:__LINE__ message:@"successfully deactivate device"]; - [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceUpdateTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; - [self sendEvent:[ARTPushActivationEventDeregistered new]]; - } - else if (error) { - [[_httpExecutor logger] error:@"%@: device deregistration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + [[_rest logger] debug:__FILE__ line:__LINE__ message:@"device deregistration with request %@", request]; + [_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (error) { + [[_rest logger] error:@"%@: device deregistration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; } - else { - [[_httpExecutor logger] error:@"%@: device deregistration failed with status code %ld", NSStringFromClass(self.class), (long)response.statusCode]; - [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:[ARTErrorInfo createWithCode:response.statusCode message:@"Device registration failed"]]]; - } + + [[_rest logger] debug:__FILE__ line:__LINE__ message:@"successfully deactivate device"]; + [self sendEvent:[ARTPushActivationEventDeregistered new]]; }]; #endif } @@ -290,9 +251,9 @@ - (void)callActivatedCallback:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { id delegate = [UIApplication sharedApplication].delegate; - SEL activateCallbackMethodSelector = @selector(ablyPushActivateCallback:); + SEL activateCallbackMethodSelector = @selector(didActivateAblyPush:); if ([delegate respondsToSelector:activateCallbackMethodSelector]) { - [delegate ablyPushActivateCallback:error]; + [delegate didActivateAblyPush:error]; } } #endif @@ -302,9 +263,9 @@ - (void)callDeactivatedCallback:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { id delegate = [UIApplication sharedApplication].delegate; - SEL deactivateCallbackMethodSelector = @selector(ablyPushDeactivateCallback:); + SEL deactivateCallbackMethodSelector = @selector(didDeactivateAblyPush:); if ([delegate respondsToSelector:deactivateCallbackMethodSelector]) { - [delegate ablyPushDeactivateCallback:error]; + [delegate didDeactivateAblyPush:error]; } } #endif @@ -314,12 +275,14 @@ - (void)callUpdateFailedCallback:(nullable ARTErrorInfo *)error { #ifdef TARGET_OS_IOS if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { id delegate = [UIApplication sharedApplication].delegate; - SEL updateFailedCallbackMethodSelector = @selector(ablyPushUpdateFailedCallback:); + SEL updateFailedCallbackMethodSelector = @selector(didAblyPushRegistrationFail:); if ([delegate respondsToSelector:updateFailedCallbackMethodSelector]) { - [delegate ablyPushUpdateFailedCallback:error]; + [delegate didAblyPushRegistrationFail:error]; } } #endif } @end + +#endif diff --git a/Source/ARTPushAdmin+Private.h b/Source/ARTPushAdmin+Private.h new file mode 100644 index 000000000..8c8a17e33 --- /dev/null +++ b/Source/ARTPushAdmin+Private.h @@ -0,0 +1,22 @@ +// +// ARTPushAdmin+Private.h +// Ably +// +// Created by Toni Cárdenas on 07/04/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#ifndef ARTPushAdmin_Private_h +#define ARTPushAdmin_Private_h + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTPushAdmin () + +- (instancetype)init:(id)httpExecutor; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* ARTPushAdmin_Private_h */ diff --git a/Source/ARTPushAdmin.h b/Source/ARTPushAdmin.h index 6e8a34a2b..dc28a78a2 100644 --- a/Source/ARTPushAdmin.h +++ b/Source/ARTPushAdmin.h @@ -18,7 +18,6 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPushAdmin : NSObject - (instancetype)init NS_UNAVAILABLE; -- (instancetype)init:(id)httpExecutor; @property (nonatomic, readonly) ARTPushDeviceRegistrations* deviceRegistrations; @property (nonatomic, readonly) ARTPushChannelSubscriptions* channelSubscriptions; diff --git a/Source/ARTPushChannel.h b/Source/ARTPushChannel.h index ac8e628d9..d60b32307 100644 --- a/Source/ARTPushChannel.h +++ b/Source/ARTPushChannel.h @@ -13,24 +13,28 @@ @class ARTPushChannelSubscription; @class ARTPaginatedResult; -@protocol ARTHTTPAuthenticatedExecutor; - NS_ASSUME_NONNULL_BEGIN @interface ARTPushChannel : NSObject - (instancetype)init NS_UNAVAILABLE; -- (instancetype)init:(id)httpExecutor withChannel:(ARTChannel *)channel; +- (instancetype)init:(ARTRestChannel *)channel; -- (void)subscribe; -- (void)subscribeDevice:(ARTDeviceId *)deviceId; +#ifdef TARGET_OS_IOS +- (void)subscribeDevice; +- (void)subscribeDevice:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback; +#endif - (void)subscribeClient:(NSString *)clientId; +- (void)subscribeClient:(NSString *)clientId callback:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback; -- (void)unsubscribe; -- (void)unsubscribeDevice:(ARTDeviceId *)deviceId; +#ifdef TARGET_OS_IOS +- (void)unsubscribeDevice; +- (void)unsubscribeDevice:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback; +#endif - (void)unsubscribeClient:(NSString *)clientId; +- (void)unsubscribeClient:(NSString *)clientId callback:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback; -- (void)subscriptions:(void(^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; +- (void)getSubscriptions:(void(^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; @end diff --git a/Source/ARTPushChannel.m b/Source/ARTPushChannel.m index 29da917ea..4a40ef467 100644 --- a/Source/ARTPushChannel.m +++ b/Source/ARTPushChannel.m @@ -18,7 +18,6 @@ const NSUInteger ARTDefaultLimit = 100; @implementation ARTPushChannel { - id _httpExecutor; __weak ARTLog *_logger; __weak ARTChannel *_channel; } diff --git a/Source/ARTPushChannelSubscription.h b/Source/ARTPushChannelSubscription.h index 83297a257..b8528a2a9 100644 --- a/Source/ARTPushChannelSubscription.h +++ b/Source/ARTPushChannelSubscription.h @@ -12,13 +12,13 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPushChannelSubscription : NSObject -@property (nonatomic, readonly) NSString *deviceId; -@property (nonatomic, readonly) NSString *clientId; -@property (nonatomic, readonly) NSString *channelName; +@property (nullable, nonatomic, readonly) NSString *deviceId; +@property (nullable, nonatomic, readonly) NSString *clientId; +@property (nonatomic, readonly) NSString *channel; - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithDeviceId:(NSString *)deviceId andChannel:(NSString *)channelName; -- (instancetype)initWithClientId:(NSString *)clientId andChannel:(NSString *)channelName; +- (instancetype)initWithDeviceId:(NSString *)deviceId channel:(NSString *)channelName; +- (instancetype)initWithClientId:(NSString *)clientId channel:(NSString *)channelName; @end diff --git a/Source/ARTPushChannelSubscription.m b/Source/ARTPushChannelSubscription.m index 192fa2601..60e5ff23c 100644 --- a/Source/ARTPushChannelSubscription.m +++ b/Source/ARTPushChannelSubscription.m @@ -13,7 +13,7 @@ @implementation ARTPushChannelSubscription - (instancetype)initWithDeviceId:(NSString *)deviceId andChannel:(NSString *)channelName { if (self = [super init]) { _deviceId = deviceId; - _channelName = channelName; + _channel = channelName; } return self; } @@ -21,7 +21,7 @@ - (instancetype)initWithDeviceId:(NSString *)deviceId andChannel:(NSString *)cha - (instancetype)initWithClientId:(NSString *)clientId andChannel:(NSString *)channelName { if (self = [super init]) { _clientId = clientId; - _channelName = channelName; + _channel = channelName; } return self; } diff --git a/Source/ARTRealtime.h b/Source/ARTRealtime.h index efe0259fe..f5648c25f 100644 --- a/Source/ARTRealtime.h +++ b/Source/ARTRealtime.h @@ -42,6 +42,9 @@ ART_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) ARTRealtimeChannels *channels; @property (readonly) ARTAuth *auth; @property (readonly) ARTPush *push; +#ifdef TARGET_OS_IOS +@property (nonnull, nonatomic, readonly, getter=getDevice) ARTLocalDevice *device; +#endif @property (readonly, art_nullable, getter=getClientId) NSString *clientId; - (instancetype)init NS_UNAVAILABLE; diff --git a/Source/ARTRealtime.m b/Source/ARTRealtime.m index 838e9f042..dce8a2225 100644 --- a/Source/ARTRealtime.m +++ b/Source/ARTRealtime.m @@ -31,7 +31,7 @@ #import "ARTStats.h" #import "ARTRealtimeTransport.h" #import "ARTFallback.h" -#import "ARTPush.h" +#import "ARTPush+Private.h" @interface ARTConnectionStateChange () @@ -60,6 +60,7 @@ - (instancetype)initWithOptions:(ARTClientOptions *)options { NSAssert(options, @"ARTRealtime: No options provided"); _rest = [[ARTRest alloc] initWithOptions:options]; + _push = _rest.push; _eventQueue = dispatch_queue_create("io.ably.realtime.events", DISPATCH_QUEUE_SERIAL); _internalEventEmitter = [[ARTEventEmitter alloc] initWithQueue:_eventQueue]; _connectedEventEmitter = [[ARTEventEmitter alloc] initWithQueue:_eventQueue]; @@ -957,4 +958,8 @@ - (void)realtimeTransportTooBig:(id)transport { [self transition:ARTRealtimeFailed]; } +- (ARTLocalDevice *)device { + return _rest.device; +} + @end diff --git a/Source/ARTRest.h b/Source/ARTRest.h index 4d20be63b..18b0ea843 100644 --- a/Source/ARTRest.h +++ b/Source/ARTRest.h @@ -10,6 +10,7 @@ #import "ARTTypes.h" #import "ARTLog.h" #import "ARTRestChannels.h" +#import "ARTLocalDevice.h" @protocol ARTHTTPExecutor; @@ -51,6 +52,9 @@ ART_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) ARTRestChannels *channels; @property (nonatomic, strong, readonly) ARTAuth *auth; @property (nonatomic, strong, readonly) ARTPush *push; +#ifdef TARGET_OS_IOS +@property (nonnull, nonatomic, readonly, getter=getDevice) ARTLocalDevice *device; +#endif @end diff --git a/Source/ARTRest.m b/Source/ARTRest.m index 2df3e0430..dbdbc1720 100644 --- a/Source/ARTRest.m +++ b/Source/ARTRest.m @@ -34,7 +34,7 @@ #import "ARTDefault.h" #import "ARTFallback.h" #import "ARTGCD.h" -#import "ARTPush.h" +#import "ARTPush+Private.h" @implementation ARTRest { ARTLog *_logger; @@ -318,4 +318,13 @@ - (NSURL *)getBaseUrl { return components.URL; } +- (ARTLocalDevice *)device { + static dispatch_once_t once; + static id device; + dispatch_once(&once, ^{ + device = [ARTLocalDevice load:self]; + }); + return device; +} + @end From fc53ff7a52e846ee2529d0757a98bfa1eb3c5e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20C=C3=A1rdenas?= Date: Mon, 24 Apr 2017 17:38:20 +0200 Subject: [PATCH 35/46] Fix things for push, and adapt to latest API. --- Source/ARTAuth+Private.h | 2 + Source/ARTChannel.h | 5 + Source/ARTChannel.m | 7 ++ Source/ARTCrypto.m | 1 - Source/ARTDeviceDetails.h | 4 +- Source/ARTDeviceDetails.m | 12 ++- Source/ARTDevicePushDetails.h | 3 +- Source/ARTDevicePushDetails.m | 2 +- Source/ARTJsonLikeEncoder.m | 20 ++-- Source/ARTLocalDevice+Private.h | 5 +- Source/ARTLocalDevice.m | 32 ++++-- Source/ARTPush.m | 19 ++-- Source/ARTPushActivationState.m | 10 +- Source/ARTPushActivationStateMachine.h | 9 +- Source/ARTPushActivationStateMachine.m | 6 +- Source/ARTPushChannel.h | 20 ++-- Source/ARTPushChannel.m | 135 +++++++++++++++---------- Source/ARTPushChannelSubscription.m | 4 +- Source/ARTPushChannelSubscriptions.h | 5 +- Source/ARTPushChannelSubscriptions.m | 22 +++- Source/ARTPushDeviceRegistrations.h | 5 +- Source/ARTPushDeviceRegistrations.m | 16 ++- Source/ARTRealtime.m | 1 - Source/ARTRealtimeChannel.h | 4 + Source/ARTRealtimeChannel.m | 14 +++ Source/ARTRest.h | 2 +- Source/ARTRest.m | 5 + Source/ARTRestChannel.m | 6 ++ Source/Ably.h | 2 + 29 files changed, 239 insertions(+), 139 deletions(-) diff --git a/Source/ARTAuth+Private.h b/Source/ARTAuth+Private.h index aa9679ae7..8b3f5a958 100644 --- a/Source/ARTAuth+Private.h +++ b/Source/ARTAuth+Private.h @@ -44,6 +44,8 @@ ART_ASSUME_NONNULL_BEGIN // Private TimeOffset setter for testing only - (void)setTimeOffset:(NSTimeInterval)offset; +- (NSString *_Nullable)getClientId; + @end ART_ASSUME_NONNULL_END diff --git a/Source/ARTChannel.h b/Source/ARTChannel.h index be05dccc2..4c54e4dc7 100644 --- a/Source/ARTChannel.h +++ b/Source/ARTChannel.h @@ -15,6 +15,7 @@ @class ARTMessage; @class __GENERIC(ARTPaginatedResult, ItemType); @class ARTDataQuery; +@class ARTLocalDevice; ART_ASSUME_NONNULL_BEGIN @@ -35,6 +36,10 @@ ART_ASSUME_NONNULL_BEGIN - (void)history:(void(^)(__GENERIC(ARTPaginatedResult, ARTMessage *) *__art_nullable result, ARTErrorInfo *__art_nullable error))callback; +#ifdef TARGET_OS_IOS +- (ARTLocalDevice *)device; +#endif + @end ART_ASSUME_NONNULL_END diff --git a/Source/ARTChannel.m b/Source/ARTChannel.m index 5248fadc4..554bad940 100644 --- a/Source/ARTChannel.m +++ b/Source/ARTChannel.m @@ -89,4 +89,11 @@ - (void)internalPostMessages:(id)data callback:(void (^)(ARTErrorInfo *__art_nul NSAssert(false, @"-[%@ %@] should always be overriden.", self.class, NSStringFromSelector(_cmd)); } +#ifdef TARGET_OS_IOS +- (ARTLocalDevice *)device { + NSAssert(false, @"-[%@ %@] should always be overriden.", self.class, NSStringFromSelector(_cmd)); + return nil; +} +#endif + @end diff --git a/Source/ARTCrypto.m b/Source/ARTCrypto.m index 6134d70ab..776859ca0 100644 --- a/Source/ARTCrypto.m +++ b/Source/ARTCrypto.m @@ -21,7 +21,6 @@ - (BOOL)ccAlgorithm:(CCAlgorithm *)algorithm error:(NSError **)error; @interface ARTCrypto () @property (nonatomic, weak) ARTLog * logger; -@property (readonly, strong, nonatomic) ARTCipherParams *params; @end diff --git a/Source/ARTDeviceDetails.h b/Source/ARTDeviceDetails.h index adb8d670c..7e1034e94 100644 --- a/Source/ARTDeviceDetails.h +++ b/Source/ARTDeviceDetails.h @@ -19,11 +19,11 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic) NSString *clientId; @property (nonatomic) NSString *platform; @property (nonatomic) NSString *formFactor; -@property (nonatomic) NSDictionary *metadata; +@property (nonatomic) NSMutableDictionary *metadata; @property (nonatomic) ARTDevicePushDetails *push; @property (nullable, nonatomic) ARTUpdateToken *updateToken; -- (instancetype)init NS_UNAVAILABLE; +- (instancetype)init; - (instancetype)initWithId:(ARTDeviceId *)deviceId; @end diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index c6e163857..5235a42c9 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -11,11 +11,17 @@ @implementation ARTDeviceDetails +- (instancetype)init { + if (self = [self init]) { + _push = [[ARTDevicePushDetails alloc] init]; + _metadata = [[NSMutableDictionary alloc] init]; + } + return self; +} + - (instancetype)initWithId:(ARTDeviceId *)deviceId { - if (self = [super init]) { + if (self = [self init]) { _id = deviceId; - _push = [[ARTDevicePushDetails alloc] init]; - _metadata = [[NSDictionary alloc] init]; } return self; } diff --git a/Source/ARTDevicePushDetails.h b/Source/ARTDevicePushDetails.h index 264859f41..7e48c9d1a 100644 --- a/Source/ARTDevicePushDetails.h +++ b/Source/ARTDevicePushDetails.h @@ -14,10 +14,9 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTDevicePushDetails : NSObject -@property (nullable, nonatomic) NSString *transportType; +@property (nonatomic) NSMutableDictionary *recipient; @property (nullable, nonatomic) NSString *state; @property (nullable, nonatomic) ARTErrorInfo *errorReason; -@property (nonatomic) NSDictionary *metadata; - (instancetype)init; diff --git a/Source/ARTDevicePushDetails.m b/Source/ARTDevicePushDetails.m index 21adba207..a0860ec4f 100644 --- a/Source/ARTDevicePushDetails.m +++ b/Source/ARTDevicePushDetails.m @@ -13,7 +13,7 @@ @implementation ARTDevicePushDetails - (instancetype)init { if (self = [super init]) { - _metadata = [[NSDictionary alloc] init]; + _recipient = [[NSMutableDictionary alloc] init]; } return self; } diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index 468d96205..30b87c770 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -198,8 +198,8 @@ - (ARTPushChannelSubscription *)decodePushChannelSubscription:(NSData *)data err - (NSDictionary *)pushChannelSubscriptionToDictionary:(ARTPushChannelSubscription *)channelSubscription { NSMutableDictionary *output = [NSMutableDictionary dictionary]; - if (channelSubscription.channelName) { - [output setObject:channelSubscription.channelName forKey:@"channel"]; + if (channelSubscription.channel) { + [output setObject:channelSubscription.channel forKey:@"channel"]; } if (channelSubscription.clientId) { @@ -238,10 +238,10 @@ - (ARTPushChannelSubscription *)pushChannelSubscriptionFromDictionary:(NSDiction ARTPushChannelSubscription *channelSubscription; if (clientId) { - channelSubscription = [[ARTPushChannelSubscription alloc] initWithClientId:clientId andChannel:channelName]; + channelSubscription = [[ARTPushChannelSubscription alloc] initWithClientId:clientId channel:channelName]; } else { - channelSubscription = [[ARTPushChannelSubscription alloc] initWithDeviceId:deviceId andChannel:channelName]; + channelSubscription = [[ARTPushChannelSubscription alloc] initWithDeviceId:deviceId channel:channelName]; } return channelSubscription; @@ -621,8 +621,7 @@ - (ARTDeviceDetails *)deviceDetailsFromDictionary:(NSDictionary *)input error:(N - (NSDictionary *)devicePushDetailsToDictionary:(ARTDevicePushDetails *)devicePushDetails { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - dictionary[@"transportType"] = devicePushDetails.transportType; - dictionary[@"metadata"] = devicePushDetails.metadata; + dictionary[@"recipient"] = devicePushDetails.recipient; return dictionary; } @@ -635,13 +634,12 @@ - (ARTDevicePushDetails *)devicePushDetailsFromDictionary:(NSDictionary *)input } ARTDevicePushDetails *devicePushDetails = [[ARTDevicePushDetails alloc] init]; - devicePushDetails.transportType = [input artString:@"transportType"]; devicePushDetails.state = [input artString:@"state"]; - NSDictionary *error = [input valueForKey:@"errorReason"]; - if (error) { - devicePushDetails.state.errorReason = [ARTErrorInfo createWithCode:[[error artNumber:@"code"] intValue] status:[[error artNumber:@"statusCode"] intValue] message:[error artString:@"message"]]; + NSDictionary *errorReason = [input valueForKey:@"errorReason"]; + if (errorReason) { + devicePushDetails.errorReason = [ARTErrorInfo createWithCode:[[errorReason artNumber:@"code"] intValue] status:[[errorReason artNumber:@"statusCode"] intValue] message:[errorReason artString:@"message"]]; } - devicePushDetails.metadata = [input valueForKey:@"metadata"]; + devicePushDetails.recipient = [input valueForKey:@"recipient"]; return devicePushDetails; } diff --git a/Source/ARTLocalDevice+Private.h b/Source/ARTLocalDevice+Private.h index e750f76cd..ce4dfacb8 100644 --- a/Source/ARTLocalDevice+Private.h +++ b/Source/ARTLocalDevice+Private.h @@ -17,9 +17,12 @@ extern NSString *const ARTDeviceIdKey; extern NSString *const ARTDeviceUpdateTokenKey; extern NSString *const ARTDeviceTokenKey; -@interface ARTLocalDevice : ARTDeviceDetails +@interface ARTLocalDevice () + +@property (weak, nonatomic) ARTRest *rest; + (ARTLocalDevice *_Nonnull)load:(ARTRest *)rest; +- (NSString *_Nullable)deviceToken; - (void)setAndPersistDeviceToken:(NSString *_Nullable)token; - (void)setAndPersistUpdateToken:(NSString *_Nullable)token; diff --git a/Source/ARTLocalDevice.m b/Source/ARTLocalDevice.m index b0533e494..22b560681 100644 --- a/Source/ARTLocalDevice.m +++ b/Source/ARTLocalDevice.m @@ -9,6 +9,9 @@ #import "ARTLocalDevice+Private.h" #import "ARTDevicePushDetails.h" #import "ARTPush.h" +#import "ARTRest+Private.h" +#import "ARTAuth+Private.h" +#import "ARTEncoder.h" #import NSString *const ARTDevicePlatform = @"ios"; @@ -30,18 +33,21 @@ NSString *const ARTDevicePushTransportType = @"apns"; -@implementation ARTLocalDevice +@implementation ARTLocalDevice { + __weak ARTLog *_logger; +} -- (instancetype)initWithRest:(ARTRest *rest) { +- (instancetype)initWithRest:(ARTRest *)rest { if (self = [super init]) { _rest = rest; + _logger = rest.logger; } return self; } + (ARTLocalDevice *)load:(ARTRest *_Nonnull)rest { - ARTLocalDevice *device = [[ARTLocalDevice alloc] init]; - device.clientId = _rest.auth.clientId; + ARTLocalDevice *device = [[ARTLocalDevice alloc] initWithRest:rest]; + device.clientId = [device.rest.auth getClientId]; device.platform = ARTDevicePlatform; switch (UI_USER_INTERFACE_IDIOM()) { case UIUserInterfaceIdiomPad: @@ -51,7 +57,7 @@ + (ARTLocalDevice *)load:(ARTRest *_Nonnull)rest { default: device.formFactor = ARTDeviceFormFactor; } - device.push.transportType = ARTDevicePushTransportType; + device.push.recipient[@"transportType"] = ARTDevicePushTransportType; NSString *deviceId = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceIdKey]; if (!deviceId) { @@ -62,18 +68,18 @@ + (ARTLocalDevice *)load:(ARTRest *_Nonnull)rest { device.id = deviceId; device.updateToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceUpdateTokenKey]; - device.setDeviceToken([[NSUserDefaults standardUserDefaults] dataForKey:ARTDeviceTokenKey]); + [device setDeviceToken:[[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceTokenKey]]; return device; } - (void)resetId { [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceIdKey]; - setAndPersistUpdateToken(nil); + [self setAndPersistUpdateToken:nil]; NSString *deviceId = [[ULID new] ulidString]; [[NSUserDefaults standardUserDefaults] setObject:deviceId forKey:ARTDeviceIdKey]; [[NSUserDefaults standardUserDefaults] synchronize]; - device.id = deviceId; + self.id = deviceId; } - (void)resetUpdateToken:(void (^)(ARTErrorInfo *error))callback { @@ -88,12 +94,12 @@ - (void)resetUpdateToken:(void (^)(ARTErrorInfo *error))callback { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:path]]; request.HTTPMethod = @"POST"; - [self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p resetUpdateToken", _rest]; + [_logger debug:__FILE__ line:__LINE__ message:@"RS:%p resetUpdateToken", _rest]; [_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (!error && data) { ARTDeviceDetails *updated = [self.rest.encoders[response.allHeaderFields[@"Content-Type"]] decodeDeviceDetails:data error:&error]; if (!error) { - self.setAndPersistUpdateToken(updated.updateToken); + [self setAndPersistUpdateToken:updated.updateToken]; } } if (callback) { @@ -103,8 +109,12 @@ - (void)resetUpdateToken:(void (^)(ARTErrorInfo *error))callback { }]; } +- (NSString *)deviceToken { + return self.push.recipient[@"deviceToken"]; +} + - (void)setDeviceToken:(NSString *_Nonnull)token { - self.push.metadata[@"deviceToken"] = token; + self.push.recipient[@"deviceToken"] = token; } - (void)setAndPersistDeviceToken:(NSString *)token { diff --git a/Source/ARTPush.m b/Source/ARTPush.m index 6e2a0604c..bc4f38c79 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -20,6 +20,7 @@ #import "ARTPushActivationEvent.h" #import "ARTClientOptions+Private.h" #import "ARTPushAdmin+Private.h" +#import "ARTLocalDevice+Private.h" NSString *const ARTDeviceIdKey = @"ARTDeviceId"; NSString *const ARTDeviceUpdateTokenKey = @"ARTDeviceUpdateToken"; @@ -33,8 +34,8 @@ @implementation ARTPush { - (instancetype)init:(ARTRest *)rest { if (self = [super init]) { _rest = rest; - _logger = [httpExecutor logger]; - _admin = [[ARTPushAdmin alloc] init:httpExecutor]; + _logger = [rest logger]; + _admin = [[ARTPushAdmin alloc] init:rest]; } return self; } @@ -71,15 +72,19 @@ - (ARTPushActivationStateMachine *)activationMachine { } -+ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken rest:(ARTRest *)rest { ++ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceTokenData rest:(ARTRest *)rest { + // HEX string, i.e.: <12ce7dda 8032c423 8f8bd40f 3484e5bb f4698da5 8b7fdf8d 5c55e0a2 XXXXXXXX> + // Normalizing token by removing symbols and spaces, i.e.: 12ce7dda8032c4238f8bd40f3484e5bbf4698da58b7fdf8d5c55e0a2XXXXXXXX + NSString *deviceToken = [[[deviceTokenData description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""]; + NSLog(@"ARTPush: device token received %@", deviceToken); - NSData *currentDeviceToken = [[NSUserDefaults standardUserDefaults] dataForKey:ARTDeviceTokenKey]; - if ([currentDeviceToken isEqualToData:deviceToken]) { + NSString *currentDeviceToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceTokenKey]; + if ([currentDeviceToken isEqualToString:deviceToken]) { // Already stored. return; } - [[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:ARTDeviceTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; + + [[rest device] setAndPersistDeviceToken:deviceToken]; NSLog(@"ARTPush: device token stored"); [[rest.push activationMachine] sendEvent:[ARTPushActivationEventGotPushDeviceDetails new]]; } diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m index 4428ce963..e85b1ea80 100644 --- a/Source/ARTPushActivationState.m +++ b/Source/ARTPushActivationState.m @@ -9,7 +9,7 @@ #import "ARTPushActivationState.h" #import "ARTPushActivationStateMachine.h" #import "ARTPushActivationEvent.h" -#import "ARTLocalDevice.h" +#import "ARTLocalDevice+Private.h" #import "ARTDevicePushDetails.h" #import "ARTLog.h" #import "ARTRest+Private.h" @@ -77,14 +77,14 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { return self; } else if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { - ARTLocalDevice *local = [ARTLocalDevice local]; + ARTLocalDevice *local = [ARTLocalDevice load:self.machine.rest]; if (local.updateToken != nil) { // Already registered. return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithMachine:self.machine]; } - if (local.registrationToken != nil) { + if ([local deviceToken] != nil) { [self.machine sendEvent:[ARTPushActivationEventGotPushDeviceDetails new]]; } @@ -103,7 +103,7 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { return self; } else if ([event isKindOfClass:[ARTPushActivationEventGotUpdateToken class]]) { - [ARTLocalDevice local].updateToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceUpdateTokenKey]; + [ARTLocalDevice load:self.machine.rest].updateToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceUpdateTokenKey]; [self.machine callActivatedCallback:nil]; return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithMachine:self.machine]; } @@ -203,7 +203,7 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { return [ARTPushActivationStateWaitingForDeregistration newWithMachine:self.machine]; } else if ([event isKindOfClass:[ARTPushActivationEventDeregistered class]]) { - ARTLocalDevice *local = [ARTLocalDevice local]; + ARTLocalDevice *local = [ARTLocalDevice load:self.machine.rest]; local.updateToken = nil; [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceUpdateTokenKey]; [[NSUserDefaults standardUserDefaults] synchronize]; diff --git a/Source/ARTPushActivationStateMachine.h b/Source/ARTPushActivationStateMachine.h index 7b7e2fe6b..e9b50a7b6 100644 --- a/Source/ARTPushActivationStateMachine.h +++ b/Source/ARTPushActivationStateMachine.h @@ -6,19 +6,18 @@ // Copyright © 2017 Ably. All rights reserved. // -#ifdef TARGET_OS_IOS - #import @class ARTErrorInfo; @class ARTPushActivationEvent; - -@protocol ARTHTTPAuthenticatedExecutor; +@class ARTRest; NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationStateMachine : NSObject +@property (nonatomic, strong) ARTRest *rest; + - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; - (instancetype)init:(ARTRest *)rest; @@ -37,5 +36,3 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END - -#endif diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m index e13dc635d..c11ba5432 100644 --- a/Source/ARTPushActivationStateMachine.m +++ b/Source/ARTPushActivationStateMachine.m @@ -27,8 +27,6 @@ @implementation ARTPushActivationStateMachine { ARTPushActivationState *_current; NSMutableArray *_pendingEvents; - ARTRest *_rest; - } - (instancetype)init:(ARTRest *)rest { @@ -180,10 +178,10 @@ - (void)deviceUpdateRegistration:(ARTErrorInfo *)error { NSData *tokenData = [local.updateToken dataUsingEncoding:NSUTF8StringEncoding]; NSString *tokenBase64 = [tokenData base64EncodedStringWithOptions:0]; [request setValue:[NSString stringWithFormat:@"Bearer %@", tokenBase64] forHTTPHeaderField:@"Authorization"]; - request.HTTPMethod = @"PUT"; + request.HTTPMethod = @"PATCH"; request.HTTPBody = [[_rest defaultEncoder] encode:@{ @"push": @{ - @"metadata": local.push.metadata + @"recipient": local.push.recipient } }]; [request setValue:[[_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; diff --git a/Source/ARTPushChannel.h b/Source/ARTPushChannel.h index d60b32307..e6569cad7 100644 --- a/Source/ARTPushChannel.h +++ b/Source/ARTPushChannel.h @@ -8,8 +8,9 @@ #import #import "ARTPush.h" +#import "ARTHttp.h" +#import "ARTChannel.h" -@class ARTChannel; @class ARTPushChannelSubscription; @class ARTPaginatedResult; @@ -18,23 +19,20 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPushChannel : NSObject - (instancetype)init NS_UNAVAILABLE; -- (instancetype)init:(ARTRestChannel *)channel; +- (instancetype)init:(id)httpExecutor withChannel:(ARTChannel *)channel; -#ifdef TARGET_OS_IOS - (void)subscribeDevice; - (void)subscribeDevice:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback; -#endif -- (void)subscribeClient:(NSString *)clientId; -- (void)subscribeClient:(NSString *)clientId callback:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback; +- (void)subscribeClient; +- (void)subscribeClient:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback; -#ifdef TARGET_OS_IOS - (void)unsubscribeDevice; - (void)unsubscribeDevice:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback; -#endif -- (void)unsubscribeClient:(NSString *)clientId; -- (void)unsubscribeClient:(NSString *)clientId callback:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback; +- (void)unsubscribeClient; +- (void)unsubscribeClient:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback; -- (void)getSubscriptions:(void(^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; +- (void)listSubscriptions:(void(^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; +- (void)listSubscriptions:(NSDictionary *_Nullable)params callback:(void(^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; @end diff --git a/Source/ARTPushChannel.m b/Source/ARTPushChannel.m index 4a40ef467..4c0531596 100644 --- a/Source/ARTPushChannel.m +++ b/Source/ARTPushChannel.m @@ -9,37 +9,52 @@ #import "ARTPushChannel.h" #import "ARTHttp.h" #import "ARTLog.h" -#import "ARTChannel.h" #import "ARTJsonLikeEncoder.h" #import "ARTClientOptions.h" #import "ARTPaginatedResult+Private.h" #import "ARTPushChannelSubscription.h" +#import "ARTChannel+Private.h" const NSUInteger ARTDefaultLimit = 100; @implementation ARTPushChannel { __weak ARTLog *_logger; __weak ARTChannel *_channel; + __weak id _httpExecutor; } - (instancetype)init:(id)httpExecutor withChannel:(ARTChannel *)channel { if (self == [super self]) { - _httpExecutor = httpExecutor; - _logger = [httpExecutor logger]; _channel = channel; + _logger = channel.logger; + _httpExecutor = httpExecutor; } return self; } -- (NSString *)clientId { - return [self clientId]; +- (void)subscribeDevice { + [self subscribeDevice:nil]; +} + +- (void)unsubscribeDevice { + [self unsubscribeDevice:nil]; +} + +- (void)subscribeClient { + [self subscribeClient:nil]; } -- (void)subscribe { - [self subscribeClient:[self clientId]]; +- (void)unsubscribeClient { + [self unsubscribeClient:nil]; } -- (void)subscribeDevice:(ARTDeviceId *)deviceId { +- (void)subscribeDevice:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback { + ARTDeviceDetails *device = [self getDevice:callback]; + if (!device) { + return; + } + NSString *deviceId = device.id; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; request.HTTPMethod = @"POST"; request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ @@ -48,21 +63,22 @@ - (void)subscribeDevice:(ARTDeviceId *)deviceId { }]; [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + [_logger debug:__FILE__ line:__LINE__ message:@"subscribe notifications for device %@ in channel %@", deviceId, _channel.name]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 200 /*OK*/) { - return; - } if (error) { [_logger error:@"%@: subscribe notifications for device %@ in channel %@ failed (%@)", NSStringFromClass(self.class), deviceId, _channel.name, error.localizedDescription]; } - else { - [_logger error:@"%@: subscribe notifications for device %@ in channel %@ failed with status code %ld", NSStringFromClass(self.class), deviceId, _channel.name, (long)response.statusCode]; - } + if (callback) callback(error ? [ARTErrorInfo createWithNSError:error] : nil); }]; } -- (void)subscribeClient:(NSString *)clientId { +- (void)subscribeClient:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback { + NSString *clientId = [self getClientId:callback]; + if (!clientId) { + return; + } + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; request.HTTPMethod = @"POST"; request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ @@ -73,23 +89,20 @@ - (void)subscribeClient:(NSString *)clientId { [_logger debug:__FILE__ line:__LINE__ message:@"subscribe notifications for clientId %@ in channel %@", clientId, _channel.name]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 200 /*OK*/) { - return; - } if (error) { [_logger error:@"%@: subscribe notifications for clientId %@ in channel %@ failed (%@)", NSStringFromClass(self.class), clientId, _channel.name, error.localizedDescription]; } - else { - [_logger error:@"%@: subscribe notifications for clientId %@ in channel %@ failed with status code %ld", NSStringFromClass(self.class), clientId, _channel.name, (long)response.statusCode]; - } + if (callback) callback(error ? [ARTErrorInfo createWithNSError:error] : nil); }]; } -- (void)unsubscribe { - [self unsubscribeClient:[self clientId]]; -} +- (void)unsubscribeDevice:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback { + ARTDeviceDetails *device = [self getDevice:callback]; + if (!device) { + return; + } + NSString *deviceId = device.id; -- (void)unsubscribeDevice:(NSString *)deviceId { NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; components.queryItems = @[ [NSURLQueryItem queryItemWithName:@"deviceId" value:deviceId], @@ -101,19 +114,19 @@ - (void)unsubscribeDevice:(NSString *)deviceId { [_logger debug:__FILE__ line:__LINE__ message:@"unsubscribe notifications for device %@ in channel %@", deviceId, _channel.name]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 200 /*OK*/) { - return; - } if (error) { [_logger error:@"%@: unsubscribe notifications for device %@ in channel %@ failed (%@)", NSStringFromClass(self.class), deviceId, _channel.name, error.localizedDescription]; } - else { - [_logger error:@"%@: unsubscribe notifications for device %@ in channel %@ failed with status code %ld", NSStringFromClass(self.class), deviceId, _channel.name, (long)response.statusCode]; - } + if (callback) callback(error ? [ARTErrorInfo createWithNSError:error] : nil); }]; } -- (void)unsubscribeClient:(NSString *)clientId { +- (void)unsubscribeClient:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback { + NSString *clientId = [self getClientId:callback]; + if (!clientId) { + return; + } + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; components.queryItems = @[ [NSURLQueryItem queryItemWithName:@"clientId" value:clientId], @@ -125,38 +138,33 @@ - (void)unsubscribeClient:(NSString *)clientId { [_logger debug:__FILE__ line:__LINE__ message:@"unsubscribe notifications for clientId %@ in channel %@", clientId, _channel.name]; [_httpExecutor executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (response.statusCode == 200 /*OK*/) { - return; - } if (error) { [_logger error:@"%@: unsubscribe notifications for clientId %@ in channel %@ failed (%@)", NSStringFromClass(self.class), clientId, _channel.name, error.localizedDescription]; } - else { - [_logger error:@"%@: unsubscribe notifications for clientId %@ in channel %@ failed with status code %ld", NSStringFromClass(self.class), clientId, _channel.name, (long)response.statusCode]; - } + if (callback) callback(error ? [ARTErrorInfo createWithNSError:error] : nil); }]; } -- (void)subscriptions:(void(^)(ARTPaginatedResult *result, ARTErrorInfo *error))callback { - [self subscriptionsClient:[self clientId] limit:ARTDefaultLimit callback:callback error:nil]; +- (void)listSubscriptions:(void(^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback { + [self listSubscriptions:nil callback:callback]; } -- (BOOL)subscriptionsClient:(NSString *)clientId limit:(NSUInteger)limit callback:(void(^)(ARTPaginatedResult *result, ARTErrorInfo *error))callback error:(NSError **)errorPtr { - if (limit > 1000) { - if (errorPtr) { - *errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain - code:ARTDataQueryErrorLimit - userInfo:@{NSLocalizedDescriptionKey:@"Limit supports up to 1000 results only"}]; - } - return NO; +- (void)listSubscriptions:(NSDictionary *)params callback:(void(^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback { + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; + ARTDeviceDetails *device = [self getDevice:callback ? ^(ARTErrorInfo *error) { + callback(nil, error); + } : nil]; + if (!device) { + return; } - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; - components.queryItems = @[ - [NSURLQueryItem queryItemWithName:@"clientId" value:clientId], - [NSURLQueryItem queryItemWithName:@"limit" value:[[NSNumber numberWithUnsignedInteger:limit] stringValue]], - ]; + NSMutableDictionary *p = params ? [NSMutableDictionary dictionaryWithDictionary:params] : [[NSMutableDictionary alloc] init]; + p[@"deviceId"] = device.id; + if (device.clientId) { + p[@"clientId"] = device.clientId; + } + components.queryItems = [p asURLQueryItems];; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; request.HTTPMethod = @"GET"; @@ -166,7 +174,26 @@ - (BOOL)subscriptionsClient:(NSString *)clientId limit:(NSUInteger)limit callbac }; [ARTPaginatedResult executePaginated:_httpExecutor withRequest:request andResponseProcessor:responseProcessor callback:callback]; - return YES; +} + +- (ARTDeviceDetails *)getDevice:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback { + ARTDeviceDetails *device = [_channel device]; + if (!device) { + if (callback) callback([ARTErrorInfo createWithCode:0 message:@"cannot use device before ARTRest.push.activate has finished"]); + } + return device; +} + +- (NSString *)getClientId:(void(^_Nullable)(ARTErrorInfo *_Nullable))callback { + ARTDeviceDetails *device = [self getDevice:callback]; + if (!device) { + return nil; + } + if (!device.clientId) { + if (callback) callback([ARTErrorInfo createWithCode:0 message:@"cannot subscribe with null client ID"]); + return nil; + } + return device.clientId; } @end diff --git a/Source/ARTPushChannelSubscription.m b/Source/ARTPushChannelSubscription.m index 60e5ff23c..9481f1b3e 100644 --- a/Source/ARTPushChannelSubscription.m +++ b/Source/ARTPushChannelSubscription.m @@ -10,7 +10,7 @@ @implementation ARTPushChannelSubscription -- (instancetype)initWithDeviceId:(NSString *)deviceId andChannel:(NSString *)channelName { +- (instancetype)initWithDeviceId:(NSString *)deviceId channel:(NSString *)channelName { if (self = [super init]) { _deviceId = deviceId; _channel = channelName; @@ -18,7 +18,7 @@ - (instancetype)initWithDeviceId:(NSString *)deviceId andChannel:(NSString *)cha return self; } -- (instancetype)initWithClientId:(NSString *)clientId andChannel:(NSString *)channelName { +- (instancetype)initWithClientId:(NSString *)clientId channel:(NSString *)channelName { if (self = [super init]) { _clientId = clientId; _channel = channelName; diff --git a/Source/ARTPushChannelSubscriptions.h b/Source/ARTPushChannelSubscriptions.h index f5cb11e40..90d980dcf 100644 --- a/Source/ARTPushChannelSubscriptions.h +++ b/Source/ARTPushChannelSubscriptions.h @@ -25,9 +25,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)listChannels:(void (^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; -- (void)get:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; +- (void)list:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; -- (void)remove:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *_Nullable))callback; +- (void)remove:(ARTPushChannelSubscription *)subscription callback:(void (^)(ARTErrorInfo *_Nullable))callback; +- (void)removeWhere:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *_Nullable))callback; @end diff --git a/Source/ARTPushChannelSubscriptions.m b/Source/ARTPushChannelSubscriptions.m index 21833fb8f..052bf1e68 100644 --- a/Source/ARTPushChannelSubscriptions.m +++ b/Source/ARTPushChannelSubscriptions.m @@ -55,13 +55,13 @@ - (void)listChannels:(void (^)(ARTPaginatedResult * _Nullable, ARTEr ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data) { return [[[_httpExecutor defaultEncoder] decodePushChannelSubscriptions:data error:nil] artMap:^NSString *(ARTPushChannelSubscription *item) { - return [(ARTPushChannelSubscription *)item channelName]; + return ((ARTPushChannelSubscription *)item).channel; }]; }; [ARTPaginatedResult executePaginated:_httpExecutor withRequest:request andResponseProcessor:responseProcessor callback:callback]; } -- (void)get:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *result, ARTErrorInfo *error))callback { +- (void)list:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *result, ARTErrorInfo *error))callback { NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; components.queryItems = [params asURLQueryItems]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; @@ -73,7 +73,23 @@ - (void)get:(NSDictionary *)params callback:(void (^)(AR [ARTPaginatedResult executePaginated:_httpExecutor withRequest:request andResponseProcessor:responseProcessor callback:callback]; } -- (void)remove:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *error))callback { +- (void)remove:(ARTPushChannelSubscription *)subscription callback:(void (^)(ARTErrorInfo *_Nullable))callback { + if ((subscription.deviceId && subscription.clientId) || (!subscription.deviceId && !subscription.clientId)) { + callback([ARTErrorInfo createWithCode:0 message:@"ARTChannelSubscription cannot be for both a deviceId and a clientId"]); + return; + } + NSMutableDictionary *where = [[NSMutableDictionary alloc] init]; + where[@"channel"] = subscription.channel; + if (subscription.deviceId) { + where[@"deviceId"] = subscription.deviceId; + } else { + where[@"clientId"] = subscription.clientId; + } + [self removeWhere:where callback:callback]; +} + + +- (void)removeWhere:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *error))callback { NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; components.queryItems = [params asURLQueryItems]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; diff --git a/Source/ARTPushDeviceRegistrations.h b/Source/ARTPushDeviceRegistrations.h index 9aacbd380..e485d17eb 100644 --- a/Source/ARTPushDeviceRegistrations.h +++ b/Source/ARTPushDeviceRegistrations.h @@ -23,9 +23,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)save:(ARTDeviceDetails *)deviceDetails callback:(void (^)(ARTErrorInfo *_Nullable))callback; -- (void)get:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; +- (void)list:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *_Nullable, ARTErrorInfo *_Nullable))callback; -- (void)remove:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *_Nullable))callback; +- (void)remove:(NSString *)deviceId callback:(void (^)(ARTErrorInfo *_Nullable))callback; +- (void)removeWhere:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *_Nullable))callback; @end diff --git a/Source/ARTPushDeviceRegistrations.m b/Source/ARTPushDeviceRegistrations.m index 9b04106f1..a31382bc9 100644 --- a/Source/ARTPushDeviceRegistrations.m +++ b/Source/ARTPushDeviceRegistrations.m @@ -38,13 +38,7 @@ - (void)save:(ARTDeviceDetails *)deviceDetails callback:(void (^)(ARTErrorInfo * NSString *tokenBase64 = [tokenData base64EncodedStringWithOptions:0]; [request setValue:[NSString stringWithFormat:@"Bearer %@", tokenBase64] forHTTPHeaderField:@"Authorization"]; request.HTTPMethod = @"PUT"; - request.HTTPBody = [[_httpExecutor defaultEncoder] encode:@{ - @"push": @{ - @"metadata": @{ - @"deviceToken": deviceDetails.push.deviceToken, - } - } - }]; + request.HTTPBody = [[_httpExecutor defaultEncoder] encodeDeviceDetails:deviceDetails]; [request setValue:[[_httpExecutor defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; [_logger debug:__FILE__ line:__LINE__ message:@"save device with request %@", request]; @@ -63,7 +57,7 @@ - (void)save:(ARTDeviceDetails *)deviceDetails callback:(void (^)(ARTErrorInfo * }]; } -- (void)get:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *result, ARTErrorInfo *error))callback { +- (void)list:(NSDictionary *)params callback:(void (^)(ARTPaginatedResult *result, ARTErrorInfo *error))callback { NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"] resolvingAgainstBaseURL:NO]; components.queryItems = [params asURLQueryItems]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; @@ -75,7 +69,11 @@ - (void)get:(NSDictionary *)params callback:(void (^)(AR [ARTPaginatedResult executePaginated:_httpExecutor withRequest:request andResponseProcessor:responseProcessor callback:callback]; } -- (void)remove:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *error))callback { +- (void)remove:(NSString *)deviceId callback:(void (^)(ARTErrorInfo *error))callback { + [self removeWhere:@{@"deviceId": deviceId} callback:callback]; +} + +- (void)removeWhere:(NSDictionary *)params callback:(void (^)(ARTErrorInfo *error))callback { NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"] resolvingAgainstBaseURL:NO]; components.queryItems = [params asURLQueryItems]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; diff --git a/Source/ARTRealtime.m b/Source/ARTRealtime.m index dce8a2225..ba2d7dfe9 100644 --- a/Source/ARTRealtime.m +++ b/Source/ARTRealtime.m @@ -60,7 +60,6 @@ - (instancetype)initWithOptions:(ARTClientOptions *)options { NSAssert(options, @"ARTRealtime: No options provided"); _rest = [[ARTRest alloc] initWithOptions:options]; - _push = _rest.push; _eventQueue = dispatch_queue_create("io.ably.realtime.events", DISPATCH_QUEUE_SERIAL); _internalEventEmitter = [[ARTEventEmitter alloc] initWithQueue:_eventQueue]; _connectedEventEmitter = [[ARTEventEmitter alloc] initWithQueue:_eventQueue]; diff --git a/Source/ARTRealtimeChannel.h b/Source/ARTRealtimeChannel.h index ffac9631b..90a6355ad 100644 --- a/Source/ARTRealtimeChannel.h +++ b/Source/ARTRealtimeChannel.h @@ -18,7 +18,9 @@ ART_ASSUME_NONNULL_BEGIN @class ARTRealtimePresence; +#ifdef TARGET_OS_IPHONE @class ARTPushChannel; +#endif @interface ARTRealtimeChannel : ARTChannel @@ -26,7 +28,9 @@ ART_ASSUME_NONNULL_BEGIN @property (readonly, strong, nonatomic, art_nullable) ARTErrorInfo *errorReason; @property (readonly) ARTRealtimePresence *presence; +#ifdef TARGET_OS_IPHONE @property (readonly) ARTPushChannel *push; +#endif - (void)attach; - (void)attach:(art_nullable void (^)(ARTErrorInfo *__art_nullable))callback; diff --git a/Source/ARTRealtimeChannel.m b/Source/ARTRealtimeChannel.m index cef62ab15..5de0b37ed 100644 --- a/Source/ARTRealtimeChannel.m +++ b/Source/ARTRealtimeChannel.m @@ -26,11 +26,15 @@ #import "ARTDefault.h" #import "ARTRest+Private.h" #import "ARTClientOptions.h" +#ifdef TARGET_OS_IPHONE #import "ARTPushChannel.h" +#endif @interface ARTRealtimeChannel () { ARTRealtimePresence *_realtimePresence; + #ifdef TARGET_OS_IPHONE ARTPushChannel *_pushChannel; + #endif CFRunLoopTimerRef _attachTimer; CFRunLoopTimerRef _detachTimer; __GENERIC(ARTEventEmitter, NSNull *, ARTErrorInfo *) *_attachedEventEmitter; @@ -74,6 +78,8 @@ - (ARTRealtimePresence *)presence { return _realtimePresence; } +#ifdef TARGET_OS_IPHONE + - (ARTPushChannel *)push { if (!_pushChannel) { _pushChannel = [[ARTPushChannel alloc] init:self.realtime.rest withChannel:self]; @@ -81,6 +87,8 @@ - (ARTPushChannel *)push { return _pushChannel; } +#endif + - (void)internalPostMessages:(id)data callback:(void (^)(ARTErrorInfo *__art_nullable error))callback { ARTProtocolMessage *msg = [[ARTProtocolMessage alloc] init]; msg.action = ARTProtocolMessageMessage; @@ -724,4 +732,10 @@ - (BOOL)history:(ARTRealtimeHistoryQuery *)query callback:(void (^)(__GENERIC(AR } } +#ifdef TARGET_OS_IOS +- (ARTLocalDevice *)device { + return _realtime.device; +} +#endif + @end diff --git a/Source/ARTRest.h b/Source/ARTRest.h index 18b0ea843..c1f2bad09 100644 --- a/Source/ARTRest.h +++ b/Source/ARTRest.h @@ -53,7 +53,7 @@ ART_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) ARTAuth *auth; @property (nonatomic, strong, readonly) ARTPush *push; #ifdef TARGET_OS_IOS -@property (nonnull, nonatomic, readonly, getter=getDevice) ARTLocalDevice *device; +@property (nonnull, nonatomic, readonly, getter=device) ARTLocalDevice *device; #endif @end diff --git a/Source/ARTRest.m b/Source/ARTRest.m index dbdbc1720..4ae1d6715 100644 --- a/Source/ARTRest.m +++ b/Source/ARTRest.m @@ -35,6 +35,7 @@ #import "ARTFallback.h" #import "ARTGCD.h" #import "ARTPush+Private.h" +#import "ARTLocalDevice+Private.h" @implementation ARTRest { ARTLog *_logger; @@ -247,6 +248,10 @@ - (void)time:(void(^)(NSDate *time, NSError *error))callback { [request setValue:accept forHTTPHeaderField:@"Accept"]; [self executeRequest:request withAuthOption:ARTAuthenticationOff completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (error) { + callback(nil, error); + return; + } if (response.statusCode >= 400) { callback(nil, [self->_encoders[response.MIMEType] decodeError:data]); } else { diff --git a/Source/ARTRestChannel.m b/Source/ARTRestChannel.m index a6fb7371e..11b0ead2d 100644 --- a/Source/ARTRestChannel.m +++ b/Source/ARTRestChannel.m @@ -147,4 +147,10 @@ - (void)internalPostMessages:(id)data callback:(void (^)(ARTErrorInfo *__art_nul }]; } +#ifdef TARGET_OS_IOS +- (ARTLocalDevice *)device { + return _rest.device; +} +#endif + @end diff --git a/Source/Ably.h b/Source/Ably.h index 9a18bf098..be2ff81fb 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -58,7 +58,9 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import "ARTOSReachability.h" #import "ARTGCD.h" #import "ARTPush.h" +#ifdef TARGET_OS_IPHONE #import "ARTPushChannel.h" +#endif #import "ARTPushChannelSubscription.h" #import "ARTPushActivationStateMachine.h" #import "ARTPushActivationEvent.h" From 0aebf3a0a35bc6c5d075ab818221fc1090f41b21 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 18 Apr 2017 15:01:53 +0100 Subject: [PATCH 36/46] Initial State Machine tests --- Ably.xcodeproj/project.pbxproj | 12 +++++++ Spec/Push.swift | 66 ++++++++++++++++++++++++++++++++++ Spec/RealtimeClientPush.swift | 17 +++++++++ Spec/RestClientPush.swift | 17 +++++++++ Spec/TestUtilities.swift | 53 +++++++++++++++++++++++++++ 5 files changed, 165 insertions(+) create mode 100644 Spec/Push.swift create mode 100644 Spec/RealtimeClientPush.swift create mode 100644 Spec/RestClientPush.swift diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index e6de4c216..38e5ed248 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -173,6 +173,9 @@ D7DC8AF11C6AA0C8005AF165 /* ARTDefault+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D7DC8AF01C6A9FFC005AF165 /* ARTDefault+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; D7DEAFD11E65926D00D23F24 /* ARTLocalDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = D7DEAFCF1E65926D00D23F24 /* ARTLocalDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7DEAFD21E65926D00D23F24 /* ARTLocalDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = D7DEAFD01E65926D00D23F24 /* ARTLocalDevice.m */; }; + D7DF73851EA600240013CD36 /* Push.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF73831EA5FE620013CD36 /* Push.swift */; }; + D7DF73861EA600290013CD36 /* RestClientPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF73811EA5FE3F0013CD36 /* RestClientPush.swift */; }; + D7DF73871EA6002C0013CD36 /* RealtimeClientPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF737F1EA5FE2F0013CD36 /* RealtimeClientPush.swift */; }; D7EBE5A31BE8391E0086E675 /* RealtimeClientConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EBE5A21BE8391E0086E675 /* RealtimeClientConnection.swift */; }; D7EBE5A41BE9F6900086E675 /* ARTConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D7D29B401BE3DD0600374295 /* ARTConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7F1D3731BF4DE07001A4B5E /* ARTRestPresence.h in Headers */ = {isa = PBXBuildFile; fileRef = D7F1D3711BF4DE07001A4B5E /* ARTRestPresence.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -438,6 +441,9 @@ D7DC8AF01C6A9FFC005AF165 /* ARTDefault+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ARTDefault+Private.h"; sourceTree = ""; }; D7DEAFCF1E65926D00D23F24 /* ARTLocalDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTLocalDevice.h; sourceTree = ""; }; D7DEAFD01E65926D00D23F24 /* ARTLocalDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTLocalDevice.m; sourceTree = ""; }; + D7DF737F1EA5FE2F0013CD36 /* RealtimeClientPush.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClientPush.swift; sourceTree = ""; }; + D7DF73811EA5FE3F0013CD36 /* RestClientPush.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestClientPush.swift; sourceTree = ""; }; + D7DF73831EA5FE620013CD36 /* Push.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Push.swift; sourceTree = ""; }; D7EBE5A21BE8391E0086E675 /* RealtimeClientConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClientConnection.swift; sourceTree = ""; }; D7F1D3711BF4DE07001A4B5E /* ARTRestPresence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRestPresence.h; sourceTree = ""; }; D7F1D3721BF4DE07001A4B5E /* ARTRestPresence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRestPresence.m; sourceTree = ""; }; @@ -567,16 +573,19 @@ 856AAC951B6E30C800B07119 /* AblySpec-Bridging-Header.h */, EBAB9A6E1C69702800AF036B /* ReadmeExamples.swift */, D7C1B8761BBEA81A0087B55F /* Auth.swift */, + D7DF73831EA5FE620013CD36 /* Push.swift */, 856AAC981B6E312F00B07119 /* RestClient.swift */, 853ED7C31B7A1A3C006F1C6F /* RestClientStats.swift */, D746AE2A1BBB625E003ECEF8 /* RestClientChannel.swift */, D746AE2B1BBB625E003ECEF8 /* RestClientChannels.swift */, D72768201C9C19040022F8B2 /* RestClientPresence.swift */, + D7DF73811EA5FE3F0013CD36 /* RestClientPush.swift */, D723046F1BB72CED00F1ABDA /* RealtimeClient.swift */, D7EBE5A21BE8391E0086E675 /* RealtimeClientConnection.swift */, D74EFAEA1C4D09B500CFF98E /* RealtimeClientChannel.swift */, D71D30031C5F7B2F002115B0 /* RealtimeClientChannels.swift */, D7CEF1311C8DD3BC004FB242 /* RealtimeClientPresence.swift */, + D7DF737F1EA5FE2F0013CD36 /* RealtimeClientPush.swift */, 851674EE1B7BA5CD00D35169 /* Stats.swift */, EB1AE0CD1C5C3A4900D62250 /* Utilities.swift */, EB7913A71C6E54C3000ABF9B /* Crypto.swift */, @@ -1254,14 +1263,17 @@ 856AAC991B6E312F00B07119 /* RestClient.swift in Sources */, D746AE2C1BBB625E003ECEF8 /* RestClientChannel.swift in Sources */, D71D30041C5F7B2F002115B0 /* RealtimeClientChannels.swift in Sources */, + D7DF73851EA600240013CD36 /* Push.swift in Sources */, D714A63E1C74D4B2002F2CA0 /* NSObject+TestSuite.swift in Sources */, 856AAC971B6E30C800B07119 /* TestUtilities.swift in Sources */, D72768211C9C19040022F8B2 /* RestClientPresence.swift in Sources */, D780846E1C68B3E50083009D /* NSObject+TestSuite.m in Sources */, + D7DF73871EA6002C0013CD36 /* RealtimeClientPush.swift in Sources */, D7CEF1321C8DD3BC004FB242 /* RealtimeClientPresence.swift in Sources */, 853ED7C41B7A1A3C006F1C6F /* RestClientStats.swift in Sources */, D74EFAEB1C4D09B500CFF98E /* RealtimeClientChannel.swift in Sources */, 851674EF1B7BA5CD00D35169 /* Stats.swift in Sources */, + D7DF73861EA600290013CD36 /* RestClientPush.swift in Sources */, EB1AE0CE1C5C3A4900D62250 /* Utilities.swift in Sources */, D72304701BB72CED00F1ABDA /* RealtimeClient.swift in Sources */, EB7913A81C6E54C3000ABF9B /* Crypto.swift in Sources */, diff --git a/Spec/Push.swift b/Spec/Push.swift new file mode 100644 index 000000000..20a01fc9f --- /dev/null +++ b/Spec/Push.swift @@ -0,0 +1,66 @@ +// +// Push.swift +// Ably +// +// Created by Ricardo Pereira on 18/04/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +import Ably +import Nimble +import Quick + +class Push : QuickSpec { + override func spec() { + + var activationStateMachine: ARTPushActivationStateMachine! + var testHTTPExecutor: MockHTTPExecutor! + var testStorage: MockDeviceStorage! + + beforeEach { + testHTTPExecutor = MockHTTPExecutor() + testStorage = MockDeviceStorage() + activationStateMachine = ARTPushActivationStateMachine(testHTTPExecutor, storage: testStorage) + } + + describe("Activation state machine") { + + context("State NotActivated") { + + it("should be the initial state") { + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + } + + it("should read the current state in disk") { + let storage = MockDeviceStorage() + + let state = ARTPushActivationStateWaitingForUpdateToken(machine: activationStateMachine) + let data = NSKeyedArchiver.archivedDataWithRootObject(state) + storage.simulateOnNextRead(data) + + let activationStateMachine = ARTPushActivationStateMachine(MockHTTPExecutor(), storage: storage) + + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + expect(storage.keysRead).to(haveCount(2)) + expect(storage.keysRead.filter({ $0.hasSuffix("CurrentState") })).to(haveCount(1)) + expect(storage.keysWrite).to(beEmpty()) + } + + it("on Event CalledDeactivate") { + var deactivatedCallbackCalled = false + let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { + deactivatedCallbackCalled = true + } + defer { hook.remove() } + + activationStateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(deactivatedCallbackCalled).to(beTrue()) + } + + } + + } + } +} diff --git a/Spec/RealtimeClientPush.swift b/Spec/RealtimeClientPush.swift new file mode 100644 index 000000000..e44873d45 --- /dev/null +++ b/Spec/RealtimeClientPush.swift @@ -0,0 +1,17 @@ +// +// RealtimeClientPush.swift +// Ably +// +// Created by Ricardo Pereira on 18/04/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +import Ably +import Nimble +import Quick + +class RealtimeClientPush : QuickSpec { + override func spec() { + + } +} diff --git a/Spec/RestClientPush.swift b/Spec/RestClientPush.swift new file mode 100644 index 000000000..27513573a --- /dev/null +++ b/Spec/RestClientPush.swift @@ -0,0 +1,17 @@ +// +// RestClientPush.swift +// Ably +// +// Created by Ricardo Pereira on 18/04/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +import Ably +import Nimble +import Quick + +class RestClientPush : QuickSpec { + override func spec() { + + } +} diff --git a/Spec/TestUtilities.swift b/Spec/TestUtilities.swift index 53fea224b..6f6bb5d63 100644 --- a/Spec/TestUtilities.swift +++ b/Spec/TestUtilities.swift @@ -574,6 +574,59 @@ class MockHTTP: ARTHttp { } +class MockDeviceStorage: NSObject, ARTDeviceStorage { + + var keysRead: [String] = [] + var keysWrite: [String] = [] + + private var simulateData: NSData? + + func readKey(key: String) -> NSData? { + keysRead.append(key) + if var data = simulateData { + defer { simulateData = nil } + return data + } + return nil + } + + func writeKey(key: String, withValue value: NSData?) { + keysWrite.append(key) + } + + func simulateOnNextRead(data: NSData) { + simulateData = data + } + +} + +class MockHTTPExecutor: NSObject, ARTHTTPAuthenticatedExecutor { + + var logger = ARTLog() + var clientOptions = ARTClientOptions() + var encoder = ARTJsonLikeEncoder() + var requests: [NSMutableURLRequest] = [] + + func options() -> ARTClientOptions { + return self.clientOptions + } + + func defaultEncoder() -> ARTEncoder { + return self.encoder + } + + func executeRequest(request: NSMutableURLRequest, withAuthOption authOption: ARTAuthentication, completion callback: (NSHTTPURLResponse?, NSData?, NSError?) -> Void) { + self.requests.append(request) + callback(nil, nil, nil) + } + + func executeRequest(request: NSMutableURLRequest, completion callback: ((NSHTTPURLResponse?, NSData?, NSError?) -> Void)?) { + self.requests.append(request) + callback?(nil, nil, nil) + } + +} + /// Records each request and response for test purpose. class TestProxyHTTPExecutor: NSObject, ARTHTTPExecutor { From 595d036471300cda40c97adf385f7f96b851275e Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 18 Apr 2017 15:03:29 +0100 Subject: [PATCH 37/46] ARTLocalDeviceStorage --- Ably.xcodeproj/project.pbxproj | 10 +++++++++- Source/ARTLocalDeviceStorage.h | 22 ++++++++++++++++++++++ Source/ARTLocalDeviceStorage.m | 21 +++++++++++++++++++++ Source/ARTPushActivationState.h | 2 ++ Source/ARTPushActivationState.m | 6 ------ Source/ARTPushActivationStateMachine.h | 5 +++++ Source/ARTPushActivationStateMachine.m | 19 ++++++++++++++----- Source/Ably.h | 1 + 8 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 Source/ARTLocalDeviceStorage.h create mode 100644 Source/ARTLocalDeviceStorage.m diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 38e5ed248..d27d4c2e7 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -138,7 +138,7 @@ D768C6AD1E4B5B0200436011 /* ARTDevicePushDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */; }; D77394031C6F6FFE00F5478F /* ARTProtocolMessage+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D77394021C6F6FFE00F5478F /* ARTProtocolMessage+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; D780846E1C68B3E50083009D /* NSObject+TestSuite.m in Sources */ = {isa = PBXBuildFile; fileRef = D780846D1C68B3E50083009D /* NSObject+TestSuite.m */; }; - D785C4291E549E33008FEC05 /* ARTPushChannelSubscription.h in Headers */ = {isa = PBXBuildFile; fileRef = D785C4271E549E33008FEC05 /* ARTPushChannelSubscription.h */; }; + D785C4291E549E33008FEC05 /* ARTPushChannelSubscription.h in Headers */ = {isa = PBXBuildFile; fileRef = D785C4271E549E33008FEC05 /* ARTPushChannelSubscription.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785C42A1E549E33008FEC05 /* ARTPushChannelSubscription.m in Sources */ = {isa = PBXBuildFile; fileRef = D785C4281E549E33008FEC05 /* ARTPushChannelSubscription.m */; }; D79FF2751D901CD50067FA6A /* ARTRealtime+TestSuite.m in Sources */ = {isa = PBXBuildFile; fileRef = D79FF2741D901CD50067FA6A /* ARTRealtime+TestSuite.m */; }; D7AE18C91E5B40C900478D82 /* ARTPushAdmin.h in Headers */ = {isa = PBXBuildFile; fileRef = D7AE18C71E5B40C900478D82 /* ARTPushAdmin.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -176,6 +176,8 @@ D7DF73851EA600240013CD36 /* Push.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF73831EA5FE620013CD36 /* Push.swift */; }; D7DF73861EA600290013CD36 /* RestClientPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF73811EA5FE3F0013CD36 /* RestClientPush.swift */; }; D7DF73871EA6002C0013CD36 /* RealtimeClientPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF737F1EA5FE2F0013CD36 /* RealtimeClientPush.swift */; }; + D7DF738A1EA645300013CD36 /* ARTLocalDeviceStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = D7DF73881EA645300013CD36 /* ARTLocalDeviceStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D7DF738B1EA645300013CD36 /* ARTLocalDeviceStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D7DF73891EA645300013CD36 /* ARTLocalDeviceStorage.m */; }; D7EBE5A31BE8391E0086E675 /* RealtimeClientConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EBE5A21BE8391E0086E675 /* RealtimeClientConnection.swift */; }; D7EBE5A41BE9F6900086E675 /* ARTConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D7D29B401BE3DD0600374295 /* ARTConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7F1D3731BF4DE07001A4B5E /* ARTRestPresence.h in Headers */ = {isa = PBXBuildFile; fileRef = D7F1D3711BF4DE07001A4B5E /* ARTRestPresence.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -444,6 +446,8 @@ D7DF737F1EA5FE2F0013CD36 /* RealtimeClientPush.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClientPush.swift; sourceTree = ""; }; D7DF73811EA5FE3F0013CD36 /* RestClientPush.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestClientPush.swift; sourceTree = ""; }; D7DF73831EA5FE620013CD36 /* Push.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Push.swift; sourceTree = ""; }; + D7DF73881EA645300013CD36 /* ARTLocalDeviceStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTLocalDeviceStorage.h; sourceTree = ""; }; + D7DF73891EA645300013CD36 /* ARTLocalDeviceStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTLocalDeviceStorage.m; sourceTree = ""; }; D7EBE5A21BE8391E0086E675 /* RealtimeClientConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClientConnection.swift; sourceTree = ""; }; D7F1D3711BF4DE07001A4B5E /* ARTRestPresence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRestPresence.h; sourceTree = ""; }; D7F1D3721BF4DE07001A4B5E /* ARTRestPresence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRestPresence.m; sourceTree = ""; }; @@ -690,6 +694,8 @@ D71966E91E5E006E000974DD /* ARTPushActivationState.m */, D71966EC1E5E0081000974DD /* ARTPushActivationEvent.h */, D71966ED1E5E0081000974DD /* ARTPushActivationEvent.m */, + D7DF73881EA645300013CD36 /* ARTLocalDeviceStorage.h */, + D7DF73891EA645300013CD36 /* ARTLocalDeviceStorage.m */, ); name = "Activation State Machine"; sourceTree = ""; @@ -992,6 +998,7 @@ D7D8F8251BC2C691009718F2 /* ARTTokenDetails.h in Headers */, D7D8F82B1BC2C706009718F2 /* ARTTokenRequest.h in Headers */, EB1AE0CC1C5C1EB200D62250 /* ARTEventEmitter+Private.h in Headers */, + D7DF738A1EA645300013CD36 /* ARTLocalDeviceStorage.h in Headers */, 960D07931A45F1D800ED8C8C /* ARTCrypto.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1333,6 +1340,7 @@ EB9121401CA0AD8200BA0A40 /* ARTMsgPackEncoder.m in Sources */, 96BF61651A35CDE1004CF2B3 /* ARTBaseMessage.m in Sources */, D7F1D3781BF4DE72001A4B5E /* ARTRealtimePresence.m in Sources */, + D7DF738B1EA645300013CD36 /* ARTLocalDeviceStorage.m in Sources */, D7B621911E4A6E0200684474 /* ARTPush.m in Sources */, D7AE18D31E5B410F00478D82 /* ARTPushChannelSubscriptions.m in Sources */, 1CD8DCA01B1C7315007EAF36 /* ARTDefault.m in Sources */, diff --git a/Source/ARTLocalDeviceStorage.h b/Source/ARTLocalDeviceStorage.h new file mode 100644 index 000000000..7b13f02d8 --- /dev/null +++ b/Source/ARTLocalDeviceStorage.h @@ -0,0 +1,22 @@ +// +// ARTLocalDeviceStorage.h +// Ably +// +// Created by Ricardo Pereira on 18/04/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol ARTDeviceStorage +- (nullable NSData *)readKey:(NSString *)key; +- (void)writeKey:(NSString *)key withValue:(nullable NSData *)value; +@end + +@interface ARTLocalDeviceStorage : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTLocalDeviceStorage.m b/Source/ARTLocalDeviceStorage.m new file mode 100644 index 000000000..ae72751af --- /dev/null +++ b/Source/ARTLocalDeviceStorage.m @@ -0,0 +1,21 @@ +// +// ARTLocalDeviceStorage.m +// Ably +// +// Created by Ricardo Pereira on 18/04/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +#import "ARTLocalDeviceStorage.h" + +@implementation ARTLocalDeviceStorage + +- (NSData *)readKey:(NSString *)key { + return [[NSUserDefaults standardUserDefaults] objectForKey:key]; +} + +- (void)writeKey:(NSString *)key withValue:(id)value { + return [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; +} + +@end diff --git a/Source/ARTPushActivationState.h b/Source/ARTPushActivationState.h index aeca68c62..a831ea0b7 100644 --- a/Source/ARTPushActivationState.h +++ b/Source/ARTPushActivationState.h @@ -17,6 +17,8 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationState : NSObject +@property (atomic) ARTPushActivationStateMachine *machine; + - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithMachine:(ARTPushActivationStateMachine *)machine; + (instancetype)new NS_UNAVAILABLE; diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m index e85b1ea80..84dd03c8c 100644 --- a/Source/ARTPushActivationState.m +++ b/Source/ARTPushActivationState.m @@ -15,12 +15,6 @@ #import "ARTRest+Private.h" #import "ARTHttp.h" -@interface ARTPushActivationState () - -@property (atomic, readonly) ARTPushActivationStateMachine *machine; - -@end - @implementation ARTPushActivationState - (instancetype)initWithMachine:(ARTPushActivationStateMachine *)machine { diff --git a/Source/ARTPushActivationStateMachine.h b/Source/ARTPushActivationStateMachine.h index e9b50a7b6..e298b4cbe 100644 --- a/Source/ARTPushActivationStateMachine.h +++ b/Source/ARTPushActivationStateMachine.h @@ -9,18 +9,23 @@ #import @class ARTErrorInfo; +@class ARTPushActivationState; @class ARTPushActivationEvent; @class ARTRest; +@protocol ARTDeviceStorage; + NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationStateMachine : NSObject @property (nonatomic, strong) ARTRest *rest; +@property (nonatomic, readonly) ARTPushActivationState *current; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; - (instancetype)init:(ARTRest *)rest; +- (instancetype)init:(ARTRest *)rest storage:(id)storage; - (void)sendEvent:(ARTPushActivationEvent *)event; diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m index c11ba5432..891cbd692 100644 --- a/Source/ARTPushActivationStateMachine.m +++ b/Source/ARTPushActivationStateMachine.m @@ -17,6 +17,7 @@ #import "ARTTypes.h" #import "ARTLocalDevice+Private.h" #import "ARTDevicePushDetails.h" +#import "ARTLocalDeviceStorage.h" #ifdef TARGET_OS_IOS #import @@ -25,20 +26,28 @@ NSString *const ARTPushActivationPendingEventsKey = @"ARTPushActivationPendingEvents"; @implementation ARTPushActivationStateMachine { - ARTPushActivationState *_current; NSMutableArray *_pendingEvents; + id _storage; } - (instancetype)init:(ARTRest *)rest { + return [self init:rest storage:[ARTLocalDeviceStorage new]]; +} + +- (instancetype)init:(ARTRest *)rest storage:(id)storage { if (self = [super init]) { _rest = rest; + _storage = storage; // Unarquiving - NSData *stateData = [[NSUserDefaults standardUserDefaults] objectForKey:ARTPushActivationCurrentStateKey]; + NSData *stateData = [_storage readKey:ARTPushActivationCurrentStateKey]; _current = [NSKeyedUnarchiver unarchiveObjectWithData:stateData]; if (!_current) { _current = [ARTPushActivationStateNotActivated newWithMachine:self]; } - NSData *pendingEventsData = [[NSUserDefaults standardUserDefaults] objectForKey:ARTPushActivationPendingEventsKey]; + else { + _current.machine = self; + } + NSData *pendingEventsData = [_storage readKey:ARTPushActivationPendingEventsKey]; _pendingEvents = [NSKeyedUnarchiver unarchiveObjectWithData:pendingEventsData]; if (!_pendingEvents) { _pendingEvents = [NSMutableArray array]; @@ -81,9 +90,9 @@ - (void)handleEvent:(nonnull ARTPushActivationEvent *)event { - (void)persist { // Archiving if ([_current isKindOfClass:[ARTPushActivationPersistentState class]]) { - [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_current] forKey:ARTPushActivationCurrentStateKey]; + [_storage writeKey:ARTPushActivationCurrentStateKey withValue:[NSKeyedArchiver archivedDataWithRootObject:_current]]; } - [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_pendingEvents] forKey:ARTPushActivationPendingEventsKey]; + [_storage writeKey:ARTPushActivationPendingEventsKey withValue:[NSKeyedArchiver archivedDataWithRootObject:_pendingEvents]]; } - (void)deviceRegistration:(ARTErrorInfo *)error { diff --git a/Source/Ably.h b/Source/Ably.h index be2ff81fb..3eaa122a3 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -71,6 +71,7 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import "ARTDeviceDetails.h" #import "ARTDevicePushDetails.h" #import "ARTLocalDevice.h" +#import "ARTLocalDeviceStorage.h" #import "ARTNSDictionary+ARTDictionaryUtil.h" #import "ARTNSDate+ARTUtil.h" From 82f99060cbbae1eea7c388e3699a0dab8d55f85b Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 18 Apr 2017 15:03:53 +0100 Subject: [PATCH 38/46] Fix: ARTPushActivationState --- Source/ARTPushActivationState.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m index 84dd03c8c..2c8b33c68 100644 --- a/Source/ARTPushActivationState.m +++ b/Source/ARTPushActivationState.m @@ -25,7 +25,7 @@ - (instancetype)initWithMachine:(ARTPushActivationStateMachine *)machine { } + (instancetype)newWithMachine:(ARTPushActivationStateMachine *)machine { - return [[ARTPushActivationState alloc] initWithMachine:machine]; + return [[self alloc] initWithMachine:machine]; } - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { From 70ab35816e3344f0fc6b25aeb8a1ae172108f7bd Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 19 Apr 2017 10:04:27 +0100 Subject: [PATCH 39/46] Storage should synchronize UserDefaults --- Source/ARTLocalDeviceStorage.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/ARTLocalDeviceStorage.m b/Source/ARTLocalDeviceStorage.m index ae72751af..71bcc00a9 100644 --- a/Source/ARTLocalDeviceStorage.m +++ b/Source/ARTLocalDeviceStorage.m @@ -15,7 +15,8 @@ - (NSData *)readKey:(NSString *)key { } - (void)writeKey:(NSString *)key withValue:(id)value { - return [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; + [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; + [[NSUserDefaults standardUserDefaults] synchronize]; } @end From 332748fab10e3693c6a6f48ddbf0aaced7131204 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 19 Apr 2017 10:05:03 +0100 Subject: [PATCH 40/46] Storage Keys accessible on tests --- Source/ARTPush.h | 7 +++++++ Source/ARTPushActivationStateMachine.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 2de117546..ae7f969e3 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -19,6 +19,13 @@ typedef NSData ARTDeviceToken; typedef NSString ARTUpdateToken; typedef ARTJsonObject ARTPushRecipient; +NS_ASSUME_NONNULL_BEGIN +extern NSString *const ARTDeviceIdKey; +extern NSString *const ARTDeviceUpdateTokenKey; +extern NSString *const ARTDeviceTokenKey; +NS_ASSUME_NONNULL_END + + #pragma mark ARTPushRegisterer interface #ifdef TARGET_OS_IOS diff --git a/Source/ARTPushActivationStateMachine.h b/Source/ARTPushActivationStateMachine.h index e298b4cbe..4499095eb 100644 --- a/Source/ARTPushActivationStateMachine.h +++ b/Source/ARTPushActivationStateMachine.h @@ -17,6 +17,9 @@ NS_ASSUME_NONNULL_BEGIN +extern NSString *const ARTPushActivationCurrentStateKey; +extern NSString *const ARTPushActivationPendingEventsKey; + @interface ARTPushActivationStateMachine : NSObject @property (nonatomic, strong) ARTRest *rest; From 81fd669fd11db0a4a658ed2a64f3564f1ab97c46 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 19 Apr 2017 10:05:25 +0100 Subject: [PATCH 41/46] State Machine tests --- Spec/Push.swift | 186 ++++++++++++++++++++++++++++++++++++++- Spec/TestUtilities.swift | 10 +-- 2 files changed, 190 insertions(+), 6 deletions(-) diff --git a/Spec/Push.swift b/Spec/Push.swift index 20a01fc9f..2784329e6 100644 --- a/Spec/Push.swift +++ b/Spec/Push.swift @@ -36,7 +36,7 @@ class Push : QuickSpec { let state = ARTPushActivationStateWaitingForUpdateToken(machine: activationStateMachine) let data = NSKeyedArchiver.archivedDataWithRootObject(state) - storage.simulateOnNextRead(data) + storage.simulateOnNextRead(data, for: ARTPushActivationCurrentStateKey) let activationStateMachine = ARTPushActivationStateMachine(MockHTTPExecutor(), storage: storage) @@ -46,6 +46,52 @@ class Push : QuickSpec { expect(storage.keysWrite).to(beEmpty()) } + it("on Event CalledDeactivate, should transition to NotActivated") { + var deactivatedCallbackCalled = false + let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { + deactivatedCallbackCalled = true + } + defer { hook.remove() } + + activationStateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(deactivatedCallbackCalled).to(beTrue()) + } + + context("on Event CalledActivate") { + it("if the local device has id and updateToken then should transition to WaitingForNewPushDeviceDetails") { + let testDeviceId = "aaaa" + testStorage.simulateOnNextRead(testDeviceId, for: ARTDeviceIdKey) + + let testDeviceUpdateToken = "xxxx-xxxx-xxxx" + testStorage.simulateOnNextRead(testDeviceUpdateToken, for: ARTDeviceUpdateTokenKey) + activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + } + + it("if the local device has the necessary push details should send event GotPushDeviceDetails") { + let testDeviceToken = "xxxx-xxxx-xxxx-xxxx-xxxx" + testStorage.simulateOnNextRead(testDeviceToken, for: ARTDeviceTokenKey) + activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationEventGotPushDeviceDetails)) + } + + it("none of them then should transition to WaitingForPushDeviceDetails") { + activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + } + } + + } + + context("State WaitingForPushDeviceDetails") { + + it("on Event CalledActivate") { + activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationEventCalledActivate)) + } + it("on Event CalledDeactivate") { var deactivatedCallbackCalled = false let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { @@ -54,8 +100,146 @@ class Push : QuickSpec { defer { hook.remove() } activationStateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(deactivatedCallbackCalled).to(beTrue()) + } + + context("on Event GotPushDeviceDetails") { + it("should make an asynchronous HTTP request to /push/deviceRegistrations") { + activationStateMachine.sendEvent(ARTPushActivationEventGotPushDeviceDetails()) + expect(testHTTPExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations" })).to(haveCount(1)) + waitUntil(timeout: testTimeout) { done in + activationStateMachine.on(ARTPushActivationEventGotUpdateToken) { + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + done() + } + } + } + } + + } + + context("State WaitingForUpdateToken") { + + it("on Event CalledActivate") { + activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + } + + it("on Event GotUpdateToken") { + var activatedCallbackCalled = false + let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { + activatedCallbackCalled = true + } + defer { hook.remove() } + + activationStateMachine.sendEvent(ARTPushActivationEventGotUpdateToken()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(activatedCallbackCalled).to(beTrue()) + } + it("on Event GettingUpdateTokenFailed") { + var activatedCallbackCalled = false + let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { + activatedCallbackCalled = true + } + defer { hook.remove() } + + activationStateMachine.sendEvent(ARTPushActivationEventGettingUpdateTokenFailed()) expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(activatedCallbackCalled).to(beTrue()) + } + + } + + context("State WaitingForNewPushDeviceDetails") { + + it("on Event CalledActivate") { + var activatedCallbackCalled = false + let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { + activatedCallbackCalled = true + } + defer { hook.remove() } + + activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(activatedCallbackCalled).to(beTrue()) + } + + it("on Event CalledDeactivate") { + activationStateMachine.sendEvent(ARTPushActivationEventGotPushDeviceDetails()) + expect(testHTTPExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations " })).to(haveCount(1)) + waitUntil(timeout: testTimeout) { done in + activationStateMachine.on(ARTPushActivationEventDeregistered) { + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + done() + } + } + } + + } + + context("State WaitingForRegistrationUpdate") { + + it("on Event CalledActivate") { + var activatedCallbackCalled = false + let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { + activatedCallbackCalled = true + } + defer { hook.remove() } + + activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(activatedCallbackCalled).to(beTrue()) + } + + } + + context("State AfterRegistrationUpdateFailed") { + + it("on Event CalledActivate") { + + } + + it("on Event GotPushDeviceDetails") { + + } + + it("on Event CalledDeactivate") { + + } + + } + + context("State WaitingForDeregistration") { + + it("on Event CalledDeactivate") { + activationStateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + } + + it("on Event Deregistered") { + var deactivatedCallbackCalled = false + let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { + deactivatedCallbackCalled = true + } + defer { hook.remove() } + + activationStateMachine.sendEvent(ARTPushActivationEventDeregistered()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(deactivatedCallbackCalled).to(beTrue()) + expect(testStorage.keysWrite.filter({ $0 == ARTDeviceUpdateTokenKey })).to(haveCount(1)) + } + + it("on Event DeregistrationFailed") { + var deactivatedCallbackCalled = false + let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { + deactivatedCallbackCalled = true + } + defer { hook.remove() } + + activationStateMachine.sendEvent(ARTPushActivationEventDeregistrationFailed()) + expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) expect(deactivatedCallbackCalled).to(beTrue()) } diff --git a/Spec/TestUtilities.swift b/Spec/TestUtilities.swift index 6f6bb5d63..fd376281c 100644 --- a/Spec/TestUtilities.swift +++ b/Spec/TestUtilities.swift @@ -579,12 +579,12 @@ class MockDeviceStorage: NSObject, ARTDeviceStorage { var keysRead: [String] = [] var keysWrite: [String] = [] - private var simulateData: NSData? + private var simulateData: [String: NSData] = [:] func readKey(key: String) -> NSData? { keysRead.append(key) - if var data = simulateData { - defer { simulateData = nil } + if var data = simulateData[key] { + defer { simulateData.removeValueForKey(key) } return data } return nil @@ -594,8 +594,8 @@ class MockDeviceStorage: NSObject, ARTDeviceStorage { keysWrite.append(key) } - func simulateOnNextRead(data: NSData) { - simulateData = data + func simulateOnNextRead(data: NSData, `for` key: String) { + simulateData[key] = data } } From c65d63e44685c43f5d0dcc15c9188dd6d7ef1eb9 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 26 Apr 2017 09:55:56 +0100 Subject: [PATCH 42/46] ActivationStateMachine: custom delegate and other fixes --- Source/ARTAuth.h | 2 +- Source/ARTAuth.m | 2 +- Source/ARTDeviceDetails.m | 2 +- Source/ARTLocalDevice.m | 2 +- Source/ARTPushActivationEvent.h | 4 ++ Source/ARTPushActivationEvent.m | 10 +++ Source/ARTPushActivationState.h | 3 + Source/ARTPushActivationState.m | 10 +++ Source/ARTPushActivationStateMachine.h | 1 + Source/ARTPushActivationStateMachine.m | 84 +++++++++++++------------- 10 files changed, 73 insertions(+), 47 deletions(-) diff --git a/Source/ARTAuth.h b/Source/ARTAuth.h index a2a6bd6f4..a66be6b53 100644 --- a/Source/ARTAuth.h +++ b/Source/ARTAuth.h @@ -24,7 +24,7 @@ ART_ASSUME_NONNULL_BEGIN @interface ARTAuth : NSObject -@property (art_nullable, readonly, getter=getClientId) NSString *clientId; +@property (nullable, readonly) NSString *clientId; // FIXME: review (Why rest?) - (instancetype)init:(ARTRest *)rest withOptions:(ARTClientOptions *)options; diff --git a/Source/ARTAuth.m b/Source/ARTAuth.m index ab6326779..ba91a9859 100644 --- a/Source/ARTAuth.m +++ b/Source/ARTAuth.m @@ -428,7 +428,7 @@ - (void)setProtocolClientId:(NSString *)clientId { _protocolClientId = clientId; } -- (NSString *)getClientId { +- (NSString *)clientId { if (_protocolClientId) { return _protocolClientId; } diff --git a/Source/ARTDeviceDetails.m b/Source/ARTDeviceDetails.m index 5235a42c9..a915d8464 100644 --- a/Source/ARTDeviceDetails.m +++ b/Source/ARTDeviceDetails.m @@ -12,7 +12,7 @@ @implementation ARTDeviceDetails - (instancetype)init { - if (self = [self init]) { + if (self = [super init]) { _push = [[ARTDevicePushDetails alloc] init]; _metadata = [[NSMutableDictionary alloc] init]; } diff --git a/Source/ARTLocalDevice.m b/Source/ARTLocalDevice.m index 22b560681..31e8bce58 100644 --- a/Source/ARTLocalDevice.m +++ b/Source/ARTLocalDevice.m @@ -47,7 +47,7 @@ - (instancetype)initWithRest:(ARTRest *)rest { + (ARTLocalDevice *)load:(ARTRest *_Nonnull)rest { ARTLocalDevice *device = [[ARTLocalDevice alloc] initWithRest:rest]; - device.clientId = [device.rest.auth getClientId]; + device.clientId = [device.rest.auth clientId]; device.platform = ARTDevicePlatform; switch (UI_USER_INTERFACE_IDIOM()) { case UIUserInterfaceIdiomPad: diff --git a/Source/ARTPushActivationEvent.h b/Source/ARTPushActivationEvent.h index a5a78a8bc..7b0c5a864 100644 --- a/Source/ARTPushActivationEvent.h +++ b/Source/ARTPushActivationEvent.h @@ -9,11 +9,15 @@ #import @class ARTErrorInfo; +@class ARTPushActivationState; NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationEvent : NSObject +- (NSData *)archive; ++ (nullable ARTPushActivationState *)unarchive:(NSData *)data; + @end /// Event with Error info diff --git a/Source/ARTPushActivationEvent.m b/Source/ARTPushActivationEvent.m index 6e9a26fbe..d54d5e60a 100644 --- a/Source/ARTPushActivationEvent.m +++ b/Source/ARTPushActivationEvent.m @@ -27,6 +27,16 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { // Just to persist the class info, no properties } +#pragma mark - Archive/Unarchive + +- (NSData *)archive { + return [NSKeyedArchiver archivedDataWithRootObject:self]; +} + ++ (ARTPushActivationEvent *)unarchive:(NSData *)data { + return [NSKeyedUnarchiver unarchiveObjectWithData:data]; +} + @end #pragma mark - Event with Error info diff --git a/Source/ARTPushActivationState.h b/Source/ARTPushActivationState.h index a831ea0b7..c1367e3ac 100644 --- a/Source/ARTPushActivationState.h +++ b/Source/ARTPushActivationState.h @@ -26,6 +26,9 @@ NS_ASSUME_NONNULL_BEGIN - (nullable ARTPushActivationState *)transition:(ARTPushActivationEvent *)event; +- (NSData *)archive; ++ (nullable ARTPushActivationState *)unarchive:(NSData *)data; + @end /// Persistent State diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m index 2c8b33c68..ee23361da 100644 --- a/Source/ARTPushActivationState.m +++ b/Source/ARTPushActivationState.m @@ -53,6 +53,16 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { // Just to persist the class info, no properties } +#pragma mark - Archive/Unarchive + +- (NSData *)archive { + return [NSKeyedArchiver archivedDataWithRootObject:self]; +} + ++ (ARTPushActivationState *)unarchive:(NSData *)data { + return [NSKeyedUnarchiver unarchiveObjectWithData:data]; +} + @end #pragma mark - Persistent State diff --git a/Source/ARTPushActivationStateMachine.h b/Source/ARTPushActivationStateMachine.h index 4499095eb..b9e805ca8 100644 --- a/Source/ARTPushActivationStateMachine.h +++ b/Source/ARTPushActivationStateMachine.h @@ -24,6 +24,7 @@ extern NSString *const ARTPushActivationPendingEventsKey; @property (nonatomic, strong) ARTRest *rest; @property (nonatomic, readonly) ARTPushActivationState *current; +@property (nonatomic, weak) id delegate; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m index 891cbd692..3bb559d74 100644 --- a/Source/ARTPushActivationStateMachine.m +++ b/Source/ARTPushActivationStateMachine.m @@ -22,6 +22,8 @@ #ifdef TARGET_OS_IOS #import +NSString *const ARTPushException = @"ARTPushException"; + NSString *const ARTPushActivationCurrentStateKey = @"ARTPushActivationCurrentState"; NSString *const ARTPushActivationPendingEventsKey = @"ARTPushActivationPendingEvents"; @@ -40,7 +42,7 @@ - (instancetype)init:(ARTRest *)rest storage:(id)storage { _storage = storage; // Unarquiving NSData *stateData = [_storage readKey:ARTPushActivationCurrentStateKey]; - _current = [NSKeyedUnarchiver unarchiveObjectWithData:stateData]; + _current = [ARTPushActivationState unarchive:stateData]; if (!_current) { _current = [ARTPushActivationStateNotActivated newWithMachine:self]; } @@ -56,6 +58,13 @@ - (instancetype)init:(ARTRest *)rest storage:(id)storage { return self; } +- (id)delegate { + if (!_delegate) { + _delegate = [UIApplication sharedApplication].delegate; + } + return _delegate; +} + - (void)sendEvent:(ARTPushActivationEvent *)event { [self handleEvent:event]; } @@ -90,7 +99,7 @@ - (void)handleEvent:(nonnull ARTPushActivationEvent *)event { - (void)persist { // Archiving if ([_current isKindOfClass:[ARTPushActivationPersistentState class]]) { - [_storage writeKey:ARTPushActivationCurrentStateKey withValue:[NSKeyedArchiver archivedDataWithRootObject:_current]]; + [_storage writeKey:ARTPushActivationCurrentStateKey withValue:[_current archive]]; } [_storage writeKey:ARTPushActivationPendingEventsKey withValue:[NSKeyedArchiver archivedDataWithRootObject:_pendingEvents]]; } @@ -99,30 +108,28 @@ - (void)deviceRegistration:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS ARTLocalDevice *local = _rest.device; - if (![[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { - [NSException raise:@"ARTPushRegistererDelegate must be implemented on AppDelegate" format:@""]; + if (![[self delegate] conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { + [NSException raise:ARTPushException format:@"ARTPushRegistererDelegate must be implemented"]; } - id delegate = [UIApplication sharedApplication].delegate; - // Custom register SEL customRegisterMethodSelector = @selector(ablyPushCustomRegister:deviceDetails:callback:); - if ([delegate respondsToSelector:customRegisterMethodSelector]) { - [delegate ablyPushCustomRegister:error deviceDetails:local callback:^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { + if ([[self delegate] respondsToSelector:customRegisterMethodSelector]) { + [[self delegate] ablyPushCustomRegister:error deviceDetails:local callback:^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { if (error) { // Failed - [delegate didActivateAblyPush:error]; + [[self delegate] didActivateAblyPush:error]; [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:error]]; } else if (updateToken) { // Success [local setAndPersistUpdateToken:updateToken]; - [delegate didActivateAblyPush:nil]; + [[self delegate] didActivateAblyPush:nil]; [self sendEvent:[ARTPushActivationEventGotUpdateToken new]]; } else { ARTErrorInfo *missingUpdateTokenError = [ARTErrorInfo createWithCode:0 message:@"UpdateToken is expected"]; - [delegate didActivateAblyPush:missingUpdateTokenError]; + [[self delegate] didActivateAblyPush:missingUpdateTokenError]; [self sendEvent:[ARTPushActivationEventGettingUpdateTokenFailed newWithError:missingUpdateTokenError]]; } }]; @@ -153,30 +160,28 @@ - (void)deviceUpdateRegistration:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS ARTLocalDevice *local = _rest.device; - if (![[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { - [NSException raise:@"ARTPushRegistererDelegate must be implemented on AppDelegate" format:@""]; + if (![[self delegate] conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { + [NSException raise:ARTPushException format:@"ARTPushRegistererDelegate must be implemented"]; } - id delegate = [UIApplication sharedApplication].delegate; - // Custom register SEL customRegisterMethodSelector = @selector(ablyPushCustomRegister:deviceDetails:callback:); - if ([delegate respondsToSelector:customRegisterMethodSelector]) { - [delegate ablyPushCustomRegister:error deviceDetails:local callback:^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { + if ([[self delegate] respondsToSelector:customRegisterMethodSelector]) { + [[self delegate] ablyPushCustomRegister:error deviceDetails:local callback:^(ARTUpdateToken *updateToken, ARTErrorInfo *error) { if (error) { // Failed - [delegate didActivateAblyPush:error]; + [[self delegate] didActivateAblyPush:error]; [self sendEvent:[ARTPushActivationEventUpdatingRegistrationFailed newWithError:error]]; } else if (updateToken) { // Success [local setAndPersistUpdateToken:updateToken]; - [delegate didActivateAblyPush:nil]; + [[self delegate] didActivateAblyPush:nil]; [self sendEvent:[ARTPushActivationEventRegistrationUpdated new]]; } else { ARTErrorInfo *missingUpdateTokenError = [ARTErrorInfo createWithCode:0 message:@"UpdateToken is expected"]; - [delegate didActivateAblyPush:missingUpdateTokenError]; + [[self delegate] didActivateAblyPush:missingUpdateTokenError]; [self sendEvent:[ARTPushActivationEventUpdatingRegistrationFailed newWithError:missingUpdateTokenError]]; } }]; @@ -213,21 +218,17 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS ARTLocalDevice *local = _rest.device; - id delegate = [UIApplication sharedApplication].delegate; - // Custom register SEL customDeregisterMethodSelector = @selector(ablyPushCustomDeregister:deviceId:callback:); - if ([delegate respondsToSelector:customDeregisterMethodSelector]) { - [delegate ablyPushCustomDeregister:error deviceId:local.id callback:^(ARTErrorInfo *error) { + if ([[self delegate] respondsToSelector:customDeregisterMethodSelector]) { + [[self delegate] ablyPushCustomDeregister:error deviceId:local.id callback:^(ARTErrorInfo *error) { if (error) { - // Failed - [delegate didDeactivateAblyPush:error]; + [[self delegate] didDeactivateAblyPush:error]; [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:error]]; + return; } - else { - // Success - [delegate didDeactivateAblyPush:nil]; - } + [[self delegate] didDeactivateAblyPush:nil]; + [self sendEvent:[ARTPushActivationEventDeregistered new]]; }]; return; } @@ -246,8 +247,8 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { if (error) { [[_rest logger] error:@"%@: device deregistration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:[ARTErrorInfo createWithNSError:error]]]; + return; } - [[_rest logger] debug:__FILE__ line:__LINE__ message:@"successfully deactivate device"]; [self sendEvent:[ARTPushActivationEventDeregistered new]]; }]; @@ -256,11 +257,10 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { - (void)callActivatedCallback:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS - if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { - id delegate = [UIApplication sharedApplication].delegate; + if ([[self delegate] conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { SEL activateCallbackMethodSelector = @selector(didActivateAblyPush:); - if ([delegate respondsToSelector:activateCallbackMethodSelector]) { - [delegate didActivateAblyPush:error]; + if ([[self delegate] respondsToSelector:activateCallbackMethodSelector]) { + [[self delegate] didActivateAblyPush:error]; } } #endif @@ -268,11 +268,10 @@ - (void)callActivatedCallback:(ARTErrorInfo *)error { - (void)callDeactivatedCallback:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS - if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { - id delegate = [UIApplication sharedApplication].delegate; + if ([[self delegate] conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { SEL deactivateCallbackMethodSelector = @selector(didDeactivateAblyPush:); - if ([delegate respondsToSelector:deactivateCallbackMethodSelector]) { - [delegate didDeactivateAblyPush:error]; + if ([[self delegate] respondsToSelector:deactivateCallbackMethodSelector]) { + [[self delegate] didDeactivateAblyPush:error]; } } #endif @@ -280,11 +279,10 @@ - (void)callDeactivatedCallback:(ARTErrorInfo *)error { - (void)callUpdateFailedCallback:(nullable ARTErrorInfo *)error { #ifdef TARGET_OS_IOS - if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { - id delegate = [UIApplication sharedApplication].delegate; + if ([[self delegate] conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { SEL updateFailedCallbackMethodSelector = @selector(didAblyPushRegistrationFail:); - if ([delegate respondsToSelector:updateFailedCallbackMethodSelector]) { - [delegate didAblyPushRegistrationFail:error]; + if ([[self delegate] respondsToSelector:updateFailedCallbackMethodSelector]) { + [[self delegate] didAblyPushRegistrationFail:error]; } } #endif From 3c984eb814865dbaebab17c91a594950ef73021d Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 26 Apr 2017 09:56:44 +0100 Subject: [PATCH 43/46] PushActivationStateMachine --- Ably.xcodeproj/project.pbxproj | 8 +- Spec/Push.swift | 250 ------------ Spec/PushActivationStateMachine.swift | 565 ++++++++++++++++++++++++++ Spec/TestUtilities.swift | 28 +- 4 files changed, 594 insertions(+), 257 deletions(-) delete mode 100644 Spec/Push.swift create mode 100644 Spec/PushActivationStateMachine.swift diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index d27d4c2e7..1872fc9c3 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -173,7 +173,7 @@ D7DC8AF11C6AA0C8005AF165 /* ARTDefault+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D7DC8AF01C6A9FFC005AF165 /* ARTDefault+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; D7DEAFD11E65926D00D23F24 /* ARTLocalDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = D7DEAFCF1E65926D00D23F24 /* ARTLocalDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7DEAFD21E65926D00D23F24 /* ARTLocalDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = D7DEAFD01E65926D00D23F24 /* ARTLocalDevice.m */; }; - D7DF73851EA600240013CD36 /* Push.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF73831EA5FE620013CD36 /* Push.swift */; }; + D7DF73851EA600240013CD36 /* PushActivationStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF73831EA5FE620013CD36 /* PushActivationStateMachine.swift */; }; D7DF73861EA600290013CD36 /* RestClientPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF73811EA5FE3F0013CD36 /* RestClientPush.swift */; }; D7DF73871EA6002C0013CD36 /* RealtimeClientPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF737F1EA5FE2F0013CD36 /* RealtimeClientPush.swift */; }; D7DF738A1EA645300013CD36 /* ARTLocalDeviceStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = D7DF73881EA645300013CD36 /* ARTLocalDeviceStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -445,7 +445,7 @@ D7DEAFD01E65926D00D23F24 /* ARTLocalDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTLocalDevice.m; sourceTree = ""; }; D7DF737F1EA5FE2F0013CD36 /* RealtimeClientPush.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClientPush.swift; sourceTree = ""; }; D7DF73811EA5FE3F0013CD36 /* RestClientPush.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestClientPush.swift; sourceTree = ""; }; - D7DF73831EA5FE620013CD36 /* Push.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Push.swift; sourceTree = ""; }; + D7DF73831EA5FE620013CD36 /* PushActivationStateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushActivationStateMachine.swift; sourceTree = ""; }; D7DF73881EA645300013CD36 /* ARTLocalDeviceStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTLocalDeviceStorage.h; sourceTree = ""; }; D7DF73891EA645300013CD36 /* ARTLocalDeviceStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTLocalDeviceStorage.m; sourceTree = ""; }; D7EBE5A21BE8391E0086E675 /* RealtimeClientConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClientConnection.swift; sourceTree = ""; }; @@ -577,7 +577,7 @@ 856AAC951B6E30C800B07119 /* AblySpec-Bridging-Header.h */, EBAB9A6E1C69702800AF036B /* ReadmeExamples.swift */, D7C1B8761BBEA81A0087B55F /* Auth.swift */, - D7DF73831EA5FE620013CD36 /* Push.swift */, + D7DF73831EA5FE620013CD36 /* PushActivationStateMachine.swift */, 856AAC981B6E312F00B07119 /* RestClient.swift */, 853ED7C31B7A1A3C006F1C6F /* RestClientStats.swift */, D746AE2A1BBB625E003ECEF8 /* RestClientChannel.swift */, @@ -1270,7 +1270,7 @@ 856AAC991B6E312F00B07119 /* RestClient.swift in Sources */, D746AE2C1BBB625E003ECEF8 /* RestClientChannel.swift in Sources */, D71D30041C5F7B2F002115B0 /* RealtimeClientChannels.swift in Sources */, - D7DF73851EA600240013CD36 /* Push.swift in Sources */, + D7DF73851EA600240013CD36 /* PushActivationStateMachine.swift in Sources */, D714A63E1C74D4B2002F2CA0 /* NSObject+TestSuite.swift in Sources */, 856AAC971B6E30C800B07119 /* TestUtilities.swift in Sources */, D72768211C9C19040022F8B2 /* RestClientPresence.swift in Sources */, diff --git a/Spec/Push.swift b/Spec/Push.swift deleted file mode 100644 index 2784329e6..000000000 --- a/Spec/Push.swift +++ /dev/null @@ -1,250 +0,0 @@ -// -// Push.swift -// Ably -// -// Created by Ricardo Pereira on 18/04/2017. -// Copyright © 2017 Ably. All rights reserved. -// - -import Ably -import Nimble -import Quick - -class Push : QuickSpec { - override func spec() { - - var activationStateMachine: ARTPushActivationStateMachine! - var testHTTPExecutor: MockHTTPExecutor! - var testStorage: MockDeviceStorage! - - beforeEach { - testHTTPExecutor = MockHTTPExecutor() - testStorage = MockDeviceStorage() - activationStateMachine = ARTPushActivationStateMachine(testHTTPExecutor, storage: testStorage) - } - - describe("Activation state machine") { - - context("State NotActivated") { - - it("should be the initial state") { - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) - } - - it("should read the current state in disk") { - let storage = MockDeviceStorage() - - let state = ARTPushActivationStateWaitingForUpdateToken(machine: activationStateMachine) - let data = NSKeyedArchiver.archivedDataWithRootObject(state) - storage.simulateOnNextRead(data, for: ARTPushActivationCurrentStateKey) - - let activationStateMachine = ARTPushActivationStateMachine(MockHTTPExecutor(), storage: storage) - - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) - expect(storage.keysRead).to(haveCount(2)) - expect(storage.keysRead.filter({ $0.hasSuffix("CurrentState") })).to(haveCount(1)) - expect(storage.keysWrite).to(beEmpty()) - } - - it("on Event CalledDeactivate, should transition to NotActivated") { - var deactivatedCallbackCalled = false - let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { - deactivatedCallbackCalled = true - } - defer { hook.remove() } - - activationStateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) - - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) - expect(deactivatedCallbackCalled).to(beTrue()) - } - - context("on Event CalledActivate") { - it("if the local device has id and updateToken then should transition to WaitingForNewPushDeviceDetails") { - let testDeviceId = "aaaa" - testStorage.simulateOnNextRead(testDeviceId, for: ARTDeviceIdKey) - - let testDeviceUpdateToken = "xxxx-xxxx-xxxx" - testStorage.simulateOnNextRead(testDeviceUpdateToken, for: ARTDeviceUpdateTokenKey) - activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) - } - - it("if the local device has the necessary push details should send event GotPushDeviceDetails") { - let testDeviceToken = "xxxx-xxxx-xxxx-xxxx-xxxx" - testStorage.simulateOnNextRead(testDeviceToken, for: ARTDeviceTokenKey) - activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationEventGotPushDeviceDetails)) - } - - it("none of them then should transition to WaitingForPushDeviceDetails") { - activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) - } - } - - } - - context("State WaitingForPushDeviceDetails") { - - it("on Event CalledActivate") { - activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationEventCalledActivate)) - } - - it("on Event CalledDeactivate") { - var deactivatedCallbackCalled = false - let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { - deactivatedCallbackCalled = true - } - defer { hook.remove() } - - activationStateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) - expect(deactivatedCallbackCalled).to(beTrue()) - } - - context("on Event GotPushDeviceDetails") { - it("should make an asynchronous HTTP request to /push/deviceRegistrations") { - activationStateMachine.sendEvent(ARTPushActivationEventGotPushDeviceDetails()) - expect(testHTTPExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations" })).to(haveCount(1)) - waitUntil(timeout: testTimeout) { done in - activationStateMachine.on(ARTPushActivationEventGotUpdateToken) { - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) - done() - } - } - } - } - - } - - context("State WaitingForUpdateToken") { - - it("on Event CalledActivate") { - activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) - } - - it("on Event GotUpdateToken") { - var activatedCallbackCalled = false - let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { - activatedCallbackCalled = true - } - defer { hook.remove() } - - activationStateMachine.sendEvent(ARTPushActivationEventGotUpdateToken()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) - expect(activatedCallbackCalled).to(beTrue()) - } - - it("on Event GettingUpdateTokenFailed") { - var activatedCallbackCalled = false - let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { - activatedCallbackCalled = true - } - defer { hook.remove() } - - activationStateMachine.sendEvent(ARTPushActivationEventGettingUpdateTokenFailed()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) - expect(activatedCallbackCalled).to(beTrue()) - } - - } - - context("State WaitingForNewPushDeviceDetails") { - - it("on Event CalledActivate") { - var activatedCallbackCalled = false - let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { - activatedCallbackCalled = true - } - defer { hook.remove() } - - activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) - expect(activatedCallbackCalled).to(beTrue()) - } - - it("on Event CalledDeactivate") { - activationStateMachine.sendEvent(ARTPushActivationEventGotPushDeviceDetails()) - expect(testHTTPExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations " })).to(haveCount(1)) - waitUntil(timeout: testTimeout) { done in - activationStateMachine.on(ARTPushActivationEventDeregistered) { - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) - done() - } - } - } - - } - - context("State WaitingForRegistrationUpdate") { - - it("on Event CalledActivate") { - var activatedCallbackCalled = false - let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { - activatedCallbackCalled = true - } - defer { hook.remove() } - - activationStateMachine.sendEvent(ARTPushActivationEventCalledActivate()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) - expect(activatedCallbackCalled).to(beTrue()) - } - - } - - context("State AfterRegistrationUpdateFailed") { - - it("on Event CalledActivate") { - - } - - it("on Event GotPushDeviceDetails") { - - } - - it("on Event CalledDeactivate") { - - } - - } - - context("State WaitingForDeregistration") { - - it("on Event CalledDeactivate") { - activationStateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) - } - - it("on Event Deregistered") { - var deactivatedCallbackCalled = false - let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { - deactivatedCallbackCalled = true - } - defer { hook.remove() } - - activationStateMachine.sendEvent(ARTPushActivationEventDeregistered()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) - expect(deactivatedCallbackCalled).to(beTrue()) - expect(testStorage.keysWrite.filter({ $0 == ARTDeviceUpdateTokenKey })).to(haveCount(1)) - } - - it("on Event DeregistrationFailed") { - var deactivatedCallbackCalled = false - let hook = activationStateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { - deactivatedCallbackCalled = true - } - defer { hook.remove() } - - activationStateMachine.sendEvent(ARTPushActivationEventDeregistrationFailed()) - expect(activationStateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) - expect(deactivatedCallbackCalled).to(beTrue()) - } - - } - - } - } -} diff --git a/Spec/PushActivationStateMachine.swift b/Spec/PushActivationStateMachine.swift new file mode 100644 index 000000000..d48040bd9 --- /dev/null +++ b/Spec/PushActivationStateMachine.swift @@ -0,0 +1,565 @@ +// +// PushActivationStateMachine.swift +// Ably +// +// Created by Ricardo Pereira on 18/04/2017. +// Copyright © 2017 Ably. All rights reserved. +// + +import Ably +import Nimble +import Quick + +class PushActivationStateMachine : QuickSpec { + override func spec() { + + var rest: ARTRest! + var httpExecutor: MockHTTPExecutor! + var storage: MockDeviceStorage! + var initialStateMachine: ARTPushActivationStateMachine! + + beforeEach { + rest = ARTRest(key: "xxxx:xxxx") + httpExecutor = MockHTTPExecutor() + rest.httpExecutor = httpExecutor + storage = MockDeviceStorage() + initialStateMachine = ARTPushActivationStateMachine(rest, storage: storage) + } + + describe("Activation state machine") { + + it("should set NotActivated state is current state in disk is empty") { + expect(initialStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + } + + it("should read the current state in disk") { + let storage = MockDeviceStorage(startWith: ARTPushActivationStateWaitingForUpdateToken(machine: initialStateMachine)) + let stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + expect(storage.keysRead).to(haveCount(2)) + expect(storage.keysRead.filter({ $0.hasSuffix("CurrentState") })).to(haveCount(1)) + expect(storage.keysWrite).to(beEmpty()) + } + + context("State NotActivated") { + + var stateMachine: ARTPushActivationStateMachine! + var storage: MockDeviceStorage! + + beforeEach { + storage = MockDeviceStorage(startWith: ARTPushActivationStateNotActivated(machine: initialStateMachine)) + stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + } + + it("on Event CalledDeactivate, should transition to NotActivated") { + var deactivatedCallbackCalled = false + let hook = stateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { + deactivatedCallbackCalled = true + } + defer { hook.remove() } + + stateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(deactivatedCallbackCalled).to(beTrue()) + } + + context("on Event CalledActivate") { + it("if the local device has id and updateToken then should transition to WaitingForNewPushDeviceDetails") { + let testDeviceId = "aaaa" + //testStorage.simulateOnNextRead(testDeviceId, for: ARTDeviceIdKey) + + let testDeviceUpdateToken = "xxxx-xxxx-xxxx" + //testStorage.simulateOnNextRead(testDeviceUpdateToken, for: ARTDeviceUpdateTokenKey) + stateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + } + + it("if the local device has the necessary push details should send event GotPushDeviceDetails") { + let testDeviceToken = "xxxx-xxxx-xxxx-xxxx-xxxx" + //testStorage.simulateOnNextRead(testDeviceToken, for: ARTDeviceTokenKey) + stateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationEventGotPushDeviceDetails)) + } + + it("none of them then should transition to WaitingForPushDeviceDetails") { + stateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + } + } + + } + + // RSH3b + context("State WaitingForPushDeviceDetails") { + + var stateMachine: ARTPushActivationStateMachine! + var storage: MockDeviceStorage! + + beforeEach { + storage = MockDeviceStorage(startWith: ARTPushActivationStateWaitingForPushDeviceDetails(machine: initialStateMachine)) + stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + } + + it("on Event CalledActivate") { + stateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + } + + it("on Event CalledDeactivate") { + var deactivatedCallbackCalled = false + let hook = stateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { + deactivatedCallbackCalled = true + } + defer { hook.remove() } + + stateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(deactivatedCallbackCalled).to(beTrue()) + } + + // RSH3b3 + context("on Event GotPushDeviceDetails") { + + it("should raise exception if ARTPushRegistererDelegate is not implemented") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails)) + expect{ stateMachine.sendEvent(ARTPushActivationEventGotPushDeviceDetails()) }.to(raiseException { exception in + expect(exception.reason).to(contain("ARTPushRegistererDelegate must be implemented")) + }) + } + + it("should use custom registerCallback and fire GotUpdateToken event") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails)) + + let delegate = StateMachineDelegateCustomCallbacks() + stateMachine.delegate = delegate + + waitUntil(timeout: testTimeout) { done in + let partialDone = AblyTests.splitDone(2, done: done) + stateMachine.testSuite_getArgumentFrom(NSSelectorFromString("handleEvent:"), atIndex: 0) { event in + if event is ARTPushActivationEventGotUpdateToken { + partialDone() + } + } + delegate.onPushCustomRegister = { error, deviceDetails in + expect(error).to(beNil()) + expect(deviceDetails).to(beIdenticalTo(rest.device)) + partialDone() + return nil + } + stateMachine.sendEvent(ARTPushActivationEventGotPushDeviceDetails()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + } + + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(httpExecutor.requests.count) == 0 + } + + it("should use custom registerCallback and fire GettingUpdateTokenFailed event") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails)) + + let delegate = StateMachineDelegateCustomCallbacks() + stateMachine.delegate = delegate + + waitUntil(timeout: testTimeout) { done in + let simulatedError = NSError(domain: "", code: 1234, userInfo: nil) + let partialDone = AblyTests.splitDone(2, done: done) + stateMachine.testSuite_getArgumentFrom(NSSelectorFromString("handleEvent:"), atIndex: 0) { event in + if let event = event as? ARTPushActivationEventGettingUpdateTokenFailed { + expect(event.error.domain) == ARTAblyErrorDomain + expect(event.error.code) == simulatedError.code + partialDone() + } + } + delegate.onPushCustomRegister = { error, deviceDetails in + expect(error).to(beNil()) + expect(deviceDetails).to(beIdenticalTo(rest.device)) + partialDone() + return simulatedError + } + stateMachine.sendEvent(ARTPushActivationEventGotPushDeviceDetails()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + } + + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(httpExecutor.requests.count) == 0 + } + + it("should fire GotUpdateToken event") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails)) + + let delegate = StateMachineDelegate() + stateMachine.delegate = delegate + + waitUntil(timeout: testTimeout) { done in + stateMachine.testSuite_getArgumentFrom(NSSelectorFromString("handleEvent:"), atIndex: 0) { event in + if event is ARTPushActivationEventGotUpdateToken { + done() + } + } + stateMachine.sendEvent(ARTPushActivationEventGotPushDeviceDetails()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + } + + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(httpExecutor.requests.count) == 1 + let requests = httpExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations" }) + expect(requests).to(haveCount(1)) + guard let request = httpExecutor.requests.first else { + fail("should have a \"/push/deviceRegistrations\" request"); return + } + guard let url = request.URL else { + fail("should have a \"/push/deviceRegistrations\" URL"); return + } + guard let rawBody = request.HTTPBody else { + fail("should have a body"); return + } + guard let body = stateMachine.rest.defaultEncoder.decode(rawBody) as? NSDictionary else { + fail("body is invalid"); return + } + expect(body.valueForKey("id") as? String).to(equal(rest.device.id)) + expect(body.valueForKey("push")).toNot(beNil()) + expect(body.valueForKey("formFactor")).toNot(beNil()) + expect(body.valueForKey("platform")).toNot(beNil()) + } + + fit("should fire GettingUpdateTokenFailed event") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails)) + + let delegate = StateMachineDelegate() + stateMachine.delegate = delegate + + let simulatedError = NSError(domain: "", code: 1234, userInfo: nil) + httpExecutor.simulateIncomingErrorOnNextRequest(simulatedError) + + waitUntil(timeout: testTimeout) { done in + stateMachine.testSuite_getArgumentFrom(NSSelectorFromString("handleEvent:"), atIndex: 0) { event in + if let event = event as? ARTPushActivationEventGettingUpdateTokenFailed { + expect(event.error.domain) == ARTAblyErrorDomain + expect(event.error.code) == simulatedError.code + done() + } + } + stateMachine.sendEvent(ARTPushActivationEventGotPushDeviceDetails()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + } + + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(httpExecutor.requests.count) == 1 + let requests = httpExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations" }) + expect(requests).to(haveCount(1)) + guard let request = httpExecutor.requests.first else { + fail("should have a \"/push/deviceRegistrations\" request"); return + } + guard let url = request.URL else { + fail("should have a \"/push/deviceRegistrations\" URL"); return + } + guard let rawBody = request.HTTPBody else { + fail("should have a body"); return + } + guard let body = stateMachine.rest.defaultEncoder.decode(rawBody) as? NSDictionary else { + fail("body is invalid"); return + } + expect(body.valueForKey("id") as? String).to(equal(rest.device.id)) + expect(body.valueForKey("push")).toNot(beNil()) + expect(body.valueForKey("formFactor")).toNot(beNil()) + expect(body.valueForKey("platform")).toNot(beNil()) + } + + } + + } + + context("State WaitingForUpdateToken") { + + var stateMachine: ARTPushActivationStateMachine! + var storage: MockDeviceStorage! + + beforeEach { + storage = MockDeviceStorage(startWith: ARTPushActivationStateWaitingForUpdateToken(machine: initialStateMachine)) + stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + } + + it("on Event CalledActivate") { + stateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + } + + it("on Event GotUpdateToken") { + var activatedCallbackCalled = false + let hook = stateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { + activatedCallbackCalled = true + } + defer { hook.remove() } + + stateMachine.sendEvent(ARTPushActivationEventGotUpdateToken()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(activatedCallbackCalled).to(beTrue()) + } + + it("on Event GettingUpdateTokenFailed") { + var activatedCallbackCalled = false + let hook = stateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { + activatedCallbackCalled = true + } + defer { hook.remove() } + + stateMachine.sendEvent(ARTPushActivationEventGettingUpdateTokenFailed()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(activatedCallbackCalled).to(beTrue()) + } + + } + + // RSH3d + context("State WaitingForNewPushDeviceDetails") { + + var stateMachine: ARTPushActivationStateMachine! + var storage: MockDeviceStorage! + + beforeEach { + storage = MockDeviceStorage(startWith: ARTPushActivationStateWaitingForNewPushDeviceDetails(machine: initialStateMachine)) + stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + } + + it("on Event CalledActivate") { + var activatedCallbackCalled = false + let hook = stateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callActivatedCallback:")) { + activatedCallbackCalled = true + } + defer { hook.remove() } + + stateMachine.sendEvent(ARTPushActivationEventCalledActivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(activatedCallbackCalled).to(beTrue()) + } + + // RSH3d2 + context("on Event CalledDeactivate") { + + it("should use custom deregisterCallback and fire Deregistered event") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + + let delegate = StateMachineDelegateCustomCallbacks() + stateMachine.delegate = delegate + + waitUntil(timeout: testTimeout) { done in + let partialDone = AblyTests.splitDone(2, done: done) + stateMachine.testSuite_getArgumentFrom(NSSelectorFromString("handleEvent:"), atIndex: 0) { event in + if event is ARTPushActivationEventDeregistered { + partialDone() + } + } + delegate.onPushCustomDeregister = { error, deviceId in + expect(error).to(beNil()) + expect(deviceId) == rest.device.id + partialDone() + return nil + } + stateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + } + + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(httpExecutor.requests.count) == 0 + } + + it("should use custom deregisterCallback and fire DeregistrationFailed event") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + + let delegate = StateMachineDelegateCustomCallbacks() + stateMachine.delegate = delegate + + waitUntil(timeout: testTimeout) { done in + let simulatedError = NSError(domain: "", code: 1234, userInfo: nil) + let partialDone = AblyTests.splitDone(2, done: done) + stateMachine.testSuite_getArgumentFrom(NSSelectorFromString("handleEvent:"), atIndex: 0) { event in + if let event = event as? ARTPushActivationEventDeregistrationFailed { + expect(event.error.domain) == ARTAblyErrorDomain + expect(event.error.code) == simulatedError.code + partialDone() + } + } + delegate.onPushCustomDeregister = { error, deviceId in + expect(error).to(beNil()) + expect(deviceId) == rest.device.id + partialDone() + return simulatedError + } + stateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + } + + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(httpExecutor.requests.count) == 0 + } + + it("should fire Deregistered event") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + + waitUntil(timeout: testTimeout) { done in + stateMachine.testSuite_getArgumentFrom(NSSelectorFromString("handleEvent:"), atIndex: 0) { event in + if event is ARTPushActivationEventDeregistered { + done() + } + } + stateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + } + + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(httpExecutor.requests.count) == 1 + let requests = httpExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations" }) + expect(requests).to(haveCount(1)) + guard let request = httpExecutor.requests.first else { + fail("should have a \"/push/deviceRegistrations\" request"); return + } + guard let url = request.URL else { + fail("should have a \"/push/deviceRegistrations\" URL"); return + } + expect(request.HTTPMethod) == "DELETE" + expect(url.query).to(contain(rest.device.id)) + } + + it("should fire DeregistrationFailed event") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + + let simulatedError = NSError(domain: "", code: 1234, userInfo: nil) + httpExecutor.simulateIncomingErrorOnNextRequest(simulatedError) + + waitUntil(timeout: testTimeout) { done in + stateMachine.testSuite_getArgumentFrom(NSSelectorFromString("handleEvent:"), atIndex: 0) { event in + if let event = event as? ARTPushActivationEventDeregistrationFailed { + expect(event.error.domain) == ARTAblyErrorDomain + expect(event.error.code) == simulatedError.code + done() + } + } + stateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + } + + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(httpExecutor.requests.count) == 1 + let requests = httpExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations" }) + expect(requests).to(haveCount(1)) + guard let request = httpExecutor.requests.first else { + fail("should have a \"/push/deviceRegistrations\" request"); return + } + guard let url = request.URL else { + fail("should have a \"/push/deviceRegistrations\" URL"); return + } + expect(request.HTTPMethod) == "DELETE" + expect(url.query).to(contain(rest.device.id)) + } + + } + + } + + context("State WaitingForRegistrationUpdate") { + + } + + context("State AfterRegistrationUpdateFailed") { + + it("on Event CalledActivate") { + + } + + it("on Event GotPushDeviceDetails") { + + } + + it("on Event CalledDeactivate") { + + } + + } + + context("State WaitingForDeregistration") { + + var stateMachine: ARTPushActivationStateMachine! + var storage: MockDeviceStorage! + + beforeEach { + storage = MockDeviceStorage(startWith: ARTPushActivationStateWaitingForDeregistration(machine: initialStateMachine)) + stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + } + + it("on Event CalledDeactivate") { + stateMachine.sendEvent(ARTPushActivationEventCalledDeactivate()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + } + + it("on Event Deregistered") { + var deactivatedCallbackCalled = false + let hook = stateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { + deactivatedCallbackCalled = true + } + defer { hook.remove() } + + stateMachine.sendEvent(ARTPushActivationEventDeregistered()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(deactivatedCallbackCalled).to(beTrue()) + expect(storage.keysWrite.filter({ $0 == ARTDeviceUpdateTokenKey })).to(haveCount(1)) + } + + it("on Event DeregistrationFailed") { + var deactivatedCallbackCalled = false + let hook = stateMachine.testSuite_injectIntoMethodAfter(NSSelectorFromString("callDeactivatedCallback:")) { + deactivatedCallbackCalled = true + } + defer { hook.remove() } + + stateMachine.sendEvent(ARTPushActivationEventDeregistrationFailed()) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(deactivatedCallbackCalled).to(beTrue()) + } + + } + + } + } +} + +class StateMachineDelegate: NSObject, ARTPushRegistererDelegate { + + var onDidActivateAblyPush: ((ARTErrorInfo?) -> Void)? + var onDidDeactivateAblyPush: ((ARTErrorInfo?) -> Void)? + var onDidAblyPushRegistrationFail: ((ARTErrorInfo?) -> Void)? + + func didActivateAblyPush(error: ARTErrorInfo?) { + onDidActivateAblyPush?(error) + } + + func didDeactivateAblyPush(error: ARTErrorInfo?) { + onDidDeactivateAblyPush?(error) + } + + func didAblyPushRegistrationFail(error: ARTErrorInfo?) { + onDidAblyPushRegistrationFail?(error) + } + +} + +class StateMachineDelegateCustomCallbacks: StateMachineDelegate { + + var onPushCustomRegister: ((ARTErrorInfo?, deviceDetails: ARTDeviceDetails?) -> NSError?)? + var onPushCustomDeregister: ((ARTErrorInfo?, deviceId: String?) -> NSError?)? + + func ablyPushCustomRegister(error: ARTErrorInfo?, deviceDetails: ARTDeviceDetails?, callback: (String, ARTErrorInfo?) -> Void) { + let error = onPushCustomRegister?(error, deviceDetails: deviceDetails) + delay(0) { + callback("", error == nil ? nil : ARTErrorInfo.createWithNSError(error!)) + } + } + + func ablyPushCustomDeregister(error: ARTErrorInfo?, deviceId: String?, callback: ((ARTErrorInfo?) -> Void)?) { + let error = onPushCustomDeregister?(error, deviceId: deviceId) + delay(0) { + callback?(error == nil ? nil : ARTErrorInfo.createWithNSError(error!)) + } + } + +} diff --git a/Spec/TestUtilities.swift b/Spec/TestUtilities.swift index fd376281c..5ecbe7efa 100644 --- a/Spec/TestUtilities.swift +++ b/Spec/TestUtilities.swift @@ -581,6 +581,13 @@ class MockDeviceStorage: NSObject, ARTDeviceStorage { private var simulateData: [String: NSData] = [:] + init(startWith state: ARTPushActivationState? = nil) { + super.init() + if let state = state { + simulateOnNextRead(state.archive(), for: ARTPushActivationCurrentStateKey) + } + } + func readKey(key: String) -> NSData? { keysRead.append(key) if var data = simulateData[key] { @@ -606,6 +613,8 @@ class MockHTTPExecutor: NSObject, ARTHTTPAuthenticatedExecutor { var clientOptions = ARTClientOptions() var encoder = ARTJsonLikeEncoder() var requests: [NSMutableURLRequest] = [] + + private var simulateError: NSError? func options() -> ARTClientOptions { return self.clientOptions @@ -616,13 +625,26 @@ class MockHTTPExecutor: NSObject, ARTHTTPAuthenticatedExecutor { } func executeRequest(request: NSMutableURLRequest, withAuthOption authOption: ARTAuthentication, completion callback: (NSHTTPURLResponse?, NSData?, NSError?) -> Void) { - self.requests.append(request) - callback(nil, nil, nil) + executeRequest(request, completion: callback) } func executeRequest(request: NSMutableURLRequest, completion callback: ((NSHTTPURLResponse?, NSData?, NSError?) -> Void)?) { self.requests.append(request) - callback?(nil, nil, nil) + delay(0) { + defer { + self.simulateError = nil + } + if let simulateError = self.simulateError { + callback?(nil, nil, simulateError) + } + else { + callback?(nil, nil, nil) + } + } + } + + func simulateIncomingErrorOnNextRequest(error: NSError) { + simulateError = error } } From c6c0d46106bd1db80d532d193836518381dec8a9 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 31 Jan 2018 19:03:02 +0000 Subject: [PATCH 44/46] Implement Device storage --- Ably.xcodeproj/project.pbxproj | 10 +- Source/ARTLocalDevice.m | 21 ++-- Source/ARTLocalDeviceStorage.h | 4 +- Source/ARTLocalDeviceStorage.m | 4 +- Source/ARTPush.m | 3 +- Source/ARTPushActivationState.m | 10 +- .../ARTPushActivationStateMachine+Private.h | 27 +++++ Source/ARTPushActivationStateMachine.h | 3 + Source/ARTPushActivationStateMachine.m | 114 ++++++++++++++---- Source/ARTRest+Private.h | 2 + Source/ARTRest.m | 2 + Source/Ably.h | 1 + Source/Ably.modulemap | 1 + 13 files changed, 153 insertions(+), 49 deletions(-) create mode 100644 Source/ARTPushActivationStateMachine+Private.h diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 6aecfffeb..4f6c69738 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ D71D30041C5F7B2F002115B0 /* RealtimeClientChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71D30031C5F7B2F002115B0 /* RealtimeClientChannels.swift */; }; D72304701BB72CED00F1ABDA /* RealtimeClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723046F1BB72CED00F1ABDA /* RealtimeClient.swift */; }; D72768211C9C19040022F8B2 /* RestClientPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72768201C9C19040022F8B2 /* RestClientPresence.swift */; }; + D72C67DF201AB74000978EBB /* ARTPushActivationStateMachine+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D72C67DE201AB74000978EBB /* ARTPushActivationStateMachine+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; D73691FF1DB788C40062C150 /* ARTAuthDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = D73691FD1DB788C40062C150 /* ARTAuthDetails.h */; settings = {ATTRIBUTES = (Public, ); }; }; D73692001DB788C40062C150 /* ARTAuthDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = D73691FE1DB788C40062C150 /* ARTAuthDetails.m */; }; D736A0C91F916553005ABE81 /* KSCrash.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D736A0C61F916553005ABE81 /* KSCrash.framework */; }; @@ -343,6 +344,7 @@ D71D30031C5F7B2F002115B0 /* RealtimeClientChannels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClientChannels.swift; sourceTree = ""; }; D723046F1BB72CED00F1ABDA /* RealtimeClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeClient.swift; sourceTree = ""; }; D72768201C9C19040022F8B2 /* RestClientPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestClientPresence.swift; sourceTree = ""; }; + D72C67DE201AB74000978EBB /* ARTPushActivationStateMachine+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARTPushActivationStateMachine+Private.h"; sourceTree = ""; }; D73691FD1DB788C40062C150 /* ARTAuthDetails.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTAuthDetails.h; sourceTree = ""; }; D73691FE1DB788C40062C150 /* ARTAuthDetails.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTAuthDetails.m; sourceTree = ""; }; D736A0C61F916553005ABE81 /* KSCrash.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KSCrash.framework; path = Carthage/Build/iOS/KSCrash.framework; sourceTree = ""; }; @@ -667,13 +669,12 @@ isa = PBXGroup; children = ( D71966E21E5DF360000974DD /* ARTPushActivationStateMachine.h */, + D72C67DE201AB74000978EBB /* ARTPushActivationStateMachine+Private.h */, D71966E31E5DF360000974DD /* ARTPushActivationStateMachine.m */, D71966E81E5E006E000974DD /* ARTPushActivationState.h */, D71966E91E5E006E000974DD /* ARTPushActivationState.m */, D71966EC1E5E0081000974DD /* ARTPushActivationEvent.h */, D71966ED1E5E0081000974DD /* ARTPushActivationEvent.m */, - D7DF73881EA645300013CD36 /* ARTLocalDeviceStorage.h */, - D7DF73891EA645300013CD36 /* ARTLocalDeviceStorage.m */, ); name = "Activation State Machine"; sourceTree = ""; @@ -866,9 +867,11 @@ D7B621931E4A6FE600684474 /* ARTDeviceDetails.m */, D768C6AA1E4B5B0200436011 /* ARTDevicePushDetails.h */, D768C6AB1E4B5B0200436011 /* ARTDevicePushDetails.m */, - EBFFAC181E97919C003E7326 /* ARTLocalDevice+Private.h */, D7DEAFCF1E65926D00D23F24 /* ARTLocalDevice.h */, + EBFFAC181E97919C003E7326 /* ARTLocalDevice+Private.h */, D7DEAFD01E65926D00D23F24 /* ARTLocalDevice.m */, + D7DF73881EA645300013CD36 /* ARTLocalDeviceStorage.h */, + D7DF73891EA645300013CD36 /* ARTLocalDeviceStorage.m */, D71966E71E5DFFC6000974DD /* Activation State Machine */, D71966E61E5DFFB2000974DD /* Admin */, ); @@ -901,6 +904,7 @@ EB91213E1CA0AD6600BA0A40 /* ARTMsgPackEncoder.h in Headers */, 96A507BD1A3791490077CDF8 /* ARTRealtime.h in Headers */, EB5E058D1C77027600A48B39 /* ARTCrypto+Private.h in Headers */, + D72C67DF201AB74000978EBB /* ARTPushActivationStateMachine+Private.h in Headers */, D7DC8AF11C6AA0C8005AF165 /* ARTDefault+Private.h in Headers */, 961343D81A42E0B7006DC822 /* ARTClientOptions.h in Headers */, D7534C321D79E5C20054C182 /* Ably.h in Headers */, diff --git a/Source/ARTLocalDevice.m b/Source/ARTLocalDevice.m index cf92fad82..f4cd891ef 100644 --- a/Source/ARTLocalDevice.m +++ b/Source/ARTLocalDevice.m @@ -12,6 +12,7 @@ #import "ARTRest+Private.h" #import "ARTAuth+Private.h" #import "ARTEncoder.h" +#import "ARTLocalDeviceStorage.h" #import NSString *const ARTDevicePlatform = @"ios"; @@ -59,26 +60,24 @@ + (ARTLocalDevice *)load:(ARTRest *_Nonnull)rest { } device.push.recipient[@"transportType"] = ARTDevicePushTransportType; - NSString *deviceId = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceIdKey]; + NSString *deviceId = [rest.storage objectForKey:ARTDeviceIdKey]; if (!deviceId) { deviceId = [[ULID new] ulidString]; - [[NSUserDefaults standardUserDefaults] setObject:deviceId forKey:ARTDeviceIdKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [rest.storage setObject:deviceId forKey:ARTDeviceIdKey]; } device.id = deviceId; - device.updateToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceUpdateTokenKey]; + device.updateToken = [rest.storage objectForKey:ARTDeviceUpdateTokenKey]; - [device setDeviceToken:[[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceTokenKey]]; + [device setDeviceToken:[rest.storage objectForKey:ARTDeviceTokenKey]]; return device; } - (void)resetId { - [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceIdKey]; + [self.rest.storage setObject:nil forKey:ARTDeviceIdKey]; [self setAndPersistUpdateToken:nil]; NSString *deviceId = [[ULID new] ulidString]; - [[NSUserDefaults standardUserDefaults] setObject:deviceId forKey:ARTDeviceIdKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [self.rest.storage setObject:deviceId forKey:ARTDeviceIdKey]; self.id = deviceId; } @@ -118,14 +117,12 @@ - (void)setDeviceToken:(NSString *_Nonnull)token { } - (void)setAndPersistDeviceToken:(NSString *)token { - [[NSUserDefaults standardUserDefaults] setObject:token forKey:ARTDeviceTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [self.rest.storage setObject:token forKey:ARTDeviceTokenKey]; [self setDeviceToken:token]; } - (void)setAndPersistUpdateToken:(NSString *)token { - [[NSUserDefaults standardUserDefaults] setObject:token forKey:ARTDeviceUpdateTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [self.rest.storage setObject:token forKey:ARTDeviceUpdateTokenKey]; self.updateToken = token; } diff --git a/Source/ARTLocalDeviceStorage.h b/Source/ARTLocalDeviceStorage.h index 7b13f02d8..f88adc3f2 100644 --- a/Source/ARTLocalDeviceStorage.h +++ b/Source/ARTLocalDeviceStorage.h @@ -11,8 +11,8 @@ NS_ASSUME_NONNULL_BEGIN @protocol ARTDeviceStorage -- (nullable NSData *)readKey:(NSString *)key; -- (void)writeKey:(NSString *)key withValue:(nullable NSData *)value; +- (nullable id)objectForKey:(NSString *)key; +- (void)setObject:(nullable id)value forKey:(NSString *)key; @end @interface ARTLocalDeviceStorage : NSObject diff --git a/Source/ARTLocalDeviceStorage.m b/Source/ARTLocalDeviceStorage.m index 71bcc00a9..2bc90cdd1 100644 --- a/Source/ARTLocalDeviceStorage.m +++ b/Source/ARTLocalDeviceStorage.m @@ -10,11 +10,11 @@ @implementation ARTLocalDeviceStorage -- (NSData *)readKey:(NSString *)key { +- (nullable id)objectForKey:(NSString *)key { return [[NSUserDefaults standardUserDefaults] objectForKey:key]; } -- (void)writeKey:(NSString *)key withValue:(id)value { +- (void)setObject:(nullable id)value forKey:(NSString *)key { [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; [[NSUserDefaults standardUserDefaults] synchronize]; } diff --git a/Source/ARTPush.m b/Source/ARTPush.m index e7d6a0994..35e88ccc3 100644 --- a/Source/ARTPush.m +++ b/Source/ARTPush.m @@ -21,6 +21,7 @@ #import "ARTClientOptions+Private.h" #import "ARTPushAdmin+Private.h" #import "ARTLocalDevice+Private.h" +#import "ARTLocalDeviceStorage.h" #import "ARTRealtime+Private.h" NSString *const ARTDeviceIdKey = @"ARTDeviceId"; @@ -59,7 +60,7 @@ + (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceTokenDa NSString *deviceToken = [[[deviceTokenData description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""]; NSLog(@"ARTPush: device token received %@", deviceToken); - NSString *currentDeviceToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceTokenKey]; + NSString *currentDeviceToken = [rest.storage objectForKey:ARTDeviceTokenKey]; if ([currentDeviceToken isEqualToString:deviceToken]) { // Already stored. return; diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m index 4be71bf8f..d12f011a1 100644 --- a/Source/ARTPushActivationState.m +++ b/Source/ARTPushActivationState.m @@ -10,6 +10,7 @@ #import "ARTPushActivationStateMachine.h" #import "ARTPushActivationEvent.h" #import "ARTLocalDevice+Private.h" +#import "ARTLocalDeviceStorage.h" #import "ARTDevicePushDetails.h" #import "ARTLog.h" #import "ARTRest+Private.h" @@ -83,12 +84,12 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { else if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { ARTLocalDevice *local = [ARTLocalDevice load:self.machine.rest]; - if (local.updateToken != nil) { + if (local.updateToken) { // Already registered. return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithMachine:self.machine]; } - if ([local deviceToken] != nil) { + if ([local deviceToken]) { [self.machine sendEvent:[ARTPushActivationEventGotPushDeviceDetails new]]; } @@ -107,7 +108,7 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { return self; } else if ([event isKindOfClass:[ARTPushActivationEventGotUpdateToken class]]) { - [ARTLocalDevice load:self.machine.rest].updateToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARTDeviceUpdateTokenKey]; + [ARTLocalDevice load:self.machine.rest].updateToken = [self.machine.rest.storage objectForKey:ARTDeviceUpdateTokenKey]; [self.machine callActivatedCallback:nil]; return [ARTPushActivationStateWaitingForNewPushDeviceDetails newWithMachine:self.machine]; } @@ -209,8 +210,7 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { else if ([event isKindOfClass:[ARTPushActivationEventDeregistered class]]) { ARTLocalDevice *local = [ARTLocalDevice load:self.machine.rest]; local.updateToken = nil; - [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTDeviceUpdateTokenKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [self.machine.rest.storage setObject:nil forKey:ARTDeviceUpdateTokenKey]; [self.machine callDeactivatedCallback:nil]; return [ARTPushActivationStateNotActivated newWithMachine:self.machine]; } diff --git a/Source/ARTPushActivationStateMachine+Private.h b/Source/ARTPushActivationStateMachine+Private.h new file mode 100644 index 000000000..eafc820d4 --- /dev/null +++ b/Source/ARTPushActivationStateMachine+Private.h @@ -0,0 +1,27 @@ +// +// ARTPushActivationStateMachine+Private.h +// ably-ios +// +// Created by Ricardo Pereira on 26/01/2018. +// Copyright (c) 2014 Ably. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NSString *const ARTPushActivationCurrentStateKey; +NSString *const ARTPushActivationPendingEventsKey; + +@interface ARTPushActivationStateMachine () + +@property (weak, nonatomic) id delegate; +@property (nonatomic, copy, nullable) void (^transitions)(ARTPushActivationEvent *event, ARTPushActivationState *from, ARTPushActivationState *to); +@property (readonly, nonatomic) ARTPushActivationEvent *lastEvent; +@property (readonly, nonatomic) ARTPushActivationEvent *lastEvent_nosync; +@property (readonly, nonatomic) ARTPushActivationState *current; +@property (readonly, nonatomic) ARTPushActivationState *current_nosync; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTPushActivationStateMachine.h b/Source/ARTPushActivationStateMachine.h index e9b50a7b6..5fa428f3a 100644 --- a/Source/ARTPushActivationStateMachine.h +++ b/Source/ARTPushActivationStateMachine.h @@ -9,9 +9,12 @@ #import @class ARTErrorInfo; +@class ARTPushActivationState; @class ARTPushActivationEvent; @class ARTRest; +@protocol ARTDeviceStorage; + NS_ASSUME_NONNULL_BEGIN @interface ARTPushActivationStateMachine : NSObject diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m index ff8c9d966..301f8dbfb 100644 --- a/Source/ARTPushActivationStateMachine.m +++ b/Source/ARTPushActivationStateMachine.m @@ -6,7 +6,7 @@ // Copyright © 2017 Ably. All rights reserved. // -#import "ARTPushActivationStateMachine.h" +#import "ARTPushActivationStateMachine+Private.h" #import "ARTPush.h" #import "ARTPushActivationEvent.h" #import "ARTPushActivationState.h" @@ -16,6 +16,7 @@ #import "ARTJsonLikeEncoder.h" #import "ARTTypes.h" #import "ARTLocalDevice+Private.h" +#import "ARTLocalDeviceStorage.h" #import "ARTDevicePushDetails.h" #ifdef TARGET_OS_IOS @@ -25,6 +26,7 @@ NSString *const ARTPushActivationPendingEventsKey = @"ARTPushActivationPendingEvents"; @implementation ARTPushActivationStateMachine { + ARTPushActivationEvent *_lastHandledEvent; ARTPushActivationState *_current; NSMutableArray *_pendingEvents; dispatch_queue_t _queue; @@ -37,14 +39,14 @@ - (instancetype)init:(ARTRest *)rest { _queue = _rest.queue; _userQueue = _rest.userQueue; // Unarchiving - NSData *stateData = [[NSUserDefaults standardUserDefaults] objectForKey:ARTPushActivationCurrentStateKey]; + NSData *stateData = [rest.storage objectForKey:ARTPushActivationCurrentStateKey]; _current = [NSKeyedUnarchiver unarchiveObjectWithData:stateData]; if (!_current) { _current = [[ARTPushActivationStateNotActivated alloc] initWithMachine:self]; } else { _current.machine = self; } - NSData *pendingEventsData = [[NSUserDefaults standardUserDefaults] objectForKey:ARTPushActivationPendingEventsKey]; + NSData *pendingEventsData = [rest.storage objectForKey:ARTPushActivationPendingEventsKey]; _pendingEvents = [NSKeyedUnarchiver unarchiveObjectWithData:pendingEventsData]; if (!_pendingEvents) { _pendingEvents = [NSMutableArray array]; @@ -53,6 +55,30 @@ - (instancetype)init:(ARTRest *)rest { return self; } +- (ARTPushActivationEvent *)lastEvent { + __block ARTPushActivationEvent *ret; + dispatch_sync(_queue, ^{ + ret = [self lastEvent_nosync]; + }); + return ret; +} + +- (ARTPushActivationEvent *)lastEvent_nosync { + return _lastHandledEvent; +} + +- (ARTPushActivationState *)current { + __block ARTPushActivationState *ret; + dispatch_sync(_queue, ^{ + ret = [self current_nosync]; + }); + return ret; +} + +- (ARTPushActivationState *)current_nosync { + return _current; +} + - (void)sendEvent:(ARTPushActivationEvent *)event { dispatch_async(_queue, ^{ [self handleEvent:event]; @@ -61,6 +87,7 @@ - (void)sendEvent:(ARTPushActivationEvent *)event { - (void)handleEvent:(nonnull ARTPushActivationEvent *)event { NSLog(@"handling event %@ from %@", NSStringFromClass(event.class), NSStringFromClass(_current.class)); + _lastHandledEvent = event; ARTPushActivationState *maybeNext = [_current transition:event]; @@ -70,6 +97,7 @@ - (void)handleEvent:(nonnull ARTPushActivationEvent *)event { return; } NSLog(@"transition: %@ -> %@", NSStringFromClass(_current.class), NSStringFromClass(maybeNext.class)); + if (self.transitions) self.transitions(event, _current, maybeNext); _current = maybeNext; while (true) { @@ -85,6 +113,7 @@ - (void)handleEvent:(nonnull ARTPushActivationEvent *)event { [_pendingEvents dequeue]; NSLog(@"transition: %@ -> %@", NSStringFromClass(_current.class), NSStringFromClass(maybeNext.class)); + if (self.transitions) self.transitions(event, _current, maybeNext); _current = maybeNext; } @@ -94,9 +123,9 @@ - (void)handleEvent:(nonnull ARTPushActivationEvent *)event { - (void)persist { // Archiving if ([_current isKindOfClass:[ARTPushActivationPersistentState class]]) { - [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_current] forKey:ARTPushActivationCurrentStateKey]; + [self.rest.storage setObject:[NSKeyedArchiver archivedDataWithRootObject:_current] forKey:ARTPushActivationCurrentStateKey]; } - [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_pendingEvents] forKey:ARTPushActivationPendingEventsKey]; + [self.rest.storage setObject:[NSKeyedArchiver archivedDataWithRootObject:_pendingEvents] forKey:ARTPushActivationPendingEventsKey]; } - (void)deviceRegistration:(ARTErrorInfo *)error { @@ -104,10 +133,15 @@ - (void)deviceRegistration:(ARTErrorInfo *)error { ARTLocalDevice *local = _rest.device_nosync; __block id delegate; - dispatch_sync(_userQueue, ^{ - // -[UIApplication delegate] is an UI API call - delegate = [UIApplication sharedApplication].delegate; - }); + if (self.delegate) { + delegate = self.delegate; + } + else { + dispatch_sync(_userQueue, ^{ + // -[UIApplication delegate] is an UI API call + delegate = UIApplication.sharedApplication.delegate; + }); + } if (![delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { [NSException raise:@"ARTPushRegistererDelegate must be implemented on AppDelegate" format:@""]; @@ -163,10 +197,15 @@ - (void)deviceUpdateRegistration:(ARTErrorInfo *)error { ARTLocalDevice *local = _rest.device_nosync; __block id delegate; - dispatch_sync(_userQueue, ^{ - // -[UIApplication delegate] is an UI API call - delegate = [UIApplication sharedApplication].delegate; - }); + if (self.delegate) { + delegate = self.delegate; + } + else { + dispatch_sync(_userQueue, ^{ + // -[UIApplication delegate] is an UI API call + delegate = UIApplication.sharedApplication.delegate; + }); + } if (![delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { [NSException raise:@"ARTPushRegistererDelegate must be implemented on AppDelegate" format:@""]; @@ -229,10 +268,15 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { ARTLocalDevice *local = _rest.device_nosync; __block id delegate; - dispatch_sync(_userQueue, ^{ - // -[UIApplication delegate] is an UI API call - delegate = [UIApplication sharedApplication].delegate; - }); + if (self.delegate) { + delegate = self.delegate; + } + else { + dispatch_sync(_userQueue, ^{ + // -[UIApplication delegate] is an UI API call + delegate = UIApplication.sharedApplication.delegate; + }); + } // Custom register SEL customDeregisterMethodSelector = @selector(ablyPushCustomDeregister:deviceId:callback:); @@ -247,6 +291,7 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { else { // Success [delegate didDeactivateAblyPush:nil]; + [self sendEvent:[ARTPushActivationEventDeregistered new]]; } }]; }); @@ -267,8 +312,8 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { if (error) { [[_rest logger] error:@"%@: device deregistration failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:[ARTErrorInfo createFromNSError:error]]]; + return; } - [[_rest logger] debug:__FILE__ line:__LINE__ message:@"successfully deactivate device"]; [self sendEvent:[ARTPushActivationEventDeregistered new]]; }]; @@ -278,8 +323,15 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { - (void)callActivatedCallback:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS dispatch_async(_userQueue, ^{ - if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { - id delegate = [UIApplication sharedApplication].delegate; + id delegate; + if (self.delegate) { + delegate = self.delegate; + } + else { + delegate = UIApplication.sharedApplication.delegate; + } + + if ([delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { SEL activateCallbackMethodSelector = @selector(didActivateAblyPush:); if ([delegate respondsToSelector:activateCallbackMethodSelector]) { [delegate didActivateAblyPush:error]; @@ -292,8 +344,15 @@ - (void)callActivatedCallback:(ARTErrorInfo *)error { - (void)callDeactivatedCallback:(ARTErrorInfo *)error { #ifdef TARGET_OS_IOS dispatch_async(_userQueue, ^{ - if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { - id delegate = [UIApplication sharedApplication].delegate; + id delegate; + if (self.delegate) { + delegate = self.delegate; + } + else { + delegate = UIApplication.sharedApplication.delegate; + } + + if ([delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { SEL deactivateCallbackMethodSelector = @selector(didDeactivateAblyPush:); if ([delegate respondsToSelector:deactivateCallbackMethodSelector]) { [delegate didDeactivateAblyPush:error]; @@ -306,8 +365,15 @@ - (void)callDeactivatedCallback:(ARTErrorInfo *)error { - (void)callUpdateFailedCallback:(nullable ARTErrorInfo *)error { #ifdef TARGET_OS_IOS dispatch_async(_userQueue, ^{ - if ([[UIApplication sharedApplication].delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { - id delegate = [UIApplication sharedApplication].delegate; + id delegate; + if (self.delegate) { + delegate = self.delegate; + } + else { + delegate = UIApplication.sharedApplication.delegate; + } + + if ([delegate conformsToProtocol:@protocol(ARTPushRegistererDelegate)]) { SEL updateFailedCallbackMethodSelector = @selector(didAblyPushRegistrationFail:); if ([delegate respondsToSelector:updateFailedCallbackMethodSelector]) { [delegate didAblyPushRegistrationFail:error]; diff --git a/Source/ARTRest+Private.h b/Source/ARTRest+Private.h index 89d8a669d..3cda83d2f 100644 --- a/Source/ARTRest+Private.h +++ b/Source/ARTRest+Private.h @@ -13,6 +13,7 @@ @protocol ARTEncoder; @protocol ARTHTTPExecutor; +@protocol ARTDeviceStorage; NS_ASSUME_NONNULL_BEGIN @@ -29,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN @property (readwrite, strong, atomic, nullable) NSString *prioritizedHost; @property (nonatomic, weak) id httpExecutor; +@property (nonatomic) id storage; @property (nonatomic, readonly, getter=getBaseUrl) NSURL *baseUrl; diff --git a/Source/ARTRest.m b/Source/ARTRest.m index 5eef26063..78a725d65 100644 --- a/Source/ARTRest.m +++ b/Source/ARTRest.m @@ -39,6 +39,7 @@ #import "ARTPush.h" #import "ARTPush+Private.h" #import "ARTLocalDevice+Private.h" +#import "ARTLocalDeviceStorage.h" #if COCOAPODS #import @@ -88,6 +89,7 @@ - (instancetype)initWithOptions:(ARTClientOptions *)options realtime:(ARTRealtim ART_TRY_OR_REPORT_CRASH_START(self) { _queue = options.internalDispatchQueue; _userQueue = options.dispatchQueue; + _storage = [ARTLocalDeviceStorage new]; _http = [[ARTHttp alloc] init:_queue logger:_logger]; [_logger verbose:__FILE__ line:__LINE__ message:@"RS:%p %p alloc HTTP", self, _http]; _httpExecutor = _http; diff --git a/Source/Ably.h b/Source/Ably.h index 476d3b88c..4c61ed2cb 100644 --- a/Source/Ably.h +++ b/Source/Ably.h @@ -72,6 +72,7 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import #import #import +#import #import #import diff --git a/Source/Ably.modulemap b/Source/Ably.modulemap index 3e7d1357c..95d3edb48 100644 --- a/Source/Ably.modulemap +++ b/Source/Ably.modulemap @@ -36,5 +36,6 @@ framework module Ably { header "ARTLocalDevice+Private.h" header "ARTPush+Private.h" header "ARTPushAdmin+Private.h" + header "ARTPushActivationStateMachine+Private.h" } } From f24ab95d90a9c6edaf66d0adfc709486cc5e4b9d Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 31 Jan 2018 19:04:10 +0000 Subject: [PATCH 45/46] Fix headers --- Source/ARTDeviceDetails.h | 2 +- Source/ARTLocalDevice+Private.h | 5 ----- Source/ARTLocalDevice.h | 2 +- Source/ARTPush+Private.h | 5 ----- Source/ARTPush.h | 4 ++-- Source/ARTPushAdmin.h | 2 +- Source/ARTPushChannel.h | 6 +++--- Source/ARTPushChannelSubscriptions.h | 2 +- Source/ARTPushDeviceRegistrations.h | 2 +- 9 files changed, 10 insertions(+), 20 deletions(-) diff --git a/Source/ARTDeviceDetails.h b/Source/ARTDeviceDetails.h index 7e1034e94..186f86935 100644 --- a/Source/ARTDeviceDetails.h +++ b/Source/ARTDeviceDetails.h @@ -7,7 +7,7 @@ // #import -#import "ARTPush.h" +#import @class ARTDevicePushDetails; diff --git a/Source/ARTLocalDevice+Private.h b/Source/ARTLocalDevice+Private.h index c989dc34b..030b3a027 100644 --- a/Source/ARTLocalDevice+Private.h +++ b/Source/ARTLocalDevice+Private.h @@ -6,9 +6,6 @@ // Copyright © 2017 Ably. All rights reserved. // -#ifndef ARTLocalDevice_Private_h -#define ARTLocalDevice_Private_h - #import NS_ASSUME_NONNULL_BEGIN @@ -29,5 +26,3 @@ extern NSString *const ARTDeviceTokenKey; @end NS_ASSUME_NONNULL_END - -#endif /* ARTLocalDevice_Private_h */ diff --git a/Source/ARTLocalDevice.h b/Source/ARTLocalDevice.h index f45241328..bb29a14c3 100644 --- a/Source/ARTLocalDevice.h +++ b/Source/ARTLocalDevice.h @@ -7,7 +7,7 @@ // #import -#import "ARTDeviceDetails.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/ARTPush+Private.h b/Source/ARTPush+Private.h index 743c469f8..b547f3fbe 100644 --- a/Source/ARTPush+Private.h +++ b/Source/ARTPush+Private.h @@ -6,9 +6,6 @@ // Copyright © 2017 Ably. All rights reserved. // -#ifndef ARTPush_Private_h -#define ARTPush_Private_h - #import NS_ASSUME_NONNULL_BEGIN @@ -20,5 +17,3 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END - -#endif /* ARTPush_Private_h */ diff --git a/Source/ARTPush.h b/Source/ARTPush.h index 383723f24..b0a02e0ce 100644 --- a/Source/ARTPush.h +++ b/Source/ARTPush.h @@ -7,8 +7,8 @@ // #import -#import "ARTTypes.h" -#import "ARTPushAdmin.h" +#import +#import @class ARTRest; @class ARTRealtime; diff --git a/Source/ARTPushAdmin.h b/Source/ARTPushAdmin.h index bda43757d..a9314b26b 100644 --- a/Source/ARTPushAdmin.h +++ b/Source/ARTPushAdmin.h @@ -7,7 +7,7 @@ // #import -#import "ARTTypes.h" +#import @class ARTPushDeviceRegistrations; @class ARTPushChannelSubscriptions; diff --git a/Source/ARTPushChannel.h b/Source/ARTPushChannel.h index d017fcaa2..7f8f722a9 100644 --- a/Source/ARTPushChannel.h +++ b/Source/ARTPushChannel.h @@ -7,9 +7,9 @@ // #import -#import "ARTPush.h" -#import "ARTHttp.h" -#import "ARTChannel.h" +#import +#import +#import @class ARTPushChannelSubscription; @class ARTPaginatedResult; diff --git a/Source/ARTPushChannelSubscriptions.h b/Source/ARTPushChannelSubscriptions.h index 5c43d352d..9a8680238 100644 --- a/Source/ARTPushChannelSubscriptions.h +++ b/Source/ARTPushChannelSubscriptions.h @@ -7,7 +7,7 @@ // #import -#import "ARTTypes.h" +#import @class ARTPushChannelSubscription; @class ARTPaginatedResult; diff --git a/Source/ARTPushDeviceRegistrations.h b/Source/ARTPushDeviceRegistrations.h index 42e824013..61c976cef 100644 --- a/Source/ARTPushDeviceRegistrations.h +++ b/Source/ARTPushDeviceRegistrations.h @@ -7,7 +7,7 @@ // #import -#import "ARTTypes.h" +#import @class ARTDeviceDetails; @class ARTPaginatedResult; From 4c3bc4d98b4624c0c407159e7f91e4fe6b54cdb9 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 31 Jan 2018 19:06:04 +0000 Subject: [PATCH 46/46] Fix tests --- Spec/PushActivationStateMachine.swift | 227 +++++++++++++++----------- Spec/PushAdmin.swift | 7 +- Spec/RealtimeClientPush.swift | 17 -- Spec/RestClient.swift | 18 +- Spec/RestClientPush.swift | 17 -- Spec/TestUtilities.swift | 104 ++++++++---- 6 files changed, 214 insertions(+), 176 deletions(-) delete mode 100644 Spec/RealtimeClientPush.swift delete mode 100644 Spec/RestClientPush.swift diff --git a/Spec/PushActivationStateMachine.swift b/Spec/PushActivationStateMachine.swift index ccaf6fdfc..a89ffb2c0 100644 --- a/Spec/PushActivationStateMachine.swift +++ b/Spec/PushActivationStateMachine.swift @@ -23,22 +23,24 @@ class PushActivationStateMachine : QuickSpec { httpExecutor = MockHTTPExecutor() rest.httpExecutor = httpExecutor storage = MockDeviceStorage() - initialStateMachine = ARTPushActivationStateMachine(rest, storage: storage) + rest.storage = storage + initialStateMachine = ARTPushActivationStateMachine(rest) } describe("Activation state machine") { it("should set NotActivated state is current state in disk is empty") { - expect(initialStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(initialStateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated.self)) } it("should read the current state in disk") { let storage = MockDeviceStorage(startWith: ARTPushActivationStateWaitingForUpdateToken(machine: initialStateMachine)) - let stateMachine = ARTPushActivationStateMachine(rest, storage: storage) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + rest.storage = storage + let stateMachine = ARTPushActivationStateMachine(rest) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken.self)) expect(storage.keysRead).to(haveCount(2)) expect(storage.keysRead.filter({ $0.hasSuffix("CurrentState") })).to(haveCount(1)) - expect(storage.keysWrite).to(beEmpty()) + expect(storage.keysWritten).to(beEmpty()) } context("State NotActivated") { @@ -48,7 +50,8 @@ class PushActivationStateMachine : QuickSpec { beforeEach { storage = MockDeviceStorage(startWith: ARTPushActivationStateNotActivated(machine: initialStateMachine)) - stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + rest.storage = storage + stateMachine = ARTPushActivationStateMachine(rest) } it("on Event CalledDeactivate, should transition to NotActivated") { @@ -60,31 +63,44 @@ class PushActivationStateMachine : QuickSpec { stateMachine.send(ARTPushActivationEventCalledDeactivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated.self)) expect(deactivatedCallbackCalled).to(beTrue()) } context("on Event CalledActivate") { it("if the local device has id and updateToken then should transition to WaitingForNewPushDeviceDetails") { let testDeviceId = "aaaa" - //testStorage.simulateOnNextRead(testDeviceId, for: ARTDeviceIdKey) + storage.simulateOnNextRead(string: testDeviceId, for: ARTDeviceIdKey) + + let testDeviceUpdateToken = "xxxx-xxxx-xxx" + storage.simulateOnNextRead(string: testDeviceUpdateToken, for: ARTDeviceUpdateTokenKey) - let testDeviceUpdateToken = "xxxx-xxxx-xxxx" - //testStorage.simulateOnNextRead(testDeviceUpdateToken, for: ARTDeviceUpdateTokenKey) stateMachine.send(ARTPushActivationEventCalledActivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails.self)) } it("if the local device has the necessary push details should send event GotPushDeviceDetails") { + let delegate = StateMachineDelegate() + stateMachine.delegate = delegate let testDeviceToken = "xxxx-xxxx-xxxx-xxxx-xxxx" - //testStorage.simulateOnNextRead(testDeviceToken, for: ARTDeviceTokenKey) - stateMachine.send(ARTPushActivationEventCalledActivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationEventGotPushDeviceDetails)) + storage.simulateOnNextRead(string: testDeviceToken, for: ARTDeviceTokenKey) + waitUntil(timeout: testTimeout) { done in + let partialDone = AblyTests.splitDone(2, done: done) + stateMachine.transitions = { event, from, to in + if event is ARTPushActivationEventCalledActivate { + partialDone() + } + if event is ARTPushActivationEventGotPushDeviceDetails { + partialDone() + } + } + stateMachine.send(ARTPushActivationEventCalledActivate()) + } } it("none of them then should transition to WaitingForPushDeviceDetails") { stateMachine.send(ARTPushActivationEventCalledActivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails.self)) } } @@ -98,12 +114,13 @@ class PushActivationStateMachine : QuickSpec { beforeEach { storage = MockDeviceStorage(startWith: ARTPushActivationStateWaitingForPushDeviceDetails(machine: initialStateMachine)) - stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + rest.storage = storage + stateMachine = ARTPushActivationStateMachine(rest) } it("on Event CalledActivate") { stateMachine.send(ARTPushActivationEventCalledActivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails.self)) } it("on Event CalledDeactivate") { @@ -114,30 +131,34 @@ class PushActivationStateMachine : QuickSpec { defer { hook.remove() } stateMachine.send(ARTPushActivationEventCalledDeactivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated.self)) expect(deactivatedCallbackCalled).to(beTrue()) } // RSH3b3 context("on Event GotPushDeviceDetails") { - it("should raise exception if ARTPushRegistererDelegate is not implemented") { - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails)) - expect{ stateMachine.send(ARTPushActivationEventGotPushDeviceDetails()) }.to(raiseException { exception in + // TODO: The exception is raised but the `send:` method is doing an async call and the `expect` doesn't catch it + pending("should raise exception if ARTPushRegistererDelegate is not implemented") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails.self)) + expect { + stateMachine.send(ARTPushActivationEventGotPushDeviceDetails()) + }.to(raiseException { exception in expect(exception.reason).to(contain("ARTPushRegistererDelegate must be implemented")) }) } it("should use custom registerCallback and fire GotUpdateToken event") { - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails.self)) let delegate = StateMachineDelegateCustomCallbacks() stateMachine.delegate = delegate waitUntil(timeout: testTimeout) { done in let partialDone = AblyTests.splitDone(2, done: done) - stateMachine.testSuite_getArgument(from: NSSelectorFromString("handleEvent:"), at: 0) { event in + stateMachine.transitions = { event, from, to in if event is ARTPushActivationEventGotUpdateToken { + stateMachine.transitions = nil partialDone() } } @@ -148,15 +169,15 @@ class PushActivationStateMachine : QuickSpec { return nil } stateMachine.send(ARTPushActivationEventGotPushDeviceDetails()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken.self)) } - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails.self)) expect(httpExecutor.requests.count) == 0 } it("should use custom registerCallback and fire GettingUpdateTokenFailed event") { - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails.self)) let delegate = StateMachineDelegateCustomCallbacks() stateMachine.delegate = delegate @@ -164,10 +185,11 @@ class PushActivationStateMachine : QuickSpec { waitUntil(timeout: testTimeout) { done in let simulatedError = NSError(domain: "", code: 1234, userInfo: nil) let partialDone = AblyTests.splitDone(2, done: done) - stateMachine.testSuite_getArgument(from: NSSelectorFromString("handleEvent:"), at: 0) { event in + stateMachine.transitions = { event, from, to in if let event = event as? ARTPushActivationEventGettingUpdateTokenFailed { expect(event.error.domain) == ARTAblyErrorDomain expect(event.error.code) == simulatedError.code + stateMachine.transitions = nil partialDone() } } @@ -178,53 +200,54 @@ class PushActivationStateMachine : QuickSpec { return simulatedError } stateMachine.send(ARTPushActivationEventGotPushDeviceDetails()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken.self)) } - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated.self)) expect(httpExecutor.requests.count) == 0 } it("should fire GotUpdateToken event") { - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails.self)) let delegate = StateMachineDelegate() stateMachine.delegate = delegate waitUntil(timeout: testTimeout) { done in - stateMachine.testSuite_getArgument(from: NSSelectorFromString("handleEvent:"), at: 0) { event in + stateMachine.transitions = { event, from, to in if event is ARTPushActivationEventGotUpdateToken { + stateMachine.transitions = nil done() } } stateMachine.send(ARTPushActivationEventGotPushDeviceDetails()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken.self)) } - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails.self)) expect(httpExecutor.requests.count) == 1 - let requests = httpExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations" }) + let requests = httpExecutor.requests.flatMap({ $0.url?.path }).filter({ $0 == "/push/deviceRegistrations" }) expect(requests).to(haveCount(1)) guard let request = httpExecutor.requests.first else { fail("should have a \"/push/deviceRegistrations\" request"); return } - guard let url = request.URL else { + guard let url = request.url else { fail("should have a \"/push/deviceRegistrations\" URL"); return } - guard let rawBody = request.HTTPBody else { + guard let rawBody = request.httpBody else { fail("should have a body"); return } - guard let body = stateMachine.rest.defaultEncoder.decode(rawBody) as? NSDictionary else { + guard let body = stateMachine.rest.defaultEncoder.decode(rawBody, error: nil) as? NSDictionary else { fail("body is invalid"); return } - expect(body.valueForKey("id") as? String).to(equal(rest.device.id)) - expect(body.valueForKey("push")).toNot(beNil()) - expect(body.valueForKey("formFactor")).toNot(beNil()) - expect(body.valueForKey("platform")).toNot(beNil()) + expect(body.value(forKey: "id") as? String).to(equal(rest.device.id)) + expect(body.value(forKey: "push")).toNot(beNil()) + expect(body.value(forKey: "formFactor")).toNot(beNil()) + expect(body.value(forKey: "platform")).toNot(beNil()) } - fit("should fire GettingUpdateTokenFailed event") { - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails)) + it("should fire GettingUpdateTokenFailed event") { + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForPushDeviceDetails.self)) let delegate = StateMachineDelegate() stateMachine.delegate = delegate @@ -233,37 +256,38 @@ class PushActivationStateMachine : QuickSpec { httpExecutor.simulateIncomingErrorOnNextRequest(simulatedError) waitUntil(timeout: testTimeout) { done in - stateMachine.testSuite_getArgument(from: NSSelectorFromString("handleEvent:"), at: 0) { event in + stateMachine.transitions = { event, from, to in if let event = event as? ARTPushActivationEventGettingUpdateTokenFailed { expect(event.error.domain) == ARTAblyErrorDomain expect(event.error.code) == simulatedError.code + stateMachine.transitions = nil done() } } stateMachine.send(ARTPushActivationEventGotPushDeviceDetails()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken.self)) } - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated.self)) expect(httpExecutor.requests.count) == 1 - let requests = httpExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations" }) + let requests = httpExecutor.requests.flatMap({ $0.url?.path }).filter({ $0 == "/push/deviceRegistrations" }) expect(requests).to(haveCount(1)) guard let request = httpExecutor.requests.first else { fail("should have a \"/push/deviceRegistrations\" request"); return } - guard let url = request.URL else { + guard let url = request.url else { fail("should have a \"/push/deviceRegistrations\" URL"); return } - guard let rawBody = request.HTTPBody else { + guard let rawBody = request.httpBody else { fail("should have a body"); return } - guard let body = stateMachine.rest.defaultEncoder.decode(rawBody) as? NSDictionary else { + guard let body = stateMachine.rest.defaultEncoder.decode(rawBody, error: nil) as? NSDictionary else { fail("body is invalid"); return } - expect(body.valueForKey("id") as? String).to(equal(rest.device.id)) - expect(body.valueForKey("push")).toNot(beNil()) - expect(body.valueForKey("formFactor")).toNot(beNil()) - expect(body.valueForKey("platform")).toNot(beNil()) + expect(body.value(forKey: "id") as? String).to(equal(rest.device.id)) + expect(body.value(forKey: "push")).toNot(beNil()) + expect(body.value(forKey: "formFactor")).toNot(beNil()) + expect(body.value(forKey: "platform")).toNot(beNil()) } } @@ -277,12 +301,13 @@ class PushActivationStateMachine : QuickSpec { beforeEach { storage = MockDeviceStorage(startWith: ARTPushActivationStateWaitingForUpdateToken(machine: initialStateMachine)) - stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + rest.storage = storage + stateMachine = ARTPushActivationStateMachine(rest) } it("on Event CalledActivate") { stateMachine.send(ARTPushActivationEventCalledActivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForUpdateToken.self)) } it("on Event GotUpdateToken") { @@ -293,7 +318,7 @@ class PushActivationStateMachine : QuickSpec { defer { hook.remove() } stateMachine.send(ARTPushActivationEventGotUpdateToken()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails.self)) expect(activatedCallbackCalled).to(beTrue()) } @@ -305,7 +330,7 @@ class PushActivationStateMachine : QuickSpec { defer { hook.remove() } stateMachine.send(ARTPushActivationEventGettingUpdateTokenFailed()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated.self)) expect(activatedCallbackCalled).to(beTrue()) } @@ -319,7 +344,8 @@ class PushActivationStateMachine : QuickSpec { beforeEach { storage = MockDeviceStorage(startWith: ARTPushActivationStateWaitingForNewPushDeviceDetails(machine: initialStateMachine)) - stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + rest.storage = storage + stateMachine = ARTPushActivationStateMachine(rest) } it("on Event CalledActivate") { @@ -330,7 +356,7 @@ class PushActivationStateMachine : QuickSpec { defer { hook.remove() } stateMachine.send(ARTPushActivationEventCalledActivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails.self)) expect(activatedCallbackCalled).to(beTrue()) } @@ -338,15 +364,16 @@ class PushActivationStateMachine : QuickSpec { context("on Event CalledDeactivate") { it("should use custom deregisterCallback and fire Deregistered event") { - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails.self)) let delegate = StateMachineDelegateCustomCallbacks() stateMachine.delegate = delegate waitUntil(timeout: testTimeout) { done in let partialDone = AblyTests.splitDone(2, done: done) - stateMachine.testSuite_getArgument(from: NSSelectorFromString("handleEvent:"), at: 0) { event in + stateMachine.transitions = { event, from, to in if event is ARTPushActivationEventDeregistered { + stateMachine.transitions = nil partialDone() } } @@ -357,15 +384,15 @@ class PushActivationStateMachine : QuickSpec { return nil } stateMachine.send(ARTPushActivationEventCalledDeactivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration.self)) } - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated.self)) expect(httpExecutor.requests.count) == 0 } it("should use custom deregisterCallback and fire DeregistrationFailed event") { - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails.self)) let delegate = StateMachineDelegateCustomCallbacks() stateMachine.delegate = delegate @@ -373,10 +400,11 @@ class PushActivationStateMachine : QuickSpec { waitUntil(timeout: testTimeout) { done in let simulatedError = NSError(domain: "", code: 1234, userInfo: nil) let partialDone = AblyTests.splitDone(2, done: done) - stateMachine.testSuite_getArgument(from: NSSelectorFromString("handleEvent:"), at: 0) { event in + stateMachine.transitions = { event, from, to in if let event = event as? ARTPushActivationEventDeregistrationFailed { expect(event.error.domain) == ARTAblyErrorDomain expect(event.error.code) == simulatedError.code + stateMachine.transitions = nil partialDone() } } @@ -387,61 +415,69 @@ class PushActivationStateMachine : QuickSpec { return simulatedError } stateMachine.send(ARTPushActivationEventCalledDeactivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration.self)) } - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration.self)) expect(httpExecutor.requests.count) == 0 } it("should fire Deregistered event") { - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails.self)) + + let delegate = StateMachineDelegate() + stateMachine.delegate = delegate waitUntil(timeout: testTimeout) { done in - stateMachine.testSuite_getArgumentFrom(NSSelectorFromString("handleEvent:"), atIndex: 0) { event in + stateMachine.transitions = { event, from, to in if event is ARTPushActivationEventDeregistered { + stateMachine.transitions = nil done() } } stateMachine.send(ARTPushActivationEventCalledDeactivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration.self)) } - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated.self)) expect(httpExecutor.requests.count) == 1 - let requests = httpExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations" }) + let requests = httpExecutor.requests.flatMap({ $0.url?.path }).filter({ $0 == "/push/deviceRegistrations" }) expect(requests).to(haveCount(1)) guard let request = httpExecutor.requests.first else { fail("should have a \"/push/deviceRegistrations\" request"); return } - guard let url = request.URL else { + guard let url = request.url else { fail("should have a \"/push/deviceRegistrations\" URL"); return } - expect(request.HTTPMethod) == "DELETE" + expect(request.httpMethod) == "DELETE" expect(url.query).to(contain(rest.device.id)) } it("should fire DeregistrationFailed event") { - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForNewPushDeviceDetails.self)) + + let delegate = StateMachineDelegate() + stateMachine.delegate = delegate let simulatedError = NSError(domain: "", code: 1234, userInfo: nil) httpExecutor.simulateIncomingErrorOnNextRequest(simulatedError) waitUntil(timeout: testTimeout) { done in - stateMachine.testSuite_getArgument(from: NSSelectorFromString("handleEvent:"), at: 0) { event in + stateMachine.transitions = { event, from, to in if let event = event as? ARTPushActivationEventDeregistrationFailed { expect(event.error.domain) == ARTAblyErrorDomain expect(event.error.code) == simulatedError.code + stateMachine.transitions = nil done() } } stateMachine.send(ARTPushActivationEventCalledDeactivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration.self)) } - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration.self)) expect(httpExecutor.requests.count) == 1 - let requests = httpExecutor.requests.flatMap({ $0.URL?.path }).filter({ $0 == "/push/deviceRegistrations" }) + let requests = httpExecutor.requests.flatMap({ $0.url?.path }).filter({ $0 == "/push/deviceRegistrations" }) expect(requests).to(haveCount(1)) guard let request = httpExecutor.requests.first else { fail("should have a \"/push/deviceRegistrations\" request"); return @@ -458,21 +494,21 @@ class PushActivationStateMachine : QuickSpec { } context("State WaitingForRegistrationUpdate") { - + // Doesn't happen in iOS } context("State AfterRegistrationUpdateFailed") { it("on Event CalledActivate") { - + // Doesn't happen in iOS } it("on Event GotPushDeviceDetails") { - + // Doesn't happen in iOS } it("on Event CalledDeactivate") { - + // Doesn't happen in iOS } } @@ -484,12 +520,13 @@ class PushActivationStateMachine : QuickSpec { beforeEach { storage = MockDeviceStorage(startWith: ARTPushActivationStateWaitingForDeregistration(machine: initialStateMachine)) - stateMachine = ARTPushActivationStateMachine(rest, storage: storage) + rest.storage = storage + stateMachine = ARTPushActivationStateMachine(rest) } it("on Event CalledDeactivate") { stateMachine.send(ARTPushActivationEventCalledDeactivate()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration.self)) } it("on Event Deregistered") { @@ -500,9 +537,9 @@ class PushActivationStateMachine : QuickSpec { defer { hook.remove() } stateMachine.send(ARTPushActivationEventDeregistered()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateNotActivated.self)) expect(deactivatedCallbackCalled).to(beTrue()) - expect(storage.keysWrite.filter({ $0 == ARTDeviceUpdateTokenKey })).to(haveCount(1)) + expect(storage.keysWritten.filter({ $0 == ARTDeviceUpdateTokenKey })).to(haveCount(1)) } it("on Event DeregistrationFailed") { @@ -513,7 +550,7 @@ class PushActivationStateMachine : QuickSpec { defer { hook.remove() } stateMachine.send(ARTPushActivationEventDeregistrationFailed()) - expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration)) + expect(stateMachine.current).to(beAKindOf(ARTPushActivationStateWaitingForDeregistration.self)) expect(deactivatedCallbackCalled).to(beTrue()) } @@ -543,23 +580,25 @@ class StateMachineDelegate: NSObject, ARTPushRegistererDelegate { } +typealias ARTDeviceId = String + class StateMachineDelegateCustomCallbacks: StateMachineDelegate { var onPushCustomRegister: ((ARTErrorInfo?, ARTDeviceDetails?) -> NSError?)? var onPushCustomDeregister: ((ARTErrorInfo?, ARTDeviceId?) -> NSError?)? - func ablyPushCustomRegister(error: ARTErrorInfo?, deviceDetails: ARTDeviceDetails?, callback: @escaping (String, ARTErrorInfo?) -> Void) { + func ablyPushCustomRegister(_ error: ARTErrorInfo?, deviceDetails: ARTDeviceDetails?, callback: @escaping (String, ARTErrorInfo?) -> Void) { let error = onPushCustomRegister?(error, deviceDetails) delay(0) { callback("", error == nil ? nil : ARTErrorInfo.create(from: error!)) } } - func ablyPushCustomDeregister(error: ARTErrorInfo?, deviceId: String?, callback: ((ARTErrorInfo?) -> Void)?) { - let error = onPushCustomDeregister?(error, deviceId: deviceId) + func ablyPushCustomDeregister(_ error: ARTErrorInfo?, deviceId: String?, callback: ((ARTErrorInfo?) -> Void)? = nil) { + let error = onPushCustomDeregister?(error, deviceId) delay(0) { - callback?(error == nil ? nil : ARTErrorInfo.createWithNSError(error!)) + callback?(error == nil ? nil : ARTErrorInfo.create(from: error!)) } } - + } diff --git a/Spec/PushAdmin.swift b/Spec/PushAdmin.swift index 808cf3f6d..72374e435 100644 --- a/Spec/PushAdmin.swift +++ b/Spec/PushAdmin.swift @@ -178,7 +178,7 @@ class PushAdmin : QuickSpec { } // RHS1a - fdescribe("publish") { + describe("publish") { it("should perform an HTTP request to /push/publish") { waitUntil(timeout: testTimeout) { done in @@ -227,6 +227,7 @@ class PushAdmin : QuickSpec { waitUntil(timeout: testTimeout) { done in let partialDone = AblyTests.splitDone(2, done: done) // FIXME: waiting a response + // https://github.com/ably/ably-ios/issues/669 channel.subscribe("__ably_push__") { message in guard let data = message.data as? NSDictionary else { fail("Data is not a JSON Object"); partialDone(); return @@ -319,7 +320,7 @@ class PushAdmin : QuickSpec { } - fdescribe("Device Registrations") { + describe("Device Registrations") { // RHS1b1 context("get") { @@ -512,7 +513,7 @@ class PushAdmin : QuickSpec { } - fdescribe("Channel Subscriptions") { + describe("Channel Subscriptions") { let subscription = ARTPushChannelSubscription(clientId: "newClient", channel: "pushenabled:qux") diff --git a/Spec/RealtimeClientPush.swift b/Spec/RealtimeClientPush.swift deleted file mode 100644 index e44873d45..000000000 --- a/Spec/RealtimeClientPush.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// RealtimeClientPush.swift -// Ably -// -// Created by Ricardo Pereira on 18/04/2017. -// Copyright © 2017 Ably. All rights reserved. -// - -import Ably -import Nimble -import Quick - -class RealtimeClientPush : QuickSpec { - override func spec() { - - } -} diff --git a/Spec/RestClient.swift b/Spec/RestClient.swift index fd4e8aa4a..bcfd832a9 100644 --- a/Spec/RestClient.swift +++ b/Spec/RestClient.swift @@ -351,27 +351,27 @@ class RestClient: QuickSpec { testHTTPExecutor = TestProxyHTTPExecutor(options.logHandler) clientHttps.httpExecutor = testHTTPExecutor - options.clientId = "client_http" - options.tls = false - let clientHttp = ARTRest(options: options) - testHTTPExecutor = TestProxyHTTPExecutor(options.logHandler) - clientHttp.httpExecutor = testHTTPExecutor - waitUntil(timeout: testTimeout) { done in publishTestMessage(clientHttps) { error in done() } } + let requestUrlA = testHTTPExecutor.requests.first!.url! + expect(requestUrlA.scheme).to(equal("https")) + + options.clientId = "client_http" + options.tls = false + let clientHttp = ARTRest(options: options) + testHTTPExecutor = TestProxyHTTPExecutor(options.logHandler) + clientHttp.httpExecutor = testHTTPExecutor + waitUntil(timeout: testTimeout) { done in publishTestMessage(clientHttp) { error in done() } } - let requestUrlA = testHTTPExecutor.requests.first!.url! - expect(requestUrlA.scheme).to(equal("https")) - let requestUrlB = testHTTPExecutor.requests.last!.url! expect(requestUrlB.scheme).to(equal("http")) } diff --git a/Spec/RestClientPush.swift b/Spec/RestClientPush.swift deleted file mode 100644 index 27513573a..000000000 --- a/Spec/RestClientPush.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// RestClientPush.swift -// Ably -// -// Created by Ricardo Pereira on 18/04/2017. -// Copyright © 2017 Ably. All rights reserved. -// - -import Ably -import Nimble -import Quick - -class RestClientPush : QuickSpec { - override func spec() { - - } -} diff --git a/Spec/TestUtilities.swift b/Spec/TestUtilities.swift index 79de19b58..eb1d281ad 100644 --- a/Spec/TestUtilities.swift +++ b/Spec/TestUtilities.swift @@ -616,38 +616,78 @@ class MockHTTP: ARTHttp { class MockDeviceStorage: NSObject, ARTDeviceStorage { var keysRead: [String] = [] - var keysWrite: [String] = [] + var keysWritten: [String] = [] - private var simulateData: [String: NSData] = [:] + private var simulateData: [String: Data] = [:] + private var simulateString: [String: String] = [:] init(startWith state: ARTPushActivationState? = nil) { super.init() if let state = state { - simulateOnNextRead(state.archive(), for: ARTPushActivationCurrentStateKey) + simulateOnNextRead(data: state.archive(), for: ARTPushActivationCurrentStateKey) } } - func readKey(key: String) -> NSData? { + func object(forKey key: String) -> Any? { keysRead.append(key) if var data = simulateData[key] { defer { simulateData.removeValue(forKey: key) } return data } + if var string = simulateString[key] { + defer { simulateString.removeValue(forKey: key) } + return string + } return nil } - func writeKey(key: String, withValue value: NSData?) { - keysWrite.append(key) + func setObject(_ value: Any?, forKey key: String) { + keysWritten.append(key) + } + + func simulateOnNextRead(data value: Data, `for` key: String) { + simulateData[key] = value } - func simulateOnNextRead(data: NSData, `for` key: String) { - simulateData[key] = data + func simulateOnNextRead(string value: String, `for` key: String) { + simulateString[key] = value } } +fileprivate struct ErrorSimulator { + let value: Int + let description: String + let serverId = "server-test-suite" + var statusCode: Int = 401 + + mutating func stubResponse(_ url: URL) -> HTTPURLResponse? { + return HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: [ + "Content-Length": String(stubData?.count ?? 0), + "Content-Type": "application/json", + "X-Ably-Errorcode": String(value), + "X-Ably-Errormessage": description, + "X-Ably-Serverid": serverId, + ] + ) + } + + lazy var stubData: Data? = { + let jsonObject = ["error": [ + "statusCode": modf(Float(self.value)/100).0, //whole number part + "code": self.value, + "message": self.description, + "serverId": self.serverId, + ] + ] + return try? JSONSerialization.data(withJSONObject: jsonObject, options: JSONSerialization.WritingOptions.init(rawValue: 0)) + }() +} + class MockHTTPExecutor: NSObject, ARTHTTPAuthenticatedExecutor { + fileprivate var errorSimulator: NSError? + var _logger = ARTLog() var clientOptions = ARTClientOptions() var encoder = ARTJsonLikeEncoder() @@ -667,46 +707,37 @@ class MockHTTPExecutor: NSObject, ARTHTTPAuthenticatedExecutor { func execute(_ request: NSMutableURLRequest, withAuthOption authOption: ARTAuthentication, completion callback: @escaping (HTTPURLResponse?, Data?, Error?) -> Void) { self.requests.append(request as URLRequest) + + if var simulatedError = errorSimulator, var requestURL = request.url { + defer { errorSimulator = nil } + callback(nil, nil, simulatedError) + return + } + callback(nil, nil, nil) } func execute(_ request: URLRequest, completion callback: ((HTTPURLResponse?, Data?, Error?) -> Void)? = nil) { self.requests.append(request) + + if var simulatedError = errorSimulator, var requestURL = request.url { + defer { errorSimulator = nil } + callback?(nil, nil, simulatedError) + return + } + callback?(nil, nil, nil) } + func simulateIncomingErrorOnNextRequest(_ error: NSError) { + errorSimulator = error + } + } /// Records each request and response for test purpose. class TestProxyHTTPExecutor: NSObject, ARTHTTPExecutor { - struct ErrorSimulator { - let value: Int - let description: String - let serverId = "server-test-suite" - var statusCode: Int = 401 - - mutating func stubResponse(_ url: URL) -> HTTPURLResponse? { - return HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: [ - "Content-Length": String(stubData?.count ?? 0), - "Content-Type": "application/json", - "X-Ably-Errorcode": String(value), - "X-Ably-Errormessage": description, - "X-Ably-Serverid": serverId, - ] - ) - } - lazy var stubData: Data? = { - let jsonObject = ["error": [ - "statusCode": modf(Float(self.value)/100).0, //whole number part - "code": self.value, - "message": self.description, - "serverId": self.serverId, - ] - ] - return try? JSONSerialization.data(withJSONObject: jsonObject, options: JSONSerialization.WritingOptions.init(rawValue: 0)) - }() - } fileprivate var errorSimulator: ErrorSimulator? var http: ARTHttp! @@ -892,6 +923,7 @@ class TestProxyTransport: ARTWebSocketTransport { send(data, withSource: message) } + @discardableResult override func send(_ data: Data, withSource decodedObject: Any?) -> Bool { if let msg = decodedObject as? ARTProtocolMessage { if ignoreSends { @@ -966,7 +998,7 @@ class TestProxyTransport: ARTWebSocketTransport { override func webSocket(_ webSocket: SRWebSocket, didReceiveMessage message: Any?) { if !ignoreWebSocket { - super.webSocket(webSocket, didReceiveMessage: message) + super.webSocket(webSocket, didReceiveMessage: message!) } }