Skip to content

Commit ef23574

Browse files
committed
chore(clerk-js): Refactor SessionCookieService and auth/cookies handlers for consistency
There is also a fix for duplicate TokenUpdate event and for cleaning the token in sign-out flow.
1 parent 803ee6f commit ef23574

File tree

6 files changed

+184
-129
lines changed

6 files changed

+184
-129
lines changed

packages/clerk-js/src/core/auth/SessionCookieService.ts

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,74 @@
1+
import { setDevBrowserJWTInURL } from '@clerk/shared/devBrowser';
12
import { is4xxError, isClerkAPIResponseError, isNetworkError } from '@clerk/shared/error';
2-
import type { Clerk, EnvironmentResource, SessionResource, TokenResource } from '@clerk/types';
3+
import type { Clerk, EnvironmentResource } from '@clerk/types';
34

4-
import { inBrowser } from '../../utils';
5-
import { clerkCoreErrorTokenRefreshFailed } from '../errors';
5+
import { clerkCoreErrorTokenRefreshFailed, clerkMissingDevBrowserJwt } from '../errors';
66
import { eventBus, events } from '../events';
7-
import { setClientUatCookie } from './cookies/clientUat';
8-
import { removeSessionCookie, setSessionCookie } from './cookies/session';
7+
import type { FapiClient } from '../fapiClient';
8+
import type { ClientUatCookieHandler } from './cookies/clientUat';
9+
import { createClientUatCookie } from './cookies/clientUat';
10+
import type { SessionCookieHandler } from './cookies/session';
11+
import { createSessionCookie } from './cookies/session';
12+
import type { DevBrowser } from './devBrowser';
13+
import { createDevBrowser } from './devBrowser';
914
import { SessionCookiePoller } from './SessionCookiePoller';
1015

16+
// TODO: make SessionCookieService singleton since it handles updating cookies using a poller
17+
// and we need to avoid updating them concurrently.
1118
export class SessionCookieService {
1219
private environment: EnvironmentResource | undefined;
1320
private poller: SessionCookiePoller | null = null;
21+
private clientUat: ClientUatCookieHandler;
22+
private sessionCookie: SessionCookieHandler;
23+
private devBrowser: DevBrowser;
1424

15-
constructor(private clerk: Clerk) {
25+
constructor(private clerk: Clerk, fapiClient: FapiClient) {
1626
// set cookie on token update
1727
eventBus.on(events.TokenUpdate, ({ token }) => {
18-
this.updateSessionCookie(token?.getRawString());
28+
this.updateSessionCookie(token && token.getRawString());
29+
this.setClientUatCookieForDevelopmentInstances();
1930
});
2031

2132
this.refreshTokenOnVisibilityChange();
2233
this.startPollingForToken();
34+
35+
this.clientUat = createClientUatCookie();
36+
this.sessionCookie = createSessionCookie();
37+
this.devBrowser = createDevBrowser({
38+
frontendApi: clerk.frontendApi,
39+
fapiClient,
40+
});
2341
}
2442

2543
public setEnvironment(environment: EnvironmentResource) {
2644
this.environment = environment;
2745
this.setClientUatCookieForDevelopmentInstances();
2846
}
2947

30-
public async setAuthCookiesFromSession(session: SessionResource | undefined | null): Promise<void> {
31-
this.updateSessionCookie(await session?.getToken());
32-
this.setClientUatCookieForDevelopmentInstances();
48+
public isSignedOut() {
49+
return this.clientUat.get() <= 0;
50+
}
51+
52+
public async setupDevelopment() {
53+
await this.devBrowser.setup();
54+
}
55+
56+
public setupProduction() {
57+
this.devBrowser.clear();
58+
}
59+
60+
public async handleUnauthenticatedDevBrowser() {
61+
this.devBrowser.clear();
62+
await this.devBrowser.setup();
63+
}
64+
65+
public urlWithAuth(url: URL): URL {
66+
const devBrowserJwt = this.devBrowser.getDevBrowserJWT();
67+
if (!devBrowserJwt) {
68+
return clerkMissingDevBrowserJwt();
69+
}
70+
71+
return setDevBrowserJWTInURL(url, devBrowserJwt);
3372
}
3473

3574
private startPollingForToken() {
@@ -40,10 +79,6 @@ export class SessionCookieService {
4079
}
4180

4281
private refreshTokenOnVisibilityChange() {
43-
if (!inBrowser()) {
44-
return;
45-
}
46-
4782
document.addEventListener('visibilitychange', () => {
4883
if (document.visibilityState === 'visible') {
4984
void this.refreshSessionToken();
@@ -52,33 +87,24 @@ export class SessionCookieService {
5287
}
5388

5489
private async refreshSessionToken(): Promise<void> {
55-
if (!inBrowser()) {
56-
return;
57-
}
58-
5990
if (!this.clerk.session) {
6091
return;
6192
}
6293

6394
try {
64-
this.updateSessionCookie(await this.clerk.session?.getToken());
95+
await this.clerk.session.getToken();
6596
} catch (e) {
6697
return this.handleGetTokenError(e);
6798
}
6899
}
69100

70-
private updateSessionCookie(token: TokenResource | string | undefined | null) {
71-
const rawToken = typeof token === 'string' ? token : token?.getRawString();
72-
73-
if (rawToken) {
74-
return setSessionCookie(rawToken);
75-
}
76-
return removeSessionCookie();
101+
private updateSessionCookie(token: string | null) {
102+
return token ? this.sessionCookie.set(token) : this.sessionCookie.remove();
77103
}
78104

79105
private setClientUatCookieForDevelopmentInstances() {
80-
if (this.environment && this.environment.isDevelopmentOrStaging() && this.inCustomDevelopmentDomain()) {
81-
setClientUatCookie(this.clerk.client);
106+
if (this.environment?.isDevelopmentOrStaging() && this.inCustomDevelopmentDomain()) {
107+
this.clientUat.set(this.clerk.client);
82108
}
83109
}
84110

packages/clerk-js/src/core/auth/cookies/clientUat.ts

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,45 @@ import { getCookieDomain } from '../getCookieDomain';
77

88
const CLIENT_UAT_COOKIE_NAME = '__client_uat';
99

10-
export const clientUatCookie = createCookieHandler(CLIENT_UAT_COOKIE_NAME);
11-
12-
export const getClientUatCookie = (): number => {
13-
return parseInt(clientUatCookie.get() || '0', 10);
10+
export type ClientUatCookieHandler = {
11+
set: (client: ClientResource | undefined) => void;
12+
get: () => number;
1413
};
1514

16-
export const setClientUatCookie = (client: ClientResource | undefined) => {
17-
const expires = addYears(Date.now(), 1);
18-
const sameSite = inCrossOriginIframe() ? 'None' : 'Strict';
19-
const secure = window.location.protocol === 'https:';
20-
const domain = getCookieDomain();
21-
22-
// '0' indicates the user is signed out
23-
let val = '0';
24-
25-
if (client && client.updatedAt && client.activeSessions.length > 0) {
26-
// truncate timestamp to seconds, since this is a unix timestamp
27-
val = Math.floor(client.updatedAt.getTime() / 1000).toString();
28-
}
29-
30-
// Removes any existing cookies without a domain specified to ensure the change doesn't break existing sessions.
31-
clientUatCookie.remove();
32-
33-
return clientUatCookie.set(val, {
34-
expires,
35-
sameSite,
36-
domain,
37-
secure,
38-
});
15+
export const createClientUatCookie = (): ClientUatCookieHandler => {
16+
const clientUatCookie = createCookieHandler(CLIENT_UAT_COOKIE_NAME);
17+
18+
const get = (): number => {
19+
return parseInt(clientUatCookie.get() || '0', 10);
20+
};
21+
22+
const set = (client: ClientResource | undefined) => {
23+
const expires = addYears(Date.now(), 1);
24+
const sameSite = inCrossOriginIframe() ? 'None' : 'Strict';
25+
const secure = window.location.protocol === 'https:';
26+
const domain = getCookieDomain();
27+
28+
// '0' indicates the user is signed out
29+
let val = '0';
30+
31+
if (client && client.updatedAt && client.activeSessions.length > 0) {
32+
// truncate timestamp to seconds, since this is a unix timestamp
33+
val = Math.floor(client.updatedAt.getTime() / 1000).toString();
34+
}
35+
36+
// Removes any existing cookies without a domain specified to ensure the change doesn't break existing sessions.
37+
clientUatCookie.remove();
38+
39+
return clientUatCookie.set(val, {
40+
expires,
41+
sameSite,
42+
domain,
43+
secure,
44+
});
45+
};
46+
47+
return {
48+
set,
49+
get,
50+
};
3951
};

packages/clerk-js/src/core/auth/cookies/devBrowser.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,34 @@ import { DEV_BROWSER_JWT_KEY } from '@clerk/shared/devBrowser';
44

55
import { inCrossOriginIframe } from '../../../utils';
66

7-
export const devBrowserCookie = createCookieHandler(DEV_BROWSER_JWT_KEY);
7+
export type DevBrowserCookieHandler = {
8+
set: (jwt: string) => void;
9+
get: () => string | undefined;
10+
remove: () => void;
11+
};
812

9-
export const getDevBrowserCookie = () => devBrowserCookie.get();
13+
export const createDevBrowserCookie = (): DevBrowserCookieHandler => {
14+
const devBrowserCookie = createCookieHandler(DEV_BROWSER_JWT_KEY);
1015

11-
export const setDevBrowserCookie = (jwt: string) => {
12-
const expires = addYears(Date.now(), 1);
13-
const sameSite = inCrossOriginIframe() ? 'None' : 'Lax';
14-
const secure = window.location.protocol === 'https:';
16+
const get = () => devBrowserCookie.get();
1517

16-
return devBrowserCookie.set(jwt, {
17-
expires,
18-
sameSite,
19-
secure,
20-
});
21-
};
18+
const set = (jwt: string) => {
19+
const expires = addYears(Date.now(), 1);
20+
const sameSite = inCrossOriginIframe() ? 'None' : 'Lax';
21+
const secure = window.location.protocol === 'https:';
22+
23+
return devBrowserCookie.set(jwt, {
24+
expires,
25+
sameSite,
26+
secure,
27+
});
28+
};
2229

23-
export const removeDevBrowserCookie = () => devBrowserCookie.remove();
30+
const remove = () => devBrowserCookie.remove();
31+
32+
return {
33+
get,
34+
set,
35+
remove,
36+
};
37+
};

packages/clerk-js/src/core/auth/cookies/session.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,35 @@ import { inCrossOriginIframe } from '../../../utils';
55

66
const SESSION_COOKIE_NAME = '__session';
77

8-
/**
9-
*
10-
* This is a short-lived JS cookie used to store the current user JWT.
11-
*
12-
*/
13-
export const sessionCookie = createCookieHandler(SESSION_COOKIE_NAME);
14-
15-
export const removeSessionCookie = () => sessionCookie.remove();
16-
17-
export const setSessionCookie = (token: string) => {
18-
const expires = addYears(Date.now(), 1);
19-
const sameSite = inCrossOriginIframe() ? 'None' : 'Lax';
20-
const secure = window.location.protocol === 'https:';
21-
22-
return sessionCookie.set(token, {
23-
expires,
24-
sameSite,
25-
secure,
26-
});
8+
export type SessionCookieHandler = {
9+
set: (token: string) => void;
10+
remove: () => void;
11+
};
12+
13+
export const createSessionCookie = (): SessionCookieHandler => {
14+
/**
15+
*
16+
* This is a short-lived JS cookie used to store the current user JWT.
17+
*
18+
*/
19+
const sessionCookie = createCookieHandler(SESSION_COOKIE_NAME);
20+
21+
const remove = () => sessionCookie.remove();
22+
23+
const set = (token: string) => {
24+
const expires = addYears(Date.now(), 1);
25+
const sameSite = inCrossOriginIframe() ? 'None' : 'Lax';
26+
const secure = window.location.protocol === 'https:';
27+
28+
return sessionCookie.set(token, {
29+
expires,
30+
sameSite,
31+
secure,
32+
});
33+
};
34+
35+
return {
36+
set,
37+
remove,
38+
};
2739
};

packages/clerk-js/src/core/auth/devBrowser.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { ClerkAPIErrorJSON } from '@clerk/types';
55
import { isDevOrStagingUrl } from '../../utils';
66
import { clerkErrorDevInitFailed } from '../errors';
77
import type { FapiClient } from '../fapiClient';
8-
import { getDevBrowserCookie, removeDevBrowserCookie, setDevBrowserCookie } from './cookies/devBrowser';
8+
import { createDevBrowserCookie } from './cookies/devBrowser';
99

1010
export interface DevBrowser {
1111
clear(): void;
@@ -25,16 +25,18 @@ export type CreateDevBrowserOptions = {
2525
};
2626

2727
export function createDevBrowser({ frontendApi, fapiClient }: CreateDevBrowserOptions): DevBrowser {
28+
const devBrowserCookie = createDevBrowserCookie();
29+
2830
function getDevBrowserJWT() {
29-
return getDevBrowserCookie();
31+
return devBrowserCookie.get();
3032
}
3133

3234
function setDevBrowserJWT(jwt: string) {
33-
setDevBrowserCookie(jwt);
35+
devBrowserCookie.set(jwt);
3436
}
3537

3638
function removeDevBrowserJWT() {
37-
removeDevBrowserCookie();
39+
devBrowserCookie.remove();
3840
}
3941

4042
function clear() {
@@ -69,7 +71,7 @@ export function createDevBrowser({ frontendApi, fapiClient }: CreateDevBrowserOp
6971
}
7072

7173
// 2. If no JWT is found in the first step, check if a JWT is already available in the __clerk_db_jwt JS cookie
72-
if (getDevBrowserCookie()) {
74+
if (devBrowserCookie.get()) {
7375
return;
7476
}
7577

0 commit comments

Comments
 (0)