From 4daf4ba8925fe6a4869e722cfe9f5821f960b822 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 14 Jan 2025 16:11:08 -0300 Subject: [PATCH] Create wrapper SDK proxy channel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #2020; that is, implementing ADR-117 [1]. There are no functional changes here; it's preparation for adding the `agent` channel param when a channel is fetched via an ARTWrapperSDKProxyRealtime. I initially implemented this with internal storage of proxy instances to ensure that there be only one proxy instance per underlying channel. However, it wasn't really clear to me _why_ I did this, other than for some sort of symmetry with the underlying ARTRealtimeChannels behaviour. So it didn't really seem worth the time of trying to figure out the right behaviour. So I decided to not implement this behaviour in the end, instead creating a new proxy instance each time -get:… is called. [1] https://ably.atlassian.net/wiki/spaces/ENG/pages/3413016589/ADR-117+Tracking+wrapper+SDK+usage+continued --- Ably.xcodeproj/project.pbxproj | 48 +++++ Source/ARTWrapperSDKProxyRealtime.m | 8 +- Source/ARTWrapperSDKProxyRealtimeChannel.m | 180 ++++++++++++++++++ Source/ARTWrapperSDKProxyRealtimeChannels.m | 49 +++++ Source/Ably.modulemap | 2 + ...RTWrapperSDKProxyRealtimeChannel+Private.h | 14 ++ ...TWrapperSDKProxyRealtimeChannels+Private.h | 14 ++ Source/include/Ably.modulemap | 2 + .../include/Ably/ARTWrapperSDKProxyRealtime.h | 4 +- .../Ably/ARTWrapperSDKProxyRealtimeChannel.h | 26 +++ .../Ably/ARTWrapperSDKProxyRealtimeChannels.h | 23 +++ Source/include/Ably/AblyInternal.h | 2 + Test/Tests/WrapperSDKProxyTests.swift | 108 +++++++++++ 13 files changed, 473 insertions(+), 7 deletions(-) create mode 100644 Source/ARTWrapperSDKProxyRealtimeChannel.m create mode 100644 Source/ARTWrapperSDKProxyRealtimeChannels.m create mode 100644 Source/PrivateHeaders/Ably/ARTWrapperSDKProxyRealtimeChannel+Private.h create mode 100644 Source/PrivateHeaders/Ably/ARTWrapperSDKProxyRealtimeChannels+Private.h create mode 100644 Source/include/Ably/ARTWrapperSDKProxyRealtimeChannel.h create mode 100644 Source/include/Ably/ARTWrapperSDKProxyRealtimeChannels.h diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 645935ba2..d52df1be6 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -296,6 +296,24 @@ 21881E7A283BD08300CFD9E2 /* GCDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A22171266F526600C87C42 /* GCDTests.swift */; }; 21881E7B283BD0DF00CFD9E2 /* StringifiableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D520C4DD2680A1E3000012B2 /* StringifiableTests.swift */; }; 21881E7C283BD0E100CFD9E2 /* StringifiableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D520C4DD2680A1E3000012B2 /* StringifiableTests.swift */; }; + 21AC0CC22D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC02D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 21AC0CC32D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC12D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 21AC0CC42D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC02D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 21AC0CC52D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC12D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 21AC0CC62D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC02D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 21AC0CC72D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC12D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 21AC0CCA2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC82D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 21AC0CCB2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC92D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 21AC0CCC2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC82D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 21AC0CCD2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC92D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 21AC0CCE2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC82D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 21AC0CCF2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 21AC0CC92D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 21AC0CD22D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 21AC0CD02D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m */; }; + 21AC0CD32D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m in Sources */ = {isa = PBXBuildFile; fileRef = 21AC0CD12D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m */; }; + 21AC0CD42D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 21AC0CD02D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m */; }; + 21AC0CD52D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m in Sources */ = {isa = PBXBuildFile; fileRef = 21AC0CD12D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m */; }; + 21AC0CD62D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 21AC0CD02D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m */; }; + 21AC0CD72D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m in Sources */ = {isa = PBXBuildFile; fileRef = 21AC0CD12D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m */; }; 21E1C0E52A0DC47400A5DB65 /* ARTWebSocketFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 21E1C0E42A0DC47400A5DB65 /* ARTWebSocketFactory.h */; settings = {ATTRIBUTES = (Private, ); }; }; 21E1C0E62A0DC47400A5DB65 /* ARTWebSocketFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 21E1C0E42A0DC47400A5DB65 /* ARTWebSocketFactory.h */; settings = {ATTRIBUTES = (Private, ); }; }; 21E1C0E72A0DC47400A5DB65 /* ARTWebSocketFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 21E1C0E42A0DC47400A5DB65 /* ARTWebSocketFactory.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -1269,6 +1287,12 @@ 217FCF3D29D626E4006E5F2D /* StaticJitterCoefficients.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticJitterCoefficients.swift; sourceTree = ""; }; 217FCF3E29D626E4006E5F2D /* MockJitterCoefficientGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockJitterCoefficientGenerator.swift; sourceTree = ""; }; 217FCF4529D626F6006E5F2D /* DefaultJitterCoefficientGeneratorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultJitterCoefficientGeneratorTests.swift; sourceTree = ""; }; + 21AC0CC02D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTWrapperSDKProxyRealtimeChannel.h; path = include/Ably/ARTWrapperSDKProxyRealtimeChannel.h; sourceTree = ""; }; + 21AC0CC12D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTWrapperSDKProxyRealtimeChannels.h; path = include/Ably/ARTWrapperSDKProxyRealtimeChannels.h; sourceTree = ""; }; + 21AC0CC82D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "ARTWrapperSDKProxyRealtimeChannel+Private.h"; path = "PrivateHeaders/Ably/ARTWrapperSDKProxyRealtimeChannel+Private.h"; sourceTree = ""; }; + 21AC0CC92D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "ARTWrapperSDKProxyRealtimeChannels+Private.h"; path = "PrivateHeaders/Ably/ARTWrapperSDKProxyRealtimeChannels+Private.h"; sourceTree = ""; }; + 21AC0CD02D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTWrapperSDKProxyRealtimeChannel.m; sourceTree = ""; }; + 21AC0CD12D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTWrapperSDKProxyRealtimeChannels.m; sourceTree = ""; }; 21DCDA8229F818630073A211 /* Ably-iOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = "Ably-iOS.xctestplan"; path = "Test/Ably-iOS.xctestplan"; sourceTree = SOURCE_ROOT; }; 21DCDA8329F81B350073A211 /* Ably-macOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Ably-macOS.xctestplan"; sourceTree = ""; }; 21DCDA8429F81B550073A211 /* Ably-tvOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Ably-tvOS.xctestplan"; sourceTree = ""; }; @@ -1633,6 +1657,12 @@ 213AEA262D35A7FC0067FD5F /* ARTWrapperSDKProxyRealtime.h */, 213AEA2E2D36E9D30067FD5F /* ARTWrapperSDKProxyRealtime+Private.h */, 213AEA1E2D35A7CD0067FD5F /* ARTWrapperSDKProxyRealtime.m */, + 21AC0CC12D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h */, + 21AC0CC92D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h */, + 21AC0CD12D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m */, + 21AC0CC02D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h */, + 21AC0CC82D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h */, + 21AC0CD02D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m */, ); name = "Wrapper SDK Proxy"; sourceTree = ""; @@ -2352,6 +2382,8 @@ 217FCF3629D6269D006E5F2D /* ARTJitterCoefficientGenerator.h in Headers */, 2132C32029D5FE74000C4355 /* ARTTypes+Private.h in Headers */, D7D8F8211BC2BE16009718F2 /* ARTAuthOptions.h in Headers */, + 21AC0CCE2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h in Headers */, + 21AC0CCF2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h in Headers */, EB82F8511C59D29B00661917 /* ARTDataEncoder.h in Headers */, D7B621981E4A762A00684474 /* ARTPushChannel.h in Headers */, D768C6AC1E4B5B0200436011 /* ARTDevicePushDetails.h in Headers */, @@ -2406,6 +2438,8 @@ D7AE18CE1E5B40FE00478D82 /* ARTPushDeviceRegistrations.h in Headers */, D520C4D626809BB5000012B2 /* ARTStringifiable.h in Headers */, 21276CBA29EF323100107B5F /* ARTContinuousClock.h in Headers */, + 21AC0CC62D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h in Headers */, + 21AC0CC72D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h in Headers */, 2132C31129D5E4C6000C4355 /* ARTBackoffRetryDelayCalculator.h in Headers */, 2114D42A2D4BC5470032839A /* AblyInternal.h in Headers */, 2114D42B2D4BC5470032839A /* AblyPublic.h in Headers */, @@ -2508,6 +2542,8 @@ D710D4CE21949BB2008F54AD /* ARTWebSocketTransport+Private.h in Headers */, D710D56C21949CB9008F54AD /* ARTPushChannelSubscriptions.h in Headers */, D710D56721949CA1008F54AD /* ARTPushActivationStateMachine+Private.h in Headers */, + 21AC0CC42D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h in Headers */, + 21AC0CC52D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h in Headers */, 213AEA1B2D35A6120067FD5F /* ARTWrapperSDKProxyOptions.h in Headers */, D798556123ECCDAF00946BE2 /* ARTVCDiffDecoder.h in Headers */, 21E1C0E62A0DC47400A5DB65 /* ARTWebSocketFactory.h in Headers */, @@ -2539,6 +2575,8 @@ D710D48F21949AAE008F54AD /* ARTRest.h in Headers */, D710D5B821949D4F008F54AD /* ARTAuthOptions+Private.h in Headers */, 2124B79429DB13C400AD8361 /* ARTInternalLog.h in Headers */, + 21AC0CCC2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h in Headers */, + 21AC0CCD2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h in Headers */, D710D51D21949C42008F54AD /* ARTDeviceStorage.h in Headers */, D710D60D21949DDB008F54AD /* ARTDataQuery.h in Headers */, D710D58221949D28008F54AD /* ARTTokenParams.h in Headers */, @@ -2684,6 +2722,8 @@ 21447D45254A2ED100B3905A /* ARTSRWebSocket.h in Headers */, EBB721CD2376B454001C3550 /* ARTURLSession.h in Headers */, D710D4D021949BB3008F54AD /* ARTWebSocketTransport+Private.h in Headers */, + 21AC0CC22D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannel.h in Headers */, + 21AC0CC32D4AA0630030BD23 /* ARTWrapperSDKProxyRealtimeChannels.h in Headers */, 213AEA1A2D35A6120067FD5F /* ARTWrapperSDKProxyOptions.h in Headers */, D710D57221949CBA008F54AD /* ARTPushChannelSubscriptions.h in Headers */, 21E1C0E72A0DC47400A5DB65 /* ARTWebSocketFactory.h in Headers */, @@ -2715,6 +2755,8 @@ D710D49121949AAF008F54AD /* ARTRest.h in Headers */, D710D5C821949D50008F54AD /* ARTAuthOptions+Private.h in Headers */, 2124B79529DB13C400AD8361 /* ARTInternalLog.h in Headers */, + 21AC0CCA2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannel+Private.h in Headers */, + 21AC0CCB2D4AA0F50030BD23 /* ARTWrapperSDKProxyRealtimeChannels+Private.h in Headers */, D710D52F21949C44008F54AD /* ARTDeviceStorage.h in Headers */, D710D61721949DDC008F54AD /* ARTDataQuery.h in Headers */, D710D5A821949D2A008F54AD /* ARTTokenParams.h in Headers */, @@ -3222,6 +3264,8 @@ 217FCF3A29D626C1006E5F2D /* ARTJitterCoefficientGenerator.m in Sources */, 96BF61591A35B52C004CF2B3 /* ARTHttp.m in Sources */, 217D1833254222F600DFF07E /* ARTSRPinningSecurityPolicy.m in Sources */, + 21AC0CD22D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m in Sources */, + 21AC0CD32D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m in Sources */, D3AD0EBE215E2FB000312105 /* ARTNSString+ARTUtil.m in Sources */, D73692001DB788C40062C150 /* ARTAuthDetails.m in Sources */, D71966EB1E5E006E000974DD /* ARTPushActivationState.m in Sources */, @@ -3468,6 +3512,8 @@ 210F67A729E9D93D007B9345 /* ARTRealtimeTransportFactory.m in Sources */, 217FCF3B29D626C1006E5F2D /* ARTJitterCoefficientGenerator.m in Sources */, D710D4C821949BAA008F54AD /* ARTRealtimeTransport.m in Sources */, + 21AC0CD62D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m in Sources */, + 21AC0CD72D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m in Sources */, 217D184A254222F700DFF07E /* ARTSRPinningSecurityPolicy.m in Sources */, D710D49821949ACA008F54AD /* ARTRestChannel.m in Sources */, D710D49A21949ACA008F54AD /* ARTRestPresence.m in Sources */, @@ -3594,6 +3640,8 @@ 210F67A829E9D93D007B9345 /* ARTRealtimeTransportFactory.m in Sources */, 217FCF3C29D626C1006E5F2D /* ARTJitterCoefficientGenerator.m in Sources */, D710D4CC21949BAB008F54AD /* ARTRealtimeTransport.m in Sources */, + 21AC0CD42D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannel.m in Sources */, + 21AC0CD52D4AA3200030BD23 /* ARTWrapperSDKProxyRealtimeChannels.m in Sources */, 217D1861254222FA00DFF07E /* ARTSRPinningSecurityPolicy.m in Sources */, D710D4A221949ACB008F54AD /* ARTRestChannel.m in Sources */, D710D4A421949ACB008F54AD /* ARTRestPresence.m in Sources */, diff --git a/Source/ARTWrapperSDKProxyRealtime.m b/Source/ARTWrapperSDKProxyRealtime.m index 61595c1f8..46d1193a9 100644 --- a/Source/ARTWrapperSDKProxyRealtime.m +++ b/Source/ARTWrapperSDKProxyRealtime.m @@ -1,5 +1,5 @@ -#import "ARTWrapperSDKProxyRealtime.h" #import "ARTWrapperSDKProxyRealtime+Private.h" +#import "ARTWrapperSDKProxyRealtimeChannels+Private.h" #import "ARTWrapperSDKProxyOptions.h" #import "ARTRealtime+Private.h" @@ -21,6 +21,8 @@ - (instancetype)initWithRealtime:(ARTRealtime *)realtime if (self = [super init]) { _underlyingRealtime = realtime; _proxyOptions = proxyOptions; + _channels = [[ARTWrapperSDKProxyRealtimeChannels alloc] initWithChannels:realtime.channels + proxyOptions:proxyOptions]; } return self; @@ -30,10 +32,6 @@ - (ARTConnection *)connection { return self.underlyingRealtime.connection; } -- (ARTRealtimeChannels *)channels { - return self.underlyingRealtime.channels; -} - - (ARTPush *)push { return self.underlyingRealtime.push; } diff --git a/Source/ARTWrapperSDKProxyRealtimeChannel.m b/Source/ARTWrapperSDKProxyRealtimeChannel.m new file mode 100644 index 000000000..76e1fb82a --- /dev/null +++ b/Source/ARTWrapperSDKProxyRealtimeChannel.m @@ -0,0 +1,180 @@ +#import "ARTWrapperSDKProxyRealtimeChannel+Private.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTWrapperSDKProxyRealtimeChannel () + +@property (nonatomic, readonly) ARTRealtimeChannel *underlyingChannel; +@property (nonatomic, readonly) ARTWrapperSDKProxyOptions *proxyOptions; + +@end + +NS_ASSUME_NONNULL_END + +@implementation ARTWrapperSDKProxyRealtimeChannel + +- (instancetype)initWithChannel:(ARTRealtimeChannel *)channel proxyOptions:(ARTWrapperSDKProxyOptions *)proxyOptions { + if (self = [super init]) { + _underlyingChannel = channel; + _proxyOptions = proxyOptions; + } + + return self; +} + +- (ARTErrorInfo *)errorReason { + return self.underlyingChannel.errorReason; +} + +- (NSString *)name { + return self.underlyingChannel.name; +} + +- (ARTRealtimeChannelOptions *)getOptions { + return self.underlyingChannel.options; +} + +- (id)presence { + return self.underlyingChannel.presence; +} + +- (ARTChannelProperties *)properties { + return self.underlyingChannel.properties; +} + +- (ARTRealtimeChannelState)state { + return self.underlyingChannel.state; + +} + +#if TARGET_OS_IOS +- (ARTPushChannel *)push { + return self.underlyingChannel.push; +} +#endif + +- (void)history:(nonnull ARTPaginatedMessagesCallback)callback { + [self.underlyingChannel history:callback]; +} + +- (void)publish:(nonnull NSArray *)messages { + [self.underlyingChannel publish:messages]; +} + +- (void)publish:(nonnull NSArray *)messages callback:(nullable ARTCallback)callback { + [self.underlyingChannel publish:messages callback:callback]; +} + +- (void)publish:(nullable NSString *)name data:(nullable id)data { + [self.underlyingChannel publish:name data:data]; +} + +- (void)publish:(nullable NSString *)name data:(nullable id)data callback:(nullable ARTCallback)callback { + [self.underlyingChannel publish:name data:data callback:callback]; +} + +- (void)publish:(nullable NSString *)name data:(nullable id)data clientId:(nonnull NSString *)clientId { + [self.underlyingChannel publish:name data:data clientId:clientId]; +} + +- (void)publish:(nullable NSString *)name data:(nullable id)data clientId:(nonnull NSString *)clientId callback:(nullable ARTCallback)callback { + [self.underlyingChannel publish:name data:data clientId:clientId callback:callback]; +} + +- (void)publish:(nullable NSString *)name data:(nullable id)data clientId:(nonnull NSString *)clientId extras:(nullable id)extras { + [self.underlyingChannel publish:name data:data clientId:clientId extras:extras]; +} + +- (void)publish:(nullable NSString *)name data:(nullable id)data clientId:(nonnull NSString *)clientId extras:(nullable id)extras callback:(nullable ARTCallback)callback { + [self.underlyingChannel publish:name data:data clientId:clientId extras:extras callback:callback]; +} + +- (void)publish:(nullable NSString *)name data:(nullable id)data extras:(nullable id)extras { + [self.underlyingChannel publish:name data:data extras:extras]; +} + +- (void)publish:(nullable NSString *)name data:(nullable id)data extras:(nullable id)extras callback:(nullable ARTCallback)callback { + [self.underlyingChannel publish:name data:data extras:extras callback:callback]; +} + +- (void)attach { + [self.underlyingChannel attach]; +} + +- (void)attach:(nullable ARTCallback)callback { + [self.underlyingChannel attach:callback]; +} + +- (void)detach { + [self.underlyingChannel detach]; +} + +- (void)detach:(nullable ARTCallback)callback { + [self.underlyingChannel detach:callback]; +} + +- (BOOL)history:(ARTRealtimeHistoryQuery * _Nullable)query callback:(nonnull ARTPaginatedMessagesCallback)callback error:(NSError * _Nullable __autoreleasing * _Nullable)errorPtr { + return [self.underlyingChannel history:query callback:callback error:errorPtr]; +} + +- (void)off { + [self.underlyingChannel off]; +} + +- (void)off:(nonnull ARTEventListener *)listener { + [self.underlyingChannel off:listener]; +} + +- (void)off:(ARTChannelEvent)event listener:(nonnull ARTEventListener *)listener { + [self.underlyingChannel off:event listener:listener]; +} + +- (nonnull ARTEventListener *)on:(nonnull void (^)(ARTChannelStateChange * _Nonnull))cb { + return [self.underlyingChannel on:cb]; +} + +- (nonnull ARTEventListener *)on:(ARTChannelEvent)event callback:(nonnull void (^)(ARTChannelStateChange * _Nonnull))cb { + return [self.underlyingChannel on:event callback:cb]; +} + +- (nonnull ARTEventListener *)once:(nonnull void (^)(ARTChannelStateChange * _Nonnull))cb { + return [self.underlyingChannel once:cb]; +} + +- (nonnull ARTEventListener *)once:(ARTChannelEvent)event callback:(nonnull void (^)(ARTChannelStateChange * _Nonnull))cb { + return [self.underlyingChannel once:event callback:cb]; +} + +- (void)setOptions:(ARTRealtimeChannelOptions * _Nullable)options callback:(nullable ARTCallback)callback { + [self.underlyingChannel setOptions:options callback:callback]; +} + +- (ARTEventListener * _Nullable)subscribe:(nonnull ARTMessageCallback)callback { + return [self.underlyingChannel subscribe:callback]; +} + +- (ARTEventListener * _Nullable)subscribe:(nonnull NSString *)name callback:(nonnull ARTMessageCallback)callback { + return [self.underlyingChannel subscribe:name callback:callback]; +} + +- (ARTEventListener * _Nullable)subscribe:(nonnull NSString *)name onAttach:(nullable ARTCallback)onAttach callback:(nonnull ARTMessageCallback)callback { + return [self.underlyingChannel subscribe:name onAttach:onAttach callback:callback]; +} + +- (ARTEventListener * _Nullable)subscribeWithAttachCallback:(nullable ARTCallback)onAttach callback:(nonnull ARTMessageCallback)callback { + return [self.underlyingChannel subscribeWithAttachCallback:onAttach callback:callback]; +} + +- (void)unsubscribe { + [self.underlyingChannel unsubscribe]; +} + +- (void)unsubscribe:(ARTEventListener * _Nullable)listener { + [self.underlyingChannel unsubscribe:listener]; +} + +- (void)unsubscribe:(nonnull NSString *)name listener:(ARTEventListener * _Nullable)listener { + [self.underlyingChannel unsubscribe:name listener:listener]; +} + +@end diff --git a/Source/ARTWrapperSDKProxyRealtimeChannels.m b/Source/ARTWrapperSDKProxyRealtimeChannels.m new file mode 100644 index 000000000..74a2e994a --- /dev/null +++ b/Source/ARTWrapperSDKProxyRealtimeChannels.m @@ -0,0 +1,49 @@ +#import "ARTWrapperSDKProxyRealtimeChannels+Private.h" +#import "ARTWrapperSDKProxyRealtimeChannel+Private.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTWrapperSDKProxyRealtimeChannels () + +@property (nonatomic, readonly) ARTRealtimeChannels *underlyingChannels; +@property (nonatomic, readonly) ARTWrapperSDKProxyOptions *proxyOptions; + +@end + +NS_ASSUME_NONNULL_END + +@implementation ARTWrapperSDKProxyRealtimeChannels + +- (instancetype)initWithChannels:(ARTRealtimeChannels *)channels proxyOptions:(ARTWrapperSDKProxyOptions *)proxyOptions { + if (self = [super init]) { + _underlyingChannels = channels; + _proxyOptions = proxyOptions; + } + + return self; +} + +- (BOOL)exists:(nonnull NSString *)name { + return [self.underlyingChannels exists:name]; +} + +- (void)release:(nonnull NSString *)name { + [self.underlyingChannels release:name]; +} + +- (void)release:(nonnull NSString *)name callback:(nullable ARTCallback)errorInfo { + [self.underlyingChannels release:name callback:errorInfo]; +} + +- (ARTWrapperSDKProxyRealtimeChannel *)get:(NSString *)name { + ARTRealtimeChannel *channel = [self.underlyingChannels get:name]; + return [[ARTWrapperSDKProxyRealtimeChannel alloc] initWithChannel:channel proxyOptions:self.proxyOptions]; +} + +- (ARTWrapperSDKProxyRealtimeChannel *)get:(NSString *)name options:(ARTRealtimeChannelOptions *)options { + ARTRealtimeChannel *channel = [self.underlyingChannels get:name options:options]; + return [[ARTWrapperSDKProxyRealtimeChannel alloc] initWithChannel:channel proxyOptions:self.proxyOptions]; +} + +@end + diff --git a/Source/Ably.modulemap b/Source/Ably.modulemap index 8ef54530e..5123299a4 100644 --- a/Source/Ably.modulemap +++ b/Source/Ably.modulemap @@ -113,5 +113,7 @@ framework module Ably { header "ARTVCDiffDecoder.h" header "ARTDeltaCodec.h" header "ARTWrapperSDKProxyRealtime+Private.h" + header "ARTWrapperSDKProxyRealtimeChannels+Private.h" + header "ARTWrapperSDKProxyRealtimeChannel+Private.h" } } diff --git a/Source/PrivateHeaders/Ably/ARTWrapperSDKProxyRealtimeChannel+Private.h b/Source/PrivateHeaders/Ably/ARTWrapperSDKProxyRealtimeChannel+Private.h new file mode 100644 index 000000000..644bbc872 --- /dev/null +++ b/Source/PrivateHeaders/Ably/ARTWrapperSDKProxyRealtimeChannel+Private.h @@ -0,0 +1,14 @@ +#import + +@class ARTWrapperSDKProxyOptions; + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTWrapperSDKProxyRealtimeChannel () + +- (instancetype)initWithChannel:(ARTRealtimeChannel *)channel + proxyOptions:(ARTWrapperSDKProxyOptions *)proxyOptions NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/PrivateHeaders/Ably/ARTWrapperSDKProxyRealtimeChannels+Private.h b/Source/PrivateHeaders/Ably/ARTWrapperSDKProxyRealtimeChannels+Private.h new file mode 100644 index 000000000..229f7ba2f --- /dev/null +++ b/Source/PrivateHeaders/Ably/ARTWrapperSDKProxyRealtimeChannels+Private.h @@ -0,0 +1,14 @@ +#import + +@class ARTWrapperSDKProxyOptions; + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTWrapperSDKProxyRealtimeChannels () + +- (instancetype)initWithChannels:(ARTRealtimeChannels *)channels + proxyOptions:(ARTWrapperSDKProxyOptions *)proxyOptions NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/include/Ably.modulemap b/Source/include/Ably.modulemap index d10e4f81d..d891ce3b9 100644 --- a/Source/include/Ably.modulemap +++ b/Source/include/Ably.modulemap @@ -111,5 +111,7 @@ framework module Ably { header "Ably/ARTVCDiffDecoder.h" header "Ably/ARTDeltaCodec.h" header "Ably/ARTWrapperSDKProxyRealtime+Private.h" + header "Ably/ARTWrapperSDKProxyRealtimeChannels+Private.h" + header "Ably/ARTWrapperSDKProxyRealtimeChannel+Private.h" } } diff --git a/Source/include/Ably/ARTWrapperSDKProxyRealtime.h b/Source/include/Ably/ARTWrapperSDKProxyRealtime.h index 7d3181208..2250df095 100644 --- a/Source/include/Ably/ARTWrapperSDKProxyRealtime.h +++ b/Source/include/Ably/ARTWrapperSDKProxyRealtime.h @@ -2,7 +2,7 @@ #import @class ARTConnection; -@class ARTRealtimeChannels; +@class ARTWrapperSDKProxyRealtimeChannels; @class ARTPush; @class ARTAuth; @@ -19,7 +19,7 @@ NS_SWIFT_SENDABLE - (instancetype)init NS_UNAVAILABLE; @property (readonly) ARTConnection *connection; -@property (readonly) ARTRealtimeChannels *channels; +@property (readonly) ARTWrapperSDKProxyRealtimeChannels *channels; @property (readonly) ARTPush *push; @property (readonly) ARTAuth *auth; diff --git a/Source/include/Ably/ARTWrapperSDKProxyRealtimeChannel.h b/Source/include/Ably/ARTWrapperSDKProxyRealtimeChannel.h new file mode 100644 index 000000000..77bad352c --- /dev/null +++ b/Source/include/Ably/ARTWrapperSDKProxyRealtimeChannel.h @@ -0,0 +1,26 @@ +#import +#import + +@class ARTWrapperSDKProxyRealtimeChannel; + +NS_ASSUME_NONNULL_BEGIN + +/** + * An object which wraps an instance of `ARTRealtimeChannel` and provides a similar API. It allows Ably-authored wrapper SDKs to send analytics information so that Ably can track the usage of the wrapper SDK. + * + * - Important: This class should only be used by Ably-authored SDKs. + */ +NS_SWIFT_SENDABLE +@interface ARTWrapperSDKProxyRealtimeChannel : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@property (readonly) ARTRealtimePresence *presence; + +#if TARGET_OS_IPHONE +@property (readonly) ARTPushChannel *push; +#endif + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/include/Ably/ARTWrapperSDKProxyRealtimeChannels.h b/Source/include/Ably/ARTWrapperSDKProxyRealtimeChannels.h new file mode 100644 index 000000000..593da5d64 --- /dev/null +++ b/Source/include/Ably/ARTWrapperSDKProxyRealtimeChannels.h @@ -0,0 +1,23 @@ +#import +#import + +@class ARTWrapperSDKProxyRealtimeChannel; + +NS_ASSUME_NONNULL_BEGIN + +/** + * An object which wraps an instance of `ARTRealtimeChannels` and provides a similar API. It allows Ably-authored wrapper SDKs to send analytics information so that Ably can track the usage of the wrapper SDK. + * + * - Important: This class should only be used by Ably-authored SDKs. + */ +NS_SWIFT_SENDABLE +@interface ARTWrapperSDKProxyRealtimeChannels : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (ARTWrapperSDKProxyRealtimeChannel *)get:(NSString *)name; +- (ARTWrapperSDKProxyRealtimeChannel *)get:(NSString *)name options:(ARTRealtimeChannelOptions *)options; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/include/Ably/AblyInternal.h b/Source/include/Ably/AblyInternal.h index 7c7e4540a..8028836ed 100644 --- a/Source/include/Ably/AblyInternal.h +++ b/Source/include/Ably/AblyInternal.h @@ -3,3 +3,5 @@ #import #import #import +#import +#import diff --git a/Test/Tests/WrapperSDKProxyTests.swift b/Test/Tests/WrapperSDKProxyTests.swift index c52ce70b3..b843c0b7f 100644 --- a/Test/Tests/WrapperSDKProxyTests.swift +++ b/Test/Tests/WrapperSDKProxyTests.swift @@ -72,6 +72,114 @@ class WrapperSDKProxyTests: XCTestCase { XCTAssertFalse(underlyingConnectionReceivedEvent) } + // MARK: - Testing that channel state is shared with underlying Realtime client + + func test_sharesChannelStateWithUnderlyingChannel() throws { + // Given: A channel fetched from a proxy client + let test = Test() + let options = try AblyTests.commonAppSetup(for: test) + let client = AblyTests.newRealtime(options).client + defer { client.dispose(); client.close() } + + let proxyClient = client.createWrapperSDKProxy(with: .init(agents: ["some-agent": test.id.uuidString])) + + let channelName = test.uniqueChannelName() + let channel = client.channels.get(channelName) + let proxyChannel = proxyClient.channels.get(channelName) + + // (An example of changes to the underlying channel being reflected in the proxy channel) + // + // When: The underlying connection becomes CONNECTED + // Then: So does the proxy channel + waitUntil(timeout: testTimeout) { done in + proxyChannel.on(.attached) { _ in + done() + } + + channel.attach() + } + XCTAssertEqual(proxyChannel.state, .attached) + + // (An example of the proxy channel provoking changes in the underlying channel) + // + // When: We call `detach()` on the proxy channel + // Then: It detaches the underlying channel + waitUntil(timeout: testTimeout) { done in + channel.on(.detached) { _ in + done() + } + + proxyChannel.detach() + } + XCTAssertEqual(channel.state, .detached) + } + + func test_sharesChannelStateSubscriptionsWithUnderlyingChannel() throws { + // Given: A channel fetched from a proxy client + let test = Test() + let options = try AblyTests.commonAppSetup(for: test) + let client = AblyTests.newRealtime(options).client + defer { client.dispose(); client.close() } + + let proxyClient = client.createWrapperSDKProxy(with: .init(agents: ["some-agent": test.id.uuidString])) + + let channelName = test.uniqueChannelName() + let channel = client.channels.get(channelName) + let proxyChannel = proxyClient.channels.get(channelName) + + // When: We add a channel state event listener to the underlying channel, then call `off()` on the proxy channel and provoke a channel state event + var underlyingChannelReceivedEvent = false + channel.on(.attached) { _ in + underlyingChannelReceivedEvent = true + } + + proxyChannel.off() + + // this is the "provoke a channel state event" above + waitUntil(timeout: testTimeout) { done in + channel.on(.attached) { _ in + done() + } + channel.attach() + } + + // Then: We do not receive the channel state event on the aforementioned listener, because calling `off()` on the proxy channel removed the listener + XCTAssertFalse(underlyingChannelReceivedEvent) + } + + func test_sharesChannelMessageSubscriptionsWithUnderlyingChannel() throws { + // Given: A channel fetched from a proxy client + let test = Test() + let options = try AblyTests.commonAppSetup(for: test) + let client = AblyTests.newRealtime(options).client + defer { client.dispose(); client.close() } + + let proxyClient = client.createWrapperSDKProxy(with: .init(agents: ["some-agent": test.id.uuidString])) + + let channelName = test.uniqueChannelName() + let channel = client.channels.get(channelName) + let proxyChannel = proxyClient.channels.get(channelName) + + // When: We add a message listener to the underlying channel, then call `unsubscribe()` on the proxy channel and send a message on the channel + var underlyingChannelReceivedMessage = false + channel.subscribe { _ in + underlyingChannelReceivedMessage = true + } + + proxyChannel.unsubscribe() + + // this is the "send a message on the channel" above + waitUntil(timeout: testTimeout) { done in + channel.subscribe { _ in + done() + } + channel.publish(nil, data: nil) + } + + // Then: We do not receive the message on the aforementioned listener, because calling `unsubscribe()` on the proxy channel removed the listener + XCTAssertFalse(underlyingChannelReceivedMessage) + } + // MARK: - `request()` func test_request_addsWrapperSDKAgentToHeader() throws {