AppView in a box as a Vite plugin thing
hatk.dev
1// Namespaced browser storage for OAuth state and tokens
2//
3// sessionStorage: PKCE verifier, OAuth state (per-tab, cleared on callback)
4// localStorage: tokens, user DID (shared across tabs)
5
6export function createStorage(namespace) {
7 const key = (name) => `appview_${namespace}_${name}`
8
9 // sessionStorage keys (per-tab OAuth flow state)
10 const SESSION_KEYS = new Set(['codeVerifier', 'oauthState', 'redirectUri'])
11
12 return {
13 get(name) {
14 const store = SESSION_KEYS.has(name) ? sessionStorage : localStorage
15 return store.getItem(key(name))
16 },
17 set(name, value) {
18 const store = SESSION_KEYS.has(name) ? sessionStorage : localStorage
19 store.setItem(key(name), value)
20 },
21 remove(name) {
22 const store = SESSION_KEYS.has(name) ? sessionStorage : localStorage
23 store.removeItem(key(name))
24 },
25 clear() {
26 for (const name of SESSION_KEYS) sessionStorage.removeItem(key(name))
27 for (const name of ['accessToken', 'refreshToken', 'tokenExpiresAt', 'userDid', 'clientId']) {
28 localStorage.removeItem(key(name))
29 }
30 },
31 }
32}
33
34// Multi-tab lock for token refresh coordination
35const LOCK_TIMEOUT = 5000
36
37export async function acquireLock(namespace, lockName) {
38 const lockKey = `appview_${namespace}_lock_${lockName}`
39 const lockValue = `${Date.now()}_${Math.random()}`
40 const deadline = Date.now() + LOCK_TIMEOUT
41
42 while (Date.now() < deadline) {
43 const existing = localStorage.getItem(lockKey)
44 if (existing) {
45 const ts = parseInt(existing.split('_')[0])
46 if (Date.now() - ts > LOCK_TIMEOUT) {
47 localStorage.removeItem(lockKey)
48 } else {
49 await new Promise((r) => setTimeout(r, 50))
50 continue
51 }
52 }
53
54 localStorage.setItem(lockKey, lockValue)
55 await new Promise((r) => setTimeout(r, 10))
56 if (localStorage.getItem(lockKey) === lockValue) return lockValue
57 }
58 return null
59}
60
61export function releaseLock(namespace, lockName, lockValue) {
62 const lockKey = `appview_${namespace}_lock_${lockName}`
63 if (localStorage.getItem(lockKey) === lockValue) localStorage.removeItem(lockKey)
64}