diff --git a/.changeset/shy-sloths-laugh.md b/.changeset/shy-sloths-laugh.md
new file mode 100644
index 0000000000..3459ac9842
--- /dev/null
+++ b/.changeset/shy-sloths-laugh.md
@@ -0,0 +1,16 @@
+---
+"@clerk/clerk-js": minor
+---
+
+We recently shipped an experimental feature to persist the Clerk client (under `persistClient` flag) as an opt-in. This allows for matching a user's device with a client. We want to test this behavior with more users, so we're making it opt-out as the next step. After more successful testing we'll remove the experimental flag and enable it by default.
+
+If you're encountering issues, please open an issue. You can disable this new behavior like so:
+
+```js
+// React
+
+
+// Vanilla JS
+await clerk.load({ experimental: { persistClient: false } })
+```
+
diff --git a/integration/presets/envs.ts b/integration/presets/envs.ts
index e2a9f8030d..21b1794e7b 100644
--- a/integration/presets/envs.ts
+++ b/integration/presets/envs.ts
@@ -35,9 +35,9 @@ const withEmailCodes = base
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-email-codes').pk)
.setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.E2E_CLERK_ENCRYPTION_KEY || 'a-key');
-const withEmailCodes_persist_client = withEmailCodes
+const withEmailCodes_destroy_client = withEmailCodes
.clone()
- .setEnvVariable('public', 'EXPERIMENTAL_PERSIST_CLIENT', 'true');
+ .setEnvVariable('public', 'EXPERIMENTAL_PERSIST_CLIENT', 'false');
const withEmailLinks = base
.clone()
@@ -91,7 +91,7 @@ const withDynamicKeys = withEmailCodes
export const envs = {
base,
withEmailCodes,
- withEmailCodes_persist_client,
+ withEmailCodes_destroy_client,
withEmailLinks,
withCustomRoles,
withEmailCodesQuickstart,
diff --git a/integration/presets/longRunningApps.ts b/integration/presets/longRunningApps.ts
index 718b2915ca..eee5510608 100644
--- a/integration/presets/longRunningApps.ts
+++ b/integration/presets/longRunningApps.ts
@@ -19,14 +19,14 @@ export const createLongRunningApps = () => {
const configs = [
{ id: 'express.vite.withEmailCodes', config: express.vite, env: envs.withEmailCodes },
{ id: 'react.vite.withEmailCodes', config: react.vite, env: envs.withEmailCodes },
- { id: 'react.vite.withEmailCodes_persist_client', config: react.vite, env: envs.withEmailCodes_persist_client },
+ { id: 'react.vite.withEmailCodes_persist_client', config: react.vite, env: envs.withEmailCodes_destroy_client },
{ id: 'react.vite.withEmailLinks', config: react.vite, env: envs.withEmailLinks },
{ id: 'remix.node.withEmailCodes', config: remix.remixNode, env: envs.withEmailCodes },
{ id: 'next.appRouter.withEmailCodes', config: next.appRouter, env: envs.withEmailCodes },
{
id: 'next.appRouter.withEmailCodes_persist_client',
config: next.appRouter,
- env: envs.withEmailCodes_persist_client,
+ env: envs.withEmailCodes_destroy_client,
},
{ id: 'next.appRouter.withCustomRoles', config: next.appRouter, env: envs.withCustomRoles },
{ id: 'quickstart.next.appRouter', config: next.appRouterQuickstart, env: envs.withEmailCodesQuickstart },
diff --git a/integration/templates/next-app-router/src/app/layout.tsx b/integration/templates/next-app-router/src/app/layout.tsx
index d73009ca87..b8b377146c 100644
--- a/integration/templates/next-app-router/src/app/layout.tsx
+++ b/integration/templates/next-app-router/src/app/layout.tsx
@@ -13,7 +13,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
diff --git a/integration/templates/react-vite/src/client-id.tsx b/integration/templates/react-vite/src/client-id.tsx
index 49a76a45dc..88ccc8cf7c 100644
--- a/integration/templates/react-vite/src/client-id.tsx
+++ b/integration/templates/react-vite/src/client-id.tsx
@@ -1,5 +1,4 @@
import { useClerk, useSession } from '@clerk/clerk-react';
-import React from 'react';
export function ClientId() {
const clerk = useClerk();
diff --git a/integration/templates/react-vite/src/main.tsx b/integration/templates/react-vite/src/main.tsx
index 2841869bf4..21366dff08 100644
--- a/integration/templates/react-vite/src/main.tsx
+++ b/integration/templates/react-vite/src/main.tsx
@@ -21,7 +21,9 @@ const Root = () => {
routerPush={(to: string) => navigate(to)}
routerReplace={(to: string) => navigate(to, { replace: true })}
experimental={{
- persistClient: import.meta.env.VITE_EXPERIMENTAL_PERSIST_CLIENT === 'true',
+ persistClient: import.meta.env.VITE_EXPERIMENTAL_PERSIST_CLIENT
+ ? import.meta.env.VITE_EXPERIMENTAL_PERSIST_CLIENT === 'true'
+ : undefined,
}}
>
diff --git a/integration/tests/sign-out-smoke.test.ts b/integration/tests/sign-out-smoke.test.ts
index 27381295be..129947774c 100644
--- a/integration/tests/sign-out-smoke.test.ts
+++ b/integration/tests/sign-out-smoke.test.ts
@@ -20,7 +20,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('sign out
await app.teardown();
});
- test('sign out throught all open tabs at once', async ({ page, context }) => {
+ test('sign out through all open tabs at once', async ({ page, context }) => {
const mainTab = createTestUtils({ app, page, context });
await mainTab.po.signIn.goTo();
await mainTab.po.signIn.setIdentifier(fakeUser.email);
@@ -46,7 +46,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('sign out
await mainTab.po.expect.toBeSignedOut();
});
- test('sign out destroying client', async ({ page, context }) => {
+ test('sign out persisting client', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.po.signIn.goTo();
await u.po.signIn.setIdentifier(fakeUser.email);
@@ -55,21 +55,23 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('sign out
await u.po.signIn.continue();
await u.po.expect.toBeSignedIn();
await u.page.goToAppHome();
-
- await u.page.waitForSelector('p[data-clerk-id]', { state: 'attached' });
+ const client_id_element = await u.page.waitForSelector('p[data-clerk-id]', { state: 'attached' });
+ const client_id = await client_id_element.innerHTML();
await u.page.evaluate(async () => {
await window.Clerk.signOut();
});
await u.po.expect.toBeSignedOut();
- await u.page.waitForSelector('p[data-clerk-id]', { state: 'detached' });
await u.page.waitForSelector('p[data-clerk-session]', { state: 'detached' });
+
+ const client_id_after_sign_out = await u.page.locator('p[data-clerk-id]').innerHTML();
+ expect(client_id).toEqual(client_id_after_sign_out);
});
});
-testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes_persist_client] })(
- 'sign out with persistClient smoke test @generic',
+testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes_destroy_client] })(
+ 'sign out with destroy client smoke test @generic',
({ app }) => {
test.describe.configure({ mode: 'serial' });
@@ -86,7 +88,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes_persist_client
await app.teardown();
});
- test('sign out persisting client', async ({ page, context }) => {
+ test('sign out destroying client', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.po.signIn.goTo();
await u.po.signIn.setIdentifier(fakeUser.email);
@@ -95,18 +97,16 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes_persist_client
await u.po.signIn.continue();
await u.po.expect.toBeSignedIn();
await u.page.goToAppHome();
- const client_id_element = await u.page.waitForSelector('p[data-clerk-id]', { state: 'attached' });
- const client_id = await client_id_element.innerHTML();
+
+ await u.page.waitForSelector('p[data-clerk-id]', { state: 'attached' });
await u.page.evaluate(async () => {
await window.Clerk.signOut();
});
await u.po.expect.toBeSignedOut();
+ await u.page.waitForSelector('p[data-clerk-id]', { state: 'detached' });
await u.page.waitForSelector('p[data-clerk-session]', { state: 'detached' });
-
- const client_id_after_sign_out = await u.page.locator('p[data-clerk-id]').innerHTML();
- expect(client_id).toEqual(client_id_after_sign_out);
});
},
);
diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts
index 31da1d950d..bbea30c621 100644
--- a/packages/clerk-js/src/core/__tests__/clerk.test.ts
+++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts
@@ -448,11 +448,13 @@ describe('Clerk singleton', () => {
describe('.signOut()', () => {
const mockClientDestroy = jest.fn();
+ const mockClientRemoveSessions = jest.fn();
const mockSession1 = { id: '1', remove: jest.fn(), status: 'active', user: {}, getToken: jest.fn() };
const mockSession2 = { id: '2', remove: jest.fn(), status: 'active', user: {}, getToken: jest.fn() };
beforeEach(() => {
mockClientDestroy.mockReset();
+ mockClientRemoveSessions.mockReset();
mockSession1.remove.mockReset();
mockSession2.remove.mockReset();
});
@@ -480,6 +482,7 @@ describe('Clerk singleton', () => {
activeSessions: [mockSession1, mockSession2],
sessions: [mockSession1, mockSession2],
destroy: mockClientDestroy,
+ removeSessions: mockClientRemoveSessions,
}),
);
@@ -488,7 +491,8 @@ describe('Clerk singleton', () => {
await sut.load();
await sut.signOut();
await waitFor(() => {
- expect(mockClientDestroy).toHaveBeenCalled();
+ expect(mockClientDestroy).not.toHaveBeenCalled();
+ expect(mockClientRemoveSessions).toHaveBeenCalled();
expect(sut.setActive).toHaveBeenCalledWith({
session: null,
beforeEmit: expect.any(Function),
@@ -502,6 +506,7 @@ describe('Clerk singleton', () => {
activeSessions: [mockSession1],
sessions: [mockSession1],
destroy: mockClientDestroy,
+ removeSessions: mockClientRemoveSessions,
}),
);
@@ -510,7 +515,8 @@ describe('Clerk singleton', () => {
await sut.load();
await sut.signOut();
await waitFor(() => {
- expect(mockClientDestroy).toHaveBeenCalled();
+ expect(mockClientDestroy).not.toHaveBeenCalled();
+ expect(mockClientRemoveSessions).toHaveBeenCalled();
expect(mockSession1.remove).not.toHaveBeenCalled();
expect(sut.setActive).toHaveBeenCalledWith({ session: null, beforeEmit: expect.any(Function) });
});
diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index ddd5e93a50..a5d961e91c 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -331,7 +331,7 @@ export class Clerk implements ClerkInterface {
const cb = typeof callbackOrOptions === 'function' ? callbackOrOptions : defaultCb;
if (!opts.sessionId || this.client.activeSessions.length === 1) {
- if (this.#options.experimental?.persistClient) {
+ if (this.#options.experimental?.persistClient ?? true) {
await this.client.removeSessions();
} else {
await this.client.destroy();