@kehto/paja 0.16.4 → 0.17.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 +5 -1
- package/dist/browser-host.js +497 -28
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-AY3UYDP2.js → chunk-ESE55K3Q.js} +29 -3
- package/dist/chunk-ESE55K3Q.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-AY3UYDP2.js.map +0 -1
package/README.md
CHANGED
|
@@ -59,7 +59,11 @@ warning, when the target would block the sandboxed frame.
|
|
|
59
59
|
|
|
60
60
|
The console shows supported interfaces with per-domain injection toggles,
|
|
61
61
|
runtime ACL controls, signer controls, and a filterable message log with visible
|
|
62
|
-
error details.
|
|
62
|
+
error details. It starts expanded and collapses to the left with the chevron
|
|
63
|
+
button in the top bar; the same button brings the whole panel back. Collapsing
|
|
64
|
+
is presentation-only — the target iframe keeps its identity and the loaded
|
|
65
|
+
napplet keeps running — and Paja remembers the choice per browser origin. In
|
|
66
|
+
runtime-pointer mode, ACL controls always display and mutate
|
|
63
67
|
the active tab's resolver-verified d-tag and aggregate hash; a grant or revoke
|
|
64
68
|
rerenders that same identity immediately. Paja auto-connects a browser NIP-07 signer when `window.nostr` is
|
|
65
69
|
available, can connect to a bunker/NIP-46 URI, and only uses the generated local
|
package/dist/browser-host.js
CHANGED
|
@@ -5363,6 +5363,39 @@ function nappletNamespacePrelude(domains) {
|
|
|
5363
5363
|
});
|
|
5364
5364
|
}
|
|
5365
5365
|
|
|
5366
|
+
// src/browser-console-panel.ts
|
|
5367
|
+
var PAJA_CONSOLE_PANEL_STORAGE_KEY = "kehto:paja:console:v1";
|
|
5368
|
+
var PAJA_CONSOLE_TOGGLE_ID = "paja-console-toggle";
|
|
5369
|
+
var PAJA_CONSOLE_COLLAPSE_LABEL = "Collapse the Paja development console";
|
|
5370
|
+
var PAJA_CONSOLE_EXPAND_LABEL = "Expand the Paja development console";
|
|
5371
|
+
function installPajaConsolePanel() {
|
|
5372
|
+
const root = document.documentElement;
|
|
5373
|
+
const button2 = document.getElementById(PAJA_CONSOLE_TOGGLE_ID);
|
|
5374
|
+
let collapsed = false;
|
|
5375
|
+
try {
|
|
5376
|
+
collapsed = localStorage.getItem(PAJA_CONSOLE_PANEL_STORAGE_KEY) === "collapsed";
|
|
5377
|
+
} catch {
|
|
5378
|
+
}
|
|
5379
|
+
const apply = () => {
|
|
5380
|
+
root.dataset.pajaConsole = collapsed ? "collapsed" : "expanded";
|
|
5381
|
+
const label = collapsed ? PAJA_CONSOLE_EXPAND_LABEL : PAJA_CONSOLE_COLLAPSE_LABEL;
|
|
5382
|
+
button2?.setAttribute("aria-expanded", String(!collapsed));
|
|
5383
|
+
button2?.setAttribute("aria-label", label);
|
|
5384
|
+
button2?.setAttribute("title", label);
|
|
5385
|
+
};
|
|
5386
|
+
const toggle = () => {
|
|
5387
|
+
collapsed = !collapsed;
|
|
5388
|
+
apply();
|
|
5389
|
+
try {
|
|
5390
|
+
localStorage.setItem(PAJA_CONSOLE_PANEL_STORAGE_KEY, root.dataset.pajaConsole);
|
|
5391
|
+
} catch {
|
|
5392
|
+
}
|
|
5393
|
+
};
|
|
5394
|
+
apply();
|
|
5395
|
+
button2?.addEventListener("click", toggle);
|
|
5396
|
+
return () => button2?.removeEventListener("click", toggle);
|
|
5397
|
+
}
|
|
5398
|
+
|
|
5366
5399
|
// ../../node_modules/.pnpm/@noble+hashes@2.0.1/node_modules/@noble/hashes/utils.js
|
|
5367
5400
|
function isBytes(a) {
|
|
5368
5401
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
@@ -15255,15 +15288,35 @@ var KIND_GIFT_WRAP_EPHEMERAL = 21059;
|
|
|
15255
15288
|
var KIND_GIFT_WRAP_REGULAR = 1059;
|
|
15256
15289
|
var KIND_ANNOUNCE_SERVER = 11316;
|
|
15257
15290
|
var KIND_ANNOUNCE_TOOLS = 11317;
|
|
15291
|
+
var SUPPORT_ENCRYPTION = "support_encryption";
|
|
15292
|
+
var SUPPORT_ENCRYPTION_EPHEMERAL = "support_encryption_ephemeral";
|
|
15293
|
+
var SUPPORT_OVERSIZED_TRANSFER = "support_oversized_transfer";
|
|
15294
|
+
var SUPPORT_OPEN_STREAM = "support_open_stream";
|
|
15258
15295
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
15259
15296
|
var DEFAULT_DISCOVER_TIMEOUT_MS = 6e3;
|
|
15260
15297
|
var MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
15261
15298
|
var SEEN_WRAP_LIMIT = 512;
|
|
15299
|
+
var DIGEST_PREFIX = "sha256:";
|
|
15300
|
+
var MAX_TRANSFER_CHUNKS = 1e4;
|
|
15301
|
+
var MAX_TRANSFER_BYTES = 100 * 1024 * 1024;
|
|
15302
|
+
var MAX_ACTIVE_TRANSFERS = 8;
|
|
15303
|
+
var MAX_TRANSFER_LIFETIME_MS = 12e4;
|
|
15304
|
+
var COMPLETION_MODE_RENDER = "render";
|
|
15305
|
+
var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
15306
|
+
var DEFAULT_STREAM_PROBE_TIMEOUT_MS = 1e4;
|
|
15307
|
+
var MAX_STREAM_BUFFERED_CHUNKS = 256;
|
|
15308
|
+
var MAX_STREAM_BUFFERED_CODE_UNITS = 8 * 1024 * 1024;
|
|
15309
|
+
var MAX_PING_NONCE_BYTES = 64;
|
|
15262
15310
|
var correlationCounter = 0;
|
|
15263
15311
|
function nextCorrelationId() {
|
|
15264
15312
|
correlationCounter += 1;
|
|
15265
15313
|
return `cvm-${correlationCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;
|
|
15266
15314
|
}
|
|
15315
|
+
var streamNonceCounter = 0;
|
|
15316
|
+
function nextStreamNonce() {
|
|
15317
|
+
streamNonceCounter += 1;
|
|
15318
|
+
return `cvm-ping-${streamNonceCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;
|
|
15319
|
+
}
|
|
15267
15320
|
function randomizedPastTimestamp() {
|
|
15268
15321
|
const jitter = Math.floor(Math.random() * 172800);
|
|
15269
15322
|
return Math.floor(Date.now() / 1e3) - jitter;
|
|
@@ -15271,6 +15324,19 @@ function randomizedPastTimestamp() {
|
|
|
15271
15324
|
function tagValue(tags, name) {
|
|
15272
15325
|
return tags.find((tag) => tag[0] === name)?.[1];
|
|
15273
15326
|
}
|
|
15327
|
+
function progressTokenKey(token) {
|
|
15328
|
+
if (typeof token === "string") return token.length > 0 ? `s:${token}` : null;
|
|
15329
|
+
if (typeof token === "number" && Number.isFinite(token)) return `n:${token}`;
|
|
15330
|
+
return null;
|
|
15331
|
+
}
|
|
15332
|
+
function isBoundedInteger(value, min, max) {
|
|
15333
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
15334
|
+
}
|
|
15335
|
+
async function sha256Hex(text) {
|
|
15336
|
+
const bytes = new TextEncoder().encode(text);
|
|
15337
|
+
const buf = await crypto.subtle.digest("SHA-256", bytes);
|
|
15338
|
+
return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
15339
|
+
}
|
|
15274
15340
|
function simplePoolAdapter(sp) {
|
|
15275
15341
|
return {
|
|
15276
15342
|
subscribe(relays, filter, params) {
|
|
@@ -15295,11 +15361,14 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15295
15361
|
const clientSecretKey = options.clientSecretKey ?? generateSecretKey();
|
|
15296
15362
|
const clientPubkey = getPublicKey(clientSecretKey);
|
|
15297
15363
|
const clientInfo = options.clientInfo ?? { name: "kehto-cvm", version: "1.0.0" };
|
|
15364
|
+
const streamIdleTimeoutMs = options.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
15365
|
+
const streamProbeTimeoutMs = options.streamProbeTimeoutMs ?? DEFAULT_STREAM_PROBE_TIMEOUT_MS;
|
|
15298
15366
|
const sessions = /* @__PURE__ */ new Map();
|
|
15299
15367
|
const pending = /* @__PURE__ */ new Map();
|
|
15300
15368
|
const eventHandlers = /* @__PURE__ */ new Set();
|
|
15301
15369
|
const relayRefcount = /* @__PURE__ */ new Map();
|
|
15302
15370
|
const seenWraps = /* @__PURE__ */ new Set();
|
|
15371
|
+
const issuedTokens = /* @__PURE__ */ new Map();
|
|
15303
15372
|
let inbound = null;
|
|
15304
15373
|
let subscribedRelays = "";
|
|
15305
15374
|
function resolveRelays(server) {
|
|
@@ -15361,23 +15430,379 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15361
15430
|
} catch {
|
|
15362
15431
|
return;
|
|
15363
15432
|
}
|
|
15433
|
+
if (mcp.method === "notifications/progress") {
|
|
15434
|
+
const params = mcp.params ?? {};
|
|
15435
|
+
const cvm = params.cvm;
|
|
15436
|
+
if (cvm && typeof cvm.type === "string") {
|
|
15437
|
+
if (cvm.type === "oversized-transfer") {
|
|
15438
|
+
void handleOversizedFrame(serverPubkey, params, cvm);
|
|
15439
|
+
return;
|
|
15440
|
+
}
|
|
15441
|
+
if (cvm.type === "open-stream") {
|
|
15442
|
+
const entry = admitFrame(serverPubkey, params.progressToken);
|
|
15443
|
+
if (!entry) return;
|
|
15444
|
+
handleOpenStreamFrame(serverPubkey, entry, params, cvm, mcp);
|
|
15445
|
+
return;
|
|
15446
|
+
}
|
|
15447
|
+
}
|
|
15448
|
+
}
|
|
15449
|
+
routeMessage(serverPubkey, mcp);
|
|
15450
|
+
}
|
|
15451
|
+
async function publishStreamFrame(entry, stream, frame) {
|
|
15452
|
+
const session = sessions.get(entry.serverPubkey);
|
|
15453
|
+
if (!session) return;
|
|
15454
|
+
stream.localProgress += 1;
|
|
15455
|
+
await publishMcp(
|
|
15456
|
+
{ pubkey: entry.serverPubkey },
|
|
15457
|
+
session.relays,
|
|
15458
|
+
{
|
|
15459
|
+
jsonrpc: "2.0",
|
|
15460
|
+
method: "notifications/progress",
|
|
15461
|
+
params: {
|
|
15462
|
+
progressToken: stream.token,
|
|
15463
|
+
progress: stream.localProgress,
|
|
15464
|
+
cvm: { type: "open-stream", ...frame }
|
|
15465
|
+
}
|
|
15466
|
+
}
|
|
15467
|
+
);
|
|
15468
|
+
}
|
|
15469
|
+
function admitFrame(serverPubkey, token) {
|
|
15470
|
+
const tokenKey = progressTokenKey(token);
|
|
15471
|
+
if (!tokenKey) return null;
|
|
15472
|
+
const correlationId = issuedTokens.get(`${serverPubkey}:${tokenKey}`);
|
|
15473
|
+
if (!correlationId) return null;
|
|
15474
|
+
const entry = pending.get(correlationId);
|
|
15475
|
+
if (!entry || entry.serverPubkey !== serverPubkey) return null;
|
|
15476
|
+
return entry;
|
|
15477
|
+
}
|
|
15478
|
+
function releaseTransfer(entry) {
|
|
15479
|
+
if (!entry.oversized) return;
|
|
15480
|
+
clearTimeout(entry.oversized.expiryTimer);
|
|
15481
|
+
entry.oversized = void 0;
|
|
15482
|
+
}
|
|
15483
|
+
function releaseStream(entry) {
|
|
15484
|
+
if (!entry.stream) return;
|
|
15485
|
+
clearTimeout(entry.stream.idleTimer);
|
|
15486
|
+
if (entry.stream.probeTimer) clearTimeout(entry.stream.probeTimer);
|
|
15487
|
+
entry.stream = void 0;
|
|
15488
|
+
}
|
|
15489
|
+
function endStream(entry) {
|
|
15490
|
+
releaseStream(entry);
|
|
15491
|
+
entry.streamEnded = true;
|
|
15492
|
+
}
|
|
15493
|
+
function failStream(entry, reason) {
|
|
15494
|
+
const stream = entry.stream;
|
|
15495
|
+
if (!stream || entry.streamEnded) return;
|
|
15496
|
+
endStream(entry);
|
|
15497
|
+
void publishStreamFrame(entry, stream, { frameType: "abort", reason }).catch(() => {
|
|
15498
|
+
});
|
|
15499
|
+
}
|
|
15500
|
+
function resetStreamIdleTimer(entry, stream) {
|
|
15501
|
+
clearTimeout(stream.idleTimer);
|
|
15502
|
+
stream.idleTimer = setTimeout(() => onStreamIdle(entry), streamIdleTimeoutMs);
|
|
15503
|
+
}
|
|
15504
|
+
function onStreamIdle(entry) {
|
|
15505
|
+
const stream = entry.stream;
|
|
15506
|
+
if (!stream || entry.streamEnded) return;
|
|
15507
|
+
if (stream.pendingNonce !== null) return;
|
|
15508
|
+
const session = sessions.get(entry.serverPubkey);
|
|
15509
|
+
if (!session) {
|
|
15510
|
+
failStream(entry, "session closed");
|
|
15511
|
+
return;
|
|
15512
|
+
}
|
|
15513
|
+
stream.pendingNonce = nextStreamNonce();
|
|
15514
|
+
stream.probeTimer = setTimeout(() => failStream(entry, "probe timeout"), streamProbeTimeoutMs);
|
|
15515
|
+
void publishStreamFrame(entry, stream, { frameType: "ping", nonce: stream.pendingNonce }).catch(() => failStream(entry, "ping publication failed"));
|
|
15516
|
+
}
|
|
15517
|
+
function releaseFrameState(entry) {
|
|
15518
|
+
releaseTransfer(entry);
|
|
15519
|
+
releaseStream(entry);
|
|
15520
|
+
if (entry.tokenKey && issuedTokens.get(entry.tokenKey) === entry.correlationId) {
|
|
15521
|
+
issuedTokens.delete(entry.tokenKey);
|
|
15522
|
+
}
|
|
15523
|
+
entry.tokenKey = void 0;
|
|
15524
|
+
}
|
|
15525
|
+
function settlePending(correlationId) {
|
|
15526
|
+
const entry = pending.get(correlationId);
|
|
15527
|
+
if (!entry) return null;
|
|
15528
|
+
clearTimeout(entry.timer);
|
|
15529
|
+
pending.delete(correlationId);
|
|
15530
|
+
releaseFrameState(entry);
|
|
15531
|
+
return entry;
|
|
15532
|
+
}
|
|
15533
|
+
function countActiveTransfers() {
|
|
15534
|
+
let active = 0;
|
|
15535
|
+
for (const entry of pending.values()) {
|
|
15536
|
+
if (entry.oversized) active += 1;
|
|
15537
|
+
}
|
|
15538
|
+
return active;
|
|
15539
|
+
}
|
|
15540
|
+
async function handleOversizedFrame(serverPubkey, params, cvm) {
|
|
15541
|
+
const entry = admitFrame(serverPubkey, params.progressToken);
|
|
15542
|
+
if (!entry) return;
|
|
15543
|
+
const progress = Number(params.progress ?? NaN);
|
|
15544
|
+
if (!Number.isSafeInteger(progress) || progress < 0) return;
|
|
15545
|
+
if (cvm.frameType === "start") {
|
|
15546
|
+
const digest = typeof cvm.digest === "string" ? cvm.digest : "";
|
|
15547
|
+
const { totalBytes, totalChunks } = cvm;
|
|
15548
|
+
if (cvm.completionMode !== COMPLETION_MODE_RENDER) return;
|
|
15549
|
+
if (digest === "") return;
|
|
15550
|
+
if (!isBoundedInteger(totalChunks, 1, MAX_TRANSFER_CHUNKS)) return;
|
|
15551
|
+
if (!isBoundedInteger(totalBytes, 1, MAX_TRANSFER_BYTES)) return;
|
|
15552
|
+
if (progress > Number.MAX_SAFE_INTEGER - totalChunks - 1) return;
|
|
15553
|
+
if (!entry.oversized && countActiveTransfers() >= MAX_ACTIVE_TRANSFERS) return;
|
|
15554
|
+
releaseTransfer(entry);
|
|
15555
|
+
entry.oversized = {
|
|
15556
|
+
digest,
|
|
15557
|
+
totalBytes,
|
|
15558
|
+
totalChunks,
|
|
15559
|
+
startProgress: progress,
|
|
15560
|
+
acceptProgress: null,
|
|
15561
|
+
chunks: /* @__PURE__ */ new Map(),
|
|
15562
|
+
codeUnitsReceived: 0,
|
|
15563
|
+
// Hard expiry: progress frames extend the request deadline but never
|
|
15564
|
+
// how long buffers may be held.
|
|
15565
|
+
expiryTimer: setTimeout(() => releaseTransfer(entry), MAX_TRANSFER_LIFETIME_MS)
|
|
15566
|
+
};
|
|
15567
|
+
refreshPendingTimeout(entry);
|
|
15568
|
+
return;
|
|
15569
|
+
}
|
|
15570
|
+
const transfer = entry.oversized;
|
|
15571
|
+
if (!transfer) return;
|
|
15572
|
+
if (cvm.frameType === "accept") {
|
|
15573
|
+
if (progress !== transfer.startProgress + 1) {
|
|
15574
|
+
releaseTransfer(entry);
|
|
15575
|
+
return;
|
|
15576
|
+
}
|
|
15577
|
+
transfer.acceptProgress = progress;
|
|
15578
|
+
refreshPendingTimeout(entry);
|
|
15579
|
+
return;
|
|
15580
|
+
}
|
|
15581
|
+
if (cvm.frameType === "chunk") {
|
|
15582
|
+
if (typeof cvm.data !== "string" || progress <= transfer.startProgress) return;
|
|
15583
|
+
if (transfer.chunks.has(progress)) return;
|
|
15584
|
+
if (transfer.chunks.size >= transfer.totalChunks) {
|
|
15585
|
+
releaseTransfer(entry);
|
|
15586
|
+
return;
|
|
15587
|
+
}
|
|
15588
|
+
transfer.codeUnitsReceived += cvm.data.length;
|
|
15589
|
+
if (transfer.codeUnitsReceived > MAX_TRANSFER_BYTES) {
|
|
15590
|
+
releaseTransfer(entry);
|
|
15591
|
+
return;
|
|
15592
|
+
}
|
|
15593
|
+
transfer.chunks.set(progress, cvm.data);
|
|
15594
|
+
refreshPendingTimeout(entry);
|
|
15595
|
+
return;
|
|
15596
|
+
}
|
|
15597
|
+
if (cvm.frameType === "abort") {
|
|
15598
|
+
releaseTransfer(entry);
|
|
15599
|
+
return;
|
|
15600
|
+
}
|
|
15601
|
+
if (cvm.frameType === "end") {
|
|
15602
|
+
const message = await assembleTransfer(transfer, progress);
|
|
15603
|
+
releaseTransfer(entry);
|
|
15604
|
+
if (message) routeMessage(serverPubkey, message);
|
|
15605
|
+
return;
|
|
15606
|
+
}
|
|
15607
|
+
}
|
|
15608
|
+
async function assembleTransfer(transfer, endProgress) {
|
|
15609
|
+
const { startProgress, acceptProgress, totalChunks } = transfer;
|
|
15610
|
+
const hasCompleteRange = (first) => {
|
|
15611
|
+
for (let i4 = 0; i4 < totalChunks; i4++) {
|
|
15612
|
+
if (!transfer.chunks.has(first + i4)) return false;
|
|
15613
|
+
}
|
|
15614
|
+
return true;
|
|
15615
|
+
};
|
|
15616
|
+
const directStart = startProgress + 1;
|
|
15617
|
+
const acceptGatedStart = startProgress + 2;
|
|
15618
|
+
const firstProgress = hasCompleteRange(directStart) && endProgress === directStart + totalChunks ? directStart : acceptProgress === startProgress + 1 && hasCompleteRange(acceptGatedStart) && endProgress === acceptGatedStart + totalChunks ? acceptGatedStart : null;
|
|
15619
|
+
if (firstProgress === null) return null;
|
|
15620
|
+
let payload = "";
|
|
15621
|
+
for (let i4 = 0; i4 < totalChunks; i4++) {
|
|
15622
|
+
payload += transfer.chunks.get(firstProgress + i4);
|
|
15623
|
+
}
|
|
15624
|
+
if (new TextEncoder().encode(payload).byteLength !== transfer.totalBytes) return null;
|
|
15625
|
+
const expected = transfer.digest.startsWith(DIGEST_PREFIX) ? transfer.digest.slice(DIGEST_PREFIX.length) : transfer.digest;
|
|
15626
|
+
if (await sha256Hex(payload) !== expected) return null;
|
|
15627
|
+
try {
|
|
15628
|
+
return JSON.parse(payload);
|
|
15629
|
+
} catch {
|
|
15630
|
+
return null;
|
|
15631
|
+
}
|
|
15632
|
+
}
|
|
15633
|
+
function handleOpenStreamFrame(serverPubkey, entry, params, cvm, mcp) {
|
|
15634
|
+
const progress = Number(params.progress ?? NaN);
|
|
15635
|
+
if (!Number.isSafeInteger(progress) || progress < 0) return;
|
|
15636
|
+
if (entry.streamEnded) return;
|
|
15637
|
+
const stream = entry.stream;
|
|
15638
|
+
if (!stream) {
|
|
15639
|
+
if (cvm.frameType !== "start") return;
|
|
15640
|
+
entry.stream = {
|
|
15641
|
+
token: params.progressToken,
|
|
15642
|
+
lastProgress: progress,
|
|
15643
|
+
localProgress: 0,
|
|
15644
|
+
nextChunkIndex: 0,
|
|
15645
|
+
outOfOrder: /* @__PURE__ */ new Map(),
|
|
15646
|
+
outOfOrderCodeUnits: 0,
|
|
15647
|
+
pendingNonce: null,
|
|
15648
|
+
idleTimer: setTimeout(() => onStreamIdle(entry), streamIdleTimeoutMs),
|
|
15649
|
+
probeTimer: null
|
|
15650
|
+
};
|
|
15651
|
+
refreshPendingTimeout(entry);
|
|
15652
|
+
routeEvent(serverPubkey, mcp);
|
|
15653
|
+
return;
|
|
15654
|
+
}
|
|
15655
|
+
const admitOrderedActivity = () => {
|
|
15656
|
+
if (progress <= stream.lastProgress) {
|
|
15657
|
+
failStream(entry, "non-monotonic progress");
|
|
15658
|
+
return false;
|
|
15659
|
+
}
|
|
15660
|
+
stream.lastProgress = progress;
|
|
15661
|
+
resetStreamIdleTimer(entry, stream);
|
|
15662
|
+
refreshPendingTimeout(entry);
|
|
15663
|
+
return true;
|
|
15664
|
+
};
|
|
15665
|
+
switch (cvm.frameType) {
|
|
15666
|
+
case "start":
|
|
15667
|
+
failStream(entry, "duplicate start");
|
|
15668
|
+
return;
|
|
15669
|
+
case "accept":
|
|
15670
|
+
if (!admitOrderedActivity()) return;
|
|
15671
|
+
routeEvent(serverPubkey, mcp);
|
|
15672
|
+
return;
|
|
15673
|
+
case "chunk": {
|
|
15674
|
+
if (typeof cvm.data !== "string" || !isBoundedInteger(cvm.chunkIndex, 0, Number.MAX_SAFE_INTEGER)) {
|
|
15675
|
+
failStream(entry, "malformed chunk");
|
|
15676
|
+
return;
|
|
15677
|
+
}
|
|
15678
|
+
const index = cvm.chunkIndex;
|
|
15679
|
+
if (index < stream.nextChunkIndex) return;
|
|
15680
|
+
if (index > stream.nextChunkIndex) {
|
|
15681
|
+
const existing = stream.outOfOrder.get(index);
|
|
15682
|
+
if (existing) {
|
|
15683
|
+
if (existing.progress !== progress || JSON.stringify(existing.message) !== JSON.stringify(mcp)) {
|
|
15684
|
+
failStream(entry, "conflicting duplicate chunk");
|
|
15685
|
+
}
|
|
15686
|
+
return;
|
|
15687
|
+
}
|
|
15688
|
+
if (progress <= stream.lastProgress || stream.outOfOrder.size >= MAX_STREAM_BUFFERED_CHUNKS || stream.outOfOrderCodeUnits + cvm.data.length > MAX_STREAM_BUFFERED_CODE_UNITS) {
|
|
15689
|
+
failStream(entry, progress <= stream.lastProgress ? "non-monotonic progress" : "gap buffer exhausted");
|
|
15690
|
+
return;
|
|
15691
|
+
}
|
|
15692
|
+
stream.outOfOrder.set(index, { message: mcp, progress, codeUnits: cvm.data.length });
|
|
15693
|
+
stream.outOfOrderCodeUnits += cvm.data.length;
|
|
15694
|
+
resetStreamIdleTimer(entry, stream);
|
|
15695
|
+
refreshPendingTimeout(entry);
|
|
15696
|
+
return;
|
|
15697
|
+
}
|
|
15698
|
+
const ready = [
|
|
15699
|
+
{ message: mcp, progress, codeUnits: 0 }
|
|
15700
|
+
];
|
|
15701
|
+
let nextIndex = index + 1;
|
|
15702
|
+
while (stream.outOfOrder.has(nextIndex)) {
|
|
15703
|
+
ready.push(stream.outOfOrder.get(nextIndex));
|
|
15704
|
+
nextIndex += 1;
|
|
15705
|
+
}
|
|
15706
|
+
let previousProgress = stream.lastProgress;
|
|
15707
|
+
for (const item of ready) {
|
|
15708
|
+
if (item.progress <= previousProgress) {
|
|
15709
|
+
failStream(entry, "non-monotonic chunk order");
|
|
15710
|
+
return;
|
|
15711
|
+
}
|
|
15712
|
+
previousProgress = item.progress;
|
|
15713
|
+
}
|
|
15714
|
+
for (let bufferedIndex = index + 1; bufferedIndex < nextIndex; bufferedIndex++) {
|
|
15715
|
+
const buffered = stream.outOfOrder.get(bufferedIndex);
|
|
15716
|
+
stream.outOfOrder.delete(bufferedIndex);
|
|
15717
|
+
stream.outOfOrderCodeUnits -= buffered.codeUnits;
|
|
15718
|
+
}
|
|
15719
|
+
for (const item of ready) {
|
|
15720
|
+
routeEvent(serverPubkey, item.message);
|
|
15721
|
+
stream.nextChunkIndex += 1;
|
|
15722
|
+
}
|
|
15723
|
+
stream.lastProgress = previousProgress;
|
|
15724
|
+
resetStreamIdleTimer(entry, stream);
|
|
15725
|
+
refreshPendingTimeout(entry);
|
|
15726
|
+
return;
|
|
15727
|
+
}
|
|
15728
|
+
case "ping": {
|
|
15729
|
+
if (typeof cvm.nonce !== "string") return;
|
|
15730
|
+
if (new TextEncoder().encode(cvm.nonce).byteLength > MAX_PING_NONCE_BYTES) return;
|
|
15731
|
+
if (!admitOrderedActivity()) return;
|
|
15732
|
+
void publishStreamFrame(entry, stream, { frameType: "pong", nonce: cvm.nonce }).catch(() => {
|
|
15733
|
+
failStream(entry, "pong publication failed");
|
|
15734
|
+
});
|
|
15735
|
+
return;
|
|
15736
|
+
}
|
|
15737
|
+
case "pong": {
|
|
15738
|
+
if (stream.pendingNonce === null || cvm.nonce !== stream.pendingNonce) return;
|
|
15739
|
+
if (!admitOrderedActivity()) return;
|
|
15740
|
+
stream.pendingNonce = null;
|
|
15741
|
+
if (stream.probeTimer) clearTimeout(stream.probeTimer);
|
|
15742
|
+
stream.probeTimer = null;
|
|
15743
|
+
return;
|
|
15744
|
+
}
|
|
15745
|
+
case "close": {
|
|
15746
|
+
if (cvm.lastChunkIndex !== void 0) {
|
|
15747
|
+
const bound = cvm.lastChunkIndex;
|
|
15748
|
+
const complete = isBoundedInteger(bound, 0, Number.MAX_SAFE_INTEGER) && stream.nextChunkIndex === bound + 1 && stream.outOfOrder.size === 0;
|
|
15749
|
+
if (!complete) {
|
|
15750
|
+
failStream(entry, "close with unresolved chunk gaps");
|
|
15751
|
+
return;
|
|
15752
|
+
}
|
|
15753
|
+
}
|
|
15754
|
+
if (!admitOrderedActivity()) return;
|
|
15755
|
+
routeEvent(serverPubkey, mcp);
|
|
15756
|
+
endStream(entry);
|
|
15757
|
+
return;
|
|
15758
|
+
}
|
|
15759
|
+
case "abort":
|
|
15760
|
+
if (!admitOrderedActivity()) return;
|
|
15761
|
+
routeEvent(serverPubkey, mcp);
|
|
15762
|
+
endStream(entry);
|
|
15763
|
+
return;
|
|
15764
|
+
default:
|
|
15765
|
+
return;
|
|
15766
|
+
}
|
|
15767
|
+
}
|
|
15768
|
+
function refreshPendingTimeout(entry) {
|
|
15769
|
+
clearTimeout(entry.timer);
|
|
15770
|
+
entry.timer = setTimeout(() => {
|
|
15771
|
+
const settled = settlePending(entry.correlationId);
|
|
15772
|
+
settled?.reject(new Error("relay timeout"));
|
|
15773
|
+
}, entry.timeoutMs);
|
|
15774
|
+
}
|
|
15775
|
+
function routeMessage(serverPubkey, mcp) {
|
|
15364
15776
|
const id = mcp.id;
|
|
15365
15777
|
if (id != null && pending.has(String(id))) {
|
|
15366
15778
|
const entry = pending.get(String(id));
|
|
15367
15779
|
if (entry.serverPubkey !== serverPubkey) return;
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
entry.resolve({ ...mcp, id: entry.originalId });
|
|
15780
|
+
const settled = settlePending(String(id));
|
|
15781
|
+
settled?.resolve({ ...mcp, id: entry.originalId });
|
|
15371
15782
|
return;
|
|
15372
15783
|
}
|
|
15784
|
+
routeEvent(serverPubkey, mcp);
|
|
15785
|
+
}
|
|
15786
|
+
function routeEvent(serverPubkey, mcp) {
|
|
15373
15787
|
if (mcp.method !== void 0 && sessions.has(serverPubkey)) {
|
|
15374
15788
|
const server = { pubkey: serverPubkey };
|
|
15375
15789
|
for (const handler of eventHandlers) handler(server, mcp);
|
|
15376
15790
|
}
|
|
15377
15791
|
}
|
|
15378
15792
|
async function publishMcp(server, relays, message) {
|
|
15793
|
+
const capabilityTags = [
|
|
15794
|
+
...encrypt22 ? [[SUPPORT_ENCRYPTION]] : [],
|
|
15795
|
+
...encrypt22 && wrapKind === KIND_GIFT_WRAP_EPHEMERAL ? [[SUPPORT_ENCRYPTION_EPHEMERAL]] : [],
|
|
15796
|
+
[SUPPORT_OVERSIZED_TRANSFER],
|
|
15797
|
+
[SUPPORT_OPEN_STREAM]
|
|
15798
|
+
];
|
|
15379
15799
|
const inner = finalizeEvent(
|
|
15380
|
-
{
|
|
15800
|
+
{
|
|
15801
|
+
kind: KIND_CVM,
|
|
15802
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
15803
|
+
tags: [["p", server.pubkey], ...capabilityTags],
|
|
15804
|
+
content: JSON.stringify(message)
|
|
15805
|
+
},
|
|
15381
15806
|
clientSecretKey
|
|
15382
15807
|
);
|
|
15383
15808
|
if (!encrypt22) {
|
|
@@ -15401,17 +15826,52 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15401
15826
|
function sendCorrelated(server, relays, message, timeout) {
|
|
15402
15827
|
const correlationId = nextCorrelationId();
|
|
15403
15828
|
const originalId = message.id;
|
|
15404
|
-
|
|
15829
|
+
let outgoing = { ...message, id: correlationId };
|
|
15830
|
+
let issuedToken;
|
|
15831
|
+
const params = message.params && typeof message.params === "object" ? message.params : void 0;
|
|
15832
|
+
const meta = params?._meta && typeof params._meta === "object" ? params._meta : void 0;
|
|
15833
|
+
const explicit = meta?.progressToken;
|
|
15834
|
+
if (message.method === "tools/call") {
|
|
15835
|
+
issuedToken = typeof explicit === "string" || typeof explicit === "number" ? explicit : correlationId;
|
|
15836
|
+
outgoing = {
|
|
15837
|
+
...outgoing,
|
|
15838
|
+
params: {
|
|
15839
|
+
...params,
|
|
15840
|
+
_meta: {
|
|
15841
|
+
...meta,
|
|
15842
|
+
progressToken: issuedToken
|
|
15843
|
+
}
|
|
15844
|
+
}
|
|
15845
|
+
};
|
|
15846
|
+
} else if (typeof explicit === "string" || typeof explicit === "number") {
|
|
15847
|
+
issuedToken = explicit;
|
|
15848
|
+
}
|
|
15849
|
+
const tokenKey = progressTokenKey(issuedToken);
|
|
15850
|
+
const issuedKey = tokenKey ? `${server.pubkey}:${tokenKey}` : null;
|
|
15851
|
+
if (issuedKey && issuedTokens.has(issuedKey)) {
|
|
15852
|
+
return Promise.reject(new Error("duplicate progress token"));
|
|
15853
|
+
}
|
|
15405
15854
|
return new Promise((resolve, reject2) => {
|
|
15406
|
-
const
|
|
15407
|
-
|
|
15408
|
-
reject2
|
|
15409
|
-
|
|
15410
|
-
|
|
15855
|
+
const entry = {
|
|
15856
|
+
resolve,
|
|
15857
|
+
reject: reject2,
|
|
15858
|
+
timer: setTimeout(() => {
|
|
15859
|
+
const settled = settlePending(correlationId);
|
|
15860
|
+
settled?.reject(new Error("relay timeout"));
|
|
15861
|
+
}, timeout),
|
|
15862
|
+
timeoutMs: timeout,
|
|
15863
|
+
originalId,
|
|
15864
|
+
serverPubkey: server.pubkey,
|
|
15865
|
+
correlationId
|
|
15866
|
+
};
|
|
15867
|
+
if (issuedKey) {
|
|
15868
|
+
entry.tokenKey = issuedKey;
|
|
15869
|
+
issuedTokens.set(issuedKey, correlationId);
|
|
15870
|
+
}
|
|
15871
|
+
pending.set(correlationId, entry);
|
|
15411
15872
|
void publishMcp(server, relays, outgoing).catch((err2) => {
|
|
15412
|
-
|
|
15413
|
-
|
|
15414
|
-
reject2(err2 instanceof Error ? err2 : new Error("publish failed"));
|
|
15873
|
+
const settled = settlePending(correlationId);
|
|
15874
|
+
settled?.reject(err2 instanceof Error ? err2 : new Error("publish failed"));
|
|
15415
15875
|
});
|
|
15416
15876
|
});
|
|
15417
15877
|
}
|
|
@@ -15509,6 +15969,10 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15509
15969
|
if (!session) return;
|
|
15510
15970
|
sessions.delete(server.pubkey);
|
|
15511
15971
|
releaseRelays(session.relays);
|
|
15972
|
+
for (const [correlationId, entry] of pending) {
|
|
15973
|
+
if (entry.serverPubkey !== server.pubkey) continue;
|
|
15974
|
+
settlePending(correlationId)?.reject(new Error("server closed"));
|
|
15975
|
+
}
|
|
15512
15976
|
},
|
|
15513
15977
|
onEvent(handler) {
|
|
15514
15978
|
eventHandlers.add(handler);
|
|
@@ -15523,9 +15987,12 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15523
15987
|
inbound = null;
|
|
15524
15988
|
for (const entry of pending.values()) {
|
|
15525
15989
|
clearTimeout(entry.timer);
|
|
15990
|
+
releaseTransfer(entry);
|
|
15991
|
+
releaseStream(entry);
|
|
15526
15992
|
entry.reject(new Error("transport disposed"));
|
|
15527
15993
|
}
|
|
15528
15994
|
pending.clear();
|
|
15995
|
+
issuedTokens.clear();
|
|
15529
15996
|
sessions.clear();
|
|
15530
15997
|
relayRefcount.clear();
|
|
15531
15998
|
eventHandlers.clear();
|
|
@@ -16430,14 +16897,14 @@ var CacheStorageNappletArtifactCache = class {
|
|
|
16430
16897
|
this.softCeilingBytes = options.softCeilingBytes ?? DEFAULT_SOFT_CEILING_BYTES;
|
|
16431
16898
|
this.hardCeilingBytes = options.hardCeilingBytes ?? DEFAULT_HARD_CEILING_BYTES;
|
|
16432
16899
|
}
|
|
16433
|
-
async readBlob(
|
|
16900
|
+
async readBlob(sha256Hex2) {
|
|
16434
16901
|
try {
|
|
16435
16902
|
const cache2 = await this.open();
|
|
16436
|
-
const response = await cache2.match(this.blobUrl(
|
|
16903
|
+
const response = await cache2.match(this.blobUrl(sha256Hex2));
|
|
16437
16904
|
if (!response) return void 0;
|
|
16438
16905
|
const bytes = await responseBytes(response);
|
|
16439
16906
|
const index = await this.readIndex(cache2);
|
|
16440
|
-
const entry = index.blobs[
|
|
16907
|
+
const entry = index.blobs[sha256Hex2];
|
|
16441
16908
|
if (entry) {
|
|
16442
16909
|
entry.lastAccessed = this.now();
|
|
16443
16910
|
index.updatedAt = entry.lastAccessed;
|
|
@@ -16445,20 +16912,20 @@ var CacheStorageNappletArtifactCache = class {
|
|
|
16445
16912
|
}
|
|
16446
16913
|
return bytes;
|
|
16447
16914
|
} catch (cause) {
|
|
16448
|
-
this.diagnostic({ type: "cache-read-failed", message: `failed to read blob ${
|
|
16915
|
+
this.diagnostic({ type: "cache-read-failed", message: `failed to read blob ${sha256Hex2}`, cause });
|
|
16449
16916
|
return void 0;
|
|
16450
16917
|
}
|
|
16451
16918
|
}
|
|
16452
|
-
async deleteBlob(
|
|
16919
|
+
async deleteBlob(sha256Hex2) {
|
|
16453
16920
|
try {
|
|
16454
16921
|
const cache2 = await this.open();
|
|
16455
|
-
await cache2.delete(this.blobUrl(
|
|
16922
|
+
await cache2.delete(this.blobUrl(sha256Hex2));
|
|
16456
16923
|
const index = await this.readIndex(cache2);
|
|
16457
|
-
delete index.blobs[
|
|
16924
|
+
delete index.blobs[sha256Hex2];
|
|
16458
16925
|
index.updatedAt = this.now();
|
|
16459
16926
|
await this.writeIndex(cache2, index);
|
|
16460
16927
|
} catch (cause) {
|
|
16461
|
-
this.diagnostic({ type: "cache-delete-failed", message: `failed to delete blob ${
|
|
16928
|
+
this.diagnostic({ type: "cache-delete-failed", message: `failed to delete blob ${sha256Hex2}`, cause });
|
|
16462
16929
|
}
|
|
16463
16930
|
}
|
|
16464
16931
|
async writeVerifiedResolution(input) {
|
|
@@ -16839,22 +17306,22 @@ function verifyManifestSignature(event) {
|
|
|
16839
17306
|
return false;
|
|
16840
17307
|
}
|
|
16841
17308
|
}
|
|
16842
|
-
function verifyBlobHash(bytes,
|
|
16843
|
-
return bytesToHex2(sha256(bytes)) ===
|
|
17309
|
+
function verifyBlobHash(bytes, sha256Hex2) {
|
|
17310
|
+
return bytesToHex2(sha256(bytes)) === sha256Hex2;
|
|
16844
17311
|
}
|
|
16845
17312
|
function stripTrailingSlash(url) {
|
|
16846
17313
|
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
16847
17314
|
}
|
|
16848
|
-
async function fetchBlob(servers,
|
|
17315
|
+
async function fetchBlob(servers, sha256Hex2, fetchBytes) {
|
|
16849
17316
|
for (const server of servers) {
|
|
16850
|
-
const url = `${stripTrailingSlash(server)}/${
|
|
17317
|
+
const url = `${stripTrailingSlash(server)}/${sha256Hex2}`;
|
|
16851
17318
|
try {
|
|
16852
17319
|
const bytes = await fetchBytes(url);
|
|
16853
|
-
if (verifyBlobHash(bytes,
|
|
17320
|
+
if (verifyBlobHash(bytes, sha256Hex2)) return bytes;
|
|
16854
17321
|
} catch {
|
|
16855
17322
|
}
|
|
16856
17323
|
}
|
|
16857
|
-
throw new NappletResolutionError("blob-unavailable", `no server served blob ${
|
|
17324
|
+
throw new NappletResolutionError("blob-unavailable", `no server served blob ${sha256Hex2}`);
|
|
16858
17325
|
}
|
|
16859
17326
|
var INDEX_PATHS = ["/index.html", "index.html", "/"];
|
|
16860
17327
|
function defaultDecode(bytes) {
|
|
@@ -20466,7 +20933,7 @@ async function resolvePajaPointer(value, options = {}) {
|
|
|
20466
20933
|
const resolved = await resolveNapplet({
|
|
20467
20934
|
event,
|
|
20468
20935
|
cache: await openNappletArtifactCache({ requireStorageEstimate: false }),
|
|
20469
|
-
fetchBlob: (
|
|
20936
|
+
fetchBlob: (sha256Hex2, servers) => fetchBlob([...servers, ...blossomServers], sha256Hex2, async (url) => {
|
|
20470
20937
|
const response = await (options.fetcher ?? fetch)(url);
|
|
20471
20938
|
if (!response.ok) throw new Error(`[paja-runtime] blob ${url}: ${response.status}`);
|
|
20472
20939
|
return new Uint8Array(await response.arrayBuffer());
|
|
@@ -23247,6 +23714,7 @@ function createPajaBrowserState(context) {
|
|
|
23247
23714
|
};
|
|
23248
23715
|
}
|
|
23249
23716
|
async function installPajaHost() {
|
|
23717
|
+
const disposeConsolePanel = installPajaConsolePanel();
|
|
23250
23718
|
const config = await readLatestConfig(readConfig());
|
|
23251
23719
|
const stage = getStage();
|
|
23252
23720
|
const frame = config.target.mode === "runtime-pointer" ? null : getFrame();
|
|
@@ -23337,6 +23805,7 @@ async function installPajaHost() {
|
|
|
23337
23805
|
window.addEventListener("pagehide", () => confirmationController.dispose(), { once: true });
|
|
23338
23806
|
window.addEventListener("pagehide", () => notifyController?.dispose(), { once: true });
|
|
23339
23807
|
window.addEventListener("pagehide", () => configController?.dispose(), { once: true });
|
|
23808
|
+
window.addEventListener("pagehide", disposeConsolePanel, { once: true });
|
|
23340
23809
|
window.__KEHTO_PAJA__ = state;
|
|
23341
23810
|
window.addEventListener("message", (event) => {
|
|
23342
23811
|
const source = event.source;
|