mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
chore: release v0.98.0
This commit is contained in:
@@ -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`)
|
||||
- 桌面端主动断开链路(默认开启)
|
||||
|
||||
## 兼容说明
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user