229 lines
8.0 KiB
JavaScript
229 lines
8.0 KiB
JavaScript
'use strict';
|
||
|
||
const BUSINESS_SOURCE = 'LIANSYN_PLATFORM';
|
||
const EXTENSION_SOURCE = 'LTJT_ORDER_ASSISTANT_EXTENSION';
|
||
const PRODUCTION_PLATFORM_ORIGIN = 'https://lwlt.nianxx.cn';
|
||
|
||
const $ = (selector) => document.querySelector(selector);
|
||
|
||
function setText(selector, text) {
|
||
const el = $(selector);
|
||
if (el) el.textContent = text;
|
||
}
|
||
|
||
function setBadge(text, mode = '') {
|
||
const badge = $('#stateBadge');
|
||
if (!badge) return;
|
||
badge.textContent = text;
|
||
badge.classList.toggle('bad', mode === 'bad');
|
||
badge.classList.toggle('paused', mode === 'paused');
|
||
}
|
||
|
||
function isBusinessSystemUrl(url) {
|
||
try {
|
||
const parsed = new URL(String(url || ''));
|
||
if (parsed.origin === PRODUCTION_PLATFORM_ORIGIN) return true;
|
||
return parsed.protocol === 'http:'
|
||
&& ['localhost', '127.0.0.1'].includes(parsed.hostname)
|
||
&& ['', '8765', '8786'].includes(parsed.port);
|
||
} catch (error) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function friendlyError(error) {
|
||
const message = error?.message || String(error || '');
|
||
if (/Extension context invalidated/i.test(message)) {
|
||
return '插件刚重新加载,旧页面桥接已失效。请刷新业务系统页面后重新打开本面板。';
|
||
}
|
||
return message;
|
||
}
|
||
|
||
async function findBusinessTab() {
|
||
const tabs = await chrome.tabs.query({});
|
||
return tabs.find((tab) => isBusinessSystemUrl(tab.url)) || null;
|
||
}
|
||
|
||
async function pingBusinessBridge(tabId) {
|
||
await chrome.scripting.executeScript({
|
||
target: { tabId },
|
||
files: ['business-bridge.js']
|
||
});
|
||
|
||
const [probe] = await chrome.scripting.executeScript({
|
||
target: { tabId },
|
||
func: async (businessSource, extensionSource) => {
|
||
const requestId = `POPUP-PING-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||
return new Promise((resolve) => {
|
||
let staleError = null;
|
||
const timer = window.setTimeout(() => {
|
||
window.removeEventListener('message', onMessage);
|
||
resolve(staleError || {
|
||
ok: false,
|
||
message: '业务系统页面未收到插件桥接响应。'
|
||
});
|
||
}, 1500);
|
||
|
||
function onMessage(event) {
|
||
if (event.source !== window) return;
|
||
const message = event.data || {};
|
||
if (message.source !== extensionSource || message.requestId !== requestId) return;
|
||
if (message.type === 'ERROR') {
|
||
const errorMessage = String(message.payload?.message || '');
|
||
if (/Extension context invalidated/i.test(errorMessage)) {
|
||
// An extension reload can leave old content-script handlers in the
|
||
// page. Keep waiting for the current bridge's valid PONG.
|
||
staleError = { ok: false, message: errorMessage };
|
||
return;
|
||
}
|
||
}
|
||
if (message.type !== 'PONG' || message.payload?.ok === false) return;
|
||
window.clearTimeout(timer);
|
||
window.removeEventListener('message', onMessage);
|
||
resolve(message.payload || { ok: false, message: '插件桥接返回为空。' });
|
||
}
|
||
|
||
window.addEventListener('message', onMessage);
|
||
window.postMessage({
|
||
source: businessSource,
|
||
requestId,
|
||
type: 'PING',
|
||
payload: {}
|
||
}, window.location.origin);
|
||
});
|
||
},
|
||
args: [BUSINESS_SOURCE, EXTENSION_SOURCE]
|
||
});
|
||
|
||
return probe?.result || {
|
||
ok: false,
|
||
message: '无法读取业务系统桥接检测结果。'
|
||
};
|
||
}
|
||
|
||
async function detectBusinessConnection() {
|
||
const tab = await findBusinessTab();
|
||
if (!tab?.id) {
|
||
return {
|
||
connected: false,
|
||
bridge: null,
|
||
text: '未打开业务系统页面',
|
||
hint: '请打开生产控制平面或 http://127.0.0.1:8786/,然后刷新本面板。'
|
||
};
|
||
}
|
||
|
||
try {
|
||
const bridge = await pingBusinessBridge(tab.id);
|
||
if (!bridge.ok) {
|
||
return {
|
||
connected: false,
|
||
bridge,
|
||
text: '业务系统未联通',
|
||
hint: bridge.message || '插件桥接未响应。'
|
||
};
|
||
}
|
||
return {
|
||
connected: true,
|
||
bridge,
|
||
text: '已联通',
|
||
hint: '业务系统页面已收到插件桥接响应。'
|
||
};
|
||
} catch (error) {
|
||
return {
|
||
connected: false,
|
||
bridge: null,
|
||
text: '业务系统未联通',
|
||
hint: friendlyError(error)
|
||
};
|
||
}
|
||
}
|
||
|
||
async function readAutomationEnabled() {
|
||
const saved = await chrome.storage.local.get('erpAutomationEnabled');
|
||
return saved.erpAutomationEnabled !== false;
|
||
}
|
||
|
||
async function readKeepaliveEnabled() {
|
||
const saved = await chrome.storage.local.get('erpKeepaliveEnabled');
|
||
return saved.erpKeepaliveEnabled !== false;
|
||
}
|
||
|
||
function keepaliveStatusText(connection, automationEnabled, userEnabled) {
|
||
if (!automationEnabled) return '随“允许插件操作 ERP”关闭。';
|
||
if (!userEnabled) return '已关闭;插件不会发送待机保活请求。';
|
||
const keepalive = connection.bridge?.erp_session?.erp_keepalive;
|
||
if (['scheduled', 'already_running', 'running'].includes(keepalive?.recovery_status)) {
|
||
return '检测到 ERP 链路异常,正在刷新现有 ERP 页面并进行只读复测。';
|
||
}
|
||
if (keepalive?.recovery_status === 'success' && keepalive.last_recovered_at) {
|
||
return `已自动刷新并恢复 ERP 链路:${new Date(keepalive.last_recovered_at).toLocaleString()}`;
|
||
}
|
||
if (keepalive?.recovery_status === 'failed' && keepalive.recovery_error) {
|
||
return `ERP 链路异常,已尝试刷新但仍未恢复:${keepalive.recovery_error}。`;
|
||
}
|
||
if (keepalive?.last_status === 'success' && keepalive.last_success_at) {
|
||
return `已开启;最近一次成功:${new Date(keepalive.last_success_at).toLocaleString()}`;
|
||
}
|
||
if (keepalive?.last_status === 'session_expired') return '已开启,但最近一次检测发现 ERP 会话已失效,请重新登录。';
|
||
if (keepalive?.last_status === 'failed') return `已开启;最近一次检测失败:${keepalive.last_error || '网络或页面不可用'}。`;
|
||
return '已开启;约每 5 分钟发送一次只读请求,不刷新当前页面。';
|
||
}
|
||
|
||
async function updateStatusPanel() {
|
||
setBadge('检测中');
|
||
setText('#versionLabel', `v${chrome.runtime.getManifest().version}`);
|
||
|
||
const [enabled, keepaliveEnabled, connection] = await Promise.all([
|
||
readAutomationEnabled(),
|
||
readKeepaliveEnabled(),
|
||
detectBusinessConnection()
|
||
]);
|
||
|
||
const toggle = $('#erpAutomationToggle');
|
||
if (toggle) toggle.checked = enabled;
|
||
const keepaliveToggle = $('#erpKeepaliveToggle');
|
||
if (keepaliveToggle) {
|
||
keepaliveToggle.checked = keepaliveEnabled;
|
||
keepaliveToggle.disabled = !enabled;
|
||
}
|
||
setText('#keepaliveText', keepaliveStatusText(connection, enabled, keepaliveEnabled));
|
||
|
||
if (!enabled) {
|
||
setBadge('已关闭', 'paused');
|
||
setText('#connectionText', connection.connected ? '已联通,ERP 操作已关闭' : '未联通,ERP 操作已关闭');
|
||
setText('#connectionHint', connection.connected
|
||
? '业务系统已连接;插件不会打开、填写或提交 ERP。'
|
||
: connection.hint);
|
||
return;
|
||
}
|
||
|
||
setBadge(connection.connected ? '已联通' : '未联通', connection.connected ? '' : 'bad');
|
||
setText('#connectionText', connection.connected ? '已联通,ERP 操作已开启' : connection.text);
|
||
setText('#connectionHint', connection.connected
|
||
? '插件允许操作 ERP。任务创建和进度跟踪请在业务系统中完成。'
|
||
: connection.hint);
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', async () => {
|
||
const toggle = $('#erpAutomationToggle');
|
||
if (toggle) {
|
||
toggle.checked = await readAutomationEnabled();
|
||
toggle.addEventListener('change', async (event) => {
|
||
await chrome.storage.local.set({ erpAutomationEnabled: event.currentTarget.checked });
|
||
await updateStatusPanel();
|
||
});
|
||
}
|
||
|
||
const keepaliveToggle = $('#erpKeepaliveToggle');
|
||
if (keepaliveToggle) {
|
||
keepaliveToggle.checked = await readKeepaliveEnabled();
|
||
keepaliveToggle.addEventListener('change', async (event) => {
|
||
await chrome.storage.local.set({ erpKeepaliveEnabled: event.currentTarget.checked });
|
||
await updateStatusPanel();
|
||
});
|
||
}
|
||
|
||
await updateStatusPanel();
|
||
window.setTimeout(() => updateStatusPanel().catch(() => {}), 800);
|
||
});
|