| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- /**
- * 开发态自动登录(**仅本地静态服务**,绝不进入应用包)
- *
- * ## 它解决什么
- *
- * 本地跑生产构建(`npm run build` + 静态服务)时,登录要人工点「Token 直登」并粘贴
- * 一长串 `r:` Token —— 每建一个浏览器上下文就要重复一次,Playwright / 冒烟 /
- * 真实账号回归全都卡在这一步,无法自动化。
- *
- * ## 它怎么工作
- *
- * 本模块由 `scripts/dev-server.mjs` 进程通过 `import()` 加载,把一段**内联脚本**
- * 注入 `index.html` 的 `<head>`。那段脚本唯一做的事:在 Angular 启动前,把
- * `.claude/dev-login.json` 里的 token / me 写进 localStorage(键名与
- * `SessionService` 完全一致:`fmode_session_token` / `fmode_me`)。
- *
- * · 不在 `src/` 下 → 不在任何 tsconfig 的输入里 → **不会被打进应用构建**
- * · 不写进 `index.html` 源文件 → 仓库里的 `index.html` 保持干净
- * · 只有 `scripts/dev-server.mjs` 会调用 → 生产部署链路完全不知道它存在
- *
- * ## 为什么不放在环境文件里
- *
- * `environment*.ts` 会被编译进包,`ng build` 之后 token 就固定在产物里了。
- * 本模块走运行时注入,token 改了只需刷新页面,且产物始终是干净的生产包。
- */
- import { readFileSync, existsSync } from 'node:fs';
- import { resolve, dirname } from 'node:path';
- import { fileURLToPath } from 'node:url';
- const here = dirname(fileURLToPath(import.meta.url));
- // 本文件在 scripts/ 下;.claude/dev-login.json 在**项目根**下,故上溯一级
- const CONFIG_PATH = resolve(here, '../.claude/dev-login.json');
- export const DEV_LOGIN_KEYS = { TOKEN: 'fmode_session_token', ME: 'fmode_me' };
- /**
- * 读 `.claude/dev-login.json`。
- *
- * @returns {{ enabled: boolean, reason?: string, payload?: { token: string, me: object } }}
- */
- export function loadDevLogin() {
- if (!existsSync(CONFIG_PATH)) {
- return { enabled: false, reason: `未找到 ${CONFIG_PATH}` };
- }
- let raw;
- try {
- raw = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
- } catch (e) {
- return { enabled: false, reason: `dev-login.json 解析失败:${e.message}` };
- }
- const token = typeof raw.token === 'string' ? raw.token.trim() : '';
- const me = raw.me && typeof raw.me === 'object' ? raw.me : null;
- if (!token) return { enabled: false, reason: 'dev-login.json 的 token 为空' };
- if (!me || !me.id) return { enabled: false, reason: 'dev-login.json 的 me.id 为空' };
- return { enabled: true, payload: { token, me } };
- }
- /**
- * 生成注入用的内联脚本。
- *
- * 🔴 用 `JSON.stringify` 而不是模板拼接 —— 值里的引号/换行会被正确转义。
- * 🔴 但 `JSON.stringify` **不会**转义 `<` / `>` —— 值里若含 `</script>` 会提前闭合
- * 标签,把后面的内容当成 HTML 执行(实测踩到)。所以额外把 `<` `>` `&`
- * 转成 `<` / `>` / `&`(JSON 里等价,且无法闭合标签)。
- */
- export function buildInjectionScript(payload) {
- const { TOKEN, ME } = DEV_LOGIN_KEYS;
- const data = JSON.stringify({ key: TOKEN, meKey: ME, token: payload.token, me: payload.me })
- .replace(/</g, '\\u003c')
- .replace(/>/g, '\\u003e')
- .replace(/&/g, '\\u0026');
- return `<script>(function(){try{var d=${data};` +
- `if(!localStorage.getItem(d.key)){localStorage.setItem(d.key,d.token);` +
- `localStorage.setItem(d.meKey,JSON.stringify(d.me));}}catch(e){}})();</script>`;
- }
- /**
- * 把注入脚本插到 `</head>` 前。幂等:已注入过就原样返回。
- */
- export function injectIntoHtml(html, payload) {
- const marker = '<!-- dev-login -->';
- if (html.includes(marker)) return html;
- const script = marker + buildInjectionScript(payload);
- const i = html.toLowerCase().indexOf('</head>');
- if (i < 0) return script + html;
- return html.slice(0, i) + script + html.slice(i);
- }
|