diff --git a/ably-ios/ARTRealtime.m b/ably-ios/ARTRealtime.m index bc1984ce6..4c1392a05 100644 --- a/ably-ios/ARTRealtime.m +++ b/ably-ios/ARTRealtime.m @@ -789,7 +789,7 @@ - (void)realtimeTransportUnavailable:(id)transport { } - (void)realtimeTransportClosed:(id)transport { - //Close succeeded. Nothing more to do. + // Close succeeded. Nothing more to do. [self transition:ARTRealtimeClosed]; } @@ -797,9 +797,8 @@ - (void)realtimeTransportDisconnected:(id)transport { [self transition:ARTRealtimeDisconnected]; } -- (void)realtimeTransportFailed:(id)transport { - // TODO add error codes to these failed transitions - [self transition:ARTRealtimeFailed]; +- (void)realtimeTransportFailed:(id)transport withErrorInfo:(ARTErrorInfo *)errorInfo { + [self transition:ARTRealtimeFailed withErrorInfo:errorInfo]; } - (void)realtimeTransportNeverConnected:(id)transport { diff --git a/ably-ios/ARTRealtimeTransport.h b/ably-ios/ARTRealtimeTransport.h index ff3655a78..b5353c097 100644 --- a/ably-ios/ARTRealtimeTransport.h +++ b/ably-ios/ARTRealtimeTransport.h @@ -12,6 +12,9 @@ @class ARTProtocolMessage; @class ARTStatus; +@class ARTErrorInfo; + +ART_ASSUME_NONNULL_BEGIN @protocol ARTRealtimeTransportDelegate @@ -25,7 +28,7 @@ - (void)realtimeTransportNeverConnected:(id)transport; - (void)realtimeTransportRefused:(id)transport; - (void)realtimeTransportTooBig:(id)transport; -- (void)realtimeTransportFailed:(id)transport; +- (void)realtimeTransportFailed:(id)transport withErrorInfo:(ARTErrorInfo *)errorInfo; @end @@ -40,3 +43,5 @@ - (void)abort:(ARTStatus *)reason; @end + +ART_ASSUME_NONNULL_END diff --git a/ably-ios/ARTStatus.h b/ably-ios/ARTStatus.h index 87de10256..27fd62cf5 100644 --- a/ably-ios/ARTStatus.h +++ b/ably-ios/ARTStatus.h @@ -29,18 +29,20 @@ ART_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString *const ARTAblyErrorDomain; -// FIXME: base NSError @interface ARTErrorInfo : NSObject @property (readonly, copy, nonatomic) NSString *message; @property (readonly, assign, nonatomic) int statusCode; @property (readonly, assign, nonatomic) int code; +// FIXME: use NSInteger instead int (don't know what kind of processor architecture your code might run) - (ARTErrorInfo *)setCode:(int) code message:(NSString *) message; - (ARTErrorInfo *)setCode:(int) code status:(int) status message:(NSString *) message; + (ARTErrorInfo *)createWithCode:(int)code message:(NSString *)message; + (ARTErrorInfo *)createWithCode:(int)code status:(int)status message:(NSString *)message; +// FIXME: base NSError ++ (ARTErrorInfo *)createWithNSError:(NSError *)error; - (NSString *)description; diff --git a/ably-ios/ARTStatus.m b/ably-ios/ARTStatus.m index 2025eec7c..b9074c9e0 100644 --- a/ably-ios/ARTStatus.m +++ b/ably-ios/ARTStatus.m @@ -12,11 +12,15 @@ NSString *const ARTAblyErrorDomain = @"ARTAblyErrorDomain"; +NSInteger getStatusFromCode(NSInteger code) { + return code / 100; +} + @implementation ARTErrorInfo - (ARTErrorInfo *)setCode:(int)code message:(NSString *)message { _code = code; - _statusCode = code / 100; + _statusCode = getStatusFromCode(code); _message = message; return self; } @@ -36,6 +40,10 @@ + (ARTErrorInfo *)createWithCode:(int)code status:(int)status message:(NSString return [[[ARTErrorInfo alloc] init] setCode:code status:status message:message]; } ++ (ARTErrorInfo *)createWithNSError:(NSError *)error { + return [[[ARTErrorInfo alloc] init] setCode:error.code status:getStatusFromCode(error.code) message:error.description]; +} + - (NSString *)description { return [NSString stringWithFormat:@"ARTErrorInfo with code %d, message: %@", self.statusCode, self.message]; } diff --git a/ably-ios/ARTWebSocketTransport.m b/ably-ios/ARTWebSocketTransport.m index 452c4b45f..b2a75bde2 100644 --- a/ably-ios/ARTWebSocketTransport.m +++ b/ably-ios/ARTWebSocketTransport.m @@ -17,6 +17,7 @@ #import "ARTClientOptions.h" #import "ARTAuthTokenParams.h" #import "ARTAuthTokenDetails.h" +#import "ARTStatus.h" enum { ARTWsNeverConnected = -1, @@ -97,7 +98,7 @@ - (void)connect { if (error) { [selfStrong.logger error:@"ARTWebSocketTransport: token auth failed with %@", error.description]; - [selfStrong.delegate realtimeTransportFailed:selfStrong]; + [selfStrong.delegate realtimeTransportFailed:selfStrong withErrorInfo:[ARTErrorInfo createWithNSError:error]]; return; } @@ -229,7 +230,7 @@ - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { CFRunLoopPerformBlock(self.rl, kCFRunLoopDefaultMode, ^{ ARTWebSocketTransport *s = weakSelf; if (s) { - [s.delegate realtimeTransportFailed:s]; + [s.delegate realtimeTransportFailed:s withErrorInfo:[ARTErrorInfo createWithNSError:error]]; } }); CFRunLoopWakeUp(self.rl); @@ -284,8 +285,7 @@ - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reas default: { // Failed - // no idea why - [s.delegate realtimeTransportFailed:s]; + [s.delegate realtimeTransportFailed:s withErrorInfo:[ARTErrorInfo createWithCode:code message:reason]]; break; } } diff --git a/ably-iosTests/ARTRealtimeAttachTest.m b/ably-iosTests/ARTRealtimeAttachTest.m index fcbac2344..b0cebc6de 100644 --- a/ably-iosTests/ARTRealtimeAttachTest.m +++ b/ably-iosTests/ARTRealtimeAttachTest.m @@ -16,6 +16,9 @@ #import "ARTLog.h" #import "ARTEventEmitter.h" #import "ARTStatus.h" +#import "ARTAuth.h" +#import "ARTAuthTokenParams.h" +#import "ARTAuthTokenDetails.h" @interface ARTRealtimeAttachTest : XCTestCase { ARTRealtime *_realtime; @@ -346,16 +349,22 @@ - (void)testChannelClosesOnClose { - (void)testPresenceEnterRestricted { XCTestExpectation *expect = [self expectationWithDescription:@"testSimpleDisconnected"]; - // Debug - ARTClientOptions *clientOptions = [ARTTestUtil clientOptions]; - clientOptions.logLevel = ARTLogLevelVerbose; - - // FIXME: why is `setupApp` using a callback? hard reading... could be a blocking method (only once per test) - [ARTTestUtil setupApp:clientOptions withAlteration:TestAlterationRestrictCapability cb:^(ARTClientOptions *options) { + [ARTTestUtil setupApp:[ARTTestUtil clientOptions] withAlteration:TestAlterationRestrictCapability cb:^(ARTClientOptions *options) { // Connection options.clientId = @"some_client_id"; + options.autoConnect = false; + ARTRealtime *realtime = [[ARTRealtime alloc] initWithOptions:options]; + // FIXME: there is setupApp, testRealtime, testRest, ... try to unify them and then use this code + ARTAuthTokenParams *tokenParams = [[ARTAuthTokenParams alloc] initWithClientId:options.clientId]; + tokenParams.capability = @"{\"canpublish:*\":[\"publish\"],\"canpublish:andpresence\":[\"presence\",\"publish\"],\"cansubscribe:*\":[\"subscribe\"]}"; + + [realtime.auth authorise:tokenParams options:options force:false callback:^(ARTAuthTokenDetails *tokenDetails, NSError *error) { + options.token = tokenDetails.token; + [realtime connect]; + }]; + [realtime.eventEmitter on:^(ARTRealtimeConnectionState state, ARTErrorInfo *errorInfo) { if (state == ARTRealtimeConnected) { ARTRealtimeChannel *channel = [realtime channel:@"some_unpermitted_channel"]; @@ -364,6 +373,15 @@ - (void)testPresenceEnterRestricted { [expect fulfill]; }]; } + else if (state == ARTRealtimeFailed) { + if (errorInfo) { + XCTFail(@"%@", errorInfo); + } + else { + XCTFail(); + } + [expect fulfill]; + } }]; }]; [self waitForExpectationsWithTimeout:[ARTTestUtil timeout] handler:nil]; diff --git a/ably-iosTests/ARTTestUtil.h b/ably-iosTests/ARTTestUtil.h index 91e96abc8..abebbf645 100644 --- a/ably-iosTests/ARTTestUtil.h +++ b/ably-iosTests/ARTTestUtil.h @@ -29,8 +29,10 @@ typedef NS_ENUM(NSUInteger, TestAlteration) { + (ARTCipherPayloadEncoder *)getTestCipherEncoder; +// FIXME: why is `setupApp` using a callback? hard reading... could be a blocking method (only once per test) + (void)setupApp:(ARTClientOptions *)options cb:(void(^)(ARTClientOptions *options))cb; + (void)setupApp:(ARTClientOptions *)options withAlteration:(TestAlteration) alt cb:(void (^)(ARTClientOptions *))cb; ++ (void)setupApp:(ARTClientOptions *)options withDebug:(BOOL)debug withAlteration:(TestAlteration) alt cb:(void (^)(ARTClientOptions *))cb; + (float)timeout; + (ARTClientOptions *)clientOptions; diff --git a/ably-iosTests/ARTTestUtil.m b/ably-iosTests/ARTTestUtil.m index 11b384209..95f3435c1 100644 --- a/ably-iosTests/ARTTestUtil.m +++ b/ably-iosTests/ARTTestUtil.m @@ -44,7 +44,7 @@ + (NSString *)getCrypto128Json { return [ARTTestUtil getFileByName:@"ably-common/test-resources/crypto-data-128.json"]; } -+ (void)setupApp:(ARTClientOptions *)options withAlteration:(TestAlteration)alt appId:(NSString *)appId cb:(void (^)(ARTClientOptions *))cb { ++ (void)setupApp:(ARTClientOptions *)options withDebug:(BOOL)debug withAlteration:(TestAlteration)alt appId:(NSString *)appId cb:(void (^)(ARTClientOptions *))cb { NSString *str = [ARTTestUtil getTestAppSetupJson]; if (str == nil) { [NSException raise:@"error getting test-app-setup.json loaded. Maybe ably-common is missing" format:@""]; @@ -62,6 +62,9 @@ + (void)setupApp:(ARTClientOptions *)options withAlteration:(TestAlteration)alt options.environment = @"sandbox"; } options.binary = NO; + if (debug) { + options.logLevel = ARTLogLevelVerbose; + } NSString *urlStr = [NSString stringWithFormat:@"https://%@:%d/apps", options.restHost, options.restPort]; NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlStr]]; @@ -71,8 +74,10 @@ + (void)setupApp:(ARTClientOptions *)options withAlteration:(TestAlteration)alt [req setValue:@"application/json" forHTTPHeaderField:@"Accept"]; [req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - // NSLog(@"Creating test app. URL: %@, Method: %@, Body: %@, Headers: %@", req.URL, req.HTTPMethod, [[NSString alloc] initWithData:req.HTTPBody encoding:NSUTF8StringEncoding], req.allHTTPHeaderFields); - + if (debug) { + NSLog(@"Creating test app. URL: %@, Method: %@, Body: %@, Headers: %@", req.URL, req.HTTPMethod, [[NSString alloc] initWithData:req.HTTPBody encoding:NSUTF8StringEncoding], req.allHTTPHeaderFields); + } + CFRunLoopRef rl = CFRunLoopGetCurrent(); NSURLSession *urlSession = [NSURLSession sharedSession]; @@ -92,11 +97,16 @@ + (void)setupApp:(ARTClientOptions *)options withAlteration:(TestAlteration)alt NSLog(@"No response"); return; } + else if (debug) { + NSLog(@"Response: %@", json); + } NSDictionary *key = json[@"keys"][(alt == TestAlterationRestrictCapability ? 1 :0)]; ARTClientOptions *testOptions = [options copy]; + // TODO: assign key[@"capability"] + testOptions.key = key[@"keyStr"]; if (alt == TestAlterationBadKeyId || alt == TestAlterationBadKeyValue) @@ -112,8 +122,12 @@ + (void)setupApp:(ARTClientOptions *)options withAlteration:(TestAlteration)alt [task resume]; } -+ (void)setupApp:(ARTClientOptions *)options withAlteration:(TestAlteration) alt cb:(void (^)(ARTClientOptions *))cb { - [ARTTestUtil setupApp:options withAlteration:alt appId:nil cb:cb]; ++ (void)setupApp:(ARTClientOptions *)options withDebug:(BOOL)debug withAlteration:(TestAlteration)alt cb:(void (^)(ARTClientOptions *))cb { + [ARTTestUtil setupApp:options withDebug:debug withAlteration:alt appId:nil cb:cb]; +} + ++ (void)setupApp:(ARTClientOptions *)options withAlteration:(TestAlteration)alt cb:(void (^)(ARTClientOptions *))cb { + [ARTTestUtil setupApp:options withDebug:NO withAlteration:alt cb:cb]; } + (void)setupApp:(ARTClientOptions *)options cb:(void (^)(ARTClientOptions *))cb {