@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.
@@ -1,3 +1,6 @@
1
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
2
+ const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
3
+ // __frontmcpCreateRequire
1
4
  // libs/uipack/src/bridge-runtime/iife-generator.ts
2
5
  function generateBridgeIIFE(options = {}) {
3
6
  const { debug = false, trustedOrigins = [], minify = false } = options;
@@ -265,12 +268,38 @@ function __applyHostTheme(theme) {
265
268
  } catch (e) {}
266
269
  }
267
270
 
271
+ function extractToolOutput(params) {
272
+ // A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
273
+ if (!params) return undefined;
274
+ if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
275
+ var content = params.content;
276
+ if (Array.isArray(content)) {
277
+ for (var i = 0; i < content.length; i++) {
278
+ var item = content[i];
279
+ if (item && item.type === 'text' && typeof item.text === 'string') {
280
+ try { return JSON.parse(item.text); } catch (e) { return item.text; }
281
+ }
282
+ }
283
+ }
284
+ return content;
285
+ }
286
+
268
287
  function readInjectedData() {
269
288
  var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
270
289
  if (typeof window !== 'undefined') {
271
290
  if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
272
291
  if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
273
292
  if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
293
+ // The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
294
+ // (toolOutput is the tool's structuredContent).
295
+ var oa = window.openai;
296
+ if (oa && typeof oa === 'object') {
297
+ if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
298
+ if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
299
+ if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
300
+ if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
301
+ }
302
+ }
274
303
  }
275
304
  return data;
276
305
  }
@@ -319,7 +348,24 @@ var OpenAIAdapter = {
319
348
  if (sdk.displayMode) {
320
349
  context.hostContext.displayMode = sdk.displayMode;
321
350
  }
322
- // Note: OpenAI SDK does not have an onContextChange equivalent
351
+ // The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
352
+ window.addEventListener('openai:set_globals', function(event) {
353
+ var globals = (event && event.detail && event.detail.globals) || window.openai || {};
354
+ if (globals.toolInput !== undefined && globals.toolInput !== null) {
355
+ context.toolInput = globals.toolInput;
356
+ window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
357
+ }
358
+ if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
359
+ context.structuredContent = globals.toolOutput;
360
+ context.notifyToolResult(globals.toolOutput);
361
+ }
362
+ if (globals.theme && globals.theme !== context.hostContext.theme) {
363
+ context.notifyContextChange({ theme: globals.theme });
364
+ }
365
+ if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
366
+ context.notifyContextChange({ displayMode: globals.displayMode });
367
+ }
368
+ });
323
369
  return Promise.resolve();
324
370
  },
325
371
  callTool: function(context, name, args) {
@@ -466,9 +512,10 @@ var ExtAppsAdapter = {
466
512
  window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
467
513
  break;
468
514
  case 'ui/notifications/tool-result':
469
- context.toolOutput = params.content;
515
+ var resultOutput = extractToolOutput(params);
516
+ context.toolOutput = resultOutput;
470
517
  context.structuredContent = params.structuredContent;
471
- context.notifyToolResult(params.content);
518
+ context.notifyToolResult(resultOutput);
472
519
  window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
473
520
  break;
474
521
  case 'ui/notifications/host-context-changed':
@@ -522,6 +569,15 @@ var ExtAppsAdapter = {
522
569
  window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
523
570
  });
524
571
  },
572
+ sendNotification: function(method, params) {
573
+ // JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
574
+ if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
575
+ return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
576
+ }
577
+ var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
578
+ window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
579
+ return Promise.resolve();
580
+ },
525
581
  sendHandshake: function(context) {
526
582
  // Send ui/initialize using '*' as target origin since TOFU hasn't
527
583
  // been established yet. The response from the host will establish
@@ -587,7 +643,7 @@ var ExtAppsAdapter = {
587
643
  var pending = this.pendingSize;
588
644
  if (!pending) return;
589
645
  this.pendingSize = null;
590
- this.sendRequest('ui/setSize', pending.params).then(function(result) {
646
+ this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
591
647
  for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
592
648
  }, function(err) {
593
649
  for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
@@ -614,13 +670,11 @@ var ExtAppsAdapter = {
614
670
  return this.sendRequest('ui/setDisplayMode', { mode: mode });
615
671
  },
616
672
  setSize: function(context, size) {
617
- // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
618
- // measured/desired widget dimensions to the host.
619
- var params = {
620
- height: size && size.height,
621
- width: size && size.width,
622
- aspectRatio: size && size.aspectRatio
623
- };
673
+ // Standard ext-apps sizing: the view tells the host its dimensions with the
674
+ // ui/notifications/size-changed notification ({ width?, height? } in px).
675
+ var params = {};
676
+ if (size && typeof size.width === 'number') params.width = size.width;
677
+ if (size && typeof size.height === 'number') params.height = size.height;
624
678
  if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
625
679
  // No host origin yet: keep only the latest size and send it when the
626
680
  // handshake settles; every caller's promise follows that one request.
@@ -631,7 +685,7 @@ var ExtAppsAdapter = {
631
685
  self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
632
686
  });
633
687
  }
634
- return this.sendRequest('ui/setSize', params);
688
+ return this.sendNotification('ui/notifications/size-changed', params);
635
689
  },
636
690
  requestClose: function(context) {
637
691
  return this.sendRequest('ui/close', {});
@@ -1054,7 +1108,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1054
1108
 
1055
1109
  // Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
1056
1110
  // Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
1057
- // ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
1111
+ // ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
1058
1112
  FrontMcpBridge.prototype.setSize = function(size) {
1059
1113
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1060
1114
  if (!this._adapter.setSize) return Promise.resolve();
@@ -1156,7 +1210,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
1156
1210
  target.classList.add('opacity-50', 'cursor-not-allowed');
1157
1211
 
1158
1212
  // Add spinner for buttons
1159
- 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>';
1213
+ 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>';
1160
1214
  if (target.tagName === 'BUTTON') {
1161
1215
  target.innerHTML = spinner + 'Loading...';
1162
1216
  }
@@ -1237,7 +1291,22 @@ function minifyJS(code) {
1237
1291
  if (code.length > MAX_MINIFY_CODE_LENGTH) {
1238
1292
  return code;
1239
1293
  }
1240
- return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
1294
+ const out = [];
1295
+ let inBlockComment = false;
1296
+ for (const raw of code.split("\n")) {
1297
+ const line = raw.trim();
1298
+ if (inBlockComment) {
1299
+ if (line.includes("*/")) inBlockComment = false;
1300
+ continue;
1301
+ }
1302
+ if (line === "" || line.startsWith("//")) continue;
1303
+ if (line.startsWith("/*")) {
1304
+ if (!line.includes("*/")) inBlockComment = true;
1305
+ continue;
1306
+ }
1307
+ out.push(line);
1308
+ }
1309
+ return out.join("\n");
1241
1310
  }
1242
1311
  function generatePlatformBundle(platform, options = {}) {
1243
1312
  const platformAdapters = {
@@ -1301,11 +1370,8 @@ function buildCSPDirectives(csp) {
1301
1370
  directives.push(`img-src ${imgSources.join(" ")}`);
1302
1371
  const fontSources = ["'self'", "data:", ...allResourceDomains];
1303
1372
  directives.push(`font-src ${fontSources.join(" ")}`);
1304
- if (validConnectDomains.length) {
1305
- directives.push(`connect-src ${validConnectDomains.join(" ")}`);
1306
- } else {
1307
- directives.push(`connect-src ${allResourceDomains.join(" ")}`);
1308
- }
1373
+ const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
1374
+ directives.push(`connect-src ${connectSources.join(" ")}`);
1309
1375
  directives.push("object-src 'self' data:");
1310
1376
  return directives;
1311
1377
  }
@@ -1314,26 +1380,34 @@ function buildCSPMetaTag(csp) {
1314
1380
  const content = directives.join("; ");
1315
1381
  return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
1316
1382
  }
1383
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
1317
1384
  function validateCSPDomain(domain) {
1318
- if (domain.startsWith("https://*.")) {
1319
- const rest = domain.slice(10);
1320
- return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
1385
+ if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
1386
+ if (/[?#]/.test(domain)) return false;
1387
+ if (domain.includes("*")) {
1388
+ const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
1389
+ return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
1321
1390
  }
1322
1391
  try {
1323
1392
  const url = new URL(domain);
1324
- return url.protocol === "https:";
1393
+ if (url.protocol === "https:" || url.protocol === "wss:") return true;
1394
+ return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
1325
1395
  } catch {
1326
1396
  return false;
1327
1397
  }
1328
1398
  }
1399
+ var reportedInvalidDomains = /* @__PURE__ */ new Set();
1329
1400
  function sanitizeCSPDomains(domains) {
1330
1401
  if (!domains) return [];
1331
1402
  const valid = [];
1332
1403
  for (const domain of domains) {
1333
1404
  if (validateCSPDomain(domain)) {
1334
1405
  valid.push(domain);
1335
- } else {
1336
- console.warn(`Invalid CSP domain ignored: ${domain}`);
1406
+ } else if (!reportedInvalidDomains.has(domain)) {
1407
+ reportedInvalidDomains.add(domain);
1408
+ console.warn(
1409
+ `Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
1410
+ );
1337
1411
  }
1338
1412
  }
1339
1413
  return valid;
@@ -1,3 +1,6 @@
1
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
2
+ const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
3
+ // __frontmcpCreateRequire
1
4
  // libs/uipack/src/types/ui-runtime.ts
2
5
  function isUIType(value) {
3
6
  return typeof value === "string" && ["html", "react", "mdx", "markdown", "auto"].includes(value);
@@ -1,3 +1,6 @@
1
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
2
+ const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
3
+ // __frontmcpCreateRequire
1
4
  // libs/uipack/src/typings/types.ts
2
5
  var DEFAULT_ALLOWED_PACKAGES = ["react", "react-dom", "react/jsx-runtime", "zod", "@frontmcp/*"];
3
6
  var DEFAULT_TYPE_FETCHER_OPTIONS = {
@@ -1,3 +1,6 @@
1
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
2
+ const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
3
+ // __frontmcpCreateRequire
1
4
  // libs/uipack/src/utils/index.ts
2
5
  function safeStringify(value, space) {
3
6
  const seen = /* @__PURE__ */ new WeakSet();