/** * DMS token 的**唯一**读取口径(vite 代理与 harness 脚本共用这一份)。 * * 为什么要有这个模块:token 是外部 OAuth 签发的 JWT,**约 24 小时就过期**, * 而它以前散在三处(`.env.development.local` 的 `DMS_TOKEN`、脚本的参数、 * `process.env.DMS_TOKEN`),每处都要人肉填一遍 —— 用户的原话是「每次都要重新填 token 太麻烦了」。 * * 现在的顺序:**显式参数 → 环境变量 → 本机文件 `.dms-token`**。 * `.dms-token` 是被 gitignore 的单行文件,由 `npm run dms:token` 写入(可从剪贴板读)。 * * vite 代理**每个请求**调用一次 `readDmsToken()`(见 vite.config.ts), * 所以换 token 之后 **dev server 不用重启**。 */ import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; /** 本机单行 token 文件(gitignore;仓库根) */ export const DMS_TOKEN_FILE = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '.dms-token'); /** * 取 token。`explicit` 通常来自命令行参数;都拿不到时返回空串(调用方自己决定是报错还是告警)。 * 每次调用都**现读文件**,所以运行中换 token 立即生效。 */ export const readDmsToken = (explicit) => { const fromArg = String(explicit || '').trim(); if (fromArg) return fromArg; const fromEnv = String(process.env.DMS_TOKEN || '').trim(); if (fromEnv) return fromEnv; try { return readFileSync(DMS_TOKEN_FILE, 'utf8').trim(); } catch { return ''; } }; /** 只解 JWT payload(不校验签名;拿不到返回 null)—— 只为给出人能看懂的过期提示 */ export const parseJwtPayload = (token) => { const parts = String(token || '').split('.'); if (parts.length !== 3 || !parts[1]) return null; try { return JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8')); } catch { return null; } }; /** 人类可读的 token 状态:用户名 + 有效期 + 剩余时长(脚本与 vite 告警共用同一套措辞) */ export const describeDmsToken = (token) => { const payload = parseJwtPayload(token); if (!payload) return '⚠️ 不像 JWT(应为 a.b.c 三段),请确认复制的是 access_token'; const who = payload.username || payload.userId || '未知用户'; const exp = Number(payload.exp); if (!Number.isFinite(exp)) return `用户 ${who};payload 里没有 exp,无法判断有效期`; const leftMs = exp * 1000 - Date.now(); const at = new Date(exp * 1000).toLocaleString('zh-CN'); return leftMs > 0 ? `用户 ${who};有效期至 ${at}(还剩 ${(leftMs / 3600000).toFixed(1)} 小时)` : `用户 ${who};**已于 ${at} 过期**(${(Math.abs(leftMs) / 3600000).toFixed(1)} 小时前)`; };