diff --git a/CHANGELOG.md b/CHANGELOG.md index a86ed00d9..59b5a0cc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Change Log +## [1.2.33](https://github.com/ably/ably-cocoa/tree/1.2.33) + +[Full Changelog](https://github.com/ably/ably-cocoa/compare/1.2.32...1.2.33) + +**What's Changed:** + +- Attach according to RTL7c by @maratal in https://github.com/ably/ably-cocoa/pull/1935 +- Enforce RTP6c attach rule for presence. by @maratal in https://github.com/ably/ably-cocoa/pull/1942 +- Max message size fix by @maratal in https://github.com/ably/ably-cocoa/pull/1947 +- Respect RSH3a3a by @maratal in https://github.com/ably/ably-cocoa/pull/1952 + +## [1.2.32](https://github.com/ably/ably-cocoa/tree/1.2.32) + +[Full Changelog](https://github.com/ably/ably-cocoa/compare/1.2.31...1.2.32) + +**Fixed issues:** + +- Thread priority inversion runtime warning [\#1897](https://github.com/ably/ably-cocoa/issues/1897) + +- Fix for ignored `ARTProtocolMessage.resumed` property [\#1920](https://github.com/ably/ably-cocoa/issues/1920) + ## [1.2.31](https://github.com/ably/ably-cocoa/tree/1.2.31) [Full Changelog](https://github.com/ably/ably-cocoa/compare/1.2.30...1.2.31) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f54091736..8f7645d0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,23 +60,19 @@ To run tests use `make test_[iOS|tvOS|macOS]`. These tests expect you to have a For each release, the following needs to be done: * Create a new branch `release/x.x.x` (where `x.x.x` is the new version number) from the `main` branch -* Run `make bump_[major|minor|patch]` to bump the new version number (creates a Git commit) -* Run [`github_changelog_generator`](https://github.com/github-changelog-generator/github-changelog-generator) to automate the update of the [CHANGELOG](./CHANGELOG.md). This may require some manual intervention, both in terms of how the command is run and how the change log file is modified. Your mileage may vary: - * The command you will need to run will look something like this: `github_changelog_generator -u ably -p ably-cocoa --since-tag 1.2.9 --output delta.md` - * Using the command above, `--output delta.md` writes changes made after `--since-tag` to a new file - * The contents of that new file (`delta.md`) then need to be manually inserted at the top of the `CHANGELOG.md`, changing the "Unreleased" heading and linking with the current version numbers - * Also ensure that the "Full Changelog" link points to the new version tag instead of the `HEAD` - * Commit this change: `git add CHANGELOG.md && git commit -m "Update change log."` -* Push both commits to origin: `git push -u origin release/x.x.x` +* Run `make bump_[major|minor|patch]` to bump the new version number. This will create a Git commit, push it to origin: `git push -u origin release/x.x.x` +* Go to [Github releases](https://github.com/ably/ably-cocoa/releases) and press the `Draft a new release` button. Choose your new branch as a target +* Press the `Choose a tag` dropdown and start typing a new tag, Github will suggest the `Create new tag x.x.x on publish` option. After you select it Github will unveil the `Generate release notes` button +* From the newly generated changes remove everything that don't make much sense to the library user +* Copy the final list of changes to the top of the `CHANGELOG.md` file. Modify as necessary to fit the existing format of this file +* Commit these changes and push to the origin `git add CHANGELOG.md && git commit -m "Update change log." && git push -u origin release/x.x.x` * Make a pull request against `main` and await approval of reviewer(s) * Once approved and/or any additional commits have been added, merge the PR (f you do this from Github's web interface then use the "Rebase and merge" option) * After merging the PR, wait for all CI jobs for `main` to pass. -* Checkout `main` locally, pulling in changes from above using `git checkout main && git pull` -* If any fixes are needed (e.g. the lint fails with warnings) then either commit them to `main` branch now if they are simple warning fixes or perhaps consider raising a new PR if they are complex or likely to need review. -* Create a tag for this version number using `git tag x.x.x` -* Push the tag using `git push origin x.x.x` -* Release an update for CocoaPods using `pod trunk push Ably.podspec`. Details on this command, as well as instructions for adding other contributors as maintainers, are at [Getting setup with Trunk](https://guides.cocoapods.org/making/getting-setup-with-trunk.html) in the [CocoaPods Guides](https://guides.cocoapods.org/) -* Add to [releases](https://github.com/ably/ably-cocoa/releases) +* If any fixes are needed (e.g. the lint fails with warnings) then either commit them to the `main` branch now (don't forget to pull the changes first `git checkout main && git pull`) if they are simple warning fixes or perhaps consider raising a new PR if they are complex or likely need a review. +* Publish your drafted release: * refer to previous releases for release notes format * attach to the release the prebuilt framework file (`Ably.framework.zip`) generated by Carthage – you can find this file in the `carthage-built-framework` artifact uploaded by the `check-pod` CI workflow +* Checkout `main` locally, pulling in changes using `git checkout main && git pull`. Make sure the new tag you need was created on publish +* Release an update for CocoaPods using `pod trunk push Ably.podspec`. Details on this command, as well as instructions for adding other contributors as maintainers, are at [Getting setup with Trunk](https://guides.cocoapods.org/making/getting-setup-with-trunk.html) in the [CocoaPods Guides](https://guides.cocoapods.org/) * Test the integration of the library in a Xcode project using Carthage and CocoaPods using the [installation guide](https://github.com/ably/ably-cocoa#installation-guide) diff --git a/README.md b/README.md index e661bca95..8c5f1b25e 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ You can install Ably for iOS and macOS through Package Manager, CocoaPods, Carth - [This apple guide](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app) explains the steps in more detail. - To install the `ably-cocoa` package in another **Swift Package**, then add the following to your `Package.Swift`: ```swift - .package(url: "https://github.com/ably/ably-cocoa", from: "1.2.31"), + .package(url: "https://github.com/ably/ably-cocoa", from: "1.2.33"), ``` ### Installing through [CocoaPods](https://cocoapods.org/) @@ -101,7 +101,7 @@ If you see, for example, a `dyld: Library not loaded: @rpath/AblyDeltaCodec.fram ### Manual installation -1. Get the code from GitHub [from the release page](https://github.com/ably/ably-cocoa/releases/tag/1.2.31), or clone it to get the latest, unstable and possibly underdocumented version: `git clone git@github.com:ably/ably-cocoa.git` +1. Get the code from GitHub [from the release page](https://github.com/ably/ably-cocoa/releases/tag/1.2.33), or clone it to get the latest, unstable and possibly underdocumented version: `git clone git@github.com:ably/ably-cocoa.git` 2. Drag the directory `ably-cocoa/ably-cocoa` into your project as a group. 3. Ably depends on our [MessagePack Fork](https://github.com/ably-forks/msgpack-objective-C) 0.2.0; get it [from the releases page](https://github.com/ably-forks/msgpack-objective-C/releases/tag/0.2.0-ably-1) and link it into your project. diff --git a/Scripts/jazzy.sh b/Scripts/jazzy.sh index 6b2526e7b..7519fce88 100755 --- a/Scripts/jazzy.sh +++ b/Scripts/jazzy.sh @@ -7,7 +7,7 @@ jazzy \ --objc \ --clean \ --author Ably \ - --module-version 1.2.31 \ + --module-version 1.2.33 \ --umbrella-header Source/include/Ably/Ably.h \ --framework-root Source \ --module Ably \ diff --git a/Source/ARTClientInformation.m b/Source/ARTClientInformation.m index 857e11f29..8c5cc8c77 100644 --- a/Source/ARTClientInformation.m +++ b/Source/ARTClientInformation.m @@ -6,7 +6,7 @@ #import NSString *const ARTClientInformationAgentNotVersioned = @"ARTClientInformationAgentNotVersioned"; -NSString *const ARTClientInformation_libraryVersion = @"1.2.31"; +NSString *const ARTClientInformation_libraryVersion = @"1.2.33"; static NSString *const _libraryName = @"ably-cocoa"; // NSOperatingSystemVersion has NSInteger as version components for some reason, so mitigate it here. diff --git a/Source/ARTConnection.m b/Source/ARTConnection.m index c708d585b..f56a71a40 100644 --- a/Source/ARTConnection.m +++ b/Source/ARTConnection.m @@ -1,10 +1,12 @@ #import "ARTConnection+Private.h" - +#import "ARTDefault.h" #import "ARTRealtime+Private.h" #import "ARTEventEmitter+Private.h" #import "ARTQueuedDealloc.h" #import "ARTRealtimeChannels+Private.h" #import "ARTRealtimeChannel+Private.h" +#import "ARTDefault+Private.h" +#import "ARTClientOptions+Private.h" #define IsInactiveConnectionState(state) (state == ARTRealtimeClosing || state == ARTRealtimeClosed || state == ARTRealtimeFailed || state == ARTRealtimeSuspended) @@ -193,6 +195,12 @@ - (NSString *)key_nosync { return _key; } +- (NSInteger)maxMessageSize { + if (_maxMessageSize) + return _maxMessageSize; + return _realtime.options.isProductionEnvironment ? [ARTDefault maxProductionMessageSize] : [ARTDefault maxSandboxMessageSize]; +} + - (ARTRealtimeConnectionState)state_nosync { return _state; } diff --git a/Source/ARTDefault.m b/Source/ARTDefault.m index 5daab3df1..ca7bd43c2 100644 --- a/Source/ARTDefault.m +++ b/Source/ARTDefault.m @@ -11,7 +11,8 @@ static NSString *const ARTDefault_realtimeHost = @"realtime.ably.io"; static NSTimeInterval _connectionStateTtl = 60.0; -static NSInteger _maxMessageSize = 65536; +static NSInteger _maxProductionMessageSize = 65536; +static NSInteger _maxSandboxMessageSize = 16384; @implementation ARTDefault @@ -70,7 +71,19 @@ + (NSTimeInterval)realtimeRequestTimeout { } + (NSInteger)maxMessageSize { - return _maxMessageSize; +#if DEBUG + return _maxSandboxMessageSize; +#else + return _maxProductionMessageSize; +#endif +} + ++ (NSInteger)maxSandboxMessageSize { + return _maxSandboxMessageSize; +} + ++ (NSInteger)maxProductionMessageSize { + return _maxProductionMessageSize; } + (void)setConnectionStateTtl:(NSTimeInterval)value { @@ -81,7 +94,23 @@ + (void)setConnectionStateTtl:(NSTimeInterval)value { + (void)setMaxMessageSize:(NSInteger)value { @synchronized (self) { - _maxMessageSize = value; +#if DEBUG + _maxSandboxMessageSize = value; +#else + _maxProductionMessageSize = value; +#endif + } +} + ++ (void)setMaxProductionMessageSize:(NSInteger)value { + @synchronized (self) { + _maxProductionMessageSize = value; + } +} + ++ (void)setMaxSandboxMessageSize:(NSInteger)value { + @synchronized (self) { + _maxSandboxMessageSize = value; } } diff --git a/Source/ARTEventEmitter.m b/Source/ARTEventEmitter.m index a4b2c974a..395ae69a7 100644 --- a/Source/ARTEventEmitter.m +++ b/Source/ARTEventEmitter.m @@ -176,8 +176,9 @@ - (instancetype)initWithQueues:(dispatch_queue_t)queue userQueue:(dispatch_queue return self; } -- (ARTEventListener *)on:(id)event callback:(void (^)(id))cb { - NSString *eventId = [NSString stringWithFormat:@"%p-%@", self, [event identification]]; +- (ARTEventListener *)_on:(nullable id)event callback:(void (^)(id))cb { + NSString *eventId = event == nil ? [NSString stringWithFormat:@"%p", self] : + [NSString stringWithFormat:@"%p-%@", self, [event identification]]; __block ARTEventListener *listener; id observer = [_notificationCenter addObserverForName:eventId object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { if (listener == nil || [listener invalidated]) return; @@ -186,12 +187,17 @@ - (ARTEventListener *)on:(id)event callback:(void (^)(id cb(note.object); }]; listener = [[ARTEventListener alloc] initWithId:eventId observer:observer handler:self center:_notificationCenter]; - [self addObject:listener toArrayWithKey:listener.eventId inDictionary:self.listeners]; + [self addObject:listener toArrayWithKey:event == nil ? nil : eventId]; return listener; } -- (ARTEventListener *)once:(id)event callback:(void (^)(id))cb { - NSString *eventId = [NSString stringWithFormat:@"%p-%@", self, [event identification]]; +- (ARTEventListener *)on:(id)event callback:(void (^)(id))cb { + return [self _on:event callback:cb]; +} + +- (ARTEventListener *)_once:(nullable id)event callback:(void (^)(id))cb { + NSString *eventId = event == nil ? [NSString stringWithFormat:@"%p", self] : + [NSString stringWithFormat:@"%p-%@", self, [event identification]]; __block ARTEventListener *listener; __weak typeof(self) weakSelf = self; // weak to avoid a warning, but strong should be safe too since the cycle is broken when the notification fires or the observer is cancelled id observer = [_notificationCenter addObserverForName:eventId object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { @@ -199,43 +205,24 @@ - (ARTEventListener *)once:(id)event callback:(void (^)( if ([listener hasTimer] && ![listener timerIsRunning]) return; if ([listener handled]) return; [listener removeObserver]; - [weakSelf removeObject:listener fromArrayWithKey:[listener eventId] inDictionary:[weakSelf listeners]]; + [weakSelf removeObject:listener fromArrayWithKey:event == nil ? nil : eventId]; cb(note.object); }]; listener = [[ARTEventListener alloc] initWithId:eventId observer:observer handler:self center:_notificationCenter]; - [self addObject:listener toArrayWithKey:listener.eventId inDictionary:self.listeners]; + [self addObject:listener toArrayWithKey:event == nil ? nil : eventId]; return listener; } +- (ARTEventListener *)once:(id)event callback:(void (^)(id))cb { + return [self _once:event callback:cb]; +} + - (ARTEventListener *)on:(void (^)(id))cb { - NSString *eventId = [NSString stringWithFormat:@"%p", self]; - __block ARTEventListener *listener; - id observer = [_notificationCenter addObserverForName:eventId object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { - if (listener == nil || [listener invalidated]) return; - if ([listener hasTimer] && ![listener timerIsRunning]) return; - [listener stopTimer]; - cb(note.object); - }]; - listener = [[ARTEventListener alloc] initWithId:eventId observer:observer handler:self center:_notificationCenter]; - [self.anyListeners addObject:listener]; - return listener; + return [self _on:nil callback:cb]; } - (ARTEventListener *)once:(void (^)(id))cb { - NSString *eventId = [NSString stringWithFormat:@"%p", self]; - __block ARTEventListener *listener; - __weak typeof(self) weakSelf = self; // weak to avoid a warning, but strong should be safe too since the cycle is broken when the notification fires or the observer is cancelled - id observer = [_notificationCenter addObserverForName:eventId object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { - if (listener == nil || [listener invalidated]) return; - if ([listener hasTimer] && ![listener timerIsRunning]) return; - if ([listener handled]) return; - [listener removeObserver]; - [[weakSelf anyListeners] removeObject:listener]; - cb(note.object); - }]; - listener = [[ARTEventListener alloc] initWithId:eventId observer:observer handler:self center:_notificationCenter]; - [self.anyListeners addObject:listener]; - return listener; + return [self _once:nil callback:cb]; } - (void)off:(id)event listener:(ARTEventListener *)listener { @@ -281,37 +268,44 @@ - (void)emit:(id)event with:(id)data { [self.notificationCenter postNotificationName:[NSString stringWithFormat:@"%p", self] object:data]; } -- (void)addObject:(id)obj toArrayWithKey:(id)key inDictionary:(NSMutableDictionary *)dict { - NSMutableArray *array = [dict objectForKey:key]; - if (array == nil) { - array = [[NSMutableArray alloc] init]; - [dict setObject:array forKey:key]; +- (void)addObject:(id)obj toArrayWithKey:(nullable id)key { + if (key == nil) { + [_anyListeners addObject:obj]; } - if ([array indexOfObject:obj] == NSNotFound) { - [array addObject:obj]; + else { + NSMutableArray *array = [_listeners objectForKey:key]; + if (array == nil) { + array = [[NSMutableArray alloc] init]; + [_listeners setObject:array forKey:key]; + } + if ([array indexOfObject:obj] == NSNotFound) { + [array addObject:obj]; + } } } -- (void)removeObject:(id)obj fromArrayWithKey:(id)key inDictionary:(NSMutableDictionary *)dict { - NSMutableArray *array = [dict objectForKey:key]; - if (array == nil) { - return; +- (void)removeObject:(id)obj fromArrayWithKey:(nullable id)key where:(nullable BOOL(^)(id))cond { + if (key == nil) { + [_anyListeners removeObject:obj]; } - [array removeObject:obj]; - if ([array count] == 0) { - [dict removeObjectForKey:key]; + else { + NSMutableArray *array = [_listeners objectForKey:key]; + if (array == nil) { + return; + } + if (cond) { + [array artRemoveWhere:cond]; + } else { + [array removeObject:obj]; + } + if ([array count] == 0) { + [_listeners removeObjectForKey:key]; + } } } -- (void)removeObject:(id)obj fromArrayWithKey:(id)key inDictionary:(NSMutableDictionary *)dict where:(BOOL(^)(id))cond { - NSMutableArray *array = [dict objectForKey:key]; - if (array == nil) { - return; - } - [array artRemoveWhere:cond]; - if ([array count] == 0) { - [dict removeObjectForKey:key]; - } +- (void)removeObject:(id)obj fromArrayWithKey:(id)key { + [self removeObject:obj fromArrayWithKey:key where:nil]; } @end diff --git a/Source/ARTPresenceMessage.m b/Source/ARTPresenceMessage.m index 7d2ded38a..16b4e241a 100644 --- a/Source/ARTPresenceMessage.m +++ b/Source/ARTPresenceMessage.m @@ -102,6 +102,8 @@ - (NSUInteger)hash { return @"Leave"; //3 case ARTPresenceUpdate: return @"Update"; //4 + default: + return @"All"; } } diff --git a/Source/ARTProtocolMessage.m b/Source/ARTProtocolMessage.m index e36d0bb1c..178b35269 100644 --- a/Source/ARTProtocolMessage.m +++ b/Source/ARTProtocolMessage.m @@ -77,12 +77,12 @@ - (id)copyWithZone:(NSZone *)zone { return pm; } - - (BOOL)mergeFrom:(ARTProtocolMessage *)src { +- (BOOL)mergeFrom:(ARTProtocolMessage *)src maxSize:(NSInteger)maxSize { if (![src.channel isEqualToString:self.channel] || src.action != self.action) { // RTL6d3 return NO; } - if ([self mergeWouldExceedMaxSize:src.messages]) { + if ([self mergeWithMessages:src.messages wouldExceedMaxSize:maxSize]) { // RTL6d1 return NO; } @@ -142,7 +142,7 @@ - (BOOL)clientIdsAreDifferent:(NSArray*)messages { } } -- (BOOL)mergeWouldExceedMaxSize:(NSArray*)messages { +- (BOOL)mergeWithMessages:(NSArray*)messages wouldExceedMaxSize:(NSInteger)maxSize { NSInteger queuedMessagesSize = 0; for (ARTMessage *message in self.messages) { queuedMessagesSize += [message messageSize]; @@ -152,10 +152,6 @@ - (BOOL)mergeWouldExceedMaxSize:(NSArray*)messages { messagesSize += [message messageSize]; } NSInteger totalSize = queuedMessagesSize + messagesSize; - NSInteger maxSize = [ARTDefault maxMessageSize]; - if (_connectionDetails.maxMessageSize) { - maxSize = _connectionDetails.maxMessageSize; - } return totalSize > maxSize; } diff --git a/Source/ARTPushActivationState.m b/Source/ARTPushActivationState.m index 751c921a2..96e0ea7bb 100644 --- a/Source/ARTPushActivationState.m +++ b/Source/ARTPushActivationState.m @@ -135,6 +135,9 @@ - (ARTPushActivationState *)transition:(ARTPushActivationEvent *)event { else if ([event isKindOfClass:[ARTPushActivationEventCalledActivate class]]) { return validateAndSync(self.machine, event, self.logger); } + else if ([event isKindOfClass:[ARTPushActivationEventGotPushDeviceDetails class]]) { + return self; // Consuming event (RSH3a3a) + } return nil; } diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m index 1f968654a..f5e20e3a7 100644 --- a/Source/ARTPushActivationStateMachine.m +++ b/Source/ARTPushActivationStateMachine.m @@ -37,6 +37,7 @@ @implementation ARTPushActivationStateMachine { ARTPushActivationState *_current; dispatch_queue_t _queue; dispatch_queue_t _userQueue; + NSMutableArray *_pendingEvents; } - (instancetype)initWithRest:(ARTRestInternal *const)rest @@ -76,6 +77,14 @@ - (instancetype)initWithRest:(ARTRestInternal *const)rest return self; } +- (NSArray *)pendingEvents { + __block NSArray *ret; + dispatch_sync(_queue, ^{ + ret = [self->_pendingEvents copy]; + }); + return ret; +} + - (ARTPushActivationEvent *)lastEvent { __block ARTPushActivationEvent *ret; dispatch_sync(_queue, ^{ @@ -132,7 +141,7 @@ - (void)handleEvent:(nonnull ARTPushActivationEvent *)event { if (maybeNext == nil) { break; } - [_pendingEvents art_dequeue]; + [_pendingEvents art_dequeue]; // consuming event ARTLogDebug(_logger, @"%@: transition: %@ -> %@", NSStringFromClass(self.class), NSStringFromClass(_current.class), NSStringFromClass(maybeNext.class)); if (self.transitions) self.transitions(event, _current, maybeNext); diff --git a/Source/ARTQueuedMessage.m b/Source/ARTQueuedMessage.m index 76c62654d..17c0dc821 100644 --- a/Source/ARTQueuedMessage.m +++ b/Source/ARTQueuedMessage.m @@ -25,8 +25,8 @@ - (NSString *)description { return [self.msg description]; } -- (BOOL)mergeFrom:(ARTProtocolMessage *)msg sentCallback:(ARTCallback)sentCallback ackCallback:(ARTStatusCallback)ackCallback { - if ([self.msg mergeFrom:msg]) { +- (BOOL)mergeFrom:(ARTProtocolMessage *)msg maxSize:(NSInteger)maxSize sentCallback:(ARTCallback)sentCallback ackCallback:(ARTStatusCallback)ackCallback { + if ([self.msg mergeFrom:msg maxSize:maxSize]) { if (sentCallback) { [self.sentCallbacks addObject:sentCallback]; } diff --git a/Source/ARTRealtime.m b/Source/ARTRealtime.m index 0b557e90e..4df091e40 100644 --- a/Source/ARTRealtime.m +++ b/Source/ARTRealtime.m @@ -742,18 +742,13 @@ - (void)performTransitionToState:(ARTRealtimeConnectionState)state withParams:(A } if ([self shouldSendEvents]) { - [self sendQueuedMessages]; - - // Channels for (ARTRealtimeChannelInternal *channel in channels) { - if (stateChange.previous == ARTRealtimeInitialized || - stateChange.previous == ARTRealtimeConnecting || - stateChange.previous == ARTRealtimeDisconnected) { - // RTL4i - [channel _attach:nil]; - } + ARTAttachRequestParams *const params = [[ARTAttachRequestParams alloc] initWithReason:stateChange.reason]; + [channel proceedAttachDetachWithParams:params]; } - } else if (![self shouldQueueEvents]) { + [self sendQueuedMessages]; + } + else if (!self.isActive) { if (!channelStateChangeParams) { if (stateChange.reason) { channelStateChangeParams = [[ARTChannelStateChangeParams alloc] initWithState:ARTStateError @@ -867,12 +862,6 @@ - (void)onConnected:(ARTProtocolMessage *)message { else { ARTLogWarn(self.logger, @"RT:%p connection \"%@\" has reconnected, but resume failed. Error: \"%@\"", self, message.connectionId, message.error.message); } - // Reattach all channels regardless resume success - RTN15c6, RTN15c7 - for (ARTRealtimeChannelInternal *channel in self.channels.nosyncIterable) { - ARTAttachRequestParams *const params = [[ARTAttachRequestParams alloc] initWithReason:message.error]; - [channel reattachWithParams:params]; - } - _resuming = false; } // If there's no previous connectionId, then don't reset the msgSerial //as it may have been set by recover data (unless the recover failed). @@ -1218,9 +1207,9 @@ - (BOOL)shouldSendEvents { } } -- (BOOL)shouldQueueEvents { - if(!self.options.queueMessages) { - return false; +- (BOOL)isActive { + if (self.shouldSendEvents) { + return true; } switch (self.connection.state_nosync) { case ARTRealtimeInitialized: @@ -1234,10 +1223,6 @@ - (BOOL)shouldQueueEvents { } } -- (BOOL)isActive { - return [self shouldQueueEvents] || [self shouldSendEvents]; -} - - (void)sendImpl:(ARTProtocolMessage *)pm reuseMsgSerial:(BOOL)reuseMsgSerial sentCallback:(ARTCallback)sentCallback ackCallback:(ARTStatusCallback)ackCallback { if (pm.ackRequired) { if (!reuseMsgSerial) { // RTN19a2 @@ -1284,22 +1269,35 @@ - (void)send:(ARTProtocolMessage *)msg reuseMsgSerial:(BOOL)reuseMsgSerial sentC if ([self shouldSendEvents]) { [self sendImpl:msg reuseMsgSerial:reuseMsgSerial sentCallback:sentCallback ackCallback:ackCallback]; } - else if ([self shouldQueueEvents]) { - ARTQueuedMessage *lastQueuedMessage = self.queuedMessages.lastObject; //RTL6d5 - BOOL merged = [lastQueuedMessage mergeFrom:msg sentCallback:nil ackCallback:ackCallback]; - if (!merged) { - ARTQueuedMessage *qm = [[ARTQueuedMessage alloc] initWithProtocolMessage:msg sentCallback:nil ackCallback:ackCallback]; - [self.queuedMessages addObject:qm]; - ARTLogDebug(self.logger, @"RT:%p (channel: %@) protocol message with action '%lu - %@' has been queued (%@)", self, msg.channel, (unsigned long)msg.action, ARTProtocolMessageActionToStr(msg.action), msg.messages); + // see RTL6c2, RTN19, RTN7 and TO3g + else if (msg.ackRequired) { + if (self.isActive && self.options.queueMessages) { + ARTQueuedMessage *lastQueuedMessage = self.queuedMessages.lastObject; //RTL6d5 + NSInteger maxSize = _connection.maxMessageSize; + BOOL merged = [lastQueuedMessage mergeFrom:msg maxSize:maxSize sentCallback:nil ackCallback:ackCallback]; + if (!merged) { + ARTQueuedMessage *qm = [[ARTQueuedMessage alloc] initWithProtocolMessage:msg sentCallback:sentCallback ackCallback:ackCallback]; + [self.queuedMessages addObject:qm]; + ARTLogDebug(self.logger, @"RT:%p (channel: %@) protocol message with action '%lu - %@' has been queued (%@)", self, msg.channel, (unsigned long)msg.action, ARTProtocolMessageActionToStr(msg.action), msg.messages); + } + else { + ARTLogVerbose(self.logger, @"RT:%p (channel: %@) message %@ has been bundled to %@", self, msg.channel, msg, lastQueuedMessage.msg); + } } + // RTL6c4 else { - ARTLogVerbose(self.logger, @"RT:%p (channel: %@) message %@ has been bundled to %@", self, msg.channel, msg, lastQueuedMessage.msg); + ARTErrorInfo *error = self.connection.error_nosync; + ARTLogDebug(self.logger, @"RT:%p (channel: %@) protocol message with action '%lu - %@' can't be sent or queued: %@", self, msg.channel, (unsigned long)msg.action, ARTProtocolMessageActionToStr(msg.action), error); + if (sentCallback) { + sentCallback(error); + } + if (ackCallback) { + ackCallback([ARTStatus state:ARTStateError info:error]); + } } } - else if (ackCallback) { - ARTErrorInfo *error = self.connection.errorReason_nosync; - if (!error) error = [ARTErrorInfo createWithCode:ARTErrorChannelOperationFailed status:400 message:[NSString stringWithFormat:@"not possile to send message (state is %@)", ARTRealtimeConnectionStateToStr(self.connection.state_nosync)]]; - ackCallback([ARTStatus state:ARTStateError info:error]); + else { + ARTLogDebug(self.logger, @"RT:%p (channel: %@) sending protocol message with action '%lu - %@' was ignored: %@", self, msg.channel, (unsigned long)msg.action, ARTProtocolMessageActionToStr(msg.action), self.connection.error_nosync); } } diff --git a/Source/ARTRealtimeChannel.m b/Source/ARTRealtimeChannel.m index ed860c3ea..e4510f3cf 100644 --- a/Source/ARTRealtimeChannel.m +++ b/Source/ARTRealtimeChannel.m @@ -305,6 +305,17 @@ - (BOOL)canBeReattached { } } +- (BOOL)shouldAttach { + switch (self.state_nosync) { + case ARTRealtimeChannelInitialized: + case ARTRealtimeChannelDetaching: + case ARTRealtimeChannelDetached: + return YES; + default: + return NO; + } +} + - (ARTErrorInfo *)errorReason_nosync { return _errorReason; } @@ -397,21 +408,11 @@ - (void)publishProtocolMessage:(ARTProtocolMessage *)pm callback:(ARTStatusCallb } } -- (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]; - } -} - -- (ARTEventListener *)subscribe:(ARTMessageCallback)callback { - return [self subscribeWithAttachCallback:nil callback:callback]; -} - -- (ARTEventListener *)subscribeWithAttachCallback:(ARTCallback)onAttach callback:(ARTMessageCallback)cb { +- (ARTEventListener *)_subscribe:(nullable NSString *)name onAttach:(nullable ARTCallback)onAttach callback:(nullable ARTMessageCallback)cb { if (cb) { ARTMessageCallback userCallback = cb; cb = ^(ARTMessage *_Nonnull m) { - if (self.state_nosync != ARTRealtimeChannelAttached) { //RTL17 + if (self.state_nosync != ARTRealtimeChannelAttached) { // RTL17 return; } dispatch_async(self->_userQueue, ^{ @@ -431,53 +432,33 @@ - (ARTEventListener *)subscribeWithAttachCallback:(ARTCallback)onAttach callback __block ARTEventListener *listener = nil; dispatch_sync(_queue, ^{ if (self.state_nosync == ARTRealtimeChannelFailed) { - if (onAttach) onAttach([ARTErrorInfo createWithCode:0 message:@"attempted to subscribe while channel is in FAILED state."]); - ARTLogWarn(self.logger, @"R:%p C:%p (%@) subscribe has been ignored (attempted to subscribe while channel is in FAILED state)", self->_realtime, self, self.name); + if (onAttach) onAttach([ARTErrorInfo createWithCode:ARTErrorChannelOperationFailedInvalidState message:@"attempted to subscribe while channel is in FAILED state."]); + ARTLogWarn(self.logger, @"R:%p C:%p (%@) subscribe of '%@' has been ignored (attempted to subscribe while channel is in FAILED state)", self->_realtime, self, self.name, name == nil ? @"all" : name); return; } - if (self.state_nosync == ARTRealtimeChannelInitialized) { //RTL7c + if (self.shouldAttach) { // RTL7c [self _attach:onAttach]; } - listener = [self.messagesEventEmitter on:cb]; - ARTLogVerbose(self.logger, @"R:%p C:%p (%@) subscribe to all events", self->_realtime, self, self.name); + listener = name == nil ? [self.messagesEventEmitter on:cb] : [self.messagesEventEmitter on:name callback:cb]; + ARTLogVerbose(self.logger, @"R:%p C:%p (%@) subscribe to '%@' event(s)", self->_realtime, self, self.name, name == nil ? @"all" : name); }); return listener; } +- (ARTEventListener *)subscribe:(ARTMessageCallback)cb { + return [self _subscribe:nil onAttach:nil callback:cb]; +} + +- (ARTEventListener *)subscribeWithAttachCallback:(ARTCallback)onAttach callback:(ARTMessageCallback)cb { + return [self _subscribe:nil onAttach:onAttach callback:cb]; +} + - (ARTEventListener *)subscribe:(NSString *)name callback:(ARTMessageCallback)cb { - return [self subscribe:name onAttach:nil callback:cb]; + return [self _subscribe:name onAttach:nil callback:cb]; } - (ARTEventListener *)subscribe:(NSString *)name onAttach:(ARTCallback)onAttach callback:(ARTMessageCallback)cb { - if (cb) { - ARTMessageCallback userCallback = cb; - cb = ^(ARTMessage *_Nonnull m) { - dispatch_async(self->_userQueue, ^{ - userCallback(m); - }); - }; - } - if (onAttach) { - ARTCallback userOnAttach = onAttach; - onAttach = ^(ARTErrorInfo *_Nullable e) { - dispatch_async(self->_userQueue, ^{ - userOnAttach(e); - }); - }; - } - - __block ARTEventListener *listener = nil; -dispatch_sync(_queue, ^{ - if (self.state_nosync == ARTRealtimeChannelFailed) { - if (onAttach) onAttach([ARTErrorInfo createWithCode:0 message:@"attempted to subscribe while channel is in FAILED state."]); - ARTLogWarn(self.logger, @"R:%p C:%p (%@) subscribe of '%@' has been ignored (attempted to subscribe while channel is in FAILED state)", self->_realtime, self, self.name, name); - return; - } - [self _attach:onAttach]; - listener = [self.messagesEventEmitter on:name callback:cb]; - ARTLogVerbose(self.logger, @"R:%p C:%p (%@) subscribe to event '%@'", self->_realtime, self, self.name, name); -}); - return listener; + return [self _subscribe:name onAttach:onAttach callback:cb]; } - (void)unsubscribe { @@ -873,6 +854,15 @@ - (void)reattachWithParams:(ARTAttachRequestParams *)params { } } +- (void)proceedAttachDetachWithParams:(ARTAttachRequestParams *)params { + if (self.state_nosync == ARTChannelEventDetaching) { + ARTLogDebug(self.logger, @"RT:%p C:%p (%@) %@ proceeding with detach", _realtime, self, self.name, ARTRealtimeChannelStateToStr(self.state_nosync)); + [self internalDetach:nil]; + } else { + [self reattachWithParams:params]; + } +} + - (void)internalAttach:(ARTCallback)callback withParams:(ARTAttachRequestParams *)params { switch (self.state_nosync) { case ARTRealtimeChannelDetaching: { @@ -896,13 +886,15 @@ - (void)internalAttach:(ARTCallback)callback withParams:(ARTAttachRequestParams if (callback) [_attachedEventEmitter once:callback]; // Set state: Attaching - const ARTState state = params.reason ? ARTStateError : ARTStateOk; - ARTChannelStateChangeParams *const stateChangeParams = [[ARTChannelStateChangeParams alloc] initWithState:state errorInfo:params.reason storeErrorInfo:NO retryAttempt:params.retryAttempt]; - [self performTransitionToState:ARTRealtimeChannelAttaching withParams:stateChangeParams]; - [self attachAfterChecks:callback]; + if (self.state_nosync != ARTRealtimeChannelAttaching) { + const ARTState state = params.reason ? ARTStateError : ARTStateOk; + ARTChannelStateChangeParams *const stateChangeParams = [[ARTChannelStateChangeParams alloc] initWithState:state errorInfo:params.reason storeErrorInfo:NO retryAttempt:params.retryAttempt]; + [self performTransitionToState:ARTRealtimeChannelAttaching withParams:stateChangeParams]; + } + [self attachAfterChecks]; } -- (void)attachAfterChecks:(ARTCallback)callback { +- (void)attachAfterChecks { ARTProtocolMessage *attachMessage = [[ARTProtocolMessage alloc] init]; attachMessage.action = ARTProtocolMessageAttach; attachMessage.channel = self.name; @@ -949,17 +941,6 @@ - (void)_detach:(ARTCallback)callback { ARTLogDebug(self.logger, @"RT:%p C:%p (%@) can't detach when not attached", _realtime, self, self.name); if (callback) callback(nil); return; - case ARTRealtimeChannelAttaching: { - ARTLogDebug(self.logger, @"RT:%p C:%p (%@) waiting for the completion of the attaching operation", _realtime, self, self.name); - [_attachedEventEmitter once:^(ARTErrorInfo *errorInfo) { - if (callback && errorInfo) { - callback(errorInfo); - return; - } - [self _detach:callback]; - }]; - return; - } case ARTRealtimeChannelDetaching: ARTLogDebug(self.logger, @"RT:%p C:%p (%@) already detaching", _realtime, self, self.name); if (callback) [_detachedEventEmitter once:callback]; @@ -982,6 +963,27 @@ - (void)_detach:(ARTCallback)callback { default: break; } + [self internalDetach:callback]; +} + +- (void)internalDetach:(ARTCallback)callback { + switch (self.state_nosync) { + case ARTRealtimeChannelAttaching: { + ARTLogDebug(self.logger, @"RT:%p C:%p (%@) waiting for the completion of the attaching operation", _realtime, self, self.name); + [_attachedEventEmitter once:^(ARTErrorInfo *errorInfo) { + if (callback && errorInfo) { + callback(errorInfo); + return; + } + [self _detach:callback]; + }]; + return; + } + default: + break; + } + + _errorReason = nil; if (![self.realtime isActive]) { ARTLogDebug(self.logger, @"RT:%p C:%p (%@) can't detach when not in an active state", _realtime, self, self.name); @@ -994,10 +996,10 @@ - (void)_detach:(ARTCallback)callback { ARTChannelStateChangeParams *const params = [[ARTChannelStateChangeParams alloc] initWithState:ARTStateOk]; [self performTransitionToState:ARTRealtimeChannelDetaching withParams:params]; - [self detachAfterChecks:callback]; + [self detachAfterChecks]; } -- (void)detachAfterChecks:(ARTCallback)callback { +- (void)detachAfterChecks { ARTProtocolMessage *detachMessage = [[ARTProtocolMessage alloc] init]; detachMessage.action = ARTProtocolMessageDetach; detachMessage.channel = self.name; @@ -1015,18 +1017,8 @@ - (void)detachAfterChecks:(ARTCallback)callback { [self performTransitionToState:ARTRealtimeChannelAttached withParams:params]; [self->_detachedEventEmitter emit:nil with:errorInfo]; }] startTimer]; - - if (![self.realtime shouldQueueEvents]) { - ARTEventListener *reconnectedListener = [self.realtime.connectedEventEmitter once:^(NSNull *n) { - // Disconnected and connected while detaching, re-detach. - [self detachAfterChecks:callback]; - }]; - [_detachedEventEmitter once:^(ARTErrorInfo *err) { - [self.realtime.connectedEventEmitter off:reconnectedListener]; - }]; - } - - if (self.presence.syncInProgress) { + + if (self.presence.syncInProgress_nosync) { [self.presence failsSync:[ARTErrorInfo createWithCode:ARTErrorChannelOperationFailed message:@"channel is being DETACHED"]]; } } @@ -1074,10 +1066,7 @@ - (BOOL)exceedMaxSize:(NSArray *)messages { for (ARTMessage *message in messages) { size += [message messageSize]; } - NSInteger maxSize = [ARTDefault maxMessageSize]; - if (self.realtime.connection.maxMessageSize) { - maxSize = self.realtime.connection.maxMessageSize; - } + NSInteger maxSize = _realtime.connection.maxMessageSize; return size > maxSize; } diff --git a/Source/ARTRealtimePresence.m b/Source/ARTRealtimePresence.m index 7d47bd006..8d27e63dc 100644 --- a/Source/ARTRealtimePresence.m +++ b/Source/ARTRealtimePresence.m @@ -144,6 +144,8 @@ - (BOOL)history:(ARTRealtimeHistoryQuery *_Nullable)query callback:(ARTPaginated #pragma mark - ARTRealtimePresenceInternal +static const NSUInteger ARTPresenceActionAll = NSIntegerMax; + NS_ASSUME_NONNULL_BEGIN @interface ARTRealtimePresenceInternal () @@ -247,7 +249,7 @@ - (void)get:(ARTRealtimePresenceQuery *)query callback:(ARTPresenceMessagesCallb callback(nil, error); return; } - const BOOL syncInProgress = self.syncInProgress; + const BOOL syncInProgress = self.syncInProgress_nosync; if (syncInProgress && query.waitForSync) { 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) { @@ -475,11 +477,7 @@ - (BOOL)syncComplete_nosync { // RTP6 -- (ARTEventListener *)subscribe:(ARTPresenceMessageCallback)callback { - return [self subscribeWithAttachCallback:nil callback:callback]; -} - -- (ARTEventListener *)subscribeWithAttachCallback:(ARTCallback)onAttach callback:(ARTPresenceMessageCallback)cb { +- (ARTEventListener *)_subscribe:(ARTPresenceAction)action onAttach:(nullable ARTCallback)onAttach callback:(nullable ARTPresenceMessageCallback)cb { if (cb) { ARTPresenceMessageCallback userCallback = cb; cb = ^(ARTPresenceMessage *_Nullable m) { @@ -501,48 +499,32 @@ - (ARTEventListener *)subscribeWithAttachCallback:(ARTCallback)onAttach callback dispatch_sync(_queue, ^{ if (self->_channel.state_nosync == ARTRealtimeChannelFailed) { if (onAttach) onAttach([ARTErrorInfo createWithCode:ARTErrorChannelOperationFailedInvalidState message:@"attempted to subscribe while channel is in Failed state."]); + ARTLogWarn(self.logger, @"R:%p C:%p (%@) presence subscribe to '%@' action(s) has been ignored (attempted to subscribe while channel is in FAILED state)", self->_realtime, self->_channel, self->_channel.name, ARTPresenceActionToStr(action)); return; } - [self->_channel _attach:onAttach]; - listener = [_eventEmitter on:cb]; - ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence subscribe to all actions", self->_realtime, self->_channel, self->_channel.name); + if (self->_channel.shouldAttach) { // RTP6c + [self->_channel _attach:onAttach]; + } + listener = action == ARTPresenceActionAll ? [_eventEmitter on:cb] : [_eventEmitter on:[ARTEvent newWithPresenceAction:action] callback:cb]; + ARTLogVerbose(self.logger, @"R:%p C:%p (%@) presence subscribe to '%@' action(s)", self->_realtime, self->_channel, self->_channel.name, ARTPresenceActionToStr(action)); }); return listener; } +- (ARTEventListener *)subscribe:(ARTPresenceMessageCallback)cb { + return [self _subscribe:ARTPresenceActionAll onAttach:nil callback:cb]; +} + +- (ARTEventListener *)subscribeWithAttachCallback:(ARTCallback)onAttach callback:(ARTPresenceMessageCallback)cb { + return [self _subscribe:ARTPresenceActionAll onAttach:onAttach callback:cb]; +} + - (ARTEventListener *)subscribe:(ARTPresenceAction)action callback:(ARTPresenceMessageCallback)cb { - return [self subscribe:action onAttach:nil callback:cb]; + return [self _subscribe:action onAttach:nil callback:cb]; } - (ARTEventListener *)subscribe:(ARTPresenceAction)action onAttach:(ARTCallback)onAttach callback:(ARTPresenceMessageCallback)cb { - if (cb) { - ARTPresenceMessageCallback userCallback = cb; - cb = ^(ARTPresenceMessage *_Nullable m) { - dispatch_async(self->_userQueue, ^{ - userCallback(m); - }); - }; - } - if (onAttach) { - ARTCallback userOnAttach = onAttach; - onAttach = ^(ARTErrorInfo *_Nullable m) { - dispatch_async(self->_userQueue, ^{ - userOnAttach(m); - }); - }; - } - - __block ARTEventListener *listener = nil; -dispatch_sync(_queue, ^{ - if (self->_channel.state_nosync == ARTRealtimeChannelFailed) { - if (onAttach) onAttach([ARTErrorInfo createWithCode:ARTErrorChannelOperationFailedInvalidState message:@"attempted to subscribe while channel is in Failed state."]); - return; - } - [self->_channel _attach:onAttach]; - 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; + return [self _subscribe:action onAttach:onAttach callback:cb]; } // RTP7 @@ -748,7 +730,7 @@ - (void)onMessage:(ARTProtocolMessage *)message { } - (void)onSync:(ARTProtocolMessage *)message { - if (!self.syncInProgress) { + if (!self.syncInProgress_nosync) { [self startSync]; } else { @@ -838,7 +820,7 @@ - (void)processMember:(ARTPresenceMessage *)message { memberUpdated = [self addMember:messageCopy]; break; case ARTPresenceLeave: - if (self.syncInProgress) { + if (self.syncInProgress_nosync) { messageCopy.action = ARTPresenceAbsent; // RTP2f memberUpdated = [self addMember:messageCopy]; } else { @@ -974,10 +956,18 @@ - (void)onceSyncFails:(ARTCallback)callback { [_syncEventEmitter once:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncFailed] callback:callback]; } -- (BOOL)syncInProgress { +- (BOOL)syncInProgress_nosync { return _syncState == ARTPresenceSyncStarted; } +- (BOOL)syncInProgress { + __block BOOL ret; + dispatch_sync(_queue, ^{ + ret = [self syncInProgress_nosync]; + }); + return ret; +} + @end #pragma mark - ARTEvent diff --git a/Source/PrivateHeaders/Ably/ARTDefault+Private.h b/Source/PrivateHeaders/Ably/ARTDefault+Private.h index a8b352707..328326353 100644 --- a/Source/PrivateHeaders/Ably/ARTDefault+Private.h +++ b/Source/PrivateHeaders/Ably/ARTDefault+Private.h @@ -7,4 +7,7 @@ extern NSString *const ARTDefaultProduction; + (void)setConnectionStateTtl:(NSTimeInterval)value; + (void)setMaxMessageSize:(NSInteger)value; ++ (NSInteger)maxSandboxMessageSize; ++ (NSInteger)maxProductionMessageSize; + @end diff --git a/Source/PrivateHeaders/Ably/ARTProtocolMessage+Private.h b/Source/PrivateHeaders/Ably/ARTProtocolMessage+Private.h index 0722be468..fd3e50330 100644 --- a/Source/PrivateHeaders/Ably/ARTProtocolMessage+Private.h +++ b/Source/PrivateHeaders/Ably/ARTProtocolMessage+Private.h @@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly, nonatomic) BOOL hasBacklog; @property (readonly, nonatomic) BOOL resumed; -- (BOOL)mergeFrom:(ARTProtocolMessage *)msg; +- (BOOL)mergeFrom:(ARTProtocolMessage *)msg maxSize:(NSInteger)maxSize; @end diff --git a/Source/PrivateHeaders/Ably/ARTPushActivationStateMachine.h b/Source/PrivateHeaders/Ably/ARTPushActivationStateMachine.h index c61806f79..3c65bfbf3 100644 --- a/Source/PrivateHeaders/Ably/ARTPushActivationStateMachine.h +++ b/Source/PrivateHeaders/Ably/ARTPushActivationStateMachine.h @@ -14,7 +14,7 @@ NS_SWIFT_NAME(PushActivationStateMachine) @property (readonly, nonatomic) ARTPushActivationEvent *lastEvent; @property (readonly, nonatomic) ARTPushActivationState *current; -@property (readonly, nonatomic) NSMutableArray *pendingEvents; +@property (readonly, nonatomic) NSArray *pendingEvents; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; diff --git a/Source/PrivateHeaders/Ably/ARTRealtime+Private.h b/Source/PrivateHeaders/Ably/ARTRealtime+Private.h index bb12e3175..4c819255d 100644 --- a/Source/PrivateHeaders/Ably/ARTRealtime+Private.h +++ b/Source/PrivateHeaders/Ably/ARTRealtime+Private.h @@ -51,7 +51,6 @@ NS_ASSUME_NONNULL_BEGIN // State properties - (BOOL)shouldSendEvents; -- (BOOL)shouldQueueEvents; // Message sending - (void)sendQueuedMessages; diff --git a/Source/PrivateHeaders/Ably/ARTRealtimeChannel+Private.h b/Source/PrivateHeaders/Ably/ARTRealtimeChannel+Private.h index 79dc55276..15e9e0a0a 100644 --- a/Source/PrivateHeaders/Ably/ARTRealtimeChannel+Private.h +++ b/Source/PrivateHeaders/Ably/ARTRealtimeChannel+Private.h @@ -33,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN - (ARTErrorInfo *)errorReason_nosync; - (NSString * _Nullable)clientId_nosync; - (BOOL)canBeReattached; +- (BOOL)shouldAttach; @property (readonly, weak, nonatomic) ARTRealtimeInternal *realtime; // weak because realtime owns self @property (readonly, nonatomic) ARTRestChannelInternal *restChannel; @@ -47,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithRealtime:(ARTRealtimeInternal *)realtime andName:(NSString *)name withOptions:(ARTRealtimeChannelOptions *)options logger:(ARTInternalLog *)logger; -- (void)reattachWithParams:(ARTAttachRequestParams *)params; +- (void)proceedAttachDetachWithParams:(ARTAttachRequestParams *)params; - (void)_attach:(nullable ARTCallback)callback; - (void)_detach:(nullable ARTCallback)callback; @@ -76,7 +77,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)setSuspended:(ARTChannelStateChangeParams *)params; - (void)setFailed:(ARTChannelStateChangeParams *)params; -- (void)throwOnDisconnectedOrFailed; - (void)detachChannel:(ARTChannelStateChangeParams *)params; diff --git a/Source/PrivateHeaders/Ably/ARTRealtimePresence+Private.h b/Source/PrivateHeaders/Ably/ARTRealtimePresence+Private.h index e7a4b6c63..5098aa085 100644 --- a/Source/PrivateHeaders/Ably/ARTRealtimePresence+Private.h +++ b/Source/PrivateHeaders/Ably/ARTRealtimePresence+Private.h @@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithChannel:(ARTRealtimeChannelInternal *)channel logger:(ARTInternalLog *)logger; - (void)_unsubscribe; - (BOOL)syncComplete_nosync; +- (BOOL)syncInProgress_nosync; - (void)failPendingPresence:(ARTStatus *)status; - (void)broadcast:(ARTPresenceMessage *)pm; diff --git a/Source/include/Ably/ARTQueuedMessage.h b/Source/include/Ably/ARTQueuedMessage.h index 951490f2b..9c9473780 100644 --- a/Source/include/Ably/ARTQueuedMessage.h +++ b/Source/include/Ably/ARTQueuedMessage.h @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithProtocolMessage:(ARTProtocolMessage *)msg sentCallback:(nullable ARTCallback)sentCallback ackCallback:(nullable ARTStatusCallback)ackCallback; -- (BOOL)mergeFrom:(ARTProtocolMessage *)msg sentCallback:(nullable ARTCallback)sentCallback ackCallback:(nullable ARTStatusCallback)ackCallback; +- (BOOL)mergeFrom:(ARTProtocolMessage *)msg maxSize:(NSInteger)maxSize sentCallback:(nullable ARTCallback)sentCallback ackCallback:(nullable ARTStatusCallback)ackCallback; - (ARTCallback)sentCallback; - (ARTStatusCallback)ackCallback; diff --git a/Test/Tests/ARTDefaultTests.swift b/Test/Tests/ARTDefaultTests.swift index 4ef2037b9..f7cc6c928 100644 --- a/Test/Tests/ARTDefaultTests.swift +++ b/Test/Tests/ARTDefaultTests.swift @@ -6,6 +6,6 @@ class ARTDefaultTests: XCTestCase { func testVersions() { XCTAssertEqual(Default.apiVersion(), "2") - XCTAssertEqual(Default.libraryVersion(), "1.2.31") + XCTAssertEqual(Default.libraryVersion(), "1.2.33") } } diff --git a/Test/Tests/AuthTests.swift b/Test/Tests/AuthTests.swift index 9c4dded11..2c8e5ec86 100644 --- a/Test/Tests/AuthTests.swift +++ b/Test/Tests/AuthTests.swift @@ -1000,7 +1000,7 @@ class AuthTests: XCTestCase { waitUntil(timeout: testTimeout) { done in realtime.connection.once(.failed) { stateChange in - XCTAssertEqual(stateChange.reason?.code, ARTErrorCode.invalidCredentials.intValue) + XCTAssertEqual(stateChange.reason?.code, ARTErrorCode.incompatibleCredentials.intValue) done() } realtime.connect() @@ -1914,7 +1914,7 @@ class AuthTests: XCTestCase { guard let error = error else { fail("Error is nil"); done(); return } - expect(error.message).to(contain("mismatched clientId")) + expect(error.message).to(contain("invalid clientId")) done() } } @@ -3430,7 +3430,7 @@ class AuthTests: XCTestCase { // RSA10k - func skipped__test__115__authorize__server_time_offset__should_obtain_server_time_once_and_persist_the_offset_from_the_local_clock() throws { + func test__115__authorize__server_time_offset__should_obtain_server_time_once_and_persist_the_offset_from_the_local_clock() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let rest = Rest(options: options) @@ -4058,7 +4058,7 @@ class AuthTests: XCTestCase { expect { try ARTTokenDetails.fromJson("[]" as JsonCompatible) }.to(throwError()) } - func skipped__test__140__JWT_and_realtime__client_initialized_with_a_JWT_token_in_ClientOptions__with_valid_credentials__pulls_stats_successfully() throws { + func test__140__JWT_and_realtime__client_initialized_with_a_JWT_token_in_ClientOptions__with_valid_credentials__pulls_stats_successfully() throws { let test = Test() let options = try AblyTests.clientOptions(for: test) options.token = try getJWTToken(for: test) @@ -4210,7 +4210,7 @@ class AuthTests: XCTestCase { // RSA8g - func skipped__test__146__JWT_and_realtime__when_using_authCallback__with_valid_credentials__pulls_stats_successfully() throws { + func test__146__JWT_and_realtime__when_using_authCallback__with_valid_credentials__pulls_stats_successfully() throws { let test = Test() let options = try AblyTests.clientOptions(for: test) options.authCallback = { _, completion in diff --git a/Test/Tests/ClientInformationTests.swift b/Test/Tests/ClientInformationTests.swift index b17c2ef61..60ac5084f 100644 --- a/Test/Tests/ClientInformationTests.swift +++ b/Test/Tests/ClientInformationTests.swift @@ -9,7 +9,7 @@ final class ClientInformationTests: XCTestCase { XCTAssertEqual(agents.keys.count, 2) - XCTAssertEqual(agents["ably-cocoa"], "1.2.31") + XCTAssertEqual(agents["ably-cocoa"], "1.2.33") #if os(iOS) XCTAssertTrue(agents.keys.contains("iOS")) @@ -27,7 +27,7 @@ final class ClientInformationTests: XCTestCase { // CR3, CR3b func testAgentIdentifierWithAdditionalAgents_withNilAdditionalAgents() { let expectedIdentifier = [ - "ably-cocoa/1.2.31", + "ably-cocoa/1.2.33", Default.platformAgent() ].sorted().joined(separator: " ") @@ -42,7 +42,7 @@ final class ClientInformationTests: XCTestCase { ] let expectedIdentifier = [ - "ably-cocoa/1.2.31", + "ably-cocoa/1.2.33", "demolib/0.0.1", "morelib", Default.platformAgent() diff --git a/Test/Tests/PushActivationStateMachineTests.swift b/Test/Tests/PushActivationStateMachineTests.swift index f87528e7b..30c8fb7fe 100644 --- a/Test/Tests/PushActivationStateMachineTests.swift +++ b/Test/Tests/PushActivationStateMachineTests.swift @@ -203,7 +203,9 @@ class PushActivationStateMachineTests: XCTestCase { beforeEach__Activation_state_machine__State_NotActivated() stateMachine.send(PushActivationEventGotPushDeviceDetails()) + expect(stateMachine.current).to(beAKindOf(PushActivationStateNotActivated.self)) + XCTAssertEqual(stateMachine.pendingEvents.count, 0) } // RSH3b @@ -982,7 +984,7 @@ class PushActivationStateMachineTests: XCTestCase { expect(stateMachine.pendingEvents).toEventually(haveCount(1), timeout: testTimeout) stateMachine.transitions = nil - let pendingEvent = try XCTUnwrap(stateMachine.pendingEvents.firstObject, "Pending event is missing") + let pendingEvent = try XCTUnwrap(stateMachine.pendingEvents.first, "Pending event is missing") expect(pendingEvent).to(beAKindOf(PushActivationEventCalledActivate.self)) waitUntil(timeout: testTimeout) { done in diff --git a/Test/Tests/PushAdminTests.swift b/Test/Tests/PushAdminTests.swift index d994ce29c..38435d7d9 100644 --- a/Test/Tests/PushAdminTests.swift +++ b/Test/Tests/PushAdminTests.swift @@ -223,7 +223,7 @@ class PushAdminTests: XCTestCase { } } - func skipped__test__002__publish__should_publish_successfully() throws { + func test__002__publish__should_publish_successfully() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let realtime = Realtime(options: options) @@ -257,7 +257,7 @@ class PushAdminTests: XCTestCase { } } - func skipped__test__003__publish__should_fail_with_a_bad_recipient() throws { + func test__003__publish__should_fail_with_a_bad_recipient() throws { let test = Test() let realtime = Realtime(options: try AblyTests.commonAppSetup(for: test)) defer { realtime.dispose(); realtime.close() } @@ -285,7 +285,7 @@ class PushAdminTests: XCTestCase { } } - func skipped__test__004__publish__should_fail_with_an_empty_recipient() throws { + func test__004__publish__should_fail_with_an_empty_recipient() throws { let test = Test() let realtime = Realtime(options: try AblyTests.commonAppSetup(for: test)) defer { realtime.dispose(); realtime.close() } diff --git a/Test/Tests/RealtimeClientChannelTests.swift b/Test/Tests/RealtimeClientChannelTests.swift index 10027f937..d507c4ca9 100644 --- a/Test/Tests/RealtimeClientChannelTests.swift +++ b/Test/Tests/RealtimeClientChannelTests.swift @@ -155,7 +155,7 @@ class RealtimeClientChannelTests: XCTestCase { } // RTL1 - func skipped__test__001__Channel__should_process_all_incoming_messages_and_presence_messages_as_soon_as_a_Channel_becomes_attached() throws { + func test__001__Channel__should_process_all_incoming_messages_and_presence_messages_as_soon_as_a_Channel_becomes_attached() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let client1 = AblyTests.newRealtime(options).client @@ -563,6 +563,7 @@ class RealtimeClientChannelTests: XCTestCase { XCTAssertFalse(stateChange.resumed) } } + channel.attach() channel.publish(nil, data: "A message") } @@ -583,6 +584,7 @@ class RealtimeClientChannelTests: XCTestCase { XCTAssertFalse(stateChange.resumed) } } + recoveredChannel.attach() } } @@ -712,7 +714,6 @@ class RealtimeClientChannelTests: XCTestCase { done() } } - expect(channel.state).toEventually(equal(.attached), timeout: testTimeout) } // TO3g and https://github.com/ably/ably-cocoa/issues/1004 @@ -727,14 +728,13 @@ class RealtimeClientChannelTests: XCTestCase { defer { client.dispose(); client.close() } client.internal.setReachabilityClass(TestReachability.self) let channel = client.channels.get(test.uniqueChannelName()) - - waitUntil(timeout: testTimeout) { done in - client.connection.once(.connected) { _ in - done() - } - client.connect() - } - + + client.connect() + expect(client.connection.state).toEventually(equal(.connected), timeout: testTimeout) + + channel.attach() + expect(channel.state).toEventually(equal(.attached), timeout: testTimeout) + waitUntil(timeout: testTimeout) { done in channel.publish(nil, data: "message") { error in XCTAssertNil(error) @@ -742,13 +742,6 @@ class RealtimeClientChannelTests: XCTestCase { } } - XCTAssertEqual(channel.state, .attached) - channel.on { stateChange in - if stateChange.current != .attached { - fail("Channel state should not change") - } - } - waitUntil(timeout: testTimeout) { done in client.connection.once(.disconnected) { stateChange in expect(stateChange.reason?.message).to(satisfyAnyOf(contain("unreachable host"), contain("network is down"))) @@ -766,7 +759,7 @@ class RealtimeClientChannelTests: XCTestCase { } channel.off() - XCTAssertEqual(channel.state, .attached) + expect(channel.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) } // RTL3b @@ -882,10 +875,13 @@ class RealtimeClientChannelTests: XCTestCase { } // RTL3d - func test__013__Channel__connection_state__if_the_connection_state_enters_the_CONNECTED_state__then_a_SUSPENDED_channel_will_initiate_an_attach_operation() throws { + func test__013__Channel__connection_state__if_the_connection_state_enters_the_CONNECTED_state__then_a_SUSPENDED_channel_will_transition_to_ATTACHING_and_goes_back_to_SUSPNDED_on_timeout() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) + options.testOptions.transportFactory = TestProxyTransportFactory() options.suspendedRetryTimeout = 1.0 + options.channelRetryTimeout = 1.0 + options.testOptions.realtimeRequestTimeout = 1.0 let client = Realtime(options: options) defer { client.dispose(); client.close() } @@ -908,29 +904,28 @@ class RealtimeClientChannelTests: XCTestCase { } expect(client.connection.state).toEventually(equal(RealtimeConnectionState.connected), timeout: testTimeout) - expect(channel.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) - } - - // RTL3d - func test__014__Channel__connection_state__if_the_attach_operation_for_the_channel_times_out_and_the_channel_returns_to_the_SUSPENDED_state() throws { - let test = Test() - let client = AblyTests.newRealtime(try AblyTests.commonAppSetup(for: test)).client - defer { client.dispose(); client.close() } - - let channel = client.channels.get(test.uniqueChannelName()) + + XCTAssertEqual(channel.state, RealtimeChannelState.attaching) + + let transport = client.internal.transport as! TestProxyTransport + transport.actionsIgnored += [.attached] + waitUntil(timeout: testTimeout) { done in - channel.attach { error in - XCTAssertNil(error) - done() - } - } - - client.simulateSuspended(beforeSuspension: { done in + let splitDone = AblyTests.splitDone(2, done: done) + var wasSuspended = false channel.once(.suspended) { stateChange in + XCTAssertEqual(stateChange.reason?.message.contains("attach timed out"), true) + transport.actionsIgnored.removeAll() + wasSuspended = true + splitDone() + } + // make sure the channel will attach eventually (RTL4f) + channel.once(.attached) { stateChange in + XCTAssertTrue(wasSuspended) XCTAssertNil(stateChange.reason) - done() + splitDone() } - }) + } } // RTL3d - https://github.com/ably/ably-cocoa/issues/881 @@ -1214,8 +1209,8 @@ class RealtimeClientChannelTests: XCTestCase { XCTAssertNil(stateChange.reason) done() } - client.connect() + channel.attach() } XCTAssertEqual(channel.state, .attached) } @@ -1241,7 +1236,7 @@ class RealtimeClientChannelTests: XCTestCase { } } - func skipped__test__045__Channel__attach__happens_when_connection_is_CONNECTED_if_it_s_currently__DISCONNECTED() throws { + func test__045__Channel__attach__happens_when_connection_is_CONNECTED_if_it_s_currently__DISCONNECTED() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let client = Realtime(options: options) @@ -2325,6 +2320,7 @@ class RealtimeClientChannelTests: XCTestCase { XCTAssertNil(error) done() } + XCTAssertEqual(client.internal.queuedMessages.count, 0) XCTAssertEqual((client.internal.transport as! TestProxyTransport).protocolMessagesSent.filter { $0.action == .message }.count, 1) } @@ -2378,10 +2374,12 @@ class RealtimeClientChannelTests: XCTestCase { transport.actionsIgnored += [.attached] waitUntil(timeout: testTimeout) { done in + XCTAssertEqual(channel.state, RealtimeChannelState.attaching) channel.publish(nil, data: "message") { error in XCTAssertNil(error) done() } + XCTAssertEqual(client.internal.queuedMessages.count, 0) XCTAssertEqual((client.internal.transport as! TestProxyTransport).protocolMessagesSent.filter { $0.action == .message }.count, 1) } @@ -2475,80 +2473,6 @@ class RealtimeClientChannelTests: XCTestCase { afterEach__Channel__publish__Connection_state_conditions__the_message() } - func test__079__Channel__publish__Connection_state_conditions__the_message__should_NOT_be_queued_instead_it_should_be_published_if_the_channel_is__INITIALIZED() throws { - let test = Test() - try beforeEach__Channel__publish__Connection_state_conditions__the_message(for: test, channelName: test.uniqueChannelName()) - - rtl6c2TestsClient.connect() - XCTAssertEqual(rtl6c2TestsChannel.state, RealtimeChannelState.initialized) - - expect(rtl6c2TestsClient.connection.state).toEventually(equal(RealtimeConnectionState.connected), timeout: testTimeout) - - waitUntil(timeout: testTimeout) { done in - rtl16c2TestsPublish(done) - XCTAssertEqual(rtl6c2TestsClient.internal.queuedMessages.count, 0) - XCTAssertEqual((rtl6c2TestsClient.internal.transport as! TestProxyTransport).protocolMessagesSent.filter { $0.action == .message }.count, 1) - } - - afterEach__Channel__publish__Connection_state_conditions__the_message() - } - - func test__080__Channel__publish__Connection_state_conditions__the_message__should_NOT_be_queued_instead_it_should_be_published_if_the_channel_is__ATTACHING() throws { - let test = Test() - try beforeEach__Channel__publish__Connection_state_conditions__the_message(for: test, channelName: test.uniqueChannelName()) - - rtl6c2TestsClient.connect() - expect(rtl6c2TestsClient.connection.state).toEventually(equal(RealtimeConnectionState.connected), timeout: testTimeout) - - waitUntil(timeout: testTimeout) { done in - rtl6c2TestsChannel.attach() - XCTAssertEqual(rtl6c2TestsChannel.state, RealtimeChannelState.attaching) - rtl16c2TestsPublish(done) - XCTAssertEqual(rtl6c2TestsClient.internal.queuedMessages.count, 0) - XCTAssertEqual((rtl6c2TestsClient.internal.transport as! TestProxyTransport).protocolMessagesSent.filter { $0.action == .message }.count, 1) - } - - afterEach__Channel__publish__Connection_state_conditions__the_message() - } - - func test__081__Channel__publish__Connection_state_conditions__the_message__should_NOT_be_queued_instead_it_should_be_published_if_the_channel_is__ATTACHED() throws { - let test = Test() - try beforeEach__Channel__publish__Connection_state_conditions__the_message(for: test, channelName: test.uniqueChannelName()) - - waitUntil(timeout: testTimeout) { done in - rtl6c2TestsChannel.attach { error in - XCTAssertNil(error) - done() - } - rtl6c2TestsClient.connect() - } - - waitUntil(timeout: testTimeout) { done in - let tokenParams = ARTTokenParams() - tokenParams.ttl = 5.0 - rtl6c2TestsClient.auth.authorize(tokenParams, options: nil) { tokenDetails, error in - XCTAssertNil(error) - XCTAssertNotNil(tokenDetails) - done() - } - } - - waitUntil(timeout: testTimeout) { done in - rtl6c2TestsClient.connection.once(.disconnected) { _ in - done() - } - } - - XCTAssertEqual(rtl6c2TestsChannel.state, RealtimeChannelState.attached) - - waitUntil(timeout: testTimeout) { done in - rtl16c2TestsPublish(done) - XCTAssertEqual(rtl6c2TestsClient.internal.queuedMessages.count, 1) - } - - afterEach__Channel__publish__Connection_state_conditions__the_message() - } - // RTL6c4 func beforeEach__Channel__publish__Connection_state_conditions__will_result_in_an_error_if_the(for test: Test, channelName: String) throws { @@ -2852,8 +2776,7 @@ class RealtimeClientChannelTests: XCTestCase { XCTAssertEqual(protocolMessages.count, 1) } - // FIXME: Fix flaky presence tests and re-enable. See https://ably-real-time.slack.com/archives/C030C5YLY/p1623172436085700 - func skipped__test__090__Channel__publish__message_bundling__should_only_bundle_messages_when_it_respects_all_of_the_constraints() throws { + func test__090__Channel__publish__message_bundling__should_only_bundle_messages_when_it_respects_all_of_the_constraints() throws { let test = Test() let defaultMaxMessageSize = Default.maxMessageSize() Default.setMaxMessageSize(256) @@ -2937,6 +2860,7 @@ class RealtimeClientChannelTests: XCTestCase { XCTAssertNil(stateChange.reason) done() } + channel.attach() } let expectationEvent0 = XCTestExpectation(description: "event0") @@ -3393,11 +3317,24 @@ class RealtimeClientChannelTests: XCTestCase { let test = Test() let client = Realtime(options: try AblyTests.commonAppSetup(for: test)) defer { client.dispose(); client.close() } - let channel = client.channels.get(test.uniqueChannelName()) + // Initialized + XCTAssertEqual(channel.state, RealtimeChannelState.initialized) + channel.subscribe { _ in } + XCTAssertEqual(channel.state, RealtimeChannelState.attaching) + expect(channel.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) + + // Detaching + channel.detach() + channel.subscribe { _ in } + XCTAssertEqual(channel.state, RealtimeChannelState.detaching) + expect(channel.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) + + // Detached + channel.detach() + expect(channel.state).toEventually(equal(RealtimeChannelState.detached), timeout: testTimeout) channel.subscribe { _ in } - expect(channel.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) } @@ -4683,6 +4620,7 @@ class RealtimeClientChannelTests: XCTestCase { partialDone() } } + channel.attach() } waitUntil(timeout: testTimeout) { done in diff --git a/Test/Tests/RealtimeClientConnectionTests.swift b/Test/Tests/RealtimeClientConnectionTests.swift index 536a52348..535f7f98a 100644 --- a/Test/Tests/RealtimeClientConnectionTests.swift +++ b/Test/Tests/RealtimeClientConnectionTests.swift @@ -122,7 +122,7 @@ private func expectDataToMatch(_ message: Message, _ fixtureMessage: Any) { fail("expected NSArray") } case "binary": - XCTAssertEqual(message.data as? NSData, (dictionaryValue["dictionaryValue"] as! String).dataFromHexadecimalString()! as NSData?) + XCTAssertEqual(message.data as? NSData, (dictionaryValue["expectedHexValue"] as! String).dataFromHexadecimalString()! as NSData?) default: fail("unhandled: \(dictionaryValue["expectedType"] as! String)") } @@ -168,19 +168,22 @@ class RealtimeClientConnectionTests: XCTestCase { options.testOptions.transportFactory = TestProxyTransportFactory() let client = Realtime(options: options) let defaultMaxMessageSize = Default.maxMessageSize() - XCTAssertEqual(defaultMaxMessageSize, 65536) + // Sandbox apps have a 16384 limit + XCTAssertEqual(defaultMaxMessageSize, 16384) defer { Default.setMaxMessageSize(defaultMaxMessageSize) client.dispose() client.close() } + // Setting different value to check override below Default.setMaxMessageSize(1) waitUntil(timeout: testTimeout) { done in client.connection.once(.connected) { _ in let transport = client.internal.transport as! TestProxyTransport - let firstConnectionDetails = transport.protocolMessagesReceived.filter { $0.action == .connected }[0].connectionDetails - XCTAssertEqual(firstConnectionDetails!.maxMessageSize, 16384) // Sandbox apps have a 16384 limit + let maxMessageSize = transport.protocolMessagesReceived.filter { $0.action == .connected }[0].connectionDetails?.maxMessageSize + XCTAssertEqual(maxMessageSize, defaultMaxMessageSize) + XCTAssertEqual(client.connection.maxMessageSize, maxMessageSize) done() } client.connect() @@ -381,7 +384,7 @@ class RealtimeClientConnectionTests: XCTestCase { done() case .connected: if let transport = client.internal.transport as? TestProxyTransport, let query = transport.lastUrl?.query { - expect(query).to(haveParam("agent", hasPrefix: "ably-cocoa/1.2.31")) + expect(query).to(haveParam("agent", hasPrefix: "ably-cocoa/1.2.33")) } else { XCTFail("MockTransport isn't working") } @@ -2565,7 +2568,8 @@ class RealtimeClientConnectionTests: XCTestCase { let client = AblyTests.newRealtime(options).client defer { client.dispose(); client.close() } let channel = client.channels.get(test.uniqueChannelName()) - + channel.attach() + XCTAssertTrue(client.waitUntilConnected()) let expectedConnectionId = client.connection.id @@ -2601,7 +2605,9 @@ class RealtimeClientConnectionTests: XCTestCase { defer { client.dispose(); client.close() } let channel1 = client.channels.get(test.uniqueChannelName()) let channel2 = client.channels.get(test.uniqueChannelName(prefix: "second_")) - + channel1.attach() + channel2.attach() + XCTAssertTrue(client.waitUntilConnected()) expect(channel1.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) expect(channel2.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) @@ -2642,7 +2648,8 @@ class RealtimeClientConnectionTests: XCTestCase { let client = AblyTests.newRealtime(options).client defer { client.dispose(); client.close() } let channel = client.channels.get(test.uniqueChannelName()) - + channel.attach() + XCTAssertTrue(client.waitUntilConnected()) let expectedConnectionId = client.connection.id @@ -2766,7 +2773,7 @@ class RealtimeClientConnectionTests: XCTestCase { XCTAssertTrue(channel.errorReason === protocolError.error) } - func skipped__test__072__Connection__connection_failures_once_CONNECTED__System_s_response_to_a_resume_request__should_resume_the_connection_after_an_auth_renewal() throws { + func test__072__Connection__connection_failures_once_CONNECTED__System_s_response_to_a_resume_request__should_resume_the_connection_after_an_auth_renewal() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) options.tokenDetails = try getTestTokenDetails(for: test, ttl: 5.0) @@ -2843,10 +2850,9 @@ class RealtimeClientConnectionTests: XCTestCase { } } } - - // FIXME: Fix flaky presence tests and re-enable. See https://ably-real-time.slack.com/archives/C030C5YLY/p1623172436085700 + // RTN15d - func skipped__test__065__Connection__connection_failures_once_CONNECTED__should_recover_from_disconnection_and_messages_should_be_delivered_once_the_connection_is_resumed() throws { + func test__065__Connection__connection_failures_once_CONNECTED__should_recover_from_disconnection_and_messages_should_be_delivered_once_the_connection_is_resumed() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) @@ -2988,7 +2994,7 @@ class RealtimeClientConnectionTests: XCTestCase { } // RTN15g RTN15g1 - func skipped__test__074__Connection__connection_failures_once_CONNECTED__when_connection__ttl_plus_idle_interval__period_has_passed_since_last_activity__uses_a_new_connection() throws { + func test__074__Connection__connection_failures_once_CONNECTED__when_connection__ttl_plus_idle_interval__period_has_passed_since_last_activity__uses_a_new_connection() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) // We want this to be > than the sum of customTtlInterval and customIdleInterval @@ -3091,7 +3097,7 @@ class RealtimeClientConnectionTests: XCTestCase { // RTN15h - func skipped__test__077__Connection__connection_failures_once_CONNECTED__DISCONNECTED_message_contains_a_token_error__if_the_token_is_renewable_then_error_should_not_be_emitted() throws { + func test__077__Connection__connection_failures_once_CONNECTED__DISCONNECTED_message_contains_a_token_error__if_the_token_is_renewable_then_error_should_not_be_emitted() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) options.autoConnect = false @@ -3169,7 +3175,7 @@ class RealtimeClientConnectionTests: XCTestCase { } // RTN15h2 - func skipped__test__079__Connection__connection_failures_once_CONNECTED__DISCONNECTED_message_contains_a_token_error__should_transition_to_disconnected_when_the_token_renewal_fails_and_the_error_should_be_emitted() throws { + func test__079__Connection__connection_failures_once_CONNECTED__DISCONNECTED_message_contains_a_token_error__should_transition_to_disconnected_when_the_token_renewal_fails_and_the_error_should_be_emitted() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) options.autoConnect = false @@ -3292,25 +3298,6 @@ class RealtimeClientConnectionTests: XCTestCase { } } } - - // RTN16c - func skipped__test__083__Connection__Connection_recovery__Connection_recoveryKey_should_become_becomes_null_when_a_connection_is_explicitly_CLOSED_or_CLOSED() throws { - let test = Test() - let options = try AblyTests.commonAppSetup(for: test) - let client = Realtime(options: options) - defer { client.dispose(); client.close() } - waitUntil(timeout: testTimeout) { done in - client.connection.once(.connected) { _ in - client.connection.once(.closed) { _ in - XCTAssertNil(client.connection.createRecoveryKey()) - XCTAssertNil(client.connection.key) - XCTAssertNil(client.connection.id) - done() - } - client.close() - } - } - } // RTN16g func test__110__Connection__Connection_recovery__connection_recovery_key_is_correctly_constructed_from_defined_parts() throws { @@ -3916,7 +3903,7 @@ class RealtimeClientConnectionTests: XCTestCase { let test = Test() let options = ClientOptions(key: "xxxx:xxxx") options.autoConnect = false - options.testOptions.realtimeRequestTimeout = 1.0 + options.testOptions.realtimeRequestTimeout = 2.0 // this timeout should be longer than `internetIsUp` + `performFakeConnectionError` timeouts let transportFactory = TestProxyTransportFactory() options.testOptions.transportFactory = transportFactory let client = Realtime(options: options) @@ -4002,17 +3989,17 @@ class RealtimeClientConnectionTests: XCTestCase { // RTN17d - func skipped__test__097__Connection__Host_Fallback__should_use_an_alternative_host_when___hostUnreachable() { + func test__097__Connection__Host_Fallback__should_use_an_alternative_host_when___hostUnreachable() { let test = Test() testUsesAlternativeHostOnResponse(.hostUnreachable, channelName: test.uniqueChannelName()) } - func skipped__test__098__Connection__Host_Fallback__should_use_an_alternative_host_when___requestTimeout_timeout__0_1_() { + func test__098__Connection__Host_Fallback__should_use_an_alternative_host_when___requestTimeout_timeout__0_1_() { let test = Test() testUsesAlternativeHostOnResponse(.requestTimeout(timeout: 0.1), channelName: test.uniqueChannelName()) } - func skipped__test__099__Connection__Host_Fallback__should_use_an_alternative_host_when___hostInternalError_code__501_() { + func test__099__Connection__Host_Fallback__should_use_an_alternative_host_when___hostInternalError_code__501_() { let test = Test() testUsesAlternativeHostOnResponse(.hostInternalError(code: 501), channelName: test.uniqueChannelName()) } @@ -4608,7 +4595,7 @@ class RealtimeClientConnectionTests: XCTestCase { } // RTN19b - func skipped__test__104__Connection__Transport_disconnected_side_effects__should_resend_the_ATTACH_message_if_there_are_any_pending_channels() throws { + func test__104__Connection__Transport_disconnected_side_effects__should_resend_the_ATTACH_message_if_there_are_any_pending_channels() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let client = AblyTests.newRealtime(options).client @@ -5200,7 +5187,7 @@ class RealtimeClientConnectionTests: XCTestCase { } // https://github.com/ably/wiki/issues/22 - func skipped__test__111__Connection__with_fixture_messages__should_encode_and_decode_fixture_messages_as_expected() throws { + func test__111__Connection__with_fixture_messages__should_encode_and_decode_fixture_messages_as_expected() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) options.useBinaryProtocol = false @@ -5274,7 +5261,7 @@ class RealtimeClientConnectionTests: XCTestCase { } } - func skipped__test__112__Connection__with_fixture_messages__should_send_messages_through_raw_JSON_POST_and_retrieve_equal_messages_through_MsgPack_and_JSON() throws { + func test__112__Connection__with_fixture_messages__should_send_messages_through_raw_JSON_POST_and_retrieve_equal_messages_through_MsgPack_and_JSON() throws { let test = Test() try setupDependencies(for: test) let restPublishClient = Rest(options: jsonOptions) @@ -5328,7 +5315,7 @@ class RealtimeClientConnectionTests: XCTestCase { } } - func skipped__test__113__Connection__with_fixture_messages__should_send_messages_through_MsgPack_and_JSON_and_retrieve_equal_messages_through_raw_JSON_GET() throws { + func test__113__Connection__with_fixture_messages__should_send_messages_through_MsgPack_and_JSON_and_retrieve_equal_messages_through_raw_JSON_GET() throws { let test = Test() try setupDependencies(for: test) let restPublishClientMsgPack = Rest(options: msgpackOptions) diff --git a/Test/Tests/RealtimeClientPresenceTests.swift b/Test/Tests/RealtimeClientPresenceTests.swift index 2aca33337..064b24a70 100644 --- a/Test/Tests/RealtimeClientPresenceTests.swift +++ b/Test/Tests/RealtimeClientPresenceTests.swift @@ -647,12 +647,6 @@ class RealtimeClientPresenceTests: XCTestCase { waitUntil(timeout: testTimeout) { done in let partialDone = AblyTests.splitDone(2, done: done) - channel2.presence.enterClient("Client 2", data: nil) { error in - XCTAssertNil(error) - XCTAssertEqual(client2.internal.queuedMessages.count, 0) - XCTAssertEqual(channel2.state, RealtimeChannelState.attached) - partialDone() - } channel2.presence.subscribe(.enter) { _ in if channel2.presence.syncComplete { XCTAssertEqual(channel2.internal.presence.members.count, 2) @@ -662,8 +656,13 @@ class RealtimeClientPresenceTests: XCTestCase { channel2.presence.unsubscribe() partialDone() } - - XCTAssertEqual(client2.internal.queuedMessages.count, 1) + channel2.presence.enterClient("Client 2", data: nil) { error in + XCTAssertNil(error) + XCTAssertEqual(client2.internal.queuedMessages.count, 0) + XCTAssertEqual(channel2.state, RealtimeChannelState.attached) + partialDone() + } + XCTAssertEqual(channel2.internal.presence.pendingPresence.count, 1) XCTAssertFalse(channel2.presence.syncComplete) XCTAssertEqual(channel2.internal.presence.members.count, 0) } @@ -894,16 +893,22 @@ class RealtimeClientPresenceTests: XCTestCase { defer { client.dispose(); client.close() } let channel = client.channels.get(test.uniqueChannelName()) + // Initialized XCTAssertEqual(channel.state, RealtimeChannelState.initialized) channel.presence.subscribe { _ in } XCTAssertEqual(channel.state, RealtimeChannelState.attaching) expect(channel.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) - + + // Detaching + channel.detach() + channel.presence.subscribe { _ in } + XCTAssertEqual(channel.state, RealtimeChannelState.detaching) + expect(channel.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) + + // Detached channel.detach() expect(channel.state).toEventually(equal(RealtimeChannelState.detached), timeout: testTimeout) - - channel.presence.subscribe(.present) { _ in } - XCTAssertEqual(channel.state, RealtimeChannelState.attaching) + channel.presence.subscribe { _ in } expect(channel.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) } @@ -920,12 +925,10 @@ class RealtimeClientPresenceTests: XCTestCase { waitUntil(timeout: testTimeout) { done in channel.presence.subscribe(attachCallback: { errorInfo in XCTAssertNotNil(errorInfo) - - channel.presence.subscribe(.enter, onAttach: { errorInfo in - XCTAssertNotNil(errorInfo) - done() - }) { _ in } - }) { _ in } + done() + }, callback: { _ in + fail("Should not be called") + }) } } @@ -938,14 +941,13 @@ class RealtimeClientPresenceTests: XCTestCase { waitUntil(timeout: testTimeout) { done in let error = AblyTests.newErrorProtocolMessage() - channel.presence.subscribe(attachCallback: { errorInfo in - XCTAssertNotNil(errorInfo) - - channel.presence.subscribe(.enter, onAttach: { errorInfo in - XCTAssertNotNil(errorInfo) - done() - }) { _ in } - }) { _ in } + channel.presence.subscribe(attachCallback: { error in + XCTAssertEqual(channel.state, RealtimeChannelState.failed) + XCTAssertNotNil(error) + done() + }, callback: { _ in + fail("Should not be called") + }) AblyTests.queue.async { channel.internal.onError(error) } @@ -1698,7 +1700,7 @@ class RealtimeClientPresenceTests: XCTestCase { return protocolMessage } channel.internal.presence.testSuite_injectIntoMethod(after: #selector(ARTRealtimePresenceInternal.endSync)) { - XCTAssertFalse(channel.internal.presence.syncInProgress) + XCTAssertFalse(channel.internal.presence.syncInProgress_nosync()) XCTAssertEqual(channel.internal.presence.members.count, 19) XCTAssertEqual(channel.internal.presence.members.filter { _, presence in presence.clientId == "user10" && presence.action == .present }.count, 1) // LEAVE for user10 is ignored, because it's timestamped before SYNC XCTAssertEqual(channel.internal.presence.members.filter { _, presence in presence.clientId == "user12" && presence.action == .present }.count, 0) // LEAVE for user12 is not ignored, because it's timestamped after SYNC @@ -1757,7 +1759,7 @@ class RealtimeClientPresenceTests: XCTestCase { waitUntil(timeout: testTimeout) { done in let partialDone = AblyTests.splitDone(2, done: done) channel.internal.presence.testSuite_injectIntoMethod(after: #selector(ARTRealtimePresenceInternal.endSync)) { - XCTAssertFalse(channel.internal.presence.syncInProgress) + XCTAssertFalse(channel.internal.presence.syncInProgress_nosync()) partialDone() } channel.attach { error in @@ -1833,35 +1835,37 @@ class RealtimeClientPresenceTests: XCTestCase { fail("TestProxyTransport is not set"); return } + var leaveMessage: ProtocolMessage! + + let hook = channel.presence.internal.testSuite_injectIntoMethod(before: #selector(ARTRealtimePresenceInternal.endSync)) { + if leaveMessage != nil { + transport.receive(leaveMessage) + let absentMember = channel.internal.presence.members.first { _, m in m.action == .absent }.map { $0.value } + XCTAssertNotNil(absentMember) + XCTAssertEqual(absentMember?.clientId, "user11") + } + } + defer { hook.remove() } + waitUntil(timeout: testTimeout) { done in let partialDone = AblyTests.splitDone(2, done: done) channel.presence.subscribe(.leave) { leave in XCTAssertEqual(leave.clientId, "user11") - let absentMember = channel.internal.presence.members.first { _, m in m.clientId == "user11" }.map { $0.value } - if channel.internal.presence.syncInProgress { - XCTAssertEqual(absentMember?.action, .absent) - } else { - XCTAssertEqual(absentMember?.action, .leave) - } partialDone() } channel.attach { error in XCTAssertNil(error) - XCTAssertTrue(channel.internal.presence.syncInProgress) - - // Inject a fabricated Presence message - let leaveMessage = ProtocolMessage() + + // Initialize a fabricated Presence message to inject it before second sync ends + leaveMessage = ProtocolMessage() leaveMessage.action = .presence leaveMessage.channel = channel.name leaveMessage.timestamp = Date() leaveMessage.presence = [ PresenceMessage(clientId: "user11", action: .leave, connectionId: "another", id: "another:123:0", timestamp: Date()), ] - channel.internalAsync { _ in - transport.receive(leaveMessage) - } partialDone() } } @@ -2331,73 +2335,6 @@ class RealtimeClientPresenceTests: XCTestCase { } } - // RTP6 - - // RTP6c - func test__075__Presence__subscribe__should_implicitly_attach_the_channel() throws { - let test = Test() - let client = Realtime(options: try AblyTests.commonAppSetup(for: test)) - defer { client.dispose(); client.close() } - let channel = client.channels.get(test.uniqueChannelName()) - - XCTAssertEqual(channel.state, RealtimeChannelState.initialized) - channel.presence.subscribe { _ in } - XCTAssertEqual(channel.state, RealtimeChannelState.attaching) - expect(channel.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) - - channel.detach() - expect(channel.state).toEventually(equal(RealtimeChannelState.detached), timeout: testTimeout) - - channel.presence.subscribe(.present) { _ in } - XCTAssertEqual(channel.state, RealtimeChannelState.attaching) - expect(channel.state).toEventually(equal(RealtimeChannelState.attached), timeout: testTimeout) - } - - // RTP6c - func test__076__Presence__subscribe__should_result_in_an_error_if_the_channel_is_in_the_FAILED_state() throws { - let test = Test() - let client = Realtime(options: try AblyTests.commonAppSetup(for: test)) - defer { client.dispose(); client.close() } - let channel = client.channels.get(test.uniqueChannelName()) - - let protocolError = AblyTests.newErrorProtocolMessage() - AblyTests.queue.async { - channel.internal.onError(protocolError) - } - - waitUntil(timeout: testTimeout) { done in - channel.presence.subscribe(attachCallback: { error in - XCTAssertEqual(channel.state, RealtimeChannelState.failed) - XCTAssertNotNil(error) - done() - }, callback: { _ in - fail("Should not be called") - }) - } - } - - // RTP6c - func test__077__Presence__subscribe__should_result_in_an_error_if_the_channel_moves_to_the_FAILED_state() throws { - let test = Test() - let client = Realtime(options: try AblyTests.commonAppSetup(for: test)) - defer { client.dispose(); client.close() } - let channel = client.channels.get(test.uniqueChannelName()) - - waitUntil(timeout: testTimeout) { done in - let error = AblyTests.newErrorProtocolMessage() - channel.presence.subscribe(attachCallback: { error in - XCTAssertEqual(channel.state, RealtimeChannelState.failed) - XCTAssertNotNil(error) - done() - }, callback: { _ in - fail("Should not be called") - }) - AblyTests.queue.async { - channel.internal.onError(error) - } - } - } - // RTP8 // RTP8e @@ -2995,7 +2932,7 @@ class RealtimeClientPresenceTests: XCTestCase { done() } XCTAssertEqual(client.connection.state, RealtimeConnectionState.connecting) - XCTAssertEqual(client.internal.queuedMessages.count, 1) + XCTAssertEqual(channel.internal.presence.pendingPresence.count, 1) } } @@ -3069,7 +3006,7 @@ class RealtimeClientPresenceTests: XCTestCase { waitUntil(timeout: testTimeout) { done in channel.presence.enterClient("user", data: nil) { error in - XCTAssertEqual(error?.code, ARTErrorCode.invalidTransportHandle.intValue) + XCTAssertEqual(error?.code, ARTErrorCode.unableToEnterPresenceChannelInvalidState.intValue) XCTAssertEqual(channel.presence.internal.pendingPresence.count, 0) done() } @@ -3353,6 +3290,7 @@ class RealtimeClientPresenceTests: XCTestCase { expect(client.connection.state).toEventually(equal(.closed), timeout: testTimeout) } let channel = client.channels.get(channelName) + channel.attach() expect(channel.internal.presence.syncInProgress).toEventually(beTrue(), timeout: testTimeout) waitUntil(timeout: testTimeout) { done in diff --git a/Test/Tests/RealtimeClientTests.swift b/Test/Tests/RealtimeClientTests.swift index 5499f9605..4b0629953 100644 --- a/Test/Tests/RealtimeClientTests.swift +++ b/Test/Tests/RealtimeClientTests.swift @@ -320,7 +320,7 @@ class RealtimeClientTests: XCTestCase { } // RTC5b - func skipped__test__023__RealtimeClient__stats__should_accept_all_the_same_params_as_RestClient() throws { + func test__023__RealtimeClient__stats__should_accept_all_the_same_params_as_RestClient() throws { let test = Test() let client = Realtime(options: try AblyTests.commonAppSetup(for: test)) defer { client.close() } @@ -1014,7 +1014,7 @@ class RealtimeClientTests: XCTestCase { } // RTC8b1 - part 4 - func skipped__test__036__RealtimeClient__Auth_authorize_should_upgrade_the_connection_with_current_token__authorize_call_should_complete_with_an_error_if_the_connection_moves_to_the_CLOSED_state() throws { + func test__036__RealtimeClient__Auth_authorize_should_upgrade_the_connection_with_current_token__authorize_call_should_complete_with_an_error_if_the_connection_moves_to_the_CLOSED_state() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) options.autoConnect = false @@ -1280,13 +1280,12 @@ class RealtimeClientTests: XCTestCase { } } } - - // FIXME: Fix flaky presence tests and re-enable. See https://ably-real-time.slack.com/archives/C030C5YLY/p1623172436085700 + // https://github.com/ably/ably-cocoa/issues/577 - func skipped__test__005__RealtimeClient__background_behaviour() { + func test__005__RealtimeClient__background_behaviour() { let test = Test() waitUntil(timeout: testTimeout) { done in - URLSession.shared.dataTask(with: URL(string: "https://ably.io")!) { _, _, _ in + URLSession.shared.dataTask(with: URL(string: "https://ably.com")!) { _, _, _ in let realtime: Realtime do { @@ -1515,7 +1514,7 @@ class RealtimeClientTests: XCTestCase { } } - func skipped__test__012__RealtimeClient__moves_to_DISCONNECTED_on_an_unexpected_normal_WebSocket_close() throws { + func test__012__RealtimeClient__moves_to_DISCONNECTED_on_an_unexpected_normal_WebSocket_close() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let client = Realtime(options: options) diff --git a/Test/Tests/RestClientChannelTests.swift b/Test/Tests/RestClientChannelTests.swift index 0d2fa9748..bc0006ee6 100644 --- a/Test/Tests/RestClientChannelTests.swift +++ b/Test/Tests/RestClientChannelTests.swift @@ -391,6 +391,7 @@ class RestClientChannelTests: XCTestCase { let chanelName = test.uniqueChannelName(prefix: "ch1") let subscriber = realtime.channels.get(chanelName) + subscriber.attach() waitUntil(timeout: testTimeout) { done in subscriber.once(.attached) { _ in done() @@ -423,6 +424,7 @@ class RestClientChannelTests: XCTestCase { let chanelName = test.uniqueChannelName(prefix: "ch1") let subscriber = realtime.channels.get(chanelName) + subscriber.attach() waitUntil(timeout: testTimeout) { done in subscriber.once(.attached) { _ in done() @@ -453,6 +455,7 @@ class RestClientChannelTests: XCTestCase { let chanelName = test.uniqueChannelName(prefix: "ch1") let subscriber = realtime.channels.get(chanelName) + subscriber.attach() waitUntil(timeout: testTimeout) { done in subscriber.once(.attached) { _ in done() @@ -484,6 +487,7 @@ class RestClientChannelTests: XCTestCase { let chanelName = test.uniqueChannelName(prefix: "ch1") let subscriber = realtime.channels.get(chanelName) + subscriber.attach() waitUntil(timeout: testTimeout) { done in subscriber.once(.attached) { _ in done() @@ -1207,7 +1211,7 @@ class RestClientChannelTests: XCTestCase { // RSP3 - func skipped__test__035__presence__get__should_return_presence_fixture_data() throws { + func test__035__presence__get__should_return_presence_fixture_data() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) options.testOptions.channelNamePrefix = nil diff --git a/Test/Tests/RestClientPresenceTests.swift b/Test/Tests/RestClientPresenceTests.swift index f5e723daa..18f7d1140 100644 --- a/Test/Tests/RestClientPresenceTests.swift +++ b/Test/Tests/RestClientPresenceTests.swift @@ -6,7 +6,7 @@ class RestClientPresenceTests: XCTestCase { // RSP3 // RSP3a - func skipped__test__002__Presence__get__should_return_a_PaginatedResult_page_containing_the_first_page_of_members() throws { + func test__002__Presence__get__should_return_a_PaginatedResult_page_containing_the_first_page_of_members() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let client = Rest(options: options) @@ -222,9 +222,7 @@ class RestClientPresenceTests: XCTestCase { // RSP4b // RSP4b2 - // Disabled because there's something wrong in the Sandbox. - // More info at https://ably-real-time.slack.com/archives/C030C5YLY/p1614269570000400 - func skipped__test__007__Presence__history__query_argument__direction_should_change_the_order_of_the_members() throws { + func test__007__Presence__history__query_argument__direction_should_change_the_order_of_the_members() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) let client = Rest(options: options) diff --git a/Test/Tests/RestClientStatsTests.swift b/Test/Tests/RestClientStatsTests.swift index 5a908fef9..7b5dd445c 100644 --- a/Test/Tests/RestClientStatsTests.swift +++ b/Test/Tests/RestClientStatsTests.swift @@ -99,7 +99,7 @@ class RestClientStatsTests: XCTestCase { statsOptions = try postTestStats(statsFixtures, for: test) } - func skipped__test__001__RestClient__stats__result__should_match_minute_level_inbound_and_outbound_fixture_data__forwards_() throws { + func test__001__RestClient__stats__result__should_match_minute_level_inbound_and_outbound_fixture_data__forwards_() throws { let test = Test() try beforeEach__RestClient__stats__result(for: test) @@ -164,7 +164,7 @@ class RestClientStatsTests: XCTestCase { XCTAssertEqual(totalOutbound, 20 + 10 + 40) } - func skipped__test__004__RestClient__stats__result__should_match_month_level_inbound_and_outbound_fixture_data__forwards_() throws { + func test__004__RestClient__stats__result__should_match_month_level_inbound_and_outbound_fixture_data__forwards_() throws { let test = Test() try beforeEach__RestClient__stats__result(for: test) @@ -183,7 +183,7 @@ class RestClientStatsTests: XCTestCase { XCTAssertEqual(totalOutbound, 20 + 10 + 40) } - func skipped__test__005__RestClient__stats__result__should_contain_only_one_item_when_limit_is_1__backwards() throws { + func test__005__RestClient__stats__result__should_contain_only_one_item_when_limit_is_1__backwards() throws { let test = Test() try beforeEach__RestClient__stats__result(for: test) @@ -269,7 +269,7 @@ class RestClientStatsTests: XCTestCase { XCTAssertEqual((firstPageAgain.items)[0].inbound.all.messages.data, 7000) } - func skipped__test__008__RestClient__stats__result__should_be_paginated_according_to_the_limit__fowards_() throws { + func test__008__RestClient__stats__result__should_be_paginated_according_to_the_limit__fowards_() throws { let test = Test() try beforeEach__RestClient__stats__result(for: test) diff --git a/Test/Tests/RestClientTests.swift b/Test/Tests/RestClientTests.swift index 81b90431c..493f1cfdb 100644 --- a/Test/Tests/RestClientTests.swift +++ b/Test/Tests/RestClientTests.swift @@ -1742,7 +1742,7 @@ class RestClientTests: XCTestCase { let headerAgent = testHTTPExecutor.requests.first!.allHTTPHeaderFields?["Ably-Agent"] let ablyAgent = ARTClientInformation.agentIdentifier(withAdditionalAgents: options.agents) XCTAssertEqual(headerAgent, ablyAgent) - XCTAssertTrue(headerAgent!.hasPrefix("ably-cocoa/1.2.31")) + XCTAssertTrue(headerAgent!.hasPrefix("ably-cocoa/1.2.33")) done() } } diff --git a/Version.xcconfig b/Version.xcconfig index 491034bdd..e1ea69c15 100644 --- a/Version.xcconfig +++ b/Version.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 1.2.31 +CURRENT_PROJECT_VERSION = 1.2.33