Skip to content

Commit e0cfbc5

Browse files
committed
chore(chrome-extension): Interim commit
1 parent 5ded5a2 commit e0cfbc5

10 files changed

Lines changed: 65 additions & 242 deletions

File tree

packages/chrome-extension/src/internal/clerk.ts

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import browser from 'webextension-polyfill';
55

66
import { SCOPE, type Scope } from '../types';
77
import { AUTH_HEADER, CLIENT_JWT_KEY, DEFAULT_LOCAL_HOST_PERMISSION } from './constants';
8-
import type { GetClientCookieParams } from './utils/cookies';
98
import { assertPublishableKey } from './utils/errors';
109
import { JWTHandler } from './utils/jwt-handler';
11-
import { getValidPossibleManifestHosts, validateHostPermissionExistence, validateManifest } from './utils/manifest';
10+
import { validateManifest } from './utils/manifest';
1211
import { BrowserStorageCache, type StorageCache } from './utils/storage';
1312

1413
export let clerk: Clerk;
@@ -22,13 +21,15 @@ export type CreateClerkClientOptions = {
2221
publishableKey: string;
2322
scope?: Scope;
2423
storageCache?: StorageCache;
24+
syncHost?: string;
2525
syncSessionWithTab?: boolean;
2626
};
2727

2828
export async function createClerkClient({
2929
publishableKey,
3030
scope,
3131
storageCache = BrowserStorageCache,
32+
syncHost = process.env.CLERK_SYNC_HOST,
3233
syncSessionWithTab = false,
3334
}: CreateClerkClientOptions): Promise<Clerk> {
3435
if (clerk) {
@@ -37,46 +38,26 @@ export async function createClerkClient({
3738

3839
// Parse publishableKey and assert it's present/valid, throw if not
3940
const key = parsePublishableKey(publishableKey);
41+
42+
console.log('KEY', key, key?.instanceType, key?.frontendApi);
4043
assertPublishableKey(key);
4144

4245
const isProd = key.instanceType === 'production';
4346
const manifest = browser.runtime.getManifest();
4447

4548
// Will throw if manifest is invalid
4649
validateManifest(manifest, {
47-
sync: syncSessionWithTab,
4850
background: scope === SCOPE.background,
51+
sync: syncSessionWithTab,
4952
});
5053

51-
let jwt: JWTHandler | undefined;
52-
53-
if (syncSessionWithTab) {
54-
const hostHint = isProd ? key.frontendApi : DEFAULT_LOCAL_HOST_PERMISSION;
55-
const validHosts = getValidPossibleManifestHosts(manifest);
56-
57-
// Will throw if manifest host_permissions doesn't contain a valid host
58-
validateHostPermissionExistence(validHosts, hostHint);
59-
60-
// Set up cookie params based on environment
61-
const getClientCookieParams: GetClientCookieParams = isProd
62-
? {
63-
urls: `https://${key.frontendApi}`,
64-
name: CLIENT_JWT_KEY,
65-
}
66-
: {
67-
urls: validHosts,
68-
name: DEV_BROWSER_JWT_KEY,
69-
};
70-
71-
// Set up JWT handler and attempt to get JWT from storage on initialization
72-
jwt = JWTHandler(storageCache, { ...getClientCookieParams, frontendApi: key.frontendApi, sync: true });
73-
} else {
74-
jwt = JWTHandler(storageCache, { frontendApi: key.frontendApi, sync: false });
75-
}
76-
77-
if (!jwt) {
78-
throw new Error('StorageCache could not be initialized.'); // TODO: Update error
79-
}
54+
// Set up JWT handler and attempt to get JWT from storage on initialization
55+
const jwt = JWTHandler(storageCache, {
56+
frontendApi: key.frontendApi,
57+
name: isProd ? CLIENT_JWT_KEY : DEV_BROWSER_JWT_KEY,
58+
sync: syncSessionWithTab,
59+
url: syncHost || isProd ? `https://${key.frontendApi}` : DEFAULT_LOCAL_HOST_PERMISSION,
60+
});
8061

8162
// Create Clerk instance
8263
clerk = new Clerk(publishableKey);

packages/chrome-extension/src/internal/constants.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export const CLIENT_JWT_KEY = '__client';
22
export const STORAGE_KEY_CLIENT_JWT = '__clerk_client_jwt';
3-
export const VALID_HOST_PERMISSION_REGEX = /(https?:\/\/[\w.-]+)/;
43
export const DEFAULT_LOCAL_HOST_PERMISSION = 'http://localhost';
54
export const AUTH_HEADER = {
65
production: 'Authorization',

packages/chrome-extension/src/internal/utils/__tests__/cookies.test.ts

Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -35,71 +35,14 @@ describe('Cookies', () => {
3535
}
3636

3737
describe('getClientCookie', () => {
38-
describe('Single Host', () => {
39-
test('returns cookie value from browser.cookies if is set for url', async () => {
40-
const url = urls[0];
38+
test('returns cookie value from browser.cookies if is set for url', async () => {
39+
const url = urls[0];
4140

42-
getMock.mockResolvedValue(cookie);
41+
getMock.mockResolvedValue(cookie);
4342

44-
expect(await getClientCookie({ urls: url, name })).toBe(cookie);
43+
expect(await getClientCookie({ url, name })).toBe(cookie);
4544

46-
expectMockCalls(getMock, name, [url]);
47-
});
48-
});
49-
50-
describe('Multiple Hosts', () => {
51-
test('with valid urls', async () => {
52-
getMock.mockResolvedValueOnce(cookie).mockResolvedValueOnce(null).mockResolvedValueOnce(null);
53-
54-
expect(await getClientCookie({ urls, name })).toBe(cookie);
55-
56-
expectMockCalls(getMock, name, urls);
57-
});
58-
59-
test('with invalid urls', async () => {
60-
const urls = ['foo'];
61-
62-
getMock.mockResolvedValue(null);
63-
expect(await getClientCookie({ urls, name })).toBe(null);
64-
65-
expectMockCalls(getMock, name, urls);
66-
});
67-
68-
test('with single result', async () => {
69-
getMock.mockResolvedValueOnce(cookie).mockResolvedValueOnce(null);
70-
71-
expect(await getClientCookie({ urls, name })).toBe(cookie);
72-
73-
expectMockCalls(getMock, name, urls);
74-
});
75-
76-
test('with multiple results - should pick first result', async () => {
77-
const cookie2 = createCookie({ name, value: 'result2', domain });
78-
79-
getMock.mockResolvedValueOnce(cookie).mockResolvedValueOnce(cookie2);
80-
81-
expect(await getClientCookie({ urls, name })).toBe(cookie);
82-
83-
expectMockCalls(getMock, name, urls);
84-
});
85-
86-
test('with rejected result', async () => {
87-
const urls = [`https://${domain}`, 'https://foo.com'];
88-
89-
getMock.mockResolvedValueOnce(cookie).mockRejectedValueOnce(null);
90-
91-
expect(await getClientCookie({ urls, name })).toBe(cookie);
92-
93-
expectMockCalls(getMock, name, urls);
94-
});
95-
96-
test('with empty result', async () => {
97-
getMock.mockResolvedValueOnce(null).mockRejectedValueOnce(null);
98-
99-
expect(await getClientCookie({ urls, name })).toBe(null);
100-
101-
expectMockCalls(getMock, name, urls);
102-
});
45+
expectMockCalls(getMock, name, [url]);
10346
});
10447
});
10548
});

packages/chrome-extension/src/internal/utils/__tests__/manifest.test.ts

Lines changed: 2 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import type { Manifest } from 'webextension-polyfill';
22

3-
import { missingManifestKeyError, missingValidManifestHostPermission } from '../errors';
4-
import type { ValidatedManifest } from '../manifest';
5-
import { getValidPossibleManifestHosts, validateHostPermissionExistence, validateManifest } from '../manifest';
6-
7-
const validClerkManifest = {
8-
permissions: ['cookies', 'storage'],
9-
host_permissions: ['http://localhost:3000'],
10-
} as ValidatedManifest;
3+
import { missingManifestKeyError } from '../errors';
4+
import { validateManifest } from '../manifest';
115

126
describe('Manifest', () => {
137
describe('validateManifest(manifest)', () => {
@@ -94,69 +88,4 @@ describe('Manifest', () => {
9488
});
9589
});
9690
});
97-
98-
describe('validateHostPermissionExistence(manifest.host_permissions[])', () => {
99-
describe('valid configuration', () => {
100-
const hostHint = 'https://clerk.clerk.com';
101-
102-
test('valid', () => {
103-
expect(() => validateHostPermissionExistence(['http://localhost:3000'], hostHint)).not.toThrow();
104-
});
105-
106-
test('invalid', () => {
107-
expect(() => validateHostPermissionExistence([], hostHint)).toThrow(
108-
missingValidManifestHostPermission(hostHint),
109-
);
110-
});
111-
});
112-
});
113-
114-
describe('getPossibleManifestHosts(manifest)', () => {
115-
describe('valid configuration', () => {
116-
test('should not throw error', async () => {
117-
expect(() => getValidPossibleManifestHosts(validClerkManifest)).not.toThrow();
118-
});
119-
120-
test('should return localhost', async () => {
121-
expect(() => getValidPossibleManifestHosts(validClerkManifest)).not.toThrow();
122-
});
123-
});
124-
125-
describe('configurations', () => {
126-
it('should appropriately parse host_permissions', () => {
127-
const host_permissions = [
128-
'<ALL_URLS>',
129-
'http://localhost',
130-
'http://localhost/',
131-
'http://localhost/*',
132-
'http://localhost:80/*',
133-
'http://localhost:*/*',
134-
'https://*.com/*',
135-
'*://developer.mozilla.org/*',
136-
'*://developer.mozilla.org*',
137-
'*://*.example.org/*',
138-
'https://developer.mozilla.org/*',
139-
'ftp://*.example.org/*',
140-
'https://example.org:80/',
141-
'https://example.org:*',
142-
'http://example.org:*',
143-
'https://example.org:*/*',
144-
];
145-
146-
const manifest = {
147-
permissions: ['cookies', 'storage'],
148-
host_permissions,
149-
} as ValidatedManifest;
150-
151-
const result = [
152-
'http://localhost',
153-
'https://developer.mozilla.org',
154-
'https://example.org',
155-
'http://example.org',
156-
];
157-
158-
expect(getValidPossibleManifestHosts(manifest)).toStrictEqual(result);
159-
});
160-
});
161-
});
16291
});
Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,15 @@
11
import browser from 'webextension-polyfill';
22

3+
export type FormattedUrl = `http${string}`;
34
export type GetClientCookieParams = {
4-
urls: string | string[];
55
name: string;
6+
url: string;
67
};
78

8-
function isSingleHost(urls: string | string[]): urls is string {
9-
return typeof urls === 'string';
9+
function ensureFormattedUrl(url: string): FormattedUrl {
10+
return url.startsWith('http') ? (url as FormattedUrl) : `https://${url}`;
1011
}
1112

12-
function ensureFormattedUrl(url: string): string {
13-
return url.startsWith('http') ? url : `https://${url}`;
14-
}
15-
16-
export async function getClientCookie({ urls, name }: GetClientCookieParams) {
17-
// Handle single host request
18-
if (isSingleHost(urls)) {
19-
const url = ensureFormattedUrl(urls);
20-
return await browser.cookies.get({ url, name });
21-
}
22-
23-
// Handle multi-host request
24-
const cookiePromises = urls.map(url => browser.cookies.get({ url, name }));
25-
const cookieResults = await Promise.allSettled(cookiePromises);
26-
27-
for (const cookie of cookieResults) {
28-
if (cookie.status === 'fulfilled' && cookie.value) {
29-
return cookie.value;
30-
}
31-
}
32-
33-
return null;
13+
export async function getClientCookie({ url, name }: GetClientCookieParams) {
14+
return await browser.cookies.get({ name, url: ensureFormattedUrl(url) });
3415
}

packages/chrome-extension/src/internal/utils/errors.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ export const errorLogger = (err: Error) => console.error(err, err.stack);
88
export const errorThrower = buildErrorThrower({ packageName: '@clerk/chrome-extension' });
99

1010
export const missingManifestKeyError = (key: string) => `Missing \`${key}\` entry in manifest.json`;
11-
export const missingValidManifestHostPermission = (hostHint: string) =>
12-
`You're missing a valid host permission. Please add ${hostHint} to \`host_permissions\` in manifest.json.`;
1311

1412
export function assertPublishableKey(publishableKey: unknown): asserts publishableKey {
1513
if (!publishableKey) {

packages/chrome-extension/src/internal/utils/manifest.ts

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,45 @@ import type { SetRequired } from 'type-fest';
22
import type { Manifest } from 'webextension-polyfill';
33

44
import type { ClerkClientExtensionFeatures } from '../../types';
5-
import { VALID_HOST_PERMISSION_REGEX } from '../constants';
6-
import { errorThrower, missingManifestKeyError, missingValidManifestHostPermission } from './errors';
5+
import { errorThrower, missingManifestKeyError } from './errors';
76

87
export type ValidatedManifest = SetRequired<Manifest.WebExtensionManifest, 'permissions' | 'host_permissions'>;
8+
export type ManifestKeys = keyof Manifest.WebExtensionManifest;
99

10-
export function validateManifest(
11-
manifest: Manifest.WebExtensionManifest,
12-
features: ClerkClientExtensionFeatures,
13-
): asserts manifest is ValidatedManifest {
14-
const hasFeatures = Boolean(features) && Object.keys(features).length > 0;
15-
16-
if (!manifest.permissions) {
17-
return errorThrower.throw(missingManifestKeyError('permissions'));
18-
}
19-
20-
if (!manifest.permissions.includes('storage')) {
21-
return errorThrower.throw(missingManifestKeyError('permissions.storage'));
22-
}
23-
24-
if (!hasFeatures) {
25-
return;
26-
}
27-
28-
if (features.background && !manifest.background) {
29-
return errorThrower.throw(missingManifestKeyError('background'));
30-
}
31-
32-
if (features.sync && !manifest.permissions.includes('cookies')) {
33-
return errorThrower.throw(missingManifestKeyError('permissions.cookies'));
10+
function validateRootManifestKey(manifest: Manifest.WebExtensionManifest, key: ManifestKeys): void {
11+
if (!manifest[key]) {
12+
errorThrower.throw(missingManifestKeyError(key));
3413
}
14+
}
3515

36-
if (features.sync && !manifest.host_permissions) {
37-
return errorThrower.throw(missingManifestKeyError('host_permissions'));
16+
function validateManifestPermission(manifest: Manifest.WebExtensionManifest, key: Manifest.Permission): void {
17+
if (!manifest.permissions?.includes(key)) {
18+
errorThrower.throw(missingManifestKeyError(`permissions.${key}`));
3819
}
3920
}
4021

41-
export function validateHostPermissionExistence(hostPermissions: string[], hostHint: string): void {
42-
if (!hostPermissions?.length) {
43-
errorThrower.throw(missingValidManifestHostPermission(hostHint));
44-
}
22+
function hasAdditionalFeatures(features: ClerkClientExtensionFeatures): boolean {
23+
return Boolean(features) && Object.keys(features).length > 0;
4524
}
4625

47-
export function getValidPossibleManifestHosts(manifest: ValidatedManifest): string[] {
48-
const uniqueHosts = new Set<string>();
26+
export function validateManifest(
27+
manifest: Manifest.WebExtensionManifest,
28+
features: ClerkClientExtensionFeatures,
29+
): asserts manifest is ValidatedManifest {
30+
validateRootManifestKey(manifest, 'permissions');
31+
validateManifestPermission(manifest, 'storage');
4932

50-
for (const host of manifest.host_permissions) {
51-
const res = host.match(VALID_HOST_PERMISSION_REGEX)?.[1];
33+
// If no additional features are provided, we can return success early
34+
if (!hasAdditionalFeatures(features)) {
35+
return;
36+
}
5237

53-
if (res) {
54-
uniqueHosts.add(res);
55-
}
38+
if (features.background) {
39+
validateRootManifestKey(manifest, 'background');
5640
}
5741

58-
return [...uniqueHosts];
42+
if (features.sync) {
43+
validateManifestPermission(manifest, 'cookies');
44+
validateRootManifestKey(manifest, 'host_permissions');
45+
}
5946
}

0 commit comments

Comments
 (0)