@herbertgao/pi-extensions 2026.8.15 → 2026.9.0
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/README.md +4 -1
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +20 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +46 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +6 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +5 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +34 -30
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +5 -1
- package/node_modules/@narumitw/pi-btw/dist/index.ts +143 -14
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +4 -4
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +158 -7
- package/node_modules/@narumitw/pi-btw/src/text.ts +18 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +5 -16
- package/node_modules/@narumitw/pi-caffeinate/LICENSE +21 -0
- package/node_modules/@narumitw/pi-caffeinate/README.md +224 -0
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +1112 -0
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +7 -0
- package/node_modules/@narumitw/pi-caffeinate/package.json +53 -0
- package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +811 -0
- package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +153 -0
- package/node_modules/@narumitw/pi-caffeinate/src/index.ts +1 -0
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +43 -0
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +167 -0
- package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +202 -0
- package/node_modules/@tifan/pi-handoff/README.md +6 -3
- package/node_modules/@tifan/pi-handoff/package.json +1 -1
- package/node_modules/@tifan/pi-handoff/src/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +27 -0
- package/node_modules/pi-mcp-adapter/README.md +13 -13
- package/node_modules/pi-mcp-adapter/commands.ts +58 -21
- package/node_modules/pi-mcp-adapter/config.ts +18 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +26 -6
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +4 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +13 -4
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +11 -3
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/error-signal.ts +15 -4
- package/node_modules/pi-mcp-adapter/errors.ts +141 -0
- package/node_modules/pi-mcp-adapter/host-html-template.ts +58 -5
- package/node_modules/pi-mcp-adapter/index.ts +2 -3
- package/node_modules/pi-mcp-adapter/init.ts +5 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +30 -9
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +66 -28
- package/node_modules/pi-mcp-adapter/mcp-status.ts +2 -0
- package/node_modules/pi-mcp-adapter/package.json +2 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +66 -13
- package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +217 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +337 -6
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +1 -0
- package/node_modules/pi-mcp-adapter/types.ts +18 -3
- package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +18 -2
- package/node_modules/pi-mcp-adapter/ui-server.ts +179 -54
- package/node_modules/pi-mcp-adapter/ui-session.ts +28 -8
- package/package.json +11 -7
|
@@ -19,6 +19,7 @@ import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session
|
|
|
19
19
|
import { paginate, rankSuggestions, rankToolMatches, resolveSearchKeywords } from "./search-ranking.ts";
|
|
20
20
|
import { ensureToolCallApproved, isToolCallApprovalRequired } from "./tool-approval.ts";
|
|
21
21
|
import { isServerInActiveFailureBackoff } from "./failure-backoff.ts";
|
|
22
|
+
import { getInputRequiredNeedsUiDetails } from "./errors.ts";
|
|
22
23
|
|
|
23
24
|
type ProxyToolResult = AgentToolResult<Record<string, unknown>>;
|
|
24
25
|
type ClientCallToolResult = Awaited<ReturnType<Client["callTool"]>>;
|
|
@@ -339,7 +340,7 @@ export function executeUiMessages(state: McpExtensionState): ProxyToolResult {
|
|
|
339
340
|
}
|
|
340
341
|
|
|
341
342
|
export function executeStatus(state: McpExtensionState): ProxyToolResult {
|
|
342
|
-
const servers: Array<{ name: string; status: string; toolCount: number; failedAgo: number | null; disabled?: boolean }> = [];
|
|
343
|
+
const servers: Array<{ name: string; status: string; listenState: string; catalogStale?: boolean; toolCount: number; failedAgo: number | null; disabled?: boolean }> = [];
|
|
343
344
|
|
|
344
345
|
for (const name of Object.keys(state.config.mcpServers)) {
|
|
345
346
|
const definition = state.config.mcpServers[name];
|
|
@@ -360,7 +361,16 @@ export function executeStatus(state: McpExtensionState): ProxyToolResult {
|
|
|
360
361
|
|
|
361
362
|
const toolCount = status === "failed" ? 0 : metadata?.length ?? 0;
|
|
362
363
|
|
|
363
|
-
|
|
364
|
+
const listenState = connection?.status === "connected" ? connection.listenState : "disconnected";
|
|
365
|
+
servers.push({
|
|
366
|
+
name,
|
|
367
|
+
status,
|
|
368
|
+
listenState,
|
|
369
|
+
...(connection?.status === "connected" && connection.listenCatalogStale ? { catalogStale: true } : {}),
|
|
370
|
+
toolCount,
|
|
371
|
+
failedAgo,
|
|
372
|
+
...(disabled ? { disabled: true } : {}),
|
|
373
|
+
});
|
|
364
374
|
}
|
|
365
375
|
|
|
366
376
|
const disabledCount = servers.filter(s => s.disabled).length;
|
|
@@ -377,7 +387,18 @@ export function executeStatus(state: McpExtensionState): ProxyToolResult {
|
|
|
377
387
|
continue;
|
|
378
388
|
}
|
|
379
389
|
if (server.status === "connected") {
|
|
380
|
-
|
|
390
|
+
const listen = server.listenState === "active"
|
|
391
|
+
? server.catalogStale
|
|
392
|
+
? ", listen active, catalog may be stale"
|
|
393
|
+
: ", listen active"
|
|
394
|
+
: server.listenState === "dropped"
|
|
395
|
+
? ", catalog may be stale; will reconcile on next keep-alive or tool use"
|
|
396
|
+
: server.listenState === "re-establishing"
|
|
397
|
+
? ", re-establishing listen"
|
|
398
|
+
: server.listenState === "legacy"
|
|
399
|
+
? ", legacy notification path"
|
|
400
|
+
: ", not listening for catalog updates";
|
|
401
|
+
text += `✓ ${server.name} (${server.toolCount} tools${listen})\n`;
|
|
381
402
|
continue;
|
|
382
403
|
}
|
|
383
404
|
if (server.status === "needs-auth") {
|
|
@@ -385,14 +406,19 @@ export function executeStatus(state: McpExtensionState): ProxyToolResult {
|
|
|
385
406
|
continue;
|
|
386
407
|
}
|
|
387
408
|
if (server.status === "cached") {
|
|
388
|
-
text += `○ ${server.name} (${server.toolCount} tools, cached)\n`;
|
|
409
|
+
text += `○ ${server.name} (${server.toolCount} tools, cached; not listening)\n`;
|
|
389
410
|
continue;
|
|
390
411
|
}
|
|
391
412
|
if (server.status === "failed") {
|
|
392
413
|
text += `✗ ${server.name} (failed ${server.failedAgo ?? 0}s ago)\n`;
|
|
393
414
|
continue;
|
|
394
415
|
}
|
|
395
|
-
text += `○ ${server.name} (not
|
|
416
|
+
text += `○ ${server.name} (not listening; disconnected)\n`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const directToolsFrozen = state.config.settings?.freezeDirectTools === true;
|
|
420
|
+
if (directToolsFrozen) {
|
|
421
|
+
text += "\nDirect tools frozen; active registrations may differ from current metadata.\n";
|
|
396
422
|
}
|
|
397
423
|
|
|
398
424
|
if (servers.length > 0) {
|
|
@@ -401,7 +427,7 @@ export function executeStatus(state: McpExtensionState): ProxyToolResult {
|
|
|
401
427
|
|
|
402
428
|
return {
|
|
403
429
|
content: [{ type: "text" as const, text: text.trim() }],
|
|
404
|
-
details: { mode: "status", servers, totalTools, connectedCount, disabledCount },
|
|
430
|
+
details: { mode: "status", servers, totalTools, connectedCount, disabledCount, directToolsFrozen },
|
|
405
431
|
};
|
|
406
432
|
}
|
|
407
433
|
|
|
@@ -751,7 +777,17 @@ export function executeList(state: McpExtensionState, server: string): ProxyTool
|
|
|
751
777
|
};
|
|
752
778
|
}
|
|
753
779
|
|
|
754
|
-
|
|
780
|
+
// "not connected" alone reads as an outage while the tools listed below are
|
|
781
|
+
// real and the connection is simply lazy (tools served from cache).
|
|
782
|
+
// Distinguish auth from plain laziness so the caller knows which remedy applies.
|
|
783
|
+
let cachedNote = "";
|
|
784
|
+
if (connection?.status !== "connected") {
|
|
785
|
+
if (connection?.status === "needs-auth") {
|
|
786
|
+
cachedNote = ` (needs auth — run mcp({ action: "auth-start", server: "${server}" }))`;
|
|
787
|
+
} else {
|
|
788
|
+
cachedNote = ` (lazy: tools from cache, not connected yet — mcp({ connect: "${server}" }) to connect)`;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
755
791
|
let text = `${server} (${toolNames.length} tools${cachedNote}):\n\n`;
|
|
756
792
|
|
|
757
793
|
const descMap = new Map<string, string>();
|
|
@@ -1300,7 +1336,13 @@ export async function executeCall(
|
|
|
1300
1336
|
onNeedsAuth: recoverAuthConnection,
|
|
1301
1337
|
},
|
|
1302
1338
|
serverName,
|
|
1303
|
-
(conn) =>
|
|
1339
|
+
async (conn) => {
|
|
1340
|
+
const refreshRead = await state.manager.prepareResourceUse?.(serverName, toolMeta.resourceUri!, conn);
|
|
1341
|
+
return conn.client.readResource(
|
|
1342
|
+
{ uri: toolMeta.resourceUri! },
|
|
1343
|
+
refreshRead ? { ...requestOptions, cacheMode: "refresh" } : requestOptions,
|
|
1344
|
+
);
|
|
1345
|
+
},
|
|
1304
1346
|
);
|
|
1305
1347
|
const content = transformMcpResourceContents(result.contents ?? [], state.owner?.signal);
|
|
1306
1348
|
const guarded = await guardMcpOutput(content.length > 0 ? content : [{ type: "text" as const, text: "(empty resource)" }], outputGuardOptions);
|
|
@@ -1330,11 +1372,14 @@ export async function executeCall(
|
|
|
1330
1372
|
onNeedsAuth: recoverAuthConnection,
|
|
1331
1373
|
},
|
|
1332
1374
|
serverName,
|
|
1333
|
-
(conn) =>
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1375
|
+
async (conn) => {
|
|
1376
|
+
await state.manager.ensureListen?.(serverName, conn);
|
|
1377
|
+
return abortable(conn.client.callTool({
|
|
1378
|
+
name: toolMeta.originalName,
|
|
1379
|
+
arguments: normalizedArgs,
|
|
1380
|
+
_meta: uiSession?.requestMeta,
|
|
1381
|
+
}, requestOptions), ownedSignal);
|
|
1382
|
+
},
|
|
1338
1383
|
);
|
|
1339
1384
|
|
|
1340
1385
|
if (toolMeta.uiResourceUri) {
|
|
@@ -1408,6 +1453,14 @@ export async function executeCall(
|
|
|
1408
1453
|
details: { mode: "call", error: "url_elicitation_required", ...callIdentity, action },
|
|
1409
1454
|
};
|
|
1410
1455
|
}
|
|
1456
|
+
const inputRequired = getInputRequiredNeedsUiDetails(error, callIdentity);
|
|
1457
|
+
if (inputRequired) {
|
|
1458
|
+
uiSession?.sendToolCancelled(inputRequired.message);
|
|
1459
|
+
return {
|
|
1460
|
+
content: [{ type: "text" as const, text: inputRequired.message }],
|
|
1461
|
+
details: { mode: "call", ...inputRequired },
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1411
1464
|
const message = error instanceof Error ? error.message : String(error);
|
|
1412
1465
|
uiSession?.sendToolCancelled(message);
|
|
1413
1466
|
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The proxy is served from a different loopback origin than the trusted host.
|
|
3
|
+
* `allow-same-origin` is safe here because this document never contains the
|
|
4
|
+
* host's session capability; provider HTML is loaded into the nested frame.
|
|
5
|
+
*/
|
|
6
|
+
export const SANDBOX_PROXY_SANDBOX =
|
|
7
|
+
"allow-scripts allow-forms allow-modals allow-popups allow-downloads allow-same-origin";
|
|
8
|
+
export const SANDBOX_INNER_SANDBOX = SANDBOX_PROXY_SANDBOX;
|
|
9
|
+
export const SANDBOX_PROXY_PATH = "/sandbox";
|
|
10
|
+
|
|
11
|
+
export interface SandboxProxyTemplateInput {
|
|
12
|
+
parentOrigin: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build the static document used by the trusted, second-origin sandbox proxy.
|
|
17
|
+
* The only per-session value is the exact origin of the parent host.
|
|
18
|
+
*/
|
|
19
|
+
export function buildSandboxProxyHtml(input: SandboxProxyTemplateInput): string {
|
|
20
|
+
const parentOrigin = safeInlineJSON(input.parentOrigin);
|
|
21
|
+
|
|
22
|
+
return `<!doctype html>
|
|
23
|
+
<html lang="en">
|
|
24
|
+
<head>
|
|
25
|
+
<meta charset="utf-8" />
|
|
26
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
27
|
+
<title>MCP App Sandbox</title>
|
|
28
|
+
<style>
|
|
29
|
+
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: transparent; }
|
|
30
|
+
iframe { display: block; width: 100%; height: 100%; border: 0; }
|
|
31
|
+
</style>
|
|
32
|
+
</head>
|
|
33
|
+
<body>
|
|
34
|
+
<iframe id="mcp-app" title="MCP App" sandbox="${SANDBOX_INNER_SANDBOX}" referrerpolicy="no-referrer"></iframe>
|
|
35
|
+
<script>
|
|
36
|
+
const EXPECTED_PARENT_ORIGIN = ${parentOrigin};
|
|
37
|
+
const SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
|
|
38
|
+
const SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
|
|
39
|
+
const INNER_SANDBOX = ${safeInlineJSON(SANDBOX_INNER_SANDBOX)};
|
|
40
|
+
const MAX_PENDING_MESSAGES = 64;
|
|
41
|
+
const innerFrame = document.getElementById("mcp-app");
|
|
42
|
+
const pendingToInner = [];
|
|
43
|
+
let innerReady = false;
|
|
44
|
+
|
|
45
|
+
const isObject = (value) => value !== null && typeof value === "object";
|
|
46
|
+
const isMessageFromParent = (event) =>
|
|
47
|
+
event.source === window.parent && event.origin === EXPECTED_PARENT_ORIGIN;
|
|
48
|
+
const isMessageFromInner = (event) =>
|
|
49
|
+
event.source === innerFrame.contentWindow && event.origin === window.location.origin;
|
|
50
|
+
|
|
51
|
+
const postToParent = (data) => {
|
|
52
|
+
window.parent.postMessage(data, EXPECTED_PARENT_ORIGIN);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const postToInner = (data) => {
|
|
56
|
+
if (!innerReady || !innerFrame.contentWindow) {
|
|
57
|
+
if (pendingToInner.length < MAX_PENDING_MESSAGES) pendingToInner.push(data);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
innerFrame.contentWindow.postMessage(data, window.location.origin);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const flushPendingMessages = () => {
|
|
64
|
+
if (!innerFrame.contentWindow) return;
|
|
65
|
+
innerReady = true;
|
|
66
|
+
for (const data of pendingToInner.splice(0)) {
|
|
67
|
+
innerFrame.contentWindow.postMessage(data, window.location.origin);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const sanitizeDomains = (domains) => {
|
|
72
|
+
if (!Array.isArray(domains)) return [];
|
|
73
|
+
return [...new Set(domains.filter((domain) =>
|
|
74
|
+
typeof domain === "string" &&
|
|
75
|
+
domain.length > 0 &&
|
|
76
|
+
/^[\\x21-\\x7E]+$/.test(domain) &&
|
|
77
|
+
!/[;'\"]/.test(domain),
|
|
78
|
+
))];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const toDirective = (name, trustedSources, domains) =>
|
|
82
|
+
name + " " + [...new Set([...trustedSources, ...domains])].join(" ");
|
|
83
|
+
|
|
84
|
+
const buildResourceCsp = (csp) => {
|
|
85
|
+
const resourceDomains = sanitizeDomains(csp?.resourceDomains);
|
|
86
|
+
const connectDomains = sanitizeDomains(csp?.connectDomains);
|
|
87
|
+
const frameDomains = sanitizeDomains(csp?.frameDomains);
|
|
88
|
+
const baseUriDomains = sanitizeDomains(csp?.baseUriDomains);
|
|
89
|
+
return [
|
|
90
|
+
"default-src 'none'",
|
|
91
|
+
"sandbox " + INNER_SANDBOX,
|
|
92
|
+
toDirective("script-src", ["'self'", "'unsafe-inline'"], resourceDomains),
|
|
93
|
+
toDirective("style-src", ["'self'", "'unsafe-inline'"], resourceDomains),
|
|
94
|
+
toDirective("font-src", ["'self'"], resourceDomains),
|
|
95
|
+
toDirective("img-src", ["'self'", "data:"], resourceDomains),
|
|
96
|
+
toDirective("media-src", ["'self'", "data:"], resourceDomains),
|
|
97
|
+
connectDomains.length > 0 ? "connect-src " + connectDomains.join(" ") : "connect-src 'none'",
|
|
98
|
+
frameDomains.length > 0 ? "frame-src " + frameDomains.join(" ") : "frame-src 'none'",
|
|
99
|
+
"worker-src 'none'",
|
|
100
|
+
"object-src 'none'",
|
|
101
|
+
baseUriDomains.length > 0 ? "base-uri " + baseUriDomains.join(" ") : "base-uri 'self'",
|
|
102
|
+
].join("; ");
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const escapeAttribute = (value) => value
|
|
106
|
+
.replace(/&/g, "&")
|
|
107
|
+
.replace(/\"/g, """)
|
|
108
|
+
.replace(/</g, "<")
|
|
109
|
+
.replace(/>/g, ">");
|
|
110
|
+
|
|
111
|
+
const injectCsp = (html, csp) => {
|
|
112
|
+
const meta = '<meta http-equiv="Content-Security-Policy" content="' + escapeAttribute(csp) + '">';
|
|
113
|
+
const head = html.match(/<head(?:\\s[^>]*)?>/i);
|
|
114
|
+
if (head && head.index !== undefined) {
|
|
115
|
+
const end = head.index + head[0].length;
|
|
116
|
+
return html.slice(0, end) + meta + html.slice(end);
|
|
117
|
+
}
|
|
118
|
+
return meta + html;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const safeSandbox = (requested) => {
|
|
122
|
+
const requestedTokens = typeof requested === "string" ? requested.split(/\\s+/) : [];
|
|
123
|
+
const allowedTokens = new Set(INNER_SANDBOX.split(" "));
|
|
124
|
+
const tokens = requestedTokens.filter((token) => allowedTokens.has(token));
|
|
125
|
+
// Provider HTML needs both script execution and a real proxy-origin
|
|
126
|
+
// storage context. Never accept popup escape or top-navigation tokens.
|
|
127
|
+
return [...new Set([
|
|
128
|
+
"allow-scripts",
|
|
129
|
+
"allow-same-origin",
|
|
130
|
+
...tokens,
|
|
131
|
+
])].filter((token) => allowedTokens.has(token)).join(" ");
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const buildAllowAttribute = (permissions) => {
|
|
135
|
+
if (!isObject(permissions) || Array.isArray(permissions)) return "";
|
|
136
|
+
const allowed = [];
|
|
137
|
+
if (permissions.camera) allowed.push("camera");
|
|
138
|
+
if (permissions.microphone) allowed.push("microphone");
|
|
139
|
+
if (permissions.geolocation) allowed.push("geolocation");
|
|
140
|
+
if (permissions.clipboardWrite) allowed.push("clipboard-write");
|
|
141
|
+
return allowed.join("; ");
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const loadResource = (params) => {
|
|
145
|
+
if (!isObject(params) || typeof params.html !== "string" || !innerFrame.contentDocument) return;
|
|
146
|
+
innerFrame.setAttribute("sandbox", safeSandbox(params.sandbox));
|
|
147
|
+
const allow = buildAllowAttribute(params.permissions);
|
|
148
|
+
if (allow) innerFrame.setAttribute("allow", allow);
|
|
149
|
+
else innerFrame.removeAttribute("allow");
|
|
150
|
+
innerReady = false;
|
|
151
|
+
const document = innerFrame.contentDocument;
|
|
152
|
+
document.open();
|
|
153
|
+
document.write(injectCsp(params.html, buildResourceCsp(params.csp)));
|
|
154
|
+
document.close();
|
|
155
|
+
flushPendingMessages();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
window.addEventListener("message", (event) => {
|
|
159
|
+
if (isMessageFromParent(event)) {
|
|
160
|
+
const data = event.data;
|
|
161
|
+
if (!isObject(data)) return;
|
|
162
|
+
if (data.method === SANDBOX_RESOURCE_READY_METHOD) {
|
|
163
|
+
loadResource(data.params);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (data.method === SANDBOX_PROXY_READY_METHOD) return;
|
|
167
|
+
if (typeof data.method === "string" && data.method.startsWith("ui/notifications/sandbox-")) return;
|
|
168
|
+
postToInner(data);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!isMessageFromInner(event)) return;
|
|
173
|
+
const data = event.data;
|
|
174
|
+
if (!isObject(data)) return;
|
|
175
|
+
if (typeof data.method === "string" && data.method.startsWith("ui/notifications/sandbox-")) return;
|
|
176
|
+
postToParent(data);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
postToParent({
|
|
180
|
+
jsonrpc: "2.0",
|
|
181
|
+
method: SANDBOX_PROXY_READY_METHOD,
|
|
182
|
+
params: {},
|
|
183
|
+
});
|
|
184
|
+
</script>
|
|
185
|
+
</body>
|
|
186
|
+
</html>`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* CSP for the proxy document itself. It contains only the inline relay script
|
|
191
|
+
* and a nested about:blank frame; provider policy is applied separately after
|
|
192
|
+
* the host sends the resource-ready notification.
|
|
193
|
+
*/
|
|
194
|
+
export function buildSandboxProxyCsp(): string {
|
|
195
|
+
return [
|
|
196
|
+
"default-src 'none'",
|
|
197
|
+
"script-src 'unsafe-inline'",
|
|
198
|
+
"style-src 'unsafe-inline'",
|
|
199
|
+
"frame-src 'self'",
|
|
200
|
+
"connect-src 'none'",
|
|
201
|
+
"worker-src 'none'",
|
|
202
|
+
"object-src 'none'",
|
|
203
|
+
"base-uri 'none'",
|
|
204
|
+
`sandbox ${SANDBOX_PROXY_SANDBOX}`,
|
|
205
|
+
].join("; ");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function safeInlineJSON(value: unknown): string {
|
|
209
|
+
const json = JSON.stringify(value);
|
|
210
|
+
if (json === undefined) return "undefined";
|
|
211
|
+
return json
|
|
212
|
+
.replace(/</g, "\\u003c")
|
|
213
|
+
.replace(/>/g, "\\u003e")
|
|
214
|
+
.replace(/&/g, "\\u0026")
|
|
215
|
+
.replace(/\u2028/g, "\\u2028")
|
|
216
|
+
.replace(/\u2029/g, "\\u2029");
|
|
217
|
+
}
|