wsl-chrome-bridge 0.2.0 → 0.3.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-zh.md +25 -9
- package/README.md +25 -8
- package/dist/bridge-options.d.ts +2 -0
- package/dist/bridge-options.js +6 -0
- package/dist/bridge-options.js.map +1 -1
- package/dist/bridge-powershell-scripts.d.ts +11 -0
- package/dist/bridge-powershell-scripts.js +475 -0
- package/dist/bridge-powershell-scripts.js.map +1 -0
- package/dist/bridge-runner.js +1006 -369
- package/dist/bridge-runner.js.map +1 -1
- package/dist/bridge-watchdog.d.ts +1 -0
- package/dist/bridge-watchdog.js +189 -0
- package/dist/bridge-watchdog.js.map +1 -0
- package/docs/BRIDGE_CONNECTION_LIFECYCLE-zh.md +199 -0
- package/docs/BRIDGE_CONNECTION_LIFECYCLE.md +198 -0
- package/package.json +4 -3
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Bridge Connection Lifecycle (Draft)
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This document defines how `wsl-chrome-bridge` manages lifecycle and recovery with Windows Chrome, especially for:
|
|
6
|
+
|
|
7
|
+
- shared `--user-data-dir` reuse behavior
|
|
8
|
+
- different behavior between `pipe` (`chrome-devtools-mcp`) and `ws` (`playwright-mcp`) transport modes
|
|
9
|
+
- recovery behavior after Chrome is manually closed and a new upstream request arrives
|
|
10
|
+
|
|
11
|
+
## Core Principles
|
|
12
|
+
|
|
13
|
+
1. Use `windowsUserDataDir` as the primary instance identity key and prefer reusing an existing Chrome instance.
|
|
14
|
+
2. If reuse is possible, keep the existing `remote-debugging-port` and do not force a restart.
|
|
15
|
+
3. Only launch a new Chrome instance when no reusable instance is available.
|
|
16
|
+
4. In `pipe` mode, bridge is allowed to exit when downstream Chrome disconnects (so upstream can restart on next operation).
|
|
17
|
+
5. In `ws` mode, bridge should not exit on Chrome disconnect; it enters a recoverable state and retries on the next upstream request.
|
|
18
|
+
6. `pipe + headless` requires explicit exit handling:
|
|
19
|
+
- normal exit: bridge cleanup actively stops headless Chrome
|
|
20
|
+
- abnormal exit (for example `SIGKILL`): a detached Node watchdog observes bridge PID death and then stops Chrome
|
|
21
|
+
|
|
22
|
+
## Terms
|
|
23
|
+
|
|
24
|
+
- `TransportMode`
|
|
25
|
+
- `pipe`: upstream uses fd3/fd4 CDP pipe
|
|
26
|
+
- `ws`: upstream uses local websocket proxy
|
|
27
|
+
- `Ownership`
|
|
28
|
+
- `attached`: bridge attached to an existing Chrome (not launched in this run)
|
|
29
|
+
- `launched`: bridge launched Chrome in this run
|
|
30
|
+
- `BridgeRuntimeState`
|
|
31
|
+
- `starting`
|
|
32
|
+
- `connected`
|
|
33
|
+
- `degraded` (Chrome unavailable but bridge still alive)
|
|
34
|
+
- `closing`
|
|
35
|
+
|
|
36
|
+
## Startup Lifecycle
|
|
37
|
+
|
|
38
|
+
### Phase 1: Plan and argument resolution
|
|
39
|
+
|
|
40
|
+
1. Parse args/env and determine:
|
|
41
|
+
- `windowsUserDataDir`
|
|
42
|
+
- `requestedLocalDebugPort` (commonly provided by Playwright)
|
|
43
|
+
- `windowsDebugPort` (candidate port)
|
|
44
|
+
- transport mode (`pipe` / `ws` / mixed)
|
|
45
|
+
2. Determine effective `TransportMode`:
|
|
46
|
+
- fd3/fd4 present and pipe requested -> `pipe`
|
|
47
|
+
- localProxyPort present -> `ws` (or mixed)
|
|
48
|
+
|
|
49
|
+
### Phase 2: Detect and reuse
|
|
50
|
+
|
|
51
|
+
1. Query all Windows `chrome.exe` processes.
|
|
52
|
+
2. Filter instances whose `--user-data-dir` is equivalent to `windowsUserDataDir`.
|
|
53
|
+
3. Extract `--remote-debugging-port` from matched process:
|
|
54
|
+
- if valid, use it as `resolvedWindowsDebugPort` and mark `ownership = attached`
|
|
55
|
+
- otherwise continue to Phase 3
|
|
56
|
+
|
|
57
|
+
### Phase 3: Launch fallback
|
|
58
|
+
|
|
59
|
+
1. Resolve launch port using current precedence:
|
|
60
|
+
- bridge arg / env / upstream port / auto-random
|
|
61
|
+
2. Launch Windows Chrome and mark `ownership = launched`.
|
|
62
|
+
3. Poll `/json/version` until `webSocketDebuggerUrl` is available.
|
|
63
|
+
|
|
64
|
+
### Phase 4: Relay and upstream channel setup
|
|
65
|
+
|
|
66
|
+
1. Start CDP relay to `remoteBrowserWsUrl`.
|
|
67
|
+
2. In `ws` mode, start local debug proxy (for Playwright).
|
|
68
|
+
3. If `pipe + headless + ownership=launched + chromePid available`, start detached Node watchdog:
|
|
69
|
+
- pass `bridgePid`, `chromePid`, `powershellPath`, and `expectedExecutablePath`
|
|
70
|
+
- watchdog only monitors process lifetime; it does not relay CDP traffic
|
|
71
|
+
4. Transition state to `connected`.
|
|
72
|
+
|
|
73
|
+
## Runtime Event Handling
|
|
74
|
+
|
|
75
|
+
### Event A: User manually closes Chrome (relay disconnect)
|
|
76
|
+
|
|
77
|
+
#### A1. `pipe` mode
|
|
78
|
+
|
|
79
|
+
1. Bridge logs disconnect reason.
|
|
80
|
+
2. Bridge runs cleanup and exits (non-zero or current policy).
|
|
81
|
+
3. If this run is `pipe + headless`, cleanup stops the matching browser root process using `user-data-dir + active remote-debugging-port` (PowerShell `Stop-Process`).
|
|
82
|
+
4. Upstream (`chrome-devtools-mcp`) restarts on next operation.
|
|
83
|
+
|
|
84
|
+
#### A2. `ws` mode
|
|
85
|
+
|
|
86
|
+
1. Bridge stays alive and keeps local proxy listener open.
|
|
87
|
+
2. When strong signal is observed (for example `ConnectionClosedPrematurely` / `READER_CLOSE`), bridge enters `degraded` immediately (without waiting relay process exit).
|
|
88
|
+
3. Forwarding to Chrome is paused.
|
|
89
|
+
4. Recovery is triggered by the next upstream CDP request (Event B).
|
|
90
|
+
|
|
91
|
+
### Chrome-close detection rules (runtime heuristics)
|
|
92
|
+
|
|
93
|
+
To avoid false positives from a single event (for example `Inspector.detached`), runtime uses a signal combination:
|
|
94
|
+
|
|
95
|
+
1. Weak signals (not enough by themselves to conclude browser shutdown):
|
|
96
|
+
- `Inspector.detached` (any reason)
|
|
97
|
+
- `Target.detachedFromTarget`
|
|
98
|
+
2. Strong signals (browser-level connection failure):
|
|
99
|
+
- relay receives `READER_ERROR` with `webSocketErrorCode=ConnectionClosedPrematurely`
|
|
100
|
+
- relay receives `READER_CLOSE` and then can no longer exchange CDP traffic
|
|
101
|
+
3. Recommended decision rule:
|
|
102
|
+
- cache weak events from `chrome -> relay` with full JSON (ring buffer up to 10)
|
|
103
|
+
- on websocket interruption (`relayFatal` / `relayError` / `relayExit`), compare interruption timestamp with cached weak events
|
|
104
|
+
- if weak signal exists within 2 seconds and strong signal is also present, treat as browser disconnected and enter `degraded` (`ws`) or `closing` (`pipe`)
|
|
105
|
+
- weak-only signals are treated as target/session-level issues, not full browser shutdown
|
|
106
|
+
4. Notes:
|
|
107
|
+
- reason strings (for example `Render process gone.`) are observational only and not a single source of truth
|
|
108
|
+
- distinguishing user-close vs crash/system-kill requires extra process/port probes; recovery flow does not depend on this distinction
|
|
109
|
+
|
|
110
|
+
### Event B: upstream request arrives during `ws` `degraded`
|
|
111
|
+
|
|
112
|
+
0. Special short-circuit (avoid relaunch-on-exit loop):
|
|
113
|
+
- if bridge has `chromeDisconnectedLikely=true` and current request is `Browser.close`
|
|
114
|
+
- return synthetic success response (`{ id: <same>, result: {} }`) immediately
|
|
115
|
+
- do not trigger recovery and do not queue this request
|
|
116
|
+
1. Bridge runs `recoverChromeForCurrentUserDataDir()`:
|
|
117
|
+
- first try attach to existing same-profile Chrome
|
|
118
|
+
- if attach succeeds: refresh `remoteBrowserWsUrl`, rebuild relay, transition to `connected`
|
|
119
|
+
- if attach fails: relaunch Chrome using normal launch policy, wait for `/json/version`, rebuild relay, transition to `connected`
|
|
120
|
+
2. On recovery relay rebuild, run bootstrap before queue flush:
|
|
121
|
+
- send internal root-session request:
|
|
122
|
+
- `Target.setAutoAttach { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }`
|
|
123
|
+
- do not flush queued requests before bootstrap response success
|
|
124
|
+
- flush queue only after bootstrap success
|
|
125
|
+
3. Internal request protocol boundaries:
|
|
126
|
+
- internal requests use bridge-owned id space, separate from upstream ids
|
|
127
|
+
- internal responses are consumed inside bridge and never forwarded upstream
|
|
128
|
+
- `Target.attachedToTarget` / `Target.detachedFromTarget` events are still forwarded so upstream gets new `sessionId`
|
|
129
|
+
4. On success:
|
|
130
|
+
- forward current request (queue -> bootstrap -> flush)
|
|
131
|
+
5. On failure:
|
|
132
|
+
- throw and terminate bridge process
|
|
133
|
+
|
|
134
|
+
#### Why bootstrap is required
|
|
135
|
+
|
|
136
|
+
- `Target.createTarget` returns `targetId`, not `sessionId`
|
|
137
|
+
- `sessionId` comes from attach flow (`Target.attachToTarget` or auto-attach events)
|
|
138
|
+
- flushing `Target.createTarget` too early (before auto-attach restore) can produce upstream page/session mismatches (for example `undefined (_page)` style failures)
|
|
139
|
+
|
|
140
|
+
## Cleanup Rules
|
|
141
|
+
|
|
142
|
+
1. Default case (not `pipe + headless`):
|
|
143
|
+
- bridge does not actively stop Windows Chrome
|
|
144
|
+
2. `pipe + headless`:
|
|
145
|
+
- bridge cleanup performs stop with `windowsUserDataDir + activeWindowsDebugPort` filtering
|
|
146
|
+
- this prevents orphaned background headless processes
|
|
147
|
+
3. In `pipe + headless + ownership=launched + chromePid available`, detached Node watchdog is started as abnormal-exit safety:
|
|
148
|
+
- watchdog polls `kill -0 <bridgePid>` in Linux/WSL
|
|
149
|
+
- after bridge PID disappears, watchdog invokes PowerShell stop for target `chromePid`
|
|
150
|
+
- pre-stop validation:
|
|
151
|
+
- target PID command line must contain `--headless`
|
|
152
|
+
- if `expectedExecutablePath` is provided, executable path must match
|
|
153
|
+
4. `ws + degraded` should not auto-destroy process; terminate only when:
|
|
154
|
+
- upstream connection is intentionally closed and service is no longer needed
|
|
155
|
+
- bridge receives SIGINT/SIGTERM
|
|
156
|
+
- fatal error (for example proxy cannot listen)
|
|
157
|
+
|
|
158
|
+
### Definition of `stop-process`
|
|
159
|
+
|
|
160
|
+
- `stop-process` means bridge uses PowerShell `Stop-Process -Id <pid> -Force` to terminate Windows Chrome.
|
|
161
|
+
- in `pipe + headless`, this is used in cleanup after profile+port filtering
|
|
162
|
+
- on abnormal bridge exit, detached Node watchdog can trigger equivalent stop via PID validation flow
|
|
163
|
+
|
|
164
|
+
## State Machine Summary
|
|
165
|
+
|
|
166
|
+
- `starting` -> `connected`
|
|
167
|
+
- condition: successful attach or launch and relay connection
|
|
168
|
+
- `connected` -> `degraded`
|
|
169
|
+
- condition: relay disconnect in `ws` mode
|
|
170
|
+
- `degraded` -> `connected`
|
|
171
|
+
- condition: next upstream request triggers successful recovery
|
|
172
|
+
- `connected` -> `closing`
|
|
173
|
+
- condition: downstream disconnect in `pipe`, signal, or fatal error
|
|
174
|
+
- `degraded` -> `closing`
|
|
175
|
+
- condition: signal, fatal error, or policy-driven termination
|
|
176
|
+
|
|
177
|
+
## Observability Recommendations
|
|
178
|
+
|
|
179
|
+
Debug log fields should include:
|
|
180
|
+
|
|
181
|
+
- `transportMode=pipe|ws`
|
|
182
|
+
- `runtimeState=starting|connected|degraded|closing`
|
|
183
|
+
- `ownership=attached|launched`
|
|
184
|
+
- `windowsUserDataDir=...`
|
|
185
|
+
- `resolvedWindowsDebugPort=...`
|
|
186
|
+
- `recoveryAttempt=<n>`
|
|
187
|
+
- `recoveryResult=attached|relaunched|failed`
|
|
188
|
+
- `recoveryBootstrap=started|acknowledged|failed`
|
|
189
|
+
- `shortCircuitBrowserClose=true|false`
|
|
190
|
+
- `disconnectSignals=...` (for example `Inspector.detached,Target.detachedFromTarget,ConnectionClosedPrematurely`)
|
|
191
|
+
|
|
192
|
+
## Confirmed Policies
|
|
193
|
+
|
|
194
|
+
1. On `ws` recovery failure: throw and terminate bridge process.
|
|
195
|
+
2. `ws` degraded waiting window: no timeout.
|
|
196
|
+
3. `pipe + headless` termination policy:
|
|
197
|
+
- normal cleanup: actively stop matching headless Chrome
|
|
198
|
+
- abnormal exit (including `SIGKILL`): detached Node watchdog performs fallback stop
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wsl-chrome-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Bridge Chrome DevTools MCP/Playwright MCP in WSL2 to Windows Chrome",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
|
+
"docs",
|
|
20
21
|
"README*.md",
|
|
21
22
|
"UPGRADE*.md"
|
|
22
23
|
],
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"@types/node": "^20.19.39",
|
|
39
40
|
"@types/ws": "^8.18.1",
|
|
40
41
|
"tsx": "^4.21.0",
|
|
41
|
-
"typescript": "^6.0.
|
|
42
|
-
"vitest": "^4.1.
|
|
42
|
+
"typescript": "^6.0.3",
|
|
43
|
+
"vitest": "^4.1.5"
|
|
43
44
|
}
|
|
44
45
|
}
|