diff --git a/ffigen/config.yaml b/ffigen/config.yaml index bd87bab7f2..2fcf317f67 100644 --- a/ffigen/config.yaml +++ b/ffigen/config.yaml @@ -5,6 +5,7 @@ headers: entry-points: - 'realm.h' - 'realm_dart.h' + - 'realm_dart_app.h' - 'realm_dart_scheduler.h' - 'realm_dart_collections.h' - 'realm_dart_http_transport.h' @@ -12,6 +13,7 @@ headers: include-directives: #generate only for these headers - 'realm.h' - 'realm_dart.h' + - 'realm_dart_app.h' - 'realm_dart_scheduler.h' - 'realm_dart_collections.h' - 'realm_dart_http_transport.h' diff --git a/ffigen/realm_dart_app.h b/ffigen/realm_dart_app.h new file mode 120000 index 0000000000..6f5c0c6849 --- /dev/null +++ b/ffigen/realm_dart_app.h @@ -0,0 +1 @@ +../src/realm_dart_app.h \ No newline at end of file diff --git a/lib/src/application.dart b/lib/src/application.dart new file mode 100644 index 0000000000..d5384b7309 --- /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 RealmAppHandle _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 { + RealmAppHandle get handle => _handle; +} diff --git a/lib/src/application_configuration.dart b/lib/src/application_configuration.dart index a064eff741..e7e3725ac7 100644 --- a/lib/src/application_configuration.dart +++ b/lib/src/application_configuration.dart @@ -33,4 +33,4 @@ class ApplicationConfiguration { this.localAppName, this.localAppVersion, }); -} +} \ No newline at end of file diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index e1cf9c64ba..8ea22423ac 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -3,7 +3,6 @@ // Generated by `package:ffigen`. import 'dart:ffi' as ffi; -/// A MacOs config for ffigen Usage: dart run ffigen --config macos.yaml class RealmLibrary { /// Holds the symbol lookup function. final ffi.Pointer Function(String symbolName) @@ -7450,6 +7449,41 @@ class RealmLibrary { late final _realm_delete_finalizable = _realm_delete_finalizablePtr .asFunction(); + /// @brief + /// + /// @param completion + /// @param userdata + /// @return true if operation started successfully, false if an error occurred. + bool realm_dart_app_log_in_with_credentials( + ffi.Pointer arg0, + ffi.Pointer arg1, + realm_dart_app_user_completion_func_t completion, + Object userdata, + ) { + return _realm_dart_app_log_in_with_credentials( + arg0, + arg1, + completion, + userdata, + ) != + 0; + } + + late final _realm_dart_app_log_in_with_credentialsPtr = _lookup< + ffi.NativeFunction< + ffi.Uint8 Function( + ffi.Pointer, + ffi.Pointer, + realm_dart_app_user_completion_func_t, + ffi.Handle)>>('realm_dart_app_log_in_with_credentials'); + late final _realm_dart_app_log_in_with_credentials = + _realm_dart_app_log_in_with_credentialsPtr.asFunction< + int Function( + ffi.Pointer, + ffi.Pointer, + realm_dart_app_user_completion_func_t, + Object)>(); + ffi.Pointer realm_dart_create_scheduler( int isolateId, int port, @@ -8574,6 +8608,20 @@ typedef Dart_FinalizableHandle = ffi.Pointer<_Dart_FinalizableHandle>; class _Dart_FinalizableHandle extends ffi.Opaque {} +/// Completion callback for asynchronous Realm App operations that yield a user object. +/// +/// @param userdata The userdata the asynchronous operation was started with. +/// @param user User object produced by the operation, or null if it failed. +/// The pointer is alive only for the duration of the callback, +/// if you wish to use it further make a copy with realm_clone(). +/// @param error Pointer to an error object if the operation failed, otherwise null if it completed successfully. +/// +/// This is a dart specific version of the completion callback for asynchronous Realm operations. +typedef realm_dart_app_user_completion_func_t = ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Handle, ffi.Pointer, + ffi.Pointer)>>; + /// A port is used to send or receive inter-isolate messages typedef Dart_Port = ffi.Int64; typedef realm_dart_on_collection_change_func_t = ffi.Pointer< diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 254f4a73e0..4204155a24 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:ffi' as ffi show Handle; @@ -28,9 +29,10 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; import 'package:pub_semver/pub_semver.dart'; -import '../application_configuration.dart'; +import '../application.dart'; import '../collections.dart'; import '../configuration.dart'; +import '../credentials.dart'; import '../init.dart'; import '../list.dart'; import '../realm_class.dart'; @@ -42,6 +44,37 @@ late RealmLibrary _realmLib; final _RealmCore realmCore = _RealmCore(); +enum _CustomErrorCode { + // Awaiting enhanced enums feature in dart 2.17 + /* + final int code; + const _CustomErrorCode(this.code); + */ + noError, //(0), + httpClientClosed, //(997), + unknownHttp, //(998), + unknown, //(999), + timeout, //(1000), +} + +extension on _CustomErrorCode { + // TODO: Drop once enhanced enums feature lands in dart 2.17 + int get code { + switch (this) { + case _CustomErrorCode.noError: + return 0; + case _CustomErrorCode.httpClientClosed: + return 997; + case _CustomErrorCode.unknownHttp: + return 998; + case _CustomErrorCode.unknown: + return 999; + case _CustomErrorCode.timeout: + return 1000; + } + } +} + enum _HttpMethod { get, post, @@ -238,9 +271,6 @@ class _RealmCore { await using((arena) async { final response_pointer = arena(); final responseRef = response_pointer.ref; - // pre-fill in case something fails - responseRef.custom_status_code = 100; // TODO! - try { // Build request late HttpClientRequest request; @@ -294,7 +324,12 @@ class _RealmCore { } }); - responseRef.custom_status_code = 0; // all is well, reset custom_status_code + responseRef.custom_status_code = _CustomErrorCode.noError.code; // all is well + } on SocketException catch (_) { + // TODO: Timeouts are socket exceptions, but not not all socket exceptions are timeout.. + responseRef.custom_status_code = _CustomErrorCode.timeout.code; + } catch (_) { + responseRef.custom_status_code = _CustomErrorCode.unknown.code; } finally { _realmLib.realm_http_transport_complete_request(request_context, response_pointer); } @@ -784,6 +819,34 @@ class _RealmCore { return handle; }); } + + RealmAppHandle getApp(ApplicationConfiguration configuration) { + final httpTransport = realmCore.createHttpTransport(HttpClient()); + final appConfig = realmCore.createAppConfig(configuration, httpTransport); + return RealmAppHandle._(_realmLib.realm_app_get(appConfig._pointer, nullptr)); // TODO: realm_sync_client_config + } + + static void _loginCallback(Object userdata, Pointer user, Pointer error) { + if (userdata is Completer) { + if (error != nullptr) { + final message = error.ref.message.cast().toDartString(); + userdata.completeError(RealmException(message)); + } else { + userdata.complete(RealmUserHandle._(_realmLib.realm_clone(user.cast()).cast())); + } + } + } + + Future logIn(Application application, Credentials credentials) { + final completer = Completer(); + _realmLib.invokeGetBool(() => _realmLib.realm_dart_app_log_in_with_credentials( + application.handle._pointer, + credentials.handle._pointer, + Pointer.fromFunction(_loginCallback), + completer, + )); + return completer.future; + } } class LastError { @@ -888,6 +951,14 @@ class AppConfigHandle extends Handle { AppConfigHandle._(Pointer pointer) : super(pointer, 256); // TODO: What should hint be? } +class RealmAppHandle extends Handle { + RealmAppHandle._(Pointer pointer) : super(pointer, 256); // TODO: What should hint be? +} + +class RealmUserHandle extends Handle { + RealmUserHandle._(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 7052cb8bf2..e26373e779 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -32,6 +32,9 @@ import 'results.dart'; // always expose with `show` to explicitly control the public API surface 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 'list.dart' show RealmList, RealmListOfObject, RealmListChanges; export 'realm_object.dart' show RealmEntity, RealmException, RealmObject, RealmObjectChanges; @@ -39,6 +42,7 @@ export 'realm_property.dart'; export 'results.dart' show RealmResults, RealmResultsChanges; export 'credentials.dart' show Credentials, AuthProvider; + /// A [Realm] instance represents a `Realm` database. /// /// {@category Realm} diff --git a/lib/src/user.dart b/lib/src/user.dart new file mode 100644 index 0000000000..e92a605b8c --- /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 RealmUserHandle _handle; + + User._(this._handle); +} + +extension UserInternal on User { + static User create(RealmUserHandle handle) => User._(handle); +} \ No newline at end of file diff --git a/src/realm_dart_app.h b/src/realm_dart_app.h new file mode 100644 index 0000000000..bf90f828b7 --- /dev/null +++ b/src/realm_dart_app.h @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_DART_APP_H +#define REALM_DART_APP_H + +#include "realm.h" +#include "dart_api_dl.h" + +/** + * Completion callback for asynchronous Realm App operations that yield a user object. + * + * @param userdata The userdata the asynchronous operation was started with. + * @param user User object produced by the operation, or null if it failed. + * The pointer is alive only for the duration of the callback, + * if you wish to use it further make a copy with realm_clone(). + * @param error Pointer to an error object if the operation failed, otherwise null if it completed successfully. + * + * This is a dart specific version of the completion callback for asynchronous Realm operations. + */ +typedef void (*realm_dart_app_user_completion_func_t)(Dart_Handle userdata, realm_user_t* user, const realm_app_error_t* error); + +/** + * @brief + * + * @param completion + * @param userdata + * @return true if operation started successfully, false if an error occurred. + */ +RLM_API bool realm_dart_app_log_in_with_credentials(realm_app_t*, realm_app_credentials_t*, + realm_dart_app_user_completion_func_t completion, Dart_Handle userdata); + +#endif \ No newline at end of file