@maintainer-pro/ai-bridge 0.1.18 → 0.1.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maintainer-pro/ai-bridge",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Local bridge daemon that pairs a machine to Maintainer Pro and configures multiple client sandboxes.",
5
5
  "keywords": [
6
6
  "maintainer-pro",
package/src/daemon.mjs CHANGED
@@ -2343,7 +2343,8 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
2343
2343
  ? Buffer.from(msg.body, "base64")
2344
2344
  : Buffer.alloc(0),
2345
2345
  headers,
2346
- ctx.portUrls
2346
+ ctx.portUrls,
2347
+ ctx.publicBase
2347
2348
  );
2348
2349
 
2349
2350
  if (pathname === "/ai-ui.iife.js") {
@@ -2615,6 +2616,7 @@ function handleProxyHttpFromAdmin(msg) {
2615
2616
  chunks: [],
2616
2617
  headers,
2617
2618
  portUrls: ctx.portUrls,
2619
+ publicBase: ctx.publicBase,
2618
2620
  });
2619
2621
  const entry = proxyHttpReqs.get(id);
2620
2622
  if (entry.rewrite) {
@@ -2623,7 +2625,8 @@ function handleProxyHttpFromAdmin(msg) {
2623
2625
  const body = rewriteShareRequestBody(
2624
2626
  Buffer.concat(entry.chunks),
2625
2627
  headers,
2626
- ctx.portUrls
2628
+ ctx.portUrls,
2629
+ ctx.publicBase
2627
2630
  );
2628
2631
  if (body?.length) req.write(body);
2629
2632
  req.end();
@@ -2651,7 +2654,8 @@ function handleProxyHttpBodyFromAdmin(msg) {
2651
2654
  const body = rewriteShareRequestBody(
2652
2655
  Buffer.concat(entry.chunks || []),
2653
2656
  entry.headers,
2654
- entry.portUrls
2657
+ entry.portUrls,
2658
+ entry.publicBase
2655
2659
  );
2656
2660
  if (body?.length) req.write(body);
2657
2661
  req.end();
@@ -284,8 +284,8 @@ function isProbablyUtf8Text(buf) {
284
284
  }
285
285
 
286
286
  /**
287
- * Replace app-list origins (`localhost:8080`, LAN IPs, hostnames) with the
288
- * share URL for that port. Self-contained so the browser shim/SW can embed it.
287
+ * Replace loopback app origins only (`localhost:8080`, `127.0.0.1`, `::1`,
288
+ * `0.0.0.0`). Real hostnames and other base URLs are left unchanged.
289
289
  *
290
290
  * @param {string} text
291
291
  * @param {Record<string, string> | null | undefined} portUrls
@@ -297,14 +297,7 @@ export function rewriteMappedLocalUrls(text, portUrls) {
297
297
  .filter((port) => Number.isInteger(port) && port > 0 && port <= 65535)
298
298
  .sort((a, b) => b - a);
299
299
  if (!ports.length) return text;
300
- const localHost =
301
- "(?:localhost|127\\.0\\.0\\.1|\\[::1\\]|::1|0\\.0\\.0\\.0|" +
302
- "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" +
303
- "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" +
304
- "172\\.(?:1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3}|" +
305
- "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" +
306
- "[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.(?:local|lan|internal|home))?)";
307
- const anyHost = "(?:[^\\s'\"\\\\/<>@:]+|\\[[0-9a-fA-F:]+\\])";
300
+ const localHost = "(?:localhost|127\\.0\\.0\\.1|\\[::1\\]|::1|0\\.0\\.0\\.0)";
308
301
  let out = String(text);
309
302
  for (let i = 0; i < ports.length; i++) {
310
303
  const port = ports[i];
@@ -339,12 +332,6 @@ export function rewriteMappedLocalUrls(text, portUrls) {
339
332
  out = out.replace(new RegExp("//" + localHost + ":" + port + tail, "gi"), function (m) {
340
333
  return swap(m, dest.replace(/^https?:/i, ""));
341
334
  });
342
- out = out.replace(new RegExp("http://" + anyHost + ":" + port + tail, "gi"), function (m) {
343
- return swap(m, dest);
344
- });
345
- out = out.replace(new RegExp("ws://" + anyHost + ":" + port + tail, "gi"), function (m) {
346
- return swap(m, destWs);
347
- });
348
335
  out = out.replace(
349
336
  new RegExp("(?<![\\w./])" + localHost + ":" + port + tail, "gi"),
350
337
  function (m) {
@@ -360,13 +347,17 @@ export function rewriteMappedLocalUrls(text, portUrls) {
360
347
  * @param {Record<string, string>} headers
361
348
  * @param {Record<string, string> | null | undefined} portUrls
362
349
  */
363
- export function rewriteShareRequestBody(body, headers, portUrls) {
364
- if (!body || !portUrls || !Object.keys(portUrls).length) return body;
350
+ export function rewriteShareRequestBody(body, headers, portUrls, publicBase) {
351
+ if (!body) return body;
365
352
  const buf = Buffer.isBuffer(body) ? body : Buffer.from(body);
366
353
  if (!buf.length) return buf;
367
354
  if (!shouldRewriteBody(headers) && !isProbablyUtf8Text(buf)) return buf;
368
- const text = buf.toString("utf8");
369
- const next = rewriteMappedLocalUrls(text, portUrls);
355
+ let text = buf.toString("utf8");
356
+ let next = text;
357
+ if (portUrls && Object.keys(portUrls).length) {
358
+ next = rewriteMappedLocalUrls(next, portUrls);
359
+ }
360
+ if (publicBase) next = rewriteShareOriginPaths(next, publicBase);
370
361
  if (next === text) return buf;
371
362
  return Buffer.from(next, "utf8");
372
363
  }
@@ -405,35 +396,107 @@ function rewriteHeaderUrls(headers, publicBase, port, portUrls) {
405
396
  publicBase
406
397
  );
407
398
  }
408
- if (!(portUrls && Object.keys(portUrls).length)) return;
409
- for (const key of Object.keys(headers)) {
410
- const lower = key.toLowerCase();
411
- if (lower === "set-cookie" || lower === "content-length" || lower === "content-encoding") {
412
- continue;
399
+ if (portUrls && Object.keys(portUrls).length) {
400
+ for (const key of Object.keys(headers)) {
401
+ const lower = key.toLowerCase();
402
+ if (lower === "set-cookie" || lower === "content-length" || lower === "content-encoding") {
403
+ continue;
404
+ }
405
+ headers[key] = rewriteMappedLocalUrls(headers[key], portUrls);
406
+ }
407
+ }
408
+ if (publicBase) {
409
+ for (const key of Object.keys(headers)) {
410
+ const lower = key.toLowerCase();
411
+ if (lower === "set-cookie" || lower === "content-length" || lower === "content-encoding") {
412
+ continue;
413
+ }
414
+ headers[key] = rewriteShareOriginPaths(headers[key], publicBase);
415
+ }
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Apps that honor X-Forwarded-Host emit https://admin-host/oauth2 instead of
421
+ * localhost:8080/oauth2. Put those back under /p/{token}/{slug} so the token
422
+ * and app route stay on the URL.
423
+ *
424
+ * @param {string} value
425
+ * @param {string} publicBase
426
+ */
427
+ export function prefixShareOriginUrl(value, publicBase) {
428
+ if (!value || !publicBase) return value;
429
+ try {
430
+ const base = new URL(String(publicBase).replace(/\/$/, ""));
431
+ const prefix = base.pathname.replace(/\/$/, "");
432
+ if (!prefix || prefix.indexOf("/p/") !== 0) return value;
433
+ const u = new URL(String(value), base);
434
+ if (u.protocol !== "http:" && u.protocol !== "https:") return value;
435
+ if (String(u.hostname || "").toLowerCase() !== String(base.hostname || "").toLowerCase()) {
436
+ return value;
413
437
  }
414
- headers[key] = rewriteMappedLocalUrls(headers[key], portUrls);
438
+ const reqPort = u.port || (u.protocol === "https:" ? "443" : "80");
439
+ const basePort = base.port || (base.protocol === "https:" ? "443" : "80");
440
+ if (reqPort !== basePort) return value;
441
+ const path = u.pathname || "/";
442
+ if (path === "/" || path === "") return value;
443
+ if (path === prefix || path.indexOf(prefix + "/") === 0) return u.toString();
444
+ if (path.indexOf("/p/") === 0) return u.toString();
445
+ u.pathname = prefix + path;
446
+ return u.toString();
447
+ } catch {
448
+ return value;
415
449
  }
416
450
  }
417
451
 
452
+ export function rewriteShareOriginPaths(text, publicBase) {
453
+ if (!text || !publicBase) return text;
454
+ let origin = "";
455
+ try {
456
+ origin = new URL(String(publicBase).replace(/\/$/, "")).origin;
457
+ } catch {
458
+ return text;
459
+ }
460
+ if (!origin) return text;
461
+ const originEsc = origin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
462
+ const originSlashEsc = origin
463
+ .replace(/\//g, "\\/")
464
+ .replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
465
+ let out = String(text);
466
+ out = out.replace(new RegExp(originEsc + "/(?!p/)[^\\s'\"<>\\\\]+", "gi"), function (matched) {
467
+ return prefixShareOriginUrl(matched, publicBase);
468
+ });
469
+ out = out.replace(
470
+ new RegExp(originSlashEsc + "\\\\/(?!p/)[^\\s'\"<>]+", "gi"),
471
+ function (matched) {
472
+ const unescaped = matched.replace(/\\\//g, "/");
473
+ return prefixShareOriginUrl(unescaped, publicBase).replace(/\//g, "\\/");
474
+ }
475
+ );
476
+ return out;
477
+ }
478
+
418
479
  function rewriteLocation(value, publicBase, port, portUrls) {
419
480
  if (!publicBase && !(portUrls && Object.keys(portUrls).length)) return value;
420
481
  try {
421
482
  const u = new URL(value, `http://127.0.0.1:${Number(port) || 0}`);
422
- if (!isLoopbackHost(u.hostname)) {
423
- return rewriteMappedLocalUrls(String(value), portUrls);
424
- }
425
- const locPort = Number(u.port) || Number(port) || 0;
426
- const mapped =
427
- mappedUrlForPort(portUrls, locPort) ||
428
- String(publicBase || "").replace(/\/$/, "");
429
- if (!mapped) return value;
430
- const prefix = proxyPathPrefix(mapped);
431
- let path = u.pathname || "/";
432
- if (prefix && (path === prefix || path.startsWith(`${prefix}/`))) {
433
- path = path.slice(prefix.length) || "/";
483
+ if (isLoopbackHost(u.hostname)) {
484
+ const locPort = Number(u.port) || Number(port) || 0;
485
+ const mapped =
486
+ mappedUrlForPort(portUrls, locPort) ||
487
+ String(publicBase || "").replace(/\/$/, "");
488
+ if (!mapped) return value;
489
+ const prefix = proxyPathPrefix(mapped);
490
+ let path = u.pathname || "/";
491
+ if (prefix && (path === prefix || path.startsWith(`${prefix}/`))) {
492
+ path = path.slice(prefix.length) || "/";
493
+ }
494
+ if (path === "/") return `${mapped}${u.search}${u.hash}`;
495
+ return `${mapped}${path}${u.search}${u.hash}`;
434
496
  }
435
- if (path === "/") return `${mapped}${u.search}${u.hash}`;
436
- return `${mapped}${path}${u.search}${u.hash}`;
497
+ const fromPorts = rewriteMappedLocalUrls(String(value), portUrls);
498
+ if (fromPorts !== String(value)) return fromPorts;
499
+ if (publicBase) return prefixShareOriginUrl(String(value), publicBase);
437
500
  } catch {
438
501
  /* keep */
439
502
  }
@@ -911,6 +974,7 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
911
974
  if (path === p || path.indexOf(p + "/") === 0) return collapse(path);
912
975
  var root = p.slice(0, p.lastIndexOf("/"));
913
976
  if (root && (path === root || path.indexOf(root + "/") === 0)) return path;
977
+ if (path.indexOf("/p/") === 0) return path;
914
978
  if (path === "/api/v1" || path.indexOf("/api/v1/") === 0) return path;
915
979
  return collapse(p + path);
916
980
  }
@@ -948,31 +1012,14 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
948
1012
  try {
949
1013
  var nav = new URL(v, location.href);
950
1014
  if (nav.origin !== location.origin) return v;
1015
+ if (nav.pathname === "/" || nav.pathname === "") return v;
951
1016
  nav.pathname = prefixPath(nav.pathname);
952
1017
  return nav.toString();
953
1018
  } catch (e) {}
954
1019
  return v;
955
1020
  }
956
1021
  function addNet(v) {
957
- if (typeof v !== "string" || !v) return v;
958
- var mapped = mapLocal(v);
959
- if (mapped !== v) return mapped;
960
- try {
961
- if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(v) || v.indexOf("//") === 0) {
962
- var net = new URL(v, location.href);
963
- if (net.origin !== location.origin) return v;
964
- if (net.pathname === p || net.pathname.indexOf(p + "/") === 0) return v;
965
- if (!isAsset(net.pathname)) return v;
966
- net.pathname = prefixPath(net.pathname);
967
- return net.toString();
968
- }
969
- } catch (e) {}
970
- if (v.charAt(0) === "/" && v.charAt(1) !== "/") {
971
- if (v === p || v.indexOf(p + "/") === 0) return collapse(v);
972
- if (!isAsset(v)) return v;
973
- return prefixPath(v);
974
- }
975
- return v;
1022
+ return addNav(v);
976
1023
  }
977
1024
  function mapSel(sel) {
978
1025
  if (typeof sel !== "string") return sel;
@@ -1156,17 +1203,48 @@ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
1156
1203
  return text;
1157
1204
  }
1158
1205
  }
1159
- function mapUrl(v) {
1206
+ function appPrefixFromHref(href) {
1207
+ try {
1208
+ var u = new URL(String(href || ""));
1209
+ var parts = u.pathname.split("/").filter(Boolean);
1210
+ if (parts[0] === "p" && parts[1] && parts[2]) {
1211
+ return "/" + parts[0] + "/" + parts[1] + "/" + parts[2];
1212
+ }
1213
+ } catch (e) {}
1214
+ return "";
1215
+ }
1216
+ function prefixWith(url, prefix) {
1217
+ if (!prefix) return url;
1218
+ try {
1219
+ var u = new URL(url, self.location.href);
1220
+ if (u.origin !== self.location.origin) return url;
1221
+ if (u.pathname === "/" || u.pathname === "") return url;
1222
+ if (u.pathname.indexOf("/p/") === 0) return url;
1223
+ if (u.pathname === prefix || u.pathname.indexOf(prefix + "/") === 0) return url;
1224
+ u.pathname = u.pathname === "/" ? prefix : prefix + u.pathname;
1225
+ return u.toString();
1226
+ } catch (e) {}
1227
+ return url;
1228
+ }
1229
+ function mapUrl(v, prefix) {
1160
1230
  if (typeof v !== "string" || !v) return v;
1161
1231
  if (v.indexOf("/__mp/") !== -1) return v;
1162
- return rewriteText(v);
1232
+ return prefixWith(rewriteText(v), prefix);
1163
1233
  }
1164
1234
  self.addEventListener("fetch", function (event) {
1165
1235
  var req = event.request;
1166
1236
  if (req.url.indexOf("/__mp/") !== -1) return;
1167
1237
  event.respondWith(
1168
1238
  (async function () {
1169
- var url = mapUrl(req.url);
1239
+ var client = null;
1240
+ try {
1241
+ if (event.clientId) client = await self.clients.get(event.clientId);
1242
+ } catch (e) {}
1243
+ var prefix =
1244
+ appPrefixFromHref(client && client.url) ||
1245
+ appPrefixFromHref(req.referrer) ||
1246
+ appPrefixFromHref(self.registration && self.registration.scope);
1247
+ var url = mapUrl(req.url, prefix);
1170
1248
  var method = req.method;
1171
1249
  var headers = new Headers(req.headers);
1172
1250
  var body;
@@ -1195,15 +1273,24 @@ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
1195
1273
  } catch (e) {}
1196
1274
  }
1197
1275
  var res = await fetch(new Request(url, init));
1276
+ var outHeaders = new Headers(res.headers);
1277
+ var loc = outHeaders.get("location");
1278
+ if (loc) outHeaders.set("location", mapUrl(loc, prefix));
1198
1279
  var resType = res.headers.get("content-type") || "";
1199
1280
  if (
1200
1281
  !/html|javascript|ecmascript|json|css|xml|svg|text|urlencoded/.test(resType)
1201
1282
  ) {
1283
+ if (loc && outHeaders.get("location") !== loc) {
1284
+ return new Response(res.body, {
1285
+ status: res.status,
1286
+ statusText: res.statusText,
1287
+ headers: outHeaders,
1288
+ });
1289
+ }
1202
1290
  return res;
1203
1291
  }
1204
1292
  var resText = await res.text();
1205
1293
  var mapped = rewriteText(resText);
1206
- var outHeaders = new Headers(res.headers);
1207
1294
  outHeaders.delete("content-length");
1208
1295
  outHeaders.delete("content-encoding");
1209
1296
  return new Response(mapped, {
@@ -1305,6 +1392,13 @@ export function processShareHttpResponse(opts) {
1305
1392
  mutated = true;
1306
1393
  }
1307
1394
  }
1395
+ if (publicBase && rewriteTextBody) {
1396
+ const prefixed = rewriteShareOriginPaths(text, publicBase);
1397
+ if (prefixed !== text) {
1398
+ text = prefixed;
1399
+ mutated = true;
1400
+ }
1401
+ }
1308
1402
  if (aiPublicBase) {
1309
1403
  const withShareUrls = rewriteLocalSidecarUrls(text, aiPublicBase, publicBase);
1310
1404
  if (withShareUrls !== text) {