@kehto/paja 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -2
- package/dist/browser-host.js +2529 -1038
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-NFJCS5PG.js → chunk-OWVINAW7.js} +369 -10
- package/dist/chunk-OWVINAW7.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +199 -5
- package/dist/index.js +11 -1
- package/package.json +10 -10
- 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 === "deliver" || 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.29.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,19 +4862,85 @@ function nappletNamespacePrelude(domains) {
|
|
|
4298
4862
|
};
|
|
4299
4863
|
}
|
|
4300
4864
|
function makeIntent() {
|
|
4301
|
-
const
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4865
|
+
const deliveryHandlers = /* @__PURE__ */ new Set();
|
|
4866
|
+
const pendingDeliveries = [];
|
|
4867
|
+
const hasOwn2 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
4868
|
+
const normalizeDelivery = (value) => {
|
|
4869
|
+
if (!value || typeof value !== "object" || typeof value.sender !== "string" || typeof value.archetype !== "string" || typeof value.action !== "string" || typeof value.convention !== "string") {
|
|
4870
|
+
return void 0;
|
|
4871
|
+
}
|
|
4872
|
+
const delivery = value;
|
|
4873
|
+
return {
|
|
4874
|
+
sender: delivery.sender,
|
|
4875
|
+
archetype: delivery.archetype,
|
|
4876
|
+
action: delivery.action,
|
|
4877
|
+
convention: delivery.convention,
|
|
4878
|
+
...hasOwn2(delivery, "payload") ? { payload: delivery.payload } : {}
|
|
4879
|
+
};
|
|
4880
|
+
};
|
|
4881
|
+
listen((event) => {
|
|
4882
|
+
if (!isParentMessage(event)) return;
|
|
4883
|
+
const msg = event.data;
|
|
4884
|
+
if (typeof msg !== "object" || msg === null || msg.type !== "intent.deliver") return;
|
|
4885
|
+
const delivery = normalizeDelivery(msg.delivery);
|
|
4886
|
+
if (!delivery) return;
|
|
4887
|
+
if (deliveryHandlers.size === 0) pendingDeliveries.push(delivery);
|
|
4888
|
+
else for (const handler of deliveryHandlers) handler(delivery);
|
|
4889
|
+
});
|
|
4890
|
+
function normalizeIntentUri(uri, explicitPayload, actionMustBe) {
|
|
4891
|
+
if (typeof uri !== "string" || !uri.startsWith("napplet:")) {
|
|
4892
|
+
throw new TypeError("Intent convention URI must start with napplet:");
|
|
4893
|
+
}
|
|
4894
|
+
if (uri.includes("#")) throw new TypeError("Intent convention URI cannot contain fragments");
|
|
4895
|
+
const normalized = normalizeConventionUri(uri, explicitPayload);
|
|
4896
|
+
const match = /^napplet:([^/?#]+)\/([^/?#]+)$/.exec(normalized.topic);
|
|
4897
|
+
if (!match) throw new TypeError("Intent convention URI must be napplet:<archetype>/<intent>");
|
|
4898
|
+
if (actionMustBe !== void 0 && match[2] !== actionMustBe) {
|
|
4899
|
+
throw new TypeError(`Intent convention URI must use the ${actionMustBe} intent`);
|
|
4900
|
+
}
|
|
4901
|
+
return {
|
|
4902
|
+
archetype: match[1],
|
|
4903
|
+
action: match[2],
|
|
4904
|
+
convention: normalized.topic,
|
|
4905
|
+
...normalized.payload === void 0 ? {} : { payload: normalized.payload }
|
|
4906
|
+
};
|
|
4907
|
+
}
|
|
4908
|
+
const invoke = (uri, opts, actionMustBe) => {
|
|
4909
|
+
const supplied = opts && typeof opts === "object" ? opts : void 0;
|
|
4910
|
+
const explicitPayload = supplied?.payload !== void 0;
|
|
4911
|
+
const normalized = normalizeIntentUri(uri, explicitPayload, actionMustBe);
|
|
4912
|
+
if (supplied && hasOwn2(supplied, "sender")) {
|
|
4913
|
+
throw new TypeError("Intent callers cannot supply sender");
|
|
4914
|
+
}
|
|
4915
|
+
for (const field of ["archetype", "action", "convention"]) {
|
|
4916
|
+
if (supplied && hasOwn2(supplied, field) && supplied[field] !== normalized[field]) {
|
|
4917
|
+
throw new TypeError(`Intent options cannot override URI-derived ${field}`);
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
const behaviorValue = supplied?.behavior;
|
|
4921
|
+
const behavior = behaviorValue && typeof behaviorValue === "object" ? behaviorValue : void 0;
|
|
4922
|
+
const sanitizedBehavior = {
|
|
4923
|
+
...typeof behavior?.focus === "boolean" ? { focus: behavior.focus } : {},
|
|
4924
|
+
...typeof behavior?.reuse === "boolean" ? { reuse: behavior.reuse } : {}
|
|
4925
|
+
};
|
|
4926
|
+
const handler = typeof supplied?.handler === "string" ? supplied.handler : void 0;
|
|
4927
|
+
return request(
|
|
4928
|
+
{
|
|
4929
|
+
type: "intent.invoke",
|
|
4930
|
+
request: {
|
|
4931
|
+
...normalized,
|
|
4932
|
+
...explicitPayload ? { payload: supplied?.payload } : {},
|
|
4933
|
+
...handler === void 0 ? {} : { handler },
|
|
4934
|
+
...Object.keys(sanitizedBehavior).length === 0 ? {} : { behavior: sanitizedBehavior }
|
|
4935
|
+
}
|
|
4936
|
+
},
|
|
4937
|
+
"intent.invoke.result",
|
|
4938
|
+
(msg) => fieldOrThrow(msg, "result", "intent.invoke.result missing result")
|
|
4939
|
+
);
|
|
4940
|
+
};
|
|
4306
4941
|
return {
|
|
4307
4942
|
invoke,
|
|
4308
|
-
open: (
|
|
4309
|
-
archetype,
|
|
4310
|
-
action: "open",
|
|
4311
|
-
payload,
|
|
4312
|
-
...opts && typeof opts === "object" ? opts : {}
|
|
4313
|
-
}),
|
|
4943
|
+
open: (uri, opts) => invoke(uri, opts, "open"),
|
|
4314
4944
|
available: (archetype) => request(
|
|
4315
4945
|
{ type: "intent.available", archetype },
|
|
4316
4946
|
"intent.available.result",
|
|
@@ -4325,7 +4955,14 @@ function nappletNamespacePrelude(domains) {
|
|
|
4325
4955
|
"intent.changed",
|
|
4326
4956
|
callback,
|
|
4327
4957
|
(msg) => msg.availability
|
|
4328
|
-
)
|
|
4958
|
+
),
|
|
4959
|
+
onDelivery: (callback) => {
|
|
4960
|
+
if (typeof callback !== "function") throw new TypeError("Intent delivery handler must be a function");
|
|
4961
|
+
deliveryHandlers.add(callback);
|
|
4962
|
+
const pending = pendingDeliveries.splice(0);
|
|
4963
|
+
for (const delivery of pending) callback(delivery);
|
|
4964
|
+
return subscriptionHandle(() => deliveryHandlers.delete(callback));
|
|
4965
|
+
}
|
|
4329
4966
|
};
|
|
4330
4967
|
}
|
|
4331
4968
|
function makeWebrtc() {
|
|
@@ -4460,14 +5097,40 @@ function nappletNamespacePrelude(domains) {
|
|
|
4460
5097
|
};
|
|
4461
5098
|
}
|
|
4462
5099
|
const shell = makeShell();
|
|
5100
|
+
let inc;
|
|
5101
|
+
let identity;
|
|
5102
|
+
let theme;
|
|
5103
|
+
let intent;
|
|
5104
|
+
function makeProtectedInc(existing) {
|
|
5105
|
+
const extensions = existing && typeof existing === "object" ? existing : {};
|
|
5106
|
+
inc ??= makeInc();
|
|
5107
|
+
return { ...extensions, ...inc };
|
|
5108
|
+
}
|
|
5109
|
+
function makeProtectedIdentity() {
|
|
5110
|
+
identity ??= makeIdentity();
|
|
5111
|
+
return identity;
|
|
5112
|
+
}
|
|
5113
|
+
function makeProtectedTheme() {
|
|
5114
|
+
theme ??= Object.freeze(makeTheme());
|
|
5115
|
+
return theme;
|
|
5116
|
+
}
|
|
5117
|
+
function makeProtectedIntent() {
|
|
5118
|
+
intent ??= Object.freeze(makeIntent());
|
|
5119
|
+
return intent;
|
|
5120
|
+
}
|
|
5121
|
+
function isProtectedDomain(domain) {
|
|
5122
|
+
return domain === "shell" || domain === "inc" || domain === "identity" || domain === "theme" || domain === "intent";
|
|
5123
|
+
}
|
|
4463
5124
|
function makeDomain(domain, existing) {
|
|
4464
5125
|
if (domain === "shell") return shell;
|
|
5126
|
+
if (domain === "inc") return makeProtectedInc(existing);
|
|
5127
|
+
if (domain === "identity") return makeProtectedIdentity();
|
|
5128
|
+
if (domain === "theme") return makeProtectedTheme();
|
|
5129
|
+
if (domain === "intent") return makeProtectedIntent();
|
|
4465
5130
|
if (existing && typeof existing === "object" && Object.keys(existing).length > 0) return existing;
|
|
4466
5131
|
switch (domain) {
|
|
4467
5132
|
case "relay":
|
|
4468
5133
|
return makeRelay();
|
|
4469
|
-
case "inc":
|
|
4470
|
-
return makeInc();
|
|
4471
5134
|
case "storage":
|
|
4472
5135
|
return makeStorage();
|
|
4473
5136
|
case "keys":
|
|
@@ -4476,10 +5139,6 @@ function nappletNamespacePrelude(domains) {
|
|
|
4476
5139
|
return makeMedia();
|
|
4477
5140
|
case "notify":
|
|
4478
5141
|
return makeNotify();
|
|
4479
|
-
case "identity":
|
|
4480
|
-
return makeIdentity();
|
|
4481
|
-
case "theme":
|
|
4482
|
-
return makeTheme();
|
|
4483
5142
|
case "config":
|
|
4484
5143
|
return makeConfig();
|
|
4485
5144
|
case "resource":
|
|
@@ -4490,8 +5149,6 @@ function nappletNamespacePrelude(domains) {
|
|
|
4490
5149
|
return makeOutbox();
|
|
4491
5150
|
case "upload":
|
|
4492
5151
|
return makeUpload();
|
|
4493
|
-
case "intent":
|
|
4494
|
-
return makeIntent();
|
|
4495
5152
|
case "webrtc":
|
|
4496
5153
|
return makeWebrtc();
|
|
4497
5154
|
case "ble":
|
|
@@ -4521,10 +5178,22 @@ function nappletNamespacePrelude(domains) {
|
|
|
4521
5178
|
},
|
|
4522
5179
|
defineProperty(obj, prop, descriptor) {
|
|
4523
5180
|
if (typeof prop !== "string" || !allowed2.has(prop)) return true;
|
|
5181
|
+
if (isProtectedDomain(prop)) {
|
|
5182
|
+
return Reflect.defineProperty(obj, prop, {
|
|
5183
|
+
value: makeDomain(prop, descriptor.value),
|
|
5184
|
+
enumerable: true,
|
|
5185
|
+
configurable: true,
|
|
5186
|
+
writable: true
|
|
5187
|
+
});
|
|
5188
|
+
}
|
|
4524
5189
|
return Reflect.defineProperty(obj, prop, {
|
|
4525
5190
|
...descriptor,
|
|
4526
5191
|
value: makeDomain(prop, descriptor.value)
|
|
4527
5192
|
});
|
|
5193
|
+
},
|
|
5194
|
+
deleteProperty(obj, prop) {
|
|
5195
|
+
if (typeof prop === "string" && allowed2.has(prop) && isProtectedDomain(prop)) return true;
|
|
5196
|
+
return Reflect.deleteProperty(obj, prop);
|
|
4528
5197
|
}
|
|
4529
5198
|
});
|
|
4530
5199
|
}
|
|
@@ -4554,7 +5223,7 @@ function nappletNamespacePrelude(domains) {
|
|
|
4554
5223
|
root = buildNappletNamespace(value);
|
|
4555
5224
|
},
|
|
4556
5225
|
enumerable: false,
|
|
4557
|
-
configurable:
|
|
5226
|
+
configurable: false
|
|
4558
5227
|
});
|
|
4559
5228
|
}
|
|
4560
5229
|
|
|
@@ -8159,14 +8828,11 @@ function enforceLimit(store2, list) {
|
|
|
8159
8828
|
list.shift();
|
|
8160
8829
|
}
|
|
8161
8830
|
}
|
|
8162
|
-
function findById(store2, id) {
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
}
|
|
8168
|
-
}
|
|
8169
|
-
return void 0;
|
|
8831
|
+
function findById(store2, windowId, id) {
|
|
8832
|
+
const list = store2.notifications.get(windowId);
|
|
8833
|
+
const index = list?.findIndex((notification) => notification.id === id) ?? -1;
|
|
8834
|
+
if (!list || index === -1) return void 0;
|
|
8835
|
+
return [list[index], index];
|
|
8170
8836
|
}
|
|
8171
8837
|
function createNotification(store2, windowId, title, body) {
|
|
8172
8838
|
const notification = {
|
|
@@ -8183,20 +8849,20 @@ function createNotification(store2, windowId, title, body) {
|
|
|
8183
8849
|
notify(store2);
|
|
8184
8850
|
return notification;
|
|
8185
8851
|
}
|
|
8186
|
-
function dismissNotification(store2, id) {
|
|
8187
|
-
const found = findById(store2, id);
|
|
8852
|
+
function dismissNotification(store2, windowId, id) {
|
|
8853
|
+
const found = findById(store2, windowId, id);
|
|
8188
8854
|
if (!found) return;
|
|
8189
|
-
const [
|
|
8190
|
-
const list = store2.notifications.get(
|
|
8855
|
+
const [, index] = found;
|
|
8856
|
+
const list = store2.notifications.get(windowId);
|
|
8191
8857
|
if (!list) return;
|
|
8192
8858
|
list.splice(index, 1);
|
|
8193
|
-
if (list.length === 0) store2.notifications.delete(
|
|
8859
|
+
if (list.length === 0) store2.notifications.delete(windowId);
|
|
8194
8860
|
notify(store2);
|
|
8195
8861
|
}
|
|
8196
|
-
function markNotificationRead(store2, id) {
|
|
8197
|
-
const found = findById(store2, id);
|
|
8862
|
+
function markNotificationRead(store2, windowId, id) {
|
|
8863
|
+
const found = findById(store2, windowId, id);
|
|
8198
8864
|
if (!found) return;
|
|
8199
|
-
const [
|
|
8865
|
+
const [notification] = found;
|
|
8200
8866
|
if (!notification.read) {
|
|
8201
8867
|
notification.read = true;
|
|
8202
8868
|
notify(store2);
|
|
@@ -8213,12 +8879,12 @@ function handleNotifyEnvelope(store2, windowId, action, msg, send) {
|
|
|
8213
8879
|
}
|
|
8214
8880
|
case "dismiss": {
|
|
8215
8881
|
const notifId = typeof msg.notificationId === "string" ? msg.notificationId : "";
|
|
8216
|
-
if (notifId) dismissNotification(store2, notifId);
|
|
8882
|
+
if (notifId) dismissNotification(store2, windowId, notifId);
|
|
8217
8883
|
break;
|
|
8218
8884
|
}
|
|
8219
8885
|
case "read": {
|
|
8220
8886
|
const notifId = typeof msg.notificationId === "string" ? msg.notificationId : "";
|
|
8221
|
-
if (notifId) markNotificationRead(store2, notifId);
|
|
8887
|
+
if (notifId) markNotificationRead(store2, windowId, notifId);
|
|
8222
8888
|
break;
|
|
8223
8889
|
}
|
|
8224
8890
|
case "list": {
|
|
@@ -8230,34 +8896,6 @@ function handleNotifyEnvelope(store2, windowId, action, msg, send) {
|
|
|
8230
8896
|
break;
|
|
8231
8897
|
}
|
|
8232
8898
|
}
|
|
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
8899
|
function createNotificationService(options) {
|
|
8262
8900
|
const store2 = {
|
|
8263
8901
|
notifications: /* @__PURE__ */ new Map(),
|
|
@@ -8265,23 +8903,21 @@ function createNotificationService(options) {
|
|
|
8265
8903
|
maxPerWindow: options?.maxPerWindow ?? DEFAULT_MAX_PER_WINDOW
|
|
8266
8904
|
};
|
|
8267
8905
|
const descriptor = {
|
|
8268
|
-
name: "
|
|
8906
|
+
name: "notify",
|
|
8269
8907
|
version: NOTIFICATION_SERVICE_VERSION,
|
|
8270
8908
|
description: "Notification state registry \u2014 tracks notifications per napplet window"
|
|
8271
8909
|
};
|
|
8272
8910
|
return {
|
|
8273
8911
|
descriptor,
|
|
8274
8912
|
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);
|
|
8913
|
+
if (!message.type.startsWith("notify.")) return;
|
|
8914
|
+
handleNotifyEnvelope(
|
|
8915
|
+
store2,
|
|
8916
|
+
windowId,
|
|
8917
|
+
message.type.slice(7),
|
|
8918
|
+
message,
|
|
8919
|
+
send
|
|
8920
|
+
);
|
|
8285
8921
|
},
|
|
8286
8922
|
onWindowDestroyed(windowId) {
|
|
8287
8923
|
if (store2.notifications.delete(windowId)) {
|
|
@@ -8291,16 +8927,16 @@ function createNotificationService(options) {
|
|
|
8291
8927
|
};
|
|
8292
8928
|
}
|
|
8293
8929
|
var IDENTITY_SERVICE_VERSION = "1.0.0";
|
|
8294
|
-
function sendProviderError(send, result2, fallback,
|
|
8930
|
+
function sendProviderError(send, result2, fallback, _err) {
|
|
8295
8931
|
send({
|
|
8296
8932
|
...result2,
|
|
8297
|
-
error:
|
|
8933
|
+
error: fallback
|
|
8298
8934
|
});
|
|
8299
8935
|
}
|
|
8300
8936
|
async function getCurrentPubkey(options) {
|
|
8301
|
-
const currentSigner = options.getSigner();
|
|
8302
|
-
if (!currentSigner?.getPublicKey) return "";
|
|
8303
8937
|
try {
|
|
8938
|
+
const currentSigner = options.getSigner();
|
|
8939
|
+
if (!currentSigner?.getPublicKey) return "";
|
|
8304
8940
|
return await currentSigner.getPublicKey() ?? "";
|
|
8305
8941
|
} catch {
|
|
8306
8942
|
return "";
|
|
@@ -8313,46 +8949,29 @@ function sendOptionalProviderResult(options, send, fallbackResult, errorFallback
|
|
|
8313
8949
|
}
|
|
8314
8950
|
Promise.resolve(getCurrentPubkey(options)).then((pubkey) => buildResult(pubkey)).then((result2) => send(result2)).catch((err2) => sendProviderError(send, fallbackResult, errorFallback, err2));
|
|
8315
8951
|
}
|
|
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
8952
|
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) => {
|
|
8953
|
+
Promise.resolve().then(() => options.getSigner()).then(async (signer) => signer?.getPublicKey ? signer.getPublicKey() : "").catch(() => "").then((pubkey) => {
|
|
8334
8954
|
const result2 = {
|
|
8335
8955
|
type: "identity.getPublicKey.result",
|
|
8336
8956
|
id,
|
|
8337
8957
|
pubkey: pubkey ?? ""
|
|
8338
8958
|
};
|
|
8339
8959
|
send(result2);
|
|
8340
|
-
})
|
|
8960
|
+
});
|
|
8341
8961
|
}
|
|
8342
8962
|
function handleGetRelays(options, id, send) {
|
|
8343
|
-
const
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
}
|
|
8348
|
-
Promise.resolve(
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
relays
|
|
8963
|
+
const fallbackResult = {
|
|
8964
|
+
type: "identity.getRelays.result",
|
|
8965
|
+
id,
|
|
8966
|
+
relays: {}
|
|
8967
|
+
};
|
|
8968
|
+
Promise.resolve().then(() => options.getSigner()).then(async (signer) => {
|
|
8969
|
+
if (!signer?.getRelays) return fallbackResult;
|
|
8970
|
+
return {
|
|
8971
|
+
...fallbackResult,
|
|
8972
|
+
relays: await signer.getRelays() ?? {}
|
|
8353
8973
|
};
|
|
8354
|
-
|
|
8355
|
-
}).catch((err2) => sendSignerError(send, id, "identity.getRelays", "getRelays failed", err2));
|
|
8974
|
+
}).catch(() => ({ ...fallbackResult, error: "identity.getRelays failed" })).then(send);
|
|
8356
8975
|
}
|
|
8357
8976
|
function handleReadProvider(options, id, message, send) {
|
|
8358
8977
|
switch (message.type) {
|
|
@@ -8361,11 +8980,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8361
8980
|
options,
|
|
8362
8981
|
send,
|
|
8363
8982
|
{ type: "identity.getProfile.result", id, profile: null },
|
|
8364
|
-
"getProfile failed",
|
|
8983
|
+
"identity.getProfile failed",
|
|
8365
8984
|
options.getProfile ? async (pubkey) => ({
|
|
8366
8985
|
type: "identity.getProfile.result",
|
|
8367
8986
|
id,
|
|
8368
|
-
profile: await options.getProfile(pubkey, message)
|
|
8987
|
+
profile: await options.getProfile(pubkey, message) ?? null
|
|
8369
8988
|
}) : void 0
|
|
8370
8989
|
);
|
|
8371
8990
|
return true;
|
|
@@ -8374,11 +8993,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8374
8993
|
options,
|
|
8375
8994
|
send,
|
|
8376
8995
|
{ type: "identity.getFollows.result", id, pubkeys: [] },
|
|
8377
|
-
"getFollows failed",
|
|
8996
|
+
"identity.getFollows failed",
|
|
8378
8997
|
options.getFollows ? async (pubkey) => ({
|
|
8379
8998
|
type: "identity.getFollows.result",
|
|
8380
8999
|
id,
|
|
8381
|
-
pubkeys: await options.getFollows(pubkey, message)
|
|
9000
|
+
pubkeys: await options.getFollows(pubkey, message) ?? []
|
|
8382
9001
|
}) : void 0
|
|
8383
9002
|
);
|
|
8384
9003
|
return true;
|
|
@@ -8390,11 +9009,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8390
9009
|
options,
|
|
8391
9010
|
send,
|
|
8392
9011
|
{ type: "identity.getZaps.result", id, zaps: [] },
|
|
8393
|
-
"getZaps failed",
|
|
9012
|
+
"identity.getZaps failed",
|
|
8394
9013
|
options.getZaps ? async (pubkey) => ({
|
|
8395
9014
|
type: "identity.getZaps.result",
|
|
8396
9015
|
id,
|
|
8397
|
-
zaps: await options.getZaps(pubkey, message)
|
|
9016
|
+
zaps: await options.getZaps(pubkey, message) ?? []
|
|
8398
9017
|
}) : void 0
|
|
8399
9018
|
);
|
|
8400
9019
|
return true;
|
|
@@ -8403,11 +9022,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8403
9022
|
options,
|
|
8404
9023
|
send,
|
|
8405
9024
|
{ type: "identity.getMutes.result", id, pubkeys: [] },
|
|
8406
|
-
"getMutes failed",
|
|
9025
|
+
"identity.getMutes failed",
|
|
8407
9026
|
options.getMutes ? async (pubkey) => ({
|
|
8408
9027
|
type: "identity.getMutes.result",
|
|
8409
9028
|
id,
|
|
8410
|
-
pubkeys: await options.getMutes(pubkey, message)
|
|
9029
|
+
pubkeys: await options.getMutes(pubkey, message) ?? []
|
|
8411
9030
|
}) : void 0
|
|
8412
9031
|
);
|
|
8413
9032
|
return true;
|
|
@@ -8416,11 +9035,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8416
9035
|
options,
|
|
8417
9036
|
send,
|
|
8418
9037
|
{ type: "identity.getBlocked.result", id, pubkeys: [] },
|
|
8419
|
-
"getBlocked failed",
|
|
9038
|
+
"identity.getBlocked failed",
|
|
8420
9039
|
options.getBlocked ? async (pubkey) => ({
|
|
8421
9040
|
type: "identity.getBlocked.result",
|
|
8422
9041
|
id,
|
|
8423
|
-
pubkeys: await options.getBlocked(pubkey, message)
|
|
9042
|
+
pubkeys: await options.getBlocked(pubkey, message) ?? []
|
|
8424
9043
|
}) : void 0
|
|
8425
9044
|
);
|
|
8426
9045
|
return true;
|
|
@@ -8429,11 +9048,11 @@ function handleReadProvider(options, id, message, send) {
|
|
|
8429
9048
|
options,
|
|
8430
9049
|
send,
|
|
8431
9050
|
{ type: "identity.getBadges.result", id, badges: [] },
|
|
8432
|
-
"getBadges failed",
|
|
9051
|
+
"identity.getBadges failed",
|
|
8433
9052
|
options.getBadges ? async (pubkey) => ({
|
|
8434
9053
|
type: "identity.getBadges.result",
|
|
8435
9054
|
id,
|
|
8436
|
-
badges: await options.getBadges(pubkey, message)
|
|
9055
|
+
badges: await options.getBadges(pubkey, message) ?? []
|
|
8437
9056
|
}) : void 0
|
|
8438
9057
|
);
|
|
8439
9058
|
return true;
|
|
@@ -8446,11 +9065,11 @@ function handleGetList(options, id, message, send) {
|
|
|
8446
9065
|
options,
|
|
8447
9066
|
send,
|
|
8448
9067
|
{ type: "identity.getList.result", id, entries: [] },
|
|
8449
|
-
"getList failed",
|
|
9068
|
+
"identity.getList failed",
|
|
8450
9069
|
options.getList ? async (pubkey) => ({
|
|
8451
9070
|
type: "identity.getList.result",
|
|
8452
9071
|
id,
|
|
8453
|
-
entries: await options.getList(message.listType, pubkey, message)
|
|
9072
|
+
entries: await options.getList(message.listType, pubkey, message) ?? []
|
|
8454
9073
|
}) : void 0
|
|
8455
9074
|
);
|
|
8456
9075
|
}
|
|
@@ -8464,9 +9083,7 @@ function handleIdentityServiceMessage(options, message, send) {
|
|
|
8464
9083
|
handleGetRelays(options, id, send);
|
|
8465
9084
|
return;
|
|
8466
9085
|
default:
|
|
8467
|
-
|
|
8468
|
-
sendIdentityError(send, id, message.type, `Unknown identity method: ${message.type}`);
|
|
8469
|
-
}
|
|
9086
|
+
handleReadProvider(options, id, message, send);
|
|
8470
9087
|
}
|
|
8471
9088
|
}
|
|
8472
9089
|
function createIdentityService(options) {
|
|
@@ -8487,6 +9104,37 @@ function createIdentityService(options) {
|
|
|
8487
9104
|
var EOSE_FALLBACK_MS = 15e3;
|
|
8488
9105
|
function createRelayPoolService(options) {
|
|
8489
9106
|
const tracked = /* @__PURE__ */ new Map();
|
|
9107
|
+
function publishEvent(relayMessage, resultType, send) {
|
|
9108
|
+
const id = typeof relayMessage.id === "string" ? relayMessage.id : "";
|
|
9109
|
+
const event = relayMessage.event;
|
|
9110
|
+
if (!event || typeof event !== "object") {
|
|
9111
|
+
send({ type: resultType, id, ok: false, error: "invalid event" });
|
|
9112
|
+
return;
|
|
9113
|
+
}
|
|
9114
|
+
if (!options.isAvailable()) {
|
|
9115
|
+
send({ type: resultType, id, ok: false, error: "no relay pool available" });
|
|
9116
|
+
return;
|
|
9117
|
+
}
|
|
9118
|
+
void (async () => {
|
|
9119
|
+
try {
|
|
9120
|
+
await options.publish(event);
|
|
9121
|
+
send({
|
|
9122
|
+
type: resultType,
|
|
9123
|
+
id,
|
|
9124
|
+
ok: true,
|
|
9125
|
+
event,
|
|
9126
|
+
eventId: event.id
|
|
9127
|
+
});
|
|
9128
|
+
} catch (error) {
|
|
9129
|
+
send({
|
|
9130
|
+
type: resultType,
|
|
9131
|
+
id,
|
|
9132
|
+
ok: false,
|
|
9133
|
+
error: error instanceof Error && error.message ? error.message : "publish failed"
|
|
9134
|
+
});
|
|
9135
|
+
}
|
|
9136
|
+
})();
|
|
9137
|
+
}
|
|
8490
9138
|
return {
|
|
8491
9139
|
descriptor: {
|
|
8492
9140
|
name: "relay-pool",
|
|
@@ -8546,17 +9194,11 @@ function createRelayPoolService(options) {
|
|
|
8546
9194
|
return;
|
|
8547
9195
|
}
|
|
8548
9196
|
if (message.type === "relay.publish") {
|
|
8549
|
-
|
|
8550
|
-
if (event && typeof event === "object" && options.isAvailable()) {
|
|
8551
|
-
options.publish(event);
|
|
8552
|
-
}
|
|
9197
|
+
publishEvent(relayMessage, "relay.publish.result", send);
|
|
8553
9198
|
return;
|
|
8554
9199
|
}
|
|
8555
9200
|
if (message.type === "relay.publishEncrypted") {
|
|
8556
|
-
|
|
8557
|
-
if (event && typeof event === "object" && options.isAvailable()) {
|
|
8558
|
-
options.publish(event);
|
|
8559
|
-
}
|
|
9201
|
+
publishEvent(relayMessage, "relay.publishEncrypted.result", send);
|
|
8560
9202
|
return;
|
|
8561
9203
|
}
|
|
8562
9204
|
},
|
|
@@ -9488,8 +10130,15 @@ var DEFAULT_THEME = {
|
|
|
9488
10130
|
// fonts, background, title intentionally undefined — all optional per
|
|
9489
10131
|
// @napplet/nap/theme Theme interface.
|
|
9490
10132
|
};
|
|
10133
|
+
function isCompleteTheme(theme) {
|
|
10134
|
+
const colors = theme?.colors;
|
|
10135
|
+
return typeof colors?.background === "string" && typeof colors.text === "string" && typeof colors.primary === "string";
|
|
10136
|
+
}
|
|
10137
|
+
function normalizeTheme(theme) {
|
|
10138
|
+
return isCompleteTheme(theme) ? theme : DEFAULT_THEME;
|
|
10139
|
+
}
|
|
9491
10140
|
function createThemeService(options = {}) {
|
|
9492
|
-
let currentTheme = options.initialTheme
|
|
10141
|
+
let currentTheme = normalizeTheme(options.initialTheme);
|
|
9493
10142
|
const descriptor = {
|
|
9494
10143
|
name: "theme",
|
|
9495
10144
|
version: THEME_SERVICE_VERSION,
|
|
@@ -9508,19 +10157,15 @@ function createThemeService(options = {}) {
|
|
|
9508
10157
|
send(result2);
|
|
9509
10158
|
return;
|
|
9510
10159
|
}
|
|
9511
|
-
|
|
9512
|
-
type: `${message.type}.error`,
|
|
9513
|
-
id,
|
|
9514
|
-
error: `Unknown theme method: ${message.type}`
|
|
9515
|
-
});
|
|
10160
|
+
return;
|
|
9516
10161
|
},
|
|
9517
10162
|
// Theme service has no per-window state to clean up.
|
|
9518
10163
|
onWindowDestroyed(_windowId) {
|
|
9519
10164
|
}
|
|
9520
10165
|
};
|
|
9521
10166
|
function publishTheme(theme) {
|
|
9522
|
-
currentTheme = theme;
|
|
9523
|
-
const envelope = { type: "theme.changed", theme };
|
|
10167
|
+
currentTheme = normalizeTheme(theme);
|
|
10168
|
+
const envelope = { type: "theme.changed", theme: currentTheme };
|
|
9524
10169
|
options.onBroadcast?.(envelope);
|
|
9525
10170
|
return envelope;
|
|
9526
10171
|
}
|
|
@@ -11071,42 +11716,125 @@ function toErrorMessage4(err2) {
|
|
|
11071
11716
|
if (typeof err2 === "string") return err2;
|
|
11072
11717
|
return "intent request failed";
|
|
11073
11718
|
}
|
|
11719
|
+
function hasOwn(value, key) {
|
|
11720
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
11721
|
+
}
|
|
11722
|
+
function validateIntentRequest(value) {
|
|
11723
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
11724
|
+
return { error: "invalid convention" };
|
|
11725
|
+
}
|
|
11726
|
+
const request = value;
|
|
11727
|
+
if (typeof request.archetype !== "string" || request.archetype.length === 0 || typeof request.action !== "string" || request.action.length === 0 || typeof request.convention !== "string" || request.convention.length === 0) {
|
|
11728
|
+
return { error: "invalid convention" };
|
|
11729
|
+
}
|
|
11730
|
+
const match = /^napplet:([^/?#\s]+)\/([^/?#\s]+)$/.exec(request.convention);
|
|
11731
|
+
if (!match || match[1] !== request.archetype || match[2] !== request.action) {
|
|
11732
|
+
return { error: "invalid convention" };
|
|
11733
|
+
}
|
|
11734
|
+
const allowedKeys = ["archetype", "action", "convention", "payload", "handler", "behavior"];
|
|
11735
|
+
if (Object.keys(request).some((key) => !allowedKeys.includes(key))) {
|
|
11736
|
+
return { error: "invoke rejected" };
|
|
11737
|
+
}
|
|
11738
|
+
if (hasOwn(request, "handler") && request.handler !== void 0 && (typeof request.handler !== "string" || request.handler.length === 0)) {
|
|
11739
|
+
return { error: "invoke rejected" };
|
|
11740
|
+
}
|
|
11741
|
+
let behavior;
|
|
11742
|
+
if (hasOwn(request, "behavior") && request.behavior !== void 0) {
|
|
11743
|
+
if (typeof request.behavior !== "object" || request.behavior === null || Array.isArray(request.behavior)) {
|
|
11744
|
+
return { error: "invoke rejected" };
|
|
11745
|
+
}
|
|
11746
|
+
const record = request.behavior;
|
|
11747
|
+
if (Object.keys(record).some((key) => key !== "focus" && key !== "reuse") || hasOwn(record, "focus") && typeof record.focus !== "boolean" || hasOwn(record, "reuse") && typeof record.reuse !== "boolean") {
|
|
11748
|
+
return { error: "invoke rejected" };
|
|
11749
|
+
}
|
|
11750
|
+
behavior = {
|
|
11751
|
+
...record.focus === void 0 ? {} : { focus: record.focus },
|
|
11752
|
+
...record.reuse === void 0 ? {} : { reuse: record.reuse }
|
|
11753
|
+
};
|
|
11754
|
+
}
|
|
11755
|
+
return {
|
|
11756
|
+
request: {
|
|
11757
|
+
archetype: request.archetype,
|
|
11758
|
+
action: request.action,
|
|
11759
|
+
convention: request.convention,
|
|
11760
|
+
...hasOwn(request, "payload") ? { payload: request.payload } : {},
|
|
11761
|
+
...typeof request.handler === "string" ? { handler: request.handler } : {},
|
|
11762
|
+
...behavior === void 0 ? {} : { behavior }
|
|
11763
|
+
}
|
|
11764
|
+
};
|
|
11765
|
+
}
|
|
11766
|
+
function settleResolverCall(call, send, resultType, id, onValue) {
|
|
11767
|
+
let pending;
|
|
11768
|
+
try {
|
|
11769
|
+
pending = Promise.resolve(call());
|
|
11770
|
+
} catch (error) {
|
|
11771
|
+
send({ type: resultType, id, error: toErrorMessage4(error) });
|
|
11772
|
+
return;
|
|
11773
|
+
}
|
|
11774
|
+
pending.then((value) => send(onValue(value))).catch((error) => send({ type: resultType, id, error: toErrorMessage4(error) }));
|
|
11775
|
+
}
|
|
11776
|
+
function rejectInvoke(send, id) {
|
|
11777
|
+
send({
|
|
11778
|
+
type: "intent.invoke.result",
|
|
11779
|
+
id,
|
|
11780
|
+
result: { ok: false, error: "invoke rejected" }
|
|
11781
|
+
});
|
|
11782
|
+
}
|
|
11074
11783
|
function createIntentService(options) {
|
|
11075
11784
|
if (!options || typeof options.resolver !== "object" || options.resolver === null) {
|
|
11076
11785
|
throw new Error("createIntentService: options.resolver is required");
|
|
11077
11786
|
}
|
|
11078
11787
|
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
|
-
}
|
|
11788
|
+
let runtimeContext;
|
|
11789
|
+
let unsubscribeChanged;
|
|
11095
11790
|
function handleInvoke(windowId, msg, send) {
|
|
11096
11791
|
const m = msg;
|
|
11097
11792
|
const id = m.id ?? "";
|
|
11098
|
-
const
|
|
11099
|
-
if (
|
|
11100
|
-
send({
|
|
11793
|
+
const validation = validateIntentRequest(m.request);
|
|
11794
|
+
if ("error" in validation) {
|
|
11795
|
+
send({
|
|
11796
|
+
type: "intent.invoke.result",
|
|
11797
|
+
id,
|
|
11798
|
+
result: { ok: false, error: validation.error }
|
|
11799
|
+
});
|
|
11101
11800
|
return;
|
|
11102
11801
|
}
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
send,
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11802
|
+
const sender = runtimeContext?.resolveDTag(windowId);
|
|
11803
|
+
if (!sender) {
|
|
11804
|
+
rejectInvoke(send, id);
|
|
11805
|
+
return;
|
|
11806
|
+
}
|
|
11807
|
+
let pending;
|
|
11808
|
+
try {
|
|
11809
|
+
pending = Promise.resolve(resolver.invoke(validation.request, { sender }));
|
|
11810
|
+
} catch {
|
|
11811
|
+
rejectInvoke(send, id);
|
|
11812
|
+
return;
|
|
11813
|
+
}
|
|
11814
|
+
void pending.then(
|
|
11815
|
+
(outcome) => {
|
|
11816
|
+
if (outcome.result.ok && (!("retained" in outcome) || typeof outcome.retained.start !== "function")) {
|
|
11817
|
+
rejectInvoke(send, id);
|
|
11818
|
+
return;
|
|
11819
|
+
}
|
|
11820
|
+
send({
|
|
11821
|
+
type: "intent.invoke.result",
|
|
11822
|
+
id,
|
|
11823
|
+
result: outcome.result
|
|
11824
|
+
});
|
|
11825
|
+
if (!outcome.result.ok || !("retained" in outcome)) return;
|
|
11826
|
+
try {
|
|
11827
|
+
const started = outcome.retained.start();
|
|
11828
|
+
void Promise.resolve(started).catch(() => {
|
|
11829
|
+
});
|
|
11830
|
+
} catch {
|
|
11831
|
+
}
|
|
11832
|
+
},
|
|
11833
|
+
() => {
|
|
11834
|
+
rejectInvoke(send, id);
|
|
11835
|
+
}
|
|
11836
|
+
).catch(() => {
|
|
11837
|
+
});
|
|
11110
11838
|
}
|
|
11111
11839
|
function handleAvailable(msg, send) {
|
|
11112
11840
|
const m = msg;
|
|
@@ -11116,7 +11844,7 @@ function createIntentService(options) {
|
|
|
11116
11844
|
return;
|
|
11117
11845
|
}
|
|
11118
11846
|
const archetype = m.archetype;
|
|
11119
|
-
|
|
11847
|
+
settleResolverCall(
|
|
11120
11848
|
() => resolver.available(archetype),
|
|
11121
11849
|
send,
|
|
11122
11850
|
"intent.available.result",
|
|
@@ -11127,7 +11855,7 @@ function createIntentService(options) {
|
|
|
11127
11855
|
function handleHandlers(msg, send) {
|
|
11128
11856
|
const m = msg;
|
|
11129
11857
|
const id = m.id ?? "";
|
|
11130
|
-
|
|
11858
|
+
settleResolverCall(
|
|
11131
11859
|
() => resolver.handlers(),
|
|
11132
11860
|
send,
|
|
11133
11861
|
"intent.handlers.result",
|
|
@@ -11137,8 +11865,31 @@ function createIntentService(options) {
|
|
|
11137
11865
|
}
|
|
11138
11866
|
return {
|
|
11139
11867
|
descriptor: INTENT_DESCRIPTOR,
|
|
11140
|
-
|
|
11141
|
-
|
|
11868
|
+
onRegistered(context) {
|
|
11869
|
+
try {
|
|
11870
|
+
unsubscribeChanged?.();
|
|
11871
|
+
} catch {
|
|
11872
|
+
}
|
|
11873
|
+
runtimeContext = context;
|
|
11874
|
+
unsubscribeChanged = resolver.onChanged?.((availability) => {
|
|
11875
|
+
const current = runtimeContext;
|
|
11876
|
+
if (!current) return;
|
|
11877
|
+
const message = { type: "intent.changed", availability };
|
|
11878
|
+
for (const windowId of current.listWindowIds()) {
|
|
11879
|
+
current.sendToEligibleNapplet(windowId, message);
|
|
11880
|
+
}
|
|
11881
|
+
});
|
|
11882
|
+
},
|
|
11883
|
+
onUnregistered() {
|
|
11884
|
+
const unsubscribe = unsubscribeChanged;
|
|
11885
|
+
unsubscribeChanged = void 0;
|
|
11886
|
+
runtimeContext = void 0;
|
|
11887
|
+
try {
|
|
11888
|
+
unsubscribe?.();
|
|
11889
|
+
} catch {
|
|
11890
|
+
}
|
|
11891
|
+
},
|
|
11892
|
+
handleMessage(windowId, message, send) {
|
|
11142
11893
|
switch (message.type) {
|
|
11143
11894
|
case "intent.invoke":
|
|
11144
11895
|
handleInvoke(windowId, message, send);
|
|
@@ -11152,12 +11903,199 @@ function createIntentService(options) {
|
|
|
11152
11903
|
default:
|
|
11153
11904
|
return;
|
|
11154
11905
|
}
|
|
11906
|
+
}
|
|
11907
|
+
};
|
|
11908
|
+
}
|
|
11909
|
+
function candidatesFor(catalog, archetype, defaultHandler) {
|
|
11910
|
+
const candidates = [];
|
|
11911
|
+
for (const entry of catalog) {
|
|
11912
|
+
const support = entry.archetypes[archetype];
|
|
11913
|
+
if (!support) continue;
|
|
11914
|
+
candidates.push({
|
|
11915
|
+
dTag: entry.dTag,
|
|
11916
|
+
...entry.title === void 0 ? {} : { title: entry.title },
|
|
11917
|
+
actions: [...support.actions],
|
|
11918
|
+
conventions: [...support.conventions],
|
|
11919
|
+
contracts: support.contracts.map((contract) => ({
|
|
11920
|
+
convention: contract.convention,
|
|
11921
|
+
...contract.eventKinds === void 0 ? {} : { eventKinds: [...contract.eventKinds] }
|
|
11922
|
+
})),
|
|
11923
|
+
...entry.dTag === defaultHandler ? { isDefault: true } : {}
|
|
11924
|
+
});
|
|
11925
|
+
}
|
|
11926
|
+
return candidates;
|
|
11927
|
+
}
|
|
11928
|
+
function reject(error) {
|
|
11929
|
+
return { ok: false, error };
|
|
11930
|
+
}
|
|
11931
|
+
function createCatalogIntentResolver(options) {
|
|
11932
|
+
if (!options || typeof options.loadCatalog !== "function") {
|
|
11933
|
+
throw new Error("createCatalogIntentResolver: options.loadCatalog is required");
|
|
11934
|
+
}
|
|
11935
|
+
if (!options.targets || typeof options.targets.retain !== "function") {
|
|
11936
|
+
throw new Error("createCatalogIntentResolver: options.targets is required");
|
|
11937
|
+
}
|
|
11938
|
+
const {
|
|
11939
|
+
loadCatalog,
|
|
11940
|
+
targets,
|
|
11941
|
+
getDefaultHandler,
|
|
11942
|
+
chooseHandler,
|
|
11943
|
+
authorizeExplicitHandler
|
|
11944
|
+
} = options;
|
|
11945
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
11946
|
+
async function availabilityFor(archetype) {
|
|
11947
|
+
const catalog = await loadCatalog();
|
|
11948
|
+
const defaultHandler = getDefaultHandler?.(archetype);
|
|
11949
|
+
const candidates = candidatesFor(catalog, archetype, defaultHandler);
|
|
11950
|
+
return {
|
|
11951
|
+
archetype,
|
|
11952
|
+
available: candidates.length > 0,
|
|
11953
|
+
candidates,
|
|
11954
|
+
hasDefault: defaultHandler !== void 0 && candidates.some((candidate) => candidate.dTag === defaultHandler)
|
|
11955
|
+
};
|
|
11956
|
+
}
|
|
11957
|
+
async function chooseCompatible(request, candidates, sender) {
|
|
11958
|
+
if (!chooseHandler) {
|
|
11959
|
+
return request.handler === "choose" ? { error: "user cancelled" } : { error: "invoke rejected" };
|
|
11960
|
+
}
|
|
11961
|
+
const picked = await chooseHandler(request.archetype, candidates, sender);
|
|
11962
|
+
if (picked === void 0) return { error: "user cancelled" };
|
|
11963
|
+
const candidate = candidates.find((item) => item.dTag === picked);
|
|
11964
|
+
return candidate ? { candidate } : { error: "invoke rejected" };
|
|
11965
|
+
}
|
|
11966
|
+
async function pickHandler(request, compatible, sender) {
|
|
11967
|
+
const preference = request.handler;
|
|
11968
|
+
if (typeof preference === "string" && preference !== "default" && preference !== "choose") {
|
|
11969
|
+
const candidate = compatible.find((item) => item.dTag === preference);
|
|
11970
|
+
if (!candidate || !authorizeExplicitHandler) return { error: "invoke rejected" };
|
|
11971
|
+
let authorized = false;
|
|
11972
|
+
try {
|
|
11973
|
+
authorized = await authorizeExplicitHandler(sender, preference, request, candidate);
|
|
11974
|
+
} catch {
|
|
11975
|
+
return { error: "invoke rejected" };
|
|
11976
|
+
}
|
|
11977
|
+
return authorized ? { candidate } : { error: "invoke rejected" };
|
|
11978
|
+
}
|
|
11979
|
+
if (preference === "choose") {
|
|
11980
|
+
return chooseCompatible(request, compatible, sender);
|
|
11981
|
+
}
|
|
11982
|
+
const defaultCandidate = compatible.find((candidate) => candidate.isDefault === true);
|
|
11983
|
+
if (preference === "default") {
|
|
11984
|
+
return defaultCandidate ? { candidate: defaultCandidate } : { error: "invoke rejected" };
|
|
11985
|
+
}
|
|
11986
|
+
if (defaultCandidate) return { candidate: defaultCandidate };
|
|
11987
|
+
if (compatible.length === 1) return { candidate: compatible[0] };
|
|
11988
|
+
return chooseCompatible(request, compatible, sender);
|
|
11989
|
+
}
|
|
11990
|
+
async function invoke(request, context) {
|
|
11991
|
+
const catalog = await loadCatalog();
|
|
11992
|
+
const defaultHandler = getDefaultHandler?.(request.archetype);
|
|
11993
|
+
const candidates = candidatesFor(catalog, request.archetype, defaultHandler);
|
|
11994
|
+
if (candidates.length === 0) return { result: reject("no handler") };
|
|
11995
|
+
const compatible = candidates.filter((candidate) => candidate.contracts.some((contract) => contract.convention === request.convention));
|
|
11996
|
+
if (compatible.length === 0) return { result: reject("unsupported convention") };
|
|
11997
|
+
const sender = context.sender;
|
|
11998
|
+
if (typeof sender !== "string" || sender.length === 0) {
|
|
11999
|
+
return { result: reject("invoke rejected") };
|
|
12000
|
+
}
|
|
12001
|
+
const selected = await pickHandler(request, compatible, sender);
|
|
12002
|
+
if ("error" in selected) return { result: reject(selected.error) };
|
|
12003
|
+
const delivery = Object.freeze({
|
|
12004
|
+
sender,
|
|
12005
|
+
archetype: request.archetype,
|
|
12006
|
+
action: request.action,
|
|
12007
|
+
convention: request.convention,
|
|
12008
|
+
...request.payload === void 0 ? {} : { payload: request.payload }
|
|
12009
|
+
});
|
|
12010
|
+
const behavior = request.behavior === void 0 ? void 0 : Object.freeze({
|
|
12011
|
+
...request.behavior.focus === void 0 ? {} : { focus: request.behavior.focus },
|
|
12012
|
+
...request.behavior.reuse === void 0 ? {} : { reuse: request.behavior.reuse }
|
|
12013
|
+
});
|
|
12014
|
+
const params = Object.freeze({
|
|
12015
|
+
handler: selected.candidate.dTag,
|
|
12016
|
+
delivery,
|
|
12017
|
+
...behavior === void 0 ? {} : { behavior }
|
|
12018
|
+
});
|
|
12019
|
+
let retained;
|
|
12020
|
+
try {
|
|
12021
|
+
retained = await targets.retain(params);
|
|
12022
|
+
} catch {
|
|
12023
|
+
return { result: reject("invoke rejected") };
|
|
12024
|
+
}
|
|
12025
|
+
if (!retained || typeof retained.start !== "function") {
|
|
12026
|
+
return { result: reject("invoke rejected") };
|
|
12027
|
+
}
|
|
12028
|
+
return {
|
|
12029
|
+
result: {
|
|
12030
|
+
ok: true,
|
|
12031
|
+
archetype: request.archetype,
|
|
12032
|
+
action: request.action,
|
|
12033
|
+
convention: request.convention,
|
|
12034
|
+
handler: selected.candidate.dTag
|
|
12035
|
+
},
|
|
12036
|
+
retained
|
|
12037
|
+
};
|
|
12038
|
+
}
|
|
12039
|
+
async function handlers() {
|
|
12040
|
+
const catalog = await loadCatalog();
|
|
12041
|
+
const archetypes = /* @__PURE__ */ new Set();
|
|
12042
|
+
for (const entry of catalog) {
|
|
12043
|
+
for (const slug of Object.keys(entry.archetypes)) archetypes.add(slug);
|
|
12044
|
+
}
|
|
12045
|
+
return Promise.all([...archetypes].map((archetype) => availabilityFor(archetype)));
|
|
12046
|
+
}
|
|
12047
|
+
return {
|
|
12048
|
+
invoke,
|
|
12049
|
+
available: availabilityFor,
|
|
12050
|
+
handlers,
|
|
12051
|
+
onChanged(listener) {
|
|
12052
|
+
listeners.add(listener);
|
|
12053
|
+
return () => listeners.delete(listener);
|
|
11155
12054
|
},
|
|
11156
|
-
|
|
11157
|
-
|
|
12055
|
+
notifyChanged(archetype) {
|
|
12056
|
+
if (listeners.size === 0) return;
|
|
12057
|
+
void availabilityFor(archetype).then((availability) => {
|
|
12058
|
+
for (const listener of listeners) listener(availability);
|
|
12059
|
+
});
|
|
11158
12060
|
}
|
|
11159
12061
|
};
|
|
11160
12062
|
}
|
|
12063
|
+
function actionFromConvention(slug, convention) {
|
|
12064
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
|
|
12065
|
+
throw new TypeError("manifest archetype slug is invalid");
|
|
12066
|
+
}
|
|
12067
|
+
const match = /^napplet:([^/?#\s]+)\/([^/?#\s]+)$/.exec(convention);
|
|
12068
|
+
if (!match || match[1] !== slug) {
|
|
12069
|
+
throw new TypeError("manifest archetype convention is invalid or mismatched");
|
|
12070
|
+
}
|
|
12071
|
+
return match[2];
|
|
12072
|
+
}
|
|
12073
|
+
function manifestToIntentCatalogEntry(manifest) {
|
|
12074
|
+
const archetypes = {};
|
|
12075
|
+
for (const { slug, convention, eventKinds } of manifest.archetypes) {
|
|
12076
|
+
const action = actionFromConvention(slug, convention);
|
|
12077
|
+
if (eventKinds?.some((kind) => !Number.isSafeInteger(kind) || kind < 0)) {
|
|
12078
|
+
throw new TypeError("manifest archetype event kinds must be unsigned safe integers");
|
|
12079
|
+
}
|
|
12080
|
+
const support = archetypes[slug] ??= {
|
|
12081
|
+
actions: [],
|
|
12082
|
+
conventions: [],
|
|
12083
|
+
contracts: []
|
|
12084
|
+
};
|
|
12085
|
+
const contract = {
|
|
12086
|
+
convention,
|
|
12087
|
+
...eventKinds === void 0 || eventKinds.length === 0 ? {} : { eventKinds: [...eventKinds] }
|
|
12088
|
+
};
|
|
12089
|
+
support.contracts.push(contract);
|
|
12090
|
+
if (!support.actions.includes(action)) support.actions.push(action);
|
|
12091
|
+
if (!support.conventions.includes(convention)) support.conventions.push(convention);
|
|
12092
|
+
}
|
|
12093
|
+
return {
|
|
12094
|
+
dTag: manifest.dTag,
|
|
12095
|
+
...manifest.title === void 0 ? {} : { title: manifest.title },
|
|
12096
|
+
archetypes
|
|
12097
|
+
};
|
|
12098
|
+
}
|
|
11161
12099
|
var CVM_SERVICE_VERSION = "1.0.0";
|
|
11162
12100
|
var CVM_DESCRIPTOR = {
|
|
11163
12101
|
name: "cvm",
|
|
@@ -12148,6 +13086,200 @@ function createDevWebrtcController() {
|
|
|
12148
13086
|
};
|
|
12149
13087
|
}
|
|
12150
13088
|
|
|
13089
|
+
// src/browser-intent-controller.ts
|
|
13090
|
+
var MAX_INTENT_DELIVERY_ATTEMPTS = 10;
|
|
13091
|
+
var BrowserIntentController = class {
|
|
13092
|
+
constructor(options) {
|
|
13093
|
+
this.options = options;
|
|
13094
|
+
this.maxAttempts = normalizeAttempts(options.maxAttempts);
|
|
13095
|
+
}
|
|
13096
|
+
options;
|
|
13097
|
+
maxAttempts;
|
|
13098
|
+
retain(params) {
|
|
13099
|
+
const retained = freezeRetention(params);
|
|
13100
|
+
let started;
|
|
13101
|
+
let delivered = false;
|
|
13102
|
+
return {
|
|
13103
|
+
start: () => {
|
|
13104
|
+
started ??= this.start(retained, () => delivered, () => {
|
|
13105
|
+
delivered = true;
|
|
13106
|
+
});
|
|
13107
|
+
return started;
|
|
13108
|
+
}
|
|
13109
|
+
};
|
|
13110
|
+
}
|
|
13111
|
+
async start(params, isDelivered, markDelivered) {
|
|
13112
|
+
let reason = "no-current-target";
|
|
13113
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt += 1) {
|
|
13114
|
+
let generation;
|
|
13115
|
+
try {
|
|
13116
|
+
generation = await this.options.openOrReuse(params, attempt);
|
|
13117
|
+
} catch {
|
|
13118
|
+
reason = "open-failed";
|
|
13119
|
+
continue;
|
|
13120
|
+
}
|
|
13121
|
+
if (!generation) {
|
|
13122
|
+
reason = "open-failed";
|
|
13123
|
+
continue;
|
|
13124
|
+
}
|
|
13125
|
+
try {
|
|
13126
|
+
await this.options.waitForReady(generation);
|
|
13127
|
+
} catch {
|
|
13128
|
+
reason = "ready-failed";
|
|
13129
|
+
continue;
|
|
13130
|
+
}
|
|
13131
|
+
if (!await this.options.isCurrent(generation)) {
|
|
13132
|
+
reason = "no-current-target";
|
|
13133
|
+
continue;
|
|
13134
|
+
}
|
|
13135
|
+
if (isDelivered()) return;
|
|
13136
|
+
markDelivered();
|
|
13137
|
+
try {
|
|
13138
|
+
await this.options.send(generation, params.delivery);
|
|
13139
|
+
return;
|
|
13140
|
+
} catch {
|
|
13141
|
+
this.options.onTerminal?.(params, "send-failed");
|
|
13142
|
+
return;
|
|
13143
|
+
}
|
|
13144
|
+
}
|
|
13145
|
+
this.options.onTerminal?.(params, reason);
|
|
13146
|
+
}
|
|
13147
|
+
};
|
|
13148
|
+
function normalizeAttempts(value) {
|
|
13149
|
+
if (value === void 0) return 2;
|
|
13150
|
+
if (!Number.isFinite(value)) throw new TypeError("maxAttempts must be finite");
|
|
13151
|
+
return Math.min(MAX_INTENT_DELIVERY_ATTEMPTS, Math.max(1, Math.floor(value)));
|
|
13152
|
+
}
|
|
13153
|
+
function freezeRetention(params) {
|
|
13154
|
+
if (!params || typeof params.handler !== "string" || params.handler.length === 0) {
|
|
13155
|
+
throw new TypeError("Intent retention requires a handler");
|
|
13156
|
+
}
|
|
13157
|
+
const delivery = freezeValue({ ...params.delivery });
|
|
13158
|
+
const behavior = params.behavior === void 0 ? void 0 : freezeValue({ ...params.behavior });
|
|
13159
|
+
return Object.freeze({
|
|
13160
|
+
handler: params.handler,
|
|
13161
|
+
delivery,
|
|
13162
|
+
...behavior === void 0 ? {} : { behavior }
|
|
13163
|
+
});
|
|
13164
|
+
}
|
|
13165
|
+
function freezeValue(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
13166
|
+
if (typeof value !== "object" || value === null) return value;
|
|
13167
|
+
const existing = seen.get(value);
|
|
13168
|
+
if (existing !== void 0) return existing;
|
|
13169
|
+
if (Array.isArray(value)) {
|
|
13170
|
+
const copy2 = [];
|
|
13171
|
+
seen.set(value, copy2);
|
|
13172
|
+
for (const item of value) copy2.push(freezeValue(item, seen));
|
|
13173
|
+
return Object.freeze(copy2);
|
|
13174
|
+
}
|
|
13175
|
+
const copy = {};
|
|
13176
|
+
seen.set(value, copy);
|
|
13177
|
+
for (const [key, item] of Object.entries(value)) copy[key] = freezeValue(item, seen);
|
|
13178
|
+
return Object.freeze(copy);
|
|
13179
|
+
}
|
|
13180
|
+
|
|
13181
|
+
// src/installed-napplet-catalog.ts
|
|
13182
|
+
function matchesInstalledNappletRecord(record, target) {
|
|
13183
|
+
return target.dTag === record.dTag && target.aggregateHash === record.aggregateHash;
|
|
13184
|
+
}
|
|
13185
|
+
var InstalledNappletCatalog = class {
|
|
13186
|
+
records = /* @__PURE__ */ new Map();
|
|
13187
|
+
listeners = /* @__PURE__ */ new Set();
|
|
13188
|
+
/** Insert or replace a record from an already resolver-verified pointer. */
|
|
13189
|
+
install(resolved) {
|
|
13190
|
+
const record = freezeRecord({
|
|
13191
|
+
dTag: resolved.dTag,
|
|
13192
|
+
aggregateHash: resolved.aggregateHash,
|
|
13193
|
+
pointer: copyPointer(resolved.pointer),
|
|
13194
|
+
...resolved.manifest.title === void 0 ? {} : { title: resolved.manifest.title },
|
|
13195
|
+
archetypes: resolved.manifest.archetypes.map((archetype) => ({
|
|
13196
|
+
slug: archetype.slug,
|
|
13197
|
+
convention: archetype.convention,
|
|
13198
|
+
...archetype.eventKinds === void 0 ? {} : { eventKinds: [...archetype.eventKinds] }
|
|
13199
|
+
}))
|
|
13200
|
+
});
|
|
13201
|
+
this.records.set(record.dTag, record);
|
|
13202
|
+
this.notify(record.dTag);
|
|
13203
|
+
return record;
|
|
13204
|
+
}
|
|
13205
|
+
/** Remove an artifact explicitly; frame teardown never calls this method. */
|
|
13206
|
+
remove(dTag) {
|
|
13207
|
+
const removed = this.records.delete(dTag);
|
|
13208
|
+
if (removed) this.notify(dTag);
|
|
13209
|
+
return removed;
|
|
13210
|
+
}
|
|
13211
|
+
/** Return whether a verified installation exists for a d-tag. */
|
|
13212
|
+
has(dTag) {
|
|
13213
|
+
return this.records.has(dTag);
|
|
13214
|
+
}
|
|
13215
|
+
/** Return immutable serializable installed-artifact facts. */
|
|
13216
|
+
installed() {
|
|
13217
|
+
return [...this.records.values()];
|
|
13218
|
+
}
|
|
13219
|
+
/** Return a verified record by d-tag. */
|
|
13220
|
+
get(dTag) {
|
|
13221
|
+
return this.records.get(dTag);
|
|
13222
|
+
}
|
|
13223
|
+
/**
|
|
13224
|
+
* Atomically confirm that a resolved target still belongs to the exact record
|
|
13225
|
+
* selected before an async operation. The record object is the catalog version
|
|
13226
|
+
* token, so even a same-identity replacement cannot pass this check.
|
|
13227
|
+
*/
|
|
13228
|
+
validateCurrent(selected, target) {
|
|
13229
|
+
if (this.records.get(selected.dTag) !== selected) return null;
|
|
13230
|
+
return matchesInstalledNappletRecord(selected, target) ? selected : null;
|
|
13231
|
+
}
|
|
13232
|
+
/** Return exact handler candidates derived only from installed manifests. */
|
|
13233
|
+
intentCatalog() {
|
|
13234
|
+
return this.installed().map((record) => manifestToIntentCatalogEntry({
|
|
13235
|
+
dTag: record.dTag,
|
|
13236
|
+
...record.title === void 0 ? {} : { title: record.title },
|
|
13237
|
+
archetypes: record.archetypes.map((archetype) => ({
|
|
13238
|
+
slug: archetype.slug,
|
|
13239
|
+
convention: archetype.convention,
|
|
13240
|
+
...archetype.eventKinds === void 0 ? {} : { eventKinds: [...archetype.eventKinds] }
|
|
13241
|
+
}))
|
|
13242
|
+
}));
|
|
13243
|
+
}
|
|
13244
|
+
/** Subscribe to explicit install and artifact-removal changes. */
|
|
13245
|
+
onChanged(listener) {
|
|
13246
|
+
this.listeners.add(listener);
|
|
13247
|
+
return () => this.listeners.delete(listener);
|
|
13248
|
+
}
|
|
13249
|
+
notify(dTag) {
|
|
13250
|
+
for (const listener of this.listeners) listener(dTag);
|
|
13251
|
+
}
|
|
13252
|
+
};
|
|
13253
|
+
function copyPointer(pointer) {
|
|
13254
|
+
if (pointer.type === "naddr") {
|
|
13255
|
+
return Object.freeze({
|
|
13256
|
+
type: "naddr",
|
|
13257
|
+
value: pointer.value,
|
|
13258
|
+
identifier: pointer.identifier,
|
|
13259
|
+
pubkey: pointer.pubkey,
|
|
13260
|
+
kind: pointer.kind,
|
|
13261
|
+
relays: Object.freeze([...pointer.relays])
|
|
13262
|
+
});
|
|
13263
|
+
}
|
|
13264
|
+
return Object.freeze({
|
|
13265
|
+
type: "nevent",
|
|
13266
|
+
value: pointer.value,
|
|
13267
|
+
id: pointer.id,
|
|
13268
|
+
...pointer.author === void 0 ? {} : { author: pointer.author },
|
|
13269
|
+
...pointer.kind === void 0 ? {} : { kind: pointer.kind },
|
|
13270
|
+
relays: Object.freeze([...pointer.relays])
|
|
13271
|
+
});
|
|
13272
|
+
}
|
|
13273
|
+
function freezeRecord(record) {
|
|
13274
|
+
return Object.freeze({
|
|
13275
|
+
...record,
|
|
13276
|
+
archetypes: Object.freeze(record.archetypes.map((archetype) => Object.freeze({
|
|
13277
|
+
...archetype,
|
|
13278
|
+
...archetype.eventKinds === void 0 ? {} : { eventKinds: Object.freeze([...archetype.eventKinds]) }
|
|
13279
|
+
})))
|
|
13280
|
+
});
|
|
13281
|
+
}
|
|
13282
|
+
|
|
12151
13283
|
// src/simulation.ts
|
|
12152
13284
|
var PajaSimulationError = class extends Error {
|
|
12153
13285
|
constructor(message) {
|
|
@@ -12725,10 +13857,10 @@ var AbstractRelay = class {
|
|
|
12725
13857
|
this.challenge = void 0;
|
|
12726
13858
|
this.authPromise = void 0;
|
|
12727
13859
|
this.skipReconnection = false;
|
|
12728
|
-
this.connectionPromise = new Promise((resolve,
|
|
13860
|
+
this.connectionPromise = new Promise((resolve, reject2) => {
|
|
12729
13861
|
if (opts?.timeout) {
|
|
12730
13862
|
connectionTimeoutHandle = setTimeout(() => {
|
|
12731
|
-
|
|
13863
|
+
reject2("connection timed out");
|
|
12732
13864
|
this.connectionPromise = void 0;
|
|
12733
13865
|
this.skipReconnection = true;
|
|
12734
13866
|
this.onclose?.();
|
|
@@ -12736,13 +13868,13 @@ var AbstractRelay = class {
|
|
|
12736
13868
|
}, opts.timeout);
|
|
12737
13869
|
}
|
|
12738
13870
|
if (opts?.abort) {
|
|
12739
|
-
opts.abort.onabort =
|
|
13871
|
+
opts.abort.onabort = reject2;
|
|
12740
13872
|
}
|
|
12741
13873
|
try {
|
|
12742
13874
|
this.ws = new this._WebSocket(this.url);
|
|
12743
13875
|
} catch (err) {
|
|
12744
13876
|
clearTimeout(connectionTimeoutHandle);
|
|
12745
|
-
|
|
13877
|
+
reject2(err);
|
|
12746
13878
|
return;
|
|
12747
13879
|
}
|
|
12748
13880
|
this.ws.onopen = () => {
|
|
@@ -12772,7 +13904,7 @@ var AbstractRelay = class {
|
|
|
12772
13904
|
};
|
|
12773
13905
|
this.ws.onerror = () => {
|
|
12774
13906
|
clearTimeout(connectionTimeoutHandle);
|
|
12775
|
-
|
|
13907
|
+
reject2("connection failed");
|
|
12776
13908
|
this.connectionPromise = void 0;
|
|
12777
13909
|
this.skipReconnection = true;
|
|
12778
13910
|
this.onclose?.();
|
|
@@ -12780,7 +13912,7 @@ var AbstractRelay = class {
|
|
|
12780
13912
|
};
|
|
12781
13913
|
this.ws.onclose = (ev) => {
|
|
12782
13914
|
clearTimeout(connectionTimeoutHandle);
|
|
12783
|
-
|
|
13915
|
+
reject2(ev.message || "websocket closed");
|
|
12784
13916
|
this.handleHardClose("relay connection closed");
|
|
12785
13917
|
};
|
|
12786
13918
|
this.ws.onmessage = this._onmessage.bind(this);
|
|
@@ -12795,9 +13927,9 @@ var AbstractRelay = class {
|
|
|
12795
13927
|
});
|
|
12796
13928
|
}
|
|
12797
13929
|
waitForDummyReq() {
|
|
12798
|
-
return new Promise((resolve,
|
|
13930
|
+
return new Promise((resolve, reject2) => {
|
|
12799
13931
|
if (!this.connectionPromise)
|
|
12800
|
-
return
|
|
13932
|
+
return reject2(new Error(`no connection to ${this.url}, can't ping`));
|
|
12801
13933
|
try {
|
|
12802
13934
|
const sub = this.subscribe(
|
|
12803
13935
|
[{ ids: ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], limit: 0 }],
|
|
@@ -12814,7 +13946,7 @@ var AbstractRelay = class {
|
|
|
12814
13946
|
}
|
|
12815
13947
|
);
|
|
12816
13948
|
} catch (err) {
|
|
12817
|
-
|
|
13949
|
+
reject2(err);
|
|
12818
13950
|
}
|
|
12819
13951
|
});
|
|
12820
13952
|
}
|
|
@@ -12844,7 +13976,7 @@ var AbstractRelay = class {
|
|
|
12844
13976
|
throw new Error("can't perform auth, no challenge was received");
|
|
12845
13977
|
if (this.authPromise)
|
|
12846
13978
|
return this.authPromise;
|
|
12847
|
-
this.authPromise = new Promise(async (resolve,
|
|
13979
|
+
this.authPromise = new Promise(async (resolve, reject2) => {
|
|
12848
13980
|
try {
|
|
12849
13981
|
let evt = await signAuthEvent(makeAuthEvent(this.url, challenge2));
|
|
12850
13982
|
let timeout = setTimeout(() => {
|
|
@@ -12854,7 +13986,7 @@ var AbstractRelay = class {
|
|
|
12854
13986
|
this.openEventPublishes.delete(evt.id);
|
|
12855
13987
|
}
|
|
12856
13988
|
}, this.publishTimeout);
|
|
12857
|
-
this.openEventPublishes.set(evt.id, { resolve, reject, timeout });
|
|
13989
|
+
this.openEventPublishes.set(evt.id, { resolve, reject: reject2, timeout });
|
|
12858
13990
|
this.send('["AUTH",' + JSON.stringify(evt) + "]");
|
|
12859
13991
|
} catch (err) {
|
|
12860
13992
|
console.warn("subscribe auth function failed:", err);
|
|
@@ -12865,7 +13997,7 @@ var AbstractRelay = class {
|
|
|
12865
13997
|
async publish(event) {
|
|
12866
13998
|
this.idleSince = void 0;
|
|
12867
13999
|
this.ongoingOperations++;
|
|
12868
|
-
const ret = new Promise((resolve,
|
|
14000
|
+
const ret = new Promise((resolve, reject2) => {
|
|
12869
14001
|
const timeout = setTimeout(() => {
|
|
12870
14002
|
const ep = this.openEventPublishes.get(event.id);
|
|
12871
14003
|
if (ep) {
|
|
@@ -12873,7 +14005,7 @@ var AbstractRelay = class {
|
|
|
12873
14005
|
this.openEventPublishes.delete(event.id);
|
|
12874
14006
|
}
|
|
12875
14007
|
}, this.publishTimeout);
|
|
12876
|
-
this.openEventPublishes.set(event.id, { resolve, reject, timeout });
|
|
14008
|
+
this.openEventPublishes.set(event.id, { resolve, reject: reject2, timeout });
|
|
12877
14009
|
});
|
|
12878
14010
|
this.send('["EVENT",' + JSON.stringify(event) + "]");
|
|
12879
14011
|
this.ongoingOperations--;
|
|
@@ -12884,8 +14016,8 @@ var AbstractRelay = class {
|
|
|
12884
14016
|
async count(filters, params) {
|
|
12885
14017
|
this.serial++;
|
|
12886
14018
|
const id = params?.id || "count:" + this.serial;
|
|
12887
|
-
const ret = new Promise((resolve,
|
|
12888
|
-
this.openCountRequests.set(id, { resolve, reject });
|
|
14019
|
+
const ret = new Promise((resolve, reject2) => {
|
|
14020
|
+
this.openCountRequests.set(id, { resolve, reject: reject2 });
|
|
12889
14021
|
});
|
|
12890
14022
|
this.send('["COUNT","' + id + '",' + JSON.stringify(filters).substring(1));
|
|
12891
14023
|
return ret;
|
|
@@ -13472,9 +14604,8 @@ async function publishLive(pool, relayUrls, event) {
|
|
|
13472
14604
|
const results = await Promise.allSettled(pool.publish(relayUrls, event));
|
|
13473
14605
|
return Object.fromEntries(relayUrls.map((relayUrl, index) => [relayUrl, results[index]?.status === "fulfilled"]));
|
|
13474
14606
|
}
|
|
13475
|
-
function createPajaRelayBackend(getSimulation, confirmRequest) {
|
|
14607
|
+
function createPajaRelayBackend(getSimulation, confirmRequest, livePool = new SimplePool()) {
|
|
13476
14608
|
const events = getSimulation().relay.fixtures.flatMap(toNostrEvent);
|
|
13477
|
-
const livePool = new SimplePool();
|
|
13478
14609
|
const subscribers = /* @__PURE__ */ new Set();
|
|
13479
14610
|
const isAvailable = () => getSimulation().relay.mode !== "disabled" && getPajaRelayUrls(getSimulation()).length > 0;
|
|
13480
14611
|
const query = async (relayUrls, filters, maxWaitMs) => {
|
|
@@ -13485,6 +14616,28 @@ function createPajaRelayBackend(getSimulation, confirmRequest) {
|
|
|
13485
14616
|
for (const event of [...memoryEvents, ...liveEvents]) out.set(event.id, event);
|
|
13486
14617
|
return [...out.values()].sort((a, b) => b.created_at - a.created_at);
|
|
13487
14618
|
};
|
|
14619
|
+
function retainPublishedEvent(event) {
|
|
14620
|
+
events.push(event);
|
|
14621
|
+
for (const subscriber of subscribers) {
|
|
14622
|
+
if (matchesAnyFilter2(event, subscriber.filters)) subscriber.next(event);
|
|
14623
|
+
}
|
|
14624
|
+
}
|
|
14625
|
+
async function attemptPublish(relayUrls, event) {
|
|
14626
|
+
const simulation = getSimulation();
|
|
14627
|
+
const rejected = Object.fromEntries(relayUrls.map((url) => [url, false]));
|
|
14628
|
+
if (simulation.relay.mode === "disabled") {
|
|
14629
|
+
return { outcomes: rejected, error: "relay unavailable" };
|
|
14630
|
+
}
|
|
14631
|
+
if (!confirmRequest({ action: "publish", event })) {
|
|
14632
|
+
return { outcomes: rejected, error: "publish denied" };
|
|
14633
|
+
}
|
|
14634
|
+
const outcomes = simulation.relay.mode === "live" ? await publishLive(livePool, relayUrls, event) : Object.fromEntries(relayUrls.map((url) => [url, true]));
|
|
14635
|
+
if (!Object.values(outcomes).some(Boolean)) {
|
|
14636
|
+
return { outcomes, error: "publish failed" };
|
|
14637
|
+
}
|
|
14638
|
+
retainPublishedEvent(event);
|
|
14639
|
+
return { outcomes };
|
|
14640
|
+
}
|
|
13488
14641
|
const backend = {
|
|
13489
14642
|
subscription(relayUrls, filtersInput) {
|
|
13490
14643
|
return {
|
|
@@ -13509,16 +14662,9 @@ function createPajaRelayBackend(getSimulation, confirmRequest) {
|
|
|
13509
14662
|
}
|
|
13510
14663
|
};
|
|
13511
14664
|
},
|
|
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
|
-
}
|
|
14665
|
+
async publish(relayUrls, event) {
|
|
14666
|
+
const attempt = await attemptPublish(relayUrls, event);
|
|
14667
|
+
if (attempt.error) throw new Error(attempt.error);
|
|
13522
14668
|
},
|
|
13523
14669
|
request(relayUrls, filtersInput) {
|
|
13524
14670
|
return {
|
|
@@ -13538,14 +14684,7 @@ function createPajaRelayBackend(getSimulation, confirmRequest) {
|
|
|
13538
14684
|
},
|
|
13539
14685
|
query,
|
|
13540
14686
|
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);
|
|
14687
|
+
return (await attemptPublish(relayUrls, event)).outcomes;
|
|
13549
14688
|
},
|
|
13550
14689
|
isAvailable,
|
|
13551
14690
|
close() {
|
|
@@ -13666,7 +14805,6 @@ function createPajaIdentityProviders(backend, getSimulation, signerProvider) {
|
|
|
13666
14805
|
}
|
|
13667
14806
|
|
|
13668
14807
|
// src/browser-adapter.ts
|
|
13669
|
-
var DEV_INTENT_ARCHETYPE = "paja-target";
|
|
13670
14808
|
var DEV_COMMON_PUBKEY = "1".repeat(64);
|
|
13671
14809
|
var DEV_COMMON_EVENT_ID = "2".repeat(64);
|
|
13672
14810
|
var DEV_SIGNER_SECRET_KEY = generateSecretKey();
|
|
@@ -13693,10 +14831,14 @@ function createRelayHooks(pool, getSimulation) {
|
|
|
13693
14831
|
},
|
|
13694
14832
|
closeScopedRelay: () => {
|
|
13695
14833
|
},
|
|
13696
|
-
publishToScopedRelay: (_windowId, event) => {
|
|
14834
|
+
publishToScopedRelay: async (_windowId, event) => {
|
|
13697
14835
|
if (getSimulation().relay.mode === "disabled") return false;
|
|
13698
|
-
|
|
13699
|
-
|
|
14836
|
+
try {
|
|
14837
|
+
await pool.publish(getPajaRelayUrls(getSimulation()), event);
|
|
14838
|
+
return true;
|
|
14839
|
+
} catch {
|
|
14840
|
+
return false;
|
|
14841
|
+
}
|
|
13700
14842
|
},
|
|
13701
14843
|
selectRelayTier: () => getPajaRelayUrls(getSimulation())
|
|
13702
14844
|
};
|
|
@@ -13749,18 +14891,15 @@ function createDevUploader(getSimulation) {
|
|
|
13749
14891
|
}
|
|
13750
14892
|
};
|
|
13751
14893
|
}
|
|
13752
|
-
function
|
|
14894
|
+
function createDefaultIntentHost() {
|
|
13753
14895
|
return {
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
protocols: ["NAP-01"],
|
|
13762
|
-
isDefault: true
|
|
13763
|
-
}]
|
|
14896
|
+
catalog: new InstalledNappletCatalog(),
|
|
14897
|
+
controller: new BrowserIntentController({
|
|
14898
|
+
openOrReuse: () => null,
|
|
14899
|
+
waitForReady: () => void 0,
|
|
14900
|
+
isCurrent: () => false,
|
|
14901
|
+
send: () => void 0
|
|
14902
|
+
})
|
|
13764
14903
|
};
|
|
13765
14904
|
}
|
|
13766
14905
|
function createDevCvmTransport(getSimulation) {
|
|
@@ -13853,12 +14992,11 @@ function createRuntimeSigner(getSimulation, confirmRequest, signerProvider) {
|
|
|
13853
14992
|
}
|
|
13854
14993
|
return signer;
|
|
13855
14994
|
}
|
|
13856
|
-
function createDevServices(backend, getSimulation, onThemeService, confirmRequest, uploadRuntime, signerProvider) {
|
|
14995
|
+
function createDevServices(backend, getSimulation, onThemeService, onThemeBroadcast, confirmRequest, uploadRuntime, signerProvider, intentHost = createDefaultIntentHost()) {
|
|
13857
14996
|
const notification = createNotificationService({ maxPerWindow: 50 });
|
|
13858
14997
|
const theme = createThemeService({
|
|
13859
14998
|
initialTheme: createDevTheme(getSimulation().theme.mode, getSimulation().theme.values),
|
|
13860
|
-
onBroadcast:
|
|
13861
|
-
}
|
|
14999
|
+
onBroadcast: onThemeBroadcast
|
|
13862
15000
|
});
|
|
13863
15001
|
onThemeService(theme);
|
|
13864
15002
|
const config = createConfigService({
|
|
@@ -13938,26 +15076,15 @@ function createDevServices(backend, getSimulation, onThemeService, confirmReques
|
|
|
13938
15076
|
});
|
|
13939
15077
|
}
|
|
13940
15078
|
if (getSimulation().intent.enabled) {
|
|
15079
|
+
const resolver = createCatalogIntentResolver({
|
|
15080
|
+
loadCatalog: () => intentHost.catalog.intentCatalog(),
|
|
15081
|
+
targets: intentHost.controller,
|
|
15082
|
+
getDefaultHandler: intentHost.getDefaultHandler,
|
|
15083
|
+
chooseHandler: intentHost.chooseHandler,
|
|
15084
|
+
authorizeExplicitHandler: intentHost.authorizeExplicitHandler
|
|
15085
|
+
});
|
|
13941
15086
|
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
|
-
}
|
|
15087
|
+
resolver
|
|
13961
15088
|
});
|
|
13962
15089
|
}
|
|
13963
15090
|
if (getSimulation().capabilities.domains.link) {
|
|
@@ -13999,7 +15126,7 @@ function createDevServices(backend, getSimulation, onThemeService, confirmReques
|
|
|
13999
15126
|
}
|
|
14000
15127
|
return services;
|
|
14001
15128
|
}
|
|
14002
|
-
function createPajaAdapter(config, getSimulation, onThemeService, confirmRequest, signerProvider, getIdentity) {
|
|
15129
|
+
function createPajaAdapter(config, getSimulation, onThemeService, onThemeBroadcast, confirmRequest, signerProvider, getIdentity, onEnvironmentChanged, intentHost) {
|
|
14003
15130
|
const relayBackend = createPajaRelayBackend(getSimulation, confirmRequest);
|
|
14004
15131
|
const uploadRuntime = getSimulation().upload.mode === "blossom" ? createPajaUploadRuntime({
|
|
14005
15132
|
getSimulation,
|
|
@@ -14015,7 +15142,11 @@ function createPajaAdapter(config, getSimulation, onThemeService, confirmRequest
|
|
|
14015
15142
|
subscribeSignerChange: signerProvider?.subscribe?.bind(signerProvider)
|
|
14016
15143
|
}) : void 0;
|
|
14017
15144
|
void uploadRuntime?.refreshIdentity();
|
|
15145
|
+
signerProvider?.subscribe?.(() => {
|
|
15146
|
+
void uploadRuntime?.refreshIdentity().finally(() => onEnvironmentChanged?.());
|
|
15147
|
+
});
|
|
14018
15148
|
const workerRelayEvents = [];
|
|
15149
|
+
const resolvedIntentHost = intentHost ?? createDefaultIntentHost();
|
|
14019
15150
|
return {
|
|
14020
15151
|
relayPool: createRelayHooks(relayBackend, getSimulation),
|
|
14021
15152
|
relayConfig: {
|
|
@@ -14034,7 +15165,16 @@ function createPajaAdapter(config, getSimulation, onThemeService, confirmRequest
|
|
|
14034
15165
|
getUserPubkey: () => getRuntimePubkey(getSimulation, signerProvider),
|
|
14035
15166
|
getSigner: () => createRuntimeSigner(getSimulation, confirmRequest, signerProvider)
|
|
14036
15167
|
},
|
|
14037
|
-
services: createDevServices(
|
|
15168
|
+
services: createDevServices(
|
|
15169
|
+
relayBackend,
|
|
15170
|
+
getSimulation,
|
|
15171
|
+
onThemeService,
|
|
15172
|
+
onThemeBroadcast,
|
|
15173
|
+
confirmRequest,
|
|
15174
|
+
uploadRuntime,
|
|
15175
|
+
signerProvider,
|
|
15176
|
+
resolvedIntentHost
|
|
15177
|
+
),
|
|
14038
15178
|
get capabilities() {
|
|
14039
15179
|
return { disabledDomains: getSimulation().capabilities.disabledDomains };
|
|
14040
15180
|
},
|
|
@@ -14052,103 +15192,342 @@ function createPajaAdapter(config, getSimulation, onThemeService, confirmRequest
|
|
|
14052
15192
|
webrtc: { isAvailable: () => getSimulation().capabilities.domains.webrtc },
|
|
14053
15193
|
crypto: {
|
|
14054
15194
|
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
15195
|
}
|
|
14063
15196
|
};
|
|
14064
15197
|
}
|
|
14065
15198
|
|
|
14066
|
-
//
|
|
14067
|
-
var
|
|
14068
|
-
var
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
15199
|
+
// src/browser-devtools.ts
|
|
15200
|
+
var PAJA_LOG_LIMIT = 500;
|
|
15201
|
+
var proxyToReal = /* @__PURE__ */ new WeakMap();
|
|
15202
|
+
var realToProxy = /* @__PURE__ */ new WeakMap();
|
|
15203
|
+
var proxyContexts = /* @__PURE__ */ new WeakMap();
|
|
15204
|
+
function isNappletMessage(value) {
|
|
15205
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.type === "string";
|
|
14072
15206
|
}
|
|
14073
|
-
function
|
|
14074
|
-
|
|
15207
|
+
function describeMessage(raw) {
|
|
15208
|
+
if (isNappletMessage(raw)) return raw.type;
|
|
15209
|
+
if (Array.isArray(raw) && typeof raw[0] === "string") return raw[0];
|
|
15210
|
+
return "unknown";
|
|
14075
15211
|
}
|
|
14076
|
-
function
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
15212
|
+
function previewMessage(raw) {
|
|
15213
|
+
try {
|
|
15214
|
+
return JSON.stringify(raw);
|
|
15215
|
+
} catch (error) {
|
|
15216
|
+
return `unserializable:${error instanceof Error ? error.message : String(error)}`;
|
|
14081
15217
|
}
|
|
14082
|
-
return res;
|
|
14083
15218
|
}
|
|
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;
|
|
15219
|
+
function compactMessageDetail(raw) {
|
|
15220
|
+
const detail = {};
|
|
15221
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
15222
|
+
if (key !== "type" && key !== "id" && value !== void 0) detail[key] = value;
|
|
14094
15223
|
}
|
|
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
|
-
}
|
|
15224
|
+
if (Object.keys(detail).length === 0) return "";
|
|
15225
|
+
try {
|
|
15226
|
+
return JSON.stringify(detail);
|
|
15227
|
+
} catch {
|
|
15228
|
+
return "";
|
|
14118
15229
|
}
|
|
14119
|
-
return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 };
|
|
14120
15230
|
}
|
|
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;
|
|
15231
|
+
function describeMessageDetail(raw) {
|
|
15232
|
+
if (!isNappletMessage(raw)) return "";
|
|
15233
|
+
const value = raw;
|
|
15234
|
+
for (const key of ["error", "message", "reason"]) {
|
|
15235
|
+
const detail = value[key];
|
|
15236
|
+
if (typeof detail === "string" && detail.length > 0) return detail;
|
|
14149
15237
|
}
|
|
14150
|
-
|
|
14151
|
-
|
|
15238
|
+
return raw.type.endsWith(".error") ? compactMessageDetail(value) : "";
|
|
15239
|
+
}
|
|
15240
|
+
function domainEnabled(simulation, domain) {
|
|
15241
|
+
return simulation.capabilities.domains[domain] === true;
|
|
15242
|
+
}
|
|
15243
|
+
function getTargetIdentity(config) {
|
|
15244
|
+
return {
|
|
15245
|
+
pubkey: "",
|
|
15246
|
+
dTag: config.window.dTag,
|
|
15247
|
+
aggregateHash: config.window.aggregateHash
|
|
15248
|
+
};
|
|
15249
|
+
}
|
|
15250
|
+
function appendPajaMessageLog(state, direction, raw, windowId) {
|
|
15251
|
+
if (!state) return;
|
|
15252
|
+
state.messageLog.push({
|
|
15253
|
+
index: state.messageLog.length,
|
|
15254
|
+
timestamp: Date.now(),
|
|
15255
|
+
direction,
|
|
15256
|
+
type: describeMessage(raw),
|
|
15257
|
+
windowId,
|
|
15258
|
+
preview: previewMessage(raw),
|
|
15259
|
+
detail: describeMessageDetail(raw)
|
|
15260
|
+
});
|
|
15261
|
+
if (state.messageLog.length > PAJA_LOG_LIMIT) {
|
|
15262
|
+
state.messageLog.splice(0, state.messageLog.length - PAJA_LOG_LIMIT);
|
|
15263
|
+
}
|
|
15264
|
+
renderPajaMessageLog(state);
|
|
15265
|
+
}
|
|
15266
|
+
function createPajaPostMessageProxy(realWin, state, windowId) {
|
|
15267
|
+
const existing = realToProxy.get(realWin);
|
|
15268
|
+
if (existing) {
|
|
15269
|
+
proxyContexts.set(existing, { state, windowId });
|
|
15270
|
+
return existing;
|
|
15271
|
+
}
|
|
15272
|
+
const proxy = new Proxy(realWin, {
|
|
15273
|
+
get(target, prop) {
|
|
15274
|
+
if (prop === "postMessage") {
|
|
15275
|
+
return (msg, targetOrigin, transfer) => {
|
|
15276
|
+
const context = proxyContexts.get(proxy);
|
|
15277
|
+
appendPajaMessageLog(
|
|
15278
|
+
context?.state ?? state,
|
|
15279
|
+
"shell->napplet",
|
|
15280
|
+
msg,
|
|
15281
|
+
context?.windowId ?? windowId
|
|
15282
|
+
);
|
|
15283
|
+
return target.postMessage(msg, targetOrigin, transfer);
|
|
15284
|
+
};
|
|
15285
|
+
}
|
|
15286
|
+
try {
|
|
15287
|
+
const val = Reflect.get(target, prop, target);
|
|
15288
|
+
return typeof val === "function" ? val.bind(target) : val;
|
|
15289
|
+
} catch {
|
|
15290
|
+
return void 0;
|
|
15291
|
+
}
|
|
15292
|
+
}
|
|
15293
|
+
});
|
|
15294
|
+
proxyToReal.set(proxy, realWin);
|
|
15295
|
+
realToProxy.set(realWin, proxy);
|
|
15296
|
+
proxyContexts.set(proxy, { state, windowId });
|
|
15297
|
+
return proxy;
|
|
15298
|
+
}
|
|
15299
|
+
function installPajaOriginRegistryProxy(originRegistry2, stateRef) {
|
|
15300
|
+
const originalGetIframeWindow = originRegistry2.getIframeWindow.bind(originRegistry2);
|
|
15301
|
+
originRegistry2.getIframeWindow = (windowId) => {
|
|
15302
|
+
const win = originalGetIframeWindow(windowId);
|
|
15303
|
+
if (!win) return null;
|
|
15304
|
+
return createPajaPostMessageProxy(win, stateRef(), windowId);
|
|
15305
|
+
};
|
|
15306
|
+
const originalGetWindowId = originRegistry2.getWindowId.bind(originRegistry2);
|
|
15307
|
+
originRegistry2.getWindowId = (win) => {
|
|
15308
|
+
const result2 = originalGetWindowId(win);
|
|
15309
|
+
if (result2) return result2;
|
|
15310
|
+
const real = proxyToReal.get(win);
|
|
15311
|
+
return real ? originalGetWindowId(real) : void 0;
|
|
15312
|
+
};
|
|
15313
|
+
const originalGetIdentity = originRegistry2.getIdentity.bind(originRegistry2);
|
|
15314
|
+
originRegistry2.getIdentity = (win) => {
|
|
15315
|
+
const real = proxyToReal.get(win);
|
|
15316
|
+
return originalGetIdentity(win) ?? (real ? originalGetIdentity(real) : void 0);
|
|
15317
|
+
};
|
|
15318
|
+
const originalGetRegistrationId = originRegistry2.getRegistrationId.bind(originRegistry2);
|
|
15319
|
+
originRegistry2.getRegistrationId = (win) => {
|
|
15320
|
+
const real = proxyToReal.get(win);
|
|
15321
|
+
return originalGetRegistrationId(win) ?? (real ? originalGetRegistrationId(real) : void 0);
|
|
15322
|
+
};
|
|
15323
|
+
const originalGetEnvironment = originRegistry2.getEnvironment.bind(originRegistry2);
|
|
15324
|
+
originRegistry2.getEnvironment = (win) => {
|
|
15325
|
+
const real = proxyToReal.get(win);
|
|
15326
|
+
return originalGetEnvironment(win) ?? (real ? originalGetEnvironment(real) : void 0);
|
|
15327
|
+
};
|
|
15328
|
+
}
|
|
15329
|
+
function renderPajaDevtools(state, options) {
|
|
15330
|
+
renderInterfaceControls(state);
|
|
15331
|
+
renderAclControls(state, options.bridge);
|
|
15332
|
+
renderSignerStatus(state, options.devSignerPubkey);
|
|
15333
|
+
}
|
|
15334
|
+
function renderPajaMessageLog(state) {
|
|
15335
|
+
const container = document.getElementById("message-log");
|
|
15336
|
+
if (!container) return;
|
|
15337
|
+
const filter = state.messageFilter.trim().toLowerCase();
|
|
15338
|
+
const rows = state.messageLog.filter((entry) => filter.length === 0 || `${entry.direction} ${entry.type} ${entry.detail} ${entry.preview}`.toLowerCase().includes(filter)).slice(-120).map((entry) => {
|
|
15339
|
+
const row = document.createElement("div");
|
|
15340
|
+
row.className = "log-row";
|
|
15341
|
+
row.dataset.messageType = entry.type;
|
|
15342
|
+
if (entry.type.endsWith(".error")) row.dataset.error = "true";
|
|
15343
|
+
const dir = document.createElement("div");
|
|
15344
|
+
dir.className = "log-dir";
|
|
15345
|
+
dir.textContent = entry.direction;
|
|
15346
|
+
const body = document.createElement("div");
|
|
15347
|
+
body.className = "log-body";
|
|
15348
|
+
const type = document.createElement("div");
|
|
15349
|
+
type.className = "log-type";
|
|
15350
|
+
type.title = entry.preview;
|
|
15351
|
+
type.textContent = entry.type;
|
|
15352
|
+
body.append(type);
|
|
15353
|
+
if (entry.detail) {
|
|
15354
|
+
const detail = document.createElement("div");
|
|
15355
|
+
detail.className = "log-detail";
|
|
15356
|
+
detail.textContent = entry.detail;
|
|
15357
|
+
detail.title = entry.preview;
|
|
15358
|
+
body.append(detail);
|
|
15359
|
+
}
|
|
15360
|
+
row.append(dir, body);
|
|
15361
|
+
return row;
|
|
15362
|
+
});
|
|
15363
|
+
container.replaceChildren(...rows);
|
|
15364
|
+
container.scrollTop = container.scrollHeight;
|
|
15365
|
+
}
|
|
15366
|
+
function renderInterfaceControls(state) {
|
|
15367
|
+
const container = document.getElementById("interface-toggles");
|
|
15368
|
+
if (!container) return;
|
|
15369
|
+
container.replaceChildren(...PAJA_SIMULATION_DOMAINS.map((domain) => {
|
|
15370
|
+
const enabled = domainEnabled(state.simulation, domain);
|
|
15371
|
+
const button = document.createElement("button");
|
|
15372
|
+
button.type = "button";
|
|
15373
|
+
button.className = "toggle";
|
|
15374
|
+
button.dataset.interfaceDomain = domain;
|
|
15375
|
+
button.dataset.enabled = String(enabled);
|
|
15376
|
+
button.textContent = domain;
|
|
15377
|
+
button.title = `${domain} injection ${enabled ? "enabled" : "disabled"}`;
|
|
15378
|
+
button.addEventListener("click", () => {
|
|
15379
|
+
state.setDomainEnabled(domain, !domainEnabled(state.simulation, domain));
|
|
15380
|
+
});
|
|
15381
|
+
return button;
|
|
15382
|
+
}));
|
|
15383
|
+
}
|
|
15384
|
+
function renderAclControls(state, bridge) {
|
|
15385
|
+
const container = document.getElementById("acl-controls");
|
|
15386
|
+
if (!container) return;
|
|
15387
|
+
const identity = getTargetIdentity(state.config);
|
|
15388
|
+
container.replaceChildren(...ALL_CAPABILITIES.map((capability) => {
|
|
15389
|
+
const allowed2 = bridge?.runtime.aclState.check(
|
|
15390
|
+
identity.pubkey,
|
|
15391
|
+
identity.dTag,
|
|
15392
|
+
identity.aggregateHash,
|
|
15393
|
+
capability
|
|
15394
|
+
) ?? true;
|
|
15395
|
+
const button = document.createElement("button");
|
|
15396
|
+
button.type = "button";
|
|
15397
|
+
button.className = "toggle";
|
|
15398
|
+
button.dataset.aclCapability = capability;
|
|
15399
|
+
button.dataset.enabled = String(allowed2);
|
|
15400
|
+
button.textContent = capability;
|
|
15401
|
+
button.title = `${capability} ${allowed2 ? "allowed" : "blocked"}`;
|
|
15402
|
+
button.addEventListener("click", () => state.setAclCapability(capability, !allowed2));
|
|
15403
|
+
return button;
|
|
15404
|
+
}));
|
|
15405
|
+
}
|
|
15406
|
+
function renderSignerStatus(state, signerPubkey) {
|
|
15407
|
+
const el = document.getElementById("signer-status");
|
|
15408
|
+
const controls = document.getElementById("signer-controls");
|
|
15409
|
+
if (!el || !controls) return;
|
|
15410
|
+
const fallbackPubkey = state.simulation.identity.pubkey || (state.signer.method === "dev" ? signerPubkey : "");
|
|
15411
|
+
const pubkey = state.simulation.identity.pubkey || state.signer.pubkey || (state.signer.method === "dev" ? signerPubkey : "");
|
|
15412
|
+
const methodLabel = state.signer.method === "nip07" ? "NIP-07" : state.signer.method === "nip46" ? "bunker" : state.signer.method === "dev" ? "dev" : "none";
|
|
15413
|
+
const relay = state.signer.relay ? ` \xB7 relay ${state.signer.relay}` : "";
|
|
15414
|
+
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}`;
|
|
15415
|
+
el.textContent = `${prefix} \xB7 every sign/publish request prompts`;
|
|
15416
|
+
const previousInput = controls.querySelector("#signer-bunker-uri")?.value ?? "";
|
|
15417
|
+
const devButton = document.createElement("button");
|
|
15418
|
+
devButton.type = "button";
|
|
15419
|
+
devButton.id = "signer-dev";
|
|
15420
|
+
devButton.dataset.active = String(state.signer.method === "dev");
|
|
15421
|
+
devButton.textContent = "Dev";
|
|
15422
|
+
devButton.addEventListener("click", () => state.useDevSigner());
|
|
15423
|
+
const nip07Button = document.createElement("button");
|
|
15424
|
+
nip07Button.type = "button";
|
|
15425
|
+
nip07Button.id = "signer-nip07";
|
|
15426
|
+
nip07Button.dataset.active = String(state.signer.method === "nip07");
|
|
15427
|
+
nip07Button.textContent = "NIP-07";
|
|
15428
|
+
nip07Button.addEventListener("click", () => void state.connectNip07());
|
|
15429
|
+
const bunkerInput = document.createElement("input");
|
|
15430
|
+
bunkerInput.id = "signer-bunker-uri";
|
|
15431
|
+
bunkerInput.type = "text";
|
|
15432
|
+
bunkerInput.autocomplete = "off";
|
|
15433
|
+
bunkerInput.placeholder = "bunker://...";
|
|
15434
|
+
bunkerInput.setAttribute("aria-label", "Bunker connection URI");
|
|
15435
|
+
bunkerInput.value = previousInput;
|
|
15436
|
+
const bunkerButton = document.createElement("button");
|
|
15437
|
+
bunkerButton.type = "button";
|
|
15438
|
+
bunkerButton.id = "signer-bunker";
|
|
15439
|
+
bunkerButton.dataset.active = String(state.signer.method === "nip46");
|
|
15440
|
+
bunkerButton.textContent = "Bunker";
|
|
15441
|
+
bunkerButton.addEventListener("click", () => void state.connectBunker(bunkerInput.value));
|
|
15442
|
+
controls.replaceChildren(devButton, nip07Button, bunkerInput, bunkerButton);
|
|
15443
|
+
}
|
|
15444
|
+
|
|
15445
|
+
// ../../node_modules/.pnpm/@noble+ciphers@2.1.1/node_modules/@noble/ciphers/aes.js
|
|
15446
|
+
var BLOCK_SIZE = 16;
|
|
15447
|
+
var POLY = 283;
|
|
15448
|
+
function validateKeyLength(key) {
|
|
15449
|
+
if (![16, 24, 32].includes(key.length))
|
|
15450
|
+
throw new Error('"aes key" expected Uint8Array of length 16/24/32, got length=' + key.length);
|
|
15451
|
+
}
|
|
15452
|
+
function mul2(n) {
|
|
15453
|
+
return n << 1 ^ POLY & -(n >> 7);
|
|
15454
|
+
}
|
|
15455
|
+
function mul(a, b) {
|
|
15456
|
+
let res = 0;
|
|
15457
|
+
for (; b > 0; b >>= 1) {
|
|
15458
|
+
res ^= a & -(b & 1);
|
|
15459
|
+
a = mul2(a);
|
|
15460
|
+
}
|
|
15461
|
+
return res;
|
|
15462
|
+
}
|
|
15463
|
+
var sbox = /* @__PURE__ */ (() => {
|
|
15464
|
+
const t = new Uint8Array(256);
|
|
15465
|
+
for (let i4 = 0, x = 1; i4 < 256; i4++, x ^= mul2(x))
|
|
15466
|
+
t[i4] = x;
|
|
15467
|
+
const box = new Uint8Array(256);
|
|
15468
|
+
box[0] = 99;
|
|
15469
|
+
for (let i4 = 0; i4 < 255; i4++) {
|
|
15470
|
+
let x = t[255 - i4];
|
|
15471
|
+
x |= x << 8;
|
|
15472
|
+
box[t[i4]] = (x ^ x >> 4 ^ x >> 5 ^ x >> 6 ^ x >> 7 ^ 99) & 255;
|
|
15473
|
+
}
|
|
15474
|
+
clean2(t);
|
|
15475
|
+
return box;
|
|
15476
|
+
})();
|
|
15477
|
+
var invSbox = /* @__PURE__ */ sbox.map((_, j) => sbox.indexOf(j));
|
|
15478
|
+
var rotr32_8 = (n) => n << 24 | n >>> 8;
|
|
15479
|
+
var rotl32_8 = (n) => n << 8 | n >>> 24;
|
|
15480
|
+
function genTtable(sbox2, fn) {
|
|
15481
|
+
if (sbox2.length !== 256)
|
|
15482
|
+
throw new Error("Wrong sbox length");
|
|
15483
|
+
const T0 = new Uint32Array(256).map((_, j) => fn(sbox2[j]));
|
|
15484
|
+
const T1 = T0.map(rotl32_8);
|
|
15485
|
+
const T2 = T1.map(rotl32_8);
|
|
15486
|
+
const T3 = T2.map(rotl32_8);
|
|
15487
|
+
const T01 = new Uint32Array(256 * 256);
|
|
15488
|
+
const T23 = new Uint32Array(256 * 256);
|
|
15489
|
+
const sbox22 = new Uint16Array(256 * 256);
|
|
15490
|
+
for (let i4 = 0; i4 < 256; i4++) {
|
|
15491
|
+
for (let j = 0; j < 256; j++) {
|
|
15492
|
+
const idx = i4 * 256 + j;
|
|
15493
|
+
T01[idx] = T0[i4] ^ T1[j];
|
|
15494
|
+
T23[idx] = T2[i4] ^ T3[j];
|
|
15495
|
+
sbox22[idx] = sbox2[i4] << 8 | sbox2[j];
|
|
15496
|
+
}
|
|
15497
|
+
}
|
|
15498
|
+
return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 };
|
|
15499
|
+
}
|
|
15500
|
+
var tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => mul(s, 3) << 24 | s << 16 | s << 8 | mul(s, 2));
|
|
15501
|
+
var tableDecoding = /* @__PURE__ */ genTtable(invSbox, (s) => mul(s, 11) << 24 | mul(s, 13) << 16 | mul(s, 9) << 8 | mul(s, 14));
|
|
15502
|
+
var xPowers = /* @__PURE__ */ (() => {
|
|
15503
|
+
const p = new Uint8Array(16);
|
|
15504
|
+
for (let i4 = 0, x = 1; i4 < 16; i4++, x = mul2(x))
|
|
15505
|
+
p[i4] = x;
|
|
15506
|
+
return p;
|
|
15507
|
+
})();
|
|
15508
|
+
function expandKeyLE(key) {
|
|
15509
|
+
abytes3(key);
|
|
15510
|
+
const len = key.length;
|
|
15511
|
+
validateKeyLength(key);
|
|
15512
|
+
const { sbox2 } = tableEncoding;
|
|
15513
|
+
const toClean = [];
|
|
15514
|
+
if (!isAligned32(key))
|
|
15515
|
+
toClean.push(key = copyBytes2(key));
|
|
15516
|
+
const k32 = u32(key);
|
|
15517
|
+
const Nk = k32.length;
|
|
15518
|
+
const subByte = (n) => applySbox(sbox2, n, n, n, n);
|
|
15519
|
+
const xk = new Uint32Array(len + 28);
|
|
15520
|
+
xk.set(k32);
|
|
15521
|
+
for (let i4 = Nk; i4 < xk.length; i4++) {
|
|
15522
|
+
let t = xk[i4 - 1];
|
|
15523
|
+
if (i4 % Nk === 0)
|
|
15524
|
+
t = subByte(rotr32_8(t)) ^ xPowers[i4 / Nk - 1];
|
|
15525
|
+
else if (Nk > 6 && i4 % Nk === 4)
|
|
15526
|
+
t = subByte(t);
|
|
15527
|
+
xk[i4] = xk[i4 - Nk] ^ t;
|
|
15528
|
+
}
|
|
15529
|
+
clean2(...toClean);
|
|
15530
|
+
return xk;
|
|
14152
15531
|
}
|
|
14153
15532
|
function expandKeyDecLE(key) {
|
|
14154
15533
|
const encKey = expandKeyLE(key);
|
|
@@ -16890,12 +18269,12 @@ async function sendRequestWithId(state, correlationId, method, params) {
|
|
|
16890
18269
|
tags: [["p", state.bunkerPubkey]],
|
|
16891
18270
|
content: encryptedContent
|
|
16892
18271
|
}, state.localSecretKey);
|
|
16893
|
-
return new Promise((resolve,
|
|
18272
|
+
return new Promise((resolve, reject2) => {
|
|
16894
18273
|
const timer = setTimeout(() => {
|
|
16895
18274
|
state.pending.delete(correlationId);
|
|
16896
|
-
|
|
18275
|
+
reject2(new Error(`NIP-46 request timed out: ${method}`));
|
|
16897
18276
|
}, state.timeout);
|
|
16898
|
-
state.pending.set(correlationId, { resolve, reject, timer });
|
|
18277
|
+
state.pending.set(correlationId, { resolve, reject: reject2, timer });
|
|
16899
18278
|
state.ws.send(JSON.stringify(["EVENT", signedEvent]));
|
|
16900
18279
|
});
|
|
16901
18280
|
}
|
|
@@ -16934,7 +18313,7 @@ function rejectPendingOnClose(state) {
|
|
|
16934
18313
|
state.pending.clear();
|
|
16935
18314
|
}
|
|
16936
18315
|
function openWebSocket(state) {
|
|
16937
|
-
return new Promise((resolve,
|
|
18316
|
+
return new Promise((resolve, reject2) => {
|
|
16938
18317
|
state.ws = new WebSocket(state.relayUrl);
|
|
16939
18318
|
state.ws.onopen = () => {
|
|
16940
18319
|
state.ws.send(JSON.stringify([
|
|
@@ -16948,7 +18327,7 @@ function openWebSocket(state) {
|
|
|
16948
18327
|
handleRelayMessage(state, event.data).catch(() => {
|
|
16949
18328
|
});
|
|
16950
18329
|
};
|
|
16951
|
-
state.ws.onerror = () =>
|
|
18330
|
+
state.ws.onerror = () => reject2(new Error(`NIP-46 WebSocket connection failed: ${state.relayUrl}`));
|
|
16952
18331
|
state.ws.onclose = () => rejectPendingOnClose(state);
|
|
16953
18332
|
});
|
|
16954
18333
|
}
|
|
@@ -17123,245 +18502,77 @@ function createPajaSignerController(options) {
|
|
|
17123
18502
|
};
|
|
17124
18503
|
}
|
|
17125
18504
|
|
|
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;
|
|
18505
|
+
// src/browser-host-signer.ts
|
|
18506
|
+
function confirmPajaRequest(state, request) {
|
|
18507
|
+
if (request.action === "upload") {
|
|
18508
|
+
const filename = request.filename ?? "(unnamed blob)";
|
|
18509
|
+
const mimeType = request.mimeType ?? "application/octet-stream";
|
|
18510
|
+
const allowed3 = window.confirm([
|
|
18511
|
+
"Paja upload request",
|
|
18512
|
+
`napplet: ${request.napplet.dTag} (${request.windowId})`,
|
|
18513
|
+
`file: ${filename}`,
|
|
18514
|
+
`size: ${request.size} bytes`,
|
|
18515
|
+
`type: ${mimeType}`,
|
|
18516
|
+
`server: ${request.server}`,
|
|
18517
|
+
request.warning
|
|
18518
|
+
].join("\n"));
|
|
18519
|
+
appendPajaMessageLog(state, "paja", {
|
|
18520
|
+
type: `paja.upload.${allowed3 ? "confirmed" : "denied"}`,
|
|
18521
|
+
windowId: request.windowId,
|
|
18522
|
+
dTag: request.napplet.dTag,
|
|
18523
|
+
aggregateHash: request.napplet.aggregateHash,
|
|
18524
|
+
filename,
|
|
18525
|
+
size: request.size,
|
|
18526
|
+
mimeType,
|
|
18527
|
+
server: request.server,
|
|
18528
|
+
warning: request.warning
|
|
18529
|
+
});
|
|
18530
|
+
return allowed3;
|
|
17198
18531
|
}
|
|
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
|
-
}
|
|
18532
|
+
const event = request.event;
|
|
18533
|
+
const kind = typeof event.kind === "number" ? event.kind : "unknown";
|
|
18534
|
+
const content = typeof event.content === "string" && event.content.length > 0 ? `
|
|
18535
|
+
|
|
18536
|
+
${event.content.slice(0, 240)}` : "";
|
|
18537
|
+
const allowed2 = window.confirm(`Paja ${request.action} request
|
|
18538
|
+
kind: ${kind}${content}`);
|
|
18539
|
+
appendPajaMessageLog(state, "paja", {
|
|
18540
|
+
type: `paja.${request.action}.${allowed2 ? "confirmed" : "denied"}`,
|
|
18541
|
+
kind
|
|
17220
18542
|
});
|
|
17221
|
-
|
|
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);
|
|
18543
|
+
return allowed2;
|
|
17255
18544
|
}
|
|
17256
|
-
function
|
|
17257
|
-
|
|
17258
|
-
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
|
|
17263
|
-
|
|
17264
|
-
|
|
17265
|
-
|
|
17266
|
-
|
|
17267
|
-
|
|
17268
|
-
|
|
17269
|
-
|
|
17270
|
-
|
|
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);
|
|
18545
|
+
function createHostSignerController(getState, refreshState) {
|
|
18546
|
+
return createPajaSignerController({
|
|
18547
|
+
confirmRequest: (request) => confirmPajaRequest(getState(), request),
|
|
18548
|
+
onChange(signer) {
|
|
18549
|
+
const state = getState();
|
|
18550
|
+
if (!state) return;
|
|
18551
|
+
state.signer = signer;
|
|
18552
|
+
appendPajaMessageLog(state, "paja", {
|
|
18553
|
+
type: `paja.signer.${signer.method}.${signer.status}`,
|
|
18554
|
+
pubkey: signer.pubkey,
|
|
18555
|
+
relay: signer.relay,
|
|
18556
|
+
error: signer.error
|
|
18557
|
+
});
|
|
18558
|
+
refreshState(state);
|
|
18559
|
+
if (signer.status === "connected") state.reload();
|
|
17281
18560
|
}
|
|
17282
|
-
row.append(dir, body);
|
|
17283
|
-
return row;
|
|
17284
18561
|
});
|
|
17285
|
-
container.replaceChildren(...rows);
|
|
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
|
-
}));
|
|
17305
|
-
}
|
|
17306
|
-
function renderAclControls(state, bridge) {
|
|
17307
|
-
const container = document.getElementById("acl-controls");
|
|
17308
|
-
if (!container) return;
|
|
17309
|
-
const identity = getTargetIdentity(state.config);
|
|
17310
|
-
container.replaceChildren(...ALL_CAPABILITIES.map((capability) => {
|
|
17311
|
-
const allowed2 = bridge?.runtime.aclState.check(
|
|
17312
|
-
identity.pubkey,
|
|
17313
|
-
identity.dTag,
|
|
17314
|
-
identity.aggregateHash,
|
|
17315
|
-
capability
|
|
17316
|
-
) ?? true;
|
|
17317
|
-
const button = document.createElement("button");
|
|
17318
|
-
button.type = "button";
|
|
17319
|
-
button.className = "toggle";
|
|
17320
|
-
button.dataset.aclCapability = capability;
|
|
17321
|
-
button.dataset.enabled = String(allowed2);
|
|
17322
|
-
button.textContent = capability;
|
|
17323
|
-
button.title = `${capability} ${allowed2 ? "allowed" : "blocked"}`;
|
|
17324
|
-
button.addEventListener("click", () => state.setAclCapability(capability, !allowed2));
|
|
17325
|
-
return button;
|
|
17326
|
-
}));
|
|
17327
18562
|
}
|
|
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);
|
|
18563
|
+
function hasNip07Signer() {
|
|
18564
|
+
const signer = globalThis.nostr;
|
|
18565
|
+
return typeof signer === "object" && signer !== null;
|
|
18566
|
+
}
|
|
18567
|
+
|
|
18568
|
+
// src/browser-host-runtime.ts
|
|
18569
|
+
function unregisterSingleFrameWindow(bridge, runtime, windowId) {
|
|
18570
|
+
if (!windowId) return;
|
|
18571
|
+
bridge.runtime.destroyWindow(windowId);
|
|
18572
|
+
bridge.runtime.sessionRegistry.unregister(windowId);
|
|
18573
|
+
originRegistry.unregister(windowId);
|
|
18574
|
+
runtime.readyWindowIds.delete(windowId);
|
|
18575
|
+
if (runtime.currentWindowId === windowId) runtime.currentWindowId = null;
|
|
17365
18576
|
}
|
|
17366
18577
|
|
|
17367
18578
|
// src/browser-runtime-tabs.ts
|
|
@@ -17374,6 +18585,9 @@ function resolvedTargetKey(target) {
|
|
|
17374
18585
|
target.aggregateHash
|
|
17375
18586
|
].join(":");
|
|
17376
18587
|
}
|
|
18588
|
+
function runtimeTabGenerationId(tab) {
|
|
18589
|
+
return `${tab.id}:${tab.generation}`;
|
|
18590
|
+
}
|
|
17377
18591
|
function getActiveTab(state) {
|
|
17378
18592
|
return state.tabs.find((tab) => tab.id === state.activeTabId) ?? null;
|
|
17379
18593
|
}
|
|
@@ -17619,11 +18833,13 @@ function resolvedTargetTitle(target) {
|
|
|
17619
18833
|
return title && title.length > 0 ? title : target.dTag || target.aggregateHash.slice(0, 12);
|
|
17620
18834
|
}
|
|
17621
18835
|
function destroyRuntimeTab(tab, context) {
|
|
18836
|
+
context.onTabDestroyed?.(tab);
|
|
17622
18837
|
if (tab.windowId) {
|
|
17623
18838
|
context.bridge.runtime.destroyWindow(tab.windowId);
|
|
17624
18839
|
context.bridge.runtime.sessionRegistry.unregister(tab.windowId);
|
|
17625
18840
|
originRegistry.unregister(tab.windowId);
|
|
17626
18841
|
context.runtime.readyWindowIds.delete(tab.windowId);
|
|
18842
|
+
if (context.runtime.currentWindowId === tab.windowId) context.runtime.currentWindowId = null;
|
|
17627
18843
|
}
|
|
17628
18844
|
tab.frame.remove();
|
|
17629
18845
|
}
|
|
@@ -17637,18 +18853,6 @@ function createRuntimeTabFrame(tab, state, context) {
|
|
|
17637
18853
|
frame.dataset.tabId = tab.id;
|
|
17638
18854
|
frame.dataset.targetUrl = tab.pointerValue;
|
|
17639
18855
|
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
18856
|
frame.addEventListener("error", () => {
|
|
17653
18857
|
tab.status = "error";
|
|
17654
18858
|
if (state.activeTabId === tab.id) context.setStatus(state, "error");
|
|
@@ -17662,14 +18866,18 @@ function startRuntimeTabNavigation(tab, state, context) {
|
|
|
17662
18866
|
context.setStatus(state, "booting");
|
|
17663
18867
|
renderRuntimeTabs(state);
|
|
17664
18868
|
void context.navigateFrame(
|
|
17665
|
-
context.bridge,
|
|
17666
18869
|
tab.frame,
|
|
17667
18870
|
context.config,
|
|
17668
18871
|
generation,
|
|
17669
|
-
context.
|
|
17670
|
-
context.runtime.currentSimulation,
|
|
18872
|
+
context.adapter,
|
|
17671
18873
|
tab.resolvedTarget,
|
|
17672
|
-
runtimeTabWindowId(context.config, tab)
|
|
18874
|
+
runtimeTabWindowId(context.config, tab),
|
|
18875
|
+
() => tab.generation === generation,
|
|
18876
|
+
(windowId) => {
|
|
18877
|
+
if (tab.generation !== generation) return;
|
|
18878
|
+
tab.windowId = windowId;
|
|
18879
|
+
if (state.activeTabId === tab.id) context.runtime.currentWindowId = windowId;
|
|
18880
|
+
}
|
|
17673
18881
|
).then((windowId) => {
|
|
17674
18882
|
if (tab.generation !== generation) return;
|
|
17675
18883
|
tab.windowId = windowId;
|
|
@@ -17692,13 +18900,6 @@ function runtimeTabWindowId(config, tab) {
|
|
|
17692
18900
|
return `${config.window.id}:${tab.id}:${tab.generation}`;
|
|
17693
18901
|
}
|
|
17694
18902
|
|
|
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
18903
|
// ../nip/dist/chunk-JT5WCCCX.js
|
|
17703
18904
|
function computeAggregateHash(entries) {
|
|
17704
18905
|
const lines = entries.map((entry) => `${entry.sha256} ${entry.path}
|
|
@@ -17724,7 +18925,7 @@ function aggregateTagValue(tags) {
|
|
|
17724
18925
|
return void 0;
|
|
17725
18926
|
}
|
|
17726
18927
|
|
|
17727
|
-
// ../nip/dist/chunk-
|
|
18928
|
+
// ../nip/dist/chunk-AGRSQG35.js
|
|
17728
18929
|
var MIB = 1024 * 1024;
|
|
17729
18930
|
var NAPPLET_ARTIFACT_CACHE_NAME = "kehto:napplet-artifacts:v1";
|
|
17730
18931
|
var SCHEMA_VERSION = 1;
|
|
@@ -18159,10 +19360,58 @@ function archetypesFromTags(tags) {
|
|
|
18159
19360
|
const out = [];
|
|
18160
19361
|
for (const tag of tags) {
|
|
18161
19362
|
if (tag[0] !== "archetype") continue;
|
|
18162
|
-
|
|
18163
|
-
|
|
18164
|
-
|
|
18165
|
-
|
|
19363
|
+
const slug = tag[1];
|
|
19364
|
+
if (typeof slug !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
|
|
19365
|
+
throw new NappletResolutionError(
|
|
19366
|
+
"invalid-manifest",
|
|
19367
|
+
"archetype slug must contain lowercase letters, numbers, and hyphens"
|
|
19368
|
+
);
|
|
19369
|
+
}
|
|
19370
|
+
const convention = tag[2];
|
|
19371
|
+
if (typeof convention !== "string" || convention.length === 0) {
|
|
19372
|
+
throw new NappletResolutionError("invalid-manifest", "archetype convention is required");
|
|
19373
|
+
}
|
|
19374
|
+
if (/^NAP-\d+$/.test(convention)) {
|
|
19375
|
+
throw new NappletResolutionError(
|
|
19376
|
+
"invalid-manifest",
|
|
19377
|
+
"numbered NAP identifier is not an archetype convention"
|
|
19378
|
+
);
|
|
19379
|
+
}
|
|
19380
|
+
const conventionMatch = /^napplet:([^/?#\s]+)\/([^/?#\s]+)$/.exec(convention);
|
|
19381
|
+
if (!conventionMatch) {
|
|
19382
|
+
throw new NappletResolutionError(
|
|
19383
|
+
"invalid-manifest",
|
|
19384
|
+
"archetype convention must be a queryless napplet:<archetype>/<intent> identity"
|
|
19385
|
+
);
|
|
19386
|
+
}
|
|
19387
|
+
if (conventionMatch[1] !== slug) {
|
|
19388
|
+
throw new NappletResolutionError(
|
|
19389
|
+
"invalid-manifest",
|
|
19390
|
+
"archetype slug must match the convention archetype"
|
|
19391
|
+
);
|
|
19392
|
+
}
|
|
19393
|
+
const eventKinds = tag.slice(3).map((value) => {
|
|
19394
|
+
const match = /^kind:(\d+)$/.exec(value);
|
|
19395
|
+
if (!match) {
|
|
19396
|
+
throw new NappletResolutionError(
|
|
19397
|
+
"invalid-manifest",
|
|
19398
|
+
"archetype trailing fields must use kind:<unsigned-integer>"
|
|
19399
|
+
);
|
|
19400
|
+
}
|
|
19401
|
+
const kind = Number(match[1]);
|
|
19402
|
+
if (!Number.isSafeInteger(kind) || kind < 0) {
|
|
19403
|
+
throw new NappletResolutionError(
|
|
19404
|
+
"invalid-manifest",
|
|
19405
|
+
"archetype event kinds must be unsigned safe integers"
|
|
19406
|
+
);
|
|
19407
|
+
}
|
|
19408
|
+
return kind;
|
|
19409
|
+
});
|
|
19410
|
+
out.push({
|
|
19411
|
+
slug,
|
|
19412
|
+
convention,
|
|
19413
|
+
...eventKinds.length === 0 ? {} : { eventKinds }
|
|
19414
|
+
});
|
|
18166
19415
|
}
|
|
18167
19416
|
return out;
|
|
18168
19417
|
}
|
|
@@ -18295,12 +19544,12 @@ async function resolvePajaPointer(value, options = {}) {
|
|
|
18295
19544
|
const pointer = decodePajaPointer(value);
|
|
18296
19545
|
const relays = uniqueRelayUrls([...pointer.relays, ...options.relays ?? []]);
|
|
18297
19546
|
if (relays.length === 0) {
|
|
18298
|
-
throw new Error("Pointer
|
|
19547
|
+
throw new Error("Pointer has no relay hints or enabled configured live relays.");
|
|
18299
19548
|
}
|
|
18300
19549
|
const pool = options.pool ?? new SimplePool();
|
|
18301
19550
|
const ownsPool = options.pool === void 0;
|
|
18302
19551
|
try {
|
|
18303
|
-
const event = await resolvePointerEvent(pointer, pool, relays, options.maxWaitMs
|
|
19552
|
+
const event = await resolvePointerEvent(pointer, pool, relays, normalizeMaxWaitMs(options.maxWaitMs));
|
|
18304
19553
|
const blossomServers = [...options.blossomServers ?? []];
|
|
18305
19554
|
const resolved = await resolveNapplet({
|
|
18306
19555
|
event,
|
|
@@ -18329,7 +19578,26 @@ async function resolvePajaPointer(value, options = {}) {
|
|
|
18329
19578
|
}
|
|
18330
19579
|
}
|
|
18331
19580
|
function injectPajaRuntimeCsp(html, origins) {
|
|
18332
|
-
const
|
|
19581
|
+
const grantedOrigins = [...new Set(origins)].sort();
|
|
19582
|
+
const connectSrc = grantedOrigins.length > 0 ? `connect-src ${grantedOrigins.join(" ")}` : "connect-src 'none'";
|
|
19583
|
+
const value = [
|
|
19584
|
+
"default-src 'none'",
|
|
19585
|
+
"script-src 'unsafe-inline'",
|
|
19586
|
+
"style-src 'unsafe-inline'",
|
|
19587
|
+
"img-src data: blob:",
|
|
19588
|
+
"font-src data:",
|
|
19589
|
+
connectSrc,
|
|
19590
|
+
"worker-src 'none'",
|
|
19591
|
+
"child-src 'none'",
|
|
19592
|
+
"frame-src 'none'",
|
|
19593
|
+
"media-src 'none'",
|
|
19594
|
+
"object-src 'none'",
|
|
19595
|
+
"manifest-src 'none'",
|
|
19596
|
+
"prefetch-src 'none'",
|
|
19597
|
+
"base-uri 'none'",
|
|
19598
|
+
"form-action 'none'",
|
|
19599
|
+
"frame-ancestors 'self'"
|
|
19600
|
+
].join("; ");
|
|
18333
19601
|
const meta = `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(value)}">`;
|
|
18334
19602
|
if (/<head[^>]*>/i.test(html)) {
|
|
18335
19603
|
return html.replace(/<head[^>]*>/i, (open) => `${open}${meta}`);
|
|
@@ -18341,19 +19609,96 @@ function injectPajaRuntimeCsp(html, origins) {
|
|
|
18341
19609
|
}
|
|
18342
19610
|
async function resolvePointerEvent(pointer, pool, relays, maxWaitMs) {
|
|
18343
19611
|
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];
|
|
19612
|
+
const result2 = await queryPointerRelays(pool, relays, filter, maxWaitMs);
|
|
19613
|
+
const event = result2.events.filter((candidate) => matchesPointer(pointer, candidate)).sort((a, b) => b.created_at - a.created_at)[0];
|
|
18349
19614
|
if (!event) {
|
|
18350
|
-
|
|
19615
|
+
if (result2.timedOut) {
|
|
19616
|
+
throw new Error(
|
|
19617
|
+
`Relay resolution timed out after ${maxWaitMs}ms for ${pointer.type} pointer across ${relays.length} relays before complete EOSE.`
|
|
19618
|
+
);
|
|
19619
|
+
}
|
|
19620
|
+
const failures = relayFailureReasons(result2.reasons);
|
|
19621
|
+
if (result2.error || failures.length > 0) {
|
|
19622
|
+
const detail = result2.error instanceof Error ? result2.error.message : result2.error === void 0 ? failures.join("; ") : String(result2.error);
|
|
19623
|
+
throw new Error(`Relay query failed for ${pointer.type} pointer before complete EOSE: ${detail}`);
|
|
19624
|
+
}
|
|
19625
|
+
throw new Error(
|
|
19626
|
+
`All ${relays.length} relays reached EOSE without a matching ${pointer.type} napplet manifest event.`
|
|
19627
|
+
);
|
|
18351
19628
|
}
|
|
18352
19629
|
if (!isNappletManifestKind(event.kind)) {
|
|
18353
19630
|
throw new Error(`Pointer resolved kind ${event.kind}; expected ${formatNappletManifestKinds()}.`);
|
|
18354
19631
|
}
|
|
18355
19632
|
return event;
|
|
18356
19633
|
}
|
|
19634
|
+
function queryPointerRelays(pool, relays, filter, maxWaitMs) {
|
|
19635
|
+
if (pool.subscribeManyEose) {
|
|
19636
|
+
return queryPointerRelaySubscription(pool, relays, filter, maxWaitMs);
|
|
19637
|
+
}
|
|
19638
|
+
return queryPointerRelaySync(pool, relays, filter, maxWaitMs);
|
|
19639
|
+
}
|
|
19640
|
+
function queryPointerRelaySubscription(pool, relays, filter, maxWaitMs) {
|
|
19641
|
+
return new Promise((resolve) => {
|
|
19642
|
+
const events = [];
|
|
19643
|
+
let settled = false;
|
|
19644
|
+
let subscription;
|
|
19645
|
+
const finish = (result2) => {
|
|
19646
|
+
if (settled) return;
|
|
19647
|
+
settled = true;
|
|
19648
|
+
clearTimeout(deadline);
|
|
19649
|
+
resolve({ events, ...result2 });
|
|
19650
|
+
};
|
|
19651
|
+
const deadline = setTimeout(() => {
|
|
19652
|
+
finish({ reasons: [], timedOut: true });
|
|
19653
|
+
void subscription?.close("Paja pointer relay deadline exceeded");
|
|
19654
|
+
}, maxWaitMs);
|
|
19655
|
+
try {
|
|
19656
|
+
subscription = pool.subscribeManyEose([...relays], filter, {
|
|
19657
|
+
maxWait: maxWaitMs,
|
|
19658
|
+
label: "kehto-paja-runtime",
|
|
19659
|
+
onevent(event) {
|
|
19660
|
+
events.push(event);
|
|
19661
|
+
},
|
|
19662
|
+
onclose(reasons) {
|
|
19663
|
+
finish({ reasons, timedOut: false });
|
|
19664
|
+
}
|
|
19665
|
+
});
|
|
19666
|
+
} catch (error) {
|
|
19667
|
+
finish({ reasons: [], timedOut: false, error });
|
|
19668
|
+
}
|
|
19669
|
+
});
|
|
19670
|
+
}
|
|
19671
|
+
function queryPointerRelaySync(pool, relays, filter, maxWaitMs) {
|
|
19672
|
+
return new Promise((resolve) => {
|
|
19673
|
+
let settled = false;
|
|
19674
|
+
const finish = (result2) => {
|
|
19675
|
+
if (settled) return;
|
|
19676
|
+
settled = true;
|
|
19677
|
+
clearTimeout(deadline);
|
|
19678
|
+
resolve(result2);
|
|
19679
|
+
};
|
|
19680
|
+
const deadline = setTimeout(() => {
|
|
19681
|
+
finish({ events: [], reasons: [], timedOut: true });
|
|
19682
|
+
}, maxWaitMs);
|
|
19683
|
+
void pool.querySync([...relays], filter, {
|
|
19684
|
+
maxWait: maxWaitMs,
|
|
19685
|
+
label: "kehto-paja-runtime"
|
|
19686
|
+
}).then(
|
|
19687
|
+
(events) => finish({ events, reasons: [], timedOut: false }),
|
|
19688
|
+
(error) => finish({ events: [], reasons: [], timedOut: false, error })
|
|
19689
|
+
);
|
|
19690
|
+
});
|
|
19691
|
+
}
|
|
19692
|
+
function relayFailureReasons(reasons) {
|
|
19693
|
+
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);
|
|
19694
|
+
}
|
|
19695
|
+
function normalizeMaxWaitMs(value) {
|
|
19696
|
+
if (value === void 0) return 5e3;
|
|
19697
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
19698
|
+
throw new Error("Pointer relay deadline must be a positive number of milliseconds.");
|
|
19699
|
+
}
|
|
19700
|
+
return Math.floor(value);
|
|
19701
|
+
}
|
|
18357
19702
|
function addressableFilter(pointer) {
|
|
18358
19703
|
if (pointer.kind !== NAPPLET_KIND_NAMED) {
|
|
18359
19704
|
throw new Error(`naddr kind ${pointer.kind} is not a named NIP-5D napplet manifest kind.`);
|
|
@@ -18413,7 +19758,185 @@ function formatNappletManifestKinds() {
|
|
|
18413
19758
|
return PAJA_NAPPLET_MANIFEST_KINDS.join(" / ");
|
|
18414
19759
|
}
|
|
18415
19760
|
|
|
19761
|
+
// src/browser-intent-host.ts
|
|
19762
|
+
function createPajaIntentTargetOptions(getState, getContext, effects = {}) {
|
|
19763
|
+
return {
|
|
19764
|
+
async openOrReuse(params) {
|
|
19765
|
+
const state = getState();
|
|
19766
|
+
const context = getContext();
|
|
19767
|
+
if (!state || !context) return null;
|
|
19768
|
+
const record = context.runtime.catalog.get(params.handler);
|
|
19769
|
+
if (!record || !recordSupportsDelivery(record, params)) return null;
|
|
19770
|
+
for (const stale of state.tabs) {
|
|
19771
|
+
if (stale.resolvedTarget.dTag === params.handler && !matchesInstalledNappletRecord(record, stale.resolvedTarget)) closeRuntimeTab(state, context, stale.id);
|
|
19772
|
+
}
|
|
19773
|
+
const current = state.tabs.find(
|
|
19774
|
+
(tab2) => matchesInstalledNappletRecord(record, tab2.resolvedTarget) && isCurrentRuntimeTabGeneration(state, context, tab2)
|
|
19775
|
+
);
|
|
19776
|
+
if (current && params.behavior?.reuse !== false) {
|
|
19777
|
+
return bindPajaIntentGeneration(current, record, context.runtime);
|
|
19778
|
+
}
|
|
19779
|
+
const resolved = await resolvePajaPointer(record.pointer.value, pajaPointerResolverOptions(context));
|
|
19780
|
+
if (resolved.dTag !== record.dTag || resolved.aggregateHash !== record.aggregateHash || !resolvedSupportsDelivery(resolved, params)) return null;
|
|
19781
|
+
const currentRecord = context.runtime.catalog.validateCurrent(record, resolved);
|
|
19782
|
+
if (!currentRecord) return null;
|
|
19783
|
+
const tab = addRuntimeTab(state, context, currentRecord.pointer.value, resolved);
|
|
19784
|
+
effects.persistTabs?.(state);
|
|
19785
|
+
return bindPajaIntentGeneration(tab, currentRecord, context.runtime);
|
|
19786
|
+
},
|
|
19787
|
+
waitForReady(generation) {
|
|
19788
|
+
const state = getState();
|
|
19789
|
+
const context = getContext();
|
|
19790
|
+
if (!state || !context) return Promise.reject(new Error("Paja host is not available"));
|
|
19791
|
+
const tab = findRuntimeTabGeneration(state, generation);
|
|
19792
|
+
if (!tab || !isCurrentPajaIntentGeneration(generation, state, context, tab)) {
|
|
19793
|
+
invalidatePajaIntentGeneration(generation, context.runtime);
|
|
19794
|
+
return Promise.reject(new Error("intent target generation is not current"));
|
|
19795
|
+
}
|
|
19796
|
+
if (tab.windowId && context.runtime.readyWindowIds.has(tab.windowId)) return void 0;
|
|
19797
|
+
return new Promise((resolve, reject2) => {
|
|
19798
|
+
const waiters = context.runtime.readyWaiters.get(generation) ?? /* @__PURE__ */ new Set();
|
|
19799
|
+
waiters.add({ resolve, reject: reject2 });
|
|
19800
|
+
context.runtime.readyWaiters.set(generation, waiters);
|
|
19801
|
+
});
|
|
19802
|
+
},
|
|
19803
|
+
isCurrent(generation) {
|
|
19804
|
+
const state = getState();
|
|
19805
|
+
const context = getContext();
|
|
19806
|
+
const tab = state && context ? findRuntimeTabGeneration(state, generation) : null;
|
|
19807
|
+
if (!tab || !state || !context || !isCurrentPajaIntentGeneration(generation, state, context, tab)) {
|
|
19808
|
+
if (context) invalidatePajaIntentGeneration(generation, context.runtime);
|
|
19809
|
+
return false;
|
|
19810
|
+
}
|
|
19811
|
+
return true;
|
|
19812
|
+
},
|
|
19813
|
+
send(generation, delivery) {
|
|
19814
|
+
const state = getState();
|
|
19815
|
+
const context = getContext();
|
|
19816
|
+
const tab = state && context ? findRuntimeTabGeneration(state, generation) : null;
|
|
19817
|
+
if (!state || !context || !tab || !tab.windowId || !isCurrentPajaIntentGeneration(generation, state, context, tab)) {
|
|
19818
|
+
if (context) invalidatePajaIntentGeneration(generation, context.runtime);
|
|
19819
|
+
throw new Error("intent target generation is not current and ready");
|
|
19820
|
+
}
|
|
19821
|
+
const source = tab.frame.contentWindow;
|
|
19822
|
+
if (!source || originRegistry.getWindowId(source) !== tab.windowId) {
|
|
19823
|
+
throw new Error("intent target source is no longer registered");
|
|
19824
|
+
}
|
|
19825
|
+
createPajaPostMessageProxy(source, state, tab.windowId).postMessage({
|
|
19826
|
+
type: "intent.deliver",
|
|
19827
|
+
delivery
|
|
19828
|
+
}, "*");
|
|
19829
|
+
}
|
|
19830
|
+
};
|
|
19831
|
+
}
|
|
19832
|
+
function pajaPointerResolverOptions(context) {
|
|
19833
|
+
return {
|
|
19834
|
+
relays: [
|
|
19835
|
+
...context.config.target.pointer?.relays ?? [],
|
|
19836
|
+
...getPajaRelayUrls(context.runtime.currentSimulation)
|
|
19837
|
+
],
|
|
19838
|
+
blossomServers: context.config.target.pointer?.blossomServers ?? [],
|
|
19839
|
+
maxWaitMs: context.config.target.pointer?.maxWaitMs
|
|
19840
|
+
};
|
|
19841
|
+
}
|
|
19842
|
+
function recordSupportsDelivery(record, params) {
|
|
19843
|
+
return record.archetypes.some(
|
|
19844
|
+
(archetype) => archetype.slug === params.delivery.archetype && archetype.convention === params.delivery.convention
|
|
19845
|
+
);
|
|
19846
|
+
}
|
|
19847
|
+
function resolvedSupportsDelivery(resolved, params) {
|
|
19848
|
+
return resolved.manifest.archetypes.some(
|
|
19849
|
+
(archetype) => archetype.slug === params.delivery.archetype && archetype.convention === params.delivery.convention
|
|
19850
|
+
);
|
|
19851
|
+
}
|
|
19852
|
+
function findRuntimeTabGeneration(state, generation) {
|
|
19853
|
+
return state.tabs.find((tab) => runtimeTabGenerationId(tab) === generation.id) ?? null;
|
|
19854
|
+
}
|
|
19855
|
+
function bindPajaIntentGeneration(tab, record, runtime) {
|
|
19856
|
+
const generation = { id: runtimeTabGenerationId(tab) };
|
|
19857
|
+
runtime.intentRecords.set(generation, record);
|
|
19858
|
+
return generation;
|
|
19859
|
+
}
|
|
19860
|
+
function isCurrentPajaIntentGeneration(generation, state, context, tab) {
|
|
19861
|
+
const record = context.runtime.intentRecords.get(generation);
|
|
19862
|
+
return record !== void 0 && context.runtime.catalog.validateCurrent(record, tab.resolvedTarget) !== null && isCurrentRuntimeTabGeneration(state, context, tab);
|
|
19863
|
+
}
|
|
19864
|
+
function invalidatePajaIntentGeneration(generation, runtime) {
|
|
19865
|
+
const waiters = runtime.readyWaiters.get(generation);
|
|
19866
|
+
if (!waiters) return;
|
|
19867
|
+
runtime.readyWaiters.delete(generation);
|
|
19868
|
+
for (const waiter of waiters) waiter.reject(new Error("intent target catalog record was replaced"));
|
|
19869
|
+
}
|
|
19870
|
+
function subscribePajaIntentCatalogChanges(state, context) {
|
|
19871
|
+
return context.runtime.catalog.onChanged((dTag) => {
|
|
19872
|
+
for (const [generation] of context.runtime.readyWaiters) {
|
|
19873
|
+
const record = context.runtime.intentRecords.get(generation);
|
|
19874
|
+
if (!record || record.dTag !== dTag) continue;
|
|
19875
|
+
const tab = findRuntimeTabGeneration(state, generation);
|
|
19876
|
+
if (!tab || !isCurrentPajaIntentGeneration(generation, state, context, tab)) {
|
|
19877
|
+
invalidatePajaIntentGeneration(generation, context.runtime);
|
|
19878
|
+
}
|
|
19879
|
+
}
|
|
19880
|
+
});
|
|
19881
|
+
}
|
|
19882
|
+
function isCurrentRuntimeTabGeneration(state, context, tab) {
|
|
19883
|
+
const source = tab.frame.contentWindow;
|
|
19884
|
+
return state.tabs.includes(tab) && tab.windowId !== null && source !== null && originRegistry.getWindowId(source) === tab.windowId && runtimeTabGenerationId(tab) === `${tab.id}:${tab.generation}`;
|
|
19885
|
+
}
|
|
19886
|
+
function markRuntimeTabReady(state, context, tab, source, registeredWindowId, effects = {}) {
|
|
19887
|
+
if (!tab.windowId || tab.frame.contentWindow !== source || registeredWindowId !== tab.windowId || !isCurrentRuntimeTabGeneration(state, context, tab)) return false;
|
|
19888
|
+
context.runtime.readyWindowIds.add(tab.windowId);
|
|
19889
|
+
for (const [generation, waiters] of context.runtime.readyWaiters) {
|
|
19890
|
+
if (findRuntimeTabGeneration(state, generation) !== tab) continue;
|
|
19891
|
+
context.runtime.readyWaiters.delete(generation);
|
|
19892
|
+
if (!isCurrentPajaIntentGeneration(generation, state, context, tab)) {
|
|
19893
|
+
for (const waiter of waiters) waiter.reject(new Error("intent target catalog record was replaced"));
|
|
19894
|
+
continue;
|
|
19895
|
+
}
|
|
19896
|
+
for (const waiter of waiters) waiter.resolve();
|
|
19897
|
+
}
|
|
19898
|
+
tab.status = "ready";
|
|
19899
|
+
if (state.activeTabId === tab.id) {
|
|
19900
|
+
if (effects.setReadyStatus) effects.setReadyStatus(state);
|
|
19901
|
+
else state.status = "ready";
|
|
19902
|
+
}
|
|
19903
|
+
renderRuntimeTabs(state);
|
|
19904
|
+
return true;
|
|
19905
|
+
}
|
|
19906
|
+
function clearRuntimeTabGeneration(tab, runtime) {
|
|
19907
|
+
for (const [generation, waiters] of runtime.readyWaiters) {
|
|
19908
|
+
if (generation.id !== runtimeTabGenerationId(tab)) continue;
|
|
19909
|
+
runtime.readyWaiters.delete(generation);
|
|
19910
|
+
for (const waiter of waiters) waiter.reject(new Error("intent target generation replaced"));
|
|
19911
|
+
}
|
|
19912
|
+
}
|
|
19913
|
+
|
|
19914
|
+
// src/theme-broadcast.ts
|
|
19915
|
+
function createPajaThemeBroadcastLink() {
|
|
19916
|
+
let attachedBridge;
|
|
19917
|
+
return {
|
|
19918
|
+
attach(bridge) {
|
|
19919
|
+
if (attachedBridge) {
|
|
19920
|
+
throw new Error("Paja theme broadcast link is already attached.");
|
|
19921
|
+
}
|
|
19922
|
+
attachedBridge = bridge;
|
|
19923
|
+
},
|
|
19924
|
+
onBroadcast(envelope) {
|
|
19925
|
+
if (!attachedBridge) {
|
|
19926
|
+
throw new Error("Paja theme broadcast occurred before a ShellBridge is attached.");
|
|
19927
|
+
}
|
|
19928
|
+
attachedBridge.publishTheme(envelope.theme);
|
|
19929
|
+
}
|
|
19930
|
+
};
|
|
19931
|
+
}
|
|
19932
|
+
|
|
18416
19933
|
// src/browser-target-frame.ts
|
|
19934
|
+
function resolvePajaFrameEnvironment(hooks, identity) {
|
|
19935
|
+
if (!identity.dTag || !identity.aggregateHash) {
|
|
19936
|
+
throw new Error("Paja frame identity requires a dTag and aggregate hash");
|
|
19937
|
+
}
|
|
19938
|
+
return resolveShellEnvironment(hooks, identity);
|
|
19939
|
+
}
|
|
18417
19940
|
function getTargetIdentity2(config, resolvedTarget) {
|
|
18418
19941
|
return {
|
|
18419
19942
|
pubkey: "",
|
|
@@ -18421,18 +19944,21 @@ function getTargetIdentity2(config, resolvedTarget) {
|
|
|
18421
19944
|
aggregateHash: resolvedTarget?.aggregateHash ?? config.window.aggregateHash
|
|
18422
19945
|
};
|
|
18423
19946
|
}
|
|
18424
|
-
function
|
|
19947
|
+
function getTargetOriginIdentity(config, resolvedTarget) {
|
|
19948
|
+
const target = getTargetIdentity2(config, resolvedTarget);
|
|
19949
|
+
return Object.freeze({ dTag: target.dTag, aggregateHash: target.aggregateHash });
|
|
19950
|
+
}
|
|
19951
|
+
function registerFrameForGeneration(frame, config, generation, identity, environment, windowId = `${config.window.id}:${generation}`) {
|
|
18425
19952
|
const win = frame.contentWindow;
|
|
18426
19953
|
if (!win) return null;
|
|
18427
|
-
|
|
18428
|
-
originRegistry.
|
|
18429
|
-
dTag: identity.dTag,
|
|
18430
|
-
aggregateHash: identity.aggregateHash
|
|
18431
|
-
});
|
|
19954
|
+
originRegistry.register(win, windowId, identity);
|
|
19955
|
+
originRegistry.setEnvironment(win, environment);
|
|
18432
19956
|
return windowId;
|
|
18433
19957
|
}
|
|
18434
|
-
async function navigateFrame(
|
|
18435
|
-
const
|
|
19958
|
+
async function navigateFrame(frame, config, generation, adapter, resolvedTarget, windowId, isCurrent, onRegistered) {
|
|
19959
|
+
const identity = getTargetOriginIdentity(config, resolvedTarget);
|
|
19960
|
+
const environment = resolvePajaFrameEnvironment(adapter, identity);
|
|
19961
|
+
const domains = environment.capabilities.domains;
|
|
18436
19962
|
if (config.target.mode === "runtime-pointer") {
|
|
18437
19963
|
if (!resolvedTarget) {
|
|
18438
19964
|
frame.removeAttribute("src");
|
|
@@ -18440,7 +19966,8 @@ async function navigateFrame(bridge, frame, config, generation, capabilities, si
|
|
|
18440
19966
|
return null;
|
|
18441
19967
|
}
|
|
18442
19968
|
if (isCurrent && !isCurrent()) return null;
|
|
18443
|
-
const registeredWindowId2 = registerFrameForGeneration(
|
|
19969
|
+
const registeredWindowId2 = registerFrameForGeneration(frame, config, generation, identity, environment, windowId);
|
|
19970
|
+
onRegistered?.(registeredWindowId2);
|
|
18444
19971
|
frame.removeAttribute("src");
|
|
18445
19972
|
frame.srcdoc = injectNappletNamespacePrelude(
|
|
18446
19973
|
injectPajaRuntimeCsp(
|
|
@@ -18453,7 +19980,8 @@ async function navigateFrame(bridge, frame, config, generation, capabilities, si
|
|
|
18453
19980
|
}
|
|
18454
19981
|
const html = await fetchTargetHtml();
|
|
18455
19982
|
if (isCurrent && !isCurrent()) return null;
|
|
18456
|
-
const registeredWindowId = registerFrameForGeneration(
|
|
19983
|
+
const registeredWindowId = registerFrameForGeneration(frame, config, generation, identity, environment, windowId);
|
|
19984
|
+
onRegistered?.(registeredWindowId);
|
|
18457
19985
|
frame.removeAttribute("src");
|
|
18458
19986
|
frame.srcdoc = injectNappletNamespacePrelude(
|
|
18459
19987
|
injectBaseHref(html, config.target.url),
|
|
@@ -18480,21 +20008,6 @@ function connectOrigins(urls) {
|
|
|
18480
20008
|
}
|
|
18481
20009
|
return [...out];
|
|
18482
20010
|
}
|
|
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
20011
|
async function fetchTargetHtml() {
|
|
18499
20012
|
const response = await fetch(new URL("./__kehto/target.html", window.location.href), {
|
|
18500
20013
|
cache: "no-store",
|
|
@@ -18524,6 +20037,30 @@ function escapeHtml(value) {
|
|
|
18524
20037
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
18525
20038
|
}
|
|
18526
20039
|
|
|
20040
|
+
// src/browser-target-diagnostics.ts
|
|
20041
|
+
async function reportTargetCorsDiagnostic(state) {
|
|
20042
|
+
let diagnostic;
|
|
20043
|
+
try {
|
|
20044
|
+
const response = await fetch(new URL("./__kehto/target-cors.json", window.location.href), {
|
|
20045
|
+
cache: "no-store"
|
|
20046
|
+
});
|
|
20047
|
+
if (!response.ok) return;
|
|
20048
|
+
diagnostic = await response.json();
|
|
20049
|
+
} catch {
|
|
20050
|
+
return;
|
|
20051
|
+
}
|
|
20052
|
+
if (diagnostic.status === "allowed") return;
|
|
20053
|
+
appendPajaMessageLog(state, "paja", {
|
|
20054
|
+
type: "paja.target.cors.error",
|
|
20055
|
+
status: diagnostic.status,
|
|
20056
|
+
targetUrl: diagnostic.targetUrl,
|
|
20057
|
+
allowOrigin: diagnostic.allowOrigin,
|
|
20058
|
+
message: `${diagnostic.detail} ${diagnostic.hint ?? ""}`.trim()
|
|
20059
|
+
});
|
|
20060
|
+
console.warn(`[paja] ${diagnostic.detail}
|
|
20061
|
+
[paja] ${diagnostic.hint ?? ""}`.trimEnd());
|
|
20062
|
+
}
|
|
20063
|
+
|
|
18527
20064
|
// src/browser-host.ts
|
|
18528
20065
|
var bridgeRef = null;
|
|
18529
20066
|
function readConfig() {
|
|
@@ -18621,68 +20158,22 @@ function getFrame() {
|
|
|
18621
20158
|
frame.sandbox.remove("allow-same-origin");
|
|
18622
20159
|
return frame;
|
|
18623
20160
|
}
|
|
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
20161
|
function startFrameNavigation(state, context) {
|
|
18672
|
-
const { config, frame, bridge,
|
|
20162
|
+
const { config, frame, bridge, adapter, runtime } = context;
|
|
18673
20163
|
if (!frame) return;
|
|
18674
20164
|
const generation = state.generation;
|
|
18675
20165
|
const isCurrentGeneration = () => state.generation === generation;
|
|
18676
20166
|
void navigateFrame(
|
|
18677
|
-
bridge,
|
|
18678
20167
|
frame,
|
|
18679
20168
|
config,
|
|
18680
20169
|
generation,
|
|
18681
|
-
|
|
18682
|
-
runtime.currentSimulation,
|
|
20170
|
+
adapter,
|
|
18683
20171
|
state.resolvedTarget,
|
|
18684
20172
|
void 0,
|
|
18685
|
-
isCurrentGeneration
|
|
20173
|
+
isCurrentGeneration,
|
|
20174
|
+
(windowId) => {
|
|
20175
|
+
if (isCurrentGeneration()) runtime.currentWindowId = windowId;
|
|
20176
|
+
}
|
|
18686
20177
|
).then((windowId) => {
|
|
18687
20178
|
if (!isCurrentGeneration()) {
|
|
18688
20179
|
unregisterSingleFrameWindow(bridge, runtime, windowId);
|
|
@@ -18701,28 +20192,6 @@ function startFrameNavigation(state, context) {
|
|
|
18701
20192
|
console.error(error);
|
|
18702
20193
|
});
|
|
18703
20194
|
}
|
|
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
20195
|
function installPajaControlListeners(state) {
|
|
18727
20196
|
document.getElementById("reload-target")?.addEventListener("click", () => {
|
|
18728
20197
|
state.reload();
|
|
@@ -18784,7 +20253,7 @@ function setRuntimeDomainEnabled(state, context, domain, enabled) {
|
|
|
18784
20253
|
state.reload();
|
|
18785
20254
|
}
|
|
18786
20255
|
async function loadRuntimePointer(state, context, value, options = {}) {
|
|
18787
|
-
const { config } = context;
|
|
20256
|
+
const { config, runtime } = context;
|
|
18788
20257
|
if (config.target.mode !== "runtime-pointer") return;
|
|
18789
20258
|
const pointer = value.trim();
|
|
18790
20259
|
const input = document.getElementById("runtime-pointer-input");
|
|
@@ -18798,11 +20267,8 @@ async function loadRuntimePointer(state, context, value, options = {}) {
|
|
|
18798
20267
|
setStatus(state, "booting");
|
|
18799
20268
|
appendPajaMessageLog(state, "paja", { type: "paja.pointer.resolve", pointer });
|
|
18800
20269
|
try {
|
|
18801
|
-
const resolvedTarget = await resolvePajaPointer(pointer,
|
|
18802
|
-
|
|
18803
|
-
blossomServers: config.target.pointer?.blossomServers ?? [],
|
|
18804
|
-
maxWaitMs: config.target.pointer?.maxWaitMs
|
|
18805
|
-
});
|
|
20270
|
+
const resolvedTarget = await resolvePajaPointer(pointer, pajaPointerResolverOptions(context));
|
|
20271
|
+
runtime.catalog.install(resolvedTarget);
|
|
18806
20272
|
const pointerStatus = `${resolvedTarget.dTag}:${resolvedTarget.aggregateHash.slice(0, 12)}`;
|
|
18807
20273
|
setPointerStatus(state, pointerStatus);
|
|
18808
20274
|
appendPajaMessageLog(state, "paja", {
|
|
@@ -18947,16 +20413,30 @@ async function installPajaHost() {
|
|
|
18947
20413
|
const runtime = {
|
|
18948
20414
|
currentSimulation: config.simulation,
|
|
18949
20415
|
themeService: null,
|
|
20416
|
+
catalog: new InstalledNappletCatalog(),
|
|
20417
|
+
readyWaiters: /* @__PURE__ */ new Map(),
|
|
20418
|
+
intentRecords: /* @__PURE__ */ new WeakMap(),
|
|
18950
20419
|
currentWindowId: null,
|
|
18951
20420
|
readyWindowIds: /* @__PURE__ */ new Set()
|
|
18952
20421
|
};
|
|
18953
20422
|
const getSimulation = () => runtime.currentSimulation;
|
|
20423
|
+
const themeBroadcast = createPajaThemeBroadcastLink();
|
|
18954
20424
|
let stateRef = null;
|
|
18955
|
-
|
|
20425
|
+
let contextRef = null;
|
|
20426
|
+
const intentController = new BrowserIntentController(createPajaIntentTargetOptions(
|
|
20427
|
+
() => stateRef,
|
|
20428
|
+
() => contextRef,
|
|
20429
|
+
{ persistTabs: persistRuntimeTabs }
|
|
20430
|
+
));
|
|
20431
|
+
const signerController = createHostSignerController(() => stateRef, setSimulationStatus);
|
|
18956
20432
|
const adapter = createPajaAdapter(config, getSimulation, (theme) => {
|
|
18957
20433
|
runtime.themeService = theme;
|
|
18958
|
-
}, (request) => confirmPajaRequest(stateRef, request), signerController, () => getTargetIdentity2(config, stateRef?.resolvedTarget))
|
|
20434
|
+
}, themeBroadcast.onBroadcast, (request) => confirmPajaRequest(stateRef, request), signerController, () => getTargetIdentity2(config, stateRef?.resolvedTarget), () => stateRef?.reload(), {
|
|
20435
|
+
catalog: runtime.catalog,
|
|
20436
|
+
controller: intentController
|
|
20437
|
+
});
|
|
18959
20438
|
const bridge = createShellBridge(adapter);
|
|
20439
|
+
themeBroadcast.attach(bridge);
|
|
18960
20440
|
bridgeRef = bridge;
|
|
18961
20441
|
installPajaOriginRegistryProxy(originRegistry, () => stateRef);
|
|
18962
20442
|
const capabilities = buildShellCapabilities(adapter);
|
|
@@ -18970,21 +20450,25 @@ async function installPajaHost() {
|
|
|
18970
20450
|
capabilities,
|
|
18971
20451
|
runtime,
|
|
18972
20452
|
navigateFrame,
|
|
18973
|
-
registerFrameForGeneration,
|
|
18974
20453
|
renderTargetErrorHtml,
|
|
20454
|
+
onTabDestroyed: (tab) => clearRuntimeTabGeneration(tab, runtime),
|
|
18975
20455
|
setPointerStatus: (state2, message) => setPointerStatus(state2, message),
|
|
18976
20456
|
setStatus: (state2, status) => setStatus(state2, status)
|
|
18977
20457
|
};
|
|
20458
|
+
contextRef = context;
|
|
18978
20459
|
const state = createPajaBrowserState(context);
|
|
18979
20460
|
stateRef = state;
|
|
20461
|
+
const stopIntentCatalogChanges = subscribePajaIntentCatalogChanges(state, context);
|
|
20462
|
+
window.addEventListener("pagehide", stopIntentCatalogChanges, { once: true });
|
|
18980
20463
|
window.__KEHTO_PAJA__ = state;
|
|
18981
20464
|
window.addEventListener("message", (event) => {
|
|
18982
20465
|
const source = event.source;
|
|
18983
20466
|
const sourceTab = source ? state.tabs.find((tab) => tab.frame.contentWindow === source) ?? null : null;
|
|
18984
20467
|
const isSingleFrameMessage = frame ? event.source === frame.contentWindow : false;
|
|
18985
20468
|
if (!sourceTab && !isSingleFrameMessage) return;
|
|
18986
|
-
const registeredWindowId = source ? originRegistry.getWindowId(source) : null;
|
|
20469
|
+
const registeredWindowId = source ? originRegistry.getWindowId(source) ?? null : null;
|
|
18987
20470
|
const sourceWindowId = sourceTab?.windowId ?? registeredWindowId ?? void 0;
|
|
20471
|
+
if (sourceTab && (!source || !sourceWindowId || registeredWindowId !== sourceWindowId)) return;
|
|
18988
20472
|
if (isSingleFrameMessage && (!sourceWindowId || sourceWindowId !== runtime.currentWindowId)) return;
|
|
18989
20473
|
appendPajaMessageLog(state, "napplet->shell", event.data, sourceWindowId);
|
|
18990
20474
|
const proxiedSource = createPajaPostMessageProxy(event.source, state, sourceWindowId);
|
|
@@ -18998,22 +20482,21 @@ async function installPajaHost() {
|
|
|
18998
20482
|
bridge.handleMessage(syntheticEvent);
|
|
18999
20483
|
const data = event.data;
|
|
19000
20484
|
if (data && typeof data === "object" && data.type === "shell.ready") {
|
|
19001
|
-
if (sourceWindowId) runtime.readyWindowIds.add(sourceWindowId);
|
|
19002
20485
|
if (sourceTab) {
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
20486
|
+
if (source && !markRuntimeTabReady(
|
|
20487
|
+
state,
|
|
20488
|
+
context,
|
|
20489
|
+
sourceTab,
|
|
20490
|
+
source,
|
|
20491
|
+
registeredWindowId,
|
|
20492
|
+
{ setReadyStatus: (current) => setStatus(current, "ready") }
|
|
20493
|
+
)) return;
|
|
19006
20494
|
} else {
|
|
20495
|
+
if (sourceWindowId) runtime.readyWindowIds.add(sourceWindowId);
|
|
19007
20496
|
setStatus(state, "ready");
|
|
19008
20497
|
}
|
|
19009
20498
|
}
|
|
19010
20499
|
});
|
|
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
20500
|
frame?.addEventListener("error", () => {
|
|
19018
20501
|
setStatus(state, "error");
|
|
19019
20502
|
});
|
|
@@ -19034,20 +20517,28 @@ async function installPajaHost() {
|
|
|
19034
20517
|
}
|
|
19035
20518
|
} else {
|
|
19036
20519
|
startFrameNavigation(state, context);
|
|
20520
|
+
void reportTargetCorsDiagnostic(state);
|
|
19037
20521
|
}
|
|
19038
20522
|
if (hasNip07Signer()) void state.connectNip07();
|
|
19039
20523
|
}
|
|
19040
|
-
|
|
19041
|
-
|
|
20524
|
+
if (typeof document !== "undefined") {
|
|
20525
|
+
try {
|
|
20526
|
+
void installPajaHost().catch((error) => {
|
|
20527
|
+
const statusEl = document.getElementById("lifecycle-status");
|
|
20528
|
+
if (statusEl) statusEl.textContent = "error";
|
|
20529
|
+
console.error(error);
|
|
20530
|
+
});
|
|
20531
|
+
} catch (error) {
|
|
19042
20532
|
const statusEl = document.getElementById("lifecycle-status");
|
|
19043
20533
|
if (statusEl) statusEl.textContent = "error";
|
|
19044
20534
|
console.error(error);
|
|
19045
|
-
}
|
|
19046
|
-
} catch (error) {
|
|
19047
|
-
const statusEl = document.getElementById("lifecycle-status");
|
|
19048
|
-
if (statusEl) statusEl.textContent = "error";
|
|
19049
|
-
console.error(error);
|
|
20535
|
+
}
|
|
19050
20536
|
}
|
|
20537
|
+
export {
|
|
20538
|
+
createPajaIntentTargetOptions,
|
|
20539
|
+
markRuntimeTabReady,
|
|
20540
|
+
subscribePajaIntentCatalogChanges
|
|
20541
|
+
};
|
|
19051
20542
|
/*! Bundled license information:
|
|
19052
20543
|
|
|
19053
20544
|
@noble/hashes/utils.js:
|