diff --git a/.changeset/shaggy-zebras-attend.md b/.changeset/shaggy-zebras-attend.md new file mode 100644 index 00000000000..84ae2d50326 --- /dev/null +++ b/.changeset/shaggy-zebras-attend.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': patch +'@clerk/shared': patch +--- + +Re-organize cookie codebase into a central place, fix TokenUpdate event to be triggered on sign-out and drop duplicate event on refreshing token. diff --git a/packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts b/packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts index 9cd2364ecf3..0f6dad0113a 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts @@ -1,5 +1,5 @@ +import type { DevBrowser } from '../auth/devBrowser'; import { Clerk } from '../clerk'; -import type { DevBrowser } from '../devBrowser'; import type { DisplayConfig } from '../resources/internal'; import { Client, Environment } from '../resources/internal'; @@ -10,7 +10,7 @@ jest.mock('../resources/Client'); jest.mock('../resources/Environment'); // Because Jest, don't ask me why... -jest.mock('../devBrowser', () => ({ +jest.mock('../auth/devBrowser', () => ({ createDevBrowser: (): DevBrowser => ({ clear: jest.fn(), setup: jest.fn(), diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index 7d61fd5c75e..b18787dda80 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -2,12 +2,11 @@ import type { ActiveSessionResource, SignInJSON, SignUpJSON, TokenResource } fro import { waitFor } from '@testing-library/dom'; import { mockNativeRuntime } from '../../testUtils'; +import type { DevBrowser } from '../auth/devBrowser'; import { Clerk } from '../clerk'; -import type { DevBrowser } from '../devBrowser'; import { eventBus, events } from '../events'; import type { DisplayConfig, Organization } from '../resources/internal'; import { BaseResource, Client, EmailLinkErrorCode, Environment, SignIn, SignUp } from '../resources/internal'; -import { SessionCookieService } from '../services'; import { mockJwt } from '../test/fixtures'; const mockClientFetch = jest.fn(); @@ -17,7 +16,7 @@ jest.mock('../resources/Client'); jest.mock('../resources/Environment'); // Because Jest, don't ask me why... -jest.mock('../devBrowser', () => ({ +jest.mock('../auth/devBrowser', () => ({ createDevBrowser: (): DevBrowser => ({ clear: jest.fn(), setup: jest.fn(), @@ -158,17 +157,17 @@ describe('Clerk singleton', () => { touch: jest.fn(), getToken: jest.fn(), }; - let cookieSpy; + let evenBusSpy; beforeEach(() => { - cookieSpy = jest.spyOn(SessionCookieService.prototype, 'setAuthCookiesFromSession'); + evenBusSpy = jest.spyOn(eventBus, 'dispatch'); }); afterEach(() => { mockSession.remove.mockReset(); mockSession.touch.mockReset(); - cookieSpy?.mockRestore(); + evenBusSpy?.mockRestore(); // cleanup global window pollution (window as any).__unstable__onBeforeSetActive = null; (window as any).__unstable__onAfterSetActive = null; @@ -183,7 +182,7 @@ describe('Clerk singleton', () => { await sut.setActive({ session: null }); await waitFor(() => { expect(mockSession.touch).not.toHaveBeenCalled(); - expect(cookieSpy).toBeCalledWith(null); + expect(evenBusSpy).toBeCalledWith('token:update', { token: null }); }); }); @@ -194,22 +193,20 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load(); await sut.setActive({ session: mockSession as any as ActiveSessionResource }); - await waitFor(() => { - expect(mockSession.touch).toHaveBeenCalled(); - expect(cookieSpy).toBeCalledWith(mockSession); - }); + expect(mockSession.touch).toHaveBeenCalled(); }); it('does not call session.touch if Clerk was initialised with touchSession set to false', async () => { mockSession.touch.mockReturnValueOnce(Promise.resolve()); mockClientFetch.mockReturnValue(Promise.resolve({ activeSessions: [mockSession] })); + mockSession.getToken.mockResolvedValue('mocked-token'); const sut = new Clerk(productionPublishableKey); await sut.load({ touchSession: false }); await sut.setActive({ session: mockSession as any as ActiveSessionResource }); await waitFor(() => { expect(mockSession.touch).not.toHaveBeenCalled(); - expect(cookieSpy).toBeCalledWith(mockSession); + expect(mockSession.getToken).toBeCalled(); }); }); @@ -250,6 +247,7 @@ describe('Clerk singleton', () => { status: 'active', user: {}, touch: jest.fn(), + getToken: jest.fn(), }; mockClientFetch.mockReturnValue(Promise.resolve({ activeSessions: [mockSession, mockSession2] })); @@ -262,9 +260,9 @@ describe('Clerk singleton', () => { executionOrder.push('session.touch'); return Promise.resolve(); }); - cookieSpy.mockImplementationOnce(() => { + mockSession2.getToken.mockImplementation(() => { executionOrder.push('set cookie'); - return Promise.resolve(); + return 'mocked-token-2'; }); const beforeEmitMock = jest.fn().mockImplementationOnce(() => { executionOrder.push('before emit'); @@ -276,7 +274,7 @@ describe('Clerk singleton', () => { await waitFor(() => { expect(executionOrder).toEqual(['session.touch', 'set cookie', 'before emit']); expect(mockSession2.touch).toHaveBeenCalled(); - expect(cookieSpy).toBeCalledWith(mockSession2); + expect(mockSession2.getToken).toHaveBeenCalled(); expect(beforeEmitMock).toBeCalledWith(mockSession2); expect(sut.session).toMatchObject(mockSession2); }); @@ -285,7 +283,6 @@ describe('Clerk singleton', () => { // TODO: @dimkl include set transitive state it('calls with lastActiveOrganizationId session.touch -> set cookie -> before emit -> set accessors with touched session on organization switch', async () => { mockClientFetch.mockReturnValue(Promise.resolve({ activeSessions: [mockSession] })); - const sut = new Clerk(productionPublishableKey); await sut.load(); @@ -295,10 +292,11 @@ describe('Clerk singleton', () => { executionOrder.push('session.touch'); return Promise.resolve(); }); - cookieSpy.mockImplementationOnce(() => { + mockSession.getToken.mockImplementation(() => { executionOrder.push('set cookie'); - return Promise.resolve(); + return 'mocked-token'; }); + const beforeEmitMock = jest.fn().mockImplementationOnce(() => { executionOrder.push('before emit'); return Promise.resolve(); @@ -309,8 +307,8 @@ describe('Clerk singleton', () => { await waitFor(() => { expect(executionOrder).toEqual(['session.touch', 'set cookie', 'before emit']); expect(mockSession.touch).toHaveBeenCalled(); + expect(mockSession.getToken).toHaveBeenCalled(); expect((mockSession as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org-id'); - expect(cookieSpy).toBeCalledWith(mockSession); expect(beforeEmitMock).toBeCalledWith(mockSession); expect(sut.session).toMatchObject(mockSession); }); @@ -329,10 +327,6 @@ describe('Clerk singleton', () => { executionOrder.push('session.touch'); return Promise.resolve(); }); - cookieSpy.mockImplementationOnce(() => { - executionOrder.push('set cookie'); - return Promise.resolve(); - }); const beforeEmitMock = jest.fn().mockImplementationOnce(() => { executionOrder.push('before emit'); return Promise.resolve(); @@ -343,7 +337,7 @@ describe('Clerk singleton', () => { expect(executionOrder).toEqual(['session.touch', 'before emit']); expect(mockSession.touch).toHaveBeenCalled(); expect((mockSession as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org-id'); - expect(cookieSpy).not.toHaveBeenCalled(); + expect(mockSession.getToken).toBeCalled(); expect(beforeEmitMock).toBeCalledWith(mockSession); expect(sut.session).toMatchObject(mockSession); }); @@ -523,7 +517,7 @@ describe('Clerk singleton', () => { ); const sut = new Clerk(productionPublishableKey); - sut.setActive = jest.fn(({ beforeEmit }) => beforeEmit()); + sut.setActive = jest.fn(async ({ beforeEmit }) => void (beforeEmit && beforeEmit())); sut.navigate = jest.fn(); await sut.load(); await sut.signOut({ sessionId: '1', redirectUrl: '/after-sign-out' }); diff --git a/packages/clerk-js/src/core/auth/AuthCookieService.ts b/packages/clerk-js/src/core/auth/AuthCookieService.ts new file mode 100644 index 00000000000..16afda27a92 --- /dev/null +++ b/packages/clerk-js/src/core/auth/AuthCookieService.ts @@ -0,0 +1,155 @@ +import { setDevBrowserJWTInURL } from '@clerk/shared/devBrowser'; +import { is4xxError, isClerkAPIResponseError, isNetworkError } from '@clerk/shared/error'; +import type { Clerk, EnvironmentResource } from '@clerk/types'; + +import { clerkCoreErrorTokenRefreshFailed, clerkMissingDevBrowserJwt } from '../errors'; +import { eventBus, events } from '../events'; +import type { FapiClient } from '../fapiClient'; +import type { ClientUatCookieHandler } from './cookies/clientUat'; +import { createClientUatCookie } from './cookies/clientUat'; +import type { SessionCookieHandler } from './cookies/session'; +import { createSessionCookie } from './cookies/session'; +import type { DevBrowser } from './devBrowser'; +import { createDevBrowser } from './devBrowser'; +import { SessionCookiePoller } from './SessionCookiePoller'; + +// TODO(@dimkl): make AuthCookieService singleton since it handles updating cookies using a poller +// and we need to avoid updating them concurrently. +/** + * The AuthCookieService class is a service responsible to handle + * all operations and helpers required in a standard browser context + * based on the cookies to remove the dependency between cookies + * and auth from the Clerk instance. + * This service is responsible to: + * - refresh the session cookie using a poller + * - refresh the session cookie on tab visibility change + * - update the related cookies listening to the `token:update` event + * - initialize auth related cookies for development instances (eg __client_uat, __clerk_db_jwt) + * - cookie setup for production / development instances + * It also provides the following helpers: + * - isSignedOut(): check if the current user is signed-out using cookies + * - decorateUrlWithDevBrowserToken(): decorates url with auth related info (eg dev browser jwt) + * - handleUnauthenticatedDevBrowser(): resets dev browser in case of invalid dev browser + * - setEnvironment(): update cookies (eg client_uat) related to environment + */ +export class AuthCookieService { + private environment: EnvironmentResource | undefined; + private poller: SessionCookiePoller | null = null; + private clientUat: ClientUatCookieHandler; + private sessionCookie: SessionCookieHandler; + private devBrowser: DevBrowser; + + constructor(private clerk: Clerk, fapiClient: FapiClient) { + // set cookie on token update + eventBus.on(events.TokenUpdate, ({ token }) => { + this.updateSessionCookie(token && token.getRawString()); + this.setClientUatCookieForDevelopmentInstances(); + }); + + this.refreshTokenOnVisibilityChange(); + this.startPollingForToken(); + + this.clientUat = createClientUatCookie(); + this.sessionCookie = createSessionCookie(); + this.devBrowser = createDevBrowser({ + frontendApi: clerk.frontendApi, + fapiClient, + }); + } + + // TODO(@dimkl): Replace this method call with an event listener to decouple Clerk with setEnvironment + public setEnvironment(environment: EnvironmentResource) { + this.environment = environment; + this.setClientUatCookieForDevelopmentInstances(); + } + + public isSignedOut() { + if (!this.clerk.loaded) { + return this.clientUat.get() <= 0; + } + return !!this.clerk.user; + } + + public async setupDevelopment() { + await this.devBrowser.setup(); + } + + public setupProduction() { + this.devBrowser.clear(); + } + + public async handleUnauthenticatedDevBrowser() { + this.devBrowser.clear(); + await this.devBrowser.setup(); + } + + public decorateUrlWithDevBrowserToken(url: URL): URL { + const devBrowserJwt = this.devBrowser.getDevBrowserJWT(); + if (!devBrowserJwt) { + return clerkMissingDevBrowserJwt(); + } + + return setDevBrowserJWTInURL(url, devBrowserJwt); + } + + private startPollingForToken() { + if (!this.poller) { + this.poller = new SessionCookiePoller(); + } + this.poller.startPollingForSessionToken(() => this.refreshSessionToken()); + } + + private refreshTokenOnVisibilityChange() { + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + void this.refreshSessionToken(); + } + }); + } + + private async refreshSessionToken(): Promise { + if (!this.clerk.session) { + return; + } + + try { + await this.clerk.session.getToken(); + } catch (e) { + return this.handleGetTokenError(e); + } + } + + private updateSessionCookie(token: string | null) { + return token ? this.sessionCookie.set(token) : this.sessionCookie.remove(); + } + + private setClientUatCookieForDevelopmentInstances() { + if (this.environment?.isDevelopmentOrStaging() && this.inCustomDevelopmentDomain()) { + this.clientUat.set(this.clerk.client); + } + } + + private inCustomDevelopmentDomain() { + const domain = this.clerk.frontendApi.replace('clerk.', ''); + return !window.location.host.endsWith(domain); + } + + private handleGetTokenError(e: any) { + //throw if not a clerk error + if (!isClerkAPIResponseError(e)) { + clerkCoreErrorTokenRefreshFailed(e.message || e); + } + + //sign user out if a 4XX error + if (is4xxError(e)) { + void this.clerk.handleUnauthenticated(); + return; + } + + if (isNetworkError(e)) { + return; + } + + clerkCoreErrorTokenRefreshFailed(e.toString()); + } +} diff --git a/packages/clerk-js/src/core/services/authentication/SessionCookiePoller.ts b/packages/clerk-js/src/core/auth/SessionCookiePoller.ts similarity index 94% rename from packages/clerk-js/src/core/services/authentication/SessionCookiePoller.ts rename to packages/clerk-js/src/core/auth/SessionCookiePoller.ts index 16f6a5df1d5..3d4ed2fc41b 100644 --- a/packages/clerk-js/src/core/services/authentication/SessionCookiePoller.ts +++ b/packages/clerk-js/src/core/auth/SessionCookiePoller.ts @@ -1,6 +1,6 @@ import { createWorkerTimers } from '@clerk/shared'; -import { SafeLock } from '../../../utils'; +import { SafeLock } from './safeLock'; const REFRESH_SESSION_TOKEN_LOCK_KEY = 'clerk.lock.refreshSessionToken'; const INTERVAL_IN_MS = 5 * 1000; diff --git a/packages/clerk-js/src/core/__tests__/devBrowser.test.ts b/packages/clerk-js/src/core/auth/__tests__/devBrowser.test.ts similarity index 97% rename from packages/clerk-js/src/core/__tests__/devBrowser.test.ts rename to packages/clerk-js/src/core/auth/__tests__/devBrowser.test.ts index 2162649b4a5..d8849bc5a16 100644 --- a/packages/clerk-js/src/core/__tests__/devBrowser.test.ts +++ b/packages/clerk-js/src/core/auth/__tests__/devBrowser.test.ts @@ -1,5 +1,5 @@ +import type { FapiClient } from '../../fapiClient'; import { createDevBrowser } from '../devBrowser'; -import type { FapiClient } from '../fapiClient'; type RecursivePartial = { [P in keyof T]?: RecursivePartial; diff --git a/packages/clerk-js/src/utils/cookies/__tests__/getCookieDomain.test.ts b/packages/clerk-js/src/core/auth/__tests__/getCookieDomain.test.ts similarity index 100% rename from packages/clerk-js/src/utils/cookies/__tests__/getCookieDomain.test.ts rename to packages/clerk-js/src/core/auth/__tests__/getCookieDomain.test.ts diff --git a/packages/clerk-js/src/core/auth/cookies/clientUat.ts b/packages/clerk-js/src/core/auth/cookies/clientUat.ts new file mode 100644 index 00000000000..f731ce9dab8 --- /dev/null +++ b/packages/clerk-js/src/core/auth/cookies/clientUat.ts @@ -0,0 +1,57 @@ +import { createCookieHandler } from '@clerk/shared/cookie'; +import { addYears } from '@clerk/shared/date'; +import type { ClientResource } from '@clerk/types'; + +import { inCrossOriginIframe } from '../../../utils'; +import { getCookieDomain } from '../getCookieDomain'; + +const CLIENT_UAT_COOKIE_NAME = '__client_uat'; + +export type ClientUatCookieHandler = { + set: (client: ClientResource | undefined) => void; + get: () => number; +}; + +/** + * Create a long-lived JS cookie to store the client last updated_at timestamp + * for development instances (for production instance is set by FAPI). + * The cookie is used as hint from the Clerk Backend packages to identify + * if the user is authenticated or not. + */ +export const createClientUatCookie = (): ClientUatCookieHandler => { + const clientUatCookie = createCookieHandler(CLIENT_UAT_COOKIE_NAME); + + const get = (): number => { + return parseInt(clientUatCookie.get() || '0', 10); + }; + + const set = (client: ClientResource | undefined) => { + const expires = addYears(Date.now(), 1); + const sameSite = inCrossOriginIframe() ? 'None' : 'Strict'; + const secure = window.location.protocol === 'https:'; + const domain = getCookieDomain(); + + // '0' indicates the user is signed out + let val = '0'; + + if (client && client.updatedAt && client.activeSessions.length > 0) { + // truncate timestamp to seconds, since this is a unix timestamp + val = Math.floor(client.updatedAt.getTime() / 1000).toString(); + } + + // Removes any existing cookies without a domain specified to ensure the change doesn't break existing sessions. + clientUatCookie.remove(); + + return clientUatCookie.set(val, { + expires, + sameSite, + domain, + secure, + }); + }; + + return { + set, + get, + }; +}; diff --git a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts new file mode 100644 index 00000000000..d32555644b9 --- /dev/null +++ b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts @@ -0,0 +1,43 @@ +import { createCookieHandler } from '@clerk/shared/cookie'; +import { addYears } from '@clerk/shared/date'; +import { DEV_BROWSER_JWT_KEY } from '@clerk/shared/devBrowser'; + +import { inCrossOriginIframe } from '../../../utils'; + +export type DevBrowserCookieHandler = { + set: (jwt: string) => void; + get: () => string | undefined; + remove: () => void; +}; + +/** + * Create a long-lived JS cookie to store the dev browser token + * ONLY for development instances. + * The cookie is used to authenticate FAPI requests and pass + * authentication from AP to the app. + */ +export const createDevBrowserCookie = (): DevBrowserCookieHandler => { + const devBrowserCookie = createCookieHandler(DEV_BROWSER_JWT_KEY); + + const get = () => devBrowserCookie.get(); + + const set = (jwt: string) => { + const expires = addYears(Date.now(), 1); + const sameSite = inCrossOriginIframe() ? 'None' : 'Lax'; + const secure = window.location.protocol === 'https:'; + + return devBrowserCookie.set(jwt, { + expires, + sameSite, + secure, + }); + }; + + const remove = () => devBrowserCookie.remove(); + + return { + get, + set, + remove, + }; +}; diff --git a/packages/clerk-js/src/core/auth/cookies/session.ts b/packages/clerk-js/src/core/auth/cookies/session.ts new file mode 100644 index 00000000000..e36899ad7ed --- /dev/null +++ b/packages/clerk-js/src/core/auth/cookies/session.ts @@ -0,0 +1,39 @@ +import { createCookieHandler } from '@clerk/shared/cookie'; +import { addYears } from '@clerk/shared/date'; + +import { inCrossOriginIframe } from '../../../utils'; + +const SESSION_COOKIE_NAME = '__session'; + +export type SessionCookieHandler = { + set: (token: string) => void; + remove: () => void; +}; + +/** + * Create a short-lived JS cookie to store the current user JWT. + * The cookie is used by the Clerk backend SDKs to identify + * the authenticated user. + */ +export const createSessionCookie = (): SessionCookieHandler => { + const sessionCookie = createCookieHandler(SESSION_COOKIE_NAME); + + const remove = () => sessionCookie.remove(); + + const set = (token: string) => { + const expires = addYears(Date.now(), 1); + const sameSite = inCrossOriginIframe() ? 'None' : 'Lax'; + const secure = window.location.protocol === 'https:'; + + return sessionCookie.set(token, { + expires, + sameSite, + secure, + }); + }; + + return { + set, + remove, + }; +}; diff --git a/packages/clerk-js/src/core/devBrowser.ts b/packages/clerk-js/src/core/auth/devBrowser.ts similarity index 86% rename from packages/clerk-js/src/core/devBrowser.ts rename to packages/clerk-js/src/core/auth/devBrowser.ts index 41c5d42dc98..a0a71b860dd 100644 --- a/packages/clerk-js/src/core/devBrowser.ts +++ b/packages/clerk-js/src/core/auth/devBrowser.ts @@ -2,10 +2,10 @@ import { DEV_BROWSER_JWT_HEADER, extractDevBrowserJWTFromURL, setDevBrowserJWTIn import { parseErrors } from '@clerk/shared/error'; import type { ClerkAPIErrorJSON } from '@clerk/types'; -import { isDevOrStagingUrl } from '../utils'; -import { getDevBrowserCookie, removeDevBrowserCookie, setDevBrowserCookie } from '../utils/cookies/devBrowser'; -import { clerkErrorDevInitFailed } from './errors'; -import type { FapiClient } from './fapiClient'; +import { isDevOrStagingUrl } from '../../utils'; +import { clerkErrorDevInitFailed } from '../errors'; +import type { FapiClient } from '../fapiClient'; +import { createDevBrowserCookie } from './cookies/devBrowser'; export interface DevBrowser { clear(): void; @@ -25,16 +25,18 @@ export type CreateDevBrowserOptions = { }; export function createDevBrowser({ frontendApi, fapiClient }: CreateDevBrowserOptions): DevBrowser { + const devBrowserCookie = createDevBrowserCookie(); + function getDevBrowserJWT() { - return getDevBrowserCookie(); + return devBrowserCookie.get(); } function setDevBrowserJWT(jwt: string) { - setDevBrowserCookie(jwt); + devBrowserCookie.set(jwt); } function removeDevBrowserJWT() { - removeDevBrowserCookie(); + devBrowserCookie.remove(); } function clear() { @@ -69,7 +71,7 @@ export function createDevBrowser({ frontendApi, fapiClient }: CreateDevBrowserOp } // 2. If no JWT is found in the first step, check if a JWT is already available in the __clerk_db_jwt JS cookie - if (getDevBrowserCookie()) { + if (devBrowserCookie.get()) { return; } diff --git a/packages/clerk-js/src/utils/cookies/getCookieDomain.ts b/packages/clerk-js/src/core/auth/getCookieDomain.ts similarity index 100% rename from packages/clerk-js/src/utils/cookies/getCookieDomain.ts rename to packages/clerk-js/src/core/auth/getCookieDomain.ts diff --git a/packages/clerk-js/src/utils/safeLock.ts b/packages/clerk-js/src/core/auth/safeLock.ts similarity index 100% rename from packages/clerk-js/src/utils/safeLock.ts rename to packages/clerk-js/src/core/auth/safeLock.ts diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 8f5f0d7cec9..855d57a6072 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -12,7 +12,6 @@ import { noop, parsePublishableKey, proxyUrlToAbsoluteURL, - setDevBrowserJWTInURL, stripScheme, } from '@clerk/shared'; import { eventPrebuiltComponentMounted, TelemetryCollector } from '@clerk/shared/telemetry'; @@ -85,22 +84,20 @@ import { windowNavigate, } from '../utils'; import { assertNoLegacyProp } from '../utils/assertNoLegacyProp'; -import { getClientUatCookie } from '../utils/cookies/clientUat'; import { memoizeListenerCallback } from '../utils/memoizeStateListenerCallback'; import { RedirectUrls } from '../utils/redirectUrls'; +import { AuthCookieService } from './auth/AuthCookieService'; import { CLERK_SATELLITE_URL, CLERK_SYNCED, ERROR_CODES } from './constants'; -import type { DevBrowser } from './devBrowser'; -import { createDevBrowser } from './devBrowser'; import { clerkErrorInitFailed, clerkInvalidSignInUrlFormat, clerkInvalidSignInUrlOrigin, - clerkMissingDevBrowserJwt, clerkMissingProxyUrlAndDomain, clerkMissingSignInUrlAsSatellite, clerkOAuthCallbackDidNotCompleteSignInSignUp, clerkRedirectUrlIsMissingScheme, } from './errors'; +import { eventBus, events } from './events'; import type { FapiClient, FapiRequestCallback } from './fapiClient'; import { createFapiClient } from './fapiClient'; import { @@ -111,7 +108,6 @@ import { Environment, Organization, } from './resources/internal'; -import { SessionCookieService } from './services'; import { warnings } from './warnings'; export type ClerkCoreBroadcastChannelEvent = { type: 'signout' }; @@ -157,15 +153,15 @@ export class Clerk implements ClerkInterface { public telemetry: TelemetryCollector | undefined; protected internal_last_error: ClerkAPIError | null = null; + // converted to protected environment to support `updateEnvironment` type assertion + protected environment?: EnvironmentResource | null; #publishableKey: string = ''; #domain: DomainOrProxyUrl['domain']; #proxyUrl: DomainOrProxyUrl['proxyUrl']; - #authService: SessionCookieService | null = null; + #authService: AuthCookieService | null = null; #broadcastChannel: LocalStorageBroadcastChannel | null = null; #componentControls?: ReturnType | null; - #devBrowser: DevBrowser | null = null; - #environment?: EnvironmentResource | null; //@ts-expect-error with being undefined even though it's not possible - related to issue with ts and error thrower #fapiClient: FapiClient; #instanceType?: InstanceType; @@ -262,6 +258,7 @@ export class Clerk implements ClerkInterface { this.#instanceType = publishableKey.instanceType; this.#fapiClient = createFapiClient(this); + // This line is used for the piggy-backing mechanism BaseResource.clerk = this; } @@ -335,7 +332,7 @@ export class Clerk implements ClerkInterface { public openSignIn = (props?: SignInProps): void => { this.assertComponentsReady(this.#componentControls); - if (sessionExistsAndSingleSessionModeEnabled(this, this.#environment)) { + if (sessionExistsAndSingleSessionModeEnabled(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotOpenSignInOrSignUp, { code: 'cannot_render_single_session_enabled', @@ -355,7 +352,7 @@ export class Clerk implements ClerkInterface { public openSignUp = (props?: SignUpProps): void => { this.assertComponentsReady(this.#componentControls); - if (sessionExistsAndSingleSessionModeEnabled(this, this.#environment)) { + if (sessionExistsAndSingleSessionModeEnabled(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotOpenSignInOrSignUp, { code: 'cannot_render_single_session_enabled', @@ -395,7 +392,7 @@ export class Clerk implements ClerkInterface { public openOrganizationProfile = (props?: OrganizationProfileProps): void => { this.assertComponentsReady(this.#componentControls); - if (disabledOrganizationsFeature(this, this.#environment)) { + if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationProfile'), { code: 'cannot_render_organizations_disabled', @@ -423,7 +420,7 @@ export class Clerk implements ClerkInterface { public openCreateOrganization = (props?: CreateOrganizationProps): void => { this.assertComponentsReady(this.#componentControls); - if (disabledOrganizationsFeature(this, this.#environment)) { + if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('CreateOrganization'), { code: 'cannot_render_organizations_disabled', @@ -542,7 +539,7 @@ export class Clerk implements ClerkInterface { public mountOrganizationProfile = (node: HTMLDivElement, props?: OrganizationProfileProps) => { this.assertComponentsReady(this.#componentControls); - if (disabledOrganizationsFeature(this, this.#environment)) { + if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationProfile'), { code: 'cannot_render_organizations_disabled', @@ -582,7 +579,7 @@ export class Clerk implements ClerkInterface { public mountCreateOrganization = (node: HTMLDivElement, props?: CreateOrganizationProps) => { this.assertComponentsReady(this.#componentControls); - if (disabledOrganizationsFeature(this, this.#environment)) { + if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('CreateOrganization'), { code: 'cannot_render_organizations_disabled', @@ -613,7 +610,7 @@ export class Clerk implements ClerkInterface { public mountOrganizationSwitcher = (node: HTMLDivElement, props?: OrganizationSwitcherProps) => { this.assertComponentsReady(this.#componentControls); - if (disabledOrganizationsFeature(this, this.#environment)) { + if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationSwitcher'), { code: 'cannot_render_organizations_disabled', @@ -640,7 +637,7 @@ export class Clerk implements ClerkInterface { public mountOrganizationList = (node: HTMLDivElement, props?: OrganizationListProps) => { this.assertComponentsReady(this.#componentControls); - if (disabledOrganizationsFeature(this, this.#environment)) { + if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationList'), { code: 'cannot_render_organizations_disabled', @@ -744,8 +741,13 @@ export class Clerk implements ClerkInterface { newSession = this.#getSessionFromClient(newSession?.id); } - await this.#authService?.setAuthCookiesFromSession(newSession); - + // Sync __session and __client_uat to cookies using events.TokenUpdate dispatched event + // only for newSession is null since the getToken will not be executed. Since getToken + // triggers internally a events.TokenUpdate there is no need to trigger it when the newSession exists. + const token = await newSession?.getToken(); + if (!token) { + eventBus.dispatch(events.TokenUpdate, { token: null }); + } //2. If there's a beforeEmit, typically we're navigating. Emit the session as // undefined, then wait for beforeEmit to complete before emitting the new session. // When undefined, neither SignedIn nor SignedOut renders, which avoids flickers or @@ -822,12 +824,11 @@ export class Clerk implements ClerkInterface { return toURL.href; } - const devBrowserJwt = this.#devBrowser?.getDevBrowserJWT(); - if (!devBrowserJwt) { - return clerkMissingDevBrowserJwt(); + if (!this.#authService) { + return toURL.href; } - return setDevBrowserJWTInURL(toURL, devBrowserJwt).href; + return this.#authService.decorateUrlWithDevBrowserToken(toURL).href; } public buildSignInUrl(options?: SignInRedirectOptions): string { return this.#buildUrl( @@ -846,17 +847,17 @@ export class Clerk implements ClerkInterface { } public buildUserProfileUrl(): string { - if (!this.#environment || !this.#environment.displayConfig) { + if (!this.environment || !this.environment.displayConfig) { return ''; } - return this.buildUrlWithAuth(this.#environment.displayConfig.userProfileUrl); + return this.buildUrlWithAuth(this.environment.displayConfig.userProfileUrl); } public buildHomeUrl(): string { - if (!this.#environment || !this.#environment.displayConfig) { + if (!this.environment || !this.environment.displayConfig) { return ''; } - return this.buildUrlWithAuth(this.#environment.displayConfig.homeUrl); + return this.buildUrlWithAuth(this.environment.displayConfig.homeUrl); } public buildAfterSignInUrl(): string { @@ -876,17 +877,17 @@ export class Clerk implements ClerkInterface { } public buildCreateOrganizationUrl(): string { - if (!this.#environment || !this.#environment.displayConfig) { + if (!this.environment || !this.environment.displayConfig) { return ''; } - return this.buildUrlWithAuth(this.#environment.displayConfig.createOrganizationUrl); + return this.buildUrlWithAuth(this.environment.displayConfig.createOrganizationUrl); } public buildOrganizationProfileUrl(): string { - if (!this.#environment || !this.#environment.displayConfig) { + if (!this.environment || !this.environment.displayConfig) { return ''; } - return this.buildUrlWithAuth(this.#environment.displayConfig.organizationProfileUrl); + return this.buildUrlWithAuth(this.environment.displayConfig.organizationProfileUrl); } #redirectToSatellite = async (): Promise => { @@ -1022,7 +1023,7 @@ export class Clerk implements ClerkInterface { params: HandleOAuthCallbackParams, customNavigate?: (to: string) => Promise, ): Promise => { - if (!this.loaded || !this.#environment || !this.client) { + if (!this.loaded || !this.environment || !this.client) { return; } const { signIn: _signIn, signUp: _signUp } = this.client; @@ -1054,11 +1055,11 @@ export class Clerk implements ClerkInterface { navigate: (to: string) => Promise; }, ): Promise => { - if (!this.loaded || !this.#environment || !this.client) { + if (!this.loaded || !this.environment || !this.client) { return; } - const { displayConfig } = this.#environment; + const { displayConfig } = this.environment; const { firstFactorVerification } = signIn; const { externalAccount } = signUp.verifications; const su = { @@ -1247,7 +1248,7 @@ export class Clerk implements ClerkInterface { params: HandleOAuthCallbackParams = {}, customNavigate?: (to: string) => Promise, ): Promise => { - if (!this.loaded || !this.#environment || !this.client) { + if (!this.loaded || !this.environment || !this.client) { return; } const { signIn, signUp } = this.client; @@ -1306,7 +1307,7 @@ export class Clerk implements ClerkInterface { customNavigate, unsafeMetadata, }: AuthenticateWithMetamaskParams = {}): Promise => { - if (!this.client || !this.#environment) { + if (!this.client || !this.environment) { return; } @@ -1352,8 +1353,8 @@ export class Clerk implements ClerkInterface { public getOrganization = async (organizationId: string): Promise => Organization.get(organizationId); - public updateEnvironment(environment: EnvironmentResource) { - this.#environment = environment; + public updateEnvironment(environment: EnvironmentResource): asserts this is { environment: EnvironmentResource } { + this.environment = environment; this.#authService?.setEnvironment(environment); } @@ -1393,16 +1394,16 @@ export class Clerk implements ClerkInterface { }; get __unstable__environment(): EnvironmentResource | null | undefined { - return this.#environment; + return this.environment; } // TODO: Fix this properly // eslint-disable-next-line @typescript-eslint/require-await __unstable__setEnvironment = async (env: EnvironmentJSON) => { - this.#environment = new Environment(env); + this.environment = new Environment(env); if (Clerk.mountComponentRenderer) { - this.#componentControls = Clerk.mountComponentRenderer(this, this.#environment, this.#options); + this.#componentControls = Clerk.mountComponentRenderer(this, this.environment, this.#options); } }; @@ -1460,7 +1461,7 @@ export class Clerk implements ClerkInterface { return false; } - return getClientUatCookie() <= 0; + return !!this.#authService?.isSignedOut(); }; #shouldRedirectToSatellite = (): boolean => { @@ -1516,18 +1517,10 @@ export class Clerk implements ClerkInterface { }; #loadInStandardBrowser = async (): Promise => { - /** - * 1. Create the devBrowser. - * At this point the devBrowser is not yet setup, but its API is ready for use - * Multi-domain SSO needs this handler to be initiated - */ - this.#devBrowser = createDevBrowser({ - frontendApi: this.frontendApi, - fapiClient: this.#fapiClient, - }); + this.#authService = new AuthCookieService(this, this.#fapiClient); /** - * 2. Multi-domain SSO handling + * 1. Multi-domain SSO handling * If needed the app will attempt to sync with another app hosted in a different domain in order to acquire a session * - for development instances it populates dev browser JWT and `devBrowserHandler.setup()` should not have run. */ @@ -1539,19 +1532,19 @@ export class Clerk implements ClerkInterface { } /** - * 3. Setup dev browser. + * 2. Setup dev browser. * This is not needed for production instances hence the .clear() * At this point we have already attempted to pre-populate devBrowser with a fresh JWT, if Step 2 was successful this will not be overwritten. * For multi-domain we want to avoid retrieving a fresh JWT from FAPI, and we need to get the token as a result of multi-domain session syncing. */ if (this.#instanceType === 'production') { - this.#devBrowser.clear(); + this.#authService?.setupProduction(); } else { - await this.#devBrowser.setup(); + await this.#authService?.setupDevelopment(); } /** - * 4. If the app is considered a primary domain and is in the middle of the sync/link flow, interact the loading of Clerk and redirect back to the satellite app + * 3. If the app is considered a primary domain and is in the middle of the sync/link flow, interact the loading of Clerk and redirect back to the satellite app * Initially step 2 and 4 were considered one but for step 2 we need devBrowserHandler.setup() to not have run and step 4 requires a valid dev browser JWT */ if (this.#shouldRedirectToSatellite()) { @@ -1560,26 +1553,24 @@ export class Clerk implements ClerkInterface { } /** - * 5. Continue with clerk-js setup. + * 4. Continue with clerk-js setup. * - Fetch & update environment * - Fetch & update client * - Mount components */ - this.#authService = new SessionCookieService(this); this.#pageLifecycle = createPageLifecycle(); - const isInAccountsHostedPages = isDevAccountPortalOrigin(window?.location.hostname); - this.#broadcastChannel = new LocalStorageBroadcastChannel('clerk'); this.#setupListeners(); + const isInAccountsHostedPages = isDevAccountPortalOrigin(window?.location.hostname); + const shouldTouchEnv = this.#instanceType === 'development' && !isInAccountsHostedPages; + let retries = 0; while (retries < 2) { retries++; try { - const shouldTouchEnv = this.#instanceType === 'development' && !isInAccountsHostedPages; - const [environment, client] = await Promise.all([ Environment.getInstance().fetch({ touch: shouldTouchEnv }), Client.getInstance().fetch(), @@ -1596,14 +1587,13 @@ export class Clerk implements ClerkInterface { } if (Clerk.mountComponentRenderer) { - this.#componentControls = Clerk.mountComponentRenderer(this, this.#environment as Environment, this.#options); + this.#componentControls = Clerk.mountComponentRenderer(this, this.environment as Environment, this.#options); } break; } catch (err) { if (isError(err, 'dev_browser_unauthenticated')) { - this.#devBrowser.clear(); - await this.#devBrowser.setup(); + await this.#authService.handleUnauthenticatedDevBrowser(); } else if (!isValidBrowserOnline()) { console.warn(err); return false; @@ -1629,13 +1619,13 @@ export class Clerk implements ClerkInterface { Client.getInstance().fetch(), ]); - this.#environment = environment; this.updateClient(client); + this.updateEnvironment(environment); // TODO: Add an auth service also for non standard browsers that will poll for the __session JWT but won't use cookies if (Clerk.mountComponentRenderer) { - this.#componentControls = Clerk.mountComponentRenderer(this, this.#environment, this.#options); + this.#componentControls = Clerk.mountComponentRenderer(this, this.environment, this.#options); } return true; @@ -1748,10 +1738,10 @@ export class Clerk implements ClerkInterface { options: RedirectOptions, _initValues?: Record, ): string => { - if (!key || !this.loaded || !this.#environment || !this.#environment.displayConfig) { + if (!key || !this.loaded || !this.environment || !this.environment.displayConfig) { return ''; } - const signInOrUpUrl = this.#options[key] || this.#environment.displayConfig[key]; + const signInOrUpUrl = this.#options[key] || this.environment.displayConfig[key]; const redirectUrls = new RedirectUrls(this.#options, options).toSearchParams(); const initValues = new URLSearchParams(_initValues || {}); const url = buildURL({ base: signInOrUpUrl, hashSearchParams: [initValues, redirectUrls] }, { stringify: true }); @@ -1777,9 +1767,9 @@ export class Clerk implements ClerkInterface { } const userSignedIn = this.session; - const signInUrl = this.#options.signInUrl || this.#environment?.displayConfig.signInUrl; + const signInUrl = this.#options.signInUrl || this.environment?.displayConfig.signInUrl; const referrerIsSignInUrl = signInUrl && window.location.href.startsWith(signInUrl); - const signUpUrl = this.#options.signUpUrl || this.#environment?.displayConfig.signUpUrl; + const signUpUrl = this.#options.signUpUrl || this.environment?.displayConfig.signUpUrl; const referrerIsSignUpUrl = signUpUrl && window.location.href.startsWith(signUpUrl); // don't redirect if user is not signed in and referrer is sign in/up url diff --git a/packages/clerk-js/src/core/services/authentication/SessionCookieService.ts b/packages/clerk-js/src/core/services/authentication/SessionCookieService.ts deleted file mode 100644 index da024e4e33c..00000000000 --- a/packages/clerk-js/src/core/services/authentication/SessionCookieService.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { is4xxError, isClerkAPIResponseError, isNetworkError } from '@clerk/shared/error'; -import type { Clerk, EnvironmentResource, SessionResource, TokenResource } from '@clerk/types'; - -import { inBrowser } from '../../../utils'; -import { setClientUatCookie } from '../../../utils/cookies/clientUat'; -import { removeSessionCookie, setSessionCookie } from '../../../utils/cookies/session'; -import { clerkCoreErrorTokenRefreshFailed } from '../../errors'; -import { eventBus, events } from '../../events'; -import { SessionCookiePoller } from './SessionCookiePoller'; - -export class SessionCookieService { - private environment: EnvironmentResource | undefined; - private poller: SessionCookiePoller | null = null; - - constructor(private clerk: Clerk) { - // set cookie on token update - eventBus.on(events.TokenUpdate, ({ token }) => { - this.updateSessionCookie(token?.getRawString()); - }); - - this.refreshTokenOnVisibilityChange(); - this.startPollingForToken(); - } - - public setEnvironment(environment: EnvironmentResource) { - this.environment = environment; - this.setClientUatCookieForDevelopmentInstances(); - } - - public async setAuthCookiesFromSession(session: SessionResource | undefined | null): Promise { - this.updateSessionCookie(await session?.getToken()); - this.setClientUatCookieForDevelopmentInstances(); - } - - private startPollingForToken() { - if (!this.poller) { - this.poller = new SessionCookiePoller(); - } - this.poller.startPollingForSessionToken(() => this.refreshSessionToken()); - } - - private refreshTokenOnVisibilityChange() { - if (!inBrowser()) { - return; - } - - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible') { - void this.refreshSessionToken(); - } - }); - } - - private async refreshSessionToken(): Promise { - if (!inBrowser()) { - return; - } - - if (!this.clerk.session) { - return; - } - - try { - this.updateSessionCookie(await this.clerk.session?.getToken()); - } catch (e) { - return this.handleGetTokenError(e); - } - } - - private updateSessionCookie(token: TokenResource | string | undefined | null) { - const rawToken = typeof token === 'string' ? token : token?.getRawString(); - - if (rawToken) { - return setSessionCookie(rawToken); - } - return removeSessionCookie(); - } - - private setClientUatCookieForDevelopmentInstances() { - if (this.environment && this.environment.isDevelopmentOrStaging() && this.inCustomDevelopmentDomain()) { - setClientUatCookie(this.clerk.client); - } - } - - private inCustomDevelopmentDomain() { - const domain = this.clerk.frontendApi.replace('clerk.', ''); - return !window.location.host.endsWith(domain); - } - - private handleGetTokenError(e: any) { - //throw if not a clerk error - if (!isClerkAPIResponseError(e)) { - clerkCoreErrorTokenRefreshFailed(e.message || e); - } - - //sign user out if a 4XX error - if (is4xxError(e)) { - void this.clerk.handleUnauthenticated(); - return; - } - - if (isNetworkError(e)) { - return; - } - - clerkCoreErrorTokenRefreshFailed(e.toString()); - } -} diff --git a/packages/clerk-js/src/core/services/index.ts b/packages/clerk-js/src/core/services/index.ts deleted file mode 100644 index ff1ae73c0a0..00000000000 --- a/packages/clerk-js/src/core/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './authentication/SessionCookieService'; diff --git a/packages/clerk-js/src/utils/cookies/clientUat.ts b/packages/clerk-js/src/utils/cookies/clientUat.ts deleted file mode 100644 index 3c672bee5f6..00000000000 --- a/packages/clerk-js/src/utils/cookies/clientUat.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createCookieHandler } from '@clerk/shared/cookie'; -import { addYears } from '@clerk/shared/date'; -import type { ClientResource } from '@clerk/types'; - -import { inCrossOriginIframe } from '../../utils'; -import { getCookieDomain } from './getCookieDomain'; - -const CLIENT_UAT_COOKIE_NAME = '__client_uat'; - -export const clientUatCookie = createCookieHandler(CLIENT_UAT_COOKIE_NAME); - -export const getClientUatCookie = (): number => { - return parseInt(clientUatCookie.get() || '0', 10); -}; - -export const setClientUatCookie = (client: ClientResource | undefined) => { - const expires = addYears(Date.now(), 1); - const sameSite = inCrossOriginIframe() ? 'None' : 'Strict'; - const secure = window.location.protocol === 'https:'; - const domain = getCookieDomain(); - - // '0' indicates the user is signed out - let val = '0'; - - if (client && client.updatedAt && client.activeSessions.length > 0) { - // truncate timestamp to seconds, since this is a unix timestamp - val = Math.floor(client.updatedAt.getTime() / 1000).toString(); - } - - // Removes any existing cookies without a domain specified to ensure the change doesn't break existing sessions. - clientUatCookie.remove(); - - return clientUatCookie.set(val, { - expires, - sameSite, - domain, - secure, - }); -}; diff --git a/packages/clerk-js/src/utils/cookies/devBrowser.ts b/packages/clerk-js/src/utils/cookies/devBrowser.ts deleted file mode 100644 index dfd5adace48..00000000000 --- a/packages/clerk-js/src/utils/cookies/devBrowser.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createCookieHandler } from '@clerk/shared/cookie'; -import { addYears } from '@clerk/shared/date'; -import { DEV_BROWSER_JWT_KEY } from '@clerk/shared/devBrowser'; - -import { inCrossOriginIframe } from '../../utils'; - -export const devBrowserCookie = createCookieHandler(DEV_BROWSER_JWT_KEY); - -export const getDevBrowserCookie = () => devBrowserCookie.get(); - -export const setDevBrowserCookie = (jwt: string) => { - const expires = addYears(Date.now(), 1); - const sameSite = inCrossOriginIframe() ? 'None' : 'Lax'; - const secure = window.location.protocol === 'https:'; - - return devBrowserCookie.set(jwt, { - expires, - sameSite, - secure, - }); -}; - -export const removeDevBrowserCookie = () => devBrowserCookie.remove(); diff --git a/packages/clerk-js/src/utils/cookies/session.ts b/packages/clerk-js/src/utils/cookies/session.ts deleted file mode 100644 index edb6afb888d..00000000000 --- a/packages/clerk-js/src/utils/cookies/session.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createCookieHandler } from '@clerk/shared/cookie'; -import { addYears } from '@clerk/shared/date'; - -import { inCrossOriginIframe } from '../../utils'; - -const SESSION_COOKIE_NAME = '__session'; - -/** - * - * This is a short-lived JS cookie used to store the current user JWT. - * - */ -export const sessionCookie = createCookieHandler(SESSION_COOKIE_NAME); - -export const removeSessionCookie = () => sessionCookie.remove(); - -export const setSessionCookie = (token: string) => { - const expires = addYears(Date.now(), 1); - const sameSite = inCrossOriginIframe() ? 'None' : 'Lax'; - const secure = window.location.protocol === 'https:'; - - return sessionCookie.set(token, { - expires, - sameSite, - secure, - }); -}; diff --git a/packages/clerk-js/src/utils/index.ts b/packages/clerk-js/src/utils/index.ts index ddbfcb8f3b8..6799f50b67e 100644 --- a/packages/clerk-js/src/utils/index.ts +++ b/packages/clerk-js/src/utils/index.ts @@ -17,7 +17,6 @@ export * from './props'; export * from './queryStateParams'; export * from './querystring'; export * from './runtime'; -export * from './safeLock'; export * from './url'; export * from './web3'; export * from './windowNavigate'; diff --git a/packages/shared/src/cookie.ts b/packages/shared/src/cookie.ts index ee87cf233d9..87679a095a9 100644 --- a/packages/shared/src/cookie.ts +++ b/packages/shared/src/cookie.ts @@ -13,8 +13,8 @@ export function createCookieHandler(cookieName: string) { /** * Setting a cookie will use some defaults such as path being set to "/". */ - set(newValue: string, options: Cookies.CookieAttributes = {}) { - return Cookies.set(cookieName, newValue, options); + set(newValue: string, options: Cookies.CookieAttributes = {}): void { + Cookies.set(cookieName, newValue, options); }, /** * On removing a cookie, you have to pass the exact same path/domain attributes used to set it initially