/** * 平台在把请求转发给模块自己的 API(dist/api.js)之前,会先校验登录态, * 校验通过后把用户信息塞进这个内部请求头再转发。未登录的请求已经在到达 * 这里之前被平台拦截为 401 了。 * * 这是 apps/api(编码/写头,见 _utils/module-auth.ts)与本包的 * createModuleApp(解码/读头,见 create-module-app.ts)之间的内部协议, * 模块作者不需要关心:`createModuleApp()` 已经自动把解析结果 derive 到 * Elysia context 里,handler 直接解构 `user` 即可,等价于其他框架的 * `request.user`。因此本文件里只有 `MODULE_USER_HEADER`/`ModuleRequestUser` * 对外导出(供 apps/api 编码时复用同一份契约),`getModuleRequestUser` 本身 * 不对外导出,只在包内被 create-module-app.ts 调用。 */ export const MODULE_USER_HEADER = 'x-stago-user'; /** 网关透传给模块 API 的用户;字段与 live.employees 一一对应:id / phone / name */ export interface ModuleRequestUser { id: number; phone: string; name: string | null; roles: string[]; } function bytesToBinaryString(bytes: Uint8Array): string { let binary = ''; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]!); } return binary; } function binaryStringToBytes(binary: string): Uint8Array { const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; } function base64Encode(value: string): string { if (typeof Buffer !== 'undefined') { return Buffer.from(value, 'utf-8').toString('base64'); } // 浏览器兜底(dev http client 在页面里编码用户头) return btoa(bytesToBinaryString(new TextEncoder().encode(value))); } function base64Decode(value: string): string { if (typeof Buffer !== 'undefined') { return Buffer.from(value, 'base64').toString('utf-8'); } // 浏览器/边缘环境兜底(模块 API 目前只在 Node/Bun 运行时加载,理论上不会走到这里) return new TextDecoder().decode(binaryStringToBytes(atob(value))); } /** 将用户 payload 编码为 MODULE_USER_HEADER 值(与 apps/api attachModuleUserHeader 同源) */ export function encodeModuleRequestUser(user: ModuleRequestUser): string { return base64Encode(JSON.stringify(user)); } /** 克隆请求并写入 MODULE_USER_HEADER(Request 不可变) */ export function attachModuleRequestUserHeader(request: Request, user: ModuleRequestUser): Request { const headers = new Headers(request.headers); headers.set(MODULE_USER_HEADER, encodeModuleRequestUser(user)); return new Request(request, { headers }); } /** * 从请求头解析平台校验过的当前登录用户,未登录/头缺失时返回 null。 * 包内私有:仅被 create-module-app.ts 调用,不对外导出。 */ export function getModuleRequestUser(request: Request): ModuleRequestUser | null { const raw = request.headers.get(MODULE_USER_HEADER); if (!raw) return null; try { const parsed = JSON.parse(base64Decode(raw)); if ( typeof parsed !== 'object' || parsed === null || typeof parsed.id !== 'number' || typeof parsed.phone !== 'string' ) { return null; } return { id: parsed.id, phone: parsed.phone, name: typeof parsed.name === 'string' ? parsed.name : null, roles: Array.isArray(parsed.roles) ? parsed.roles.filter((r: unknown) => typeof r === 'string') : [], }; } catch { return null; } }