Skip to content

Commit 7955e9d

Browse files
brkalowclaude
andauthored
chore(clerk-js): Backport CHIPS removal to release/core-2 (#7946)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 40842e6 commit 7955e9d

16 files changed

Lines changed: 159 additions & 66 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@clerk/clerk-js": patch
3+
"@clerk/shared": patch
4+
---
5+
6+
Remove CHIPS build variant and use `partitioned_cookies` environment flag from the Clerk API to control partitioned cookie behavior at runtime.

packages/clerk-js/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
"bundlewatch:fix": "node bundlewatch-fix.mjs",
4343
"clean": "rimraf ./dist",
4444
"dev": "rspack serve --config rspack.config.js",
45-
"dev:chips": "rspack serve --config rspack.config.js --env variant=\"clerk.chips.browser\"",
4645
"dev:headless": "rspack serve --config rspack.config.js --env variant=\"clerk.headless.browser\"",
4746
"dev:origin": "rspack serve --config rspack.config.js --env devOrigin=http://localhost:${PORT:-4000}",
4847
"dev:sandbox": "rspack serve --config rspack.config.js --env devOrigin=http://localhost:${PORT:-4000} --env sandbox=1",

packages/clerk-js/rspack.config.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const variants = {
1616
clerkHeadless: 'clerk.headless',
1717
clerkHeadlessBrowser: 'clerk.headless.browser',
1818
clerkLegacyBrowser: 'clerk.legacy.browser',
19-
clerkCHIPS: 'clerk.chips.browser',
2019
};
2120

2221
const variantToSourceFile = {
@@ -26,7 +25,6 @@ const variantToSourceFile = {
2625
[variants.clerkHeadless]: './src/index.headless.ts',
2726
[variants.clerkHeadlessBrowser]: './src/index.headless.browser.ts',
2827
[variants.clerkLegacyBrowser]: './src/index.legacy.browser.ts',
29-
[variants.clerkCHIPS]: './src/index.browser.ts',
3028
};
3129

3230
/**
@@ -58,7 +56,6 @@ const common = ({ mode, variant, disableRHC = false }) => {
5856
*/
5957
__BUILD_FLAG_KEYLESS_UI__: isDevelopment(mode),
6058
__BUILD_DISABLE_RHC__: JSON.stringify(disableRHC),
61-
__BUILD_VARIANT_CHIPS__: variant === variants.clerkCHIPS,
6259
}),
6360
new rspack.EnvironmentPlugin({
6461
CLERK_ENV: mode,
@@ -447,13 +444,6 @@ const prodConfig = ({ mode, env, analysis }) => {
447444
// externalsForHeadless(),
448445
);
449446

450-
const clerkCHIPS = merge(
451-
entryForVariant(variants.clerkCHIPS),
452-
common({ mode, variant: variants.clerkCHIPS }),
453-
commonForProd(),
454-
commonForProdChunked(),
455-
);
456-
457447
const clerkEsm = merge(
458448
entryForVariant(variants.clerk),
459449
common({ mode, variant: variants.clerk }),
@@ -567,7 +557,6 @@ const prodConfig = ({ mode, env, analysis }) => {
567557
clerkLegacyBrowser,
568558
clerkHeadless,
569559
clerkHeadlessBrowser,
570-
clerkCHIPS,
571560
clerkEsm,
572561
clerkEsmNoRHC,
573562
clerkCjs,
@@ -670,11 +659,6 @@ const devConfig = ({ mode, env }) => {
670659
commonForDev(),
671660
// externalsForHeadless(),
672661
),
673-
[variants.clerkCHIPS]: merge(
674-
entryForVariant(variants.clerkCHIPS),
675-
common({ mode, variant: variants.clerkCHIPS }),
676-
commonForDev(),
677-
),
678662
};
679663

680664
if (!entryToConfigMap[variant]) {

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { debugLogger } from '@/utils/debug';
1212
import { clerkMissingDevBrowserJwt } from '../errors';
1313
import { eventBus, events } from '../events';
1414
import type { FapiClient } from '../fapiClient';
15+
import { Environment } from '../resources/Environment';
1516
import { createActiveContextCookie } from './cookies/activeContext';
1617
import type { ClientUatCookieHandler } from './cookies/clientUat';
1718
import { createClientUatCookie } from './cookies/clientUat';
@@ -74,16 +75,27 @@ export class AuthCookieService {
7475

7576
eventBus.on(events.UserSignOut, () => this.handleSignOut());
7677

78+
// After Environment resolves, re-write dev browser cookies with correct
79+
// partitioned attributes. Dev browser cookies are initially written before
80+
// Environment is fetched, so they may have stale attributes.
81+
eventBus.on(events.EnvironmentUpdate, () => {
82+
this.devBrowser.refreshCookies();
83+
});
84+
7785
this.refreshTokenOnFocus();
7886
this.startPollingForToken();
7987

80-
this.clientUat = createClientUatCookie(cookieSuffix);
81-
this.sessionCookie = createSessionCookie(cookieSuffix);
88+
const cookieOptions = {
89+
usePartitionedCookies: () => Environment.getInstance().partitionedCookies,
90+
};
91+
this.clientUat = createClientUatCookie(cookieSuffix, cookieOptions);
92+
this.sessionCookie = createSessionCookie(cookieSuffix, cookieOptions);
8293
this.activeCookie = createActiveContextCookie();
8394
this.devBrowser = createDevBrowser({
8495
frontendApi: clerk.frontendApi,
8596
fapiClient,
8697
cookieSuffix,
98+
cookieOptions,
8799
});
88100
}
89101

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ describe('Thrown errors', () => {
5050
const devBrowserHandler = createDevBrowser({
5151
frontendApi: 'white-koala-42.clerk.accounts.dev',
5252
fapiClient: mockFapiClient,
53-
publishableKey: 'pk_test_d2hpdGUta29hbGEtNDIuY2xlcmsuYWNjb3VudHMuZGV2JA',
53+
cookieSuffix: 'test-suffix',
54+
cookieOptions: { usePartitionedCookies: () => false },
5455
});
5556

5657
await expect(devBrowserHandler.setup()).rejects.toThrow(

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe('createClientUatCookie', () => {
1919
const mockCookieSuffix = 'test-suffix';
2020
const mockExpires = new Date('2024-12-31');
2121
const mockDomain = 'test.domain';
22+
const defaultOptions = { usePartitionedCookies: () => false };
2223
const mockSet = vi.fn();
2324
const mockRemove = vi.fn();
2425
const mockGet = vi.fn();
@@ -39,14 +40,14 @@ describe('createClientUatCookie', () => {
3940
});
4041

4142
it('should create both suffixed and non-suffixed cookie handlers', () => {
42-
createClientUatCookie(mockCookieSuffix);
43+
createClientUatCookie(mockCookieSuffix, defaultOptions);
4344
expect(createCookieHandler).toHaveBeenCalledTimes(2);
4445
expect(createCookieHandler).toHaveBeenCalledWith('__client_uat');
4546
expect(createCookieHandler).toHaveBeenCalledWith('__client_uat_test-suffix');
4647
});
4748

4849
it('should set cookies with correct parameters in non-cross-origin context', () => {
49-
const cookieHandler = createClientUatCookie(mockCookieSuffix);
50+
const cookieHandler = createClientUatCookie(mockCookieSuffix, defaultOptions);
5051
cookieHandler.set({
5152
id: 'test-client',
5253
updatedAt: new Date('2024-01-01'),
@@ -65,7 +66,7 @@ describe('createClientUatCookie', () => {
6566

6667
it('should set cookies with None sameSite in cross-origin context', () => {
6768
(inCrossOriginIframe as ReturnType<typeof vi.fn>).mockReturnValue(true);
68-
const cookieHandler = createClientUatCookie(mockCookieSuffix);
69+
const cookieHandler = createClientUatCookie(mockCookieSuffix, defaultOptions);
6970
cookieHandler.set({
7071
id: 'test-client',
7172
updatedAt: new Date('2024-01-01'),
@@ -82,7 +83,7 @@ describe('createClientUatCookie', () => {
8283
});
8384

8485
it('should set value to 0 when client is undefined', () => {
85-
const cookieHandler = createClientUatCookie(mockCookieSuffix);
86+
const cookieHandler = createClientUatCookie(mockCookieSuffix, defaultOptions);
8687
cookieHandler.set(undefined);
8788

8889
expect(mockSet).toHaveBeenCalledWith('0', {
@@ -95,7 +96,7 @@ describe('createClientUatCookie', () => {
9596
});
9697

9798
it('should set value to 0 when client has no signed in sessions', () => {
98-
const cookieHandler = createClientUatCookie(mockCookieSuffix);
99+
const cookieHandler = createClientUatCookie(mockCookieSuffix, defaultOptions);
99100
cookieHandler.set({
100101
id: 'test-client',
101102
updatedAt: new Date('2024-01-01'),
@@ -114,7 +115,7 @@ describe('createClientUatCookie', () => {
114115
it('should get cookie value from suffixed cookie first, then fallback to non-suffixed', () => {
115116
mockGet.mockImplementationOnce(() => '1234567890').mockImplementationOnce(() => '9876543210');
116117

117-
const cookieHandler = createClientUatCookie(mockCookieSuffix);
118+
const cookieHandler = createClientUatCookie(mockCookieSuffix, defaultOptions);
118119
const result = cookieHandler.get();
119120

120121
expect(result).toBe(1234567890);
@@ -123,15 +124,15 @@ describe('createClientUatCookie', () => {
123124
it('should return 0 when no cookie value is present', () => {
124125
mockGet.mockImplementationOnce(() => undefined).mockImplementationOnce(() => undefined);
125126

126-
const cookieHandler = createClientUatCookie(mockCookieSuffix);
127+
const cookieHandler = createClientUatCookie(mockCookieSuffix, defaultOptions);
127128
const result = cookieHandler.get();
128129

129130
expect(result).toBe(0);
130131
});
131132

132133
it('should set cookies with SameSite=None when the host requires it', () => {
133134
(requiresSameSiteNone as ReturnType<typeof vi.fn>).mockReturnValue(true);
134-
const cookieHandler = createClientUatCookie(mockCookieSuffix);
135+
const cookieHandler = createClientUatCookie(mockCookieSuffix, defaultOptions);
135136
cookieHandler.set({
136137
id: 'test-client',
137138
updatedAt: new Date('2024-01-01'),
@@ -146,4 +147,21 @@ describe('createClientUatCookie', () => {
146147
partitioned: false,
147148
});
148149
});
150+
151+
it('should set partitioned cookies when usePartitionedCookies returns true', () => {
152+
const cookieHandler = createClientUatCookie(mockCookieSuffix, { usePartitionedCookies: () => true });
153+
cookieHandler.set({
154+
id: 'test-client',
155+
updatedAt: new Date('2024-01-01'),
156+
signedInSessions: ['session1'],
157+
});
158+
159+
expect(mockSet).toHaveBeenCalledWith('1704067200', {
160+
domain: mockDomain,
161+
expires: mockExpires,
162+
sameSite: 'None',
163+
secure: true,
164+
partitioned: true,
165+
});
166+
});
149167
});

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ describe('createSessionCookie', () => {
1717
const mockCookieSuffix = 'test-suffix';
1818
const mockToken = 'test-token';
1919
const mockExpires = new Date('2024-12-31');
20+
const defaultOptions = { usePartitionedCookies: () => false };
2021
const mockSet = vi.fn();
2122
const mockRemove = vi.fn();
2223
const mockGet = vi.fn();
@@ -36,14 +37,14 @@ describe('createSessionCookie', () => {
3637
});
3738

3839
it('should create both suffixed and non-suffixed cookie handlers', () => {
39-
createSessionCookie(mockCookieSuffix);
40+
createSessionCookie(mockCookieSuffix, defaultOptions);
4041
expect(createCookieHandler).toHaveBeenCalledTimes(2);
4142
expect(createCookieHandler).toHaveBeenCalledWith('__session');
4243
expect(createCookieHandler).toHaveBeenCalledWith('__session_test-suffix');
4344
});
4445

4546
it('should set cookies with correct parameters in non-cross-origin context', () => {
46-
const cookieHandler = createSessionCookie(mockCookieSuffix);
47+
const cookieHandler = createSessionCookie(mockCookieSuffix, defaultOptions);
4748
cookieHandler.set(mockToken);
4849

4950
expect(mockSet).toHaveBeenCalledTimes(2);
@@ -57,7 +58,7 @@ describe('createSessionCookie', () => {
5758

5859
it('should set cookies with None sameSite in cross-origin context', () => {
5960
(inCrossOriginIframe as ReturnType<typeof vi.fn>).mockReturnValue(true);
60-
const cookieHandler = createSessionCookie(mockCookieSuffix);
61+
const cookieHandler = createSessionCookie(mockCookieSuffix, defaultOptions);
6162
cookieHandler.set(mockToken);
6263

6364
expect(mockSet).toHaveBeenCalledWith(mockToken, {
@@ -69,14 +70,14 @@ describe('createSessionCookie', () => {
6970
});
7071

7172
it('should remove both cookies when remove is called', () => {
72-
const cookieHandler = createSessionCookie(mockCookieSuffix);
73+
const cookieHandler = createSessionCookie(mockCookieSuffix, defaultOptions);
7374
cookieHandler.remove();
7475

7576
expect(mockRemove).toHaveBeenCalledTimes(2);
7677
});
7778

7879
it('should remove cookies with the same attributes as set', () => {
79-
const cookieHandler = createSessionCookie(mockCookieSuffix);
80+
const cookieHandler = createSessionCookie(mockCookieSuffix, defaultOptions);
8081
cookieHandler.set(mockToken);
8182
cookieHandler.remove();
8283

@@ -102,7 +103,7 @@ describe('createSessionCookie', () => {
102103
it('should get cookie value from suffixed cookie first, then fallback to non-suffixed', () => {
103104
mockGet.mockImplementationOnce(() => 'suffixed-value').mockImplementationOnce(() => 'non-suffixed-value');
104105

105-
const cookieHandler = createSessionCookie(mockCookieSuffix);
106+
const cookieHandler = createSessionCookie(mockCookieSuffix, defaultOptions);
106107
const result = cookieHandler.get();
107108

108109
expect(result).toBe('suffixed-value');
@@ -111,15 +112,15 @@ describe('createSessionCookie', () => {
111112
it('should fallback to non-suffixed cookie when suffixed cookie is not present', () => {
112113
mockGet.mockImplementationOnce(() => undefined).mockImplementationOnce(() => 'non-suffixed-value');
113114

114-
const cookieHandler = createSessionCookie(mockCookieSuffix);
115+
const cookieHandler = createSessionCookie(mockCookieSuffix, defaultOptions);
115116
const result = cookieHandler.get();
116117

117118
expect(result).toBe('non-suffixed-value');
118119
});
119120

120121
it('should set cookies with None sameSite on .replit.dev origins', () => {
121122
(requiresSameSiteNone as ReturnType<typeof vi.fn>).mockReturnValue(true);
122-
const cookieHandler = createSessionCookie(mockCookieSuffix);
123+
const cookieHandler = createSessionCookie(mockCookieSuffix, defaultOptions);
123124
cookieHandler.set(mockToken);
124125

125126
expect(mockSet).toHaveBeenCalledWith(mockToken, {
@@ -129,4 +130,17 @@ describe('createSessionCookie', () => {
129130
partitioned: false,
130131
});
131132
});
133+
134+
it('should set partitioned cookies when usePartitionedCookies returns true', () => {
135+
const cookieHandler = createSessionCookie(mockCookieSuffix, { usePartitionedCookies: () => true });
136+
cookieHandler.set(mockToken);
137+
138+
expect(mockRemove).toHaveBeenCalledTimes(2);
139+
expect(mockSet).toHaveBeenCalledWith(mockToken, {
140+
expires: mockExpires,
141+
sameSite: 'None',
142+
secure: true,
143+
partitioned: true,
144+
});
145+
});
132146
});

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@ export type ClientUatCookieHandler = {
1515
get: () => number;
1616
};
1717

18+
export type ClientUatCookieOptions = {
19+
usePartitionedCookies: () => boolean;
20+
};
21+
1822
/**
1923
* Create a long-lived JS cookie to store the client last updated_at timestamp
2024
* for development instances (for production instance is set by FAPI).
2125
* The cookie is used as hint from the Clerk Backend packages to identify
2226
* if the user is authenticated or not.
2327
*/
24-
export const createClientUatCookie = (cookieSuffix: string): ClientUatCookieHandler => {
28+
export const createClientUatCookie = (
29+
cookieSuffix: string,
30+
options: ClientUatCookieOptions,
31+
): ClientUatCookieHandler => {
2532
const clientUatCookie = createCookieHandler(CLIENT_UAT_COOKIE_NAME);
2633
const suffixedClientUatCookie = createCookieHandler(getSuffixedCookieName(CLIENT_UAT_COOKIE_NAME, cookieSuffix));
2734

@@ -38,13 +45,10 @@ export const createClientUatCookie = (cookieSuffix: string): ClientUatCookieHand
3845
* Generally, this is handled by redirectWithAuth() being called and relying on the dev browser ID in the URL,
3946
* but if that isn't used we rely on this. In production, nothing is cross-domain and Lax is used when client_uat is set from FAPI.
4047
*/
41-
const sameSite = __BUILD_VARIANT_CHIPS__
42-
? 'None'
43-
: inCrossOriginIframe() || requiresSameSiteNone()
44-
? 'None'
45-
: 'Strict';
48+
const isPartitioned = options.usePartitionedCookies();
49+
const sameSite = isPartitioned || inCrossOriginIframe() || requiresSameSiteNone() ? 'None' : 'Strict';
4650
const secure = getSecureAttribute(sameSite);
47-
const partitioned = __BUILD_VARIANT_CHIPS__ && secure;
51+
const partitioned = isPartitioned && secure;
4852
const domain = getCookieDomain(undefined, undefined, { sameSite, secure });
4953

5054
// '0' indicates the user is signed out

0 commit comments

Comments
 (0)