From 771f9224614c3815ef9a54af22ade0413a509250 Mon Sep 17 00:00:00 2001 From: Diana Perez Afanador Date: Thu, 2 May 2024 02:01:47 +0200 Subject: [PATCH] RCOCOA-2289: Update Base url API (#8560) * Add support for updating Atlas Device Sync's base url. * Solve PR comments * Solve PR comments and solve private API issues * Solve PR comments * Don't wrap callback --------- Co-authored-by: Nikola Irinchev --- CHANGELOG.md | 9 +++ Realm/ObjectServerTests/AsyncSyncTests.swift | 17 +++++- .../ObjectServerTests/RLMObjectServerTests.mm | 26 +++++++++ .../SwiftObjectServerTests.swift | 41 +++++++++++++- Realm/RLMApp.mm | 19 +++++++ Realm/RLMApp_Private.h | 9 +++ RealmSwift/App.swift | 55 +++++++++++++++++-- dependencies.list | 2 +- 8 files changed, 169 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e55d39e713..5de7ee2d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,15 @@ store. Xcode 15.1 is now the minimum supported version. ([Core #7552](https://github.com/realm/realm-core/pull/7552)). * Improve perfomance of IN queries and chained OR equality queries for UUID/ObjectId types. ([.Net #3566](https://github.com/realm/realm-dotnet/issues/3566)) +* Added support for updating Atlas Device Sync's base url, in case the need to roam between + servers (cloud and/or edge server). This API is private and can only be imported using + `@_spi(Private)` + ```swift + @_spi(RealmSwiftExperimental) import RealmSwift + + try await app.updateBaseUrl(to: "https://services.cloud.mongodb.com") + ``` + ([#8486](https://github.com/realm/realm-swift/issues/8486)). * Enable building RealmSwift as a dynamic framework when installing via SPM, which lets us supply a privacy manifest. When RealmSwift is built as a static library you must supply your own manifest, as Xcode does not build static diff --git a/Realm/ObjectServerTests/AsyncSyncTests.swift b/Realm/ObjectServerTests/AsyncSyncTests.swift index 4cecbe9912..a3ccb65f3c 100644 --- a/Realm/ObjectServerTests/AsyncSyncTests.swift +++ b/Realm/ObjectServerTests/AsyncSyncTests.swift @@ -20,7 +20,7 @@ import Realm import Realm.Private -import RealmSwift +@_spi(RealmSwiftExperimental) import RealmSwift import XCTest #if canImport(RealmTestSupport) @@ -78,6 +78,21 @@ class AsyncAwaitSyncTests: SwiftSyncTestCase { } } + func testUpdateBaseUrl() async throws { + let app = App(id: appId) + XCTAssertNotNil(app.baseURL) + XCTAssertEqual(app.baseURL, "http://localhost:9090") + + try await app.updateBaseUrl(to: "http://localhost:8080") + XCTAssertEqual(app.baseURL, "http://localhost:8080") + + try await app.updateBaseUrl(to: "http://localhost:7070/") + XCTAssertEqual(app.baseURL, "http://localhost:7070") + + try await app.updateBaseUrl(to: nil) + XCTAssertEqual(app.baseURL, "https://services.cloud.mongodb.com") + } + @MainActor func testAsyncOpenStandalone() async throws { try autoreleasepool { let configuration = Realm.Configuration(objectTypes: [SwiftPerson.self]) diff --git a/Realm/ObjectServerTests/RLMObjectServerTests.mm b/Realm/ObjectServerTests/RLMObjectServerTests.mm index c6d0f65f82..9eea48cb6f 100644 --- a/Realm/ObjectServerTests/RLMObjectServerTests.mm +++ b/Realm/ObjectServerTests/RLMObjectServerTests.mm @@ -81,6 +81,32 @@ - (NSArray *)defaultObjectTypes { #pragma mark - Authentication and Tokens +- (void)testUpdateBaseUrl { + RLMApp *app = self.app; + XCTAssertEqual(app.baseURL, @"http://localhost:9090"); + + XCTestExpectation *expectation = [self expectationWithDescription:@"should update base url"]; + [app updateBaseURL:@"http://localhost:8080" completion:^(NSError *error) { + XCTAssertNil(error); + [expectation fulfill]; + }]; + XCTAssertEqual(app.baseURL, @"http://localhost:8080"); + + XCTestExpectation *expectation1 = [self expectationWithDescription:@"should update base url"]; + [app updateBaseURL:@"http://localhost:7070/" completion:^(NSError *error) { + XCTAssertNil(error); + [expectation1 fulfill]; + }]; + XCTAssertEqual(app.baseURL, @"http://localhost:7070"); + + XCTestExpectation *expectation2 = [self expectationWithDescription:@"should update base url to default value"]; + [app updateBaseURL:nil completion:^(NSError *error) { + XCTAssertNil(error); + [expectation2 fulfill]; + }]; + XCTAssertEqual(app.baseURL, @"https://services.cloud.mongodb.com"); +} + - (void)testAnonymousAuthentication { RLMUser *syncUser = self.anonymousUser; RLMUser *currentUser = [self.app currentUser]; diff --git a/Realm/ObjectServerTests/SwiftObjectServerTests.swift b/Realm/ObjectServerTests/SwiftObjectServerTests.swift index 4fe8b94290..1d087b13c3 100644 --- a/Realm/ObjectServerTests/SwiftObjectServerTests.swift +++ b/Realm/ObjectServerTests/SwiftObjectServerTests.swift @@ -21,7 +21,7 @@ import Combine import Realm import Realm.Private -import RealmSwift +@_spi(RealmSwiftExperimental) import RealmSwift import XCTest #if canImport(RealmTestSupport) @@ -62,6 +62,45 @@ class SwiftObjectServerTests: SwiftSyncTestCase { ] } + func testUpdateBaseUrl() { + let app = App(id: appId) + XCTAssertNotNil(app.baseURL) + XCTAssertEqual(app.baseURL, "http://localhost:9090") + + let ex = expectation(description: "update base url") + app.updateBaseUrl(to: "http://localhost:8080", { result in + switch result { + case .success: + ex.fulfill() + case .failure: + XCTFail("Should not return an error") + } + }) + XCTAssertEqual(app.baseURL, "http://localhost:8080") + + let ex1 = expectation(description: "update base url") + app.updateBaseUrl(to: "http://localhost:7070/", { result in + switch result { + case .success: + ex1.fulfill() + case .failure: + XCTFail("Should not return an error") + } + }) + XCTAssertEqual(app.baseURL, "http://localhost:7070") + + let ex2 = expectation(description: "update base url") + app.updateBaseUrl(to: nil, { result in + switch result { + case .success: + ex2.fulfill() + case .failure: + XCTFail("Should not return an error") + } + }) + XCTAssertEqual(app.baseURL, "https://services.cloud.mongodb.com") + } + func testBasicSwiftSync() throws { XCTAssert(try openRealm().isEmpty, "Freshly synced Realm was not empty...") } diff --git a/Realm/RLMApp.mm b/Realm/RLMApp.mm index 23330aeffc..5778e3ced3 100644 --- a/Realm/RLMApp.mm +++ b/Realm/RLMApp.mm @@ -414,6 +414,25 @@ - (RLMEmailPasswordAuth *)emailPasswordAuth { return [[RLMEmailPasswordAuth alloc] initWithApp:_app]; } +- (NSString *)baseUrl { + return getOptionalString(_app->get_base_url()) ?: RLMStringViewToNSString(_app->default_base_url()); +} + +- (void)updateBaseURL:(NSString * _Nullable)baseURL completion:(nonnull RLMOptionalErrorBlock)completionHandler { + auto completion = ^(std::optional error) { + if (error) { + return completionHandler(makeError(*error)); + } + + completionHandler(nil); + }; + return RLMTranslateError([&] { + NSString *url = (baseURL ?: @""); + NSString *newUrl = [url stringByReplacingOccurrencesOfString:@"/" withString:@"" options:0 range:NSMakeRange(url.length-1, 1)]; + return _app->update_base_url(newUrl.UTF8String, completion); + }); +} + - (void)loginWithCredential:(RLMCredentials *)credentials completion:(RLMUserCompletionBlock)completionHandler { auto completion = ^(std::shared_ptr user, std::optional error) { diff --git a/Realm/RLMApp_Private.h b/Realm/RLMApp_Private.h index 0624f063c9..1f00557a64 100644 --- a/Realm/RLMApp_Private.h +++ b/Realm/RLMApp_Private.h @@ -31,6 +31,8 @@ typedef void(^RLMAppNotificationBlock)(RLMApp *); @end @interface RLMApp () +/// A custom base URL to request against. If not set or set to nil, the default base url for app services will be returned. +@property (nonatomic, readonly) NSString *baseURL; /// Returns all currently cached Apps + (NSArray *)allApps; /// Subscribe to notifications for this RLMApp. @@ -40,6 +42,13 @@ typedef void(^RLMAppNotificationBlock)(RLMApp *); + (RLMApp *_Nullable)cachedAppWithId:(NSString *)appId; + (void)resetAppCache; + +/// Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). +/// @param baseURL The new base url to connect to. Setting `nil` will reset the base url to the default url. +/// @note Updating the base URL would trigger a client reset. +- (void)updateBaseURL:(NSString *_Nullable)baseURL + completion:(RLMOptionalErrorBlock)completionHandler NS_REFINED_FOR_SWIFT; + @end @interface RLMAppConfiguration () diff --git a/RealmSwift/App.swift b/RealmSwift/App.swift index 83956d006c..c79afad2b3 100644 --- a/RealmSwift/App.swift +++ b/RealmSwift/App.swift @@ -220,6 +220,18 @@ let credentials = Credentials.JWT(token: myToken) public typealias App = RLMApp public extension App { + /** + Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). + - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. + - parameter completion: A callback invoked after completion. + - note: Updating the base URL would trigger a client reset. + */ + @preconcurrency + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?, _ completion: @Sendable @escaping (Error?) -> Void) { + self.__updateBaseURL(url, completion: completion) + } + /** Login to a user for the Realm app. @@ -237,21 +249,52 @@ public extension App { } } - /// Login to a user for the Realm app. - /// @param credentials The credentials identifying the user. - /// @returns A publisher that eventually return `User` or `Error`. + /** + Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). + - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. + - parameter completion: A callback invoked after completion. Will return `Result.success` or `Result.failure(Error)`. + - note: Updating the base URL would trigger a client reset. + */ + @preconcurrency + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?, _ completion: @Sendable @escaping (Result) -> Void) { + self.__updateBaseURL(url, completion: { error in + if let error = error { + completion(.failure(error)) + } else { + completion(.success(())) + } + }) + } + + /** + Login to a user for the Realm app. + - parameter credentials: The credentials identifying the user. + - returns: A publisher that eventually return `User` or `Error`. + */ @available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, *) func login(credentials: Credentials) -> Future { return future { self.login(credentials: credentials, $0) } } - /// Login to a user for the Realm app. - /// @param credentials The credentials identifying the user. - /// @returns A publisher that eventually return `User` or `Error`. + /** + Login to a user for the Realm app. + - parameter credentials: The credentials identifying the user. + */ @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func login(credentials: Credentials) async throws -> User { try await __login(withCredential: ObjectiveCSupport.convert(object: credentials)) } + + /** + Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). + - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. + - note: Updating the base URL would trigger a client reset. + */ + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?) async throws { + try await __updateBaseURL(url) + } } /// Use this delegate to be provided a callback once authentication has succeed or failed diff --git a/dependencies.list b/dependencies.list index bd8c069450..ed8bd588a5 100755 --- a/dependencies.list +++ b/dependencies.list @@ -1,3 +1,3 @@ VERSION=10.49.2 REALM_CORE_VERSION=v14.6.0-7-gb2bb4f550 -STITCH_VERSION=8bf8ebcff6e804586c30a6ccbadb060753071a42 +STITCH_VERSION=40f356fbaf61e8df4f897e58357cd6a2c211825a