From 93a611570b836fc9bd50eed973e76cf8d413963d Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Thu, 2 Nov 2023 12:31:40 +0200 Subject: [PATCH] fix(clerk-js): Re-initialize Client singleton instance on Client destroy (#1913) * fix(clerk-js): Re-initialize Client singleton on Client destroy * fix(clerk-js): Assign default values instead of re-initializing Client class * chore(repo): Adds changeset * chore(repo): Adds changeset * chore(clerk-js): Fix typo in test * test(clerk-js): Added test for Client singleton destroy * test(clerk-js): Update tests for Client singleton * test(clerk-js): Update tests for Client singleton & created new fixtures --- .changeset/soft-birds-thank.md | 5 ++ .../src/core/resources/Client.test.ts | 74 ++++++++++++++++ .../clerk-js/src/core/resources/Client.ts | 5 ++ packages/clerk-js/src/core/test/fixtures.ts | 85 ++++++++++++++++++- 4 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 .changeset/soft-birds-thank.md create mode 100644 packages/clerk-js/src/core/resources/Client.test.ts diff --git a/.changeset/soft-birds-thank.md b/.changeset/soft-birds-thank.md new file mode 100644 index 0000000000..56a7cab2df --- /dev/null +++ b/.changeset/soft-birds-thank.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Re-initialize the Client to default values when is destroyed diff --git a/packages/clerk-js/src/core/resources/Client.test.ts b/packages/clerk-js/src/core/resources/Client.test.ts new file mode 100644 index 0000000000..20f460c310 --- /dev/null +++ b/packages/clerk-js/src/core/resources/Client.test.ts @@ -0,0 +1,74 @@ +import type { ClientJSON } from '@clerk/types'; + +import { createSession, createSignIn, createSignUp, createUser } from '../test/fixtures'; +import { BaseResource, Client } from './internal'; + +describe('Client Singleton', () => { + it('destroy', async () => { + const user = createUser({ first_name: 'John', last_name: 'Doe', id: 'user_1' }); + const session = createSession({ id: 'session_1' }, user); + const clientObjectJSON: ClientJSON = { + object: 'client', + id: 'test_id', + status: 'active', + last_active_session_id: 'test_session_id', + sign_in: createSignIn({ id: 'test_sign_in_id' }, user), + sign_up: createSignUp({ id: 'test_sign_up_id' }), // This is only for testing purposes, this will never happen + sessions: [session], + created_at: jest.now() - 1000, + updated_at: jest.now(), + }; + + const destroyedSession = createSession( + { + id: 'test_session_id', + abandon_at: jest.now(), + status: 'ended', + last_active_token: undefined, + }, + user, + ); + + const clientObjectDeletedJSON = { + id: 'test_id_deleted', + status: 'ended', + last_active_session_id: null, + sign_in: null, + sign_up: null, + sessions: [destroyedSession], + created_at: jest.now() - 1000, + updated_at: jest.now(), + }; + + // @ts-expect-error This is a private method that we are mocking + BaseResource._fetch = jest.fn().mockReturnValue( + Promise.resolve({ + client: null, + response: clientObjectDeletedJSON, + }), + ); + + const client = Client.getInstance().fromJSON(clientObjectJSON); + expect(client.sessions.length).toBe(1); + expect(client.createdAt).not.toBeNull(); + expect(client.updatedAt).not.toBeNull(); + expect(client.lastActiveSessionId).not.toBeNull(); + expect(client.signUp.id).toBe('test_sign_up_id'); + expect(client.signIn.id).toBe('test_sign_in_id'); + + await client.destroy(); + + expect(client.sessions.length).toBe(0); + expect(client.createdAt).toBeNull(); + expect(client.updatedAt).toBeNull(); + expect(client.lastActiveSessionId).toBeNull(); + expect(client.signUp.id).toBeUndefined(); + expect(client.signIn.id).toBeUndefined(); + + // @ts-expect-error This is a private method that we are mocking + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'DELETE', + path: `/client`, + }); + }); +}); diff --git a/packages/clerk-js/src/core/resources/Client.ts b/packages/clerk-js/src/core/resources/Client.ts index 24b994bef0..e00a30bac7 100644 --- a/packages/clerk-js/src/core/resources/Client.ts +++ b/packages/clerk-js/src/core/resources/Client.ts @@ -57,6 +57,11 @@ export class Client extends BaseResource implements ClientResource { return this._baseDelete({ path: '/client' }).then(() => { SessionTokenCache.clear(); this.sessions = []; + this.signUp = new SignUp(null); + this.signIn = new SignIn(null); + this.lastActiveSessionId = null; + this.createdAt = null; + this.updatedAt = null; }); } diff --git a/packages/clerk-js/src/core/test/fixtures.ts b/packages/clerk-js/src/core/test/fixtures.ts index a30f1d4ef9..00db010a70 100644 --- a/packages/clerk-js/src/core/test/fixtures.ts +++ b/packages/clerk-js/src/core/test/fixtures.ts @@ -7,6 +7,9 @@ import type { OrganizationMembershipJSON, OrganizationPermission, PhoneNumberJSON, + SessionJSON, + SignInJSON, + SignUpJSON, UserJSON, } from '@clerk/types'; @@ -25,6 +28,8 @@ type WithUserParams = Omit< organization_memberships?: Array; }; +type WithSessionParams = Partial; + export const getOrganizationId = (orgParams: OrgParams) => orgParams?.id || orgParams?.name || 'test_id'; export const createOrganizationMembership = (params: OrgParams): OrganizationMembershipJSON => { @@ -158,11 +163,89 @@ export const createUser = (params: WithUserParams): UserJSON => { organization_memberships: (params.organization_memberships || []).map(o => typeof o === 'string' ? createOrganizationMembership({ name: o }) : createOrganizationMembership(o), ), - } as any as UserJSON; + } as UserJSON; res.primary_email_address_id = res.email_addresses[0]?.id; return res; }; +export const createSession = (sessionParams: WithSessionParams = {}, user: Partial = {}) => { + return { + object: 'session', + id: sessionParams.id, + status: sessionParams.status, + expire_at: sessionParams.expire_at || jest.now() + 5000, + abandon_at: sessionParams.abandon_at, + last_active_at: sessionParams.last_active_at || jest.now(), + last_active_organization_id: sessionParams.last_active_organization_id, + actor: sessionParams.actor, + user: createUser({}), + public_user_data: { + first_name: user.first_name, + last_name: user.last_name, + image_url: user.image_url, + has_image: user.has_image, + identifier: user.email_addresses?.find(e => e.id === user.primary_email_address_id)?.email_address || '', + profile_image_url: user.profile_image_url, + }, + created_at: sessionParams.created_at || jest.now() - 1000, + updated_at: sessionParams.updated_at || jest.now(), + last_active_token: { + object: 'token', + jwt: mockJwt, + }, + } as SessionJSON; +}; + +export const createSignIn = (signInParams: Partial = {}, user: Partial = {}) => { + return { + id: signInParams.id, + created_session_id: signInParams.created_session_id, + status: signInParams.status, + first_factor_verification: signInParams.first_factor_verification, + identifier: signInParams.identifier, + object: 'sign_in', + second_factor_verification: signInParams.second_factor_verification, + supported_external_accounts: signInParams.supported_external_accounts, + supported_first_factors: signInParams.supported_first_factors, + supported_identifiers: signInParams.supported_identifiers, + supported_second_factors: signInParams.supported_second_factors, + user_data: { + first_name: user.first_name, + last_name: user.last_name, + image_url: user.image_url, + has_image: user.has_image, + profile_image_url: user.profile_image_url, + }, + } as SignInJSON; +}; + +export const createSignUp = (signUpParams: Partial = {}) => { + return { + id: signUpParams.id, + created_session_id: signUpParams.created_session_id, + status: signUpParams.status, + abandon_at: signUpParams.abandon_at, + created_user_id: signUpParams.created_user_id, + email_address: signUpParams.email_address, + external_account: signUpParams.external_account, + external_account_strategy: signUpParams.external_account_strategy, + first_name: signUpParams.first_name, + has_password: signUpParams.has_password, + last_name: signUpParams.last_name, + missing_fields: signUpParams.missing_fields, + object: 'sign_up', + optional_fields: signUpParams.optional_fields, + phone_number: signUpParams.phone_number, + required_fields: signUpParams.required_fields, + supported_external_accounts: signUpParams.supported_external_accounts, + unsafe_metadata: signUpParams.unsafe_metadata, + unverified_fields: signUpParams.unverified_fields, + username: signUpParams.username, + verifications: signUpParams.verifications, + web3_wallet: signUpParams.web3_wallet, + } as SignUpJSON; +}; + export const clerkMock = () => { return { getFapiClient: jest.fn().mockReturnValue({