diff --git a/.gitignore b/.gitignore index 07ae2e34a..f823f1c37 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ out # Ignore realm files lib/default.realm lib/default.realm.lock +mongodb-realm/ # Dart packages and Flutter plugins should not commit these files pubspec.lock @@ -35,4 +36,4 @@ flutter/realm_flutter/.gradle/ # Ignore example pubspec.lock since the example always reference the local package example/pubspec.lock -/generator/coverage/ +/generator/coverage/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b2df64d8c..3a8770b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,14 @@ x.x.x Release notes (yyyy-MM-dd) ### Enhancements * Added a property `Configuration.disableFormatUpgrade`. When set to `true`, opening a Realm with an older file format will throw an exception to avoid automatically upgrading it. ([#310](https://github.com/realm/realm-dart/pull/310)) -* Support result value from write transaction callbacks ([#294](https://github.com/realm/realm-dart/pull/294/)) +* Support result value from write transaction callbacks. ([#294](https://github.com/realm/realm-dart/pull/294/)) * Added a property `Realm.isInTransaction` that indicates whether the Realm instance has an open write transaction associated with it. -* Support anonymous application credentials ([#443](https://github.com/realm/realm-dart/pull/443/)) +* Support anonymous application credentials. ([#443](https://github.com/realm/realm-dart/pull/443/)) * Added a property `Configuration.initialDataCallback`. This is a callback executed when a Realm file is first created and allows you to populate some initial data necessary for your application. ([#298](https://github.com/realm/realm-dart/issues/298)) -* Support application configuration ([#306](https://github.com/realm/realm-dart/pull/306/)) +* Support application configuration. ([#306](https://github.com/realm/realm-dart/pull/306/)) +* Support application class. ([#446](https://github.com/realm/realm-dart/pull/446/)) * Support should realm compact on open callback `Configuration.shouldCompactCallback` as option when configuring a Realm to determine if it should be compacted before being returned. ([#466](https://github.com/realm/realm-dart/pull/466/)) -* Support ObjectId ([#468](https://github.com/realm/realm-dart/pull/468)) +* Support ObjectId. ([#468](https://github.com/realm/realm-dart/pull/468)) ### Fixed * Fixed an issue that would result in the wrong transaction being rolled back if you start a write transaction inside a write transaction. ([#442](https://github.com/realm/realm-dart/issues/442)) diff --git a/flutter/realm_flutter/ios/Classes/RealmPlugin.m b/flutter/realm_flutter/ios/Classes/RealmPlugin.m index efc3e4905..fe7098465 100644 --- a/flutter/realm_flutter/ios/Classes/RealmPlugin.m +++ b/flutter/realm_flutter/ios/Classes/RealmPlugin.m @@ -42,9 +42,11 @@ void dummy(void) { realm_results_get_object(NULL, 0); realm_list_size(NULL, 0); realm_results_snapshot(NULL); + realm_http_transport_new(NULL, NULL, NULL); realm_config_set_should_compact_on_launch_function(NULL, NULL, NULL); realm_app_credentials_new_anonymous(); realm_app_config_new(NULL, NULL); + realm_sync_client_config_new(); } @end diff --git a/flutter/realm_flutter/tests/test_driver/realm_test.dart b/flutter/realm_flutter/tests/test_driver/realm_test.dart index a4e057ee9..80ac1f1e4 100644 --- a/flutter/realm_flutter/tests/test_driver/realm_test.dart +++ b/flutter/realm_flutter/tests/test_driver/realm_test.dart @@ -11,7 +11,8 @@ import '../test/realm_test.dart' as realm_tests; import '../test/realm_object_test.dart' as realm_object_tests; import '../test/list_test.dart' as list_tests; import '../test/results_test.dart' as results_tests; -import '../test/credentials_test.dart' as credentials; +import '../test/credentials_test.dart' as credentials_tests; +import '../test/application_test.dart' as application_tests; Future main(List args) async { final Completer completer = Completer(); @@ -22,7 +23,8 @@ Future main(List args) async { await realm_object_tests.main(args); await list_tests.main(args); await results_tests.main(args); - await credentials.main(args); + await credentials_tests.main(args); + await application_tests.main(args); tearDown(() { if (Invoker.current?.liveTest.state.result == test_api.Result.error || Invoker.current?.liveTest.state.result == test_api.Result.failure) { diff --git a/lib/src/application.dart b/lib/src/application.dart index 999d76d51..29ca0d5f4 100644 --- a/lib/src/application.dart +++ b/lib/src/application.dart @@ -18,11 +18,13 @@ import 'dart:io'; import 'package:meta/meta.dart'; +import 'native/realm_core.dart'; +import 'configuration.dart'; /// Specify if and how to persists user objects. enum MetadataPersistenceMode { /// Persist [User] objects, but do not encrypt them. - unencrypted, + plaintext, /// Persist [User] objects in an encrypted store. encrypted, @@ -32,6 +34,8 @@ enum MetadataPersistenceMode { } @immutable + +/// A class exposing configuration options for an [Application] class ApplicationConfiguration { /// The [appId] is the unique id that identifies the Realm application. final String appId; @@ -91,10 +95,26 @@ class ApplicationConfiguration { this.defaultRequestTimeout = const Duration(seconds: 60), this.localAppName, this.localAppVersion, - this.metadataPersistenceMode = MetadataPersistenceMode.unencrypted, this.metadataEncryptionKey, + this.metadataPersistenceMode = MetadataPersistenceMode.plaintext, HttpClient? httpClient, }) : baseUrl = baseUrl ?? Uri.parse('https://realm.mongodb.com'), - baseFilePath = baseFilePath ?? Directory.current, + baseFilePath = baseFilePath ?? Directory(Configuration.filesPath), httpClient = httpClient ?? HttpClient(); } + +/// An [Application] is the main client-side entry point for interacting with a MongoDB Realm App. +/// +/// The [Application]] can be used to +/// * Register uses and perform various user-related operations through authentication providers +/// * Synchronize data between the local device and a remote Realm App with Synchronized Realms +class Application { + final AppHandle _handle; + final ApplicationConfiguration configuration; + + Application(this.configuration) : _handle = realmCore.getApp(configuration); +} + +extension ApplicationInternal on Application { + AppHandle get handle => _handle; +} diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index d7cc68502..fa83705e1 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -67,7 +67,7 @@ class Configuration { if (Platform.isAndroid || Platform.isIOS) { return realmCore.getFilesPath(); } - return ""; + return Directory.current.absolute.path; } /// The schema version used to open the [Realm] diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index ef23182b9..5a3671ce9 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -18,6 +18,7 @@ // ignore_for_file: constant_identifier_names, non_constant_identifier_names +import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; @@ -27,6 +28,7 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; import '../application.dart'; +import '../credentials.dart'; import '../collections.dart'; import '../init.dart'; import '../list.dart'; @@ -825,6 +827,29 @@ class _RealmCore { } }); } + + SyncClientConfigHandle createSyncClientConfig(ApplicationConfiguration configuration) { + return using((arena) { + final handle = SyncClientConfigHandle._(_realmLib.realm_sync_client_config_new()); + + _realmLib.realm_sync_client_config_set_base_file_path(handle._pointer, configuration.baseFilePath.path.toUtf8Ptr(arena)); + _realmLib.realm_sync_client_config_set_metadata_mode(handle._pointer, configuration.metadataPersistenceMode.index); + + if (configuration.metadataEncryptionKey != null && configuration.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { + _realmLib.realm_sync_client_config_set_metadata_encryption_key(handle._pointer, configuration.metadataEncryptionKey!.toUint8Ptr(arena)); + } + + return handle; + }); + } + + AppHandle getApp(ApplicationConfiguration configuration) { + final httpTransport = createHttpTransport(configuration.httpClient); + final appConfig = createAppConfig(configuration, httpTransport); + final syncClientConfig = createSyncClientConfig(configuration); + final realm_app = _realmLib.invokeGetPointer(() => _realmLib.realm_app_get(appConfig._pointer, syncClientConfig._pointer)); + return AppHandle._(realm_app); + } } class LastError { @@ -930,11 +955,23 @@ class RealmHttpTransportHandle extends Handle { } class AppConfigHandle extends Handle { - AppConfigHandle._(Pointer pointer) : super(pointer, 256); // TODO: What should hint be? + AppConfigHandle._(Pointer pointer) : super(pointer, 8); +} + +class SyncClientConfigHandle extends Handle { + SyncClientConfigHandle._(Pointer pointer) : super(pointer, 8); +} + +class AppHandle extends Handle { + AppHandle._(Pointer pointer) : super(pointer, 16); } extension on List { Pointer toInt8Ptr(Allocator allocator) { + return toUint8Ptr(allocator).cast(); + } + + Pointer toUint8Ptr(Allocator allocator) { final nativeSize = length + 1; final result = allocator(nativeSize); final Uint8List native = result.asTypedList(nativeSize); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 16c3dba7a..6ca6f1095 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -30,7 +30,7 @@ import 'realm_object.dart'; import 'results.dart'; // always expose with `show` to explicitly control the public API surface -export 'application.dart' show ApplicationConfiguration, MetadataPersistenceMode; +export 'application.dart' show ApplicationConfiguration, MetadataPersistenceMode, Application; export 'package:realm_common/realm_common.dart' show Ignored, Indexed, MapTo, PrimaryKey, RealmError, RealmModel, RealmUnsupportedSetError, RealmStateError, RealmCollectionType, RealmPropertyType, ObjectId; export "configuration.dart" show Configuration, RealmSchema, SchemaObject; diff --git a/src/realm_dart.cpp b/src/realm_dart.cpp index 080c86623..75a1b06a4 100644 --- a/src/realm_dart.cpp +++ b/src/realm_dart.cpp @@ -71,6 +71,7 @@ void dummy(void) { realm_results_snapshot(nullptr); realm_config_set_should_compact_on_launch_function(nullptr, nullptr, nullptr); realm_app_config_new(nullptr, nullptr); + realm_sync_client_config_new(); realm_app_credentials_new_anonymous(); realm_http_transport_new(nullptr, nullptr, nullptr); #if (ANDROID) diff --git a/test/application_test.dart b/test/application_test.dart index a6ee37d32..76953c4be 100644 --- a/test/application_test.dart +++ b/test/application_test.dart @@ -31,7 +31,7 @@ Future main([List? args]) async { test('ApplicationConfiguration can be created', () { final a = ApplicationConfiguration('myapp'); expect(a.appId, 'myapp'); - expect(a.baseFilePath.path, Directory.current.path); + expect(a.baseFilePath.path, Configuration.filesPath); expect(a.baseUrl, Uri.parse('https://realm.mongodb.com')); expect(a.defaultRequestTimeout, const Duration(minutes: 1)); @@ -51,4 +51,11 @@ Future main([List? args]) async { expect(b.defaultRequestTimeout, const Duration(seconds: 2)); expect(b.httpClient, httpClient); }); + + test('Application can be created', () async { + + final configuration = ApplicationConfiguration(generateRandomString(10)); + final application = Application(configuration); + expect(application.configuration, configuration); + }); } diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 99b261a22..3e40aed8a 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -46,10 +46,10 @@ Future main([List? args]) async { test('Configuration files path', () { if (Platform.isAndroid || Platform.isIOS) { - expect(Configuration.filesPath, isNot(endsWith(".realm")), reason: "on Android and iOS the files path should be a directory"); - expect(Configuration.filesPath, startsWith("/"), reason: "on Android and iOS the files path should be a directory"); + expect(Configuration.filesPath, isNot(endsWith(".realm")), reason: "on Android and iOS the filesPath should be a directory"); + expect(Configuration.filesPath, startsWith("/"), reason: "on Android and iOS the filesPath should be a directory"); } else { - expect(Configuration.filesPath, equals(""), reason: "on Dart standalone the files path should be an empty string"); + expect(Configuration.filesPath, equals(Directory.current.absolute.path), reason: "on Dart standalone the filesPath should be the current dir path"); } }); diff --git a/test/test.dart b/test/test.dart index 563ea3665..e2ec7ee7a 100644 --- a/test/test.dart +++ b/test/test.dart @@ -140,7 +140,7 @@ String generateRandomRealmPath() { if (Platform.isAndroid || Platform.isIOS) { path = _path.join(Configuration.filesPath, path); } else { - path = _path.join(Directory.systemTemp.createTempSync("rt_").path, path); + path = _path.join(Directory.systemTemp.createTempSync("realm_test_").path, path); } return path; @@ -194,4 +194,4 @@ Future setupBaas() async { final client = await (cluster == null ? BaasClient.docker(baasUrl) : BaasClient.atlas(baasUrl, cluster, apiKey!, privateApiKey!, projectId!)); baasApps.addAll(await client.getOrCreateApps()); -} +} \ No newline at end of file