pi-ele Phase 2: token/API 基地址注入 —— web/src 零改动成为独立桌面客户端

- main.cjs: 从 env 读 AGENTMAIL_GATEWAY_URL / AGENTMAIL_TOKEN(USER_KEY),
  通过 additionalArguments 传给 preload;API_BASE = <gateway>/api/v1
- preload.cjs: 从 process.argv 读注入参数,contextBridge 暴露
  window.__AGENTMAIL_API_BASE__ / __AGENTMAIL_TOKEN__
  → web/src 的 config.ts 自动读取,client.ts/sse.ts 零改动
- 效果:Electron 启动后 api.me() 带 Bearer 返回用户,直接进入主界面
  (无登录页),收件箱等功能立即可用

验证(xvfb headless):
- 注入链路:渲染进程拿到 apiBase=192.168.2.60:8180 + token 
- 端到端:gui-lab key 注入 → 界面呈现 收件/授权/发件/日历/联系 导航 + 空收件箱,
  无登录表单(inputCount=0, hasLogin=false)
This commit is contained in:
2026-09-07 07:49:30 +08:00
parent dc5b9e440f
commit fe96e197d3
2 changed files with 28 additions and 4 deletions

View File

@ -2,9 +2,10 @@
* AgentMail Electron 主进程。
*
* 职责:
* - 创建 BrowserWindow 加载前端dev=http://localhost:5173prod=../dist/index.html
* - 系统托盘Phase 4 完整实现,这里先预留
* - SSE 长连接由主进程维持Phase 2这里先只做窗口生命周期
* - 创建 BrowserWindow 加载前端dev=&lt;ELECTRON_START_URL&gt;prod=../dist/index.html
* - 系统托盘Phase 4 完整实现)
* - 离线缓存Phase 4
* - 把 Gateway 地址与用户密钥注入渲染进程Phase 2让 web/src 以独立客户端身份连任意 Gateway
*
* 设计约束:
* - 渲染进程复用 web/src 的 React 代码,零改动
@ -18,11 +19,21 @@ const path = require('node:path');
const DEV_URL = process.env.ELECTRON_START_URL || 'http://localhost:5173';
const isDev = !!process.env.ELECTRON_START_URL || !app.isPackaged;
// Gateway 地址与用户密钥:环境变量注入(桌面客户端用 user_key 认证)
const GATEWAY_URL = (process.env.AGENTMAIL_GATEWAY_URL || 'http://127.0.0.1:8180').replace(/\/+$/, '');
const API_BASE = `${GATEWAY_URL}/api/v1`;
const TOKEN = process.env.AGENTMAIL_TOKEN || process.env.AGENTMAIL_USER_KEY || '';
let mainWindow = null;
let tray = null;
/** 创建主窗口 */
function createMainWindow() {
// 通过 additionalArguments 把 API 基地址与 token 传给 preloadpreload 从 renderer 的 process.argv 末尾读)
// 传入的是透传 arg用户页面不能直接改preload 能读到
const injectArgs = [`--agentmail-api-base=${API_BASE}`];
if (TOKEN) injectArgs.push(`--agentmail-token=${TOKEN}`);
mainWindow = new BrowserWindow({
width: 1280,
height: 800,
@ -31,10 +42,11 @@ function createMainWindow() {
title: 'AgentMail',
icon: path.join(__dirname, '..', 'src', 'icons', 'tray-icon.png'),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
preload: path.join(__dirname, 'preload.cjs'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
additionalArguments: injectArgs,
},
});

View File

@ -9,6 +9,18 @@
const { contextBridge, ipcRenderer } = require('electron');
// 注入 API 基地址与 token渲染进程的 config.ts 在读 window.__AGENTMAIL_API_BASE__
// 与 __AGENTMAIL_TOKEN__ 时拿到它们,于是现有 client.ts/sse.ts **零改动**复用。
// 这两个值由主进程在创建窗口时通过附加参数传进来(见 main.cjs 的 additionalArguments
function getInjected(name) {
const prefix = `--agentmail-${name}=`;
const arg = process.argv.find(a => a.startsWith(prefix));
return arg ? arg.slice(prefix.length) : undefined;
}
contextBridge.exposeInMainWorld('__AGENTMAIL_API_BASE__', getInjected('api-base') || 'http://127.0.0.1:8180/api/v1');
contextBridge.exposeInMainWorld('__AGENTMAIL_TOKEN__', getInjected('token') || undefined);
contextBridge.exposeInMainWorld('agentmail', {
/** Gateway 基础地址(主进程 env 或默认 127.0.0.1:8180 */
gatewayUrl: () => ipcRenderer.invoke('get-gateway-url'),