fix: 设置页脚本因重复声明 xt 整体加载失败(服务商选不了、所有设置不生效)

- settings.js 的局部变量改名为 api,避开 contextBridge 暴露的不可重声明全局 xt
- dev-mock 按 contextBridge 的方式定义 xt,浏览器预览也能暴露同类问题
- 加测试:渲染脚本禁止在顶层声明 xt
- 版本 0.1.2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
hopechen067andClaude Opus 5.5 committed 2026-10-02 03:54:20 -04:00
1 parent ad83db6e7f
commit 1ae69ab95e
5 files changed
+20 -11

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "xtranslate",
"version": "0.1.1",
"version": "0.1.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "xtranslate",
"version": "0.1.1",
"version": "0.1.2",
"dependencies": {
"koffi": "3.3.2"
},
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "xtranslate",
"productName": "Xtranslate",
"version": "0.1.1",
"version": "0.1.2",
"description": "中英互译输入法:热键呼出,打字即译,口语化",
"main": "src/main/main.js",
"private": true,
+3 -2
View File
@@ -10,7 +10,8 @@ if (!window.xt) {
const canned = { '我先撤了哈,明天见': "I'm heading out, see you tomorrow!", 'No worries, take your time!': '没事没事,你慢慢来!' };
const timers = {};
const merge = (a, b) => { for (const k in b) a[k] = b[k] && typeof b[k] === 'object' && !Array.isArray(b[k]) ? merge({ ...(a[k] || {}) }, b[k]) : b[k]; return a; };
window.xt = {
// 和 contextBridge 一样定义成不可重声明的全局,页面里再写 const xt 会像在 Electron 里一样报错
Object.defineProperty(window, 'xt', { configurable: false, enumerable: true, writable: false, value: {
translate({ id, text, engine }) {
const full = canned[text.trim()] || (/[一-鿿]/.test(text) ? `(mock EN) ${text}` : `(模拟中文)${text}`);
if ((engine || cfg.engine) === 'llm' && !cfg.llm.apiKeySet) return Promise.reject(new Error('还没填大模型的 API Key,去设置里填一下'));
@@ -49,6 +50,6 @@ if (!window.xt) {
: { ok: true, sample: "I'm heading out, see you tomorrow!", ms: 420 },
openSettings: () => console.log('[mock] openSettings'),
onConfigChanged: (cb) => listeners.config.push(cb),
};
} });
document.documentElement.style.background = '#8a93a3';
}
+6 -6
View File
@@ -1,7 +1,7 @@
'use strict';
const $ = (id) => document.getElementById(id);
const xt = window.xt;
const api = window.xt; // 不能叫 xt:contextBridge 已把 xt 定义为不可重声明的全局
let providers = [];
let cfg = null;
@@ -14,7 +14,7 @@ function setResult(el, text, kind) {
async function save(partial) {
try {
cfg = await xt.setConfig(partial);
cfg = await api.setConfig(partial);
render();
return true;
} catch (e) {
@@ -92,7 +92,7 @@ for (const btn of document.querySelectorAll('[data-test]')) {
await save(partial);
$('llm-key').value = '';
}
const r = await xt.testEngine({ engine });
const r = await api.testEngine({ engine });
if (r.ok) setResult(out, `✓ ${r.sample}(${r.ms} ms)`, 'ok');
else setResult(out, r.error || '失败', 'err');
} catch (e) {
@@ -160,11 +160,11 @@ hk.addEventListener('keydown', async (e) => {
}
});
xt.onConfigChanged((c) => { cfg = c; render(); });
api.onConfigChanged((c) => { cfg = c; render(); });
(async () => {
providers = await xt.getProviders();
providers = await api.getProviders();
$('llm-provider').replaceChildren(...providers.map((p) => Object.assign(document.createElement('option'), { value: p.id, textContent: p.name })));
cfg = await xt.getConfig();
cfg = await api.getConfig();
render();
})();
+8
View File
@@ -26,3 +26,11 @@ test('src 下所有 JS 文件语法正确', () => {
}
}
});
test('渲染进程脚本不在顶层声明 xt(会与 contextBridge 暴露的全局冲突,整个脚本加载失败)', () => {
const dir = path.join(__dirname, '..', 'src', 'renderer');
for (const name of fs.readdirSync(dir).filter((n) => n.endsWith('.js') && n !== 'dev-mock.js')) {
const src = fs.readFileSync(path.join(dir, name), 'utf8');
assert.doesNotMatch(src, /^(const|let|var|function|class)\s+xt\b/m, name);
}
});