1/**2 * Cross-tab coordination for account-secret store access.3 *4 * The vendor secret store persists the account secret with a localStorage5 * check-then-write, which is not atomic across tabs: two first tabs could6 * each mint a secret, the last write would win, and the losing tab would7 * spend its session writing under an identity that disappears on reload.8 * Serializing every read-or-create and replacement behind one exclusive,9 * app-scoped Web Lock closes that window — the second tab enters the store10 * only after the first tab's write is visible.11 *12 * The durable capability gate requires Web Locks before the runtime opens, so13 * a browser boot always holds the lock. Without a lock manager (single-process14 * tests) the operation runs directly; there is no concurrent tab to exclude.15 *16 * @module17 */1819/** Minimal Web Locks surface, injectable for tests. */20export type AuthSecretLockManager = {21 request<T>(22 name: string,23 options: { mode: "exclusive" },24 callback: (lock: unknown) => Promise<T> | T,25 ): Promise<T>;26};2728/** The exclusive Web Lock name serializing one app's secret-store access. */29export function authSecretLockName(appId: string): string {30 const key = appId.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "app";31 return `lofi-auth-secret-${key}`;32}3334function defaultLocks(): AuthSecretLockManager | undefined {35 const locks = typeof navigator === "undefined" ? undefined : navigator.locks;36 return typeof locks?.request === "function"37 ? locks as unknown as AuthSecretLockManager38 : undefined;39}4041/** Runs one secret-store operation under the app's exclusive secret lock. */42export async function withAuthSecretLock<T>(43 appId: string,44 operation: () => Promise<T>,45 locks: AuthSecretLockManager | undefined = defaultLocks(),46): Promise<T> {47 if (!locks) return await operation();48 return await locks.request(49 authSecretLockName(appId),50 { mode: "exclusive" },51 () => operation(),52 );53}