Skip to content

Commit

Permalink
fix(clerk-js): Re-initialize Client singleton instance on Client dest…
Browse files Browse the repository at this point in the history
…roy (#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
  • Loading branch information
octoper authored Nov 2, 2023
1 parent 743c4d2 commit 93a6115
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/soft-birds-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Re-initialize the Client to default values when is destroyed
74 changes: 74 additions & 0 deletions packages/clerk-js/src/core/resources/Client.test.ts
Original file line number Diff line number Diff line change
@@ -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`,
});
});
});
5 changes: 5 additions & 0 deletions packages/clerk-js/src/core/resources/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
}

Expand Down
85 changes: 84 additions & 1 deletion packages/clerk-js/src/core/test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import type {
OrganizationMembershipJSON,
OrganizationPermission,
PhoneNumberJSON,
SessionJSON,
SignInJSON,
SignUpJSON,
UserJSON,
} from '@clerk/types';

Expand All @@ -25,6 +28,8 @@ type WithUserParams = Omit<
organization_memberships?: Array<string | OrgParams>;
};

type WithSessionParams = Partial<SessionJSON>;

export const getOrganizationId = (orgParams: OrgParams) => orgParams?.id || orgParams?.name || 'test_id';

export const createOrganizationMembership = (params: OrgParams): OrganizationMembershipJSON => {
Expand Down Expand Up @@ -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<UserJSON> = {}) => {
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<SignInJSON> = {}, user: Partial<UserJSON> = {}) => {
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<SignUpJSON> = {}) => {
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({
Expand Down

0 comments on commit 93a6115

Please sign in to comment.