diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 4dc7c17a2..366f58362 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -9,8 +9,6 @@ /* Begin PBXBuildFile section */ 1C05CF201AC1D7EB00687AC9 /* ARTRealtime+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C05CF1E1AC1D7EB00687AC9 /* ARTRealtime+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 1C1EC3FA1AE26A8B00AAADD7 /* ARTStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 96BF61551A35B40E004CF2B3 /* ARTStatus.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1C2B0FFD1B136A6D00E3633C /* ARTPresenceMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C2B0FFB1B136A6D00E3633C /* ARTPresenceMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 1C2B0FFE1B136A6D00E3633C /* ARTPresenceMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2B0FFC1B136A6D00E3633C /* ARTPresenceMap.m */; }; 1C55427D1B148306003068DB /* ARTStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C55427C1B148306003068DB /* ARTStatus.m */; }; 1C578E1F1B3435CA00EF46EC /* ARTFallback.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C578E1D1B3435CA00EF46EC /* ARTFallback.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1C578E201B3435CA00EF46EC /* ARTFallback.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C578E1E1B3435CA00EF46EC /* ARTFallback.m */; }; @@ -69,15 +67,15 @@ 21113B5529DC6ACD00652C86 /* ARTTestClientOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5429DC6ACD00652C86 /* ARTTestClientOptions.m */; }; 21113B5629DC6ACD00652C86 /* ARTTestClientOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5429DC6ACD00652C86 /* ARTTestClientOptions.m */; }; 21113B5729DC6ACD00652C86 /* ARTTestClientOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5429DC6ACD00652C86 /* ARTTestClientOptions.m */; }; + 21113B5929DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; + 21113B5A29DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; + 21113B5B29DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; 21113B5F29DDDDD000652C86 /* LogAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5E29DDDDD000652C86 /* LogAdapterTests.swift */; }; 21113B6029DDDDD000652C86 /* LogAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5E29DDDDD000652C86 /* LogAdapterTests.swift */; }; 21113B6129DDDDD000652C86 /* LogAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5E29DDDDD000652C86 /* LogAdapterTests.swift */; }; 21113B6329DDF7E800652C86 /* ARTInternalLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B6229DDF7E800652C86 /* ARTInternalLogTests.m */; }; 21113B6429DDF7ED00652C86 /* ARTInternalLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B6229DDF7E800652C86 /* ARTInternalLogTests.m */; }; 21113B6529DDF7EF00652C86 /* ARTInternalLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B6229DDF7E800652C86 /* ARTInternalLogTests.m */; }; - 21113B5929DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; - 21113B5A29DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; - 21113B5B29DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; 211A60D729D6D2C300D169C5 /* BackoffRetryDelayCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C31829D5E574000C4355 /* BackoffRetryDelayCalculatorTests.swift */; }; 211A60D829D6D2C400D169C5 /* BackoffRetryDelayCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C31829D5E574000C4355 /* BackoffRetryDelayCalculatorTests.swift */; }; 211A60D929D6D2C500D169C5 /* BackoffRetryDelayCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C31829D5E574000C4355 /* BackoffRetryDelayCalculatorTests.swift */; }; @@ -644,7 +642,6 @@ D710D58A21949D29008F54AD /* ARTMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = D746AE361BBC3201003ECEF8 /* ARTMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; D710D58B21949D29008F54AD /* ARTPresence.h in Headers */ = {isa = PBXBuildFile; fileRef = D746AE261BBB61C9003ECEF8 /* ARTPresence.h */; settings = {ATTRIBUTES = (Public, ); }; }; D710D58C21949D29008F54AD /* ARTPresenceMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 96A5079F1A377AA50077CDF8 /* ARTPresenceMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D710D58D21949D29008F54AD /* ARTPresenceMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C2B0FFB1B136A6D00E3633C /* ARTPresenceMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; D710D58E21949D29008F54AD /* ARTDataEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = EB3239461C59AB2C00892664 /* ARTDataEncoder.h */; settings = {ATTRIBUTES = (Private, ); }; }; D710D58F21949D29008F54AD /* ARTStats.h in Headers */ = {isa = PBXBuildFile; fileRef = 96A507931A370F860077CDF8 /* ARTStats.h */; settings = {ATTRIBUTES = (Public, ); }; }; D710D59021949D29008F54AD /* ARTStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 96BF61551A35B40E004CF2B3 /* ARTStatus.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -663,7 +660,6 @@ D710D5B021949D2A008F54AD /* ARTMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = D746AE361BBC3201003ECEF8 /* ARTMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; D710D5B121949D2A008F54AD /* ARTPresence.h in Headers */ = {isa = PBXBuildFile; fileRef = D746AE261BBB61C9003ECEF8 /* ARTPresence.h */; settings = {ATTRIBUTES = (Public, ); }; }; D710D5B221949D2A008F54AD /* ARTPresenceMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 96A5079F1A377AA50077CDF8 /* ARTPresenceMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D710D5B321949D2A008F54AD /* ARTPresenceMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C2B0FFB1B136A6D00E3633C /* ARTPresenceMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; D710D5B421949D2A008F54AD /* ARTDataEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = EB3239461C59AB2C00892664 /* ARTDataEncoder.h */; settings = {ATTRIBUTES = (Private, ); }; }; D710D5B521949D2A008F54AD /* ARTStats.h in Headers */ = {isa = PBXBuildFile; fileRef = 96A507931A370F860077CDF8 /* ARTStats.h */; settings = {ATTRIBUTES = (Public, ); }; }; D710D5B621949D2A008F54AD /* ARTStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 96BF61551A35B40E004CF2B3 /* ARTStatus.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -698,7 +694,6 @@ D710D5DB21949D78008F54AD /* ARTMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = D746AE371BBC3201003ECEF8 /* ARTMessage.m */; }; D710D5DC21949D78008F54AD /* ARTPresence.m in Sources */ = {isa = PBXBuildFile; fileRef = D746AE271BBB61C9003ECEF8 /* ARTPresence.m */; }; D710D5DD21949D78008F54AD /* ARTPresenceMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 96A507A01A377AA50077CDF8 /* ARTPresenceMessage.m */; }; - D710D5DE21949D78008F54AD /* ARTPresenceMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2B0FFC1B136A6D00E3633C /* ARTPresenceMap.m */; }; D710D5DF21949D78008F54AD /* ARTDataEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = EB3239421C59AB0400892664 /* ARTDataEncoder.m */; }; D710D5E021949D78008F54AD /* ARTStats.m in Sources */ = {isa = PBXBuildFile; fileRef = 96A507941A370F860077CDF8 /* ARTStats.m */; }; D710D5E121949D78008F54AD /* ARTStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C55427C1B148306003068DB /* ARTStatus.m */; }; @@ -717,7 +712,6 @@ D710D60121949D79008F54AD /* ARTMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = D746AE371BBC3201003ECEF8 /* ARTMessage.m */; }; D710D60221949D79008F54AD /* ARTPresence.m in Sources */ = {isa = PBXBuildFile; fileRef = D746AE271BBB61C9003ECEF8 /* ARTPresence.m */; }; D710D60321949D79008F54AD /* ARTPresenceMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 96A507A01A377AA50077CDF8 /* ARTPresenceMessage.m */; }; - D710D60421949D79008F54AD /* ARTPresenceMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2B0FFC1B136A6D00E3633C /* ARTPresenceMap.m */; }; D710D60521949D79008F54AD /* ARTDataEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = EB3239421C59AB0400892664 /* ARTDataEncoder.m */; }; D710D60621949D79008F54AD /* ARTStats.m in Sources */ = {isa = PBXBuildFile; fileRef = 96A507941A370F860077CDF8 /* ARTStats.m */; }; D710D60721949D79008F54AD /* ARTStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C55427C1B148306003068DB /* ARTStatus.m */; }; @@ -1114,8 +1108,6 @@ /* Begin PBXFileReference section */ 1C05CF1E1AC1D7EB00687AC9 /* ARTRealtime+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ARTRealtime+Private.h"; path = "PrivateHeaders/Ably/ARTRealtime+Private.h"; sourceTree = ""; }; 1C118A5B1AE63D89006AD19E /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; - 1C2B0FFB1B136A6D00E3633C /* ARTPresenceMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ARTPresenceMap.h; path = PrivateHeaders/Ably/ARTPresenceMap.h; sourceTree = ""; }; - 1C2B0FFC1B136A6D00E3633C /* ARTPresenceMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPresenceMap.m; sourceTree = ""; }; 1C55427C1B148306003068DB /* ARTStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTStatus.m; sourceTree = ""; }; 1C578E1D1B3435CA00EF46EC /* ARTFallback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ARTFallback.h; path = include/Ably/ARTFallback.h; sourceTree = ""; }; 1C578E1E1B3435CA00EF46EC /* ARTFallback.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTFallback.m; sourceTree = ""; }; @@ -1140,6 +1132,7 @@ 21113B4829DB60F800652C86 /* MockRetryDelayCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRetryDelayCalculator.swift; sourceTree = ""; }; 21113B5029DC6AAF00652C86 /* ARTTestClientOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTTestClientOptions.h; path = PrivateHeaders/Ably/ARTTestClientOptions.h; sourceTree = ""; }; 21113B5429DC6ACD00652C86 /* ARTTestClientOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTTestClientOptions.m; sourceTree = ""; }; + 21113B5829DCA4C700652C86 /* DataGatherer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGatherer.swift; sourceTree = ""; }; 21113B5E29DDDDD000652C86 /* LogAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogAdapterTests.swift; sourceTree = ""; }; 21113B6229DDF7E800652C86 /* ARTInternalLogTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTInternalLogTests.m; sourceTree = ""; }; 21113B5829DCA4C700652C86 /* DataGatherer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGatherer.swift; sourceTree = ""; }; @@ -1225,7 +1218,7 @@ 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 = ""; }; - 21DCDA8229F818630073A211 /* Ably-iOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Ably-iOS.xctestplan"; path = "Test/Ably-iOS.xctestplan"; sourceTree = SOURCE_ROOT; }; + 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 = ""; }; 21E1C0E42A0DC47400A5DB65 /* ARTWebSocketFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTWebSocketFactory.h; path = PrivateHeaders/ARTWebSocketFactory.h; sourceTree = ""; }; @@ -2016,8 +2009,6 @@ 96A5079F1A377AA50077CDF8 /* ARTPresenceMessage.h */, D7F2B8B11E42410D00B65151 /* ARTPresenceMessage+Private.h */, 96A507A01A377AA50077CDF8 /* ARTPresenceMessage.m */, - 1C2B0FFB1B136A6D00E3633C /* ARTPresenceMap.h */, - 1C2B0FFC1B136A6D00E3633C /* ARTPresenceMap.m */, EB3239461C59AB2C00892664 /* ARTDataEncoder.h */, EB3239421C59AB0400892664 /* ARTDataEncoder.m */, 96A507931A370F860077CDF8 /* ARTStats.h */, @@ -2268,7 +2259,6 @@ 2105ED2229E7429E00DE6D67 /* ARTPaginatedResult+Subclass.h in Headers */, D746AE381BBC3201003ECEF8 /* ARTMessage.h in Headers */, D746AE471BBD6FE9003ECEF8 /* ARTQueuedMessage.h in Headers */, - 1C2B0FFD1B136A6D00E3633C /* ARTPresenceMap.h in Headers */, D798556023ECCDAF00946BE2 /* ARTVCDiffDecoder.h in Headers */, EB9C530B1CD7BEB100.8.557 /* ARTJsonLikeEncoder.h in Headers */, D74CBC0E212F076000D090E4 /* ARTConstants.h in Headers */, @@ -2511,7 +2501,6 @@ D710D4C321949B9C008F54AD /* ARTWebSocketTransport.h in Headers */, D710D4D621949BF9008F54AD /* ARTConnection.h in Headers */, D710D50421949C18008F54AD /* ARTRealtime+Private.h in Headers */, - D710D58D21949D29008F54AD /* ARTPresenceMap.h in Headers */, D5BB210E26AA98A800AA5F3E /* ARTStringifiable.h in Headers */, D710D56B21949CB9008F54AD /* ARTPushDeviceRegistrations.h in Headers */, 2132C31229D5E4C6000C4355 /* ARTBackoffRetryDelayCalculator.h in Headers */, @@ -2679,7 +2668,6 @@ D710D4C721949B9D008F54AD /* ARTWebSocketTransport.h in Headers */, D710D4E621949BFB008F54AD /* ARTConnection.h in Headers */, D710D51021949C19008F54AD /* ARTRealtime+Private.h in Headers */, - D710D5B321949D2A008F54AD /* ARTPresenceMap.h in Headers */, D710D57121949CBA008F54AD /* ARTPushDeviceRegistrations.h in Headers */, D710D62C21949DED008F54AD /* ARTNSMutableURLRequest+ARTPaginated.h in Headers */, 2132C31329D5E4C6000C4355 /* ARTBackoffRetryDelayCalculator.h in Headers */, @@ -3112,7 +3100,6 @@ D7D8F8261BC2C691009718F2 /* ARTTokenDetails.m in Sources */, D75A3F1C1DDE5B62002A4AAD /* ARTGCD.m in Sources */, 215F75FB2922B1DB009E0E76 /* ARTClientInformation.m in Sources */, - 1C2B0FFE1B136A6D00E3633C /* ARTPresenceMap.m in Sources */, 217D182A254222F500DFF07E /* ARTSRRandom.m in Sources */, 960D07941A45F1D800ED8C8C /* ARTCrypto.m in Sources */, D777EEE52063A64E002EBA03 /* ARTNSMutableRequest+ARTPush.m in Sources */, @@ -3368,7 +3355,6 @@ D710D4C821949BAA008F54AD /* ARTRealtimeTransport.m in Sources */, 217D184A254222F700DFF07E /* ARTSRPinningSecurityPolicy.m in Sources */, D710D49821949ACA008F54AD /* ARTRestChannel.m in Sources */, - D710D5DE21949D78008F54AD /* ARTPresenceMap.m in Sources */, D710D49A21949ACA008F54AD /* ARTRestPresence.m in Sources */, 21113B5629DC6ACD00652C86 /* ARTTestClientOptions.m in Sources */, D710D66E21949E78008F54AD /* ARTNSDate+ARTUtil.m in Sources */, @@ -3493,7 +3479,6 @@ D710D4CC21949BAB008F54AD /* ARTRealtimeTransport.m in Sources */, 217D1861254222FA00DFF07E /* ARTSRPinningSecurityPolicy.m in Sources */, D710D4A221949ACB008F54AD /* ARTRestChannel.m in Sources */, - D710D60421949D79008F54AD /* ARTPresenceMap.m in Sources */, D710D4A421949ACB008F54AD /* ARTRestPresence.m in Sources */, 21113B5729DC6ACD00652C86 /* ARTTestClientOptions.m in Sources */, D710D65421949E77008F54AD /* ARTNSDate+ARTUtil.m in Sources */, diff --git a/Source/ARTConnection.m b/Source/ARTConnection.m index 2da822fd9..c708d585b 100644 --- a/Source/ARTConnection.m +++ b/Source/ARTConnection.m @@ -271,7 +271,7 @@ - (NSString *)createRecoveryKey_nosync { NSMutableDictionary *channelSerials = [NSMutableDictionary new]; for (ARTRealtimeChannelInternal *const channel in _realtime.channels.nosyncIterable) { if (channel.state_nosync == ARTRealtimeChannelAttached) { - channelSerials[channel.name] = channel.serial; + channelSerials[channel.name] = channel.channelSerial; } } diff --git a/Source/ARTPresenceMap.m b/Source/ARTPresenceMap.m deleted file mode 100644 index 7649c9071..000000000 --- a/Source/ARTPresenceMap.m +++ /dev/null @@ -1,228 +0,0 @@ -#import "ARTPresenceMap.h" -#import "ARTPresenceMessage.h" -#import "ARTPresenceMessage+Private.h" -#import "ARTEventEmitter+Private.h" -#import "ARTInternalLog.h" - -typedef NS_ENUM(NSUInteger, ARTPresenceSyncState) { - ARTPresenceSyncInitialized, - ARTPresenceSyncStarted, //ItemType: nil - ARTPresenceSyncEnded, //ItemType: NSArray* - ARTPresenceSyncFailed //ItemType: ARTErrorInfo* -}; - -NSString *ARTPresenceSyncStateToStr(ARTPresenceSyncState state) { - switch (state) { - case ARTPresenceSyncInitialized: - return @"Initialized"; //0 - case ARTPresenceSyncStarted: - return @"Started"; //1 - case ARTPresenceSyncEnded: - return @"Ended"; //2 - case ARTPresenceSyncFailed: - return @"Failed"; //3 - } -} - -#pragma mark - ARTEvent - -@interface ARTEvent (PresenceSyncState) - -- (instancetype)initWithPresenceSyncState:(ARTPresenceSyncState)value; -+ (instancetype)newWithPresenceSyncState:(ARTPresenceSyncState)value; - -@end - -#pragma mark - ARTPresenceMap - -@interface ARTPresenceMap () { - ARTPresenceSyncState _syncState; - ARTEventEmitter *_syncEventEmitter; - NSMutableDictionary *_members; - NSMutableDictionary *_localMembers; // RTP17h -} - -@end - -@implementation ARTPresenceMap { - ARTInternalLog *_logger; -} - -- (instancetype)initWithQueue:(_Nonnull dispatch_queue_t)queue logger:(ARTInternalLog *)logger { - self = [super init]; - if(self) { - _logger = logger; - [self reset]; - _syncSessionId = 0; - _syncState = ARTPresenceSyncInitialized; - _syncEventEmitter = [[ARTInternalEventEmitter alloc] initWithQueue:queue]; - } - return self; -} - -- (NSDictionary *)members { - return _members; -} - -- (NSDictionary *)localMembers { - return _localMembers; -} - -- (BOOL)add:(ARTPresenceMessage *)message { - ARTPresenceMessage *latest = [_members objectForKey:message.memberKey]; - if ([message isNewerThan:latest]) { - ARTPresenceMessage *messageCopy = [message copy]; - switch (message.action) { - case ARTPresenceEnter: - case ARTPresenceUpdate: - messageCopy.action = ARTPresencePresent; - // intentional fallthrough - case ARTPresencePresent: - [self internalAdd:messageCopy]; - break; - case ARTPresenceLeave: - [self internalRemove:messageCopy]; - break; - default: - break; - } - return YES; - } - ARTLogDebug(_logger, @"Presence member \"%@\" with action %@ has been ignored", message.memberKey, ARTPresenceActionToStr(message.action)); - latest.syncSessionId = _syncSessionId; - return NO; -} - -- (void)internalAdd:(ARTPresenceMessage *)message { - [self internalAdd:message withSessionId:_syncSessionId]; -} - -- (void)internalAdd:(ARTPresenceMessage *)message withSessionId:(NSUInteger)sessionId { - message.syncSessionId = sessionId; - [_members setObject:message forKey:message.memberKey]; - // Local member - if ([message.connectionId isEqualToString:self.delegate.connectionId]) { - _localMembers[message.clientId] = message; - ARTLogDebug(_logger, @"local member %@ with action %@ has been added", message.memberKey, ARTPresenceActionToStr(message.action).uppercaseString); - } -} - -- (void)internalRemove:(ARTPresenceMessage *)message { - [self internalRemove:message force:false]; -} - -- (void)internalRemove:(ARTPresenceMessage *)message force:(BOOL)force { - if ([message.connectionId isEqualToString:self.delegate.connectionId] && !message.isSynthesized) { - [_localMembers removeObjectForKey:message.clientId]; - } - - const BOOL syncInProgress = self.syncInProgress; - if (!force && syncInProgress) { - ARTLogDebug(_logger, @"%p \"%@\" should be removed after sync ends (syncInProgress=%d)", self, message.clientId, syncInProgress); - message.action = ARTPresenceAbsent; - // Should be removed after Sync ends - [self internalAdd:message withSessionId:message.syncSessionId]; - } - else { - [_members removeObjectForKey:message.memberKey]; - } -} - -- (void)cleanUpAbsentMembers { - ARTLogDebug(_logger, @"%p cleaning up absent members (syncSessionId=%lu)", self, (unsigned long)_syncSessionId); - NSSet *filteredMembers = [_members keysOfEntriesPassingTest:^BOOL(NSString *key, ARTPresenceMessage *message, BOOL *stop) { - return message.action == ARTPresenceAbsent; - }]; - for (NSString *key in filteredMembers) { - [self internalRemove:[_members objectForKey:key] force:true]; - } -} - -- (void)leaveMembersNotPresentInSync { - ARTLogDebug(_logger, @"%p leaving members not present in sync (syncSessionId=%lu)", self, (unsigned long)_syncSessionId); - for (ARTPresenceMessage *member in [_members allValues]) { - if (member.syncSessionId != _syncSessionId) { - // Handle members that have not been added or updated in the PresenceMap during the sync process - ARTPresenceMessage *leave = [member copy]; - [self internalRemove:member force:true]; - [self.delegate map:self didRemovedMemberNoLongerPresent:leave]; - } - } -} - -- (void)reenterLocalMembers { - ARTLogDebug(_logger, @"%p reentering local members", self); - for (ARTPresenceMessage *localMember in [_localMembers allValues]) { - ARTPresenceMessage *reenter = [localMember copy]; - [self.delegate map:self shouldReenterLocalMember:reenter]; - } - [self cleanUpAbsentMembers]; -} - -- (void)reset { - _members = [NSMutableDictionary new]; - _localMembers = [NSMutableDictionary new]; -} - -- (void)startSync { - ARTLogDebug(_logger, @"%p PresenceMap sync started", self); - _syncSessionId++; - _syncState = ARTPresenceSyncStarted; - [_syncEventEmitter emit:[ARTEvent newWithPresenceSyncState:_syncState] with:nil]; -} - -- (void)endSync { - ARTLogVerbose(_logger, @"%p PresenceMap sync ending", self); - [self cleanUpAbsentMembers]; - [self leaveMembersNotPresentInSync]; - _syncState = ARTPresenceSyncEnded; - - [_syncEventEmitter emit:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncEnded] with:[_members allValues]]; - [_syncEventEmitter off]; - ARTLogDebug(_logger, @"%p PresenceMap sync ended", self); -} - -- (void)failsSync:(ARTErrorInfo *)error { - [self reset]; - _syncState = ARTPresenceSyncFailed; - [_syncEventEmitter emit:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncFailed] with:error]; - [_syncEventEmitter off]; -} - -- (void)onceSyncEnds:(void (^)(NSArray *))callback { - [_syncEventEmitter once:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncEnded] callback:callback]; -} - -- (void)onceSyncFails:(ARTCallback)callback { - [_syncEventEmitter once:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncFailed] callback:callback]; -} - -- (BOOL)syncComplete { - return !(_syncState == ARTPresenceSyncInitialized || _syncState == ARTPresenceSyncStarted); -} - -- (BOOL)syncInProgress { - return _syncState == ARTPresenceSyncStarted; -} - -#pragma mark private - -- (NSString *)memberKey:(ARTPresenceMessage *) message { - return [NSString stringWithFormat:@"%@:%@", message.connectionId, message.clientId]; -} - -@end - -#pragma mark - ARTEvent - -@implementation ARTEvent (PresenceSyncState) - -- (instancetype)initWithPresenceSyncState:(ARTPresenceSyncState)value { - return [self initWithString:[NSString stringWithFormat:@"ARTPresenceSyncState%@", ARTPresenceSyncStateToStr(value)]]; -} - -+ (instancetype)newWithPresenceSyncState:(ARTPresenceSyncState)value { - return [[self alloc] initWithPresenceSyncState:value]; -} - -@end diff --git a/Source/ARTRealtime.m b/Source/ARTRealtime.m index 9a8fbde5f..247bfa72d 100644 --- a/Source/ARTRealtime.m +++ b/Source/ARTRealtime.m @@ -20,7 +20,6 @@ #import "ARTWebSocketTransport+Private.h" #import "ARTOSReachability.h" #import "ARTNSArray+ARTFunctional.h" -#import "ARTPresenceMap.h" #import "ARTProtocolMessage.h" #import "ARTProtocolMessage+Private.h" #import "ARTEventEmitter+Private.h" @@ -254,7 +253,7 @@ - (instancetype)initWithOptions:(ARTClientOptions *)options { _msgSerial = recoveryKey.msgSerial; // RTN16f for (NSString *const channelName in recoveryKey.channelSerials) { ARTRealtimeChannelInternal *const channel = [_channels get:channelName]; - channel.serial = recoveryKey.channelSerials[channelName]; // RTN16j + channel.channelSerial = recoveryKey.channelSerials[channelName]; // RTN16j } } } diff --git a/Source/ARTRealtimeChannel.m b/Source/ARTRealtimeChannel.m index 50a1083fb..fad5bc603 100644 --- a/Source/ARTRealtimeChannel.m +++ b/Source/ARTRealtimeChannel.m @@ -13,7 +13,6 @@ #import "ARTRealtimeChannelOptions.h" #import "ARTProtocolMessage.h" #import "ARTProtocolMessage+Private.h" -#import "ARTPresenceMap.h" #import "ARTNSArray+ARTFunctional.h" #import "ARTStatus.h" #import "ARTDefault.h" @@ -260,11 +259,9 @@ - (instancetype)initWithRealtime:(ARTRealtimeInternal *)realtime andName:(NSStri _restChannel = [_realtime.rest.channels _getChannel:self.name options:options addPrefix:true]; _state = ARTRealtimeChannelInitialized; _attachSerial = nil; - _presenceMap = [[ARTPresenceMap alloc] initWithQueue:_queue logger:self.logger]; - _presenceMap.delegate = self; + _realtimePresence = [[ARTRealtimePresenceInternal alloc] initWithChannel:self logger:self.logger]; _statesEventEmitter = [[ARTPublicEventEmitter alloc] initWithRest:_realtime.rest logger:logger]; _messagesEventEmitter = [[ARTInternalEventEmitter alloc] initWithQueues:_queue userQueue:_userQueue]; - _presenceEventEmitter = [[ARTInternalEventEmitter alloc] initWithQueue:_queue]; _attachedEventEmitter = [[ARTInternalEventEmitter alloc] initWithQueue:_queue]; _detachedEventEmitter = [[ARTInternalEventEmitter alloc] initWithQueue:_queue]; _internalEventEmitter = [[ARTInternalEventEmitter alloc] initWithQueue:_queue]; @@ -313,9 +310,6 @@ - (ARTErrorInfo *)errorReason_nosync { } - (ARTRealtimePresenceInternal *)presence { - if (!_realtimePresence) { - _realtimePresence = [[ARTRealtimePresenceInternal alloc] initWithChannel:self logger:self.logger]; - } return _realtimePresence; } @@ -380,55 +374,6 @@ - (void)internalPostMessages:(id)data callback:(ARTCallback)callback { }); } -- (void)sync { - [self sync:nil]; -} - -- (void)sync:(ARTCallback)callback { - if (callback) { - ARTCallback userCallback = callback; - callback = ^(ARTErrorInfo *__nullable error) { - dispatch_async(self->_userQueue, ^{ - userCallback(error); - }); - }; - } - - switch (self.state_nosync) { - case ARTRealtimeChannelInitialized: - case ARTRealtimeChannelDetaching: - case ARTRealtimeChannelDetached: { - ARTErrorInfo *error = [ARTErrorInfo createWithCode:ARTErrorBadRequest - message:@"Unable to sync to channel; not attached."]; - ARTLogError(self.logger, @"%@", error.message); - if (callback) callback(error); - return; - } - default: - break; - } - - ARTLogVerbose(self.logger, @"R:%p C:%p (%@) requesting a sync operation", _realtime, self, self.name); - - ARTProtocolMessage *msg = [[ARTProtocolMessage alloc] init]; - msg.action = ARTProtocolMessageSync; - msg.channel = self.name; - msg.channelSerial = self.presenceMap.syncChannelSerial; - - [self.presenceMap startSync]; - [self.realtime send:msg sentCallback:^(ARTErrorInfo *error) { - if (error) { - ARTLogDebug(self.logger, @"R:%p C:%p (%@) SYNC request failed with %@", self->_realtime, self, self.name, error); - [self.presenceMap endSync]; - if (callback) callback(error); - } - else { - ARTLogDebug(self.logger, @"R:%p C:%p (%@) SYNC requested with success", self->_realtime, self, self.name); - if (callback) callback(nil); - } - } ackCallback:nil]; -} - - (void)publishProtocolMessage:(ARTProtocolMessage *)pm callback:(ARTStatusCallback)cb { switch (self.state_nosync) { case ARTRealtimeChannelSuspended: @@ -452,10 +397,6 @@ - (void)publishProtocolMessage:(ARTProtocolMessage *)pm callback:(ARTStatusCallb } } -- (ARTPresenceMap *)presenceMap { - return _presenceMap; -} - - (void)throwOnDisconnectedOrFailed { if (self.realtime.connection.state_nosync == ARTRealtimeFailed || self.realtime.connection.state_nosync == ARTRealtimeDisconnected) { [ARTException raise:@"realtime cannot perform action in disconnected or failed state" format:@"state: %d", (int)self.realtime.connection.state_nosync]; @@ -619,7 +560,7 @@ - (void)performTransitionToState:(ARTRealtimeChannelState)state withParams:(ARTC self.attachResume = true; break; case ARTRealtimeChannelSuspended: { - self.serial = nil; // RTP5a1 + self.channelSerial = nil; // RTP5a1 ARTRetryAttempt *const retryAttempt = [self.attachRetryState addRetryAttempt]; [_attachedEventEmitter emit:nil with:params.errorInfo]; @@ -636,15 +577,15 @@ - (void)performTransitionToState:(ARTRealtimeChannelState)state withParams:(ARTC self.attachResume = false; break; case ARTRealtimeChannelDetached: - self.serial = nil; // RTP5a1 - [self.presenceMap failsSync:params.errorInfo]; + self.channelSerial = nil; // RTP5a1 + [self.presence failsSync:params.errorInfo]; break; case ARTRealtimeChannelFailed: - self.serial = nil; // RTP5a1 + self.channelSerial = nil; // RTP5a1 self.attachResume = false; [_attachedEventEmitter emit:nil with:params.errorInfo]; [_detachedEventEmitter emit:nil with:params.errorInfo]; - [self.presenceMap failsSync:params.errorInfo]; + [self.presence failsSync:params.errorInfo]; break; default: break; @@ -667,18 +608,6 @@ - (ARTEventListener *)unlessStateChangesBefore:(NSTimeInterval)deadline do:(void }]; } -/** - Checks that a channelSerial is the final serial in a sequence of sync messages, - by checking that there is nothing after the colon - */ -- (bool)isLastChannelSerial:(NSString *)channelSerial { - NSArray * a = [channelSerial componentsSeparatedByString:@":"]; - if([a count] >1 && ![[a objectAtIndex:1] isEqualToString:@""] ) { - return false; - } - return true; -} - - (void)onChannelMessage:(ARTProtocolMessage *)message { ARTLogDebug(self.logger, @"R:%p C:%p (%@) received channel message %tu - %@", _realtime, self, self.name, message.action, ARTProtocolMessageActionToStr(message.action)); switch (message.action) { @@ -713,7 +642,8 @@ - (void)onChannelMessage:(ARTProtocolMessage *)message { } - (void)setAttached:(ARTProtocolMessage *)message { - switch (self.state_nosync) { + ARTRealtimeChannelState state = self.state_nosync; + switch (state) { case ARTRealtimeChannelDetaching: case ARTRealtimeChannelFailed: // Ignore @@ -729,27 +659,17 @@ - (void)setAttached:(ARTProtocolMessage *)message { self.attachSerial = message.channelSerial; // RTL15b if (message.channelSerial) { - self.serial = message.channelSerial; + self.channelSerial = message.channelSerial; } - if (message.hasPresence) { - [self.presenceMap startSync]; - } - else { - // RTP1 - when an ATTACHED message is received without a HAS_PRESENCE flag, reset PresenceMap - [self.presenceMap startSync]; - [self.presenceMap endSync]; - ARTLogDebug(self.logger, @"R:%p C:%p (%@) PresenceMap has been reset", _realtime, self, self.name); - } - - if (self.state_nosync == ARTRealtimeChannelAttached) { + if (state == ARTRealtimeChannelAttached) { if (!message.resumed) { // RTL12 if (message.error != nil) { _errorReason = message.error; } - ARTChannelStateChange *stateChange = [[ARTChannelStateChange alloc] initWithCurrent:self.state_nosync previous:self.state_nosync event:ARTChannelEventUpdate reason:message.error resumed:message.resumed]; + ARTChannelStateChange *stateChange = [[ARTChannelStateChange alloc] initWithCurrent:state previous:state event:ARTChannelEventUpdate reason:message.error resumed:message.resumed]; [self emit:stateChange.event with:stateChange]; - [self.presenceMap reenterLocalMembers]; // RTP17i + [self.presence onAttached:message]; } return; } @@ -761,10 +681,8 @@ - (void)setAttached:(ARTProtocolMessage *)message { params = [[ARTChannelStateChangeParams alloc] initWithState:ARTStateOk]; } [self performTransitionToState:ARTRealtimeChannelAttached withParams:params]; + [self.presence onAttached:message]; [_attachedEventEmitter emit:nil with:nil]; - - [self.presence sendPendingPresence]; - [self.presenceMap reenterLocalMembers]; // RTP17i } - (void)setDetached:(ARTProtocolMessage *)message { @@ -873,6 +791,9 @@ - (void)onMessage:(ARTProtocolMessage *)pm { if (!msg.id) { msg.id = [NSString stringWithFormat:@"%@:%d", pm.id, i]; } + if (!msg.connectionId) { + msg.connectionId = pm.connectionId; + } _lastPayloadMessageId = msg.id; @@ -883,7 +804,7 @@ - (void)onMessage:(ARTProtocolMessage *)pm { // RTL15b if (pm.channelSerial) { - self.serial = pm.channelSerial; + self.channelSerial = pm.channelSerial; } } @@ -891,65 +812,13 @@ - (void)onPresence:(ARTProtocolMessage *)message { ARTLogDebug(self.logger, @"RT:%p C:%p (%@) handle PRESENCE message", _realtime, self, self.name); // RTL15b if (message.channelSerial) { - self.serial = message.channelSerial; - } - - int i = 0; - ARTDataEncoder *dataEncoder = self.dataEncoder; - for (ARTPresenceMessage *p in message.presence) { - ARTPresenceMessage *presence = p; - if (presence.data && dataEncoder) { - NSError *decodeError = nil; - presence = [p decodeWithEncoder:dataEncoder error:&decodeError]; - if (decodeError != nil) { - ARTErrorInfo *errorInfo = [ARTErrorInfo wrap:[ARTErrorInfo createWithCode:ARTErrorUnableToDecodeMessage message:decodeError.localizedFailureReason] prepend:@"Failed to decode data: "]; - ARTLogError(self.logger, @"RT:%p C:%p (%@) %@", _realtime, self, self.name, errorInfo.message); - } - } - - if (!presence.timestamp) { - presence.timestamp = message.timestamp; - } - - if (!presence.id) { - presence.id = [NSString stringWithFormat:@"%@:%d", message.id, i]; - } - - if ([self.presenceMap add:presence]) { - [self broadcastPresence:presence]; - } - - ++i; + self.channelSerial = message.channelSerial; } + [self.presence onMessage:message]; } - (void)onSync:(ARTProtocolMessage *)message { - self.presenceMap.syncMsgSerial = [message.msgSerial longLongValue]; - self.presenceMap.syncChannelSerial = message.channelSerial; - - if (!self.presenceMap.syncInProgress) { - [self.presenceMap startSync]; - } - else { - ARTLogDebug(self.logger, @"RT:%p C:%p (%@) PresenceMap sync is in progress", _realtime, self, self.name); - } - - for (int i=0; i<[message.presence count]; i++) { - ARTPresenceMessage *presence = [message.presence objectAtIndex:i]; - if ([self.presenceMap add:presence]) { - [self broadcastPresence:presence]; - } - } - - if ([self isLastChannelSerial:message.channelSerial]) { - [self.presenceMap endSync]; - self.presenceMap.syncChannelSerial = nil; - ARTLogDebug(self.logger, @"RT:%p C:%p (%@) PresenceMap sync ended", _realtime, self, self.name); - } -} - -- (void)broadcastPresence:(ARTPresenceMessage *)pm { - [self.presenceEventEmitter emit:[ARTEvent newWithPresenceAction:pm.action] with:pm]; + [self.presence onSync:message]; } - (void)onError:(ARTProtocolMessage *)msg { @@ -1036,7 +905,7 @@ - (void)attachAfterChecks:(ARTCallback)callback { ARTProtocolMessage *attachMessage = [[ARTProtocolMessage alloc] init]; attachMessage.action = ARTProtocolMessageAttach; attachMessage.channel = self.name; - attachMessage.channelSerial = self.serial; // RTL4c1 + attachMessage.channelSerial = self.channelSerial; // RTL4c1 attachMessage.params = self.options_nosync.params; attachMessage.flags = self.options_nosync.modes; @@ -1156,8 +1025,8 @@ - (void)detachAfterChecks:(ARTCallback)callback { }]; } - if (self.presenceMap.syncInProgress) { - [self.presenceMap failsSync:[ARTErrorInfo createWithCode:ARTErrorChannelOperationFailed message:@"channel is being DETACHED"]]; + if (self.presence.syncInProgress) { + [self.presence failsSync:[ARTErrorInfo createWithCode:ARTErrorChannelOperationFailed message:@"channel is being DETACHED"]]; } } @@ -1195,36 +1064,10 @@ - (void)startDecodeFailureRecoveryWithErrorInfo:(ARTErrorInfo *)error { } withParams:params]; } -#pragma mark - ARTPresenceMapDelegate - - (NSString *)connectionId { return _realtime.connection.id_nosync; } -- (void)map:(ARTPresenceMap *)map didRemovedMemberNoLongerPresent:(ARTPresenceMessage *)presence { - presence.action = ARTPresenceLeave; - presence.id = nil; - presence.timestamp = [NSDate date]; - [self broadcastPresence:presence]; - ARTLogDebug(self.logger, @"RT:%p C:%p (%@) member \"%@\" no longer present", _realtime, self, self.name, presence.memberKey); -} - -- (void)map:(ARTPresenceMap *)map shouldReenterLocalMember:(ARTPresenceMessage *)presence { - [self.presence enterWithPresenceMessageId:presence.id clientId:presence.clientId data:presence.data callback:^(ARTErrorInfo *error) { - if (error != nil) { - NSString *message = [NSString stringWithFormat:@"Re-entering member \"%@\" is failed with code %ld (%@)", presence.memberKey, (long)error.code, error.message]; - ARTErrorInfo *reenterError = [ARTErrorInfo createWithCode:ARTErrorUnableToAutomaticallyReEnterPresenceChannel message:message]; - ARTChannelStateChange *stateChange = [[ARTChannelStateChange alloc] initWithCurrent:self.state_nosync previous:self.state_nosync event:ARTChannelEventUpdate reason:reenterError resumed:true]; // RTP17e - [self emit:stateChange.event with:stateChange]; - ARTLogWarn(self.logger, @"RT:%p C:%p (%@) Re-entering member \"%@\" is failed with code %ld (%@)", self->_realtime, self, self.name, presence.memberKey, (long)error.code, error.message); - } - else { - ARTLogDebug(self.logger, @"RT:%p C:%p (%@) re-entered local member \"%@\"", self->_realtime, self, self.name, presence.memberKey); - } - }]; - ARTLogDebug(self.logger, @"RT:%p C:%p (%@) re-entering local member \"%@\"", _realtime, self, self.name, presence.memberKey); -} - - (BOOL)exceedMaxSize:(NSArray *)messages { NSInteger size = 0; for (ARTMessage *message in messages) { diff --git a/Source/ARTRealtimePresence.m b/Source/ARTRealtimePresence.m index 5d76cbd57..50e9a87d8 100644 --- a/Source/ARTRealtimePresence.m +++ b/Source/ARTRealtimePresence.m @@ -1,16 +1,20 @@ #import "ARTRealtimePresence+Private.h" - #import "ARTRealtime+Private.h" #import "ARTChannel+Private.h" #import "ARTRealtimeChannel+Private.h" -#import "ARTPresenceMap.h" #import "ARTPresenceMessage.h" +#import "ARTPresenceMessage+Private.h" #import "ARTStatus.h" #import "ARTPresence+Private.h" #import "ARTDataQuery+Private.h" #import "ARTConnection+Private.h" #import "ARTNSArray+ARTFunctional.h" #import "ARTInternalLog.h" +#import "ARTEventEmitter+Private.h" +#import "ARTDataEncoder.h" +#import "ARTBaseMessage+Private.h" +#import "ARTProtocolMessage+Private.h" +#import "ARTEventEmitter+Private.h" #pragma mark - ARTRealtimePresenceQuery @@ -149,20 +153,52 @@ @interface ARTRealtimePresenceInternal () NS_ASSUME_NONNULL_END +typedef NS_ENUM(NSUInteger, ARTPresenceSyncState) { + ARTPresenceSyncInitialized, + ARTPresenceSyncStarted, //ItemType: nil + ARTPresenceSyncEnded, //ItemType: NSArray* + ARTPresenceSyncFailed //ItemType: ARTErrorInfo* +}; + +@interface ARTEvent (PresenceSyncState) + +- (instancetype)initWithPresenceSyncState:(ARTPresenceSyncState)value; ++ (instancetype)newWithPresenceSyncState:(ARTPresenceSyncState)value; + +@end + @implementation ARTRealtimePresenceInternal { __weak ARTRealtimeChannelInternal *_channel; // weak because channel owns self + __weak ARTRealtimeInternal *_realtime; dispatch_queue_t _userQueue; NSMutableArray *_pendingPresence; + ARTEventEmitter *_eventEmitter; + ARTDataEncoder *_dataEncoder; + + NSUInteger _syncSessionId; + ARTPresenceSyncState _syncState; + ARTEventEmitter *_syncEventEmitter; + + NSMutableDictionary *_members; + NSMutableDictionary *_internalMembers; // RTP17h } - (instancetype)initWithChannel:(ARTRealtimeChannelInternal *)channel logger:(ARTInternalLog *)logger { if (self = [super init]) { _channel = channel; - _userQueue = channel.realtime.rest.userQueue; - _queue = channel.realtime.rest.queue; + _realtime = channel.realtime; + _userQueue = _realtime.rest.userQueue; + _queue = _realtime.rest.queue; _pendingPresence = [NSMutableArray array]; _lastPresenceAction = ARTPresenceAbsent; _logger = logger; + _eventEmitter = [[ARTInternalEventEmitter alloc] initWithQueue:_queue]; + _dataEncoder = _channel.dataEncoder; + _members = [NSMutableDictionary new]; + _internalMembers = [NSMutableDictionary new]; + _syncSessionId = 0; + _syncState = ARTPresenceSyncInitialized; + _syncEventEmitter = [[ARTInternalEventEmitter alloc] initWithQueue:_queue]; } return self; } @@ -189,7 +225,7 @@ - (void)get:(ARTRealtimePresenceQuery *)query callback:(ARTPresenceMessagesCallb return; case ARTRealtimeChannelSuspended: if (query && !query.waitForSync) { - if (callback) callback(self->_channel.presenceMap.members.allValues, nil); + if (callback) callback(self->_members.allValues, nil); return; } if (callback) callback(nil, [ARTErrorInfo createWithCode:ARTErrorPresenceStateIsOutOfSync message:@"presence state is out of sync due to the channel being SUSPENDED"]); @@ -208,19 +244,19 @@ - (void)get:(ARTRealtimePresenceQuery *)query callback:(ARTPresenceMessagesCallb callback(nil, error); return; } - const BOOL syncInProgress = self->_channel.presenceMap.syncInProgress; + const BOOL syncInProgress = self.syncInProgress; if (syncInProgress && query.waitForSync) { - ARTLogDebug(self.logger, @"R:%p C:%p (%@) sync is in progress, waiting until the presence members is synchronized", self->_channel.realtime, self->_channel, self->_channel.name); - [self->_channel.presenceMap onceSyncEnds:^(NSArray *members) { + ARTLogDebug(self.logger, @"R:%p C:%p (%@) sync is in progress, waiting until the presence members is synchronized", self->_realtime, self->_channel, self->_channel.name); + [self onceSyncEnds:^(NSArray *members) { NSArray *filteredMembers = [members artFilter:filterMemberBlock]; callback(filteredMembers, nil); }]; - [self->_channel.presenceMap onceSyncFails:^(ARTErrorInfo *error) { + [self onceSyncFails:^(ARTErrorInfo *error) { callback(nil, error); }]; } else { - ARTLogDebug(self.logger, @"R:%p C:%p (%@) returning presence members (syncInProgress=%d)", self->_channel.realtime, self->_channel, self->_channel.name, syncInProgress); - NSArray *members = self->_channel.presenceMap.members.allValues; + ARTLogDebug(self.logger, @"R:%p C:%p (%@) returning presence members (syncInProgress=%d)", self->_realtime, self->_channel, self->_channel.name, syncInProgress); + NSArray *members = self->_members.allValues; NSArray *filteredMembers = [members artFilter:filterMemberBlock]; callback(filteredMembers, nil); } @@ -346,7 +382,7 @@ - (void)enterOrUpdateAfterChecks:(ARTPresenceAction)action messageId:(NSString * msg.id = messageId; msg.clientId = clientId; msg.data = data; - msg.connectionId = _channel.realtime.connection.id_nosync; + msg.connectionId = _realtime.connection.id_nosync; [self publishPresence:msg callback:cb]; } @@ -402,7 +438,7 @@ - (void)leaveAfterChecks:(NSString *_Nullable)clientId data:(id)data callback:(A msg.action = ARTPresenceLeave; msg.data = data; msg.clientId = clientId; - msg.connectionId = _channel.realtime.connection.id_nosync; + msg.connectionId = _realtime.connection.id_nosync; [self publishPresence:msg callback:cb]; } @@ -415,7 +451,7 @@ - (BOOL)syncComplete { } - (BOOL)syncComplete_nosync { - return _channel.presenceMap.syncComplete; + return _syncState == ARTPresenceSyncEnded || _syncState == ARTPresenceSyncFailed; } - (ARTEventListener *)subscribe:(ARTPresenceMessageCallback)callback { @@ -447,8 +483,8 @@ - (ARTEventListener *)subscribeWithAttachCallback:(ARTCallback)onAttach callback return; } [self->_channel _attach:onAttach]; - listener = [self->_channel.presenceEventEmitter on:cb]; - ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence subscribe to all actions", self->_channel.realtime, self->_channel, self->_channel.name); + listener = [_eventEmitter on:cb]; + ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence subscribe to all actions", self->_realtime, self->_channel, self->_channel.name); }); return listener; } @@ -482,8 +518,8 @@ - (ARTEventListener *)subscribe:(ARTPresenceAction)action onAttach:(ARTCallback) return; } [self->_channel _attach:onAttach]; - listener = [self->_channel.presenceEventEmitter on:[ARTEvent newWithPresenceAction:action] callback:cb]; - ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence subscribe to action %@", self->_channel.realtime, self->_channel, self->_channel.name, ARTPresenceActionToStr(action)); + listener = [_eventEmitter on:[ARTEvent newWithPresenceAction:action] callback:cb]; + ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence subscribe to action %@", self->_realtime, self->_channel, self->_channel.name, ARTPresenceActionToStr(action)); }); return listener; } @@ -491,25 +527,25 @@ - (ARTEventListener *)subscribe:(ARTPresenceAction)action onAttach:(ARTCallback) - (void)unsubscribe { dispatch_sync(_queue, ^{ [self _unsubscribe]; - ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence unsubscribe to all actions", self->_channel.realtime, self->_channel, self->_channel.name); + ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence unsubscribe to all actions", self->_realtime, self->_channel, self->_channel.name); }); } - (void)_unsubscribe { - [_channel.presenceEventEmitter off]; + [_eventEmitter off]; } - (void)unsubscribe:(ARTEventListener *)listener { dispatch_sync(_queue, ^{ - [self->_channel.presenceEventEmitter off:listener]; - ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence unsubscribe to all actions", self->_channel.realtime, self->_channel, self->_channel.name); + [_eventEmitter off:listener]; + ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence unsubscribe to all actions", self->_realtime, self->_channel, self->_channel.name); }); } - (void)unsubscribe:(ARTPresenceAction)action listener:(ARTEventListener *)listener { dispatch_sync(_queue, ^{ - [self->_channel.presenceEventEmitter off:[ARTEvent newWithPresenceAction:action] listener:listener]; - ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence unsubscribe to action %@", self->_channel.realtime, self->_channel, self->_channel.name, ARTPresenceActionToStr(action)); + [_eventEmitter off:[ARTEvent newWithPresenceAction:action] listener:listener]; + ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence unsubscribe to action %@", self->_realtime, self->_channel, self->_channel.name, ARTPresenceActionToStr(action)); }); } @@ -520,8 +556,8 @@ - (void)addPendingPresence:(ARTProtocolMessage *)msg callback:(ARTStatusCallback - (void)publishPresence:(ARTPresenceMessage *)msg callback:(ARTCallback)callback { if (msg.clientId == nil) { - NSString *authClientId = _channel.realtime.auth.clientId_nosync; - BOOL connected = _channel.realtime.connection.state_nosync == ARTRealtimeConnected; + NSString *authClientId = _realtime.auth.clientId_nosync; + BOOL connected = _realtime.connection.state_nosync == ARTRealtimeConnected; if (connected && (authClientId == nil || [authClientId isEqualToString:@"*"])) { if (callback) { callback([ARTErrorInfo createWithCode:ARTStateNoClientId message:@"Invalid attempt to publish presence message without clientId."]); @@ -530,8 +566,8 @@ - (void)publishPresence:(ARTPresenceMessage *)msg callback:(ARTCallback)callback } } - if (!_channel.realtime.connection.isActive_nosync) { - if (callback) callback([_channel.realtime.connection error_nosync]); + if (!_realtime.connection.isActive_nosync) { + if (callback) callback([_realtime.connection error_nosync]); return; } @@ -549,7 +585,7 @@ - (void)publishPresence:(ARTPresenceMessage *)msg callback:(ARTCallback)callback if (msg.data && _channel.dataEncoder) { ARTDataEncoderOutput *encoded = [_channel.dataEncoder encode:msg.data]; if (encoded.errorInfo) { - ARTLogWarn(self.logger, @"RT:%p C:%p (%@) error encoding presence message: %@", _channel.realtime, self, _channel.name, encoded.errorInfo); + ARTLogWarn(self.logger, @"RT:%p C:%p (%@) error encoding presence message: %@", _realtime, self, _channel.name, encoded.errorInfo); } msg.data = encoded.data; msg.encoding = encoded.encoding; @@ -574,7 +610,7 @@ - (void)publishPresence:(ARTPresenceMessage *)msg callback:(ARTCallback)callback break; } case ARTRealtimeChannelAttached: { - [_channel.realtime send:pm sentCallback:nil ackCallback:^(ARTStatus *status) { + [_realtime send:pm sentCallback:nil ackCallback:^(ARTStatus *status) { if (callback) callback(status.errorInfo); }]; break; @@ -610,7 +646,7 @@ - (void)sendPendingPresence { [_pendingPresence addObject:qm]; continue; } - [_channel.realtime send:qm.msg sentCallback:nil ackCallback:qm.ackCallback]; + [_realtime send:qm.msg sentCallback:nil ackCallback:qm.ackCallback]; } } @@ -622,4 +658,277 @@ - (void)failPendingPresence:(ARTStatus *)status { } } +- (void)broadcast:(ARTPresenceMessage *)pm { + [_eventEmitter emit:[ARTEvent newWithPresenceAction:pm.action] with:pm]; +} + +/* + * Checks that a channelSerial is the final serial in a sequence of sync messages, + * by checking that there is nothing after the colon - RTP18b, RTP18c + */ +- (bool)isLastChannelSerial:(NSString *)channelSerial { + if ([channelSerial isEqualToString:@""]) { + return true; + } + NSArray *a = [channelSerial componentsSeparatedByString:@":"]; + if (a.count > 1 && ![[a objectAtIndex:1] isEqualToString:@""]) { + return false; + } + return true; +} + +- (void)onAttached:(ARTProtocolMessage *)message { + [self startSync]; + if (!message.hasPresence) { + // RTP1 - when an ATTACHED message is received without a HAS_PRESENCE flag, reset PresenceMap + [self endSync]; + ARTLogDebug(self.logger, @"R:%p C:%p (%@) PresenceMap has been reset", _realtime, self, _channel.name); + } + [self sendPendingPresence]; + [self reenterInternalMembers]; // RTP17i +} + +- (void)onMessage:(ARTProtocolMessage *)message { + int i = 0; + for (ARTPresenceMessage *p in message.presence) { + ARTPresenceMessage *presence = p; + if (presence.data && _dataEncoder) { + NSError *decodeError = nil; + presence = [p decodeWithEncoder:_dataEncoder error:&decodeError]; + if (decodeError != nil) { + ARTErrorInfo *errorInfo = [ARTErrorInfo wrap:[ARTErrorInfo createWithCode:ARTErrorUnableToDecodeMessage message:decodeError.localizedFailureReason] prepend:@"Failed to decode data: "]; + ARTLogError(self.logger, @"RT:%p C:%p (%@) %@", _realtime, _channel, _channel.name, errorInfo.message); + } + } + + if (!presence.timestamp) { + presence.timestamp = message.timestamp; + } + + if (!presence.id) { + presence.id = [NSString stringWithFormat:@"%@:%d", message.id, i]; + } + + if (!presence.connectionId) { + presence.connectionId = message.connectionId; + } + + if ([self add:presence]) { + [self broadcast:presence]; + } + + ++i; + } +} + +- (void)onSync:(ARTProtocolMessage *)message { + if (!self.syncInProgress) { + [self startSync]; + } + else { + ARTLogDebug(self.logger, @"RT:%p C:%p (%@) PresenceMap sync is in progress", _realtime, _channel, _channel.name); + } + + [self onMessage:message]; + + if ([self isLastChannelSerial:message.channelSerial]) { + [self endSync]; + ARTLogDebug(self.logger, @"RT:%p C:%p (%@) PresenceMap sync ended", _realtime, _channel, _channel.name); + } +} + +- (NSString *)connectionId { + return _realtime.connection.id_nosync; +} + +- (void)didRemovedMemberNoLongerPresent:(ARTPresenceMessage *)pm { + pm.action = ARTPresenceLeave; + pm.id = nil; + pm.timestamp = [NSDate date]; + [self broadcast:pm]; + ARTLogDebug(self.logger, @"RT:%p C:%p (%@) member \"%@\" no longer present", _realtime, _channel, _channel.name, pm.memberKey); +} + +- (void)reenterInternalMembers { + ARTLogDebug(self.logger, @"%p reentering local members", self); + for (ARTPresenceMessage *member in [self.internalMembers allValues]) { + [self enterWithPresenceMessageId:member.id clientId:member.clientId data:member.data callback:^(ARTErrorInfo *error) { + if (error != nil) { + NSString *message = [NSString stringWithFormat:@"Re-entering member \"%@\" is failed with code %ld (%@)", member.memberKey, (long)error.code, error.message]; + ARTErrorInfo *reenterError = [ARTErrorInfo createWithCode:ARTErrorUnableToAutomaticallyReEnterPresenceChannel message:message]; + ARTChannelStateChange *stateChange = [[ARTChannelStateChange alloc] initWithCurrent:self->_channel.state_nosync previous:self->_channel.state_nosync event:ARTChannelEventUpdate reason:reenterError resumed:true]; // RTP17e + + [self->_channel emit:stateChange.event with:stateChange]; + + ARTLogWarn(self.logger, @"RT:%p C:%p (%@) Re-entering member \"%@\" is failed with code %ld (%@)", self->_realtime, self->_channel, self->_channel.name, member.memberKey, (long)error.code, error.message); + } + else { + ARTLogDebug(self.logger, @"RT:%p C:%p (%@) re-entered local member \"%@\"", self->_realtime, self->_channel, self->_channel.name, member.memberKey); + } + }]; + ARTLogDebug(self.logger, @"RT:%p C:%p (%@) re-entering local member \"%@\"", _realtime, _channel, _channel.name, member.memberKey); + } +} + +#pragma mark - Presence Map + +- (NSDictionary *)members { + return _members; +} + +- (NSDictionary *)internalMembers { + return _internalMembers; +} + +- (BOOL)add:(ARTPresenceMessage *)message { + ARTPresenceMessage *latest = [_members objectForKey:message.memberKey]; + if ([message isNewerThan:latest]) { + ARTPresenceMessage *messageCopy = [message copy]; + switch (message.action) { + case ARTPresenceEnter: + case ARTPresenceUpdate: + messageCopy.action = ARTPresencePresent; + // intentional fallthrough + case ARTPresencePresent: + [self internalAdd:messageCopy]; + break; + case ARTPresenceLeave: + [self internalRemove:messageCopy]; + break; + default: + break; + } + return YES; + } + ARTLogDebug(_logger, @"Presence member \"%@\" with action %@ has been ignored", message.memberKey, ARTPresenceActionToStr(message.action)); + latest.syncSessionId = _syncSessionId; + return NO; +} + +- (void)internalAdd:(ARTPresenceMessage *)message { + [self internalAdd:message withSessionId:_syncSessionId]; +} + +- (void)internalAdd:(ARTPresenceMessage *)message withSessionId:(NSUInteger)sessionId { + message.syncSessionId = sessionId; + [_members setObject:message forKey:message.memberKey]; + // Local member + if ([message.connectionId isEqualToString:self.connectionId]) { + _internalMembers[message.clientId] = message; + ARTLogDebug(_logger, @"local member %@ with action %@ has been added", message.memberKey, ARTPresenceActionToStr(message.action).uppercaseString); + } +} + +- (void)internalRemove:(ARTPresenceMessage *)message { + [self internalRemove:message force:false]; +} + +- (void)internalRemove:(ARTPresenceMessage *)message force:(BOOL)force { + if ([message.connectionId isEqualToString:self.connectionId] && !message.isSynthesized) { + [_internalMembers removeObjectForKey:message.clientId]; + } + + const BOOL syncInProgress = self.syncInProgress; + if (!force && syncInProgress) { + ARTLogDebug(_logger, @"%p \"%@\" should be removed after sync ends (syncInProgress=%d)", self, message.clientId, syncInProgress); + message.action = ARTPresenceAbsent; + // Should be removed after Sync ends + [self internalAdd:message withSessionId:message.syncSessionId]; + } + else { + [_members removeObjectForKey:message.memberKey]; + } +} + +- (void)cleanUpAbsentMembers { + ARTLogDebug(_logger, @"%p cleaning up absent members (syncSessionId=%lu)", self, (unsigned long)_syncSessionId); + NSSet *filteredMembers = [_members keysOfEntriesPassingTest:^BOOL(NSString *key, ARTPresenceMessage *message, BOOL *stop) { + return message.action == ARTPresenceAbsent; + }]; + for (NSString *key in filteredMembers) { + [self internalRemove:[_members objectForKey:key] force:true]; + } +} + +- (void)leaveMembersNotPresentInSync { + ARTLogDebug(_logger, @"%p leaving members not present in sync (syncSessionId=%lu)", self, (unsigned long)_syncSessionId); + for (ARTPresenceMessage *member in [_members allValues]) { + if (member.syncSessionId != _syncSessionId) { + // Handle members that have not been added or updated in the PresenceMap during the sync process + ARTPresenceMessage *leave = [member copy]; + [self internalRemove:member force:true]; + [self didRemovedMemberNoLongerPresent:leave]; + } + } +} + +- (void)reset { + _members = [NSMutableDictionary new]; + _internalMembers = [NSMutableDictionary new]; +} + +- (void)startSync { + ARTLogDebug(_logger, @"%p PresenceMap sync started", self); + _syncSessionId++; + _syncState = ARTPresenceSyncStarted; + [_syncEventEmitter emit:[ARTEvent newWithPresenceSyncState:_syncState] with:nil]; +} + +- (void)endSync { + ARTLogVerbose(_logger, @"%p PresenceMap sync ending", self); + [self cleanUpAbsentMembers]; + [self leaveMembersNotPresentInSync]; + _syncState = ARTPresenceSyncEnded; + + [_syncEventEmitter emit:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncEnded] with:[_members allValues]]; + [_syncEventEmitter off]; + ARTLogDebug(_logger, @"%p PresenceMap sync ended", self); +} + +- (void)failsSync:(ARTErrorInfo *)error { + [self reset]; + _syncState = ARTPresenceSyncFailed; + [_syncEventEmitter emit:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncFailed] with:error]; + [_syncEventEmitter off]; +} + +- (void)onceSyncEnds:(void (^)(NSArray *))callback { + [_syncEventEmitter once:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncEnded] callback:callback]; +} + +- (void)onceSyncFails:(ARTCallback)callback { + [_syncEventEmitter once:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncFailed] callback:callback]; +} + +- (BOOL)syncInProgress { + return _syncState == ARTPresenceSyncStarted; +} + +@end + +#pragma mark - ARTEvent + +NSString *ARTPresenceSyncStateToStr(ARTPresenceSyncState state) { + switch (state) { + case ARTPresenceSyncInitialized: + return @"Initialized"; //0 + case ARTPresenceSyncStarted: + return @"Started"; //1 + case ARTPresenceSyncEnded: + return @"Ended"; //2 + case ARTPresenceSyncFailed: + return @"Failed"; //3 + } +} + +@implementation ARTEvent (PresenceSyncState) + +- (instancetype)initWithPresenceSyncState:(ARTPresenceSyncState)value { + return [self initWithString:[NSString stringWithFormat:@"ARTPresenceSyncState%@", ARTPresenceSyncStateToStr(value)]]; +} + ++ (instancetype)newWithPresenceSyncState:(ARTPresenceSyncState)value { + return [[self alloc] initWithPresenceSyncState:value]; +} + @end diff --git a/Source/Ably.modulemap b/Source/Ably.modulemap index 2d9bda1d4..327d34883 100644 --- a/Source/Ably.modulemap +++ b/Source/Ably.modulemap @@ -99,7 +99,6 @@ framework module Ably { header "ARTDeviceIdentityTokenDetails+Private.h" header "ARTHttp.h" header "ARTLocalDeviceStorage.h" - header "ARTPresenceMap.h" header "ARTInternalLogCore.h" header "ARTInternalLogCore+Testing.h" header "ARTDataEncoder.h" diff --git a/Source/PrivateHeaders/Ably/ARTPresenceMap.h b/Source/PrivateHeaders/Ably/ARTPresenceMap.h deleted file mode 100644 index 99ea6c957..000000000 --- a/Source/PrivateHeaders/Ably/ARTPresenceMap.h +++ /dev/null @@ -1,56 +0,0 @@ -#import -#import - -@class ARTPresenceMap; -@class ARTPresenceMessage; -@class ARTErrorInfo; -@class ARTInternalLog; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ARTPresenceMapDelegate -@property (nonatomic, readonly) NSString *connectionId; -- (void)map:(ARTPresenceMap *)map didRemovedMemberNoLongerPresent:(ARTPresenceMessage *)presence; -- (void)map:(ARTPresenceMap *)map shouldReenterLocalMember:(ARTPresenceMessage *)presence; -@end - -/// Used to maintain a list of members present on a channel -@interface ARTPresenceMap : NSObject - -/// List of members. -/// The key is the memberKey and the value is the latest relevant ARTPresenceMessage for that clientId. -@property (readonly, atomic) NSDictionary *members; - -/// List of internal members. -/// The key is the clientId and the value is the latest relevant ARTPresenceMessage for that clientId. -@property (readonly, atomic) NSMutableDictionary *localMembers; - -@property (nullable, weak) id delegate; // weak because delegates outlive their counterpart - -@property (readwrite, nonatomic) int64_t syncMsgSerial; -@property (readwrite, nonatomic, nullable) NSString *syncChannelSerial; -@property (readonly, nonatomic) NSUInteger syncSessionId; -@property (readonly, nonatomic, getter=syncComplete) BOOL syncComplete; -@property (readonly, nonatomic, getter=syncInProgress) BOOL syncInProgress; - -- (instancetype)init UNAVAILABLE_ATTRIBUTE; -- (instancetype)initWithQueue:(_Nonnull dispatch_queue_t)queue logger:(ARTInternalLog *)logger; - -- (BOOL)add:(ARTPresenceMessage *)message; -- (void)reset; - -- (void)startSync; -- (void)endSync; -- (void)failsSync:(ARTErrorInfo *)error; - -- (void)onceSyncEnds:(void (^)(NSArray *))callback; -- (void)onceSyncFails:(ARTCallback)callback; - -- (void)internalAdd:(ARTPresenceMessage *)message; -- (void)internalAdd:(ARTPresenceMessage *)message withSessionId:(NSUInteger)sessionId; - -- (void)reenterLocalMembers; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/PrivateHeaders/Ably/ARTRealtimeChannel+Private.h b/Source/PrivateHeaders/Ably/ARTRealtimeChannel+Private.h index a8b848857..79dc55276 100644 --- a/Source/PrivateHeaders/Ably/ARTRealtimeChannel+Private.h +++ b/Source/PrivateHeaders/Ably/ARTRealtimeChannel+Private.h @@ -5,7 +5,6 @@ #import #import -#import #import #import #import @@ -18,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface ARTRealtimeChannelInternal : ARTChannel +@interface ARTRealtimeChannelInternal : ARTChannel @property (readonly) ARTRealtimePresenceInternal *presence; #if TARGET_OS_IPHONE @@ -28,6 +27,7 @@ NS_ASSUME_NONNULL_BEGIN @property (readwrite, nonatomic) ARTRealtimeChannelState state; @property (readonly, nonatomic, nullable) ARTErrorInfo *errorReason; @property (readonly, nullable, getter=getOptions_nosync) ARTRealtimeChannelOptions *options_nosync; +@property (nonatomic, readonly) NSString *connectionId; - (ARTRealtimeChannelState)state_nosync; - (ARTErrorInfo *)errorReason_nosync; @@ -37,20 +37,16 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly, weak, nonatomic) ARTRealtimeInternal *realtime; // weak because realtime owns self @property (readonly, nonatomic) ARTRestChannelInternal *restChannel; @property (readwrite, nonatomic, nullable) NSString *attachSerial; -@property (readwrite, nonatomic, nullable) NSString *serial; // CP2b +@property (readwrite, nonatomic, nullable) NSString *channelSerial; // CP2b @property (readonly, nullable, getter=getClientId) NSString *clientId; @property (readonly, nonatomic) ARTEventEmitter *internalEventEmitter; @property (readonly, nonatomic) ARTEventEmitter *statesEventEmitter; @property (readonly, nonatomic) ARTEventEmitter, ARTMessage *> *messagesEventEmitter; -@property (readonly, nonatomic) ARTEventEmitter *presenceEventEmitter; -@property (readwrite, nonatomic) ARTPresenceMap *presenceMap; @property (readwrite, nonatomic) BOOL attachResume; - (instancetype)initWithRealtime:(ARTRealtimeInternal *)realtime andName:(NSString *)name withOptions:(ARTRealtimeChannelOptions *)options logger:(ARTInternalLog *)logger; -- (bool)isLastChannelSerial:(NSString *)channelSerial; - - (void)reattachWithParams:(ARTAttachRequestParams *)params; - (void)_attach:(nullable ARTCallback)callback; @@ -82,11 +78,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)setFailed:(ARTChannelStateChangeParams *)params; - (void)throwOnDisconnectedOrFailed; -- (void)broadcastPresence:(ARTPresenceMessage *)pm; - (void)detachChannel:(ARTChannelStateChangeParams *)params; -- (void)sync; -- (void)sync:(nullable ARTCallback)callback; +- (void)emit:(ARTChannelEvent)event with:(ARTChannelStateChange *)data; @end diff --git a/Source/PrivateHeaders/Ably/ARTRealtimePresence+Private.h b/Source/PrivateHeaders/Ably/ARTRealtimePresence+Private.h index 05a5d157f..e3a09847e 100644 --- a/Source/PrivateHeaders/Ably/ARTRealtimePresence+Private.h +++ b/Source/PrivateHeaders/Ably/ARTRealtimePresence+Private.h @@ -5,17 +5,19 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTRealtimePresenceInternal : NSObject +@property (nonatomic, readonly) NSString *connectionId; +@property (readonly, nonatomic) ARTEventEmitter *eventEmitter; + - (instancetype)initWithChannel:(ARTRealtimeChannelInternal *)channel logger:(ARTInternalLog *)logger; - (void)_unsubscribe; - (BOOL)syncComplete_nosync; -- (void)sendPendingPresence; - (void)failPendingPresence:(ARTStatus *)status; +- (void)broadcast:(ARTPresenceMessage *)pm; -/* - * Helper method for enforcing RTP17g, where in addition to clientId and data, message id is required. - */ -- (void)enterWithPresenceMessageId:(NSString *)messageId clientId:(NSString *)clientId data:(id)data callback:(ARTCallback)cb; +- (void)onMessage:(ARTProtocolMessage *)message; +- (void)onSync:(ARTProtocolMessage *)message; +- (void)onAttached:(ARTProtocolMessage *)message; @property (nonatomic) dispatch_queue_t queue; @property (readwrite, nonatomic) ARTPresenceAction lastPresenceAction; @@ -31,4 +33,38 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ARTRealtimePresenceInternal (PresenceMap) + +/// List of members. +/// The key is the memberKey and the value is the latest relevant ARTPresenceMessage for that clientId. +@property (readonly, atomic) NSDictionary *members; + +/// List of internal members. +/// The key is the clientId and the value is the latest relevant ARTPresenceMessage for that clientId. +@property (readonly, atomic) NSMutableDictionary *internalMembers; + +@property (readonly, nonatomic) NSUInteger syncSessionId; +@property (readonly, nonatomic) BOOL syncComplete; +@property (readonly, nonatomic) BOOL syncInProgress; + +- (instancetype)init UNAVAILABLE_ATTRIBUTE; +- (instancetype)initWithQueue:(_Nonnull dispatch_queue_t)queue logger:(ARTInternalLog *)logger; + +- (BOOL)add:(ARTPresenceMessage *)message; +- (void)reset; + +- (void)startSync; +- (void)endSync; +- (void)failsSync:(ARTErrorInfo *)error; + +- (void)onceSyncEnds:(void (^)(NSArray *))callback; +- (void)onceSyncFails:(ARTCallback)callback; + +- (void)internalAdd:(ARTPresenceMessage *)message; +- (void)internalAdd:(ARTPresenceMessage *)message withSessionId:(NSUInteger)sessionId; + +- (void)cleanUpAbsentMembers; + +@end + NS_ASSUME_NONNULL_END diff --git a/Source/include/Ably/ARTTypes.h b/Source/include/Ably/ARTTypes.h index 0db42f07c..afdcda17a 100644 --- a/Source/include/Ably/ARTTypes.h +++ b/Source/include/Ably/ARTTypes.h @@ -482,6 +482,9 @@ typedef void (^ARTConnectionStateCallback)(ARTConnectionStateChange *stateChange /// :nodoc: typedef void (^ARTPresenceMessageCallback)(ARTPresenceMessage *message); +/// :nodoc: +typedef void (^ARTPresenceMessageErrorCallback)(ARTPresenceMessage *message, ARTErrorInfo *_Nullable error); + /// :nodoc: typedef void (^ARTPresenceMessagesCallback)(NSArray *_Nullable result, ARTErrorInfo *_Nullable error); diff --git a/Test/Test Utilities/TestUtilities.swift b/Test/Test Utilities/TestUtilities.swift index d9664fb93..2349a2368 100644 --- a/Test/Test Utilities/TestUtilities.swift +++ b/Test/Test Utilities/TestUtilities.swift @@ -1605,7 +1605,7 @@ extension String { } extension ARTRealtime { - + var transportFactory: TestProxyTransportFactory? { self.internal.options.testOptions.transportFactory as? TestProxyTransportFactory } @@ -1664,7 +1664,7 @@ extension ARTRealtime { reachability.simulate(true) } } - + func simulateRestoreInternetConnection(after seconds: TimeInterval? = nil) { guard let transportFactory = self.transportFactory else { fatalError("Expected test TestProxyTransportFactory") @@ -1701,7 +1701,17 @@ extension ARTRealtime { } self.connection.off() } - + + func requestPresenceSyncForChannel(_ channel: ARTRealtimeChannel) { + let syncMessage = ARTProtocolMessage() + syncMessage.action = .sync + syncMessage.channel = channel.name + guard let transport = self.internal.transport as? TestProxyTransport else { + fail("TestProxyTransport is not set"); return + } + channel.internal.presence.startSync() + transport.send(syncMessage) + } } extension ARTWebSocketTransport { diff --git a/Test/Tests/RealtimeClientChannelTests.swift b/Test/Tests/RealtimeClientChannelTests.swift index 1da4b3ef6..92254672e 100644 --- a/Test/Tests/RealtimeClientChannelTests.swift +++ b/Test/Tests/RealtimeClientChannelTests.swift @@ -189,14 +189,14 @@ class RealtimeClientChannelTests: XCTestCase { channel2.attach() XCTAssertFalse(channel2.presence.syncComplete) - XCTAssertEqual(channel1.internal.presenceMap.members.count, 1) - XCTAssertEqual(channel2.internal.presenceMap.members.count, 0) + XCTAssertEqual(channel1.internal.presence.members.count, 1) + XCTAssertEqual(channel2.internal.presence.members.count, 0) } expect(channel2.presence.syncComplete).toEventually(beTrue(), timeout: testTimeout) - XCTAssertEqual(channel1.internal.presenceMap.members.count, 1) - expect(channel2.internal.presenceMap.members).toEventually(haveCount(1), timeout: testTimeout) + XCTAssertEqual(channel1.internal.presence.members.count, 1) + expect(channel2.internal.presence.members).toEventually(haveCount(1), timeout: testTimeout) waitUntil(timeout: testTimeout) { done in channel1.publish("message", data: nil) { errorInfo in @@ -212,14 +212,14 @@ class RealtimeClientChannelTests: XCTestCase { } } - expect(channel1.internal.presenceMap.members).toEventually(haveCount(2), timeout: testTimeout) - expect(channel1.internal.presenceMap.members.keys).to(allPass { $0.hasPrefix("\(channel1.internal.connectionId):Client") || $0.hasPrefix("\(channel2.internal.connectionId):Client") }) - expect(channel1.internal.presenceMap.members.values).to(allPass { $0.action == .present }) + expect(channel1.internal.presence.members).toEventually(haveCount(2), timeout: testTimeout) + expect(channel1.internal.presence.members.keys).to(allPass { $0.hasPrefix("\(channel1.internal.connectionId):Client") || $0.hasPrefix("\(channel2.internal.connectionId):Client") }) + expect(channel1.internal.presence.members.values).to(allPass { $0.action == .present }) - expect(channel2.internal.presenceMap.members).toEventually(haveCount(2), timeout: testTimeout) - expect(channel2.internal.presenceMap.members.keys).to(allPass { $0.hasPrefix("\(channel1.internal.connectionId):Client") || $0.hasPrefix("\(channel2.internal.connectionId):Client") }) - XCTAssertEqual(channel2.internal.presenceMap.members["\(channel1.internal.connectionId):Client 1"]!.action, ARTPresenceAction.present) - XCTAssertEqual(channel2.internal.presenceMap.members["\(channel2.internal.connectionId):Client 2"]!.action, ARTPresenceAction.present) + expect(channel2.internal.presence.members).toEventually(haveCount(2), timeout: testTimeout) + expect(channel2.internal.presence.members.keys).to(allPass { $0.hasPrefix("\(channel1.internal.connectionId):Client") || $0.hasPrefix("\(channel2.internal.connectionId):Client") }) + XCTAssertEqual(channel2.internal.presence.members["\(channel1.internal.connectionId):Client 1"]!.action, ARTPresenceAction.present) + XCTAssertEqual(channel2.internal.presence.members["\(channel2.internal.connectionId):Client 2"]!.action, ARTPresenceAction.present) } // RTL2 @@ -1240,7 +1240,7 @@ class RealtimeClientChannelTests: XCTestCase { partialDone() } } - expect(channel.internal.serial).toEventuallyNot(beNil()) + expect(channel.internal.channelSerial).toEventuallyNot(beNil()) client.simulateNoInternetConnection(transportFactory: transportFactory) client.simulateRestoreInternetConnection(after: 0.1, transportFactory: transportFactory) @@ -1250,7 +1250,7 @@ class RealtimeClientChannelTests: XCTestCase { expect(channel.state).toEventually(equal(ARTRealtimeChannelState.attached), timeout: testTimeout) let secondProtocolAttachMessage = try latestAttachProtocolMessage() - expect(secondProtocolAttachMessage.channelSerial).to(equal(channel.internal.serial)) + expect(secondProtocolAttachMessage.channelSerial).to(equal(channel.internal.channelSerial)) } // RTL4e @@ -4232,14 +4232,14 @@ class RealtimeClientChannelTests: XCTestCase { expect(error).to(beNil()) let attachMessage = transport.protocolMessagesReceived.filter { $0.action == .attached }[0] if attachMessage.channelSerial != nil { - expect(attachMessage.channelSerial).to(equal(channel.internal.serial)) + expect(attachMessage.channelSerial).to(equal(channel.internal.channelSerial)) } partialDone() channel.subscribe { message in let messageMessage = transport.protocolMessagesReceived.filter { $0.action == .message }[0] if messageMessage.channelSerial != nil { - expect(messageMessage.channelSerial).to(equal(channel.internal.serial)) + expect(messageMessage.channelSerial).to(equal(channel.internal.channelSerial)) } channel.presence.enterClient("client1", data: "Hey") partialDone() @@ -4247,7 +4247,7 @@ class RealtimeClientChannelTests: XCTestCase { channel.presence.subscribe { presenceMessage in let presenceMessage = transport.protocolMessagesReceived.filter { $0.action == .presence }[0] if presenceMessage.channelSerial != nil { - expect(presenceMessage.channelSerial).to(equal(channel.internal.serial)) + expect(presenceMessage.channelSerial).to(equal(channel.internal.channelSerial)) } partialDone() } @@ -4269,29 +4269,29 @@ class RealtimeClientChannelTests: XCTestCase { // Case for detached channel.attach() expect(channel.state).toEventually(equal(ARTRealtimeChannelState.attached), timeout: testTimeout) - expect(channel.internal.serial).toNot(beNil()) + expect(channel.internal.channelSerial).toNot(beNil()) channel.detach() expect(channel.state).toEventually(equal(ARTRealtimeChannelState.detached), timeout: testTimeout) - expect(channel.internal.serial).to(beNil()) + expect(channel.internal.channelSerial).to(beNil()) // Case for suspended channel.attach() expect(channel.state).toEventually(equal(ARTRealtimeChannelState.attached), timeout: testTimeout) - expect(channel.internal.serial).toNot(beNil()) + expect(channel.internal.channelSerial).toNot(beNil()) channel.internal.setSuspended(.init(state: .ok)) expect(channel.state).to(equal(ARTRealtimeChannelState.suspended)) - expect(channel.internal.serial).to(beNil()) + expect(channel.internal.channelSerial).to(beNil()) // Case for failed channel.attach() expect(channel.state).toEventually(equal(ARTRealtimeChannelState.attached), timeout: testTimeout) - expect(channel.internal.serial).toNot(beNil()) + expect(channel.internal.channelSerial).toNot(beNil()) channel.internal.setFailed(.init(state: .ok)) expect(channel.state).to(equal(ARTRealtimeChannelState.failed)) - expect(channel.internal.serial).to(beNil()) + expect(channel.internal.channelSerial).to(beNil()) } // RTL16 diff --git a/Test/Tests/RealtimeClientConnectionTests.swift b/Test/Tests/RealtimeClientConnectionTests.swift index 47d3c20e6..60c63d99a 100644 --- a/Test/Tests/RealtimeClientConnectionTests.swift +++ b/Test/Tests/RealtimeClientConnectionTests.swift @@ -3331,12 +3331,12 @@ class RealtimeClientConnectionTests: XCTestCase { client.connection.once(.connected) { stateChange in let firstChannel = client.channels.get(firstChannelName) firstChannel.on(.attached) {_ in - firstChannelSerial = firstChannel.internal.serial + firstChannelSerial = firstChannel.internal.channelSerial partialDone() } let secondChannel = client.channels.get(secondChannelName) secondChannel.on(.attached) {_ in - secondChannelSerial = secondChannel.internal.serial + secondChannelSerial = secondChannel.internal.channelSerial secondChannel.publish(nil, data: "message") { error in expect(error).to(beNil()) partialDone() @@ -3345,7 +3345,7 @@ class RealtimeClientConnectionTests: XCTestCase { } let thirdChannel = client.channels.get(thirdChannelName) thirdChannel.on(.attached) {_ in - thirdChannelSerial = thirdChannel.internal.serial + thirdChannelSerial = thirdChannel.internal.channelSerial partialDone() } firstChannel.attach() @@ -3399,12 +3399,12 @@ class RealtimeClientConnectionTests: XCTestCase { client.connection.once(.connected) { stateChange in let firstChannel = client.channels.get(sanskritChannelName) firstChannel.on(.attached) {_ in - firstChannelSerial = firstChannel.internal.serial + firstChannelSerial = firstChannel.internal.channelSerial partialDone() } let secondChannel = client.channels.get(koreanChannelName) secondChannel.on(.attached) {_ in - secondChannelSerial = secondChannel.internal.serial + secondChannelSerial = secondChannel.internal.channelSerial secondChannel.publish(nil, data: "message") { error in expect(error).to(beNil()) @@ -3539,7 +3539,7 @@ class RealtimeClientConnectionTests: XCTestCase { client.connection.once(.connected) { stateChange in let channel = client.channels.get(channelName) channel.on(.attached) { _ in - expectedChannelSerial = channel.internal.serial + expectedChannelSerial = channel.internal.channelSerial partialDone() } channel.attach() @@ -3556,7 +3556,7 @@ class RealtimeClientConnectionTests: XCTestCase { XCTAssertTrue(recoveredClient.channels.exists(channelName)) let recoveredChannel = recoveredClient.channels.get(channelName) - expect(recoveredChannel.internal.serial).to(equal(expectedChannelSerial)) + expect(recoveredChannel.internal.channelSerial).to(equal(expectedChannelSerial)) } // RTN16k diff --git a/Test/Tests/RealtimeClientPresenceTests.swift b/Test/Tests/RealtimeClientPresenceTests.swift index 5cb2407c7..f345d08b5 100644 --- a/Test/Tests/RealtimeClientPresenceTests.swift +++ b/Test/Tests/RealtimeClientPresenceTests.swift @@ -125,10 +125,10 @@ class RealtimeClientPresenceTests: XCTestCase { XCTAssertEqual(attached.flags & 0x1, 0) XCTAssertFalse(attached.hasPresence) XCTAssertFalse(channel.presence.syncComplete) - XCTAssertFalse(channel.internal.presenceMap.syncComplete) + XCTAssertFalse(channel.internal.presence.syncComplete) } - func skipped__test__010__Presence__ProtocolMessage_bit_flag__when_members_are_present() throws { + func test__FLAKY__010__Presence__ProtocolMessage_bit_flag__when_members_are_present() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) @@ -188,8 +188,8 @@ class RealtimeClientPresenceTests: XCTestCase { fail("TestProxyTransport is not set"); return } - XCTAssertFalse(channel.internal.presenceMap.syncInProgress) - expect(channel.internal.presenceMap.members).to(beEmpty()) + XCTAssertFalse(channel.internal.presence.syncInProgress) + expect(channel.internal.presence.members).to(beEmpty()) waitUntil(timeout: testTimeout) { done in channel.presence.subscribe(.present) { msg in @@ -266,8 +266,8 @@ class RealtimeClientPresenceTests: XCTestCase { fail("TestProxyTransport is not set"); return } - XCTAssertFalse(channel.internal.presenceMap.syncInProgress) - expect(channel.internal.presenceMap.members).to(beEmpty()) + XCTAssertFalse(channel.internal.presence.syncInProgress) + expect(channel.internal.presence.members).to(beEmpty()) waitUntil(timeout: testTimeout) { done in var aClientHasLeft = false @@ -307,7 +307,7 @@ class RealtimeClientPresenceTests: XCTestCase { // RTP19 - func skipped__test__013__Presence__PresenceMap_has_existing_members_when_a_SYNC_is_started__should_ensure_that_members_no_longer_present_on_the_channel_are_removed_from_the_local_PresenceMap_once_the_sync_is_complete() throws { + func test__FLAKY__013__Presence__PresenceMap_has_existing_members_when_a_SYNC_is_started__should_ensure_that_members_no_longer_present_on_the_channel_are_removed_from_the_local_PresenceMap_once_the_sync_is_complete() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let channelName = test.uniqueChannelName() @@ -330,12 +330,12 @@ class RealtimeClientPresenceTests: XCTestCase { } } - XCTAssertEqual(channel.internal.presenceMap.members.count, 2) + XCTAssertEqual(channel.internal.presence.members.count, 2) // Inject a local member - let localMember = ARTPresenceMessage(clientId: NSUUID().uuidString, action: .enter, connectionId: "another", id: "another:0:0") - channel.internal.presenceMap.add(localMember) - XCTAssertEqual(channel.internal.presenceMap.members.count, 3) - XCTAssertEqual(channel.internal.presenceMap.members.filter { memberKey, _ in memberKey.contains(localMember.clientId!) }.count, 1) + let internalMember = ARTPresenceMessage(clientId: NSUUID().uuidString, action: .enter, connectionId: "another", id: "another:0:0") + channel.internal.presence.add(internalMember) + XCTAssertEqual(channel.internal.presence.members.count, 3) + XCTAssertEqual(channel.internal.presence.members.filter { memberKey, _ in memberKey.contains(internalMember.clientId!) }.count, 1) waitUntil(timeout: testTimeout) { done in channel.presence.get { members, error in @@ -343,25 +343,17 @@ class RealtimeClientPresenceTests: XCTestCase { guard let members = members, members.count == 3 else { fail("Should at least have 3 members"); done(); return } - XCTAssertEqual(members.filter { $0.clientId == localMember.clientId }.count, 1) + XCTAssertEqual(members.filter { $0.clientId == internalMember.clientId }.count, 1) done() } } waitUntil(timeout: testTimeout) { done in channel.presence.subscribe(.leave) { leave in - XCTAssertEqual(leave.clientId, localMember.clientId) + XCTAssertEqual(leave.clientId, internalMember.clientId) done() } - - // Request a sync - let syncMessage = ARTProtocolMessage() - syncMessage.action = .sync - syncMessage.channel = channel.name - guard let transport = client.internal.transport as? TestProxyTransport else { - fail("TestProxyTransport is not set"); done(); return - } - transport.send(syncMessage) + client.requestPresenceSyncForChannel(channel) } waitUntil(timeout: testTimeout) { done in @@ -370,7 +362,7 @@ class RealtimeClientPresenceTests: XCTestCase { guard let members = members, members.count == 2 else { fail("Should at least have 2 members"); done(); return } - expect(members.filter { $0.clientId == localMember.clientId }).to(beEmpty()) + expect(members.filter { $0.clientId == internalMember.clientId }).to(beEmpty()) done() } } @@ -387,8 +379,8 @@ class RealtimeClientPresenceTests: XCTestCase { let channel = client.channels.get(channelName) // Inject local members - channel.internal.presenceMap.add(ARTPresenceMessage(clientId: "tester1", action: .enter, connectionId: "another", id: "another:0:0")) - channel.internal.presenceMap.add(ARTPresenceMessage(clientId: "tester2", action: .enter, connectionId: "another", id: "another:0:1")) + channel.internal.presence.add(ARTPresenceMessage(clientId: "tester1", action: .enter, connectionId: "another", id: "another:0:0")) + channel.internal.presence.add(ARTPresenceMessage(clientId: "tester2", action: .enter, connectionId: "another", id: "another:0:1")) guard let transport = client.internal.transport as? TestProxyTransport else { fail("TestProxyTransport is not set"); return @@ -429,7 +421,7 @@ class RealtimeClientPresenceTests: XCTestCase { } // RTP4 - func skipped__test__002__Presence__should_receive_all_250_members() throws { + func test__FLAKY__002__Presence__should_receive_all_250_members() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) var clientSource: ARTRealtime! @@ -531,9 +523,9 @@ class RealtimeClientPresenceTests: XCTestCase { let channel = client.channels.get(test.uniqueChannelName()) let listener = channel.presence.subscribe { _ in }! - XCTAssertEqual(channel.internal.presenceEventEmitter.anyListeners.count, 1) + XCTAssertEqual(channel.internal.presence.eventEmitter.anyListeners.count, 1) channel.presence.unsubscribe(listener) - XCTAssertEqual(channel.internal.presenceEventEmitter.anyListeners.count, 0) + XCTAssertEqual(channel.internal.presence.eventEmitter.anyListeners.count, 0) } // RTP5 @@ -560,7 +552,7 @@ class RealtimeClientPresenceTests: XCTestCase { } } - func skipped__test__019__Presence__Channel_state_change_side_effects__if_the_channel_enters_the_FAILED_state__should_clear_the_PresenceMap_including_local_members_and_does_not_emit_any_presence_events() throws { + func test__FLAKY__019__Presence__Channel_state_change_side_effects__if_the_channel_enters_the_FAILED_state__should_clear_the_PresenceMap_including_local_members_and_does_not_emit_any_presence_events() throws { let test = Test() let client = ARTRealtime(options: try AblyTests.commonAppSetup(for: test)) defer { client.dispose(); client.close() } @@ -578,8 +570,8 @@ class RealtimeClientPresenceTests: XCTestCase { } } - XCTAssertEqual(channel.internal.presenceMap.members.count, 1) - XCTAssertEqual(channel.internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(channel.internal.presence.members.count, 1) + XCTAssertEqual(channel.internal.presence.internalMembers.count, 1) channel.subscribe { _ in fail("Shouldn't receive any presence event") @@ -588,8 +580,8 @@ class RealtimeClientPresenceTests: XCTestCase { waitUntil(timeout: testTimeout) { done in channel.once(.failed) { _ in - expect(channel.internal.presenceMap.members).to(beEmpty()) - expect(channel.internal.presenceMap.localMembers).to(beEmpty()) + expect(channel.internal.presence.members).to(beEmpty()) + expect(channel.internal.presence.internalMembers).to(beEmpty()) done() } AblyTests.queue.async { @@ -608,13 +600,16 @@ class RealtimeClientPresenceTests: XCTestCase { waitUntil(timeout: testTimeout) { done in channel.once(.attaching) { _ in - channel.detach() + AblyTests.queue.async { + channel.internal.detachChannel(ChannelStateChangeParams(state: .error, errorInfo: ARTErrorInfo.create(withCode: 0, message: "Fail test"))) + } } channel.presence.enterClient("user", data: nil) { error in XCTAssertNotNil(error) - XCTAssertEqual(client.internal.queuedMessages.count, 0) + XCTAssertEqual(channel.internal.presence.pendingPresence.count, 0) done() } + XCTAssertEqual(channel.internal.presence.pendingPresence.count, 1) } } @@ -636,8 +631,8 @@ class RealtimeClientPresenceTests: XCTestCase { } } - XCTAssertEqual(channel.internal.presenceMap.members.count, 1) - XCTAssertEqual(channel.internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(channel.internal.presence.members.count, 1) + XCTAssertEqual(channel.internal.presence.internalMembers.count, 1) channel.subscribe { _ in fail("Shouldn't receive any presence event") @@ -646,8 +641,8 @@ class RealtimeClientPresenceTests: XCTestCase { waitUntil(timeout: testTimeout) { done in channel.once(.detached) { _ in - expect(channel.internal.presenceMap.members).to(beEmpty()) - expect(channel.internal.presenceMap.localMembers).to(beEmpty()) + expect(channel.internal.presence.members).to(beEmpty()) + expect(channel.internal.presence.internalMembers).to(beEmpty()) done() } channel.detach() @@ -686,9 +681,9 @@ class RealtimeClientPresenceTests: XCTestCase { } channel2.presence.subscribe(.enter) { _ in if channel2.presence.syncComplete { - XCTAssertEqual(channel2.internal.presenceMap.members.count, 2) + XCTAssertEqual(channel2.internal.presence.members.count, 2) } else { - XCTAssertEqual(channel2.internal.presenceMap.members.count, 1) + XCTAssertEqual(channel2.internal.presence.members.count, 1) } channel2.presence.unsubscribe() partialDone() @@ -696,7 +691,7 @@ class RealtimeClientPresenceTests: XCTestCase { XCTAssertEqual(client2.internal.queuedMessages.count, 1) XCTAssertFalse(channel2.presence.syncComplete) - XCTAssertEqual(channel2.internal.presenceMap.members.count, 0) + XCTAssertEqual(channel2.internal.presence.members.count, 0) } guard let transport = client2.internal.transport as? TestProxyTransport else { @@ -706,7 +701,7 @@ class RealtimeClientPresenceTests: XCTestCase { XCTAssertEqual(transport.protocolMessagesReceived.filter { $0.action == .sync }.count, 1) expect(channel2.presence.syncComplete).toEventually(beTrue(), timeout: testTimeout) - XCTAssertEqual(channel2.internal.presenceMap.members.count, 2) + XCTAssertEqual(channel2.internal.presence.members.count, 2) } // RTP5f @@ -749,7 +744,7 @@ class RealtimeClientPresenceTests: XCTestCase { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let channelName = test.uniqueChannelName() - + let clientMembers = AblyTests.addMembersSequentiallyToChannel(channelName, members: 2, options: options) defer { clientMembers.dispose(); clientMembers.close() } @@ -770,7 +765,7 @@ class RealtimeClientPresenceTests: XCTestCase { // Move to SUSPENDED let ttlHookToken = mainClient.overrideConnectionStateTTL(1.0) defer { ttlHookToken.remove() } - + let leavesChannel = leavesClient.channels.get(channelName) let mainChannel = mainClient.channels.get(channelName) @@ -812,9 +807,9 @@ class RealtimeClientPresenceTests: XCTestCase { done() } } - + var presenceEvents = [ARTPresenceMessage]() - + waitUntil(timeout: testTimeout) { done in let partialDone = AblyTests.splitDone(4, done: done) mainChannel.presence.subscribe { presence in @@ -826,8 +821,8 @@ class RealtimeClientPresenceTests: XCTestCase { } mainChannel.once(.suspended) { _ in mainChannel.internalSync { _internal in - XCTAssertEqual(_internal.presenceMap.members.count, 4) // "main", "user1", "user2", "leaves" - XCTAssertEqual(_internal.presenceMap.localMembers.count, 1) // "main" + XCTAssertEqual(_internal.presence.members.count, 4) // "main", "user1", "user2", "leaves" + XCTAssertEqual(_internal.presence.internalMembers.count, 1) // "main" } leavesChannel.presence.leave(nil) { error in XCTAssertNil(error) @@ -867,10 +862,10 @@ class RealtimeClientPresenceTests: XCTestCase { done() } } - + mainChannel.internalSync { _internal in - XCTAssertEqual(_internal.presenceMap.members.count, 3) // "main", "user1", "user2" - XCTAssertEqual(_internal.presenceMap.localMembers.count, 1) // "main" + XCTAssertEqual(_internal.presence.members.count, 3) // "main", "user1", "user2" + XCTAssertEqual(_internal.presence.internalMembers.count, 1) // "main" } } @@ -915,9 +910,9 @@ class RealtimeClientPresenceTests: XCTestCase { let channel = client.channels.get(test.uniqueChannelName()) let listener = channel.presence.subscribe(.present) { _ in }! - XCTAssertEqual(channel.internal.presenceEventEmitter.listeners.count, 1) + XCTAssertEqual(channel.internal.presence.eventEmitter.listeners.count, 1) channel.presence.unsubscribe(.present, listener: listener) - XCTAssertEqual(channel.internal.presenceEventEmitter.listeners.count, 0) + XCTAssertEqual(channel.internal.presence.eventEmitter.listeners.count, 0) } // RTP6 @@ -1032,7 +1027,7 @@ class RealtimeClientPresenceTests: XCTestCase { // FIXME: Fix flaky presence tests and re-enable. See https://ably-real-time.slack.com/archives/C030C5YLY/p1623172436085700 // RTP8b - func skipped__test__030__Presence__enter__optionally_a_callback_can_be_provided_that_is_called_for_success() throws { + func test__FLAKY__030__Presence__enter__optionally_a_callback_can_be_provided_that_is_called_for_success() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) options.clientId = "john" @@ -1304,7 +1299,7 @@ class RealtimeClientPresenceTests: XCTestCase { defer { client.dispose(); client.close() } let channel = client.channels.get(test.uniqueChannelName()) - XCTAssertEqual(channel.internal.presenceMap.members.count, 0) + XCTAssertEqual(channel.internal.presence.members.count, 0) waitUntil(timeout: testTimeout) { done in channel.presence.subscribe(.enter) { member in XCTAssertEqual(member.clientId, "john") @@ -1407,7 +1402,7 @@ class RealtimeClientPresenceTests: XCTestCase { channel.presence.enter("online") } - expect(channel.internal.presenceMap.members).toEventually(haveCount(1), timeout: testTimeout) + expect(channel.internal.presence.members).toEventually(haveCount(1), timeout: testTimeout) waitUntil(timeout: testTimeout) { done in channel.presence.subscribe(.leave) { member in @@ -1417,7 +1412,7 @@ class RealtimeClientPresenceTests: XCTestCase { channel.presence.leave("offline") } - expect(channel.internal.presenceMap.members).toEventually(haveCount(0), timeout: testTimeout) + expect(channel.internal.presence.members).toEventually(haveCount(0), timeout: testTimeout) } // RTP10a @@ -1447,7 +1442,7 @@ class RealtimeClientPresenceTests: XCTestCase { } // RTP2 - func skipped__test__003__Presence__should_be_used_a_PresenceMap_to_maintain_a_list_of_members() throws { + func test__FLAKY__003__Presence__should_be_used_a_PresenceMap_to_maintain_a_list_of_members() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) var clientSecondary: ARTRealtime! @@ -1506,7 +1501,7 @@ class RealtimeClientPresenceTests: XCTestCase { } } - guard let intialPresenceMessage = channel.internal.presenceMap.members["\(channel.internal.connectionId):tester"] else { + guard let intialPresenceMessage = channel.internal.presence.members["\(channel.internal.connectionId):tester"] else { fail("Missing Presence message"); return } @@ -1524,7 +1519,7 @@ class RealtimeClientPresenceTests: XCTestCase { } } - guard let updatedPresenceMessage = channel.internal.presenceMap.members["\(channel.internal.connectionId):tester"] else { + guard let updatedPresenceMessage = channel.internal.presence.members["\(channel.internal.connectionId):tester"] else { fail("Missing Presence message"); return } @@ -1738,10 +1733,10 @@ class RealtimeClientPresenceTests: XCTestCase { } return protocolMessage } - channel.internal.presenceMap.testSuite_injectIntoMethod(after: #selector(ARTPresenceMap.endSync)) { - XCTAssertFalse(channel.internal.presenceMap.syncInProgress) - XCTAssertEqual(channel.internal.presenceMap.members.count, 120) - XCTAssertEqual(channel.internal.presenceMap.members.filter { _, presence in presence.clientId == "user110" && presence.action == .present }.count, 1) + channel.internal.presence.testSuite_injectIntoMethod(after: #selector(ARTRealtimePresenceInternal.endSync)) { + XCTAssertFalse(channel.internal.presence.syncInProgress) + XCTAssertEqual(channel.internal.presence.members.count, 120) + XCTAssertEqual(channel.internal.presence.members.filter { _, presence in presence.clientId == "user110" && presence.action == .present }.count, 1) partialDone() } channel.attach { error in @@ -1791,10 +1786,10 @@ class RealtimeClientPresenceTests: XCTestCase { } return protocolMessage } - channel.internal.presenceMap.testSuite_injectIntoMethod(after: #selector(ARTPresenceMap.endSync)) { - XCTAssertFalse(channel.internal.presenceMap.syncInProgress) - XCTAssertEqual(channel.internal.presenceMap.members.count, 119) - expect(channel.internal.presenceMap.members.filter { _, presence in presence.clientId == "user110" }).to(beEmpty()) + channel.internal.presence.testSuite_injectIntoMethod(after: #selector(ARTRealtimePresenceInternal.endSync)) { + XCTAssertFalse(channel.internal.presence.syncInProgress) + XCTAssertEqual(channel.internal.presence.members.count, 119) + expect(channel.internal.presence.members.filter { _, presence in presence.clientId == "user110" }).to(beEmpty()) partialDone() } channel.attach { error in @@ -1824,13 +1819,13 @@ class RealtimeClientPresenceTests: XCTestCase { } } - XCTAssertEqual(channel.internal.presenceMap.members.filter { _, presence in presence.action == .present }.count, 1) - expect(channel.internal.presenceMap.members.filter { _, presence in presence.action == .enter }).to(beEmpty()) + XCTAssertEqual(channel.internal.presence.members.filter { _, presence in presence.action == .present }.count, 1) + expect(channel.internal.presence.members.filter { _, presence in presence.action == .enter }).to(beEmpty()) } // FIXME: Fix flaky presence tests and re-enable. See https://ably-real-time.slack.com/archives/C030C5YLY/p1623172436085700 // RTP2d - func skipped__test__047__Presence__PresenceMap__if_action_of_UPDATE_arrives__it_should_be_added_to_the_presence_map_with_the_action_set_to_PRESENT() throws { + func test__FLAKY__047__Presence__PresenceMap__if_action_of_UPDATE_arrives__it_should_be_added_to_the_presence_map_with_the_action_set_to_PRESENT() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let client = ARTRealtime(options: options) @@ -1853,9 +1848,9 @@ class RealtimeClientPresenceTests: XCTestCase { } } - XCTAssertEqual(channel.internal.presenceMap.members.count, 1) - XCTAssertEqual(channel.internal.presenceMap.members.filter { _, presence in presence.action == .present }.count, 1) - expect(channel.internal.presenceMap.members.filter { _, presence in presence.action == .update }).to(beEmpty()) + XCTAssertEqual(channel.internal.presence.members.count, 1) + XCTAssertEqual(channel.internal.presence.members.filter { _, presence in presence.action == .present }.count, 1) + expect(channel.internal.presence.members.filter { _, presence in presence.action == .update }).to(beEmpty()) } // RTP2d @@ -1873,8 +1868,8 @@ class RealtimeClientPresenceTests: XCTestCase { waitUntil(timeout: testTimeout) { done in let partialDone = AblyTests.splitDone(2, done: done) - channel.internal.presenceMap.testSuite_injectIntoMethod(after: #selector(ARTPresenceMap.endSync)) { - XCTAssertFalse(channel.internal.presenceMap.syncInProgress) + channel.internal.presence.testSuite_injectIntoMethod(after: #selector(ARTRealtimePresenceInternal.endSync)) { + XCTAssertFalse(channel.internal.presence.syncInProgress) partialDone() } channel.attach { error in @@ -1883,11 +1878,11 @@ class RealtimeClientPresenceTests: XCTestCase { } } - XCTAssertEqual(channel.internal.presenceMap.members.count, 1) + XCTAssertEqual(channel.internal.presence.members.count, 1) } // RTP2e - func skipped__test__049__Presence__PresenceMap__if_a_SYNC_is_not_in_progress__then_when_a_presence_message_with_an_action_of_LEAVE_arrives__that_memberKey_should_be_deleted_from_the_presence_map__if_present() throws { + func test__FLAKY__049__Presence__PresenceMap__if_a_SYNC_is_not_in_progress__then_when_a_presence_message_with_an_action_of_LEAVE_arrives__that_memberKey_should_be_deleted_from_the_presence_map__if_present() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) @@ -1912,9 +1907,9 @@ class RealtimeClientPresenceTests: XCTestCase { } } - expect(channel.internal.presenceMap.syncInProgress).toEventually(beFalse(), timeout: testTimeout) + expect(channel.internal.presence.syncInProgress).toEventually(beFalse(), timeout: testTimeout) - guard let user11MemberKey = channel.internal.presenceMap.members["\(clientMembers?.connection.id ?? ""):user11"]?.memberKey() else { + guard let user11MemberKey = channel.internal.presence.members["\(clientMembers?.connection.id ?? ""):user11"]?.memberKey() else { fail("user11 memberKey is not present"); return } @@ -1928,12 +1923,12 @@ class RealtimeClientPresenceTests: XCTestCase { channel.presence.unsubscribe() channel.internalSync { _internal in - expect(_internal.presenceMap.members.filter { _, presence in presence.memberKey() == user11MemberKey }).to(beEmpty()) + expect(_internal.presence.members.filter { _, presence in presence.memberKey() == user11MemberKey }).to(beEmpty()) } } // RTP2f - func skipped__test__050__Presence__PresenceMap__if_a_SYNC_is_in_progress__then_when_a_presence_message_with_an_action_of_LEAVE_arrives__it_should_be_stored_in_the_presence_map_with_the_action_set_to_ABSENT() throws { + func test__FLAKY__050__Presence__PresenceMap__if_a_SYNC_is_in_progress__then_when_a_presence_message_with_an_action_of_LEAVE_arrives__it_should_be_stored_in_the_presence_map_with_the_action_set_to_ABSENT() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let channelName = test.uniqueChannelName() @@ -1959,8 +1954,8 @@ class RealtimeClientPresenceTests: XCTestCase { partialDone() } - hook = channel.internal.presenceMap.testSuite_getArgument( - from: #selector(ARTPresenceMap.internalAdd(_:withSessionId:)), + hook = channel.internal.presence.testSuite_getArgument( + from: #selector(ARTRealtimePresenceInternal.internalAdd(_:withSessionId:)), at: 0 ) { arg in let m = arg as? ARTPresenceMessage @@ -1971,7 +1966,7 @@ class RealtimeClientPresenceTests: XCTestCase { channel.attach { error in XCTAssertNil(error) - XCTAssertTrue(channel.internal.presenceMap.syncInProgress) + XCTAssertTrue(channel.internal.presence.syncInProgress) // Inject a fabricated Presence message let leaveMessage = ARTProtocolMessage() @@ -1987,16 +1982,16 @@ class RealtimeClientPresenceTests: XCTestCase { hook?.remove() channel.presence.unsubscribe() - expect(channel.internal.presenceMap.syncInProgress).toEventually(beFalse(), timeout: testTimeout) - expect(channel.internal.presenceMap.members.filter { _, presence in presence.action == .leave }).to(beEmpty()) - expect(channel.internal.presenceMap.members.filter { _, presence in presence.action == .absent }).to(beEmpty()) + expect(channel.internal.presence.syncInProgress).toEventually(beFalse(), timeout: testTimeout) + expect(channel.internal.presence.members.filter { _, presence in presence.action == .leave }).to(beEmpty()) + expect(channel.internal.presence.members.filter { _, presence in presence.action == .absent }).to(beEmpty()) // A single clientId may be present multiple times on the same channel via different client connections and that's way user11 is present because user11 presences messages were in distinct connections. - XCTAssertEqual(channel.internal.presenceMap.members.count, 20) + XCTAssertEqual(channel.internal.presence.members.count, 20) } // RTP2g - func skipped__test__051__Presence__PresenceMap__any_incoming_presence_message_that_passes_the_newness_check_should_be_emitted_on_the_Presence_object__with_an_event_name_set_to_its_original_action() throws { + func test__FLAKY__051__Presence__PresenceMap__any_incoming_presence_message_that_passes_the_newness_check_should_be_emitted_on_the_Presence_object__with_an_event_name_set_to_its_original_action() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let client = ARTRealtime(options: options) @@ -2016,8 +2011,8 @@ class RealtimeClientPresenceTests: XCTestCase { } channel.internalSync { _internal in - XCTAssertEqual(_internal.presenceMap.members.filter { _, presence in presence.action == .present }.count, 1) - expect(_internal.presenceMap.members.filter { _, presence in presence.action == .enter }).to(beEmpty()) + XCTAssertEqual(_internal.presence.members.filter { _, presence in presence.action == .present }.count, 1) + expect(_internal.presence.members.filter { _, presence in presence.action == .enter }).to(beEmpty()) } } @@ -2626,8 +2621,8 @@ class RealtimeClientPresenceTests: XCTestCase { } XCTAssertEqual(presence.action, ARTPresenceAction.enter) XCTAssertEqual(presence.connectionId, currentConnectionId) - XCTAssertEqual(channelA.internal.presenceMap.members.count, 1) - XCTAssertEqual(channelA.internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(channelA.internal.presence.members.count, 1) + XCTAssertEqual(channelA.internal.presence.internalMembers.count, 1) channelA.presence.unsubscribe() partialDone() } @@ -2637,8 +2632,8 @@ class RealtimeClientPresenceTests: XCTestCase { } expect(presence.action).to(equal(ARTPresenceAction.enter) || equal(ARTPresenceAction.present)) XCTAssertNotEqual(presence.connectionId, currentConnectionId) - XCTAssertEqual(channelB.internal.presenceMap.members.count, 1) - XCTAssertEqual(channelB.internal.presenceMap.localMembers.count, 0) + XCTAssertEqual(channelB.internal.presence.members.count, 1) + XCTAssertEqual(channelB.internal.presence.internalMembers.count, 0) channelB.presence.unsubscribe() partialDone() } @@ -2653,8 +2648,8 @@ class RealtimeClientPresenceTests: XCTestCase { } XCTAssertEqual(presence.action, ARTPresenceAction.enter) XCTAssertNotEqual(presence.connectionId, currentConnectionId) - XCTAssertEqual(channelA.internal.presenceMap.members.count, 2) - XCTAssertEqual(channelA.internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(channelA.internal.presence.members.count, 2) + XCTAssertEqual(channelA.internal.presence.internalMembers.count, 1) channelA.presence.unsubscribe() partialDone() } @@ -2664,8 +2659,8 @@ class RealtimeClientPresenceTests: XCTestCase { } XCTAssertEqual(presence.action, ARTPresenceAction.enter) XCTAssertEqual(presence.connectionId, currentConnectionId) - XCTAssertEqual(channelB.internal.presenceMap.members.count, 2) - XCTAssertEqual(channelB.internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(channelB.internal.presence.members.count, 2) + XCTAssertEqual(channelB.internal.presence.internalMembers.count, 1) channelB.presence.unsubscribe() partialDone() } @@ -2682,8 +2677,8 @@ class RealtimeClientPresenceTests: XCTestCase { XCTAssertEqual(presence.action, ARTPresenceAction.update) XCTAssertEqual(presence.data as? String, "hello") XCTAssertNotEqual(presence.connectionId, currentConnectionId) - XCTAssertEqual(channelA.internal.presenceMap.members.count, 2) - XCTAssertEqual(channelA.internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(channelA.internal.presence.members.count, 2) + XCTAssertEqual(channelA.internal.presence.internalMembers.count, 1) channelA.presence.unsubscribe() partialDone() } @@ -2694,8 +2689,8 @@ class RealtimeClientPresenceTests: XCTestCase { XCTAssertEqual(presence.action, ARTPresenceAction.update) XCTAssertEqual(presence.data as? String, "hello") XCTAssertEqual(presence.connectionId, currentConnectionId) - XCTAssertEqual(channelB.internal.presenceMap.members.count, 2) - XCTAssertEqual(channelB.internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(channelB.internal.presence.members.count, 2) + XCTAssertEqual(channelB.internal.presence.internalMembers.count, 1) channelB.presence.unsubscribe() partialDone() } @@ -2712,8 +2707,8 @@ class RealtimeClientPresenceTests: XCTestCase { XCTAssertEqual(presence.action, ARTPresenceAction.leave) XCTAssertEqual(presence.data as? String, "bye") XCTAssertNotEqual(presence.connectionId, currentConnectionId) - XCTAssertEqual(channelA.internal.presenceMap.members.count, 1) - XCTAssertEqual(channelA.internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(channelA.internal.presence.members.count, 1) + XCTAssertEqual(channelA.internal.presence.internalMembers.count, 1) channelA.presence.unsubscribe() partialDone() } @@ -2724,8 +2719,8 @@ class RealtimeClientPresenceTests: XCTestCase { XCTAssertEqual(presence.action, ARTPresenceAction.leave) XCTAssertEqual(presence.data as? String, "bye") XCTAssertEqual(presence.connectionId, currentConnectionId) - XCTAssertEqual(channelB.internal.presenceMap.members.count, 1) - XCTAssertEqual(channelB.internal.presenceMap.localMembers.count, 0) + XCTAssertEqual(channelB.internal.presence.members.count, 1) + XCTAssertEqual(channelB.internal.presence.internalMembers.count, 0) channelB.presence.unsubscribe() partialDone() } @@ -2734,7 +2729,7 @@ class RealtimeClientPresenceTests: XCTestCase { } // RTP17a - func skipped__test__081__Presence__private_and_internal_PresenceMap_containing_only_members_that_match_the_current_connectionId__all_members_belonging_to_the_current_connection_are_published_as_a_PresenceMessage_on_the_Channel_by_the_server_irrespective_of_whether_the_client_has_permission_to_subscribe_or_the_Channel_is_configured_to_publish_presence_events() throws { + func test__FLAKY__081__Presence__private_and_internal_PresenceMap_containing_only_members_that_match_the_current_connectionId__all_members_belonging_to_the_current_connection_are_published_as_a_PresenceMessage_on_the_Channel_by_the_server_irrespective_of_whether_the_client_has_permission_to_subscribe_or_the_Channel_is_configured_to_publish_presence_events() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let channelName = test.uniqueChannelName() @@ -2764,8 +2759,8 @@ class RealtimeClientPresenceTests: XCTestCase { } else { XCTFail("Expected members to be non-nil") } - XCTAssertEqual(channel.internal.presenceMap.members.count, 1) - XCTAssertEqual(channel.internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(channel.internal.presence.members.count, 1) + XCTAssertEqual(channel.internal.presence.internalMembers.count, 1) done() } } @@ -2776,7 +2771,7 @@ class RealtimeClientPresenceTests: XCTestCase { func skipped__test__082__Presence__private_and_internal_PresenceMap_containing_only_members_that_match_the_current_connectionId__events_applied_to_presence_map__should_be_applied_to_ENTER__PRESENT_or_UPDATE_events_with_a_connectionId_that_matches_the_current_client_s_connectionId() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) - let client = ARTRealtime(options: options) + let client = AblyTests.newRealtime(options).client defer { client.dispose(); client.close() } let channel = client.channels.get(test.uniqueChannelName()) @@ -2798,7 +2793,7 @@ class RealtimeClientPresenceTests: XCTestCase { } channel.internalSync { _internal in - XCTAssertEqual(_internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(_internal.presence.internalMembers.count, 1) } let additionalMember = ARTPresenceMessage( @@ -2810,15 +2805,15 @@ class RealtimeClientPresenceTests: XCTestCase { // Inject an additional member into the myMember set, then force a suspended state client.simulateSuspended(beforeSuspension: { done in - channel.internal.presenceMap.localMembers[additionalMember.clientId!] = additionalMember + channel.internal.presence.internalMembers[additionalMember.clientId!] = additionalMember done() }) expect(client.connection.state).toEventually(equal(.suspended), timeout: testTimeout) - XCTAssertEqual(channel.internal.presenceMap.localMembers.count, 2) + XCTAssertEqual(channel.internal.presence.internalMembers.count, 2) channel.internalSync { _internal in - XCTAssertEqual(_internal.presenceMap.localMembers.count, 2) + XCTAssertEqual(_internal.presence.internalMembers.count, 2) } waitUntil(timeout: testTimeout) { done in @@ -2830,10 +2825,10 @@ class RealtimeClientPresenceTests: XCTestCase { } // Await Sync - channel.internal.presenceMap.onceSyncEnds { _ in + channel.internal.presence.onceSyncEnds { _ in // Should remove the "two" member that was added manually because the connectionId // doesn't match and it's not synthesized, it will be re-entered. - XCTAssertEqual(channel.internal.presenceMap.localMembers.count, 1) + XCTAssertEqual(channel.internal.presence.internalMembers.count, 1) partialDone() } @@ -2853,9 +2848,7 @@ class RealtimeClientPresenceTests: XCTestCase { delay(1, closure: done) } - channel.internalAsync { _internal in - _internal.sync() - } + client.requestPresenceSyncForChannel(channel) XCTAssertFalse(channel.presence.syncComplete) waitUntil(timeout: testTimeout) { done in @@ -2909,7 +2902,7 @@ class RealtimeClientPresenceTests: XCTestCase { } channel.presence.unsubscribe() - expect(channel.internal.presenceMap.localMembers).to(haveCount(2)) + expect(channel.internal.presence.internalMembers).to(haveCount(2)) // All pending messages should complete (receive ACK or NACK) before disconnect for valid count of transport.protocolMessagesSent client.waitForPendingMessages() @@ -2920,7 +2913,7 @@ class RealtimeClientPresenceTests: XCTestCase { // RTP17i expect(channel.state).toEventually(equal(ARTRealtimeChannelState.attached), timeout: testTimeout) - expect(channel.internal.presenceMap.localMembers).to(haveCount(2)) + expect(channel.internal.presence.internalMembers).to(haveCount(2)) let newTransport = client.internal.transport as! TestProxyTransport expect(newTransport).toNot(beIdenticalTo(transport)) @@ -2977,9 +2970,9 @@ class RealtimeClientPresenceTests: XCTestCase { } waitUntil(timeout: .seconds(20)) { done in - channel.internal.presenceMap.onceSyncEnds { _ in + channel.internal.presence.onceSyncEnds { _ in // Synthesized leave - expect(channel.internal.presenceMap.localMembers).to(beEmpty()) + expect(channel.internal.presence.internalMembers).to(beEmpty()) done() } client.internal.onDisconnected() @@ -3000,7 +2993,7 @@ class RealtimeClientPresenceTests: XCTestCase { guard let presences = presences else { fail("Presences is nil"); done(); return } - XCTAssertTrue(channel.internal.presenceMap.syncComplete) + XCTAssertTrue(channel.internal.presence.syncComplete) XCTAssertEqual(presences.count, 1) expect(presences.map { $0.clientId }).to(contain(["one"])) done() @@ -3374,7 +3367,7 @@ class RealtimeClientPresenceTests: XCTestCase { // FIXME: Fix flaky presence tests and re-enable. See https://ably-real-time.slack.com/archives/C030C5YLY/p1623172436085700 // RTP11a - func skipped__test__100__Presence__get__should_return_a_list_of_current_members_on_the_channel() throws { + func test__FLAKY__100__Presence__get__should_return_a_list_of_current_members_on_the_channel() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) @@ -3585,7 +3578,7 @@ class RealtimeClientPresenceTests: XCTestCase { for i in 0 ..< 3 { let msg = ARTPresenceMessage(clientId: "client\(i)", action: .present, connectionId: "foo", id: "foo:0:0") msgs[msg.clientId!] = msg - channel.internal.presenceMap.internalAdd(msg) + channel.internal.presence.internalAdd(msg) } channel.presence.get(getParams) { result, err in @@ -3995,7 +3988,7 @@ class RealtimeClientPresenceTests: XCTestCase { let client = ARTRealtime(options: try AblyTests.commonAppSetup(for: test)) defer { client.dispose(); client.close() } let channel = client.channels.get(test.uniqueChannelName()) - XCTAssertEqual(channel.internal.presenceMap.members.count, 0) + XCTAssertEqual(channel.internal.presence.members.count, 0) let expectedData = ["test": 1] @@ -4020,7 +4013,7 @@ class RealtimeClientPresenceTests: XCTestCase { channel.presence.enterClient("john", data: nil) channel.presence.enterClient("sara", data: nil) - expect(channel.internal.presenceMap.members).toEventually(haveCount(3), timeout: testTimeout) + expect(channel.internal.presence.members).toEventually(haveCount(3), timeout: testTimeout) waitUntil(timeout: testTimeout) { done in channel.presence.get { members, _ in diff --git a/Test/Tests/RestClientPresenceTests.swift b/Test/Tests/RestClientPresenceTests.swift index 25bfb14f8..39f8c4791 100644 --- a/Test/Tests/RestClientPresenceTests.swift +++ b/Test/Tests/RestClientPresenceTests.swift @@ -97,7 +97,7 @@ class RestClientPresenceTests: XCTestCase { realtimeChannel.presence.enterClient("john", data: "web") realtimeChannel.presence.enterClient("casey", data: "mobile") - expect(realtimeChannel.internal.presenceMap.members).toEventually(haveCount(3), timeout: testTimeout) + expect(realtimeChannel.internal.presence.members).toEventually(haveCount(3), timeout: testTimeout) let query = ARTPresenceQuery() query.clientId = "john"