diff --git a/CHANGELOG.md b/CHANGELOG.md index bfce4f54a..2e713ce5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,17 +7,12 @@ x.x.x Release notes (yyyy-MM-dd) * Made all `Configuration` fields final so they can only be initialized in the constructor. This better conveys the immutability of the configuration class. ([#455](https://github.com/realm/realm-dart/pull/455)) ### Enhancements -* Added a new `Configuration` option: `disableFormatUpgrade`. When set to `true`, opening a Realm with an older file format will throw an exception to avoid automatically migrating it. ([#310](https://github.com/realm/realm-dart/pull/310)) - ```dart - var config = Configuration([Car.schema], disableFormatUpgrade: true); - await File('old-format.realm').copy(config.path); - var realm = Realm(config); - // Opening 'Realm' will throw a RealmException with message: "The Realm file format must be allowed to be upgraded in order to proceed." - ``` +* 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/)) * 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/)) -* Added support for `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)) +* 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/)) ### 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 644e7c2f7..98917c015 100644 --- a/flutter/realm_flutter/ios/Classes/RealmPlugin.m +++ b/flutter/realm_flutter/ios/Classes/RealmPlugin.m @@ -43,6 +43,7 @@ void dummy(void) { realm_list_size(NULL, 0); realm_results_snapshot(NULL); realm_app_credentials_new_anonymous(); + realm_app_config_new(NULL, NULL); } @end diff --git a/lib/src/application.dart b/lib/src/application.dart new file mode 100644 index 000000000..999d76d51 --- /dev/null +++ b/lib/src/application.dart @@ -0,0 +1,100 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +import 'dart:io'; +import 'package:meta/meta.dart'; + +/// Specify if and how to persists user objects. +enum MetadataPersistenceMode { + /// Persist [User] objects, but do not encrypt them. + unencrypted, + + /// Persist [User] objects in an encrypted store. + encrypted, + + /// Do not persist [User] objects. + disabled, +} + +@immutable +class ApplicationConfiguration { + /// The [appId] is the unique id that identifies the Realm application. + final String appId; + + /// The [baseFilePath] is the [Directory] relative to which all local data for this application will be stored. + /// + /// This data includes metadata for users and synchronized Realms. If set, you must ensure that the [baseFilePath] + /// directory exists. + final Directory baseFilePath; + + /// The [baseUrl] is the [Uri] used to reach the MongoDB Realm server. + /// + /// [baseUrl] only needs to be set if for some reason your application isn't hosted on realm.mongodb.com. + /// This can be the case if you're testing locally or are using a pre-production environment. + final Uri baseUrl; + + /// The [defaultRequestTimeout] for HTTP requests. Defaults to 60 seconds. + final Duration defaultRequestTimeout; + + /// The [localAppName] is the friendly name identifying the current client application. + /// + /// This is typically used to differentiate between client applications that use the same + /// MongoDB Realm app. + /// + /// These can be the same conceptual app developed for different platforms, or + /// significantly different client side applications that operate on the same data - e.g. an event managing + /// service that has different clients apps for organizers and attendees. + final String? localAppName; + + /// The [localAppVersion] can be specified, if you wish to distinguish different client versions of the + /// same application. + final String? localAppVersion; + + /// Enumeration that specifies how and if logged-in User objects are persisted across application launches. + final MetadataPersistenceMode metadataPersistenceMode; + + /// The encryption key to use for user metadata on this device, if [metadataPersistenceMode] is + /// [MetadataPersistenceMode.encrypted]. + /// + /// The [metadataEncryptionKey] must be exactly 64 bytes. + /// Setting this will not change the encryption key for individual Realms, which is set in the [Configuration]. + final List? metadataEncryptionKey; + + /// The [HttpClient] that will be used for HTTP requests during authentication. + /// + /// You can use this to override the default http client handler and configure settings like proxies, + /// client certificates, and cookies. While these are not required to connect to MongoDB Realm under + /// normal circumstances, they can be useful if client devices are behind corporate firewall or use + /// a more complex networking setup. + final HttpClient httpClient; + + /// Instantiates a new [ApplicationConfiguration] with the specified appId. + ApplicationConfiguration( + this.appId, { + Uri? baseUrl, + Directory? baseFilePath, + this.defaultRequestTimeout = const Duration(seconds: 60), + this.localAppName, + this.localAppVersion, + this.metadataPersistenceMode = MetadataPersistenceMode.unencrypted, + this.metadataEncryptionKey, + HttpClient? httpClient, + }) : baseUrl = baseUrl ?? Uri.parse('https://realm.mongodb.com'), + baseFilePath = baseFilePath ?? Directory.current, + httpClient = httpClient ?? HttpClient(); +} diff --git a/lib/src/cli/metrics/metrics_command.dart b/lib/src/cli/metrics/metrics_command.dart index 1d9af251a..3cba46b0f 100644 --- a/lib/src/cli/metrics/metrics_command.dart +++ b/lib/src/cli/metrics/metrics_command.dart @@ -216,7 +216,7 @@ Future getInfo(Options options) async { // Sanity check full info, if we have it if (info != null && (version == null || version == info.frameworkVersion) && flutterVersionConstraints.allows(info.frameworkVersion)) { // The returned info match both the projects constraints and the - // flutter version of the lastest flutter command run on the project + // flutter version of the latest flutter command run on the project return info; } @@ -224,6 +224,6 @@ Future getInfo(Options options) async { // secondly the min constraint of the flutter SDK used return FlutterInfo( frameworkVersion: version ?? (await safe(() => (flutterVersionConstraints as VersionRange).min!)) ?? Version.none, - dartSdkVersion: Version.parse(Platform.version.toString().takeUntil(' ')), + dartSdkVersion: Version.parse(Platform.version.takeUntil(' ')), ); } diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 86fe1a0fd..a01af1bac 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -25,7 +25,9 @@ import 'dart:typed_data'; // Hide StringUtf8Pointer.toNativeUtf8 and StringUtf16Pointer since these allows silently allocating memory. Use toUtf8Ptr instead import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; +import 'package:pub_semver/pub_semver.dart'; +import '../application.dart'; import '../collections.dart'; import '../init.dart'; import '../list.dart'; @@ -644,6 +646,33 @@ class _RealmCore { return out_modified.asTypedList(count).toList(); }); } + + AppConfigHandle createAppConfig(ApplicationConfiguration configuration, RealmHttpTransportHandle httpTransport) { + return using((arena) { + final app_id = configuration.appId.toUtf8Ptr(arena); + final handle = AppConfigHandle._(_realmLib.realm_app_config_new(app_id, httpTransport._pointer)); + + _realmLib.realm_app_config_set_base_url(handle._pointer, configuration.baseUrl.toString().toUtf8Ptr(arena)); + _realmLib.realm_app_config_set_default_request_timeout(handle._pointer, configuration.defaultRequestTimeout!.inMilliseconds); + + if (configuration.localAppName != null) { + _realmLib.realm_app_config_set_local_app_name(handle._pointer, configuration.localAppName!.toUtf8Ptr(arena)); + } + + if (configuration.localAppVersion != null) { + _realmLib.realm_app_config_set_local_app_version(handle._pointer, configuration.localAppVersion!.toUtf8Ptr(arena)); + } + + _realmLib.realm_app_config_set_platform(handle._pointer, Platform.operatingSystem.toUtf8Ptr(arena)); + _realmLib.realm_app_config_set_platform_version(handle._pointer, Platform.operatingSystemVersion.toUtf8Ptr(arena)); + + //This sets the realm lib version instead of the SDK version. + //TODO: Read the SDK version from code generated version field + _realmLib.realm_app_config_set_sdk_version(handle._pointer, libraryVersion.toUtf8Ptr(arena)); + + return handle; + }); + } RealmAppCredentialsHandle createAppCredentialsAnonymous() { return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_anonymous()); @@ -885,6 +914,10 @@ class RealmHttpTransportHandle extends Handle { RealmHttpTransportHandle._(Pointer pointer) : super(pointer, 24); } +class AppConfigHandle extends Handle { + AppConfigHandle._(Pointer pointer) : super(pointer, 256); // TODO: What should hint be? +} + extension on List { Pointer toInt8Ptr(Allocator allocator) { final nativeSize = length + 1; diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index a9d326381..524e0f827 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -30,6 +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 'package:realm_common/realm_common.dart' show Ignored, Indexed, MapTo, PrimaryKey, RealmError, RealmModel, RealmUnsupportedSetError, RealmStateError, RealmCollectionType, RealmPropertyType; export "configuration.dart" show Configuration, RealmSchema, SchemaObject; diff --git a/src/realm_dart.cpp b/src/realm_dart.cpp index a06ac98ed..74388f739 100644 --- a/src/realm_dart.cpp +++ b/src/realm_dart.cpp @@ -69,6 +69,7 @@ void dummy(void) { realm_list_size(nullptr, 0); realm_results_add_notification_callback(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); realm_results_snapshot(nullptr); + realm_app_config_new(nullptr, nullptr); 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 new file mode 100644 index 000000000..6a12a3987 --- /dev/null +++ b/test/application_test.dart @@ -0,0 +1,54 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +import 'dart:io'; + +import 'package:test/expect.dart'; + +import '../lib/realm.dart'; +import 'test.dart'; + +Future main([List? args]) async { + print("Current PID $pid"); + + setupTests(args); + + test('ApplicationConfiguration can be created', () { + final a = ApplicationConfiguration('myapp'); + expect(a.appId, 'myapp'); + expect(a.baseFilePath.path, Directory.current.path); + expect(a.baseUrl, Uri.parse('https://realm.mongodb.com')); + expect(a.defaultRequestTimeout, const Duration(minutes: 1)); + + final httpClient = HttpClient(context: SecurityContext(withTrustedRoots: false)); + final b = ApplicationConfiguration( + 'myapp1', + baseFilePath: Directory.systemTemp, + baseUrl: Uri.parse('https://not_re.al'), + defaultRequestTimeout: const Duration(seconds: 2), + localAppName: 'bar', + localAppVersion: "1.0.0", + httpClient: httpClient, + ); + expect(b.appId, 'myapp1'); + expect(b.baseFilePath.path, Directory.systemTemp.path); + expect(b.baseUrl,Uri.parse('https://not_re.al')); + expect(b.defaultRequestTimeout, const Duration(seconds: 2)); + expect(b.httpClient, httpClient); + }); +}