github-router 0.3.35 → 0.3.36

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.
@@ -3639,6 +3639,64 @@ function discoveryPath() {
3639
3639
  return path.join(homedir(), ".local", "share", "github-router", "browser-mcp", "bridge.json");
3640
3640
  }
3641
3641
 
3642
+ //#endregion
3643
+ //#region src/browser-bridge/pending.ts
3644
+ const pendingMap = /* @__PURE__ */ new Map();
3645
+ /**
3646
+ * Register a pending request. The TTL timer ensures the entry is removed
3647
+ * even when the extension hangs (MV3 SW dormancy, tab crash, navigation
3648
+ * interrupt) and the WS client stays connected — which would otherwise
3649
+ * leave the entry in the Map for the lifetime of a long proxy session.
3650
+ *
3651
+ * On TTL expiry the caller receives a structured timeout error so the
3652
+ * dispatcher can surface a meaningful message rather than hanging.
3653
+ *
3654
+ * @param id Request ID (must match the extension's response).
3655
+ * @param client WS client to send the response back on.
3656
+ * @param ttlMs How long to wait before forcibly resolving with an error.
3657
+ * @param sendResp Callback that encodes and writes BridgeResponse to `client`.
3658
+ */
3659
+ function pendingAdd(id, client, ttlMs, sendResp) {
3660
+ const ttlTimer = setTimeout(() => {
3661
+ if (!pendingMap.has(id)) return;
3662
+ pendingMap.delete(id);
3663
+ sendResp({
3664
+ id,
3665
+ ok: false,
3666
+ error: `bridge timeout after ${ttlMs}ms`,
3667
+ code: "timeout"
3668
+ });
3669
+ }, ttlMs);
3670
+ pendingMap.set(id, {
3671
+ resolve: sendResp,
3672
+ client,
3673
+ ttlTimer
3674
+ });
3675
+ }
3676
+ /**
3677
+ * Resolve a pending request with the browser's response and cancel the
3678
+ * TTL timer. No-ops if the id is unknown (double-resolve from a
3679
+ * misbehaving extension is harmless).
3680
+ */
3681
+ function pendingResolve(id, msg) {
3682
+ const entry = pendingMap.get(id);
3683
+ if (!entry) return;
3684
+ clearTimeout(entry.ttlTimer);
3685
+ pendingMap.delete(id);
3686
+ entry.resolve(msg);
3687
+ }
3688
+ /**
3689
+ * Drop all pending entries belonging to a specific WS client and cancel
3690
+ * their TTL timers. Called from the WS "close" handler so we don't leak
3691
+ * entries when the dispatcher disconnects mid-flight.
3692
+ */
3693
+ function pendingDropClient(client) {
3694
+ for (const [id, entry] of pendingMap) if (entry.client === client) {
3695
+ clearTimeout(entry.ttlTimer);
3696
+ pendingMap.delete(id);
3697
+ }
3698
+ }
3699
+
3642
3700
  //#endregion
3643
3701
  //#region src/browser-bridge/index.ts
3644
3702
  try {
@@ -3646,6 +3704,7 @@ try {
3646
3704
  } catch {}
3647
3705
  const HEARTBEAT_MS = 5e3;
3648
3706
  const HEARTBEAT_MISS_LIMIT = 3;
3707
+ const PENDING_TTL_MS = 3e5;
3649
3708
  function writeDiscoveryFile(payload) {
3650
3709
  const file = discoveryPath();
3651
3710
  mkdirSync(path.dirname(file), { recursive: true });
@@ -3726,7 +3785,6 @@ httpServer.on("upgrade", (req, socket, head) => {
3726
3785
  handleWsConnection(ws);
3727
3786
  });
3728
3787
  });
3729
- const pending = /* @__PURE__ */ new Map();
3730
3788
  function handleWsConnection(ws) {
3731
3789
  let alive = true;
3732
3790
  let misses = 0;
@@ -3765,15 +3823,12 @@ function handleWsConnection(ws) {
3765
3823
  return;
3766
3824
  }
3767
3825
  if (typeof msg.id !== "string" || typeof msg.tool !== "string") return;
3768
- pending.set(msg.id, {
3769
- resolve: (resp) => ws.send(JSON.stringify(resp)),
3770
- client: ws
3771
- });
3826
+ pendingAdd(msg.id, ws, PENDING_TTL_MS, (resp) => ws.send(JSON.stringify(resp)));
3772
3827
  sendToBrowser(msg);
3773
3828
  });
3774
3829
  ws.on("close", () => {
3775
3830
  clearInterval(heartbeat);
3776
- for (const [id, p] of pending) if (p.client === ws) pending.delete(id);
3831
+ pendingDropClient(ws);
3777
3832
  });
3778
3833
  }
3779
3834
  let lastBrowserContactMs = 0;
@@ -3784,10 +3839,7 @@ fromBrowserListeners.push((msg) => {
3784
3839
  lastBrowserContactMs = Date.now();
3785
3840
  const r = msg;
3786
3841
  if (typeof r.id !== "string") return;
3787
- const p = pending.get(r.id);
3788
- if (!p) return;
3789
- pending.delete(r.id);
3790
- p.resolve(r);
3842
+ pendingResolve(r.id, r);
3791
3843
  });
3792
3844
  httpServer.listen(0, "127.0.0.1", () => {
3793
3845
  const addr = httpServer.address();