Something went wrong. Try again.
Browser extension: detect and subscribe to standard.site publications on ATProto
Something went wrong. Try again.
6.0 kB · 153 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154// Worker side of interactive sign-in. The OAuth client cannot run in the// service worker, so it lives in an offscreen document; this module owns what// that document cannot touch: opening the consent window, spotting the// redirect back, and cleaning up.//// Consent can take minutes and the worker can be reaped meanwhile, so no flow// state lives in worker memory: the pending window id sits in// storage.session, and the tab/window listeners are registered at the top// level so their events revive the worker.
import { AUTH_ERROR_KEY, callbackFromUpdate, oauthRedirectUri, replacedSession } from './lib/authflow'import { invalidateAccount } from './lib/cache'import { ensureOffscreenDocument } from './lib/offscreen'import { getStoredSession, saveSessionMirror } from './lib/session-store'import type { OffscreenMsg, SessionInfo } from './lib/types'
const PENDING_KEY = 'pendingAuth'
// Enough for every PDS consent page we know of; Chrome clamps to the screen.const CONSENT_WIDTH = 480const CONSENT_HEIGHT = 720
interface PendingAuth { windowId: number}
async function getPending(): Promise<PendingAuth | undefined> { return (await chrome.storage.session.get(PENDING_KEY))[PENDING_KEY] as PendingAuth | undefined}
function ensureOffscreen(): Promise<void> { return ensureOffscreenDocument({ url: 'offscreen.html', reasons: [chrome.offscreen.Reason.LOCAL_STORAGE], justification: 'The AT Protocol OAuth client keeps its sign-in state in DOM storage', })}
async function closeOffscreen(): Promise<void> { await chrome.offscreen.closeDocument().catch(() => { // already gone })}
async function toOffscreen<T>(msg: OffscreenMsg): Promise<T> { const res = await chrome.runtime.sendMessage(msg) if (res && typeof res === 'object' && '__error' in res) { throw new Error(String((res as { __error: unknown }).__error)) } return res as T}
/** * Open a consent window for `handle`. Resolves once the window is up; the * rest of the flow continues in the listeners below. Rejects (into the * popup's form) if the handle can't be resolved or the PDS refuses the * authorization request. */export async function startSignIn(handle: string): Promise<void> { // A new attempt supersedes any window still waiting for consent. const prev = await getPending() if (prev) { await chrome.storage.session.remove(PENDING_KEY) await chrome.windows.remove(prev.windowId).catch(() => {}) } await chrome.storage.session.remove(AUTH_ERROR_KEY)
await ensureOffscreen() const url = await toOffscreen<string>({ target: 'offscreen', type: 'oauth-authorize', handle }) const win = await chrome.windows.create({ url, type: 'popup', width: CONSENT_WIDTH, height: CONSENT_HEIGHT, }) if (win.id === undefined) throw new Error('Could not open the consent window') console.debug('[substandard] consent window opened', win.id) await chrome.storage.session.set({ [PENDING_KEY]: { windowId: win.id } satisfies PendingAuth })}
chrome.tabs.onUpdated.addListener((_tabId, changeInfo, tab) => { void onConsentTabUpdated(changeInfo, tab)})
/** * Windows whose redirect is already being handled by this worker instance. * tabs.onUpdated delivers the redirect URL more than once (commit, complete); * the dedupe check must stay synchronous — an await before it lets a second * event start a second exchange, which fails on the consumed state and closes * the offscreen document under the first one. */const claimedWindows = new Set<number>()
async function onConsentTabUpdated( changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab,): Promise<void> { const callbackUrl = callbackFromUpdate(oauthRedirectUri(chrome.runtime.id), changeInfo, tab) if (!callbackUrl || claimedWindows.has(tab.windowId)) return claimedWindows.add(tab.windowId) try { const pending = await getPending() if (!pending || tab.windowId !== pending.windowId) return
// Claim the flow in storage too, so the onRemoved listener below doesn't // read the window close as a cancellation (and a restarted worker doesn't // see a stale flow). await chrome.storage.session.remove(PENDING_KEY) await chrome.windows.remove(pending.windowId).catch(() => {}) try { await ensureOffscreen() const info = await toOffscreen<SessionInfo>({ target: 'offscreen', type: 'oauth-callback', url: callbackUrl, }) // An account switch keeps the old login usable until the new one has // landed — which is now, so retire it, and drop everything cached from // its repo: the next answers must be the new account's. const replaced = replacedSession(await getStoredSession(), info) if (replaced) { await toOffscreen({ target: 'offscreen', type: 'oauth-revoke', sub: replaced }) await invalidateAccount(replaced) console.debug('[substandard] revoked replaced session', replaced) } // The offscreen document cannot persist this itself (no chrome.storage // there); the mirror write is ours. await saveSessionMirror(info) console.debug('[substandard] signed in as', info.did) } catch (err) { console.debug('[substandard] token exchange failed', err) await chrome.storage.session.set({ [AUTH_ERROR_KEY]: err instanceof Error ? err.message : String(err), }) } await closeOffscreen() } finally { claimedWindows.delete(tab.windowId) }}
chrome.windows.onRemoved.addListener((windowId) => { void onConsentWindowClosed(windowId)})
async function onConsentWindowClosed(windowId: number): Promise<void> { const pending = await getPending() if (!pending || pending.windowId !== windowId) return // User closed the window without consenting; not an error worth surfacing. console.debug('[substandard] consent window closed before redirect') await chrome.storage.session.remove(PENDING_KEY) await closeOffscreen()}