diff --git a/flutter/realm_flutter/tests/test_driver/realm_test.dart b/flutter/realm_flutter/tests/test_driver/realm_test.dart index a4e057ee9..9e4d391c4 100644 --- a/flutter/realm_flutter/tests/test_driver/realm_test.dart +++ b/flutter/realm_flutter/tests/test_driver/realm_test.dart @@ -6,6 +6,8 @@ import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports import 'package:test_api/src/backend/state.dart' as test_api; +import '../test/application_test.dart' as application_test; +import '../test/application_configuration_test.dart' as application_configuration; import '../test/configuration_test.dart' as configuration_test; import '../test/realm_test.dart' as realm_tests; import '../test/realm_object_test.dart' as realm_object_tests; @@ -17,6 +19,8 @@ Future main(List args) async { final Completer completer = Completer(); final List failedTests = []; + await application_test.main(args); + await application_configuration.main(args); await configuration_test.main(args); await realm_tests.main(args); await realm_object_tests.main(args); diff --git a/lib/src/application.dart b/lib/src/application.dart new file mode 100644 index 000000000..4cbe24858 --- /dev/null +++ b/lib/src/application.dart @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 'application_configuration.dart'; + +import 'native/realm_core.dart'; +import 'credentials.dart'; +import 'user.dart'; + +class Application { + final AppHandle _handle; + final ApplicationConfiguration configuration; + + Application(this.configuration) : _handle = realmCore.getApp(configuration); + + Future logIn(Credentials credentials) async { + return UserInternal.create(await realmCore.logIn(this, credentials)); + } +} + +extension ApplicationInternal on Application { + AppHandle get handle => _handle; +} diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index e16a007b0..eb65bbaa7 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -18,9 +18,9 @@ // ignore_for_file: constant_identifier_names, non_constant_identifier_names +import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; -import 'dart:ffi' as ffi show Handle; import 'dart:io'; import 'dart:typed_data'; @@ -28,9 +28,11 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; import 'package:pub_semver/pub_semver.dart'; +import '../application.dart'; import '../application_configuration.dart'; import '../collections.dart'; import '../configuration.dart'; +import '../credentials.dart'; import '../init.dart'; import '../list.dart'; import '../realm_class.dart'; @@ -67,11 +69,16 @@ enum _CustomErrorCode { extension on _CustomErrorCode { int get code { switch (this) { - case _CustomErrorCode.noError: return 0; - case _CustomErrorCode.httpClientDisposed: return 997; - case _CustomErrorCode.unknownHttp: return 998; - case _CustomErrorCode.unknown: return 999; - case _CustomErrorCode.timeout: return 1000; + case _CustomErrorCode.noError: + return 0; + case _CustomErrorCode.httpClientDisposed: + return 997; + case _CustomErrorCode.unknownHttp: + return 998; + case _CustomErrorCode.unknown: + return 999; + case _CustomErrorCode.timeout: + return 1000; } } } @@ -309,7 +316,7 @@ class _RealmCore { // Report back to core responseRef.status_code = response.statusCode; - responseRef.body = responseBody.toInt8Ptr(arena); + responseRef.body = responseBody.toUint8Ptr(arena).cast(); responseRef.body_size = responseBody.length; int headerCnt = 0; @@ -832,12 +839,62 @@ class _RealmCore { } _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)); - final version = Version.parse(Platform.version); + final version = Version.parse(Platform.version.split(' ')[0]); _realmLib.realm_app_config_set_sdk_version(handle._pointer, version.toString().toUtf8Ptr(arena)); return handle; }); } + + SyncClientConfigHandle createSyncClientConfig(ApplicationConfiguration configuration) { + return using((arena) { + final c = configuration; + final handle = SyncClientConfigHandle._(_realmLib.realm_sync_client_config_new()); + if (c.baseFilePath != null) { + _realmLib.realm_sync_client_config_set_base_file_path(handle._pointer, c.baseFilePath!.path.toUtf8Ptr(arena)); + } + _realmLib.realm_sync_client_config_set_metadata_mode(handle._pointer, c.metadataPersistenceMode.index); + if (c.metadataEncryptionKey != null && c.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { + _realmLib.realm_sync_client_config_set_metadata_encryption_key(handle._pointer, c.metadataEncryptionKey!.toUint8Ptr(arena)); + } + return handle; + }); + } + + AppHandle getApp(ApplicationConfiguration configuration) { + final httpTransport = createHttpTransport(configuration.httpClient); + final appConfig = createAppConfig(configuration, httpTransport); + final syncClientConfig = createSyncClientConfig(configuration); + return AppHandle._( + _realmLib.realm_app_get(appConfig._pointer, syncClientConfig._pointer), + syncClientConfig, + httpTransport, + ); + } + + static void _logInCallback(Pointer userdata, Pointer user, Pointer error) { + final userHandleCompleter = _realmLib.gc_handle_deref(userdata); + if (userHandleCompleter is Completer) { + if (error == nullptr) { + userHandleCompleter.complete(UserHandle._(user)); + } else { + final message = error.ref.message.cast().toDartString(); + userHandleCompleter.completeError(RealmException(message)); + } + } + } + + Future logIn(Application application, Credentials credentials) async { + final completer = Completer(); + _realmLib.realm_app_log_in_with_credentials( + application.handle._pointer, + credentials.handle._pointer, + Pointer.fromFunction(_logInCallback), + _realmLib.gc_handle_new(completer), + nullptr, + ); + return completer.future; + } } class LastError { @@ -942,21 +999,39 @@ class AppConfigHandle extends Handle { AppConfigHandle._(Pointer pointer) : super(pointer, 256); // TODO: What should hint be? } +class SyncClientConfigHandle extends Handle { + SyncClientConfigHandle._(Pointer pointer) : super(pointer, 256); // TODO: What should hint be? +} + +class AppHandle extends Handle { + final SyncClientConfigHandle syncClientConfigHandle; + final RealmHttpTransportHandle httpTransportHandle; + AppHandle._( + Pointer pointer, + this.syncClientConfigHandle, + this.httpTransportHandle, + ) : super(pointer, 256); // TODO: What should hint be? +} + +class UserHandle extends Handle { + UserHandle._(Pointer pointer) : super(pointer, 256); // TODO: What should hint be? +} + extension on List { - Pointer toInt8Ptr(Allocator allocator) { + Pointer toUint8Ptr(Allocator allocator) { final nativeSize = length + 1; final result = allocator(nativeSize); final Uint8List native = result.asTypedList(nativeSize); native.setAll(0, this); // copy native.last = 0; // zero terminate - return result.cast(); + return result; } } extension _StringEx on String { Pointer toUtf8Ptr(Allocator allocator) { final units = utf8.encode(this); - return units.toInt8Ptr(allocator); + return units.toUint8Ptr(allocator).cast(); } } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index f7a722ea7..5f5173ee1 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -30,15 +30,17 @@ import 'realm_object.dart'; import 'results.dart'; // always expose with `show` to explicitly control the public API surface -export "application_configuration.dart" show ApplicationConfiguration; export 'package:realm_common/realm_common.dart' show Ignored, Indexed, MapTo, PrimaryKey, RealmError, RealmModel, RealmUnsupportedSetError, RealmStateError, RealmCollectionType, RealmPropertyType; + +export 'application.dart' show Application; +export 'application_configuration.dart' show ApplicationConfiguration; export "configuration.dart" show Configuration, RealmSchema, SchemaObject; +export 'credentials.dart' show Credentials, AuthProvider; export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges; export 'realm_object.dart' show RealmEntity, RealmException, RealmObject, RealmObjectChanges; export 'realm_property.dart'; export 'results.dart' show RealmResults, RealmResultsChanges; -export 'credentials.dart' show Credentials, AuthProvider; /// A [Realm] instance represents a `Realm` database. /// diff --git a/lib/src/user.dart b/lib/src/user.dart new file mode 100644 index 000000000..a5ec0647b --- /dev/null +++ b/lib/src/user.dart @@ -0,0 +1,29 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 'native/realm_core.dart'; + +class User { + final UserHandle _handle; + + User._(this._handle); +} + +extension UserInternal on User { + static User create(UserHandle handle) => User._(handle); +} \ No newline at end of file diff --git a/src/realm_dart.cpp b/src/realm_dart.cpp index 38450e861..9e4399ae8 100644 --- a/src/realm_dart.cpp +++ b/src/realm_dart.cpp @@ -74,6 +74,7 @@ void dummy(void) { gc_handle_deref(nullptr); realm_http_transport_new(nullptr, nullptr, nullptr); realm_app_config_new(nullptr, nullptr); + realm_sync_client_config_new(); #if (ANDROID) realm_android_dummy(); #endif diff --git a/test/application_test.dart b/test/application_test.dart new file mode 100644 index 000000000..b931e02a8 --- /dev/null +++ b/test/application_test.dart @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 '../lib/realm.dart'; +import 'test.dart'; + +Future main([List? args]) async { + print("Current PID $pid"); + + setupTests(args); + + test('Application can be created', () async { + final configuration = ApplicationConfiguration(Platform.environment['BAAS_PROJECT_ID'] ?? generateRandomString(10)); + final application = Application(configuration); + final credentials = Credentials.anonymous(); + final user = await application.logIn(credentials); + }); +}