@kehto/shell 0.26.1 → 0.28.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/dist/index.js CHANGED
@@ -28,8 +28,7 @@ function hexToBytes(hex) {
28
28
  }
29
29
  function createSendToNapplet(originRegistry2) {
30
30
  return (windowId, msg) => {
31
- const win = originRegistry2.getIframeWindow(windowId);
32
- if (win) win.postMessage(msg, "*");
31
+ originRegistry2.postToNapplet(windowId, msg);
33
32
  };
34
33
  }
35
34
  function createRelayPoolAdapter(shellHooks, originRegistry2) {
@@ -61,8 +60,8 @@ function createRelayPoolAdapter(shellHooks, originRegistry2) {
61
60
  shellHooks.relayPool.untrackSubscription(subKey);
62
61
  },
63
62
  openScopedRelay(windowId, relayUrl, subId, filters, _sendFn) {
64
- const win = originRegistry2.getIframeWindow(windowId);
65
- if (win) shellHooks.relayPool.openScopedRelay(windowId, relayUrl, subId, filters, win);
63
+ const target = originRegistry2.getNappletTarget(windowId);
64
+ if (target) shellHooks.relayPool.openScopedRelay(windowId, relayUrl, subId, filters, target);
66
65
  },
67
66
  closeScopedRelay(windowId) {
68
67
  shellHooks.relayPool.closeScopedRelay(windowId);
@@ -329,7 +328,21 @@ function adaptHooks(shellHooks, deps) {
329
328
 
330
329
  // src/origin-registry.ts
331
330
  var registry = /* @__PURE__ */ new Map();
331
+ var spentWindows = /* @__PURE__ */ new WeakSet();
332
332
  var nextRegistrationId = 0;
333
+ function entryFor(windowId) {
334
+ for (const entry of registry.values()) {
335
+ if (entry.windowId === windowId) return entry;
336
+ }
337
+ return void 0;
338
+ }
339
+ function closePort(entry) {
340
+ const port = entry.port;
341
+ if (!port) return;
342
+ entry.port = void 0;
343
+ port.onmessage = null;
344
+ port.close();
345
+ }
333
346
  var originRegistry = {
334
347
  /**
335
348
  * Register a window reference with a windowId and optional identity metadata.
@@ -337,10 +350,17 @@ var originRegistry = {
337
350
  * @param win - The iframe's contentWindow reference
338
351
  * @param windowId - The unique identifier for this napplet window
339
352
  * @param identity - Optional NIP-5D identity metadata (dTag and aggregateHash)
353
+ * @throws {Error} When `win` already bound a NIP-5D port. NIP-5D identity is
354
+ * bound to one document; hosts must create a fresh iframe instead.
340
355
  */
341
356
  register(win, windowId, identity) {
357
+ if (spentWindows.has(win)) {
358
+ throw new Error("NIP-5D: cannot re-bind a napplet identity to an existing iframe; create a fresh iframe");
359
+ }
342
360
  for (const [registeredWin, entry] of registry.entries()) {
343
361
  if (registeredWin === win || entry.windowId === windowId) {
362
+ closePort(entry);
363
+ if (registeredWin !== win) spentWindows.add(registeredWin);
344
364
  registry.delete(registeredWin);
345
365
  }
346
366
  }
@@ -359,10 +379,73 @@ var originRegistry = {
359
379
  unregister(windowId) {
360
380
  for (const [win, entry] of registry.entries()) {
361
381
  if (entry.windowId === windowId) {
382
+ closePort(entry);
383
+ spentWindows.add(win);
362
384
  registry.delete(win);
363
385
  }
364
386
  }
365
387
  },
388
+ /**
389
+ * Bind the NIP-5D port from a frame's single `napplet.hello`.
390
+ *
391
+ * @param windowId - The registered window identifier
392
+ * @param port - The one port transferred with the hello
393
+ * @returns `false` when the window is unknown or already bound
394
+ */
395
+ bindPort(windowId, port) {
396
+ for (const [win, entry] of registry.entries()) {
397
+ if (entry.windowId !== windowId) continue;
398
+ if (entry.port || spentWindows.has(win)) return false;
399
+ entry.port = port;
400
+ spentWindows.add(win);
401
+ return true;
402
+ }
403
+ return false;
404
+ },
405
+ /**
406
+ * Look up the bound NIP-5D port for a window.
407
+ *
408
+ * @param windowId - The window identifier
409
+ * @returns The bound port, or undefined before `napplet.hello`
410
+ */
411
+ getPort(windowId) {
412
+ return entryFor(windowId)?.port;
413
+ },
414
+ /**
415
+ * Send one NAP envelope to a napplet over its bound port. Never falls back
416
+ * to `iframeWindow.postMessage`.
417
+ *
418
+ * @param windowId - The window identifier
419
+ * @param message - The envelope to send
420
+ * @returns `true` when a bound port carried the message
421
+ */
422
+ postToNapplet(windowId, message) {
423
+ const port = entryFor(windowId)?.port;
424
+ if (!port) return false;
425
+ port.postMessage(message);
426
+ return true;
427
+ },
428
+ /**
429
+ * Return a port-backed outbound target for one napplet window.
430
+ *
431
+ * @param windowId - The window identifier
432
+ * @returns A target that sends through {@link OriginRegistry.postToNapplet}, or null when unregistered
433
+ */
434
+ getNappletTarget(windowId) {
435
+ if (!entryFor(windowId)) return null;
436
+ return { postMessage: (message) => {
437
+ originRegistry.postToNapplet(windowId, message);
438
+ } };
439
+ },
440
+ /**
441
+ * Whether a Window already handed over (or lost) its one NIP-5D binding.
442
+ *
443
+ * @param win - The iframe `contentWindow`
444
+ * @returns `true` when the Window can never be registered again
445
+ */
446
+ isSpent(win) {
447
+ return spentWindows.has(win);
448
+ },
366
449
  /**
367
450
  * Look up the windowId for a given Window reference.
368
451
  *
@@ -425,9 +508,11 @@ var originRegistry = {
425
508
  getRegistrationId(win) {
426
509
  return registry.get(win)?.registrationId;
427
510
  },
428
- /** Clear all registrations. */
511
+ /** Clear all registrations and close every bound port. */
429
512
  clear() {
513
+ for (const entry of registry.values()) closePort(entry);
430
514
  registry.clear();
515
+ spentWindows = /* @__PURE__ */ new WeakSet();
431
516
  }
432
517
  };
433
518
 
@@ -913,7 +998,7 @@ var audioManager = {
913
998
  bump();
914
999
  },
915
1000
  /**
916
- * Mute or unmute an audio source and notify the napplet via postMessage.
1001
+ * Mute or unmute an audio source and notify the napplet over its bound NIP-5D port.
917
1002
  *
918
1003
  * @param windowId - The window identifier
919
1004
  * @param muted - True to mute, false to unmute
@@ -929,8 +1014,7 @@ var audioManager = {
929
1014
  src.muted = muted;
930
1015
  bump();
931
1016
  }
932
- const iframeWindow = originRegistry.getIframeWindow(windowId);
933
- if (iframeWindow) {
1017
+ if (originRegistry.getPort(windowId)) {
934
1018
  const muteEvent = {
935
1019
  kind: 29e3,
936
1020
  // INC_PEER — inlined numeric after Phase 24 DRIFT-01 shim removal
@@ -941,7 +1025,7 @@ var audioManager = {
941
1025
  id: `audio-mute-${windowId}-${Date.now()}`,
942
1026
  sig: ""
943
1027
  };
944
- iframeWindow.postMessage(["EVENT", "__shell__", muteEvent], "*");
1028
+ originRegistry.postToNapplet(windowId, ["EVENT", "__shell__", muteEvent]);
945
1029
  }
946
1030
  },
947
1031
  /**
@@ -1120,7 +1204,7 @@ function handleShellReady({
1120
1204
  }
1121
1205
  const environment = registerSourceSession({ hooks, origin, runtime, state, sourceRegistrationId, sourceWindow, windowId });
1122
1206
  if (!environment?.capabilities.domains.includes("shell")) return;
1123
- postShellInit(sourceWindow, environment);
1207
+ postShellInit(windowId, environment);
1124
1208
  state.initSent.set(sourceWindow, sourceRegistrationId);
1125
1209
  }
1126
1210
  function registerSourceSession(options) {
@@ -1187,16 +1271,17 @@ function resolveNip5dIdentity(hooks, windowId, sourceWindow, existingSession) {
1187
1271
  aggregateHash: originIdentity.aggregateHash
1188
1272
  });
1189
1273
  }
1190
- function postShellInit(win, environment) {
1274
+ function postShellInit(windowId, environment) {
1191
1275
  const initMsg = {
1192
1276
  type: "shell.init",
1193
1277
  capabilities: environment.capabilities,
1194
1278
  services: environment.services
1195
1279
  };
1196
- win.postMessage(initMsg, "*");
1280
+ originRegistry.postToNapplet(windowId, initMsg);
1197
1281
  }
1198
1282
 
1199
- // src/shell-bridge.ts
1283
+ // src/nip5d-transport.ts
1284
+ var NAPPLET_HELLO = "napplet.hello";
1200
1285
  function reportUnrouted(hooks, event, reason) {
1201
1286
  if (!hooks.onUnroutedMessage) return;
1202
1287
  const data = event.data;
@@ -1206,6 +1291,138 @@ function reportUnrouted(hooks, event, reason) {
1206
1291
  } catch {
1207
1292
  }
1208
1293
  }
1294
+ var REVOCATION_DETAIL = {
1295
+ "window-message": "A window-level message other than the single napplet.hello arrived from the frame",
1296
+ "second-hello": "A second napplet.hello arrived from the frame",
1297
+ "invalid-hello": "napplet.hello did not carry exactly one MessagePort",
1298
+ "second-load": "The frame loaded a second document (navigation or reload)",
1299
+ "port-closed": "The bound NIP-5D port closed or failed",
1300
+ host: "The host revoked the napplet binding"
1301
+ };
1302
+ function isHello(data) {
1303
+ return typeof data === "object" && data !== null && data.type === NAPPLET_HELLO;
1304
+ }
1305
+ function createNip5dTransport({ hooks, runtime, state: shellReadyState }) {
1306
+ const bindings = /* @__PURE__ */ new Map();
1307
+ function currentBinding(windowId) {
1308
+ const binding = bindings.get(windowId);
1309
+ if (binding && originRegistry.getPort(windowId) !== binding.port) {
1310
+ binding.port.onmessage = null;
1311
+ bindings.delete(windowId);
1312
+ return void 0;
1313
+ }
1314
+ return binding;
1315
+ }
1316
+ function revokeNapplet(windowId, reason = "host") {
1317
+ const binding = currentBinding(windowId);
1318
+ const registered = originRegistry.getIframeWindow(windowId) !== null;
1319
+ if (!binding && !registered) return;
1320
+ bindings.delete(windowId);
1321
+ if (binding) binding.port.onmessage = null;
1322
+ originRegistry.unregister(windowId);
1323
+ runtime.destroyWindow(windowId);
1324
+ runtime.sessionRegistry.unregister(windowId);
1325
+ shellReadyState.environments.delete(windowId);
1326
+ shellReadyState.sessionRegistration.delete(windowId);
1327
+ try {
1328
+ hooks.onNappletRevoked?.({ windowId, reason, detail: REVOCATION_DETAIL[reason] });
1329
+ } catch {
1330
+ }
1331
+ }
1332
+ function dispatchEnvelope(windowId, binding, msg) {
1333
+ if (typeof msg !== "object" || msg === null || typeof msg.type !== "string") return;
1334
+ const envelope = msg;
1335
+ try {
1336
+ hooks.onNappletMessage?.(windowId, envelope);
1337
+ } catch {
1338
+ }
1339
+ const context = {
1340
+ hooks,
1341
+ origin: binding.origin,
1342
+ runtime,
1343
+ state: shellReadyState,
1344
+ sourceRegistrationId: binding.registrationId,
1345
+ sourceWindow: binding.sourceWindow,
1346
+ windowId
1347
+ };
1348
+ if (!registerSourceSession(context)) return;
1349
+ if (envelope.type === "shell.ready") {
1350
+ handleShellReady(context);
1351
+ return;
1352
+ }
1353
+ runtime.handleMessage(windowId, envelope);
1354
+ }
1355
+ function bindPort(windowId, sourceWindow, origin, port) {
1356
+ if (!originRegistry.bindPort(windowId, port)) {
1357
+ port.close();
1358
+ revokeNapplet(windowId, "second-hello");
1359
+ return;
1360
+ }
1361
+ const binding = {
1362
+ port,
1363
+ sourceWindow,
1364
+ origin,
1365
+ registrationId: originRegistry.getRegistrationId(sourceWindow) ?? 0
1366
+ };
1367
+ bindings.set(windowId, binding);
1368
+ const isCurrent = () => currentBinding(windowId) === binding;
1369
+ port.onmessage = (portEvent) => {
1370
+ if (isCurrent()) dispatchEnvelope(windowId, binding, portEvent.data);
1371
+ };
1372
+ const onPortFailure = () => {
1373
+ if (isCurrent()) revokeNapplet(windowId, "port-closed");
1374
+ };
1375
+ port.addEventListener("close", onPortFailure);
1376
+ port.addEventListener("messageerror", onPortFailure);
1377
+ port.start();
1378
+ }
1379
+ function handleMessage(event) {
1380
+ const sourceWindow = event.source;
1381
+ if (!sourceWindow) {
1382
+ reportUnrouted(hooks, event, "no-source-window");
1383
+ return;
1384
+ }
1385
+ const windowId = originRegistry.getWindowId(sourceWindow);
1386
+ if (!windowId) {
1387
+ reportUnrouted(hooks, event, "unregistered-window");
1388
+ return;
1389
+ }
1390
+ if (currentBinding(windowId) || originRegistry.getPort(windowId)) {
1391
+ revokeNapplet(windowId, isHello(event.data) ? "second-hello" : "window-message");
1392
+ return;
1393
+ }
1394
+ if (!isHello(event.data)) {
1395
+ revokeNapplet(windowId, "window-message");
1396
+ return;
1397
+ }
1398
+ const ports = event.ports ?? [];
1399
+ if (ports.length !== 1) {
1400
+ for (const port of ports) port.close();
1401
+ revokeNapplet(windowId, "invalid-hello");
1402
+ return;
1403
+ }
1404
+ bindPort(windowId, sourceWindow, event.origin, ports[0]);
1405
+ }
1406
+ function observeFrame(frame, windowId) {
1407
+ let loads = 0;
1408
+ const onLoad = () => {
1409
+ loads += 1;
1410
+ if (loads > 1) revokeNapplet(windowId, "second-load");
1411
+ };
1412
+ frame.addEventListener("load", onLoad);
1413
+ return () => frame.removeEventListener("load", onLoad);
1414
+ }
1415
+ function destroy() {
1416
+ for (const [windowId, binding] of bindings) {
1417
+ binding.port.onmessage = null;
1418
+ if (originRegistry.getPort(windowId) === binding.port) originRegistry.unregister(windowId);
1419
+ }
1420
+ bindings.clear();
1421
+ }
1422
+ return { handleMessage, revokeNapplet, observeFrame, destroy };
1423
+ }
1424
+
1425
+ // src/shell-bridge.ts
1209
1426
  function createShellBridge(hooks) {
1210
1427
  const shellReadyState = createShellReadyState();
1211
1428
  const runtimeHooks = adaptHooks(hooks, {
@@ -1224,52 +1441,17 @@ function createShellBridge(hooks) {
1224
1441
  const environment = shellReadyState.environments.get(entry.windowId);
1225
1442
  if (!environment?.capabilities.domains.includes(domain)) continue;
1226
1443
  if (!runtime.aclState.check(entry.pubkey, entry.dTag, entry.aggregateHash, capability)) continue;
1227
- const win = originRegistry.getIframeWindow(entry.windowId);
1228
- if (!win) continue;
1229
- win.postMessage(envelope, "*");
1444
+ originRegistry.postToNapplet(entry.windowId, envelope);
1230
1445
  }
1231
1446
  }
1447
+ const transport = createNip5dTransport({ hooks, runtime, state: shellReadyState });
1232
1448
  return {
1233
- handleMessage(event) {
1234
- const sourceWindow = event.source;
1235
- if (!sourceWindow) {
1236
- reportUnrouted(hooks, event, "no-source-window");
1237
- return;
1238
- }
1239
- const windowId = originRegistry.getWindowId(sourceWindow);
1240
- if (!windowId) {
1241
- reportUnrouted(hooks, event, "unregistered-window");
1242
- return;
1243
- }
1244
- const msg = event.data;
1245
- if (typeof msg !== "object" || msg === null || typeof msg.type !== "string") return;
1246
- if (!registerSourceSession({
1247
- hooks,
1248
- origin: event.origin,
1249
- runtime,
1250
- state: shellReadyState,
1251
- sourceRegistrationId: originRegistry.getRegistrationId(sourceWindow) ?? 0,
1252
- sourceWindow,
1253
- windowId
1254
- })) return;
1255
- if (msg.type === "shell.ready") {
1256
- handleShellReady({
1257
- hooks,
1258
- origin: event.origin,
1259
- runtime,
1260
- state: shellReadyState,
1261
- sourceRegistrationId: originRegistry.getRegistrationId(sourceWindow) ?? 0,
1262
- sourceWindow,
1263
- windowId
1264
- });
1265
- return;
1266
- }
1267
- runtime.handleMessage(windowId, msg);
1268
- },
1449
+ handleMessage: transport.handleMessage,
1269
1450
  injectEvent(topic, payload) {
1270
1451
  runtime.injectEvent(topic, payload);
1271
1452
  },
1272
1453
  destroy() {
1454
+ transport.destroy();
1273
1455
  runtime.destroy();
1274
1456
  shellReadyState.clear();
1275
1457
  },
@@ -1284,6 +1466,8 @@ function createShellBridge(hooks) {
1284
1466
  const envelope = { type: "identity.changed", pubkey };
1285
1467
  publishToEligibleNapplets(envelope, "identity", "identity:read");
1286
1468
  },
1469
+ observeFrame: transport.observeFrame,
1470
+ revokeNapplet: transport.revokeNapplet,
1287
1471
  get runtime() {
1288
1472
  return runtime;
1289
1473
  }
@@ -1511,19 +1695,34 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
1511
1695
  const target = window;
1512
1696
  const allowed = new Set(domains);
1513
1697
  const requestTimeoutMs = 3e4;
1698
+ for (const rtcCtor of ["RTCPeerConnection", "webkitRTCPeerConnection"]) {
1699
+ try {
1700
+ Object.defineProperty(target, rtcCtor, {
1701
+ value: void 0,
1702
+ writable: false,
1703
+ configurable: false,
1704
+ enumerable: false
1705
+ });
1706
+ } catch {
1707
+ }
1708
+ }
1514
1709
  function id() {
1515
1710
  if (target.crypto?.randomUUID) return target.crypto.randomUUID();
1516
1711
  return `napplet-${Date.now()}-${Math.random().toString(16).slice(2)}`;
1517
1712
  }
1713
+ const channel = new target.MessageChannel();
1714
+ const port = channel.port1;
1715
+ port.start();
1716
+ target.parent.postMessage({ type: "napplet.hello" }, "*", [channel.port2]);
1518
1717
  function post(message) {
1519
- target.parent.postMessage(message, "*");
1718
+ port.postMessage(message);
1520
1719
  }
1521
1720
  function listen(listener) {
1522
- target.addEventListener("message", listener);
1523
- return () => target.removeEventListener("message", listener);
1721
+ port.addEventListener("message", listener);
1722
+ return () => port.removeEventListener("message", listener);
1524
1723
  }
1525
- function isParentMessage(event) {
1526
- return event.source === target.parent;
1724
+ function isShellMessage(event) {
1725
+ return event.currentTarget === port;
1527
1726
  }
1528
1727
  function request(message, resultType, map, options = {}) {
1529
1728
  const requestId = message.id ?? id();
@@ -1544,7 +1743,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
1544
1743
  reject(new DOMException("Aborted", "AbortError"));
1545
1744
  };
1546
1745
  off = listen((event) => {
1547
- if (!isParentMessage(event)) return;
1746
+ if (!isShellMessage(event)) return;
1548
1747
  const incoming = event.data;
1549
1748
  if (typeof incoming !== "object" || incoming === null) return;
1550
1749
  const msg = incoming;
@@ -1649,7 +1848,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
1649
1848
  resolveReady = resolve;
1650
1849
  });
1651
1850
  const off = listen((event) => {
1652
- if (!isParentMessage(event) || environment) return;
1851
+ if (!isShellMessage(event) || environment) return;
1653
1852
  const incoming = event.data;
1654
1853
  if (typeof incoming !== "object" || incoming === null) return;
1655
1854
  const message = incoming;
@@ -1703,7 +1902,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
1703
1902
  }
1704
1903
  function domainEventHandle(eventType, callback, select = (message) => message.event) {
1705
1904
  const off = listen((event) => {
1706
- if (!isParentMessage(event)) return;
1905
+ if (!isShellMessage(event)) return;
1707
1906
  const msg = event.data;
1708
1907
  if (typeof msg === "object" && msg !== null && msg.type === eventType) callback(select(msg));
1709
1908
  });
@@ -1714,7 +1913,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
1714
1913
  subscribe(filters, onEvent, onEose, options) {
1715
1914
  const subId = id();
1716
1915
  const off = listen((event) => {
1717
- if (!isParentMessage(event)) return;
1916
+ if (!isShellMessage(event)) return;
1718
1917
  const msg = event.data;
1719
1918
  if (typeof msg !== "object" || msg === null || msg.subId !== subId) return;
1720
1919
  if (msg.type === "relay.event") onEvent(msg.result);
@@ -1861,7 +2060,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
1861
2060
  state.pendingEvents.push(event);
1862
2061
  }
1863
2062
  listen((event) => {
1864
- if (!isParentMessage(event)) return;
2063
+ if (!isShellMessage(event)) return;
1865
2064
  const msg = event.data;
1866
2065
  if (typeof msg !== "object" || msg === null || typeof msg.type !== "string") return;
1867
2066
  if (msg.type === "inc.channel.opened" && typeof msg.channelId === "string" && typeof msg.peer === "string") {
@@ -1896,14 +2095,14 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
1896
2095
  const handlers = /* @__PURE__ */ new Set();
1897
2096
  const subscriptionId = id();
1898
2097
  const offEvent = listen((event) => {
1899
- if (!isParentMessage(event)) return;
2098
+ if (!isShellMessage(event)) return;
1900
2099
  const msg = event.data;
1901
2100
  if (typeof msg !== "object" || msg === null || msg.type !== "inc.event" || msg.topic !== stableTopic || typeof msg.sender !== "string") return;
1902
2101
  const delivered = Object.prototype.hasOwnProperty.call(msg, "payload") ? { topic: stableTopic, sender: msg.sender, payload: msg.payload } : { topic: stableTopic, sender: msg.sender };
1903
2102
  for (const handler of handlers) handler(delivered);
1904
2103
  });
1905
2104
  const offResult = listen((event) => {
1906
- if (!isParentMessage(event)) return;
2105
+ if (!isShellMessage(event)) return;
1907
2106
  const msg = event.data;
1908
2107
  if (typeof msg !== "object" || msg === null || msg.type !== "inc.subscribe.result" || msg.id !== subscriptionId) return;
1909
2108
  offResult();
@@ -1958,9 +2157,9 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
1958
2157
  return request(
1959
2158
  { type: "inc.channel.list" },
1960
2159
  "inc.channel.list.result",
1961
- (msg) => Object.freeze((Array.isArray(msg.channels) ? msg.channels : []).flatMap((channel) => channel && typeof channel === "object" && typeof channel.id === "string" && typeof channel.peer === "string" ? [Object.freeze({
1962
- id: channel.id,
1963
- peer: channel.peer
2160
+ (msg) => Object.freeze((Array.isArray(msg.channels) ? msg.channels : []).flatMap((channel2) => channel2 && typeof channel2 === "object" && typeof channel2.id === "string" && typeof channel2.peer === "string" ? [Object.freeze({
2161
+ id: channel2.id,
2162
+ peer: channel2.peer
1964
2163
  })] : []))
1965
2164
  );
1966
2165
  },
@@ -2022,7 +2221,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2022
2221
  getBadges: () => read("identity.getBadges", "badges", []),
2023
2222
  onChanged(handler) {
2024
2223
  const off = listen((event) => {
2025
- if (!isParentMessage(event)) return;
2224
+ if (!isShellMessage(event)) return;
2026
2225
  const msg = event.data;
2027
2226
  if (typeof msg !== "object" || msg === null || msg.type !== "identity.changed") return;
2028
2227
  if (typeof msg.pubkey === "string") handler(msg.pubkey);
@@ -2036,7 +2235,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2036
2235
  get: () => request({ type: "theme.get" }, "theme.get.result", (msg) => msg.theme),
2037
2236
  onChanged(handler) {
2038
2237
  const off = listen((event) => {
2039
- if (!isParentMessage(event)) return;
2238
+ if (!isShellMessage(event)) return;
2040
2239
  const msg = event.data;
2041
2240
  if (typeof msg === "object" && msg !== null && msg.type === "theme.changed") handler(msg.theme);
2042
2241
  });
@@ -2152,7 +2351,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2152
2351
  if (keysSmartForwardingInstalled) return;
2153
2352
  keysSmartForwardingInstalled = true;
2154
2353
  listen((event) => {
2155
- if (!isParentMessage(event)) return;
2354
+ if (!isShellMessage(event)) return;
2156
2355
  const msg = event.data;
2157
2356
  if (typeof msg === "object" && msg !== null && msg.type === "keys.bindings") {
2158
2357
  replaceKeyBindings(msg.bindings);
@@ -2206,7 +2405,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2206
2405
  }
2207
2406
  callbacks.add(callback);
2208
2407
  const off = listen((event) => {
2209
- if (!isParentMessage(event)) return;
2408
+ if (!isShellMessage(event)) return;
2210
2409
  const msg = event.data;
2211
2410
  if (typeof msg === "object" && msg !== null && msg.type === "keys.action" && msg.actionId === actionId) callback();
2212
2411
  });
@@ -2230,7 +2429,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2230
2429
  console.warn(`[KEHTO-MEDIA-${name}] Deprecated media API; use ${replacement}. Warned once per endpoint.`);
2231
2430
  };
2232
2431
  listen((event) => {
2233
- if (!isParentMessage(event)) return;
2432
+ if (!isShellMessage(event)) return;
2234
2433
  const msg = event.data;
2235
2434
  if (!msg || typeof msg !== "object" || typeof msg.sessionId !== "string") return;
2236
2435
  const sessionId = msg.sessionId;
@@ -2307,7 +2506,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2307
2506
  function makeNotify() {
2308
2507
  const on = (type, callback) => {
2309
2508
  const off = listen((event) => {
2310
- if (!isParentMessage(event)) return;
2509
+ if (!isShellMessage(event)) return;
2311
2510
  const msg = event.data;
2312
2511
  if (typeof msg === "object" && msg !== null && msg.type === type) callback(msg);
2313
2512
  });
@@ -2321,9 +2520,9 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2321
2520
  ),
2322
2521
  dismiss: (notificationId) => fire({ type: "notify.dismiss", notificationId }),
2323
2522
  badge: (count) => fire({ type: "notify.badge", count }),
2324
- registerChannel: (channel) => fire({ type: "notify.channel.register", ...channel }),
2325
- requestPermission: (channel) => request(
2326
- { type: "notify.permission.request", ...channel ? { channel } : {} },
2523
+ registerChannel: (channel2) => fire({ type: "notify.channel.register", ...channel2 }),
2524
+ requestPermission: (channel2) => request(
2525
+ { type: "notify.permission.request", ...channel2 ? { channel: channel2 } : {} },
2327
2526
  "notify.permission.result",
2328
2527
  (msg) => ({ granted: msg.granted })
2329
2528
  ),
@@ -2347,7 +2546,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2347
2546
  get: () => request({ type: "config.get" }, "config.values", (msg) => msg.values ?? {}),
2348
2547
  subscribe(callback) {
2349
2548
  const off = listen((event) => {
2350
- if (!isParentMessage(event)) return;
2549
+ if (!isShellMessage(event)) return;
2351
2550
  const msg = event.data;
2352
2551
  if (typeof msg === "object" && msg !== null && msg.type === "config.values") callback(msg.values ?? {});
2353
2552
  });
@@ -2360,7 +2559,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2360
2559
  openSettings: (options) => fire({ type: "config.openSettings", ...options && typeof options === "object" ? options : {} }),
2361
2560
  onSchemaError(callback) {
2362
2561
  const off = listen((event) => {
2363
- if (!isParentMessage(event)) return;
2562
+ if (!isShellMessage(event)) return;
2364
2563
  const msg = event.data;
2365
2564
  if (typeof msg === "object" && msg !== null && msg.type === "config.schemaError") callback(msg);
2366
2565
  });
@@ -2473,7 +2672,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2473
2672
  close: (server) => req("cvm.close", { server }, (msg) => voidResult(msg, "cvm close failed")),
2474
2673
  onEvent(callback) {
2475
2674
  const off = listen((event) => {
2476
- if (!isParentMessage(event)) return;
2675
+ if (!isShellMessage(event)) return;
2477
2676
  const msg = event.data;
2478
2677
  if (typeof msg === "object" && msg !== null && msg.type === "cvm.event" && msg.server !== void 0 && msg.message !== void 0) {
2479
2678
  callback(msg.server, msg.message);
@@ -2497,7 +2696,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2497
2696
  const subId = id();
2498
2697
  const handlers = { event: /* @__PURE__ */ new Set(), closed: /* @__PURE__ */ new Set() };
2499
2698
  const off = listen((event) => {
2500
- if (!isParentMessage(event)) return;
2699
+ if (!isShellMessage(event)) return;
2501
2700
  const msg = event.data;
2502
2701
  if (typeof msg !== "object" || msg === null || msg.subId !== subId) return;
2503
2702
  if (msg.type === "outbox.event") for (const handler of handlers.event) handler(msg.result);
@@ -2531,7 +2730,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2531
2730
  status: (uploadId) => request({ type: "upload.status", uploadId }, "upload.status.result", (msg) => fieldOrThrow(msg, "status", "upload status unavailable")),
2532
2731
  onStatus(callback) {
2533
2732
  const off = listen((event) => {
2534
- if (!isParentMessage(event)) return;
2733
+ if (!isShellMessage(event)) return;
2535
2734
  const msg = event.data;
2536
2735
  if (typeof msg === "object" && msg !== null && msg.type === "upload.status.changed" && msg.status !== void 0) callback(msg.status);
2537
2736
  });
@@ -2572,7 +2771,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2572
2771
  }
2573
2772
  }
2574
2773
  listen((event) => {
2575
- if (!isParentMessage(event)) return;
2774
+ if (!isShellMessage(event)) return;
2576
2775
  const message = event.data;
2577
2776
  if (!message || message.type !== "intent.deliver") return;
2578
2777
  const value = message.delivery;
@@ -2846,7 +3045,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2846
3045
  });
2847
3046
  };
2848
3047
  listen((event) => {
2849
- if (!isParentMessage(event)) return;
3048
+ if (!isShellMessage(event)) return;
2850
3049
  const incoming = event.data;
2851
3050
  if (!incoming || incoming.type !== "gamepad.state" || "id" in incoming || !valid(incoming)) return;
2852
3051
  state = copy(incoming);
@@ -2882,7 +3081,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2882
3081
  unsubscribe: (subscriptionId) => req("dm.unsubscribe", { subscriptionId }),
2883
3082
  onMessage(callback) {
2884
3083
  const off = listen((event) => {
2885
- if (!isParentMessage(event)) return;
3084
+ if (!isShellMessage(event)) return;
2886
3085
  const msg = event.data;
2887
3086
  if (typeof msg === "object" && msg !== null && msg.type === "dm.message" && typeof msg.subscriptionId === "string") {
2888
3087
  callback(msg.message, msg.subscriptionId);
@@ -2931,7 +3130,7 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
2931
3130
  post({ type: "llm.cancel", id: id(), requestId });
2932
3131
  };
2933
3132
  const off = listen((event) => {
2934
- if (!isParentMessage(event)) return;
3133
+ if (!isShellMessage(event)) return;
2935
3134
  const msg = event.data;
2936
3135
  if (typeof msg !== "object" || msg === null) return;
2937
3136
  if (type === "llm.generate" && msg.type === "llm.delta" && msg.requestId === requestId) {
@@ -3265,8 +3464,7 @@ function createKeysProxy(deps) {
3265
3464
  deps.runtime.handleMessage(windowId, envelope);
3266
3465
  },
3267
3466
  emit(windowId, envelope) {
3268
- const win = deps.originRegistry.getIframeWindow(windowId);
3269
- if (win) win.postMessage(envelope, "*");
3467
+ deps.originRegistry.postToNapplet(windowId, envelope);
3270
3468
  }
3271
3469
  };
3272
3470
  }
@@ -3278,8 +3476,7 @@ function createMediaProxy(deps) {
3278
3476
  deps.runtime.handleMessage(windowId, envelope);
3279
3477
  },
3280
3478
  emit(windowId, envelope) {
3281
- const win = deps.originRegistry.getIframeWindow(windowId);
3282
- if (win) win.postMessage(envelope, "*");
3479
+ deps.originRegistry.postToNapplet(windowId, envelope);
3283
3480
  }
3284
3481
  };
3285
3482
  }
@@ -3291,13 +3488,13 @@ function createNotifyProxy(deps) {
3291
3488
  deps.runtime.handleMessage(windowId, envelope);
3292
3489
  },
3293
3490
  emit(windowId, envelope) {
3294
- const win = deps.originRegistry.getIframeWindow(windowId);
3295
- if (win) win.postMessage(envelope, "*");
3491
+ deps.originRegistry.postToNapplet(windowId, envelope);
3296
3492
  }
3297
3493
  };
3298
3494
  }
3299
3495
  export {
3300
3496
  ALL_CAPABILITIES2 as ALL_CAPABILITIES,
3497
+ NAPPLET_HELLO,
3301
3498
  TOPICS,
3302
3499
  adaptHooks,
3303
3500
  audioManager,