From 5e915b44216237e14668da3694b0a10eb6be5f9e Mon Sep 17 00:00:00 2001 From: Marat Al Date: Mon, 28 Nov 2022 01:15:36 +0100 Subject: [PATCH 01/15] Basic NSURLSessionWebSocketTask functionality --- Ably.xcodeproj/project.pbxproj | 17 +++++ Source/ARTURLSessionWebSocket.h | 12 ++++ Source/ARTURLSessionWebSocket.m | 87 +++++++++++++++++++++++++ Source/ARTWebSocket.h | 9 ++- Source/ARTWebSocketTransport.m | 29 +++++---- Source/Ably.modulemap | 1 + Spec/Test Utilities/TestUtilities.swift | 20 ++++-- 7 files changed, 156 insertions(+), 19 deletions(-) create mode 100644 Source/ARTURLSessionWebSocket.h create mode 100644 Source/ARTURLSessionWebSocket.m diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index dc1946d14..d7ce4c11c 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -112,6 +112,12 @@ 8412FDFE2661AC7B001FE9E6 /* Nimble.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8412FDF42661AC7B001FE9E6 /* Nimble.xcframework */; }; 8412FDFF2661AC7B001FE9E6 /* Nimble.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8412FDF42661AC7B001FE9E6 /* Nimble.xcframework */; }; 8412FE002661AC7B001FE9E6 /* Nimble.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8412FDF42661AC7B001FE9E6 /* Nimble.xcframework */; }; + 84767F682933FF7700899C1A /* ARTURLSessionWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 84767F662933FF7700899C1A /* ARTURLSessionWebSocket.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 84767F692933FF7700899C1A /* ARTURLSessionWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 84767F662933FF7700899C1A /* ARTURLSessionWebSocket.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 84767F6A2933FF7700899C1A /* ARTURLSessionWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 84767F662933FF7700899C1A /* ARTURLSessionWebSocket.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 84767F6B2933FF7700899C1A /* ARTURLSessionWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 84767F672933FF7700899C1A /* ARTURLSessionWebSocket.m */; }; + 84767F6C2933FF7700899C1A /* ARTURLSessionWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 84767F672933FF7700899C1A /* ARTURLSessionWebSocket.m */; }; + 84767F6D2933FF7700899C1A /* ARTURLSessionWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 84767F672933FF7700899C1A /* ARTURLSessionWebSocket.m */; }; 848ED97326E50D0F0087E800 /* ObjcppTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 848ED97226E50D0F0087E800 /* ObjcppTest.mm */; settings = {COMPILER_FLAGS = "-fmodules"; }; }; 848ED97426E50D0F0087E800 /* ObjcppTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 848ED97226E50D0F0087E800 /* ObjcppTest.mm */; settings = {COMPILER_FLAGS = "-fmodules"; }; }; 848ED97526E50D0F0087E800 /* ObjcppTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 848ED97226E50D0F0087E800 /* ObjcppTest.mm */; settings = {COMPILER_FLAGS = "-fmodules"; }; }; @@ -1023,6 +1029,8 @@ 8412FDF12661AC7A001FE9E6 /* Aspects.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Aspects.xcframework; path = Carthage/Build/Aspects.xcframework; sourceTree = ""; }; 8412FDF22661AC7B001FE9E6 /* SwiftyJSON.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SwiftyJSON.xcframework; path = Carthage/Build/SwiftyJSON.xcframework; sourceTree = ""; }; 8412FDF42661AC7B001FE9E6 /* Nimble.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Nimble.xcframework; path = Carthage/Build/Nimble.xcframework; sourceTree = ""; }; + 84767F662933FF7700899C1A /* ARTURLSessionWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTURLSessionWebSocket.h; sourceTree = ""; }; + 84767F672933FF7700899C1A /* ARTURLSessionWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTURLSessionWebSocket.m; sourceTree = ""; }; 848ED97226E50D0F0087E800 /* ObjcppTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ObjcppTest.mm; sourceTree = ""; }; 850BFB4A1B79323C009D0ADD /* ARTPaginatedResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPaginatedResult.h; sourceTree = ""; }; 850BFB4B1B79323C009D0ADD /* ARTPaginatedResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPaginatedResult.m; sourceTree = ""; }; @@ -1695,6 +1703,8 @@ 96E408461A3895E800087F77 /* ARTWebSocketTransport.m */, EB20F8D61C653F1E00EF3978 /* ARTPresence+Private.h */, EBB721C42376A948001C3550 /* ARTWebSocket.h */, + 84767F662933FF7700899C1A /* ARTURLSessionWebSocket.h */, + 84767F672933FF7700899C1A /* ARTURLSessionWebSocket.m */, ); name = Transport; sourceTree = ""; @@ -2021,6 +2031,7 @@ D5BB210B26AA98A200AA5F3E /* ARTStringifiable+Private.h in Headers */, EB4B1A0C1F2190BB00467F07 /* ARTRestChannels+Private.h in Headers */, D785C4291E549E33008FEC05 /* ARTPushChannelSubscription.h in Headers */, + 84767F682933FF7700899C1A /* ARTURLSessionWebSocket.h in Headers */, D7F1D3731BF4DE07001A4B5E /* ARTRestPresence.h in Headers */, D7B17EE31C07208B00A6958E /* ARTConnectionDetails.h in Headers */, D7F2B8B21E42410D00B65151 /* ARTPresenceMessage+Private.h in Headers */, @@ -2173,6 +2184,7 @@ D710D50421949C18008F54AD /* ARTRealtime+Private.h in Headers */, D710D58D21949D29008F54AD /* ARTPresenceMap.h in Headers */, D5BB210E26AA98A800AA5F3E /* ARTStringifiable.h in Headers */, + 84767F692933FF7700899C1A /* ARTURLSessionWebSocket.h in Headers */, D710D56B21949CB9008F54AD /* ARTPushDeviceRegistrations.h in Headers */, D710D62021949DEC008F54AD /* ARTNSMutableURLRequest+ARTPaginated.h in Headers */, D710D4B021949AF8008F54AD /* ARTAuth.h in Headers */, @@ -2317,6 +2329,7 @@ D710D51021949C19008F54AD /* ARTRealtime+Private.h in Headers */, D710D5B321949D2A008F54AD /* ARTPresenceMap.h in Headers */, D710D57121949CBA008F54AD /* ARTPushDeviceRegistrations.h in Headers */, + 84767F6A2933FF7700899C1A /* ARTURLSessionWebSocket.h in Headers */, D710D62C21949DED008F54AD /* ARTNSMutableURLRequest+ARTPaginated.h in Headers */, D710D4B221949AF9008F54AD /* ARTAuth.h in Headers */, D710D5B121949D2A008F54AD /* ARTPresence.h in Headers */, @@ -2728,6 +2741,7 @@ 1C55427D1B148306003068DB /* ARTStatus.m in Sources */, D785C42A1E549E33008FEC05 /* ARTPushChannelSubscription.m in Sources */, D7B17EE41C07208B00A6958E /* ARTConnectionDetails.m in Sources */, + 84767F6B2933FF7700899C1A /* ARTURLSessionWebSocket.m in Sources */, 96BF61591A35B52C004CF2B3 /* ARTHttp.m in Sources */, 217D1833254222F600DFF07E /* ARTSRPinningSecurityPolicy.m in Sources */, D3AD0EBE215E2FB000312105 /* ARTNSString+ARTUtil.m in Sources */, @@ -2907,6 +2921,7 @@ D710D5D921949D78008F54AD /* ARTProtocolMessage.m in Sources */, D710D53721949C54008F54AD /* ARTLocalDevice.m in Sources */, D710D53621949C54008F54AD /* ARTDeviceIdentityTokenDetails.m in Sources */, + 84767F6C2933FF7700899C1A /* ARTURLSessionWebSocket.m in Sources */, D710D4C821949BAA008F54AD /* ARTRealtimeTransport.m in Sources */, 217D184A254222F700DFF07E /* ARTSRPinningSecurityPolicy.m in Sources */, D710D49821949ACA008F54AD /* ARTRestChannel.m in Sources */, @@ -3018,6 +3033,7 @@ D54C55AC26957FDE00729EC4 /* ARTNSURL+ARTUtils.m in Sources */, D710D54921949C55008F54AD /* ARTLocalDevice.m in Sources */, D710D54821949C55008F54AD /* ARTDeviceIdentityTokenDetails.m in Sources */, + 84767F6D2933FF7700899C1A /* ARTURLSessionWebSocket.m in Sources */, D710D4CC21949BAB008F54AD /* ARTRealtimeTransport.m in Sources */, 217D1861254222FA00DFF07E /* ARTSRPinningSecurityPolicy.m in Sources */, D710D4A221949ACB008F54AD /* ARTRestChannel.m in Sources */, @@ -3414,6 +3430,7 @@ SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "Spec/AblySpec-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; diff --git a/Source/ARTURLSessionWebSocket.h b/Source/ARTURLSessionWebSocket.h new file mode 100644 index 000000000..dfe36efac --- /dev/null +++ b/Source/ARTURLSessionWebSocket.h @@ -0,0 +1,12 @@ +#import + +#import "ARTWebSocket.h" + +NS_ASSUME_NONNULL_BEGIN + +API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0)) +@interface ARTURLSessionWebSocket : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ARTURLSessionWebSocket.m b/Source/ARTURLSessionWebSocket.m new file mode 100644 index 000000000..379b1644a --- /dev/null +++ b/Source/ARTURLSessionWebSocket.m @@ -0,0 +1,87 @@ +#import "ARTURLSessionWebSocket.h" + +@implementation ARTURLSessionWebSocket { + NSURLSession *_urlSession; + NSURLSessionWebSocketTask *_webSocketTask; + dispatch_queue_t _delegateQueue; +} + +@synthesize delegate = _delegate; +@synthesize readyState = _readyState; +@synthesize delegateDispatchQueue = _delegateDispatchQueue; + +- (nonnull instancetype)initWithURLRequest:(nonnull NSURLRequest *)request { + if (self = [super init]) { + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + _urlSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; + _webSocketTask = [_urlSession webSocketTaskWithRequest:request]; + _readyState = ARTWS_CLOSED; + } + return self; +} + +- (void)setDelegateDispatchQueue:(nonnull dispatch_queue_t)queue { + _delegateQueue = queue; +} + +- (void)listenForMessage { + [_webSocketTask receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage *message, NSError *error) { + if (error != nil) { + NSLog(@"Error: %@", error); + return; + } + dispatch_async(self->_delegateQueue, ^{ + switch (message.type) { + case NSURLSessionWebSocketMessageTypeData: + [self->_delegate webSocket:self didReceiveMessage:[message data]]; + break; + case NSURLSessionWebSocketMessageTypeString: + [self->_delegate webSocket:self didReceiveMessage:[message string]]; + break; + } + }); + [self listenForMessage]; + }]; +} + +- (void)open { + assert(_delegateQueue); + _readyState = ARTWS_CONNECTING; + [self listenForMessage]; + [_webSocketTask resume]; +} + +- (void)send:(nullable id)data { + NSURLSessionWebSocketMessage *wsMessage = [data isKindOfClass:[NSString class]] ? + [[NSURLSessionWebSocketMessage alloc] initWithString:data] : + [[NSURLSessionWebSocketMessage alloc] initWithData:data]; + [_webSocketTask sendMessage:wsMessage completionHandler: ^(NSError *error) { + dispatch_async(self->_delegateQueue, ^{ + if (error != nil) { + [self->_delegate webSocket:self didFailWithError:error]; + } + }); + }]; +} + +- (void)closeWithCode:(NSInteger)code reason:(nullable NSString *)reason { + _readyState = ARTWS_CLOSING; + [_webSocketTask cancelWithCloseCode:code reason:[reason dataUsingEncoding:NSUTF8StringEncoding]]; +} + +- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *) protocol { + dispatch_async(self->_delegateQueue, ^{ + self->_readyState = ARTWS_OPEN; + [self->_delegate webSocketDidOpen:self]; + }); +} + +- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode reason:(NSData *)reason { + dispatch_async(self->_delegateQueue, ^{ + self->_readyState = ARTWS_CLOSED; + NSString *reasonString = [[NSString alloc] initWithData:reason encoding:NSUTF8StringEncoding]; + [self->_delegate webSocket:self didCloseWithCode:closeCode reason:reasonString wasClean:YES]; + }); +} + +@end diff --git a/Source/ARTWebSocket.h b/Source/ARTWebSocket.h index 06eae355a..2b5f8da33 100644 --- a/Source/ARTWebSocket.h +++ b/Source/ARTWebSocket.h @@ -7,6 +7,13 @@ NS_ASSUME_NONNULL_BEGIN @protocol ARTWebSocketDelegate; +typedef NS_ENUM(NSInteger, ARTWebSocketState) { + ARTWS_CONNECTING = 0, + ARTWS_OPEN = 1, + ARTWS_CLOSING = 2, + ARTWS_CLOSED = 3, +}; + /** This protocol has the subset of ARTSRWebSocket we actually use. */ @@ -14,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) id _Nullable delegate; @property (nullable, nonatomic, strong) dispatch_queue_t delegateDispatchQueue; -@property (atomic, assign, readonly) ARTSRReadyState readyState; +@property (atomic, assign, readonly) ARTWebSocketState readyState; - (instancetype)initWithURLRequest:(NSURLRequest *)request; diff --git a/Source/ARTWebSocketTransport.m b/Source/ARTWebSocketTransport.m index 6cdddb3ba..28fd76d55 100644 --- a/Source/ARTWebSocketTransport.m +++ b/Source/ARTWebSocketTransport.m @@ -18,6 +18,7 @@ #import "ARTNSMutableDictionary+ARTDictionaryUtil.h" #import "ARTStringifiable.h" #import "ARTClientInformation.h" +#import "ARTURLSessionWebSocket.h" enum { ARTWsNeverConnected = -1, @@ -34,7 +35,7 @@ ARTWsTlsError = 1015 }; -NSString *WebSocketStateToStr(ARTSRReadyState state); +NSString *WebSocketStateToStr(ARTWebSocketState state); @interface ARTSRWebSocket () @end @@ -84,7 +85,7 @@ - (void)dealloc { } - (BOOL)send:(NSData *)data withSource:(id)decodedObject { - if (self.websocket.readyState == ARTSR_OPEN) { + if (self.websocket.readyState == ARTWS_OPEN) { if ([decodedObject isKindOfClass:[ARTProtocolMessage class]]) { [_protocolMessagesLogger info:@"send %@", [decodedObject description]]; } @@ -197,8 +198,13 @@ - (NSURL *)setupWebSocket:(NSDictionary *)params w NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; - const Class websocketClass = configuredWebsocketClass ? configuredWebsocketClass : [ARTSRWebSocket class]; - self.websocket = [[websocketClass alloc] initWithURLRequest:request]; + if (@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)) { + const Class websocketClass = configuredWebsocketClass ?: [ARTURLSessionWebSocket class]; + self.websocket = [[websocketClass alloc] initWithURLRequest:request]; + } else { + const Class websocketClass = configuredWebsocketClass ? configuredWebsocketClass : [ARTSRWebSocket class]; + self.websocket = [[websocketClass alloc] initWithURLRequest:request]; + } [self.websocket setDelegateDispatchQueue:_workQueue]; self.websocket.delegate = self; self.websocketURL = url; @@ -248,7 +254,7 @@ - (NSString *)host { } - (ARTRealtimeTransportState)state { - if (self.websocket.readyState == ARTSR_OPEN) { + if (self.websocket.readyState == ARTWS_OPEN) { return ARTRealtimeTransportStateOpened; } return _state; @@ -340,7 +346,7 @@ - (ARTRealtimeTransportError *)classifyError:(NSError *)error { - (void)webSocket:(id)webSocket didReceiveMessage:(id)message { [self.logger verbose:__FILE__ line:__LINE__ message:@"R:%p WS:%p websocket did receive message", _delegate, self]; - if (self.websocket.readyState == ARTSR_CLOSED) { + if (self.websocket.readyState == ARTWS_CLOSED) { [self.logger debug:__FILE__ line:__LINE__ message:@"R:%p WS:%p websocket is closed, message has been ignored", _delegate, self]; return; } @@ -377,17 +383,18 @@ - (void)webSocketMessageProtocol:(ARTProtocolMessage *)message { @end -NSString *WebSocketStateToStr(ARTSRReadyState state) { +NSString *WebSocketStateToStr(ARTWebSocketState state) { switch (state) { - case ARTSR_CONNECTING: + case ARTWS_CONNECTING: return @"Connecting"; //0 - case ARTSR_OPEN: + case ARTWS_OPEN: return @"Open"; //1 - case ARTSR_CLOSING: + case ARTWS_CLOSING: return @"Closing"; //2 - case ARTSR_CLOSED: + case ARTWS_CLOSED: return @"Closed"; //3 } + return @"Unknown"; } NSString *ARTRealtimeTransportStateToStr(ARTRealtimeTransportState state) { diff --git a/Source/Ably.modulemap b/Source/Ably.modulemap index 3eb1d4c68..d44e2f109 100644 --- a/Source/Ably.modulemap +++ b/Source/Ably.modulemap @@ -65,6 +65,7 @@ framework module Ably { header "ARTFormEncode.h" header "ARTStringifiable+Private.h" header "ARTSRWebSocket.h" + header "ARTURLSessionWebSocket.h" header "ARTGCD.h" header "ARTNSArray+ARTFunctional.h" header "ARTNSDictionary+ARTDictionaryUtil.h" diff --git a/Spec/Test Utilities/TestUtilities.swift b/Spec/Test Utilities/TestUtilities.swift index 3c67994f2..720a46925 100644 --- a/Spec/Test Utilities/TestUtilities.swift +++ b/Spec/Test Utilities/TestUtilities.swift @@ -1212,32 +1212,38 @@ class TestProxyTransport: ARTWebSocketTransport { private func setupFakeNetworkResponse(_ networkResponse: FakeNetworkResponse) { var hook: AspectToken? - hook = ARTSRWebSocket.testSuite_replaceClassMethod(#selector(ARTSRWebSocket.open)) { + let handler = { if TestProxyTransport.fakeNetworkResponse == nil { return } - + func performFakeConnectionError(_ secondsForDelay: TimeInterval, error: ARTRealtimeTransportError) { self.queue.asyncAfter(deadline: .now() + secondsForDelay) { self.delegate?.realtimeTransportFailed(self, withError: error) hook?.remove() } } - + guard let url = self.lastUrl else { fatalError("MockNetworkResponse: lastUrl should not be nil") } - + switch networkResponse { case .noInternet, - .hostUnreachable, - .hostInternalError, - .host400BadRequest: + .hostUnreachable, + .hostInternalError, + .host400BadRequest: performFakeConnectionError(0.1, error: networkResponse.transportError(for: url)) case .requestTimeout(let timeout): performFakeConnectionError(0.1 + timeout, error: networkResponse.transportError(for: url)) } } + if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { + hook = ARTURLSessionWebSocket.testSuite_replaceClassMethod(#selector(ARTURLSessionWebSocket.open), code: handler) + } + else { + hook = ARTSRWebSocket.testSuite_replaceClassMethod(#selector(ARTSRWebSocket.open), code: handler) + } } private func performNetworkConnectEvent() { From 031975312c4b11319fb4398650b2f8b5419aeb66 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 4 Dec 2022 23:48:19 +0100 Subject: [PATCH 02/15] Check readyState before setting. --- Source/ARTURLSessionWebSocket.m | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Source/ARTURLSessionWebSocket.m b/Source/ARTURLSessionWebSocket.m index 379b1644a..eaab783b4 100644 --- a/Source/ARTURLSessionWebSocket.m +++ b/Source/ARTURLSessionWebSocket.m @@ -1,5 +1,11 @@ #import "ARTURLSessionWebSocket.h" +@interface ARTURLSessionWebSocket () + +@property (atomic, assign, readwrite) ARTWebSocketState readyState; + +@end + @implementation ARTURLSessionWebSocket { NSURLSession *_urlSession; NSURLSessionWebSocketTask *_webSocketTask; @@ -65,22 +71,28 @@ - (void)send:(nullable id)data { } - (void)closeWithCode:(NSInteger)code reason:(nullable NSString *)reason { - _readyState = ARTWS_CLOSING; + if (self.readyState != ARTWS_CLOSED) { + self.readyState = ARTWS_CLOSING; + } [_webSocketTask cancelWithCloseCode:code reason:[reason dataUsingEncoding:NSUTF8StringEncoding]]; } - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *) protocol { + self.readyState = ARTWS_OPEN; dispatch_async(self->_delegateQueue, ^{ - self->_readyState = ARTWS_OPEN; [self->_delegate webSocketDidOpen:self]; }); } -- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode reason:(NSData *)reason { +- (void)URLSession:(NSURLSession *)session + webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask + didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode reason:(NSData *)reasonData { + if (self.readyState == ARTWS_CLOSING || self.readyState == ARTWS_OPEN) { + self.readyState = ARTWS_CLOSED; + } dispatch_async(self->_delegateQueue, ^{ - self->_readyState = ARTWS_CLOSED; - NSString *reasonString = [[NSString alloc] initWithData:reason encoding:NSUTF8StringEncoding]; - [self->_delegate webSocket:self didCloseWithCode:closeCode reason:reasonString wasClean:YES]; + NSString *reason = [[NSString alloc] initWithData:reasonData encoding:NSUTF8StringEncoding]; + [self->_delegate webSocket:self didCloseWithCode:closeCode reason:reason wasClean:YES]; }); } From 30d0fc19fc34e737ad9089a2add36206247e5a5f Mon Sep 17 00:00:00 2001 From: Marat Al Date: Mon, 5 Dec 2022 14:53:36 +0100 Subject: [PATCH 03/15] Weakified self + added logger --- Source/ARTURLSessionWebSocket.h | 4 ++ Source/ARTURLSessionWebSocket.m | 74 +++++++++++++++++++++++---------- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/Source/ARTURLSessionWebSocket.h b/Source/ARTURLSessionWebSocket.h index dfe36efac..a4e4069bb 100644 --- a/Source/ARTURLSessionWebSocket.h +++ b/Source/ARTURLSessionWebSocket.h @@ -2,11 +2,15 @@ #import "ARTWebSocket.h" +@class ARTLog; + NS_ASSUME_NONNULL_BEGIN API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0)) @interface ARTURLSessionWebSocket : NSObject +@property (readonly, strong, nonatomic) ARTLog *logger; + @end NS_ASSUME_NONNULL_END diff --git a/Source/ARTURLSessionWebSocket.m b/Source/ARTURLSessionWebSocket.m index eaab783b4..dd6483178 100644 --- a/Source/ARTURLSessionWebSocket.m +++ b/Source/ARTURLSessionWebSocket.m @@ -1,4 +1,5 @@ #import "ARTURLSessionWebSocket.h" +#import "ARTLog.h" @interface ARTURLSessionWebSocket () @@ -15,6 +16,7 @@ @implementation ARTURLSessionWebSocket { @synthesize delegate = _delegate; @synthesize readyState = _readyState; @synthesize delegateDispatchQueue = _delegateDispatchQueue; +@synthesize logger = _logger; - (nonnull instancetype)initWithURLRequest:(nonnull NSURLRequest *)request { if (self = [super init]) { @@ -31,42 +33,58 @@ - (void)setDelegateDispatchQueue:(nonnull dispatch_queue_t)queue { } - (void)listenForMessage { + __weak ARTURLSessionWebSocket *weakSelf = self; [_webSocketTask receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage *message, NSError *error) { - if (error != nil) { - NSLog(@"Error: %@", error); - return; + ARTURLSessionWebSocket *strongSelf = weakSelf; + if (strongSelf) { + dispatch_async(strongSelf->_delegateQueue, ^{ + /* + * We will ignore `error` object here, relying only on `message` object presence, since: + * 1) there is additional handler for connectivity issues (`URLSession:task:didCompleteWithError:`) and + * 2) the `ARTWebSocketTransport` is not very welcoming for error emerging from here, causing some tests to fail. + */ + if (error != nil) { + [strongSelf->_logger debug:__FILE__ line:__LINE__ message:@"Receive message error: %@, task state = %@", error, @(strongSelf->_webSocketTask.state)]; + } + if (message != nil) { + switch (message.type) { + case NSURLSessionWebSocketMessageTypeData: + [strongSelf->_delegate webSocket:strongSelf didReceiveMessage:[message data]]; + break; + case NSURLSessionWebSocketMessageTypeString: + [strongSelf->_delegate webSocket:strongSelf didReceiveMessage:[message string]]; + break; + } + } + if (strongSelf.readyState == ARTWS_OPEN) { + [strongSelf listenForMessage]; + } + }); } - dispatch_async(self->_delegateQueue, ^{ - switch (message.type) { - case NSURLSessionWebSocketMessageTypeData: - [self->_delegate webSocket:self didReceiveMessage:[message data]]; - break; - case NSURLSessionWebSocketMessageTypeString: - [self->_delegate webSocket:self didReceiveMessage:[message string]]; - break; - } - }); - [self listenForMessage]; }]; } - (void)open { assert(_delegateQueue); _readyState = ARTWS_CONNECTING; - [self listenForMessage]; [_webSocketTask resume]; } - (void)send:(nullable id)data { + assert(_delegateQueue); NSURLSessionWebSocketMessage *wsMessage = [data isKindOfClass:[NSString class]] ? [[NSURLSessionWebSocketMessage alloc] initWithString:data] : [[NSURLSessionWebSocketMessage alloc] initWithData:data]; - [_webSocketTask sendMessage:wsMessage completionHandler: ^(NSError *error) { - dispatch_async(self->_delegateQueue, ^{ - if (error != nil) { - [self->_delegate webSocket:self didFailWithError:error]; - } - }); + __weak ARTURLSessionWebSocket *weakSelf = self; + [_webSocketTask sendMessage:wsMessage completionHandler:^(NSError *error) { + ARTURLSessionWebSocket *strongSelf = weakSelf; + if (strongSelf) { + dispatch_async(strongSelf->_delegateQueue, ^{ + if (error != nil) { + [strongSelf->_delegate webSocket:strongSelf didFailWithError:error]; + } + }); + } }]; } @@ -79,6 +97,7 @@ - (void)closeWithCode:(NSInteger)code reason:(nullable NSString *)reason { - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *) protocol { self.readyState = ARTWS_OPEN; + [self listenForMessage]; dispatch_async(self->_delegateQueue, ^{ [self->_delegate webSocketDidOpen:self]; }); @@ -96,4 +115,17 @@ - (void)URLSession:(NSURLSession *)session }); } +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { + self.readyState = ARTWS_CLOSED; + if (error != nil) { + [_logger debug:__FILE__ line:__LINE__ message:@"Session completion error: %@, task state = %@", error, @(_webSocketTask.state)]; + dispatch_async(self->_delegateQueue, ^{ + [self->_delegate webSocket:self didFailWithError:error]; + }); + } + else { + [_logger debug:__FILE__ line:__LINE__ message:@"Session completion task state = %@", @(_webSocketTask.state)]; + } +} + @end From 2d685ef76138e6a93db3a95d8d3dd575c97cf6c7 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sat, 10 Dec 2022 20:34:22 +0100 Subject: [PATCH 04/15] Removed unnecessary null specifiers --- Source/ARTURLSessionWebSocket.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/ARTURLSessionWebSocket.m b/Source/ARTURLSessionWebSocket.m index a99008f6e..cd8a2ee9f 100644 --- a/Source/ARTURLSessionWebSocket.m +++ b/Source/ARTURLSessionWebSocket.m @@ -18,7 +18,7 @@ @implementation ARTURLSessionWebSocket { @synthesize readyState = _readyState; @synthesize delegateDispatchQueue = _delegateDispatchQueue; -- (nonnull instancetype)initWithURLRequest:(nonnull NSURLRequest *)request logger:(ARTLog *)logger { +- (instancetype)initWithURLRequest:(NSURLRequest *)request logger:(ARTLog *)logger { if (self = [super init]) { NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; _urlSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; @@ -29,7 +29,7 @@ - (nonnull instancetype)initWithURLRequest:(nonnull NSURLRequest *)request logge return self; } -- (void)setDelegateDispatchQueue:(nonnull dispatch_queue_t)queue { +- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue { _delegateQueue = queue; } @@ -71,7 +71,7 @@ - (void)open { [_webSocketTask resume]; } -- (void)send:(nullable id)data { +- (void)send:(id)data { assert(_delegateQueue); NSURLSessionWebSocketMessage *wsMessage = [data isKindOfClass:[NSString class]] ? [[NSURLSessionWebSocketMessage alloc] initWithString:data] : @@ -89,7 +89,7 @@ - (void)send:(nullable id)data { }]; } -- (void)closeWithCode:(NSInteger)code reason:(nullable NSString *)reason { +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason { if (self.readyState != ARTWS_CLOSED) { self.readyState = ARTWS_CLOSING; } From 0a13cd6332b5a640a4282fdd9f16e6f04f6fe943 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 11 Dec 2022 22:28:21 +0100 Subject: [PATCH 05/15] Fixed reconnection issue --- Source/ARTWebSocketTransport.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/ARTWebSocketTransport.m b/Source/ARTWebSocketTransport.m index 9e5e54c17..f91327d5a 100644 --- a/Source/ARTWebSocketTransport.m +++ b/Source/ARTWebSocketTransport.m @@ -325,6 +325,8 @@ - (ARTRealtimeTransportError *)classifyError:(NSError *)error { type = ARTRealtimeTransportErrorTypeHostUnreachable; } else if ([error.domain isEqualToString:@"NSPOSIXErrorDomain"] && (error.code == 57 || error.code == 50)) { type = ARTRealtimeTransportErrorTypeNoInternet; + } else if ([error.domain isEqualToString:@"NSURLErrorDomain"] && (error.code == -1009)) { + type = ARTRealtimeTransportErrorTypeNoInternet; } else if ([error.domain isEqualToString:ARTSRWebSocketErrorDomain] && error.code == 2132) { id status = error.userInfo[ARTSRHTTPResponseErrorKey]; if (status) { From 4793ebdb4ed192865744c81ac16809c6cea459c8 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sat, 24 Dec 2022 22:26:49 +0100 Subject: [PATCH 06/15] Removed unintended setting change --- Ably.xcodeproj/project.pbxproj | 1 - 1 file changed, 1 deletion(-) diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index d7ce4c11c..8bc34345b 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -3430,7 +3430,6 @@ SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "Spec/AblySpec-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; From 565a5507218863d22b28b57017a4d16d7fd68052 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sat, 24 Dec 2022 22:29:47 +0100 Subject: [PATCH 07/15] Removed unnecessary declaration --- Source/ARTURLSessionWebSocket.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/ARTURLSessionWebSocket.h b/Source/ARTURLSessionWebSocket.h index d6eb0bcfb..182e8d800 100644 --- a/Source/ARTURLSessionWebSocket.h +++ b/Source/ARTURLSessionWebSocket.h @@ -1,9 +1,6 @@ #import - #import "ARTWebSocket.h" -@class ARTLog; - NS_ASSUME_NONNULL_BEGIN API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0)) From b7b9be9e97cbe731c309b2b1a717fb76c5512205 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sat, 24 Dec 2022 22:35:43 +0100 Subject: [PATCH 08/15] Moved delegate conformance declaration to implementation file --- Source/ARTURLSessionWebSocket.h | 2 +- Source/ARTURLSessionWebSocket.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/ARTURLSessionWebSocket.h b/Source/ARTURLSessionWebSocket.h index 182e8d800..962857fc6 100644 --- a/Source/ARTURLSessionWebSocket.h +++ b/Source/ARTURLSessionWebSocket.h @@ -4,7 +4,7 @@ NS_ASSUME_NONNULL_BEGIN API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0)) -@interface ARTURLSessionWebSocket : NSObject +@interface ARTURLSessionWebSocket : NSObject @end diff --git a/Source/ARTURLSessionWebSocket.m b/Source/ARTURLSessionWebSocket.m index cd8a2ee9f..450a5cd1d 100644 --- a/Source/ARTURLSessionWebSocket.m +++ b/Source/ARTURLSessionWebSocket.m @@ -1,7 +1,7 @@ #import "ARTURLSessionWebSocket.h" #import "ARTLog.h" -@interface ARTURLSessionWebSocket () +@interface ARTURLSessionWebSocket () @property (atomic, assign, readwrite) ARTWebSocketState readyState; From b05377d15f16ea9e1bae34ef2308e4dfda21fe49 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 25 Dec 2022 13:57:45 +0100 Subject: [PATCH 09/15] Scheduling on main dispatch queue if `delegateDispatchQueue` is not set. --- Source/ARTURLSessionWebSocket.m | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Source/ARTURLSessionWebSocket.m b/Source/ARTURLSessionWebSocket.m index 450a5cd1d..1c41da4e2 100644 --- a/Source/ARTURLSessionWebSocket.m +++ b/Source/ARTURLSessionWebSocket.m @@ -10,7 +10,6 @@ @interface ARTURLSessionWebSocket () @implementation ARTURLSessionWebSocket { NSURLSession *_urlSession; NSURLSessionWebSocketTask *_webSocketTask; - dispatch_queue_t _delegateQueue; ARTLog *_logger; } @@ -29,8 +28,8 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request logger:(ARTLog *)logg return self; } -- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue { - _delegateQueue = queue; +- (dispatch_queue_t)getDelegateDispatchQueue { + return _delegateDispatchQueue ?: dispatch_get_main_queue(); } - (void)listenForMessage { @@ -38,7 +37,7 @@ - (void)listenForMessage { [_webSocketTask receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage *message, NSError *error) { ARTURLSessionWebSocket *strongSelf = weakSelf; if (strongSelf) { - dispatch_async(strongSelf->_delegateQueue, ^{ + dispatch_async([strongSelf getDelegateDispatchQueue], ^{ /* * We will ignore `error` object here, relying only on `message` object presence, since: * 1) there is additional handler for connectivity issues (`URLSession:task:didCompleteWithError:`) and @@ -66,13 +65,11 @@ - (void)listenForMessage { } - (void)open { - assert(_delegateQueue); _readyState = ARTWS_CONNECTING; [_webSocketTask resume]; } - (void)send:(id)data { - assert(_delegateQueue); NSURLSessionWebSocketMessage *wsMessage = [data isKindOfClass:[NSString class]] ? [[NSURLSessionWebSocketMessage alloc] initWithString:data] : [[NSURLSessionWebSocketMessage alloc] initWithData:data]; @@ -80,7 +77,7 @@ - (void)send:(id)data { [_webSocketTask sendMessage:wsMessage completionHandler:^(NSError *error) { ARTURLSessionWebSocket *strongSelf = weakSelf; if (strongSelf) { - dispatch_async(strongSelf->_delegateQueue, ^{ + dispatch_async([strongSelf getDelegateDispatchQueue], ^{ if (error != nil) { [strongSelf->_delegate webSocket:strongSelf didFailWithError:error]; } @@ -96,10 +93,10 @@ - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason { [_webSocketTask cancelWithCloseCode:code reason:[reason dataUsingEncoding:NSUTF8StringEncoding]]; } -- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *) protocol { +- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *)protocol { self.readyState = ARTWS_OPEN; [self listenForMessage]; - dispatch_async(self->_delegateQueue, ^{ + dispatch_async([self getDelegateDispatchQueue], ^{ [self->_delegate webSocketDidOpen:self]; }); } @@ -110,7 +107,7 @@ - (void)URLSession:(NSURLSession *)session if (self.readyState == ARTWS_CLOSING || self.readyState == ARTWS_OPEN) { self.readyState = ARTWS_CLOSED; } - dispatch_async(self->_delegateQueue, ^{ + dispatch_async([self getDelegateDispatchQueue], ^{ NSString *reason = [[NSString alloc] initWithData:reasonData encoding:NSUTF8StringEncoding]; [self->_delegate webSocket:self didCloseWithCode:closeCode reason:reason wasClean:YES]; }); @@ -120,7 +117,7 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didComp self.readyState = ARTWS_CLOSED; if (error != nil) { [_logger debug:__FILE__ line:__LINE__ message:@"Session completion error: %@, task state = %@", error, @(_webSocketTask.state)]; - dispatch_async(self->_delegateQueue, ^{ + dispatch_async([self getDelegateDispatchQueue], ^{ [self->_delegate webSocket:self didFailWithError:error]; }); } From 297a0b95383e8f28e15de3c42f16e0056a3a9c06 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 25 Dec 2022 14:10:10 +0100 Subject: [PATCH 10/15] Removed unnecessary declaration. --- Source/ARTWebSocket.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/ARTWebSocket.h b/Source/ARTWebSocket.h index 4e1180e78..e63fe7c28 100644 --- a/Source/ARTWebSocket.h +++ b/Source/ARTWebSocket.h @@ -26,8 +26,6 @@ typedef NS_ENUM(NSInteger, ARTWebSocketState) { - (instancetype)initWithURLRequest:(NSURLRequest *)request logger:(nullable ARTLog *)logger; -- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; - - (void)open; - (void)closeWithCode:(NSInteger)code reason:(nullable NSString *)reason; - (void)send:(nullable id)message; From 78efcd9d5c55fdce1237b01ef7b309d8937ef3c2 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 25 Dec 2022 14:12:13 +0100 Subject: [PATCH 11/15] Cleaned nullable specifiers --- Source/ARTWebSocket.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/ARTWebSocket.h b/Source/ARTWebSocket.h index e63fe7c28..6c0db2280 100644 --- a/Source/ARTWebSocket.h +++ b/Source/ARTWebSocket.h @@ -20,7 +20,7 @@ typedef NS_ENUM(NSInteger, ARTWebSocketState) { */ @protocol ARTWebSocket -@property (nonatomic, weak) id _Nullable delegate; +@property (nullable, nonatomic, weak) id delegate; @property (nullable, nonatomic, strong) dispatch_queue_t delegateDispatchQueue; @property (atomic, assign, readonly) ARTWebSocketState readyState; @@ -28,14 +28,14 @@ typedef NS_ENUM(NSInteger, ARTWebSocketState) { - (void)open; - (void)closeWithCode:(NSInteger)code reason:(nullable NSString *)reason; -- (void)send:(nullable id)message; +- (void)send:(id)message; @end @protocol ARTWebSocketDelegate - (void)webSocketDidOpen:(id)websocket; -- (void)webSocket:(id)webSocket didCloseWithCode:(NSInteger)code reason:(NSString * _Nullable)reason wasClean:(BOOL)wasClean; +- (void)webSocket:(id)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean; - (void)webSocket:(id)webSocket didFailWithError:(NSError *)error; - (void)webSocket:(id)webSocket didReceiveMessage:(id)message; From 737451193228231593cb15b04649b570ef1ce4df Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 25 Dec 2022 14:14:03 +0100 Subject: [PATCH 12/15] Added documentation commentary for `ARTWebSocket` and `ARTWebSocketDelegate`. --- Source/ARTWebSocket.h | 74 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/Source/ARTWebSocket.h b/Source/ARTWebSocket.h index 6c0db2280..1d17f35b9 100644 --- a/Source/ARTWebSocket.h +++ b/Source/ARTWebSocket.h @@ -16,27 +16,97 @@ typedef NS_ENUM(NSInteger, ARTWebSocketState) { }; /** - This protocol has the subset of ARTSRWebSocket we actually use. + * Describes an object that lets you connect, send and receive data to a remote web socket. */ @protocol ARTWebSocket +/** + * The delegate of the web socket. + * + * The web socket delegate is notified on all state changes that happen to the web socket. + */ @property (nullable, nonatomic, weak) id delegate; + +/** + * A dispatch queue for scheduling the delegate calls. + * + * If `nil`, the socket uses main queue for performing all delegate method calls. + */ @property (nullable, nonatomic, strong) dispatch_queue_t delegateDispatchQueue; + +/** + * Current ready state of the socket. + * + * This property is thread-safe. + */ @property (atomic, assign, readonly) ARTWebSocketState readyState; +/** + * Initializes a web socket with a given `NSURLRequest`. + * + * @param request A request to initialize with. + * @param logger An `ARTLog` object to initialize with. + */ - (instancetype)initWithURLRequest:(NSURLRequest *)request logger:(nullable ARTLog *)logger; +/** + * Opens web socket, which will trigger connection, authentication and start receiving/sending events. + */ - (void)open; + +/** + * Closes a web socket using a given code and reason. + * + * @param code Code to close the socket with. + * @param reason Reason to send to the server or `nil`. + */ - (void)closeWithCode:(NSInteger)code reason:(nullable NSString *)reason; + +/** + * Sends a UTF-8 string or a binary data to the server. + * + * @param message UTF-8 `NSString` or `NSData` to send. + */ - (void)send:(id)message; @end +/** + * Describes methods that `ARTWebSocket` objects call on their delegates to handle status and messsage events. + */ @protocol ARTWebSocketDelegate -- (void)webSocketDidOpen:(id)websocket; +/** + * Called when a given web socket was open and authenticated. + * + * @param webSocket An instance of an `ARTWebSocket` conforming object that was open. + */ +- (void)webSocketDidOpen:(id)webSocket; + +/** + * Called when a given web socket was closed. + * + * @param webSocket An instance of an `ARTWebSocket` conforming object that was closed. + * @param code Code reported by the server. + * @param reason Reason in a form of a string that was reported by the server or `nil`. + * @param wasClean Boolean value indicating whether a socket was closed in a clean state. + */ - (void)webSocket:(id)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean; + +/** + * Called when a given web socket encountered an error. + * + * @param webSocket An instance of an `ARTWebSocket` conforming object that failed with an error. + * @param error An instance of `NSError`. + */ - (void)webSocket:(id)webSocket didFailWithError:(NSError *)error; + +/** + * Called when any message was received from a web socket. + * + * @param webSocket An instance of an `ARTWebSocket` conforming object that received a message. + * @param message Received message. Either a `NSString` or `NSData`. + */ - (void)webSocket:(id)webSocket didReceiveMessage:(id)message; @end From 7aef46831f9987db3f96588b897aea3729053822 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 25 Dec 2022 14:16:31 +0100 Subject: [PATCH 13/15] Renamed enum constants. --- Source/ARTURLSessionWebSocket.m | 18 +++++++++--------- Source/ARTWebSocket.h | 8 ++++---- Source/ARTWebSocketTransport.m | 14 +++++++------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Source/ARTURLSessionWebSocket.m b/Source/ARTURLSessionWebSocket.m index 1c41da4e2..76e5633f4 100644 --- a/Source/ARTURLSessionWebSocket.m +++ b/Source/ARTURLSessionWebSocket.m @@ -22,7 +22,7 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request logger:(ARTLog *)logg NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; _urlSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; _webSocketTask = [_urlSession webSocketTaskWithRequest:request]; - _readyState = ARTWS_CLOSED; + _readyState = ARTWebSocketStateClosed; _logger = logger; } return self; @@ -56,7 +56,7 @@ - (void)listenForMessage { break; } } - if (strongSelf.readyState == ARTWS_OPEN) { + if (strongSelf.readyState == ARTWebSocketStateOpened) { [strongSelf listenForMessage]; } }); @@ -65,7 +65,7 @@ - (void)listenForMessage { } - (void)open { - _readyState = ARTWS_CONNECTING; + _readyState = ARTWebSocketStateConnecting; [_webSocketTask resume]; } @@ -87,14 +87,14 @@ - (void)send:(id)data { } - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason { - if (self.readyState != ARTWS_CLOSED) { - self.readyState = ARTWS_CLOSING; + if (self.readyState != ARTWebSocketStateClosed) { + self.readyState = ARTWebSocketStateClosing; } [_webSocketTask cancelWithCloseCode:code reason:[reason dataUsingEncoding:NSUTF8StringEncoding]]; } - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *)protocol { - self.readyState = ARTWS_OPEN; + self.readyState = ARTWebSocketStateOpened; [self listenForMessage]; dispatch_async([self getDelegateDispatchQueue], ^{ [self->_delegate webSocketDidOpen:self]; @@ -104,8 +104,8 @@ - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketT - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode reason:(NSData *)reasonData { - if (self.readyState == ARTWS_CLOSING || self.readyState == ARTWS_OPEN) { - self.readyState = ARTWS_CLOSED; + if (self.readyState == ARTWebSocketStateClosing || self.readyState == ARTWebSocketStateOpened) { + self.readyState = ARTWebSocketStateClosed; } dispatch_async([self getDelegateDispatchQueue], ^{ NSString *reason = [[NSString alloc] initWithData:reasonData encoding:NSUTF8StringEncoding]; @@ -114,7 +114,7 @@ - (void)URLSession:(NSURLSession *)session } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { - self.readyState = ARTWS_CLOSED; + self.readyState = ARTWebSocketStateClosed; if (error != nil) { [_logger debug:__FILE__ line:__LINE__ message:@"Session completion error: %@, task state = %@", error, @(_webSocketTask.state)]; dispatch_async([self getDelegateDispatchQueue], ^{ diff --git a/Source/ARTWebSocket.h b/Source/ARTWebSocket.h index 1d17f35b9..8e77d7ad5 100644 --- a/Source/ARTWebSocket.h +++ b/Source/ARTWebSocket.h @@ -9,10 +9,10 @@ NS_ASSUME_NONNULL_BEGIN @protocol ARTWebSocketDelegate; typedef NS_ENUM(NSInteger, ARTWebSocketState) { - ARTWS_CONNECTING = 0, - ARTWS_OPEN = 1, - ARTWS_CLOSING = 2, - ARTWS_CLOSED = 3, + ARTWebSocketStateConnecting = 0, + ARTWebSocketStateOpened = 1, + ARTWebSocketStateClosing = 2, + ARTWebSocketStateClosed = 3, }; /** diff --git a/Source/ARTWebSocketTransport.m b/Source/ARTWebSocketTransport.m index f91327d5a..bdda97912 100644 --- a/Source/ARTWebSocketTransport.m +++ b/Source/ARTWebSocketTransport.m @@ -84,7 +84,7 @@ - (void)dealloc { } - (BOOL)send:(NSData *)data withSource:(id)decodedObject { - if (self.websocket.readyState == ARTWS_OPEN) { + if (self.websocket.readyState == ARTWebSocketStateOpened) { [self.websocket send:data]; return true; } @@ -248,7 +248,7 @@ - (NSString *)host { } - (ARTRealtimeTransportState)state { - if (self.websocket.readyState == ARTWS_OPEN) { + if (self.websocket.readyState == ARTWebSocketStateOpened) { return ARTRealtimeTransportStateOpened; } return _state; @@ -342,7 +342,7 @@ - (ARTRealtimeTransportError *)classifyError:(NSError *)error { - (void)webSocket:(id)webSocket didReceiveMessage:(id)message { [self.logger verbose:__FILE__ line:__LINE__ message:@"R:%p WS:%p websocket did receive message", _delegate, self]; - if (self.websocket.readyState == ARTWS_CLOSED) { + if (self.websocket.readyState == ARTWebSocketStateClosed) { [self.logger debug:__FILE__ line:__LINE__ message:@"R:%p WS:%p websocket is closed, message has been ignored", _delegate, self]; return; } @@ -381,13 +381,13 @@ - (void)webSocketMessageProtocol:(ARTProtocolMessage *)message { NSString *WebSocketStateToStr(ARTWebSocketState state) { switch (state) { - case ARTWS_CONNECTING: + case ARTWebSocketStateConnecting: return @"Connecting"; //0 - case ARTWS_OPEN: + case ARTWebSocketStateOpened: return @"Open"; //1 - case ARTWS_CLOSING: + case ARTWebSocketStateClosing: return @"Closing"; //2 - case ARTWS_CLOSED: + case ARTWebSocketStateClosed: return @"Closed"; //3 } return @"Unknown"; From 2752fab445dd9fe332120b0d654f92fe013e2494 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 25 Dec 2022 22:29:59 +0100 Subject: [PATCH 14/15] Replaced numeric error code with constant. --- Source/ARTWebSocketTransport.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ARTWebSocketTransport.m b/Source/ARTWebSocketTransport.m index bdda97912..a71caae1b 100644 --- a/Source/ARTWebSocketTransport.m +++ b/Source/ARTWebSocketTransport.m @@ -325,7 +325,7 @@ - (ARTRealtimeTransportError *)classifyError:(NSError *)error { type = ARTRealtimeTransportErrorTypeHostUnreachable; } else if ([error.domain isEqualToString:@"NSPOSIXErrorDomain"] && (error.code == 57 || error.code == 50)) { type = ARTRealtimeTransportErrorTypeNoInternet; - } else if ([error.domain isEqualToString:@"NSURLErrorDomain"] && (error.code == -1009)) { + } else if ([error.domain isEqualToString:@"NSURLErrorDomain"] && (error.code == NSURLErrorNotConnectedToInternet)) { type = ARTRealtimeTransportErrorTypeNoInternet; } else if ([error.domain isEqualToString:ARTSRWebSocketErrorDomain] && error.code == 2132) { id status = error.userInfo[ARTSRHTTPResponseErrorKey]; From 703482c7e4dab2109cae7d686a80d36a827f2b69 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Mon, 26 Dec 2022 00:40:35 +0100 Subject: [PATCH 15/15] Error classification is now delegated to the implementations of the `ARTWebSocket` protocol. --- Source/ARTURLSessionWebSocket.m | 26 +++++++++++++++ Source/ARTWebSocket.h | 8 +++++ Source/ARTWebSocketTransport.m | 57 ++++++++++++++++++--------------- 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/Source/ARTURLSessionWebSocket.m b/Source/ARTURLSessionWebSocket.m index 76e5633f4..ac5b37ca4 100644 --- a/Source/ARTURLSessionWebSocket.m +++ b/Source/ARTURLSessionWebSocket.m @@ -1,4 +1,5 @@ #import "ARTURLSessionWebSocket.h" +#import "ARTRealtimeTransport.h" #import "ARTLog.h" @interface ARTURLSessionWebSocket () @@ -13,6 +14,8 @@ @implementation ARTURLSessionWebSocket { ARTLog *_logger; } +#pragma mark - ARTWebSocket + @synthesize delegate = _delegate; @synthesize readyState = _readyState; @synthesize delegateDispatchQueue = _delegateDispatchQueue; @@ -93,6 +96,29 @@ - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason { [_webSocketTask cancelWithCloseCode:code reason:[reason dataUsingEncoding:NSUTF8StringEncoding]]; } +- (ARTRealtimeTransportError *)classifyError:(NSError *)error { + ARTRealtimeTransportErrorType type = ARTRealtimeTransportErrorTypeOther; + + if ([error.domain isEqualToString:(NSString *)kCFErrorDomainCFNetwork]) { + type = ARTRealtimeTransportErrorTypeHostUnreachable; + } + else if ([error.domain isEqualToString:@"NSPOSIXErrorDomain"] && (error.code == 57 || error.code == 50)) { + type = ARTRealtimeTransportErrorTypeNoInternet; + } + else if ([error.domain isEqualToString:@"NSURLErrorDomain"] && (error.code == NSURLErrorNotConnectedToInternet)) { + type = ARTRealtimeTransportErrorTypeNoInternet; + } + else if ([error.domain isEqualToString:@"NSURLErrorDomain"] && (error.code == NSURLErrorTimedOut)) { + type = ARTRealtimeTransportErrorTypeTimeout; + } + else if ([error.domain isEqualToString:@"NSURLErrorDomain"] && (error.code == NSURLErrorBadServerResponse)) { + type = ARTRealtimeTransportErrorTypeBadResponse; + } + return [[ARTRealtimeTransportError alloc] initWithError:error type:type url:_webSocketTask.originalRequest.URL]; +} + +#pragma mark - NSURLSessionWebSocketDelegate + - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *)protocol { self.readyState = ARTWebSocketStateOpened; [self listenForMessage]; diff --git a/Source/ARTWebSocket.h b/Source/ARTWebSocket.h index 8e77d7ad5..c41941461 100644 --- a/Source/ARTWebSocket.h +++ b/Source/ARTWebSocket.h @@ -6,6 +6,7 @@ NS_ASSUME_NONNULL_BEGIN @class ARTLog; +@class ARTRealtimeTransportError; @protocol ARTWebSocketDelegate; typedef NS_ENUM(NSInteger, ARTWebSocketState) { @@ -69,6 +70,13 @@ typedef NS_ENUM(NSInteger, ARTWebSocketState) { */ - (void)send:(id)message; +/** + * Classifies error for reconnection attemts. + * + * @param error An error object to classify for reconnection. + */ +- (ARTRealtimeTransportError *)classifyError:(NSError *)error; + @end /** diff --git a/Source/ARTWebSocketTransport.m b/Source/ARTWebSocketTransport.m index a71caae1b..475235ea8 100644 --- a/Source/ARTWebSocketTransport.m +++ b/Source/ARTWebSocketTransport.m @@ -37,7 +37,7 @@ NSString *WebSocketStateToStr(ARTWebSocketState state); -@interface ARTSRWebSocket () +@interface ARTSRWebSocket (ARTWebSocket) @end Class configuredWebsocketClass = nil; @@ -258,7 +258,7 @@ - (void)setState:(ARTRealtimeTransportState)state { _state = state; } -#pragma mark - ARTSRWebSocketDelegate +#pragma mark - ARTWebSocketDelegate // All delegate methods from SocketRocket are called from rest's serial queue, // since we pass it as delegate queue on setupWebSocket. So we can safely @@ -312,33 +312,10 @@ - (void)webSocket:(id)webSocket didCloseWithCode:(NSInteger)code r - (void)webSocket:(id)webSocket didFailWithError:(NSError *)error { [self.logger debug:__FILE__ line:__LINE__ message:@"R:%p WS:%p websocket did receive error %@", _delegate, self, error]; - [_delegate realtimeTransportFailed:self withError:[self classifyError:error]]; + [_delegate realtimeTransportFailed:self withError:[webSocket classifyError:error]]; _state = ARTRealtimeTransportStateClosed; } -- (ARTRealtimeTransportError *)classifyError:(NSError *)error { - ARTRealtimeTransportErrorType type = ARTRealtimeTransportErrorTypeOther; - - if ([error.domain isEqualToString:@"com.squareup.SocketRocket"] && error.code == 504) { - type = ARTRealtimeTransportErrorTypeTimeout; - } else if ([error.domain isEqualToString:(NSString *)kCFErrorDomainCFNetwork]) { - type = ARTRealtimeTransportErrorTypeHostUnreachable; - } else if ([error.domain isEqualToString:@"NSPOSIXErrorDomain"] && (error.code == 57 || error.code == 50)) { - type = ARTRealtimeTransportErrorTypeNoInternet; - } else if ([error.domain isEqualToString:@"NSURLErrorDomain"] && (error.code == NSURLErrorNotConnectedToInternet)) { - type = ARTRealtimeTransportErrorTypeNoInternet; - } else if ([error.domain isEqualToString:ARTSRWebSocketErrorDomain] && error.code == 2132) { - id status = error.userInfo[ARTSRHTTPResponseErrorKey]; - if (status) { - return [[ARTRealtimeTransportError alloc] initWithError:error - badResponseCode:[(NSNumber *)status integerValue] - url:self.websocketURL]; - } - } - - return [[ARTRealtimeTransportError alloc] initWithError:error type:type url:self.websocketURL]; -} - - (void)webSocket:(id)webSocket didReceiveMessage:(id)message { [self.logger verbose:__FILE__ line:__LINE__ message:@"R:%p WS:%p websocket did receive message", _delegate, self]; @@ -419,3 +396,31 @@ + (instancetype)newWithTransportState:(ARTRealtimeTransportState)value { } @end + +@implementation ARTSRWebSocket (ARTWebSocket) + +- (ARTRealtimeTransportError *)classifyError:(NSError *)error { + ARTRealtimeTransportErrorType type = ARTRealtimeTransportErrorTypeOther; + + if ([error.domain isEqualToString:@"com.squareup.SocketRocket"] && error.code == 504) { + type = ARTRealtimeTransportErrorTypeTimeout; + } + else if ([error.domain isEqualToString:(NSString *)kCFErrorDomainCFNetwork]) { + type = ARTRealtimeTransportErrorTypeHostUnreachable; + } + else if ([error.domain isEqualToString:@"NSPOSIXErrorDomain"] && (error.code == 57 || error.code == 50)) { + type = ARTRealtimeTransportErrorTypeNoInternet; + } + else if ([error.domain isEqualToString:@"NSURLErrorDomain"] && (error.code == NSURLErrorNotConnectedToInternet)) { + type = ARTRealtimeTransportErrorTypeNoInternet; + } + else if ([error.domain isEqualToString:ARTSRWebSocketErrorDomain] && error.code == 2132) { + NSNumber *status = error.userInfo[ARTSRHTTPResponseErrorKey]; + if (status != nil) { + return [[ARTRealtimeTransportError alloc] initWithError:error badResponseCode:status.integerValue url:self.url]; + } + } + return [[ARTRealtimeTransportError alloc] initWithError:error type:type url:self.url]; +} + +@end