@kehto/paja 0.6.0 → 0.8.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 +65 -16
- package/dist/browser-host.js +13762 -10815
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-5QKVKI4K.js → chunk-NFJCS5PG.js} +177 -32
- package/dist/chunk-NFJCS5PG.js.map +1 -0
- package/dist/cli.d.ts +18 -1
- package/dist/cli.js +10 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +89 -5
- package/dist/index.js +3 -1
- package/dist/{server-qAXYczds.d.ts → server-efRTBcMR.d.ts} +132 -7
- package/package.json +13 -13
- package/dist/chunk-5QKVKI4K.js.map +0 -1
|
@@ -32,7 +32,12 @@ var PAJA_SIMULATION_DOMAINS = [
|
|
|
32
32
|
"cvm",
|
|
33
33
|
"inc"
|
|
34
34
|
];
|
|
35
|
-
var DEFAULT_RELAY_URLS = [
|
|
35
|
+
var DEFAULT_RELAY_URLS = [
|
|
36
|
+
"wss://relay.damus.io",
|
|
37
|
+
"wss://nos.lol",
|
|
38
|
+
"wss://relay.primal.net",
|
|
39
|
+
"wss://relay.snort.social"
|
|
40
|
+
];
|
|
36
41
|
var DEFAULT_CONFIG_VALUES = {
|
|
37
42
|
runtime: "kehto paja",
|
|
38
43
|
mode: "development",
|
|
@@ -46,7 +51,7 @@ function normalizePajaSimulation(raw) {
|
|
|
46
51
|
const domains = Object.fromEntries(
|
|
47
52
|
PAJA_SIMULATION_DOMAINS.map((domain) => [domain, domainOverrides[domain] ?? true])
|
|
48
53
|
);
|
|
49
|
-
const relayMode = raw?.relay?.mode ?? (domains.relay ? "
|
|
54
|
+
const relayMode = raw?.relay?.mode ?? (domains.relay ? "live" : "disabled");
|
|
50
55
|
if (!domains.relay && relayMode !== "disabled") {
|
|
51
56
|
throw new PajaSimulationError('Invalid simulation: relay.mode must be "disabled" when capabilities.domains.relay is false.');
|
|
52
57
|
}
|
|
@@ -94,8 +99,8 @@ function normalizePajaSimulation(raw) {
|
|
|
94
99
|
throw new PajaSimulationError('Invalid simulation: identity.pubkey must be a 64-character hex string when identity.mode is "fixed".');
|
|
95
100
|
}
|
|
96
101
|
const relayUrls = raw?.relay?.urls ?? DEFAULT_RELAY_URLS;
|
|
97
|
-
if (relayMode
|
|
98
|
-
throw new PajaSimulationError(
|
|
102
|
+
if (relayMode !== "disabled" && relayUrls.length === 0) {
|
|
103
|
+
throw new PajaSimulationError("Invalid simulation: relay.urls must contain at least one URL when relay.mode is enabled.");
|
|
99
104
|
}
|
|
100
105
|
for (const url of relayUrls) {
|
|
101
106
|
if (typeof url !== "string" || url.trim().length === 0) {
|
|
@@ -106,6 +111,13 @@ function normalizePajaSimulation(raw) {
|
|
|
106
111
|
if (uploadMode === "memory" && uploadRail.length === 0) {
|
|
107
112
|
throw new PajaSimulationError('Invalid simulation: upload.rail must be non-empty when upload.mode is "memory".');
|
|
108
113
|
}
|
|
114
|
+
const uploadServers = normalizeUploadServers(raw?.upload?.servers ?? []);
|
|
115
|
+
const discoverServers = raw?.upload?.discoverServers ?? uploadMode === "blossom";
|
|
116
|
+
const maxBytes = raw?.upload?.maxBytes;
|
|
117
|
+
if (maxBytes !== void 0 && (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)) {
|
|
118
|
+
throw new PajaSimulationError("Invalid simulation: upload.maxBytes must be a positive safe integer.");
|
|
119
|
+
}
|
|
120
|
+
const mimeTypes = normalizeMimeTypes(raw?.upload?.mimeTypes);
|
|
109
121
|
return {
|
|
110
122
|
capabilities: {
|
|
111
123
|
domains,
|
|
@@ -134,7 +146,11 @@ function normalizePajaSimulation(raw) {
|
|
|
134
146
|
},
|
|
135
147
|
upload: {
|
|
136
148
|
mode: uploadMode,
|
|
137
|
-
rail: uploadRail
|
|
149
|
+
...uploadMode === "memory" ? { rail: uploadRail } : {},
|
|
150
|
+
servers: uploadServers,
|
|
151
|
+
discoverServers,
|
|
152
|
+
...maxBytes !== void 0 ? { maxBytes } : {},
|
|
153
|
+
...mimeTypes ? { mimeTypes } : {}
|
|
138
154
|
},
|
|
139
155
|
media: {
|
|
140
156
|
enabled: mediaEnabled
|
|
@@ -159,12 +175,54 @@ function normalizePajaSimulation(raw) {
|
|
|
159
175
|
};
|
|
160
176
|
}
|
|
161
177
|
function summarizePajaSimulation(simulation) {
|
|
162
|
-
const relay = simulation.relay.mode === "
|
|
178
|
+
const relay = simulation.relay.mode === "disabled" ? "relay:off" : `relay:${simulation.relay.mode}:${simulation.relay.urls.length}`;
|
|
163
179
|
const identity = simulation.identity.mode === "fixed" ? "identity:fixed" : "identity:anon";
|
|
164
180
|
const storage = `storage:${simulation.storage.mode}`;
|
|
181
|
+
const upload = simulation.upload.mode === "memory" ? "upload:memory:simulator" : simulation.upload.mode === "blossom" ? `upload:blossom:${simulation.upload.servers.length}` : "upload:off";
|
|
165
182
|
const theme = `theme:${simulation.theme.mode}`;
|
|
166
183
|
const disabled = simulation.capabilities.disabledDomains.length > 0 ? `off:${simulation.capabilities.disabledDomains.join(",")}` : "off:none";
|
|
167
|
-
return `${identity} ${relay} ${storage} ${theme} ${disabled}`;
|
|
184
|
+
return `${identity} ${relay} ${storage} ${upload} ${theme} ${disabled}`;
|
|
185
|
+
}
|
|
186
|
+
function normalizeUploadServers(servers) {
|
|
187
|
+
const normalized = [];
|
|
188
|
+
for (const value of servers) {
|
|
189
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
190
|
+
throw new PajaSimulationError("Invalid simulation: upload.servers entries must be non-empty URLs.");
|
|
191
|
+
}
|
|
192
|
+
let url;
|
|
193
|
+
try {
|
|
194
|
+
url = new URL(value.trim());
|
|
195
|
+
} catch {
|
|
196
|
+
throw new PajaSimulationError(`Invalid simulation: upload server "${value}" is not a valid URL.`);
|
|
197
|
+
}
|
|
198
|
+
if (url.username || url.password) {
|
|
199
|
+
throw new PajaSimulationError("Invalid simulation: upload server URLs must not contain credentials.");
|
|
200
|
+
}
|
|
201
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost(url.hostname))) {
|
|
202
|
+
throw new PajaSimulationError("Invalid simulation: upload servers require HTTPS, except loopback HTTP for development.");
|
|
203
|
+
}
|
|
204
|
+
if (url.search || url.hash) {
|
|
205
|
+
throw new PajaSimulationError("Invalid simulation: upload server URLs must not contain a query or fragment.");
|
|
206
|
+
}
|
|
207
|
+
const result = url.href.replace(/\/+$/, "");
|
|
208
|
+
if (!normalized.includes(result)) normalized.push(result);
|
|
209
|
+
}
|
|
210
|
+
return normalized;
|
|
211
|
+
}
|
|
212
|
+
function isLoopbackHost(hostname) {
|
|
213
|
+
return hostname === "localhost" || hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(hostname);
|
|
214
|
+
}
|
|
215
|
+
function normalizeMimeTypes(mimeTypes) {
|
|
216
|
+
if (mimeTypes === void 0) return void 0;
|
|
217
|
+
const normalized = [];
|
|
218
|
+
for (const value of mimeTypes) {
|
|
219
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
220
|
+
throw new PajaSimulationError("Invalid simulation: upload.mimeTypes entries must be non-empty strings.");
|
|
221
|
+
}
|
|
222
|
+
const mimeType = value.trim();
|
|
223
|
+
if (!normalized.includes(mimeType)) normalized.push(mimeType);
|
|
224
|
+
}
|
|
225
|
+
return normalized;
|
|
168
226
|
}
|
|
169
227
|
function isHexPubkey(value) {
|
|
170
228
|
return /^[0-9a-fA-F]{64}$/.test(value);
|
|
@@ -416,22 +474,32 @@ function renderPajaHtml(config) {
|
|
|
416
474
|
--text: #f4f0df;
|
|
417
475
|
--muted: #a9ad9f;
|
|
418
476
|
--accent: #d8c36a;
|
|
477
|
+
--paja-console-column: minmax(320px, 380px);
|
|
419
478
|
}
|
|
420
479
|
* { box-sizing: border-box; }
|
|
421
480
|
html, body { margin: 0; min-height: 100%; background: var(--bg); color: var(--text); font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
422
481
|
body { height: 100vh; display: grid; grid-template-rows: 38px minmax(0, 1fr) 30px; overflow: hidden; }
|
|
423
482
|
.bar { display: flex; align-items: center; gap: 14px; min-width: 0; padding: 0 12px; background: var(--bar); border-color: var(--line); }
|
|
424
|
-
.top { border-bottom: 1px solid var(--line); }
|
|
483
|
+
.top { display: grid; grid-template-columns: var(--paja-console-column) minmax(0, 1fr); align-items: stretch; gap: 0; padding: 0; border-bottom: 1px solid var(--line); }
|
|
484
|
+
.top-console { min-width: 0; display: flex; align-items: center; gap: 14px; padding: 0 12px; border-right: 1px solid var(--line); }
|
|
485
|
+
.top-stage { min-width: 0; display: flex; align-items: stretch; gap: 14px; padding: 0 12px 0 0; overflow: hidden; }
|
|
425
486
|
.bottom { border-top: 1px solid var(--line); color: var(--muted); font-size: 12px; }
|
|
426
487
|
.brand { font-weight: 700; letter-spacing: 0; color: var(--accent); white-space: nowrap; }
|
|
427
488
|
.brand-product { color: var(--text); }
|
|
428
489
|
.target { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
|
|
429
490
|
.spacer { flex: 1; min-width: 0; }
|
|
491
|
+
.tabs { display: flex; align-items: stretch; align-self: flex-end; gap: 4px; min-width: 120px; max-width: min(100%, 760px); overflow-x: auto; scrollbar-width: thin; }
|
|
492
|
+
.tabs:empty { display: none; }
|
|
493
|
+
.tab { min-width: 116px; max-width: 240px; height: 30px; display: grid; grid-template-columns: minmax(0, 1fr) 24px 24px; align-items: center; gap: 2px; border: 1px solid var(--line); border-bottom-color: transparent; background: #151815; color: var(--muted); border-radius: 5px 5px 0 0; padding: 0 2px 0 9px; }
|
|
494
|
+
.tab[data-active="true"] { color: var(--text); border-color: var(--accent); border-bottom-color: #151815; background: #20241f; }
|
|
495
|
+
.tab-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; text-align: left; }
|
|
496
|
+
.tab-share, .tab-close { width: 20px; height: 20px; padding: 0; border: 0; background: transparent; color: var(--muted); display: inline-grid; place-items: center; }
|
|
497
|
+
.tab-share:hover, .tab-close:hover { color: var(--text); background: #2d302b; border-color: transparent; }
|
|
430
498
|
button { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; padding: 0 10px; border-radius: 4px; font: inherit; cursor: pointer; }
|
|
431
499
|
button:hover { border-color: var(--accent); }
|
|
432
500
|
label { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); white-space: nowrap; }
|
|
433
501
|
select, input { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; border-radius: 4px; font: inherit; }
|
|
434
|
-
main { min-height: 0; display: grid; grid-template-columns:
|
|
502
|
+
main { min-height: 0; display: grid; grid-template-columns: var(--paja-console-column) minmax(0, 1fr); }
|
|
435
503
|
.console { min-height: 0; overflow: auto; border-right: 1px solid var(--line); background: #121512; padding: 10px; display: flex; flex-direction: column; gap: 12px; }
|
|
436
504
|
.section { display: grid; gap: 8px; }
|
|
437
505
|
.section-title { color: var(--accent); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0; }
|
|
@@ -454,10 +522,22 @@ function renderPajaHtml(config) {
|
|
|
454
522
|
.log-body { min-width: 0; display: grid; gap: 2px; }
|
|
455
523
|
.log-type { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
456
524
|
.log-detail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #e6a5a5; }
|
|
457
|
-
.stage { min-width: 0; min-height: 0; }
|
|
525
|
+
.stage { min-width: 0; min-height: 0; position: relative; background: #050705; }
|
|
526
|
+
.empty-stage { position: absolute; inset: 0; display: grid; place-items: center; color: var(--muted); font-size: 12px; }
|
|
527
|
+
.empty-stage[hidden] { display: none; }
|
|
528
|
+
.tab-panel { position: absolute; inset: 0; min-width: 0; min-height: 0; }
|
|
529
|
+
.tab-panel[hidden] { display: none; }
|
|
458
530
|
iframe { width: 100%; height: 100%; border: 0; background: white; display: block; }
|
|
459
531
|
code { color: var(--text); }
|
|
532
|
+
.dialog-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 24px; background: rgb(0 0 0 / 0.58); }
|
|
533
|
+
.dialog-backdrop[hidden] { display: none; }
|
|
534
|
+
.dialog { width: min(420px, 100%); border: 1px solid var(--line); border-radius: 6px; background: #181b19; box-shadow: 0 18px 60px rgb(0 0 0 / 0.45); padding: 16px; display: grid; gap: 14px; }
|
|
535
|
+
.dialog-title { font-weight: 700; color: var(--text); }
|
|
536
|
+
.dialog-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; }
|
|
460
537
|
@media (max-width: 900px) {
|
|
538
|
+
.top { grid-template-columns: minmax(0, 1fr); }
|
|
539
|
+
.top-console { display: none; }
|
|
540
|
+
.top-stage { padding-left: 0; }
|
|
461
541
|
main { grid-template-columns: 1fr; grid-template-rows: minmax(240px, 40vh) minmax(0, 1fr); }
|
|
462
542
|
.console { border-right: 0; border-bottom: 1px solid var(--line); }
|
|
463
543
|
}
|
|
@@ -465,16 +545,21 @@ function renderPajaHtml(config) {
|
|
|
465
545
|
</head>
|
|
466
546
|
<body>
|
|
467
547
|
<header class="bar top">
|
|
468
|
-
<div class="
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
548
|
+
<div class="top-console">
|
|
549
|
+
<div class="brand">@kehto/<span class="brand-product">paja</span></div>
|
|
550
|
+
<div class="target" title="${targetLabel}">${targetLabel}</div>
|
|
551
|
+
</div>
|
|
552
|
+
<div class="top-stage">
|
|
553
|
+
<div class="tabs" id="napplet-tabs" role="tablist" aria-label="Loaded napplets"></div>
|
|
554
|
+
<div class="spacer"></div>
|
|
555
|
+
<label>theme
|
|
556
|
+
<select id="simulation-theme" aria-label="Simulation theme">
|
|
557
|
+
<option value="dark"${config.simulation.theme.mode === "dark" ? " selected" : ""}>dark</option>
|
|
558
|
+
<option value="light"${config.simulation.theme.mode === "light" ? " selected" : ""}>light</option>
|
|
559
|
+
</select>
|
|
560
|
+
</label>
|
|
561
|
+
<button type="button" id="reload-target">Reload</button>
|
|
562
|
+
</div>
|
|
478
563
|
</header>
|
|
479
564
|
<main>
|
|
480
565
|
<aside class="console" aria-label="Paja development controls">
|
|
@@ -501,10 +586,9 @@ function renderPajaHtml(config) {
|
|
|
501
586
|
<div class="log-list" id="message-log" aria-live="polite"></div>
|
|
502
587
|
</section>
|
|
503
588
|
</aside>
|
|
504
|
-
|
|
505
|
-
<iframe id="napplet-frame" title="Napplet ${config.target.mode === "runtime-pointer" ? "runtime" : "development"} target" sandbox="allow-scripts" data-target-url="${targetLabel}"></iframe>
|
|
506
|
-
</section>
|
|
589
|
+
${renderStage(config, targetLabel)}
|
|
507
590
|
</main>
|
|
591
|
+
${renderDuplicateDialog()}
|
|
508
592
|
<footer class="bar bottom">
|
|
509
593
|
<span>mode: <code>${escapeHtml(getModeLabel(config))}</code></span>
|
|
510
594
|
<span>hmr: <code>${config.target.hmrStrategy}</code></span>
|
|
@@ -517,6 +601,28 @@ function renderPajaHtml(config) {
|
|
|
517
601
|
</body>
|
|
518
602
|
</html>`;
|
|
519
603
|
}
|
|
604
|
+
function renderStage(config, targetLabel) {
|
|
605
|
+
if (config.target.mode === "runtime-pointer") {
|
|
606
|
+
return `<section class="stage" id="napplet-stage" aria-label="Loaded napplet runtimes">
|
|
607
|
+
<div class="empty-stage" id="empty-runtime-stage">Load a napplet pointer to start a runtime tab.</div>
|
|
608
|
+
</section>`;
|
|
609
|
+
}
|
|
610
|
+
return `<section class="stage" id="napplet-stage">
|
|
611
|
+
<iframe id="napplet-frame" title="Napplet development target" sandbox="allow-scripts" data-target-url="${targetLabel}"></iframe>
|
|
612
|
+
</section>`;
|
|
613
|
+
}
|
|
614
|
+
function renderDuplicateDialog() {
|
|
615
|
+
return `<div class="dialog-backdrop" id="duplicate-pointer-dialog" hidden>
|
|
616
|
+
<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="duplicate-pointer-title">
|
|
617
|
+
<div class="dialog-title" id="duplicate-pointer-title">this napplet is already running.</div>
|
|
618
|
+
<div class="dialog-actions">
|
|
619
|
+
<button type="button" id="duplicate-load-again">load it again</button>
|
|
620
|
+
<button type="button" id="duplicate-open-tab">open it in tab</button>
|
|
621
|
+
<button type="button" id="duplicate-cancel">cancel</button>
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
</div>`;
|
|
625
|
+
}
|
|
520
626
|
function renderPointerControls(config) {
|
|
521
627
|
if (config.target.mode !== "runtime-pointer") return "";
|
|
522
628
|
const value = escapeAttribute(config.target.pointer?.value ?? "");
|
|
@@ -554,10 +660,14 @@ import { SimplePool } from "nostr-tools/pool";
|
|
|
554
660
|
import { decode } from "nostr-tools/nip19";
|
|
555
661
|
import {
|
|
556
662
|
fetchBlob,
|
|
663
|
+
isNappletManifestKind,
|
|
664
|
+
NAPPLET_KIND_NAMED,
|
|
665
|
+
NAPPLET_KINDS,
|
|
557
666
|
openNappletArtifactCache,
|
|
558
667
|
resolveNapplet
|
|
559
668
|
} from "@kehto/nip/5d";
|
|
560
|
-
var PAJA_NAPPLET_MANIFEST_KIND =
|
|
669
|
+
var PAJA_NAPPLET_MANIFEST_KIND = NAPPLET_KIND_NAMED;
|
|
670
|
+
var PAJA_NAPPLET_MANIFEST_KINDS = NAPPLET_KINDS;
|
|
561
671
|
function decodePajaPointer(value) {
|
|
562
672
|
const trimmed = value.trim();
|
|
563
673
|
const decoded = decode(trimmed);
|
|
@@ -641,17 +751,17 @@ async function resolvePointerEvent(pointer, pool, relays, maxWaitMs) {
|
|
|
641
751
|
if (!event) {
|
|
642
752
|
throw new Error(`No napplet manifest event found for ${pointer.type} pointer.`);
|
|
643
753
|
}
|
|
644
|
-
if (event.kind
|
|
645
|
-
throw new Error(`Pointer resolved kind ${event.kind}; expected ${
|
|
754
|
+
if (!isNappletManifestKind(event.kind)) {
|
|
755
|
+
throw new Error(`Pointer resolved kind ${event.kind}; expected ${formatNappletManifestKinds()}.`);
|
|
646
756
|
}
|
|
647
757
|
return event;
|
|
648
758
|
}
|
|
649
759
|
function addressableFilter(pointer) {
|
|
650
|
-
if (pointer.kind !==
|
|
651
|
-
throw new Error(`naddr kind ${pointer.kind} is not a napplet manifest kind.`);
|
|
760
|
+
if (pointer.kind !== NAPPLET_KIND_NAMED) {
|
|
761
|
+
throw new Error(`naddr kind ${pointer.kind} is not a named NIP-5D napplet manifest kind.`);
|
|
652
762
|
}
|
|
653
763
|
return {
|
|
654
|
-
kinds: [
|
|
764
|
+
kinds: [NAPPLET_KIND_NAMED],
|
|
655
765
|
authors: [pointer.pubkey],
|
|
656
766
|
"#d": [pointer.identifier],
|
|
657
767
|
limit: 1
|
|
@@ -701,6 +811,9 @@ function normalizeRelayUrl(value) {
|
|
|
701
811
|
function escapeAttribute2(value) {
|
|
702
812
|
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<").replaceAll(">", ">");
|
|
703
813
|
}
|
|
814
|
+
function formatNappletManifestKinds() {
|
|
815
|
+
return PAJA_NAPPLET_MANIFEST_KINDS.join(" / ");
|
|
816
|
+
}
|
|
704
817
|
|
|
705
818
|
// src/parity.ts
|
|
706
819
|
var PAJA_LEGACY_COMPATIBILITY_DOMAIN = `i${"fc"}`;
|
|
@@ -842,8 +955,7 @@ async function startPajaServer(input) {
|
|
|
842
955
|
configJson = JSON.stringify(hostConfig, null, 2);
|
|
843
956
|
return hostConfig;
|
|
844
957
|
};
|
|
845
|
-
const
|
|
846
|
-
const requestUrl = request.url ?? "/";
|
|
958
|
+
const handleRequest = async (requestUrl, response) => {
|
|
847
959
|
if (requestUrl === "/" || requestUrl.startsWith("/?")) {
|
|
848
960
|
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
849
961
|
response.end(html);
|
|
@@ -854,6 +966,15 @@ async function startPajaServer(input) {
|
|
|
854
966
|
response.end(configJson);
|
|
855
967
|
return;
|
|
856
968
|
}
|
|
969
|
+
if (requestUrl === "/__kehto/target.html") {
|
|
970
|
+
const targetHtml = await fetchTargetHtml(hostConfig.target.url);
|
|
971
|
+
response.writeHead(200, {
|
|
972
|
+
"cache-control": "no-store",
|
|
973
|
+
"content-type": "text/html; charset=utf-8"
|
|
974
|
+
});
|
|
975
|
+
response.end(targetHtml);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
857
978
|
if (requestUrl === "/__kehto/browser-host.js") {
|
|
858
979
|
const browserScript = readBrowserHostScript();
|
|
859
980
|
response.writeHead(200, { "content-type": "text/javascript; charset=utf-8" });
|
|
@@ -862,6 +983,13 @@ async function startPajaServer(input) {
|
|
|
862
983
|
}
|
|
863
984
|
response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
864
985
|
response.end("Not found");
|
|
986
|
+
};
|
|
987
|
+
const server = createServer((request, response) => {
|
|
988
|
+
void handleRequest(request.url ?? "/", response).catch((error) => {
|
|
989
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
990
|
+
response.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
|
|
991
|
+
response.end(message);
|
|
992
|
+
});
|
|
865
993
|
});
|
|
866
994
|
await listen(server, options.host, options.port);
|
|
867
995
|
const servedOptions = withBoundPort(options, getBoundPort(server, options.port));
|
|
@@ -880,6 +1008,17 @@ async function startPajaServer(input) {
|
|
|
880
1008
|
function readBrowserHostScript() {
|
|
881
1009
|
return readFileSync2(new URL("./browser-host.js", import.meta.url), "utf8");
|
|
882
1010
|
}
|
|
1011
|
+
async function fetchTargetHtml(targetUrl) {
|
|
1012
|
+
const response = await fetch(targetUrl, {
|
|
1013
|
+
headers: {
|
|
1014
|
+
accept: "text/html, application/xhtml+xml;q=0.9, */*;q=0.8"
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
if (!response.ok) {
|
|
1018
|
+
throw new Error(`Target ${targetUrl} returned ${response.status} ${response.statusText}`);
|
|
1019
|
+
}
|
|
1020
|
+
return response.text();
|
|
1021
|
+
}
|
|
883
1022
|
function listen(server, host, port) {
|
|
884
1023
|
return new Promise((resolve2, reject) => {
|
|
885
1024
|
server.once("error", reject);
|
|
@@ -891,13 +1030,18 @@ function listen(server, host, port) {
|
|
|
891
1030
|
}
|
|
892
1031
|
function close(server) {
|
|
893
1032
|
return new Promise((resolve2, reject) => {
|
|
1033
|
+
const forceClose = setTimeout(() => {
|
|
1034
|
+
server.closeAllConnections?.();
|
|
1035
|
+
}, 500);
|
|
894
1036
|
server.close((error) => {
|
|
1037
|
+
clearTimeout(forceClose);
|
|
895
1038
|
if (error) {
|
|
896
1039
|
reject(error);
|
|
897
1040
|
return;
|
|
898
1041
|
}
|
|
899
1042
|
resolve2();
|
|
900
1043
|
});
|
|
1044
|
+
server.closeIdleConnections?.();
|
|
901
1045
|
});
|
|
902
1046
|
}
|
|
903
1047
|
function getBoundPort(server, fallback) {
|
|
@@ -947,6 +1091,7 @@ export {
|
|
|
947
1091
|
formatPajaUrl,
|
|
948
1092
|
renderPajaHtml,
|
|
949
1093
|
PAJA_NAPPLET_MANIFEST_KIND,
|
|
1094
|
+
PAJA_NAPPLET_MANIFEST_KINDS,
|
|
950
1095
|
decodePajaPointer,
|
|
951
1096
|
resolvePajaPointer,
|
|
952
1097
|
injectPajaRuntimeCsp,
|
|
@@ -961,4 +1106,4 @@ export {
|
|
|
961
1106
|
waitForTargetUrl,
|
|
962
1107
|
startPajaServer
|
|
963
1108
|
};
|
|
964
|
-
//# sourceMappingURL=chunk-
|
|
1109
|
+
//# sourceMappingURL=chunk-NFJCS5PG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config-file.ts","../src/simulation.ts","../src/options.ts","../src/host-page.ts","../src/runtime-resolver.ts","../src/parity.ts","../src/readiness.ts","../src/server.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport type { PajaRawOptions } from './options.js';\nimport type { PajaSimulationRawOptions } from './simulation.js';\nimport { PajaSimulationError } from './simulation.js';\n\n/**\n * Load a JSON Paja config file from disk.\n *\n * @param configPath - Path to a JSON config file.\n * @returns Raw options parsed from the config file.\n */\nexport function loadPajaConfigFile(configPath: string): PajaRawOptions {\n const resolved = resolve(configPath);\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(resolved, 'utf8'));\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n throw new PajaSimulationError(`Unable to read --config \"${configPath}\": ${detail}`);\n }\n\n if (!isRecord(parsed)) {\n throw new PajaSimulationError(`Invalid --config \"${configPath}\": expected a JSON object.`);\n }\n\n return parsed as PajaRawOptions;\n}\n\n/**\n * Resolve raw options by merging an optional config file with explicit values.\n *\n * @param raw - CLI/config options, optionally including `configPath`.\n * @returns Raw options with config-file defaults applied.\n */\nexport function resolvePajaRawOptions(raw: PajaRawOptions): PajaRawOptions {\n if (!raw.configPath?.trim()) return raw;\n return mergePajaRawOptions(loadPajaConfigFile(raw.configPath), raw);\n}\n\n/**\n * Merge two raw Paja option objects, with override values taking precedence.\n *\n * @param base - Base options, usually from a config file.\n * @param override - Explicit options, usually from CLI args.\n * @returns Merged raw options.\n */\nexport function mergePajaRawOptions(\n base: PajaRawOptions,\n override: PajaRawOptions,\n): PajaRawOptions {\n return {\n targetUrl: override.targetUrl ?? base.targetUrl,\n command: override.command ?? base.command,\n host: override.host ?? base.host,\n port: override.port ?? base.port,\n readyTimeoutMs: override.readyTimeoutMs ?? base.readyTimeoutMs,\n configPath: override.configPath ?? base.configPath,\n simulation: mergeSimulationOptions(base.simulation, override.simulation),\n };\n}\n\nfunction mergeSimulationOptions(\n base: PajaSimulationRawOptions | undefined,\n override: PajaSimulationRawOptions | undefined,\n): PajaSimulationRawOptions | undefined {\n if (!base) return override;\n if (!override) return base;\n\n return {\n capabilities: {\n ...base.capabilities,\n ...override.capabilities,\n domains: {\n ...base.capabilities?.domains,\n ...override.capabilities?.domains,\n },\n },\n acl: { ...base.acl, ...override.acl },\n firewall: { ...base.firewall, ...override.firewall },\n identity: { ...base.identity, ...override.identity },\n relay: { ...base.relay, ...override.relay },\n storage: { ...base.storage, ...override.storage },\n cache: { ...base.cache, ...override.cache },\n upload: { ...base.upload, ...override.upload },\n media: { ...base.media, ...override.media },\n notifications: { ...base.notifications, ...override.notifications },\n config: {\n ...base.config,\n ...override.config,\n values: { ...base.config?.values, ...override.config?.values },\n },\n theme: {\n ...base.theme,\n ...override.theme,\n values: { ...base.theme?.values, ...override.theme?.values },\n },\n intent: { ...base.intent, ...override.intent },\n cvm: { ...base.cvm, ...override.cvm },\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","/** JSON primitive accepted in Paja simulation config values. */\nexport type JsonPrimitive = string | number | boolean | null;\n/** JSON value accepted in Paja simulation config values. */\nexport type JsonValue = JsonPrimitive | JsonValue[] | { readonly [key: string]: JsonValue };\n/** JSON object used for Paja simulation fixtures and config/theme values. */\nexport type JsonRecord = Record<string, JsonValue>;\n\n/** Capability domain names Paja can advertise or disable. */\nexport type PajaCapabilityDomain =\n | 'relay'\n | 'outbox'\n | 'storage'\n | 'identity'\n | 'keys'\n | 'config'\n | 'resource'\n | 'theme'\n | 'notify'\n | 'media'\n | 'upload'\n | 'intent'\n | 'count'\n | 'link'\n | 'common'\n | 'lists'\n | 'serial'\n | 'ble'\n | 'webrtc'\n | 'cvm'\n | 'inc';\n\n/** Raw simulation controls accepted from CLI args or JSON config. */\nexport interface PajaSimulationRawOptions {\n /** Per-domain capability overrides. */\n readonly capabilities?: {\n readonly domains?: Partial<Record<PajaCapabilityDomain, boolean>>;\n };\n /** ACL behavior mode for simulated services. */\n readonly acl?: {\n readonly mode?: 'allow' | 'deny';\n };\n readonly firewall?: {\n readonly mode?: 'allow' | 'deny' | 'observe';\n };\n readonly identity?: {\n readonly mode?: 'anonymous' | 'fixed';\n readonly pubkey?: string;\n };\n readonly relay?: {\n readonly mode?: 'live' | 'memory' | 'disabled';\n readonly urls?: readonly string[];\n readonly fixtures?: readonly JsonRecord[];\n };\n readonly storage?: {\n readonly mode?: 'local' | 'memory' | 'disabled';\n };\n readonly cache?: {\n readonly mode?: 'memory' | 'disabled';\n };\n readonly upload?: {\n readonly mode?: 'memory' | 'blossom' | 'disabled';\n readonly rail?: string;\n readonly servers?: readonly string[];\n readonly discoverServers?: boolean;\n readonly maxBytes?: number;\n readonly mimeTypes?: readonly string[];\n };\n readonly media?: {\n readonly enabled?: boolean;\n };\n readonly notifications?: {\n readonly enabled?: boolean;\n readonly grant?: boolean;\n };\n readonly config?: {\n readonly values?: JsonRecord;\n };\n readonly theme?: {\n readonly mode?: 'dark' | 'light';\n readonly values?: JsonRecord;\n };\n readonly intent?: {\n readonly enabled?: boolean;\n };\n readonly cvm?: {\n readonly enabled?: boolean;\n };\n}\n\n/** Fully normalized Paja simulation model served to the browser host. */\nexport interface PajaSimulation {\n /** Capability domains and disabled-domain summary. */\n readonly capabilities: {\n readonly domains: Record<PajaCapabilityDomain, boolean>;\n readonly disabledDomains: readonly PajaCapabilityDomain[];\n };\n readonly acl: {\n readonly mode: 'allow' | 'deny';\n };\n readonly firewall: {\n readonly mode: 'allow' | 'deny' | 'observe';\n };\n readonly identity: {\n readonly mode: 'anonymous' | 'fixed';\n readonly pubkey: string;\n };\n readonly relay: {\n readonly mode: 'live' | 'memory' | 'disabled';\n readonly urls: readonly string[];\n readonly fixtures: readonly JsonRecord[];\n };\n readonly storage: {\n readonly mode: 'local' | 'memory' | 'disabled';\n };\n readonly cache: {\n readonly mode: 'memory' | 'disabled';\n };\n readonly upload: {\n readonly mode: 'memory' | 'blossom' | 'disabled';\n readonly rail?: string;\n readonly servers: readonly string[];\n readonly discoverServers: boolean;\n readonly maxBytes?: number;\n readonly mimeTypes?: readonly string[];\n };\n readonly media: {\n readonly enabled: boolean;\n };\n readonly notifications: {\n readonly enabled: boolean;\n readonly grant: boolean;\n };\n readonly config: {\n readonly values: JsonRecord;\n };\n readonly theme: {\n readonly mode: 'dark' | 'light';\n readonly values: JsonRecord;\n };\n readonly intent: {\n readonly enabled: boolean;\n };\n readonly cvm: {\n readonly enabled: boolean;\n };\n}\n\n/** Thrown when Paja simulation options are internally inconsistent. */\nexport class PajaSimulationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PajaSimulationError';\n }\n}\n\n/** Capability domains exposed by Paja simulation controls. */\nexport const PAJA_SIMULATION_DOMAINS: readonly PajaCapabilityDomain[] = [\n 'relay',\n 'outbox',\n 'storage',\n 'identity',\n 'keys',\n 'config',\n 'resource',\n 'theme',\n 'notify',\n 'media',\n 'upload',\n 'intent',\n 'count',\n 'link',\n 'common',\n 'lists',\n 'serial',\n 'ble',\n 'webrtc',\n 'cvm',\n 'inc',\n];\n\nconst DEFAULT_RELAY_URLS = [\n 'wss://relay.damus.io',\n 'wss://nos.lol',\n 'wss://relay.primal.net',\n 'wss://relay.snort.social',\n] as const;\nconst DEFAULT_CONFIG_VALUES: JsonRecord = {\n runtime: 'kehto paja',\n mode: 'development',\n target: 'single-window',\n};\n\nconst DEFAULT_THEME_VALUES: JsonRecord = {\n title: 'Kehto Paja',\n};\n\n/**\n * Normalize raw simulation options into a complete deterministic model.\n *\n * @param raw - Partial simulation options from CLI args or config.\n * @returns Complete simulation model.\n */\nexport function normalizePajaSimulation(\n raw: PajaSimulationRawOptions | undefined,\n): PajaSimulation {\n const domainOverrides = raw?.capabilities?.domains ?? {};\n const domains = Object.fromEntries(\n PAJA_SIMULATION_DOMAINS.map((domain) => [domain, domainOverrides[domain] ?? true]),\n ) as Record<PajaCapabilityDomain, boolean>;\n\n const relayMode = raw?.relay?.mode ?? (domains.relay ? 'live' : 'disabled');\n if (!domains.relay && relayMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: relay.mode must be \"disabled\" when capabilities.domains.relay is false.');\n }\n if (!domains.outbox && relayMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: relay.mode must be \"disabled\" when capabilities.domains.outbox is false.');\n }\n if (relayMode === 'disabled') {\n domains.relay = false;\n domains.outbox = false;\n }\n\n const uploadMode = raw?.upload?.mode ?? (domains.upload ? 'memory' : 'disabled');\n if (!domains.upload && uploadMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: upload.mode must be \"disabled\" when capabilities.domains.upload is false.');\n }\n if (uploadMode === 'disabled') domains.upload = false;\n\n const intentEnabled = raw?.intent?.enabled ?? domains.intent;\n if (!domains.intent && intentEnabled) {\n throw new PajaSimulationError('Invalid simulation: intent.enabled must be false when capabilities.domains.intent is false.');\n }\n if (!intentEnabled) domains.intent = false;\n\n if (!domains.relay) domains.count = false;\n\n const mediaEnabled = raw?.media?.enabled ?? domains.media;\n if (!domains.media && mediaEnabled) {\n throw new PajaSimulationError('Invalid simulation: media.enabled must be false when capabilities.domains.media is false.');\n }\n if (!mediaEnabled) domains.media = false;\n\n const cvmEnabled = raw?.cvm?.enabled ?? domains.cvm;\n if (!domains.cvm && cvmEnabled) {\n throw new PajaSimulationError('Invalid simulation: cvm.enabled must be false when capabilities.domains.cvm is false.');\n }\n if (!cvmEnabled) domains.cvm = false;\n\n const notificationsEnabled = raw?.notifications?.enabled ?? domains.notify;\n if (!domains.notify && notificationsEnabled) {\n throw new PajaSimulationError('Invalid simulation: notifications.enabled must be false when capabilities.domains.notify is false.');\n }\n if (!notificationsEnabled) domains.notify = false;\n\n const storageMode = raw?.storage?.mode ?? (domains.storage ? 'local' : 'disabled');\n if (!domains.storage && storageMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: storage.mode must be \"disabled\" when capabilities.domains.storage is false.');\n }\n if (storageMode === 'disabled') domains.storage = false;\n\n const identityMode = raw?.identity?.mode ?? 'anonymous';\n const pubkey = raw?.identity?.pubkey?.trim() ?? '';\n if (identityMode === 'fixed' && !isHexPubkey(pubkey)) {\n throw new PajaSimulationError('Invalid simulation: identity.pubkey must be a 64-character hex string when identity.mode is \"fixed\".');\n }\n\n const relayUrls = raw?.relay?.urls ?? DEFAULT_RELAY_URLS;\n if (relayMode !== 'disabled' && relayUrls.length === 0) {\n throw new PajaSimulationError('Invalid simulation: relay.urls must contain at least one URL when relay.mode is enabled.');\n }\n for (const url of relayUrls) {\n if (typeof url !== 'string' || url.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: relay.urls entries must be non-empty strings.');\n }\n }\n\n const uploadRail = raw?.upload?.rail?.trim() || 'dev-memory';\n if (uploadMode === 'memory' && uploadRail.length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.rail must be non-empty when upload.mode is \"memory\".');\n }\n const uploadServers = normalizeUploadServers(raw?.upload?.servers ?? []);\n const discoverServers = raw?.upload?.discoverServers ?? uploadMode === 'blossom';\n const maxBytes = raw?.upload?.maxBytes;\n if (maxBytes !== undefined && (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)) {\n throw new PajaSimulationError('Invalid simulation: upload.maxBytes must be a positive safe integer.');\n }\n const mimeTypes = normalizeMimeTypes(raw?.upload?.mimeTypes);\n\n return {\n capabilities: {\n domains,\n disabledDomains: PAJA_SIMULATION_DOMAINS.filter((domain) => !domains[domain]),\n },\n acl: {\n mode: raw?.acl?.mode ?? 'allow',\n },\n firewall: {\n mode: raw?.firewall?.mode ?? 'observe',\n },\n identity: {\n mode: identityMode,\n pubkey: identityMode === 'fixed' ? pubkey : '',\n },\n relay: {\n mode: relayMode,\n urls: relayUrls.map((url) => url.trim()),\n fixtures: raw?.relay?.fixtures ?? [],\n },\n storage: {\n mode: storageMode,\n },\n cache: {\n mode: raw?.cache?.mode ?? 'memory',\n },\n upload: {\n mode: uploadMode,\n ...(uploadMode === 'memory' ? { rail: uploadRail } : {}),\n servers: uploadServers,\n discoverServers,\n ...(maxBytes !== undefined ? { maxBytes } : {}),\n ...(mimeTypes ? { mimeTypes } : {}),\n },\n media: {\n enabled: mediaEnabled,\n },\n notifications: {\n enabled: notificationsEnabled,\n grant: raw?.notifications?.grant ?? true,\n },\n config: {\n values: { ...DEFAULT_CONFIG_VALUES, ...raw?.config?.values },\n },\n theme: {\n mode: raw?.theme?.mode ?? 'dark',\n values: { ...DEFAULT_THEME_VALUES, ...raw?.theme?.values },\n },\n intent: {\n enabled: intentEnabled,\n },\n cvm: {\n enabled: cvmEnabled,\n },\n };\n}\n\n/**\n * Format a compact, human-readable simulation summary.\n *\n * @param simulation - Normalized simulation model.\n * @returns One-line summary used by CLI and host-page chrome.\n */\nexport function summarizePajaSimulation(simulation: PajaSimulation): string {\n const relay = simulation.relay.mode === 'disabled' ? 'relay:off' : `relay:${simulation.relay.mode}:${simulation.relay.urls.length}`;\n const identity = simulation.identity.mode === 'fixed' ? 'identity:fixed' : 'identity:anon';\n const storage = `storage:${simulation.storage.mode}`;\n const upload = simulation.upload.mode === 'memory'\n ? 'upload:memory:simulator'\n : simulation.upload.mode === 'blossom'\n ? `upload:blossom:${simulation.upload.servers.length}`\n : 'upload:off';\n const theme = `theme:${simulation.theme.mode}`;\n const disabled = simulation.capabilities.disabledDomains.length > 0\n ? `off:${simulation.capabilities.disabledDomains.join(',')}`\n : 'off:none';\n return `${identity} ${relay} ${storage} ${upload} ${theme} ${disabled}`;\n}\n\n/** Normalize an ordered list of shell-owned Blossom server base URLs. */\nexport function normalizeUploadServers(servers: readonly string[]): string[] {\n const normalized: string[] = [];\n for (const value of servers) {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.servers entries must be non-empty URLs.');\n }\n let url: URL;\n try {\n url = new URL(value.trim());\n } catch {\n throw new PajaSimulationError(`Invalid simulation: upload server \"${value}\" is not a valid URL.`);\n }\n if (url.username || url.password) {\n throw new PajaSimulationError('Invalid simulation: upload server URLs must not contain credentials.');\n }\n if (url.protocol !== 'https:' && !(url.protocol === 'http:' && isLoopbackHost(url.hostname))) {\n throw new PajaSimulationError('Invalid simulation: upload servers require HTTPS, except loopback HTTP for development.');\n }\n if (url.search || url.hash) {\n throw new PajaSimulationError('Invalid simulation: upload server URLs must not contain a query or fragment.');\n }\n const result = url.href.replace(/\\/+$/, '');\n if (!normalized.includes(result)) normalized.push(result);\n }\n return normalized;\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === 'localhost'\n || hostname === '[::1]'\n || /^127(?:\\.\\d{1,3}){3}$/.test(hostname);\n}\n\nfunction normalizeMimeTypes(mimeTypes: readonly string[] | undefined): string[] | undefined {\n if (mimeTypes === undefined) return undefined;\n const normalized: string[] = [];\n for (const value of mimeTypes) {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.mimeTypes entries must be non-empty strings.');\n }\n const mimeType = value.trim();\n if (!normalized.includes(mimeType)) normalized.push(mimeType);\n }\n return normalized;\n}\n\nfunction isHexPubkey(value: string): boolean {\n return /^[0-9a-fA-F]{64}$/.test(value);\n}\n","import {\n normalizePajaSimulation,\n type PajaSimulation,\n type PajaSimulationRawOptions,\n} from './simulation.js';\n\n/** Command configuration for a managed Paja target process. */\nexport type PajaCommand =\n /** Spawn an executable with argv preserved exactly after `--`. */\n | { readonly mode: 'argv'; readonly argv: readonly string[] }\n /** Run a shell command string. */\n | { readonly mode: 'shell'; readonly command: string };\n\n/** Raw, unnormalized options accepted from CLI arguments or config files. */\nexport interface PajaRawOptions {\n /** App dev-server URL that Paja should load. */\n readonly targetUrl?: string;\n /** Optional managed command to start before serving Paja. */\n readonly command?: PajaCommand;\n /** Hostname or IP address for the Paja HTTP server. */\n readonly host?: string;\n /** Runtime HTTP port. */\n readonly port?: number | string;\n /** Target readiness timeout in milliseconds. */\n readonly readyTimeoutMs?: number | string;\n /** Optional JSON config file path. */\n readonly configPath?: string;\n /** Simulation controls for injected shell domains. */\n readonly simulation?: PajaSimulationRawOptions;\n}\n\n/** Paja target loading mode. */\nexport type PajaTargetMode = 'iframe-url' | 'runtime-pointer';\n/** Hot-module-reload strategy for the hosted target. */\nexport type PajaHmrStrategy = 'iframe-target-url' | 'none';\n\n/** Runtime-pointer resolution config embedded into the Paja host page. */\nexport interface PajaPointerRuntimeConfig {\n /** Optional naddr/nevent pointer to resolve at boot. */\n readonly value?: string;\n /** Relay hints used to resolve the pointer. */\n readonly relays: readonly string[];\n /** Blossom server hints used for artifact blob fetches. */\n readonly blossomServers: readonly string[];\n /** Maximum pointer-resolution wait in milliseconds. */\n readonly maxWaitMs: number;\n}\n\n/** Normalized Paja runtime options. */\nexport interface PajaOptions {\n /** Absolute target URL for iframe-url mode. */\n readonly targetUrl: string;\n /** Optional managed target command. */\n readonly command?: PajaCommand;\n /** Paja server host. */\n readonly host: string;\n /** Paja server port. */\n readonly port: number;\n /** Target readiness timeout in milliseconds. */\n readonly readyTimeoutMs: number;\n /** Optional config path retained for host diagnostics. */\n readonly configPath?: string;\n /** Normalized simulation model. */\n readonly simulation: PajaSimulation;\n /** Whether Paja owns a managed command or attaches to an external target. */\n readonly mode: 'external-target' | 'managed-command';\n}\n\n/** Serialized host-page config served to the browser runtime. */\nexport interface PajaHostConfig {\n /** Config schema version. */\n readonly version: 1;\n /** Window identity used for the development target. */\n readonly window: {\n readonly id: string;\n readonly dTag: string;\n readonly aggregateHash: string;\n };\n readonly target: {\n readonly mode: PajaTargetMode;\n readonly url: string;\n readonly hmrStrategy: PajaHmrStrategy;\n readonly command?: PajaCommand;\n readonly pointer?: PajaPointerRuntimeConfig;\n };\n readonly runtime: {\n readonly host: string;\n readonly port: number;\n readonly readyTimeoutMs: number;\n readonly configPath?: string;\n readonly reloadToken: string;\n readonly createdAt: string;\n };\n readonly chrome: {\n readonly topBar: true;\n readonly bottomBar: true;\n readonly sidePanels: false;\n };\n readonly simulation: PajaSimulation;\n}\n\n/** Thrown when raw Paja options cannot be normalized. */\nexport class PajaOptionsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PajaOptionsError';\n }\n}\n\n/** Default loopback host for the Paja HTTP server. */\nexport const DEFAULT_PAJA_HOST = '127.0.0.1';\n/** Default Paja HTTP port. */\nexport const DEFAULT_PAJA_PORT = 5197;\n/** Default readiness timeout for the target URL. */\nexport const DEFAULT_READY_TIMEOUT_MS = 30_000;\n/** Default development window id used in host config. */\nexport const DEFAULT_PAJA_WINDOW_ID = 'kehto-paja-window';\n/** Default development `d` tag used for target identity. */\nexport const DEFAULT_PAJA_DTAG = 'dev-target';\n/** Default development aggregate hash used for target identity. */\nexport const DEFAULT_PAJA_AGGREGATE_HASH = 'paja';\n/** Default pointer resolution wait in runtime-pointer mode. */\nexport const DEFAULT_PAJA_RUNTIME_WAIT_MS = 5_000;\n\n/**\n * Normalize raw CLI/config options into a validated Paja runtime config.\n *\n * @param raw - Raw options from CLI parsing or a config file.\n * @returns Fully normalized runtime options.\n */\nexport function normalizePajaOptions(raw: PajaRawOptions): PajaOptions {\n const targetUrl = normalizeTargetUrl(raw.targetUrl);\n const host = normalizeHost(raw.host);\n const port = normalizePort(raw.port, 'port');\n const readyTimeoutMs = normalizePositiveInteger(\n raw.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,\n 'ready-timeout',\n );\n const command = normalizeCommand(raw.command);\n const configPath = raw.configPath?.trim();\n const simulation = normalizePajaSimulation(raw.simulation);\n\n return {\n targetUrl,\n command,\n host,\n port,\n readyTimeoutMs,\n configPath: configPath && configPath.length > 0 ? configPath : undefined,\n simulation,\n mode: command ? 'managed-command' : 'external-target',\n };\n}\n\n/**\n * Create browser host-page config for iframe-url mode.\n *\n * @param options - Normalized Paja options.\n * @param now - Clock value used for reload token and timestamps.\n * @returns Serializable host-page config.\n */\nexport function createPajaHostConfig(\n options: PajaOptions,\n now: Date = new Date(),\n): PajaHostConfig {\n const base = createPajaHostConfigBase(options.simulation);\n return {\n ...base,\n target: {\n mode: 'iframe-url',\n url: options.targetUrl,\n hmrStrategy: 'iframe-target-url',\n command: options.command,\n },\n runtime: {\n host: options.host,\n port: options.port,\n readyTimeoutMs: options.readyTimeoutMs,\n configPath: options.configPath,\n reloadToken: createReloadToken(now),\n createdAt: now.toISOString(),\n },\n };\n}\n\n/**\n * Create browser host-page config for runtime-pointer mode.\n *\n * @param options - Pointer, relay, and Blossom hints.\n * @param now - Clock value used for reload token and timestamps.\n * @returns Serializable host-page config.\n */\nexport function createPajaRuntimeHostConfig(\n options: {\n readonly pointer?: string;\n readonly relays?: readonly string[];\n readonly blossomServers?: readonly string[];\n readonly maxWaitMs?: number;\n } = {},\n now: Date = new Date(),\n): PajaHostConfig {\n const base = createPajaHostConfigBase(normalizePajaSimulation({}));\n return {\n ...base,\n target: {\n mode: 'runtime-pointer',\n url: 'about:blank',\n hmrStrategy: 'none',\n pointer: {\n ...(options.pointer ? { value: options.pointer } : {}),\n relays: [...(options.relays ?? [])],\n blossomServers: [...(options.blossomServers ?? [])],\n maxWaitMs: normalizePositiveInteger(\n options.maxWaitMs ?? DEFAULT_PAJA_RUNTIME_WAIT_MS,\n 'runtime-wait',\n ),\n },\n },\n runtime: {\n host: 'static',\n port: 0,\n readyTimeoutMs: 1,\n reloadToken: createReloadToken(now),\n createdAt: now.toISOString(),\n },\n };\n}\n\n/**\n * Format the local Paja runtime URL for display and CLI output.\n *\n * @param options - Host and port pair.\n * @returns HTTP URL for the Paja runtime.\n */\nexport function formatPajaUrl(options: Pick<PajaOptions, 'host' | 'port'>): string {\n return `http://${options.host}:${options.port}/`;\n}\n\nfunction normalizeTargetUrl(value: string | undefined): string {\n const raw = value?.trim();\n if (!raw) {\n throw new PajaOptionsError('Missing --target-url. Provide the napplet app URL that the iframe should load.');\n }\n\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new PajaOptionsError(`Invalid --target-url \"${raw}\". Expected an absolute http(s) URL.`);\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new PajaOptionsError(`Invalid --target-url \"${raw}\". Only http: and https: URLs are supported.`);\n }\n\n return parsed.href;\n}\n\nfunction normalizeHost(value: string | undefined): string {\n const host = value?.trim() || DEFAULT_PAJA_HOST;\n if (host.length === 0 || host.includes('/')) {\n throw new PajaOptionsError(`Invalid --host \"${value ?? ''}\". Provide a hostname or IP address.`);\n }\n return host;\n}\n\nfunction normalizePort(value: number | string | undefined, label: string): number {\n return normalizeIntegerInRange(value ?? DEFAULT_PAJA_PORT, label, 0, 65_535);\n}\n\nfunction normalizePositiveInteger(value: number | string, label: string, max = Number.MAX_SAFE_INTEGER): number {\n return normalizeIntegerInRange(value, label, 1, max);\n}\n\nfunction createPajaHostConfigBase(simulation: PajaSimulation): Pick<PajaHostConfig, 'version' | 'window' | 'chrome' | 'simulation'> {\n return {\n version: 1,\n window: {\n id: DEFAULT_PAJA_WINDOW_ID,\n dTag: DEFAULT_PAJA_DTAG,\n aggregateHash: DEFAULT_PAJA_AGGREGATE_HASH,\n },\n chrome: {\n topBar: true,\n bottomBar: true,\n sidePanels: false,\n },\n simulation,\n };\n}\n\nfunction normalizeIntegerInRange(value: number | string, label: string, min: number, max: number): number {\n const parsed = typeof value === 'number' ? value : Number(value);\n if (!Number.isInteger(parsed) || parsed < min || parsed > max) {\n throw new PajaOptionsError(`Invalid --${label} \"${String(value)}\". Expected an integer from ${min} to ${max}.`);\n }\n return parsed;\n}\n\nfunction normalizeCommand(command: PajaCommand | undefined): PajaCommand | undefined {\n if (!command) return undefined;\n\n if (command.mode === 'argv') {\n const argv = command.argv.map((part) => part.trim()).filter((part) => part.length > 0);\n if (argv.length === 0) {\n throw new PajaOptionsError('Command mode requires at least one command argument after --.');\n }\n return { mode: 'argv', argv };\n }\n\n const shellCommand = command.command.trim();\n if (shellCommand.length === 0) {\n throw new PajaOptionsError('--command requires a non-empty command string.');\n }\n return { mode: 'shell', command: shellCommand };\n}\n\nfunction createReloadToken(now: Date): string {\n const iso = now.toISOString();\n return `reload-${iso.replaceAll(/[^0-9A-Za-z]/g, '')}`;\n}\n","import type { PajaHostConfig } from './options.js';\nimport { summarizePajaSimulation } from './simulation.js';\n\n/**\n * Render the browser host page for a Paja runtime config.\n *\n * @param config - Serializable host-page config.\n * @returns Complete HTML document served by Paja.\n */\nexport function renderPajaHtml(config: PajaHostConfig): string {\n const configJson = escapeJsonForHtml(JSON.stringify(config));\n const targetLabel = escapeAttribute(getTargetLabel(config));\n\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>@kehto/paja</title>\n <style>\n :root {\n color-scheme: dark;\n --bg: #101211;\n --bar: #181b19;\n --line: #30352f;\n --text: #f4f0df;\n --muted: #a9ad9f;\n --accent: #d8c36a;\n --paja-console-column: minmax(320px, 380px);\n }\n * { box-sizing: border-box; }\n html, body { margin: 0; min-height: 100%; background: var(--bg); color: var(--text); font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; }\n body { height: 100vh; display: grid; grid-template-rows: 38px minmax(0, 1fr) 30px; overflow: hidden; }\n .bar { display: flex; align-items: center; gap: 14px; min-width: 0; padding: 0 12px; background: var(--bar); border-color: var(--line); }\n .top { display: grid; grid-template-columns: var(--paja-console-column) minmax(0, 1fr); align-items: stretch; gap: 0; padding: 0; border-bottom: 1px solid var(--line); }\n .top-console { min-width: 0; display: flex; align-items: center; gap: 14px; padding: 0 12px; border-right: 1px solid var(--line); }\n .top-stage { min-width: 0; display: flex; align-items: stretch; gap: 14px; padding: 0 12px 0 0; overflow: hidden; }\n .bottom { border-top: 1px solid var(--line); color: var(--muted); font-size: 12px; }\n .brand { font-weight: 700; letter-spacing: 0; color: var(--accent); white-space: nowrap; }\n .brand-product { color: var(--text); }\n .target { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }\n .spacer { flex: 1; min-width: 0; }\n .tabs { display: flex; align-items: stretch; align-self: flex-end; gap: 4px; min-width: 120px; max-width: min(100%, 760px); overflow-x: auto; scrollbar-width: thin; }\n .tabs:empty { display: none; }\n .tab { min-width: 116px; max-width: 240px; height: 30px; display: grid; grid-template-columns: minmax(0, 1fr) 24px 24px; align-items: center; gap: 2px; border: 1px solid var(--line); border-bottom-color: transparent; background: #151815; color: var(--muted); border-radius: 5px 5px 0 0; padding: 0 2px 0 9px; }\n .tab[data-active=\"true\"] { color: var(--text); border-color: var(--accent); border-bottom-color: #151815; background: #20241f; }\n .tab-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; text-align: left; }\n .tab-share, .tab-close { width: 20px; height: 20px; padding: 0; border: 0; background: transparent; color: var(--muted); display: inline-grid; place-items: center; }\n .tab-share:hover, .tab-close:hover { color: var(--text); background: #2d302b; border-color: transparent; }\n button { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; padding: 0 10px; border-radius: 4px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--accent); }\n label { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); white-space: nowrap; }\n select, input { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; border-radius: 4px; font: inherit; }\n main { min-height: 0; display: grid; grid-template-columns: var(--paja-console-column) minmax(0, 1fr); }\n .console { min-height: 0; overflow: auto; border-right: 1px solid var(--line); background: #121512; padding: 10px; display: flex; flex-direction: column; gap: 12px; }\n .section { display: grid; gap: 8px; }\n .section-title { color: var(--accent); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0; }\n .switch-grid, .acl-grid { display: flex; flex-wrap: wrap; gap: 6px; }\n .toggle { height: 24px; padding: 0 8px; color: var(--muted); }\n .toggle[data-enabled=\"true\"] { color: var(--text); border-color: #5f724f; background: #24301f; }\n .toggle[data-enabled=\"false\"] { color: #8d9187; border-color: #453536; background: #241d1d; }\n .signer { color: var(--muted); word-break: break-all; font-size: 12px; }\n .signer-controls { display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto; gap: 6px; }\n .signer-controls button[data-active=\"true\"] { border-color: var(--accent); color: var(--text); background: #2a2a1d; }\n .signer-controls input { min-width: 0; padding: 0 8px; }\n .pointer-controls { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px; }\n .pointer-status { min-width: 0; color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }\n .log-tools { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px; }\n .log-list { min-height: 160px; max-height: 38vh; overflow: auto; border: 1px solid var(--line); border-radius: 4px; background: #0b0d0b; }\n .log-row { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 6px; padding: 5px 7px; border-bottom: 1px solid #1b1f1a; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; }\n .log-row[data-error=\"true\"] { background: #1d1414; }\n .log-row:last-child { border-bottom: 0; }\n .log-dir { color: var(--muted); }\n .log-body { min-width: 0; display: grid; gap: 2px; }\n .log-type { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .log-detail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #e6a5a5; }\n .stage { min-width: 0; min-height: 0; position: relative; background: #050705; }\n .empty-stage { position: absolute; inset: 0; display: grid; place-items: center; color: var(--muted); font-size: 12px; }\n .empty-stage[hidden] { display: none; }\n .tab-panel { position: absolute; inset: 0; min-width: 0; min-height: 0; }\n .tab-panel[hidden] { display: none; }\n iframe { width: 100%; height: 100%; border: 0; background: white; display: block; }\n code { color: var(--text); }\n .dialog-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 24px; background: rgb(0 0 0 / 0.58); }\n .dialog-backdrop[hidden] { display: none; }\n .dialog { width: min(420px, 100%); border: 1px solid var(--line); border-radius: 6px; background: #181b19; box-shadow: 0 18px 60px rgb(0 0 0 / 0.45); padding: 16px; display: grid; gap: 14px; }\n .dialog-title { font-weight: 700; color: var(--text); }\n .dialog-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; }\n @media (max-width: 900px) {\n .top { grid-template-columns: minmax(0, 1fr); }\n .top-console { display: none; }\n .top-stage { padding-left: 0; }\n main { grid-template-columns: 1fr; grid-template-rows: minmax(240px, 40vh) minmax(0, 1fr); }\n .console { border-right: 0; border-bottom: 1px solid var(--line); }\n }\n </style>\n </head>\n <body>\n <header class=\"bar top\">\n <div class=\"top-console\">\n <div class=\"brand\">@kehto/<span class=\"brand-product\">paja</span></div>\n <div class=\"target\" title=\"${targetLabel}\">${targetLabel}</div>\n </div>\n <div class=\"top-stage\">\n <div class=\"tabs\" id=\"napplet-tabs\" role=\"tablist\" aria-label=\"Loaded napplets\"></div>\n <div class=\"spacer\"></div>\n <label>theme\n <select id=\"simulation-theme\" aria-label=\"Simulation theme\">\n <option value=\"dark\"${config.simulation.theme.mode === 'dark' ? ' selected' : ''}>dark</option>\n <option value=\"light\"${config.simulation.theme.mode === 'light' ? ' selected' : ''}>light</option>\n </select>\n </label>\n <button type=\"button\" id=\"reload-target\">Reload</button>\n </div>\n </header>\n <main>\n <aside class=\"console\" aria-label=\"Paja development controls\">\n ${renderPointerControls(config)}\n <section class=\"section\">\n <div class=\"section-title\">Interfaces</div>\n <div class=\"switch-grid\" id=\"interface-toggles\"></div>\n </section>\n <section class=\"section\">\n <div class=\"section-title\">ACL</div>\n <div class=\"acl-grid\" id=\"acl-controls\"></div>\n </section>\n <section class=\"section\">\n <div class=\"section-title\">Signer</div>\n <div class=\"signer\" id=\"signer-status\">loading</div>\n <div class=\"signer-controls\" id=\"signer-controls\"></div>\n </section>\n <section class=\"section\">\n <div class=\"section-title\">Messages</div>\n <div class=\"log-tools\">\n <input id=\"message-filter\" type=\"search\" autocomplete=\"off\" placeholder=\"filter messages\" aria-label=\"Filter message log\">\n <button type=\"button\" id=\"clear-log\">Clear</button>\n </div>\n <div class=\"log-list\" id=\"message-log\" aria-live=\"polite\"></div>\n </section>\n </aside>\n ${renderStage(config, targetLabel)}\n </main>\n ${renderDuplicateDialog()}\n <footer class=\"bar bottom\">\n <span>mode: <code>${escapeHtml(getModeLabel(config))}</code></span>\n <span>hmr: <code>${config.target.hmrStrategy}</code></span>\n <span>runtime: <code>${escapeHtml(config.runtime.host)}:${config.runtime.port}</code></span>\n <span>sim: <code id=\"simulation-status\">${escapeHtml(summarizePajaSimulation(config.simulation))}</code></span>\n <span>state: <code id=\"lifecycle-status\">booting</code></span>\n </footer>\n <script type=\"application/json\" id=\"kehto-paja-config\">${configJson}</script>\n <script type=\"module\" src=\"./__kehto/browser-host.js\"></script>\n </body>\n</html>`;\n}\n\nfunction renderStage(config: PajaHostConfig, targetLabel: string): string {\n if (config.target.mode === 'runtime-pointer') {\n return `<section class=\"stage\" id=\"napplet-stage\" aria-label=\"Loaded napplet runtimes\">\n <div class=\"empty-stage\" id=\"empty-runtime-stage\">Load a napplet pointer to start a runtime tab.</div>\n </section>`;\n }\n return `<section class=\"stage\" id=\"napplet-stage\">\n <iframe id=\"napplet-frame\" title=\"Napplet development target\" sandbox=\"allow-scripts\" data-target-url=\"${targetLabel}\"></iframe>\n </section>`;\n}\n\nfunction renderDuplicateDialog(): string {\n return `<div class=\"dialog-backdrop\" id=\"duplicate-pointer-dialog\" hidden>\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"duplicate-pointer-title\">\n <div class=\"dialog-title\" id=\"duplicate-pointer-title\">this napplet is already running.</div>\n <div class=\"dialog-actions\">\n <button type=\"button\" id=\"duplicate-load-again\">load it again</button>\n <button type=\"button\" id=\"duplicate-open-tab\">open it in tab</button>\n <button type=\"button\" id=\"duplicate-cancel\">cancel</button>\n </div>\n </div>\n </div>`;\n}\n\nfunction renderPointerControls(config: PajaHostConfig): string {\n if (config.target.mode !== 'runtime-pointer') return '';\n const value = escapeAttribute(config.target.pointer?.value ?? '');\n return `<section class=\"section\" id=\"runtime-pointer-section\">\n <div class=\"section-title\">Pointer</div>\n <form class=\"pointer-controls\" id=\"runtime-pointer-form\">\n <input id=\"runtime-pointer-input\" type=\"text\" inputmode=\"url\" autocomplete=\"off\" spellcheck=\"false\" placeholder=\"naddr or nevent\" aria-label=\"Runtime napplet pointer\" value=\"${value}\">\n <button type=\"submit\" id=\"runtime-pointer-load\">Load</button>\n </form>\n <div class=\"pointer-status\" id=\"runtime-pointer-status\" aria-live=\"polite\">idle</div>\n </section>`;\n}\n\nfunction getModeLabel(config: PajaHostConfig): string {\n if (config.target.mode === 'runtime-pointer') return 'runtime-pointer';\n return config.target.command ? 'managed-command' : 'external-target';\n}\n\nfunction getTargetLabel(config: PajaHostConfig): string {\n if (config.target.mode === 'runtime-pointer') {\n return config.target.pointer?.value ?? 'runtime pointer';\n }\n return config.target.url;\n}\n\nfunction escapeAttribute(value: string): string {\n return escapeHtml(value).replaceAll('\"', '"');\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>');\n}\n\nfunction escapeJsonForHtml(value: string): string {\n return value.replaceAll('<', '\\\\u003c');\n}\n","import type { NostrEvent } from 'nostr-tools';\nimport type { Filter } from 'nostr-tools/filter';\nimport { SimplePool } from 'nostr-tools/pool';\nimport { decode } from 'nostr-tools/nip19';\nimport {\n fetchBlob,\n isNappletManifestKind,\n NAPPLET_KIND_NAMED,\n NAPPLET_KINDS,\n openNappletArtifactCache,\n resolveNapplet,\n type ResolvedNapplet,\n} from '@kehto/nip/5d';\n\n/** Named/addressable NIP-5D napplet manifest kind (`35129`). */\nexport const PAJA_NAPPLET_MANIFEST_KIND = NAPPLET_KIND_NAMED;\n/** All NIP-5D napplet manifest kinds accepted by Paja pointers (`5129`, `15129`, `35129`). */\nexport const PAJA_NAPPLET_MANIFEST_KINDS = NAPPLET_KINDS;\n\n/** Decoded naddr/nevent pointer accepted by Paja runtime-pointer mode. */\nexport type PajaDecodedPointer =\n | {\n readonly type: 'naddr';\n readonly value: string;\n readonly identifier: string;\n readonly pubkey: string;\n readonly kind: number;\n readonly relays: readonly string[];\n }\n | {\n readonly type: 'nevent';\n readonly value: string;\n readonly id: string;\n readonly author?: string;\n readonly kind?: number;\n readonly relays: readonly string[];\n };\n\n/** Fully resolved pointer plus verified napplet artifact bytes. */\nexport interface PajaResolvedPointer {\n /** Decoded pointer input. */\n readonly pointer: PajaDecodedPointer;\n /** Manifest event resolved from relays. */\n readonly event: NostrEvent;\n /** Relay URLs queried for the pointer. */\n readonly relays: readonly string[];\n /** Blossom server hints used for blob fetches. */\n readonly blossomServers: readonly string[];\n /** Resolved manifest `d` tag. */\n readonly dTag: string;\n /** Verified NIP-5A aggregate hash. */\n readonly aggregateHash: string;\n /** Verified target `/index.html` content. */\n readonly indexHtml: string;\n /** Parsed NIP-5D manifest. */\n readonly manifest: ResolvedNapplet['manifest'];\n}\n\n/** Relay-pool contract for runtime-pointer resolution. */\nexport interface PajaPointerRelayPool {\n /** Query relays synchronously for matching manifest events. */\n querySync(\n relays: string[],\n filter: Filter,\n params?: { readonly maxWait?: number; readonly label?: string; readonly id?: string },\n ): Promise<NostrEvent[]>;\n close?(relays: string[]): void;\n destroy?(): void;\n}\n\n/** Options for resolving a Paja runtime pointer. */\nexport interface ResolvePajaPointerOptions {\n /** Relay pool override; defaults to a `nostr-tools` `SimplePool`. */\n readonly pool?: PajaPointerRelayPool;\n /** Fetch override used for Blossom blob URLs. */\n readonly fetcher?: (url: string) => Promise<Response>;\n /** Extra relay hints appended to pointer relays. */\n readonly relays?: readonly string[];\n /** Extra Blossom server hints appended during artifact fetch. */\n readonly blossomServers?: readonly string[];\n /** Maximum relay query wait in milliseconds. */\n readonly maxWaitMs?: number;\n}\n\n/**\n * Decode a NIP-19 naddr/nevent pointer for Paja runtime-pointer mode.\n *\n * @param value - NIP-19 pointer string.\n * @returns Structured pointer metadata.\n */\nexport function decodePajaPointer(value: string): PajaDecodedPointer {\n const trimmed = value.trim();\n const decoded = decode(trimmed);\n if (decoded.type === 'naddr') {\n return {\n type: 'naddr',\n value: trimmed,\n identifier: decoded.data.identifier,\n pubkey: decoded.data.pubkey,\n kind: decoded.data.kind,\n relays: [...(decoded.data.relays ?? [])],\n };\n }\n if (decoded.type === 'nevent') {\n return {\n type: 'nevent',\n value: trimmed,\n id: decoded.data.id,\n ...(decoded.data.author ? { author: decoded.data.author } : {}),\n ...(decoded.data.kind ? { kind: decoded.data.kind } : {}),\n relays: [...(decoded.data.relays ?? [])],\n };\n }\n throw new Error(`Expected naddr or nevent pointer, got ${decoded.type}.`);\n}\n\n/**\n * Resolve a Paja runtime pointer into verified napplet HTML and manifest data.\n *\n * @param value - NIP-19 naddr or nevent pointer.\n * @param options - Relay, fetch, and Blossom overrides.\n * @returns Verified napplet artifact ready for srcdoc injection.\n */\nexport async function resolvePajaPointer(\n value: string,\n options: ResolvePajaPointerOptions = {},\n): Promise<PajaResolvedPointer> {\n const pointer = decodePajaPointer(value);\n const relays = uniqueRelayUrls([...pointer.relays, ...(options.relays ?? [])]);\n if (relays.length === 0) {\n throw new Error('Pointer does not include relay hints; add a relay-bearing naddr or nevent.');\n }\n\n const pool = options.pool ?? new SimplePool();\n const ownsPool = options.pool === undefined;\n try {\n const event = await resolvePointerEvent(pointer, pool, relays, options.maxWaitMs ?? 5_000);\n const blossomServers = [...(options.blossomServers ?? [])];\n const resolved = await resolveNapplet({\n event,\n cache: await openNappletArtifactCache({ requireStorageEstimate: false }),\n fetchBlob: (sha256Hex, servers) =>\n fetchBlob([...servers, ...blossomServers], sha256Hex, async (url) => {\n const response = await (options.fetcher ?? fetch)(url);\n if (!response.ok) throw new Error(`[paja-runtime] blob ${url}: ${response.status}`);\n return new Uint8Array(await response.arrayBuffer());\n }),\n });\n return {\n pointer,\n event,\n relays,\n blossomServers,\n dTag: resolved.dTag,\n aggregateHash: resolved.aggregateHash,\n indexHtml: resolved.indexHtml,\n manifest: resolved.manifest,\n };\n } finally {\n if (ownsPool) {\n pool.close?.(relays);\n pool.destroy?.();\n }\n }\n}\n\n/**\n * Inject a restrictive connect-src CSP for runtime-pointer srcdoc output.\n *\n * @param html - Verified target HTML.\n * @param origins - Origins the resolved target may connect to.\n * @returns HTML with a CSP meta tag inserted.\n */\nexport function injectPajaRuntimeCsp(html: string, origins: readonly string[]): string {\n const value = origins.length > 0\n ? `connect-src ${[...new Set(origins)].sort().join(' ')}`\n : \"connect-src 'none'\";\n const meta = `<meta http-equiv=\"Content-Security-Policy\" content=\"${escapeAttribute(value)}\">`;\n if (/<head[^>]*>/i.test(html)) {\n return html.replace(/<head[^>]*>/i, (open) => `${open}${meta}`);\n }\n if (/<html[^>]*>/i.test(html)) {\n return html.replace(/<html[^>]*>/i, (open) => `${open}<head>${meta}</head>`);\n }\n return `${meta}${html}`;\n}\n\nasync function resolvePointerEvent(\n pointer: PajaDecodedPointer,\n pool: PajaPointerRelayPool,\n relays: readonly string[],\n maxWaitMs: number,\n): Promise<NostrEvent> {\n const filter = pointer.type === 'naddr'\n ? addressableFilter(pointer)\n : eventFilter(pointer);\n const events = await pool.querySync([...relays], filter, {\n maxWait: maxWaitMs,\n label: 'kehto-paja-runtime',\n });\n const event = events\n .filter((candidate) => matchesPointer(pointer, candidate))\n .sort((a, b) => b.created_at - a.created_at)[0];\n if (!event) {\n throw new Error(`No napplet manifest event found for ${pointer.type} pointer.`);\n }\n if (!isNappletManifestKind(event.kind)) {\n throw new Error(`Pointer resolved kind ${event.kind}; expected ${formatNappletManifestKinds()}.`);\n }\n return event;\n}\n\nfunction addressableFilter(pointer: Extract<PajaDecodedPointer, { type: 'naddr' }>): Filter {\n if (pointer.kind !== NAPPLET_KIND_NAMED) {\n throw new Error(`naddr kind ${pointer.kind} is not a named NIP-5D napplet manifest kind.`);\n }\n return {\n kinds: [NAPPLET_KIND_NAMED],\n authors: [pointer.pubkey],\n '#d': [pointer.identifier],\n limit: 1,\n };\n}\n\nfunction eventFilter(pointer: Extract<PajaDecodedPointer, { type: 'nevent' }>): Filter {\n return {\n ids: [pointer.id],\n ...(pointer.kind ? { kinds: [pointer.kind] } : {}),\n ...(pointer.author ? { authors: [pointer.author] } : {}),\n limit: 1,\n };\n}\n\nfunction matchesPointer(pointer: PajaDecodedPointer, event: NostrEvent): boolean {\n if (pointer.type === 'nevent') {\n if (event.id !== pointer.id) return false;\n if (pointer.author && event.pubkey !== pointer.author) return false;\n if (pointer.kind && event.kind !== pointer.kind) return false;\n return true;\n }\n return event.kind === pointer.kind\n && event.pubkey === pointer.pubkey\n && event.tags.some((tag) => tag[0] === 'd' && tag[1] === pointer.identifier);\n}\n\nfunction uniqueRelayUrls(values: readonly string[]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const value of values) {\n const url = normalizeRelayUrl(value);\n if (!url || seen.has(url)) continue;\n seen.add(url);\n out.push(url);\n }\n return out;\n}\n\nfunction normalizeRelayUrl(value: string): string | undefined {\n const raw = value.trim();\n if (!raw) return undefined;\n try {\n const url = new URL(raw);\n if (url.protocol !== 'wss:' && url.protocol !== 'ws:') return undefined;\n url.hash = '';\n url.search = '';\n return url.href.replace(/\\/$/, '');\n } catch {\n return undefined;\n }\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('\"', '"')\n .replaceAll('<', '<')\n .replaceAll('>', '>');\n}\n\nfunction formatNappletManifestKinds(): string {\n return PAJA_NAPPLET_MANIFEST_KINDS.join(' / ');\n}\n","import type { ShellCapabilities } from '@kehto/shell';\n\nconst PAJA_LEGACY_COMPATIBILITY_DOMAIN: string = `i${'fc'}`;\n\n/** Domain list mirrored from the upstream web runtime for parity checks. */\nexport const PAJA_UPSTREAM_WEB_DOMAINS = [\n 'ble',\n 'common',\n 'config',\n 'count',\n 'cvm',\n 'dm',\n 'identity',\n PAJA_LEGACY_COMPATIBILITY_DOMAIN,\n 'inc',\n 'intent',\n 'keys',\n 'link',\n 'lists',\n 'media',\n 'notify',\n 'outbox',\n 'relay',\n 'resource',\n 'serial',\n 'storage',\n 'theme',\n 'upload',\n 'webrtc',\n] as const;\n\n/** Domains required during the initial Paja handshake. */\nexport const PAJA_HANDSHAKE_DOMAINS = ['shell'] as const;\n/** Domains intentionally deferred until their runtime service exists. */\nexport const PAJA_DEFERRED_DOMAINS = ['dm'] as const;\n\n/** Compatibility aliases for renamed upstream capability domains. */\nexport const PAJA_COMPATIBILITY_ALIASES = {\n [PAJA_LEGACY_COMPATIBILITY_DOMAIN]: 'inc',\n} as const;\n\n/** Domains Paja advertises through its simulated shell capability surface. */\nexport const PAJA_ADVERTISED_DOMAINS = [\n 'relay',\n 'outbox',\n 'identity',\n 'storage',\n 'inc',\n 'theme',\n 'keys',\n 'link',\n 'common',\n 'lists',\n 'serial',\n 'ble',\n 'webrtc',\n 'media',\n 'notify',\n 'config',\n 'resource',\n 'cvm',\n 'upload',\n 'intent',\n 'count',\n] as const;\n\n/** Service names Paja expects the host runtime to provide. */\nexport const PAJA_REQUIRED_SERVICES = [\n 'config',\n 'cvm',\n 'identity',\n 'intent',\n 'keys',\n 'link',\n 'common',\n 'lists',\n 'serial',\n 'ble',\n 'webrtc',\n 'media',\n 'notifications',\n 'notify',\n 'outbox',\n 'relay',\n 'resource',\n 'theme',\n 'upload',\n 'count',\n] as const;\n\n/**\n * Find advertised Paja domains missing from a shell capability snapshot.\n *\n * @param capabilities - Shell capability snapshot to inspect.\n * @returns Advertised domains not present in the snapshot.\n */\nexport function getMissingAdvertisedDomains(capabilities: ShellCapabilities): string[] {\n const advertised = new Set(capabilities.domains);\n return PAJA_ADVERTISED_DOMAINS.filter((domain) => !advertised.has(domain));\n}\n\n/**\n * Find required service names missing from a runtime service list.\n *\n * @param services - Runtime service names to inspect.\n * @returns Required service names that are absent.\n */\nexport function getMissingServices(services: readonly string[]): string[] {\n const wired = new Set(services);\n return PAJA_REQUIRED_SERVICES.filter((service) => !wired.has(service));\n}\n","/** Options for polling a target URL until it is ready. */\nexport interface WaitForTargetUrlOptions {\n /** Total wait budget in milliseconds. */\n readonly timeoutMs: number;\n /** Poll interval in milliseconds. Defaults to 250. */\n readonly intervalMs?: number;\n /** Fetch implementation override for tests. */\n readonly fetch?: ReadinessFetch;\n /** Sleep implementation override for tests. */\n readonly sleep?: (ms: number) => Promise<void>;\n /** Clock override for tests. */\n readonly now?: () => number;\n}\n\n/** Fetch-like target readiness probe. */\nexport type ReadinessFetch = (url: string) => Promise<{ readonly status: number }>;\n\n/** Thrown when a target URL does not become ready before the timeout. */\nexport class ReadinessError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ReadinessError';\n }\n}\n\n/**\n * Poll a target URL until it returns a non-5xx response or times out.\n *\n * @param url - Target URL to poll.\n * @param options - Timeout and polling controls.\n */\nexport async function waitForTargetUrl(url: string, options: WaitForTargetUrlOptions): Promise<void> {\n const intervalMs = options.intervalMs ?? 250;\n const fetcher = options.fetch ?? defaultFetch;\n const sleep = options.sleep ?? defaultSleep;\n const now = options.now ?? Date.now;\n const deadline = now() + options.timeoutMs;\n let lastError = 'no response';\n\n while (now() <= deadline) {\n try {\n const response = await fetcher(url);\n if (response.status < 500) {\n return;\n }\n lastError = `status ${response.status}`;\n } catch (error) {\n lastError = error instanceof Error ? error.message : String(error);\n }\n\n await sleep(intervalMs);\n }\n\n throw new ReadinessError(`Timed out waiting ${options.timeoutMs}ms for target URL ${url}: ${lastError}`);\n}\n\nasync function defaultFetch(url: string): Promise<{ readonly status: number }> {\n return fetch(url, { method: 'GET', redirect: 'follow' });\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","import { createServer } from 'node:http';\nimport { readFileSync } from 'node:fs';\nimport {\n createPajaHostConfig,\n formatPajaUrl,\n normalizePajaOptions,\n type PajaHostConfig,\n type PajaOptions,\n type PajaRawOptions,\n} from './options.js';\nimport { renderPajaHtml } from './host-page.js';\nimport { resolvePajaRawOptions } from './config-file.js';\n\n/** Options for starting the Paja local HTTP server. */\nexport interface PajaServerOptions {\n /** Raw options accepted by Paja; config-file references are resolved before serving. */\n readonly options: PajaRawOptions;\n /** Clock override used for deterministic host config in tests. */\n readonly now?: Date;\n}\n\n/** Running Paja server handle. */\nexport interface PajaServer {\n /** Runtime URL where the Paja host page is served. */\n readonly url: string;\n /** Current host-page config. */\n readonly hostConfig: PajaHostConfig;\n /** Update the target iframe URL without restarting the server. */\n updateTargetUrl(targetUrl: string): PajaHostConfig;\n /** Stop the HTTP server. */\n close(): Promise<void>;\n}\n\n/**\n * Start the local Paja development HTTP server.\n *\n * @param input - Raw options and optional clock override.\n * @returns Running server handle.\n */\nexport async function startPajaServer(input: PajaServerOptions): Promise<PajaServer> {\n const rawOptions = resolvePajaRawOptions(input.options);\n const options = normalizePajaOptions(rawOptions);\n const createdAt = input.now ?? new Date();\n let hostConfig = createPajaHostConfig(options, createdAt);\n let html = renderPajaHtml(hostConfig);\n let configJson = JSON.stringify(hostConfig, null, 2);\n let currentOptions = options;\n\n const setHostConfig = (nextOptions: PajaOptions): PajaHostConfig => {\n currentOptions = nextOptions;\n hostConfig = createPajaHostConfig(currentOptions, createdAt);\n html = renderPajaHtml(hostConfig);\n configJson = JSON.stringify(hostConfig, null, 2);\n return hostConfig;\n };\n\n const handleRequest = async (requestUrl: string, response: HttpResponse): Promise<void> => {\n if (requestUrl === '/' || requestUrl.startsWith('/?')) {\n response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });\n response.end(html);\n return;\n }\n\n if (requestUrl === '/__kehto/config.json') {\n response.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });\n response.end(configJson);\n return;\n }\n\n if (requestUrl === '/__kehto/target.html') {\n const targetHtml = await fetchTargetHtml(hostConfig.target.url);\n response.writeHead(200, {\n 'cache-control': 'no-store',\n 'content-type': 'text/html; charset=utf-8',\n });\n response.end(targetHtml);\n return;\n }\n\n if (requestUrl === '/__kehto/browser-host.js') {\n const browserScript = readBrowserHostScript();\n response.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8' });\n response.end(browserScript);\n return;\n }\n\n response.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });\n response.end('Not found');\n };\n\n const server = createServer((request, response) => {\n void handleRequest(request.url ?? '/', response).catch((error) => {\n const message = error instanceof Error ? error.message : String(error);\n response.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });\n response.end(message);\n });\n });\n\n await listen(server, options.host, options.port);\n const servedOptions = withBoundPort(options, getBoundPort(server, options.port));\n setHostConfig(servedOptions);\n\n return {\n url: formatPajaUrl(servedOptions),\n get hostConfig() {\n return hostConfig;\n },\n updateTargetUrl(targetUrl) {\n return setHostConfig(withTargetUrl(currentOptions, targetUrl));\n },\n close: () => close(server),\n };\n}\n\nfunction readBrowserHostScript(): string {\n return readFileSync(new URL('./browser-host.js', import.meta.url), 'utf8');\n}\n\nasync function fetchTargetHtml(targetUrl: string): Promise<string> {\n const response = await fetch(targetUrl, {\n headers: {\n accept: 'text/html, application/xhtml+xml;q=0.9, */*;q=0.8',\n },\n });\n if (!response.ok) {\n throw new Error(`Target ${targetUrl} returned ${response.status} ${response.statusText}`);\n }\n return response.text();\n}\n\nfunction listen(server: HttpServer, host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(port, host, () => {\n server.off('error', reject);\n resolve();\n });\n });\n}\n\nfunction close(server: HttpServer): Promise<void> {\n return new Promise((resolve, reject) => {\n const forceClose = setTimeout(() => {\n server.closeAllConnections?.();\n }, 500);\n server.close((error?: Error) => {\n clearTimeout(forceClose);\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n server.closeIdleConnections?.();\n });\n}\n\ninterface HttpServer {\n listen(port: number, host: string, callback: () => void): void;\n once(event: 'error', callback: (error: Error) => void): void;\n off(event: 'error', callback: (error: Error) => void): void;\n close(callback: (error?: Error) => void): void;\n closeAllConnections?(): void;\n closeIdleConnections?(): void;\n address(): string | { port: number } | null;\n}\n\ninterface HttpResponse {\n writeHead(statusCode: number, headers: Record<string, string>): void;\n end(chunk: string): void;\n}\n\nfunction getBoundPort(server: HttpServer, fallback: number): number {\n const address = server.address();\n return typeof address === 'object' && address !== null ? address.port : fallback;\n}\n\nfunction withBoundPort(options: PajaOptions, port: number): PajaOptions {\n return port === options.port ? options : { ...options, port };\n}\n\nfunction withTargetUrl(options: PajaOptions, targetUrl: string): PajaOptions {\n const url = normalizeServerTargetUrl(targetUrl);\n return url === options.targetUrl ? options : { ...options, targetUrl: url };\n}\n\nfunction normalizeServerTargetUrl(value: string): string {\n const raw = value.trim();\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error(`Invalid managed target URL \"${raw}\". Expected an absolute http(s) URL.`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`Invalid managed target URL \"${raw}\". Only http: and https: URLs are supported.`);\n }\n return parsed.href;\n}\n"],"mappings":"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","names":["escapeAttribute","resolve","readFileSync","readFileSync","resolve"]}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { P as PajaRawOptions, s as startPajaServer, a as PajaCommand, w as waitForTargetUrl } from './server-
|
|
2
|
+
import { P as PajaRawOptions, s as startPajaServer, a as PajaCommand, w as waitForTargetUrl } from './server-efRTBcMR.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Minimal output streams used by the CLI runner.
|
|
@@ -40,10 +40,27 @@ interface RunPajaCliOptions {
|
|
|
40
40
|
* @returns Parsed raw options, or a help flag when `--help` is present.
|
|
41
41
|
*/
|
|
42
42
|
declare function parsePajaArgs(args: readonly string[]): CliParseResult;
|
|
43
|
+
/**
|
|
44
|
+
* Run the Paja CLI command.
|
|
45
|
+
*
|
|
46
|
+
* @param args - Argument vector without the node executable or script path.
|
|
47
|
+
* @param io - Output streams used by the command.
|
|
48
|
+
* @param runOptions - Test/embedding controls for server and process spawning.
|
|
49
|
+
* @returns Process exit code.
|
|
50
|
+
*/
|
|
43
51
|
declare function runPajaCli(args?: readonly string[], io?: CliIo, runOptions?: RunPajaCliOptions): Promise<number>;
|
|
52
|
+
/**
|
|
53
|
+
* Detect whether this module is running as the direct Paja CLI entrypoint.
|
|
54
|
+
*
|
|
55
|
+
* @param entryPath - Process entry path to inspect.
|
|
56
|
+
* @returns `true` when the entry path looks like Paja's built CLI.
|
|
57
|
+
*/
|
|
44
58
|
declare function isDirectPajaCli(entryPath?: string): boolean;
|
|
59
|
+
/** Managed child process handle. */
|
|
45
60
|
interface ManagedChildProcess {
|
|
61
|
+
/** Stop the managed target process. */
|
|
46
62
|
kill(): void;
|
|
63
|
+
/** First local target URL detected from process output. */
|
|
47
64
|
readonly detectedTargetUrl: Promise<string>;
|
|
48
65
|
}
|
|
49
66
|
|