@kehto/paja 0.19.0 → 0.20.1
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 +73 -35
- package/dist/browser-host.js +2272 -1059
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-GY5ET3PC.js → chunk-FUBJAN4W.js} +255 -25
- package/dist/chunk-FUBJAN4W.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +88 -14
- package/dist/index.js +9 -1
- package/package.json +5 -5
- package/dist/chunk-GY5ET3PC.js.map +0 -1
|
@@ -574,6 +574,8 @@ function renderPajaHtml(config) {
|
|
|
574
574
|
.empty-stage { position: absolute; inset: 0; display: grid; place-items: center; color: var(--muted); font-size: 12px; }
|
|
575
575
|
.empty-stage[hidden] { display: none; }
|
|
576
576
|
.stage.drop-active { outline: 2px dashed var(--accent); outline-offset: -6px; }
|
|
577
|
+
.intent-link-notice { position: fixed; bottom: 36px; right: 16px; max-width: min(420px, calc(100vw - 32px)); z-index: 30; padding: 12px; background: var(--bar); border: 1px solid var(--line); border-radius: 8px; }
|
|
578
|
+
.intent-link-notice[hidden] { display: none; }
|
|
577
579
|
.tab-panel { position: absolute; inset: 0; min-width: 0; min-height: 0; }
|
|
578
580
|
.tab-panel[hidden] { display: none; }
|
|
579
581
|
iframe { width: 100%; height: 100%; border: 0; background: white; display: block; }
|
|
@@ -698,6 +700,7 @@ function renderPajaHtml(config) {
|
|
|
698
700
|
<section class="paja-notification-center" id="paja-notification-center" aria-label="Napplet notifications" aria-live="polite"></section>
|
|
699
701
|
${renderConfirmationDialog()}
|
|
700
702
|
${renderConfigDialog()}
|
|
703
|
+
${renderIntentLinkDialog()}
|
|
701
704
|
${renderDuplicateDialog()}
|
|
702
705
|
<footer class="bar bottom">
|
|
703
706
|
<span>mode: <code>${escapeHtml(getModeLabel(config))}</code></span>
|
|
@@ -755,6 +758,24 @@ function renderConfigDialog() {
|
|
|
755
758
|
</div>
|
|
756
759
|
</dialog>`;
|
|
757
760
|
}
|
|
761
|
+
function renderIntentLinkDialog() {
|
|
762
|
+
return `<div class="intent-link-notice" id="paja-intent-link-notice" hidden>
|
|
763
|
+
<div id="paja-intent-link-status" role="status" aria-live="polite"></div>
|
|
764
|
+
<button type="button" id="paja-intent-link-retry" hidden>Try again</button>
|
|
765
|
+
<button type="button" id="paja-intent-link-dismiss">Close</button>
|
|
766
|
+
</div>
|
|
767
|
+
<dialog class="config-dialog" id="paja-intent-link-dialog" aria-labelledby="paja-intent-link-title" aria-describedby="paja-intent-link-copy">
|
|
768
|
+
<div class="dialog">
|
|
769
|
+
<div class="dialog-title" id="paja-intent-link-title">Which app would you like to use?</div>
|
|
770
|
+
<div class="dialog-copy" id="paja-intent-link-copy"></div>
|
|
771
|
+
<div class="dialog-actions" id="paja-intent-link-apps"></div>
|
|
772
|
+
<label><input id="paja-intent-link-save-default" type="checkbox"> Always use this app</label>
|
|
773
|
+
<div class="dialog-actions">
|
|
774
|
+
<button type="button" id="paja-intent-link-cancel">Cancel</button>
|
|
775
|
+
</div>
|
|
776
|
+
</div>
|
|
777
|
+
</dialog>`;
|
|
778
|
+
}
|
|
758
779
|
function renderStage(config, targetLabel) {
|
|
759
780
|
if (config.target.mode === "runtime-pointer") {
|
|
760
781
|
return `<section class="stage" id="napplet-stage" aria-label="Loaded napplet runtimes">
|
|
@@ -1291,7 +1312,7 @@ async function sha256Hex(bytes) {
|
|
|
1291
1312
|
// src/installed-napplet-catalog.ts
|
|
1292
1313
|
import { manifestToIntentCatalogEntry } from "@kehto/services";
|
|
1293
1314
|
function matchesInstalledNappletRecord(record, target) {
|
|
1294
|
-
return target.
|
|
1315
|
+
return target.id === record.id && target.aggregateHash === record.aggregateHash;
|
|
1295
1316
|
}
|
|
1296
1317
|
var InstalledNappletCatalog = class {
|
|
1297
1318
|
records = /* @__PURE__ */ new Map();
|
|
@@ -1299,6 +1320,7 @@ var InstalledNappletCatalog = class {
|
|
|
1299
1320
|
/** Insert or replace a record from an already resolver-verified pointer. */
|
|
1300
1321
|
install(resolved) {
|
|
1301
1322
|
const record = freezeRecord({
|
|
1323
|
+
id: resolved.manifest.catalogId,
|
|
1302
1324
|
dTag: resolved.dTag,
|
|
1303
1325
|
aggregateHash: resolved.aggregateHash,
|
|
1304
1326
|
pointer: copyPointer(resolved.pointer),
|
|
@@ -1307,31 +1329,31 @@ var InstalledNappletCatalog = class {
|
|
|
1307
1329
|
optional: [...resolved.manifest.optional ?? []],
|
|
1308
1330
|
archetypes: resolved.manifest.archetypes.map((archetype) => ({
|
|
1309
1331
|
slug: archetype.slug,
|
|
1310
|
-
convention: archetype.convention
|
|
1332
|
+
convention: archetype.convention,
|
|
1333
|
+
params: [...archetype.params]
|
|
1311
1334
|
}))
|
|
1312
1335
|
});
|
|
1313
|
-
|
|
1314
|
-
this.
|
|
1315
|
-
this.notify(record.dTag);
|
|
1336
|
+
this.records.set(record.id, record);
|
|
1337
|
+
this.notify(record.id);
|
|
1316
1338
|
return record;
|
|
1317
1339
|
}
|
|
1318
1340
|
/** Remove an artifact explicitly; frame teardown never calls this method. */
|
|
1319
|
-
remove(
|
|
1320
|
-
const removed = this.records.delete(
|
|
1321
|
-
if (removed) this.notify(
|
|
1341
|
+
remove(id) {
|
|
1342
|
+
const removed = this.records.delete(id);
|
|
1343
|
+
if (removed) this.notify(id);
|
|
1322
1344
|
return removed;
|
|
1323
1345
|
}
|
|
1324
|
-
/** Return whether a verified installation exists for a
|
|
1325
|
-
has(
|
|
1326
|
-
return this.records.has(
|
|
1346
|
+
/** Return whether a verified installation exists for a catalog ID. */
|
|
1347
|
+
has(id) {
|
|
1348
|
+
return this.records.has(id);
|
|
1327
1349
|
}
|
|
1328
1350
|
/** Return immutable serializable installed-artifact facts. */
|
|
1329
1351
|
installed() {
|
|
1330
1352
|
return [...this.records.values()];
|
|
1331
1353
|
}
|
|
1332
|
-
/** Return a verified record by
|
|
1333
|
-
get(
|
|
1334
|
-
return this.records.get(
|
|
1354
|
+
/** Return a verified record by catalog ID. */
|
|
1355
|
+
get(id) {
|
|
1356
|
+
return this.records.get(id);
|
|
1335
1357
|
}
|
|
1336
1358
|
/**
|
|
1337
1359
|
* Atomically confirm that a resolved target still belongs to the exact record
|
|
@@ -1339,7 +1361,7 @@ var InstalledNappletCatalog = class {
|
|
|
1339
1361
|
* token, so even a same-identity replacement cannot pass this check.
|
|
1340
1362
|
*/
|
|
1341
1363
|
validateCurrent(selected, target) {
|
|
1342
|
-
if (this.records.get(selected.
|
|
1364
|
+
if (this.records.get(selected.id) !== selected) return null;
|
|
1343
1365
|
return matchesInstalledNappletRecord(selected, target) ? selected : null;
|
|
1344
1366
|
}
|
|
1345
1367
|
/**
|
|
@@ -1350,11 +1372,12 @@ var InstalledNappletCatalog = class {
|
|
|
1350
1372
|
*/
|
|
1351
1373
|
intentCatalog(canReceiveIntent = () => true) {
|
|
1352
1374
|
return this.installed().filter(canReceiveIntent).map((record) => manifestToIntentCatalogEntry({
|
|
1353
|
-
|
|
1375
|
+
catalogId: record.id,
|
|
1354
1376
|
...record.title === void 0 ? {} : { title: record.title },
|
|
1355
1377
|
archetypes: record.archetypes.map((archetype) => ({
|
|
1356
1378
|
slug: archetype.slug,
|
|
1357
|
-
convention: archetype.convention
|
|
1379
|
+
convention: archetype.convention,
|
|
1380
|
+
params: [...archetype.params]
|
|
1358
1381
|
}))
|
|
1359
1382
|
}));
|
|
1360
1383
|
}
|
|
@@ -1363,8 +1386,8 @@ var InstalledNappletCatalog = class {
|
|
|
1363
1386
|
this.listeners.add(listener);
|
|
1364
1387
|
return () => this.listeners.delete(listener);
|
|
1365
1388
|
}
|
|
1366
|
-
notify(
|
|
1367
|
-
for (const listener of this.listeners) listener(
|
|
1389
|
+
notify(id) {
|
|
1390
|
+
for (const listener of this.listeners) listener(id);
|
|
1368
1391
|
}
|
|
1369
1392
|
};
|
|
1370
1393
|
function copyPointer(pointer) {
|
|
@@ -1392,7 +1415,7 @@ function freezeRecord(record) {
|
|
|
1392
1415
|
...record,
|
|
1393
1416
|
requires: Object.freeze([...record.requires]),
|
|
1394
1417
|
optional: Object.freeze([...record.optional ?? []]),
|
|
1395
|
-
archetypes: Object.freeze(record.archetypes.map((archetype) => Object.freeze({ ...archetype })))
|
|
1418
|
+
archetypes: Object.freeze(record.archetypes.map((archetype) => Object.freeze({ ...archetype, params: Object.freeze([...archetype.params]) })))
|
|
1396
1419
|
});
|
|
1397
1420
|
}
|
|
1398
1421
|
|
|
@@ -1405,6 +1428,13 @@ var BrowserIntentController = class {
|
|
|
1405
1428
|
}
|
|
1406
1429
|
options;
|
|
1407
1430
|
maxAttempts;
|
|
1431
|
+
/** Retain delivery work immediately; completion remains host-observable only. */
|
|
1432
|
+
accept(params) {
|
|
1433
|
+
const acceptance = Object.freeze({ completion: this.dispatch(params) });
|
|
1434
|
+
this.options.onAccepted?.(params, acceptance);
|
|
1435
|
+
return acceptance;
|
|
1436
|
+
}
|
|
1437
|
+
/** Complete an already accepted delivery. Kept public for host diagnostics and tests. */
|
|
1408
1438
|
async dispatch(params) {
|
|
1409
1439
|
const dispatch = freezeDispatch(params);
|
|
1410
1440
|
let reason = "no-current-target";
|
|
@@ -1432,9 +1462,8 @@ var BrowserIntentController = class {
|
|
|
1432
1462
|
}
|
|
1433
1463
|
try {
|
|
1434
1464
|
await this.options.send(generation, dispatch);
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
return { windowId };
|
|
1465
|
+
this.options.onDelivered?.(dispatch);
|
|
1466
|
+
return;
|
|
1438
1467
|
} catch {
|
|
1439
1468
|
this.options.onTerminal?.(dispatch, "send-failed");
|
|
1440
1469
|
throw new Error("intent target send failed");
|
|
@@ -1465,7 +1494,8 @@ function freezeDispatch(params) {
|
|
|
1465
1494
|
action: params.action,
|
|
1466
1495
|
convention: params.convention,
|
|
1467
1496
|
...params.payload === void 0 ? {} : { payload },
|
|
1468
|
-
...behavior === void 0 ? {} : { behavior }
|
|
1497
|
+
...behavior === void 0 ? {} : { behavior },
|
|
1498
|
+
...params.sourceWindowId === void 0 ? {} : { sourceWindowId: params.sourceWindowId }
|
|
1469
1499
|
});
|
|
1470
1500
|
}
|
|
1471
1501
|
function freezeValue(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
@@ -1484,6 +1514,202 @@ function freezeValue(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
|
1484
1514
|
return Object.freeze(copy);
|
|
1485
1515
|
}
|
|
1486
1516
|
|
|
1517
|
+
// src/intent-link.ts
|
|
1518
|
+
import { normalizeIntentUri } from "@kehto/shell";
|
|
1519
|
+
var PAJA_INTENT_LINK_MAX_BYTES = 16 * 1024;
|
|
1520
|
+
function fail(message) {
|
|
1521
|
+
throw new TypeError(message);
|
|
1522
|
+
}
|
|
1523
|
+
function hasOwn(value, key) {
|
|
1524
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
1525
|
+
}
|
|
1526
|
+
function strictDecode(value, label) {
|
|
1527
|
+
try {
|
|
1528
|
+
return decodeURIComponent(value);
|
|
1529
|
+
} catch {
|
|
1530
|
+
return fail(`Paja intent link has malformed percent-encoding in ${label}`);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
function parseOuterQuery(search) {
|
|
1534
|
+
const fields = /* @__PURE__ */ Object.create(null);
|
|
1535
|
+
if (!search) return fields;
|
|
1536
|
+
for (const part of search.slice(1).split("&")) {
|
|
1537
|
+
const equal = part.indexOf("=");
|
|
1538
|
+
if (equal <= 0) fail("Paja intent link fields must be name=value");
|
|
1539
|
+
const name = strictDecode(part.slice(0, equal), "a field name");
|
|
1540
|
+
const value = strictDecode(part.slice(equal + 1), `the ${name || "unnamed"} field`);
|
|
1541
|
+
if (!name || hasOwn(fields, name)) fail("Paja intent link field names must be unique after decoding");
|
|
1542
|
+
Object.defineProperty(fields, name, { value, enumerable: true, configurable: true });
|
|
1543
|
+
}
|
|
1544
|
+
return fields;
|
|
1545
|
+
}
|
|
1546
|
+
function rawOuterValue(search, expectedName) {
|
|
1547
|
+
if (!search) return void 0;
|
|
1548
|
+
for (const part of search.slice(1).split("&")) {
|
|
1549
|
+
const equal = part.indexOf("=");
|
|
1550
|
+
if (equal <= 0) continue;
|
|
1551
|
+
try {
|
|
1552
|
+
if (decodeURIComponent(part.slice(0, equal)) === expectedName) return part.slice(equal + 1);
|
|
1553
|
+
} catch {
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
return void 0;
|
|
1557
|
+
}
|
|
1558
|
+
function assertJsonValue(value) {
|
|
1559
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
1560
|
+
if (typeof value === "number") {
|
|
1561
|
+
if (Number.isFinite(value)) return;
|
|
1562
|
+
fail("Paja intent payload must be JSON");
|
|
1563
|
+
}
|
|
1564
|
+
if (Array.isArray(value)) {
|
|
1565
|
+
value.forEach(assertJsonValue);
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
if (!value || typeof value !== "object" || Object.getPrototypeOf(value) !== Object.prototype) {
|
|
1569
|
+
fail("Paja intent payload must be JSON");
|
|
1570
|
+
}
|
|
1571
|
+
for (const [, nested] of Object.entries(value)) {
|
|
1572
|
+
assertJsonValue(nested);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
function parseJsonPayload(value) {
|
|
1576
|
+
let parsed;
|
|
1577
|
+
try {
|
|
1578
|
+
parsed = JSON.parse(value);
|
|
1579
|
+
} catch {
|
|
1580
|
+
return fail("Paja intent payload must be valid JSON");
|
|
1581
|
+
}
|
|
1582
|
+
assertJsonValue(parsed);
|
|
1583
|
+
return parsed;
|
|
1584
|
+
}
|
|
1585
|
+
function encodeJsonPayload(value) {
|
|
1586
|
+
assertJsonValue(value);
|
|
1587
|
+
const encoded = JSON.stringify(value);
|
|
1588
|
+
if (encoded === void 0) fail("Paja intent payload must be JSON");
|
|
1589
|
+
return encoded;
|
|
1590
|
+
}
|
|
1591
|
+
function pointerParameter(pointer) {
|
|
1592
|
+
if (pointer.startsWith("naddr")) return "naddr";
|
|
1593
|
+
if (pointer.startsWith("nevent")) return "nevent";
|
|
1594
|
+
return "pointer";
|
|
1595
|
+
}
|
|
1596
|
+
function assertLinkSize(url) {
|
|
1597
|
+
if (new TextEncoder().encode(url.href).length > PAJA_INTENT_LINK_MAX_BYTES) {
|
|
1598
|
+
fail(`Paja intent links must not exceed ${PAJA_INTENT_LINK_MAX_BYTES} UTF-8 bytes`);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
function parsePajaIntentLink(href) {
|
|
1602
|
+
let url;
|
|
1603
|
+
try {
|
|
1604
|
+
url = new URL(href);
|
|
1605
|
+
} catch {
|
|
1606
|
+
return fail("Paja intent link must be an absolute URL");
|
|
1607
|
+
}
|
|
1608
|
+
const rawIntent = rawOuterValue(url.search, "intent");
|
|
1609
|
+
if (rawIntent === void 0) {
|
|
1610
|
+
if (rawOuterValue(url.search, "payload") !== void 0) fail("Paja intent payload requires an intent URI");
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
if (url.hash) fail("Paja intent links do not support an outer fragment");
|
|
1614
|
+
assertLinkSize(url);
|
|
1615
|
+
const fields = parseOuterQuery(url.search);
|
|
1616
|
+
if (Object.keys(fields).some((field) => !["intent", "payload", "pointer", "naddr", "nevent"].includes(field))) {
|
|
1617
|
+
fail("Paja intent link contains unsupported fields");
|
|
1618
|
+
}
|
|
1619
|
+
const pointers = ["pointer", "naddr", "nevent"].filter((name) => hasOwn(fields, name));
|
|
1620
|
+
if (pointers.length > 1) fail("Paja intent link must not contain multiple pointer aliases");
|
|
1621
|
+
if (!hasOwn(fields, "intent") || !rawIntent || /[:/?#&=]/u.test(rawIntent) || !/%[0-9a-f]{2}/iu.test(rawIntent)) {
|
|
1622
|
+
fail("Paja intent URI must be completely percent-encoded");
|
|
1623
|
+
}
|
|
1624
|
+
const uri = fields.intent;
|
|
1625
|
+
const payload = hasOwn(fields, "payload") ? parseJsonPayload(fields.payload) : void 0;
|
|
1626
|
+
const request = normalizeIntentUri(uri, payload === void 0 ? void 0 : { payload });
|
|
1627
|
+
const pointer = pointers.length === 1 ? fields[pointers[0]] : void 0;
|
|
1628
|
+
if (pointer !== void 0 && pointer.length === 0) fail("Paja intent pointer must be non-empty text");
|
|
1629
|
+
if (pointer !== void 0 && request.handlerHint !== void 0) {
|
|
1630
|
+
fail("Paja intent link cannot combine a target pointer with a URI recommendation");
|
|
1631
|
+
}
|
|
1632
|
+
return {
|
|
1633
|
+
uri,
|
|
1634
|
+
...pointer === void 0 ? {} : { pointer },
|
|
1635
|
+
...payload === void 0 ? {} : { payload },
|
|
1636
|
+
request
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function createPajaIntentLink(descriptor, href) {
|
|
1640
|
+
if (!descriptor || typeof descriptor !== "object" || Array.isArray(descriptor)) fail("Paja intent link descriptor must be an object");
|
|
1641
|
+
if (typeof descriptor.uri !== "string") fail("Paja intent URI must be text");
|
|
1642
|
+
if (descriptor.pointer !== void 0 && (typeof descriptor.pointer !== "string" || descriptor.pointer.length === 0)) {
|
|
1643
|
+
fail("Paja intent pointer must be non-empty text");
|
|
1644
|
+
}
|
|
1645
|
+
const request = normalizeIntentUri(
|
|
1646
|
+
descriptor.uri,
|
|
1647
|
+
hasOwn(descriptor, "payload") ? { payload: descriptor.payload } : void 0
|
|
1648
|
+
);
|
|
1649
|
+
if (descriptor.pointer !== void 0 && request.handlerHint !== void 0) {
|
|
1650
|
+
fail("Paja intent link cannot combine a target pointer with a URI recommendation");
|
|
1651
|
+
}
|
|
1652
|
+
let url;
|
|
1653
|
+
try {
|
|
1654
|
+
url = new URL(href);
|
|
1655
|
+
} catch {
|
|
1656
|
+
return fail("Paja intent link must use an absolute base URL");
|
|
1657
|
+
}
|
|
1658
|
+
url.search = "";
|
|
1659
|
+
url.hash = "";
|
|
1660
|
+
const pairs = [`intent=${encodeURIComponent(descriptor.uri)}`];
|
|
1661
|
+
if (descriptor.pointer !== void 0) pairs.push(`${pointerParameter(descriptor.pointer)}=${encodeURIComponent(descriptor.pointer)}`);
|
|
1662
|
+
if (hasOwn(descriptor, "payload")) pairs.push(`payload=${encodeURIComponent(encodeJsonPayload(descriptor.payload))}`);
|
|
1663
|
+
url.search = `?${pairs.join("&")}`;
|
|
1664
|
+
assertLinkSize(url);
|
|
1665
|
+
return url.href;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// src/intent-launcher.ts
|
|
1669
|
+
import { resolveNapplet as resolveNapplet2 } from "@kehto/nip/5d";
|
|
1670
|
+
import { verifyEvent } from "nostr-tools/pure";
|
|
1671
|
+
import { neventEncode } from "nostr-tools/nip19";
|
|
1672
|
+
var LAUNCHER_HTML = `<!doctype html><meta charset="utf-8"><title>Paja intent launcher</title><script>let launched=false;addEventListener('message',(event)=>{if(launched||event.source!==parent)return;const data=event.data;if(!data||data.type!=='paja.intent.launch'||typeof data.uri!=='string'||typeof data.correlation!=='string')return;launched=true;window.napplet?.intent?.invoke(data.uri,data.options).then((result)=>parent.postMessage({type:'paja.intent.launcher.result',correlation:data.correlation,result},'*')).catch(()=>parent.postMessage({type:'paja.intent.launcher.result',correlation:data.correlation,result:{ok:false,error:'invoke rejected'}},'*'));});parent.postMessage({type:'paja.intent.launcher.ready'},'*');</script>`;
|
|
1673
|
+
var LAUNCHER_EVENT = {
|
|
1674
|
+
kind: 35129,
|
|
1675
|
+
created_at: 1760054400,
|
|
1676
|
+
content: "Paja verified intent launcher",
|
|
1677
|
+
tags: [
|
|
1678
|
+
["d", "paja-intent-launcher"],
|
|
1679
|
+
["x", "77cf3d2df847786177f4c16eb0b6ae1be6f5cd4e8d9be02a80bccd7c88e51bb8"],
|
|
1680
|
+
["R", "intent"],
|
|
1681
|
+
["title", "Paja intent launcher"]
|
|
1682
|
+
],
|
|
1683
|
+
pubkey: "24653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c",
|
|
1684
|
+
id: "37a0b8b71857b161ea7a8749f4642915c9101258509be6d1ce978e07c683bfb6",
|
|
1685
|
+
sig: "eb8e92509894dd3d86a28111a62412cff86ec36f591e4f3f692b5058174add6ef64c7996d66aae38bbd887355b3338ed6a64b300c8241875619b2e12e87d7903"
|
|
1686
|
+
};
|
|
1687
|
+
async function resolvePajaIntentLauncher() {
|
|
1688
|
+
const event = { ...LAUNCHER_EVENT, tags: LAUNCHER_EVENT.tags.map((tag) => [...tag]) };
|
|
1689
|
+
if (!verifyEvent(event)) throw new Error("Paja intent launcher manifest signature is invalid");
|
|
1690
|
+
const resolved = await resolveNapplet2({
|
|
1691
|
+
event,
|
|
1692
|
+
fetchBlob: async (hash) => {
|
|
1693
|
+
if (hash !== event.tags[1]?.[1]) throw new Error("Paja intent launcher requested an unknown artifact");
|
|
1694
|
+
return new TextEncoder().encode(LAUNCHER_HTML);
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
1697
|
+
return Object.freeze({
|
|
1698
|
+
pointer: Object.freeze({ type: "nevent", value: neventEncode({ id: event.id, author: event.pubkey, kind: event.kind }), id: event.id, author: event.pubkey, kind: event.kind, relays: Object.freeze([]) }),
|
|
1699
|
+
event: Object.freeze({ ...event, tags: Object.freeze(event.tags.map((tag) => Object.freeze([...tag]))) }),
|
|
1700
|
+
relays: Object.freeze([]),
|
|
1701
|
+
blossomServers: Object.freeze([]),
|
|
1702
|
+
dTag: resolved.dTag,
|
|
1703
|
+
aggregateHash: resolved.aggregateHash,
|
|
1704
|
+
indexHtml: resolved.indexHtml,
|
|
1705
|
+
manifest: resolved.manifest
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
function postPajaIntentLauncherMessage(frame, message) {
|
|
1709
|
+
if (!frame.contentWindow) throw new Error("Paja intent launcher frame is unavailable");
|
|
1710
|
+
frame.contentWindow.postMessage(message, "*");
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1487
1713
|
// src/server.ts
|
|
1488
1714
|
import { createServer } from "http";
|
|
1489
1715
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -1722,9 +1948,13 @@ export {
|
|
|
1722
1948
|
findRelativeAssetReferences,
|
|
1723
1949
|
InstalledNappletCatalog,
|
|
1724
1950
|
BrowserIntentController,
|
|
1951
|
+
parsePajaIntentLink,
|
|
1952
|
+
createPajaIntentLink,
|
|
1953
|
+
resolvePajaIntentLauncher,
|
|
1954
|
+
postPajaIntentLauncherMessage,
|
|
1725
1955
|
PAJA_TARGET_CORS_HINT,
|
|
1726
1956
|
classifyTargetCors,
|
|
1727
1957
|
probeTargetCors,
|
|
1728
1958
|
startPajaServer
|
|
1729
1959
|
};
|
|
1730
|
-
//# sourceMappingURL=chunk-
|
|
1960
|
+
//# sourceMappingURL=chunk-FUBJAN4W.js.map
|