// API key 凭证模块:登录门存取(localStorage 优先,无 DOM 环境回退内存)。 // 服务端开启认证时(PG 档默认),所有请求带 Bearer 头;SSE/静态资源链接走 ?key=。 const KEY = 'agent-system.apiKey' interface KV { getItem(k: string): string | null; setItem(k: string, v: string): void; removeItem(k: string): void } function pickStorage(): KV { try { if (typeof localStorage !== 'undefined') return localStorage } catch { /* 隐私模式等 */ } const mem = new Map() return { getItem: k => mem.get(k) ?? null, setItem: (k, v) => void mem.set(k, v), removeItem: k => void mem.delete(k), } } const storage = pickStorage() export const auth = { apiKey(): string { return storage.getItem(KEY) ?? '' }, setKey(key: string) { storage.setItem(KEY, key.trim()) }, clear() { storage.removeItem(KEY) }, // 密码登录成功后切换到 HttpOnly 会话 cookie:必须清掉残留 Bearer——服务端 // 认证中间件 Bearer 优先于会话 cookie,失效残留 key 会把有效会话也判成 401。 beginSession() { this.clear() }, // 带凭证的 fetch:未设置密钥时与原生 fetch 等价(嵌入式档无认证)。 fetch(input: string, init?: RequestInit): Promise { const key = this.apiKey() if (!key) return fetch(input, init) const headers = new Headers(init?.headers ?? {}) headers.set('Authorization', `Bearer ${key}`) return fetch(input, { ...init, headers }) }, // 退出登录:清凭证并整页回登录门(服务端会话/密钥均为无状态凭证,无需服务端注销)。 logout() { this.clear() try { location.reload() } catch { /* 非 DOM 环境(测试)忽略 */ } }, // 无法带头的资源 URL(EventSource / / 下载):追加 ?key=。 withKey(url: string): string { const key = this.apiKey() if (!key) return url return url + (url.includes('?') ? '&' : '?') + 'key=' + encodeURIComponent(key) }, }