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

Expose customData and refreshCustomData on User #525

Merged
merged 15 commits into from
May 4, 2022
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ jobs:

build-windows:
# TODO: build on windows-latest
runs-on: windows-2019
runs-on: windows-2022
name: Build Windows
steps:
- name: Checkout
Expand Down
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ x.x.x Release notes (yyyy-MM-dd)
* Support EmailPassword calling custom reset password functions. ([#482](https://github.com/realm/realm-dart/pull/482))
* Support EmailPassword retry custom user confirmation functions. ([#484](https://github.com/realm/realm-dart/pull/484))
* Expose currentUser property on App. ([473](https://github.com/realm/realm-dart/pull/473))
* Support logout user. ([#476](https://github.com/realm/realm-dart/pull/476))
* Support app logout user. ([#476](https://github.com/realm/realm-dart/pull/476))
* Support remove user. ([#492](https://github.com/realm/realm-dart/pull/492))
* Support switch current user. ([#493](https://github.com/realm/realm-dart/pull/493))
* Support user custom data and refresh. ([#525](https://github.com/realm/realm-dart/pull/525))
* Support linking user credentials. ([#525](https://github.com/realm/realm-dart/pull/525))
* Support user state. ([#525](https://github.com/realm/realm-dart/pull/525))
* Support getting user identity and all identities. ([#525](https://github.com/realm/realm-dart/pull/525))
* Support user logout. ([#525](https://github.com/realm/realm-dart/pull/525))

### 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))
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ BAAS_API_KEY=<public_key>
BAAS_PRIVATE_API_KEY=<private_key>
BAAS_PROJECT_ID=<project_id>
```
10) Now you can run `dart test` and it should include the integration tests (`testWithBaaS`).
10) Now you can run `dart test` and it should include the integration tests.

If you are a MongoDB employee, you can instead choose to run the tests against [cloud-dev](cloud-dev.mongodb.com).
The procedure is the same, except you need to use your qa credentials instead.
Expand Down
10 changes: 5 additions & 5 deletions lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class App {
/// Logs in a user with the given credentials.
Future<User> logIn(Credentials credentials) async {
var userHandle = await realmCore.logIn(this, credentials);
return UserInternal.create(userHandle);
return UserInternal.create(this, userHandle);
}

/// Gets the currently logged in [User]. If none exists, `null` is returned.
Expand All @@ -132,16 +132,16 @@ class App {
if (userHandle == null) {
return null;
}
return UserInternal.create(userHandle);
return UserInternal.create(this, userHandle);
}

/// Gets all currently logged in users.
Iterable<User> get users {
return realmCore.getUsers(this).map((handle) => UserInternal.create(handle));
return realmCore.getUsers(this).map((handle) => UserInternal.create(this, handle));
}

/// Removes the user's local credentials and attempts to invalidate their refresh token from the server.
///
/// Removes the user's local credentials. This will also close any associated Sessions.
///
/// If [user] is null logs out [currentUser] if it exists.
Future<void> logout(User? user) async {
user ??= currentUser;
Expand Down
110 changes: 103 additions & 7 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ class _RealmCore {
return AppHandle._(realmAppPtr);
}

static void _logInCallback(Pointer<Void> userdata, Pointer<realm_user> user, Pointer<realm_app_error> error) {
static void _app_user_completion_callback(Pointer<Void> userdata, Pointer<realm_user> user, Pointer<realm_app_error> error) {
final Completer<UserHandle>? completer = userdata.toObject(isPersistent: true);
if (completer == null) {
return;
Expand All @@ -875,7 +875,7 @@ class _RealmCore {

var userClone = _realmLib.realm_clone(user.cast());
if (userClone == nullptr) {
completer.completeError(RealmException("Error while cloning login data"));
completer.completeError(RealmException("Error while cloning user object."));
return;
}

Expand All @@ -888,7 +888,7 @@ class _RealmCore {
() => _realmLib.realm_app_log_in_with_credentials(
app.handle._pointer,
credentials.handle._pointer,
Pointer.fromFunction(_logInCallback),
Pointer.fromFunction(_app_user_completion_callback),
completer.toPersistentHandle(),
_deletePersistentHandleFuncPtr,
),
Expand All @@ -897,7 +897,7 @@ class _RealmCore {
}

static void void_completion_callback(Pointer<Void> userdata, Pointer<realm_app_error> error) {
final Completer<void>? completer = userdata.toObject();
final Completer<void>? completer = userdata.toObject(isPersistent: true);
if (completer == null) {
return;
}
Expand Down Expand Up @@ -1038,7 +1038,7 @@ class _RealmCore {
completer.complete();
}

Future<void> logOut(App application, User user) async {
Future<void> logOut(App application, User user) {
final completer = Completer<void>();
_realmLib.invokeGetBool(
() => _realmLib.realm_app_log_out(
Expand Down Expand Up @@ -1079,7 +1079,7 @@ class _RealmCore {
});
}

Future<void> removeUser(App app, User user) async {
Future<void> removeUser(App app, User user) {
final completer = Completer<void>();
_realmLib.invokeGetBool(
() => _realmLib.realm_app_remove_user(
Expand All @@ -1092,7 +1092,7 @@ class _RealmCore {
"Remove user failed");
return completer.future;
}

void switchUser(App application, User user) {
return using((arena) {
_realmLib.invokeGetBool(
Expand All @@ -1104,6 +1104,82 @@ class _RealmCore {
"Switch user failed");
});
}

String userGetCustomData(User user) {
final customDataPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_user_get_custom_data(user.handle._pointer));
try {
final customData = customDataPtr.cast<Utf8>().toDartString();
return customData;
} finally {
_realmLib.realm_free(customDataPtr.cast());
}
}

Future<void> userRefreshCustomData(App app, User user) {
final completer = Completer<void>();
_realmLib.invokeGetBool(
() => _realmLib.realm_app_refresh_custom_data(
app.handle._pointer,
user.handle._pointer,
Pointer.fromFunction(void_completion_callback),
completer.toPersistentHandle(),
_deletePersistentHandleFuncPtr,
),
"Refresh custom data failed");
return completer.future;
}

Future<UserHandle> userLinkCredentials(App app, User user, Credentials credentials) {
final completer = Completer<UserHandle>();
_realmLib.invokeGetBool(
() => _realmLib.realm_app_link_user(
app.handle._pointer,
user.handle._pointer,
credentials.handle._pointer,
Pointer.fromFunction(_app_user_completion_callback),
completer.toPersistentHandle(),
_deletePersistentHandleFuncPtr,
),
"Link credentials failed");
return completer.future;
}

UserState userGetState(User user) {
final nativeUserState = _realmLib.realm_user_get_state(user.handle._pointer);
return UserState.values.fromIndex(nativeUserState);
}

String userGetId(User user) {
final idPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_user_get_identity(user.handle._pointer), "Error while getting user id");
final userId = idPtr.cast<Utf8>().toDartString();
return userId;
}

List<UserIdentity> userGetIdentities(User user) {
return using((arena) {
//TODO: This approach is prone to race conditions. Fix this once Core changes how count is retrieved.
final idsCount = arena<IntPtr>();
_realmLib.invokeGetBool(
() => _realmLib.realm_user_get_all_identities(user.handle._pointer, nullptr, 0, idsCount), "Error while getting user identities count");

final idsPtr = arena<realm_user_identity_t>(idsCount.value);
_realmLib.invokeGetBool(
() => _realmLib.realm_user_get_all_identities(user.handle._pointer, idsPtr, idsCount.value, idsCount), "Error while getting user identities");
blagoev marked this conversation as resolved.
Show resolved Hide resolved

final userIdentities = <UserIdentity>[];
for (var i = 0; i < idsCount.value; i++) {
final idPtr = idsPtr.elementAt(i);
userIdentities.add(UserIdentityInternal.create(idPtr.ref.id.cast<Utf8>().toDartString(), AuthProviderType.values.fromIndex(idPtr.ref.provider_type)));
}

return userIdentities;
});
}

Future<void> userLogOut(User user) {
_realmLib.invokeGetBool(() => _realmLib.realm_user_log_out(user.handle._pointer), "Logout failed");
return Future<void>.value();
}
}

class LastError {
Expand Down Expand Up @@ -1405,6 +1481,26 @@ extension on Object {
}
}

extension on List<AuthProviderType> {
AuthProviderType fromIndex(int index) {
if (!AuthProviderType.values.any((value) => value.index == index)) {
blagoev marked this conversation as resolved.
Show resolved Hide resolved
throw RealmError("Unknown AuthProviderType $index");
}

return AuthProviderType.values[index];
}
}

extension on List<UserState> {
UserState fromIndex(int index) {
if (!UserState.values.any((value) => value.index == index)) {
throw RealmError("Unknown user state $index");
}

return UserState.values[index];
}
}

// TODO: Once enhanced-enums land in 2.17, replace with:
/*
enum _CustomErrorCode {
Expand Down
82 changes: 80 additions & 2 deletions lib/src/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
//
////////////////////////////////////////////////////////////////////////////////

import 'dart:convert';

import 'native/realm_core.dart';
import 'app.dart';
import 'credentials.dart';
import 'realm_class.dart';

/// This class represents a user in a MongoDB Realm app.
/// A user can log in to the server and, if access is granted, it is possible to synchronize the local Realm to MongoDB.
Expand All @@ -27,12 +32,85 @@ import 'native/realm_core.dart';
class User {
final UserHandle _handle;

User._(this._handle);
/// The [App] with which the user is associated with.
final App app;

User._(this.app, this._handle);

/// The custom user data associated with this user.
dynamic get customData {
final data = realmCore.userGetCustomData(this);
return jsonDecode(data);
}

/// Refreshes the custom data for a this [User].
Future<dynamic> refreshCustomData() async {
await realmCore.userRefreshCustomData(app, this);
return customData;
}

/// Links this [User] with a new [User] identity represented by the given credentials.
///
/// Linking a user with more credentials, mean the user can login either of these credentials. It also makes it possible to "upgrade" an anonymous user
/// by linking it with e.g. Email/Password credentials.
/// Note: It is not possible to link two existing users of MongoDB Realm. The provided credentials must not have been used by another user.
Future<User> linkCredentials(Credentials credentials) async {
final userHandle = await realmCore.userLinkCredentials(app, this, credentials);
return UserInternal.create(app, userHandle);
}

/// The current state of this [User].
UserState get state {
return realmCore.userGetState(this);
}

/// Get this [User]'s id on MongoDB Realm
String get id {
return realmCore.userGetId(this);
}

/// Gets a collection of all identities associated with this [User]
List<UserIdentity> get identities {
return realmCore.userGetIdentities(this);
}

/// Removes the user's local credentials. This will also close any associated Sessions.
Future<void> logout() async {
return await realmCore.userLogOut(this);
}
}

/// The current state of a [User].
enum UserState {
/// The user is logged in, and any Realms associated with it are synchronizing with MongoDB Realm.
loogedIn,

/// The user is logged out. Call LogInAsync(Credentials) with valid credentials to log the user back in.
loggedOut,

/// The user has been logged out and their local data has been removed.
removed,
}

/// The user identity associated with a [User]
class UserIdentity {
/// The unique identifier for this [UserIdentity]
final String id;

/// The authentication provider defining this identity
final AuthProviderType provider;

const UserIdentity._(this.id, this.provider);
}

/// @nodoc
extension UserIdentityInternal on UserIdentity {
static UserIdentity create(String identity, AuthProviderType provider) => UserIdentity._(identity, provider);
}

/// @nodoc
extension UserInternal on User {
UserHandle get handle => _handle;

static User create(UserHandle handle) => User._(handle);
static User create(App app, UserHandle handle) => User._(app, handle);
}
2 changes: 1 addition & 1 deletion scripts/build.bat
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pushd %PROJECT_ROOT%\build-windows
SET EXIT_CODE=0

cmake ^
-G "Visual Studio 16 2019" ^
-G "Visual Studio 17 2022" ^
-A x64 ^
-DCMAKE_TOOLCHAIN_FILE="%PROJECT_ROOT%/src/realm-core/tools/vcpkg/ports/scripts/buildsystems/vcpkg.cmake" ^
-DVCPKG_MANIFEST_DIR="%PROJECT_ROOT%/src/realm-core/tools/vcpkg" ^
Expand Down