@frontmcp/uipack 1.3.0 → 1.4.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/adapters/index.js +1046 -698
- package/adapters/template-renderer.d.ts +14 -0
- package/adapters/template-renderer.d.ts.map +1 -1
- package/bridge-runtime/iife-generator.d.ts.map +1 -1
- package/bridge-runtime/index.js +149 -0
- package/component/index.d.ts +1 -0
- package/component/index.d.ts.map +1 -1
- package/component/index.js +468 -145
- package/component/loader.d.ts +21 -2
- package/component/loader.d.ts.map +1 -1
- package/component/renderer.d.ts +2 -2
- package/component/renderer.d.ts.map +1 -1
- package/component/transpiler.d.ts +16 -1
- package/component/transpiler.d.ts.map +1 -1
- package/component/types.d.ts +19 -0
- package/component/types.d.ts.map +1 -1
- package/component/ui-availability.d.ts +27 -0
- package/component/ui-availability.d.ts.map +1 -0
- package/esm/adapters/index.mjs +1046 -698
- package/esm/bridge-runtime/index.mjs +149 -0
- package/esm/component/index.mjs +468 -145
- package/esm/index.mjs +444 -109
- package/esm/package.json +2 -2
- package/esm/shell/index.mjs +420 -171
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +445 -109
- package/package.json +2 -2
- package/shell/builder.d.ts.map +1 -1
- package/shell/data-injector.d.ts +27 -1
- package/shell/data-injector.d.ts.map +1 -1
- package/shell/index.d.ts +3 -2
- package/shell/index.d.ts.map +1 -1
- package/shell/index.js +423 -171
- package/shell/sizing-css.d.ts +27 -0
- package/shell/sizing-css.d.ts.map +1 -0
- package/shell/types.d.ts +102 -0
- package/shell/types.d.ts.map +1 -1
- package/types/index.d.ts +1 -1
- package/types/index.d.ts.map +1 -1
- package/types/ui-config.d.ts +105 -11
- package/types/ui-config.d.ts.map +1 -1
- package/types/ui-runtime.d.ts +23 -2
- package/types/ui-runtime.d.ts.map +1 -1
package/shell/index.js
CHANGED
|
@@ -30,10 +30,13 @@ __export(shell_exports, {
|
|
|
30
30
|
applyShellTemplate: () => applyShellTemplate,
|
|
31
31
|
buildCSPDirectives: () => buildCSPDirectives,
|
|
32
32
|
buildCSPMetaTag: () => buildCSPMetaTag,
|
|
33
|
+
buildCustomDataInjectionScript: () => buildCustomDataInjectionScript,
|
|
33
34
|
buildDataInjectionScript: () => buildDataInjectionScript,
|
|
34
35
|
buildShell: () => buildShell,
|
|
36
|
+
buildSizingStyleTag: () => buildSizingStyleTag,
|
|
35
37
|
clearShellTemplateCache: () => clearShellTemplateCache,
|
|
36
38
|
createTemplateHelpers: () => createTemplateHelpers,
|
|
39
|
+
hasSizing: () => hasSizing,
|
|
37
40
|
isInlineShellSource: () => isInlineShellSource,
|
|
38
41
|
isNpmShellSource: () => isNpmShellSource,
|
|
39
42
|
isUrlShellSource: () => isUrlShellSource,
|
|
@@ -44,161 +47,6 @@ __export(shell_exports, {
|
|
|
44
47
|
});
|
|
45
48
|
module.exports = __toCommonJS(shell_exports);
|
|
46
49
|
|
|
47
|
-
// libs/uipack/src/shell/csp.ts
|
|
48
|
-
var DEFAULT_CDN_DOMAINS = [
|
|
49
|
-
"https://cdn.jsdelivr.net",
|
|
50
|
-
"https://cdnjs.cloudflare.com",
|
|
51
|
-
"https://fonts.googleapis.com",
|
|
52
|
-
"https://fonts.gstatic.com",
|
|
53
|
-
"https://esm.sh"
|
|
54
|
-
];
|
|
55
|
-
var DEFAULT_CSP_DIRECTIVES = [
|
|
56
|
-
"default-src 'none'",
|
|
57
|
-
`script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
58
|
-
`style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
59
|
-
`img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
60
|
-
`font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
61
|
-
`connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
62
|
-
"object-src 'self' data:"
|
|
63
|
-
];
|
|
64
|
-
var RESTRICTIVE_CSP_DIRECTIVES = [
|
|
65
|
-
"default-src 'none'",
|
|
66
|
-
"script-src 'self' 'unsafe-inline'",
|
|
67
|
-
"style-src 'self' 'unsafe-inline'",
|
|
68
|
-
"img-src 'self' data:",
|
|
69
|
-
"font-src 'self' data:",
|
|
70
|
-
"connect-src 'none'",
|
|
71
|
-
"object-src 'self' data:"
|
|
72
|
-
];
|
|
73
|
-
function buildCSPDirectives(csp) {
|
|
74
|
-
if (!csp) {
|
|
75
|
-
return [...DEFAULT_CSP_DIRECTIVES];
|
|
76
|
-
}
|
|
77
|
-
const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
|
|
78
|
-
const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
|
|
79
|
-
const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
|
|
80
|
-
const directives = [
|
|
81
|
-
"default-src 'none'",
|
|
82
|
-
`script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
|
|
83
|
-
`style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
|
|
84
|
-
];
|
|
85
|
-
const imgSources = ["'self'", "data:", ...allResourceDomains];
|
|
86
|
-
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
87
|
-
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
88
|
-
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
89
|
-
if (validConnectDomains.length) {
|
|
90
|
-
directives.push(`connect-src ${validConnectDomains.join(" ")}`);
|
|
91
|
-
} else {
|
|
92
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
93
|
-
}
|
|
94
|
-
directives.push("object-src 'self' data:");
|
|
95
|
-
return directives;
|
|
96
|
-
}
|
|
97
|
-
function buildCSPMetaTag(csp) {
|
|
98
|
-
const directives = buildCSPDirectives(csp);
|
|
99
|
-
const content = directives.join("; ");
|
|
100
|
-
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
101
|
-
}
|
|
102
|
-
function validateCSPDomain(domain) {
|
|
103
|
-
if (domain.startsWith("https://*.")) {
|
|
104
|
-
const rest = domain.slice(10);
|
|
105
|
-
return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
|
|
106
|
-
}
|
|
107
|
-
try {
|
|
108
|
-
const url = new URL(domain);
|
|
109
|
-
return url.protocol === "https:";
|
|
110
|
-
} catch {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function sanitizeCSPDomains(domains) {
|
|
115
|
-
if (!domains) return [];
|
|
116
|
-
const valid = [];
|
|
117
|
-
for (const domain of domains) {
|
|
118
|
-
if (validateCSPDomain(domain)) {
|
|
119
|
-
valid.push(domain);
|
|
120
|
-
} else {
|
|
121
|
-
console.warn(`Invalid CSP domain ignored: ${domain}`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return valid;
|
|
125
|
-
}
|
|
126
|
-
function escapeAttribute(str) {
|
|
127
|
-
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// libs/uipack/src/utils/index.ts
|
|
131
|
-
function escapeHtml(str) {
|
|
132
|
-
if (str === null || str === void 0) {
|
|
133
|
-
return "";
|
|
134
|
-
}
|
|
135
|
-
const s = String(str);
|
|
136
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
137
|
-
}
|
|
138
|
-
function escapeScriptClose(jsonString) {
|
|
139
|
-
return jsonString.replace(/<\//g, "<\\/");
|
|
140
|
-
}
|
|
141
|
-
function safeJsonForScript(value) {
|
|
142
|
-
if (value === void 0) {
|
|
143
|
-
return "null";
|
|
144
|
-
}
|
|
145
|
-
try {
|
|
146
|
-
const jsonString = JSON.stringify(value, (_key, val) => {
|
|
147
|
-
if (typeof val === "bigint") {
|
|
148
|
-
return val.toString();
|
|
149
|
-
}
|
|
150
|
-
return val;
|
|
151
|
-
});
|
|
152
|
-
if (jsonString === void 0) {
|
|
153
|
-
return "null";
|
|
154
|
-
}
|
|
155
|
-
return escapeScriptClose(jsonString);
|
|
156
|
-
} catch {
|
|
157
|
-
return '{"error":"Value could not be serialized"}';
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// libs/uipack/src/shell/data-injector.ts
|
|
162
|
-
function buildDataInjectionScript(options) {
|
|
163
|
-
const { toolName, input, output, structuredContent } = options;
|
|
164
|
-
const lines = [
|
|
165
|
-
`window.__mcpAppsEnabled = true;`,
|
|
166
|
-
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
167
|
-
`window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
|
|
168
|
-
`window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
|
|
169
|
-
`window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
|
|
170
|
-
];
|
|
171
|
-
return `<script>
|
|
172
|
-
${lines.join("\n")}
|
|
173
|
-
</script>`;
|
|
174
|
-
}
|
|
175
|
-
var _uniqueIdCounter = 0;
|
|
176
|
-
function createTemplateHelpers() {
|
|
177
|
-
return {
|
|
178
|
-
escapeHtml: (str) => escapeHtml(str),
|
|
179
|
-
formatDate: (date, format) => {
|
|
180
|
-
const d = date instanceof Date ? date : new Date(date);
|
|
181
|
-
if (isNaN(d.getTime())) return String(date);
|
|
182
|
-
if (format === "iso") return d.toISOString();
|
|
183
|
-
if (format === "date") return d.toLocaleDateString();
|
|
184
|
-
if (format === "time") return d.toLocaleTimeString();
|
|
185
|
-
return d.toLocaleString();
|
|
186
|
-
},
|
|
187
|
-
formatCurrency: (amount, currency = "USD") => {
|
|
188
|
-
return new Intl.NumberFormat("en-US", {
|
|
189
|
-
style: "currency",
|
|
190
|
-
currency
|
|
191
|
-
}).format(amount);
|
|
192
|
-
},
|
|
193
|
-
uniqueId: (prefix = "mcp") => {
|
|
194
|
-
return `${prefix}-${++_uniqueIdCounter}`;
|
|
195
|
-
},
|
|
196
|
-
jsonEmbed: (data) => {
|
|
197
|
-
return escapeScriptClose(JSON.stringify(data));
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
50
|
// libs/uipack/src/bridge-runtime/iife-generator.ts
|
|
203
51
|
function generateBridgeIIFE(options = {}) {
|
|
204
52
|
const { debug = false, trustedOrigins = [], minify = false } = options;
|
|
@@ -253,6 +101,8 @@ function generateBridgeIIFE(options = {}) {
|
|
|
253
101
|
parts.push("});");
|
|
254
102
|
parts.push("");
|
|
255
103
|
parts.push("window.FrontMcpBridge = bridge;");
|
|
104
|
+
parts.push("");
|
|
105
|
+
parts.push(generateAutoResize());
|
|
256
106
|
parts.push("function __showLoading() {");
|
|
257
107
|
parts.push(' var root = document.getElementById("root");');
|
|
258
108
|
parts.push(" if (root && !root.hasChildNodes()) {");
|
|
@@ -273,6 +123,112 @@ function generateBridgeIIFE(options = {}) {
|
|
|
273
123
|
}
|
|
274
124
|
return code;
|
|
275
125
|
}
|
|
126
|
+
function generateAutoResize() {
|
|
127
|
+
return `
|
|
128
|
+
function __applySizingCss(sizing) {
|
|
129
|
+
if (typeof document === 'undefined' || !document.documentElement) return;
|
|
130
|
+
function toLen(v) { return typeof v === 'number' ? v + 'px' : v; }
|
|
131
|
+
var de = document.documentElement;
|
|
132
|
+
var body = document.body;
|
|
133
|
+
var root = document.getElementById('root');
|
|
134
|
+
if (sizing.preferredHeight != null) {
|
|
135
|
+
var ph = toLen(sizing.preferredHeight);
|
|
136
|
+
de.style.height = ph;
|
|
137
|
+
if (body) body.style.height = ph;
|
|
138
|
+
if (root && !root.style.minHeight) root.style.minHeight = ph;
|
|
139
|
+
}
|
|
140
|
+
if (sizing.minHeight != null) {
|
|
141
|
+
var mh = toLen(sizing.minHeight);
|
|
142
|
+
de.style.minHeight = mh;
|
|
143
|
+
if (body) body.style.minHeight = mh;
|
|
144
|
+
if (root) root.style.minHeight = mh;
|
|
145
|
+
}
|
|
146
|
+
if (sizing.maxHeight != null) {
|
|
147
|
+
var mx = toLen(sizing.maxHeight);
|
|
148
|
+
de.style.maxHeight = mx;
|
|
149
|
+
if (body) body.style.maxHeight = mx;
|
|
150
|
+
if (root) root.style.maxHeight = mx;
|
|
151
|
+
}
|
|
152
|
+
if (sizing.aspectRatio != null && root) {
|
|
153
|
+
root.style.aspectRatio = String(sizing.aspectRatio);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function __initAutoResize() {
|
|
158
|
+
if (typeof window === 'undefined') return;
|
|
159
|
+
// Idempotent: a re-injected IIFE must not stack observers.
|
|
160
|
+
if (window.__mcpAutoResizeInit) return;
|
|
161
|
+
window.__mcpAutoResizeInit = true;
|
|
162
|
+
var sizing = window.__mcpWidgetSizing;
|
|
163
|
+
if (!sizing || typeof sizing !== 'object') return;
|
|
164
|
+
|
|
165
|
+
// Apply CSS as a runtime fallback (the static <style> may be absent on some
|
|
166
|
+
// render paths). Wait for the body if it isn't ready yet.
|
|
167
|
+
function apply() { try { __applySizingCss(sizing); } catch (e) {} }
|
|
168
|
+
if (typeof document !== 'undefined' && document.readyState === 'loading') {
|
|
169
|
+
document.addEventListener('DOMContentLoaded', apply);
|
|
170
|
+
} else {
|
|
171
|
+
apply();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Auto-resize defaults ON; opt out with autoResize:false.
|
|
175
|
+
if (sizing.autoResize === false) return;
|
|
176
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
177
|
+
|
|
178
|
+
function startObserving() {
|
|
179
|
+
var target = document.getElementById('root') || document.body;
|
|
180
|
+
if (!target) return;
|
|
181
|
+
|
|
182
|
+
var rafId = null;
|
|
183
|
+
var lastReported = -1;
|
|
184
|
+
function report() {
|
|
185
|
+
rafId = null;
|
|
186
|
+
try {
|
|
187
|
+
var rect = target.getBoundingClientRect();
|
|
188
|
+
var height = Math.ceil(rect.height);
|
|
189
|
+
var width = Math.ceil(rect.width);
|
|
190
|
+
if (height === lastReported || height <= 0) return;
|
|
191
|
+
lastReported = height;
|
|
192
|
+
var payload = { height: height, width: width };
|
|
193
|
+
if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
|
|
194
|
+
if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
|
|
195
|
+
window.FrontMcpBridge.setSize(payload).catch(function() {});
|
|
196
|
+
}
|
|
197
|
+
window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
|
|
198
|
+
} catch (e) {}
|
|
199
|
+
}
|
|
200
|
+
function schedule() {
|
|
201
|
+
// Debounce via rAF; fall back to setTimeout if rAF is unavailable.
|
|
202
|
+
if (rafId != null) return;
|
|
203
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
204
|
+
rafId = requestAnimationFrame(report);
|
|
205
|
+
} else {
|
|
206
|
+
rafId = setTimeout(report, 100);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
// Disconnect any prior observer before creating a new one (no leaks/dupes).
|
|
212
|
+
if (window.__mcpResizeObserver && typeof window.__mcpResizeObserver.disconnect === 'function') {
|
|
213
|
+
window.__mcpResizeObserver.disconnect();
|
|
214
|
+
}
|
|
215
|
+
var ro = new ResizeObserver(function() { schedule(); });
|
|
216
|
+
ro.observe(target);
|
|
217
|
+
window.__mcpResizeObserver = ro;
|
|
218
|
+
} catch (e) {}
|
|
219
|
+
// Report once on init so the host gets an initial measurement.
|
|
220
|
+
schedule();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (typeof document !== 'undefined' && document.readyState === 'loading') {
|
|
224
|
+
document.addEventListener('DOMContentLoaded', startObserving);
|
|
225
|
+
} else {
|
|
226
|
+
startObserving();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
__initAutoResize();
|
|
230
|
+
`.trim();
|
|
231
|
+
}
|
|
276
232
|
function generateContextDetection() {
|
|
277
233
|
return `
|
|
278
234
|
function detectTheme() {
|
|
@@ -380,6 +336,19 @@ var OpenAIAdapter = {
|
|
|
380
336
|
requestDisplayMode: function(context, mode) {
|
|
381
337
|
return Promise.resolve();
|
|
382
338
|
},
|
|
339
|
+
setSize: function(context, size) {
|
|
340
|
+
// OpenAI Apps SDK measures DOM height itself; if a sizing API surfaces on
|
|
341
|
+
// window.openai, forward to it, otherwise no-op (CSS drives layout).
|
|
342
|
+
try {
|
|
343
|
+
if (window.openai && typeof window.openai.requestDisplayMode === 'function' && size && size.displayMode) {
|
|
344
|
+
window.openai.requestDisplayMode(size.displayMode);
|
|
345
|
+
}
|
|
346
|
+
if (window.openai && typeof window.openai.setWidgetHeight === 'function' && size && typeof size.height === 'number') {
|
|
347
|
+
window.openai.setWidgetHeight(size.height);
|
|
348
|
+
}
|
|
349
|
+
} catch (e) {}
|
|
350
|
+
return Promise.resolve();
|
|
351
|
+
},
|
|
383
352
|
requestClose: function(context) {
|
|
384
353
|
return Promise.resolve();
|
|
385
354
|
}
|
|
@@ -624,6 +593,15 @@ var ExtAppsAdapter = {
|
|
|
624
593
|
requestDisplayMode: function(context, mode) {
|
|
625
594
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
626
595
|
},
|
|
596
|
+
setSize: function(context, size) {
|
|
597
|
+
// FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
|
|
598
|
+
// measured/desired widget dimensions to the host.
|
|
599
|
+
return this.sendRequest('ui/setSize', {
|
|
600
|
+
height: size && size.height,
|
|
601
|
+
width: size && size.width,
|
|
602
|
+
aspectRatio: size && size.aspectRatio
|
|
603
|
+
});
|
|
604
|
+
},
|
|
627
605
|
requestClose: function(context) {
|
|
628
606
|
return this.sendRequest('ui/close', {});
|
|
629
607
|
},
|
|
@@ -712,6 +690,10 @@ var ClaudeAdapter = {
|
|
|
712
690
|
requestDisplayMode: function() {
|
|
713
691
|
return Promise.resolve();
|
|
714
692
|
},
|
|
693
|
+
setSize: function() {
|
|
694
|
+
// Claude measures the rendered DOM height itself \u2014 CSS-only, no reporting.
|
|
695
|
+
return Promise.resolve();
|
|
696
|
+
},
|
|
715
697
|
requestClose: function() {
|
|
716
698
|
return Promise.resolve();
|
|
717
699
|
}
|
|
@@ -763,6 +745,9 @@ var GeminiAdapter = {
|
|
|
763
745
|
requestDisplayMode: function() {
|
|
764
746
|
return Promise.resolve();
|
|
765
747
|
},
|
|
748
|
+
setSize: function() {
|
|
749
|
+
return Promise.resolve();
|
|
750
|
+
},
|
|
766
751
|
requestClose: function() {
|
|
767
752
|
return Promise.resolve();
|
|
768
753
|
}
|
|
@@ -798,6 +783,9 @@ var GenericAdapter = {
|
|
|
798
783
|
requestDisplayMode: function() {
|
|
799
784
|
return Promise.resolve();
|
|
800
785
|
},
|
|
786
|
+
setSize: function() {
|
|
787
|
+
return Promise.resolve();
|
|
788
|
+
},
|
|
801
789
|
requestClose: function() {
|
|
802
790
|
return Promise.resolve();
|
|
803
791
|
}
|
|
@@ -1032,6 +1020,15 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1032
1020
|
});
|
|
1033
1021
|
};
|
|
1034
1022
|
|
|
1023
|
+
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1024
|
+
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1025
|
+
// ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
|
|
1026
|
+
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1027
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1028
|
+
if (!this._adapter.setSize) return Promise.resolve();
|
|
1029
|
+
return this._adapter.setSize(this._context, size || {});
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1035
1032
|
FrontMcpBridge.prototype.requestClose = function() {
|
|
1036
1033
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1037
1034
|
return this._adapter.requestClose(this._context);
|
|
@@ -1230,6 +1227,89 @@ var BRIDGE_SCRIPT_TAGS = {
|
|
|
1230
1227
|
gemini: `<script>${generatePlatformBundle("gemini")}</script>`
|
|
1231
1228
|
};
|
|
1232
1229
|
|
|
1230
|
+
// libs/uipack/src/shell/csp.ts
|
|
1231
|
+
var DEFAULT_CDN_DOMAINS = [
|
|
1232
|
+
"https://cdn.jsdelivr.net",
|
|
1233
|
+
"https://cdnjs.cloudflare.com",
|
|
1234
|
+
"https://fonts.googleapis.com",
|
|
1235
|
+
"https://fonts.gstatic.com",
|
|
1236
|
+
"https://esm.sh"
|
|
1237
|
+
];
|
|
1238
|
+
var DEFAULT_CSP_DIRECTIVES = [
|
|
1239
|
+
"default-src 'none'",
|
|
1240
|
+
`script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1241
|
+
`style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1242
|
+
`img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1243
|
+
`font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1244
|
+
`connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1245
|
+
"object-src 'self' data:"
|
|
1246
|
+
];
|
|
1247
|
+
var RESTRICTIVE_CSP_DIRECTIVES = [
|
|
1248
|
+
"default-src 'none'",
|
|
1249
|
+
"script-src 'self' 'unsafe-inline'",
|
|
1250
|
+
"style-src 'self' 'unsafe-inline'",
|
|
1251
|
+
"img-src 'self' data:",
|
|
1252
|
+
"font-src 'self' data:",
|
|
1253
|
+
"connect-src 'none'",
|
|
1254
|
+
"object-src 'self' data:"
|
|
1255
|
+
];
|
|
1256
|
+
function buildCSPDirectives(csp) {
|
|
1257
|
+
if (!csp) {
|
|
1258
|
+
return [...DEFAULT_CSP_DIRECTIVES];
|
|
1259
|
+
}
|
|
1260
|
+
const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
|
|
1261
|
+
const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
|
|
1262
|
+
const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
|
|
1263
|
+
const directives = [
|
|
1264
|
+
"default-src 'none'",
|
|
1265
|
+
`script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
|
|
1266
|
+
`style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
|
|
1267
|
+
];
|
|
1268
|
+
const imgSources = ["'self'", "data:", ...allResourceDomains];
|
|
1269
|
+
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
1270
|
+
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
1271
|
+
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
1272
|
+
if (validConnectDomains.length) {
|
|
1273
|
+
directives.push(`connect-src ${validConnectDomains.join(" ")}`);
|
|
1274
|
+
} else {
|
|
1275
|
+
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
1276
|
+
}
|
|
1277
|
+
directives.push("object-src 'self' data:");
|
|
1278
|
+
return directives;
|
|
1279
|
+
}
|
|
1280
|
+
function buildCSPMetaTag(csp) {
|
|
1281
|
+
const directives = buildCSPDirectives(csp);
|
|
1282
|
+
const content = directives.join("; ");
|
|
1283
|
+
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
1284
|
+
}
|
|
1285
|
+
function validateCSPDomain(domain) {
|
|
1286
|
+
if (domain.startsWith("https://*.")) {
|
|
1287
|
+
const rest = domain.slice(10);
|
|
1288
|
+
return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
|
|
1289
|
+
}
|
|
1290
|
+
try {
|
|
1291
|
+
const url = new URL(domain);
|
|
1292
|
+
return url.protocol === "https:";
|
|
1293
|
+
} catch {
|
|
1294
|
+
return false;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
function sanitizeCSPDomains(domains) {
|
|
1298
|
+
if (!domains) return [];
|
|
1299
|
+
const valid = [];
|
|
1300
|
+
for (const domain of domains) {
|
|
1301
|
+
if (validateCSPDomain(domain)) {
|
|
1302
|
+
valid.push(domain);
|
|
1303
|
+
} else {
|
|
1304
|
+
console.warn(`Invalid CSP domain ignored: ${domain}`);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
return valid;
|
|
1308
|
+
}
|
|
1309
|
+
function escapeAttribute(str) {
|
|
1310
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1233
1313
|
// libs/uipack/src/shell/custom-shell-types.ts
|
|
1234
1314
|
var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
|
|
1235
1315
|
var SHELL_PLACEHOLDERS = {
|
|
@@ -1251,6 +1331,17 @@ function isNpmShellSource(source) {
|
|
|
1251
1331
|
return typeof source === "object" && source !== null && "npm" in source;
|
|
1252
1332
|
}
|
|
1253
1333
|
|
|
1334
|
+
// libs/uipack/src/shell/custom-shell-applier.ts
|
|
1335
|
+
function applyShellTemplate(template, values) {
|
|
1336
|
+
let result = template;
|
|
1337
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
|
|
1338
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
|
|
1339
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
|
|
1340
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
|
|
1341
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
|
|
1342
|
+
return result;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1254
1345
|
// libs/uipack/src/shell/custom-shell-validator.ts
|
|
1255
1346
|
function validateShellTemplate(template) {
|
|
1256
1347
|
const found = {};
|
|
@@ -1267,22 +1358,169 @@ function validateShellTemplate(template) {
|
|
|
1267
1358
|
};
|
|
1268
1359
|
}
|
|
1269
1360
|
|
|
1270
|
-
// libs/uipack/src/
|
|
1271
|
-
function
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1361
|
+
// libs/uipack/src/utils/index.ts
|
|
1362
|
+
function escapeHtml(str) {
|
|
1363
|
+
if (str === null || str === void 0) {
|
|
1364
|
+
return "";
|
|
1365
|
+
}
|
|
1366
|
+
const s = String(str);
|
|
1367
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
1368
|
+
}
|
|
1369
|
+
function escapeScriptClose(jsonString) {
|
|
1370
|
+
return jsonString.replace(/<\//g, "<\\/");
|
|
1371
|
+
}
|
|
1372
|
+
function safeJsonForScript(value) {
|
|
1373
|
+
if (value === void 0) {
|
|
1374
|
+
return "null";
|
|
1375
|
+
}
|
|
1376
|
+
try {
|
|
1377
|
+
const jsonString = JSON.stringify(value, (_key, val) => {
|
|
1378
|
+
if (typeof val === "bigint") {
|
|
1379
|
+
return val.toString();
|
|
1380
|
+
}
|
|
1381
|
+
return val;
|
|
1382
|
+
});
|
|
1383
|
+
if (jsonString === void 0) {
|
|
1384
|
+
return "null";
|
|
1385
|
+
}
|
|
1386
|
+
return escapeScriptClose(jsonString);
|
|
1387
|
+
} catch {
|
|
1388
|
+
return '{"error":"Value could not be serialized"}';
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// libs/uipack/src/shell/data-injector.ts
|
|
1393
|
+
function hasSizing(sizing) {
|
|
1394
|
+
if (!sizing) return false;
|
|
1395
|
+
return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
|
|
1396
|
+
}
|
|
1397
|
+
function buildDataInjectionScript(options) {
|
|
1398
|
+
const { toolName, input, output, structuredContent, sizing } = options;
|
|
1399
|
+
const lines = [
|
|
1400
|
+
`window.__mcpAppsEnabled = true;`,
|
|
1401
|
+
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
1402
|
+
`window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
|
|
1403
|
+
`window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
|
|
1404
|
+
`window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
|
|
1405
|
+
];
|
|
1406
|
+
if (hasSizing(sizing)) {
|
|
1407
|
+
lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
|
|
1408
|
+
}
|
|
1409
|
+
return `<script>
|
|
1410
|
+
${lines.join("\n")}
|
|
1411
|
+
</script>`;
|
|
1412
|
+
}
|
|
1413
|
+
function buildCustomDataInjectionScript(descriptor) {
|
|
1414
|
+
if (descriptor.script !== void 0) {
|
|
1415
|
+
return descriptor.script;
|
|
1416
|
+
}
|
|
1417
|
+
if (descriptor.globalKey !== void 0) {
|
|
1418
|
+
return `<script>window[${safeJsonForScript(descriptor.globalKey)}] = ${safeJsonForScript(
|
|
1419
|
+
descriptor.value ?? null
|
|
1420
|
+
)};</script>`;
|
|
1421
|
+
}
|
|
1422
|
+
return "";
|
|
1423
|
+
}
|
|
1424
|
+
var _uniqueIdCounter = 0;
|
|
1425
|
+
function createTemplateHelpers() {
|
|
1426
|
+
return {
|
|
1427
|
+
escapeHtml: (str) => escapeHtml(str),
|
|
1428
|
+
formatDate: (date, format) => {
|
|
1429
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
1430
|
+
if (isNaN(d.getTime())) return String(date);
|
|
1431
|
+
if (format === "iso") return d.toISOString();
|
|
1432
|
+
if (format === "date") return d.toLocaleDateString();
|
|
1433
|
+
if (format === "time") return d.toLocaleTimeString();
|
|
1434
|
+
return d.toLocaleString();
|
|
1435
|
+
},
|
|
1436
|
+
formatCurrency: (amount, currency = "USD") => {
|
|
1437
|
+
return new Intl.NumberFormat("en-US", {
|
|
1438
|
+
style: "currency",
|
|
1439
|
+
currency
|
|
1440
|
+
}).format(amount);
|
|
1441
|
+
},
|
|
1442
|
+
uniqueId: (prefix = "mcp") => {
|
|
1443
|
+
return `${prefix}-${++_uniqueIdCounter}`;
|
|
1444
|
+
},
|
|
1445
|
+
jsonEmbed: (data) => {
|
|
1446
|
+
return escapeScriptClose(JSON.stringify(data));
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// libs/uipack/src/shell/sizing-css.ts
|
|
1452
|
+
function sanitizeCssValue(value) {
|
|
1453
|
+
return value.replace(/[<>{};]/g, "").trim();
|
|
1454
|
+
}
|
|
1455
|
+
function toCssLength(value) {
|
|
1456
|
+
return typeof value === "number" ? `${value}px` : sanitizeCssValue(value);
|
|
1457
|
+
}
|
|
1458
|
+
function buildSizingStyleTag(sizing) {
|
|
1459
|
+
if (!hasSizing(sizing)) return "";
|
|
1460
|
+
const hasCssSizing = sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0;
|
|
1461
|
+
if (!hasCssSizing) return "";
|
|
1462
|
+
const rootRules = [];
|
|
1463
|
+
const docRules = ["margin: 0;"];
|
|
1464
|
+
if (sizing.preferredHeight !== void 0) {
|
|
1465
|
+
const h = toCssLength(sizing.preferredHeight);
|
|
1466
|
+
docRules.push(`height: ${h};`);
|
|
1467
|
+
rootRules.push(`min-height: ${h};`);
|
|
1468
|
+
}
|
|
1469
|
+
if (sizing.minHeight !== void 0) {
|
|
1470
|
+
const mh = toCssLength(sizing.minHeight);
|
|
1471
|
+
docRules.push(`min-height: ${mh};`);
|
|
1472
|
+
rootRules.push(`min-height: ${mh};`);
|
|
1473
|
+
}
|
|
1474
|
+
if (sizing.maxHeight !== void 0) {
|
|
1475
|
+
const mx = toCssLength(sizing.maxHeight);
|
|
1476
|
+
docRules.push(`max-height: ${mx};`);
|
|
1477
|
+
rootRules.push(`max-height: ${mx};`);
|
|
1478
|
+
}
|
|
1479
|
+
if (sizing.aspectRatio !== void 0) {
|
|
1480
|
+
const ar = typeof sizing.aspectRatio === "number" ? String(sizing.aspectRatio) : sanitizeCssValue(sizing.aspectRatio);
|
|
1481
|
+
if (ar) rootRules.push(`aspect-ratio: ${ar};`);
|
|
1482
|
+
}
|
|
1483
|
+
const parts = [`html, body { ${docRules.join(" ")} }`];
|
|
1484
|
+
if (rootRules.length > 0) {
|
|
1485
|
+
parts.push(`#root { ${rootRules.join(" ")} }`);
|
|
1486
|
+
}
|
|
1487
|
+
return `<style>${parts.join("\n")}</style>`;
|
|
1279
1488
|
}
|
|
1280
1489
|
|
|
1281
1490
|
// libs/uipack/src/shell/builder.ts
|
|
1491
|
+
function resolveDataInjectionScript(args) {
|
|
1492
|
+
if (args.dataInjection) {
|
|
1493
|
+
return buildCustomDataInjectionScript(args.dataInjection);
|
|
1494
|
+
}
|
|
1495
|
+
return buildDataInjectionScript({
|
|
1496
|
+
toolName: args.toolName,
|
|
1497
|
+
input: args.input,
|
|
1498
|
+
output: args.output,
|
|
1499
|
+
structuredContent: args.structuredContent,
|
|
1500
|
+
sizing: args.sizing
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1282
1503
|
function buildShell(content, config) {
|
|
1283
|
-
const {
|
|
1284
|
-
|
|
1285
|
-
|
|
1504
|
+
const {
|
|
1505
|
+
toolName,
|
|
1506
|
+
csp,
|
|
1507
|
+
withShell = true,
|
|
1508
|
+
input,
|
|
1509
|
+
output,
|
|
1510
|
+
structuredContent,
|
|
1511
|
+
includeBridge = true,
|
|
1512
|
+
title,
|
|
1513
|
+
sizing
|
|
1514
|
+
} = config;
|
|
1515
|
+
const { customShell, dataInjection } = config;
|
|
1516
|
+
const dataScript = resolveDataInjectionScript({
|
|
1517
|
+
dataInjection,
|
|
1518
|
+
toolName,
|
|
1519
|
+
input,
|
|
1520
|
+
output,
|
|
1521
|
+
structuredContent,
|
|
1522
|
+
sizing
|
|
1523
|
+
});
|
|
1286
1524
|
if (!withShell) {
|
|
1287
1525
|
const html2 = `${dataScript}
|
|
1288
1526
|
${content}`;
|
|
@@ -1300,7 +1538,9 @@ ${content}`;
|
|
|
1300
1538
|
output,
|
|
1301
1539
|
structuredContent,
|
|
1302
1540
|
includeBridge,
|
|
1303
|
-
title
|
|
1541
|
+
title,
|
|
1542
|
+
sizing,
|
|
1543
|
+
dataInjection
|
|
1304
1544
|
});
|
|
1305
1545
|
}
|
|
1306
1546
|
const headParts = [
|
|
@@ -1312,6 +1552,10 @@ ${content}`;
|
|
|
1312
1552
|
}
|
|
1313
1553
|
headParts.push(buildCSPMetaTag(csp));
|
|
1314
1554
|
headParts.push(dataScript);
|
|
1555
|
+
const sizingStyle = buildSizingStyleTag(sizing);
|
|
1556
|
+
if (sizingStyle) {
|
|
1557
|
+
headParts.push(sizingStyle);
|
|
1558
|
+
}
|
|
1315
1559
|
if (includeBridge) {
|
|
1316
1560
|
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
1317
1561
|
headParts.push(`<script>${bridgeScript}</script>`);
|
|
@@ -1345,12 +1589,17 @@ function buildCustomShell(content, customShell, ctx) {
|
|
|
1345
1589
|
template = customShell.template;
|
|
1346
1590
|
}
|
|
1347
1591
|
const cspTag = buildCSPMetaTag(ctx.csp);
|
|
1348
|
-
const dataScript =
|
|
1592
|
+
const dataScript = resolveDataInjectionScript({
|
|
1593
|
+
dataInjection: ctx.dataInjection,
|
|
1349
1594
|
toolName: ctx.toolName,
|
|
1350
1595
|
input: ctx.input,
|
|
1351
1596
|
output: ctx.output,
|
|
1352
|
-
structuredContent: ctx.structuredContent
|
|
1597
|
+
structuredContent: ctx.structuredContent,
|
|
1598
|
+
sizing: ctx.sizing
|
|
1353
1599
|
});
|
|
1600
|
+
const sizingStyle = buildSizingStyleTag(ctx.sizing);
|
|
1601
|
+
const dataWithSizing = sizingStyle ? `${dataScript}
|
|
1602
|
+
${sizingStyle}` : dataScript;
|
|
1354
1603
|
let bridgeHtml = "";
|
|
1355
1604
|
if (ctx.includeBridge) {
|
|
1356
1605
|
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
@@ -1358,7 +1607,7 @@ function buildCustomShell(content, customShell, ctx) {
|
|
|
1358
1607
|
}
|
|
1359
1608
|
const html = applyShellTemplate(template, {
|
|
1360
1609
|
csp: cspTag,
|
|
1361
|
-
data:
|
|
1610
|
+
data: dataWithSizing,
|
|
1362
1611
|
bridge: bridgeHtml,
|
|
1363
1612
|
content,
|
|
1364
1613
|
title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
|
|
@@ -1501,10 +1750,13 @@ function escapeRegExp(str) {
|
|
|
1501
1750
|
applyShellTemplate,
|
|
1502
1751
|
buildCSPDirectives,
|
|
1503
1752
|
buildCSPMetaTag,
|
|
1753
|
+
buildCustomDataInjectionScript,
|
|
1504
1754
|
buildDataInjectionScript,
|
|
1505
1755
|
buildShell,
|
|
1756
|
+
buildSizingStyleTag,
|
|
1506
1757
|
clearShellTemplateCache,
|
|
1507
1758
|
createTemplateHelpers,
|
|
1759
|
+
hasSizing,
|
|
1508
1760
|
isInlineShellSource,
|
|
1509
1761
|
isNpmShellSource,
|
|
1510
1762
|
isUrlShellSource,
|