@kehto/paja 0.8.0 → 0.10.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 +68 -2
- package/dist/browser-host.js +2509 -1050
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-NFJCS5PG.js → chunk-EOCGC7A2.js} +363 -10
- package/dist/chunk-EOCGC7A2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +198 -6
- package/dist/index.js +11 -1
- package/package.json +11 -11
- package/dist/chunk-NFJCS5PG.js.map +0 -1
package/dist/browser-host.js
CHANGED
|
@@ -196,6 +196,9 @@ function countMap(_action) {
|
|
|
196
196
|
return { senderCap: "relay:read", recipientCap: null };
|
|
197
197
|
}
|
|
198
198
|
function identityMap(action) {
|
|
199
|
+
if (action === "changed") {
|
|
200
|
+
return { senderCap: null, recipientCap: "identity:read" };
|
|
201
|
+
}
|
|
199
202
|
if (action === "getPublicKey" || action === "getRelays") {
|
|
200
203
|
return { senderCap: null, recipientCap: null };
|
|
201
204
|
}
|
|
@@ -219,9 +222,12 @@ function storageMap(action) {
|
|
|
219
222
|
return { senderCap: null, recipientCap: null };
|
|
220
223
|
}
|
|
221
224
|
function incMap(action) {
|
|
222
|
-
if (action === "emit"
|
|
225
|
+
if (action === "emit") {
|
|
223
226
|
return { senderCap: "relay:write", recipientCap: "relay:read" };
|
|
224
227
|
}
|
|
228
|
+
if (action === "channel.emit" || action === "channel.broadcast" || action === "channel.list" || action === "channel.close") {
|
|
229
|
+
return { senderCap: null, recipientCap: null };
|
|
230
|
+
}
|
|
225
231
|
return { senderCap: "relay:read", recipientCap: null };
|
|
226
232
|
}
|
|
227
233
|
function configMap(action) {
|
|
@@ -256,11 +262,14 @@ function uploadMap(action) {
|
|
|
256
262
|
return { senderCap: "upload:write", recipientCap: null };
|
|
257
263
|
}
|
|
258
264
|
function intentMap(action) {
|
|
259
|
-
if (action === "changed" || action
|
|
265
|
+
if (action === "changed" || action === "invoke.result" || action === "available.result" || action === "handlers.result") {
|
|
260
266
|
return { senderCap: null, recipientCap: "intent:read" };
|
|
261
267
|
}
|
|
262
268
|
if (action === "invoke") return { senderCap: "intent:write", recipientCap: null };
|
|
263
|
-
|
|
269
|
+
if (action === "available" || action === "handlers") {
|
|
270
|
+
return { senderCap: "intent:read", recipientCap: null };
|
|
271
|
+
}
|
|
272
|
+
return { senderCap: null, recipientCap: null };
|
|
264
273
|
}
|
|
265
274
|
function dmMap(action) {
|
|
266
275
|
if (action === "message" || action.endsWith(".result") || action.endsWith(".error")) {
|
|
@@ -363,12 +372,14 @@ function evaluate(config, state, observation) {
|
|
|
363
372
|
}
|
|
364
373
|
const { initElapsedMs } = observation;
|
|
365
374
|
if (initElapsedMs !== void 0 && initElapsedMs < config.burstGuard.windowMs) {
|
|
366
|
-
const
|
|
375
|
+
const burstKey = observation.initKey ?? napplet;
|
|
376
|
+
const existingBurst = state.bursts[burstKey];
|
|
377
|
+
const activeBurst = existingBurst !== void 0 && now2 - existingBurst.windowStart < config.burstGuard.windowMs ? existingBurst : void 0;
|
|
367
378
|
const newBurst = {
|
|
368
|
-
count: (
|
|
369
|
-
windowStart:
|
|
379
|
+
count: (activeBurst?.count ?? 0) + 1,
|
|
380
|
+
windowStart: activeBurst?.windowStart ?? now2
|
|
370
381
|
};
|
|
371
|
-
const newBursts = { ...state.bursts, [
|
|
382
|
+
const newBursts = { ...state.bursts, [burstKey]: newBurst };
|
|
372
383
|
if (newBurst.count > config.burstGuard.maxOps) {
|
|
373
384
|
const burstAction = config.burstGuard.action;
|
|
374
385
|
return {
|
|
@@ -612,7 +623,7 @@ function deserialize2(json) {
|
|
|
612
623
|
return defaultConfig();
|
|
613
624
|
}
|
|
614
625
|
|
|
615
|
-
// ../../node_modules/.pnpm/@napplet+core@0.
|
|
626
|
+
// ../../node_modules/.pnpm/@napplet+core@0.31.0/node_modules/@napplet/core/dist/index.js
|
|
616
627
|
function createDispatch() {
|
|
617
628
|
const handlers = /* @__PURE__ */ new Map();
|
|
618
629
|
function registerNap2(domain, handler) {
|
|
@@ -705,11 +716,20 @@ function createSessionRegistry(notifier) {
|
|
|
705
716
|
getWindowId(pubkey) {
|
|
706
717
|
return byPubkey2.get(pubkey)?.windowId;
|
|
707
718
|
},
|
|
719
|
+
getWindowIdByDTag(dTag) {
|
|
720
|
+
let resolvedWindowId;
|
|
721
|
+
for (const [windowId, entry] of byWindowIdEntry) {
|
|
722
|
+
if (entry.dTag !== dTag) continue;
|
|
723
|
+
if (resolvedWindowId !== void 0) return void 0;
|
|
724
|
+
resolvedWindowId = windowId;
|
|
725
|
+
}
|
|
726
|
+
return resolvedWindowId;
|
|
727
|
+
},
|
|
708
728
|
isRegistered(windowId) {
|
|
709
729
|
return byWindowId2.has(windowId);
|
|
710
730
|
},
|
|
711
731
|
getAllEntries() {
|
|
712
|
-
return Array.from(
|
|
732
|
+
return Array.from(byWindowIdEntry.values());
|
|
713
733
|
},
|
|
714
734
|
getInstanceId(windowId) {
|
|
715
735
|
const pubkey = byWindowId2.get(windowId);
|
|
@@ -795,7 +815,7 @@ function bitsToCapabilities(bits) {
|
|
|
795
815
|
function toIdentity(pubkey, dTag, hash) {
|
|
796
816
|
return { pubkey, dTag, hash };
|
|
797
817
|
}
|
|
798
|
-
function createAclState(persistence, defaultPolicy = "permissive") {
|
|
818
|
+
function createAclState(persistence, defaultPolicy = "permissive", onMutation) {
|
|
799
819
|
let state = createState(defaultPolicy);
|
|
800
820
|
function ensureRuntimeDefaultEntry(id) {
|
|
801
821
|
if (state.defaultPolicy !== "permissive") return;
|
|
@@ -816,11 +836,13 @@ function createAclState(persistence, defaultPolicy = "permissive") {
|
|
|
816
836
|
const id = toIdentity(pubkey, dTag, aggregateHash);
|
|
817
837
|
ensureRuntimeDefaultEntry(id);
|
|
818
838
|
state = revoke(state, id, capToBit(capability));
|
|
839
|
+
onMutation?.({ type: "revoke", dTag, aggregateHash, capability });
|
|
819
840
|
},
|
|
820
841
|
block(pubkey, dTag, aggregateHash) {
|
|
821
842
|
const id = toIdentity(pubkey, dTag, aggregateHash);
|
|
822
843
|
ensureRuntimeDefaultEntry(id);
|
|
823
844
|
state = block(state, id);
|
|
845
|
+
onMutation?.({ type: "block", dTag, aggregateHash });
|
|
824
846
|
},
|
|
825
847
|
unblock(pubkey, dTag, aggregateHash) {
|
|
826
848
|
const id = toIdentity(pubkey, dTag, aggregateHash);
|
|
@@ -999,18 +1021,39 @@ function createManifestCache(persistence) {
|
|
|
999
1021
|
var REPLAY_WINDOW_SECONDS = 30;
|
|
1000
1022
|
function createReplayDetector(getReplayWindow) {
|
|
1001
1023
|
const seenEventIds = /* @__PURE__ */ new Map();
|
|
1024
|
+
function checkAndTrack(event, state) {
|
|
1025
|
+
const replayWindow = getReplayWindow?.() ?? REPLAY_WINDOW_SECONDS;
|
|
1026
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
1027
|
+
for (const [id, entry] of seenEventIds) {
|
|
1028
|
+
if (entry.state === "committed" && now2 - entry.timestamp > replayWindow) {
|
|
1029
|
+
seenEventIds.delete(id);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
if (now2 - event.created_at > replayWindow) return "invalid: event created_at too old";
|
|
1033
|
+
if (event.created_at - now2 > 10) return "invalid: event created_at in the future";
|
|
1034
|
+
if (seenEventIds.has(event.id)) return "duplicate: already processed";
|
|
1035
|
+
seenEventIds.set(event.id, { state, timestamp: now2 });
|
|
1036
|
+
return null;
|
|
1037
|
+
}
|
|
1002
1038
|
return {
|
|
1003
1039
|
check(event) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1040
|
+
return checkAndTrack(event, "committed");
|
|
1041
|
+
},
|
|
1042
|
+
reserve(event) {
|
|
1043
|
+
return checkAndTrack(event, "pending");
|
|
1044
|
+
},
|
|
1045
|
+
commit(eventId) {
|
|
1046
|
+
const entry = seenEventIds.get(eventId);
|
|
1047
|
+
if (entry?.state !== "pending") return;
|
|
1048
|
+
seenEventIds.set(eventId, {
|
|
1049
|
+
state: "committed",
|
|
1050
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
1051
|
+
});
|
|
1052
|
+
},
|
|
1053
|
+
release(eventId) {
|
|
1054
|
+
if (seenEventIds.get(eventId)?.state === "pending") {
|
|
1055
|
+
seenEventIds.delete(eventId);
|
|
1012
1056
|
}
|
|
1013
|
-
return null;
|
|
1014
1057
|
},
|
|
1015
1058
|
clear() {
|
|
1016
1059
|
seenEventIds.clear();
|
|
@@ -1135,7 +1178,7 @@ function createRelayHandler(context) {
|
|
|
1135
1178
|
handleRelayClose(context, windowId, msg, m);
|
|
1136
1179
|
return;
|
|
1137
1180
|
case "publish":
|
|
1138
|
-
handleRelayPublish(context, windowId,
|
|
1181
|
+
handleRelayPublish(context, windowId, m);
|
|
1139
1182
|
return;
|
|
1140
1183
|
case "publishEncrypted":
|
|
1141
1184
|
handleRelayPublishEncrypted(context, windowId, msg);
|
|
@@ -1259,29 +1302,106 @@ function handleRelayClose(context, windowId, msg, m) {
|
|
|
1259
1302
|
hooks.relayPool?.untrackSubscription(subKey);
|
|
1260
1303
|
hooks.sendToNapplet(windowId, { type: "relay.closed", subId, message: "" });
|
|
1261
1304
|
}
|
|
1262
|
-
function handleRelayPublish(context, windowId,
|
|
1263
|
-
const {
|
|
1264
|
-
const
|
|
1305
|
+
function handleRelayPublish(context, windowId, m) {
|
|
1306
|
+
const { hooks, replayDetector } = context;
|
|
1307
|
+
const eventTemplate = m.event;
|
|
1265
1308
|
const id = m.id ?? "";
|
|
1266
|
-
if (!
|
|
1267
|
-
hooks
|
|
1309
|
+
if (!eventTemplate || typeof eventTemplate !== "object") {
|
|
1310
|
+
sendRelayPublishResult(hooks, windowId, id, false, void 0, "invalid event template");
|
|
1268
1311
|
return;
|
|
1269
1312
|
}
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1313
|
+
const signer = hooks.auth.getSigner();
|
|
1314
|
+
const signEvent = signer?.signEvent?.bind(signer);
|
|
1315
|
+
if (!signEvent) {
|
|
1316
|
+
sendRelayPublishResult(hooks, windowId, id, false, void 0, "no signer configured");
|
|
1273
1317
|
return;
|
|
1274
1318
|
}
|
|
1319
|
+
void (async () => {
|
|
1320
|
+
try {
|
|
1321
|
+
const signed = await signEvent(eventTemplate);
|
|
1322
|
+
if (!signed) {
|
|
1323
|
+
sendRelayPublishResult(hooks, windowId, id, false, void 0, "signEvent returned no event");
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const replayResult = replayDetector.reserve(signed);
|
|
1327
|
+
if (replayResult !== null) {
|
|
1328
|
+
sendRelayPublishResult(hooks, windowId, id, false, void 0, replayResult);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
publishSignedRelayEvent(context, windowId, id, signed, (ok, error) => {
|
|
1332
|
+
if (ok) replayDetector.commit(signed.id);
|
|
1333
|
+
else replayDetector.release(signed.id);
|
|
1334
|
+
sendRelayPublishResult(hooks, windowId, id, ok, ok ? signed : void 0, error);
|
|
1335
|
+
});
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
sendRelayPublishResult(
|
|
1338
|
+
hooks,
|
|
1339
|
+
windowId,
|
|
1340
|
+
id,
|
|
1341
|
+
false,
|
|
1342
|
+
void 0,
|
|
1343
|
+
error instanceof Error && error.message ? error.message : "event signing failed"
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
})();
|
|
1347
|
+
}
|
|
1348
|
+
function sendRelayPublishResult(hooks, windowId, id, ok, event, error) {
|
|
1349
|
+
const result2 = ok && event ? { type: "relay.publish.result", id, ok: true, event, eventId: event.id } : { type: "relay.publish.result", id, ok: false, error: error ?? "publish failed" };
|
|
1350
|
+
hooks.sendToNapplet(windowId, result2);
|
|
1351
|
+
}
|
|
1352
|
+
function publishSignedRelayEvent(context, windowId, id, signed, reply) {
|
|
1353
|
+
const { eventBuffer, hooks } = context;
|
|
1354
|
+
let settled = false;
|
|
1355
|
+
const settle6 = (ok, error) => {
|
|
1356
|
+
if (settled) return;
|
|
1357
|
+
settled = true;
|
|
1358
|
+
if (ok) {
|
|
1359
|
+
try {
|
|
1360
|
+
eventBuffer.bufferAndDeliver(signed, windowId);
|
|
1361
|
+
} catch {
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
reply(ok, error);
|
|
1365
|
+
};
|
|
1275
1366
|
const relayService = relayServiceFrom(context);
|
|
1276
1367
|
if (relayService) {
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1368
|
+
const publishMessage = { type: "relay.publish", id, event: signed };
|
|
1369
|
+
try {
|
|
1370
|
+
relayService.handleMessage(windowId, publishMessage, (response) => {
|
|
1371
|
+
if (response.type !== "relay.publish.result" && response.type !== "relay.publish.error") {
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const result2 = response;
|
|
1375
|
+
const ok = response.type === "relay.publish.result" && (result2.ok ?? result2.accepted ?? false);
|
|
1376
|
+
settle6(ok, ok ? void 0 : result2.error ?? result2.message ?? "publish failed");
|
|
1377
|
+
});
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
settle6(
|
|
1380
|
+
false,
|
|
1381
|
+
error instanceof Error && error.message ? error.message : "relay service failed"
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
if (!hooks.relayPool?.isAvailable()) {
|
|
1387
|
+
settle6(false, "no relay pool available");
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
try {
|
|
1391
|
+
const publishResult = hooks.relayPool.publish(signed);
|
|
1392
|
+
void Promise.resolve(publishResult).then(
|
|
1393
|
+
() => settle6(true),
|
|
1394
|
+
(error) => settle6(
|
|
1395
|
+
false,
|
|
1396
|
+
error instanceof Error && error.message ? error.message : "publish failed"
|
|
1397
|
+
)
|
|
1398
|
+
);
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
settle6(
|
|
1401
|
+
false,
|
|
1402
|
+
error instanceof Error && error.message ? error.message : "publish failed"
|
|
1403
|
+
);
|
|
1283
1404
|
}
|
|
1284
|
-
eventBuffer.bufferAndDeliver(event, windowId);
|
|
1285
1405
|
}
|
|
1286
1406
|
function handleRelayPublishEncrypted(context, windowId, msg) {
|
|
1287
1407
|
const { hooks } = context;
|
|
@@ -1313,7 +1433,7 @@ function handleRelayPublishEncrypted(context, windowId, msg) {
|
|
|
1313
1433
|
publishEncrypted(context, windowId, id, recipient, encryption, eventTemplate, replyPe);
|
|
1314
1434
|
}
|
|
1315
1435
|
function publishEncrypted(context, windowId, id, recipient, encryption, eventTemplate, replyPe) {
|
|
1316
|
-
const {
|
|
1436
|
+
const { hooks } = context;
|
|
1317
1437
|
const peSigner = hooks.auth.getSigner();
|
|
1318
1438
|
if (!peSigner) return;
|
|
1319
1439
|
(async () => {
|
|
@@ -1326,46 +1446,14 @@ function publishEncrypted(context, windowId, id, recipient, encryption, eventTem
|
|
|
1326
1446
|
replyPe(false, { error: "signEvent returned null" });
|
|
1327
1447
|
return;
|
|
1328
1448
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
} catch {
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1449
|
+
publishSignedRelayEvent(context, windowId, id, signed, (ok, error) => {
|
|
1450
|
+
replyPe(ok, ok ? { event: signed, eventId: signed.id } : { error: error ?? "publish failed" });
|
|
1451
|
+
});
|
|
1335
1452
|
} catch (err) {
|
|
1336
1453
|
replyPe(false, { error: err?.message ?? "encryption failed" });
|
|
1337
1454
|
}
|
|
1338
1455
|
})();
|
|
1339
1456
|
}
|
|
1340
|
-
function publishSignedEncrypted(context, windowId, id, signed, replyPe) {
|
|
1341
|
-
const { hooks } = context;
|
|
1342
|
-
const relayService = relayServiceFrom(context);
|
|
1343
|
-
if (!relayService) {
|
|
1344
|
-
if (hooks.relayPool?.isAvailable()) {
|
|
1345
|
-
hooks.relayPool.publish(signed);
|
|
1346
|
-
replyPe(true, { event: signed, eventId: signed.id });
|
|
1347
|
-
} else {
|
|
1348
|
-
replyPe(false, { error: "no relay pool available" });
|
|
1349
|
-
}
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
const publishMsg = { type: "relay.publish", id, event: signed };
|
|
1353
|
-
let replied = false;
|
|
1354
|
-
relayService.handleMessage(windowId, publishMsg, (resp) => {
|
|
1355
|
-
if (replied) return;
|
|
1356
|
-
const r = resp;
|
|
1357
|
-
if (typeof r.type !== "string" || !r.type.startsWith("relay.publish")) return;
|
|
1358
|
-
const okVal = r.ok ?? r.accepted ?? false;
|
|
1359
|
-
replied = true;
|
|
1360
|
-
const publishResult = { event: signed, eventId: signed.id };
|
|
1361
|
-
if (!okVal) publishResult.error = r.error ?? r.message ?? "publish failed";
|
|
1362
|
-
replyPe(okVal, publishResult);
|
|
1363
|
-
});
|
|
1364
|
-
if (!replied) {
|
|
1365
|
-
replied = true;
|
|
1366
|
-
replyPe(true, { event: signed, eventId: signed.id });
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
1457
|
function handleRelayQuery(context, windowId, m) {
|
|
1370
1458
|
const { hooks, serviceRegistry, eventBuffer } = context;
|
|
1371
1459
|
const id = m.id ?? "";
|
|
@@ -1491,6 +1579,61 @@ function handleRelayQuery(context, windowId, m) {
|
|
|
1491
1579
|
}
|
|
1492
1580
|
}, relayUrls);
|
|
1493
1581
|
}
|
|
1582
|
+
var RUNTIME_THEME_FALLBACK = {
|
|
1583
|
+
colors: { background: "#0a0a0a", text: "#e0e0e0", primary: "#7aa2f7" }
|
|
1584
|
+
};
|
|
1585
|
+
var IDENTITY_SAFE_DEFAULTS = {
|
|
1586
|
+
"identity.getPublicKey": { pubkey: "" },
|
|
1587
|
+
"identity.getRelays": { relays: {} },
|
|
1588
|
+
"identity.getProfile": { profile: null },
|
|
1589
|
+
"identity.getFollows": { pubkeys: [] },
|
|
1590
|
+
"identity.getList": { entries: [] },
|
|
1591
|
+
"identity.getZaps": { zaps: [] },
|
|
1592
|
+
"identity.getMutes": { pubkeys: [] },
|
|
1593
|
+
"identity.getBlocked": { pubkeys: [] },
|
|
1594
|
+
"identity.getBadges": { badges: [] }
|
|
1595
|
+
};
|
|
1596
|
+
function isRuntimeOwnedIdentityOrThemeRequest(type) {
|
|
1597
|
+
return type === "theme.get" || type in IDENTITY_SAFE_DEFAULTS;
|
|
1598
|
+
}
|
|
1599
|
+
function createRuntimeOwnedResultEnvelope(type, id) {
|
|
1600
|
+
return Object.assign({ type }, { id });
|
|
1601
|
+
}
|
|
1602
|
+
function isIdentityOrThemeMessage(message) {
|
|
1603
|
+
return message.type.startsWith("identity.") || message.type.startsWith("theme.");
|
|
1604
|
+
}
|
|
1605
|
+
function createIntentPolicyDenial(message) {
|
|
1606
|
+
const id = message.id ?? "";
|
|
1607
|
+
if (message.type === "intent.invoke") {
|
|
1608
|
+
return {
|
|
1609
|
+
type: "intent.invoke.result",
|
|
1610
|
+
id,
|
|
1611
|
+
result: { ok: false, error: "invoke rejected" }
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
if (message.type === "intent.available" || message.type === "intent.handlers") {
|
|
1615
|
+
return {
|
|
1616
|
+
type: `${message.type}.result`,
|
|
1617
|
+
id,
|
|
1618
|
+
error: "intent request denied"
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
return void 0;
|
|
1622
|
+
}
|
|
1623
|
+
function createCanonicalDomainResult(message) {
|
|
1624
|
+
if (!isRuntimeOwnedIdentityOrThemeRequest(message.type)) return void 0;
|
|
1625
|
+
const id = message.id ?? "";
|
|
1626
|
+
if (message.type === "theme.get") {
|
|
1627
|
+
return Object.assign(
|
|
1628
|
+
createRuntimeOwnedResultEnvelope("theme.get.result", id),
|
|
1629
|
+
{ theme: RUNTIME_THEME_FALLBACK }
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
return Object.assign(
|
|
1633
|
+
createRuntimeOwnedResultEnvelope(`${message.type}.result`, id),
|
|
1634
|
+
IDENTITY_SAFE_DEFAULTS[message.type]
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1494
1637
|
function createIdentityHandler(context) {
|
|
1495
1638
|
return function handleIdentityMessage(windowId, msg) {
|
|
1496
1639
|
const { hooks, serviceRegistry } = context;
|
|
@@ -1499,14 +1642,12 @@ function createIdentityHandler(context) {
|
|
|
1499
1642
|
identityService.handleMessage(windowId, msg, (resp) => hooks.sendToNapplet(windowId, resp));
|
|
1500
1643
|
return;
|
|
1501
1644
|
}
|
|
1502
|
-
const id = msg.id ?? "";
|
|
1503
1645
|
const action = msg.type.slice("identity.".length);
|
|
1504
1646
|
const signer = hooks.auth.getSigner();
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
hooks.sendToNapplet(windowId, { type: `${msg.type}.result`, id, ...payload });
|
|
1647
|
+
const fallback = createCanonicalDomainResult(msg);
|
|
1648
|
+
if (!fallback) return;
|
|
1649
|
+
const sendResult = (payload = {}) => {
|
|
1650
|
+
hooks.sendToNapplet(windowId, { ...fallback, ...payload });
|
|
1510
1651
|
};
|
|
1511
1652
|
switch (action) {
|
|
1512
1653
|
case "getPublicKey":
|
|
@@ -1514,14 +1655,14 @@ function createIdentityHandler(context) {
|
|
|
1514
1655
|
sendResult({ pubkey: "" });
|
|
1515
1656
|
return;
|
|
1516
1657
|
}
|
|
1517
|
-
Promise.resolve(signer.getPublicKey?.()).then((pubkey) => sendResult({ pubkey: pubkey ?? "" })).catch((
|
|
1658
|
+
Promise.resolve(signer.getPublicKey?.()).then((pubkey) => sendResult({ pubkey: pubkey ?? "" })).catch(() => sendResult({ pubkey: "" }));
|
|
1518
1659
|
return;
|
|
1519
1660
|
case "getRelays":
|
|
1520
1661
|
if (!signer) {
|
|
1521
|
-
|
|
1662
|
+
sendResult();
|
|
1522
1663
|
return;
|
|
1523
1664
|
}
|
|
1524
|
-
Promise.resolve(signer.getRelays?.() ?? {}).then((relays) => sendResult({ relays })).catch((
|
|
1665
|
+
Promise.resolve(signer.getRelays?.() ?? {}).then((relays) => sendResult({ relays })).catch(() => sendResult());
|
|
1525
1666
|
return;
|
|
1526
1667
|
case "getProfile":
|
|
1527
1668
|
sendResult({ profile: null });
|
|
@@ -1545,7 +1686,7 @@ function createIdentityHandler(context) {
|
|
|
1545
1686
|
sendResult({ badges: [] });
|
|
1546
1687
|
return;
|
|
1547
1688
|
default:
|
|
1548
|
-
|
|
1689
|
+
return;
|
|
1549
1690
|
}
|
|
1550
1691
|
};
|
|
1551
1692
|
}
|
|
@@ -1608,7 +1749,8 @@ function isValidFilterArray(value) {
|
|
|
1608
1749
|
function isPlainObject(value) {
|
|
1609
1750
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1610
1751
|
}
|
|
1611
|
-
|
|
1752
|
+
var MAX_CHANNELS_PER_WINDOW = 32;
|
|
1753
|
+
function createIncRuntime(hooks, sessionRegistry2, authorizeChannelOpen) {
|
|
1612
1754
|
const state = {
|
|
1613
1755
|
subscriptions: /* @__PURE__ */ new Map(),
|
|
1614
1756
|
channels: /* @__PURE__ */ new Map(),
|
|
@@ -1616,12 +1758,15 @@ function createIncRuntime(hooks, sessionRegistry2) {
|
|
|
1616
1758
|
};
|
|
1617
1759
|
return {
|
|
1618
1760
|
handleMessage(windowId, msg) {
|
|
1619
|
-
handleIncMessage(state, hooks, sessionRegistry2, windowId, msg);
|
|
1761
|
+
handleIncMessage(state, hooks, sessionRegistry2, authorizeChannelOpen, windowId, msg);
|
|
1620
1762
|
},
|
|
1621
1763
|
destroyWindow(windowId) {
|
|
1622
1764
|
removeWindowChannels(state, hooks, windowId);
|
|
1623
1765
|
removeWindowSubscriptions(state, windowId);
|
|
1624
1766
|
},
|
|
1767
|
+
revokeWindow(windowId) {
|
|
1768
|
+
revokeWindowChannels(state, hooks, windowId);
|
|
1769
|
+
},
|
|
1625
1770
|
clear() {
|
|
1626
1771
|
state.subscriptions.clear();
|
|
1627
1772
|
state.channels.clear();
|
|
@@ -1659,18 +1804,18 @@ function peerOf(state, channelId, self) {
|
|
|
1659
1804
|
return null;
|
|
1660
1805
|
}
|
|
1661
1806
|
function resolveTarget(sessionRegistry2, target) {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
return
|
|
1807
|
+
return sessionRegistry2.getWindowIdByDTag(target) ?? null;
|
|
1808
|
+
}
|
|
1809
|
+
function hasReachedChannelLimit(state, windowId) {
|
|
1810
|
+
return (state.channelsByWindow.get(windowId)?.size ?? 0) >= MAX_CHANNELS_PER_WINDOW;
|
|
1666
1811
|
}
|
|
1667
|
-
function handleIncMessage(state, hooks, sessionRegistry2, windowId, msg) {
|
|
1812
|
+
function handleIncMessage(state, hooks, sessionRegistry2, authorizeChannelOpen, windowId, msg) {
|
|
1668
1813
|
const m = msg;
|
|
1669
1814
|
const dotIdx = msg.type.indexOf(".");
|
|
1670
1815
|
const action = msg.type.slice(dotIdx + 1);
|
|
1671
1816
|
switch (action) {
|
|
1672
1817
|
case "emit":
|
|
1673
|
-
handleEmit(state, hooks, windowId, m);
|
|
1818
|
+
handleEmit(state, hooks, sessionRegistry2, windowId, m);
|
|
1674
1819
|
return;
|
|
1675
1820
|
case "subscribe":
|
|
1676
1821
|
handleSubscribe(state, hooks, windowId, m);
|
|
@@ -1679,16 +1824,16 @@ function handleIncMessage(state, hooks, sessionRegistry2, windowId, msg) {
|
|
|
1679
1824
|
handleUnsubscribe(state, windowId, m);
|
|
1680
1825
|
return;
|
|
1681
1826
|
case "channel.open":
|
|
1682
|
-
handleChannelOpen(state, hooks, sessionRegistry2, windowId, m);
|
|
1827
|
+
handleChannelOpen(state, hooks, sessionRegistry2, authorizeChannelOpen, windowId, m);
|
|
1683
1828
|
return;
|
|
1684
1829
|
case "channel.emit":
|
|
1685
|
-
handleChannelEmit(state, hooks, windowId, m);
|
|
1830
|
+
handleChannelEmit(state, hooks, sessionRegistry2, windowId, m);
|
|
1686
1831
|
return;
|
|
1687
1832
|
case "channel.broadcast":
|
|
1688
|
-
handleChannelBroadcast(state, hooks, windowId, m);
|
|
1833
|
+
handleChannelBroadcast(state, hooks, sessionRegistry2, windowId, m);
|
|
1689
1834
|
return;
|
|
1690
1835
|
case "channel.list":
|
|
1691
|
-
handleChannelList(state, hooks, windowId, m);
|
|
1836
|
+
handleChannelList(state, hooks, sessionRegistry2, windowId, m);
|
|
1692
1837
|
return;
|
|
1693
1838
|
case "channel.close":
|
|
1694
1839
|
handleChannelClose(state, hooks, windowId, m);
|
|
@@ -1697,14 +1842,16 @@ function handleIncMessage(state, hooks, sessionRegistry2, windowId, msg) {
|
|
|
1697
1842
|
return;
|
|
1698
1843
|
}
|
|
1699
1844
|
}
|
|
1700
|
-
function handleEmit(state, hooks, windowId, m) {
|
|
1845
|
+
function handleEmit(state, hooks, sessionRegistry2, windowId, m) {
|
|
1701
1846
|
const topic = m.topic ?? "";
|
|
1847
|
+
const sender = sessionRegistry2.getEntryByWindowId(windowId)?.dTag;
|
|
1848
|
+
if (!sender) return;
|
|
1702
1849
|
if (!topic) return;
|
|
1703
1850
|
const subscribers = state.subscriptions.get(topic);
|
|
1704
1851
|
if (!subscribers) return;
|
|
1705
1852
|
for (const subscriberWindowId of subscribers) {
|
|
1706
1853
|
if (subscriberWindowId !== windowId) {
|
|
1707
|
-
hooks.sendToNapplet(subscriberWindowId, { type: "inc.event", topic, payload: m.payload, sender
|
|
1854
|
+
hooks.sendToNapplet(subscriberWindowId, { type: "inc.event", topic, payload: m.payload, sender });
|
|
1708
1855
|
}
|
|
1709
1856
|
}
|
|
1710
1857
|
}
|
|
@@ -1731,51 +1878,96 @@ function handleUnsubscribe(state, windowId, m) {
|
|
|
1731
1878
|
subscriptions.delete(windowId);
|
|
1732
1879
|
if (subscriptions.size === 0) state.subscriptions.delete(topic);
|
|
1733
1880
|
}
|
|
1734
|
-
function handleChannelOpen(state, hooks, sessionRegistry2, windowId, m) {
|
|
1881
|
+
function handleChannelOpen(state, hooks, sessionRegistry2, authorizeChannelOpen, windowId, m) {
|
|
1735
1882
|
const id = m.id ?? "";
|
|
1736
1883
|
const peerWindow = resolveTarget(sessionRegistry2, m.target ?? "");
|
|
1737
|
-
|
|
1884
|
+
const openerDTag = sessionRegistry2.getEntryByWindowId(windowId)?.dTag;
|
|
1885
|
+
const peerDTag = peerWindow ? sessionRegistry2.getEntryByWindowId(peerWindow)?.dTag : void 0;
|
|
1886
|
+
if (!peerWindow || !openerDTag || !peerDTag) {
|
|
1738
1887
|
hooks.sendToNapplet(windowId, { type: "inc.channel.open.result", id, error: "target not found" });
|
|
1739
1888
|
return;
|
|
1740
1889
|
}
|
|
1890
|
+
if (!authorizeChannelOpen(peerWindow, m)) {
|
|
1891
|
+
hooks.sendToNapplet(windowId, { type: "inc.channel.open.result", id, error: "target denied" });
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
if (hasReachedChannelLimit(state, windowId) || hasReachedChannelLimit(state, peerWindow)) {
|
|
1895
|
+
hooks.sendToNapplet(windowId, { type: "inc.channel.open.result", id, error: "channel limit reached" });
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1741
1898
|
const channelId = hooks.crypto.randomUUID().replace(/-/g, "").slice(0, 32);
|
|
1742
1899
|
addChannel(state, channelId, windowId, peerWindow);
|
|
1743
|
-
|
|
1900
|
+
try {
|
|
1901
|
+
hooks.sendToNapplet(peerWindow, { type: "inc.channel.opened", channelId, peer: openerDTag });
|
|
1902
|
+
} catch {
|
|
1903
|
+
removeChannel(state, channelId);
|
|
1904
|
+
hooks.sendToNapplet(windowId, { type: "inc.channel.open.result", id, error: "target unavailable" });
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
try {
|
|
1908
|
+
hooks.sendToNapplet(windowId, { type: "inc.channel.open.result", id, channelId, peer: peerDTag });
|
|
1909
|
+
} catch {
|
|
1910
|
+
teardownChannel(state, hooks, channelId);
|
|
1911
|
+
}
|
|
1744
1912
|
}
|
|
1745
|
-
function handleChannelEmit(state, hooks, windowId, m) {
|
|
1913
|
+
function handleChannelEmit(state, hooks, sessionRegistry2, windowId, m) {
|
|
1746
1914
|
const peer = peerOf(state, m.channelId ?? "", windowId);
|
|
1747
|
-
|
|
1748
|
-
|
|
1915
|
+
const sender = sessionRegistry2.getEntryByWindowId(windowId)?.dTag;
|
|
1916
|
+
if (peer && sender) {
|
|
1917
|
+
hooks.sendToNapplet(peer, { type: "inc.channel.event", channelId: m.channelId ?? "", sender, payload: m.payload });
|
|
1749
1918
|
}
|
|
1750
1919
|
}
|
|
1751
|
-
function handleChannelBroadcast(state, hooks, windowId, m) {
|
|
1920
|
+
function handleChannelBroadcast(state, hooks, sessionRegistry2, windowId, m) {
|
|
1752
1921
|
const channels = state.channelsByWindow.get(windowId);
|
|
1753
|
-
|
|
1922
|
+
const sender = sessionRegistry2.getEntryByWindowId(windowId)?.dTag;
|
|
1923
|
+
if (!channels || !sender) return;
|
|
1754
1924
|
for (const channelId of channels) {
|
|
1755
1925
|
const peer = peerOf(state, channelId, windowId);
|
|
1756
1926
|
if (peer) {
|
|
1757
|
-
hooks.sendToNapplet(peer, { type: "inc.channel.event", channelId, sender
|
|
1927
|
+
hooks.sendToNapplet(peer, { type: "inc.channel.event", channelId, sender, payload: m.payload });
|
|
1758
1928
|
}
|
|
1759
1929
|
}
|
|
1760
1930
|
}
|
|
1761
|
-
function handleChannelList(state, hooks, windowId, m) {
|
|
1931
|
+
function handleChannelList(state, hooks, sessionRegistry2, windowId, m) {
|
|
1762
1932
|
const channels = [];
|
|
1763
1933
|
const set = state.channelsByWindow.get(windowId);
|
|
1764
1934
|
if (set) {
|
|
1765
1935
|
for (const channelId of set) {
|
|
1766
1936
|
const peer = peerOf(state, channelId, windowId);
|
|
1767
|
-
|
|
1937
|
+
const peerDTag = peer ? sessionRegistry2.getEntryByWindowId(peer)?.dTag : void 0;
|
|
1938
|
+
if (peerDTag) channels.push({ id: channelId, peer: peerDTag });
|
|
1768
1939
|
}
|
|
1769
1940
|
}
|
|
1770
1941
|
hooks.sendToNapplet(windowId, { type: "inc.channel.list.result", id: m.id ?? "", channels });
|
|
1771
1942
|
}
|
|
1772
1943
|
function handleChannelClose(state, hooks, windowId, m) {
|
|
1773
1944
|
const channelId = m.channelId ?? "";
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1945
|
+
if (!peerOf(state, channelId, windowId)) return;
|
|
1946
|
+
teardownChannel(state, hooks, channelId);
|
|
1947
|
+
}
|
|
1948
|
+
function teardownChannel(state, hooks, channelId, destroyedWindowId) {
|
|
1949
|
+
const channel = state.channels.get(channelId);
|
|
1950
|
+
if (!channel) return;
|
|
1951
|
+
try {
|
|
1952
|
+
if (destroyedWindowId) {
|
|
1953
|
+
const survivor = peerOf(state, channelId, destroyedWindowId);
|
|
1954
|
+
if (survivor) {
|
|
1955
|
+
try {
|
|
1956
|
+
hooks.sendToNapplet(survivor, { type: "inc.channel.closed", channelId, reason: "peer destroyed" });
|
|
1957
|
+
} catch {
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
for (const peer of [channel.peerA, channel.peerB]) {
|
|
1963
|
+
try {
|
|
1964
|
+
hooks.sendToNapplet(peer, { type: "inc.channel.closed", channelId });
|
|
1965
|
+
} catch {
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
} finally {
|
|
1969
|
+
removeChannel(state, channelId);
|
|
1970
|
+
}
|
|
1779
1971
|
}
|
|
1780
1972
|
function removeWindowSubscriptions(state, windowId) {
|
|
1781
1973
|
for (const [topic, subscriptions] of state.subscriptions) {
|
|
@@ -1787,11 +1979,14 @@ function removeWindowChannels(state, hooks, windowId) {
|
|
|
1787
1979
|
const channelIds = state.channelsByWindow.get(windowId);
|
|
1788
1980
|
if (!channelIds) return;
|
|
1789
1981
|
for (const channelId of Array.from(channelIds)) {
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1982
|
+
teardownChannel(state, hooks, channelId, windowId);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
function revokeWindowChannels(state, hooks, windowId) {
|
|
1986
|
+
const channelIds = state.channelsByWindow.get(windowId);
|
|
1987
|
+
if (!channelIds) return;
|
|
1988
|
+
for (const channelId of Array.from(channelIds)) {
|
|
1989
|
+
teardownChannel(state, hooks, channelId);
|
|
1795
1990
|
}
|
|
1796
1991
|
}
|
|
1797
1992
|
var INSTANCE_MARKER = "@i/";
|
|
@@ -1932,9 +2127,6 @@ function handleStorageNap(windowId, msg, sendToNapplet, sessionRegistry2, aclSta
|
|
|
1932
2127
|
break;
|
|
1933
2128
|
}
|
|
1934
2129
|
}
|
|
1935
|
-
var THEME_FALLBACK_DEFAULT = {
|
|
1936
|
-
colors: { background: "#0a0a0a", text: "#e0e0e0", primary: "#7aa2f7" }
|
|
1937
|
-
};
|
|
1938
2130
|
function createRuntimeDomainHandlers(context) {
|
|
1939
2131
|
return {
|
|
1940
2132
|
storage: (windowId, msg) => handleStorageMessage(context, windowId, msg),
|
|
@@ -2050,16 +2242,13 @@ function handleThemeMessage(context, windowId, msg) {
|
|
|
2050
2242
|
themeService.handleMessage(windowId, msg, (resp) => hooks.sendToNapplet(windowId, resp));
|
|
2051
2243
|
return;
|
|
2052
2244
|
}
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
hooks.sendToNapplet(windowId, {
|
|
2056
|
-
type: "theme.get.result",
|
|
2057
|
-
id: m.id ?? "",
|
|
2058
|
-
theme: THEME_FALLBACK_DEFAULT
|
|
2059
|
-
});
|
|
2060
|
-
}
|
|
2245
|
+
const fallback = createCanonicalDomainResult(msg);
|
|
2246
|
+
if (fallback) hooks.sendToNapplet(windowId, fallback);
|
|
2061
2247
|
}
|
|
2062
2248
|
function handleServiceOnlyMessage(context, name, windowId, msg) {
|
|
2249
|
+
if (name === "intent" && msg.type !== "intent.invoke" && msg.type !== "intent.available" && msg.type !== "intent.handlers") {
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2063
2252
|
const service = context.serviceRegistry[name];
|
|
2064
2253
|
if (!service) return;
|
|
2065
2254
|
service.handleMessage(windowId, msg, (resp) => context.hooks.sendToNapplet(windowId, resp));
|
|
@@ -2145,7 +2334,20 @@ function buildObservation(envelope, windowId, senderCap, sessionRegistry2, getFo
|
|
|
2145
2334
|
const focus = getFocusContext?.(windowId) ?? { focused: true };
|
|
2146
2335
|
const opClass = senderCap ?? envelope.type;
|
|
2147
2336
|
const { kind, size } = extractKindSize(envelope);
|
|
2148
|
-
return {
|
|
2337
|
+
return {
|
|
2338
|
+
napplet,
|
|
2339
|
+
opClass,
|
|
2340
|
+
kind,
|
|
2341
|
+
size,
|
|
2342
|
+
initElapsedMs,
|
|
2343
|
+
// `windowId` comes from the host's source-bound session lookup, never the
|
|
2344
|
+
// napplet message envelope. It scopes startup burst accounting to this
|
|
2345
|
+
// registered realm while rate limits remain dTag-wide.
|
|
2346
|
+
initKey: windowId,
|
|
2347
|
+
focused: focus.focused,
|
|
2348
|
+
msSinceFocusGain: focus.msSinceFocusGain,
|
|
2349
|
+
now: now2
|
|
2350
|
+
};
|
|
2149
2351
|
}
|
|
2150
2352
|
function createFirewallGate(config) {
|
|
2151
2353
|
const { firewallState, sessionRegistry: sessionRegistry2, hooks, fireConsent } = config;
|
|
@@ -2156,10 +2358,25 @@ function createFirewallGate(config) {
|
|
|
2156
2358
|
const napplet = obs.napplet;
|
|
2157
2359
|
const opClass = obs.opClass;
|
|
2158
2360
|
if (decision === "reject" || decision === "prompt") {
|
|
2361
|
+
const intentDenial = createIntentPolicyDenial(envelope);
|
|
2362
|
+
if (intentDenial) {
|
|
2363
|
+
hooks.sendToNapplet(windowId, intentDenial);
|
|
2364
|
+
hooks.onFirewallEvent?.({ windowId, napplet, opClass, decision, action, ruleId, reason, message: envelope });
|
|
2365
|
+
if (decision === "prompt") fireConsent(windowId, napplet);
|
|
2366
|
+
return "drop";
|
|
2367
|
+
}
|
|
2368
|
+
const canonicalResult = createCanonicalDomainResult(envelope);
|
|
2369
|
+
if (canonicalResult) {
|
|
2370
|
+
hooks.sendToNapplet(windowId, canonicalResult);
|
|
2371
|
+
hooks.onFirewallEvent?.({ windowId, napplet, opClass, decision, action, ruleId, reason, message: envelope });
|
|
2372
|
+
if (decision === "prompt") fireConsent(windowId, napplet);
|
|
2373
|
+
return "drop";
|
|
2374
|
+
}
|
|
2159
2375
|
const id = envelope.id ?? "";
|
|
2160
|
-
const
|
|
2161
|
-
|
|
2162
|
-
|
|
2376
|
+
const type = denialResponseType(envelope);
|
|
2377
|
+
if (type) {
|
|
2378
|
+
hooks.sendToNapplet(windowId, { type, id, error: `firewall: ${reason}` });
|
|
2379
|
+
}
|
|
2163
2380
|
hooks.onFirewallEvent?.({ windowId, napplet, opClass, decision, action, ruleId, reason, message: envelope });
|
|
2164
2381
|
if (decision === "prompt") {
|
|
2165
2382
|
fireConsent(windowId, napplet);
|
|
@@ -2172,21 +2389,45 @@ function createFirewallGate(config) {
|
|
|
2172
2389
|
return "dispatch";
|
|
2173
2390
|
};
|
|
2174
2391
|
}
|
|
2175
|
-
function
|
|
2392
|
+
function denialResponseType(envelope) {
|
|
2393
|
+
if (envelope.type.startsWith("intent.")) return null;
|
|
2394
|
+
if (envelope.type.startsWith("storage.")) return `${envelope.type}.result`;
|
|
2395
|
+
if (envelope.type === "inc.subscribe" || envelope.type === "inc.channel.open") {
|
|
2396
|
+
return `${envelope.type}.result`;
|
|
2397
|
+
}
|
|
2398
|
+
if (envelope.type.startsWith("inc.")) return null;
|
|
2399
|
+
return `${envelope.type}.error`;
|
|
2400
|
+
}
|
|
2401
|
+
function createMessageHandler(hooks, sessionRegistry2, enforceNap, dispatchNapEnvelope, firewallGate) {
|
|
2176
2402
|
return (windowId, msg) => {
|
|
2177
2403
|
if (typeof msg !== "object" || msg === null || !("type" in msg)) return;
|
|
2178
2404
|
const envelope = msg;
|
|
2179
2405
|
const dotIdx = envelope.type.indexOf(".");
|
|
2180
2406
|
if (dotIdx === -1) return;
|
|
2407
|
+
if (!sessionRegistry2.getEntryByWindowId(windowId)) return;
|
|
2408
|
+
const domain = envelope.type.slice(0, dotIdx);
|
|
2409
|
+
if (hooks.isDomainAllowed && !hooks.isDomainAllowed(windowId, domain)) return;
|
|
2410
|
+
if (isIdentityOrThemeMessage(envelope) && !createCanonicalDomainResult(envelope)) return;
|
|
2181
2411
|
const caps = resolveCapabilitiesNap(envelope);
|
|
2182
2412
|
if (caps.senderCap) {
|
|
2183
2413
|
const result2 = enforceNap(windowId, caps.senderCap, envelope);
|
|
2184
2414
|
if (!result2.allowed) {
|
|
2415
|
+
const intentDenial = createIntentPolicyDenial(envelope);
|
|
2416
|
+
if (intentDenial) {
|
|
2417
|
+
hooks.sendToNapplet(windowId, intentDenial);
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
const canonicalResult = createCanonicalDomainResult(envelope);
|
|
2421
|
+
if (canonicalResult) {
|
|
2422
|
+
hooks.sendToNapplet(windowId, canonicalResult);
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2185
2425
|
const id = envelope.id ?? "";
|
|
2186
|
-
const isStorageEnvelope = envelope.type.startsWith("storage.");
|
|
2187
2426
|
const error = formatDenialReason(result2.capability);
|
|
2188
|
-
const type =
|
|
2189
|
-
|
|
2427
|
+
const type = denialResponseType(envelope);
|
|
2428
|
+
if (type) {
|
|
2429
|
+
hooks.sendToNapplet(windowId, { type, id, error });
|
|
2430
|
+
}
|
|
2190
2431
|
return;
|
|
2191
2432
|
}
|
|
2192
2433
|
}
|
|
@@ -2223,12 +2464,64 @@ function createRuntimeInstance(context) {
|
|
|
2223
2464
|
consentHandlerRef
|
|
2224
2465
|
} = context;
|
|
2225
2466
|
const undeclaredServiceConsents = /* @__PURE__ */ new Set();
|
|
2467
|
+
const attachedServices = /* @__PURE__ */ new Map();
|
|
2468
|
+
const isDomainAllowed = hooks.isDomainAllowed;
|
|
2469
|
+
const sendToNapplet = hooks.sendToNapplet;
|
|
2470
|
+
const serviceContext = Object.freeze({
|
|
2471
|
+
resolveDTag(windowId) {
|
|
2472
|
+
return sessionRegistry2.getEntryByWindowId(windowId)?.dTag;
|
|
2473
|
+
},
|
|
2474
|
+
listWindowIds() {
|
|
2475
|
+
return Object.freeze(
|
|
2476
|
+
sessionRegistry2.getAllEntries().map((entry) => entry.windowId)
|
|
2477
|
+
);
|
|
2478
|
+
},
|
|
2479
|
+
sendToEligibleNapplet(windowId, message) {
|
|
2480
|
+
const entry = sessionRegistry2.getEntryByWindowId(windowId);
|
|
2481
|
+
if (!entry || typeof message.type !== "string") return false;
|
|
2482
|
+
const dotIndex = message.type.indexOf(".");
|
|
2483
|
+
if (dotIndex <= 0) return false;
|
|
2484
|
+
const domain = message.type.slice(0, dotIndex);
|
|
2485
|
+
try {
|
|
2486
|
+
if (isDomainAllowed && !isDomainAllowed(windowId, domain)) return false;
|
|
2487
|
+
const { recipientCap } = resolveCapabilitiesNap(message);
|
|
2488
|
+
if (!recipientCap) return false;
|
|
2489
|
+
if (!aclState.check("", entry.dTag, entry.aggregateHash, recipientCap)) {
|
|
2490
|
+
return false;
|
|
2491
|
+
}
|
|
2492
|
+
sendToNapplet(windowId, message);
|
|
2493
|
+
return true;
|
|
2494
|
+
} catch {
|
|
2495
|
+
return false;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
});
|
|
2499
|
+
function attachService(name, handler) {
|
|
2500
|
+
attachedServices.set(name, handler);
|
|
2501
|
+
try {
|
|
2502
|
+
handler.onRegistered?.(serviceContext);
|
|
2503
|
+
} catch {
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
function detachService(name) {
|
|
2507
|
+
const handler = attachedServices.get(name);
|
|
2508
|
+
if (!handler) return;
|
|
2509
|
+
attachedServices.delete(name);
|
|
2510
|
+
try {
|
|
2511
|
+
handler.onUnregistered?.();
|
|
2512
|
+
} catch {
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
for (const [name, handler] of Object.entries(serviceRegistry)) {
|
|
2516
|
+
attachService(name, handler);
|
|
2517
|
+
}
|
|
2226
2518
|
return {
|
|
2227
2519
|
handleMessage: context.handleMessage,
|
|
2228
2520
|
injectEvent(topic, payload) {
|
|
2229
2521
|
eventBuffer.bufferAndDeliver(createInjectedEvent(hooks, topic, payload), null);
|
|
2230
2522
|
},
|
|
2231
2523
|
destroy() {
|
|
2524
|
+
for (const name of attachedServices.keys()) detachService(name);
|
|
2232
2525
|
manifestCache2.persist();
|
|
2233
2526
|
aclState.persist();
|
|
2234
2527
|
firewallState.persist();
|
|
@@ -2243,14 +2536,17 @@ function createRuntimeInstance(context) {
|
|
|
2243
2536
|
consentHandlerRef.current = handler;
|
|
2244
2537
|
},
|
|
2245
2538
|
registerService(name, handler) {
|
|
2539
|
+
detachService(name);
|
|
2246
2540
|
serviceRegistry[name] = handler;
|
|
2247
2541
|
registeredServices.set(name, {
|
|
2248
2542
|
name: handler.descriptor.name,
|
|
2249
2543
|
version: handler.descriptor.version,
|
|
2250
2544
|
description: handler.descriptor.description
|
|
2251
2545
|
});
|
|
2546
|
+
attachService(name, handler);
|
|
2252
2547
|
},
|
|
2253
2548
|
unregisterService(name) {
|
|
2549
|
+
detachService(name);
|
|
2254
2550
|
delete serviceRegistry[name];
|
|
2255
2551
|
registeredServices.delete(name);
|
|
2256
2552
|
},
|
|
@@ -2283,7 +2579,15 @@ function createRuntime(hooks) {
|
|
|
2283
2579
|
const serviceRegistry = { ...hooks.services };
|
|
2284
2580
|
const registeredServices = createRegisteredServices(serviceRegistry);
|
|
2285
2581
|
const sessionRegistry2 = createSessionRegistry(hooks.onPendingUpdate);
|
|
2286
|
-
|
|
2582
|
+
let incRuntime = null;
|
|
2583
|
+
const aclState = createAclState(hooks.aclPersistence, "permissive", (mutation) => {
|
|
2584
|
+
if (mutation.type === "revoke" && mutation.capability !== "relay:read") return;
|
|
2585
|
+
for (const entry of sessionRegistry2.getAllEntries()) {
|
|
2586
|
+
if (entry.dTag === mutation.dTag && entry.aggregateHash === mutation.aggregateHash) {
|
|
2587
|
+
incRuntime?.revokeWindow(entry.windowId);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2287
2591
|
const firewallState = createFirewallState(hooks.firewallPersistence);
|
|
2288
2592
|
const manifestCache2 = createManifestCache(hooks.manifestPersistence);
|
|
2289
2593
|
const replayDetector = createReplayDetector(
|
|
@@ -2320,6 +2624,11 @@ function createRuntime(hooks) {
|
|
|
2320
2624
|
},
|
|
2321
2625
|
onAclCheck: hooks.onAclCheck
|
|
2322
2626
|
});
|
|
2627
|
+
incRuntime = createIncRuntime(
|
|
2628
|
+
hooks,
|
|
2629
|
+
sessionRegistry2,
|
|
2630
|
+
(targetWindowId, message) => enforceNap(targetWindowId, "relay:read", message).allowed
|
|
2631
|
+
);
|
|
2323
2632
|
const firewallGate = createFirewallGate({ firewallState, sessionRegistry: sessionRegistry2, hooks, fireConsent });
|
|
2324
2633
|
const eventBuffer = createEventBuffer(
|
|
2325
2634
|
hooks.sendToNapplet,
|
|
@@ -2331,7 +2640,6 @@ function createRuntime(hooks) {
|
|
|
2331
2640
|
aclState.load();
|
|
2332
2641
|
firewallState.load();
|
|
2333
2642
|
manifestCache2.load();
|
|
2334
|
-
const incRuntime = createIncRuntime(hooks, sessionRegistry2);
|
|
2335
2643
|
const domainHandlers = createRuntimeDomainHandlers({ hooks, serviceRegistry, sessionRegistry: sessionRegistry2, aclState });
|
|
2336
2644
|
const dispatchNapEnvelope = createNapEnvelopeDispatcher({
|
|
2337
2645
|
relay: createRelayHandler({ hooks, serviceRegistry, subscriptions, eventBuffer, replayDetector }),
|
|
@@ -2340,7 +2648,7 @@ function createRuntime(hooks) {
|
|
|
2340
2648
|
inc: incRuntime.handleMessage,
|
|
2341
2649
|
...domainHandlers
|
|
2342
2650
|
});
|
|
2343
|
-
const handleMessage = createMessageHandler(hooks, enforceNap, dispatchNapEnvelope, firewallGate);
|
|
2651
|
+
const handleMessage = createMessageHandler(hooks, sessionRegistry2, enforceNap, dispatchNapEnvelope, firewallGate);
|
|
2344
2652
|
return createRuntimeInstance({
|
|
2345
2653
|
hooks,
|
|
2346
2654
|
serviceRegistry,
|
|
@@ -2392,7 +2700,7 @@ function createRelayPoolAdapter(shellHooks, originRegistry2) {
|
|
|
2392
2700
|
const pool = shellHooks.relayPool.getRelayPool();
|
|
2393
2701
|
if (!pool) return;
|
|
2394
2702
|
const relayUrls = shellHooks.relayPool.selectRelayTier([]);
|
|
2395
|
-
pool.publish(relayUrls, event);
|
|
2703
|
+
return pool.publish(relayUrls, event);
|
|
2396
2704
|
},
|
|
2397
2705
|
selectRelayTier(filters) {
|
|
2398
2706
|
return shellHooks.relayPool.selectRelayTier(filters);
|
|
@@ -2747,6 +3055,19 @@ var originRegistry = {
|
|
|
2747
3055
|
if (!entry?.dTag || !entry?.aggregateHash) return void 0;
|
|
2748
3056
|
return { dTag: entry.dTag, aggregateHash: entry.aggregateHash };
|
|
2749
3057
|
},
|
|
3058
|
+
/** Store the immutable environment captured for this exact frame registration. */
|
|
3059
|
+
setEnvironment(win, environment) {
|
|
3060
|
+
const entry = registry.get(win);
|
|
3061
|
+
if (!entry) return;
|
|
3062
|
+
entry.environment = Object.freeze({
|
|
3063
|
+
capabilities: Object.freeze({ domains: Object.freeze([...environment.capabilities.domains]) }),
|
|
3064
|
+
services: Object.freeze([...environment.services])
|
|
3065
|
+
});
|
|
3066
|
+
},
|
|
3067
|
+
/** Return the immutable environment captured before this frame executes. */
|
|
3068
|
+
getEnvironment(win) {
|
|
3069
|
+
return registry.get(win)?.environment;
|
|
3070
|
+
},
|
|
2750
3071
|
/**
|
|
2751
3072
|
* Look up the monotonically increasing registration id for a Window.
|
|
2752
3073
|
*
|
|
@@ -3305,111 +3626,151 @@ var audioManager = {
|
|
|
3305
3626
|
version = 0;
|
|
3306
3627
|
}
|
|
3307
3628
|
};
|
|
3308
|
-
var
|
|
3629
|
+
var RUNTIME_NATIVE_DOMAINS = [
|
|
3630
|
+
"relay",
|
|
3309
3631
|
"identity",
|
|
3310
3632
|
"storage",
|
|
3311
3633
|
"inc",
|
|
3312
3634
|
"theme",
|
|
3313
3635
|
"keys",
|
|
3314
3636
|
"media",
|
|
3315
|
-
"notify"
|
|
3637
|
+
"notify"
|
|
3638
|
+
];
|
|
3639
|
+
var SERVICE_ONLY_DOMAINS = [
|
|
3640
|
+
"outbox",
|
|
3316
3641
|
"config",
|
|
3317
3642
|
"resource",
|
|
3318
|
-
"cvm"
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
"
|
|
3322
|
-
"
|
|
3323
|
-
"
|
|
3324
|
-
"
|
|
3325
|
-
"
|
|
3326
|
-
"
|
|
3643
|
+
"cvm",
|
|
3644
|
+
"upload",
|
|
3645
|
+
"intent",
|
|
3646
|
+
"link",
|
|
3647
|
+
"common",
|
|
3648
|
+
"lists",
|
|
3649
|
+
"serial",
|
|
3650
|
+
"ble",
|
|
3651
|
+
"webrtc",
|
|
3652
|
+
"dm",
|
|
3653
|
+
"count"
|
|
3327
3654
|
];
|
|
3328
3655
|
function buildShellCapabilities(hooks) {
|
|
3329
|
-
const
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
if (hooks.lists?.isAvailable()) domains.push("lists");
|
|
3336
|
-
if (hooks.serial?.isAvailable()) domains.push("serial");
|
|
3337
|
-
if (hooks.ble?.isAvailable()) domains.push("ble");
|
|
3338
|
-
if (hooks.webrtc?.isAvailable()) domains.push("webrtc");
|
|
3339
|
-
if (hooks.dm) domains.push("dm");
|
|
3340
|
-
const sandbox = [];
|
|
3341
|
-
const protocols = {};
|
|
3342
|
-
for (const entry of NAP_INC_PROTOCOLS) {
|
|
3343
|
-
const [domain, protocol] = entry.split(":");
|
|
3344
|
-
(protocols[domain] ??= []).push(protocol);
|
|
3345
|
-
}
|
|
3346
|
-
const naps = hooks.relayPool ? ["relay", "outbox", ...NAP_DOMAINS, ...NAP_INC_PROTOCOLS] : [...NAP_DOMAINS, ...NAP_INC_PROTOCOLS];
|
|
3347
|
-
if (hooks.upload) naps.push("upload");
|
|
3348
|
-
if (hooks.intent?.isAvailable()) naps.push("intent");
|
|
3349
|
-
if (hooks.services?.count) naps.push("count");
|
|
3350
|
-
if (hooks.link?.isAvailable()) naps.push("link");
|
|
3351
|
-
if (hooks.common?.isAvailable()) naps.push("common");
|
|
3352
|
-
if (hooks.lists?.isAvailable()) naps.push("lists");
|
|
3353
|
-
if (hooks.serial?.isAvailable()) naps.push("serial");
|
|
3354
|
-
if (hooks.ble?.isAvailable()) naps.push("ble");
|
|
3355
|
-
if (hooks.webrtc?.isAvailable()) naps.push("webrtc");
|
|
3356
|
-
if (hooks.dm) naps.push("dm");
|
|
3357
|
-
return applyCapabilityOverrides(
|
|
3358
|
-
{ domains, protocols, naps, sandbox },
|
|
3359
|
-
hooks.capabilities?.disabledDomains ?? []
|
|
3360
|
-
);
|
|
3656
|
+
const disabled = new Set(hooks.capabilities?.disabledDomains ?? []);
|
|
3657
|
+
const domains = RUNTIME_NATIVE_DOMAINS.filter((domain) => !disabled.has(domain));
|
|
3658
|
+
for (const domain of SERVICE_ONLY_DOMAINS) {
|
|
3659
|
+
if (!disabled.has(domain) && isServiceDomainLive(hooks, domain)) domains.push(domain);
|
|
3660
|
+
}
|
|
3661
|
+
return Object.freeze({ domains: freezeList(domains) });
|
|
3361
3662
|
}
|
|
3362
|
-
function
|
|
3363
|
-
|
|
3364
|
-
const disabled = new Set(disabledDomains);
|
|
3365
|
-
const
|
|
3366
|
-
|
|
3367
|
-
|
|
3663
|
+
function resolveShellEnvironment(hooks, identity) {
|
|
3664
|
+
const capabilities = buildShellCapabilities(hooks);
|
|
3665
|
+
const disabled = new Set(hooks.capabilities?.disabledDomains ?? []);
|
|
3666
|
+
const services = getLiveServices(hooks, capabilities.domains, disabled);
|
|
3667
|
+
const available = Object.freeze({
|
|
3668
|
+
domains: freezeList(capabilities.domains),
|
|
3669
|
+
services: freezeList(services)
|
|
3670
|
+
});
|
|
3671
|
+
const granted = hooks.capabilities?.resolveEnvironment?.(identity, available) ?? available;
|
|
3672
|
+
return Object.freeze({
|
|
3673
|
+
capabilities: Object.freeze({
|
|
3674
|
+
domains: freezeList(intersectInOrder(available.domains, granted.domains))
|
|
3675
|
+
}),
|
|
3676
|
+
services: freezeList(intersectInOrder(available.services, granted.services))
|
|
3677
|
+
});
|
|
3678
|
+
}
|
|
3679
|
+
function isServiceDomainLive(hooks, domain) {
|
|
3680
|
+
if (!hooks.services?.[domain]) return false;
|
|
3681
|
+
switch (domain) {
|
|
3682
|
+
case "outbox":
|
|
3683
|
+
return Boolean(hooks.relayPool);
|
|
3684
|
+
case "upload":
|
|
3685
|
+
return hooks.upload?.getUploader() !== null && hooks.upload?.getUploader() !== void 0;
|
|
3686
|
+
case "intent":
|
|
3687
|
+
return hooks.intent?.isAvailable() === true;
|
|
3688
|
+
case "link":
|
|
3689
|
+
return hooks.link?.isAvailable() === true;
|
|
3690
|
+
case "common":
|
|
3691
|
+
return hooks.common?.isAvailable() === true;
|
|
3692
|
+
case "lists":
|
|
3693
|
+
return hooks.lists?.isAvailable() === true;
|
|
3694
|
+
case "serial":
|
|
3695
|
+
return hooks.serial?.isAvailable() === true;
|
|
3696
|
+
case "ble":
|
|
3697
|
+
return hooks.ble?.isAvailable() === true;
|
|
3698
|
+
case "webrtc":
|
|
3699
|
+
return hooks.webrtc?.isAvailable() === true;
|
|
3700
|
+
case "dm":
|
|
3701
|
+
return hooks.dm !== void 0;
|
|
3702
|
+
default:
|
|
3703
|
+
return true;
|
|
3368
3704
|
}
|
|
3705
|
+
}
|
|
3706
|
+
function getLiveServices(hooks, domains, disabled) {
|
|
3707
|
+
const liveDomains = new Set(domains);
|
|
3708
|
+
return Object.keys(hooks.services ?? {}).filter((name) => {
|
|
3709
|
+
if (disabled.has(name)) return false;
|
|
3710
|
+
return !SERVICE_ONLY_DOMAINS.includes(name) || liveDomains.has(name);
|
|
3711
|
+
});
|
|
3712
|
+
}
|
|
3713
|
+
function intersectInOrder(available, requested) {
|
|
3714
|
+
const requestedSet = new Set(requested);
|
|
3715
|
+
return available.filter((entry) => requestedSet.has(entry));
|
|
3716
|
+
}
|
|
3717
|
+
function freezeList(values) {
|
|
3718
|
+
return Object.freeze([...values]);
|
|
3719
|
+
}
|
|
3720
|
+
function createShellReadyState() {
|
|
3721
|
+
const initSent = /* @__PURE__ */ new WeakMap();
|
|
3722
|
+
const sessionRegistration = /* @__PURE__ */ new Map();
|
|
3723
|
+
const environments = /* @__PURE__ */ new Map();
|
|
3369
3724
|
return {
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3725
|
+
initSent,
|
|
3726
|
+
sessionRegistration,
|
|
3727
|
+
environments,
|
|
3728
|
+
isDomainAllowed(windowId, domain) {
|
|
3729
|
+
return environments.get(windowId)?.capabilities.domains.includes(domain) ?? false;
|
|
3730
|
+
},
|
|
3731
|
+
clear() {
|
|
3732
|
+
sessionRegistration.clear();
|
|
3733
|
+
environments.clear();
|
|
3734
|
+
}
|
|
3374
3735
|
};
|
|
3375
3736
|
}
|
|
3376
|
-
var initSent = /* @__PURE__ */ new WeakMap();
|
|
3377
3737
|
function handleShellReady({
|
|
3378
3738
|
hooks,
|
|
3379
3739
|
origin,
|
|
3380
3740
|
runtime,
|
|
3741
|
+
state,
|
|
3381
3742
|
sourceRegistrationId,
|
|
3382
3743
|
sourceWindow,
|
|
3383
3744
|
windowId
|
|
3384
3745
|
}) {
|
|
3385
|
-
|
|
3386
|
-
hooks,
|
|
3387
|
-
origin,
|
|
3388
|
-
runtime,
|
|
3389
|
-
sourceRegistrationId,
|
|
3390
|
-
sourceWindow,
|
|
3391
|
-
windowId
|
|
3392
|
-
});
|
|
3393
|
-
if (initSent.get(sourceWindow) === sourceRegistrationId) {
|
|
3746
|
+
if (state.initSent.get(sourceWindow) === sourceRegistrationId) {
|
|
3394
3747
|
return;
|
|
3395
3748
|
}
|
|
3396
|
-
const
|
|
3397
|
-
|
|
3398
|
-
|
|
3749
|
+
const identity = resolveNip5dIdentity(hooks, windowId, sourceWindow);
|
|
3750
|
+
if (!identity) return;
|
|
3751
|
+
registerNip5dSessionIfNeeded({ origin, runtime, state, sourceRegistrationId, windowId, identity });
|
|
3752
|
+
const environment = originRegistry.getEnvironment(sourceWindow) ?? resolveShellEnvironment(hooks, identity);
|
|
3753
|
+
state.environments.set(windowId, environment);
|
|
3754
|
+
postShellInit(sourceWindow, environment);
|
|
3755
|
+
state.initSent.set(sourceWindow, sourceRegistrationId);
|
|
3399
3756
|
}
|
|
3400
3757
|
function registerNip5dSessionIfNeeded({
|
|
3401
|
-
hooks,
|
|
3402
3758
|
origin,
|
|
3403
3759
|
runtime,
|
|
3404
|
-
|
|
3760
|
+
state,
|
|
3761
|
+
sourceRegistrationId,
|
|
3762
|
+
windowId,
|
|
3763
|
+
identity
|
|
3405
3764
|
}) {
|
|
3406
|
-
|
|
3765
|
+
const existing = runtime.sessionRegistry.getEntryByWindowId(windowId);
|
|
3766
|
+
const previousRegistration = state.sessionRegistration.get(windowId);
|
|
3767
|
+
if (existing && previousRegistration === sourceRegistrationId) {
|
|
3407
3768
|
return;
|
|
3408
3769
|
}
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
return;
|
|
3770
|
+
if (existing && previousRegistration !== void 0) {
|
|
3771
|
+
runtime.sessionRegistry.unregister(windowId);
|
|
3412
3772
|
}
|
|
3773
|
+
if (existing && previousRegistration === void 0) return;
|
|
3413
3774
|
const entry = {
|
|
3414
3775
|
pubkey: "",
|
|
3415
3776
|
windowId,
|
|
@@ -3422,33 +3783,30 @@ function registerNip5dSessionIfNeeded({
|
|
|
3422
3783
|
provenance: "nip-5d"
|
|
3423
3784
|
};
|
|
3424
3785
|
runtime.sessionRegistry.register(windowId, entry);
|
|
3786
|
+
state.sessionRegistration.set(windowId, sourceRegistrationId);
|
|
3425
3787
|
}
|
|
3426
|
-
function resolveNip5dIdentity(hooks, windowId) {
|
|
3788
|
+
function resolveNip5dIdentity(hooks, windowId, sourceWindow) {
|
|
3427
3789
|
const hookIdentity = hooks.onNip5dIframeCreate?.(windowId);
|
|
3428
3790
|
if (hookIdentity !== null && hookIdentity !== void 0) {
|
|
3429
|
-
return {
|
|
3791
|
+
return Object.freeze({
|
|
3430
3792
|
dTag: hookIdentity.dTag,
|
|
3431
3793
|
aggregateHash: hookIdentity.aggregateHash
|
|
3432
|
-
};
|
|
3433
|
-
}
|
|
3434
|
-
const win = originRegistry.getIframeWindow(windowId);
|
|
3435
|
-
if (!win) {
|
|
3436
|
-
return null;
|
|
3794
|
+
});
|
|
3437
3795
|
}
|
|
3438
|
-
const originIdentity = originRegistry.getIdentity(
|
|
3796
|
+
const originIdentity = originRegistry.getIdentity(sourceWindow);
|
|
3439
3797
|
if (!originIdentity) {
|
|
3440
3798
|
return null;
|
|
3441
3799
|
}
|
|
3442
|
-
return {
|
|
3800
|
+
return Object.freeze({
|
|
3443
3801
|
dTag: originIdentity.dTag,
|
|
3444
3802
|
aggregateHash: originIdentity.aggregateHash
|
|
3445
|
-
};
|
|
3803
|
+
});
|
|
3446
3804
|
}
|
|
3447
|
-
function postShellInit(win,
|
|
3805
|
+
function postShellInit(win, environment) {
|
|
3448
3806
|
const initMsg = {
|
|
3449
3807
|
type: "shell.init",
|
|
3450
|
-
capabilities,
|
|
3451
|
-
services
|
|
3808
|
+
capabilities: environment.capabilities,
|
|
3809
|
+
services: environment.services
|
|
3452
3810
|
};
|
|
3453
3811
|
win.postMessage(initMsg, "*");
|
|
3454
3812
|
}
|
|
@@ -3462,6 +3820,7 @@ function reportUnrouted(hooks, event, reason) {
|
|
|
3462
3820
|
}
|
|
3463
3821
|
}
|
|
3464
3822
|
function createShellBridge(hooks) {
|
|
3823
|
+
const shellReadyState = createShellReadyState();
|
|
3465
3824
|
const runtimeHooks = adaptHooks(hooks, {
|
|
3466
3825
|
originRegistry,
|
|
3467
3826
|
manifestCache,
|
|
@@ -3469,11 +3828,16 @@ function createShellBridge(hooks) {
|
|
|
3469
3828
|
audioManager,
|
|
3470
3829
|
nappKeyRegistry
|
|
3471
3830
|
});
|
|
3472
|
-
const runtime = createRuntime(
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3831
|
+
const runtime = createRuntime({
|
|
3832
|
+
...runtimeHooks,
|
|
3833
|
+
isDomainAllowed: shellReadyState.isDomainAllowed
|
|
3834
|
+
});
|
|
3835
|
+
function publishToEligibleNapplets(envelope, domain, capability) {
|
|
3836
|
+
for (const entry of runtime.sessionRegistry.getAllEntries()) {
|
|
3837
|
+
const environment = shellReadyState.environments.get(entry.windowId);
|
|
3838
|
+
if (!environment?.capabilities.domains.includes(domain)) continue;
|
|
3839
|
+
if (!runtime.aclState.check(entry.pubkey, entry.dTag, entry.aggregateHash, capability)) continue;
|
|
3840
|
+
const win = originRegistry.getIframeWindow(entry.windowId);
|
|
3477
3841
|
if (!win) continue;
|
|
3478
3842
|
win.postMessage(envelope, "*");
|
|
3479
3843
|
}
|
|
@@ -3497,6 +3861,7 @@ function createShellBridge(hooks) {
|
|
|
3497
3861
|
hooks,
|
|
3498
3862
|
origin: event.origin,
|
|
3499
3863
|
runtime,
|
|
3864
|
+
state: shellReadyState,
|
|
3500
3865
|
sourceRegistrationId: originRegistry.getRegistrationId(sourceWindow) ?? 0,
|
|
3501
3866
|
sourceWindow,
|
|
3502
3867
|
windowId
|
|
@@ -3510,17 +3875,18 @@ function createShellBridge(hooks) {
|
|
|
3510
3875
|
},
|
|
3511
3876
|
destroy() {
|
|
3512
3877
|
runtime.destroy();
|
|
3878
|
+
shellReadyState.clear();
|
|
3513
3879
|
},
|
|
3514
3880
|
registerConsentHandler(handler) {
|
|
3515
3881
|
runtime.registerConsentHandler(handler);
|
|
3516
3882
|
},
|
|
3517
3883
|
publishTheme(theme) {
|
|
3518
3884
|
const envelope = { type: "theme.changed", theme };
|
|
3519
|
-
|
|
3885
|
+
publishToEligibleNapplets(envelope, "theme", "theme:read");
|
|
3520
3886
|
},
|
|
3521
3887
|
publishIdentityChanged(pubkey) {
|
|
3522
3888
|
const envelope = { type: "identity.changed", pubkey };
|
|
3523
|
-
|
|
3889
|
+
publishToEligibleNapplets(envelope, "identity", "identity:read");
|
|
3524
3890
|
},
|
|
3525
3891
|
get runtime() {
|
|
3526
3892
|
return runtime;
|
|
@@ -3582,7 +3948,7 @@ function nappletNamespacePrelude(domains) {
|
|
|
3582
3948
|
function request(message, resultType, map, options = {}) {
|
|
3583
3949
|
const requestId = message.id ?? id();
|
|
3584
3950
|
const outbound = { ...message, id: requestId };
|
|
3585
|
-
return new Promise((resolve,
|
|
3951
|
+
return new Promise((resolve, reject2) => {
|
|
3586
3952
|
const off = listen((event) => {
|
|
3587
3953
|
if (!isParentMessage(event)) return;
|
|
3588
3954
|
const incoming = event.data;
|
|
@@ -3594,18 +3960,18 @@ function nappletNamespacePrelude(domains) {
|
|
|
3594
3960
|
clearTimeout(timer);
|
|
3595
3961
|
const error = typeof msg.error === "string" ? msg.error : "";
|
|
3596
3962
|
if (error && options.rejectOnError !== false) {
|
|
3597
|
-
|
|
3963
|
+
reject2(new Error(error));
|
|
3598
3964
|
return;
|
|
3599
3965
|
}
|
|
3600
3966
|
try {
|
|
3601
3967
|
resolve(map(msg));
|
|
3602
3968
|
} catch (err) {
|
|
3603
|
-
|
|
3969
|
+
reject2(err);
|
|
3604
3970
|
}
|
|
3605
3971
|
});
|
|
3606
3972
|
const timer = setTimeout(() => {
|
|
3607
3973
|
off();
|
|
3608
|
-
|
|
3974
|
+
reject2(new Error(`${message.type} timed out`));
|
|
3609
3975
|
}, requestTimeoutMs);
|
|
3610
3976
|
post(outbound);
|
|
3611
3977
|
});
|
|
@@ -3613,12 +3979,47 @@ function nappletNamespacePrelude(domains) {
|
|
|
3613
3979
|
function fire(message) {
|
|
3614
3980
|
post(message);
|
|
3615
3981
|
}
|
|
3616
|
-
function
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
const
|
|
3621
|
-
|
|
3982
|
+
function normalizeConventionUri(topic, hasExplicitPayload) {
|
|
3983
|
+
if (typeof topic !== "string") throw new TypeError("INC topic must be text");
|
|
3984
|
+
if (!topic.startsWith("napplet:")) return { topic };
|
|
3985
|
+
if (topic.includes("#")) throw new TypeError("Convention topics cannot contain fragments");
|
|
3986
|
+
const queryStart = topic.indexOf("?");
|
|
3987
|
+
if (queryStart === -1) {
|
|
3988
|
+
return { topic };
|
|
3989
|
+
}
|
|
3990
|
+
if (hasExplicitPayload) {
|
|
3991
|
+
throw new TypeError("Convention queries cannot be combined with an explicit payload");
|
|
3992
|
+
}
|
|
3993
|
+
const values = {};
|
|
3994
|
+
const names = /* @__PURE__ */ new Set();
|
|
3995
|
+
const query = topic.slice(queryStart + 1);
|
|
3996
|
+
if (query) {
|
|
3997
|
+
for (const pair of query.split("&")) {
|
|
3998
|
+
const separator = pair.indexOf("=");
|
|
3999
|
+
if (separator < 0) {
|
|
4000
|
+
throw new TypeError("Convention query parameters must use name=value form");
|
|
4001
|
+
}
|
|
4002
|
+
let name;
|
|
4003
|
+
let value;
|
|
4004
|
+
try {
|
|
4005
|
+
name = decodeURIComponent(pair.slice(0, separator));
|
|
4006
|
+
value = decodeURIComponent(pair.slice(separator + 1));
|
|
4007
|
+
} catch {
|
|
4008
|
+
throw new TypeError("Convention query contains malformed percent encoding");
|
|
4009
|
+
}
|
|
4010
|
+
if (names.has(name)) throw new TypeError("Convention query names must be unique");
|
|
4011
|
+
names.add(name);
|
|
4012
|
+
values[name] = value;
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
return { topic: topic.slice(0, queryStart), payload: values };
|
|
4016
|
+
}
|
|
4017
|
+
function resultOrError(msg) {
|
|
4018
|
+
return msg;
|
|
4019
|
+
}
|
|
4020
|
+
function withoutEnvelope(msg) {
|
|
4021
|
+
const { type: _type, id: _id, ...result2 } = msg;
|
|
4022
|
+
return result2;
|
|
3622
4023
|
}
|
|
3623
4024
|
function fieldOrThrow(msg, field, fallback) {
|
|
3624
4025
|
if (Object.prototype.hasOwnProperty.call(msg, field)) return msg[field];
|
|
@@ -3652,11 +4053,13 @@ function nappletNamespacePrelude(domains) {
|
|
|
3652
4053
|
if (typeof incoming !== "object" || incoming === null) return;
|
|
3653
4054
|
const message = incoming;
|
|
3654
4055
|
if (message.type !== "shell.init") return;
|
|
3655
|
-
const
|
|
3656
|
-
|
|
3657
|
-
);
|
|
4056
|
+
const rawCapabilities = message.capabilities;
|
|
4057
|
+
const rawDomains = typeof rawCapabilities === "object" && rawCapabilities !== null ? rawCapabilities.domains : void 0;
|
|
4058
|
+
if (!Array.isArray(rawDomains)) return;
|
|
4059
|
+
const domains2 = freezeStrings(rawDomains);
|
|
4060
|
+
const services = freezeStrings(Array.isArray(message.services) ? message.services : []);
|
|
3658
4061
|
environment = Object.freeze({
|
|
3659
|
-
capabilities:
|
|
4062
|
+
capabilities: Object.freeze({ domains: domains2 }),
|
|
3660
4063
|
services
|
|
3661
4064
|
});
|
|
3662
4065
|
off();
|
|
@@ -3664,20 +4067,18 @@ function nappletNamespacePrelude(domains) {
|
|
|
3664
4067
|
for (const handler of readyHandlers) handler(environment);
|
|
3665
4068
|
readyHandlers.clear();
|
|
3666
4069
|
});
|
|
3667
|
-
function
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
if (typeof protocols === "object" && protocols !== null) {
|
|
3675
|
-
const supported = protocols[domain];
|
|
3676
|
-
if (Array.isArray(supported) && supported.includes(protocol)) return true;
|
|
3677
|
-
}
|
|
3678
|
-
return Array.isArray(capabilities.naps) && capabilities.naps.includes(`${domain}:${protocol}`);
|
|
4070
|
+
function freezeStrings(values) {
|
|
4071
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4072
|
+
const strings = [];
|
|
4073
|
+
for (const value of values) {
|
|
4074
|
+
if (typeof value !== "string" || value.length === 0 || seen.has(value)) continue;
|
|
4075
|
+
seen.add(value);
|
|
4076
|
+
strings.push(value);
|
|
3679
4077
|
}
|
|
3680
|
-
return
|
|
4078
|
+
return Object.freeze(strings);
|
|
4079
|
+
}
|
|
4080
|
+
function supports(domain) {
|
|
4081
|
+
return typeof domain === "string" && environment?.capabilities.domains.includes(domain) === true;
|
|
3681
4082
|
}
|
|
3682
4083
|
const shell2 = {
|
|
3683
4084
|
ready: () => ready,
|
|
@@ -3761,38 +4162,205 @@ function nappletNamespacePrelude(domains) {
|
|
|
3761
4162
|
};
|
|
3762
4163
|
}
|
|
3763
4164
|
function makeInc() {
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
4165
|
+
const topicStates = /* @__PURE__ */ new Map();
|
|
4166
|
+
const maxRetainedEvents = 32;
|
|
4167
|
+
const channelStates = /* @__PURE__ */ new Map();
|
|
4168
|
+
const openedHandlers = /* @__PURE__ */ new Set();
|
|
4169
|
+
const pendingOpened = [];
|
|
4170
|
+
const unopenedEvents = /* @__PURE__ */ new Map();
|
|
4171
|
+
const unopenedClosed = /* @__PURE__ */ new Map();
|
|
4172
|
+
function stableSubscriptionTopic(topic) {
|
|
4173
|
+
if (typeof topic !== "string") throw new TypeError("INC topic must be text");
|
|
4174
|
+
if (topic.includes("?") || topic.includes("#")) {
|
|
4175
|
+
throw new TypeError("INC subscriptions require a queryless topic identity");
|
|
4176
|
+
}
|
|
4177
|
+
return topic;
|
|
4178
|
+
}
|
|
4179
|
+
function closeChannelState(state, closed, notifyShell) {
|
|
4180
|
+
if (state.closed) return;
|
|
4181
|
+
state.closed = closed;
|
|
4182
|
+
if (notifyShell) fire({ type: "inc.channel.close", channelId: state.channelId });
|
|
4183
|
+
for (const handler of state.closedHandlers) handler(closed);
|
|
4184
|
+
}
|
|
4185
|
+
function makeChannelHandle(state) {
|
|
4186
|
+
return {
|
|
4187
|
+
id: state.channelId,
|
|
4188
|
+
peer: state.peer,
|
|
4189
|
+
emit(payload) {
|
|
4190
|
+
if (state.closed) return;
|
|
4191
|
+
fire({ type: "inc.channel.emit", channelId: state.channelId, ...payload === void 0 ? {} : { payload } });
|
|
4192
|
+
},
|
|
4193
|
+
on(handler) {
|
|
4194
|
+
if (typeof handler !== "function") throw new TypeError("INC channel event handler must be a function");
|
|
4195
|
+
if (state.closed) return subscriptionHandle(() => void 0);
|
|
4196
|
+
state.eventHandlers.add(handler);
|
|
4197
|
+
const pending = state.pendingEvents.splice(0);
|
|
4198
|
+
for (const event of pending) handler(event);
|
|
4199
|
+
return subscriptionHandle(() => state.eventHandlers.delete(handler));
|
|
4200
|
+
},
|
|
4201
|
+
onClosed(handler) {
|
|
4202
|
+
if (typeof handler !== "function") throw new TypeError("INC channel close handler must be a function");
|
|
4203
|
+
state.closedHandlers.add(handler);
|
|
4204
|
+
if (state.closed) handler(state.closed);
|
|
4205
|
+
return subscriptionHandle(() => state.closedHandlers.delete(handler));
|
|
4206
|
+
},
|
|
4207
|
+
close() {
|
|
4208
|
+
closeChannelState(state, { channelId: state.channelId }, true);
|
|
4209
|
+
}
|
|
4210
|
+
};
|
|
4211
|
+
}
|
|
4212
|
+
function materializeChannel(channelId, peer) {
|
|
4213
|
+
const existing = channelStates.get(channelId);
|
|
4214
|
+
if (existing) return existing;
|
|
4215
|
+
const state = {
|
|
4216
|
+
channelId,
|
|
4217
|
+
peer,
|
|
4218
|
+
eventHandlers: /* @__PURE__ */ new Set(),
|
|
4219
|
+
closedHandlers: /* @__PURE__ */ new Set(),
|
|
4220
|
+
pendingEvents: unopenedEvents.get(channelId) ?? []
|
|
4221
|
+
};
|
|
4222
|
+
unopenedEvents.delete(channelId);
|
|
4223
|
+
channelStates.set(channelId, state);
|
|
4224
|
+
const closed = unopenedClosed.get(channelId);
|
|
4225
|
+
unopenedClosed.delete(channelId);
|
|
4226
|
+
if (closed) closeChannelState(state, closed, false);
|
|
4227
|
+
return state;
|
|
4228
|
+
}
|
|
4229
|
+
function deliverOpened(state) {
|
|
4230
|
+
const handle = makeChannelHandle(state);
|
|
4231
|
+
if (openedHandlers.size > 0) {
|
|
4232
|
+
for (const handler of openedHandlers) handler(handle);
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
pendingOpened.push(state);
|
|
4236
|
+
}
|
|
4237
|
+
function receiveChannelEvent(channelId, sender, payloadPresent, payload) {
|
|
4238
|
+
const event = payloadPresent ? { channelId, sender, payload } : { channelId, sender };
|
|
4239
|
+
const state = channelStates.get(channelId);
|
|
4240
|
+
if (!state) {
|
|
4241
|
+
const pending = unopenedEvents.get(channelId) ?? [];
|
|
4242
|
+
if (pending.length >= maxRetainedEvents) {
|
|
4243
|
+
unopenedEvents.delete(channelId);
|
|
4244
|
+
unopenedClosed.set(channelId, { channelId, reason: "buffer overflow" });
|
|
4245
|
+
fire({ type: "inc.channel.close", channelId });
|
|
4246
|
+
return;
|
|
3773
4247
|
}
|
|
3774
|
-
|
|
4248
|
+
pending.push(event);
|
|
4249
|
+
unopenedEvents.set(channelId, pending);
|
|
4250
|
+
return;
|
|
4251
|
+
}
|
|
4252
|
+
if (state.closed) return;
|
|
4253
|
+
if (state.eventHandlers.size > 0) {
|
|
4254
|
+
for (const handler of state.eventHandlers) handler(event);
|
|
4255
|
+
return;
|
|
4256
|
+
}
|
|
4257
|
+
if (state.pendingEvents.length >= maxRetainedEvents) {
|
|
4258
|
+
closeChannelState(state, { channelId, reason: "buffer overflow" }, true);
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
4261
|
+
state.pendingEvents.push(event);
|
|
4262
|
+
}
|
|
4263
|
+
listen((event) => {
|
|
4264
|
+
if (!isParentMessage(event)) return;
|
|
4265
|
+
const msg = event.data;
|
|
4266
|
+
if (typeof msg !== "object" || msg === null || typeof msg.type !== "string") return;
|
|
4267
|
+
if (msg.type === "inc.channel.opened" && typeof msg.channelId === "string" && typeof msg.peer === "string") {
|
|
4268
|
+
deliverOpened(materializeChannel(msg.channelId, msg.peer));
|
|
4269
|
+
return;
|
|
4270
|
+
}
|
|
4271
|
+
if (msg.type === "inc.channel.event" && typeof msg.channelId === "string" && typeof msg.sender === "string") {
|
|
4272
|
+
receiveChannelEvent(msg.channelId, msg.sender, Object.prototype.hasOwnProperty.call(msg, "payload"), msg.payload);
|
|
4273
|
+
return;
|
|
4274
|
+
}
|
|
4275
|
+
if (msg.type === "inc.channel.closed" && typeof msg.channelId === "string") {
|
|
4276
|
+
const closed = typeof msg.reason === "string" ? { channelId: msg.channelId, reason: msg.reason } : { channelId: msg.channelId };
|
|
4277
|
+
const state = channelStates.get(msg.channelId);
|
|
4278
|
+
if (state) closeChannelState(state, closed, false);
|
|
4279
|
+
else unopenedClosed.set(msg.channelId, closed);
|
|
4280
|
+
}
|
|
4281
|
+
});
|
|
4282
|
+
return {
|
|
4283
|
+
emit(topic, payload) {
|
|
4284
|
+
const transposed = normalizeConventionUri(topic, arguments.length > 1);
|
|
4285
|
+
fire({
|
|
4286
|
+
type: "inc.emit",
|
|
4287
|
+
topic: transposed.topic,
|
|
4288
|
+
...transposed.payload === void 0 && payload === void 0 ? {} : { payload: transposed.payload ?? payload }
|
|
4289
|
+
});
|
|
3775
4290
|
},
|
|
3776
4291
|
on(topic, callback) {
|
|
3777
|
-
const
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
4292
|
+
const stableTopic = stableSubscriptionTopic(topic);
|
|
4293
|
+
if (typeof callback !== "function") throw new TypeError("INC subscription handler must be a function");
|
|
4294
|
+
let state = topicStates.get(stableTopic);
|
|
4295
|
+
if (!state) {
|
|
4296
|
+
const handlers = /* @__PURE__ */ new Set();
|
|
4297
|
+
const subscriptionId = id();
|
|
4298
|
+
const offEvent = listen((event) => {
|
|
4299
|
+
if (!isParentMessage(event)) return;
|
|
4300
|
+
const msg = event.data;
|
|
4301
|
+
if (typeof msg !== "object" || msg === null || msg.type !== "inc.event" || msg.topic !== stableTopic || typeof msg.sender !== "string") return;
|
|
4302
|
+
const delivered = Object.prototype.hasOwnProperty.call(msg, "payload") ? { topic: stableTopic, sender: msg.sender, payload: msg.payload } : { topic: stableTopic, sender: msg.sender };
|
|
4303
|
+
for (const handler of handlers) handler(delivered);
|
|
3789
4304
|
});
|
|
3790
|
-
|
|
3791
|
-
|
|
4305
|
+
const offResult = listen((event) => {
|
|
4306
|
+
if (!isParentMessage(event)) return;
|
|
4307
|
+
const msg = event.data;
|
|
4308
|
+
if (typeof msg !== "object" || msg === null || msg.type !== "inc.subscribe.result" || msg.id !== subscriptionId) return;
|
|
4309
|
+
offResult();
|
|
4310
|
+
if (typeof msg.error === "string" && msg.error) {
|
|
4311
|
+
offEvent();
|
|
4312
|
+
topicStates.delete(stableTopic);
|
|
4313
|
+
}
|
|
4314
|
+
});
|
|
4315
|
+
state = { handlers, offEvent, offResult };
|
|
4316
|
+
topicStates.set(stableTopic, state);
|
|
4317
|
+
fire({ type: "inc.subscribe", id: subscriptionId, topic: stableTopic });
|
|
4318
|
+
}
|
|
4319
|
+
state.handlers.add(callback);
|
|
3792
4320
|
return subscriptionHandle(() => {
|
|
3793
|
-
|
|
3794
|
-
|
|
4321
|
+
const current = topicStates.get(stableTopic);
|
|
4322
|
+
if (!current) return;
|
|
4323
|
+
current.handlers.delete(callback);
|
|
4324
|
+
if (current.handlers.size !== 0) return;
|
|
4325
|
+
current.offEvent();
|
|
4326
|
+
current.offResult();
|
|
4327
|
+
topicStates.delete(stableTopic);
|
|
4328
|
+
fire({ type: "inc.unsubscribe", topic: stableTopic });
|
|
3795
4329
|
});
|
|
4330
|
+
},
|
|
4331
|
+
channel: {
|
|
4332
|
+
open(targetDTag) {
|
|
4333
|
+
return request(
|
|
4334
|
+
{ type: "inc.channel.open", target: targetDTag },
|
|
4335
|
+
"inc.channel.open.result",
|
|
4336
|
+
(msg) => {
|
|
4337
|
+
if (typeof msg.channelId !== "string" || typeof msg.peer !== "string") {
|
|
4338
|
+
throw new Error("inc.channel.open.result missing channel identity");
|
|
4339
|
+
}
|
|
4340
|
+
return makeChannelHandle(materializeChannel(msg.channelId, msg.peer));
|
|
4341
|
+
}
|
|
4342
|
+
);
|
|
4343
|
+
},
|
|
4344
|
+
onOpened(handler) {
|
|
4345
|
+
if (typeof handler !== "function") throw new TypeError("INC opened handler must be a function");
|
|
4346
|
+
openedHandlers.add(handler);
|
|
4347
|
+
const pending = pendingOpened.splice(0);
|
|
4348
|
+
for (const state of pending) handler(makeChannelHandle(state));
|
|
4349
|
+
return subscriptionHandle(() => openedHandlers.delete(handler));
|
|
4350
|
+
},
|
|
4351
|
+
list() {
|
|
4352
|
+
return request(
|
|
4353
|
+
{ type: "inc.channel.list" },
|
|
4354
|
+
"inc.channel.list.result",
|
|
4355
|
+
(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({
|
|
4356
|
+
id: channel.id,
|
|
4357
|
+
peer: channel.peer
|
|
4358
|
+
})] : []))
|
|
4359
|
+
);
|
|
4360
|
+
},
|
|
4361
|
+
broadcast(payload) {
|
|
4362
|
+
fire({ type: "inc.channel.broadcast", ...payload === void 0 ? {} : { payload } });
|
|
4363
|
+
}
|
|
3796
4364
|
}
|
|
3797
4365
|
};
|
|
3798
4366
|
}
|
|
@@ -3830,14 +4398,22 @@ function nappletNamespacePrelude(domains) {
|
|
|
3830
4398
|
return { ...makeScope(), instance: makeScope("instance") };
|
|
3831
4399
|
}
|
|
3832
4400
|
function makeIdentity() {
|
|
3833
|
-
const read = (type, field, fallback) => request(
|
|
3834
|
-
{ type },
|
|
4401
|
+
const read = (type, field, fallback, payload = {}) => request(
|
|
4402
|
+
{ type, ...payload },
|
|
3835
4403
|
`${type}.result`,
|
|
3836
4404
|
(msg) => Object.prototype.hasOwnProperty.call(msg, field) ? msg[field] : fallback,
|
|
3837
4405
|
{ rejectOnError: type !== "identity.getPublicKey" }
|
|
3838
4406
|
);
|
|
3839
|
-
return {
|
|
4407
|
+
return Object.freeze({
|
|
3840
4408
|
getPublicKey: () => read("identity.getPublicKey", "pubkey", ""),
|
|
4409
|
+
getRelays: () => read("identity.getRelays", "relays", {}),
|
|
4410
|
+
getProfile: () => read("identity.getProfile", "profile", null),
|
|
4411
|
+
getFollows: () => read("identity.getFollows", "pubkeys", []),
|
|
4412
|
+
getList: (listType) => read("identity.getList", "entries", [], { listType }),
|
|
4413
|
+
getZaps: () => read("identity.getZaps", "zaps", []),
|
|
4414
|
+
getMutes: () => read("identity.getMutes", "pubkeys", []),
|
|
4415
|
+
getBlocked: () => read("identity.getBlocked", "pubkeys", []),
|
|
4416
|
+
getBadges: () => read("identity.getBadges", "badges", []),
|
|
3841
4417
|
onChanged(handler) {
|
|
3842
4418
|
const off = listen((event) => {
|
|
3843
4419
|
if (!isParentMessage(event)) return;
|
|
@@ -3846,20 +4422,8 @@ function nappletNamespacePrelude(domains) {
|
|
|
3846
4422
|
if (typeof msg.pubkey === "string") handler(msg.pubkey);
|
|
3847
4423
|
});
|
|
3848
4424
|
return subscriptionHandle(off);
|
|
3849
|
-
}
|
|
3850
|
-
|
|
3851
|
-
getProfile: () => read("identity.getProfile", "profile", null),
|
|
3852
|
-
getFollows: () => read("identity.getFollows", "pubkeys", []),
|
|
3853
|
-
getList: (listType) => request(
|
|
3854
|
-
{ type: "identity.getList", listType },
|
|
3855
|
-
"identity.getList.result",
|
|
3856
|
-
(msg) => Array.isArray(msg.entries) ? msg.entries : []
|
|
3857
|
-
),
|
|
3858
|
-
getZaps: () => read("identity.getZaps", "zaps", []),
|
|
3859
|
-
getMutes: () => read("identity.getMutes", "pubkeys", []),
|
|
3860
|
-
getBlocked: () => read("identity.getBlocked", "pubkeys", []),
|
|
3861
|
-
getBadges: () => read("identity.getBadges", "badges", [])
|
|
3862
|
-
};
|
|
4425
|
+
}
|
|
4426
|
+
});
|
|
3863
4427
|
}
|
|
3864
4428
|
function makeTheme() {
|
|
3865
4429
|
return {
|
|
@@ -4298,18 +4862,69 @@ function nappletNamespacePrelude(domains) {
|
|
|
4298
4862
|
};
|
|
4299
4863
|
}
|
|
4300
4864
|
function makeIntent() {
|
|
4301
|
-
const
|
|
4302
|
-
|
|
4303
|
-
"
|
|
4304
|
-
|
|
4305
|
-
|
|
4865
|
+
const hasOwn2 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
4866
|
+
const normalizeRequest = (value) => {
|
|
4867
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4868
|
+
throw new TypeError("Intent request must be an object");
|
|
4869
|
+
}
|
|
4870
|
+
const supplied = value;
|
|
4871
|
+
if (hasOwn2(supplied, "sender")) {
|
|
4872
|
+
throw new TypeError("Intent callers cannot supply sender");
|
|
4873
|
+
}
|
|
4874
|
+
const allowed22 = ["archetype", "action", "convention", "payload", "handler", "behavior"];
|
|
4875
|
+
if (Object.keys(supplied).some((field) => !allowed22.includes(field))) {
|
|
4876
|
+
throw new TypeError("Intent request contains unsupported fields");
|
|
4877
|
+
}
|
|
4878
|
+
if (typeof supplied.archetype !== "string" || supplied.archetype.length === 0) {
|
|
4879
|
+
throw new TypeError("Intent request requires an archetype");
|
|
4880
|
+
}
|
|
4881
|
+
const action = supplied.action === void 0 ? "open" : supplied.action;
|
|
4882
|
+
if (typeof action !== "string" || action.length === 0) {
|
|
4883
|
+
throw new TypeError("Intent action must be a non-empty string");
|
|
4884
|
+
}
|
|
4885
|
+
if (supplied.convention !== void 0 && (typeof supplied.convention !== "string" || !/^napplet:[^/?#\s]+\/[^/?#\s]+$/.test(supplied.convention))) {
|
|
4886
|
+
throw new TypeError("Intent convention must be queryless");
|
|
4887
|
+
}
|
|
4888
|
+
if (supplied.handler !== void 0 && (typeof supplied.handler !== "string" || supplied.handler.length === 0)) {
|
|
4889
|
+
throw new TypeError("Intent handler must be a non-empty string");
|
|
4890
|
+
}
|
|
4891
|
+
const behaviorValue = supplied?.behavior;
|
|
4892
|
+
const behavior = behaviorValue && typeof behaviorValue === "object" ? behaviorValue : void 0;
|
|
4893
|
+
if (behaviorValue !== void 0 && (!behavior || Array.isArray(behaviorValue) || Object.keys(behavior).some((field) => field !== "focus" && field !== "newWindow" && field !== "reuse") || hasOwn2(behavior, "focus") && typeof behavior.focus !== "boolean" || hasOwn2(behavior, "newWindow") && typeof behavior.newWindow !== "boolean" || hasOwn2(behavior, "reuse") && typeof behavior.reuse !== "boolean")) {
|
|
4894
|
+
throw new TypeError("Intent behavior contains unsupported fields");
|
|
4895
|
+
}
|
|
4896
|
+
const sanitizedBehavior = {
|
|
4897
|
+
...typeof behavior?.focus === "boolean" ? { focus: behavior.focus } : {},
|
|
4898
|
+
...typeof behavior?.newWindow === "boolean" ? { newWindow: behavior.newWindow } : {},
|
|
4899
|
+
...typeof behavior?.reuse === "boolean" ? { reuse: behavior.reuse } : {}
|
|
4900
|
+
};
|
|
4901
|
+
return {
|
|
4902
|
+
archetype: supplied.archetype,
|
|
4903
|
+
action,
|
|
4904
|
+
...typeof supplied.convention === "string" ? { convention: supplied.convention } : {},
|
|
4905
|
+
...hasOwn2(supplied, "payload") ? { payload: supplied.payload } : {},
|
|
4906
|
+
...typeof supplied.handler === "string" ? { handler: supplied.handler } : {},
|
|
4907
|
+
...Object.keys(sanitizedBehavior).length === 0 ? {} : { behavior: sanitizedBehavior }
|
|
4908
|
+
};
|
|
4909
|
+
};
|
|
4910
|
+
const invoke = (value) => {
|
|
4911
|
+
const normalized = normalizeRequest(value);
|
|
4912
|
+
return request(
|
|
4913
|
+
{
|
|
4914
|
+
type: "intent.invoke",
|
|
4915
|
+
request: normalized
|
|
4916
|
+
},
|
|
4917
|
+
"intent.invoke.result",
|
|
4918
|
+
(msg) => fieldOrThrow(msg, "result", "intent.invoke.result missing result")
|
|
4919
|
+
);
|
|
4920
|
+
};
|
|
4306
4921
|
return {
|
|
4307
4922
|
invoke,
|
|
4308
4923
|
open: (archetype, payload, opts) => invoke({
|
|
4309
4924
|
archetype,
|
|
4310
4925
|
action: "open",
|
|
4311
|
-
payload,
|
|
4312
|
-
...opts
|
|
4926
|
+
...payload === void 0 ? {} : { payload },
|
|
4927
|
+
...opts
|
|
4313
4928
|
}),
|
|
4314
4929
|
available: (archetype) => request(
|
|
4315
4930
|
{ type: "intent.available", archetype },
|
|
@@ -4460,14 +5075,40 @@ function nappletNamespacePrelude(domains) {
|
|
|
4460
5075
|
};
|
|
4461
5076
|
}
|
|
4462
5077
|
const shell = makeShell();
|
|
5078
|
+
let inc;
|
|
5079
|
+
let identity;
|
|
5080
|
+
let theme;
|
|
5081
|
+
let intent;
|
|
5082
|
+
function makeProtectedInc(existing) {
|
|
5083
|
+
const extensions = existing && typeof existing === "object" ? existing : {};
|
|
5084
|
+
inc ??= makeInc();
|
|
5085
|
+
return { ...extensions, ...inc };
|
|
5086
|
+
}
|
|
5087
|
+
function makeProtectedIdentity() {
|
|
5088
|
+
identity ??= makeIdentity();
|
|
5089
|
+
return identity;
|
|
5090
|
+
}
|
|
5091
|
+
function makeProtectedTheme() {
|
|
5092
|
+
theme ??= Object.freeze(makeTheme());
|
|
5093
|
+
return theme;
|
|
5094
|
+
}
|
|
5095
|
+
function makeProtectedIntent() {
|
|
5096
|
+
intent ??= Object.freeze(makeIntent());
|
|
5097
|
+
return intent;
|
|
5098
|
+
}
|
|
5099
|
+
function isProtectedDomain(domain) {
|
|
5100
|
+
return domain === "shell" || domain === "inc" || domain === "identity" || domain === "theme" || domain === "intent";
|
|
5101
|
+
}
|
|
4463
5102
|
function makeDomain(domain, existing) {
|
|
4464
5103
|
if (domain === "shell") return shell;
|
|
5104
|
+
if (domain === "inc") return makeProtectedInc(existing);
|
|
5105
|
+
if (domain === "identity") return makeProtectedIdentity();
|
|
5106
|
+
if (domain === "theme") return makeProtectedTheme();
|
|
5107
|
+
if (domain === "intent") return makeProtectedIntent();
|
|
4465
5108
|
if (existing && typeof existing === "object" && Object.keys(existing).length > 0) return existing;
|
|
4466
5109
|
switch (domain) {
|
|
4467
5110
|
case "relay":
|
|
4468
5111
|
return makeRelay();
|
|
4469
|
-
case "inc":
|
|
4470
|
-
return makeInc();
|
|
4471
5112
|
case "storage":
|
|
4472
5113
|
return makeStorage();
|
|
4473
5114
|
case "keys":
|
|
@@ -4476,10 +5117,6 @@ function nappletNamespacePrelude(domains) {
|
|
|
4476
5117
|
return makeMedia();
|
|
4477
5118
|
case "notify":
|
|
4478
5119
|
return makeNotify();
|
|
4479
|
-
case "identity":
|
|
4480
|
-
return makeIdentity();
|
|
4481
|
-
case "theme":
|
|
4482
|
-
return makeTheme();
|
|
4483
5120
|
case "config":
|
|
4484
5121
|
return makeConfig();
|
|
4485
5122
|
case "resource":
|
|
@@ -4490,8 +5127,6 @@ function nappletNamespacePrelude(domains) {
|
|
|
4490
5127
|
return makeOutbox();
|
|
4491
5128
|
case "upload":
|
|
4492
5129
|
return makeUpload();
|
|
4493
|
-
case "intent":
|
|
4494
|
-
return makeIntent();
|
|
4495
5130
|
case "webrtc":
|
|
4496
5131
|
return makeWebrtc();
|
|
4497
5132
|
case "ble":
|
|
@@ -4521,10 +5156,22 @@ function nappletNamespacePrelude(domains) {
|
|
|
4521
5156
|
},
|
|
4522
5157
|
defineProperty(obj, prop, descriptor) {
|
|
4523
5158
|
if (typeof prop !== "string" || !allowed2.has(prop)) return true;
|
|
5159
|
+
if (isProtectedDomain(prop)) {
|
|
5160
|
+
return Reflect.defineProperty(obj, prop, {
|
|
5161
|
+
value: makeDomain(prop, descriptor.value),
|
|
5162
|
+
enumerable: true,
|
|
5163
|
+
configurable: true,
|
|
5164
|
+
writable: true
|
|
5165
|
+
});
|
|
5166
|
+
}
|
|
4524
5167
|
return Reflect.defineProperty(obj, prop, {
|
|
4525
5168
|
...descriptor,
|
|
4526
5169
|
value: makeDomain(prop, descriptor.value)
|
|
4527
5170
|
});
|
|
5171
|
+
},
|
|
5172
|
+
deleteProperty(obj, prop) {
|
|
5173
|
+
if (typeof prop === "string" && allowed2.has(prop) && isProtectedDomain(prop)) return true;
|
|
5174
|
+
return Reflect.deleteProperty(obj, prop);
|
|
4528
5175
|
}
|
|
4529
5176
|
});
|
|
4530
5177
|
}
|
|
@@ -4554,7 +5201,7 @@ function nappletNamespacePrelude(domains) {
|
|
|
4554
5201
|
root = buildNappletNamespace(value);
|
|
4555
5202
|
},
|
|
4556
5203
|
enumerable: false,
|
|
4557
|
-
configurable:
|
|
5204
|
+
configurable: false
|
|
4558
5205
|
});
|
|
4559
5206
|
}
|
|
4560
5207
|
|
|
@@ -8159,14 +8806,11 @@ function enforceLimit(store2, list) {
|
|
|
8159
8806
|
list.shift();
|
|
8160
8807
|
}
|
|
8161
8808
|
}
|
|
8162
|
-
function findById(store2, id) {
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
}
|
|
8168
|
-
}
|
|
8169
|
-
return void 0;
|
|
8809
|
+
function findById(store2, windowId, id) {
|
|
8810
|
+
const list = store2.notifications.get(windowId);
|
|
8811
|
+
const index = list?.findIndex((notification) => notification.id === id) ?? -1;
|
|
8812
|
+
if (!list || index === -1) return void 0;
|
|
8813
|
+
return [list[index], index];
|
|
8170
8814
|
}
|
|
8171
8815
|
function createNotification(store2, windowId, title, body) {
|
|
8172
8816
|
const notification = {
|
|
@@ -8183,20 +8827,20 @@ function createNotification(store2, windowId, title, body) {
|
|
|
8183
8827
|
notify(store2);
|
|
8184
8828
|
return notification;
|
|
8185
8829
|
}
|
|
8186
|
-
function dismissNotification(store2, id) {
|
|
8187
|
-
const found = findById(store2, id);
|
|
8830
|
+
function dismissNotification(store2, windowId, id) {
|
|
8831
|
+
const found = findById(store2, windowId, id);
|
|
8188
8832
|
if (!found) return;
|
|
8189
|
-
const [
|
|
8190
|
-
const list = store2.notifications.get(
|
|
8833
|
+
const [, index] = found;
|
|
8834
|
+
const list = store2.notifications.get(windowId);
|
|
8191
8835
|
if (!list) return;
|
|
8192
8836
|
list.splice(index, 1);
|
|
8193
|
-
if (list.length === 0) store2.notifications.delete(
|
|
8837
|
+
if (list.length === 0) store2.notifications.delete(windowId);
|
|
8194
8838
|
notify(store2);
|
|
8195
8839
|
}
|
|
8196
|
-
function markNotificationRead(store2, id) {
|
|
8197
|
-
const found = findById(store2, id);
|
|
8840
|
+
function markNotificationRead(store2, windowId, id) {
|
|
8841
|
+
const found = findById(store2, windowId, id);
|
|
8198
8842
|
if (!found) return;
|
|
8199
|
-
const [
|
|
8843
|
+
const [notification] = found;
|
|
8200
8844
|
if (!notification.read) {
|
|
8201
8845
|
notification.read = true;
|
|
8202
8846
|
notify(store2);
|
|
@@ -8213,12 +8857,12 @@ function handleNotifyEnvelope(store2, windowId, action, msg, send) {
|
|
|
8213
8857
|
}
|
|
8214
8858
|
case "dismiss": {
|
|
8215
8859
|
const notifId = typeof msg.notificationId === "string" ? msg.notificationId : "";
|
|
8216
|
-
if (notifId) dismissNotification(store2, notifId);
|
|
8860
|
+
if (notifId) dismissNotification(store2, windowId, notifId);
|
|
8217
8861
|
break;
|
|
8218
8862
|
}
|
|
8219
8863
|
case "read": {
|
|
8220
8864
|
const notifId = typeof msg.notificationId === "string" ? msg.notificationId : "";
|
|
8221
|
-
if (notifId) markNotificationRead(store2, notifId);
|
|
8865
|
+
if (notifId) markNotificationRead(store2, windowId, notifId);
|
|
8222
8866
|
break;
|
|
8223
8867
|
}
|
|
8224
8868
|
case "list": {
|
|
@@ -8230,34 +8874,6 @@ function handleNotifyEnvelope(store2, windowId, action, msg, send) {
|
|
|
8230
8874
|
break;
|
|
8231
8875
|
}
|
|
8232
8876
|
}
|
|
8233
|
-
function handleIncNotification(store2, windowId, action, payload, send) {
|
|
8234
|
-
switch (action) {
|
|
8235
|
-
case "create": {
|
|
8236
|
-
const title = typeof payload.title === "string" ? payload.title : "";
|
|
8237
|
-
const body = typeof payload.body === "string" ? payload.body : "";
|
|
8238
|
-
const notification = createNotification(store2, windowId, title, body);
|
|
8239
|
-
send({ type: "inc.event", topic: "notifications:created", payload: { id: notification.id } });
|
|
8240
|
-
break;
|
|
8241
|
-
}
|
|
8242
|
-
case "dismiss": {
|
|
8243
|
-
const id = typeof payload.id === "string" ? payload.id : "";
|
|
8244
|
-
if (id) dismissNotification(store2, id);
|
|
8245
|
-
break;
|
|
8246
|
-
}
|
|
8247
|
-
case "read": {
|
|
8248
|
-
const id = typeof payload.id === "string" ? payload.id : "";
|
|
8249
|
-
if (id) markNotificationRead(store2, id);
|
|
8250
|
-
break;
|
|
8251
|
-
}
|
|
8252
|
-
case "list": {
|
|
8253
|
-
const windowNotifs = store2.notifications.get(windowId) ?? [];
|
|
8254
|
-
send({ type: "inc.event", topic: "notifications:listed", payload: { notifications: windowNotifs } });
|
|
8255
|
-
break;
|
|
8256
|
-
}
|
|
8257
|
-
default:
|
|
8258
|
-
break;
|
|
8259
|
-
}
|
|
8260
|
-
}
|
|
8261
8877
|
function createNotificationService(options) {
|
|
8262
8878
|
const store2 = {
|
|
8263
8879
|
notifications: /* @__PURE__ */ new Map(),
|
|
@@ -8265,23 +8881,21 @@ function createNotificationService(options) {
|
|
|
8265
8881
|
maxPerWindow: options?.maxPerWindow ?? DEFAULT_MAX_PER_WINDOW
|
|
8266
8882
|
};
|
|
8267
8883
|
const descriptor = {
|
|
8268
|
-
name: "
|
|
8884
|
+
name: "notify",
|
|
8269
8885
|
version: NOTIFICATION_SERVICE_VERSION,
|
|
8270
8886
|
description: "Notification state registry \u2014 tracks notifications per napplet window"
|
|
8271
8887
|
};
|
|
8272
8888
|
return {
|
|
8273
8889
|
descriptor,
|
|
8274
8890
|
handleMessage(windowId, message, send) {
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
const payload = msg.payload ?? {};
|
|
8284
|
-
handleIncNotification(store2, windowId, topic.slice(14), payload, send);
|
|
8891
|
+
if (!message.type.startsWith("notify.")) return;
|
|
8892
|
+
handleNotifyEnvelope(
|
|
8893
|
+
store2,
|
|
8894
|
+
windowId,
|
|
8895
|
+
message.type.slice(7),
|
|
8896
|
+
message,
|
|
8897
|
+
send
|
|
8898
|
+
);
|
|
8285
8899
|
},
|
|
8286
8900
|
onWindowDestroyed(windowId) {
|
|
8287
8901
|
if (store2.notifications.delete(windowId)) {
|
|
@@ -8291,16 +8905,16 @@ function createNotificationService(options) {
|
|
|
8291
8905
|
};
|
|
8292
8906
|
}
|
|
8293
8907
|
var IDENTITY_SERVICE_VERSION = "1.0.0";
|
|
8294
|
-
function sendProviderError(send, result2, fallback,
|
|
8908
|
+
function sendProviderError(send, result2, fallback, _err) {
|
|
8295
8909
|
send({
|
|
8296
8910
|
...result2,
|
|
8297
|
-
error:
|
|
8911
|
+
error: fallback
|
|
8298
8912
|
});
|
|
8299
8913
|
}
|
|
8300
8914
|
async function getCurrentPubkey(options) {
|
|
8301
|
-
const currentSigner = options.getSigner();
|
|
8302
|
-
if (!currentSigner?.getPublicKey) return "";
|
|
8303
8915
|
try {
|
|
8916
|
+
const currentSigner = options.getSigner();
|
|
8917
|
+
if (!currentSigner?.getPublicKey) return "";
|
|
8304
8918
|
return await currentSigner.getPublicKey() ?? "";
|
|
8305
8919
|
} catch {
|
|
8306
8920
|
return "";
|
|
@@ -8313,46 +8927,29 @@ function sendOptionalProviderResult(options, send, fallbackResult, errorFallback
|
|
|
8313
8927
|
}
|
|
8314
8928
|
Promise.resolve(getCurrentPubkey(options)).then((pubkey) => buildResult(pubkey)).then((result2) => send(result2)).catch((err2) => sendProviderError(send, fallbackResult, errorFallback, err2));
|
|
8315
8929
|
}
|
|
8316
|
-
function sendIdentityError(send, id, typeBase, error) {
|
|
8317
|
-
send({ type: `${typeBase}.error`, id, error });
|
|
8318
|
-
}
|
|
8319
|
-
function sendSignerError(send, id, typeBase, fallback, err2) {
|
|
8320
|
-
sendIdentityError(send, id, typeBase, err2?.message ?? fallback);
|
|
8321
|
-
}
|
|
8322
8930
|
function handleGetPublicKey(options, id, send) {
|
|
8323
|
-
|
|
8324
|
-
if (!signer) {
|
|
8325
|
-
const result2 = {
|
|
8326
|
-
type: "identity.getPublicKey.result",
|
|
8327
|
-
id,
|
|
8328
|
-
pubkey: ""
|
|
8329
|
-
};
|
|
8330
|
-
send(result2);
|
|
8331
|
-
return;
|
|
8332
|
-
}
|
|
8333
|
-
Promise.resolve(signer.getPublicKey?.()).then((pubkey) => {
|
|
8931
|
+
Promise.resolve().then(() => options.getSigner()).then(async (signer) => signer?.getPublicKey ? signer.getPublicKey() : "").catch(() => "").then((pubkey) => {
|
|
8334
8932
|
const result2 = {
|
|
8335
8933
|
type: "identity.getPublicKey.result",
|
|
8336
8934
|
id,
|
|
8337
8935
|
pubkey: pubkey ?? ""
|
|
8338
8936
|
};
|
|
8339
8937
|
send(result2);
|
|
8340
|
-
})
|
|
8938
|
+
});
|
|
8341
8939
|
}
|
|
8342
8940
|
function handleGetRelays(options, id, send) {
|
|
8343
|
-
const
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
}
|
|
8348
|
-
Promise.resolve(
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
relays
|
|
8941
|
+
const fallbackResult = {
|
|
8942
|
+
type: "identity.getRelays.result",
|
|
8943
|
+
id,
|
|
8944
|
+
relays: {}
|
|
8945
|
+
};
|
|
8946
|
+
Promise.resolve().then(() => options.getSigner()).then(async (signer) => {
|
|
8947
|
+
if (!signer?.getRelays) return fallbackResult;
|
|
8948
|
+
return {
|
|
8949
|
+
...fallbackResult,
|
|
8950
|
+
relays: await signer.getRelays() ?? {}
|
|
8353
8951
|
};
|
|
8354
|
-
|
|
8355
|
-
}).catch((err2) => sendSignerError(send, id, "identity.getRelays", "getRelays failed", err2));
|
|
8952
|
+
}).catch(() => ({ ...fallbackResult, error: "identity.getRelays failed" })).then(send);
|
|
8356
8953
|
}
|
|
8357
8954
|
function handleReadProvider(options, id, message, send) {
|
|
8358
8955
|
switch (message.type) {
|
|
@@ -8361,11 +8958,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8361
8958
|
options,
|
|
8362
8959
|
send,
|
|
8363
8960
|
{ type: "identity.getProfile.result", id, profile: null },
|
|
8364
|
-
"getProfile failed",
|
|
8961
|
+
"identity.getProfile failed",
|
|
8365
8962
|
options.getProfile ? async (pubkey) => ({
|
|
8366
8963
|
type: "identity.getProfile.result",
|
|
8367
8964
|
id,
|
|
8368
|
-
profile: await options.getProfile(pubkey, message)
|
|
8965
|
+
profile: await options.getProfile(pubkey, message) ?? null
|
|
8369
8966
|
}) : void 0
|
|
8370
8967
|
);
|
|
8371
8968
|
return true;
|
|
@@ -8374,11 +8971,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8374
8971
|
options,
|
|
8375
8972
|
send,
|
|
8376
8973
|
{ type: "identity.getFollows.result", id, pubkeys: [] },
|
|
8377
|
-
"getFollows failed",
|
|
8974
|
+
"identity.getFollows failed",
|
|
8378
8975
|
options.getFollows ? async (pubkey) => ({
|
|
8379
8976
|
type: "identity.getFollows.result",
|
|
8380
8977
|
id,
|
|
8381
|
-
pubkeys: await options.getFollows(pubkey, message)
|
|
8978
|
+
pubkeys: await options.getFollows(pubkey, message) ?? []
|
|
8382
8979
|
}) : void 0
|
|
8383
8980
|
);
|
|
8384
8981
|
return true;
|
|
@@ -8390,11 +8987,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8390
8987
|
options,
|
|
8391
8988
|
send,
|
|
8392
8989
|
{ type: "identity.getZaps.result", id, zaps: [] },
|
|
8393
|
-
"getZaps failed",
|
|
8990
|
+
"identity.getZaps failed",
|
|
8394
8991
|
options.getZaps ? async (pubkey) => ({
|
|
8395
8992
|
type: "identity.getZaps.result",
|
|
8396
8993
|
id,
|
|
8397
|
-
zaps: await options.getZaps(pubkey, message)
|
|
8994
|
+
zaps: await options.getZaps(pubkey, message) ?? []
|
|
8398
8995
|
}) : void 0
|
|
8399
8996
|
);
|
|
8400
8997
|
return true;
|
|
@@ -8403,11 +9000,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8403
9000
|
options,
|
|
8404
9001
|
send,
|
|
8405
9002
|
{ type: "identity.getMutes.result", id, pubkeys: [] },
|
|
8406
|
-
"getMutes failed",
|
|
9003
|
+
"identity.getMutes failed",
|
|
8407
9004
|
options.getMutes ? async (pubkey) => ({
|
|
8408
9005
|
type: "identity.getMutes.result",
|
|
8409
9006
|
id,
|
|
8410
|
-
pubkeys: await options.getMutes(pubkey, message)
|
|
9007
|
+
pubkeys: await options.getMutes(pubkey, message) ?? []
|
|
8411
9008
|
}) : void 0
|
|
8412
9009
|
);
|
|
8413
9010
|
return true;
|
|
@@ -8416,11 +9013,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8416
9013
|
options,
|
|
8417
9014
|
send,
|
|
8418
9015
|
{ type: "identity.getBlocked.result", id, pubkeys: [] },
|
|
8419
|
-
"getBlocked failed",
|
|
9016
|
+
"identity.getBlocked failed",
|
|
8420
9017
|
options.getBlocked ? async (pubkey) => ({
|
|
8421
9018
|
type: "identity.getBlocked.result",
|
|
8422
9019
|
id,
|
|
8423
|
-
pubkeys: await options.getBlocked(pubkey, message)
|
|
9020
|
+
pubkeys: await options.getBlocked(pubkey, message) ?? []
|
|
8424
9021
|
}) : void 0
|
|
8425
9022
|
);
|
|
8426
9023
|
return true;
|
|
@@ -8429,11 +9026,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8429
9026
|
options,
|
|
8430
9027
|
send,
|
|
8431
9028
|
{ type: "identity.getBadges.result", id, badges: [] },
|
|
8432
|
-
"getBadges failed",
|
|
9029
|
+
"identity.getBadges failed",
|
|
8433
9030
|
options.getBadges ? async (pubkey) => ({
|
|
8434
9031
|
type: "identity.getBadges.result",
|
|
8435
9032
|
id,
|
|
8436
|
-
badges: await options.getBadges(pubkey, message)
|
|
9033
|
+
badges: await options.getBadges(pubkey, message) ?? []
|
|
8437
9034
|
}) : void 0
|
|
8438
9035
|
);
|
|
8439
9036
|
return true;
|
|
@@ -8446,11 +9043,11 @@ function handleGetList(options, id, message, send) {
|
|
|
8446
9043
|
options,
|
|
8447
9044
|
send,
|
|
8448
9045
|
{ type: "identity.getList.result", id, entries: [] },
|
|
8449
|
-
"getList failed",
|
|
9046
|
+
"identity.getList failed",
|
|
8450
9047
|
options.getList ? async (pubkey) => ({
|
|
8451
9048
|
type: "identity.getList.result",
|
|
8452
9049
|
id,
|
|
8453
|
-
entries: await options.getList(message.listType, pubkey, message)
|
|
9050
|
+
entries: await options.getList(message.listType, pubkey, message) ?? []
|
|
8454
9051
|
}) : void 0
|
|
8455
9052
|
);
|
|
8456
9053
|
}
|
|
@@ -8464,9 +9061,7 @@ function handleIdentityServiceMessage(options, message, send) {
|
|
|
8464
9061
|
handleGetRelays(options, id, send);
|
|
8465
9062
|
return;
|
|
8466
9063
|
default:
|
|
8467
|
-
|
|
8468
|
-
sendIdentityError(send, id, message.type, `Unknown identity method: ${message.type}`);
|
|
8469
|
-
}
|
|
9064
|
+
handleReadProvider(options, id, message, send);
|
|
8470
9065
|
}
|
|
8471
9066
|
}
|
|
8472
9067
|
function createIdentityService(options) {
|
|
@@ -8487,6 +9082,37 @@ function createIdentityService(options) {
|
|
|
8487
9082
|
var EOSE_FALLBACK_MS = 15e3;
|
|
8488
9083
|
function createRelayPoolService(options) {
|
|
8489
9084
|
const tracked = /* @__PURE__ */ new Map();
|
|
9085
|
+
function publishEvent(relayMessage, resultType, send) {
|
|
9086
|
+
const id = typeof relayMessage.id === "string" ? relayMessage.id : "";
|
|
9087
|
+
const event = relayMessage.event;
|
|
9088
|
+
if (!event || typeof event !== "object") {
|
|
9089
|
+
send({ type: resultType, id, ok: false, error: "invalid event" });
|
|
9090
|
+
return;
|
|
9091
|
+
}
|
|
9092
|
+
if (!options.isAvailable()) {
|
|
9093
|
+
send({ type: resultType, id, ok: false, error: "no relay pool available" });
|
|
9094
|
+
return;
|
|
9095
|
+
}
|
|
9096
|
+
void (async () => {
|
|
9097
|
+
try {
|
|
9098
|
+
await options.publish(event);
|
|
9099
|
+
send({
|
|
9100
|
+
type: resultType,
|
|
9101
|
+
id,
|
|
9102
|
+
ok: true,
|
|
9103
|
+
event,
|
|
9104
|
+
eventId: event.id
|
|
9105
|
+
});
|
|
9106
|
+
} catch (error) {
|
|
9107
|
+
send({
|
|
9108
|
+
type: resultType,
|
|
9109
|
+
id,
|
|
9110
|
+
ok: false,
|
|
9111
|
+
error: error instanceof Error && error.message ? error.message : "publish failed"
|
|
9112
|
+
});
|
|
9113
|
+
}
|
|
9114
|
+
})();
|
|
9115
|
+
}
|
|
8490
9116
|
return {
|
|
8491
9117
|
descriptor: {
|
|
8492
9118
|
name: "relay-pool",
|
|
@@ -8546,17 +9172,11 @@ function createRelayPoolService(options) {
|
|
|
8546
9172
|
return;
|
|
8547
9173
|
}
|
|
8548
9174
|
if (message.type === "relay.publish") {
|
|
8549
|
-
|
|
8550
|
-
if (event && typeof event === "object" && options.isAvailable()) {
|
|
8551
|
-
options.publish(event);
|
|
8552
|
-
}
|
|
9175
|
+
publishEvent(relayMessage, "relay.publish.result", send);
|
|
8553
9176
|
return;
|
|
8554
9177
|
}
|
|
8555
9178
|
if (message.type === "relay.publishEncrypted") {
|
|
8556
|
-
|
|
8557
|
-
if (event && typeof event === "object" && options.isAvailable()) {
|
|
8558
|
-
options.publish(event);
|
|
8559
|
-
}
|
|
9179
|
+
publishEvent(relayMessage, "relay.publishEncrypted.result", send);
|
|
8560
9180
|
return;
|
|
8561
9181
|
}
|
|
8562
9182
|
},
|
|
@@ -9488,8 +10108,15 @@ var DEFAULT_THEME = {
|
|
|
9488
10108
|
// fonts, background, title intentionally undefined — all optional per
|
|
9489
10109
|
// @napplet/nap/theme Theme interface.
|
|
9490
10110
|
};
|
|
10111
|
+
function isCompleteTheme(theme) {
|
|
10112
|
+
const colors = theme?.colors;
|
|
10113
|
+
return typeof colors?.background === "string" && typeof colors.text === "string" && typeof colors.primary === "string";
|
|
10114
|
+
}
|
|
10115
|
+
function normalizeTheme(theme) {
|
|
10116
|
+
return isCompleteTheme(theme) ? theme : DEFAULT_THEME;
|
|
10117
|
+
}
|
|
9491
10118
|
function createThemeService(options = {}) {
|
|
9492
|
-
let currentTheme = options.initialTheme
|
|
10119
|
+
let currentTheme = normalizeTheme(options.initialTheme);
|
|
9493
10120
|
const descriptor = {
|
|
9494
10121
|
name: "theme",
|
|
9495
10122
|
version: THEME_SERVICE_VERSION,
|
|
@@ -9508,19 +10135,15 @@ function createThemeService(options = {}) {
|
|
|
9508
10135
|
send(result2);
|
|
9509
10136
|
return;
|
|
9510
10137
|
}
|
|
9511
|
-
|
|
9512
|
-
type: `${message.type}.error`,
|
|
9513
|
-
id,
|
|
9514
|
-
error: `Unknown theme method: ${message.type}`
|
|
9515
|
-
});
|
|
10138
|
+
return;
|
|
9516
10139
|
},
|
|
9517
10140
|
// Theme service has no per-window state to clean up.
|
|
9518
10141
|
onWindowDestroyed(_windowId) {
|
|
9519
10142
|
}
|
|
9520
10143
|
};
|
|
9521
10144
|
function publishTheme(theme) {
|
|
9522
|
-
currentTheme = theme;
|
|
9523
|
-
const envelope = { type: "theme.changed", theme };
|
|
10145
|
+
currentTheme = normalizeTheme(theme);
|
|
10146
|
+
const envelope = { type: "theme.changed", theme: currentTheme };
|
|
9524
10147
|
options.onBroadcast?.(envelope);
|
|
9525
10148
|
return envelope;
|
|
9526
10149
|
}
|
|
@@ -11071,42 +11694,132 @@ function toErrorMessage4(err2) {
|
|
|
11071
11694
|
if (typeof err2 === "string") return err2;
|
|
11072
11695
|
return "intent request failed";
|
|
11073
11696
|
}
|
|
11697
|
+
function hasOwn(value, key) {
|
|
11698
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
11699
|
+
}
|
|
11700
|
+
function validateIntentRequest(value) {
|
|
11701
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
11702
|
+
return { error: "invalid request" };
|
|
11703
|
+
}
|
|
11704
|
+
const request = value;
|
|
11705
|
+
if (typeof request.archetype !== "string" || request.archetype.length === 0) {
|
|
11706
|
+
return { error: "invalid request" };
|
|
11707
|
+
}
|
|
11708
|
+
const action = request.action === void 0 ? "open" : request.action;
|
|
11709
|
+
if (typeof action !== "string" || action.length === 0) {
|
|
11710
|
+
return { error: "invalid request" };
|
|
11711
|
+
}
|
|
11712
|
+
if (request.convention !== void 0 && (typeof request.convention !== "string" || !/^napplet:[^/?#\s]+\/[^/?#\s]+$/.test(request.convention))) {
|
|
11713
|
+
return { error: "invalid convention" };
|
|
11714
|
+
}
|
|
11715
|
+
const allowedKeys = ["archetype", "action", "convention", "payload", "handler", "behavior"];
|
|
11716
|
+
if (Object.keys(request).some((key) => !allowedKeys.includes(key))) {
|
|
11717
|
+
return { error: "invoke rejected" };
|
|
11718
|
+
}
|
|
11719
|
+
if (hasOwn(request, "handler") && request.handler !== void 0 && (typeof request.handler !== "string" || request.handler.length === 0)) {
|
|
11720
|
+
return { error: "invoke rejected" };
|
|
11721
|
+
}
|
|
11722
|
+
let behavior;
|
|
11723
|
+
if (hasOwn(request, "behavior") && request.behavior !== void 0) {
|
|
11724
|
+
if (typeof request.behavior !== "object" || request.behavior === null || Array.isArray(request.behavior)) {
|
|
11725
|
+
return { error: "invoke rejected" };
|
|
11726
|
+
}
|
|
11727
|
+
const record = request.behavior;
|
|
11728
|
+
if (Object.keys(record).some((key) => key !== "focus" && key !== "newWindow" && key !== "reuse") || hasOwn(record, "focus") && typeof record.focus !== "boolean" || hasOwn(record, "newWindow") && typeof record.newWindow !== "boolean" || hasOwn(record, "reuse") && typeof record.reuse !== "boolean") {
|
|
11729
|
+
return { error: "invoke rejected" };
|
|
11730
|
+
}
|
|
11731
|
+
behavior = {
|
|
11732
|
+
...record.focus === void 0 ? {} : { focus: record.focus },
|
|
11733
|
+
...record.newWindow === void 0 ? {} : { newWindow: record.newWindow },
|
|
11734
|
+
...record.reuse === void 0 ? {} : { reuse: record.reuse }
|
|
11735
|
+
};
|
|
11736
|
+
}
|
|
11737
|
+
return {
|
|
11738
|
+
request: {
|
|
11739
|
+
archetype: request.archetype,
|
|
11740
|
+
action,
|
|
11741
|
+
...typeof request.convention === "string" ? { convention: request.convention } : {},
|
|
11742
|
+
...hasOwn(request, "payload") ? { payload: request.payload } : {},
|
|
11743
|
+
...typeof request.handler === "string" ? { handler: request.handler } : {},
|
|
11744
|
+
...behavior === void 0 ? {} : { behavior }
|
|
11745
|
+
}
|
|
11746
|
+
};
|
|
11747
|
+
}
|
|
11748
|
+
function settleResolverCall(call, send, resultType, id, onValue) {
|
|
11749
|
+
let pending;
|
|
11750
|
+
try {
|
|
11751
|
+
pending = Promise.resolve(call());
|
|
11752
|
+
} catch (error) {
|
|
11753
|
+
send({ type: resultType, id, error: toErrorMessage4(error) });
|
|
11754
|
+
return;
|
|
11755
|
+
}
|
|
11756
|
+
pending.then((value) => send(onValue(value))).catch((error) => send({ type: resultType, id, error: toErrorMessage4(error) }));
|
|
11757
|
+
}
|
|
11758
|
+
function rejectedResult(request, error) {
|
|
11759
|
+
return {
|
|
11760
|
+
ok: false,
|
|
11761
|
+
archetype: request.archetype,
|
|
11762
|
+
action: request.action ?? "open",
|
|
11763
|
+
handled: false,
|
|
11764
|
+
error
|
|
11765
|
+
};
|
|
11766
|
+
}
|
|
11767
|
+
function rejectInvoke(send, id, request, error = "invoke rejected") {
|
|
11768
|
+
send({
|
|
11769
|
+
type: "intent.invoke.result",
|
|
11770
|
+
id,
|
|
11771
|
+
result: rejectedResult(request, error)
|
|
11772
|
+
});
|
|
11773
|
+
}
|
|
11074
11774
|
function createIntentService(options) {
|
|
11075
11775
|
if (!options || typeof options.resolver !== "object" || options.resolver === null) {
|
|
11076
11776
|
throw new Error("createIntentService: options.resolver is required");
|
|
11077
11777
|
}
|
|
11078
11778
|
const { resolver } = options;
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
for (const send of windows.values()) {
|
|
11082
|
-
send({ type: "intent.changed", availability });
|
|
11083
|
-
}
|
|
11084
|
-
});
|
|
11085
|
-
function settle6(call, send, resultType, id, onValue) {
|
|
11086
|
-
let pending;
|
|
11087
|
-
try {
|
|
11088
|
-
pending = Promise.resolve(call());
|
|
11089
|
-
} catch (err2) {
|
|
11090
|
-
send({ type: resultType, id, error: toErrorMessage4(err2) });
|
|
11091
|
-
return;
|
|
11092
|
-
}
|
|
11093
|
-
pending.then((value) => send(onValue(value))).catch((err2) => send({ type: resultType, id, error: toErrorMessage4(err2) }));
|
|
11094
|
-
}
|
|
11779
|
+
let runtimeContext;
|
|
11780
|
+
let unsubscribeChanged;
|
|
11095
11781
|
function handleInvoke(windowId, msg, send) {
|
|
11096
11782
|
const m = msg;
|
|
11097
11783
|
const id = m.id ?? "";
|
|
11098
|
-
const
|
|
11099
|
-
if (
|
|
11100
|
-
|
|
11784
|
+
const validation = validateIntentRequest(m.request);
|
|
11785
|
+
if ("error" in validation) {
|
|
11786
|
+
const raw = typeof m.request === "object" && m.request !== null ? m.request : {};
|
|
11787
|
+
rejectInvoke(
|
|
11788
|
+
send,
|
|
11789
|
+
id,
|
|
11790
|
+
{
|
|
11791
|
+
archetype: typeof raw.archetype === "string" ? raw.archetype : "",
|
|
11792
|
+
action: typeof raw.action === "string" ? raw.action : "open"
|
|
11793
|
+
},
|
|
11794
|
+
validation.error
|
|
11795
|
+
);
|
|
11101
11796
|
return;
|
|
11102
11797
|
}
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
send,
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11798
|
+
const sender = runtimeContext?.resolveDTag(windowId);
|
|
11799
|
+
if (!sender) {
|
|
11800
|
+
rejectInvoke(send, id, validation.request);
|
|
11801
|
+
return;
|
|
11802
|
+
}
|
|
11803
|
+
let pending;
|
|
11804
|
+
try {
|
|
11805
|
+
pending = Promise.resolve(resolver.invoke(validation.request, { sender }));
|
|
11806
|
+
} catch {
|
|
11807
|
+
rejectInvoke(send, id, validation.request);
|
|
11808
|
+
return;
|
|
11809
|
+
}
|
|
11810
|
+
void pending.then(
|
|
11811
|
+
(result2) => {
|
|
11812
|
+
send({
|
|
11813
|
+
type: "intent.invoke.result",
|
|
11814
|
+
id,
|
|
11815
|
+
result: result2
|
|
11816
|
+
});
|
|
11817
|
+
},
|
|
11818
|
+
() => {
|
|
11819
|
+
rejectInvoke(send, id, validation.request);
|
|
11820
|
+
}
|
|
11821
|
+
).catch(() => {
|
|
11822
|
+
});
|
|
11110
11823
|
}
|
|
11111
11824
|
function handleAvailable(msg, send) {
|
|
11112
11825
|
const m = msg;
|
|
@@ -11116,7 +11829,7 @@ function createIntentService(options) {
|
|
|
11116
11829
|
return;
|
|
11117
11830
|
}
|
|
11118
11831
|
const archetype = m.archetype;
|
|
11119
|
-
|
|
11832
|
+
settleResolverCall(
|
|
11120
11833
|
() => resolver.available(archetype),
|
|
11121
11834
|
send,
|
|
11122
11835
|
"intent.available.result",
|
|
@@ -11127,7 +11840,7 @@ function createIntentService(options) {
|
|
|
11127
11840
|
function handleHandlers(msg, send) {
|
|
11128
11841
|
const m = msg;
|
|
11129
11842
|
const id = m.id ?? "";
|
|
11130
|
-
|
|
11843
|
+
settleResolverCall(
|
|
11131
11844
|
() => resolver.handlers(),
|
|
11132
11845
|
send,
|
|
11133
11846
|
"intent.handlers.result",
|
|
@@ -11137,8 +11850,31 @@ function createIntentService(options) {
|
|
|
11137
11850
|
}
|
|
11138
11851
|
return {
|
|
11139
11852
|
descriptor: INTENT_DESCRIPTOR,
|
|
11853
|
+
onRegistered(context) {
|
|
11854
|
+
try {
|
|
11855
|
+
unsubscribeChanged?.();
|
|
11856
|
+
} catch {
|
|
11857
|
+
}
|
|
11858
|
+
runtimeContext = context;
|
|
11859
|
+
unsubscribeChanged = resolver.onChanged?.((availability) => {
|
|
11860
|
+
const current = runtimeContext;
|
|
11861
|
+
if (!current) return;
|
|
11862
|
+
const message = { type: "intent.changed", availability };
|
|
11863
|
+
for (const windowId of current.listWindowIds()) {
|
|
11864
|
+
current.sendToEligibleNapplet(windowId, message);
|
|
11865
|
+
}
|
|
11866
|
+
});
|
|
11867
|
+
},
|
|
11868
|
+
onUnregistered() {
|
|
11869
|
+
const unsubscribe = unsubscribeChanged;
|
|
11870
|
+
unsubscribeChanged = void 0;
|
|
11871
|
+
runtimeContext = void 0;
|
|
11872
|
+
try {
|
|
11873
|
+
unsubscribe?.();
|
|
11874
|
+
} catch {
|
|
11875
|
+
}
|
|
11876
|
+
},
|
|
11140
11877
|
handleMessage(windowId, message, send) {
|
|
11141
|
-
windows.set(windowId, send);
|
|
11142
11878
|
switch (message.type) {
|
|
11143
11879
|
case "intent.invoke":
|
|
11144
11880
|
handleInvoke(windowId, message, send);
|
|
@@ -11152,12 +11888,196 @@ function createIntentService(options) {
|
|
|
11152
11888
|
default:
|
|
11153
11889
|
return;
|
|
11154
11890
|
}
|
|
11155
|
-
},
|
|
11156
|
-
onWindowDestroyed(windowId) {
|
|
11157
|
-
windows.delete(windowId);
|
|
11158
11891
|
}
|
|
11159
11892
|
};
|
|
11160
11893
|
}
|
|
11894
|
+
function candidatesFor(catalog, archetype, defaultHandler) {
|
|
11895
|
+
const candidates = [];
|
|
11896
|
+
for (const entry of catalog) {
|
|
11897
|
+
const support = entry.archetypes[archetype];
|
|
11898
|
+
if (!support) continue;
|
|
11899
|
+
candidates.push({
|
|
11900
|
+
dTag: entry.dTag,
|
|
11901
|
+
...entry.title === void 0 ? {} : { title: entry.title },
|
|
11902
|
+
actions: [...support.actions],
|
|
11903
|
+
conventions: [...support.conventions],
|
|
11904
|
+
...entry.dTag === defaultHandler ? { isDefault: true } : {}
|
|
11905
|
+
});
|
|
11906
|
+
}
|
|
11907
|
+
return candidates;
|
|
11908
|
+
}
|
|
11909
|
+
function reject(request, error) {
|
|
11910
|
+
return {
|
|
11911
|
+
ok: false,
|
|
11912
|
+
archetype: request.archetype,
|
|
11913
|
+
action: request.action ?? "open",
|
|
11914
|
+
handled: false,
|
|
11915
|
+
error
|
|
11916
|
+
};
|
|
11917
|
+
}
|
|
11918
|
+
async function availabilityForCatalog(options, archetype) {
|
|
11919
|
+
const catalog = await options.loadCatalog();
|
|
11920
|
+
const defaultHandler = options.getDefaultHandler?.(archetype);
|
|
11921
|
+
const candidates = candidatesFor(catalog, archetype, defaultHandler);
|
|
11922
|
+
return {
|
|
11923
|
+
archetype,
|
|
11924
|
+
available: candidates.length > 0,
|
|
11925
|
+
candidates,
|
|
11926
|
+
hasDefault: defaultHandler !== void 0 && candidates.some((candidate) => candidate.dTag === defaultHandler)
|
|
11927
|
+
};
|
|
11928
|
+
}
|
|
11929
|
+
async function chooseCompatible(options, request, candidates, sender) {
|
|
11930
|
+
if (!options.chooseHandler) {
|
|
11931
|
+
return request.handler === "choose" ? { error: "user cancelled" } : { error: "invoke rejected" };
|
|
11932
|
+
}
|
|
11933
|
+
const picked = await options.chooseHandler(request.archetype, candidates, sender);
|
|
11934
|
+
if (picked === void 0) return { error: "user cancelled" };
|
|
11935
|
+
const candidate = candidates.find((item) => item.dTag === picked);
|
|
11936
|
+
return candidate ? { candidate } : { error: "invoke rejected" };
|
|
11937
|
+
}
|
|
11938
|
+
async function pickHandler(options, request, compatible, sender) {
|
|
11939
|
+
const preference = request.handler;
|
|
11940
|
+
if (typeof preference === "string" && preference !== "default" && preference !== "choose") {
|
|
11941
|
+
const candidate = compatible.find((item) => item.dTag === preference);
|
|
11942
|
+
if (!candidate || !options.authorizeExplicitHandler) return { error: "invoke rejected" };
|
|
11943
|
+
let authorized = false;
|
|
11944
|
+
try {
|
|
11945
|
+
authorized = await options.authorizeExplicitHandler(
|
|
11946
|
+
sender,
|
|
11947
|
+
preference,
|
|
11948
|
+
request,
|
|
11949
|
+
candidate
|
|
11950
|
+
);
|
|
11951
|
+
} catch {
|
|
11952
|
+
return { error: "invoke rejected" };
|
|
11953
|
+
}
|
|
11954
|
+
return authorized ? { candidate } : { error: "invoke rejected" };
|
|
11955
|
+
}
|
|
11956
|
+
if (preference === "choose") {
|
|
11957
|
+
return chooseCompatible(options, request, compatible, sender);
|
|
11958
|
+
}
|
|
11959
|
+
const defaultCandidate = compatible.find((candidate) => candidate.isDefault === true);
|
|
11960
|
+
if (preference === "default") {
|
|
11961
|
+
return defaultCandidate ? { candidate: defaultCandidate } : { error: "invoke rejected" };
|
|
11962
|
+
}
|
|
11963
|
+
if (defaultCandidate) return { candidate: defaultCandidate };
|
|
11964
|
+
if (compatible.length === 1) return { candidate: compatible[0] };
|
|
11965
|
+
return chooseCompatible(options, request, compatible, sender);
|
|
11966
|
+
}
|
|
11967
|
+
async function invokeFromCatalog(options, request, context) {
|
|
11968
|
+
const catalog = await options.loadCatalog();
|
|
11969
|
+
const defaultHandler = options.getDefaultHandler?.(request.archetype);
|
|
11970
|
+
const candidates = candidatesFor(catalog, request.archetype, defaultHandler);
|
|
11971
|
+
if (candidates.length === 0) return reject(request, "no handler");
|
|
11972
|
+
const action = request.action ?? "open";
|
|
11973
|
+
const actionCompatible = candidates.filter((candidate) => candidate.actions.includes(action));
|
|
11974
|
+
if (actionCompatible.length === 0) return reject(request, "unsupported action");
|
|
11975
|
+
const compatible = request.convention === void 0 ? actionCompatible : actionCompatible.filter((candidate) => candidate.conventions.includes(request.convention));
|
|
11976
|
+
if (compatible.length === 0) return reject(request, "unsupported convention");
|
|
11977
|
+
const sender = context.sender;
|
|
11978
|
+
if (typeof sender !== "string" || sender.length === 0) {
|
|
11979
|
+
return reject(request, "invoke rejected");
|
|
11980
|
+
}
|
|
11981
|
+
const selected = await pickHandler(options, request, compatible, sender);
|
|
11982
|
+
if ("error" in selected) return reject(request, selected.error);
|
|
11983
|
+
const convention = request.convention ?? selected.candidate.conventions.find((value) => {
|
|
11984
|
+
const match = /^napplet:[^/?#\s]+\/([^/?#\s]+)$/.exec(value);
|
|
11985
|
+
return match?.[1] === action;
|
|
11986
|
+
});
|
|
11987
|
+
if (!convention) return reject(request, "unsupported convention");
|
|
11988
|
+
const behavior = request.behavior === void 0 ? void 0 : Object.freeze({
|
|
11989
|
+
...request.behavior.focus === void 0 ? {} : { focus: request.behavior.focus },
|
|
11990
|
+
...request.behavior.newWindow === void 0 ? {} : { newWindow: request.behavior.newWindow },
|
|
11991
|
+
...request.behavior.reuse === void 0 ? {} : { reuse: request.behavior.reuse }
|
|
11992
|
+
});
|
|
11993
|
+
const params = Object.freeze({
|
|
11994
|
+
handler: selected.candidate.dTag,
|
|
11995
|
+
sender,
|
|
11996
|
+
archetype: request.archetype,
|
|
11997
|
+
action,
|
|
11998
|
+
convention,
|
|
11999
|
+
...request.payload === void 0 ? {} : { payload: request.payload },
|
|
12000
|
+
...behavior === void 0 ? {} : { behavior }
|
|
12001
|
+
});
|
|
12002
|
+
let target;
|
|
12003
|
+
try {
|
|
12004
|
+
target = await options.targets.dispatch(params);
|
|
12005
|
+
} catch {
|
|
12006
|
+
return reject(request, "invoke failed");
|
|
12007
|
+
}
|
|
12008
|
+
if (!target || typeof target.windowId !== "string" || target.windowId.length === 0) {
|
|
12009
|
+
return reject(request, "invoke failed");
|
|
12010
|
+
}
|
|
12011
|
+
return {
|
|
12012
|
+
ok: true,
|
|
12013
|
+
archetype: request.archetype,
|
|
12014
|
+
action,
|
|
12015
|
+
handled: true,
|
|
12016
|
+
handler: selected.candidate.dTag,
|
|
12017
|
+
windowId: target.windowId,
|
|
12018
|
+
convention
|
|
12019
|
+
};
|
|
12020
|
+
}
|
|
12021
|
+
function createCatalogIntentResolver(options) {
|
|
12022
|
+
if (!options || typeof options.loadCatalog !== "function") {
|
|
12023
|
+
throw new Error("createCatalogIntentResolver: options.loadCatalog is required");
|
|
12024
|
+
}
|
|
12025
|
+
if (!options.targets || typeof options.targets.dispatch !== "function") {
|
|
12026
|
+
throw new Error("createCatalogIntentResolver: options.targets is required");
|
|
12027
|
+
}
|
|
12028
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
12029
|
+
const availabilityFor = (archetype) => availabilityForCatalog(options, archetype);
|
|
12030
|
+
async function handlers() {
|
|
12031
|
+
const catalog = await options.loadCatalog();
|
|
12032
|
+
const archetypes = /* @__PURE__ */ new Set();
|
|
12033
|
+
for (const entry of catalog) {
|
|
12034
|
+
for (const slug of Object.keys(entry.archetypes)) archetypes.add(slug);
|
|
12035
|
+
}
|
|
12036
|
+
return Promise.all([...archetypes].map((archetype) => availabilityFor(archetype)));
|
|
12037
|
+
}
|
|
12038
|
+
return {
|
|
12039
|
+
invoke: (request, context) => invokeFromCatalog(options, request, context),
|
|
12040
|
+
available: availabilityFor,
|
|
12041
|
+
handlers,
|
|
12042
|
+
onChanged(listener) {
|
|
12043
|
+
listeners.add(listener);
|
|
12044
|
+
return () => listeners.delete(listener);
|
|
12045
|
+
},
|
|
12046
|
+
notifyChanged(archetype) {
|
|
12047
|
+
if (listeners.size === 0) return;
|
|
12048
|
+
void availabilityFor(archetype).then((availability) => {
|
|
12049
|
+
for (const listener of listeners) listener(availability);
|
|
12050
|
+
});
|
|
12051
|
+
}
|
|
12052
|
+
};
|
|
12053
|
+
}
|
|
12054
|
+
function actionFromConvention(slug, convention) {
|
|
12055
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
|
|
12056
|
+
throw new TypeError("manifest archetype slug is invalid");
|
|
12057
|
+
}
|
|
12058
|
+
const match = /^napplet:([^/?#\s]+)\/([^/?#\s]+)$/.exec(convention);
|
|
12059
|
+
if (!match || match[1] !== slug) {
|
|
12060
|
+
throw new TypeError("manifest archetype convention is invalid or mismatched");
|
|
12061
|
+
}
|
|
12062
|
+
return match[2];
|
|
12063
|
+
}
|
|
12064
|
+
function manifestToIntentCatalogEntry(manifest) {
|
|
12065
|
+
const archetypes = {};
|
|
12066
|
+
for (const { slug, convention } of manifest.archetypes) {
|
|
12067
|
+
const action = actionFromConvention(slug, convention);
|
|
12068
|
+
const support = archetypes[slug] ??= {
|
|
12069
|
+
actions: [],
|
|
12070
|
+
conventions: []
|
|
12071
|
+
};
|
|
12072
|
+
if (!support.actions.includes(action)) support.actions.push(action);
|
|
12073
|
+
if (!support.conventions.includes(convention)) support.conventions.push(convention);
|
|
12074
|
+
}
|
|
12075
|
+
return {
|
|
12076
|
+
dTag: manifest.dTag,
|
|
12077
|
+
...manifest.title === void 0 ? {} : { title: manifest.title },
|
|
12078
|
+
archetypes
|
|
12079
|
+
};
|
|
12080
|
+
}
|
|
11161
12081
|
var CVM_SERVICE_VERSION = "1.0.0";
|
|
11162
12082
|
var CVM_DESCRIPTOR = {
|
|
11163
12083
|
name: "cvm",
|
|
@@ -12148,6 +13068,193 @@ function createDevWebrtcController() {
|
|
|
12148
13068
|
};
|
|
12149
13069
|
}
|
|
12150
13070
|
|
|
13071
|
+
// src/browser-intent-controller.ts
|
|
13072
|
+
var MAX_INTENT_DELIVERY_ATTEMPTS = 10;
|
|
13073
|
+
var BrowserIntentController = class {
|
|
13074
|
+
constructor(options) {
|
|
13075
|
+
this.options = options;
|
|
13076
|
+
this.maxAttempts = normalizeAttempts(options.maxAttempts);
|
|
13077
|
+
}
|
|
13078
|
+
options;
|
|
13079
|
+
maxAttempts;
|
|
13080
|
+
async dispatch(params) {
|
|
13081
|
+
const dispatch2 = freezeDispatch(params);
|
|
13082
|
+
let reason = "no-current-target";
|
|
13083
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt += 1) {
|
|
13084
|
+
let generation;
|
|
13085
|
+
try {
|
|
13086
|
+
generation = await this.options.openOrReuse(dispatch2, attempt);
|
|
13087
|
+
} catch {
|
|
13088
|
+
reason = "open-failed";
|
|
13089
|
+
continue;
|
|
13090
|
+
}
|
|
13091
|
+
if (!generation) {
|
|
13092
|
+
reason = "open-failed";
|
|
13093
|
+
continue;
|
|
13094
|
+
}
|
|
13095
|
+
try {
|
|
13096
|
+
await this.options.waitForReady(generation);
|
|
13097
|
+
} catch {
|
|
13098
|
+
reason = "ready-failed";
|
|
13099
|
+
continue;
|
|
13100
|
+
}
|
|
13101
|
+
if (!await this.options.isCurrent(generation)) {
|
|
13102
|
+
reason = "no-current-target";
|
|
13103
|
+
continue;
|
|
13104
|
+
}
|
|
13105
|
+
try {
|
|
13106
|
+
await this.options.send(generation, dispatch2);
|
|
13107
|
+
const windowId = this.options.getWindowId(generation);
|
|
13108
|
+
if (!windowId) throw new Error("intent target window is unavailable");
|
|
13109
|
+
return { windowId };
|
|
13110
|
+
} catch {
|
|
13111
|
+
this.options.onTerminal?.(dispatch2, "send-failed");
|
|
13112
|
+
throw new Error("intent target send failed");
|
|
13113
|
+
}
|
|
13114
|
+
}
|
|
13115
|
+
this.options.onTerminal?.(dispatch2, reason);
|
|
13116
|
+
throw new Error(`intent target ${reason}`);
|
|
13117
|
+
}
|
|
13118
|
+
};
|
|
13119
|
+
function normalizeAttempts(value) {
|
|
13120
|
+
if (value === void 0) return 2;
|
|
13121
|
+
if (!Number.isFinite(value)) throw new TypeError("maxAttempts must be finite");
|
|
13122
|
+
return Math.min(MAX_INTENT_DELIVERY_ATTEMPTS, Math.max(1, Math.floor(value)));
|
|
13123
|
+
}
|
|
13124
|
+
function freezeDispatch(params) {
|
|
13125
|
+
if (!params || typeof params.handler !== "string" || params.handler.length === 0) {
|
|
13126
|
+
throw new TypeError("Intent dispatch requires a handler");
|
|
13127
|
+
}
|
|
13128
|
+
if (typeof params.sender !== "string" || params.sender.length === 0 || typeof params.archetype !== "string" || params.archetype.length === 0 || typeof params.action !== "string" || params.action.length === 0 || typeof params.convention !== "string" || params.convention.length === 0) {
|
|
13129
|
+
throw new TypeError("Intent dispatch requires canonical routing fields");
|
|
13130
|
+
}
|
|
13131
|
+
const payload = freezeValue(params.payload);
|
|
13132
|
+
const behavior = params.behavior === void 0 ? void 0 : freezeValue({ ...params.behavior });
|
|
13133
|
+
return Object.freeze({
|
|
13134
|
+
handler: params.handler,
|
|
13135
|
+
sender: params.sender,
|
|
13136
|
+
archetype: params.archetype,
|
|
13137
|
+
action: params.action,
|
|
13138
|
+
convention: params.convention,
|
|
13139
|
+
...params.payload === void 0 ? {} : { payload },
|
|
13140
|
+
...behavior === void 0 ? {} : { behavior }
|
|
13141
|
+
});
|
|
13142
|
+
}
|
|
13143
|
+
function freezeValue(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
13144
|
+
if (typeof value !== "object" || value === null) return value;
|
|
13145
|
+
const existing = seen.get(value);
|
|
13146
|
+
if (existing !== void 0) return existing;
|
|
13147
|
+
if (Array.isArray(value)) {
|
|
13148
|
+
const copy2 = [];
|
|
13149
|
+
seen.set(value, copy2);
|
|
13150
|
+
for (const item of value) copy2.push(freezeValue(item, seen));
|
|
13151
|
+
return Object.freeze(copy2);
|
|
13152
|
+
}
|
|
13153
|
+
const copy = {};
|
|
13154
|
+
seen.set(value, copy);
|
|
13155
|
+
for (const [key, item] of Object.entries(value)) copy[key] = freezeValue(item, seen);
|
|
13156
|
+
return Object.freeze(copy);
|
|
13157
|
+
}
|
|
13158
|
+
|
|
13159
|
+
// src/installed-napplet-catalog.ts
|
|
13160
|
+
function matchesInstalledNappletRecord(record, target) {
|
|
13161
|
+
return target.dTag === record.dTag && target.aggregateHash === record.aggregateHash;
|
|
13162
|
+
}
|
|
13163
|
+
var InstalledNappletCatalog = class {
|
|
13164
|
+
records = /* @__PURE__ */ new Map();
|
|
13165
|
+
listeners = /* @__PURE__ */ new Set();
|
|
13166
|
+
/** Insert or replace a record from an already resolver-verified pointer. */
|
|
13167
|
+
install(resolved) {
|
|
13168
|
+
const record = freezeRecord({
|
|
13169
|
+
dTag: resolved.dTag,
|
|
13170
|
+
aggregateHash: resolved.aggregateHash,
|
|
13171
|
+
pointer: copyPointer(resolved.pointer),
|
|
13172
|
+
...resolved.manifest.title === void 0 ? {} : { title: resolved.manifest.title },
|
|
13173
|
+
requires: [...resolved.manifest.requires],
|
|
13174
|
+
archetypes: resolved.manifest.archetypes.map((archetype) => ({
|
|
13175
|
+
slug: archetype.slug,
|
|
13176
|
+
convention: archetype.convention
|
|
13177
|
+
}))
|
|
13178
|
+
});
|
|
13179
|
+
this.records.set(record.dTag, record);
|
|
13180
|
+
this.notify(record.dTag);
|
|
13181
|
+
return record;
|
|
13182
|
+
}
|
|
13183
|
+
/** Remove an artifact explicitly; frame teardown never calls this method. */
|
|
13184
|
+
remove(dTag) {
|
|
13185
|
+
const removed = this.records.delete(dTag);
|
|
13186
|
+
if (removed) this.notify(dTag);
|
|
13187
|
+
return removed;
|
|
13188
|
+
}
|
|
13189
|
+
/** Return whether a verified installation exists for a d-tag. */
|
|
13190
|
+
has(dTag) {
|
|
13191
|
+
return this.records.has(dTag);
|
|
13192
|
+
}
|
|
13193
|
+
/** Return immutable serializable installed-artifact facts. */
|
|
13194
|
+
installed() {
|
|
13195
|
+
return [...this.records.values()];
|
|
13196
|
+
}
|
|
13197
|
+
/** Return a verified record by d-tag. */
|
|
13198
|
+
get(dTag) {
|
|
13199
|
+
return this.records.get(dTag);
|
|
13200
|
+
}
|
|
13201
|
+
/**
|
|
13202
|
+
* Atomically confirm that a resolved target still belongs to the exact record
|
|
13203
|
+
* selected before an async operation. The record object is the catalog version
|
|
13204
|
+
* token, so even a same-identity replacement cannot pass this check.
|
|
13205
|
+
*/
|
|
13206
|
+
validateCurrent(selected, target) {
|
|
13207
|
+
if (this.records.get(selected.dTag) !== selected) return null;
|
|
13208
|
+
return matchesInstalledNappletRecord(selected, target) ? selected : null;
|
|
13209
|
+
}
|
|
13210
|
+
/** Return exact handler candidates derived only from installed manifests. */
|
|
13211
|
+
intentCatalog() {
|
|
13212
|
+
return this.installed().filter((record) => record.requires.includes("inc")).map((record) => manifestToIntentCatalogEntry({
|
|
13213
|
+
dTag: record.dTag,
|
|
13214
|
+
...record.title === void 0 ? {} : { title: record.title },
|
|
13215
|
+
archetypes: record.archetypes.map((archetype) => ({
|
|
13216
|
+
slug: archetype.slug,
|
|
13217
|
+
convention: archetype.convention
|
|
13218
|
+
}))
|
|
13219
|
+
}));
|
|
13220
|
+
}
|
|
13221
|
+
/** Subscribe to explicit install and artifact-removal changes. */
|
|
13222
|
+
onChanged(listener) {
|
|
13223
|
+
this.listeners.add(listener);
|
|
13224
|
+
return () => this.listeners.delete(listener);
|
|
13225
|
+
}
|
|
13226
|
+
notify(dTag) {
|
|
13227
|
+
for (const listener of this.listeners) listener(dTag);
|
|
13228
|
+
}
|
|
13229
|
+
};
|
|
13230
|
+
function copyPointer(pointer) {
|
|
13231
|
+
if (pointer.type === "naddr") {
|
|
13232
|
+
return Object.freeze({
|
|
13233
|
+
type: "naddr",
|
|
13234
|
+
value: pointer.value,
|
|
13235
|
+
identifier: pointer.identifier,
|
|
13236
|
+
pubkey: pointer.pubkey,
|
|
13237
|
+
kind: pointer.kind,
|
|
13238
|
+
relays: Object.freeze([...pointer.relays])
|
|
13239
|
+
});
|
|
13240
|
+
}
|
|
13241
|
+
return Object.freeze({
|
|
13242
|
+
type: "nevent",
|
|
13243
|
+
value: pointer.value,
|
|
13244
|
+
id: pointer.id,
|
|
13245
|
+
...pointer.author === void 0 ? {} : { author: pointer.author },
|
|
13246
|
+
...pointer.kind === void 0 ? {} : { kind: pointer.kind },
|
|
13247
|
+
relays: Object.freeze([...pointer.relays])
|
|
13248
|
+
});
|
|
13249
|
+
}
|
|
13250
|
+
function freezeRecord(record) {
|
|
13251
|
+
return Object.freeze({
|
|
13252
|
+
...record,
|
|
13253
|
+
requires: Object.freeze([...record.requires]),
|
|
13254
|
+
archetypes: Object.freeze(record.archetypes.map((archetype) => Object.freeze({ ...archetype })))
|
|
13255
|
+
});
|
|
13256
|
+
}
|
|
13257
|
+
|
|
12151
13258
|
// src/simulation.ts
|
|
12152
13259
|
var PajaSimulationError = class extends Error {
|
|
12153
13260
|
constructor(message) {
|
|
@@ -12725,10 +13832,10 @@ var AbstractRelay = class {
|
|
|
12725
13832
|
this.challenge = void 0;
|
|
12726
13833
|
this.authPromise = void 0;
|
|
12727
13834
|
this.skipReconnection = false;
|
|
12728
|
-
this.connectionPromise = new Promise((resolve,
|
|
13835
|
+
this.connectionPromise = new Promise((resolve, reject2) => {
|
|
12729
13836
|
if (opts?.timeout) {
|
|
12730
13837
|
connectionTimeoutHandle = setTimeout(() => {
|
|
12731
|
-
|
|
13838
|
+
reject2("connection timed out");
|
|
12732
13839
|
this.connectionPromise = void 0;
|
|
12733
13840
|
this.skipReconnection = true;
|
|
12734
13841
|
this.onclose?.();
|
|
@@ -12736,13 +13843,13 @@ var AbstractRelay = class {
|
|
|
12736
13843
|
}, opts.timeout);
|
|
12737
13844
|
}
|
|
12738
13845
|
if (opts?.abort) {
|
|
12739
|
-
opts.abort.onabort =
|
|
13846
|
+
opts.abort.onabort = reject2;
|
|
12740
13847
|
}
|
|
12741
13848
|
try {
|
|
12742
13849
|
this.ws = new this._WebSocket(this.url);
|
|
12743
13850
|
} catch (err) {
|
|
12744
13851
|
clearTimeout(connectionTimeoutHandle);
|
|
12745
|
-
|
|
13852
|
+
reject2(err);
|
|
12746
13853
|
return;
|
|
12747
13854
|
}
|
|
12748
13855
|
this.ws.onopen = () => {
|
|
@@ -12772,7 +13879,7 @@ var AbstractRelay = class {
|
|
|
12772
13879
|
};
|
|
12773
13880
|
this.ws.onerror = () => {
|
|
12774
13881
|
clearTimeout(connectionTimeoutHandle);
|
|
12775
|
-
|
|
13882
|
+
reject2("connection failed");
|
|
12776
13883
|
this.connectionPromise = void 0;
|
|
12777
13884
|
this.skipReconnection = true;
|
|
12778
13885
|
this.onclose?.();
|
|
@@ -12780,7 +13887,7 @@ var AbstractRelay = class {
|
|
|
12780
13887
|
};
|
|
12781
13888
|
this.ws.onclose = (ev) => {
|
|
12782
13889
|
clearTimeout(connectionTimeoutHandle);
|
|
12783
|
-
|
|
13890
|
+
reject2(ev.message || "websocket closed");
|
|
12784
13891
|
this.handleHardClose("relay connection closed");
|
|
12785
13892
|
};
|
|
12786
13893
|
this.ws.onmessage = this._onmessage.bind(this);
|
|
@@ -12795,9 +13902,9 @@ var AbstractRelay = class {
|
|
|
12795
13902
|
});
|
|
12796
13903
|
}
|
|
12797
13904
|
waitForDummyReq() {
|
|
12798
|
-
return new Promise((resolve,
|
|
13905
|
+
return new Promise((resolve, reject2) => {
|
|
12799
13906
|
if (!this.connectionPromise)
|
|
12800
|
-
return
|
|
13907
|
+
return reject2(new Error(`no connection to ${this.url}, can't ping`));
|
|
12801
13908
|
try {
|
|
12802
13909
|
const sub = this.subscribe(
|
|
12803
13910
|
[{ ids: ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], limit: 0 }],
|
|
@@ -12814,7 +13921,7 @@ var AbstractRelay = class {
|
|
|
12814
13921
|
}
|
|
12815
13922
|
);
|
|
12816
13923
|
} catch (err) {
|
|
12817
|
-
|
|
13924
|
+
reject2(err);
|
|
12818
13925
|
}
|
|
12819
13926
|
});
|
|
12820
13927
|
}
|
|
@@ -12844,7 +13951,7 @@ var AbstractRelay = class {
|
|
|
12844
13951
|
throw new Error("can't perform auth, no challenge was received");
|
|
12845
13952
|
if (this.authPromise)
|
|
12846
13953
|
return this.authPromise;
|
|
12847
|
-
this.authPromise = new Promise(async (resolve,
|
|
13954
|
+
this.authPromise = new Promise(async (resolve, reject2) => {
|
|
12848
13955
|
try {
|
|
12849
13956
|
let evt = await signAuthEvent(makeAuthEvent(this.url, challenge2));
|
|
12850
13957
|
let timeout = setTimeout(() => {
|
|
@@ -12854,7 +13961,7 @@ var AbstractRelay = class {
|
|
|
12854
13961
|
this.openEventPublishes.delete(evt.id);
|
|
12855
13962
|
}
|
|
12856
13963
|
}, this.publishTimeout);
|
|
12857
|
-
this.openEventPublishes.set(evt.id, { resolve, reject, timeout });
|
|
13964
|
+
this.openEventPublishes.set(evt.id, { resolve, reject: reject2, timeout });
|
|
12858
13965
|
this.send('["AUTH",' + JSON.stringify(evt) + "]");
|
|
12859
13966
|
} catch (err) {
|
|
12860
13967
|
console.warn("subscribe auth function failed:", err);
|
|
@@ -12865,7 +13972,7 @@ var AbstractRelay = class {
|
|
|
12865
13972
|
async publish(event) {
|
|
12866
13973
|
this.idleSince = void 0;
|
|
12867
13974
|
this.ongoingOperations++;
|
|
12868
|
-
const ret = new Promise((resolve,
|
|
13975
|
+
const ret = new Promise((resolve, reject2) => {
|
|
12869
13976
|
const timeout = setTimeout(() => {
|
|
12870
13977
|
const ep = this.openEventPublishes.get(event.id);
|
|
12871
13978
|
if (ep) {
|
|
@@ -12873,7 +13980,7 @@ var AbstractRelay = class {
|
|
|
12873
13980
|
this.openEventPublishes.delete(event.id);
|
|
12874
13981
|
}
|
|
12875
13982
|
}, this.publishTimeout);
|
|
12876
|
-
this.openEventPublishes.set(event.id, { resolve, reject, timeout });
|
|
13983
|
+
this.openEventPublishes.set(event.id, { resolve, reject: reject2, timeout });
|
|
12877
13984
|
});
|
|
12878
13985
|
this.send('["EVENT",' + JSON.stringify(event) + "]");
|
|
12879
13986
|
this.ongoingOperations--;
|
|
@@ -12884,8 +13991,8 @@ var AbstractRelay = class {
|
|
|
12884
13991
|
async count(filters, params) {
|
|
12885
13992
|
this.serial++;
|
|
12886
13993
|
const id = params?.id || "count:" + this.serial;
|
|
12887
|
-
const ret = new Promise((resolve,
|
|
12888
|
-
this.openCountRequests.set(id, { resolve, reject });
|
|
13994
|
+
const ret = new Promise((resolve, reject2) => {
|
|
13995
|
+
this.openCountRequests.set(id, { resolve, reject: reject2 });
|
|
12889
13996
|
});
|
|
12890
13997
|
this.send('["COUNT","' + id + '",' + JSON.stringify(filters).substring(1));
|
|
12891
13998
|
return ret;
|
|
@@ -13472,9 +14579,8 @@ async function publishLive(pool, relayUrls, event) {
|
|
|
13472
14579
|
const results = await Promise.allSettled(pool.publish(relayUrls, event));
|
|
13473
14580
|
return Object.fromEntries(relayUrls.map((relayUrl, index) => [relayUrl, results[index]?.status === "fulfilled"]));
|
|
13474
14581
|
}
|
|
13475
|
-
function createPajaRelayBackend(getSimulation, confirmRequest) {
|
|
14582
|
+
function createPajaRelayBackend(getSimulation, confirmRequest, livePool = new SimplePool()) {
|
|
13476
14583
|
const events = getSimulation().relay.fixtures.flatMap(toNostrEvent);
|
|
13477
|
-
const livePool = new SimplePool();
|
|
13478
14584
|
const subscribers = /* @__PURE__ */ new Set();
|
|
13479
14585
|
const isAvailable = () => getSimulation().relay.mode !== "disabled" && getPajaRelayUrls(getSimulation()).length > 0;
|
|
13480
14586
|
const query = async (relayUrls, filters, maxWaitMs) => {
|
|
@@ -13485,6 +14591,28 @@ function createPajaRelayBackend(getSimulation, confirmRequest) {
|
|
|
13485
14591
|
for (const event of [...memoryEvents, ...liveEvents]) out.set(event.id, event);
|
|
13486
14592
|
return [...out.values()].sort((a, b) => b.created_at - a.created_at);
|
|
13487
14593
|
};
|
|
14594
|
+
function retainPublishedEvent(event) {
|
|
14595
|
+
events.push(event);
|
|
14596
|
+
for (const subscriber of subscribers) {
|
|
14597
|
+
if (matchesAnyFilter2(event, subscriber.filters)) subscriber.next(event);
|
|
14598
|
+
}
|
|
14599
|
+
}
|
|
14600
|
+
async function attemptPublish(relayUrls, event) {
|
|
14601
|
+
const simulation = getSimulation();
|
|
14602
|
+
const rejected = Object.fromEntries(relayUrls.map((url) => [url, false]));
|
|
14603
|
+
if (simulation.relay.mode === "disabled") {
|
|
14604
|
+
return { outcomes: rejected, error: "relay unavailable" };
|
|
14605
|
+
}
|
|
14606
|
+
if (!confirmRequest({ action: "publish", event })) {
|
|
14607
|
+
return { outcomes: rejected, error: "publish denied" };
|
|
14608
|
+
}
|
|
14609
|
+
const outcomes = simulation.relay.mode === "live" ? await publishLive(livePool, relayUrls, event) : Object.fromEntries(relayUrls.map((url) => [url, true]));
|
|
14610
|
+
if (!Object.values(outcomes).some(Boolean)) {
|
|
14611
|
+
return { outcomes, error: "publish failed" };
|
|
14612
|
+
}
|
|
14613
|
+
retainPublishedEvent(event);
|
|
14614
|
+
return { outcomes };
|
|
14615
|
+
}
|
|
13488
14616
|
const backend = {
|
|
13489
14617
|
subscription(relayUrls, filtersInput) {
|
|
13490
14618
|
return {
|
|
@@ -13509,16 +14637,9 @@ function createPajaRelayBackend(getSimulation, confirmRequest) {
|
|
|
13509
14637
|
}
|
|
13510
14638
|
};
|
|
13511
14639
|
},
|
|
13512
|
-
publish(relayUrls, event) {
|
|
13513
|
-
|
|
13514
|
-
if (
|
|
13515
|
-
events.push(event);
|
|
13516
|
-
for (const subscriber of subscribers) {
|
|
13517
|
-
if (matchesAnyFilter2(event, subscriber.filters)) subscriber.next(event);
|
|
13518
|
-
}
|
|
13519
|
-
if (getSimulation().relay.mode === "live") {
|
|
13520
|
-
void publishLive(livePool, relayUrls, event);
|
|
13521
|
-
}
|
|
14640
|
+
async publish(relayUrls, event) {
|
|
14641
|
+
const attempt = await attemptPublish(relayUrls, event);
|
|
14642
|
+
if (attempt.error) throw new Error(attempt.error);
|
|
13522
14643
|
},
|
|
13523
14644
|
request(relayUrls, filtersInput) {
|
|
13524
14645
|
return {
|
|
@@ -13538,14 +14659,7 @@ function createPajaRelayBackend(getSimulation, confirmRequest) {
|
|
|
13538
14659
|
},
|
|
13539
14660
|
query,
|
|
13540
14661
|
async publishToRelays(relayUrls, event) {
|
|
13541
|
-
|
|
13542
|
-
if (!confirmRequest({ action: "publish", event })) return Object.fromEntries(relayUrls.map((url) => [url, false]));
|
|
13543
|
-
events.push(event);
|
|
13544
|
-
for (const subscriber of subscribers) {
|
|
13545
|
-
if (matchesAnyFilter2(event, subscriber.filters)) subscriber.next(event);
|
|
13546
|
-
}
|
|
13547
|
-
if (getSimulation().relay.mode !== "live") return Object.fromEntries(relayUrls.map((url) => [url, true]));
|
|
13548
|
-
return publishLive(livePool, relayUrls, event);
|
|
14662
|
+
return (await attemptPublish(relayUrls, event)).outcomes;
|
|
13549
14663
|
},
|
|
13550
14664
|
isAvailable,
|
|
13551
14665
|
close() {
|
|
@@ -13666,7 +14780,6 @@ function createPajaIdentityProviders(backend, getSimulation, signerProvider) {
|
|
|
13666
14780
|
}
|
|
13667
14781
|
|
|
13668
14782
|
// src/browser-adapter.ts
|
|
13669
|
-
var DEV_INTENT_ARCHETYPE = "paja-target";
|
|
13670
14783
|
var DEV_COMMON_PUBKEY = "1".repeat(64);
|
|
13671
14784
|
var DEV_COMMON_EVENT_ID = "2".repeat(64);
|
|
13672
14785
|
var DEV_SIGNER_SECRET_KEY = generateSecretKey();
|
|
@@ -13693,10 +14806,14 @@ function createRelayHooks(pool, getSimulation) {
|
|
|
13693
14806
|
},
|
|
13694
14807
|
closeScopedRelay: () => {
|
|
13695
14808
|
},
|
|
13696
|
-
publishToScopedRelay: (_windowId, event) => {
|
|
14809
|
+
publishToScopedRelay: async (_windowId, event) => {
|
|
13697
14810
|
if (getSimulation().relay.mode === "disabled") return false;
|
|
13698
|
-
|
|
13699
|
-
|
|
14811
|
+
try {
|
|
14812
|
+
await pool.publish(getPajaRelayUrls(getSimulation()), event);
|
|
14813
|
+
return true;
|
|
14814
|
+
} catch {
|
|
14815
|
+
return false;
|
|
14816
|
+
}
|
|
13700
14817
|
},
|
|
13701
14818
|
selectRelayTier: () => getPajaRelayUrls(getSimulation())
|
|
13702
14819
|
};
|
|
@@ -13749,18 +14866,16 @@ function createDevUploader(getSimulation) {
|
|
|
13749
14866
|
}
|
|
13750
14867
|
};
|
|
13751
14868
|
}
|
|
13752
|
-
function
|
|
14869
|
+
function createDefaultIntentHost() {
|
|
13753
14870
|
return {
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
13762
|
-
isDefault: true
|
|
13763
|
-
}]
|
|
14871
|
+
catalog: new InstalledNappletCatalog(),
|
|
14872
|
+
controller: new BrowserIntentController({
|
|
14873
|
+
openOrReuse: () => null,
|
|
14874
|
+
waitForReady: () => void 0,
|
|
14875
|
+
isCurrent: () => false,
|
|
14876
|
+
getWindowId: () => null,
|
|
14877
|
+
send: () => void 0
|
|
14878
|
+
})
|
|
13764
14879
|
};
|
|
13765
14880
|
}
|
|
13766
14881
|
function createDevCvmTransport(getSimulation) {
|
|
@@ -13853,12 +14968,11 @@ function createRuntimeSigner(getSimulation, confirmRequest, signerProvider) {
|
|
|
13853
14968
|
}
|
|
13854
14969
|
return signer;
|
|
13855
14970
|
}
|
|
13856
|
-
function createDevServices(backend, getSimulation, onThemeService, confirmRequest, uploadRuntime, signerProvider) {
|
|
14971
|
+
function createDevServices(backend, getSimulation, onThemeService, onThemeBroadcast, confirmRequest, uploadRuntime, signerProvider, intentHost = createDefaultIntentHost()) {
|
|
13857
14972
|
const notification = createNotificationService({ maxPerWindow: 50 });
|
|
13858
14973
|
const theme = createThemeService({
|
|
13859
14974
|
initialTheme: createDevTheme(getSimulation().theme.mode, getSimulation().theme.values),
|
|
13860
|
-
onBroadcast:
|
|
13861
|
-
}
|
|
14975
|
+
onBroadcast: onThemeBroadcast
|
|
13862
14976
|
});
|
|
13863
14977
|
onThemeService(theme);
|
|
13864
14978
|
const config = createConfigService({
|
|
@@ -13938,26 +15052,15 @@ function createDevServices(backend, getSimulation, onThemeService, confirmReques
|
|
|
13938
15052
|
});
|
|
13939
15053
|
}
|
|
13940
15054
|
if (getSimulation().intent.enabled) {
|
|
15055
|
+
const resolver = createCatalogIntentResolver({
|
|
15056
|
+
loadCatalog: () => intentHost.catalog.intentCatalog(),
|
|
15057
|
+
targets: intentHost.controller,
|
|
15058
|
+
getDefaultHandler: intentHost.getDefaultHandler,
|
|
15059
|
+
chooseHandler: intentHost.chooseHandler,
|
|
15060
|
+
authorizeExplicitHandler: intentHost.authorizeExplicitHandler
|
|
15061
|
+
});
|
|
13941
15062
|
services.intent = createIntentService({
|
|
13942
|
-
resolver
|
|
13943
|
-
invoke(request) {
|
|
13944
|
-
return {
|
|
13945
|
-
ok: true,
|
|
13946
|
-
archetype: request.archetype,
|
|
13947
|
-
action: request.action ?? "open",
|
|
13948
|
-
handled: request.archetype === DEV_INTENT_ARCHETYPE,
|
|
13949
|
-
handler: "dev-target",
|
|
13950
|
-
windowId: "kehto-paja-window",
|
|
13951
|
-
protocol: request.protocol ?? "NAP-01"
|
|
13952
|
-
};
|
|
13953
|
-
},
|
|
13954
|
-
available: (archetype) => ({
|
|
13955
|
-
...createDevIntentAvailability(),
|
|
13956
|
-
archetype,
|
|
13957
|
-
available: archetype === DEV_INTENT_ARCHETYPE
|
|
13958
|
-
}),
|
|
13959
|
-
handlers: () => [createDevIntentAvailability()]
|
|
13960
|
-
}
|
|
15063
|
+
resolver
|
|
13961
15064
|
});
|
|
13962
15065
|
}
|
|
13963
15066
|
if (getSimulation().capabilities.domains.link) {
|
|
@@ -13999,7 +15102,7 @@ function createDevServices(backend, getSimulation, onThemeService, confirmReques
|
|
|
13999
15102
|
}
|
|
14000
15103
|
return services;
|
|
14001
15104
|
}
|
|
14002
|
-
function createPajaAdapter(config, getSimulation, onThemeService, confirmRequest, signerProvider, getIdentity) {
|
|
15105
|
+
function createPajaAdapter(config, getSimulation, onThemeService, onThemeBroadcast, confirmRequest, signerProvider, getIdentity, onEnvironmentChanged, intentHost) {
|
|
14003
15106
|
const relayBackend = createPajaRelayBackend(getSimulation, confirmRequest);
|
|
14004
15107
|
const uploadRuntime = getSimulation().upload.mode === "blossom" ? createPajaUploadRuntime({
|
|
14005
15108
|
getSimulation,
|
|
@@ -14015,7 +15118,11 @@ function createPajaAdapter(config, getSimulation, onThemeService, confirmRequest
|
|
|
14015
15118
|
subscribeSignerChange: signerProvider?.subscribe?.bind(signerProvider)
|
|
14016
15119
|
}) : void 0;
|
|
14017
15120
|
void uploadRuntime?.refreshIdentity();
|
|
15121
|
+
signerProvider?.subscribe?.(() => {
|
|
15122
|
+
void uploadRuntime?.refreshIdentity().finally(() => onEnvironmentChanged?.());
|
|
15123
|
+
});
|
|
14018
15124
|
const workerRelayEvents = [];
|
|
15125
|
+
const resolvedIntentHost = intentHost ?? createDefaultIntentHost();
|
|
14019
15126
|
return {
|
|
14020
15127
|
relayPool: createRelayHooks(relayBackend, getSimulation),
|
|
14021
15128
|
relayConfig: {
|
|
@@ -14034,7 +15141,16 @@ function createPajaAdapter(config, getSimulation, onThemeService, confirmRequest
|
|
|
14034
15141
|
getUserPubkey: () => getRuntimePubkey(getSimulation, signerProvider),
|
|
14035
15142
|
getSigner: () => createRuntimeSigner(getSimulation, confirmRequest, signerProvider)
|
|
14036
15143
|
},
|
|
14037
|
-
services: createDevServices(
|
|
15144
|
+
services: createDevServices(
|
|
15145
|
+
relayBackend,
|
|
15146
|
+
getSimulation,
|
|
15147
|
+
onThemeService,
|
|
15148
|
+
onThemeBroadcast,
|
|
15149
|
+
confirmRequest,
|
|
15150
|
+
uploadRuntime,
|
|
15151
|
+
signerProvider,
|
|
15152
|
+
resolvedIntentHost
|
|
15153
|
+
),
|
|
14038
15154
|
get capabilities() {
|
|
14039
15155
|
return { disabledDomains: getSimulation().capabilities.disabledDomains };
|
|
14040
15156
|
},
|
|
@@ -14052,119 +15168,358 @@ function createPajaAdapter(config, getSimulation, onThemeService, confirmRequest
|
|
|
14052
15168
|
webrtc: { isAvailable: () => getSimulation().capabilities.domains.webrtc },
|
|
14053
15169
|
crypto: {
|
|
14054
15170
|
verifyEvent: async () => true
|
|
14055
|
-
},
|
|
14056
|
-
onNip5dIframeCreate: () => {
|
|
14057
|
-
const identity = getIdentity?.();
|
|
14058
|
-
return {
|
|
14059
|
-
dTag: identity?.dTag ?? config.window.dTag,
|
|
14060
|
-
aggregateHash: identity?.aggregateHash ?? config.window.aggregateHash
|
|
14061
|
-
};
|
|
14062
15171
|
}
|
|
14063
15172
|
};
|
|
14064
15173
|
}
|
|
14065
15174
|
|
|
14066
|
-
//
|
|
14067
|
-
var
|
|
14068
|
-
var
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
15175
|
+
// src/browser-devtools.ts
|
|
15176
|
+
var PAJA_LOG_LIMIT = 500;
|
|
15177
|
+
var proxyToReal = /* @__PURE__ */ new WeakMap();
|
|
15178
|
+
var realToProxy = /* @__PURE__ */ new WeakMap();
|
|
15179
|
+
var proxyContexts = /* @__PURE__ */ new WeakMap();
|
|
15180
|
+
function isNappletMessage(value) {
|
|
15181
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.type === "string";
|
|
14072
15182
|
}
|
|
14073
|
-
function
|
|
14074
|
-
|
|
15183
|
+
function describeMessage(raw) {
|
|
15184
|
+
if (isNappletMessage(raw)) return raw.type;
|
|
15185
|
+
if (Array.isArray(raw) && typeof raw[0] === "string") return raw[0];
|
|
15186
|
+
return "unknown";
|
|
14075
15187
|
}
|
|
14076
|
-
function
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
15188
|
+
function previewMessage(raw) {
|
|
15189
|
+
try {
|
|
15190
|
+
return JSON.stringify(raw);
|
|
15191
|
+
} catch (error) {
|
|
15192
|
+
return `unserializable:${error instanceof Error ? error.message : String(error)}`;
|
|
14081
15193
|
}
|
|
14082
|
-
return res;
|
|
14083
15194
|
}
|
|
14084
|
-
|
|
14085
|
-
const
|
|
14086
|
-
for (
|
|
14087
|
-
|
|
14088
|
-
const box = new Uint8Array(256);
|
|
14089
|
-
box[0] = 99;
|
|
14090
|
-
for (let i4 = 0; i4 < 255; i4++) {
|
|
14091
|
-
let x = t[255 - i4];
|
|
14092
|
-
x |= x << 8;
|
|
14093
|
-
box[t[i4]] = (x ^ x >> 4 ^ x >> 5 ^ x >> 6 ^ x >> 7 ^ 99) & 255;
|
|
15195
|
+
function compactMessageDetail(raw) {
|
|
15196
|
+
const detail = {};
|
|
15197
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
15198
|
+
if (key !== "type" && key !== "id" && value !== void 0) detail[key] = value;
|
|
14094
15199
|
}
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
var rotl32_8 = (n) => n << 8 | n >>> 24;
|
|
14101
|
-
function genTtable(sbox2, fn) {
|
|
14102
|
-
if (sbox2.length !== 256)
|
|
14103
|
-
throw new Error("Wrong sbox length");
|
|
14104
|
-
const T0 = new Uint32Array(256).map((_, j) => fn(sbox2[j]));
|
|
14105
|
-
const T1 = T0.map(rotl32_8);
|
|
14106
|
-
const T2 = T1.map(rotl32_8);
|
|
14107
|
-
const T3 = T2.map(rotl32_8);
|
|
14108
|
-
const T01 = new Uint32Array(256 * 256);
|
|
14109
|
-
const T23 = new Uint32Array(256 * 256);
|
|
14110
|
-
const sbox22 = new Uint16Array(256 * 256);
|
|
14111
|
-
for (let i4 = 0; i4 < 256; i4++) {
|
|
14112
|
-
for (let j = 0; j < 256; j++) {
|
|
14113
|
-
const idx = i4 * 256 + j;
|
|
14114
|
-
T01[idx] = T0[i4] ^ T1[j];
|
|
14115
|
-
T23[idx] = T2[i4] ^ T3[j];
|
|
14116
|
-
sbox22[idx] = sbox2[i4] << 8 | sbox2[j];
|
|
14117
|
-
}
|
|
15200
|
+
if (Object.keys(detail).length === 0) return "";
|
|
15201
|
+
try {
|
|
15202
|
+
return JSON.stringify(detail);
|
|
15203
|
+
} catch {
|
|
15204
|
+
return "";
|
|
14118
15205
|
}
|
|
14119
|
-
return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 };
|
|
14120
15206
|
}
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
const
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
return p;
|
|
14128
|
-
})();
|
|
14129
|
-
function expandKeyLE(key) {
|
|
14130
|
-
abytes3(key);
|
|
14131
|
-
const len = key.length;
|
|
14132
|
-
validateKeyLength(key);
|
|
14133
|
-
const { sbox2 } = tableEncoding;
|
|
14134
|
-
const toClean = [];
|
|
14135
|
-
if (!isAligned32(key))
|
|
14136
|
-
toClean.push(key = copyBytes2(key));
|
|
14137
|
-
const k32 = u32(key);
|
|
14138
|
-
const Nk = k32.length;
|
|
14139
|
-
const subByte = (n) => applySbox(sbox2, n, n, n, n);
|
|
14140
|
-
const xk = new Uint32Array(len + 28);
|
|
14141
|
-
xk.set(k32);
|
|
14142
|
-
for (let i4 = Nk; i4 < xk.length; i4++) {
|
|
14143
|
-
let t = xk[i4 - 1];
|
|
14144
|
-
if (i4 % Nk === 0)
|
|
14145
|
-
t = subByte(rotr32_8(t)) ^ xPowers[i4 / Nk - 1];
|
|
14146
|
-
else if (Nk > 6 && i4 % Nk === 4)
|
|
14147
|
-
t = subByte(t);
|
|
14148
|
-
xk[i4] = xk[i4 - Nk] ^ t;
|
|
15207
|
+
function describeMessageDetail(raw) {
|
|
15208
|
+
if (!isNappletMessage(raw)) return "";
|
|
15209
|
+
const value = raw;
|
|
15210
|
+
for (const key of ["error", "message", "reason"]) {
|
|
15211
|
+
const detail = value[key];
|
|
15212
|
+
if (typeof detail === "string" && detail.length > 0) return detail;
|
|
14149
15213
|
}
|
|
14150
|
-
|
|
14151
|
-
return xk;
|
|
15214
|
+
return raw.type.endsWith(".error") ? compactMessageDetail(value) : "";
|
|
14152
15215
|
}
|
|
14153
|
-
function
|
|
14154
|
-
|
|
14155
|
-
|
|
14156
|
-
|
|
14157
|
-
|
|
14158
|
-
|
|
14159
|
-
|
|
14160
|
-
|
|
14161
|
-
|
|
14162
|
-
|
|
14163
|
-
|
|
14164
|
-
|
|
14165
|
-
|
|
14166
|
-
|
|
14167
|
-
|
|
15216
|
+
function domainEnabled(simulation, domain) {
|
|
15217
|
+
return simulation.capabilities.domains[domain] === true;
|
|
15218
|
+
}
|
|
15219
|
+
function getTargetIdentity(config) {
|
|
15220
|
+
return {
|
|
15221
|
+
pubkey: "",
|
|
15222
|
+
dTag: config.window.dTag,
|
|
15223
|
+
aggregateHash: config.window.aggregateHash
|
|
15224
|
+
};
|
|
15225
|
+
}
|
|
15226
|
+
function appendPajaMessageLog(state, direction, raw, windowId) {
|
|
15227
|
+
if (!state) return;
|
|
15228
|
+
state.messageLog.push({
|
|
15229
|
+
index: state.messageLog.length,
|
|
15230
|
+
timestamp: Date.now(),
|
|
15231
|
+
direction,
|
|
15232
|
+
type: describeMessage(raw),
|
|
15233
|
+
windowId,
|
|
15234
|
+
preview: previewMessage(raw),
|
|
15235
|
+
detail: describeMessageDetail(raw)
|
|
15236
|
+
});
|
|
15237
|
+
if (state.messageLog.length > PAJA_LOG_LIMIT) {
|
|
15238
|
+
state.messageLog.splice(0, state.messageLog.length - PAJA_LOG_LIMIT);
|
|
15239
|
+
}
|
|
15240
|
+
renderPajaMessageLog(state);
|
|
15241
|
+
}
|
|
15242
|
+
function createPajaPostMessageProxy(realWin, state, windowId) {
|
|
15243
|
+
const existing = realToProxy.get(realWin);
|
|
15244
|
+
if (existing) {
|
|
15245
|
+
proxyContexts.set(existing, { state, windowId });
|
|
15246
|
+
return existing;
|
|
15247
|
+
}
|
|
15248
|
+
const proxy = new Proxy(realWin, {
|
|
15249
|
+
get(target, prop) {
|
|
15250
|
+
if (prop === "postMessage") {
|
|
15251
|
+
return (msg, targetOrigin, transfer) => {
|
|
15252
|
+
const context = proxyContexts.get(proxy);
|
|
15253
|
+
appendPajaMessageLog(
|
|
15254
|
+
context?.state ?? state,
|
|
15255
|
+
"shell->napplet",
|
|
15256
|
+
msg,
|
|
15257
|
+
context?.windowId ?? windowId
|
|
15258
|
+
);
|
|
15259
|
+
return target.postMessage(msg, targetOrigin, transfer);
|
|
15260
|
+
};
|
|
15261
|
+
}
|
|
15262
|
+
try {
|
|
15263
|
+
const val = Reflect.get(target, prop, target);
|
|
15264
|
+
return typeof val === "function" ? val.bind(target) : val;
|
|
15265
|
+
} catch {
|
|
15266
|
+
return void 0;
|
|
15267
|
+
}
|
|
15268
|
+
}
|
|
15269
|
+
});
|
|
15270
|
+
proxyToReal.set(proxy, realWin);
|
|
15271
|
+
realToProxy.set(realWin, proxy);
|
|
15272
|
+
proxyContexts.set(proxy, { state, windowId });
|
|
15273
|
+
return proxy;
|
|
15274
|
+
}
|
|
15275
|
+
function installPajaOriginRegistryProxy(originRegistry2, stateRef) {
|
|
15276
|
+
const originalGetIframeWindow = originRegistry2.getIframeWindow.bind(originRegistry2);
|
|
15277
|
+
originRegistry2.getIframeWindow = (windowId) => {
|
|
15278
|
+
const win = originalGetIframeWindow(windowId);
|
|
15279
|
+
if (!win) return null;
|
|
15280
|
+
return createPajaPostMessageProxy(win, stateRef(), windowId);
|
|
15281
|
+
};
|
|
15282
|
+
const originalGetWindowId = originRegistry2.getWindowId.bind(originRegistry2);
|
|
15283
|
+
originRegistry2.getWindowId = (win) => {
|
|
15284
|
+
const result2 = originalGetWindowId(win);
|
|
15285
|
+
if (result2) return result2;
|
|
15286
|
+
const real = proxyToReal.get(win);
|
|
15287
|
+
return real ? originalGetWindowId(real) : void 0;
|
|
15288
|
+
};
|
|
15289
|
+
const originalGetIdentity = originRegistry2.getIdentity.bind(originRegistry2);
|
|
15290
|
+
originRegistry2.getIdentity = (win) => {
|
|
15291
|
+
const real = proxyToReal.get(win);
|
|
15292
|
+
return originalGetIdentity(win) ?? (real ? originalGetIdentity(real) : void 0);
|
|
15293
|
+
};
|
|
15294
|
+
const originalGetRegistrationId = originRegistry2.getRegistrationId.bind(originRegistry2);
|
|
15295
|
+
originRegistry2.getRegistrationId = (win) => {
|
|
15296
|
+
const real = proxyToReal.get(win);
|
|
15297
|
+
return originalGetRegistrationId(win) ?? (real ? originalGetRegistrationId(real) : void 0);
|
|
15298
|
+
};
|
|
15299
|
+
const originalGetEnvironment = originRegistry2.getEnvironment.bind(originRegistry2);
|
|
15300
|
+
originRegistry2.getEnvironment = (win) => {
|
|
15301
|
+
const real = proxyToReal.get(win);
|
|
15302
|
+
return originalGetEnvironment(win) ?? (real ? originalGetEnvironment(real) : void 0);
|
|
15303
|
+
};
|
|
15304
|
+
}
|
|
15305
|
+
function renderPajaDevtools(state, options) {
|
|
15306
|
+
renderInterfaceControls(state);
|
|
15307
|
+
renderAclControls(state, options.bridge);
|
|
15308
|
+
renderSignerStatus(state, options.devSignerPubkey);
|
|
15309
|
+
}
|
|
15310
|
+
function renderPajaMessageLog(state) {
|
|
15311
|
+
const container = document.getElementById("message-log");
|
|
15312
|
+
if (!container) return;
|
|
15313
|
+
const filter = state.messageFilter.trim().toLowerCase();
|
|
15314
|
+
const rows = state.messageLog.filter((entry) => filter.length === 0 || `${entry.direction} ${entry.type} ${entry.detail} ${entry.preview}`.toLowerCase().includes(filter)).slice(-120).map((entry) => {
|
|
15315
|
+
const row = document.createElement("div");
|
|
15316
|
+
row.className = "log-row";
|
|
15317
|
+
row.dataset.messageType = entry.type;
|
|
15318
|
+
if (entry.type.endsWith(".error")) row.dataset.error = "true";
|
|
15319
|
+
const dir = document.createElement("div");
|
|
15320
|
+
dir.className = "log-dir";
|
|
15321
|
+
dir.textContent = entry.direction;
|
|
15322
|
+
const body = document.createElement("div");
|
|
15323
|
+
body.className = "log-body";
|
|
15324
|
+
const type = document.createElement("div");
|
|
15325
|
+
type.className = "log-type";
|
|
15326
|
+
type.title = entry.preview;
|
|
15327
|
+
type.textContent = entry.type;
|
|
15328
|
+
body.append(type);
|
|
15329
|
+
if (entry.detail) {
|
|
15330
|
+
const detail = document.createElement("div");
|
|
15331
|
+
detail.className = "log-detail";
|
|
15332
|
+
detail.textContent = entry.detail;
|
|
15333
|
+
detail.title = entry.preview;
|
|
15334
|
+
body.append(detail);
|
|
15335
|
+
}
|
|
15336
|
+
row.append(dir, body);
|
|
15337
|
+
return row;
|
|
15338
|
+
});
|
|
15339
|
+
container.replaceChildren(...rows);
|
|
15340
|
+
container.scrollTop = container.scrollHeight;
|
|
15341
|
+
}
|
|
15342
|
+
function renderInterfaceControls(state) {
|
|
15343
|
+
const container = document.getElementById("interface-toggles");
|
|
15344
|
+
if (!container) return;
|
|
15345
|
+
container.replaceChildren(...PAJA_SIMULATION_DOMAINS.map((domain) => {
|
|
15346
|
+
const enabled = domainEnabled(state.simulation, domain);
|
|
15347
|
+
const button = document.createElement("button");
|
|
15348
|
+
button.type = "button";
|
|
15349
|
+
button.className = "toggle";
|
|
15350
|
+
button.dataset.interfaceDomain = domain;
|
|
15351
|
+
button.dataset.enabled = String(enabled);
|
|
15352
|
+
button.textContent = domain;
|
|
15353
|
+
button.title = `${domain} injection ${enabled ? "enabled" : "disabled"}`;
|
|
15354
|
+
button.addEventListener("click", () => {
|
|
15355
|
+
state.setDomainEnabled(domain, !domainEnabled(state.simulation, domain));
|
|
15356
|
+
});
|
|
15357
|
+
return button;
|
|
15358
|
+
}));
|
|
15359
|
+
}
|
|
15360
|
+
function renderAclControls(state, bridge) {
|
|
15361
|
+
const container = document.getElementById("acl-controls");
|
|
15362
|
+
if (!container) return;
|
|
15363
|
+
const identity = getTargetIdentity(state.config);
|
|
15364
|
+
container.replaceChildren(...ALL_CAPABILITIES.map((capability) => {
|
|
15365
|
+
const allowed2 = bridge?.runtime.aclState.check(
|
|
15366
|
+
identity.pubkey,
|
|
15367
|
+
identity.dTag,
|
|
15368
|
+
identity.aggregateHash,
|
|
15369
|
+
capability
|
|
15370
|
+
) ?? true;
|
|
15371
|
+
const button = document.createElement("button");
|
|
15372
|
+
button.type = "button";
|
|
15373
|
+
button.className = "toggle";
|
|
15374
|
+
button.dataset.aclCapability = capability;
|
|
15375
|
+
button.dataset.enabled = String(allowed2);
|
|
15376
|
+
button.textContent = capability;
|
|
15377
|
+
button.title = `${capability} ${allowed2 ? "allowed" : "blocked"}`;
|
|
15378
|
+
button.addEventListener("click", () => state.setAclCapability(capability, !allowed2));
|
|
15379
|
+
return button;
|
|
15380
|
+
}));
|
|
15381
|
+
}
|
|
15382
|
+
function renderSignerStatus(state, signerPubkey) {
|
|
15383
|
+
const el = document.getElementById("signer-status");
|
|
15384
|
+
const controls = document.getElementById("signer-controls");
|
|
15385
|
+
if (!el || !controls) return;
|
|
15386
|
+
const fallbackPubkey = state.simulation.identity.pubkey || (state.signer.method === "dev" ? signerPubkey : "");
|
|
15387
|
+
const pubkey = state.simulation.identity.pubkey || state.signer.pubkey || (state.signer.method === "dev" ? signerPubkey : "");
|
|
15388
|
+
const methodLabel = state.signer.method === "nip07" ? "NIP-07" : state.signer.method === "nip46" ? "bunker" : state.signer.method === "dev" ? "dev" : "none";
|
|
15389
|
+
const relay = state.signer.relay ? ` \xB7 relay ${state.signer.relay}` : "";
|
|
15390
|
+
const prefix = state.signer.status === "disconnected" ? "no signer connected" : state.signer.status === "error" ? `${methodLabel} error: ${state.signer.error ?? "unknown error"} \xB7 fallback pubkey ${fallbackPubkey}` : `${methodLabel} ${state.signer.status} \xB7 pubkey ${pubkey || "(none)"}${relay}`;
|
|
15391
|
+
el.textContent = `${prefix} \xB7 every sign/publish request prompts`;
|
|
15392
|
+
const previousInput = controls.querySelector("#signer-bunker-uri")?.value ?? "";
|
|
15393
|
+
const devButton = document.createElement("button");
|
|
15394
|
+
devButton.type = "button";
|
|
15395
|
+
devButton.id = "signer-dev";
|
|
15396
|
+
devButton.dataset.active = String(state.signer.method === "dev");
|
|
15397
|
+
devButton.textContent = "Dev";
|
|
15398
|
+
devButton.addEventListener("click", () => state.useDevSigner());
|
|
15399
|
+
const nip07Button = document.createElement("button");
|
|
15400
|
+
nip07Button.type = "button";
|
|
15401
|
+
nip07Button.id = "signer-nip07";
|
|
15402
|
+
nip07Button.dataset.active = String(state.signer.method === "nip07");
|
|
15403
|
+
nip07Button.textContent = "NIP-07";
|
|
15404
|
+
nip07Button.addEventListener("click", () => void state.connectNip07());
|
|
15405
|
+
const bunkerInput = document.createElement("input");
|
|
15406
|
+
bunkerInput.id = "signer-bunker-uri";
|
|
15407
|
+
bunkerInput.type = "text";
|
|
15408
|
+
bunkerInput.autocomplete = "off";
|
|
15409
|
+
bunkerInput.placeholder = "bunker://...";
|
|
15410
|
+
bunkerInput.setAttribute("aria-label", "Bunker connection URI");
|
|
15411
|
+
bunkerInput.value = previousInput;
|
|
15412
|
+
const bunkerButton = document.createElement("button");
|
|
15413
|
+
bunkerButton.type = "button";
|
|
15414
|
+
bunkerButton.id = "signer-bunker";
|
|
15415
|
+
bunkerButton.dataset.active = String(state.signer.method === "nip46");
|
|
15416
|
+
bunkerButton.textContent = "Bunker";
|
|
15417
|
+
bunkerButton.addEventListener("click", () => void state.connectBunker(bunkerInput.value));
|
|
15418
|
+
controls.replaceChildren(devButton, nip07Button, bunkerInput, bunkerButton);
|
|
15419
|
+
}
|
|
15420
|
+
|
|
15421
|
+
// ../../node_modules/.pnpm/@noble+ciphers@2.1.1/node_modules/@noble/ciphers/aes.js
|
|
15422
|
+
var BLOCK_SIZE = 16;
|
|
15423
|
+
var POLY = 283;
|
|
15424
|
+
function validateKeyLength(key) {
|
|
15425
|
+
if (![16, 24, 32].includes(key.length))
|
|
15426
|
+
throw new Error('"aes key" expected Uint8Array of length 16/24/32, got length=' + key.length);
|
|
15427
|
+
}
|
|
15428
|
+
function mul2(n) {
|
|
15429
|
+
return n << 1 ^ POLY & -(n >> 7);
|
|
15430
|
+
}
|
|
15431
|
+
function mul(a, b) {
|
|
15432
|
+
let res = 0;
|
|
15433
|
+
for (; b > 0; b >>= 1) {
|
|
15434
|
+
res ^= a & -(b & 1);
|
|
15435
|
+
a = mul2(a);
|
|
15436
|
+
}
|
|
15437
|
+
return res;
|
|
15438
|
+
}
|
|
15439
|
+
var sbox = /* @__PURE__ */ (() => {
|
|
15440
|
+
const t = new Uint8Array(256);
|
|
15441
|
+
for (let i4 = 0, x = 1; i4 < 256; i4++, x ^= mul2(x))
|
|
15442
|
+
t[i4] = x;
|
|
15443
|
+
const box = new Uint8Array(256);
|
|
15444
|
+
box[0] = 99;
|
|
15445
|
+
for (let i4 = 0; i4 < 255; i4++) {
|
|
15446
|
+
let x = t[255 - i4];
|
|
15447
|
+
x |= x << 8;
|
|
15448
|
+
box[t[i4]] = (x ^ x >> 4 ^ x >> 5 ^ x >> 6 ^ x >> 7 ^ 99) & 255;
|
|
15449
|
+
}
|
|
15450
|
+
clean2(t);
|
|
15451
|
+
return box;
|
|
15452
|
+
})();
|
|
15453
|
+
var invSbox = /* @__PURE__ */ sbox.map((_, j) => sbox.indexOf(j));
|
|
15454
|
+
var rotr32_8 = (n) => n << 24 | n >>> 8;
|
|
15455
|
+
var rotl32_8 = (n) => n << 8 | n >>> 24;
|
|
15456
|
+
function genTtable(sbox2, fn) {
|
|
15457
|
+
if (sbox2.length !== 256)
|
|
15458
|
+
throw new Error("Wrong sbox length");
|
|
15459
|
+
const T0 = new Uint32Array(256).map((_, j) => fn(sbox2[j]));
|
|
15460
|
+
const T1 = T0.map(rotl32_8);
|
|
15461
|
+
const T2 = T1.map(rotl32_8);
|
|
15462
|
+
const T3 = T2.map(rotl32_8);
|
|
15463
|
+
const T01 = new Uint32Array(256 * 256);
|
|
15464
|
+
const T23 = new Uint32Array(256 * 256);
|
|
15465
|
+
const sbox22 = new Uint16Array(256 * 256);
|
|
15466
|
+
for (let i4 = 0; i4 < 256; i4++) {
|
|
15467
|
+
for (let j = 0; j < 256; j++) {
|
|
15468
|
+
const idx = i4 * 256 + j;
|
|
15469
|
+
T01[idx] = T0[i4] ^ T1[j];
|
|
15470
|
+
T23[idx] = T2[i4] ^ T3[j];
|
|
15471
|
+
sbox22[idx] = sbox2[i4] << 8 | sbox2[j];
|
|
15472
|
+
}
|
|
15473
|
+
}
|
|
15474
|
+
return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 };
|
|
15475
|
+
}
|
|
15476
|
+
var tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => mul(s, 3) << 24 | s << 16 | s << 8 | mul(s, 2));
|
|
15477
|
+
var tableDecoding = /* @__PURE__ */ genTtable(invSbox, (s) => mul(s, 11) << 24 | mul(s, 13) << 16 | mul(s, 9) << 8 | mul(s, 14));
|
|
15478
|
+
var xPowers = /* @__PURE__ */ (() => {
|
|
15479
|
+
const p = new Uint8Array(16);
|
|
15480
|
+
for (let i4 = 0, x = 1; i4 < 16; i4++, x = mul2(x))
|
|
15481
|
+
p[i4] = x;
|
|
15482
|
+
return p;
|
|
15483
|
+
})();
|
|
15484
|
+
function expandKeyLE(key) {
|
|
15485
|
+
abytes3(key);
|
|
15486
|
+
const len = key.length;
|
|
15487
|
+
validateKeyLength(key);
|
|
15488
|
+
const { sbox2 } = tableEncoding;
|
|
15489
|
+
const toClean = [];
|
|
15490
|
+
if (!isAligned32(key))
|
|
15491
|
+
toClean.push(key = copyBytes2(key));
|
|
15492
|
+
const k32 = u32(key);
|
|
15493
|
+
const Nk = k32.length;
|
|
15494
|
+
const subByte = (n) => applySbox(sbox2, n, n, n, n);
|
|
15495
|
+
const xk = new Uint32Array(len + 28);
|
|
15496
|
+
xk.set(k32);
|
|
15497
|
+
for (let i4 = Nk; i4 < xk.length; i4++) {
|
|
15498
|
+
let t = xk[i4 - 1];
|
|
15499
|
+
if (i4 % Nk === 0)
|
|
15500
|
+
t = subByte(rotr32_8(t)) ^ xPowers[i4 / Nk - 1];
|
|
15501
|
+
else if (Nk > 6 && i4 % Nk === 4)
|
|
15502
|
+
t = subByte(t);
|
|
15503
|
+
xk[i4] = xk[i4 - Nk] ^ t;
|
|
15504
|
+
}
|
|
15505
|
+
clean2(...toClean);
|
|
15506
|
+
return xk;
|
|
15507
|
+
}
|
|
15508
|
+
function expandKeyDecLE(key) {
|
|
15509
|
+
const encKey = expandKeyLE(key);
|
|
15510
|
+
const xk = encKey.slice();
|
|
15511
|
+
const Nk = encKey.length;
|
|
15512
|
+
const { sbox2 } = tableEncoding;
|
|
15513
|
+
const { T0, T1, T2, T3 } = tableDecoding;
|
|
15514
|
+
for (let i4 = 0; i4 < Nk; i4 += 4) {
|
|
15515
|
+
for (let j = 0; j < 4; j++)
|
|
15516
|
+
xk[i4 + j] = encKey[Nk - i4 - 4 + j];
|
|
15517
|
+
}
|
|
15518
|
+
clean2(encKey);
|
|
15519
|
+
for (let i4 = 4; i4 < Nk - 4; i4++) {
|
|
15520
|
+
const x = xk[i4];
|
|
15521
|
+
const w = applySbox(sbox2, x, x, x, x);
|
|
15522
|
+
xk[i4] = T0[w & 255] ^ T1[w >>> 8 & 255] ^ T2[w >>> 16 & 255] ^ T3[w >>> 24];
|
|
14168
15523
|
}
|
|
14169
15524
|
return xk;
|
|
14170
15525
|
}
|
|
@@ -16890,12 +18245,12 @@ async function sendRequestWithId(state, correlationId, method, params) {
|
|
|
16890
18245
|
tags: [["p", state.bunkerPubkey]],
|
|
16891
18246
|
content: encryptedContent
|
|
16892
18247
|
}, state.localSecretKey);
|
|
16893
|
-
return new Promise((resolve,
|
|
18248
|
+
return new Promise((resolve, reject2) => {
|
|
16894
18249
|
const timer = setTimeout(() => {
|
|
16895
18250
|
state.pending.delete(correlationId);
|
|
16896
|
-
|
|
18251
|
+
reject2(new Error(`NIP-46 request timed out: ${method}`));
|
|
16897
18252
|
}, state.timeout);
|
|
16898
|
-
state.pending.set(correlationId, { resolve, reject, timer });
|
|
18253
|
+
state.pending.set(correlationId, { resolve, reject: reject2, timer });
|
|
16899
18254
|
state.ws.send(JSON.stringify(["EVENT", signedEvent]));
|
|
16900
18255
|
});
|
|
16901
18256
|
}
|
|
@@ -16934,7 +18289,7 @@ function rejectPendingOnClose(state) {
|
|
|
16934
18289
|
state.pending.clear();
|
|
16935
18290
|
}
|
|
16936
18291
|
function openWebSocket(state) {
|
|
16937
|
-
return new Promise((resolve,
|
|
18292
|
+
return new Promise((resolve, reject2) => {
|
|
16938
18293
|
state.ws = new WebSocket(state.relayUrl);
|
|
16939
18294
|
state.ws.onopen = () => {
|
|
16940
18295
|
state.ws.send(JSON.stringify([
|
|
@@ -16948,7 +18303,7 @@ function openWebSocket(state) {
|
|
|
16948
18303
|
handleRelayMessage(state, event.data).catch(() => {
|
|
16949
18304
|
});
|
|
16950
18305
|
};
|
|
16951
|
-
state.ws.onerror = () =>
|
|
18306
|
+
state.ws.onerror = () => reject2(new Error(`NIP-46 WebSocket connection failed: ${state.relayUrl}`));
|
|
16952
18307
|
state.ws.onclose = () => rejectPendingOnClose(state);
|
|
16953
18308
|
});
|
|
16954
18309
|
}
|
|
@@ -17123,245 +18478,77 @@ function createPajaSignerController(options) {
|
|
|
17123
18478
|
};
|
|
17124
18479
|
}
|
|
17125
18480
|
|
|
17126
|
-
// src/browser-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
}
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
try {
|
|
17153
|
-
return JSON.stringify(detail);
|
|
17154
|
-
} catch {
|
|
17155
|
-
return "";
|
|
17156
|
-
}
|
|
17157
|
-
}
|
|
17158
|
-
function describeMessageDetail(raw) {
|
|
17159
|
-
if (!isNappletMessage(raw)) return "";
|
|
17160
|
-
const value = raw;
|
|
17161
|
-
for (const key of ["error", "message", "reason"]) {
|
|
17162
|
-
const detail = value[key];
|
|
17163
|
-
if (typeof detail === "string" && detail.length > 0) return detail;
|
|
17164
|
-
}
|
|
17165
|
-
return raw.type.endsWith(".error") ? compactMessageDetail(value) : "";
|
|
17166
|
-
}
|
|
17167
|
-
function domainEnabled(simulation, domain) {
|
|
17168
|
-
return simulation.capabilities.domains[domain] === true;
|
|
17169
|
-
}
|
|
17170
|
-
function getTargetIdentity(config) {
|
|
17171
|
-
return {
|
|
17172
|
-
pubkey: "",
|
|
17173
|
-
dTag: config.window.dTag,
|
|
17174
|
-
aggregateHash: config.window.aggregateHash
|
|
17175
|
-
};
|
|
17176
|
-
}
|
|
17177
|
-
function appendPajaMessageLog(state, direction, raw, windowId) {
|
|
17178
|
-
if (!state) return;
|
|
17179
|
-
state.messageLog.push({
|
|
17180
|
-
index: state.messageLog.length,
|
|
17181
|
-
timestamp: Date.now(),
|
|
17182
|
-
direction,
|
|
17183
|
-
type: describeMessage(raw),
|
|
17184
|
-
windowId,
|
|
17185
|
-
preview: previewMessage(raw),
|
|
17186
|
-
detail: describeMessageDetail(raw)
|
|
17187
|
-
});
|
|
17188
|
-
if (state.messageLog.length > PAJA_LOG_LIMIT) {
|
|
17189
|
-
state.messageLog.splice(0, state.messageLog.length - PAJA_LOG_LIMIT);
|
|
17190
|
-
}
|
|
17191
|
-
renderPajaMessageLog(state);
|
|
17192
|
-
}
|
|
17193
|
-
function createPajaPostMessageProxy(realWin, state, windowId) {
|
|
17194
|
-
const existing = realToProxy.get(realWin);
|
|
17195
|
-
if (existing) {
|
|
17196
|
-
proxyContexts.set(existing, { state, windowId });
|
|
17197
|
-
return existing;
|
|
18481
|
+
// src/browser-host-signer.ts
|
|
18482
|
+
function confirmPajaRequest(state, request) {
|
|
18483
|
+
if (request.action === "upload") {
|
|
18484
|
+
const filename = request.filename ?? "(unnamed blob)";
|
|
18485
|
+
const mimeType = request.mimeType ?? "application/octet-stream";
|
|
18486
|
+
const allowed3 = window.confirm([
|
|
18487
|
+
"Paja upload request",
|
|
18488
|
+
`napplet: ${request.napplet.dTag} (${request.windowId})`,
|
|
18489
|
+
`file: ${filename}`,
|
|
18490
|
+
`size: ${request.size} bytes`,
|
|
18491
|
+
`type: ${mimeType}`,
|
|
18492
|
+
`server: ${request.server}`,
|
|
18493
|
+
request.warning
|
|
18494
|
+
].join("\n"));
|
|
18495
|
+
appendPajaMessageLog(state, "paja", {
|
|
18496
|
+
type: `paja.upload.${allowed3 ? "confirmed" : "denied"}`,
|
|
18497
|
+
windowId: request.windowId,
|
|
18498
|
+
dTag: request.napplet.dTag,
|
|
18499
|
+
aggregateHash: request.napplet.aggregateHash,
|
|
18500
|
+
filename,
|
|
18501
|
+
size: request.size,
|
|
18502
|
+
mimeType,
|
|
18503
|
+
server: request.server,
|
|
18504
|
+
warning: request.warning
|
|
18505
|
+
});
|
|
18506
|
+
return allowed3;
|
|
17198
18507
|
}
|
|
17199
|
-
const
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17209
|
-
);
|
|
17210
|
-
return target.postMessage(msg, targetOrigin, transfer);
|
|
17211
|
-
};
|
|
17212
|
-
}
|
|
17213
|
-
try {
|
|
17214
|
-
const val = Reflect.get(target, prop, target);
|
|
17215
|
-
return typeof val === "function" ? val.bind(target) : val;
|
|
17216
|
-
} catch {
|
|
17217
|
-
return void 0;
|
|
17218
|
-
}
|
|
17219
|
-
}
|
|
17220
|
-
});
|
|
17221
|
-
proxyToReal.set(proxy, realWin);
|
|
17222
|
-
realToProxy.set(realWin, proxy);
|
|
17223
|
-
proxyContexts.set(proxy, { state, windowId });
|
|
17224
|
-
return proxy;
|
|
17225
|
-
}
|
|
17226
|
-
function installPajaOriginRegistryProxy(originRegistry2, stateRef) {
|
|
17227
|
-
const originalGetIframeWindow = originRegistry2.getIframeWindow.bind(originRegistry2);
|
|
17228
|
-
originRegistry2.getIframeWindow = (windowId) => {
|
|
17229
|
-
const win = originalGetIframeWindow(windowId);
|
|
17230
|
-
if (!win) return null;
|
|
17231
|
-
return createPajaPostMessageProxy(win, stateRef(), windowId);
|
|
17232
|
-
};
|
|
17233
|
-
const originalGetWindowId = originRegistry2.getWindowId.bind(originRegistry2);
|
|
17234
|
-
originRegistry2.getWindowId = (win) => {
|
|
17235
|
-
const result2 = originalGetWindowId(win);
|
|
17236
|
-
if (result2) return result2;
|
|
17237
|
-
const real = proxyToReal.get(win);
|
|
17238
|
-
return real ? originalGetWindowId(real) : void 0;
|
|
17239
|
-
};
|
|
17240
|
-
const originalGetIdentity = originRegistry2.getIdentity.bind(originRegistry2);
|
|
17241
|
-
originRegistry2.getIdentity = (win) => {
|
|
17242
|
-
const real = proxyToReal.get(win);
|
|
17243
|
-
return originalGetIdentity(win) ?? (real ? originalGetIdentity(real) : void 0);
|
|
17244
|
-
};
|
|
17245
|
-
const originalGetRegistrationId = originRegistry2.getRegistrationId.bind(originRegistry2);
|
|
17246
|
-
originRegistry2.getRegistrationId = (win) => {
|
|
17247
|
-
const real = proxyToReal.get(win);
|
|
17248
|
-
return originalGetRegistrationId(win) ?? (real ? originalGetRegistrationId(real) : void 0);
|
|
17249
|
-
};
|
|
17250
|
-
}
|
|
17251
|
-
function renderPajaDevtools(state, options) {
|
|
17252
|
-
renderInterfaceControls(state);
|
|
17253
|
-
renderAclControls(state, options.bridge);
|
|
17254
|
-
renderSignerStatus(state, options.devSignerPubkey);
|
|
17255
|
-
}
|
|
17256
|
-
function renderPajaMessageLog(state) {
|
|
17257
|
-
const container = document.getElementById("message-log");
|
|
17258
|
-
if (!container) return;
|
|
17259
|
-
const filter = state.messageFilter.trim().toLowerCase();
|
|
17260
|
-
const rows = state.messageLog.filter((entry) => filter.length === 0 || `${entry.direction} ${entry.type} ${entry.detail} ${entry.preview}`.toLowerCase().includes(filter)).slice(-120).map((entry) => {
|
|
17261
|
-
const row = document.createElement("div");
|
|
17262
|
-
row.className = "log-row";
|
|
17263
|
-
row.dataset.messageType = entry.type;
|
|
17264
|
-
if (entry.type.endsWith(".error")) row.dataset.error = "true";
|
|
17265
|
-
const dir = document.createElement("div");
|
|
17266
|
-
dir.className = "log-dir";
|
|
17267
|
-
dir.textContent = entry.direction;
|
|
17268
|
-
const body = document.createElement("div");
|
|
17269
|
-
body.className = "log-body";
|
|
17270
|
-
const type = document.createElement("div");
|
|
17271
|
-
type.className = "log-type";
|
|
17272
|
-
type.title = entry.preview;
|
|
17273
|
-
type.textContent = entry.type;
|
|
17274
|
-
body.append(type);
|
|
17275
|
-
if (entry.detail) {
|
|
17276
|
-
const detail = document.createElement("div");
|
|
17277
|
-
detail.className = "log-detail";
|
|
17278
|
-
detail.textContent = entry.detail;
|
|
17279
|
-
detail.title = entry.preview;
|
|
17280
|
-
body.append(detail);
|
|
17281
|
-
}
|
|
17282
|
-
row.append(dir, body);
|
|
17283
|
-
return row;
|
|
18508
|
+
const event = request.event;
|
|
18509
|
+
const kind = typeof event.kind === "number" ? event.kind : "unknown";
|
|
18510
|
+
const content = typeof event.content === "string" && event.content.length > 0 ? `
|
|
18511
|
+
|
|
18512
|
+
${event.content.slice(0, 240)}` : "";
|
|
18513
|
+
const allowed2 = window.confirm(`Paja ${request.action} request
|
|
18514
|
+
kind: ${kind}${content}`);
|
|
18515
|
+
appendPajaMessageLog(state, "paja", {
|
|
18516
|
+
type: `paja.${request.action}.${allowed2 ? "confirmed" : "denied"}`,
|
|
18517
|
+
kind
|
|
17284
18518
|
});
|
|
17285
|
-
|
|
17286
|
-
container.scrollTop = container.scrollHeight;
|
|
17287
|
-
}
|
|
17288
|
-
function renderInterfaceControls(state) {
|
|
17289
|
-
const container = document.getElementById("interface-toggles");
|
|
17290
|
-
if (!container) return;
|
|
17291
|
-
container.replaceChildren(...PAJA_SIMULATION_DOMAINS.map((domain) => {
|
|
17292
|
-
const enabled = domainEnabled(state.simulation, domain);
|
|
17293
|
-
const button = document.createElement("button");
|
|
17294
|
-
button.type = "button";
|
|
17295
|
-
button.className = "toggle";
|
|
17296
|
-
button.dataset.interfaceDomain = domain;
|
|
17297
|
-
button.dataset.enabled = String(enabled);
|
|
17298
|
-
button.textContent = domain;
|
|
17299
|
-
button.title = `${domain} injection ${enabled ? "enabled" : "disabled"}`;
|
|
17300
|
-
button.addEventListener("click", () => {
|
|
17301
|
-
state.setDomainEnabled(domain, !domainEnabled(state.simulation, domain));
|
|
17302
|
-
});
|
|
17303
|
-
return button;
|
|
17304
|
-
}));
|
|
18519
|
+
return allowed2;
|
|
17305
18520
|
}
|
|
17306
|
-
function
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17319
|
-
|
|
17320
|
-
|
|
17321
|
-
|
|
17322
|
-
|
|
17323
|
-
button.title = `${capability} ${allowed2 ? "allowed" : "blocked"}`;
|
|
17324
|
-
button.addEventListener("click", () => state.setAclCapability(capability, !allowed2));
|
|
17325
|
-
return button;
|
|
17326
|
-
}));
|
|
18521
|
+
function createHostSignerController(getState, refreshState) {
|
|
18522
|
+
return createPajaSignerController({
|
|
18523
|
+
confirmRequest: (request) => confirmPajaRequest(getState(), request),
|
|
18524
|
+
onChange(signer) {
|
|
18525
|
+
const state = getState();
|
|
18526
|
+
if (!state) return;
|
|
18527
|
+
state.signer = signer;
|
|
18528
|
+
appendPajaMessageLog(state, "paja", {
|
|
18529
|
+
type: `paja.signer.${signer.method}.${signer.status}`,
|
|
18530
|
+
pubkey: signer.pubkey,
|
|
18531
|
+
relay: signer.relay,
|
|
18532
|
+
error: signer.error
|
|
18533
|
+
});
|
|
18534
|
+
refreshState(state);
|
|
18535
|
+
if (signer.status === "connected") state.reload();
|
|
18536
|
+
}
|
|
18537
|
+
});
|
|
17327
18538
|
}
|
|
17328
|
-
function
|
|
17329
|
-
const
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
17337
|
-
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
devButton.id = "signer-dev";
|
|
17342
|
-
devButton.dataset.active = String(state.signer.method === "dev");
|
|
17343
|
-
devButton.textContent = "Dev";
|
|
17344
|
-
devButton.addEventListener("click", () => state.useDevSigner());
|
|
17345
|
-
const nip07Button = document.createElement("button");
|
|
17346
|
-
nip07Button.type = "button";
|
|
17347
|
-
nip07Button.id = "signer-nip07";
|
|
17348
|
-
nip07Button.dataset.active = String(state.signer.method === "nip07");
|
|
17349
|
-
nip07Button.textContent = "NIP-07";
|
|
17350
|
-
nip07Button.addEventListener("click", () => void state.connectNip07());
|
|
17351
|
-
const bunkerInput = document.createElement("input");
|
|
17352
|
-
bunkerInput.id = "signer-bunker-uri";
|
|
17353
|
-
bunkerInput.type = "text";
|
|
17354
|
-
bunkerInput.autocomplete = "off";
|
|
17355
|
-
bunkerInput.placeholder = "bunker://...";
|
|
17356
|
-
bunkerInput.setAttribute("aria-label", "Bunker connection URI");
|
|
17357
|
-
bunkerInput.value = previousInput;
|
|
17358
|
-
const bunkerButton = document.createElement("button");
|
|
17359
|
-
bunkerButton.type = "button";
|
|
17360
|
-
bunkerButton.id = "signer-bunker";
|
|
17361
|
-
bunkerButton.dataset.active = String(state.signer.method === "nip46");
|
|
17362
|
-
bunkerButton.textContent = "Bunker";
|
|
17363
|
-
bunkerButton.addEventListener("click", () => void state.connectBunker(bunkerInput.value));
|
|
17364
|
-
controls.replaceChildren(devButton, nip07Button, bunkerInput, bunkerButton);
|
|
18539
|
+
function hasNip07Signer() {
|
|
18540
|
+
const signer = globalThis.nostr;
|
|
18541
|
+
return typeof signer === "object" && signer !== null;
|
|
18542
|
+
}
|
|
18543
|
+
|
|
18544
|
+
// src/browser-host-runtime.ts
|
|
18545
|
+
function unregisterSingleFrameWindow(bridge, runtime, windowId) {
|
|
18546
|
+
if (!windowId) return;
|
|
18547
|
+
bridge.runtime.destroyWindow(windowId);
|
|
18548
|
+
bridge.runtime.sessionRegistry.unregister(windowId);
|
|
18549
|
+
originRegistry.unregister(windowId);
|
|
18550
|
+
runtime.readyWindowIds.delete(windowId);
|
|
18551
|
+
if (runtime.currentWindowId === windowId) runtime.currentWindowId = null;
|
|
17365
18552
|
}
|
|
17366
18553
|
|
|
17367
18554
|
// src/browser-runtime-tabs.ts
|
|
@@ -17374,6 +18561,9 @@ function resolvedTargetKey(target) {
|
|
|
17374
18561
|
target.aggregateHash
|
|
17375
18562
|
].join(":");
|
|
17376
18563
|
}
|
|
18564
|
+
function runtimeTabGenerationId(tab) {
|
|
18565
|
+
return `${tab.id}:${tab.generation}`;
|
|
18566
|
+
}
|
|
17377
18567
|
function getActiveTab(state) {
|
|
17378
18568
|
return state.tabs.find((tab) => tab.id === state.activeTabId) ?? null;
|
|
17379
18569
|
}
|
|
@@ -17619,11 +18809,13 @@ function resolvedTargetTitle(target) {
|
|
|
17619
18809
|
return title && title.length > 0 ? title : target.dTag || target.aggregateHash.slice(0, 12);
|
|
17620
18810
|
}
|
|
17621
18811
|
function destroyRuntimeTab(tab, context) {
|
|
18812
|
+
context.onTabDestroyed?.(tab);
|
|
17622
18813
|
if (tab.windowId) {
|
|
17623
18814
|
context.bridge.runtime.destroyWindow(tab.windowId);
|
|
17624
18815
|
context.bridge.runtime.sessionRegistry.unregister(tab.windowId);
|
|
17625
18816
|
originRegistry.unregister(tab.windowId);
|
|
17626
18817
|
context.runtime.readyWindowIds.delete(tab.windowId);
|
|
18818
|
+
if (context.runtime.currentWindowId === tab.windowId) context.runtime.currentWindowId = null;
|
|
17627
18819
|
}
|
|
17628
18820
|
tab.frame.remove();
|
|
17629
18821
|
}
|
|
@@ -17637,18 +18829,6 @@ function createRuntimeTabFrame(tab, state, context) {
|
|
|
17637
18829
|
frame.dataset.tabId = tab.id;
|
|
17638
18830
|
frame.dataset.targetUrl = tab.pointerValue;
|
|
17639
18831
|
frame.hidden = true;
|
|
17640
|
-
frame.addEventListener("load", () => {
|
|
17641
|
-
if (tab.status !== "booting" && tab.status !== "reloading") return;
|
|
17642
|
-
tab.windowId = context.registerFrameForGeneration(
|
|
17643
|
-
context.bridge,
|
|
17644
|
-
frame,
|
|
17645
|
-
context.config,
|
|
17646
|
-
tab.generation,
|
|
17647
|
-
tab.resolvedTarget,
|
|
17648
|
-
runtimeTabWindowId(context.config, tab)
|
|
17649
|
-
);
|
|
17650
|
-
if (state.activeTabId === tab.id) context.runtime.currentWindowId = tab.windowId;
|
|
17651
|
-
});
|
|
17652
18832
|
frame.addEventListener("error", () => {
|
|
17653
18833
|
tab.status = "error";
|
|
17654
18834
|
if (state.activeTabId === tab.id) context.setStatus(state, "error");
|
|
@@ -17662,14 +18842,18 @@ function startRuntimeTabNavigation(tab, state, context) {
|
|
|
17662
18842
|
context.setStatus(state, "booting");
|
|
17663
18843
|
renderRuntimeTabs(state);
|
|
17664
18844
|
void context.navigateFrame(
|
|
17665
|
-
context.bridge,
|
|
17666
18845
|
tab.frame,
|
|
17667
18846
|
context.config,
|
|
17668
18847
|
generation,
|
|
17669
|
-
context.
|
|
17670
|
-
context.runtime.currentSimulation,
|
|
18848
|
+
context.adapter,
|
|
17671
18849
|
tab.resolvedTarget,
|
|
17672
|
-
runtimeTabWindowId(context.config, tab)
|
|
18850
|
+
runtimeTabWindowId(context.config, tab),
|
|
18851
|
+
() => tab.generation === generation,
|
|
18852
|
+
(windowId) => {
|
|
18853
|
+
if (tab.generation !== generation) return;
|
|
18854
|
+
tab.windowId = windowId;
|
|
18855
|
+
if (state.activeTabId === tab.id) context.runtime.currentWindowId = windowId;
|
|
18856
|
+
}
|
|
17673
18857
|
).then((windowId) => {
|
|
17674
18858
|
if (tab.generation !== generation) return;
|
|
17675
18859
|
tab.windowId = windowId;
|
|
@@ -17692,13 +18876,6 @@ function runtimeTabWindowId(config, tab) {
|
|
|
17692
18876
|
return `${config.window.id}:${tab.id}:${tab.generation}`;
|
|
17693
18877
|
}
|
|
17694
18878
|
|
|
17695
|
-
// src/parity.ts
|
|
17696
|
-
var PAJA_LEGACY_COMPATIBILITY_DOMAIN = `i${"fc"}`;
|
|
17697
|
-
var PAJA_HANDSHAKE_DOMAINS = ["shell"];
|
|
17698
|
-
var PAJA_COMPATIBILITY_ALIASES = {
|
|
17699
|
-
[PAJA_LEGACY_COMPATIBILITY_DOMAIN]: "inc"
|
|
17700
|
-
};
|
|
17701
|
-
|
|
17702
18879
|
// ../nip/dist/chunk-JT5WCCCX.js
|
|
17703
18880
|
function computeAggregateHash(entries) {
|
|
17704
18881
|
const lines = entries.map((entry) => `${entry.sha256} ${entry.path}
|
|
@@ -17724,7 +18901,7 @@ function aggregateTagValue(tags) {
|
|
|
17724
18901
|
return void 0;
|
|
17725
18902
|
}
|
|
17726
18903
|
|
|
17727
|
-
// ../nip/dist/chunk-
|
|
18904
|
+
// ../nip/dist/chunk-ZQSTY6HR.js
|
|
17728
18905
|
var MIB = 1024 * 1024;
|
|
17729
18906
|
var NAPPLET_ARTIFACT_CACHE_NAME = "kehto:napplet-artifacts:v1";
|
|
17730
18907
|
var SCHEMA_VERSION = 1;
|
|
@@ -18159,10 +19336,43 @@ function archetypesFromTags(tags) {
|
|
|
18159
19336
|
const out = [];
|
|
18160
19337
|
for (const tag of tags) {
|
|
18161
19338
|
if (tag[0] !== "archetype") continue;
|
|
18162
|
-
|
|
18163
|
-
|
|
18164
|
-
|
|
18165
|
-
|
|
19339
|
+
const slug = tag[1];
|
|
19340
|
+
if (typeof slug !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
|
|
19341
|
+
throw new NappletResolutionError(
|
|
19342
|
+
"invalid-manifest",
|
|
19343
|
+
"archetype slug must contain lowercase letters, numbers, and hyphens"
|
|
19344
|
+
);
|
|
19345
|
+
}
|
|
19346
|
+
const convention = tag[2];
|
|
19347
|
+
if (typeof convention !== "string" || convention.length === 0) {
|
|
19348
|
+
throw new NappletResolutionError("invalid-manifest", "archetype convention is required");
|
|
19349
|
+
}
|
|
19350
|
+
if (/^NAP-\d+$/.test(convention)) {
|
|
19351
|
+
throw new NappletResolutionError(
|
|
19352
|
+
"invalid-manifest",
|
|
19353
|
+
"numbered NAP identifier is not an archetype convention"
|
|
19354
|
+
);
|
|
19355
|
+
}
|
|
19356
|
+
const conventionMatch = /^napplet:([^/?#\s]+)\/([^/?#\s]+)$/.exec(convention);
|
|
19357
|
+
if (!conventionMatch) {
|
|
19358
|
+
throw new NappletResolutionError(
|
|
19359
|
+
"invalid-manifest",
|
|
19360
|
+
"archetype convention must be a queryless napplet:<archetype>/<intent> identity"
|
|
19361
|
+
);
|
|
19362
|
+
}
|
|
19363
|
+
if (conventionMatch[1] !== slug) {
|
|
19364
|
+
throw new NappletResolutionError(
|
|
19365
|
+
"invalid-manifest",
|
|
19366
|
+
"archetype slug must match the convention archetype"
|
|
19367
|
+
);
|
|
19368
|
+
}
|
|
19369
|
+
if (tag.length !== 3) {
|
|
19370
|
+
throw new NappletResolutionError(
|
|
19371
|
+
"invalid-manifest",
|
|
19372
|
+
"archetype tags must contain exactly slug and convention"
|
|
19373
|
+
);
|
|
19374
|
+
}
|
|
19375
|
+
out.push({ slug, convention });
|
|
18166
19376
|
}
|
|
18167
19377
|
return out;
|
|
18168
19378
|
}
|
|
@@ -18295,12 +19505,12 @@ async function resolvePajaPointer(value, options = {}) {
|
|
|
18295
19505
|
const pointer = decodePajaPointer(value);
|
|
18296
19506
|
const relays = uniqueRelayUrls([...pointer.relays, ...options.relays ?? []]);
|
|
18297
19507
|
if (relays.length === 0) {
|
|
18298
|
-
throw new Error("Pointer
|
|
19508
|
+
throw new Error("Pointer has no relay hints or enabled configured live relays.");
|
|
18299
19509
|
}
|
|
18300
19510
|
const pool = options.pool ?? new SimplePool();
|
|
18301
19511
|
const ownsPool = options.pool === void 0;
|
|
18302
19512
|
try {
|
|
18303
|
-
const event = await resolvePointerEvent(pointer, pool, relays, options.maxWaitMs
|
|
19513
|
+
const event = await resolvePointerEvent(pointer, pool, relays, normalizeMaxWaitMs(options.maxWaitMs));
|
|
18304
19514
|
const blossomServers = [...options.blossomServers ?? []];
|
|
18305
19515
|
const resolved = await resolveNapplet({
|
|
18306
19516
|
event,
|
|
@@ -18329,7 +19539,26 @@ async function resolvePajaPointer(value, options = {}) {
|
|
|
18329
19539
|
}
|
|
18330
19540
|
}
|
|
18331
19541
|
function injectPajaRuntimeCsp(html, origins) {
|
|
18332
|
-
const
|
|
19542
|
+
const grantedOrigins = [...new Set(origins)].sort();
|
|
19543
|
+
const connectSrc = grantedOrigins.length > 0 ? `connect-src ${grantedOrigins.join(" ")}` : "connect-src 'none'";
|
|
19544
|
+
const value = [
|
|
19545
|
+
"default-src 'none'",
|
|
19546
|
+
"script-src 'unsafe-inline'",
|
|
19547
|
+
"style-src 'unsafe-inline'",
|
|
19548
|
+
"img-src data: blob:",
|
|
19549
|
+
"font-src data:",
|
|
19550
|
+
connectSrc,
|
|
19551
|
+
"worker-src 'none'",
|
|
19552
|
+
"child-src 'none'",
|
|
19553
|
+
"frame-src 'none'",
|
|
19554
|
+
"media-src 'none'",
|
|
19555
|
+
"object-src 'none'",
|
|
19556
|
+
"manifest-src 'none'",
|
|
19557
|
+
"prefetch-src 'none'",
|
|
19558
|
+
"base-uri 'none'",
|
|
19559
|
+
"form-action 'none'",
|
|
19560
|
+
"frame-ancestors 'self'"
|
|
19561
|
+
].join("; ");
|
|
18333
19562
|
const meta = `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(value)}">`;
|
|
18334
19563
|
if (/<head[^>]*>/i.test(html)) {
|
|
18335
19564
|
return html.replace(/<head[^>]*>/i, (open) => `${open}${meta}`);
|
|
@@ -18341,19 +19570,96 @@ function injectPajaRuntimeCsp(html, origins) {
|
|
|
18341
19570
|
}
|
|
18342
19571
|
async function resolvePointerEvent(pointer, pool, relays, maxWaitMs) {
|
|
18343
19572
|
const filter = pointer.type === "naddr" ? addressableFilter(pointer) : eventFilter(pointer);
|
|
18344
|
-
const
|
|
18345
|
-
|
|
18346
|
-
label: "kehto-paja-runtime"
|
|
18347
|
-
});
|
|
18348
|
-
const event = events.filter((candidate) => matchesPointer(pointer, candidate)).sort((a, b) => b.created_at - a.created_at)[0];
|
|
19573
|
+
const result2 = await queryPointerRelays(pool, relays, filter, maxWaitMs);
|
|
19574
|
+
const event = result2.events.filter((candidate) => matchesPointer(pointer, candidate)).sort((a, b) => b.created_at - a.created_at)[0];
|
|
18349
19575
|
if (!event) {
|
|
18350
|
-
|
|
19576
|
+
if (result2.timedOut) {
|
|
19577
|
+
throw new Error(
|
|
19578
|
+
`Relay resolution timed out after ${maxWaitMs}ms for ${pointer.type} pointer across ${relays.length} relays before complete EOSE.`
|
|
19579
|
+
);
|
|
19580
|
+
}
|
|
19581
|
+
const failures = relayFailureReasons(result2.reasons);
|
|
19582
|
+
if (result2.error || failures.length > 0) {
|
|
19583
|
+
const detail = result2.error instanceof Error ? result2.error.message : result2.error === void 0 ? failures.join("; ") : String(result2.error);
|
|
19584
|
+
throw new Error(`Relay query failed for ${pointer.type} pointer before complete EOSE: ${detail}`);
|
|
19585
|
+
}
|
|
19586
|
+
throw new Error(
|
|
19587
|
+
`All ${relays.length} relays reached EOSE without a matching ${pointer.type} napplet manifest event.`
|
|
19588
|
+
);
|
|
18351
19589
|
}
|
|
18352
19590
|
if (!isNappletManifestKind(event.kind)) {
|
|
18353
19591
|
throw new Error(`Pointer resolved kind ${event.kind}; expected ${formatNappletManifestKinds()}.`);
|
|
18354
19592
|
}
|
|
18355
19593
|
return event;
|
|
18356
19594
|
}
|
|
19595
|
+
function queryPointerRelays(pool, relays, filter, maxWaitMs) {
|
|
19596
|
+
if (pool.subscribeManyEose) {
|
|
19597
|
+
return queryPointerRelaySubscription(pool, relays, filter, maxWaitMs);
|
|
19598
|
+
}
|
|
19599
|
+
return queryPointerRelaySync(pool, relays, filter, maxWaitMs);
|
|
19600
|
+
}
|
|
19601
|
+
function queryPointerRelaySubscription(pool, relays, filter, maxWaitMs) {
|
|
19602
|
+
return new Promise((resolve) => {
|
|
19603
|
+
const events = [];
|
|
19604
|
+
let settled = false;
|
|
19605
|
+
let subscription;
|
|
19606
|
+
const finish = (result2) => {
|
|
19607
|
+
if (settled) return;
|
|
19608
|
+
settled = true;
|
|
19609
|
+
clearTimeout(deadline);
|
|
19610
|
+
resolve({ events, ...result2 });
|
|
19611
|
+
};
|
|
19612
|
+
const deadline = setTimeout(() => {
|
|
19613
|
+
finish({ reasons: [], timedOut: true });
|
|
19614
|
+
void subscription?.close("Paja pointer relay deadline exceeded");
|
|
19615
|
+
}, maxWaitMs);
|
|
19616
|
+
try {
|
|
19617
|
+
subscription = pool.subscribeManyEose([...relays], filter, {
|
|
19618
|
+
maxWait: maxWaitMs,
|
|
19619
|
+
label: "kehto-paja-runtime",
|
|
19620
|
+
onevent(event) {
|
|
19621
|
+
events.push(event);
|
|
19622
|
+
},
|
|
19623
|
+
onclose(reasons) {
|
|
19624
|
+
finish({ reasons, timedOut: false });
|
|
19625
|
+
}
|
|
19626
|
+
});
|
|
19627
|
+
} catch (error) {
|
|
19628
|
+
finish({ reasons: [], timedOut: false, error });
|
|
19629
|
+
}
|
|
19630
|
+
});
|
|
19631
|
+
}
|
|
19632
|
+
function queryPointerRelaySync(pool, relays, filter, maxWaitMs) {
|
|
19633
|
+
return new Promise((resolve) => {
|
|
19634
|
+
let settled = false;
|
|
19635
|
+
const finish = (result2) => {
|
|
19636
|
+
if (settled) return;
|
|
19637
|
+
settled = true;
|
|
19638
|
+
clearTimeout(deadline);
|
|
19639
|
+
resolve(result2);
|
|
19640
|
+
};
|
|
19641
|
+
const deadline = setTimeout(() => {
|
|
19642
|
+
finish({ events: [], reasons: [], timedOut: true });
|
|
19643
|
+
}, maxWaitMs);
|
|
19644
|
+
void pool.querySync([...relays], filter, {
|
|
19645
|
+
maxWait: maxWaitMs,
|
|
19646
|
+
label: "kehto-paja-runtime"
|
|
19647
|
+
}).then(
|
|
19648
|
+
(events) => finish({ events, reasons: [], timedOut: false }),
|
|
19649
|
+
(error) => finish({ events: [], reasons: [], timedOut: false, error })
|
|
19650
|
+
);
|
|
19651
|
+
});
|
|
19652
|
+
}
|
|
19653
|
+
function relayFailureReasons(reasons) {
|
|
19654
|
+
return [...new Set(reasons.map((reason) => reason.trim()).filter((reason) => reason.length > 0 && reason !== "closed automatically on eose").map((reason) => reason.slice(0, 160)))].slice(0, 3);
|
|
19655
|
+
}
|
|
19656
|
+
function normalizeMaxWaitMs(value) {
|
|
19657
|
+
if (value === void 0) return 5e3;
|
|
19658
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
19659
|
+
throw new Error("Pointer relay deadline must be a positive number of milliseconds.");
|
|
19660
|
+
}
|
|
19661
|
+
return Math.floor(value);
|
|
19662
|
+
}
|
|
18357
19663
|
function addressableFilter(pointer) {
|
|
18358
19664
|
if (pointer.kind !== NAPPLET_KIND_NAMED) {
|
|
18359
19665
|
throw new Error(`naddr kind ${pointer.kind} is not a named NIP-5D napplet manifest kind.`);
|
|
@@ -18413,7 +19719,192 @@ function formatNappletManifestKinds() {
|
|
|
18413
19719
|
return PAJA_NAPPLET_MANIFEST_KINDS.join(" / ");
|
|
18414
19720
|
}
|
|
18415
19721
|
|
|
19722
|
+
// src/browser-intent-host.ts
|
|
19723
|
+
function createPajaIntentTargetOptions(getState, getContext, effects = {}) {
|
|
19724
|
+
return {
|
|
19725
|
+
async openOrReuse(params) {
|
|
19726
|
+
const state = getState();
|
|
19727
|
+
const context = getContext();
|
|
19728
|
+
if (!state || !context) return null;
|
|
19729
|
+
const record = context.runtime.catalog.get(params.handler);
|
|
19730
|
+
if (!record || !recordSupportsDelivery(record, params)) return null;
|
|
19731
|
+
for (const stale of state.tabs) {
|
|
19732
|
+
if (stale.resolvedTarget.dTag === params.handler && !matchesInstalledNappletRecord(record, stale.resolvedTarget)) closeRuntimeTab(state, context, stale.id);
|
|
19733
|
+
}
|
|
19734
|
+
const current = state.tabs.find(
|
|
19735
|
+
(tab2) => matchesInstalledNappletRecord(record, tab2.resolvedTarget) && isCurrentRuntimeTabGeneration(state, context, tab2)
|
|
19736
|
+
);
|
|
19737
|
+
if (current && params.behavior?.newWindow !== true && params.behavior?.reuse !== false) {
|
|
19738
|
+
return bindPajaIntentGeneration(current, record, context.runtime);
|
|
19739
|
+
}
|
|
19740
|
+
const resolved = await resolvePajaPointer(record.pointer.value, pajaPointerResolverOptions(context));
|
|
19741
|
+
if (resolved.dTag !== record.dTag || resolved.aggregateHash !== record.aggregateHash || !resolvedSupportsDelivery(resolved, params)) return null;
|
|
19742
|
+
const currentRecord = context.runtime.catalog.validateCurrent(record, resolved);
|
|
19743
|
+
if (!currentRecord) return null;
|
|
19744
|
+
const tab = addRuntimeTab(state, context, currentRecord.pointer.value, resolved);
|
|
19745
|
+
effects.persistTabs?.(state);
|
|
19746
|
+
return bindPajaIntentGeneration(tab, currentRecord, context.runtime);
|
|
19747
|
+
},
|
|
19748
|
+
waitForReady(generation) {
|
|
19749
|
+
const state = getState();
|
|
19750
|
+
const context = getContext();
|
|
19751
|
+
if (!state || !context) return Promise.reject(new Error("Paja host is not available"));
|
|
19752
|
+
const tab = findRuntimeTabGeneration(state, generation);
|
|
19753
|
+
if (!tab || !isCurrentPajaIntentGeneration(generation, state, context, tab)) {
|
|
19754
|
+
invalidatePajaIntentGeneration(generation, context.runtime);
|
|
19755
|
+
return Promise.reject(new Error("intent target generation is not current"));
|
|
19756
|
+
}
|
|
19757
|
+
if (tab.windowId && context.runtime.readyWindowIds.has(tab.windowId)) return void 0;
|
|
19758
|
+
return new Promise((resolve, reject2) => {
|
|
19759
|
+
const waiters = context.runtime.readyWaiters.get(generation) ?? /* @__PURE__ */ new Set();
|
|
19760
|
+
waiters.add({ resolve, reject: reject2 });
|
|
19761
|
+
context.runtime.readyWaiters.set(generation, waiters);
|
|
19762
|
+
});
|
|
19763
|
+
},
|
|
19764
|
+
isCurrent(generation) {
|
|
19765
|
+
const state = getState();
|
|
19766
|
+
const context = getContext();
|
|
19767
|
+
const tab = state && context ? findRuntimeTabGeneration(state, generation) : null;
|
|
19768
|
+
if (!tab || !state || !context || !isCurrentPajaIntentGeneration(generation, state, context, tab)) {
|
|
19769
|
+
if (context) invalidatePajaIntentGeneration(generation, context.runtime);
|
|
19770
|
+
return false;
|
|
19771
|
+
}
|
|
19772
|
+
return true;
|
|
19773
|
+
},
|
|
19774
|
+
getWindowId(generation) {
|
|
19775
|
+
const state = getState();
|
|
19776
|
+
const tab = state ? findRuntimeTabGeneration(state, generation) : null;
|
|
19777
|
+
return tab?.windowId ?? null;
|
|
19778
|
+
},
|
|
19779
|
+
send(generation, params) {
|
|
19780
|
+
const state = getState();
|
|
19781
|
+
const context = getContext();
|
|
19782
|
+
const tab = state && context ? findRuntimeTabGeneration(state, generation) : null;
|
|
19783
|
+
if (!state || !context || !tab || !tab.windowId || !isCurrentPajaIntentGeneration(generation, state, context, tab)) {
|
|
19784
|
+
if (context) invalidatePajaIntentGeneration(generation, context.runtime);
|
|
19785
|
+
throw new Error("intent target generation is not current and ready");
|
|
19786
|
+
}
|
|
19787
|
+
const source = tab.frame.contentWindow;
|
|
19788
|
+
if (!source || originRegistry.getWindowId(source) !== tab.windowId) {
|
|
19789
|
+
throw new Error("intent target source is no longer registered");
|
|
19790
|
+
}
|
|
19791
|
+
createPajaPostMessageProxy(source, state, tab.windowId).postMessage({
|
|
19792
|
+
type: "inc.event",
|
|
19793
|
+
topic: params.convention,
|
|
19794
|
+
sender: params.sender,
|
|
19795
|
+
...params.payload === void 0 ? {} : { payload: params.payload }
|
|
19796
|
+
}, "*");
|
|
19797
|
+
}
|
|
19798
|
+
};
|
|
19799
|
+
}
|
|
19800
|
+
function pajaPointerResolverOptions(context) {
|
|
19801
|
+
return {
|
|
19802
|
+
relays: [
|
|
19803
|
+
...context.config.target.pointer?.relays ?? [],
|
|
19804
|
+
...getPajaRelayUrls(context.runtime.currentSimulation)
|
|
19805
|
+
],
|
|
19806
|
+
blossomServers: context.config.target.pointer?.blossomServers ?? [],
|
|
19807
|
+
maxWaitMs: context.config.target.pointer?.maxWaitMs
|
|
19808
|
+
};
|
|
19809
|
+
}
|
|
19810
|
+
function recordSupportsDelivery(record, params) {
|
|
19811
|
+
return record.archetypes.some(
|
|
19812
|
+
(archetype) => archetype.slug === params.archetype && archetype.convention === params.convention
|
|
19813
|
+
);
|
|
19814
|
+
}
|
|
19815
|
+
function resolvedSupportsDelivery(resolved, params) {
|
|
19816
|
+
return resolved.manifest.archetypes.some(
|
|
19817
|
+
(archetype) => archetype.slug === params.archetype && archetype.convention === params.convention
|
|
19818
|
+
);
|
|
19819
|
+
}
|
|
19820
|
+
function findRuntimeTabGeneration(state, generation) {
|
|
19821
|
+
return state.tabs.find((tab) => runtimeTabGenerationId(tab) === generation.id) ?? null;
|
|
19822
|
+
}
|
|
19823
|
+
function bindPajaIntentGeneration(tab, record, runtime) {
|
|
19824
|
+
const generation = { id: runtimeTabGenerationId(tab) };
|
|
19825
|
+
runtime.intentRecords.set(generation, record);
|
|
19826
|
+
return generation;
|
|
19827
|
+
}
|
|
19828
|
+
function isCurrentPajaIntentGeneration(generation, state, context, tab) {
|
|
19829
|
+
const record = context.runtime.intentRecords.get(generation);
|
|
19830
|
+
return record !== void 0 && context.runtime.catalog.validateCurrent(record, tab.resolvedTarget) !== null && isCurrentRuntimeTabGeneration(state, context, tab);
|
|
19831
|
+
}
|
|
19832
|
+
function invalidatePajaIntentGeneration(generation, runtime) {
|
|
19833
|
+
const waiters = runtime.readyWaiters.get(generation);
|
|
19834
|
+
if (!waiters) return;
|
|
19835
|
+
runtime.readyWaiters.delete(generation);
|
|
19836
|
+
for (const waiter of waiters) waiter.reject(new Error("intent target catalog record was replaced"));
|
|
19837
|
+
}
|
|
19838
|
+
function subscribePajaIntentCatalogChanges(state, context) {
|
|
19839
|
+
return context.runtime.catalog.onChanged((dTag) => {
|
|
19840
|
+
for (const [generation] of context.runtime.readyWaiters) {
|
|
19841
|
+
const record = context.runtime.intentRecords.get(generation);
|
|
19842
|
+
if (!record || record.dTag !== dTag) continue;
|
|
19843
|
+
const tab = findRuntimeTabGeneration(state, generation);
|
|
19844
|
+
if (!tab || !isCurrentPajaIntentGeneration(generation, state, context, tab)) {
|
|
19845
|
+
invalidatePajaIntentGeneration(generation, context.runtime);
|
|
19846
|
+
}
|
|
19847
|
+
}
|
|
19848
|
+
});
|
|
19849
|
+
}
|
|
19850
|
+
function isCurrentRuntimeTabGeneration(state, context, tab) {
|
|
19851
|
+
const source = tab.frame.contentWindow;
|
|
19852
|
+
return state.tabs.includes(tab) && tab.windowId !== null && source !== null && originRegistry.getWindowId(source) === tab.windowId && runtimeTabGenerationId(tab) === `${tab.id}:${tab.generation}`;
|
|
19853
|
+
}
|
|
19854
|
+
function markRuntimeTabReady(state, context, tab, source, registeredWindowId, effects = {}) {
|
|
19855
|
+
if (!tab.windowId || tab.frame.contentWindow !== source || registeredWindowId !== tab.windowId || !isCurrentRuntimeTabGeneration(state, context, tab)) return false;
|
|
19856
|
+
context.runtime.readyWindowIds.add(tab.windowId);
|
|
19857
|
+
for (const [generation, waiters] of context.runtime.readyWaiters) {
|
|
19858
|
+
if (findRuntimeTabGeneration(state, generation) !== tab) continue;
|
|
19859
|
+
context.runtime.readyWaiters.delete(generation);
|
|
19860
|
+
if (!isCurrentPajaIntentGeneration(generation, state, context, tab)) {
|
|
19861
|
+
for (const waiter of waiters) waiter.reject(new Error("intent target catalog record was replaced"));
|
|
19862
|
+
continue;
|
|
19863
|
+
}
|
|
19864
|
+
for (const waiter of waiters) waiter.resolve();
|
|
19865
|
+
}
|
|
19866
|
+
tab.status = "ready";
|
|
19867
|
+
if (state.activeTabId === tab.id) {
|
|
19868
|
+
if (effects.setReadyStatus) effects.setReadyStatus(state);
|
|
19869
|
+
else state.status = "ready";
|
|
19870
|
+
}
|
|
19871
|
+
renderRuntimeTabs(state);
|
|
19872
|
+
return true;
|
|
19873
|
+
}
|
|
19874
|
+
function clearRuntimeTabGeneration(tab, runtime) {
|
|
19875
|
+
for (const [generation, waiters] of runtime.readyWaiters) {
|
|
19876
|
+
if (generation.id !== runtimeTabGenerationId(tab)) continue;
|
|
19877
|
+
runtime.readyWaiters.delete(generation);
|
|
19878
|
+
for (const waiter of waiters) waiter.reject(new Error("intent target generation replaced"));
|
|
19879
|
+
}
|
|
19880
|
+
}
|
|
19881
|
+
|
|
19882
|
+
// src/theme-broadcast.ts
|
|
19883
|
+
function createPajaThemeBroadcastLink() {
|
|
19884
|
+
let attachedBridge;
|
|
19885
|
+
return {
|
|
19886
|
+
attach(bridge) {
|
|
19887
|
+
if (attachedBridge) {
|
|
19888
|
+
throw new Error("Paja theme broadcast link is already attached.");
|
|
19889
|
+
}
|
|
19890
|
+
attachedBridge = bridge;
|
|
19891
|
+
},
|
|
19892
|
+
onBroadcast(envelope) {
|
|
19893
|
+
if (!attachedBridge) {
|
|
19894
|
+
throw new Error("Paja theme broadcast occurred before a ShellBridge is attached.");
|
|
19895
|
+
}
|
|
19896
|
+
attachedBridge.publishTheme(envelope.theme);
|
|
19897
|
+
}
|
|
19898
|
+
};
|
|
19899
|
+
}
|
|
19900
|
+
|
|
18416
19901
|
// src/browser-target-frame.ts
|
|
19902
|
+
function resolvePajaFrameEnvironment(hooks, identity) {
|
|
19903
|
+
if (!identity.dTag || !identity.aggregateHash) {
|
|
19904
|
+
throw new Error("Paja frame identity requires a dTag and aggregate hash");
|
|
19905
|
+
}
|
|
19906
|
+
return resolveShellEnvironment(hooks, identity);
|
|
19907
|
+
}
|
|
18417
19908
|
function getTargetIdentity2(config, resolvedTarget) {
|
|
18418
19909
|
return {
|
|
18419
19910
|
pubkey: "",
|
|
@@ -18421,18 +19912,21 @@ function getTargetIdentity2(config, resolvedTarget) {
|
|
|
18421
19912
|
aggregateHash: resolvedTarget?.aggregateHash ?? config.window.aggregateHash
|
|
18422
19913
|
};
|
|
18423
19914
|
}
|
|
18424
|
-
function
|
|
19915
|
+
function getTargetOriginIdentity(config, resolvedTarget) {
|
|
19916
|
+
const target = getTargetIdentity2(config, resolvedTarget);
|
|
19917
|
+
return Object.freeze({ dTag: target.dTag, aggregateHash: target.aggregateHash });
|
|
19918
|
+
}
|
|
19919
|
+
function registerFrameForGeneration(frame, config, generation, identity, environment, windowId = `${config.window.id}:${generation}`) {
|
|
18425
19920
|
const win = frame.contentWindow;
|
|
18426
19921
|
if (!win) return null;
|
|
18427
|
-
|
|
18428
|
-
originRegistry.
|
|
18429
|
-
dTag: identity.dTag,
|
|
18430
|
-
aggregateHash: identity.aggregateHash
|
|
18431
|
-
});
|
|
19922
|
+
originRegistry.register(win, windowId, identity);
|
|
19923
|
+
originRegistry.setEnvironment(win, environment);
|
|
18432
19924
|
return windowId;
|
|
18433
19925
|
}
|
|
18434
|
-
async function navigateFrame(
|
|
18435
|
-
const
|
|
19926
|
+
async function navigateFrame(frame, config, generation, adapter, resolvedTarget, windowId, isCurrent, onRegistered) {
|
|
19927
|
+
const identity = getTargetOriginIdentity(config, resolvedTarget);
|
|
19928
|
+
const environment = resolvePajaFrameEnvironment(adapter, identity);
|
|
19929
|
+
const domains = environment.capabilities.domains;
|
|
18436
19930
|
if (config.target.mode === "runtime-pointer") {
|
|
18437
19931
|
if (!resolvedTarget) {
|
|
18438
19932
|
frame.removeAttribute("src");
|
|
@@ -18440,7 +19934,8 @@ async function navigateFrame(bridge, frame, config, generation, capabilities, si
|
|
|
18440
19934
|
return null;
|
|
18441
19935
|
}
|
|
18442
19936
|
if (isCurrent && !isCurrent()) return null;
|
|
18443
|
-
const registeredWindowId2 = registerFrameForGeneration(
|
|
19937
|
+
const registeredWindowId2 = registerFrameForGeneration(frame, config, generation, identity, environment, windowId);
|
|
19938
|
+
onRegistered?.(registeredWindowId2);
|
|
18444
19939
|
frame.removeAttribute("src");
|
|
18445
19940
|
frame.srcdoc = injectNappletNamespacePrelude(
|
|
18446
19941
|
injectPajaRuntimeCsp(
|
|
@@ -18453,7 +19948,8 @@ async function navigateFrame(bridge, frame, config, generation, capabilities, si
|
|
|
18453
19948
|
}
|
|
18454
19949
|
const html = await fetchTargetHtml();
|
|
18455
19950
|
if (isCurrent && !isCurrent()) return null;
|
|
18456
|
-
const registeredWindowId = registerFrameForGeneration(
|
|
19951
|
+
const registeredWindowId = registerFrameForGeneration(frame, config, generation, identity, environment, windowId);
|
|
19952
|
+
onRegistered?.(registeredWindowId);
|
|
18457
19953
|
frame.removeAttribute("src");
|
|
18458
19954
|
frame.srcdoc = injectNappletNamespacePrelude(
|
|
18459
19955
|
injectBaseHref(html, config.target.url),
|
|
@@ -18480,21 +19976,6 @@ function connectOrigins(urls) {
|
|
|
18480
19976
|
}
|
|
18481
19977
|
return [...out];
|
|
18482
19978
|
}
|
|
18483
|
-
function getInjectedDomains(capabilities, simulation, resolvedTarget) {
|
|
18484
|
-
const available = capabilities.domains.filter((domain) => {
|
|
18485
|
-
const knownDomain = domain;
|
|
18486
|
-
return simulation.capabilities.domains[knownDomain] !== false;
|
|
18487
|
-
});
|
|
18488
|
-
const required = resolvedTarget?.manifest.requires ?? [];
|
|
18489
|
-
const availableSet = new Set(available);
|
|
18490
|
-
const optionalDomains = required.length === 0 ? available : required.filter((domain) => availableSet.has(domain));
|
|
18491
|
-
return [
|
|
18492
|
-
...PAJA_HANDSHAKE_DOMAINS,
|
|
18493
|
-
...optionalDomains.filter(
|
|
18494
|
-
(domain) => !PAJA_HANDSHAKE_DOMAINS.some((handshakeDomain) => handshakeDomain === domain)
|
|
18495
|
-
)
|
|
18496
|
-
];
|
|
18497
|
-
}
|
|
18498
19979
|
async function fetchTargetHtml() {
|
|
18499
19980
|
const response = await fetch(new URL("./__kehto/target.html", window.location.href), {
|
|
18500
19981
|
cache: "no-store",
|
|
@@ -18524,6 +20005,30 @@ function escapeHtml(value) {
|
|
|
18524
20005
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
18525
20006
|
}
|
|
18526
20007
|
|
|
20008
|
+
// src/browser-target-diagnostics.ts
|
|
20009
|
+
async function reportTargetCorsDiagnostic(state) {
|
|
20010
|
+
let diagnostic;
|
|
20011
|
+
try {
|
|
20012
|
+
const response = await fetch(new URL("./__kehto/target-cors.json", window.location.href), {
|
|
20013
|
+
cache: "no-store"
|
|
20014
|
+
});
|
|
20015
|
+
if (!response.ok) return;
|
|
20016
|
+
diagnostic = await response.json();
|
|
20017
|
+
} catch {
|
|
20018
|
+
return;
|
|
20019
|
+
}
|
|
20020
|
+
if (diagnostic.status === "allowed") return;
|
|
20021
|
+
appendPajaMessageLog(state, "paja", {
|
|
20022
|
+
type: "paja.target.cors.error",
|
|
20023
|
+
status: diagnostic.status,
|
|
20024
|
+
targetUrl: diagnostic.targetUrl,
|
|
20025
|
+
allowOrigin: diagnostic.allowOrigin,
|
|
20026
|
+
message: `${diagnostic.detail} ${diagnostic.hint ?? ""}`.trim()
|
|
20027
|
+
});
|
|
20028
|
+
console.warn(`[paja] ${diagnostic.detail}
|
|
20029
|
+
[paja] ${diagnostic.hint ?? ""}`.trimEnd());
|
|
20030
|
+
}
|
|
20031
|
+
|
|
18527
20032
|
// src/browser-host.ts
|
|
18528
20033
|
var bridgeRef = null;
|
|
18529
20034
|
function readConfig() {
|
|
@@ -18621,68 +20126,22 @@ function getFrame() {
|
|
|
18621
20126
|
frame.sandbox.remove("allow-same-origin");
|
|
18622
20127
|
return frame;
|
|
18623
20128
|
}
|
|
18624
|
-
function confirmPajaRequest(state, request) {
|
|
18625
|
-
if (request.action === "upload") {
|
|
18626
|
-
const filename = request.filename ?? "(unnamed blob)";
|
|
18627
|
-
const mimeType = request.mimeType ?? "application/octet-stream";
|
|
18628
|
-
const allowed3 = window.confirm([
|
|
18629
|
-
"Paja upload request",
|
|
18630
|
-
`napplet: ${request.napplet.dTag} (${request.windowId})`,
|
|
18631
|
-
`file: ${filename}`,
|
|
18632
|
-
`size: ${request.size} bytes`,
|
|
18633
|
-
`type: ${mimeType}`,
|
|
18634
|
-
`server: ${request.server}`,
|
|
18635
|
-
request.warning
|
|
18636
|
-
].join("\n"));
|
|
18637
|
-
appendPajaMessageLog(state, "paja", {
|
|
18638
|
-
type: `paja.upload.${allowed3 ? "confirmed" : "denied"}`,
|
|
18639
|
-
windowId: request.windowId,
|
|
18640
|
-
dTag: request.napplet.dTag,
|
|
18641
|
-
aggregateHash: request.napplet.aggregateHash,
|
|
18642
|
-
filename,
|
|
18643
|
-
size: request.size,
|
|
18644
|
-
mimeType,
|
|
18645
|
-
server: request.server,
|
|
18646
|
-
warning: request.warning
|
|
18647
|
-
});
|
|
18648
|
-
return allowed3;
|
|
18649
|
-
}
|
|
18650
|
-
const event = request.event;
|
|
18651
|
-
const kind = typeof event.kind === "number" ? event.kind : "unknown";
|
|
18652
|
-
const content = typeof event.content === "string" && event.content.length > 0 ? `
|
|
18653
|
-
|
|
18654
|
-
${event.content.slice(0, 240)}` : "";
|
|
18655
|
-
const allowed2 = window.confirm(`Paja ${request.action} request
|
|
18656
|
-
kind: ${kind}${content}`);
|
|
18657
|
-
appendPajaMessageLog(state, "paja", {
|
|
18658
|
-
type: `paja.${request.action}.${allowed2 ? "confirmed" : "denied"}`,
|
|
18659
|
-
kind
|
|
18660
|
-
});
|
|
18661
|
-
return allowed2;
|
|
18662
|
-
}
|
|
18663
|
-
function unregisterSingleFrameWindow(bridge, runtime, windowId) {
|
|
18664
|
-
if (!windowId) return;
|
|
18665
|
-
bridge.runtime.destroyWindow(windowId);
|
|
18666
|
-
bridge.runtime.sessionRegistry.unregister(windowId);
|
|
18667
|
-
originRegistry.unregister(windowId);
|
|
18668
|
-
runtime.readyWindowIds.delete(windowId);
|
|
18669
|
-
if (runtime.currentWindowId === windowId) runtime.currentWindowId = null;
|
|
18670
|
-
}
|
|
18671
20129
|
function startFrameNavigation(state, context) {
|
|
18672
|
-
const { config, frame, bridge,
|
|
20130
|
+
const { config, frame, bridge, adapter, runtime } = context;
|
|
18673
20131
|
if (!frame) return;
|
|
18674
20132
|
const generation = state.generation;
|
|
18675
20133
|
const isCurrentGeneration = () => state.generation === generation;
|
|
18676
20134
|
void navigateFrame(
|
|
18677
|
-
bridge,
|
|
18678
20135
|
frame,
|
|
18679
20136
|
config,
|
|
18680
20137
|
generation,
|
|
18681
|
-
|
|
18682
|
-
runtime.currentSimulation,
|
|
20138
|
+
adapter,
|
|
18683
20139
|
state.resolvedTarget,
|
|
18684
20140
|
void 0,
|
|
18685
|
-
isCurrentGeneration
|
|
20141
|
+
isCurrentGeneration,
|
|
20142
|
+
(windowId) => {
|
|
20143
|
+
if (isCurrentGeneration()) runtime.currentWindowId = windowId;
|
|
20144
|
+
}
|
|
18686
20145
|
).then((windowId) => {
|
|
18687
20146
|
if (!isCurrentGeneration()) {
|
|
18688
20147
|
unregisterSingleFrameWindow(bridge, runtime, windowId);
|
|
@@ -18701,28 +20160,6 @@ function startFrameNavigation(state, context) {
|
|
|
18701
20160
|
console.error(error);
|
|
18702
20161
|
});
|
|
18703
20162
|
}
|
|
18704
|
-
function createHostSignerController(getState) {
|
|
18705
|
-
return createPajaSignerController({
|
|
18706
|
-
confirmRequest: (request) => confirmPajaRequest(getState(), request),
|
|
18707
|
-
onChange(signer) {
|
|
18708
|
-
const state = getState();
|
|
18709
|
-
if (!state) return;
|
|
18710
|
-
state.signer = signer;
|
|
18711
|
-
appendPajaMessageLog(state, "paja", {
|
|
18712
|
-
type: `paja.signer.${signer.method}.${signer.status}`,
|
|
18713
|
-
pubkey: signer.pubkey,
|
|
18714
|
-
relay: signer.relay,
|
|
18715
|
-
error: signer.error
|
|
18716
|
-
});
|
|
18717
|
-
setSimulationStatus(state);
|
|
18718
|
-
if (signer.status === "connected") state.reload();
|
|
18719
|
-
}
|
|
18720
|
-
});
|
|
18721
|
-
}
|
|
18722
|
-
function hasNip07Signer() {
|
|
18723
|
-
const signer = globalThis.nostr;
|
|
18724
|
-
return typeof signer === "object" && signer !== null;
|
|
18725
|
-
}
|
|
18726
20163
|
function installPajaControlListeners(state) {
|
|
18727
20164
|
document.getElementById("reload-target")?.addEventListener("click", () => {
|
|
18728
20165
|
state.reload();
|
|
@@ -18784,7 +20221,7 @@ function setRuntimeDomainEnabled(state, context, domain, enabled) {
|
|
|
18784
20221
|
state.reload();
|
|
18785
20222
|
}
|
|
18786
20223
|
async function loadRuntimePointer(state, context, value, options = {}) {
|
|
18787
|
-
const { config } = context;
|
|
20224
|
+
const { config, runtime } = context;
|
|
18788
20225
|
if (config.target.mode !== "runtime-pointer") return;
|
|
18789
20226
|
const pointer = value.trim();
|
|
18790
20227
|
const input = document.getElementById("runtime-pointer-input");
|
|
@@ -18798,11 +20235,8 @@ async function loadRuntimePointer(state, context, value, options = {}) {
|
|
|
18798
20235
|
setStatus(state, "booting");
|
|
18799
20236
|
appendPajaMessageLog(state, "paja", { type: "paja.pointer.resolve", pointer });
|
|
18800
20237
|
try {
|
|
18801
|
-
const resolvedTarget = await resolvePajaPointer(pointer,
|
|
18802
|
-
|
|
18803
|
-
blossomServers: config.target.pointer?.blossomServers ?? [],
|
|
18804
|
-
maxWaitMs: config.target.pointer?.maxWaitMs
|
|
18805
|
-
});
|
|
20238
|
+
const resolvedTarget = await resolvePajaPointer(pointer, pajaPointerResolverOptions(context));
|
|
20239
|
+
runtime.catalog.install(resolvedTarget);
|
|
18806
20240
|
const pointerStatus = `${resolvedTarget.dTag}:${resolvedTarget.aggregateHash.slice(0, 12)}`;
|
|
18807
20241
|
setPointerStatus(state, pointerStatus);
|
|
18808
20242
|
appendPajaMessageLog(state, "paja", {
|
|
@@ -18947,16 +20381,30 @@ async function installPajaHost() {
|
|
|
18947
20381
|
const runtime = {
|
|
18948
20382
|
currentSimulation: config.simulation,
|
|
18949
20383
|
themeService: null,
|
|
20384
|
+
catalog: new InstalledNappletCatalog(),
|
|
20385
|
+
readyWaiters: /* @__PURE__ */ new Map(),
|
|
20386
|
+
intentRecords: /* @__PURE__ */ new WeakMap(),
|
|
18950
20387
|
currentWindowId: null,
|
|
18951
20388
|
readyWindowIds: /* @__PURE__ */ new Set()
|
|
18952
20389
|
};
|
|
18953
20390
|
const getSimulation = () => runtime.currentSimulation;
|
|
20391
|
+
const themeBroadcast = createPajaThemeBroadcastLink();
|
|
18954
20392
|
let stateRef = null;
|
|
18955
|
-
|
|
20393
|
+
let contextRef = null;
|
|
20394
|
+
const intentController = new BrowserIntentController(createPajaIntentTargetOptions(
|
|
20395
|
+
() => stateRef,
|
|
20396
|
+
() => contextRef,
|
|
20397
|
+
{ persistTabs: persistRuntimeTabs }
|
|
20398
|
+
));
|
|
20399
|
+
const signerController = createHostSignerController(() => stateRef, setSimulationStatus);
|
|
18956
20400
|
const adapter = createPajaAdapter(config, getSimulation, (theme) => {
|
|
18957
20401
|
runtime.themeService = theme;
|
|
18958
|
-
}, (request) => confirmPajaRequest(stateRef, request), signerController, () => getTargetIdentity2(config, stateRef?.resolvedTarget))
|
|
20402
|
+
}, themeBroadcast.onBroadcast, (request) => confirmPajaRequest(stateRef, request), signerController, () => getTargetIdentity2(config, stateRef?.resolvedTarget), () => stateRef?.reload(), {
|
|
20403
|
+
catalog: runtime.catalog,
|
|
20404
|
+
controller: intentController
|
|
20405
|
+
});
|
|
18959
20406
|
const bridge = createShellBridge(adapter);
|
|
20407
|
+
themeBroadcast.attach(bridge);
|
|
18960
20408
|
bridgeRef = bridge;
|
|
18961
20409
|
installPajaOriginRegistryProxy(originRegistry, () => stateRef);
|
|
18962
20410
|
const capabilities = buildShellCapabilities(adapter);
|
|
@@ -18970,21 +20418,25 @@ async function installPajaHost() {
|
|
|
18970
20418
|
capabilities,
|
|
18971
20419
|
runtime,
|
|
18972
20420
|
navigateFrame,
|
|
18973
|
-
registerFrameForGeneration,
|
|
18974
20421
|
renderTargetErrorHtml,
|
|
20422
|
+
onTabDestroyed: (tab) => clearRuntimeTabGeneration(tab, runtime),
|
|
18975
20423
|
setPointerStatus: (state2, message) => setPointerStatus(state2, message),
|
|
18976
20424
|
setStatus: (state2, status) => setStatus(state2, status)
|
|
18977
20425
|
};
|
|
20426
|
+
contextRef = context;
|
|
18978
20427
|
const state = createPajaBrowserState(context);
|
|
18979
20428
|
stateRef = state;
|
|
20429
|
+
const stopIntentCatalogChanges = subscribePajaIntentCatalogChanges(state, context);
|
|
20430
|
+
window.addEventListener("pagehide", stopIntentCatalogChanges, { once: true });
|
|
18980
20431
|
window.__KEHTO_PAJA__ = state;
|
|
18981
20432
|
window.addEventListener("message", (event) => {
|
|
18982
20433
|
const source = event.source;
|
|
18983
20434
|
const sourceTab = source ? state.tabs.find((tab) => tab.frame.contentWindow === source) ?? null : null;
|
|
18984
20435
|
const isSingleFrameMessage = frame ? event.source === frame.contentWindow : false;
|
|
18985
20436
|
if (!sourceTab && !isSingleFrameMessage) return;
|
|
18986
|
-
const registeredWindowId = source ? originRegistry.getWindowId(source) : null;
|
|
20437
|
+
const registeredWindowId = source ? originRegistry.getWindowId(source) ?? null : null;
|
|
18987
20438
|
const sourceWindowId = sourceTab?.windowId ?? registeredWindowId ?? void 0;
|
|
20439
|
+
if (sourceTab && (!source || !sourceWindowId || registeredWindowId !== sourceWindowId)) return;
|
|
18988
20440
|
if (isSingleFrameMessage && (!sourceWindowId || sourceWindowId !== runtime.currentWindowId)) return;
|
|
18989
20441
|
appendPajaMessageLog(state, "napplet->shell", event.data, sourceWindowId);
|
|
18990
20442
|
const proxiedSource = createPajaPostMessageProxy(event.source, state, sourceWindowId);
|
|
@@ -18998,22 +20450,21 @@ async function installPajaHost() {
|
|
|
18998
20450
|
bridge.handleMessage(syntheticEvent);
|
|
18999
20451
|
const data = event.data;
|
|
19000
20452
|
if (data && typeof data === "object" && data.type === "shell.ready") {
|
|
19001
|
-
if (sourceWindowId) runtime.readyWindowIds.add(sourceWindowId);
|
|
19002
20453
|
if (sourceTab) {
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
20454
|
+
if (source && !markRuntimeTabReady(
|
|
20455
|
+
state,
|
|
20456
|
+
context,
|
|
20457
|
+
sourceTab,
|
|
20458
|
+
source,
|
|
20459
|
+
registeredWindowId,
|
|
20460
|
+
{ setReadyStatus: (current) => setStatus(current, "ready") }
|
|
20461
|
+
)) return;
|
|
19006
20462
|
} else {
|
|
20463
|
+
if (sourceWindowId) runtime.readyWindowIds.add(sourceWindowId);
|
|
19007
20464
|
setStatus(state, "ready");
|
|
19008
20465
|
}
|
|
19009
20466
|
}
|
|
19010
20467
|
});
|
|
19011
|
-
frame?.addEventListener("load", () => {
|
|
19012
|
-
if (!frame) return;
|
|
19013
|
-
if (state.status === "booting" || state.status === "reloading") {
|
|
19014
|
-
runtime.currentWindowId = registerFrameForGeneration(bridge, frame, config, state.generation, state.resolvedTarget);
|
|
19015
|
-
}
|
|
19016
|
-
});
|
|
19017
20468
|
frame?.addEventListener("error", () => {
|
|
19018
20469
|
setStatus(state, "error");
|
|
19019
20470
|
});
|
|
@@ -19034,20 +20485,28 @@ async function installPajaHost() {
|
|
|
19034
20485
|
}
|
|
19035
20486
|
} else {
|
|
19036
20487
|
startFrameNavigation(state, context);
|
|
20488
|
+
void reportTargetCorsDiagnostic(state);
|
|
19037
20489
|
}
|
|
19038
20490
|
if (hasNip07Signer()) void state.connectNip07();
|
|
19039
20491
|
}
|
|
19040
|
-
|
|
19041
|
-
|
|
20492
|
+
if (typeof document !== "undefined") {
|
|
20493
|
+
try {
|
|
20494
|
+
void installPajaHost().catch((error) => {
|
|
20495
|
+
const statusEl = document.getElementById("lifecycle-status");
|
|
20496
|
+
if (statusEl) statusEl.textContent = "error";
|
|
20497
|
+
console.error(error);
|
|
20498
|
+
});
|
|
20499
|
+
} catch (error) {
|
|
19042
20500
|
const statusEl = document.getElementById("lifecycle-status");
|
|
19043
20501
|
if (statusEl) statusEl.textContent = "error";
|
|
19044
20502
|
console.error(error);
|
|
19045
|
-
}
|
|
19046
|
-
} catch (error) {
|
|
19047
|
-
const statusEl = document.getElementById("lifecycle-status");
|
|
19048
|
-
if (statusEl) statusEl.textContent = "error";
|
|
19049
|
-
console.error(error);
|
|
20503
|
+
}
|
|
19050
20504
|
}
|
|
20505
|
+
export {
|
|
20506
|
+
createPajaIntentTargetOptions,
|
|
20507
|
+
markRuntimeTabReady,
|
|
20508
|
+
subscribePajaIntentCatalogChanges
|
|
20509
|
+
};
|
|
19051
20510
|
/*! Bundled license information:
|
|
19052
20511
|
|
|
19053
20512
|
@noble/hashes/utils.js:
|