@jobshimo/browser-link 0.40.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -8
- package/dist/bridge/dispatch.js +17 -7
- package/dist/bridge/dispatch.js.map +1 -1
- package/dist/bridge/ws-bridge.d.ts +28 -3
- package/dist/bridge/ws-bridge.js +51 -7
- package/dist/bridge/ws-bridge.js.map +1 -1
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.js +62 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/free-port.d.ts +40 -1
- package/dist/commands/free-port.js +72 -7
- package/dist/commands/free-port.js.map +1 -1
- package/dist/extension/background.js +100 -8
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/manifest.json +1 -1
- package/dist/extension/popup.js +76 -1
- package/dist/extension/popup.js.map +1 -1
- package/dist/extension/repair-policy.d.ts +60 -0
- package/dist/extension/repair-policy.js +79 -0
- package/dist/extension/repair-policy.js.map +1 -0
- package/dist/extension/version-status.d.ts +44 -0
- package/dist/extension/version-status.js +60 -0
- package/dist/extension/version-status.js.map +1 -0
- package/dist/lifecycle/election.d.ts +98 -0
- package/dist/lifecycle/election.js +187 -0
- package/dist/lifecycle/election.js.map +1 -0
- package/dist/lifecycle/processes.d.ts +67 -0
- package/dist/lifecycle/processes.js +239 -0
- package/dist/lifecycle/processes.js.map +1 -0
- package/dist/lifecycle/repair.d.ts +48 -0
- package/dist/lifecycle/repair.js +67 -0
- package/dist/lifecycle/repair.js.map +1 -0
- package/dist/lifecycle/version-endpoint.d.ts +16 -0
- package/dist/lifecycle/version-endpoint.js +50 -0
- package/dist/lifecycle/version-endpoint.js.map +1 -0
- package/dist/manual/topics.js +1 -1
- package/dist/manual/topics.js.map +1 -1
- package/dist/messages.d.ts +31 -2
- package/dist/server.js +90 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +8 -0
- package/dist/tools/browser-dispatch.js +15 -1
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/dist/ui/screens/free-port.js +4 -2
- package/dist/ui/screens/free-port.js.map +1 -1
- package/dist/version.d.ts +8 -0
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Language } from './welcome.js';
|
|
2
|
+
import { type ProcessEntry } from '../lifecycle/processes.js';
|
|
2
3
|
export interface FreePortResult {
|
|
3
4
|
/** True when the port was free (nothing to do) OR the owning process was killed. */
|
|
4
5
|
ok: boolean;
|
|
@@ -8,7 +9,36 @@ export interface FreePortResult {
|
|
|
8
9
|
imageName: string | null;
|
|
9
10
|
/** Human-readable message in the requested language. */
|
|
10
11
|
message: string;
|
|
12
|
+
/** Since 0.41.0: every browser-link process this run tried to stop (the
|
|
13
|
+
* 17529 owner, the 17530 owner and every other live bridge), in kill
|
|
14
|
+
* order. Absent when there was none, so the "already free" result keeps
|
|
15
|
+
* its exact previous shape. */
|
|
16
|
+
processes?: StoppedProcess[];
|
|
11
17
|
}
|
|
18
|
+
export interface StoppedProcess {
|
|
19
|
+
pid: number;
|
|
20
|
+
/** Short display form, e.g. `node browser-link start`. */
|
|
21
|
+
command: string;
|
|
22
|
+
stopped: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Best-effort lookup of the PID owning the WS port, cross-platform.
|
|
26
|
+
* Returns null when nothing is listening on the port, when the OS tools
|
|
27
|
+
* needed to find the owner are missing, or when parsing fails.
|
|
28
|
+
*
|
|
29
|
+
* We don't shell out via a string command — `execFileSync` with an argv
|
|
30
|
+
* array avoids any quoting / injection surprises.
|
|
31
|
+
*/
|
|
32
|
+
export declare function findPidOnPort(port: number): number | null;
|
|
33
|
+
/**
|
|
34
|
+
* Return the image / executable name of a PID, or null when the lookup
|
|
35
|
+
* fails. We use this as a safety check before killing: we will only kill
|
|
36
|
+
* processes whose image name starts with "node" — refuse to nuke anything
|
|
37
|
+
* else that happens to share the port (some other dev server, malware,
|
|
38
|
+
* a misconfigured tool, etc.).
|
|
39
|
+
*/
|
|
40
|
+
export declare function imageNameOf(pid: number): string | null;
|
|
41
|
+
export declare function isNodeImage(image: string | null): boolean;
|
|
12
42
|
/**
|
|
13
43
|
* Find the process listening on WS_PORT and try to stop it.
|
|
14
44
|
*
|
|
@@ -17,5 +47,14 @@ export interface FreePortResult {
|
|
|
17
47
|
* This stops a stray browser-link from blocking the next MCP client
|
|
18
48
|
* without ever risking killing some unrelated user process that happens
|
|
19
49
|
* to share the port.
|
|
50
|
+
*
|
|
51
|
+
* Since 0.41.0 it then stops every OTHER live browser-link bridge too: the
|
|
52
|
+
* 17530 (IPC) owner when it is a node process, and every process whose
|
|
53
|
+
* command line runs this package with no subcommand or `start` (see
|
|
54
|
+
* `lifecycle/processes.ts`; never this process, its ancestors, or short
|
|
55
|
+
* commands such as `doctor`). Each one gets a line under the original
|
|
56
|
+
* message; with nothing else running, the result is exactly what it was.
|
|
20
57
|
*/
|
|
21
|
-
export declare function runFreePort(language?: Language
|
|
58
|
+
export declare function runFreePort(language?: Language, deps?: {
|
|
59
|
+
listBridgeProcesses?: () => ProcessEntry[];
|
|
60
|
+
}): FreePortResult;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { platform } from 'node:os';
|
|
3
|
+
import { IPC_PORT } from '../bridge/protocol.js';
|
|
4
|
+
import { listBridgeProcesses, shortCommand } from '../lifecycle/processes.js';
|
|
3
5
|
const WS_HOST = '127.0.0.1';
|
|
4
6
|
const WS_PORT = 17529;
|
|
5
7
|
/**
|
|
@@ -10,7 +12,7 @@ const WS_PORT = 17529;
|
|
|
10
12
|
* We don't shell out via a string command — `execFileSync` with an argv
|
|
11
13
|
* array avoids any quoting / injection surprises.
|
|
12
14
|
*/
|
|
13
|
-
function findPidOnPort(port) {
|
|
15
|
+
export function findPidOnPort(port) {
|
|
14
16
|
try {
|
|
15
17
|
if (platform() === 'win32') {
|
|
16
18
|
// netstat -ano -p TCP produces lines like:
|
|
@@ -50,7 +52,7 @@ function findPidOnPort(port) {
|
|
|
50
52
|
* else that happens to share the port (some other dev server, malware,
|
|
51
53
|
* a misconfigured tool, etc.).
|
|
52
54
|
*/
|
|
53
|
-
function imageNameOf(pid) {
|
|
55
|
+
export function imageNameOf(pid) {
|
|
54
56
|
try {
|
|
55
57
|
if (platform() === 'win32') {
|
|
56
58
|
// tasklist /FI "PID eq 13212" /FO CSV /NH produces:
|
|
@@ -78,7 +80,7 @@ function imageNameOf(pid) {
|
|
|
78
80
|
return null;
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
|
-
function isNodeImage(image) {
|
|
83
|
+
export function isNodeImage(image) {
|
|
82
84
|
if (!image)
|
|
83
85
|
return false;
|
|
84
86
|
const lc = image.toLowerCase();
|
|
@@ -108,6 +110,9 @@ const I18N = {
|
|
|
108
110
|
`Refusing to kill it — close that process yourself if you know what it is.`,
|
|
109
111
|
unknownOwner: (pid) => `✗ Port ${WS_HOST}:${WS_PORT} is held by PID ${pid}, but we could not identify the process. ` +
|
|
110
112
|
`Refusing to kill it. Close it manually with: taskkill /F /PID ${pid} (Windows) or kill ${pid} (Unix).`,
|
|
113
|
+
othersHeader: 'Other browser-link processes:',
|
|
114
|
+
stopped: '✓ stopped',
|
|
115
|
+
stopFailed: '✗ could not stop',
|
|
111
116
|
},
|
|
112
117
|
es: {
|
|
113
118
|
alreadyFree: `El puerto ${WS_HOST}:${WS_PORT} ya está libre — no hay nada que parar.`,
|
|
@@ -117,6 +122,9 @@ const I18N = {
|
|
|
117
122
|
`Por seguridad no se mata — cerralo a mano si sabés qué es.`,
|
|
118
123
|
unknownOwner: (pid) => `✗ El puerto ${WS_HOST}:${WS_PORT} lo tiene el PID ${pid}, pero no pudimos identificar el proceso. ` +
|
|
119
124
|
`Por seguridad no se mata. Cerralo a mano con: taskkill /F /PID ${pid} (Windows) o kill ${pid} (Unix).`,
|
|
125
|
+
othersHeader: 'Otros procesos de browser-link:',
|
|
126
|
+
stopped: '✓ detenido',
|
|
127
|
+
stopFailed: '✗ no se pudo detener',
|
|
120
128
|
},
|
|
121
129
|
};
|
|
122
130
|
/**
|
|
@@ -127,19 +135,75 @@ const I18N = {
|
|
|
127
135
|
* This stops a stray browser-link from blocking the next MCP client
|
|
128
136
|
* without ever risking killing some unrelated user process that happens
|
|
129
137
|
* to share the port.
|
|
138
|
+
*
|
|
139
|
+
* Since 0.41.0 it then stops every OTHER live browser-link bridge too: the
|
|
140
|
+
* 17530 (IPC) owner when it is a node process, and every process whose
|
|
141
|
+
* command line runs this package with no subcommand or `start` (see
|
|
142
|
+
* `lifecycle/processes.ts`; never this process, its ancestors, or short
|
|
143
|
+
* commands such as `doctor`). Each one gets a line under the original
|
|
144
|
+
* message; with nothing else running, the result is exactly what it was.
|
|
130
145
|
*/
|
|
131
|
-
export function runFreePort(language = 'en') {
|
|
146
|
+
export function runFreePort(language = 'en', deps = {}) {
|
|
132
147
|
const t = I18N[language];
|
|
148
|
+
const primary = stopPortOwner(t);
|
|
149
|
+
const list = (deps.listBridgeProcesses ?? (() => listBridgeProcesses()))();
|
|
150
|
+
const commandOf = (pid, fallback) => {
|
|
151
|
+
const entry = list.find((p) => p.pid === pid);
|
|
152
|
+
return entry ? shortCommand(entry.command) : (fallback ?? '?');
|
|
153
|
+
};
|
|
154
|
+
const processes = [];
|
|
155
|
+
if (primary.pid !== null && primary.attempted) {
|
|
156
|
+
processes.push({
|
|
157
|
+
pid: primary.pid,
|
|
158
|
+
command: commandOf(primary.pid, primary.imageName),
|
|
159
|
+
stopped: primary.ok,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// Everything else: the IPC owner (node only, same safety rule) and every
|
|
163
|
+
// other bridge, each once, never the 17529 owner a second time.
|
|
164
|
+
const seen = new Set(primary.pid !== null ? [primary.pid] : []);
|
|
165
|
+
const others = [];
|
|
166
|
+
const ipcPid = findPidOnPort(IPC_PORT);
|
|
167
|
+
if (ipcPid !== null && !seen.has(ipcPid) && isNodeImage(imageNameOf(ipcPid))) {
|
|
168
|
+
seen.add(ipcPid);
|
|
169
|
+
others.push({ pid: ipcPid, command: commandOf(ipcPid, 'node') });
|
|
170
|
+
}
|
|
171
|
+
for (const p of list) {
|
|
172
|
+
if (seen.has(p.pid))
|
|
173
|
+
continue;
|
|
174
|
+
seen.add(p.pid);
|
|
175
|
+
others.push({ pid: p.pid, command: shortCommand(p.command) });
|
|
176
|
+
}
|
|
177
|
+
const lines = [primary.message];
|
|
178
|
+
if (others.length > 0)
|
|
179
|
+
lines.push(t.othersHeader);
|
|
180
|
+
let allStopped = true;
|
|
181
|
+
for (const o of others) {
|
|
182
|
+
const stopped = killByPid(o.pid);
|
|
183
|
+
allStopped &&= stopped;
|
|
184
|
+
processes.push({ pid: o.pid, command: o.command, stopped });
|
|
185
|
+
lines.push(` PID ${o.pid} ${o.command} ${stopped ? t.stopped : t.stopFailed}`);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
ok: primary.ok && allStopped,
|
|
189
|
+
pid: primary.pid,
|
|
190
|
+
imageName: primary.imageName,
|
|
191
|
+
message: lines.join('\n'),
|
|
192
|
+
...(processes.length > 0 ? { processes } : {}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/** The original 17529 rule, unchanged: only a node owner is killed. */
|
|
196
|
+
function stopPortOwner(t) {
|
|
133
197
|
const pid = findPidOnPort(WS_PORT);
|
|
134
198
|
if (pid == null) {
|
|
135
|
-
return { ok: true, pid: null, imageName: null, message: t.alreadyFree };
|
|
199
|
+
return { ok: true, pid: null, imageName: null, message: t.alreadyFree, attempted: false };
|
|
136
200
|
}
|
|
137
201
|
const image = imageNameOf(pid);
|
|
138
202
|
if (image == null) {
|
|
139
|
-
return { ok: false, pid, imageName: null, message: t.unknownOwner(pid) };
|
|
203
|
+
return { ok: false, pid, imageName: null, message: t.unknownOwner(pid), attempted: false };
|
|
140
204
|
}
|
|
141
205
|
if (!isNodeImage(image)) {
|
|
142
|
-
return { ok: false, pid, imageName: image, message: t.notNode(pid, image) };
|
|
206
|
+
return { ok: false, pid, imageName: image, message: t.notNode(pid, image), attempted: false };
|
|
143
207
|
}
|
|
144
208
|
const killed = killByPid(pid);
|
|
145
209
|
return {
|
|
@@ -147,6 +211,7 @@ export function runFreePort(language = 'en') {
|
|
|
147
211
|
pid,
|
|
148
212
|
imageName: image,
|
|
149
213
|
message: killed ? t.killed(pid) : t.killFailed(pid),
|
|
214
|
+
attempted: true,
|
|
150
215
|
};
|
|
151
216
|
}
|
|
152
217
|
//# sourceMappingURL=free-port.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"free-port.js","sourceRoot":"","sources":["../../src/commands/free-port.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"free-port.js","sourceRoot":"","sources":["../../src/commands/free-port.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,8 @@ import { DEFAULT_FLOW_RECORDING_ENABLED, FLOW_RECORDING_STORAGE_KEY, FLOW_RECORD
|
|
|
13
13
|
import { buildRecorderJs, buildStopRecorderJs } from './inpage/recorder.js';
|
|
14
14
|
import { DISCARDED_WHILE_SAVING_MESSAGE, appendRecordingStep, buildAmbiguousNote, buildNavigationWaitForStep, generateRecordingSession, isNavigationForRecording, isSameRecordingSession, parseRecordedPayload, toFlowStep, } from './recording.js';
|
|
15
15
|
import { ALARM_MIN_DELAY_MS, CONNECT_IN_PROGRESS_ERROR, createReconnectScheduler, isInvoluntaryClose, planResume, reconnectStorageKey, tabIdFromReconnectKey, } from './reconnect-policy.js';
|
|
16
|
+
import { REPAIR_TIMEOUT_MS, describeReconnectResults, describeRepairReply, planRepair, prevTabIdKey, rememberedTabIds, } from './repair-policy.js';
|
|
17
|
+
import { computeVersionCheck } from './version-status.js';
|
|
16
18
|
const WS_URL = 'ws://127.0.0.1:17529';
|
|
17
19
|
const CDP_VERSION = '1.3';
|
|
18
20
|
const CONSOLE_BUFFER_MAX = 200;
|
|
@@ -148,6 +150,8 @@ function isRuntimeMessage(msg) {
|
|
|
148
150
|
const m = msg;
|
|
149
151
|
if (m.action === 'versionCheck')
|
|
150
152
|
return true;
|
|
153
|
+
if (m.action === 'repairConnection')
|
|
154
|
+
return true;
|
|
151
155
|
if (m.action === 'openActivityWindow')
|
|
152
156
|
return true;
|
|
153
157
|
if (m.action === 'activityQuery')
|
|
@@ -2486,7 +2490,7 @@ chrome.debugger.onEvent.addListener((source, method, params) => {
|
|
|
2486
2490
|
* stale tab_ids after a re-election.
|
|
2487
2491
|
*/
|
|
2488
2492
|
async function readPreviousTabId(chromeTabId) {
|
|
2489
|
-
const key =
|
|
2493
|
+
const key = prevTabIdKey(chromeTabId);
|
|
2490
2494
|
try {
|
|
2491
2495
|
const data = await chrome.storage.session.get(key);
|
|
2492
2496
|
const v = data[key];
|
|
@@ -2497,7 +2501,7 @@ async function readPreviousTabId(chromeTabId) {
|
|
|
2497
2501
|
}
|
|
2498
2502
|
}
|
|
2499
2503
|
async function storeTabId(chromeTabId, serverTabId) {
|
|
2500
|
-
const key =
|
|
2504
|
+
const key = prevTabIdKey(chromeTabId);
|
|
2501
2505
|
try {
|
|
2502
2506
|
await chrome.storage.session.set({ [key]: serverTabId });
|
|
2503
2507
|
}
|
|
@@ -2506,7 +2510,7 @@ async function storeTabId(chromeTabId, serverTabId) {
|
|
|
2506
2510
|
}
|
|
2507
2511
|
}
|
|
2508
2512
|
async function forgetTabId(chromeTabId) {
|
|
2509
|
-
const key =
|
|
2513
|
+
const key = prevTabIdKey(chromeTabId);
|
|
2510
2514
|
try {
|
|
2511
2515
|
await chrome.storage.session.remove(key);
|
|
2512
2516
|
}
|
|
@@ -2770,6 +2774,13 @@ async function doConnectTab(tabId) {
|
|
|
2770
2774
|
if (typeof msg.payload.serverVersion === 'string') {
|
|
2771
2775
|
state.serverVersion = msg.payload.serverVersion;
|
|
2772
2776
|
}
|
|
2777
|
+
// Same narrowing for the 0.41.0 fields.
|
|
2778
|
+
if (typeof msg.payload.installedVersion === 'string') {
|
|
2779
|
+
state.installedVersion = msg.payload.installedVersion;
|
|
2780
|
+
}
|
|
2781
|
+
if (typeof msg.payload.serverPid === 'number') {
|
|
2782
|
+
state.serverPid = msg.payload.serverPid;
|
|
2783
|
+
}
|
|
2773
2784
|
// Remember this id so the next reconnect (after a primary swap)
|
|
2774
2785
|
// asks the new primary to honour it. The primary emits
|
|
2775
2786
|
// `tab-renamed` in the bridge event log if it can't.
|
|
@@ -2845,6 +2856,11 @@ async function doConnectTab(tabId) {
|
|
|
2845
2856
|
settleActivityRequest(msg.request_id, msg);
|
|
2846
2857
|
return;
|
|
2847
2858
|
}
|
|
2859
|
+
case 'bridge.repair.result': {
|
|
2860
|
+
// Not tab activity: the popup's Repair button is waiting on it.
|
|
2861
|
+
settleRepairRequest(msg);
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2848
2864
|
default: {
|
|
2849
2865
|
// Compile-time exhaustiveness check; at runtime an unknown kind
|
|
2850
2866
|
// (newer server, older extension) is ignored defensively.
|
|
@@ -2927,17 +2943,87 @@ function getTabStatus(tabId) {
|
|
|
2927
2943
|
function getVersionCheck() {
|
|
2928
2944
|
const extension = chrome.runtime.getManifest().version;
|
|
2929
2945
|
let server = null;
|
|
2946
|
+
let installed = null;
|
|
2930
2947
|
for (const s of tabStates.values()) {
|
|
2931
2948
|
if (typeof s.serverVersion === 'string' && s.serverVersion.length > 0) {
|
|
2932
2949
|
server = s.serverVersion;
|
|
2950
|
+
installed =
|
|
2951
|
+
typeof s.installedVersion === 'string' && s.installedVersion.length > 0
|
|
2952
|
+
? s.installedVersion
|
|
2953
|
+
: null;
|
|
2933
2954
|
break;
|
|
2934
2955
|
}
|
|
2935
2956
|
}
|
|
2936
|
-
return {
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2957
|
+
return computeVersionCheck({ extension, server, installed });
|
|
2958
|
+
}
|
|
2959
|
+
/* "Repair connection" (popup button). The decision lives in repair-policy.ts;
|
|
2960
|
+
* this is the thin wiring that gathers its inputs and carries out the step. */
|
|
2961
|
+
/** The one in-flight `bridge.repair`, keyed by request id. A late or unknown
|
|
2962
|
+
* reply is dropped, same rule as `settleActivityRequest`. */
|
|
2963
|
+
const pendingRepairs = new Map();
|
|
2964
|
+
function settleRepairRequest(reply) {
|
|
2965
|
+
const pending = pendingRepairs.get(reply.request_id);
|
|
2966
|
+
if (typeof pending !== 'function')
|
|
2967
|
+
return;
|
|
2968
|
+
pendingRepairs.delete(reply.request_id);
|
|
2969
|
+
pending(reply);
|
|
2970
|
+
}
|
|
2971
|
+
async function readRememberedTabIds() {
|
|
2972
|
+
try {
|
|
2973
|
+
return rememberedTabIds(await chrome.storage.session.get(null));
|
|
2974
|
+
}
|
|
2975
|
+
catch {
|
|
2976
|
+
return [];
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
/** Send `bridge.repair` on one open socket and wait for the answer. On
|
|
2980
|
+
* success the server drops every socket uncleanly and the existing
|
|
2981
|
+
* auto-reconnect scheduler brings the tabs back; nothing else to do here. */
|
|
2982
|
+
function askServerRestart(socket) {
|
|
2983
|
+
const requestId = crypto.randomUUID();
|
|
2984
|
+
return new Promise((resolve) => {
|
|
2985
|
+
const timer = setTimeout(() => {
|
|
2986
|
+
pendingRepairs.delete(requestId);
|
|
2987
|
+
resolve(describeRepairReply('timeout'));
|
|
2988
|
+
}, REPAIR_TIMEOUT_MS);
|
|
2989
|
+
pendingRepairs.set(requestId, (reply) => {
|
|
2990
|
+
clearTimeout(timer);
|
|
2991
|
+
resolve(describeRepairReply(reply));
|
|
2992
|
+
});
|
|
2993
|
+
send(socket, { kind: 'bridge.repair', request_id: requestId });
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
async function repairConnection() {
|
|
2997
|
+
const socket = [...tabStates.values()]
|
|
2998
|
+
.map((s) => s.ws)
|
|
2999
|
+
.find((ws) => ws !== undefined && ws.readyState === WebSocket.OPEN);
|
|
3000
|
+
const plan = planRepair({
|
|
3001
|
+
connected: socket !== undefined,
|
|
3002
|
+
serverVersion: getVersionCheck().server,
|
|
3003
|
+
rememberedTabIds: await readRememberedTabIds(),
|
|
3004
|
+
});
|
|
3005
|
+
switch (plan.step) {
|
|
3006
|
+
case 'ask-server-restart':
|
|
3007
|
+
// `planRepair` only picks this step when a socket is open.
|
|
3008
|
+
return socket ? askServerRestart(socket) : { ok: false, message: 'Not connected' };
|
|
3009
|
+
case 'reconnect-only': {
|
|
3010
|
+
// Same path as the popup's Connect: an explicit attempt replaces any
|
|
3011
|
+
// pending auto-reconnect for the tab, and connectTab's per-tab lock
|
|
3012
|
+
// turns a race with an in-flight retry into a benign result.
|
|
3013
|
+
const results = await Promise.all(plan.tabIds.map(async (tabId) => {
|
|
3014
|
+
reconnectScheduler.cancel(tabId);
|
|
3015
|
+
try {
|
|
3016
|
+
return await connectTab(tabId);
|
|
3017
|
+
}
|
|
3018
|
+
catch {
|
|
3019
|
+
return { ok: false };
|
|
3020
|
+
}
|
|
3021
|
+
}));
|
|
3022
|
+
return describeReconnectResults(results);
|
|
3023
|
+
}
|
|
3024
|
+
case 'manual':
|
|
3025
|
+
return { ok: false, message: plan.message };
|
|
3026
|
+
}
|
|
2941
3027
|
}
|
|
2942
3028
|
// === Flow recording by demonstration ===================================
|
|
2943
3029
|
//
|
|
@@ -3304,6 +3390,12 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
|
3304
3390
|
sendResponse(getVersionCheck());
|
|
3305
3391
|
return false;
|
|
3306
3392
|
}
|
|
3393
|
+
if (msg.action === 'repairConnection') {
|
|
3394
|
+
void repairConnection().then(sendResponse, (err) => {
|
|
3395
|
+
sendResponse({ ok: false, message: err instanceof Error ? err.message : String(err) });
|
|
3396
|
+
});
|
|
3397
|
+
return true;
|
|
3398
|
+
}
|
|
3307
3399
|
if (msg.action === 'openActivityWindow') {
|
|
3308
3400
|
void openActivityWindow().then(sendResponse);
|
|
3309
3401
|
return true;
|