@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/README.md +45 -9
- package/dist/index.d.ts +168 -20
- package/dist/index.js +288 -91
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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
|
-
|
|
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
|
|
65
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1280
|
+
originRegistry.postToNapplet(windowId, initMsg);
|
|
1197
1281
|
}
|
|
1198
1282
|
|
|
1199
|
-
// src/
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1718
|
+
port.postMessage(message);
|
|
1520
1719
|
}
|
|
1521
1720
|
function listen(listener) {
|
|
1522
|
-
|
|
1523
|
-
return () =>
|
|
1721
|
+
port.addEventListener("message", listener);
|
|
1722
|
+
return () => port.removeEventListener("message", listener);
|
|
1524
1723
|
}
|
|
1525
|
-
function
|
|
1526
|
-
return event.
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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((
|
|
1962
|
-
id:
|
|
1963
|
-
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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: (
|
|
2325
|
-
requestPermission: (
|
|
2326
|
-
{ type: "notify.permission.request", ...
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|