@maintainer-pro/ai-bridge 0.1.12 → 0.1.14

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.12",
3
+ "version": "0.1.14",
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",
@@ -28,7 +28,7 @@
28
28
  "node": ">=22"
29
29
  },
30
30
  "dependencies": {
31
- "@maintainer-pro/ai-cli": "^0.1.7",
32
- "@maintainer-pro/ai-server": "^0.1.6"
31
+ "@maintainer-pro/ai-cli": "^0.1.8",
32
+ "@maintainer-pro/ai-server": "^0.1.7"
33
33
  }
34
34
  }
package/src/daemon.mjs CHANGED
@@ -18,6 +18,13 @@ import readline from "node:readline";
18
18
  import { fileURLToPath, pathToFileURL } from "node:url";
19
19
  import { createLogger, ensureProjectDataDir } from "@maintainer-pro/ai-cli";
20
20
  import { findIife, startAiServer } from "@maintainer-pro/ai-server";
21
+ import {
22
+ applyShareCacheHeaders,
23
+ lookupShareResponse,
24
+ prepareShareHttpRequest,
25
+ processShareHttpResponse,
26
+ shouldRewriteBody,
27
+ } from "./share-rewrite.mjs";
21
28
 
22
29
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
30
  const PACKAGE_VERSION = readPackageVersion();
@@ -2306,6 +2313,50 @@ function replyProxyHttp(id, status, headers, body) {
2306
2313
  bridgeSend({ type: "proxy.http.chunk", id, stream, data: "", eof: true });
2307
2314
  }
2308
2315
 
2316
+ function shareProxyContext(msg, ws, appId) {
2317
+ const apps = Array.isArray(ws?.hostApps) ? ws.hostApps : [];
2318
+ const app = apps.find((row) => row.id === appId);
2319
+ const slug =
2320
+ (typeof msg.slug === "string" && msg.slug.trim()) ||
2321
+ (isAiServerAppId(appId, ws) ? "ai" : proxySlugForApp(app));
2322
+ const publicBase = String(
2323
+ (typeof msg.publicBase === "string" && msg.publicBase) ||
2324
+ proxyUrlForApp(
2325
+ ws,
2326
+ app || {
2327
+ id: appId,
2328
+ role: slug === "ai" ? "ai-server" : "ui",
2329
+ }
2330
+ ) ||
2331
+ ""
2332
+ ).replace(/\/$/, "");
2333
+ const aiPublicBase = String(
2334
+ (typeof msg.aiPublicBase === "string" && msg.aiPublicBase) ||
2335
+ proxyUrlForApp(ws, { id: "ai-server", role: "ai-server" }) ||
2336
+ ""
2337
+ ).replace(/\/$/, "");
2338
+ const acceptEncoding =
2339
+ typeof msg.acceptEncoding === "string" ? msg.acceptEncoding : "";
2340
+ return {
2341
+ path: safeProxyPath(msg.path),
2342
+ slug,
2343
+ publicBase,
2344
+ aiPublicBase,
2345
+ acceptEncoding,
2346
+ port: localPortForProxy(ws, appId),
2347
+ };
2348
+ }
2349
+
2350
+ function sendProcessedProxyHttp(id, ctx, status, headers, body) {
2351
+ const processed = processShareHttpResponse({
2352
+ ...ctx,
2353
+ status,
2354
+ headers,
2355
+ body,
2356
+ });
2357
+ replyProxyHttp(id, processed.status, processed.headers, processed.body);
2358
+ }
2359
+
2309
2360
  function bridgeEmbedConfigJs(ws) {
2310
2361
  const store = ws?.store && typeof ws.store === "object" ? ws.store : {};
2311
2362
  const aiApp = { id: "ai-server", role: "ai-server" };
@@ -2331,7 +2382,16 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
2331
2382
  const method = String(msg.method || "GET").toUpperCase();
2332
2383
  const reqPath = safeProxyPath(msg.path);
2333
2384
  const pathname = reqPath.split("?")[0] || "/";
2334
- const headers = proxyReqHeaders(msg.headers);
2385
+ const ctx = shareProxyContext(msg, ws, "ai-server");
2386
+ const prepared = prepareShareHttpRequest(
2387
+ proxyReqHeaders(msg.headers),
2388
+ ctx.publicBase,
2389
+ ctx.acceptEncoding,
2390
+ reqPath
2391
+ );
2392
+ ctx.acceptEncoding = prepared.acceptEncoding;
2393
+ ctx.ifNoneMatch = prepared.ifNoneMatch;
2394
+ const headers = prepared.headers;
2335
2395
  const body =
2336
2396
  typeof msg.body === "string" && msg.body
2337
2397
  ? Buffer.from(msg.body, "base64")
@@ -2340,20 +2400,21 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
2340
2400
  if (pathname === "/ai-ui.iife.js") {
2341
2401
  const file = findIife();
2342
2402
  if (!file || !fs.existsSync(file)) {
2343
- replyProxyHttp(
2403
+ sendProcessedProxyHttp(
2344
2404
  id,
2405
+ ctx,
2345
2406
  404,
2346
2407
  { "content-type": "text/plain; charset=utf-8" },
2347
2408
  "Not found"
2348
2409
  );
2349
2410
  return;
2350
2411
  }
2351
- replyProxyHttp(
2412
+ sendProcessedProxyHttp(
2352
2413
  id,
2414
+ ctx,
2353
2415
  200,
2354
2416
  {
2355
2417
  "content-type": "text/javascript; charset=utf-8",
2356
- "cache-control": "no-store",
2357
2418
  },
2358
2419
  fs.readFileSync(file)
2359
2420
  );
@@ -2363,8 +2424,9 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
2363
2424
  if (pathname === "/embed-config.js") {
2364
2425
  const cfg = bridgeCfg || loadConfig();
2365
2426
  await loadSandboxStoreEnv(ws, cfg);
2366
- replyProxyHttp(
2427
+ sendProcessedProxyHttp(
2367
2428
  id,
2429
+ ctx,
2368
2430
  200,
2369
2431
  {
2370
2432
  "content-type": "text/javascript; charset=utf-8",
@@ -2387,7 +2449,7 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
2387
2449
  headers,
2388
2450
  body,
2389
2451
  });
2390
- replyProxyHttp(id, result.status, result.headers, result.body);
2452
+ sendProcessedProxyHttp(id, ctx, result.status, result.headers, result.body);
2391
2453
  } catch (err) {
2392
2454
  bridgeSend({
2393
2455
  type: "proxy.http.error",
@@ -2443,7 +2505,29 @@ function handleProxyHttpFromAdmin(msg) {
2443
2505
  }
2444
2506
  const method = String(msg.method || "GET").toUpperCase();
2445
2507
  const path = safeProxyPath(msg.path);
2446
- const headers = proxyReqHeaders(msg.headers);
2508
+ const ctx = shareProxyContext(msg, ws, appId);
2509
+ ctx.port = port;
2510
+ const prepared = prepareShareHttpRequest(
2511
+ proxyReqHeaders(msg.headers),
2512
+ ctx.publicBase,
2513
+ ctx.acceptEncoding,
2514
+ path
2515
+ );
2516
+ ctx.acceptEncoding = prepared.acceptEncoding;
2517
+ ctx.ifNoneMatch = prepared.ifNoneMatch;
2518
+ if (method === "GET" || method === "HEAD") {
2519
+ const cached = lookupShareResponse({
2520
+ publicBase: ctx.publicBase,
2521
+ path,
2522
+ ifNoneMatch: ctx.ifNoneMatch,
2523
+ acceptEncoding: ctx.acceptEncoding,
2524
+ });
2525
+ if (cached) {
2526
+ replyProxyHttp(id, cached.status, cached.headers, cached.body);
2527
+ return;
2528
+ }
2529
+ }
2530
+ const headers = prepared.headers;
2447
2531
  headers.host = `127.0.0.1:${port}`;
2448
2532
  // Next.js dev 403s `/_next` when Origin/sec-fetch look cross-site.
2449
2533
  // This hop is server-to-server; drop those so chunks always load.
@@ -2479,11 +2563,31 @@ function handleProxyHttpFromAdmin(msg) {
2479
2563
  if (value == null) continue;
2480
2564
  outHeaders[key] = Array.isArray(value) ? value.join(", ") : String(value);
2481
2565
  }
2566
+ const status = res.statusCode || 502;
2567
+ applyShareCacheHeaders(outHeaders, path);
2568
+ if (shouldRewriteBody(outHeaders)) {
2569
+ /** @type {Buffer[]} */
2570
+ const chunks = [];
2571
+ res.on("data", (chunk) => {
2572
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
2573
+ });
2574
+ res.on("end", () => {
2575
+ proxyHttpReqs.delete(id);
2576
+ sendProcessedProxyHttp(
2577
+ id,
2578
+ ctx,
2579
+ status,
2580
+ outHeaders,
2581
+ Buffer.concat(chunks)
2582
+ );
2583
+ });
2584
+ return;
2585
+ }
2482
2586
  bridgeSend({
2483
2587
  type: "proxy.http.start",
2484
2588
  id,
2485
2589
  stream,
2486
- status: res.statusCode || 502,
2590
+ status,
2487
2591
  headers: outHeaders,
2488
2592
  });
2489
2593
  res.on("data", (chunk) => {
@@ -5096,6 +5200,12 @@ async function main() {
5096
5200
 
5097
5201
  if (args.pair || !cfg.token || !cfg.adminUrl) {
5098
5202
  cfg = await pairFlow(args);
5203
+ } else if (typeof args.adminUrl === "string" && args.adminUrl.trim()) {
5204
+ const next = args.adminUrl.replace(/\/$/, "");
5205
+ if (cfg.adminUrl && cfg.adminUrl !== next) {
5206
+ log(`admin URL ${cfg.adminUrl} → ${next}`);
5207
+ }
5208
+ cfg.adminUrl = next;
5099
5209
  }
5100
5210
 
5101
5211
  cfg.noAiServer = Boolean(args.noAiServer);
@@ -5265,6 +5375,7 @@ async function main() {
5265
5375
  senderType: msg.senderType === "client" ? "client" : undefined,
5266
5376
  senderName:
5267
5377
  typeof msg.senderName === "string" ? msg.senderName : undefined,
5378
+ context: msg.context && typeof msg.context === "object" ? msg.context : undefined,
5268
5379
  };
5269
5380
  const embedded = embeddedChat.get(sandboxId);
5270
5381
  if (embedded?.runChat) {
@@ -5452,6 +5563,11 @@ async function main() {
5452
5563
  clearHeartbeatTimer();
5453
5564
  clearPingTimer();
5454
5565
  if (socket === ws) socket = null;
5566
+ if (Number(code) === 1008) {
5567
+ warn(
5568
+ "Admin rejected this computer’s token. This admin is not the one in ~/.maintainer-pro/bridge.json. Generate a new pair code (Admin → Bridges → Add computer) and run the pair command."
5569
+ );
5570
+ }
5455
5571
  scheduleReconnect(code, reason);
5456
5572
  };
5457
5573
 
@@ -0,0 +1,781 @@
1
+ /**
2
+ * Share-URL processing lives on the bridge: path rewrite, widget injection,
3
+ * cache-control, and gzip. Maintainer Pro only forwards the result.
4
+ */
5
+ import { gzipSync } from "node:zlib";
6
+ import { createHash } from "node:crypto";
7
+
8
+ const REWRITE_CACHE_MS = 10 * 60 * 1000;
9
+ const REWRITE_CACHE_MAX = 128;
10
+
11
+ /** @type {Map<string, { at: number, payload: Buffer, status: number, headers: Record<string, string>, etag: string }>} */
12
+ const rewriteCache = new Map();
13
+
14
+ /**
15
+ * @param {Record<string, string>} headers
16
+ * @returns {Record<string, string>}
17
+ */
18
+ function lowerHeaders(headers) {
19
+ /** @type {Record<string, string>} */
20
+ const out = {};
21
+ for (const [key, value] of Object.entries(headers || {})) {
22
+ if (value == null) continue;
23
+ out[String(key).toLowerCase()] = String(value);
24
+ }
25
+ return out;
26
+ }
27
+
28
+ function headerGet(headers, name) {
29
+ const lower = String(name).toLowerCase();
30
+ for (const [key, value] of Object.entries(headers || {})) {
31
+ if (String(key).toLowerCase() === lower) return String(value);
32
+ }
33
+ return "";
34
+ }
35
+
36
+ function stripProxyPrefix(urlOrPath, pathPrefix) {
37
+ if (!pathPrefix || !urlOrPath) return urlOrPath;
38
+ try {
39
+ const u = new URL(urlOrPath, "http://mp.invalid");
40
+ let path = u.pathname;
41
+ if (path === pathPrefix) path = "/";
42
+ else if (path.startsWith(`${pathPrefix}/`)) {
43
+ path = path.slice(pathPrefix.length) || "/";
44
+ }
45
+ return `${path}${u.search}${u.hash}`;
46
+ } catch {
47
+ if (urlOrPath === pathPrefix) return "/";
48
+ if (urlOrPath.startsWith(`${pathPrefix}/`)) {
49
+ return urlOrPath.slice(pathPrefix.length) || "/";
50
+ }
51
+ return urlOrPath;
52
+ }
53
+ }
54
+
55
+ function proxyPathPrefix(publicBase) {
56
+ const trimmed = String(publicBase || "").replace(/\/$/, "");
57
+ if (!trimmed) return "";
58
+ try {
59
+ return new URL(trimmed).pathname.replace(/\/$/, "") || "";
60
+ } catch {
61
+ const start = trimmed.indexOf("://");
62
+ if (start < 0) return trimmed;
63
+ const pathStart = trimmed.indexOf("/", start + 3);
64
+ return pathStart >= 0 ? trimmed.slice(pathStart).replace(/\/$/, "") : "";
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Force identity to the local app so HTML/JS can be rewritten, and strip the
70
+ * share prefix from Next's `next-url` header.
71
+ *
72
+ * @param {Record<string, string>} headers
73
+ * @param {string} [publicBase]
74
+ * @param {string} [acceptEncodingHint]
75
+ * @param {string} [path]
76
+ * @returns {{ headers: Record<string, string>, acceptEncoding: string, ifNoneMatch: string }}
77
+ */
78
+ export function prepareShareHttpRequest(
79
+ headers,
80
+ publicBase,
81
+ acceptEncodingHint,
82
+ path
83
+ ) {
84
+ const out = { ...headers };
85
+ const acceptEncoding = String(
86
+ acceptEncodingHint || headerGet(out, "accept-encoding") || ""
87
+ );
88
+ const ifNoneMatch = headerGet(out, "if-none-match");
89
+ const pathPrefix = proxyPathPrefix(publicBase || "");
90
+ if (pathPrefix) {
91
+ for (const key of Object.keys(out)) {
92
+ if (key.toLowerCase() === "next-url") {
93
+ out[key] = stripProxyPrefix(out[key], pathPrefix);
94
+ }
95
+ }
96
+ }
97
+ for (const key of Object.keys(out)) {
98
+ if (key.toLowerCase() === "accept-encoding") delete out[key];
99
+ }
100
+ out["accept-encoding"] = "identity";
101
+ // Rewritten bodies get our ETag. Do not let Next 304 on its own validator
102
+ // or we would have no HTML/JS to rewrite.
103
+ if (willRewritePath(path)) {
104
+ for (const key of Object.keys(out)) {
105
+ const lower = key.toLowerCase();
106
+ if (lower === "if-none-match" || lower === "if-modified-since") {
107
+ delete out[key];
108
+ }
109
+ }
110
+ }
111
+ if (publicBase && !headerGet(out, "x-forwarded-prefix")) {
112
+ try {
113
+ const prefix = new URL(publicBase).pathname.replace(/\/$/, "") || "/";
114
+ out["x-forwarded-prefix"] = prefix;
115
+ } catch {
116
+ /* ignore */
117
+ }
118
+ }
119
+ return { headers: out, acceptEncoding, ifNoneMatch };
120
+ }
121
+
122
+ export function shouldRewriteBody(headers) {
123
+ const enc = headerGet(headers, "content-encoding").toLowerCase();
124
+ if (enc && enc !== "identity") return false;
125
+ const ct = headerGet(headers, "content-type").toLowerCase();
126
+ return /html|javascript|ecmascript|css|json|svg|xml|text\/plain|text\/x-component|text\/x-ref|\brsc\b/.test(
127
+ ct
128
+ );
129
+ }
130
+
131
+ function isImmutableAssetPath(path) {
132
+ const p = String(path || "").split("?")[0] || "";
133
+ return (
134
+ /\/_next\/static\//.test(p) ||
135
+ /\/__nextjs_font\//.test(p) ||
136
+ /\/node_modules\/\.vite\//.test(p) ||
137
+ /\.[0-9a-f]{8,}\.(?:js|css|woff2?)$/i.test(p)
138
+ );
139
+ }
140
+
141
+ function isNoStorePath(path) {
142
+ const p = String(path || "").split("?")[0] || "";
143
+ return (
144
+ /\/embed-config\.js$/i.test(p) ||
145
+ p === "/api" ||
146
+ p.startsWith("/api/") ||
147
+ /\/_next\/webpack-hmr/i.test(p) ||
148
+ /\/_next\/webpack\/hmr/i.test(p) ||
149
+ /\/__nextjs_original-stack-frames/i.test(p) ||
150
+ /\/@vite(?:\/|$)/.test(p) ||
151
+ /\/@react-refresh/.test(p) ||
152
+ /\/@fs\//.test(p) ||
153
+ /\/@id\//.test(p)
154
+ );
155
+ }
156
+
157
+ function isCacheableMediaPath(path) {
158
+ const p = String(path || "").split("?")[0] || "";
159
+ return /\.(?:png|jpe?g|gif|webp|avif|ico|woff2?|ttf|otf|mp4|webm|mp3|wav|wasm)$/i.test(
160
+ p
161
+ );
162
+ }
163
+
164
+ function isLongCacheScriptPath(path) {
165
+ const p = String(path || "").split("?")[0] || "";
166
+ return /\/ai-ui\.iife\.js$/i.test(p);
167
+ }
168
+
169
+ function willRewritePath(path) {
170
+ const p = String(path || "").split("?")[0]?.toLowerCase() || "";
171
+ if (!p || p === "/") return true;
172
+ if (isCacheableMediaPath(p)) return false;
173
+ if (/\.(?:woff2?|ttf|otf|png|jpe?g|gif|webp|avif|ico|mp4|webm|wasm)$/i.test(p)) {
174
+ return false;
175
+ }
176
+ return true;
177
+ }
178
+
179
+ function etagFor(payload) {
180
+ const hash = createHash("sha1").update(payload).digest("base64url").slice(0, 27);
181
+ return `"${hash}"`;
182
+ }
183
+
184
+ function etagMatches(ifNoneMatch, etag) {
185
+ if (!ifNoneMatch || !etag) return false;
186
+ const want = String(etag).trim();
187
+ return String(ifNoneMatch)
188
+ .split(",")
189
+ .some((part) => {
190
+ const token = part.trim();
191
+ return token === "*" || token === want || token === `W/${want}`;
192
+ });
193
+ }
194
+
195
+ function dropHopCacheNoise(headers) {
196
+ delete headers.pragma;
197
+ delete headers.expires;
198
+ }
199
+
200
+ /**
201
+ * Cache-Control for the share URL. Hashed Next/Vite assets can sit in the
202
+ * browser for a year; HTML/RSC revalidate with ETag; secrets and HMR do not
203
+ * cache.
204
+ *
205
+ * @param {Record<string, string>} headers
206
+ * @param {string} path
207
+ * @returns {"no-store" | "immutable" | "store" | "revalidate"}
208
+ */
209
+ export function applyShareCacheHeaders(headers, path) {
210
+ dropHopCacheNoise(headers);
211
+ if (isNoStorePath(path)) {
212
+ headers["cache-control"] = "private, no-store";
213
+ return "no-store";
214
+ }
215
+ if (isImmutableAssetPath(path)) {
216
+ delete headers["set-cookie"];
217
+ headers["cache-control"] = "public, max-age=31536000, immutable";
218
+ return "immutable";
219
+ }
220
+ if (isCacheableMediaPath(path) || isLongCacheScriptPath(path)) {
221
+ delete headers["set-cookie"];
222
+ headers["cache-control"] = "public, max-age=86400";
223
+ return "store";
224
+ }
225
+ headers["cache-control"] = "no-cache";
226
+ return "revalidate";
227
+ }
228
+
229
+ function notModifiedResult(headers, etag) {
230
+ const out = {
231
+ "cache-control": headers["cache-control"] || "no-cache",
232
+ etag,
233
+ };
234
+ if (headers.vary) out.vary = headers.vary;
235
+ return { status: 304, headers: out, body: Buffer.alloc(0) };
236
+ }
237
+
238
+ function rewriteLocation(value, publicBase, port) {
239
+ if (!publicBase) return value;
240
+ const base = String(publicBase).replace(/\/$/, "");
241
+ const prefix = proxyPathPrefix(publicBase);
242
+ try {
243
+ const u = new URL(value, `http://127.0.0.1:${Number(port) || 0}`);
244
+ const host = String(u.hostname || "").toLowerCase();
245
+ const local =
246
+ host === "127.0.0.1" ||
247
+ host === "localhost" ||
248
+ host === "::1" ||
249
+ host === "0.0.0.0";
250
+ if (!local) return value;
251
+ let path = u.pathname || "/";
252
+ if (prefix && (path === prefix || path.startsWith(`${prefix}/`))) {
253
+ path = path.slice(prefix.length) || "/";
254
+ }
255
+ return `${base}${path}${u.search}${u.hash}`;
256
+ } catch {
257
+ /* keep */
258
+ }
259
+ return value;
260
+ }
261
+
262
+ function rewriteLinkHeader(value, publicBase) {
263
+ const prefix = proxyPathPrefix(publicBase);
264
+ if (!prefix || !value) return value;
265
+ return String(value).replace(/<(\/(?!\/)[^>\s]*)>/g, (full, path) =>
266
+ alreadyPrefixed(path, prefix) ? full : `<${prefix}${path}>`
267
+ );
268
+ }
269
+
270
+ function gzipIfRequested(payload, headers, acceptEncoding) {
271
+ if (payload.length < 1024) return payload;
272
+ if (!/\bgzip\b/i.test(acceptEncoding || "")) return payload;
273
+ const enc = headerGet(headers, "content-encoding");
274
+ if (enc && enc !== "identity") return payload;
275
+ try {
276
+ const gzipped = gzipSync(payload, { level: 6 });
277
+ if (gzipped.length >= payload.length) return payload;
278
+ for (const key of Object.keys(headers)) {
279
+ if (key.toLowerCase() === "content-encoding") delete headers[key];
280
+ if (key.toLowerCase() === "content-length") delete headers[key];
281
+ }
282
+ headers["content-encoding"] = "gzip";
283
+ const vary = headerGet(headers, "vary");
284
+ if (!/\baccept-encoding\b/i.test(vary)) {
285
+ for (const key of Object.keys(headers)) {
286
+ if (key.toLowerCase() === "vary") delete headers[key];
287
+ }
288
+ headers.vary = vary ? `${vary}, Accept-Encoding` : "Accept-Encoding";
289
+ }
290
+ return gzipped;
291
+ } catch {
292
+ return payload;
293
+ }
294
+ }
295
+
296
+ function rewriteCacheKey(publicBase, path) {
297
+ if (!isImmutableAssetPath(path) && !isLongCacheScriptPath(path)) return null;
298
+ return `${publicBase || ""}\0${String(path || "").split("?")[0]}`;
299
+ }
300
+
301
+ function rememberRewrite(key, payload, status, headers, etag) {
302
+ if (rewriteCache.size >= REWRITE_CACHE_MAX) {
303
+ const first = rewriteCache.keys().next().value;
304
+ if (first) rewriteCache.delete(first);
305
+ }
306
+ rewriteCache.set(key, {
307
+ at: Date.now(),
308
+ payload,
309
+ status,
310
+ headers: { ...headers },
311
+ etag,
312
+ });
313
+ }
314
+
315
+ /**
316
+ * Serve a previously rewritten immutable asset without hitting Next.
317
+ * Returns a 304 when the browser already has this ETag.
318
+ *
319
+ * @param {{ publicBase?: string, path?: string, ifNoneMatch?: string, acceptEncoding?: string }} opts
320
+ * @returns {{ status: number, headers: Record<string, string>, body: Buffer } | null}
321
+ */
322
+ export function lookupShareResponse(opts) {
323
+ const path = String(opts?.path || "/");
324
+ const key = rewriteCacheKey(opts?.publicBase || "", path);
325
+ if (!key) return null;
326
+ const hit = rewriteCache.get(key);
327
+ if (!hit || Date.now() - hit.at >= REWRITE_CACHE_MS) return null;
328
+ return finishCachedShareResponse(hit, path, opts?.ifNoneMatch, opts?.acceptEncoding);
329
+ }
330
+
331
+ function finishCachedShareResponse(hit, path, ifNoneMatch, acceptEncoding) {
332
+ const outHeaders = { ...hit.headers };
333
+ applyShareCacheHeaders(outHeaders, path);
334
+ const etag = hit.etag || etagFor(hit.payload);
335
+ outHeaders.etag = etag;
336
+ if (etagMatches(ifNoneMatch, etag)) {
337
+ return notModifiedResult(outHeaders, etag);
338
+ }
339
+ const gzipped = gzipIfRequested(hit.payload, outHeaders, acceptEncoding || "");
340
+ outHeaders["content-length"] = String(gzipped.length);
341
+ return { status: hit.status, headers: outHeaders, body: gzipped };
342
+ }
343
+
344
+ function isNextDocument(body) {
345
+ return /\/_next\//.test(body) || /__NEXT_DATA__|self\.__next_f/.test(body);
346
+ }
347
+
348
+ function isViteDocument(body) {
349
+ return (
350
+ /\/@vite(?:\/client)?(?:["'?]|$)/.test(body) ||
351
+ /\/@react-refresh/.test(body) ||
352
+ /\/node_modules\/\.vite\//.test(body) ||
353
+ /\/node_modules\/\.pnpm\/vite@/.test(body)
354
+ );
355
+ }
356
+
357
+ function viteDevBases(body) {
358
+ const bases = [];
359
+ const re =
360
+ /\bconst base(?:\$\d+)?\s*=\s*["'](\/[^"']*)["']\s*\|\|\s*["']\/["']/g;
361
+ let match;
362
+ while ((match = re.exec(body))) {
363
+ const base = match[1];
364
+ if (!base || base === "/") continue;
365
+ bases.push(base.endsWith("/") ? base : `${base}/`);
366
+ }
367
+ return bases;
368
+ }
369
+
370
+ function viteAssetRoots(body) {
371
+ const roots = new Set([
372
+ "/@vite",
373
+ "/@react-refresh",
374
+ "/@fs",
375
+ "/@id",
376
+ "/node_modules/",
377
+ "/src/",
378
+ "/config.json",
379
+ ]);
380
+ const re =
381
+ /["'`](\/[^"'`]*?)(?=\/(?:@vite|@react-refresh|@fs|@id|node_modules\/|src\/))/g;
382
+ let match;
383
+ while ((match = re.exec(body))) {
384
+ const base = match[1];
385
+ if (!base || base === "/") continue;
386
+ roots.add(base.endsWith("/") ? base : `${base}/`);
387
+ }
388
+ for (const base of viteDevBases(body)) roots.add(base);
389
+ return [...roots];
390
+ }
391
+
392
+ function joinProxyAndViteBase(pathPrefix, viteBase) {
393
+ const prefix = pathPrefix.replace(/\/$/, "");
394
+ if (!viteBase || viteBase === "/") return `${prefix}/`;
395
+ const base = viteBase.startsWith("/") ? viteBase : `/${viteBase}`;
396
+ return `${prefix}${base.endsWith("/") ? base : `${base}/`}`;
397
+ }
398
+
399
+ function rewriteViteHmrClient(body, pathPrefix) {
400
+ if (!pathPrefix || !body.includes("vite-hmr")) return body;
401
+ const viteBase = viteDevBases(body)[0] || "/";
402
+ const prefixed = joinProxyAndViteBase(pathPrefix, viteBase);
403
+ const hostPath = JSON.stringify(prefixed).slice(1, -1);
404
+ let out = body;
405
+ out = out.replace(
406
+ /\bconst socketHost = `[\s\S]*?`;/,
407
+ `const socketHost = \`\${importMetaUrl.host}${hostPath}\`;`
408
+ );
409
+ out = out.replace(
410
+ /\bconst directSocketHost = (?:"[^"]*"|'[^']*');/,
411
+ `const directSocketHost = importMetaUrl.host + ${JSON.stringify(prefixed)};`
412
+ );
413
+ return out;
414
+ }
415
+
416
+ function prefixQuotedRoots(body, pathPrefix, roots) {
417
+ if (!pathPrefix) return body;
418
+ let out = body;
419
+ for (const root of roots) {
420
+ const escaped = root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
421
+ out = out.replace(
422
+ new RegExp(`(["'\`])(${escaped})`, "g"),
423
+ `$1${pathPrefix}$2`
424
+ );
425
+ }
426
+ return out;
427
+ }
428
+
429
+ function alreadyPrefixed(path, pathPrefix) {
430
+ return path === pathPrefix || path.startsWith(`${pathPrefix}/`);
431
+ }
432
+
433
+ function rewriteNextFlightCanonical(body, pathPrefix) {
434
+ if (!pathPrefix || !body) return body;
435
+ const encoded = JSON.stringify(pathPrefix);
436
+ const escaped = pathPrefix.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
437
+ let out = body;
438
+ if (out.includes(`"c":["",""]`)) {
439
+ out = out.split(`"c":["",""]`).join(`"c":[${encoded}]`);
440
+ }
441
+ if (out.includes('\\"c\\":[\\"\\",\\"\\"]')) {
442
+ out = out
443
+ .split('\\"c\\":[\\"\\",\\"\\"]')
444
+ .join(`\\"c\\":[\\"${escaped}\\"]`);
445
+ }
446
+ out = out.replace(/("b":"[^"]+","p":)""/g, `$1${encoded}`);
447
+ out = out.replace(
448
+ /(\\?"b\\":\\?"[^"]+\\?",\\?"p\\":)\\?"\\?"/g,
449
+ `$1\\"${escaped}\\"`
450
+ );
451
+ if (out.includes(`"assetPrefix":""`)) {
452
+ out = out.split(`"assetPrefix":""`).join(`"assetPrefix":${encoded}`);
453
+ }
454
+ if (out.includes('\\"assetPrefix\\":\\"\\"')) {
455
+ out = out
456
+ .split('\\"assetPrefix\\":\\"\\"')
457
+ .join(`\\"assetPrefix\\":\\"${escaped}\\"`);
458
+ }
459
+ return out;
460
+ }
461
+
462
+ function rewriteNextTurbopackBasePath(body, pathPrefix) {
463
+ if (!pathPrefix) return body;
464
+ let out = rewriteNextFlightCanonical(body, pathPrefix);
465
+ if (out.includes("TURBOPACK compile-time value")) {
466
+ const next = JSON.stringify(pathPrefix);
467
+ out = out
468
+ .split(`("TURBOPACK compile-time value", "") || ''`)
469
+ .join(`("TURBOPACK compile-time value", ${next}) || ''`)
470
+ .split(`("TURBOPACK compile-time value", "") || ""`)
471
+ .join(`("TURBOPACK compile-time value", ${next}) || ""`);
472
+ }
473
+ const fromLocation =
474
+ "location ? (0, _createhreffromurl.createHrefFromUrl)(location) : initialCanonicalUrl";
475
+ if (out.includes(fromLocation)) {
476
+ out = out.split(fromLocation).join("initialCanonicalUrl");
477
+ }
478
+ const closeOnDomReady =
479
+ "if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', DOMContentLoaded, false);\n} else {\n // Delayed in marco task to ensure it's executed later than hydration\n setTimeout(DOMContentLoaded);\n}";
480
+ const closeOnLoad =
481
+ "if (document.readyState === 'complete') {\n setTimeout(DOMContentLoaded);\n} else {\n window.addEventListener('load', DOMContentLoaded, false);\n}";
482
+ if (out.includes(closeOnDomReady)) {
483
+ out = out.split(closeOnDomReady).join(closeOnLoad);
484
+ }
485
+ return out;
486
+ }
487
+
488
+ function contentMime(contentType) {
489
+ return contentType.toLowerCase().split(";")[0]?.trim() || "";
490
+ }
491
+
492
+ function prefixRootPaths(body, publicBase, contentType = "") {
493
+ const pathPrefix = proxyPathPrefix(publicBase);
494
+ if (!pathPrefix) return body;
495
+ const mime = contentMime(contentType);
496
+ const isCode =
497
+ /javascript|ecmascript|json|x-component|x-ref|\brsc\b/.test(mime) ||
498
+ mime === "text/plain";
499
+ const html =
500
+ !isCode &&
501
+ (mime === "text/html" ||
502
+ mime === "application/xhtml+xml" ||
503
+ (!mime && /<!doctype html|<html[\s>]/i.test(body)));
504
+ const css = mime === "text/css" || (!isCode && !html && /css/.test(mime));
505
+ const viteAndNext = ["/_next/", "/__nextjs_", ...viteAssetRoots(body)];
506
+ if (!html && !css) {
507
+ let code = rewriteViteHmrClient(body, pathPrefix);
508
+ code = prefixQuotedRoots(code, pathPrefix, viteAndNext);
509
+ code = code.replace(
510
+ /(?<![A-Za-z0-9])\/__nextjs_/g,
511
+ `${pathPrefix}/__nextjs_`
512
+ );
513
+ return rewriteNextTurbopackBasePath(code, pathPrefix);
514
+ }
515
+
516
+ let out = body;
517
+ if (
518
+ html &&
519
+ /<head[\s>]/i.test(out) &&
520
+ !/<base\s/i.test(out) &&
521
+ !isNextDocument(out)
522
+ ) {
523
+ out = out.replace(
524
+ /<head([^>]*)>/i,
525
+ `<head$1><base href="${pathPrefix}/">`
526
+ );
527
+ }
528
+ if (html) {
529
+ out = out.replace(
530
+ /(\s(?:src|href|action|poster)=["'])(\/(?!\/)[^"']*)/gi,
531
+ (full, start, path) =>
532
+ alreadyPrefixed(path, pathPrefix) ? full : `${start}${pathPrefix}${path}`
533
+ );
534
+ out = out.replace(/\s+crossorigin(?:\s*=\s*(["']).*?\1)?/gi, "");
535
+ }
536
+ if (isNextDocument(out)) {
537
+ out = rewriteNextFlightCanonical(out, pathPrefix);
538
+ out = prefixQuotedRoots(out, pathPrefix, ["/_next/", "/__nextjs_"]);
539
+ out = out.replace(
540
+ /(url\(\s*['"]?)(\/(?!\/)[^)"']*)/gi,
541
+ (full, start, path) => {
542
+ if (alreadyPrefixed(path, pathPrefix)) return full;
543
+ if (!path.startsWith("/_next/") && !path.startsWith("/__nextjs_")) {
544
+ return full;
545
+ }
546
+ return `${start}${pathPrefix}${path}`;
547
+ }
548
+ );
549
+ } else {
550
+ out = out.replace(
551
+ /(url\(\s*['"]?)(\/(?!\/)[^)"']*)/gi,
552
+ (full, start, path) =>
553
+ alreadyPrefixed(path, pathPrefix) ? full : `${start}${pathPrefix}${path}`
554
+ );
555
+ for (const root of viteAndNext) {
556
+ const escaped = root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
557
+ out = out.replace(
558
+ new RegExp(`(?<![A-Za-z0-9])${escaped}`, "g"),
559
+ `${pathPrefix}${root}`
560
+ );
561
+ }
562
+ }
563
+ return out;
564
+ }
565
+
566
+ function httpToWsOrigin(httpUrl) {
567
+ const trimmed = httpUrl.replace(/\/$/, "");
568
+ if (trimmed.startsWith("https://")) {
569
+ return `wss://${trimmed.slice("https://".length)}`;
570
+ }
571
+ if (trimmed.startsWith("http://")) {
572
+ return `ws://${trimmed.slice("http://".length)}`;
573
+ }
574
+ return trimmed;
575
+ }
576
+
577
+ function isHtmlDocument(headers, body) {
578
+ const ct = headerGet(headers, "content-type").toLowerCase();
579
+ if (ct && !/html/.test(ct) && !/text\/plain/.test(ct)) return false;
580
+ return /<!doctype html|<html[\s>]|<body[\s>]|<\/body>|<\/html>/i.test(body);
581
+ }
582
+
583
+ function isScriptRequestPath(path) {
584
+ const p = String(path || "").split("?")[0]?.toLowerCase() || "";
585
+ return /\.(?:m?js|cjs)$/.test(p);
586
+ }
587
+
588
+ function rewriteLocalSidecarUrls(html, aiPublicBase, uiPublicBase) {
589
+ const ai = String(aiPublicBase || "").replace(/\/$/, "");
590
+ const ui = String(uiPublicBase || "").replace(/\/$/, "");
591
+ let out = html.split("__AI_SERVER_URL__").join(ai);
592
+ if (ui && ui !== ai) {
593
+ out = out
594
+ .split(`${ui}/embed-config.js`)
595
+ .join(`${ai}/embed-config.js`)
596
+ .split(`${ui}/ai-ui.iife.js`)
597
+ .join(`${ai}/ai-ui.iife.js`);
598
+ }
599
+ out = out.replace(
600
+ /https?:\/\/(?:localhost|127\.0\.0\.1):\d+(?=\/(?:embed-config\.js|ai-ui\.iife\.js|api\/(?:chat|ws)))/gi,
601
+ ai
602
+ );
603
+ return out;
604
+ }
605
+
606
+ function rewriteEmbedConfigJs(body, publicBase) {
607
+ if (!/^\s*window\.__MAINTAINER_PRO__\s*=/.test(body)) return body;
608
+ const base = String(publicBase || "").replace(/\/$/, "");
609
+ const start = body.indexOf("{");
610
+ const end = body.lastIndexOf("}");
611
+ if (start < 0 || end <= start) return body;
612
+ try {
613
+ const payload = JSON.parse(body.slice(start, end + 1));
614
+ payload.aiServerUrl = base;
615
+ payload.apiUrl = `${base}/api/chat`;
616
+ payload.aiServerWsUrl = `${httpToWsOrigin(base)}/api/ws`;
617
+ try {
618
+ payload.maintainerProUrl = new URL(base).origin;
619
+ } catch {
620
+ /* keep */
621
+ }
622
+ return `window.__MAINTAINER_PRO__=${JSON.stringify(payload)};`;
623
+ } catch {
624
+ return body;
625
+ }
626
+ }
627
+
628
+ function htmlAlreadyHasWidget(html) {
629
+ return (
630
+ html.includes("data-mp-proxy-embed") ||
631
+ html.includes("AiUi.init") ||
632
+ html.includes("ai-ui.iife.js")
633
+ );
634
+ }
635
+
636
+ function injectMaintainerProEmbed(html, aiPublicBase) {
637
+ if (htmlAlreadyHasWidget(html)) return html;
638
+ const ai = String(aiPublicBase || "").replace(/\/$/, "");
639
+ const loader = `<script data-mp-proxy-embed>(function(ai,ws){if(!ai||window.__MP_EMBED_LOADING)return;window.__MP_EMBED_LOADING=1;function load(src){return new Promise(function(resolve,reject){var s=document.createElement("script");s.src=src;s.onload=function(){resolve()};s.onerror=function(){reject(new Error(src))};(document.head||document.documentElement).appendChild(s)})}function boot(){var cfg=window.__MAINTAINER_PRO__||{};if(!window.AiUi||!window.AiUi.init)return false;window.AiUi.init({apiUrl:ai+"/api/chat",aiServerWsUrl:ws,title:"AI Assistant",maintainerProUrl:cfg.maintainerProUrl||undefined,maintainerProApiKey:cfg.maintainerProApiKey||undefined});return true}load(ai+"/embed-config.js").then(function(){return load(ai+"/ai-ui.iife.js")}).then(function(){if(boot())return;var n=0,t=setInterval(function(){n+=1;if(boot()||n>50)clearInterval(t)},100)}).catch(function(){})})(${JSON.stringify(ai)},${JSON.stringify(`${httpToWsOrigin(ai)}/api/ws`)})<\/script>`;
640
+ if (/<head[^>]*>/i.test(html)) {
641
+ return html.replace(/<head([^>]*)>/i, `<head$1>${loader}`);
642
+ }
643
+ if (/<\/html>/i.test(html)) {
644
+ return html.replace(/<\/html>/i, `</html>${loader}`);
645
+ }
646
+ if (/<\/body>/i.test(html)) {
647
+ return html.replace(/<\/body>/i, `${loader}</body>`);
648
+ }
649
+ return html + loader;
650
+ }
651
+
652
+ function injectNextProxyShim(html, publicBase) {
653
+ const prefix = proxyPathPrefix(publicBase);
654
+ if (
655
+ !prefix ||
656
+ html.includes("data-mp-proxy-next") ||
657
+ (!isNextDocument(html) && !isViteDocument(html))
658
+ ) {
659
+ return html;
660
+ }
661
+ const p = JSON.stringify(prefix);
662
+ const script = `<script data-mp-proxy-next>(function(p){if(!p)return;function add(v){if(typeof v!=="string"||!v)return v;function collapse(path){var d=p+p;while(path.indexOf(d)===0)path=p+path.slice(d.length);return path}if(v.charAt(0)==="/"&&v.charAt(1)!=="/"){if(v===p||v.indexOf(p+"/")===0)return collapse(v);var root=p.slice(0,p.lastIndexOf("/"));if(root&&(v===root||v.indexOf(root+"/")===0))return v;if(v==="/api/v1"||v.indexOf("/api/v1/")===0)return v;return collapse(p+v)}try{var u=new URL(v,location.href);var host=String(u.hostname||"").toLowerCase();if(host.charAt(0)==="["&&host.charAt(host.length-1)==="]")host=host.slice(1,-1);var local=host==="localhost"||host==="127.0.0.1"||host==="::1"||host==="0.0.0.0";if(!local){if(u.pathname===p){u.pathname="/";return u.toString()}if(u.pathname.indexOf(p+"/")===0){u.pathname=u.pathname.slice(p.length)||"/";return u.toString()}return v}var samePort=String(u.port||"")===String(location.port||"");if(samePort){u.pathname=add(u.pathname);return u.toString()}if(u.pathname===p||u.pathname.indexOf(p+"/")===0){u.protocol=location.protocol;u.host=location.host;return u.toString()}u.pathname=add(u.pathname||"/");u.protocol=location.protocol;u.host=location.host;return u.toString()}catch(e){}return v}function mapSel(sel){if(typeof sel!=="string")return sel;var pairs=[["src=\\"/_next/","src=\\""+p+"/_next/"],["href=\\"/_next/","href=\\""+p+"/_next/"]];for(var i=0;i<pairs.length;i++){if(sel.indexOf(pairs[i][0])!==-1)return sel.split(pairs[i][0]).join(pairs[i][1])}return sel}function patchQS(proto,name){var orig=proto[name];proto[name]=function(sel){var r=orig.call(this,sel);if(name==="querySelector"){if(r)return r}else if(r.length)return r;var s2=mapSel(sel);return s2===sel?r:orig.call(this,s2)}}patchQS(Document.prototype,"querySelector");patchQS(Document.prototype,"querySelectorAll");patchQS(Element.prototype,"querySelector");patchQS(Element.prototype,"querySelectorAll");var sa=Element.prototype.setAttribute;Element.prototype.setAttribute=function(n,v){if((n==="src"||n==="href")&&typeof v==="string")v=add(v);return sa.call(this,n,v)};function patchUrlProp(ctor,prop){try{var d=Object.getOwnPropertyDescriptor(ctor.prototype,prop);if(!d||!d.set)return;Object.defineProperty(ctor.prototype,prop,{configurable:true,enumerable:true,get:d.get,set:function(v){d.set.call(this,add(v))}})}catch(e){}}patchUrlProp(HTMLScriptElement,"src");patchUrlProp(HTMLLinkElement,"href");var f=window.fetch;window.fetch=function(input,init){if(typeof input==="string")input=add(input);else if(typeof Request!=="undefined"&&input instanceof Request)input=new Request(add(input.url),input);else if(typeof URL!=="undefined"&&input instanceof URL)input=new URL(add(input.href));return f.call(this,input,init)};var xo=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(m,u){if(typeof u==="string")arguments[1]=add(u);return xo.apply(this,arguments)};var ps=history.pushState.bind(history);history.pushState=function(s,t,u){if(typeof u==="string")u=add(u);return ps(s,t,u)};var rs=history.replaceState.bind(history);history.replaceState=function(s,t,u){if(typeof u==="string")u=add(u);return rs(s,t,u)};var WS=window.WebSocket;function WrappedWS(url,protocols){if(typeof url==="string")url=add(url);return protocols!==undefined?new WS(url,protocols):new WS(url)}WrappedWS.prototype=WS.prototype;WrappedWS.CONNECTING=WS.CONNECTING;WrappedWS.OPEN=WS.OPEN;WrappedWS.CLOSING=WS.CLOSING;WrappedWS.CLOSED=WS.CLOSED;window.WebSocket=WrappedWS;try{var la=location.assign.bind(location);location.assign=function(u){return la(add(String(u)))};var lr=location.replace.bind(location);location.replace=function(u){return lr(add(String(u)))}}catch(e){}try{var hd=Object.getOwnPropertyDescriptor(Location.prototype,"href");if(hd&&hd.set){Object.defineProperty(location,"href",{configurable:true,enumerable:true,get:function(){return hd.get.call(location)},set:function(v){hd.set.call(location,add(String(v)))}})}}catch(e){}})(${p})</script>`;
663
+ return html.replace(/<head([^>]*)>/i, `<head$1>${script}`);
664
+ }
665
+
666
+ /**
667
+ * Rewrite + gzip a local app response before it goes over the bridge WS.
668
+ *
669
+ * @param {{
670
+ * path?: string,
671
+ * slug?: string,
672
+ * publicBase?: string,
673
+ * aiPublicBase?: string,
674
+ * status?: number,
675
+ * headers?: Record<string, string>,
676
+ * body?: Buffer | string,
677
+ * acceptEncoding?: string,
678
+ * ifNoneMatch?: string,
679
+ * port?: number,
680
+ * }} opts
681
+ * @returns {{ status: number, headers: Record<string, string>, body: Buffer }}
682
+ */
683
+ export function processShareHttpResponse(opts) {
684
+ const path = String(opts?.path || "/");
685
+ const slug = String(opts?.slug || "");
686
+ const publicBase = String(opts?.publicBase || "");
687
+ const aiPublicBase = String(opts?.aiPublicBase || "");
688
+ const acceptEncoding = String(opts?.acceptEncoding || "");
689
+ const ifNoneMatch = String(opts?.ifNoneMatch || "");
690
+ const port = Number(opts?.port) || 0;
691
+ let status =
692
+ typeof opts?.status === "number" && opts.status >= 100 ? opts.status : 200;
693
+ const headers = lowerHeaders(opts?.headers || {});
694
+ let payload = Buffer.isBuffer(opts?.body)
695
+ ? opts.body
696
+ : Buffer.from(opts?.body || "");
697
+
698
+ if (headers.location) {
699
+ headers.location = rewriteLocation(headers.location, publicBase, port);
700
+ }
701
+ if (headers.link) {
702
+ headers.link = rewriteLinkHeader(headers.link, publicBase);
703
+ }
704
+
705
+ const cachedKey = rewriteCacheKey(publicBase, path);
706
+ const hit =
707
+ cachedKey && Date.now() - (rewriteCache.get(cachedKey)?.at || 0) < REWRITE_CACHE_MS
708
+ ? rewriteCache.get(cachedKey)
709
+ : null;
710
+ if (hit) {
711
+ return finishCachedShareResponse(hit, path, ifNoneMatch, acceptEncoding);
712
+ }
713
+
714
+ if (isScriptRequestPath(path) && isHtmlDocument(headers, payload.toString("utf8"))) {
715
+ const notFound = {
716
+ "content-type": "text/plain; charset=utf-8",
717
+ "cache-control": "private, no-store",
718
+ };
719
+ return {
720
+ status: 404,
721
+ headers: notFound,
722
+ body: Buffer.from("Not found"),
723
+ };
724
+ }
725
+
726
+ let text = payload.toString("utf8");
727
+ let mutated = false;
728
+ if (publicBase && shouldRewriteBody(headers)) {
729
+ text = prefixRootPaths(text, publicBase, headers["content-type"] || "");
730
+ mutated = true;
731
+ }
732
+ if (aiPublicBase) {
733
+ const withShareUrls = rewriteLocalSidecarUrls(text, aiPublicBase, publicBase);
734
+ if (withShareUrls !== text) {
735
+ text = withShareUrls;
736
+ mutated = true;
737
+ }
738
+ }
739
+ if (aiPublicBase && /^\s*window\.__MAINTAINER_PRO__\s*=/.test(text)) {
740
+ const next = rewriteEmbedConfigJs(text, aiPublicBase);
741
+ if (next !== text) {
742
+ text = next;
743
+ mutated = true;
744
+ }
745
+ }
746
+ if (
747
+ publicBase &&
748
+ isHtmlDocument(headers, text) &&
749
+ (isNextDocument(text) || isViteDocument(text))
750
+ ) {
751
+ const next = injectNextProxyShim(text, publicBase);
752
+ if (next !== text) {
753
+ text = next;
754
+ mutated = true;
755
+ }
756
+ }
757
+ if (slug !== "ai" && aiPublicBase && isHtmlDocument(headers, text)) {
758
+ const next = injectMaintainerProEmbed(text, aiPublicBase);
759
+ if (next !== text) {
760
+ text = next;
761
+ mutated = true;
762
+ }
763
+ }
764
+
765
+ if (mutated) {
766
+ payload = Buffer.from(text, "utf8");
767
+ delete headers["content-encoding"];
768
+ }
769
+ delete headers.etag;
770
+ delete headers["last-modified"];
771
+ const mode = applyShareCacheHeaders(headers, path);
772
+ const etag = etagFor(payload);
773
+ if (mode !== "no-store") headers.etag = etag;
774
+ if (cachedKey) rememberRewrite(cachedKey, payload, status, headers, etag);
775
+ if (mode !== "no-store" && etagMatches(ifNoneMatch, etag)) {
776
+ return notModifiedResult(headers, etag);
777
+ }
778
+ payload = gzipIfRequested(payload, headers, acceptEncoding);
779
+ headers["content-length"] = String(payload.length);
780
+ return { status, headers, body: payload };
781
+ }