Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RTN15h #442

Merged
merged 3 commits into from
May 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Source/ARTRealtime+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@

ART_ASSUME_NONNULL_BEGIN

@interface ARTRealtime ()
@interface ARTRealtime () <ARTRealtimeTransportDelegate>

@property (readonly, strong, nonatomic) __GENERIC(ARTEventEmitter, NSNumber *, ARTConnectionStateChange *) *eventEmitter;
@property (readonly, strong, nonatomic) __GENERIC(ARTEventEmitter, NSNull *, NSNull *) *reconnectedEventEmitter;

+ (NSString *)protocolStr:(ARTProtocolMessageAction)action;

// State properties
- (BOOL)shouldSendEvents;
- (BOOL)shouldQueueEvents;
- (ARTStatus *)defaultError;

// Message sending
- (void)sendQueuedMessages;
- (void)failQueuedMessages:(ARTStatus *)error;

@end

/// ARTRealtime private methods that are used for whitebox testing.
Expand Down
47 changes: 25 additions & 22 deletions Source/ARTRealtime.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,6 @@
#import "ARTConnectionDetails.h"
#import "ARTStats.h"

@interface ARTRealtime () <ARTRealtimeTransportDelegate> {
Class _transportClass;
id<ARTRealtimeTransport> _transport;
}

// State properties
- (BOOL)shouldSendEvents;
- (BOOL)shouldQueueEvents;
- (ARTStatus *)defaultError;

// Message sending
- (void)sendQueuedMessages;
- (void)failQueuedMessages:(ARTStatus *)error;

@end


#pragma mark - ARTRealtime implementation

@implementation ARTRealtime {
Expand All @@ -54,6 +37,8 @@ @implementation ARTRealtime {
__GENERIC(ARTEventEmitter, NSNull *, ARTErrorInfo *) *_pingEventEmitter;
NSDate *_startedReconnection;
NSTimeInterval _connectionStateTtl;
Class _transportClass;
id<ARTRealtimeTransport> _transport;
}

- (instancetype)initWithKey:(NSString *)key {
Expand Down Expand Up @@ -410,11 +395,27 @@ - (void)onConnected:(ARTProtocolMessage *)message {
}

- (void)onDisconnected {
[self onDisconnected:nil];
}

- (void)onDisconnected:(ARTProtocolMessage *)message {
[self.logger info:@"ARTRealtime disconnected"];
switch (self.connection.state) {
case ARTRealtimeConnected:
[self transition:ARTRealtimeDisconnected];
case ARTRealtimeConnected: {
ARTErrorInfo *error;
if (message) {
error = message.error;
}
if (!_renewingToken && error && error.statusCode == 401 && error.code >= 40140 && error.code < 40150 && [self isTokenRenewable]) {
[self connectWithRenewedToken];
return;
}
if (error) {

}
[self transition:ARTRealtimeDisconnected withErrorInfo:error];
break;
}
default:
NSAssert(false, @"Invalid Realtime state transitioning to Disconnected: expected Connected");
break;
Expand Down Expand Up @@ -457,8 +458,10 @@ - (BOOL)isTokenRenewable {

- (void)connectWithRenewedToken {
_renewingToken = true;
[self.transport close];
[self.transport connectForcingNewToken:true];
[_transport close];
_transport = [[_transportClass alloc] initWithRest:self.rest options:self.options resumeKey:_transport.resumeKey connectionSerial:_transport.connectionSerial];
_transport.delegate = self;
[_transport connectForcingNewToken:true];
}

- (void)onAck:(ARTProtocolMessage *)message {
Expand Down Expand Up @@ -676,7 +679,7 @@ - (void)realtimeTransport:(id)transport didReceiveMessage:(ARTProtocolMessage *)
[self onConnected:message];
break;
case ARTProtocolMessageDisconnected:
[self onDisconnected];
[self onDisconnected:message];
break;
case ARTProtocolMessageAck:
[self onAck:message];
Expand Down
7 changes: 7 additions & 0 deletions Source/ARTRealtimeTransport.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
@class ARTProtocolMessage;
@class ARTStatus;
@class ARTErrorInfo;
@class ARTClientOptions;
@class ARTRest;

ART_ASSUME_NONNULL_BEGIN

Expand All @@ -34,6 +36,11 @@ ART_ASSUME_NONNULL_BEGIN

@protocol ARTRealtimeTransport

- (instancetype)initWithRest:(ARTRest *)rest options:(ARTClientOptions *)options resumeKey:(NSString *)resumeKey connectionSerial:(NSNumber *)connectionSerial;

@property (readonly, strong, nonatomic) NSString *resumeKey;
@property (readonly, strong, nonatomic) NSNumber *connectionSerial;

@property (readwrite, weak, nonatomic) id<ARTRealtimeTransportDelegate> delegate;
- (void)send:(ARTProtocolMessage *)msg;
- (void)receive:(ARTProtocolMessage *)msg;
Expand Down
3 changes: 2 additions & 1 deletion Source/ARTWebSocketTransport.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ ART_ASSUME_NONNULL_BEGIN
@interface ARTWebSocketTransport : NSObject <ARTRealtimeTransport>

- (instancetype)init UNAVAILABLE_ATTRIBUTE;
- (instancetype)initWithRest:(ARTRest *)rest options:(ARTClientOptions *)options resumeKey:(NSString *)resumeKey connectionSerial:(NSNumber *)connectionSerial;

@property (readonly, strong, nonatomic) NSString *resumeKey;
@property (readonly, strong, nonatomic) NSNumber *connectionSerial;
@property (readwrite, weak, nonatomic) id<ARTRealtimeTransportDelegate> delegate;

@property (readonly, getter=getIsConnected) BOOL isConnected;
Expand Down
2 changes: 0 additions & 2 deletions Source/ARTWebSocketTransport.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ @interface ARTWebSocketTransport () <WebSocketDelegate>
@property (readonly, strong, nonatomic) ARTLog *logger;
@property (readonly, strong, nonatomic) ARTAuth *auth;
@property (readonly, strong, nonatomic) ARTClientOptions *options;
@property (readonly, strong, nonatomic) NSString *resumeKey;
@property (readonly, strong, nonatomic) NSNumber *connectionSerial;

@end

Expand Down
166 changes: 162 additions & 4 deletions Spec/RealtimeClientConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1459,7 +1459,10 @@ class RealtimeClientConnection: QuickSpec {
client.close()
}

var transport: TestProxyTransport!
client.connect()
let firstTransport = client.transport as! TestProxyTransport
expect(client.transport).toEventuallyNot(beIdenticalTo(firstTransport), timeout: testTimeout)
let newTransport = client.transport as! TestProxyTransport

waitUntil(timeout: testTimeout) { done in
client.connection.on { stateChange in
Expand All @@ -1480,11 +1483,9 @@ class RealtimeClientConnection: QuickSpec {
break
}
}
client.connect()
transport = client.transport as! TestProxyTransport
}

let failures = transport.protocolMessagesReceived.filter({ $0.action == .Error })
let failures = firstTransport.protocolMessagesReceived.filter({ $0.action == .Error }) + newTransport.protocolMessagesReceived.filter({ $0.action == .Error })

if failures.count != 2 {
fail("Should have two connection request fail")
Expand Down Expand Up @@ -2040,6 +2041,163 @@ class RealtimeClientConnection: QuickSpec {
expect(channel.errorReason!.message).to(contain("Unable to recover connection"))
}

// RTN15h
context("DISCONNECTED message contains a token error") {

it("if the token is renewable then error should not be emitted") {
let options = AblyTests.commonAppSetup()
options.autoConnect = false
options.authCallback = { tokenParams, callback in
callback(getTestTokenDetails(key: options.key, capability: tokenParams.capability, ttl: tokenParams.ttl), nil)
}
let tokenTtl = 10.0
options.token = getTestToken(key: options.key, ttl: tokenTtl)

let client = ARTRealtime(options: options)
client.setTransportClass(TestProxyTransport.self)
defer {
client.dispose()
client.close()
}

client.connect()
expect(client.connection.state).toEventually(equal(ARTRealtimeConnectionState.Connected), timeout: testTimeout)
let firstTransport = client.transport as! TestProxyTransport

client.connection.on { stateChange in
fail("Should not be called, was called with \(stateChange)")
}

let protocolMessage = ARTProtocolMessage()
protocolMessage.action = .Disconnected
protocolMessage.error = ARTErrorInfo.createWithCode(40142, status: 401, message: "test error")
client.realtimeTransport(firstTransport, didReceiveMessage: protocolMessage)

expect(client.connection.state).to(equal(ARTRealtimeConnectionState.Connected))
expect(client.connection.errorReason).to(beNil())
expect(client.transport).toNot(beIdenticalTo(firstTransport))

waitUntil(timeout: testTimeout) { done in
client.ping { error in
expect(error).to(beNil())
expect((client.transport as! TestProxyTransport).protocolMessagesReceived.filter({ $0.action == .Connected })).to(haveCount(1))
done()
}
}
}

it("should transition to Failed when the token renewal fails") {
let options = AblyTests.commonAppSetup()
options.autoConnect = false
let tokenTtl = 1.0
let tokenDetails = getTestTokenDetails(key: options.key, capability: nil, ttl: tokenTtl)!
options.token = tokenDetails.token
options.authCallback = { tokenParams, callback in
// Let the token expire.
delay(tokenTtl) {
callback(tokenDetails, nil) // Return the same expired token again.
}
}

let client = ARTRealtime(options: options)
client.setTransportClass(TestProxyTransport.self)
defer {
client.dispose()
client.close()
}

client.connect()
expect(client.connection.state).toEventually(equal(ARTRealtimeConnectionState.Connected), timeout: testTimeout)
let firstTransport = client.transport as! TestProxyTransport
var newTransport: TestProxyTransport!

waitUntil(timeout: testTimeout) { done in
client.connection.on { stateChange in
let stateChange = stateChange!
let state = stateChange.current
let errorInfo = stateChange.reason
switch state {
case .Connected:
fail("Should not be connected")
done()
case .Failed, .Disconnected, .Suspended:
guard let errorInfo = errorInfo else {
fail("ErrorInfo is nil"); done(); return
}
expect(errorInfo.code).to(equal(40142)) //Token expired
done()
default:
break
}
}

let protocolMessage = ARTProtocolMessage()
protocolMessage.action = .Disconnected
protocolMessage.error = ARTErrorInfo.createWithCode(40142, status: 401, message: "test error")
client.realtimeTransport(firstTransport, didReceiveMessage: protocolMessage)

expect(client.connection.state).to(equal(ARTRealtimeConnectionState.Connected))
expect(client.connection.errorReason).to(beNil())
expect(client.transport).toNot(beIdenticalTo(firstTransport))
newTransport = client.transport as! TestProxyTransport
}

let failures = newTransport.protocolMessagesReceived.filter({ $0.action == .Error })

if failures.count != 1 {
fail("Should have one connection request fail")
return
}

expect(failures[0].error!.code).to(equal(40142))
}

it("if the token is not renewable or token creation fails then error should be emitted") {
let options = AblyTests.commonAppSetup()
options.autoConnect = false
options.key = nil
let tokenTtl = 10.0
let tokenDetails = getTestTokenDetails(key: options.key, capability: nil, ttl: tokenTtl)!
options.token = tokenDetails.token

let client = ARTRealtime(options: options)
client.setTransportClass(TestProxyTransport.self)
defer {
client.dispose()
client.close()
}

client.connect()
expect(client.connection.state).toEventually(equal(ARTRealtimeConnectionState.Connected), timeout: testTimeout)

waitUntil(timeout: testTimeout) { done in
client.connection.on { stateChange in
let stateChange = stateChange!
let state = stateChange.current
let errorInfo = stateChange.reason
switch state {
case .Connected:
fail("Should not be connected")
done()
case .Failed, .Disconnected, .Suspended:
guard let errorInfo = errorInfo else {
fail("ErrorInfo is nil"); done(); return
}
expect(errorInfo.code).to(equal(40142)) //Token expired
done()
default:
break
}
}

let protocolMessage = ARTProtocolMessage()
protocolMessage.action = .Disconnected
protocolMessage.error = ARTErrorInfo.createWithCode(40142, status: 401, message: "test error")
client.realtimeTransport(client.transport, didReceiveMessage: protocolMessage)
}
}
}

}

// RTN16
Expand Down