- commit
- ef2db49
- parent
- 9c76407
- author
- im_wower
- date
- 2026-03-23 00:51:42 +0800 CST
fix(firefox): default to local mini endpoints
8 files changed,
+95,
-51
+6,
-6
1@@ -14,7 +14,7 @@ WS 地址直接由 `BAA_CONDUCTOR_LOCAL_API` 派生,不单独引入新的环
2
3 例子:
4
5-- `BAA_CONDUCTOR_LOCAL_API=http://127.0.0.1:4317` -> `ws://127.0.0.1:4317/ws/firefox`
6+- `BAA_CONDUCTOR_LOCAL_API=http://100.71.210.78:4317` -> `ws://100.71.210.78:4317/ws/firefox`
7 - `BAA_CONDUCTOR_LOCAL_API=http://100.71.210.78:4317` -> `ws://100.71.210.78:4317/ws/firefox`
8
9 约束:
10@@ -75,8 +75,8 @@ WS 地址直接由 `BAA_CONDUCTOR_LOCAL_API` 派生,不单独引入新的环
11 "clientId": "firefox-ab12cd",
12 "protocol": "baa.firefox.local",
13 "version": 1,
14- "wsUrl": "ws://127.0.0.1:4317/ws/firefox",
15- "localApiBase": "http://127.0.0.1:4317",
16+ "wsUrl": "ws://100.71.210.78:4317/ws/firefox",
17+ "localApiBase": "http://100.71.210.78:4317",
18 "supports": {
19 "inbound": ["hello", "state_request", "action_request", "credentials", "api_endpoints", "client_log"],
20 "outbound": ["hello_ack", "state_snapshot", "action_result", "request_credentials", "error"]
21@@ -94,9 +94,9 @@ WS 地址直接由 `BAA_CONDUCTOR_LOCAL_API` 派生,不单独引入新的环
22 "version": 1,
23 "server": {
24 "identity": "mini-main@mini(primary)",
25- "local_api_base": "http://127.0.0.1:4317",
26+ "local_api_base": "http://100.71.210.78:4317",
27 "ws_path": "/ws/firefox",
28- "ws_url": "ws://127.0.0.1:4317/ws/firefox"
29+ "ws_url": "ws://100.71.210.78:4317/ws/firefox"
30 },
31 "system": {
32 "mode": "running",
33@@ -188,7 +188,7 @@ server 行为:
34 ## 最小 smoke
35
36 ```bash
37-LOCAL_API_BASE="${BAA_CONDUCTOR_LOCAL_API:-http://127.0.0.1:4317}"
38+LOCAL_API_BASE="${BAA_CONDUCTOR_LOCAL_API:-http://100.71.210.78:4317}"
39 WS_URL="$(node --input-type=module -e 'const u = new URL(process.argv[1]); u.protocol = u.protocol === \"https:\" ? \"wss:\" : \"ws:\"; u.pathname = \"/ws/firefox\"; u.search = \"\"; u.hash = \"\"; console.log(u.toString());' "$LOCAL_API_BASE")"
40
41 WS_URL="$WS_URL" node --input-type=module <<'EOF'
+7,
-7
1@@ -8,20 +8,20 @@
2
3 ## 当前固定入口
4
5-- Local WS bridge: `ws://127.0.0.1:4317/ws/firefox`
6-- Public HTTP host: `https://conductor.makefile.so`
7+- Local WS bridge: `ws://100.71.210.78:4317/ws/firefox`
8+- Local HTTP host: `http://100.71.210.78:4317`
9
10 当前插件默认同时使用这两条链路:
11
12 - 本地 WS:负责 Firefox bridge 握手、浏览器元数据同步、服务端快照展示
13-- 公网 HTTP:负责控制面状态同步,以及 `pause` / `resume` / `drain` 写入
14+- 本地 HTTP:负责控制面状态同步,以及 `pause` / `resume` / `drain` 写入
15
16 不再允许在插件管理页中手工编辑地址。
17
18 ## 目标
19
20 - 让 Firefox 插件自动接入 `mini` 本地 `/ws/firefox`
21-- 保留 `conductor.makefile.so` 上的 HTTP 状态同步
22+- 保留 `mini` 本地 HTTP 状态同步
23 - 把管理页收口成 WS 状态、HTTP 状态、控制按钮
24 - 在本地服务重启或公网 HTTP 短暂失败后自动恢复
25
26@@ -34,7 +34,7 @@
27 ## 启动行为
28
29 - Firefox 启动时,`background.js` 会确保 `controller.html` 存在。
30-- `controller.html` 启动后立刻连接 `ws://127.0.0.1:4317/ws/firefox`。
31+- `controller.html` 启动后立刻连接 `ws://100.71.210.78:4317/ws/firefox`。
32 - WS 断开后按固定间隔自动重连;本地服务恢复后连接会自动恢复。
33 - `controller.html` 启动后也会立刻请求 `GET /v1/system/state`。
34 - HTTP 成功后按 `15` 秒周期继续同步。
35@@ -45,7 +45,7 @@
36 当前管理页只保留:
37
38 - 本地 WS 状态卡片
39-- 公网 HTTP 状态卡片
40+- 本地 HTTP 状态卡片
41 - `暂停` / `恢复` / `排空` 按钮
42 - `WS 状态` 原始详情面板
43 - `HTTP 状态` 原始详情面板
44@@ -132,7 +132,7 @@
45 2. 确认 `controller.html` 自动打开。
46 3. 确认 `本地 WS` 最终显示 `已连接`。
47 4. 在 `WS 状态` 面板中确认:
48- - `wsUrl` 是 `ws://127.0.0.1:4317/ws/firefox`
49+ - `wsUrl` 是 `ws://100.71.210.78:4317/ws/firefox`
50 - 有最近一次 `state_snapshot`
51
52 ### 2. 验证断线重连
+7,
-7
1@@ -4,13 +4,13 @@
2
3 ## 当前默认连接
4
5-- 本地 WS bridge:`ws://127.0.0.1:4317/ws/firefox`
6-- 公网 HTTP 入口:`https://conductor.makefile.so`
7+- 本地 WS bridge:`ws://100.71.210.78:4317/ws/firefox`
8+- 本地 HTTP 入口:`http://100.71.210.78:4317`
9
10 管理页已经收口为两块状态和三颗控制按钮:
11
12 - 本地 WS 状态
13-- 公网 HTTP 状态
14+- 本地 HTTP 状态
15 - `Pause` / `Resume` / `Drain`
16
17 不再允许用户手工编辑地址。
18@@ -19,7 +19,7 @@
19
20 - keeps an always-open `controller.html`
21 - auto-connects the local Firefox bridge WS on startup
22-- keeps polling `conductor.makefile.so` over public HTTP with retry/backoff
23+- keeps polling the local conductor HTTP surface with retry/backoff
24 - sends `hello` / `credentials` / `api_endpoints` metadata to the local WS bridge
25 - lets the operator trigger `pause` / `resume` / `drain`
26
27@@ -45,10 +45,10 @@
28 ## How To Run
29
30 1. Load this extension temporarily.
31-2. Make sure local `conductor-daemon` is listening on `http://127.0.0.1:4317`.
32+2. Make sure local `conductor-daemon` is listening on `http://100.71.210.78:4317`.
33 3. Open `controller.html` and confirm:
34 - `本地 WS` becomes `已连接`
35- - `公网 HTTP` reaches `已连接` or enters visible auto-retry
36+ - `本地 HTTP` reaches `已连接` or enters visible auto-retry
37 4. Click `暂停` / `恢复` / `排空` as needed.
38 5. Open Claude, ChatGPT, or Gemini normally if you want the browser bridge to report credentials and endpoints.
39
40@@ -56,7 +56,7 @@
41
42 - WS connect: controller page shows `本地 WS = 已连接`
43 - WS reconnect: stop local daemon and restart it; controller page should move to retrying and then recover automatically
44-- HTTP sync: `公网 HTTP` should keep refreshing every `15` seconds
45+- HTTP sync: `本地 HTTP` should keep refreshing every `15` seconds
46 - Control buttons: after clicking `暂停` / `恢复` / `排空`, the HTTP state should reflect the new mode
47
48 ## Limitations
+57,
-15
1@@ -23,25 +23,65 @@ async function getStoredControllerTabId() {
2 return data[STORAGE_KEY] || null;
3 }
4
5-async function queryControllerTab() {
6+async function queryControllerTabs() {
7 const tabs = await browser.tabs.query({ url: CONTROLLER_URL });
8+ return [...tabs].sort((left, right) => {
9+ const leftId = Number.isInteger(left.id) ? left.id : Number.MAX_SAFE_INTEGER;
10+ const rightId = Number.isInteger(right.id) ? right.id : Number.MAX_SAFE_INTEGER;
11+ return leftId - rightId;
12+ });
13+}
14+
15+function pickCanonicalControllerTab(tabs, preferredTabId = null) {
16+ if (!Array.isArray(tabs) || tabs.length === 0) {
17+ return null;
18+ }
19+
20+ if (Number.isInteger(preferredTabId)) {
21+ const preferred = tabs.find((tab) => tab.id === preferredTabId);
22+ if (preferred) return preferred;
23+ }
24+
25 return tabs[0] || null;
26 }
27
28+async function closeDuplicateControllerTabs(tabs, canonicalTabId) {
29+ const duplicateIds = tabs
30+ .map((tab) => tab.id)
31+ .filter((tabId) => Number.isInteger(tabId) && tabId !== canonicalTabId);
32+
33+ if (duplicateIds.length === 0) {
34+ return;
35+ }
36+
37+ await browser.tabs.remove(duplicateIds);
38+}
39+
40+async function resolveControllerTab(preferredTabId = null) {
41+ const tabs = await queryControllerTabs();
42+ if (tabs.length === 0) {
43+ return null;
44+ }
45+
46+ const storedId = await getStoredControllerTabId();
47+ const canonicalTab = pickCanonicalControllerTab(
48+ tabs,
49+ Number.isInteger(storedId) ? storedId : preferredTabId
50+ );
51+
52+ if (!canonicalTab || !Number.isInteger(canonicalTab.id)) {
53+ return null;
54+ }
55+
56+ await closeDuplicateControllerTabs(tabs, canonicalTab.id);
57+ await setControllerTabId(canonicalTab.id);
58+ return canonicalTab;
59+}
60+
61 async function ensureControllerTab(options = {}) {
62 const { activate = false } = options;
63
64- let tab = await queryControllerTab();
65- if (!tab) {
66- const storedId = await getStoredControllerTabId();
67- if (storedId) {
68- try {
69- tab = await browser.tabs.get(storedId);
70- } catch (_) {
71- tab = null;
72- }
73- }
74- }
75+ let tab = await resolveControllerTab();
76
77 if (tab) {
78 await setControllerTabId(tab.id);
79@@ -58,8 +98,7 @@ async function ensureControllerTab(options = {}) {
80 url: CONTROLLER_URL,
81 active: activate
82 });
83- await setControllerTabId(created.id);
84- return created;
85+ return await resolveControllerTab(created.id);
86 }
87
88 function normalizeMode(value) {
89@@ -150,7 +189,10 @@ browser.runtime.onMessage.addListener((message) => {
90 if (!message || typeof message !== "object") return undefined;
91
92 if (message.type === "controller_ready" && Number.isInteger(message.tabId)) {
93- return setControllerTabId(message.tabId).then(() => ({ ok: true }));
94+ return resolveControllerTab(message.tabId).then((tab) => ({
95+ ok: true,
96+ tabId: tab?.id ?? null
97+ }));
98 }
99
100 if (message.type === "focus_controller") {
+3,
-3
1@@ -11,8 +11,8 @@
2 <section class="topbar">
3 <div>
4 <p class="eyebrow">BAA Firefox 控制台</p>
5- <h1>本地 WS / 公网 HTTP</h1>
6- <p class="meta">本地 bridge 自动连接 mini,公网入口固定走 <code>https://conductor.makefile.so</code>。</p>
7+ <h1>本地 WS / 本地 HTTP</h1>
8+ <p class="meta">本地 bridge 自动连接 mini,本地控制面固定走 <code>http://100.71.210.78:4317</code>。</p>
9 </div>
10 </section>
11
12@@ -30,7 +30,7 @@
13 </article>
14
15 <article class="card">
16- <p class="label">公网 HTTP</p>
17+ <p class="label">本地 HTTP</p>
18 <p id="control-mode" class="value off">连接中</p>
19 <p id="control-meta" class="meta">等待同步</p>
20 </article>
+3,
-3
1@@ -20,9 +20,9 @@ const CONTROLLER_STORAGE_KEYS = {
2 geminiSendTemplate: "baaFirefox.geminiSendTemplate"
3 };
4
5-const DEFAULT_LOCAL_API_BASE = "http://127.0.0.1:4317";
6-const DEFAULT_WS_URL = "ws://127.0.0.1:4317/ws/firefox";
7-const DEFAULT_CONTROL_BASE_URL = "https://conductor.makefile.so";
8+const DEFAULT_LOCAL_API_BASE = "http://100.71.210.78:4317";
9+const DEFAULT_WS_URL = "ws://100.71.210.78:4317/ws/firefox";
10+const DEFAULT_CONTROL_BASE_URL = "http://100.71.210.78:4317";
11 const STATUS_SCHEMA_VERSION = 3;
12 const CREDENTIAL_SEND_INTERVAL = 30_000;
13 const CREDENTIAL_TTL = 15 * 60_000;
1@@ -2,13 +2,13 @@
2
3 `baa-firefox` 现在默认同时接两条固定链路:
4
5-- 本地 WS bridge:`ws://127.0.0.1:4317/ws/firefox`
6-- 公网 HTTP 入口:`https://conductor.makefile.so`
7+- 本地 WS bridge:`ws://100.71.210.78:4317/ws/firefox`
8+- 本地 HTTP 入口:`http://100.71.210.78:4317`
9
10 管理页已经收口,只保留:
11
12 - 本地 WS 状态
13-- 公网 HTTP 状态
14+- 本地 HTTP 状态
15 - `Pause` / `Resume` / `Drain` 按钮
16
17 不再允许用户手工编辑 WS 地址或 HTTP 地址。
18@@ -28,8 +28,8 @@
19
20 ## 固定地址
21
22-- Local WS: `ws://127.0.0.1:4317/ws/firefox`
23-- Public HTTP: `https://conductor.makefile.so`
24+- Local WS: `ws://100.71.210.78:4317/ws/firefox`
25+- Local HTTP: `http://100.71.210.78:4317`
26
27 当前实现不再从 UI 或 storage 读取用户自定义地址;旧配置会被固定地址覆盖。
28
29@@ -63,7 +63,7 @@
30
31 ## HTTP 侧职责
32
33-公网 HTTP 仍然是管理页里控制状态的同步来源,也是控制按钮的写入通道。
34+本地 HTTP 是管理页里控制状态的同步来源,也是控制按钮的写入通道。
35
36 读取:
37
38@@ -91,7 +91,7 @@
39 - WS 卡片:连接状态、最近快照、最近错误
40 - HTTP 卡片:连接状态、当前 mode、最近成功/失败、下次重试
41 - WS 详情:本地 bridge 的原始状态摘要
42-- HTTP 详情:`conductor.makefile.so` 的原始状态摘要
43+- HTTP 详情:本地 `conductor-daemon` 的原始状态摘要
44
45 不再展示:
46
47@@ -105,6 +105,6 @@
48 1. 安装插件并启动 Firefox,确认 `controller.html` 自动打开。
49 2. 打开管理页,确认:
50 - `本地 WS` 最终变成 `已连接`
51- - `公网 HTTP` 能显示 `已连接` 或自动重试中的明确状态
52+ - `本地 HTTP` 能显示 `已连接` 或自动重试中的明确状态
53 3. 停掉本地 `conductor-daemon`,确认 WS 状态进入重连中;恢复服务后确认自动回到 `已连接`。
54 4. 点击 `暂停` / `恢复` / `排空`,确认 HTTP 状态会更新 mode,且服务端状态与按钮动作一致。
+4,
-2
1@@ -23,10 +23,12 @@
2 "https://gemini.google.com/*",
3 "https://conductor.makefile.so/*",
4 "http://127.0.0.1/*",
5- "ws://127.0.0.1/*"
6+ "ws://127.0.0.1/*",
7+ "http://100.71.210.78/*",
8+ "ws://100.71.210.78/*"
9 ],
10 "content_security_policy": {
11- "extension_pages": "default-src 'self'; connect-src https://conductor.makefile.so ws://127.0.0.1:4317 http://127.0.0.1:4317"
12+ "extension_pages": "default-src 'self'; connect-src https://conductor.makefile.so ws://127.0.0.1:4317 http://127.0.0.1:4317 ws://100.71.210.78:4317 http://100.71.210.78:4317"
13 },
14 "background": {
15 "scripts": [