openclaw-navigator 5.7.1 → 5.7.3

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.
Files changed (2) hide show
  1. package/cli.mjs +106 -3
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -375,6 +375,22 @@ function getChatSession(sessionKey = "main") {
375
375
  return chatSessions.get(sessionKey);
376
376
  }
377
377
 
378
+ // ── Cookie jar for BFF auth ────────────────────────────────────────────
379
+ // Captures session cookies from web UI /api/auth responses so the bridge
380
+ // can make authenticated server-side requests to /api/chat (for sidepane relay).
381
+ let bffCookieJar = "";
382
+
383
+ function captureBFFCookies(setCookieHeaders) {
384
+ if (!setCookieHeaders) return;
385
+ const cookies = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
386
+ // Extract just the cookie name=value (strip attributes like path, domain, etc.)
387
+ const extracted = cookies.map((c) => c.split(";")[0].trim()).filter(Boolean);
388
+ if (extracted.length > 0) {
389
+ bffCookieJar = extracted.join("; ");
390
+ console.log(` ${DIM}Captured BFF cookies: ${bffCookieJar.substring(0, 60)}...${RESET}`);
391
+ }
392
+ }
393
+
378
394
  // ── WebSocket server for chat (minimal, no dependencies) ────────────────
379
395
  // Tracks connected WebSocket clients. When the OC agent pushes messages
380
396
  // via /api/sessions/respond or /api/sessions/stream, we broadcast to all
@@ -1048,6 +1064,8 @@ function handleRequest(req, res) {
1048
1064
  headers["set-cookie"] = cookies.map((c) =>
1049
1065
  c.replace(/;\s*domain=[^;]*/gi, "").replace(/;\s*secure/gi, ""),
1050
1066
  );
1067
+ // Also capture these for server-side relay auth (sidepane chat)
1068
+ captureBFFCookies(proxyRes.headers["set-cookie"]);
1051
1069
  }
1052
1070
 
1053
1071
  res.writeHead(proxyRes.statusCode ?? 502, headers);
@@ -1183,6 +1201,8 @@ function handleRequest(req, res) {
1183
1201
  messages: chatHistory,
1184
1202
  stream: true,
1185
1203
  });
1204
+ // Build cookie header: prefer captured BFF cookies, fall back to browser's cookies
1205
+ const relayCookie = bffCookieJar || req.headers.cookie || "";
1186
1206
  const proxyOpts = {
1187
1207
  hostname: "127.0.0.1",
1188
1208
  port: ocUIPort,
@@ -1192,12 +1212,68 @@ function handleRequest(req, res) {
1192
1212
  headers: {
1193
1213
  "content-type": "application/json",
1194
1214
  "content-length": Buffer.byteLength(proxyBody),
1215
+ ...(relayCookie ? { cookie: relayCookie } : {}),
1195
1216
  },
1196
1217
  };
1197
1218
 
1198
- console.log(` ${DIM}→ Relaying to BFF /api/chat (port ${ocUIPort}) with ${chatHistory.length} messages${RESET}`);
1219
+ console.log(` ${DIM}→ Relaying to BFF /api/chat (port ${ocUIPort}) with ${chatHistory.length} messages${relayCookie ? " +cookie" : " NO-COOKIE"}${RESET}`);
1220
+
1221
+ // Helper: send the relay request (with optional retry after login)
1222
+ function sendBFFRelay(opts, body, retryCount = 0) {
1223
+ const proxyReq = httpRequest(opts, (proxyRes) => {
1224
+ // Capture any cookies the BFF sends (login session, etc.)
1225
+ if (proxyRes.headers["set-cookie"]) {
1226
+ captureBFFCookies(proxyRes.headers["set-cookie"]);
1227
+ }
1228
+
1229
+ // If redirect (307/302 to /login) — follow it to seed cookies, then retry
1230
+ if ((proxyRes.statusCode === 307 || proxyRes.statusCode === 302) && retryCount < 2) {
1231
+ proxyRes.resume(); // drain
1232
+ const loginPath = proxyRes.headers.location || "/login";
1233
+ console.log(` ${DIM}← BFF redirect → ${loginPath} — following to seed cookies...${RESET}`);
1234
+
1235
+ // Hit the login page to get session cookies
1236
+ const loginReq = httpRequest(
1237
+ {
1238
+ hostname: "127.0.0.1",
1239
+ port: ocUIPort,
1240
+ path: loginPath,
1241
+ method: "GET",
1242
+ timeout: 5000,
1243
+ headers: bffCookieJar ? { cookie: bffCookieJar } : {},
1244
+ },
1245
+ (loginRes) => {
1246
+ if (loginRes.headers["set-cookie"]) {
1247
+ captureBFFCookies(loginRes.headers["set-cookie"]);
1248
+ }
1249
+ loginRes.resume(); // drain
1250
+ console.log(` ${DIM}← Login page: ${loginRes.statusCode} — cookies: ${bffCookieJar ? "yes" : "no"}${RESET}`);
1251
+
1252
+ // Retry the original request with new cookies
1253
+ if (bffCookieJar) {
1254
+ opts.headers = { ...opts.headers, cookie: bffCookieJar };
1255
+ console.log(` ${DIM}→ Retrying /api/chat with cookies...${RESET}`);
1256
+ sendBFFRelay(opts, body, retryCount + 1);
1257
+ } else {
1258
+ console.log(` ${DIM}No cookies from login — BFF may require real auth${RESET}`);
1259
+ broadcastToWS({
1260
+ type: "chat.final",
1261
+ text: "⚠️ Could not authenticate with OC — try opening the web UI first to log in.",
1262
+ content: "⚠️ Could not authenticate with OC — try opening the web UI first to log in.",
1263
+ sessionKey,
1264
+ role: "assistant",
1265
+ timestamp: Date.now(),
1266
+ });
1267
+ }
1268
+ },
1269
+ );
1270
+ loginReq.on("error", () => {
1271
+ console.log(` ${DIM}Login page unreachable${RESET}`);
1272
+ });
1273
+ loginReq.end();
1274
+ return;
1275
+ }
1199
1276
 
1200
- const proxyReq = httpRequest(proxyOpts, (proxyRes) => {
1201
1277
  const contentType = (proxyRes.headers["content-type"] || "").toLowerCase();
1202
1278
  const isSSE = contentType.includes("text/event-stream");
1203
1279
  console.log(` ${DIM}← BFF response: ${proxyRes.statusCode} ${contentType || "no-content-type"}${RESET}`);
@@ -1299,8 +1375,11 @@ function handleRequest(req, res) {
1299
1375
  console.log(` ${DIM}BFF relay failed: ${err.message}${RESET}`);
1300
1376
  });
1301
1377
 
1302
- proxyReq.write(proxyBody);
1378
+ proxyReq.write(body);
1303
1379
  proxyReq.end();
1380
+ } // end sendBFFRelay
1381
+
1382
+ sendBFFRelay(proxyOpts, proxyBody);
1304
1383
  })
1305
1384
  .catch(() => sendJSON(res, 400, { ok: false, error: "Bad request body" }));
1306
1385
  return;
@@ -1411,6 +1490,11 @@ function handleRequest(req, res) {
1411
1490
  console.log(` ${DIM}← ${proxyRes.statusCode} ${ct} for ${path}${RESET}`);
1412
1491
  }
1413
1492
 
1493
+ // Capture session cookies from BFF (for server-side relay auth)
1494
+ if (proxyRes.headers["set-cookie"]) {
1495
+ captureBFFCookies(proxyRes.headers["set-cookie"]);
1496
+ }
1497
+
1414
1498
  // CORS
1415
1499
  headers["access-control-allow-origin"] = "*";
1416
1500
  headers["access-control-allow-methods"] = "GET, POST, PUT, DELETE, OPTIONS";
@@ -2081,6 +2165,25 @@ module.exports = {
2081
2165
  console.log(`${BOLD}${GREEN}Bridge is running.${RESET} Waiting for Navigator to connect...`);
2082
2166
  console.log(`${DIM}Press Ctrl+C to stop.${RESET}\n`);
2083
2167
 
2168
+ // ── Relay heartbeat — re-register every 5 minutes to prevent expiry ──
2169
+ // Cloudflare KV entries may expire. This keeps our pairing code fresh
2170
+ // so Navigator can always resolve it, even after long idle periods.
2171
+ if (tunnelURL || gatewayURL) {
2172
+ const relayHeartbeat = async () => {
2173
+ const currentURL = tunnelURL || gatewayURL;
2174
+ const ok = await registerWithRelay(pairingCode, currentURL, token, displayName);
2175
+ if (ok) {
2176
+ info(` ${DIM}Relay heartbeat: code ${pairingCode} refreshed${RESET}`);
2177
+ } else {
2178
+ warn(`Relay heartbeat failed — Navigator may not be able to resolve code`);
2179
+ }
2180
+ };
2181
+ // First heartbeat immediately (in case initial registration failed)
2182
+ relayHeartbeat();
2183
+ // Then every 5 minutes
2184
+ setInterval(relayHeartbeat, 5 * 60 * 1000);
2185
+ }
2186
+
2084
2187
  // ── Step 6 (optional): Start MCP server + register with mcporter ────
2085
2188
  let mcpProcess = null;
2086
2189
  if (withMcp) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-navigator",
3
- "version": "5.7.1",
3
+ "version": "5.7.3",
4
4
  "description": "One-command bridge + tunnel for the Navigator browser — works on any machine, any OS",
5
5
  "keywords": [
6
6
  "browser",