@kehto/paja 0.6.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -16
- package/dist/browser-host.js +15590 -11208
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-ADMLM2LH.js → chunk-OWVINAW7.js} +531 -35
- package/dist/chunk-OWVINAW7.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 +280 -5
- package/dist/index.js +11 -1
- package/dist/{server-qAXYczds.d.ts → server-efRTBcMR.d.ts} +132 -7
- package/package.json +12 -12
- package/dist/chunk-ADMLM2LH.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 ?? "");
|
|
@@ -591,12 +697,12 @@ async function resolvePajaPointer(value, options = {}) {
|
|
|
591
697
|
const pointer = decodePajaPointer(value);
|
|
592
698
|
const relays = uniqueRelayUrls([...pointer.relays, ...options.relays ?? []]);
|
|
593
699
|
if (relays.length === 0) {
|
|
594
|
-
throw new Error("Pointer
|
|
700
|
+
throw new Error("Pointer has no relay hints or enabled configured live relays.");
|
|
595
701
|
}
|
|
596
702
|
const pool = options.pool ?? new SimplePool();
|
|
597
703
|
const ownsPool = options.pool === void 0;
|
|
598
704
|
try {
|
|
599
|
-
const event = await resolvePointerEvent(pointer, pool, relays, options.maxWaitMs
|
|
705
|
+
const event = await resolvePointerEvent(pointer, pool, relays, normalizeMaxWaitMs(options.maxWaitMs));
|
|
600
706
|
const blossomServers = [...options.blossomServers ?? []];
|
|
601
707
|
const resolved = await resolveNapplet({
|
|
602
708
|
event,
|
|
@@ -625,7 +731,26 @@ async function resolvePajaPointer(value, options = {}) {
|
|
|
625
731
|
}
|
|
626
732
|
}
|
|
627
733
|
function injectPajaRuntimeCsp(html, origins) {
|
|
628
|
-
const
|
|
734
|
+
const grantedOrigins = [...new Set(origins)].sort();
|
|
735
|
+
const connectSrc = grantedOrigins.length > 0 ? `connect-src ${grantedOrigins.join(" ")}` : "connect-src 'none'";
|
|
736
|
+
const value = [
|
|
737
|
+
"default-src 'none'",
|
|
738
|
+
"script-src 'unsafe-inline'",
|
|
739
|
+
"style-src 'unsafe-inline'",
|
|
740
|
+
"img-src data: blob:",
|
|
741
|
+
"font-src data:",
|
|
742
|
+
connectSrc,
|
|
743
|
+
"worker-src 'none'",
|
|
744
|
+
"child-src 'none'",
|
|
745
|
+
"frame-src 'none'",
|
|
746
|
+
"media-src 'none'",
|
|
747
|
+
"object-src 'none'",
|
|
748
|
+
"manifest-src 'none'",
|
|
749
|
+
"prefetch-src 'none'",
|
|
750
|
+
"base-uri 'none'",
|
|
751
|
+
"form-action 'none'",
|
|
752
|
+
"frame-ancestors 'self'"
|
|
753
|
+
].join("; ");
|
|
629
754
|
const meta = `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute2(value)}">`;
|
|
630
755
|
if (/<head[^>]*>/i.test(html)) {
|
|
631
756
|
return html.replace(/<head[^>]*>/i, (open) => `${open}${meta}`);
|
|
@@ -637,19 +762,96 @@ function injectPajaRuntimeCsp(html, origins) {
|
|
|
637
762
|
}
|
|
638
763
|
async function resolvePointerEvent(pointer, pool, relays, maxWaitMs) {
|
|
639
764
|
const filter = pointer.type === "naddr" ? addressableFilter(pointer) : eventFilter(pointer);
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
label: "kehto-paja-runtime"
|
|
643
|
-
});
|
|
644
|
-
const event = events.filter((candidate) => matchesPointer(pointer, candidate)).sort((a, b) => b.created_at - a.created_at)[0];
|
|
765
|
+
const result = await queryPointerRelays(pool, relays, filter, maxWaitMs);
|
|
766
|
+
const event = result.events.filter((candidate) => matchesPointer(pointer, candidate)).sort((a, b) => b.created_at - a.created_at)[0];
|
|
645
767
|
if (!event) {
|
|
646
|
-
|
|
768
|
+
if (result.timedOut) {
|
|
769
|
+
throw new Error(
|
|
770
|
+
`Relay resolution timed out after ${maxWaitMs}ms for ${pointer.type} pointer across ${relays.length} relays before complete EOSE.`
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
const failures = relayFailureReasons(result.reasons);
|
|
774
|
+
if (result.error || failures.length > 0) {
|
|
775
|
+
const detail = result.error instanceof Error ? result.error.message : result.error === void 0 ? failures.join("; ") : String(result.error);
|
|
776
|
+
throw new Error(`Relay query failed for ${pointer.type} pointer before complete EOSE: ${detail}`);
|
|
777
|
+
}
|
|
778
|
+
throw new Error(
|
|
779
|
+
`All ${relays.length} relays reached EOSE without a matching ${pointer.type} napplet manifest event.`
|
|
780
|
+
);
|
|
647
781
|
}
|
|
648
782
|
if (!isNappletManifestKind(event.kind)) {
|
|
649
783
|
throw new Error(`Pointer resolved kind ${event.kind}; expected ${formatNappletManifestKinds()}.`);
|
|
650
784
|
}
|
|
651
785
|
return event;
|
|
652
786
|
}
|
|
787
|
+
function queryPointerRelays(pool, relays, filter, maxWaitMs) {
|
|
788
|
+
if (pool.subscribeManyEose) {
|
|
789
|
+
return queryPointerRelaySubscription(pool, relays, filter, maxWaitMs);
|
|
790
|
+
}
|
|
791
|
+
return queryPointerRelaySync(pool, relays, filter, maxWaitMs);
|
|
792
|
+
}
|
|
793
|
+
function queryPointerRelaySubscription(pool, relays, filter, maxWaitMs) {
|
|
794
|
+
return new Promise((resolve2) => {
|
|
795
|
+
const events = [];
|
|
796
|
+
let settled = false;
|
|
797
|
+
let subscription;
|
|
798
|
+
const finish = (result) => {
|
|
799
|
+
if (settled) return;
|
|
800
|
+
settled = true;
|
|
801
|
+
clearTimeout(deadline);
|
|
802
|
+
resolve2({ events, ...result });
|
|
803
|
+
};
|
|
804
|
+
const deadline = setTimeout(() => {
|
|
805
|
+
finish({ reasons: [], timedOut: true });
|
|
806
|
+
void subscription?.close("Paja pointer relay deadline exceeded");
|
|
807
|
+
}, maxWaitMs);
|
|
808
|
+
try {
|
|
809
|
+
subscription = pool.subscribeManyEose([...relays], filter, {
|
|
810
|
+
maxWait: maxWaitMs,
|
|
811
|
+
label: "kehto-paja-runtime",
|
|
812
|
+
onevent(event) {
|
|
813
|
+
events.push(event);
|
|
814
|
+
},
|
|
815
|
+
onclose(reasons) {
|
|
816
|
+
finish({ reasons, timedOut: false });
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
} catch (error) {
|
|
820
|
+
finish({ reasons: [], timedOut: false, error });
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
function queryPointerRelaySync(pool, relays, filter, maxWaitMs) {
|
|
825
|
+
return new Promise((resolve2) => {
|
|
826
|
+
let settled = false;
|
|
827
|
+
const finish = (result) => {
|
|
828
|
+
if (settled) return;
|
|
829
|
+
settled = true;
|
|
830
|
+
clearTimeout(deadline);
|
|
831
|
+
resolve2(result);
|
|
832
|
+
};
|
|
833
|
+
const deadline = setTimeout(() => {
|
|
834
|
+
finish({ events: [], reasons: [], timedOut: true });
|
|
835
|
+
}, maxWaitMs);
|
|
836
|
+
void pool.querySync([...relays], filter, {
|
|
837
|
+
maxWait: maxWaitMs,
|
|
838
|
+
label: "kehto-paja-runtime"
|
|
839
|
+
}).then(
|
|
840
|
+
(events) => finish({ events, reasons: [], timedOut: false }),
|
|
841
|
+
(error) => finish({ events: [], reasons: [], timedOut: false, error })
|
|
842
|
+
);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
function relayFailureReasons(reasons) {
|
|
846
|
+
return [...new Set(reasons.map((reason) => reason.trim()).filter((reason) => reason.length > 0 && reason !== "closed automatically on eose").map((reason) => reason.slice(0, 160)))].slice(0, 3);
|
|
847
|
+
}
|
|
848
|
+
function normalizeMaxWaitMs(value) {
|
|
849
|
+
if (value === void 0) return 5e3;
|
|
850
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
851
|
+
throw new Error("Pointer relay deadline must be a positive number of milliseconds.");
|
|
852
|
+
}
|
|
853
|
+
return Math.floor(value);
|
|
854
|
+
}
|
|
653
855
|
function addressableFilter(pointer) {
|
|
654
856
|
if (pointer.kind !== NAPPLET_KIND_NAMED) {
|
|
655
857
|
throw new Error(`naddr kind ${pointer.kind} is not a named NIP-5D napplet manifest kind.`);
|
|
@@ -831,9 +1033,258 @@ function defaultSleep(ms) {
|
|
|
831
1033
|
});
|
|
832
1034
|
}
|
|
833
1035
|
|
|
1036
|
+
// src/installed-napplet-catalog.ts
|
|
1037
|
+
import { manifestToIntentCatalogEntry } from "@kehto/services";
|
|
1038
|
+
function matchesInstalledNappletRecord(record, target) {
|
|
1039
|
+
return target.dTag === record.dTag && target.aggregateHash === record.aggregateHash;
|
|
1040
|
+
}
|
|
1041
|
+
var InstalledNappletCatalog = class {
|
|
1042
|
+
records = /* @__PURE__ */ new Map();
|
|
1043
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1044
|
+
/** Insert or replace a record from an already resolver-verified pointer. */
|
|
1045
|
+
install(resolved) {
|
|
1046
|
+
const record = freezeRecord({
|
|
1047
|
+
dTag: resolved.dTag,
|
|
1048
|
+
aggregateHash: resolved.aggregateHash,
|
|
1049
|
+
pointer: copyPointer(resolved.pointer),
|
|
1050
|
+
...resolved.manifest.title === void 0 ? {} : { title: resolved.manifest.title },
|
|
1051
|
+
archetypes: resolved.manifest.archetypes.map((archetype) => ({
|
|
1052
|
+
slug: archetype.slug,
|
|
1053
|
+
convention: archetype.convention,
|
|
1054
|
+
...archetype.eventKinds === void 0 ? {} : { eventKinds: [...archetype.eventKinds] }
|
|
1055
|
+
}))
|
|
1056
|
+
});
|
|
1057
|
+
this.records.set(record.dTag, record);
|
|
1058
|
+
this.notify(record.dTag);
|
|
1059
|
+
return record;
|
|
1060
|
+
}
|
|
1061
|
+
/** Remove an artifact explicitly; frame teardown never calls this method. */
|
|
1062
|
+
remove(dTag) {
|
|
1063
|
+
const removed = this.records.delete(dTag);
|
|
1064
|
+
if (removed) this.notify(dTag);
|
|
1065
|
+
return removed;
|
|
1066
|
+
}
|
|
1067
|
+
/** Return whether a verified installation exists for a d-tag. */
|
|
1068
|
+
has(dTag) {
|
|
1069
|
+
return this.records.has(dTag);
|
|
1070
|
+
}
|
|
1071
|
+
/** Return immutable serializable installed-artifact facts. */
|
|
1072
|
+
installed() {
|
|
1073
|
+
return [...this.records.values()];
|
|
1074
|
+
}
|
|
1075
|
+
/** Return a verified record by d-tag. */
|
|
1076
|
+
get(dTag) {
|
|
1077
|
+
return this.records.get(dTag);
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Atomically confirm that a resolved target still belongs to the exact record
|
|
1081
|
+
* selected before an async operation. The record object is the catalog version
|
|
1082
|
+
* token, so even a same-identity replacement cannot pass this check.
|
|
1083
|
+
*/
|
|
1084
|
+
validateCurrent(selected, target) {
|
|
1085
|
+
if (this.records.get(selected.dTag) !== selected) return null;
|
|
1086
|
+
return matchesInstalledNappletRecord(selected, target) ? selected : null;
|
|
1087
|
+
}
|
|
1088
|
+
/** Return exact handler candidates derived only from installed manifests. */
|
|
1089
|
+
intentCatalog() {
|
|
1090
|
+
return this.installed().map((record) => manifestToIntentCatalogEntry({
|
|
1091
|
+
dTag: record.dTag,
|
|
1092
|
+
...record.title === void 0 ? {} : { title: record.title },
|
|
1093
|
+
archetypes: record.archetypes.map((archetype) => ({
|
|
1094
|
+
slug: archetype.slug,
|
|
1095
|
+
convention: archetype.convention,
|
|
1096
|
+
...archetype.eventKinds === void 0 ? {} : { eventKinds: [...archetype.eventKinds] }
|
|
1097
|
+
}))
|
|
1098
|
+
}));
|
|
1099
|
+
}
|
|
1100
|
+
/** Subscribe to explicit install and artifact-removal changes. */
|
|
1101
|
+
onChanged(listener) {
|
|
1102
|
+
this.listeners.add(listener);
|
|
1103
|
+
return () => this.listeners.delete(listener);
|
|
1104
|
+
}
|
|
1105
|
+
notify(dTag) {
|
|
1106
|
+
for (const listener of this.listeners) listener(dTag);
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
function copyPointer(pointer) {
|
|
1110
|
+
if (pointer.type === "naddr") {
|
|
1111
|
+
return Object.freeze({
|
|
1112
|
+
type: "naddr",
|
|
1113
|
+
value: pointer.value,
|
|
1114
|
+
identifier: pointer.identifier,
|
|
1115
|
+
pubkey: pointer.pubkey,
|
|
1116
|
+
kind: pointer.kind,
|
|
1117
|
+
relays: Object.freeze([...pointer.relays])
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
return Object.freeze({
|
|
1121
|
+
type: "nevent",
|
|
1122
|
+
value: pointer.value,
|
|
1123
|
+
id: pointer.id,
|
|
1124
|
+
...pointer.author === void 0 ? {} : { author: pointer.author },
|
|
1125
|
+
...pointer.kind === void 0 ? {} : { kind: pointer.kind },
|
|
1126
|
+
relays: Object.freeze([...pointer.relays])
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
function freezeRecord(record) {
|
|
1130
|
+
return Object.freeze({
|
|
1131
|
+
...record,
|
|
1132
|
+
archetypes: Object.freeze(record.archetypes.map((archetype) => Object.freeze({
|
|
1133
|
+
...archetype,
|
|
1134
|
+
...archetype.eventKinds === void 0 ? {} : { eventKinds: Object.freeze([...archetype.eventKinds]) }
|
|
1135
|
+
})))
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/browser-intent-controller.ts
|
|
1140
|
+
var MAX_INTENT_DELIVERY_ATTEMPTS = 10;
|
|
1141
|
+
var BrowserIntentController = class {
|
|
1142
|
+
constructor(options) {
|
|
1143
|
+
this.options = options;
|
|
1144
|
+
this.maxAttempts = normalizeAttempts(options.maxAttempts);
|
|
1145
|
+
}
|
|
1146
|
+
options;
|
|
1147
|
+
maxAttempts;
|
|
1148
|
+
retain(params) {
|
|
1149
|
+
const retained = freezeRetention(params);
|
|
1150
|
+
let started;
|
|
1151
|
+
let delivered = false;
|
|
1152
|
+
return {
|
|
1153
|
+
start: () => {
|
|
1154
|
+
started ??= this.start(retained, () => delivered, () => {
|
|
1155
|
+
delivered = true;
|
|
1156
|
+
});
|
|
1157
|
+
return started;
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
async start(params, isDelivered, markDelivered) {
|
|
1162
|
+
let reason = "no-current-target";
|
|
1163
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt += 1) {
|
|
1164
|
+
let generation;
|
|
1165
|
+
try {
|
|
1166
|
+
generation = await this.options.openOrReuse(params, attempt);
|
|
1167
|
+
} catch {
|
|
1168
|
+
reason = "open-failed";
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
if (!generation) {
|
|
1172
|
+
reason = "open-failed";
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
try {
|
|
1176
|
+
await this.options.waitForReady(generation);
|
|
1177
|
+
} catch {
|
|
1178
|
+
reason = "ready-failed";
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
if (!await this.options.isCurrent(generation)) {
|
|
1182
|
+
reason = "no-current-target";
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
if (isDelivered()) return;
|
|
1186
|
+
markDelivered();
|
|
1187
|
+
try {
|
|
1188
|
+
await this.options.send(generation, params.delivery);
|
|
1189
|
+
return;
|
|
1190
|
+
} catch {
|
|
1191
|
+
this.options.onTerminal?.(params, "send-failed");
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
this.options.onTerminal?.(params, reason);
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
function normalizeAttempts(value) {
|
|
1199
|
+
if (value === void 0) return 2;
|
|
1200
|
+
if (!Number.isFinite(value)) throw new TypeError("maxAttempts must be finite");
|
|
1201
|
+
return Math.min(MAX_INTENT_DELIVERY_ATTEMPTS, Math.max(1, Math.floor(value)));
|
|
1202
|
+
}
|
|
1203
|
+
function freezeRetention(params) {
|
|
1204
|
+
if (!params || typeof params.handler !== "string" || params.handler.length === 0) {
|
|
1205
|
+
throw new TypeError("Intent retention requires a handler");
|
|
1206
|
+
}
|
|
1207
|
+
const delivery = freezeValue({ ...params.delivery });
|
|
1208
|
+
const behavior = params.behavior === void 0 ? void 0 : freezeValue({ ...params.behavior });
|
|
1209
|
+
return Object.freeze({
|
|
1210
|
+
handler: params.handler,
|
|
1211
|
+
delivery,
|
|
1212
|
+
...behavior === void 0 ? {} : { behavior }
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
function freezeValue(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
1216
|
+
if (typeof value !== "object" || value === null) return value;
|
|
1217
|
+
const existing = seen.get(value);
|
|
1218
|
+
if (existing !== void 0) return existing;
|
|
1219
|
+
if (Array.isArray(value)) {
|
|
1220
|
+
const copy2 = [];
|
|
1221
|
+
seen.set(value, copy2);
|
|
1222
|
+
for (const item of value) copy2.push(freezeValue(item, seen));
|
|
1223
|
+
return Object.freeze(copy2);
|
|
1224
|
+
}
|
|
1225
|
+
const copy = {};
|
|
1226
|
+
seen.set(value, copy);
|
|
1227
|
+
for (const [key, item] of Object.entries(value)) copy[key] = freezeValue(item, seen);
|
|
1228
|
+
return Object.freeze(copy);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
834
1231
|
// src/server.ts
|
|
835
1232
|
import { createServer } from "http";
|
|
836
1233
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1234
|
+
|
|
1235
|
+
// src/target-cors.ts
|
|
1236
|
+
var PAJA_TARGET_CORS_HINT = "Paja sandboxes the napplet frame without allow-same-origin, so its assets are requested with `Origin: null`. Allow that origin in the napplet dev server, e.g. Vite `server: { cors: { origin: '*' } }`.";
|
|
1237
|
+
function classifyTargetCors(targetUrl, allowOrigin) {
|
|
1238
|
+
const value = allowOrigin?.trim() ?? null;
|
|
1239
|
+
if (value === "*" || value === "null") {
|
|
1240
|
+
return {
|
|
1241
|
+
status: "allowed",
|
|
1242
|
+
targetUrl,
|
|
1243
|
+
allowOrigin: value,
|
|
1244
|
+
detail: `Target allows the sandboxed frame's null origin (access-control-allow-origin: ${value}).`,
|
|
1245
|
+
hint: null
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
if (value === null || value === "") {
|
|
1249
|
+
return {
|
|
1250
|
+
status: "blocked",
|
|
1251
|
+
targetUrl,
|
|
1252
|
+
allowOrigin: null,
|
|
1253
|
+
detail: "Target sent no access-control-allow-origin for an Origin: null request.",
|
|
1254
|
+
hint: PAJA_TARGET_CORS_HINT
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
return {
|
|
1258
|
+
status: "blocked",
|
|
1259
|
+
targetUrl,
|
|
1260
|
+
allowOrigin: value,
|
|
1261
|
+
detail: `Target only allows origin "${value}", not the sandboxed frame's null origin.`,
|
|
1262
|
+
hint: PAJA_TARGET_CORS_HINT
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
var defaultTargetCorsFetch = (input, init) => fetch(input, init);
|
|
1266
|
+
async function probeTargetCors(targetUrl, fetchImpl = defaultTargetCorsFetch) {
|
|
1267
|
+
try {
|
|
1268
|
+
const response = await fetchImpl(targetUrl, {
|
|
1269
|
+
method: "GET",
|
|
1270
|
+
headers: {
|
|
1271
|
+
origin: "null",
|
|
1272
|
+
accept: "text/html, application/xhtml+xml;q=0.9, */*;q=0.8"
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
return classifyTargetCors(targetUrl, response.headers.get("access-control-allow-origin"));
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
return {
|
|
1278
|
+
status: "unreachable",
|
|
1279
|
+
targetUrl,
|
|
1280
|
+
allowOrigin: null,
|
|
1281
|
+
detail: `Target CORS probe failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1282
|
+
hint: "Confirm the napplet dev server is running and --target-url points at it."
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// src/server.ts
|
|
837
1288
|
async function startPajaServer(input) {
|
|
838
1289
|
const rawOptions = resolvePajaRawOptions(input.options);
|
|
839
1290
|
const options = normalizePajaOptions(rawOptions);
|
|
@@ -849,8 +1300,7 @@ async function startPajaServer(input) {
|
|
|
849
1300
|
configJson = JSON.stringify(hostConfig, null, 2);
|
|
850
1301
|
return hostConfig;
|
|
851
1302
|
};
|
|
852
|
-
const
|
|
853
|
-
const requestUrl = request.url ?? "/";
|
|
1303
|
+
const handleRequest = async (requestUrl, response) => {
|
|
854
1304
|
if (requestUrl === "/" || requestUrl.startsWith("/?")) {
|
|
855
1305
|
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
856
1306
|
response.end(html);
|
|
@@ -861,6 +1311,24 @@ async function startPajaServer(input) {
|
|
|
861
1311
|
response.end(configJson);
|
|
862
1312
|
return;
|
|
863
1313
|
}
|
|
1314
|
+
if (requestUrl === "/__kehto/target.html") {
|
|
1315
|
+
const targetHtml = await fetchTargetHtml(hostConfig.target.url);
|
|
1316
|
+
response.writeHead(200, {
|
|
1317
|
+
"cache-control": "no-store",
|
|
1318
|
+
"content-type": "text/html; charset=utf-8"
|
|
1319
|
+
});
|
|
1320
|
+
response.end(targetHtml);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
if (requestUrl === "/__kehto/target-cors.json") {
|
|
1324
|
+
const diagnostic = await probeTargetCors(hostConfig.target.url);
|
|
1325
|
+
response.writeHead(200, {
|
|
1326
|
+
"cache-control": "no-store",
|
|
1327
|
+
"content-type": "application/json; charset=utf-8"
|
|
1328
|
+
});
|
|
1329
|
+
response.end(JSON.stringify(diagnostic));
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
864
1332
|
if (requestUrl === "/__kehto/browser-host.js") {
|
|
865
1333
|
const browserScript = readBrowserHostScript();
|
|
866
1334
|
response.writeHead(200, { "content-type": "text/javascript; charset=utf-8" });
|
|
@@ -869,6 +1337,13 @@ async function startPajaServer(input) {
|
|
|
869
1337
|
}
|
|
870
1338
|
response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
871
1339
|
response.end("Not found");
|
|
1340
|
+
};
|
|
1341
|
+
const server = createServer((request, response) => {
|
|
1342
|
+
void handleRequest(request.url ?? "/", response).catch((error) => {
|
|
1343
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1344
|
+
response.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
|
|
1345
|
+
response.end(message);
|
|
1346
|
+
});
|
|
872
1347
|
});
|
|
873
1348
|
await listen(server, options.host, options.port);
|
|
874
1349
|
const servedOptions = withBoundPort(options, getBoundPort(server, options.port));
|
|
@@ -887,6 +1362,17 @@ async function startPajaServer(input) {
|
|
|
887
1362
|
function readBrowserHostScript() {
|
|
888
1363
|
return readFileSync2(new URL("./browser-host.js", import.meta.url), "utf8");
|
|
889
1364
|
}
|
|
1365
|
+
async function fetchTargetHtml(targetUrl) {
|
|
1366
|
+
const response = await fetch(targetUrl, {
|
|
1367
|
+
headers: {
|
|
1368
|
+
accept: "text/html, application/xhtml+xml;q=0.9, */*;q=0.8"
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
if (!response.ok) {
|
|
1372
|
+
throw new Error(`Target ${targetUrl} returned ${response.status} ${response.statusText}`);
|
|
1373
|
+
}
|
|
1374
|
+
return response.text();
|
|
1375
|
+
}
|
|
890
1376
|
function listen(server, host, port) {
|
|
891
1377
|
return new Promise((resolve2, reject) => {
|
|
892
1378
|
server.once("error", reject);
|
|
@@ -898,13 +1384,18 @@ function listen(server, host, port) {
|
|
|
898
1384
|
}
|
|
899
1385
|
function close(server) {
|
|
900
1386
|
return new Promise((resolve2, reject) => {
|
|
1387
|
+
const forceClose = setTimeout(() => {
|
|
1388
|
+
server.closeAllConnections?.();
|
|
1389
|
+
}, 500);
|
|
901
1390
|
server.close((error) => {
|
|
1391
|
+
clearTimeout(forceClose);
|
|
902
1392
|
if (error) {
|
|
903
1393
|
reject(error);
|
|
904
1394
|
return;
|
|
905
1395
|
}
|
|
906
1396
|
resolve2();
|
|
907
1397
|
});
|
|
1398
|
+
server.closeIdleConnections?.();
|
|
908
1399
|
});
|
|
909
1400
|
}
|
|
910
1401
|
function getBoundPort(server, fallback) {
|
|
@@ -967,6 +1458,11 @@ export {
|
|
|
967
1458
|
getMissingServices,
|
|
968
1459
|
ReadinessError,
|
|
969
1460
|
waitForTargetUrl,
|
|
1461
|
+
InstalledNappletCatalog,
|
|
1462
|
+
BrowserIntentController,
|
|
1463
|
+
PAJA_TARGET_CORS_HINT,
|
|
1464
|
+
classifyTargetCors,
|
|
1465
|
+
probeTargetCors,
|
|
970
1466
|
startPajaServer
|
|
971
1467
|
};
|
|
972
|
-
//# sourceMappingURL=chunk-
|
|
1468
|
+
//# sourceMappingURL=chunk-OWVINAW7.js.map
|