chore: release v0.98.0

This commit is contained in:
coso
2026-03-29 14:15:34 +08:00
parent dfbbb3d204
commit deceacc699
435 changed files with 35673 additions and 8535 deletions
+15 -1
View File
@@ -4,12 +4,13 @@
## 功能
- Observer 通道自动连接:`/lime-chrome-observer/Lime_Key=...`
- Observer 通道自动连接:`/lime-chrome-observer/<bridge_key>?profileKey=...`
- 页面信息上报:标题、URL、Markdown
- 远程指令执行:`open_url` / `click` / `type` / `scroll` / `switch_tab` / `list_tabs` / `go_back` 等
- 弹窗入口:直接打开 Lime 的“连接器”页,并保留高级手动配置
- 自动配置文件:导出后自动写入 `auto_config.json`
- 弹窗配置:`serverUrl`、`bridgeKey`、`profileKey`、监控开关、手动抓取
- 桌面端主动断开:当 Lime 设置页点击“断开已连接扩展”时,扩展会关闭 observer 连接并停止自动重连,直到用户手动重连
## 安装
@@ -67,12 +68,25 @@
npm run bridge:e2e -- --server ws://127.0.0.1:8787 --key proxy_cast --profile default
```
默认还会通过 `http://127.0.0.1:3030/invoke` 调用 `disconnect_browser_connector_session`,继续验证桌面端主动断开链路:
- observer/control 均收到 `force_disconnect`
- `disconnect_browser_connector_session` 返回断开计数
- 在干净环境下,`get_chrome_bridge_status` 最终归零
如果你当前只想验证纯 WebSocket 握手和命令回路,而不依赖 DevBridge invoke,可显式跳过该阶段:
```bash
npm run bridge:e2e -- --server ws://127.0.0.1:8787 --key proxy_cast --profile default --skip-force-disconnect
```
脚本会验证:
- observer/control 握手
- 双向心跳 ack
- `wait_for_page_info=true` 命令链路(`command_result` + `page_info_update`)
- 普通命令链路(`command_result`)
- 桌面端主动断开链路(默认开启)
## 兼容说明
+60 -14
View File
@@ -19,6 +19,7 @@ let activeTabId = null;
let monitoringEnabled = true;
let latestPageInfo = null;
let lastSettings = { ...DEFAULT_SETTINGS };
const expectedClosedSockets = new WeakSet();
function logInfo(message, payload) {
if (payload === undefined) {
@@ -62,6 +63,10 @@ function buildObserverUrl(settings) {
return `${normalized}/lime-chrome-observer/${encodeURIComponent(bridgeKey)}?profileKey=${profileKey}`;
}
function isCapturableUrl(url) {
return /^https?:\/\//i.test(String(url || "").trim());
}
async function connectObserver(forceReconnect = false) {
if (ws && ws.readyState === WebSocket.OPEN && !forceReconnect) {
return;
@@ -83,15 +88,14 @@ async function connectObserver(forceReconnect = false) {
}
if (forceReconnect && ws) {
try {
ws.close();
} catch (_) {}
closeObserverSocket(ws, { shouldReconnect: false });
}
logInfo(`连接 observer: ${url}`);
ws = new WebSocket(url);
const socket = new WebSocket(url);
ws = socket;
ws.onopen = () => {
socket.onopen = () => {
reconnectAttempts = 0;
setConnectionState(true);
startHeartbeat();
@@ -99,7 +103,7 @@ async function connectObserver(forceReconnect = false) {
triggerPageCapture("ws_open");
};
ws.onmessage = async (event) => {
socket.onmessage = async (event) => {
try {
const payload = JSON.parse(event.data);
await handleObserverMessage(payload);
@@ -108,14 +112,22 @@ async function connectObserver(forceReconnect = false) {
}
};
ws.onclose = () => {
socket.onclose = () => {
const shouldReconnect = !expectedClosedSockets.has(socket);
if (ws === socket) {
ws = null;
}
setConnectionState(false);
clearHeartbeatTimer();
scheduleReconnect();
if (shouldReconnect) {
scheduleReconnect();
} else {
reconnectAttempts = 0;
}
broadcastStatus();
};
ws.onerror = (error) => {
socket.onerror = (error) => {
logWarn("WebSocket 错误", error?.message || error);
};
}
@@ -124,9 +136,7 @@ function disconnectObserver(manual = true) {
clearReconnectTimer();
clearHeartbeatTimer();
if (ws) {
try {
ws.close();
} catch (_) {}
closeObserverSocket(ws, { shouldReconnect: false });
}
if (manual) {
setConnectionState(false);
@@ -134,6 +144,18 @@ function disconnectObserver(manual = true) {
}
}
function closeObserverSocket(socket, { shouldReconnect }) {
if (!socket) {
return;
}
if (!shouldReconnect) {
expectedClosedSockets.add(socket);
}
try {
socket.close();
} catch (_) {}
}
function setConnectionState(connected) {
isConnected = connected;
chrome.action.setBadgeText({ text: connected ? "ON" : "OFF" });
@@ -211,6 +233,11 @@ async function handleObserverMessage(payload) {
if (type === "heartbeat_ack" || type === "connection_ack") {
return;
}
if (type === "force_disconnect") {
logInfo("收到桌面端主动断开指令");
disconnectObserver(true);
return;
}
if (type !== "command" || !payload.data) {
return;
}
@@ -530,6 +557,17 @@ async function triggerPageCapture(reason, retry = 0) {
return;
}
let tab = null;
try {
tab = await chrome.tabs.get(tabId);
} catch (_) {
tab = null;
}
if (!tab || !isCapturableUrl(tab.url)) {
return;
}
try {
await sendCommandToTab(tabId, {
type: "REQUEST_PAGE_CAPTURE",
@@ -736,8 +774,16 @@ async function loadAutoConfig() {
logWarn("自动配置缺少必要字段", config);
}
} catch (error) {
// 文件不存在或解析失败时记录错误
logWarn("加载自动配置失败", error?.message || String(error));
const message = error?.message || String(error);
if (
/failed to fetch/i.test(message) ||
/not found/i.test(message) ||
/networkerror/i.test(message)
) {
logInfo("未检测到 auto_config.json,继续使用本地设置");
return;
}
logWarn("加载自动配置失败", message);
}
}
+1 -1
View File
@@ -36,7 +36,7 @@ function buildObserverEndpoint(serverUrl, bridgeKey, profileKey) {
if (!base || !key) {
return "Observer URL: 未配置";
}
return `Observer URL: ${base}/lime-chrome-observer/Lime_Key=${encodeURIComponent(key)}?profileKey=${profile}`;
return `Observer URL: ${base}/lime-chrome-observer/${encodeURIComponent(key)}?profileKey=${profile}`;
}
function applyStatus(status) {