@maintainer-pro/ai-bridge 0.1.22 → 0.1.24
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 +2 -2
- package/src/daemon.mjs +303 -6
- package/src/share-rewrite.mjs +168 -74
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maintainer-pro/ai-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
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.
|
|
31
|
+
"@maintainer-pro/ai-cli": "^0.1.13",
|
|
32
32
|
"@maintainer-pro/ai-server": "^0.1.7"
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/daemon.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import os from "node:os";
|
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import readline from "node:readline";
|
|
18
18
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
19
|
+
import { createRequire } from "node:module";
|
|
19
20
|
import { createLogger, ensureProjectDataDir } from "@maintainer-pro/ai-cli";
|
|
20
21
|
import { findIife, startAiServer } from "@maintainer-pro/ai-server";
|
|
21
22
|
import {
|
|
@@ -39,7 +40,9 @@ import {
|
|
|
39
40
|
} from "./discarded-tunnels.mjs";
|
|
40
41
|
|
|
41
42
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
43
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
42
44
|
const PACKAGE_VERSION = readPackageVersion();
|
|
45
|
+
const PACKAGE_VERSIONS = readPackageVersions();
|
|
43
46
|
const HEARTBEAT_MS = 15_000;
|
|
44
47
|
const WS_PING_MS = 10_000;
|
|
45
48
|
const WS_RECONNECT_MIN_MS = 1_000;
|
|
@@ -157,6 +160,53 @@ function readPackageVersion() {
|
|
|
157
160
|
}
|
|
158
161
|
}
|
|
159
162
|
|
|
163
|
+
function readDepPackageVersion(name) {
|
|
164
|
+
const files = [];
|
|
165
|
+
try {
|
|
166
|
+
files.push(requireFromHere.resolve(`${name}/package.json`));
|
|
167
|
+
} catch {
|
|
168
|
+
/* try nested next */
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const serverDir = path.dirname(
|
|
172
|
+
requireFromHere.resolve("@maintainer-pro/ai-server/package.json")
|
|
173
|
+
);
|
|
174
|
+
files.push(path.join(serverDir, "node_modules", name, "package.json"));
|
|
175
|
+
files.push(path.join(serverDir, "..", name.replace("@maintainer-pro/", ""), "package.json"));
|
|
176
|
+
} catch {
|
|
177
|
+
/* ignore */
|
|
178
|
+
}
|
|
179
|
+
for (const file of files) {
|
|
180
|
+
try {
|
|
181
|
+
const pkg = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
182
|
+
if (typeof pkg.version === "string" && pkg.version) return pkg.version;
|
|
183
|
+
} catch {
|
|
184
|
+
/* try next */
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function readPackageVersions() {
|
|
191
|
+
return {
|
|
192
|
+
bridge: PACKAGE_VERSION,
|
|
193
|
+
cli: readDepPackageVersion("@maintainer-pro/ai-cli"),
|
|
194
|
+
server: readDepPackageVersion("@maintainer-pro/ai-server"),
|
|
195
|
+
ui: readDepPackageVersion("@maintainer-pro/ai-ui"),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function formatPackageVersions(versions = PACKAGE_VERSIONS) {
|
|
200
|
+
return [
|
|
201
|
+
versions.bridge && `bridge ${versions.bridge}`,
|
|
202
|
+
versions.cli && `cli ${versions.cli}`,
|
|
203
|
+
versions.server && `server ${versions.server}`,
|
|
204
|
+
versions.ui && `ui ${versions.ui}`,
|
|
205
|
+
]
|
|
206
|
+
.filter(Boolean)
|
|
207
|
+
.join(" · ");
|
|
208
|
+
}
|
|
209
|
+
|
|
160
210
|
async function warnIfBridgeOutdated() {
|
|
161
211
|
const fromWorkspace = path
|
|
162
212
|
.normalize(__dirname)
|
|
@@ -2259,6 +2309,7 @@ function shareProxyContext(msg, ws, appId) {
|
|
|
2259
2309
|
acceptEncoding,
|
|
2260
2310
|
port: localPortForProxy(ws, appId),
|
|
2261
2311
|
portUrls: { ...fromWs, ...fromMsg },
|
|
2312
|
+
bypassCors: msg?.bypassCors === true,
|
|
2262
2313
|
};
|
|
2263
2314
|
}
|
|
2264
2315
|
|
|
@@ -2292,9 +2343,13 @@ function replyShareInterceptor(id, ctx, path) {
|
|
|
2292
2343
|
let body = "";
|
|
2293
2344
|
if (pathname === "/__mp/sw.js") {
|
|
2294
2345
|
headers["service-worker-allowed"] = tokenRoot;
|
|
2295
|
-
body = shareServiceWorkerScript(ctx.portUrls, tokenRoot);
|
|
2346
|
+
body = shareServiceWorkerScript(ctx.portUrls, tokenRoot, ctx.bypassCors);
|
|
2296
2347
|
} else {
|
|
2297
|
-
body = shareShimScript(
|
|
2348
|
+
body = shareShimScript(
|
|
2349
|
+
publicPathPrefix(ctx.publicBase),
|
|
2350
|
+
ctx.portUrls,
|
|
2351
|
+
ctx.bypassCors
|
|
2352
|
+
);
|
|
2298
2353
|
}
|
|
2299
2354
|
replyProxyHttp(id, 200, headers, body);
|
|
2300
2355
|
return true;
|
|
@@ -2325,6 +2380,7 @@ function bridgeEmbedConfigJs(ws) {
|
|
|
2325
2380
|
logLevel: "debug",
|
|
2326
2381
|
maintainerProUrl: bridgeCfg?.adminUrl || "",
|
|
2327
2382
|
maintainerProApiKey: String(store.clientKey || "").trim(),
|
|
2383
|
+
versions: PACKAGE_VERSIONS,
|
|
2328
2384
|
};
|
|
2329
2385
|
return `window.__MAINTAINER_PRO__=${JSON.stringify(payload)};`;
|
|
2330
2386
|
}
|
|
@@ -2425,6 +2481,234 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
|
|
|
2425
2481
|
});
|
|
2426
2482
|
}
|
|
2427
2483
|
|
|
2484
|
+
const EXTERNAL_PROXY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
2485
|
+
const EXTERNAL_PROXY_MAX_REDIRECTS = 5;
|
|
2486
|
+
const EXTERNAL_PROXY_DROP_HEADERS = new Set([
|
|
2487
|
+
"cookie",
|
|
2488
|
+
"set-cookie",
|
|
2489
|
+
"host",
|
|
2490
|
+
"origin",
|
|
2491
|
+
"referer",
|
|
2492
|
+
"referrer",
|
|
2493
|
+
"connection",
|
|
2494
|
+
"keep-alive",
|
|
2495
|
+
"transfer-encoding",
|
|
2496
|
+
"upgrade",
|
|
2497
|
+
"content-length",
|
|
2498
|
+
"te",
|
|
2499
|
+
"trailer",
|
|
2500
|
+
"x-mp-target",
|
|
2501
|
+
"x-forwarded-proto",
|
|
2502
|
+
"x-forwarded-host",
|
|
2503
|
+
"x-forwarded-for",
|
|
2504
|
+
]);
|
|
2505
|
+
|
|
2506
|
+
function parseExternalProxyUrl(value) {
|
|
2507
|
+
let parsed;
|
|
2508
|
+
try {
|
|
2509
|
+
parsed = new URL(String(value || ""));
|
|
2510
|
+
} catch {
|
|
2511
|
+
return null;
|
|
2512
|
+
}
|
|
2513
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
2514
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
2515
|
+
if (parts[0] === "p" && parts[2] === "__cors") return null;
|
|
2516
|
+
return parsed;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
function externalProxyHeaders(incoming, targetHost) {
|
|
2520
|
+
/** @type {Record<string, string>} */
|
|
2521
|
+
const headers = {};
|
|
2522
|
+
if (incoming && typeof incoming === "object") {
|
|
2523
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
2524
|
+
const lower = String(key).toLowerCase();
|
|
2525
|
+
if (EXTERNAL_PROXY_DROP_HEADERS.has(lower)) continue;
|
|
2526
|
+
if (lower.startsWith("sec-fetch-")) continue;
|
|
2527
|
+
if (typeof value === "string" && value) headers[key] = value;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
headers.host = targetHost;
|
|
2531
|
+
return headers;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
function failExternalProxyHttp(id, error) {
|
|
2535
|
+
proxyHttpReqs.delete(id);
|
|
2536
|
+
bridgeSend({
|
|
2537
|
+
type: "proxy.http.error",
|
|
2538
|
+
id,
|
|
2539
|
+
error: error instanceof Error ? error.message : String(error || "Upstream error"),
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
function streamExternalProxyHttp(id, res) {
|
|
2544
|
+
const stream = randomBytes(8).toString("hex");
|
|
2545
|
+
/** @type {Record<string, string>} */
|
|
2546
|
+
const outHeaders = {};
|
|
2547
|
+
for (const [key, value] of Object.entries(res.headers || {})) {
|
|
2548
|
+
if (value == null) continue;
|
|
2549
|
+
const lower = String(key).toLowerCase();
|
|
2550
|
+
if (lower === "set-cookie" || lower === "cookie") continue;
|
|
2551
|
+
outHeaders[key] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
2552
|
+
}
|
|
2553
|
+
bridgeSend({
|
|
2554
|
+
type: "proxy.http.start",
|
|
2555
|
+
id,
|
|
2556
|
+
stream,
|
|
2557
|
+
status: res.statusCode || 502,
|
|
2558
|
+
headers: outHeaders,
|
|
2559
|
+
});
|
|
2560
|
+
res.on("data", (chunk) => {
|
|
2561
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2562
|
+
for (let offset = 0; offset < buf.length; offset += PROXY_CHUNK_BYTES) {
|
|
2563
|
+
const end = Math.min(offset + PROXY_CHUNK_BYTES, buf.length);
|
|
2564
|
+
bridgeSend({
|
|
2565
|
+
type: "proxy.http.chunk",
|
|
2566
|
+
id,
|
|
2567
|
+
stream,
|
|
2568
|
+
data: Buffer.from(buf.subarray(offset, end)).toString("base64"),
|
|
2569
|
+
eof: false,
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
});
|
|
2573
|
+
res.on("end", () => {
|
|
2574
|
+
proxyHttpReqs.delete(id);
|
|
2575
|
+
bridgeSend({
|
|
2576
|
+
type: "proxy.http.chunk",
|
|
2577
|
+
id,
|
|
2578
|
+
stream,
|
|
2579
|
+
data: "",
|
|
2580
|
+
eof: true,
|
|
2581
|
+
});
|
|
2582
|
+
});
|
|
2583
|
+
res.on("error", (err) => {
|
|
2584
|
+
failExternalProxyHttp(id, err);
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
function fetchExternalProxyUrl(entry, url, method, headers, body, redirectsLeft) {
|
|
2589
|
+
const parsed = parseExternalProxyUrl(url);
|
|
2590
|
+
if (!parsed) {
|
|
2591
|
+
failExternalProxyHttp(entry.id, "Invalid external URL");
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
const lib = parsed.protocol === "https:" ? https : http;
|
|
2595
|
+
/** @type {Record<string, string>} */
|
|
2596
|
+
const reqHeaders = { ...headers, host: parsed.host };
|
|
2597
|
+
const sendBody =
|
|
2598
|
+
body?.length && method !== "GET" && method !== "HEAD" ? body : null;
|
|
2599
|
+
if (sendBody) reqHeaders["content-length"] = String(sendBody.length);
|
|
2600
|
+
else delete reqHeaders["content-length"];
|
|
2601
|
+
|
|
2602
|
+
let req;
|
|
2603
|
+
try {
|
|
2604
|
+
req = lib.request(
|
|
2605
|
+
{
|
|
2606
|
+
protocol: parsed.protocol,
|
|
2607
|
+
hostname: parsed.hostname,
|
|
2608
|
+
port: parsed.port || undefined,
|
|
2609
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
2610
|
+
method,
|
|
2611
|
+
headers: reqHeaders,
|
|
2612
|
+
},
|
|
2613
|
+
(res) => {
|
|
2614
|
+
const status = res.statusCode || 502;
|
|
2615
|
+
const loc = res.headers.location;
|
|
2616
|
+
if (
|
|
2617
|
+
loc &&
|
|
2618
|
+
status >= 300 &&
|
|
2619
|
+
status < 400 &&
|
|
2620
|
+
redirectsLeft > 0
|
|
2621
|
+
) {
|
|
2622
|
+
res.resume();
|
|
2623
|
+
let next;
|
|
2624
|
+
try {
|
|
2625
|
+
next = new URL(loc, parsed).href;
|
|
2626
|
+
} catch {
|
|
2627
|
+
failExternalProxyHttp(entry.id, "Invalid redirect");
|
|
2628
|
+
return;
|
|
2629
|
+
}
|
|
2630
|
+
const preserve = status === 307 || status === 308;
|
|
2631
|
+
const nextMethod = preserve
|
|
2632
|
+
? method
|
|
2633
|
+
: method === "HEAD"
|
|
2634
|
+
? "HEAD"
|
|
2635
|
+
: "GET";
|
|
2636
|
+
const nextBody =
|
|
2637
|
+
preserve && nextMethod !== "GET" && nextMethod !== "HEAD"
|
|
2638
|
+
? body
|
|
2639
|
+
: Buffer.alloc(0);
|
|
2640
|
+
fetchExternalProxyUrl(
|
|
2641
|
+
entry,
|
|
2642
|
+
next,
|
|
2643
|
+
nextMethod,
|
|
2644
|
+
headers,
|
|
2645
|
+
nextBody,
|
|
2646
|
+
redirectsLeft - 1
|
|
2647
|
+
);
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
streamExternalProxyHttp(entry.id, res);
|
|
2651
|
+
}
|
|
2652
|
+
);
|
|
2653
|
+
} catch (err) {
|
|
2654
|
+
failExternalProxyHttp(entry.id, err);
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
req.setTimeout(EXTERNAL_PROXY_TIMEOUT_MS, () => {
|
|
2658
|
+
req.destroy(new Error("Upstream timeout"));
|
|
2659
|
+
});
|
|
2660
|
+
req.on("error", (err) => {
|
|
2661
|
+
failExternalProxyHttp(entry.id, err);
|
|
2662
|
+
});
|
|
2663
|
+
entry.req = req;
|
|
2664
|
+
if (sendBody) req.write(sendBody);
|
|
2665
|
+
req.end();
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
function startExternalProxyHttp(entry) {
|
|
2669
|
+
const body = Buffer.concat(entry.chunks || []);
|
|
2670
|
+
fetchExternalProxyUrl(
|
|
2671
|
+
entry,
|
|
2672
|
+
entry.url,
|
|
2673
|
+
entry.method,
|
|
2674
|
+
entry.headers,
|
|
2675
|
+
body,
|
|
2676
|
+
EXTERNAL_PROXY_MAX_REDIRECTS
|
|
2677
|
+
);
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
function handleExternalProxyHttpFromAdmin(msg) {
|
|
2681
|
+
const id = typeof msg.id === "string" ? msg.id : "";
|
|
2682
|
+
const parsed = parseExternalProxyUrl(msg.externalUrl);
|
|
2683
|
+
if (!id) return;
|
|
2684
|
+
if (!parsed) {
|
|
2685
|
+
failExternalProxyHttp(id, "Invalid external URL");
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
const existing = proxyHttpReqs.get(id);
|
|
2689
|
+
if (existing) {
|
|
2690
|
+
destroyProxyHttpReq(existing);
|
|
2691
|
+
proxyHttpReqs.delete(id);
|
|
2692
|
+
}
|
|
2693
|
+
const method = String(msg.method || "GET").toUpperCase();
|
|
2694
|
+
const initialBody =
|
|
2695
|
+
typeof msg.body === "string" && msg.body
|
|
2696
|
+
? Buffer.from(msg.body, "base64")
|
|
2697
|
+
: null;
|
|
2698
|
+
const entry = {
|
|
2699
|
+
req: null,
|
|
2700
|
+
external: true,
|
|
2701
|
+
id,
|
|
2702
|
+
url: parsed.href,
|
|
2703
|
+
method,
|
|
2704
|
+
headers: externalProxyHeaders(msg.headers, parsed.host),
|
|
2705
|
+
chunks: [],
|
|
2706
|
+
};
|
|
2707
|
+
proxyHttpReqs.set(id, entry);
|
|
2708
|
+
if (initialBody?.length) entry.chunks.push(initialBody);
|
|
2709
|
+
if (msg.bodyEof !== false) startExternalProxyHttp(entry);
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2428
2712
|
function handleProxyHttpFromAdmin(msg) {
|
|
2429
2713
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
2430
2714
|
const sandboxId = typeof msg.sandboxId === "string" ? msg.sandboxId : "";
|
|
@@ -2439,6 +2723,10 @@ function handleProxyHttpFromAdmin(msg) {
|
|
|
2439
2723
|
});
|
|
2440
2724
|
return;
|
|
2441
2725
|
}
|
|
2726
|
+
if (typeof msg.externalUrl === "string" && msg.externalUrl.trim()) {
|
|
2727
|
+
handleExternalProxyHttpFromAdmin(msg);
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2442
2730
|
if (isAiServerAppId(appId, ws)) {
|
|
2443
2731
|
void handleAiProxyHttpFromAdmin(msg, ws);
|
|
2444
2732
|
return;
|
|
@@ -2651,11 +2939,16 @@ function handleProxyHttpBodyFromAdmin(msg) {
|
|
|
2651
2939
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
2652
2940
|
const entry = proxyHttpReqs.get(id);
|
|
2653
2941
|
if (!entry) return;
|
|
2654
|
-
const req = entry.req || entry;
|
|
2655
2942
|
const chunk =
|
|
2656
2943
|
typeof msg.data === "string" && msg.data
|
|
2657
2944
|
? Buffer.from(msg.data, "base64")
|
|
2658
2945
|
: null;
|
|
2946
|
+
if (entry.external) {
|
|
2947
|
+
if (chunk?.length) entry.chunks.push(chunk);
|
|
2948
|
+
if (msg.eof === true) startExternalProxyHttp(entry);
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2951
|
+
const req = entry.req || entry;
|
|
2659
2952
|
if (entry.rewrite) {
|
|
2660
2953
|
if (chunk?.length) entry.chunks.push(chunk);
|
|
2661
2954
|
if (msg.eof === true) {
|
|
@@ -4834,6 +5127,7 @@ async function sendHeartbeat(cfg, folders, localStates) {
|
|
|
4834
5127
|
hostname: os.hostname(),
|
|
4835
5128
|
platform: `${os.platform()}-${os.arch()}`,
|
|
4836
5129
|
bridgeVersion: PACKAGE_VERSION,
|
|
5130
|
+
packageVersions: PACKAGE_VERSIONS,
|
|
4837
5131
|
folders,
|
|
4838
5132
|
issues: await buildIssues(cfg, localStates),
|
|
4839
5133
|
workspaces: localStates.map((st) => ({
|
|
@@ -4866,6 +5160,7 @@ function buildLightHeartbeatPayload(cfg, folders) {
|
|
|
4866
5160
|
hostname: os.hostname(),
|
|
4867
5161
|
platform: `${os.platform()}-${os.arch()}`,
|
|
4868
5162
|
bridgeVersion: PACKAGE_VERSION,
|
|
5163
|
+
packageVersions: PACKAGE_VERSIONS,
|
|
4869
5164
|
folders,
|
|
4870
5165
|
};
|
|
4871
5166
|
}
|
|
@@ -4887,6 +5182,7 @@ async function buildHeartbeatPayload(cfg, folders, localStates) {
|
|
|
4887
5182
|
hostname: os.hostname(),
|
|
4888
5183
|
platform: `${os.platform()}-${os.arch()}`,
|
|
4889
5184
|
bridgeVersion: PACKAGE_VERSION,
|
|
5185
|
+
packageVersions: PACKAGE_VERSIONS,
|
|
4890
5186
|
folders,
|
|
4891
5187
|
issues: await buildIssues(cfg, localStates),
|
|
4892
5188
|
workspaces: localStates.map((st) => ({
|
|
@@ -5021,6 +5317,7 @@ async function pairFlow(args) {
|
|
|
5021
5317
|
hostname: os.hostname(),
|
|
5022
5318
|
platform: `${os.platform()}-${os.arch()}`,
|
|
5023
5319
|
bridgeVersion: PACKAGE_VERSION,
|
|
5320
|
+
packageVersions: PACKAGE_VERSIONS,
|
|
5024
5321
|
name: os.hostname(),
|
|
5025
5322
|
});
|
|
5026
5323
|
|
|
@@ -5048,9 +5345,9 @@ async function main() {
|
|
|
5048
5345
|
process.exit(0);
|
|
5049
5346
|
}
|
|
5050
5347
|
|
|
5051
|
-
logger.
|
|
5052
|
-
{
|
|
5053
|
-
|
|
5348
|
+
logger.info(
|
|
5349
|
+
{ versions: PACKAGE_VERSIONS },
|
|
5350
|
+
`bridge v${PACKAGE_VERSION} starting (${formatPackageVersions()})`
|
|
5054
5351
|
);
|
|
5055
5352
|
void warnIfBridgeOutdated();
|
|
5056
5353
|
|
package/src/share-rewrite.mjs
CHANGED
|
@@ -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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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 || "")
|
|
619
|
+
return `${publicBase || ""}\0${String(path || "")}`;
|
|
600
620
|
}
|
|
601
621
|
|
|
602
622
|
function rememberRewrite(key, payload, status, headers, etag) {
|
|
@@ -698,6 +718,19 @@ function isShareAssetPath(path) {
|
|
|
698
718
|
);
|
|
699
719
|
}
|
|
700
720
|
|
|
721
|
+
function rewriteShareAsLocalEnv(body) {
|
|
722
|
+
let out = String(body);
|
|
723
|
+
out = out.replace(
|
|
724
|
+
/(\/\.\*localhost\.\*\/\.test\()([^)]+)(\))/g,
|
|
725
|
+
"($1$2$3||location.pathname.indexOf(\"/p/\")===0)"
|
|
726
|
+
);
|
|
727
|
+
out = out.replace(
|
|
728
|
+
/((?:window\.)?location\.hostname)\s*===\s*(['"])localhost\2/g,
|
|
729
|
+
"($1===$2localhost$2||location.pathname.indexOf(\"/p/\")===0)"
|
|
730
|
+
);
|
|
731
|
+
return out;
|
|
732
|
+
}
|
|
733
|
+
|
|
701
734
|
function prefixQuotedAssetPaths(body, pathPrefix) {
|
|
702
735
|
if (!pathPrefix) return body;
|
|
703
736
|
return String(body).replace(/(["'`])(\/(?!\/)[^"'`]*)/g, (full, q, path) => {
|
|
@@ -844,6 +877,7 @@ function prefixRootPaths(body, publicBase, contentType = "") {
|
|
|
844
877
|
const css = mime === "text/css" || (!isCode && !html && /css/.test(mime));
|
|
845
878
|
if (!html && !css) {
|
|
846
879
|
let code = rewriteViteBaseLiterals(body, pathPrefix);
|
|
880
|
+
code = rewriteShareAsLocalEnv(code);
|
|
847
881
|
code = prefixQuotedAssetPaths(code, pathPrefix);
|
|
848
882
|
code = code.replace(
|
|
849
883
|
/(?<![A-Za-z0-9])\/__nextjs_/g,
|
|
@@ -913,7 +947,7 @@ function isHtmlDocument(headers, body) {
|
|
|
913
947
|
|
|
914
948
|
function isScriptRequestPath(path) {
|
|
915
949
|
const p = String(path || "").split("?")[0]?.toLowerCase() || "";
|
|
916
|
-
return /\.(?:m?
|
|
950
|
+
return /\.(?:m?[jt]sx?|cjs)$/.test(p);
|
|
917
951
|
}
|
|
918
952
|
|
|
919
953
|
function isScriptOrJsonBody(headers, path) {
|
|
@@ -989,10 +1023,11 @@ function injectMaintainerProEmbed(html, aiPublicBase) {
|
|
|
989
1023
|
/**
|
|
990
1024
|
* Intercept every browser request and map listed app ports onto share URLs.
|
|
991
1025
|
*/
|
|
992
|
-
function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
|
|
1026
|
+
function shareProxyShim(p, portMap, rewriteMappedLocalUrls, bypassCors) {
|
|
993
1027
|
if (!p || window.__MP_SHARE_SHIM__) return;
|
|
994
1028
|
window.__MP_SHARE_SHIM__ = 1;
|
|
995
1029
|
window.__MP_PORT_MAP__ = portMap || {};
|
|
1030
|
+
window.__MP_BYPASS_CORS__ = Boolean(bypassCors);
|
|
996
1031
|
function rewriteText(text) {
|
|
997
1032
|
if (typeof text !== "string" || !text || typeof rewriteMappedLocalUrls !== "function") {
|
|
998
1033
|
return text;
|
|
@@ -1094,46 +1129,67 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
|
|
|
1094
1129
|
} catch (e) {}
|
|
1095
1130
|
return v;
|
|
1096
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
|
+
}
|
|
1097
1164
|
function addNet(v) {
|
|
1098
1165
|
if (typeof v !== "string" || !v) return v;
|
|
1099
1166
|
var mapped = mapLocal(v);
|
|
1100
1167
|
if (mapped !== v) return mapped;
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
if (/^(https?:|wss?:)\/\//i.test(v)) {
|
|
1108
|
-
abs = new URL(v);
|
|
1109
|
-
var host = String(abs.hostname || "")
|
|
1110
|
-
.replace(/^\[|\]$/g, "")
|
|
1111
|
-
.toLowerCase();
|
|
1112
|
-
if (!loopback(host) && host !== String(location.hostname || "").toLowerCase()) {
|
|
1113
|
-
return v;
|
|
1168
|
+
if (/^(https?:|wss?:)\/\//i.test(v)) {
|
|
1169
|
+
try {
|
|
1170
|
+
var abs = new URL(v);
|
|
1171
|
+
if (!loopback(abs.hostname)) {
|
|
1172
|
+
if (/^wss?:/i.test(abs.protocol)) return v;
|
|
1173
|
+
return corsProxy(v);
|
|
1114
1174
|
}
|
|
1115
|
-
|
|
1116
|
-
search = abs.search || "";
|
|
1117
|
-
hash = abs.hash || "";
|
|
1118
|
-
} else if (v.charAt(0) === "/" && v.charAt(1) !== "/") {
|
|
1119
|
-
var rel = new URL(v, location.href);
|
|
1120
|
-
path = rel.pathname || "/";
|
|
1121
|
-
search = rel.search || "";
|
|
1122
|
-
hash = rel.hash || "";
|
|
1123
|
-
} else {
|
|
1124
|
-
return addNav(v);
|
|
1125
|
-
}
|
|
1126
|
-
} catch (e) {
|
|
1127
|
-
return addNav(v);
|
|
1175
|
+
} catch (e) {}
|
|
1128
1176
|
}
|
|
1129
|
-
if (
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
if (
|
|
1136
|
-
|
|
1177
|
+
if (v.charAt(0) === "/" && v.charAt(1) !== "/") {
|
|
1178
|
+
var backend = backendBase();
|
|
1179
|
+
var path = v.split("?")[0];
|
|
1180
|
+
if (path === "/api/v1" || path.indexOf("/api/v1/") === 0 || path.indexOf("/p/") === 0) {
|
|
1181
|
+
return v;
|
|
1182
|
+
}
|
|
1183
|
+
if (
|
|
1184
|
+
backend &&
|
|
1185
|
+
!isAsset(path) &&
|
|
1186
|
+
(/^\/sreo(?:\/|$)/i.test(path) || /^\/api(?:\/|$)/i.test(path))
|
|
1187
|
+
) {
|
|
1188
|
+
try {
|
|
1189
|
+
var rel = new URL(v, location.href);
|
|
1190
|
+
return backend + rel.pathname + rel.search + rel.hash;
|
|
1191
|
+
} catch (e) {}
|
|
1192
|
+
}
|
|
1137
1193
|
}
|
|
1138
1194
|
return addNav(v);
|
|
1139
1195
|
}
|
|
@@ -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", {
|
|
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(
|
|
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,37 +1418,62 @@ 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
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
.replace(/^\[|\]$/g, "")
|
|
1365
|
-
.toLowerCase();
|
|
1366
|
-
if (/^(https?:|wss?:)\/\//i.test(url) && !loopback(host) && host !== pageHost) {
|
|
1367
|
-
return url;
|
|
1458
|
+
if (/^(https?:|wss?:)\/\//i.test(url)) {
|
|
1459
|
+
var abs = new URL(url);
|
|
1460
|
+
if (!loopback(abs.hostname)) {
|
|
1461
|
+
if (/^wss?:/i.test(abs.protocol)) return url;
|
|
1462
|
+
return corsProxy(url);
|
|
1463
|
+
}
|
|
1368
1464
|
}
|
|
1369
|
-
var
|
|
1465
|
+
var u = new URL(url, self.location.href);
|
|
1466
|
+
var path = u.pathname || "/";
|
|
1467
|
+
var backend = backendBase();
|
|
1370
1468
|
if (path === "/api/v1" || path.indexOf("/api/v1/") === 0) return url;
|
|
1371
1469
|
if (path.indexOf("/p/") === 0) return url;
|
|
1372
1470
|
if (
|
|
1373
1471
|
backend &&
|
|
1374
|
-
|
|
1375
|
-
path
|
|
1376
|
-
!/\/(?:_next\/|__nextjs_|@vite(?:\/|$)|@react-refresh(?:\/|$)|@fs\/|@id\/|node_modules\/)/.test(
|
|
1377
|
-
path
|
|
1378
|
-
) &&
|
|
1379
|
-
!/\.(?:m?[jt]sx?|cjs|mjs|css|scss|sass|less|map|wasm|vue|svelte)$/i.test(path)
|
|
1472
|
+
!/^(https?:|wss?:)\/\//i.test(url) &&
|
|
1473
|
+
(/^\/sreo(?:\/|$)/i.test(path) || /^\/api(?:\/|$)/i.test(path))
|
|
1380
1474
|
) {
|
|
1381
|
-
|
|
1382
|
-
if (/^ws/i.test(parsed.protocol)) dest = dest.replace(/^http/i, "ws");
|
|
1383
|
-
return dest;
|
|
1475
|
+
return backend + path + (u.search || "") + (u.hash || "");
|
|
1384
1476
|
}
|
|
1385
|
-
var u = parsed;
|
|
1386
1477
|
if (u.origin !== self.location.origin) return url;
|
|
1387
1478
|
if (u.pathname === "/" || u.pathname === "") return url;
|
|
1388
1479
|
if (u.pathname === prefix || u.pathname.indexOf(prefix + "/") === 0) return url;
|
|
@@ -1394,11 +1485,13 @@ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
|
|
|
1394
1485
|
function mapUrl(v, prefix) {
|
|
1395
1486
|
if (typeof v !== "string" || !v) return v;
|
|
1396
1487
|
if (v.indexOf("/__mp/") !== -1) return v;
|
|
1488
|
+
if (v.indexOf("/__cors") !== -1) return v;
|
|
1397
1489
|
return prefixWith(rewriteText(v), prefix);
|
|
1398
1490
|
}
|
|
1399
1491
|
self.addEventListener("fetch", function (event) {
|
|
1400
1492
|
var req = event.request;
|
|
1401
1493
|
if (req.url.indexOf("/__mp/") !== -1) return;
|
|
1494
|
+
if (req.url.indexOf("/__cors") !== -1) return;
|
|
1402
1495
|
event.respondWith(
|
|
1403
1496
|
(async function () {
|
|
1404
1497
|
var client = null;
|
|
@@ -1429,6 +1522,7 @@ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
|
|
|
1429
1522
|
credentials: req.credentials,
|
|
1430
1523
|
redirect: req.redirect,
|
|
1431
1524
|
referrer: req.referrer,
|
|
1525
|
+
cache: "no-store",
|
|
1432
1526
|
};
|
|
1433
1527
|
if (req.mode && req.mode !== "navigate") init.mode = req.mode;
|
|
1434
1528
|
if (body !== undefined) init.body = body;
|
|
@@ -1550,14 +1644,14 @@ export function processShareHttpResponse(opts) {
|
|
|
1550
1644
|
text = prefixRootPaths(text, publicBase, headers["content-type"] || "");
|
|
1551
1645
|
mutated = true;
|
|
1552
1646
|
}
|
|
1553
|
-
if (Object.keys(portUrls).length && rewriteTextBody) {
|
|
1647
|
+
if (Object.keys(portUrls).length && rewriteTextBody && !isScriptOrJsonBody(headers, path)) {
|
|
1554
1648
|
const mapped = rewriteMappedLocalUrls(text, portUrls);
|
|
1555
1649
|
if (mapped !== text) {
|
|
1556
1650
|
text = mapped;
|
|
1557
1651
|
mutated = true;
|
|
1558
1652
|
}
|
|
1559
1653
|
}
|
|
1560
|
-
if (publicBase && rewriteTextBody) {
|
|
1654
|
+
if (publicBase && rewriteTextBody && !isScriptOrJsonBody(headers, path)) {
|
|
1561
1655
|
const prefixed = rewriteShareOriginPaths(text, publicBase, portUrls);
|
|
1562
1656
|
if (prefixed !== text) {
|
|
1563
1657
|
text = prefixed;
|