Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

RCOCOA-2289: Update Base url API #8560

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,15 @@ x.y.z Release notes (yyyy-MM-dd)
([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)`
dianaafanador3 marked this conversation as resolved.
Show resolved Hide resolved
```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
Expand Down
17 changes: 16 additions & 1 deletion Realm/ObjectServerTests/AsyncSyncTests.swift
Expand Up @@ -20,7 +20,7 @@

import Realm
import Realm.Private
import RealmSwift
@_spi(RealmSwiftExperimental) import RealmSwift

Check notice on line 23 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.3 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L23

Add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'RealmSwift'

Check notice on line 23 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.1 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L23

Add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'RealmSwift'
import XCTest

#if canImport(RealmTestSupport)
Expand Down Expand Up @@ -78,6 +78,21 @@
}
}

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])
Expand Down Expand Up @@ -263,7 +278,7 @@

let configuration = try configuration()
func isolatedOpen(_ actor: isolated CustomExecutorActor) async throws {
_ = try await Realm(configuration: configuration, actor: actor, downloadBeforeOpen: .always)

Check notice on line 281 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.3 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L281

Non-sendable type 'Realm' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary

Check notice on line 281 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.1 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L281

Non-sendable type 'Realm' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
}

// Try opening the Realm with the Task being cancelled at every possible
Expand Down Expand Up @@ -406,7 +421,7 @@
["favourite_colour": "green", "apples": 10]
])

try await user.refreshCustomData()

Check notice on line 424 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.3 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L424

Non-sendable type '[AnyHashable : Any]' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary

Check notice on line 424 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.1 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L424

Non-sendable type '[AnyHashable : Any]' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
XCTAssertEqual(user.customData["favourite_colour"], .string("green"))
XCTAssertEqual(user.customData["apples"], .int64(10))
}
Expand Down Expand Up @@ -794,7 +809,7 @@
_ = try await results0.subscribe()
ex.fulfill()
}
await fulfillment(of: [ex], timeout: 1.0)

Check notice on line 812 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.3 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L812

Passing argument of non-sendable type 'XCTestCase' outside of main actor-isolated context may introduce data races
try await task.value
XCTAssertEqual(realm.subscriptions.count, 1)
}
Expand All @@ -817,7 +832,7 @@
XCTAssertEqual(realm.subscriptions.count, 1)
ex.fulfill()
}
await fulfillment(of: [ex], timeout: 5.0)

Check notice on line 835 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.3 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L835

Passing argument of non-sendable type 'XCTestCase' outside of main actor-isolated context may introduce data races
XCTAssertEqual(realm.subscriptions.count, 1)
}

Expand Down Expand Up @@ -859,7 +874,7 @@
let user = try await createUser()
var config = user.flexibleSyncConfiguration()
config.objectTypes = [SwiftPerson.self]
let realm = try await Realm(configuration: config, actor: MainActor.shared)

Check notice on line 877 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.3 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L877

Non-sendable type 'Realm' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary

Check notice on line 877 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.1 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L877

Non-sendable type 'Realm' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
let results1 = try await realm.objects(SwiftPerson.self)
.where { $0.firstName == name && $0.age > 8 }.subscribe(waitForSync: .onCreation)
XCTAssertEqual(results1.count, 2)
Expand All @@ -879,7 +894,7 @@
let user = try await createUser()
var config = user.flexibleSyncConfiguration()
config.objectTypes = [SwiftPerson.self]
let realm = try await Realm(configuration: config, actor: CustomGlobalActor.shared)

Check notice on line 897 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.3 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L897

Non-sendable type 'Realm' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary

Check notice on line 897 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.1 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L897

Non-sendable type 'Realm' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
let results1 = try await realm.objects(SwiftPerson.self)
.where { $0.firstName == name && $0.age > 8 }.subscribe(waitForSync: .onCreation)
XCTAssertEqual(results1.count, 2)
Expand Down Expand Up @@ -1047,7 +1062,7 @@
XCTAssertEqual(realm.subscriptions.count, 1)
expectation.fulfill()
}
await fulfillment(of: [expectation], timeout: 2.0)

Check notice on line 1065 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.3 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L1065

Passing argument of non-sendable type 'XCTestCase' outside of main actor-isolated context may introduce data races
try await task.value
}

Expand Down Expand Up @@ -1094,7 +1109,7 @@
XCTAssertEqual(realm.subscriptions.count, 1)
expectation.fulfill()
}
await fulfillment(of: [expectation], timeout: 1)

Check notice on line 1112 in Realm/ObjectServerTests/AsyncSyncTests.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.3 | Test - macOS

Realm/ObjectServerTests/AsyncSyncTests.swift#L1112

Passing argument of non-sendable type 'XCTestCase' outside of main actor-isolated context may introduce data races
}

@MainActor
Expand Down
26 changes: 26 additions & 0 deletions Realm/ObjectServerTests/RLMObjectServerTests.mm
Expand Up @@ -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];
Expand Down
41 changes: 40 additions & 1 deletion Realm/ObjectServerTests/SwiftObjectServerTests.swift
Expand Up @@ -21,7 +21,7 @@
import Combine
import Realm
import Realm.Private
import RealmSwift
@_spi(RealmSwiftExperimental) import RealmSwift
import XCTest

#if canImport(RealmTestSupport)
Expand Down Expand Up @@ -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...")
}
Expand Down
19 changes: 19 additions & 0 deletions Realm/RLMApp.mm
Expand Up @@ -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<app::AppError> 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<app::User> user, std::optional<app::AppError> error) {
Expand Down
9 changes: 9 additions & 0 deletions Realm/RLMApp_Private.h
Expand Up @@ -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;
dianaafanador3 marked this conversation as resolved.
Show resolved Hide resolved
/// Returns all currently cached Apps
+ (NSArray<RLMApp *> *)allApps;
/// Subscribe to notifications for this RLMApp.
Expand All @@ -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.
dianaafanador3 marked this conversation as resolved.
Show resolved Hide resolved
/// @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 ()
Expand Down
55 changes: 49 additions & 6 deletions RealmSwift/App.swift
Expand Up @@ -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.

Expand All @@ -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, Error>) -> Void) {
self.__updateBaseURL(url, completion: { error in
if let error = error {
completion(.failure(error))
} else {
completion(.success(()))
tgoyne marked this conversation as resolved.
Show resolved Hide resolved
}
})
}

/**
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<User, Error> {
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
Expand Down
2 changes: 1 addition & 1 deletion dependencies.list
@@ -1,3 +1,3 @@
VERSION=10.49.2
REALM_CORE_VERSION=v14.6.0-7-gb2bb4f550
STITCH_VERSION=8bf8ebcff6e804586c30a6ccbadb060753071a42
STITCH_VERSION=40f356fbaf61e8df4f897e58357cd6a2c211825a