Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/smart-ravens-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/chrome-extension": minor
---

Poll for active session changes allowing for synchronizing newly authenticated users after the extension has been opened.
25 changes: 9 additions & 16 deletions packages/chrome-extension/src/singleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { DEV_BROWSER_JWT_KEY } from '@clerk/shared';
import { parsePublishableKey } from '@clerk/shared/keys';
import browser from 'webextension-polyfill';

import { CLIENT_JWT_KEY, DEFAULT_LOCAL_HOST_PERMISSION, STORAGE_KEY_CLIENT_JWT } from './constants';
import { CLIENT_JWT_KEY, DEFAULT_LOCAL_HOST_PERMISSION } from './constants';
import type { GetClientCookieParams } from './utils/cookies';
import { getClientCookie } from './utils/cookies';
import { assertPublishableKey, errorLogger } from './utils/errors';
import { assertPublishableKey } from './utils/errors';
import { JWTHandler } from './utils/jwt-handler';
import { getValidPossibleManifestHosts, validateHostPermissionExistence, validateManifest } from './utils/manifest';
import { BrowserStorageCache, type StorageCache } from './utils/storage';

Expand Down Expand Up @@ -53,16 +53,9 @@ export async function buildClerk({
name: DEV_BROWSER_JWT_KEY,
};

// Get client cookie from browser
const clientCookie = await getClientCookie(getClientCookieParams).catch(errorLogger);

// Create StorageCache key
const CACHE_KEY = storageCache.createKey(key.frontendApi, STORAGE_KEY_CLIENT_JWT);

// Set client cookie in StorageCache
if (clientCookie) {
await storageCache.set(CACHE_KEY, clientCookie.value).catch(errorLogger);
}
// Set up JWT handler and attempt to get JWT from storage on initialization
const jwt = JWTHandler(storageCache, { ...getClientCookieParams, frontendApi: key.frontendApi });
void jwt.poll();

// Create Clerk instance
clerk = new Clerk(publishableKey);
Expand All @@ -73,9 +66,9 @@ export async function buildClerk({
requestInit.credentials = 'omit';
requestInit.url?.searchParams.append('_is_native', '1');

const jwt = await storageCache.get(CACHE_KEY);
const currentJWT = await jwt.get();

(requestInit.headers as Headers).set('authorization', jwt || '');
(requestInit.headers as Headers).set('authorization', currentJWT || '');
});

// Store updated JWT in StorageCache on Clerk responses
Expand All @@ -84,7 +77,7 @@ export async function buildClerk({
const authHeader = response.headers.get('authorization');

if (authHeader) {
await storageCache.set(CACHE_KEY, authHeader);
await jwt.set(authHeader);
}
});

Expand Down
66 changes: 66 additions & 0 deletions packages/chrome-extension/src/utils/jwt-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Poller } from '@clerk/shared/poller';

import { STORAGE_KEY_CLIENT_JWT } from '../constants';
import type { GetClientCookieParams } from './cookies';
import { getClientCookie } from './cookies';
import { errorLogger } from './errors';
import type { StorageCache } from './storage';

type JWTHandlerParams = GetClientCookieParams & {
frontendApi: string;
};

export function JWTHandler(store: StorageCache, { frontendApi, ...cookieParams }: JWTHandlerParams) {
const CACHE_KEY = store.createKey(frontendApi, STORAGE_KEY_CLIENT_JWT);

/**
* Sets the JWT value to the active
* @param value: JWT generally from the cookie or authorization header
*/
const set = async (value: string): Promise<void> => {
return await store.set(CACHE_KEY, value).catch(errorLogger);
};

/**
* Gets the JWT value to the active store.
* If not set, attempt to get it from the synced session and save for later use.
*/
const get = async () => {
// Get current JWT from StorageCache
const currentJWT = await store.get<string>(CACHE_KEY);

if (currentJWT) {
// Return current JWT, if it exists
return currentJWT;
}

// Get client cookie from browser
const syncedJWT = await getClientCookie(cookieParams).catch(errorLogger);

if (syncedJWT) {
// Set client cookie in StorageCache
await set(syncedJWT.value);
}

return syncedJWT?.value;
};

/**
* Polls for the synced session JWT via the get() function.
*
* @param delayInMs: Polling delay in milliseconds (default: 1500ms)
*/
const poll = async (delayInMs: number = 1500) => {
const { run, stop } = Poller({ delayInMs });

void run(async () => {
const currentJWT = await get();

if (currentJWT) {
stop();
}
});
};

return { get, poll, set };
}
16 changes: 8 additions & 8 deletions playground/chrome-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@
"@clerk/shared": "file:.yalc/@clerk/shared",
"@clerk/themes": "file:.yalc/@clerk/themes",
"@clerk/types": "file:.yalc/@clerk/types",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.9.0",
"webextension-polyfill": "^0.10.0"
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1",
"webextension-polyfill": "^0.12.0"
},
"devDependencies": {
"@crxjs/vite-plugin": "^1.0.14",
"@types/chrome": "^0.0.253",
"@types/chrome": "^0.0.268",
"@types/node": "^18.17.1",
"@types/react": "^18.2.39",
"@types/react-dom": "^18.2.17",
"@types/webextension-polyfill": "^0.10.0",
"@vitejs/plugin-react-swc": "^3.0.1",
"autoprefixer": "^10.4.16",
"autoprefixer": "^10.4.19",
"nodemon": "^2.0.20",
"postcss": "^8.4.31",
"ts-node": "^10.9.1",
"postcss": "^8.4.38",
"ts-node": "^10.9.2",
"typescript": "^4.9.4",
"vite": "^4.5.0"
},
Expand Down