@frontmcp/uipack 1.8.6 → 1.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/adapters/index.js +224 -38
- package/adapters/markdown.d.ts +9 -0
- package/adapters/markdown.d.ts.map +1 -0
- package/adapters/template-renderer.d.ts +8 -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 +81 -15
- package/component/index.js +100 -29
- package/esm/adapters/index.mjs +227 -38
- package/esm/bridge-runtime/index.mjs +84 -15
- package/esm/component/index.mjs +103 -29
- package/esm/index.mjs +227 -38
- package/esm/package.json +2 -2
- package/esm/resolver/index.mjs +3 -0
- package/esm/shell/index.mjs +100 -26
- package/esm/types/index.mjs +3 -0
- package/esm/typings/index.mjs +3 -0
- package/esm/utils/index.mjs +3 -0
- package/index.js +224 -38
- package/package.json +2 -2
- package/shell/csp.d.ts +6 -1
- package/shell/csp.d.ts.map +1 -1
- package/shell/index.js +97 -26
- package/types/ui-config.d.ts +1 -1
package/adapters/index.js
CHANGED
|
@@ -829,12 +829,38 @@ function __applyHostTheme(theme) {
|
|
|
829
829
|
} catch (e) {}
|
|
830
830
|
}
|
|
831
831
|
|
|
832
|
+
function extractToolOutput(params) {
|
|
833
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
834
|
+
if (!params) return undefined;
|
|
835
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
836
|
+
var content = params.content;
|
|
837
|
+
if (Array.isArray(content)) {
|
|
838
|
+
for (var i = 0; i < content.length; i++) {
|
|
839
|
+
var item = content[i];
|
|
840
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
841
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return content;
|
|
846
|
+
}
|
|
847
|
+
|
|
832
848
|
function readInjectedData() {
|
|
833
849
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
834
850
|
if (typeof window !== 'undefined') {
|
|
835
851
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
836
852
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
837
853
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
854
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
855
|
+
// (toolOutput is the tool's structuredContent).
|
|
856
|
+
var oa = window.openai;
|
|
857
|
+
if (oa && typeof oa === 'object') {
|
|
858
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
859
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
860
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
861
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
838
864
|
}
|
|
839
865
|
return data;
|
|
840
866
|
}
|
|
@@ -883,7 +909,24 @@ var OpenAIAdapter = {
|
|
|
883
909
|
if (sdk.displayMode) {
|
|
884
910
|
context.hostContext.displayMode = sdk.displayMode;
|
|
885
911
|
}
|
|
886
|
-
//
|
|
912
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
913
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
914
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
915
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
916
|
+
context.toolInput = globals.toolInput;
|
|
917
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
918
|
+
}
|
|
919
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
920
|
+
context.structuredContent = globals.toolOutput;
|
|
921
|
+
context.notifyToolResult(globals.toolOutput);
|
|
922
|
+
}
|
|
923
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
924
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
925
|
+
}
|
|
926
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
927
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
928
|
+
}
|
|
929
|
+
});
|
|
887
930
|
return Promise.resolve();
|
|
888
931
|
},
|
|
889
932
|
callTool: function(context, name, args) {
|
|
@@ -1030,9 +1073,10 @@ var ExtAppsAdapter = {
|
|
|
1030
1073
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
1031
1074
|
break;
|
|
1032
1075
|
case 'ui/notifications/tool-result':
|
|
1033
|
-
|
|
1076
|
+
var resultOutput = extractToolOutput(params);
|
|
1077
|
+
context.toolOutput = resultOutput;
|
|
1034
1078
|
context.structuredContent = params.structuredContent;
|
|
1035
|
-
context.notifyToolResult(
|
|
1079
|
+
context.notifyToolResult(resultOutput);
|
|
1036
1080
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
1037
1081
|
break;
|
|
1038
1082
|
case 'ui/notifications/host-context-changed':
|
|
@@ -1086,6 +1130,15 @@ var ExtAppsAdapter = {
|
|
|
1086
1130
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
1087
1131
|
});
|
|
1088
1132
|
},
|
|
1133
|
+
sendNotification: function(method, params) {
|
|
1134
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
1135
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1136
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
1137
|
+
}
|
|
1138
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
1139
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
1140
|
+
return Promise.resolve();
|
|
1141
|
+
},
|
|
1089
1142
|
sendHandshake: function(context) {
|
|
1090
1143
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
1091
1144
|
// been established yet. The response from the host will establish
|
|
@@ -1151,7 +1204,7 @@ var ExtAppsAdapter = {
|
|
|
1151
1204
|
var pending = this.pendingSize;
|
|
1152
1205
|
if (!pending) return;
|
|
1153
1206
|
this.pendingSize = null;
|
|
1154
|
-
this.
|
|
1207
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
1155
1208
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
1156
1209
|
}, function(err) {
|
|
1157
1210
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -1178,13 +1231,11 @@ var ExtAppsAdapter = {
|
|
|
1178
1231
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
1179
1232
|
},
|
|
1180
1233
|
setSize: function(context, size) {
|
|
1181
|
-
//
|
|
1182
|
-
//
|
|
1183
|
-
var params = {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
aspectRatio: size && size.aspectRatio
|
|
1187
|
-
};
|
|
1234
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
1235
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
1236
|
+
var params = {};
|
|
1237
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
1238
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
1188
1239
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1189
1240
|
// No host origin yet: keep only the latest size and send it when the
|
|
1190
1241
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -1195,7 +1246,7 @@ var ExtAppsAdapter = {
|
|
|
1195
1246
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
1196
1247
|
});
|
|
1197
1248
|
}
|
|
1198
|
-
return this.
|
|
1249
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
1199
1250
|
},
|
|
1200
1251
|
requestClose: function(context) {
|
|
1201
1252
|
return this.sendRequest('ui/close', {});
|
|
@@ -1618,7 +1669,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1618
1669
|
|
|
1619
1670
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1620
1671
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1621
|
-
// ext-apps sends ui/
|
|
1672
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1622
1673
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1623
1674
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1624
1675
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1720,7 +1771,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1720
1771
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1721
1772
|
|
|
1722
1773
|
// Add spinner for buttons
|
|
1723
|
-
var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
|
|
1774
|
+
var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" style="width:1em;height:1em;vertical-align:-0.125em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
|
|
1724
1775
|
if (target.tagName === 'BUTTON') {
|
|
1725
1776
|
target.innerHTML = spinner + 'Loading...';
|
|
1726
1777
|
}
|
|
@@ -1801,7 +1852,22 @@ function minifyJS(code) {
|
|
|
1801
1852
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1802
1853
|
return code;
|
|
1803
1854
|
}
|
|
1804
|
-
|
|
1855
|
+
const out = [];
|
|
1856
|
+
let inBlockComment = false;
|
|
1857
|
+
for (const raw of code.split("\n")) {
|
|
1858
|
+
const line = raw.trim();
|
|
1859
|
+
if (inBlockComment) {
|
|
1860
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1861
|
+
continue;
|
|
1862
|
+
}
|
|
1863
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1864
|
+
if (line.startsWith("/*")) {
|
|
1865
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
out.push(line);
|
|
1869
|
+
}
|
|
1870
|
+
return out.join("\n");
|
|
1805
1871
|
}
|
|
1806
1872
|
function generatePlatformBundle(platform, options = {}) {
|
|
1807
1873
|
const platformAdapters = {
|
|
@@ -1856,11 +1922,8 @@ function buildCSPDirectives(csp) {
|
|
|
1856
1922
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
1857
1923
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
1858
1924
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
} else {
|
|
1862
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
1863
|
-
}
|
|
1925
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
1926
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
1864
1927
|
directives.push("object-src 'self' data:");
|
|
1865
1928
|
return directives;
|
|
1866
1929
|
}
|
|
@@ -1869,26 +1932,34 @@ function buildCSPMetaTag(csp) {
|
|
|
1869
1932
|
const content = directives.join("; ");
|
|
1870
1933
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
1871
1934
|
}
|
|
1935
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
1872
1936
|
function validateCSPDomain(domain) {
|
|
1873
|
-
if (domain
|
|
1874
|
-
|
|
1875
|
-
|
|
1937
|
+
if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
|
|
1938
|
+
if (/[?#]/.test(domain)) return false;
|
|
1939
|
+
if (domain.includes("*")) {
|
|
1940
|
+
const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
|
|
1941
|
+
return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
|
|
1876
1942
|
}
|
|
1877
1943
|
try {
|
|
1878
1944
|
const url = new URL(domain);
|
|
1879
|
-
|
|
1945
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
1946
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
1880
1947
|
} catch {
|
|
1881
1948
|
return false;
|
|
1882
1949
|
}
|
|
1883
1950
|
}
|
|
1951
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
1884
1952
|
function sanitizeCSPDomains(domains) {
|
|
1885
1953
|
if (!domains) return [];
|
|
1886
1954
|
const valid = [];
|
|
1887
1955
|
for (const domain of domains) {
|
|
1888
1956
|
if (validateCSPDomain(domain)) {
|
|
1889
1957
|
valid.push(domain);
|
|
1890
|
-
} else {
|
|
1891
|
-
|
|
1958
|
+
} else if (!reportedInvalidDomains.has(domain)) {
|
|
1959
|
+
reportedInvalidDomains.add(domain);
|
|
1960
|
+
console.warn(
|
|
1961
|
+
`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
|
|
1962
|
+
);
|
|
1892
1963
|
}
|
|
1893
1964
|
}
|
|
1894
1965
|
return valid;
|
|
@@ -2292,8 +2363,8 @@ if (__root) {
|
|
|
2292
2363
|
configurable: true, enumerable: true
|
|
2293
2364
|
});
|
|
2294
2365
|
}
|
|
2295
|
-
// 2. Try injected data globals
|
|
2296
|
-
if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
|
|
2366
|
+
// 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
|
|
2367
|
+
if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
|
|
2297
2368
|
// 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
|
|
2298
2369
|
var __bridge = window.FrontMcpBridge;
|
|
2299
2370
|
if (__bridge && typeof __bridge.onToolResult === 'function') {
|
|
@@ -2559,7 +2630,7 @@ createRoot(document.getElementById('root'))
|
|
|
2559
2630
|
return `${componentImport}
|
|
2560
2631
|
import React from 'react';
|
|
2561
2632
|
import { createRoot } from 'react-dom/client';
|
|
2562
|
-
const output = window.__mcpToolOutput;
|
|
2633
|
+
const output = window.__mcpToolOutput || {};
|
|
2563
2634
|
const props = ${propsCode};
|
|
2564
2635
|
createRoot(document.getElementById('root'))
|
|
2565
2636
|
.render(React.createElement(Component, props));`;
|
|
@@ -2863,6 +2934,86 @@ function wrapDetectedContent(value) {
|
|
|
2863
2934
|
}
|
|
2864
2935
|
}
|
|
2865
2936
|
|
|
2937
|
+
// libs/uipack/src/adapters/markdown.ts
|
|
2938
|
+
var SAFE_HREF = /^(https?:|mailto:|\/|#)/i;
|
|
2939
|
+
function emphasis(text) {
|
|
2940
|
+
return text.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
2941
|
+
}
|
|
2942
|
+
function inline(text) {
|
|
2943
|
+
const stash = [];
|
|
2944
|
+
const hold = (html2) => `\uE000${stash.push(html2) - 1}\uE000`;
|
|
2945
|
+
let out = escapeHtml(text.replace(/\uE000/g, "")).replace(
|
|
2946
|
+
/`([^`]+)`/g,
|
|
2947
|
+
(_m, code) => hold(`<code>${code}</code>`)
|
|
2948
|
+
);
|
|
2949
|
+
out = out.replace(
|
|
2950
|
+
/\[([^\]]+)\]\(([^)\s]+)\)/g,
|
|
2951
|
+
(_m, label, href) => SAFE_HREF.test(href) ? hold(`<a href="${href}" rel="noopener noreferrer">${emphasis(label)}</a>`) : label
|
|
2952
|
+
);
|
|
2953
|
+
out = emphasis(out);
|
|
2954
|
+
for (let i = 0; i < 2; i++) out = out.replace(/\uE000(\d+)\uE000/g, (_m, n) => stash[Number(n)]);
|
|
2955
|
+
return out;
|
|
2956
|
+
}
|
|
2957
|
+
function markdownToHtml(source) {
|
|
2958
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
2959
|
+
const blocks = [];
|
|
2960
|
+
let paragraph = [];
|
|
2961
|
+
let list;
|
|
2962
|
+
const flushParagraph = () => {
|
|
2963
|
+
if (paragraph.length > 0) blocks.push(`<p>${inline(paragraph.join(" "))}</p>`);
|
|
2964
|
+
paragraph = [];
|
|
2965
|
+
};
|
|
2966
|
+
const flushList = () => {
|
|
2967
|
+
if (list)
|
|
2968
|
+
blocks.push(`<${list.tag}>${list.items.map((item) => `<li>${inline(item)}</li>`).join("")}</${list.tag}>`);
|
|
2969
|
+
list = void 0;
|
|
2970
|
+
};
|
|
2971
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2972
|
+
const line = lines[i];
|
|
2973
|
+
const fence = /^\s*(`{3,})/.exec(line);
|
|
2974
|
+
if (fence) {
|
|
2975
|
+
flushParagraph();
|
|
2976
|
+
flushList();
|
|
2977
|
+
const fenceLength = fence[1].length;
|
|
2978
|
+
const isClosing = (l) => {
|
|
2979
|
+
const m = /^\s*(`{3,})\s*$/.exec(l);
|
|
2980
|
+
return m !== null && m[1].length >= fenceLength;
|
|
2981
|
+
};
|
|
2982
|
+
const code = [];
|
|
2983
|
+
for (i++; i < lines.length && !isClosing(lines[i]); i++) code.push(lines[i]);
|
|
2984
|
+
blocks.push(`<pre><code>${escapeHtml(code.join("\n"))}
|
|
2985
|
+
</code></pre>`);
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2988
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
2989
|
+
if (heading) {
|
|
2990
|
+
flushParagraph();
|
|
2991
|
+
flushList();
|
|
2992
|
+
blocks.push(`<h${heading[1].length}>${inline(heading[2].trim())}</h${heading[1].length}>`);
|
|
2993
|
+
continue;
|
|
2994
|
+
}
|
|
2995
|
+
const item = /^\s*([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
2996
|
+
if (item) {
|
|
2997
|
+
flushParagraph();
|
|
2998
|
+
const tag = /\d/.test(item[1]) ? "ol" : "ul";
|
|
2999
|
+
if (list && list.tag !== tag) flushList();
|
|
3000
|
+
list ??= { tag, items: [] };
|
|
3001
|
+
list.items.push(item[2]);
|
|
3002
|
+
continue;
|
|
3003
|
+
}
|
|
3004
|
+
if (line.trim() === "") {
|
|
3005
|
+
flushParagraph();
|
|
3006
|
+
flushList();
|
|
3007
|
+
continue;
|
|
3008
|
+
}
|
|
3009
|
+
flushList();
|
|
3010
|
+
paragraph.push(line.trim());
|
|
3011
|
+
}
|
|
3012
|
+
flushParagraph();
|
|
3013
|
+
flushList();
|
|
3014
|
+
return blocks.join("\n");
|
|
3015
|
+
}
|
|
3016
|
+
|
|
2866
3017
|
// libs/uipack/src/adapters/type-detector.ts
|
|
2867
3018
|
function detectUIType(template) {
|
|
2868
3019
|
if (template === null || template === void 0) {
|
|
@@ -2896,9 +3047,15 @@ function detectUIType(template) {
|
|
|
2896
3047
|
}
|
|
2897
3048
|
|
|
2898
3049
|
// libs/uipack/src/adapters/template-renderer.ts
|
|
2899
|
-
function buildCspConfig(resolver) {
|
|
3050
|
+
function buildCspConfig(resolver, extra) {
|
|
2900
3051
|
const cspResourceDomains = ["https://esm.sh"];
|
|
2901
3052
|
const cspConnectDomains = ["https://esm.sh"];
|
|
3053
|
+
for (const domain of extra?.resourceDomains ?? []) {
|
|
3054
|
+
if (!cspResourceDomains.includes(domain)) cspResourceDomains.push(domain);
|
|
3055
|
+
}
|
|
3056
|
+
for (const domain of extra?.connectDomains ?? []) {
|
|
3057
|
+
if (!cspConnectDomains.includes(domain)) cspConnectDomains.push(domain);
|
|
3058
|
+
}
|
|
2902
3059
|
if (resolver && "overrides" in resolver) {
|
|
2903
3060
|
const overrides = resolver.overrides;
|
|
2904
3061
|
if (overrides) {
|
|
@@ -2938,6 +3095,24 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
2938
3095
|
}
|
|
2939
3096
|
return rawResult;
|
|
2940
3097
|
}
|
|
3098
|
+
var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
|
|
3099
|
+
function warnComponentReference(template, toolName, logger) {
|
|
3100
|
+
const warned = warnedComponentReferences.get(template) ?? /* @__PURE__ */ new Set();
|
|
3101
|
+
if (warned.has(toolName)) return;
|
|
3102
|
+
warned.add(toolName);
|
|
3103
|
+
warnedComponentReferences.set(template, warned);
|
|
3104
|
+
logger.warn(
|
|
3105
|
+
`[FrontMCP] Tool "${toolName}": \`ui.template\` is a React component reference, which cannot be bundled (there is no source file to compile), so the widget would render an empty page. Use \`template: { file: './widget.tsx' }\` instead.`
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
function isReactElement(value) {
|
|
3109
|
+
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
3110
|
+
}
|
|
3111
|
+
function isDefinitelyReactComponent(template) {
|
|
3112
|
+
if (typeof template !== "function") return false;
|
|
3113
|
+
const proto = template.prototype;
|
|
3114
|
+
return typeof proto?.render === "function" || template["$$typeof"] !== void 0;
|
|
3115
|
+
}
|
|
2941
3116
|
function renderToolTemplate(options) {
|
|
2942
3117
|
const { toolName, input, output, template, resolver, platformType, sizing } = options;
|
|
2943
3118
|
const uiType = detectUIType(template);
|
|
@@ -2948,30 +3123,41 @@ function renderToolTemplate(options) {
|
|
|
2948
3123
|
output,
|
|
2949
3124
|
includeBridge: true,
|
|
2950
3125
|
resolver,
|
|
2951
|
-
sizing
|
|
3126
|
+
sizing,
|
|
3127
|
+
csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
|
|
2952
3128
|
};
|
|
2953
3129
|
let html2;
|
|
2954
3130
|
let hash = "";
|
|
2955
3131
|
let size = 0;
|
|
2956
3132
|
if (typeof template === "object" && template !== null && "file" in template) {
|
|
2957
|
-
const cspConfig = buildCspConfig(resolver);
|
|
2958
3133
|
const result = renderComponent(
|
|
2959
3134
|
{ source: template, inlineReact: resourceMode === "inline" },
|
|
2960
|
-
{ ...shellConfig, csp:
|
|
3135
|
+
{ ...shellConfig, csp: buildCspConfig(resolver, options.csp) }
|
|
2961
3136
|
);
|
|
2962
3137
|
html2 = result.html;
|
|
2963
3138
|
hash = result.hash;
|
|
2964
3139
|
size = result.size;
|
|
2965
3140
|
} else if (typeof template === "function") {
|
|
2966
|
-
|
|
3141
|
+
const helpers = createTemplateHelpers();
|
|
3142
|
+
const ctx = { input: input ?? {}, output: output ?? {}, helpers };
|
|
3143
|
+
let rawResult;
|
|
3144
|
+
let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
|
|
3145
|
+
if (isHtmlBuilder) {
|
|
3146
|
+
try {
|
|
3147
|
+
rawResult = template(ctx);
|
|
3148
|
+
isHtmlBuilder = !isReactElement(rawResult);
|
|
3149
|
+
} catch (error) {
|
|
3150
|
+
if (uiType !== "react") throw error;
|
|
3151
|
+
isHtmlBuilder = false;
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
if (!isHtmlBuilder) {
|
|
3155
|
+
warnComponentReference(template, toolName, options.logger ?? console);
|
|
2967
3156
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
2968
3157
|
html2 = shellResult.html;
|
|
2969
3158
|
hash = shellResult.hash;
|
|
2970
3159
|
size = shellResult.size;
|
|
2971
3160
|
} else {
|
|
2972
|
-
const helpers = createTemplateHelpers();
|
|
2973
|
-
const ctx = { input, output, helpers };
|
|
2974
|
-
const rawResult = template(ctx);
|
|
2975
3161
|
const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
|
|
2976
3162
|
if (wrapped) {
|
|
2977
3163
|
html2 = wrapped;
|
|
@@ -2983,7 +3169,7 @@ function renderToolTemplate(options) {
|
|
|
2983
3169
|
}
|
|
2984
3170
|
}
|
|
2985
3171
|
} else if (typeof template === "string") {
|
|
2986
|
-
const shellResult = buildShell(template, shellConfig);
|
|
3172
|
+
const shellResult = buildShell(uiType === "markdown" ? markdownToHtml(template) : template, shellConfig);
|
|
2987
3173
|
html2 = shellResult.html;
|
|
2988
3174
|
hash = shellResult.hash;
|
|
2989
3175
|
size = shellResult.size;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, safe Markdown to HTML conversion for `ui.template` strings.
|
|
3
|
+
*
|
|
4
|
+
* Supports headings, paragraphs, fenced code, ordered/unordered lists and inline
|
|
5
|
+
* bold, italic, code and http(s)/mailto links. Everything else is escaped text; raw HTML in the
|
|
6
|
+
* source is never passed through.
|
|
7
|
+
*/
|
|
8
|
+
export declare function markdownToHtml(source: string): string;
|
|
9
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/adapters/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA2BH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAgErD"}
|
|
@@ -36,6 +36,14 @@ export interface RenderToolTemplateOptions {
|
|
|
36
36
|
* shell, and is mirrored onto the returned `meta` as `ui/preferredHeight` etc.
|
|
37
37
|
*/
|
|
38
38
|
sizing?: WidgetSizing;
|
|
39
|
+
/**
|
|
40
|
+
* Extra origins the widget may load from (`resourceDomains`) or call (`connectDomains`), from the
|
|
41
|
+
* tool's `ui.csp`. Merged into the page's own Content-Security-Policy on every template path.
|
|
42
|
+
*/
|
|
43
|
+
csp?: {
|
|
44
|
+
connectDomains?: string[];
|
|
45
|
+
resourceDomains?: string[];
|
|
46
|
+
};
|
|
39
47
|
/**
|
|
40
48
|
* HTML-escape a plain string returned by a template function; results built with
|
|
41
49
|
* `html` / `trustedHtml` stay markup. When unset, strings that look like HTML render as
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../../src/adapters/template-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../../src/adapters/template-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAQnD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,QAAQ,EAAE,OAAO,CAAC;IAClB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,GAAG,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChE;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,yEAAyE;IACzE,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AA8FD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAoH/F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iife-generator.d.ts","sourceRoot":"","sources":["../../src/bridge-runtime/iife-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,EAAE,CAAC;IACvE,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,CA8G7E;
|
|
1
|
+
{"version":3,"file":"iife-generator.d.ts","sourceRoot":"","sources":["../../src/bridge-runtime/iife-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,EAAE,CAAC;IACvE,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,CA8G7E;AA8xCD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,EACvD,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAM,GACnD,MAAM,CAYR;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,QAAuB,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;CAK9B,CAAC"}
|
package/bridge-runtime/index.js
CHANGED
|
@@ -294,12 +294,38 @@ function __applyHostTheme(theme) {
|
|
|
294
294
|
} catch (e) {}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
function extractToolOutput(params) {
|
|
298
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
299
|
+
if (!params) return undefined;
|
|
300
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
301
|
+
var content = params.content;
|
|
302
|
+
if (Array.isArray(content)) {
|
|
303
|
+
for (var i = 0; i < content.length; i++) {
|
|
304
|
+
var item = content[i];
|
|
305
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
306
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return content;
|
|
311
|
+
}
|
|
312
|
+
|
|
297
313
|
function readInjectedData() {
|
|
298
314
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
299
315
|
if (typeof window !== 'undefined') {
|
|
300
316
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
301
317
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
302
318
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
319
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
320
|
+
// (toolOutput is the tool's structuredContent).
|
|
321
|
+
var oa = window.openai;
|
|
322
|
+
if (oa && typeof oa === 'object') {
|
|
323
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
324
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
325
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
326
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
303
329
|
}
|
|
304
330
|
return data;
|
|
305
331
|
}
|
|
@@ -348,7 +374,24 @@ var OpenAIAdapter = {
|
|
|
348
374
|
if (sdk.displayMode) {
|
|
349
375
|
context.hostContext.displayMode = sdk.displayMode;
|
|
350
376
|
}
|
|
351
|
-
//
|
|
377
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
378
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
379
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
380
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
381
|
+
context.toolInput = globals.toolInput;
|
|
382
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
383
|
+
}
|
|
384
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
385
|
+
context.structuredContent = globals.toolOutput;
|
|
386
|
+
context.notifyToolResult(globals.toolOutput);
|
|
387
|
+
}
|
|
388
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
389
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
390
|
+
}
|
|
391
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
392
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
352
395
|
return Promise.resolve();
|
|
353
396
|
},
|
|
354
397
|
callTool: function(context, name, args) {
|
|
@@ -495,9 +538,10 @@ var ExtAppsAdapter = {
|
|
|
495
538
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
496
539
|
break;
|
|
497
540
|
case 'ui/notifications/tool-result':
|
|
498
|
-
|
|
541
|
+
var resultOutput = extractToolOutput(params);
|
|
542
|
+
context.toolOutput = resultOutput;
|
|
499
543
|
context.structuredContent = params.structuredContent;
|
|
500
|
-
context.notifyToolResult(
|
|
544
|
+
context.notifyToolResult(resultOutput);
|
|
501
545
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
502
546
|
break;
|
|
503
547
|
case 'ui/notifications/host-context-changed':
|
|
@@ -551,6 +595,15 @@ var ExtAppsAdapter = {
|
|
|
551
595
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
552
596
|
});
|
|
553
597
|
},
|
|
598
|
+
sendNotification: function(method, params) {
|
|
599
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
600
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
601
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
602
|
+
}
|
|
603
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
604
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
605
|
+
return Promise.resolve();
|
|
606
|
+
},
|
|
554
607
|
sendHandshake: function(context) {
|
|
555
608
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
556
609
|
// been established yet. The response from the host will establish
|
|
@@ -616,7 +669,7 @@ var ExtAppsAdapter = {
|
|
|
616
669
|
var pending = this.pendingSize;
|
|
617
670
|
if (!pending) return;
|
|
618
671
|
this.pendingSize = null;
|
|
619
|
-
this.
|
|
672
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
620
673
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
621
674
|
}, function(err) {
|
|
622
675
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -643,13 +696,11 @@ var ExtAppsAdapter = {
|
|
|
643
696
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
644
697
|
},
|
|
645
698
|
setSize: function(context, size) {
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
var params = {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
aspectRatio: size && size.aspectRatio
|
|
652
|
-
};
|
|
699
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
700
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
701
|
+
var params = {};
|
|
702
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
703
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
653
704
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
654
705
|
// No host origin yet: keep only the latest size and send it when the
|
|
655
706
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -660,7 +711,7 @@ var ExtAppsAdapter = {
|
|
|
660
711
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
661
712
|
});
|
|
662
713
|
}
|
|
663
|
-
return this.
|
|
714
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
664
715
|
},
|
|
665
716
|
requestClose: function(context) {
|
|
666
717
|
return this.sendRequest('ui/close', {});
|
|
@@ -1083,7 +1134,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1083
1134
|
|
|
1084
1135
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1085
1136
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1086
|
-
// ext-apps sends ui/
|
|
1137
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1087
1138
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1088
1139
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1089
1140
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1185,7 +1236,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1185
1236
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1186
1237
|
|
|
1187
1238
|
// Add spinner for buttons
|
|
1188
|
-
var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
|
|
1239
|
+
var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" style="width:1em;height:1em;vertical-align:-0.125em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
|
|
1189
1240
|
if (target.tagName === 'BUTTON') {
|
|
1190
1241
|
target.innerHTML = spinner + 'Loading...';
|
|
1191
1242
|
}
|
|
@@ -1266,7 +1317,22 @@ function minifyJS(code) {
|
|
|
1266
1317
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1267
1318
|
return code;
|
|
1268
1319
|
}
|
|
1269
|
-
|
|
1320
|
+
const out = [];
|
|
1321
|
+
let inBlockComment = false;
|
|
1322
|
+
for (const raw of code.split("\n")) {
|
|
1323
|
+
const line = raw.trim();
|
|
1324
|
+
if (inBlockComment) {
|
|
1325
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1329
|
+
if (line.startsWith("/*")) {
|
|
1330
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
out.push(line);
|
|
1334
|
+
}
|
|
1335
|
+
return out.join("\n");
|
|
1270
1336
|
}
|
|
1271
1337
|
function generatePlatformBundle(platform, options = {}) {
|
|
1272
1338
|
const platformAdapters = {
|