From 5a1d4303d291ad05e211725586dd4ee8d2984836 Mon Sep 17 00:00:00 2001 From: Jason Flax Date: Wed, 9 Mar 2022 09:48:41 -0500 Subject: [PATCH 1/8] Add socket plumbing --- Package.swift | 2 +- Realm/RLMApp.mm | 3 +- Realm/RLMSyncConfiguration.mm | 62 +++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 1be9460f06..aba0da5e13 100644 --- a/Package.swift +++ b/Package.swift @@ -110,7 +110,7 @@ let package = Package( targets: ["Realm", "RealmSwift"]), ], dependencies: [ - .package(name: "RealmDatabase", url: "https://github.com/realm/realm-core", .exact(Version(coreVersionStr)!)) + .package(name: "RealmDatabase", path: "../core-socket-plumbing") ], targets: [ .target( diff --git a/Realm/RLMApp.mm b/Realm/RLMApp.mm index 30a3d65be1..d6aad9663c 100644 --- a/Realm/RLMApp.mm +++ b/Realm/RLMApp.mm @@ -56,6 +56,7 @@ void send_request_to_server(app::Request&& request, rlmRequest.method = static_cast(request.method); rlmRequest.timeout = request.timeout_ms / 1000.0; + __block auto blockCompletionBlock = std::move(completion); // Send the request through to the Cocoa level transport auto completion_ptr = completion.release(); [m_transport sendRequestToServer:rlmRequest completion:^(RLMResponse *response) { @@ -67,7 +68,7 @@ void send_request_to_server(app::Request&& request, // Convert the RLMResponse to an app:Response and pass downstream to // the object store - completion(app::Response{ + blockCompletionBlock(app::Response{ .http_status_code = static_cast(response.httpStatusCode), .custom_status_code = static_cast(response.customStatusCode), .headers = bridgingHeaders, diff --git a/Realm/RLMSyncConfiguration.mm b/Realm/RLMSyncConfiguration.mm index 81592238cd..fd054cbdda 100644 --- a/Realm/RLMSyncConfiguration.mm +++ b/Realm/RLMSyncConfiguration.mm @@ -35,9 +35,67 @@ #import #import #import +#import using namespace realm; +@interface RLMCocoaSocketDelegate : NSObject +@property realm::util::websocket::EZObserver* observer; +@end + +@implementation RLMCocoaSocketDelegate + +- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *)protocol { + _observer->websocket_handshake_completion_handler([protocol cString]); +} + +- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode reason:(NSData *)reason { + _observer->websocket_close_message_received(std::make_error_code(std::errc::connection_aborted), + RLMStringDataWithNSString([[NSString alloc] initWithData: reason encoding:NSUTF8StringEncoding])); +} + +@end + +namespace realm::util::websocket { +class CocoaSocketFactory: public EZSocketFactory, public EZSocket { +public: + using EZSocketFactory::EZSocketFactory; + + RLMCocoaSocketDelegate *delegate; + NSURLSessionWebSocketTask *task; + + void async_write_binary(const char *data, size_t size, util::UniqueFunction &&handler) override + { + + [task sendMessage:[[NSURLSessionWebSocketMessage alloc] initWithString:@(data)] + completionHandler:^(NSError * _Nullable error) { + handler(); + }]; + } + + std::unique_ptr connect(EZObserver* observer, EZEndpoint&& endpoint) + { + task = [[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration new] delegate:delegate delegateQueue:nil] webSocketTaskWithURL:[[NSURL alloc] initWithString:@(endpoint.path.data())]]; + delegate = [RLMCocoaSocketDelegate new]; + delegate.observer = observer; + [task receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage * _Nullable message, NSError * _Nullable error) { + switch (message.type) { + case NSURLSessionWebSocketMessageTypeData: + observer->websocket_binary_message_received([[[NSString alloc] initWithData:message.data encoding:NSUTF8StringEncoding] UTF8String], + [message.data length]); + break; + case NSURLSessionWebSocketMessageTypeString: + observer->websocket_binary_message_received([message.string UTF8String], + [message.data length]); + break; + } + }]; + [task resume]; + return std::unique_ptr(this); + } +}; +} + namespace { using ProtocolError = realm::sync::ProtocolError; @@ -381,6 +439,10 @@ - (instancetype)initWithUser:(RLMUser *)user } } + using namespace realm::util::websocket; + _config->socket_factory = [](EZConfig&& config) mutable { + return std::unique_ptr(new CocoaSocketFactory(std::move(config))); + }; self.customFileURL = customFileURL; return self; } From cbf9017e1196657c85caf5078344d26dc32a976f Mon Sep 17 00:00:00 2001 From: Pavel Yakimenko Date: Tue, 22 Mar 2022 18:28:36 +0000 Subject: [PATCH 2/8] Merge with latest master --- CHANGELOG.md | 11 +++++++++++ Package.swift | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 141db64d51..b7bafc679e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,14 @@ allows submitting to the app store with Xcode 12. * Using the dynamic subscript API on unmanaged objects before first opening a Realm or if `objectTypes` was set when opening a Realm would throw an exception ([#7786](https://github.com/realm/realm-swift/issues/7786)). +* None. +* Add ability to use Swift Query syntax in `@ObservedResults`, which allows you + to filter results using the `where` parameter. +* Add ability to use `MutableSet` with `StateRealmObject` in SwiftUI. + +### Fixed +* Adding a Realm Object to a `ObservedResults` or a collections using `StateRealmObject` that is managed by the same Realm + would throw if the Object was frozen and not thawed before hand. @@ -71,6 +79,9 @@ allows submitting to the app store with Xcode 12. * Carthage release for Swift is built with Xcode 13.4. * CocoaPods: 1.10 or later. * Xcode: 13.1-13.4. +* Carthage release for Swift is built with Xcode 13.3. +* CocoaPods: 1.10 or later. +* Xcode: 12.4-13.3. ### Internal * Upgraded realm-core from ? to ? diff --git a/Package.swift b/Package.swift index aba0da5e13..21b5caedca 100644 --- a/Package.swift +++ b/Package.swift @@ -110,7 +110,7 @@ let package = Package( targets: ["Realm", "RealmSwift"]), ], dependencies: [ - .package(name: "RealmDatabase", path: "../core-socket-plumbing") + .package(name: "RealmDatabase", path: "../realm-core-networking") ], targets: [ .target( From 56faab3a194214bce1edbbbbda6cff5ff1282a99 Mon Sep 17 00:00:00 2001 From: Pavel Yakimenko Date: Fri, 22 Apr 2022 12:19:30 +0100 Subject: [PATCH 3/8] Wire CocoaSocketFactory as a default socket factory --- Realm/RLMSyncConfiguration.mm | 22 ++++++++++++++-------- Realm/RLMSyncConfiguration_Private.hpp | 4 ++++ Realm/RLMSyncManager.mm | 2 ++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Realm/RLMSyncConfiguration.mm b/Realm/RLMSyncConfiguration.mm index fd054cbdda..04d88f9423 100644 --- a/Realm/RLMSyncConfiguration.mm +++ b/Realm/RLMSyncConfiguration.mm @@ -46,7 +46,7 @@ @interface RLMCocoaSocketDelegate : NSObject @implementation RLMCocoaSocketDelegate - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *)protocol { - _observer->websocket_handshake_completion_handler([protocol cString]); + _observer->websocket_handshake_completion_handler([protocol cStringUsingEncoding:NSUTF8StringEncoding]); } - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode reason:(NSData *)reason { @@ -57,16 +57,18 @@ - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketT @end namespace realm::util::websocket { -class CocoaSocketFactory: public EZSocketFactory, public EZSocket { +class API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) CocoaSocketFactory: public EZSocketFactory, public EZSocket { public: - using EZSocketFactory::EZSocketFactory; + CocoaSocketFactory(EZConfig config) + : EZSocketFactory(config) + { + } RLMCocoaSocketDelegate *delegate; NSURLSessionWebSocketTask *task; void async_write_binary(const char *data, size_t size, util::UniqueFunction &&handler) override { - [task sendMessage:[[NSURLSessionWebSocketMessage alloc] initWithString:@(data)] completionHandler:^(NSError * _Nullable error) { handler(); @@ -439,10 +441,7 @@ - (instancetype)initWithUser:(RLMUser *)user } } - using namespace realm::util::websocket; - _config->socket_factory = [](EZConfig&& config) mutable { - return std::unique_ptr(new CocoaSocketFactory(std::move(config))); - }; + _config->socket_factory = defaultSocketFactory(); self.customFileURL = customFileURL; return self; } @@ -450,3 +449,10 @@ - (instancetype)initWithUser:(RLMUser *)user } @end + +std::function(util::websocket::EZConfig&&)> defaultSocketFactory() { + using namespace realm::util::websocket; + return [](EZConfig&& config) mutable { + return std::unique_ptr(new CocoaSocketFactory(std::move(config))); + }; +} diff --git a/Realm/RLMSyncConfiguration_Private.hpp b/Realm/RLMSyncConfiguration_Private.hpp index d76284c799..a777e429be 100644 --- a/Realm/RLMSyncConfiguration_Private.hpp +++ b/Realm/RLMSyncConfiguration_Private.hpp @@ -20,6 +20,7 @@ #import #import +#import namespace realm { class SyncSession; @@ -28,6 +29,9 @@ struct SyncError; using SyncSessionErrorHandler = void(std::shared_ptr, SyncError); } +using namespace realm::util::websocket; +extern std::function(realm::util::websocket::EZConfig&&)> defaultSocketFactory(); + NS_ASSUME_NONNULL_BEGIN @interface RLMSyncConfiguration () diff --git a/Realm/RLMSyncManager.mm b/Realm/RLMSyncManager.mm index 3be2ca9925..dd260b7dc3 100644 --- a/Realm/RLMSyncManager.mm +++ b/Realm/RLMSyncManager.mm @@ -23,6 +23,7 @@ #import "RLMUser_Private.hpp" #import "RLMSyncUtil_Private.hpp" #import "RLMUtil.hpp" +#import "RLMSyncConfiguration_Private.hpp" #import #import @@ -131,6 +132,7 @@ + (SyncClientConfig)configurationWithRootDirectory:(NSURL *)rootDirectory appId: config.user_agent_application_info = RLMStringDataWithNSString(appId); } + config.socket_factory = defaultSocketFactory(); return config; } From bf0784c965520a70213a018269871caa85c396d2 Mon Sep 17 00:00:00 2001 From: Pavel Yakimenko Date: Fri, 22 Apr 2022 18:16:38 +0100 Subject: [PATCH 4/8] Should override --- Realm/RLMSyncConfiguration.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Realm/RLMSyncConfiguration.mm b/Realm/RLMSyncConfiguration.mm index 04d88f9423..82cb30d4a6 100644 --- a/Realm/RLMSyncConfiguration.mm +++ b/Realm/RLMSyncConfiguration.mm @@ -75,7 +75,7 @@ void async_write_binary(const char *data, size_t size, util::UniqueFunction connect(EZObserver* observer, EZEndpoint&& endpoint) + std::unique_ptr connect(EZObserver* observer, EZEndpoint&& endpoint) override { task = [[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration new] delegate:delegate delegateQueue:nil] webSocketTaskWithURL:[[NSURL alloc] initWithString:@(endpoint.path.data())]]; delegate = [RLMCocoaSocketDelegate new]; From 920128373e1e0b794134217e508af05662b14b0c Mon Sep 17 00:00:00 2001 From: Pavel Yakimenko Date: Tue, 26 Apr 2022 17:59:17 +0100 Subject: [PATCH 5/8] Rename socket factory --- Realm/RLMSyncConfiguration.mm | 4 ++-- Realm/RLMSyncManager.mm | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Realm/RLMSyncConfiguration.mm b/Realm/RLMSyncConfiguration.mm index 82cb30d4a6..01dfad8454 100644 --- a/Realm/RLMSyncConfiguration.mm +++ b/Realm/RLMSyncConfiguration.mm @@ -450,8 +450,8 @@ - (instancetype)initWithUser:(RLMUser *)user @end -std::function(util::websocket::EZConfig&&)> defaultSocketFactory() { - using namespace realm::util::websocket; +using namespace realm::util::websocket; +std::function(EZConfig&&)> defaultSocketFactory() { return [](EZConfig&& config) mutable { return std::unique_ptr(new CocoaSocketFactory(std::move(config))); }; diff --git a/Realm/RLMSyncManager.mm b/Realm/RLMSyncManager.mm index dd260b7dc3..0fb2735170 100644 --- a/Realm/RLMSyncManager.mm +++ b/Realm/RLMSyncManager.mm @@ -132,7 +132,7 @@ + (SyncClientConfig)configurationWithRootDirectory:(NSURL *)rootDirectory appId: config.user_agent_application_info = RLMStringDataWithNSString(appId); } - config.socket_factory = defaultSocketFactory(); + config.socket_factory_builder = defaultSocketFactory(); return config; } From fe8dd92fa83ddbe6faaaf61ecadaf45f812cb14b Mon Sep 17 00:00:00 2001 From: Pavel Yakimenko Date: Thu, 5 May 2022 17:19:51 +0100 Subject: [PATCH 6/8] =?UTF-8?q?Separate=20CocoaSocketFactory=20an=D0=B2=20?= =?UTF-8?q?CocoaSocket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Realm/RLMSyncConfiguration.mm | 69 +++++++++++++++++++++++++++++++---- Realm/RLMSyncManager.mm | 2 +- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/Realm/RLMSyncConfiguration.mm b/Realm/RLMSyncConfiguration.mm index 01dfad8454..8651e16e2a 100644 --- a/Realm/RLMSyncConfiguration.mm +++ b/Realm/RLMSyncConfiguration.mm @@ -39,6 +39,7 @@ using namespace realm; +API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) @interface RLMCocoaSocketDelegate : NSObject @property realm::util::websocket::EZObserver* observer; @end @@ -57,30 +58,59 @@ - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketT @end namespace realm::util::websocket { -class API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) CocoaSocketFactory: public EZSocketFactory, public EZSocket { +class API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) CocoaSocket: public EZSocket { + public: - CocoaSocketFactory(EZConfig config) - : EZSocketFactory(config) + CocoaSocket(EZConfig config, EZObserver* observer, EZEndpoint&& endpoint): + m_config(config) + , delegate([RLMCocoaSocketDelegate new]) { + setup(observer, std::move(endpoint)); } RLMCocoaSocketDelegate *delegate; NSURLSessionWebSocketTask *task; + util::Logger& logger() const + { + return m_config.logger; + } + void async_write_binary(const char *data, size_t size, util::UniqueFunction &&handler) override { + logger().info(">>> CocoaSocket async_write_binary"); [task sendMessage:[[NSURLSessionWebSocketMessage alloc] initWithString:@(data)] completionHandler:^(NSError * _Nullable error) { handler(); }]; } - std::unique_ptr connect(EZObserver* observer, EZEndpoint&& endpoint) override +private: + + NSURL *buildUrl(EZEndpoint endpoint) { + NSMutableArray *strs = [NSMutableArray new]; +// endpoint.is_ssl ? [strs addObject: @"wss://"] : [strs addObject: @"ws://"]; + [strs addObject: @"ws://"]; + endpoint.proxy ? [strs addObject: @(endpoint.proxy->address.data())] : [strs addObject: @(endpoint.address.data())]; + endpoint.proxy ? [strs addObject: [NSString stringWithFormat:@":%u", endpoint.proxy->port]] : [strs addObject: [NSString stringWithFormat:@":%u", endpoint.port]]; + [strs addObject:@(endpoint.path.data())]; + logger().info("Connect to '%1'", [[strs componentsJoinedByString:@""] cStringUsingEncoding:NSUTF8StringEncoding]); + return [[NSURL alloc] initWithString:[strs componentsJoinedByString:@""]]; + } + + void setup(EZObserver* observer, EZEndpoint&& endpoint) { - task = [[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration new] delegate:delegate delegateQueue:nil] webSocketTaskWithURL:[[NSURL alloc] initWithString:@(endpoint.path.data())]]; - delegate = [RLMCocoaSocketDelegate new]; + logger().info(">>> CocoaSocket setup"); delegate.observer = observer; + NSURLSession *session = [NSURLSession sharedSession]; + + task = [session webSocketTaskWithURL:buildUrl(endpoint)]; [task receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage * _Nullable message, NSError * _Nullable error) { + if (error) { + logger().error("Failed to connect to endpoint '%1:%2'", endpoint.address, endpoint.proxy->port); // Throws +// observer->websocket_connect_error_handler(ec); // Throws + return; + } switch (message.type) { case NSURLSessionWebSocketMessageTypeData: observer->websocket_binary_message_received([[[NSString alloc] initWithData:message.data encoding:NSUTF8StringEncoding] UTF8String], @@ -93,8 +123,26 @@ void async_write_binary(const char *data, size_t size, util::UniqueFunction(this); } + + EZConfig m_config; +}; + +class API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) CocoaSocketFactory: public EZSocketFactory { +public: + CocoaSocketFactory(EZConfig config) + : EZSocketFactory(config) + , m_config(config) + { + } + + std::unique_ptr connect(EZObserver* observer, EZEndpoint&& endpoint) override + { + return std::unique_ptr(new CocoaSocket(std::move(m_config), observer, std::move(endpoint))); + } + +private: + EZConfig m_config; }; } @@ -451,8 +499,13 @@ - (instancetype)initWithUser:(RLMUser *)user @end using namespace realm::util::websocket; + std::function(EZConfig&&)> defaultSocketFactory() { return [](EZConfig&& config) mutable { - return std::unique_ptr(new CocoaSocketFactory(std::move(config))); + if (@available(macOS 10.15, *)) { + return std::unique_ptr(new CocoaSocketFactory(std::move(config))); + } else { + return std::unique_ptr(new EZSocketFactory(std::move(config))); + } }; } diff --git a/Realm/RLMSyncManager.mm b/Realm/RLMSyncManager.mm index 0fb2735170..dd260b7dc3 100644 --- a/Realm/RLMSyncManager.mm +++ b/Realm/RLMSyncManager.mm @@ -132,7 +132,7 @@ + (SyncClientConfig)configurationWithRootDirectory:(NSURL *)rootDirectory appId: config.user_agent_application_info = RLMStringDataWithNSString(appId); } - config.socket_factory_builder = defaultSocketFactory(); + config.socket_factory = defaultSocketFactory(); return config; } From 8490664806ec4e9c56c8deb14f49d163809900d3 Mon Sep 17 00:00:00 2001 From: Pavel Yakimenko Date: Fri, 6 May 2022 10:51:24 +0100 Subject: [PATCH 7/8] Default session configuration to set a delegate --- Realm/RLMSyncConfiguration.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Realm/RLMSyncConfiguration.mm b/Realm/RLMSyncConfiguration.mm index 8651e16e2a..a144f1941c 100644 --- a/Realm/RLMSyncConfiguration.mm +++ b/Realm/RLMSyncConfiguration.mm @@ -102,8 +102,10 @@ void setup(EZObserver* observer, EZEndpoint&& endpoint) { logger().info(">>> CocoaSocket setup"); delegate.observer = observer; - NSURLSession *session = [NSURLSession sharedSession]; - + + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:nil]; + task = [session webSocketTaskWithURL:buildUrl(endpoint)]; [task receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage * _Nullable message, NSError * _Nullable error) { if (error) { From 54e46e4f39c7ef565155a55406036fdfa1e2128e Mon Sep 17 00:00:00 2001 From: Pavel Yakimenko Date: Mon, 9 May 2022 19:35:16 +0100 Subject: [PATCH 8/8] Negotiate protocol Send and receive message --- Realm/RLMSyncConfiguration.mm | 58 +++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/Realm/RLMSyncConfiguration.mm b/Realm/RLMSyncConfiguration.mm index a144f1941c..91ade5140d 100644 --- a/Realm/RLMSyncConfiguration.mm +++ b/Realm/RLMSyncConfiguration.mm @@ -41,17 +41,21 @@ API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) @interface RLMCocoaSocketDelegate : NSObject -@property realm::util::websocket::EZObserver* observer; +@property realm::util::websocket::EZObserver *observer; +@property util::Logger *logger; @end @implementation RLMCocoaSocketDelegate - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *)protocol { + REALM_ASSERT([protocol length] > 0); + _logger->info(">>> CocoaSocket didOpenWithProtocol"); _observer->websocket_handshake_completion_handler([protocol cStringUsingEncoding:NSUTF8StringEncoding]); } - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode reason:(NSData *)reason { - _observer->websocket_close_message_received(std::make_error_code(std::errc::connection_aborted), + _logger->info(">>> CocoaSocket didCloseWithCode '%1'", closeCode); + _observer->websocket_close_message_received(std::error_code(static_cast(closeCode), std::generic_category()), RLMStringDataWithNSString([[NSString alloc] initWithData: reason encoding:NSUTF8StringEncoding])); } @@ -65,36 +69,62 @@ class API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) CocoaSock m_config(config) , delegate([RLMCocoaSocketDelegate new]) { + delegate.logger = &m_config.logger; setup(observer, std::move(endpoint)); } RLMCocoaSocketDelegate *delegate; NSURLSessionWebSocketTask *task; + NSURLSession *session; + + ~CocoaSocket() + { + destroySocket(); + } util::Logger& logger() const { return m_config.logger; } + util::UniqueFunction m_write_completion_handler; + void async_write_binary(const char *data, size_t size, util::UniqueFunction &&handler) override { logger().info(">>> CocoaSocket async_write_binary"); - [task sendMessage:[[NSURLSessionWebSocketMessage alloc] initWithString:@(data)] + m_write_completion_handler = std::move(handler); + + auto message = [[NSURLSessionWebSocketMessage alloc] initWithString:@(data)]; + [task sendMessage:message completionHandler:^(NSError * _Nullable error) { - handler(); + if (error) { + logger().error("Failed to send message: %1", error.localizedDescription.UTF8String); // Throws + delegate.observer->websocket_read_or_write_error_handler(std::error_code(static_cast(error.code), std::generic_category())); // Throws + destroySocket(error); + return; + } + m_write_completion_handler(); + m_write_completion_handler = nullptr; }]; } private: + void destroySocket(NSError *error = nil) { + if (error) { + [session invalidateAndCancel]; + } else { + [session finishTasksAndInvalidate]; + } + } + NSURL *buildUrl(EZEndpoint endpoint) { NSMutableArray *strs = [NSMutableArray new]; -// endpoint.is_ssl ? [strs addObject: @"wss://"] : [strs addObject: @"ws://"]; - [strs addObject: @"ws://"]; + endpoint.is_ssl ? [strs addObject: @"wss://"] : [strs addObject: @"ws://"]; endpoint.proxy ? [strs addObject: @(endpoint.proxy->address.data())] : [strs addObject: @(endpoint.address.data())]; endpoint.proxy ? [strs addObject: [NSString stringWithFormat:@":%u", endpoint.proxy->port]] : [strs addObject: [NSString stringWithFormat:@":%u", endpoint.port]]; [strs addObject:@(endpoint.path.data())]; - logger().info("Connect to '%1'", [[strs componentsJoinedByString:@""] cStringUsingEncoding:NSUTF8StringEncoding]); + logger().info("Connecting to '%1'", [[strs componentsJoinedByString:@""] cStringUsingEncoding:NSUTF8StringEncoding]); return [[NSURL alloc] initWithString:[strs componentsJoinedByString:@""]]; } @@ -104,13 +134,23 @@ void setup(EZObserver* observer, EZEndpoint&& endpoint) delegate.observer = observer; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + + auto sessionHeaders = [NSMutableDictionary new]; + for (const auto &x: endpoint.headers) { + sessionHeaders[@(x.first.c_str())] = @(x.second.c_str()); + } + configuration.HTTPAdditionalHeaders = sessionHeaders; + + auto protocols = [[NSString stringWithUTF8String:endpoint.protocols.c_str()] componentsSeparatedByString:@","]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:nil]; - task = [session webSocketTaskWithURL:buildUrl(endpoint)]; + task = [session webSocketTaskWithURL:buildUrl(endpoint) protocols:protocols]; [task receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage * _Nullable message, NSError * _Nullable error) { if (error) { logger().error("Failed to connect to endpoint '%1:%2'", endpoint.address, endpoint.proxy->port); // Throws -// observer->websocket_connect_error_handler(ec); // Throws + observer->websocket_read_or_write_error_handler(std::error_code(static_cast(error.code), std::generic_category())); // Throws + destroySocket(error); return; } switch (message.type) {