@maintainer-pro/ai-bridge 0.1.23 → 0.1.25

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.23",
3
+ "version": "0.1.25",
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",
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Cookie jar for Bypass CORS. The browser talks to /p/{token}/__cors
3
+ * (admin origin), so it never sends the remote API's cookies. The bridge
4
+ * stores Set-Cookie from those hosts and attaches them on the next hop.
5
+ */
6
+ import fs from "node:fs";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+
10
+ const MAX_COOKIES_PER_SANDBOX = 80;
11
+ const FILE_NAME = "cors-cookies.json";
12
+
13
+ /** @type {Map<string, CookieRecord[]>} */
14
+ const jars = new Map();
15
+
16
+ /**
17
+ * @typedef {{
18
+ * name: string,
19
+ * value: string,
20
+ * domain: string,
21
+ * path: string,
22
+ * expires: number | null,
23
+ * secure: boolean,
24
+ * hostOnly: boolean,
25
+ * }} CookieRecord
26
+ */
27
+
28
+ function homeDir() {
29
+ const override = String(process.env.MAINTAINER_PRO_HOME || "").trim();
30
+ return override || path.join(os.homedir(), ".maintainer-pro");
31
+ }
32
+
33
+ function sanitizeId(id) {
34
+ return String(id || "")
35
+ .trim()
36
+ .replace(/[^\w.-]+/g, "_")
37
+ .slice(0, 80);
38
+ }
39
+
40
+ function jarPath(sandboxId) {
41
+ const id = sanitizeId(sandboxId);
42
+ if (!id) return "";
43
+ return path.join(homeDir(), "projects", id, FILE_NAME);
44
+ }
45
+
46
+ function cookieKey(cookie) {
47
+ return `${cookie.name}\0${cookie.domain}\0${cookie.path}`;
48
+ }
49
+
50
+ function isExpired(cookie, now = Date.now()) {
51
+ return cookie.expires != null && cookie.expires <= now;
52
+ }
53
+
54
+ function hostMatches(hostname, cookie) {
55
+ const host = String(hostname || "").toLowerCase();
56
+ const domain = String(cookie.domain || "").toLowerCase();
57
+ if (!host || !domain) return false;
58
+ if (cookie.hostOnly) return host === domain;
59
+ return host === domain || host.endsWith(`.${domain}`);
60
+ }
61
+
62
+ function pathMatches(pathname, cookiePath) {
63
+ const pathName = pathname || "/";
64
+ const prefix = cookiePath || "/";
65
+ if (prefix === "/") return true;
66
+ if (pathName === prefix) return true;
67
+ const dir = prefix.endsWith("/") ? prefix : `${prefix}/`;
68
+ return pathName.startsWith(dir);
69
+ }
70
+
71
+ function domainAllowedForHost(host, domain) {
72
+ const h = String(host || "").toLowerCase();
73
+ const d = String(domain || "").replace(/^\./, "").toLowerCase();
74
+ if (!h || !d) return false;
75
+ return h === d || h.endsWith(`.${d}`);
76
+ }
77
+
78
+ function loadJar(sandboxId) {
79
+ const id = sanitizeId(sandboxId);
80
+ if (!id) return [];
81
+ if (jars.has(id)) return jars.get(id) || [];
82
+ let list = [];
83
+ const file = jarPath(id);
84
+ try {
85
+ if (file && fs.existsSync(file)) {
86
+ const raw = JSON.parse(fs.readFileSync(file, "utf8"));
87
+ if (Array.isArray(raw)) {
88
+ list = raw.filter((row) => row && typeof row.name === "string");
89
+ }
90
+ }
91
+ } catch {
92
+ list = [];
93
+ }
94
+ const now = Date.now();
95
+ list = list.filter((row) => !isExpired(row, now));
96
+ jars.set(id, list);
97
+ return list;
98
+ }
99
+
100
+ function saveJar(sandboxId, list) {
101
+ const id = sanitizeId(sandboxId);
102
+ if (!id) return;
103
+ const now = Date.now();
104
+ const next = list.filter((row) => !isExpired(row, now)).slice(-MAX_COOKIES_PER_SANDBOX);
105
+ jars.set(id, next);
106
+ const file = jarPath(id);
107
+ if (!file) return;
108
+ try {
109
+ fs.mkdirSync(path.dirname(file), { recursive: true });
110
+ fs.writeFileSync(file, `${JSON.stringify(next)}\n`, "utf8");
111
+ } catch {
112
+ /* ignore */
113
+ }
114
+ }
115
+
116
+ /**
117
+ * @param {string} raw
118
+ * @param {URL} requestUrl
119
+ * @returns {CookieRecord | null}
120
+ */
121
+ export function parseSetCookie(raw, requestUrl) {
122
+ const parts = String(raw || "")
123
+ .split(";")
124
+ .map((part) => part.trim())
125
+ .filter(Boolean);
126
+ if (!parts.length) return null;
127
+ const nv = parts[0];
128
+ const eq = nv.indexOf("=");
129
+ if (eq <= 0) return null;
130
+ const name = nv.slice(0, eq).trim();
131
+ const value = nv.slice(eq + 1).trim();
132
+ if (!name) return null;
133
+
134
+ const host = String(requestUrl.hostname || "").toLowerCase();
135
+ /** @type {CookieRecord} */
136
+ const cookie = {
137
+ name,
138
+ value,
139
+ domain: host,
140
+ path: "/",
141
+ expires: null,
142
+ secure: false,
143
+ hostOnly: true,
144
+ };
145
+
146
+ for (let i = 1; i < parts.length; i++) {
147
+ const part = parts[i];
148
+ const ieq = part.indexOf("=");
149
+ const key = (ieq >= 0 ? part.slice(0, ieq) : part).trim().toLowerCase();
150
+ const val = ieq >= 0 ? part.slice(ieq + 1).trim() : "";
151
+ if (key === "domain" && val) {
152
+ const domain = val.replace(/^\./, "").toLowerCase();
153
+ if (domainAllowedForHost(host, domain)) {
154
+ cookie.domain = domain;
155
+ cookie.hostOnly = false;
156
+ }
157
+ } else if (key === "path" && val.startsWith("/")) {
158
+ cookie.path = val;
159
+ } else if (key === "max-age") {
160
+ const n = Number(val);
161
+ if (Number.isFinite(n)) cookie.expires = Date.now() + n * 1000;
162
+ } else if (key === "expires") {
163
+ const t = Date.parse(val);
164
+ if (Number.isFinite(t)) cookie.expires = t;
165
+ } else if (key === "secure") {
166
+ cookie.secure = true;
167
+ }
168
+ }
169
+ return cookie;
170
+ }
171
+
172
+ function headerValues(headers, name) {
173
+ if (!headers || typeof headers !== "object") return [];
174
+ const lower = name.toLowerCase();
175
+ for (const [key, value] of Object.entries(headers)) {
176
+ if (String(key).toLowerCase() !== lower || value == null) continue;
177
+ return Array.isArray(value) ? value.map(String) : [String(value)];
178
+ }
179
+ return [];
180
+ }
181
+
182
+ /**
183
+ * @param {string} sandboxId
184
+ * @param {URL} requestUrl
185
+ * @param {Record<string, unknown>} headers
186
+ */
187
+ export function storeCorsCookies(sandboxId, requestUrl, headers) {
188
+ if (!sandboxId || !requestUrl) return;
189
+ const lines = headerValues(headers, "set-cookie");
190
+ if (!lines.length) return;
191
+ const list = loadJar(sandboxId);
192
+ const byKey = new Map(list.map((row) => [cookieKey(row), row]));
193
+ for (const line of lines) {
194
+ const cookie = parseSetCookie(line, requestUrl);
195
+ if (!cookie) continue;
196
+ const key = cookieKey(cookie);
197
+ if (!cookie.value || isExpired(cookie)) {
198
+ byKey.delete(key);
199
+ continue;
200
+ }
201
+ byKey.set(key, cookie);
202
+ }
203
+ saveJar(sandboxId, [...byKey.values()]);
204
+ }
205
+
206
+ /**
207
+ * @param {string} sandboxId
208
+ * @param {URL} requestUrl
209
+ */
210
+ export function cookieHeaderForUrl(sandboxId, requestUrl) {
211
+ if (!sandboxId || !requestUrl) return "";
212
+ const list = loadJar(sandboxId);
213
+ const now = Date.now();
214
+ const https = requestUrl.protocol === "https:";
215
+ const pathName = requestUrl.pathname || "/";
216
+ const matching = [];
217
+ const kept = [];
218
+ for (const cookie of list) {
219
+ if (isExpired(cookie, now)) continue;
220
+ kept.push(cookie);
221
+ if (cookie.secure && !https) continue;
222
+ if (!hostMatches(requestUrl.hostname, cookie)) continue;
223
+ if (!pathMatches(pathName, cookie.path)) continue;
224
+ matching.push(cookie);
225
+ }
226
+ if (kept.length !== list.length) saveJar(sandboxId, kept);
227
+ matching.sort((a, b) => String(b.path).length - String(a.path).length);
228
+ return matching.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
229
+ }
230
+
231
+ /**
232
+ * Parse a Cookie request header into name → value.
233
+ * @param {string} raw
234
+ */
235
+ export function parseCookieHeader(raw) {
236
+ /** @type {Map<string, string>} */
237
+ const out = new Map();
238
+ for (const part of String(raw || "").split(";")) {
239
+ const eq = part.indexOf("=");
240
+ if (eq <= 0) continue;
241
+ const name = part.slice(0, eq).trim();
242
+ const value = part.slice(eq + 1).trim();
243
+ if (name) out.set(name, value);
244
+ }
245
+ return out;
246
+ }
247
+
248
+ /**
249
+ * Merge browser cookies (Maintainer Pro share origin, e.g. mp_session)
250
+ * with jar cookies for the remote host. `mp_*` always come from the
251
+ * browser. Other names: jar wins, then any leftover browser cookies.
252
+ * @param {string} incoming
253
+ * @param {string} jar
254
+ */
255
+ export function mergeCookieHeader(incoming, jar) {
256
+ const browser = parseCookieHeader(incoming);
257
+ const stored = parseCookieHeader(jar);
258
+ /** @type {Map<string, string>} */
259
+ const merged = new Map();
260
+ for (const [name, value] of browser) {
261
+ if (name.toLowerCase().startsWith("mp_")) merged.set(name, value);
262
+ }
263
+ for (const [name, value] of stored) {
264
+ if (name.toLowerCase().startsWith("mp_") && merged.has(name)) continue;
265
+ merged.set(name, value);
266
+ }
267
+ for (const [name, value] of browser) {
268
+ if (merged.has(name)) continue;
269
+ merged.set(name, value);
270
+ }
271
+ return [...merged.entries()]
272
+ .map(([name, value]) => `${name}=${value}`)
273
+ .join("; ");
274
+ }
package/src/daemon.mjs CHANGED
@@ -38,6 +38,11 @@ import {
38
38
  leftoverStateKeys,
39
39
  LEGACY_TUNNEL_FILE,
40
40
  } from "./discarded-tunnels.mjs";
41
+ import {
42
+ cookieHeaderForUrl,
43
+ mergeCookieHeader,
44
+ storeCorsCookies,
45
+ } from "./cors-cookies.mjs";
41
46
 
42
47
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
43
48
  const requireFromHere = createRequire(import.meta.url);
@@ -2309,6 +2314,7 @@ function shareProxyContext(msg, ws, appId) {
2309
2314
  acceptEncoding,
2310
2315
  port: localPortForProxy(ws, appId),
2311
2316
  portUrls: { ...fromWs, ...fromMsg },
2317
+ bypassCors: msg?.bypassCors === true,
2312
2318
  };
2313
2319
  }
2314
2320
 
@@ -2342,9 +2348,13 @@ function replyShareInterceptor(id, ctx, path) {
2342
2348
  let body = "";
2343
2349
  if (pathname === "/__mp/sw.js") {
2344
2350
  headers["service-worker-allowed"] = tokenRoot;
2345
- body = shareServiceWorkerScript(ctx.portUrls, tokenRoot);
2351
+ body = shareServiceWorkerScript(ctx.portUrls, tokenRoot, ctx.bypassCors);
2346
2352
  } else {
2347
- body = shareShimScript(publicPathPrefix(ctx.publicBase), ctx.portUrls);
2353
+ body = shareShimScript(
2354
+ publicPathPrefix(ctx.publicBase),
2355
+ ctx.portUrls,
2356
+ ctx.bypassCors
2357
+ );
2348
2358
  }
2349
2359
  replyProxyHttp(id, 200, headers, body);
2350
2360
  return true;
@@ -2476,6 +2486,252 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
2476
2486
  });
2477
2487
  }
2478
2488
 
2489
+ const EXTERNAL_PROXY_TIMEOUT_MS = 10 * 60 * 1000;
2490
+ const EXTERNAL_PROXY_MAX_REDIRECTS = 5;
2491
+ const EXTERNAL_PROXY_DROP_HEADERS = new Set([
2492
+ "cookie",
2493
+ "set-cookie",
2494
+ "host",
2495
+ "origin",
2496
+ "referer",
2497
+ "referrer",
2498
+ "connection",
2499
+ "keep-alive",
2500
+ "transfer-encoding",
2501
+ "upgrade",
2502
+ "content-length",
2503
+ "te",
2504
+ "trailer",
2505
+ "x-mp-target",
2506
+ "x-forwarded-proto",
2507
+ "x-forwarded-host",
2508
+ "x-forwarded-for",
2509
+ ]);
2510
+
2511
+ function parseExternalProxyUrl(value) {
2512
+ let parsed;
2513
+ try {
2514
+ parsed = new URL(String(value || ""));
2515
+ } catch {
2516
+ return null;
2517
+ }
2518
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
2519
+ const parts = parsed.pathname.split("/").filter(Boolean);
2520
+ if (parts[0] === "p" && parts[2] === "__cors") return null;
2521
+ return parsed;
2522
+ }
2523
+
2524
+ function incomingCookieHeader(incoming) {
2525
+ if (!incoming || typeof incoming !== "object") return "";
2526
+ for (const [key, value] of Object.entries(incoming)) {
2527
+ if (String(key).toLowerCase() !== "cookie") continue;
2528
+ if (typeof value === "string" && value.trim()) return value;
2529
+ }
2530
+ return "";
2531
+ }
2532
+
2533
+ function externalProxyHeaders(incoming, targetHost) {
2534
+ /** @type {Record<string, string>} */
2535
+ const headers = {};
2536
+ if (incoming && typeof incoming === "object") {
2537
+ for (const [key, value] of Object.entries(incoming)) {
2538
+ const lower = String(key).toLowerCase();
2539
+ if (EXTERNAL_PROXY_DROP_HEADERS.has(lower)) continue;
2540
+ if (lower.startsWith("sec-fetch-")) continue;
2541
+ if (typeof value === "string" && value) headers[key] = value;
2542
+ }
2543
+ }
2544
+ headers.host = targetHost;
2545
+ return headers;
2546
+ }
2547
+
2548
+ function failExternalProxyHttp(id, error) {
2549
+ proxyHttpReqs.delete(id);
2550
+ bridgeSend({
2551
+ type: "proxy.http.error",
2552
+ id,
2553
+ error: error instanceof Error ? error.message : String(error || "Upstream error"),
2554
+ });
2555
+ }
2556
+
2557
+ function streamExternalProxyHttp(id, res) {
2558
+ const stream = randomBytes(8).toString("hex");
2559
+ /** @type {Record<string, string>} */
2560
+ const outHeaders = {};
2561
+ for (const [key, value] of Object.entries(res.headers || {})) {
2562
+ if (value == null) continue;
2563
+ const lower = String(key).toLowerCase();
2564
+ if (lower === "set-cookie" || lower === "cookie") continue;
2565
+ outHeaders[key] = Array.isArray(value) ? value.join(", ") : String(value);
2566
+ }
2567
+ bridgeSend({
2568
+ type: "proxy.http.start",
2569
+ id,
2570
+ stream,
2571
+ status: res.statusCode || 502,
2572
+ headers: outHeaders,
2573
+ });
2574
+ res.on("data", (chunk) => {
2575
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2576
+ for (let offset = 0; offset < buf.length; offset += PROXY_CHUNK_BYTES) {
2577
+ const end = Math.min(offset + PROXY_CHUNK_BYTES, buf.length);
2578
+ bridgeSend({
2579
+ type: "proxy.http.chunk",
2580
+ id,
2581
+ stream,
2582
+ data: Buffer.from(buf.subarray(offset, end)).toString("base64"),
2583
+ eof: false,
2584
+ });
2585
+ }
2586
+ });
2587
+ res.on("end", () => {
2588
+ proxyHttpReqs.delete(id);
2589
+ bridgeSend({
2590
+ type: "proxy.http.chunk",
2591
+ id,
2592
+ stream,
2593
+ data: "",
2594
+ eof: true,
2595
+ });
2596
+ });
2597
+ res.on("error", (err) => {
2598
+ failExternalProxyHttp(id, err);
2599
+ });
2600
+ }
2601
+
2602
+ function fetchExternalProxyUrl(entry, url, method, headers, body, redirectsLeft) {
2603
+ const parsed = parseExternalProxyUrl(url);
2604
+ if (!parsed) {
2605
+ failExternalProxyHttp(entry.id, "Invalid external URL");
2606
+ return;
2607
+ }
2608
+ const lib = parsed.protocol === "https:" ? https : http;
2609
+ /** @type {Record<string, string>} */
2610
+ const reqHeaders = { ...headers, host: parsed.host };
2611
+ const cookie = mergeCookieHeader(
2612
+ entry.incomingCookie || "",
2613
+ cookieHeaderForUrl(entry.sandboxId, parsed)
2614
+ );
2615
+ if (cookie) reqHeaders.cookie = cookie;
2616
+ else delete reqHeaders.cookie;
2617
+ const sendBody =
2618
+ body?.length && method !== "GET" && method !== "HEAD" ? body : null;
2619
+ if (sendBody) reqHeaders["content-length"] = String(sendBody.length);
2620
+ else delete reqHeaders["content-length"];
2621
+
2622
+ let req;
2623
+ try {
2624
+ req = lib.request(
2625
+ {
2626
+ protocol: parsed.protocol,
2627
+ hostname: parsed.hostname,
2628
+ port: parsed.port || undefined,
2629
+ path: `${parsed.pathname}${parsed.search}`,
2630
+ method,
2631
+ headers: reqHeaders,
2632
+ },
2633
+ (res) => {
2634
+ storeCorsCookies(entry.sandboxId, parsed, res.headers);
2635
+ const status = res.statusCode || 502;
2636
+ const loc = res.headers.location;
2637
+ if (
2638
+ loc &&
2639
+ status >= 300 &&
2640
+ status < 400 &&
2641
+ redirectsLeft > 0
2642
+ ) {
2643
+ res.resume();
2644
+ let next;
2645
+ try {
2646
+ next = new URL(loc, parsed).href;
2647
+ } catch {
2648
+ failExternalProxyHttp(entry.id, "Invalid redirect");
2649
+ return;
2650
+ }
2651
+ const preserve = status === 307 || status === 308;
2652
+ const nextMethod = preserve
2653
+ ? method
2654
+ : method === "HEAD"
2655
+ ? "HEAD"
2656
+ : "GET";
2657
+ const nextBody =
2658
+ preserve && nextMethod !== "GET" && nextMethod !== "HEAD"
2659
+ ? body
2660
+ : Buffer.alloc(0);
2661
+ fetchExternalProxyUrl(
2662
+ entry,
2663
+ next,
2664
+ nextMethod,
2665
+ headers,
2666
+ nextBody,
2667
+ redirectsLeft - 1
2668
+ );
2669
+ return;
2670
+ }
2671
+ streamExternalProxyHttp(entry.id, res);
2672
+ }
2673
+ );
2674
+ } catch (err) {
2675
+ failExternalProxyHttp(entry.id, err);
2676
+ return;
2677
+ }
2678
+ req.setTimeout(EXTERNAL_PROXY_TIMEOUT_MS, () => {
2679
+ req.destroy(new Error("Upstream timeout"));
2680
+ });
2681
+ req.on("error", (err) => {
2682
+ failExternalProxyHttp(entry.id, err);
2683
+ });
2684
+ entry.req = req;
2685
+ if (sendBody) req.write(sendBody);
2686
+ req.end();
2687
+ }
2688
+
2689
+ function startExternalProxyHttp(entry) {
2690
+ const body = Buffer.concat(entry.chunks || []);
2691
+ fetchExternalProxyUrl(
2692
+ entry,
2693
+ entry.url,
2694
+ entry.method,
2695
+ entry.headers,
2696
+ body,
2697
+ EXTERNAL_PROXY_MAX_REDIRECTS
2698
+ );
2699
+ }
2700
+
2701
+ function handleExternalProxyHttpFromAdmin(msg) {
2702
+ const id = typeof msg.id === "string" ? msg.id : "";
2703
+ const parsed = parseExternalProxyUrl(msg.externalUrl);
2704
+ if (!id) return;
2705
+ if (!parsed) {
2706
+ failExternalProxyHttp(id, "Invalid external URL");
2707
+ return;
2708
+ }
2709
+ const existing = proxyHttpReqs.get(id);
2710
+ if (existing) {
2711
+ destroyProxyHttpReq(existing);
2712
+ proxyHttpReqs.delete(id);
2713
+ }
2714
+ const method = String(msg.method || "GET").toUpperCase();
2715
+ const initialBody =
2716
+ typeof msg.body === "string" && msg.body
2717
+ ? Buffer.from(msg.body, "base64")
2718
+ : null;
2719
+ const entry = {
2720
+ req: null,
2721
+ external: true,
2722
+ id,
2723
+ sandboxId: typeof msg.sandboxId === "string" ? msg.sandboxId : "",
2724
+ incomingCookie: incomingCookieHeader(msg.headers),
2725
+ url: parsed.href,
2726
+ method,
2727
+ headers: externalProxyHeaders(msg.headers, parsed.host),
2728
+ chunks: [],
2729
+ };
2730
+ proxyHttpReqs.set(id, entry);
2731
+ if (initialBody?.length) entry.chunks.push(initialBody);
2732
+ if (msg.bodyEof !== false) startExternalProxyHttp(entry);
2733
+ }
2734
+
2479
2735
  function handleProxyHttpFromAdmin(msg) {
2480
2736
  const id = typeof msg.id === "string" ? msg.id : "";
2481
2737
  const sandboxId = typeof msg.sandboxId === "string" ? msg.sandboxId : "";
@@ -2490,6 +2746,10 @@ function handleProxyHttpFromAdmin(msg) {
2490
2746
  });
2491
2747
  return;
2492
2748
  }
2749
+ if (typeof msg.externalUrl === "string" && msg.externalUrl.trim()) {
2750
+ handleExternalProxyHttpFromAdmin(msg);
2751
+ return;
2752
+ }
2493
2753
  if (isAiServerAppId(appId, ws)) {
2494
2754
  void handleAiProxyHttpFromAdmin(msg, ws);
2495
2755
  return;
@@ -2702,11 +2962,16 @@ function handleProxyHttpBodyFromAdmin(msg) {
2702
2962
  const id = typeof msg.id === "string" ? msg.id : "";
2703
2963
  const entry = proxyHttpReqs.get(id);
2704
2964
  if (!entry) return;
2705
- const req = entry.req || entry;
2706
2965
  const chunk =
2707
2966
  typeof msg.data === "string" && msg.data
2708
2967
  ? Buffer.from(msg.data, "base64")
2709
2968
  : null;
2969
+ if (entry.external) {
2970
+ if (chunk?.length) entry.chunks.push(chunk);
2971
+ if (msg.eof === true) startExternalProxyHttp(entry);
2972
+ return;
2973
+ }
2974
+ const req = entry.req || entry;
2710
2975
  if (entry.rewrite) {
2711
2976
  if (chunk?.length) entry.chunks.push(chunk);
2712
2977
  if (msg.eof === true) {
@@ -155,11 +155,33 @@ function isImmutableAssetPath(path) {
155
155
  return (
156
156
  /\/_next\/static\//.test(p) ||
157
157
  /\/__nextjs_font\//.test(p) ||
158
- /\/node_modules\/\.vite\//.test(p) ||
159
158
  /\.[0-9a-f]{8,}\.(?:js|css|woff2?)$/i.test(p)
160
159
  );
161
160
  }
162
161
 
162
+ function isViteDevSourcePath(path) {
163
+ const raw = String(path || "");
164
+ const p = raw.split("?")[0] || "";
165
+ if (/[?&]t=\d+/.test(raw)) return true;
166
+ if (/\/(?:@vite(?:\/|$)|@react-refresh|@fs\/|@id\/)/.test(p)) return true;
167
+ if (/\/node_modules\/\.vite\//.test(p)) return true;
168
+ if (/\/src\//.test(p)) return true;
169
+ if (
170
+ /\.(?:m?[jt]sx?|vue|svelte|css|scss|sass|less)$/i.test(p) &&
171
+ !/\/node_modules\//.test(p)
172
+ ) {
173
+ return true;
174
+ }
175
+ return false;
176
+ }
177
+
178
+ function isDocumentPath(path) {
179
+ const p = String(path || "").split("?")[0] || "";
180
+ if (!p || p === "/") return true;
181
+ if (/\.html?$/i.test(p)) return true;
182
+ return !/\.[A-Za-z0-9]+$/.test(p);
183
+ }
184
+
163
185
  function isNoStorePath(path) {
164
186
  const p = String(path || "").split("?")[0] || "";
165
187
  return (
@@ -169,11 +191,9 @@ function isNoStorePath(path) {
169
191
  /\/_next\/webpack-hmr/i.test(p) ||
170
192
  /\/_next\/webpack\/hmr/i.test(p) ||
171
193
  /\/__nextjs_original-stack-frames/i.test(p) ||
172
- /\/@vite(?:\/|$)/.test(p) ||
173
- /\/@react-refresh/.test(p) ||
174
- /\/@fs\//.test(p) ||
175
- /\/@id\//.test(p) ||
176
- /\/__mp\//.test(p)
194
+ /\/__mp\//.test(p) ||
195
+ isViteDevSourcePath(path) ||
196
+ isDocumentPath(path)
177
197
  );
178
198
  }
179
199
 
@@ -396,12 +416,12 @@ export function shareTokenRoot(publicBase) {
396
416
  return prefix.endsWith("/") ? prefix : `${prefix}/`;
397
417
  }
398
418
 
399
- export function shareShimScript(pathPrefix, portUrls) {
400
- return `(${shareProxyShim.toString()})(${JSON.stringify(pathPrefix || "")},${JSON.stringify(portUrls || {})},${rewriteMappedLocalUrls.toString()});`;
419
+ export function shareShimScript(pathPrefix, portUrls, bypassCors) {
420
+ return `(${shareProxyShim.toString()})(${JSON.stringify(pathPrefix || "")},${JSON.stringify(portUrls || {})},${rewriteMappedLocalUrls.toString()},${JSON.stringify(Boolean(bypassCors))});`;
401
421
  }
402
422
 
403
- export function shareServiceWorkerScript(portUrls, tokenRoot) {
404
- return `(${shareServiceWorkerMain.toString()})(${JSON.stringify(portUrls || {})},${JSON.stringify(tokenRoot || "/")},${rewriteMappedLocalUrls.toString()});`;
423
+ export function shareServiceWorkerScript(portUrls, tokenRoot, bypassCors) {
424
+ return `(${shareServiceWorkerMain.toString()})(${JSON.stringify(portUrls || {})},${JSON.stringify(tokenRoot || "/")},${rewriteMappedLocalUrls.toString()},${JSON.stringify(Boolean(bypassCors))});`;
405
425
  }
406
426
 
407
427
  function rewriteHeaderUrls(headers, publicBase, port, portUrls) {
@@ -596,7 +616,7 @@ function gzipIfRequested(payload, headers, acceptEncoding) {
596
616
 
597
617
  function rewriteCacheKey(publicBase, path) {
598
618
  if (!isImmutableAssetPath(path) && !isLongCacheScriptPath(path)) return null;
599
- return `${publicBase || ""}\0${String(path || "").split("?")[0]}`;
619
+ return `${publicBase || ""}\0${String(path || "")}`;
600
620
  }
601
621
 
602
622
  function rememberRewrite(key, payload, status, headers, etag) {
@@ -1003,10 +1023,11 @@ function injectMaintainerProEmbed(html, aiPublicBase) {
1003
1023
  /**
1004
1024
  * Intercept every browser request and map listed app ports onto share URLs.
1005
1025
  */
1006
- function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
1026
+ function shareProxyShim(p, portMap, rewriteMappedLocalUrls, bypassCors) {
1007
1027
  if (!p || window.__MP_SHARE_SHIM__) return;
1008
1028
  window.__MP_SHARE_SHIM__ = 1;
1009
1029
  window.__MP_PORT_MAP__ = portMap || {};
1030
+ window.__MP_BYPASS_CORS__ = Boolean(bypassCors);
1010
1031
  function rewriteText(text) {
1011
1032
  if (typeof text !== "string" || !text || typeof rewriteMappedLocalUrls !== "function") {
1012
1033
  return text;
@@ -1108,6 +1129,38 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
1108
1129
  } catch (e) {}
1109
1130
  return v;
1110
1131
  }
1132
+ function isCorsProxyPath(pathname) {
1133
+ var parts = String(pathname || "").split("/").filter(Boolean);
1134
+ return parts[0] === "p" && parts[2] === "__cors";
1135
+ }
1136
+ function shareToken() {
1137
+ try {
1138
+ var parts = location.pathname.split("/").filter(Boolean);
1139
+ if (parts[0] === "p" && parts[1]) return parts[1];
1140
+ } catch (e) {}
1141
+ return "";
1142
+ }
1143
+ function corsProxy(v) {
1144
+ if (!bypassCors || typeof v !== "string" || !v) return v;
1145
+ if (!/^(https?:)\/\//i.test(v)) return v;
1146
+ try {
1147
+ var u = new URL(v);
1148
+ if (u.protocol !== "http:" && u.protocol !== "https:") return v;
1149
+ if (loopback(u.hostname)) return v;
1150
+ if (u.origin === location.origin) return v;
1151
+ if (isCorsProxyPath(u.pathname)) return v;
1152
+ var token = shareToken();
1153
+ if (!token) return v;
1154
+ return (
1155
+ location.origin +
1156
+ "/p/" +
1157
+ token +
1158
+ "/__cors?__mp_url=" +
1159
+ encodeURIComponent(u.href)
1160
+ );
1161
+ } catch (e) {}
1162
+ return v;
1163
+ }
1111
1164
  function addNet(v) {
1112
1165
  if (typeof v !== "string" || !v) return v;
1113
1166
  var mapped = mapLocal(v);
@@ -1115,7 +1168,10 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
1115
1168
  if (/^(https?:|wss?:)\/\//i.test(v)) {
1116
1169
  try {
1117
1170
  var abs = new URL(v);
1118
- if (!loopback(abs.hostname)) return v;
1171
+ if (!loopback(abs.hostname)) {
1172
+ if (/^wss?:/i.test(abs.protocol)) return v;
1173
+ return corsProxy(v);
1174
+ }
1119
1175
  } catch (e) {}
1120
1176
  }
1121
1177
  if (v.charAt(0) === "/" && v.charAt(1) !== "/") {
@@ -1299,17 +1355,27 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
1299
1355
  try {
1300
1356
  var tokenRoot = p.replace(/\/[^/]+$/, "/");
1301
1357
  if (navigator.serviceWorker && tokenRoot.indexOf("/p/") === 0) {
1302
- navigator.serviceWorker.register(p + "/__mp/sw.js", { scope: tokenRoot });
1358
+ navigator.serviceWorker.register(p + "/__mp/sw.js", {
1359
+ scope: tokenRoot,
1360
+ updateViaCache: "none",
1361
+ });
1303
1362
  }
1304
1363
  } catch (e) {}
1305
1364
  }
1306
1365
 
1307
- function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
1366
+ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls, bypassCors) {
1308
1367
  self.addEventListener("install", function (event) {
1309
1368
  event.waitUntil(self.skipWaiting());
1310
1369
  });
1311
1370
  self.addEventListener("activate", function (event) {
1312
- event.waitUntil(self.clients.claim());
1371
+ event.waitUntil(
1372
+ self.clients.claim().then(function () {
1373
+ if (!self.caches) return;
1374
+ return self.caches.keys().then(function (keys) {
1375
+ return Promise.all(keys.map(function (key) { return self.caches.delete(key); }));
1376
+ });
1377
+ })
1378
+ );
1313
1379
  });
1314
1380
  function rewriteText(text) {
1315
1381
  if (typeof text !== "string" || !text) return text;
@@ -1352,12 +1418,49 @@ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
1352
1418
  }
1353
1419
  return "";
1354
1420
  }
1421
+ function isCorsProxyPath(pathname) {
1422
+ var parts = String(pathname || "").split("/").filter(Boolean);
1423
+ return parts[0] === "p" && parts[2] === "__cors";
1424
+ }
1425
+ function shareToken() {
1426
+ try {
1427
+ var parts = String(self.registration && self.registration.scope || self.location.pathname)
1428
+ .split("/")
1429
+ .filter(Boolean);
1430
+ if (parts[0] === "p" && parts[1]) return parts[1];
1431
+ } catch (e) {}
1432
+ return "";
1433
+ }
1434
+ function corsProxy(v) {
1435
+ if (!bypassCors || typeof v !== "string" || !v) return v;
1436
+ if (!/^(https?:)\/\//i.test(v)) return v;
1437
+ try {
1438
+ var u = new URL(v, self.location.href);
1439
+ if (u.protocol !== "http:" && u.protocol !== "https:") return v;
1440
+ if (loopback(u.hostname)) return v;
1441
+ if (u.origin === self.location.origin) return v;
1442
+ if (isCorsProxyPath(u.pathname)) return v;
1443
+ var token = shareToken();
1444
+ if (!token) return v;
1445
+ return (
1446
+ self.location.origin +
1447
+ "/p/" +
1448
+ token +
1449
+ "/__cors?__mp_url=" +
1450
+ encodeURIComponent(u.href)
1451
+ );
1452
+ } catch (e) {}
1453
+ return v;
1454
+ }
1355
1455
  function prefixWith(url, prefix) {
1356
- if (!prefix) return url;
1456
+ if (!prefix) return corsProxy(url);
1357
1457
  try {
1358
1458
  if (/^(https?:|wss?:)\/\//i.test(url)) {
1359
1459
  var abs = new URL(url);
1360
- if (!loopback(abs.hostname)) return url;
1460
+ if (!loopback(abs.hostname)) {
1461
+ if (/^wss?:/i.test(abs.protocol)) return url;
1462
+ return corsProxy(url);
1463
+ }
1361
1464
  }
1362
1465
  var u = new URL(url, self.location.href);
1363
1466
  var path = u.pathname || "/";
@@ -1382,11 +1485,13 @@ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
1382
1485
  function mapUrl(v, prefix) {
1383
1486
  if (typeof v !== "string" || !v) return v;
1384
1487
  if (v.indexOf("/__mp/") !== -1) return v;
1488
+ if (v.indexOf("/__cors") !== -1) return v;
1385
1489
  return prefixWith(rewriteText(v), prefix);
1386
1490
  }
1387
1491
  self.addEventListener("fetch", function (event) {
1388
1492
  var req = event.request;
1389
1493
  if (req.url.indexOf("/__mp/") !== -1) return;
1494
+ if (req.url.indexOf("/__cors") !== -1) return;
1390
1495
  event.respondWith(
1391
1496
  (async function () {
1392
1497
  var client = null;
@@ -1417,6 +1522,7 @@ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
1417
1522
  credentials: req.credentials,
1418
1523
  redirect: req.redirect,
1419
1524
  referrer: req.referrer,
1525
+ cache: "no-store",
1420
1526
  };
1421
1527
  if (req.mode && req.mode !== "navigate") init.mode = req.mode;
1422
1528
  if (body !== undefined) init.body = body;