diff --git a/.changeset/dry-sheep-poke.md b/.changeset/dry-sheep-poke.md new file mode 100644 index 0000000000..a9dfccce20 --- /dev/null +++ b/.changeset/dry-sheep-poke.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': minor +--- + +Introducing sign out from all open tabs at once. diff --git a/integration/testUtils/appPageObject.ts b/integration/testUtils/appPageObject.ts index 0a8cb4614d..f5063cbe57 100644 --- a/integration/testUtils/appPageObject.ts +++ b/integration/testUtils/appPageObject.ts @@ -31,7 +31,6 @@ export const createAppPageObject = (testArgs: { page: Page }, app: Application) }, waitForClerkJsLoaded: async () => { return page.waitForFunction(() => { - // @ts-ignore return window.Clerk?.isReady(); }); }, diff --git a/integration/testUtils/index.ts b/integration/testUtils/index.ts index 3ab321d6b3..fd7de347e1 100644 --- a/integration/testUtils/index.ts +++ b/integration/testUtils/index.ts @@ -22,13 +22,11 @@ const createExpectPageObject = ({ page }: TestArgs) => { return { toBeSignedOut: () => { return page.waitForFunction(() => { - // @ts-ignore return !window.Clerk?.user; }); }, toBeSignedIn: async () => { return page.waitForFunction(() => { - // @ts-ignore return !!window.Clerk?.user; }); }, diff --git a/integration/tests/sign-out-smoke.test.ts b/integration/tests/sign-out-smoke.test.ts new file mode 100644 index 0000000000..a6fa5a1796 --- /dev/null +++ b/integration/tests/sign-out-smoke.test.ts @@ -0,0 +1,48 @@ +import { expect, test } from '@playwright/test'; + +import { appConfigs } from '../presets'; +import type { FakeUser } from '../testUtils'; +import { createTestUtils, testAgainstRunningApps } from '../testUtils'; + +testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('sign out smoke test @generic', ({ app }) => { + test.describe.configure({ mode: 'serial' }); + + let fakeUser: FakeUser; + + test.beforeAll(async () => { + const u = createTestUtils({ app }); + fakeUser = u.services.users.createFakeUser(); + await u.services.users.createBapiUser(fakeUser); + }); + + test.afterAll(async () => { + await fakeUser.deleteIfExists(); + await app.teardown(); + }); + + test('sign out throught 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); + await mainTab.po.signIn.continue(); + await mainTab.po.signIn.setPassword(fakeUser.password); + await mainTab.po.signIn.continue(); + await mainTab.po.expect.toBeSignedIn(); + + await mainTab.tabs.runInNewTab(async m => { + await m.page.goToStart(); + + await m.page.waitForClerkJsLoaded(); + + await m.po.expect.toBeSignedIn(); + + await m.page.evaluate(async () => { + await window.Clerk.signOut(); + }); + + await m.po.expect.toBeSignedOut(); + }); + + expect(await mainTab.page.evaluate('!window.Clerk.user')).toBe(false); + }); +}); diff --git a/integration/tsconfig.json b/integration/tsconfig.json index 4361874219..441382fe67 100644 --- a/integration/tsconfig.json +++ b/integration/tsconfig.json @@ -6,6 +6,6 @@ "esModuleInterop": true, "allowSyntheticDefaultImports": true }, - "include": ["./tests"], + "include": ["./tests", "types.d.ts"], "exclude": ["templates"] } diff --git a/integration/types.d.ts b/integration/types.d.ts new file mode 100644 index 0000000000..8df81fba45 --- /dev/null +++ b/integration/types.d.ts @@ -0,0 +1,7 @@ +import type { Clerk } from '@clerk/types'; + +declare global { + interface Window { + Clerk: Clerk; + } +} diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index da64be8148..e9d462b867 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -1,4 +1,3 @@ -import type { LocalStorageBroadcastChannel } from '@clerk/shared'; import { addClerkPrefix, handleValueOrFn, @@ -7,6 +6,7 @@ import { isHttpOrHttps, isValidBrowserOnline, isValidProxyUrl, + LocalStorageBroadcastChannel, noop, parsePublishableKey, proxyUrlToAbsoluteURL, @@ -1282,6 +1282,7 @@ export default class Clerk implements ClerkInterface { const isInAccountsHostedPages = isDevAccountPortalOrigin(window?.location.hostname); + this.#broadcastChannel = new LocalStorageBroadcastChannel('clerk'); this.#setupListeners(); let retries = 0; @@ -1375,7 +1376,7 @@ export default class Clerk implements ClerkInterface { this.#broadcastChannel?.addEventListener('message', ({ data }) => { if (data.type === 'signout') { - void this.handleUnauthenticated({ broadcast: false }); + void this.handleUnauthenticated(); } }); };