diff --git a/Source/ARTRealtime+Private.h b/Source/ARTRealtime+Private.h index f7e308e10..c5ee12235 100644 --- a/Source/ARTRealtime+Private.h +++ b/Source/ARTRealtime+Private.h @@ -45,7 +45,7 @@ ART_ASSUME_NONNULL_BEGIN @interface ARTRealtime () @property (readwrite, strong, nonatomic) ARTRest *rest; -@property (readonly, getter=getTransport) id transport; +@property (readonly, getter=getTransport, art_nullable) id transport; @property (readonly, strong, nonatomic, art_nonnull) id reachability; @property (readonly, getter=getLogger) ARTLog *logger; diff --git a/Source/ARTRealtime.m b/Source/ARTRealtime.m index b96c46523..46549bf3e 100644 --- a/Source/ARTRealtime.m +++ b/Source/ARTRealtime.m @@ -306,7 +306,7 @@ - (void)transitionSideEffects:(ARTConnectionStateChange *)stateChange { } _transport = [[_transportClass alloc] initWithRest:self.rest options:self.options resumeKey:resumeKey connectionSerial:connectionSerial]; _transport.delegate = self; - [self transportConnect]; + [self transportConnectForcingNewToken:_renewingToken]; } if (self.connection.state != ARTRealtimeFailed && self.connection.state != ARTRealtimeClosed && self.connection.state != ARTRealtimeDisconnected) { @@ -530,9 +530,10 @@ - (void)onDisconnected:(ARTProtocolMessage *)message { [self.logger info:@"R:%p ARTRealtime disconnected", self]; ARTErrorInfo *error = message.error; if ([self shouldRenewToken:&error]) { - [self transportReconnectWithRenewedToken]; [self transition:ARTRealtimeDisconnected withErrorInfo:error]; [self.connection setErrorReason:nil]; + _renewingToken = true; + [self transition:ARTRealtimeConnecting withErrorInfo:nil]; return; } [self transition:ARTRealtimeDisconnected withErrorInfo:error]; @@ -558,6 +559,7 @@ - (void)onAuth { switch (self.connection.state) { case ARTRealtimeConnecting: case ARTRealtimeConnected: + _resuming = true; [self transportReconnectWithRenewedToken]; break; default: @@ -612,7 +614,7 @@ - (BOOL)shouldRenewToken:(ARTErrorInfo **)errorPtr { - (void)transportReconnectWithHost:(NSString *)host { [self.transport setHost:host]; - [self transportConnect]; + [self transportConnectForcingNewToken:false]; } - (void)transportReconnectWithRenewedToken { @@ -620,10 +622,6 @@ - (void)transportReconnectWithRenewedToken { [self transportConnectForcingNewToken:true]; } -- (void)transportConnect { - [self transportConnectForcingNewToken:false]; -} - - (void)transportConnectForcingNewToken:(BOOL)forceNewToken { ARTClientOptions *options = [self.options copy]; if ([options isBasicAuth]) { diff --git a/Spec/Auth.swift b/Spec/Auth.swift index 7ea03fb24..f67c06496 100644 --- a/Spec/Auth.swift +++ b/Spec/Auth.swift @@ -409,7 +409,7 @@ class Auth : QuickSpec { // Inject AUTH let authMessage = ARTProtocolMessage() authMessage.action = ARTProtocolMessageAction.Auth - realtime.transport.receive(authMessage) + realtime.transport?.receive(authMessage) expect(realtime.connection.errorReason).toEventuallyNot(beNil(), timeout: testTimeout) guard let errorInfo = realtime.connection.errorReason else { @@ -482,7 +482,7 @@ class Auth : QuickSpec { // Inject AUTH let authMessage = ARTProtocolMessage() authMessage.action = ARTProtocolMessageAction.Auth - realtime.transport.receive(authMessage) + realtime.transport?.receive(authMessage) expect(realtime.connection.errorReason).toEventuallyNot(beNil(), timeout: testTimeout) guard let errorInfo = realtime.connection.errorReason else { @@ -573,7 +573,7 @@ class Auth : QuickSpec { // Inject AUTH let authMessage = ARTProtocolMessage() authMessage.action = ARTProtocolMessageAction.Auth - realtime.transport.receive(authMessage) + realtime.transport?.receive(authMessage) expect(realtime.connection.errorReason).toEventuallyNot(beNil(), timeout: testTimeout) guard let errorInfo = realtime.connection.errorReason else { @@ -656,7 +656,7 @@ class Auth : QuickSpec { // Inject AUTH let authMessage = ARTProtocolMessage() authMessage.action = ARTProtocolMessageAction.Auth - realtime.transport.receive(authMessage) + realtime.transport?.receive(authMessage) expect(realtime.connection.errorReason).toEventuallyNot(beNil(), timeout: testTimeout) guard let errorInfo = realtime.connection.errorReason else { @@ -714,9 +714,7 @@ class Auth : QuickSpec { options.autoConnect = false let client = ARTRealtime(options: options) - defer { - client.close() - } + defer { client.dispose(); client.close() } client.setTransportClass(TestProxyTransport.self) client.connect() diff --git a/Spec/RealtimeClientChannel.swift b/Spec/RealtimeClientChannel.swift index dd75060a6..9e49e0a58 100644 --- a/Spec/RealtimeClientChannel.swift +++ b/Spec/RealtimeClientChannel.swift @@ -2430,8 +2430,7 @@ class RealtimeClientChannel: QuickSpec { protoMsg.action = .Detach protoMsg.error = ARTErrorInfo.createWithCode(123, message: "test error") protoMsg.channel = "test" - - client.realtimeTransport(client.transport, didReceiveMessage: protoMsg) + client.transport?.receive(protoMsg) expect(channel.state).to(equal(ARTRealtimeChannelState.Detached)) expect(channel.errorReason).to(equal(protoMsg.error)) diff --git a/Spec/RealtimeClientConnection.swift b/Spec/RealtimeClientConnection.swift index 0f4a5cd30..fb9fe517c 100644 --- a/Spec/RealtimeClientConnection.swift +++ b/Spec/RealtimeClientConnection.swift @@ -2359,7 +2359,6 @@ class RealtimeClientConnection: QuickSpec { } waitUntil(timeout: testTimeout) { done in - // Wait for token to expire client.connection.once(.Connected) { stateChange in expect(stateChange?.reason).to(beNil()) done() @@ -3401,6 +3400,137 @@ class RealtimeClientConnection: QuickSpec { } } + // RTN22 + it("Ably can request that a connected client re-authenticates by sending the client an AUTH ProtocolMessage") { + let options = AblyTests.commonAppSetup() + options.useTokenAuth = true + let client = ARTRealtime(options: options) + defer { client.dispose(); client.close() } + let channel = client.channels.get("foo") + + waitUntil(timeout: testTimeout) { done in + channel.attach { error in + expect(error).to(beNil()) + done() + } + } + + guard let initialConnectionId = client.connection.id else { + fail("ConnectionId is nil"); return + } + + guard let initialToken = client.auth.tokenDetails?.token else { + fail("Initial token is nil"); return + } + + waitUntil(timeout: testTimeout) { done in + client.connection.once(.Connected) { stateChange in + expect(stateChange?.reason).to(beNil()) + expect(initialToken).toNot(equal(client.auth.tokenDetails?.token)) + done() + } + + let authMessage = ARTProtocolMessage() + authMessage.action = .Auth + client.transport?.receive(authMessage) + } + + expect(client.connection.id).to(equal(initialConnectionId)) + + waitUntil(timeout: testTimeout) { done in + let partialDone = AblyTests.splitDone(2, done: done) + let expectedMessage = ARTMessage(name: "ios", data: "message1") + + channel.subscribe() { message in + expect(message.name).to(equal(expectedMessage.name)) + expect(message.data as? String).to(equal(expectedMessage.data as? String)) + partialDone() + } + + let rest = ARTRest(options: AblyTests.clientOptions(key: options.key!)) + rest.channels.get("foo").publish([expectedMessage]) { error in + expect(error).to(beNil()) + partialDone() + } + } + + channel.off() + } + + // RTN22a + it("re-authenticate and resume the connection when the client is forcibly disconnected following a DISCONNECTED message containing an error code in the range 40140 <= code < 40150") { + let options = AblyTests.commonAppSetup() + options.token = getTestToken(key: options.key!, ttl: 5.0) + let client = ARTRealtime(options: options) + defer { client.dispose(); client.close() } + let channel = client.channels.get("foo") + + waitUntil(timeout: testTimeout) { done in + channel.attach { error in + expect(error).to(beNil()) + done() + } + } + + guard let initialConnectionId = client.connection.id else { + fail("ConnectionId is nil"); return + } + + guard let initialToken = client.auth.tokenDetails?.token else { + fail("Initial token is nil"); return + } + + channel.once(.Detached) { _ in + fail("Should not detach channels") + } + + var authorizeMethodCallCount = 0 + let hook = client.auth.testSuite_injectIntoMethodAfter(#selector(client.auth.authorize)) { + authorizeMethodCallCount += 1 + } + defer { hook.remove() } + + waitUntil(timeout: testTimeout) { done in + client.connection.once(.Disconnected) { stateChange in + guard let error = stateChange?.reason else { + fail("Error is nil"); done(); return + } + expect(error.code) == 40142 + done() + } + } + + waitUntil(timeout: testTimeout) { done in + client.connection.once(.Connected) { stateChange in + expect(stateChange?.reason).to(beNil()) + expect(initialToken).toNot(equal(client.auth.tokenDetails?.token)) + done() + } + } + + expect(client.connection.id).to(equal(initialConnectionId)) + expect(authorizeMethodCallCount) == 1 + + waitUntil(timeout: testTimeout) { done in + let partialDone = AblyTests.splitDone(2, done: done) + let expectedMessage = ARTMessage(name: "ios", data: "message1") + + channel.subscribe() { message in + expect(message.name).to(equal(expectedMessage.name)) + expect(message.data as? String).to(equal(expectedMessage.data as? String)) + partialDone() + } + + let rest = ARTRest(options: AblyTests.clientOptions(key: options.key!)) + rest.channels.get("foo").publish([expectedMessage]) { error in + expect(error).to(beNil()) + partialDone() + } + } + + channel.off() + } + } // https://github.com/ably/ably-ios/issues/454 @@ -3417,8 +3547,7 @@ class RealtimeClientConnection: QuickSpec { let protoMsg = ARTProtocolMessage() protoMsg.action = .Disconnect protoMsg.error = ARTErrorInfo.createWithCode(123, message: "test error") - - client.realtimeTransport(client.transport, didReceiveMessage: protoMsg) + client.transport?.receive(protoMsg) expect(client.connection.state).to(equal(ARTRealtimeConnectionState.Disconnected)) expect(client.connection.errorReason).to(equal(protoMsg.error)) @@ -3651,7 +3780,7 @@ class RealtimeClientConnection: QuickSpec { let authMessage = ARTProtocolMessage() authMessage.action = .Auth - client.transport.receive(authMessage) + client.transport?.receive(authMessage) client.close()