Skip to content

Commit

Permalink
Fix Current User
Browse files Browse the repository at this point in the history
* Add test case to showcase issue
* Update User and App to handle persisting the user on new App instances
  • Loading branch information
takameyer committed May 5, 2023
1 parent e7afd45 commit 9866e29
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 16 deletions.
37 changes: 31 additions & 6 deletions integration-tests/tests/src/tests/sync/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
randomVerifiableEmail,
} from "../../utils/generators";
import { KJUR } from "jsrsasign";
import { UserState } from "realm";
import { Realm, UserState } from "realm";

import { buildAppConfig } from "../../utils/build-app-config";

Expand Down Expand Up @@ -491,18 +491,18 @@ describe.skipIf(environment.missingServer, "User", () => {
exports = async function (loginPayload) {
// Get a handle for the app.users collection
const users = context.services.get("mongodb").db("app").collection("users");
// Parse out custom data from the FunctionCredential
const { username, secret } = loginPayload;
if (secret !== "v3ry-s3cret") {
throw new Error("Ah ah ah, you didn't say the magic word");
}
// Query for an existing user document with the specified username
const user = await users.findOne({ username });
if (user) {
// If the user document exists, return its unique ID
return user._id.toString();
Expand Down Expand Up @@ -574,4 +574,29 @@ describe.skipIf(environment.missingServer, "User", () => {
await expect(user.functions.error()).to.be.rejectedWith("function not found: 'error'");
});
});
describe("currentUser", () => {
importAppBefore(buildAppConfig("with-anon-auth").anonAuth());

it("persists currentUser on opening the app", async function (this: AppContext & RealmContext) {
const credentials = Realm.Credentials.anonymous();
const user = await this.app.logIn(credentials);
const appId = this.app.id;
//@ts-expect-error Wanting to prove that a completely new app instance will still return the current logged in user
delete this.app;
this.app = new Realm.App(appId);

{
const currentUser = this.app.currentUser;
expect(currentUser).not.to.be.null;
expect(user.id).to.equal(currentUser?.id);
}

{
const newApp = new Realm.App(appId);
const currentUser = newApp.currentUser;
expect(currentUser).not.to.be.null;
expect(user.id).to.equal(currentUser?.id);
}
});
});
});
27 changes: 18 additions & 9 deletions packages/realm/src/app-services/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import {
fs,
} from "../internal";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AnyApp = App<any, any>;

/**
* This describes the options used to create a Realm App instance.
*/
Expand Down Expand Up @@ -86,9 +89,9 @@ type AppListenerToken = binding.AppSubscriptionToken;
*/
export class App<FunctionsFactoryType = DefaultFunctionsFactory, CustomDataType = Record<string, unknown>> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static appById = new Map<string, binding.WeakRef<App<any, any>>>();
private static appById = new Map<string, binding.WeakRef<AnyApp>>();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static appByUserId = new Map<string, binding.WeakRef<App<any, any>>>();
private static appByUserId = new Map<string, binding.WeakRef<AnyApp>>();

/**
* Get or create a singleton Realm App from an id.
Expand All @@ -110,12 +113,12 @@ export class App<FunctionsFactoryType = DefaultFunctionsFactory, CustomDataType
* @returns The Realm App instance.
*/
public static get(id: string): App {
const cachedApp = this.appById.get(id)?.deref();
const cachedApp = App.appById.get(id)?.deref();
if (cachedApp) {
return cachedApp;
}
const newApp = new App(id);
this.appById.set(id, new binding.WeakRef(newApp));
App.appById.set(id, new binding.WeakRef(newApp));
return newApp;
}

Expand All @@ -131,13 +134,20 @@ export class App<FunctionsFactoryType = DefaultFunctionsFactory, CustomDataType

/** @internal */
public static getAppByUser(userInternal: binding.SyncUser): App {
const app = this.appByUserId.get(userInternal.identity)?.deref();
const app = App.appByUserId.get(userInternal.identity)?.deref();
if (!app) {
throw new Error(`Cannot determine which app is associated with user (id = ${userInternal.identity})`);
}
return app;
}

/** @internal */
public static setAppByUser(userInternal: binding.SyncUser, currentApp: AnyApp): void {
if (currentApp) {
App.appByUserId.set(userInternal.identity, new binding.WeakRef(currentApp));
}
}

/** @internal */
public internal: binding.App;

Expand Down Expand Up @@ -202,8 +212,7 @@ export class App<FunctionsFactoryType = DefaultFunctionsFactory, CustomDataType

public async logIn(credentials: Credentials) {
const userInternal = await this.internal.logInWithCredentials(credentials.internal);
App.appByUserId.set(userInternal.identity, new binding.WeakRef(this));
return new User(userInternal, this);
return User.get(userInternal, this);
}

public get emailPasswordAuth(): EmailPasswordAuth {
Expand All @@ -214,11 +223,11 @@ export class App<FunctionsFactoryType = DefaultFunctionsFactory, CustomDataType

public get currentUser(): User<FunctionsFactoryType, CustomDataType> | null {
const currentUser = this.internal.currentUser;
return currentUser ? User.get(currentUser) : null;
return currentUser ? User.get(currentUser, this) : null;
}

public get allUsers(): Readonly<Record<string, User<FunctionsFactoryType, CustomDataType>>> {
return Object.fromEntries(this.internal.allUsers.map((user) => [user.identity, User.get(user)]));
return Object.fromEntries(this.internal.allUsers.map((user) => [user.identity, User.get(user, this)]));
}

public switchUser(): unknown {
Expand Down
7 changes: 6 additions & 1 deletion packages/realm/src/app-services/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
////////////////////////////////////////////////////////////////////////////

import {
AnyApp,
ApiKeyAuth,
App,
Credentials,
Expand Down Expand Up @@ -104,7 +105,11 @@ export class User<
FunctionsFactoryType = DefaultFunctionsFactory,
CustomDataType = DefaultObject,
UserProfileDataType = DefaultUserProfileData,
>(internal: binding.SyncUser) {
>(internal: binding.SyncUser, app?: AnyApp) {
// Update the static user reference to the current app
if (app) {
App.setAppByUser(internal, app);
}
// TODO: Use a WeakRef to memoize the SDK object
return new User<FunctionsFactoryType, CustomDataType, UserProfileDataType>(internal, App.getAppByUser(internal));
}
Expand Down

0 comments on commit 9866e29

Please sign in to comment.