From 88bc372f7fe2d14bfb7f0f819effe8ca319ecca8 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Fri, 21 Jun 2024 15:01:57 -0400 Subject: [PATCH] feat(chrome-extension): Poll for new active sessions --- .changeset/smart-ravens-taste.md | 5 ++ packages/chrome-extension/src/singleton.ts | 25 +++---- .../chrome-extension/src/utils/jwt-handler.ts | 66 +++++++++++++++++++ playground/chrome-extension/package.json | 16 ++--- 4 files changed, 88 insertions(+), 24 deletions(-) create mode 100644 .changeset/smart-ravens-taste.md create mode 100644 packages/chrome-extension/src/utils/jwt-handler.ts diff --git a/.changeset/smart-ravens-taste.md b/.changeset/smart-ravens-taste.md new file mode 100644 index 00000000000..ab558ab62a5 --- /dev/null +++ b/.changeset/smart-ravens-taste.md @@ -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. diff --git a/packages/chrome-extension/src/singleton.ts b/packages/chrome-extension/src/singleton.ts index f2b297bf7ca..2fe3d464d13 100644 --- a/packages/chrome-extension/src/singleton.ts +++ b/packages/chrome-extension/src/singleton.ts @@ -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'; @@ -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); @@ -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 @@ -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); } }); diff --git a/packages/chrome-extension/src/utils/jwt-handler.ts b/packages/chrome-extension/src/utils/jwt-handler.ts new file mode 100644 index 00000000000..6b186ada3d8 --- /dev/null +++ b/packages/chrome-extension/src/utils/jwt-handler.ts @@ -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 => { + 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(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 }; +} diff --git a/playground/chrome-extension/package.json b/playground/chrome-extension/package.json index fe85b6c9017..64bbaa5e0e7 100644 --- a/playground/chrome-extension/package.json +++ b/playground/chrome-extension/package.json @@ -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" },