@kehto/paja 0.16.3 → 0.16.5
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/dist/browser-host.js +549 -74
- package/dist/browser-host.js.map +1 -1
- package/package.json +5 -5
package/dist/browser-host.js
CHANGED
|
@@ -702,7 +702,7 @@ function createNapEnforceGate(config) {
|
|
|
702
702
|
function formatDenialReason(capability) {
|
|
703
703
|
return `denied: ${capability}`;
|
|
704
704
|
}
|
|
705
|
-
function createSessionRegistry(notifier) {
|
|
705
|
+
function createSessionRegistry(notifier, onRegister) {
|
|
706
706
|
const byWindowId2 = /* @__PURE__ */ new Map();
|
|
707
707
|
const byPubkey2 = /* @__PURE__ */ new Map();
|
|
708
708
|
const byWindowIdEntry = /* @__PURE__ */ new Map();
|
|
@@ -712,6 +712,7 @@ function createSessionRegistry(notifier) {
|
|
|
712
712
|
byWindowId2.set(windowId, entry.pubkey);
|
|
713
713
|
byPubkey2.set(entry.pubkey, entry);
|
|
714
714
|
byWindowIdEntry.set(windowId, entry);
|
|
715
|
+
onRegister?.(windowId);
|
|
715
716
|
},
|
|
716
717
|
unregister(windowId) {
|
|
717
718
|
const pubkey2 = byWindowId2.get(windowId);
|
|
@@ -931,6 +932,12 @@ function createFirewallState(persistence) {
|
|
|
931
932
|
counters = result2.newState;
|
|
932
933
|
return result2;
|
|
933
934
|
},
|
|
935
|
+
resetInitBudget(initKey) {
|
|
936
|
+
if (!Object.hasOwn(counters.bursts, initKey)) return;
|
|
937
|
+
const bursts = { ...counters.bursts };
|
|
938
|
+
delete bursts[initKey];
|
|
939
|
+
counters = { ...counters, bursts };
|
|
940
|
+
},
|
|
934
941
|
setPolicy(napplet, policy) {
|
|
935
942
|
config = setPolicy(config, napplet, policy);
|
|
936
943
|
},
|
|
@@ -2416,8 +2423,8 @@ function buildObservation(envelope, windowId, senderCap, sessionRegistry2, getFo
|
|
|
2416
2423
|
size,
|
|
2417
2424
|
initElapsedMs,
|
|
2418
2425
|
// `windowId` comes from the host's source-bound session lookup, never the
|
|
2419
|
-
// napplet message envelope.
|
|
2420
|
-
//
|
|
2426
|
+
// napplet message envelope. Trusted register() calls retire this key's
|
|
2427
|
+
// previous startup budget; ordinary messages cannot reset it.
|
|
2421
2428
|
initKey: windowId,
|
|
2422
2429
|
focused: focus.focused,
|
|
2423
2430
|
msSinceFocusGain: focus.msSinceFocusGain,
|
|
@@ -2664,7 +2671,10 @@ function createRuntime(hooks) {
|
|
|
2664
2671
|
const subscriptions = /* @__PURE__ */ new Map();
|
|
2665
2672
|
const serviceRegistry = { ...hooks.services };
|
|
2666
2673
|
const registeredServices = createRegisteredServices(serviceRegistry);
|
|
2667
|
-
const
|
|
2674
|
+
const firewallState = createFirewallState(hooks.firewallPersistence);
|
|
2675
|
+
const sessionRegistry2 = createSessionRegistry(hooks.onPendingUpdate, (windowId) => {
|
|
2676
|
+
firewallState.resetInitBudget(windowId);
|
|
2677
|
+
});
|
|
2668
2678
|
let incRuntime = null;
|
|
2669
2679
|
const aclState = createAclState(hooks.aclPersistence, "permissive", (mutation) => {
|
|
2670
2680
|
if (mutation.type === "revoke" && mutation.capability !== "relay:read") return;
|
|
@@ -2674,7 +2684,6 @@ function createRuntime(hooks) {
|
|
|
2674
2684
|
}
|
|
2675
2685
|
}
|
|
2676
2686
|
});
|
|
2677
|
-
const firewallState = createFirewallState(hooks.firewallPersistence);
|
|
2678
2687
|
const manifestCache2 = createManifestCache(hooks.manifestPersistence);
|
|
2679
2688
|
const replayDetector = createReplayDetector(
|
|
2680
2689
|
hooks.getConfigOverrides ? () => hooks.getConfigOverrides().replayWindowSeconds : void 0
|
|
@@ -4033,16 +4042,29 @@ function nappletNamespacePrelude(domains) {
|
|
|
4033
4042
|
function request(message, resultType, map, options = {}) {
|
|
4034
4043
|
const requestId = message.id ?? id();
|
|
4035
4044
|
const outbound = { ...message, id: requestId };
|
|
4045
|
+
const timeoutMs = options.timeoutMs === void 0 ? requestTimeoutMs : options.timeoutMs;
|
|
4046
|
+
if (options.signal?.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
|
|
4036
4047
|
return new Promise((resolve, reject2) => {
|
|
4037
|
-
|
|
4048
|
+
let timer;
|
|
4049
|
+
let off;
|
|
4050
|
+
const cleanup = () => {
|
|
4051
|
+
off?.();
|
|
4052
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4053
|
+
options.signal?.removeEventListener("abort", abort);
|
|
4054
|
+
};
|
|
4055
|
+
const abort = () => {
|
|
4056
|
+
cleanup();
|
|
4057
|
+
if (options.cancelOnAbort) post({ type: "resource.cancel", id: requestId });
|
|
4058
|
+
reject2(new DOMException("Aborted", "AbortError"));
|
|
4059
|
+
};
|
|
4060
|
+
off = listen((event) => {
|
|
4038
4061
|
if (!isParentMessage(event)) return;
|
|
4039
4062
|
const incoming = event.data;
|
|
4040
4063
|
if (typeof incoming !== "object" || incoming === null) return;
|
|
4041
4064
|
const msg = incoming;
|
|
4042
4065
|
if (msg.type !== resultType && msg.type !== `${message.type}.error`) return;
|
|
4043
4066
|
if (msg.id !== requestId) return;
|
|
4044
|
-
|
|
4045
|
-
clearTimeout(timer);
|
|
4067
|
+
cleanup();
|
|
4046
4068
|
const error = typeof msg.error === "string" ? msg.error : "";
|
|
4047
4069
|
if (error && options.rejectOnError !== false) {
|
|
4048
4070
|
reject2(new Error(error));
|
|
@@ -4054,10 +4076,13 @@ function nappletNamespacePrelude(domains) {
|
|
|
4054
4076
|
reject2(err2);
|
|
4055
4077
|
}
|
|
4056
4078
|
});
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4079
|
+
if (timeoutMs !== null) {
|
|
4080
|
+
timer = setTimeout(() => {
|
|
4081
|
+
cleanup();
|
|
4082
|
+
reject2(new Error(`${message.type} timed out`));
|
|
4083
|
+
}, timeoutMs);
|
|
4084
|
+
}
|
|
4085
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
4061
4086
|
post(outbound);
|
|
4062
4087
|
});
|
|
4063
4088
|
}
|
|
@@ -4806,17 +4831,25 @@ function nappletNamespacePrelude(domains) {
|
|
|
4806
4831
|
type: "resource.bytes",
|
|
4807
4832
|
url,
|
|
4808
4833
|
...Array.isArray(options?.servers) ? { servers: Array.from(options.servers) } : {}
|
|
4809
|
-
}, "resource.bytes.result", (msg) => msg.blob
|
|
4834
|
+
}, "resource.bytes.result", (msg) => msg.blob, {
|
|
4835
|
+
timeoutMs: null,
|
|
4836
|
+
signal: options?.signal,
|
|
4837
|
+
cancelOnAbort: true
|
|
4838
|
+
});
|
|
4810
4839
|
return {
|
|
4811
4840
|
info: () => request({ type: "resource.info" }, "resource.info.result", (msg) => msg.info),
|
|
4812
4841
|
bytes,
|
|
4813
|
-
bytesMany: (requests) => request({
|
|
4842
|
+
bytesMany: (requests, options) => request({
|
|
4814
4843
|
type: "resource.bytesMany",
|
|
4815
4844
|
requests: Array.from(requests || [], (entry) => ({
|
|
4816
4845
|
url: entry.url,
|
|
4817
4846
|
...Array.isArray(entry.servers) ? { servers: Array.from(entry.servers) } : {}
|
|
4818
4847
|
}))
|
|
4819
|
-
}, "resource.bytesMany.result", (msg) => Array.isArray(msg.items) ? msg.items : []
|
|
4848
|
+
}, "resource.bytesMany.result", (msg) => Array.isArray(msg.items) ? msg.items : [], {
|
|
4849
|
+
timeoutMs: null,
|
|
4850
|
+
signal: options?.signal,
|
|
4851
|
+
cancelOnAbort: true
|
|
4852
|
+
}),
|
|
4820
4853
|
bytesAsObjectURL(url) {
|
|
4821
4854
|
let objectUrl = "";
|
|
4822
4855
|
const handle = {
|
|
@@ -9572,6 +9605,9 @@ function isValidFilterArray2(value) {
|
|
|
9572
9605
|
function isPlainObject2(value) {
|
|
9573
9606
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9574
9607
|
}
|
|
9608
|
+
function actionRegistryKey(windowId, actionId) {
|
|
9609
|
+
return JSON.stringify([windowId, actionId]);
|
|
9610
|
+
}
|
|
9575
9611
|
var MODIFIER_ALIASES = {
|
|
9576
9612
|
ctrl: "ctrl",
|
|
9577
9613
|
control: "ctrl",
|
|
@@ -9640,7 +9676,7 @@ function bindingsForWindow(windowId, registry2, windowIndex) {
|
|
|
9640
9676
|
if (!actionIds) return [];
|
|
9641
9677
|
const bindings = [];
|
|
9642
9678
|
for (const actionId of actionIds) {
|
|
9643
|
-
const entry = registry2.get(actionId);
|
|
9679
|
+
const entry = registry2.get(actionRegistryKey(windowId, actionId));
|
|
9644
9680
|
if (!entry || entry.windowId !== windowId) continue;
|
|
9645
9681
|
bindings.push({ actionId, key: entry.chordString });
|
|
9646
9682
|
}
|
|
@@ -9653,10 +9689,9 @@ function pushBindings(windowId, registry2, windowIndex, send) {
|
|
|
9653
9689
|
};
|
|
9654
9690
|
send(payload);
|
|
9655
9691
|
}
|
|
9656
|
-
function removeActionFromWindowIndex(actionId, windowIndex) {
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
}
|
|
9692
|
+
function removeActionFromWindowIndex(windowId, actionId, windowIndex) {
|
|
9693
|
+
const actions = windowIndex.get(windowId);
|
|
9694
|
+
if (actions?.delete(actionId) && actions.size === 0) windowIndex.delete(windowId);
|
|
9660
9695
|
}
|
|
9661
9696
|
var KEYS_SERVICE_VERSION = "1.3.0";
|
|
9662
9697
|
function rememberActionForWindow(windowIndex, windowId, actionId) {
|
|
@@ -9664,7 +9699,7 @@ function rememberActionForWindow(windowIndex, windowId, actionId) {
|
|
|
9664
9699
|
windowIndex.get(windowId).add(actionId);
|
|
9665
9700
|
}
|
|
9666
9701
|
function bindActionEntry(registry2, windowIndex, actionId, windowId, chord, chordString) {
|
|
9667
|
-
registry2.set(actionId, { chord, chordString, windowId });
|
|
9702
|
+
registry2.set(actionRegistryKey(windowId, actionId), { actionId, chord, chordString, windowId });
|
|
9668
9703
|
rememberActionForWindow(windowIndex, windowId, actionId);
|
|
9669
9704
|
}
|
|
9670
9705
|
function bindActionAndMarkChanged(registry2, windowIndex, changedWindowIds, actionId, windowId, chord, chordString) {
|
|
@@ -9737,6 +9772,7 @@ function createKeysService(options = {}) {
|
|
|
9737
9772
|
const bridgeSendHandles = /* @__PURE__ */ new Map();
|
|
9738
9773
|
const unsubscribeHandles = /* @__PURE__ */ new Map();
|
|
9739
9774
|
const handleBridgeRegisterAction = (windowId, m, send) => {
|
|
9775
|
+
const registryKey = actionRegistryKey(windowId, m.action.id);
|
|
9740
9776
|
bridgeSendHandles.set(windowId, send);
|
|
9741
9777
|
let binding;
|
|
9742
9778
|
const changedWindowIds = /* @__PURE__ */ new Set();
|
|
@@ -9744,8 +9780,8 @@ function createKeysService(options = {}) {
|
|
|
9744
9780
|
try {
|
|
9745
9781
|
const chord = parseChord(m.action.defaultKey);
|
|
9746
9782
|
const normalizedChord = formatChord(chord);
|
|
9747
|
-
const existingEntry = bridgeActionRegistry.get(
|
|
9748
|
-
const existing = unsubscribeHandles.get(
|
|
9783
|
+
const existingEntry = bridgeActionRegistry.get(registryKey);
|
|
9784
|
+
const existing = unsubscribeHandles.get(registryKey);
|
|
9749
9785
|
let nextUnsubscribe;
|
|
9750
9786
|
if (!isUnavailableBinding(chord)) {
|
|
9751
9787
|
nextUnsubscribe = bridge.subscribe(normalizedChord, (ev) => {
|
|
@@ -9771,13 +9807,13 @@ function createKeysService(options = {}) {
|
|
|
9771
9807
|
existing();
|
|
9772
9808
|
} catch {
|
|
9773
9809
|
}
|
|
9774
|
-
unsubscribeHandles.delete(
|
|
9810
|
+
unsubscribeHandles.delete(registryKey);
|
|
9775
9811
|
}
|
|
9776
9812
|
if (existingEntry) changedWindowIds.add(existingEntry.windowId);
|
|
9777
|
-
removeActionFromWindowIndex(m.action.id, bridgeWindowActions);
|
|
9778
|
-
bridgeActionRegistry.delete(
|
|
9813
|
+
removeActionFromWindowIndex(windowId, m.action.id, bridgeWindowActions);
|
|
9814
|
+
bridgeActionRegistry.delete(registryKey);
|
|
9779
9815
|
if (nextUnsubscribe) {
|
|
9780
|
-
unsubscribeHandles.set(
|
|
9816
|
+
unsubscribeHandles.set(registryKey, nextUnsubscribe);
|
|
9781
9817
|
binding = bindActionAndMarkChanged(
|
|
9782
9818
|
bridgeActionRegistry,
|
|
9783
9819
|
bridgeWindowActions,
|
|
@@ -9807,18 +9843,19 @@ function createKeysService(options = {}) {
|
|
|
9807
9843
|
send
|
|
9808
9844
|
);
|
|
9809
9845
|
};
|
|
9810
|
-
const handleBridgeUnregisterAction = (
|
|
9846
|
+
const handleBridgeUnregisterAction = (windowId, m, send) => {
|
|
9811
9847
|
if (!m.actionId) return;
|
|
9812
|
-
const
|
|
9813
|
-
const
|
|
9848
|
+
const registryKey = actionRegistryKey(windowId, m.actionId);
|
|
9849
|
+
const unsubscribe = unsubscribeHandles.get(registryKey);
|
|
9850
|
+
const entry = bridgeActionRegistry.get(registryKey);
|
|
9814
9851
|
if (!unsubscribe) return;
|
|
9815
9852
|
try {
|
|
9816
9853
|
unsubscribe();
|
|
9817
9854
|
} catch {
|
|
9818
9855
|
}
|
|
9819
|
-
unsubscribeHandles.delete(
|
|
9820
|
-
bridgeActionRegistry.delete(
|
|
9821
|
-
removeActionFromWindowIndex(m.actionId, bridgeWindowActions);
|
|
9856
|
+
unsubscribeHandles.delete(registryKey);
|
|
9857
|
+
bridgeActionRegistry.delete(registryKey);
|
|
9858
|
+
removeActionFromWindowIndex(windowId, m.actionId, bridgeWindowActions);
|
|
9822
9859
|
if (entry) {
|
|
9823
9860
|
const ownerSend = bridgeSendHandles.get(entry.windowId) ?? send;
|
|
9824
9861
|
pushBindings(entry.windowId, bridgeActionRegistry, bridgeWindowActions, ownerSend);
|
|
@@ -9838,15 +9875,16 @@ function createKeysService(options = {}) {
|
|
|
9838
9875
|
const actions = bridgeWindowActions.get(windowId);
|
|
9839
9876
|
if (!actions) return;
|
|
9840
9877
|
for (const actionId of actions) {
|
|
9841
|
-
const
|
|
9878
|
+
const registryKey = actionRegistryKey(windowId, actionId);
|
|
9879
|
+
const unsubscribe = unsubscribeHandles.get(registryKey);
|
|
9842
9880
|
if (unsubscribe) {
|
|
9843
9881
|
try {
|
|
9844
9882
|
unsubscribe();
|
|
9845
9883
|
} catch {
|
|
9846
9884
|
}
|
|
9847
9885
|
}
|
|
9848
|
-
unsubscribeHandles.delete(
|
|
9849
|
-
bridgeActionRegistry.delete(
|
|
9886
|
+
unsubscribeHandles.delete(registryKey);
|
|
9887
|
+
bridgeActionRegistry.delete(registryKey);
|
|
9850
9888
|
}
|
|
9851
9889
|
bridgeWindowActions.delete(windowId);
|
|
9852
9890
|
bridgeSendHandles.delete(windowId);
|
|
@@ -9899,13 +9937,13 @@ function createKeysService(options = {}) {
|
|
|
9899
9937
|
});
|
|
9900
9938
|
}
|
|
9901
9939
|
if (isReserved) return;
|
|
9902
|
-
for (const
|
|
9940
|
+
for (const entry of actionRegistry.values()) {
|
|
9903
9941
|
if (chordMatches(entry.chord, ev)) {
|
|
9904
9942
|
const send = sendHandles.get(entry.windowId);
|
|
9905
9943
|
if (send) {
|
|
9906
9944
|
const payload = {
|
|
9907
9945
|
type: "keys.action",
|
|
9908
|
-
actionId
|
|
9946
|
+
actionId: entry.actionId
|
|
9909
9947
|
};
|
|
9910
9948
|
send(payload);
|
|
9911
9949
|
}
|
|
@@ -9914,6 +9952,7 @@ function createKeysService(options = {}) {
|
|
|
9914
9952
|
};
|
|
9915
9953
|
target.addEventListener("keydown", listener);
|
|
9916
9954
|
const handleDocumentRegisterAction = (windowId, m, send) => {
|
|
9955
|
+
const registryKey = actionRegistryKey(windowId, m.action.id);
|
|
9917
9956
|
sendHandles.set(windowId, send);
|
|
9918
9957
|
let binding;
|
|
9919
9958
|
const changedWindowIds = /* @__PURE__ */ new Set();
|
|
@@ -9921,11 +9960,11 @@ function createKeysService(options = {}) {
|
|
|
9921
9960
|
try {
|
|
9922
9961
|
const chord = parseChord(m.action.defaultKey);
|
|
9923
9962
|
const normalizedChord = formatChord(chord);
|
|
9924
|
-
const existing = actionRegistry.get(
|
|
9963
|
+
const existing = actionRegistry.get(registryKey);
|
|
9925
9964
|
if (existing) {
|
|
9926
9965
|
changedWindowIds.add(existing.windowId);
|
|
9927
|
-
removeActionFromWindowIndex(m.action.id, windowActions);
|
|
9928
|
-
actionRegistry.delete(
|
|
9966
|
+
removeActionFromWindowIndex(windowId, m.action.id, windowActions);
|
|
9967
|
+
actionRegistry.delete(registryKey);
|
|
9929
9968
|
}
|
|
9930
9969
|
if (!isUnavailableBinding(chord)) {
|
|
9931
9970
|
binding = bindActionAndMarkChanged(actionRegistry, windowActions, changedWindowIds, m.action.id, windowId, chord, normalizedChord);
|
|
@@ -9943,10 +9982,12 @@ function createKeysService(options = {}) {
|
|
|
9943
9982
|
sendRegisterActionResult2(send, m, binding);
|
|
9944
9983
|
pushChangedBindings(changedWindowIds, actionRegistry, windowActions, sendHandles, send);
|
|
9945
9984
|
};
|
|
9946
|
-
const handleDocumentUnregisterAction = (
|
|
9947
|
-
if (!m.actionId
|
|
9948
|
-
const
|
|
9949
|
-
actionRegistry.
|
|
9985
|
+
const handleDocumentUnregisterAction = (windowId, m, send) => {
|
|
9986
|
+
if (!m.actionId) return;
|
|
9987
|
+
const registryKey = actionRegistryKey(windowId, m.actionId);
|
|
9988
|
+
const entry = actionRegistry.get(registryKey);
|
|
9989
|
+
if (!entry) return;
|
|
9990
|
+
actionRegistry.delete(registryKey);
|
|
9950
9991
|
const set = windowActions.get(entry.windowId);
|
|
9951
9992
|
if (set) {
|
|
9952
9993
|
set.delete(m.actionId);
|
|
@@ -9966,7 +10007,7 @@ function createKeysService(options = {}) {
|
|
|
9966
10007
|
onWindowDestroyed(windowId) {
|
|
9967
10008
|
const actions = windowActions.get(windowId);
|
|
9968
10009
|
if (actions) {
|
|
9969
|
-
for (const actionId of actions) actionRegistry.delete(actionId);
|
|
10010
|
+
for (const actionId of actions) actionRegistry.delete(actionRegistryKey(windowId, actionId));
|
|
9970
10011
|
windowActions.delete(windowId);
|
|
9971
10012
|
}
|
|
9972
10013
|
sendHandles.delete(windowId);
|
|
@@ -15214,15 +15255,35 @@ var KIND_GIFT_WRAP_EPHEMERAL = 21059;
|
|
|
15214
15255
|
var KIND_GIFT_WRAP_REGULAR = 1059;
|
|
15215
15256
|
var KIND_ANNOUNCE_SERVER = 11316;
|
|
15216
15257
|
var KIND_ANNOUNCE_TOOLS = 11317;
|
|
15258
|
+
var SUPPORT_ENCRYPTION = "support_encryption";
|
|
15259
|
+
var SUPPORT_ENCRYPTION_EPHEMERAL = "support_encryption_ephemeral";
|
|
15260
|
+
var SUPPORT_OVERSIZED_TRANSFER = "support_oversized_transfer";
|
|
15261
|
+
var SUPPORT_OPEN_STREAM = "support_open_stream";
|
|
15217
15262
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
15218
15263
|
var DEFAULT_DISCOVER_TIMEOUT_MS = 6e3;
|
|
15219
15264
|
var MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
15220
15265
|
var SEEN_WRAP_LIMIT = 512;
|
|
15266
|
+
var DIGEST_PREFIX = "sha256:";
|
|
15267
|
+
var MAX_TRANSFER_CHUNKS = 1e4;
|
|
15268
|
+
var MAX_TRANSFER_BYTES = 100 * 1024 * 1024;
|
|
15269
|
+
var MAX_ACTIVE_TRANSFERS = 8;
|
|
15270
|
+
var MAX_TRANSFER_LIFETIME_MS = 12e4;
|
|
15271
|
+
var COMPLETION_MODE_RENDER = "render";
|
|
15272
|
+
var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
15273
|
+
var DEFAULT_STREAM_PROBE_TIMEOUT_MS = 1e4;
|
|
15274
|
+
var MAX_STREAM_BUFFERED_CHUNKS = 256;
|
|
15275
|
+
var MAX_STREAM_BUFFERED_CODE_UNITS = 8 * 1024 * 1024;
|
|
15276
|
+
var MAX_PING_NONCE_BYTES = 64;
|
|
15221
15277
|
var correlationCounter = 0;
|
|
15222
15278
|
function nextCorrelationId() {
|
|
15223
15279
|
correlationCounter += 1;
|
|
15224
15280
|
return `cvm-${correlationCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;
|
|
15225
15281
|
}
|
|
15282
|
+
var streamNonceCounter = 0;
|
|
15283
|
+
function nextStreamNonce() {
|
|
15284
|
+
streamNonceCounter += 1;
|
|
15285
|
+
return `cvm-ping-${streamNonceCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;
|
|
15286
|
+
}
|
|
15226
15287
|
function randomizedPastTimestamp() {
|
|
15227
15288
|
const jitter = Math.floor(Math.random() * 172800);
|
|
15228
15289
|
return Math.floor(Date.now() / 1e3) - jitter;
|
|
@@ -15230,6 +15291,19 @@ function randomizedPastTimestamp() {
|
|
|
15230
15291
|
function tagValue(tags, name) {
|
|
15231
15292
|
return tags.find((tag) => tag[0] === name)?.[1];
|
|
15232
15293
|
}
|
|
15294
|
+
function progressTokenKey(token) {
|
|
15295
|
+
if (typeof token === "string") return token.length > 0 ? `s:${token}` : null;
|
|
15296
|
+
if (typeof token === "number" && Number.isFinite(token)) return `n:${token}`;
|
|
15297
|
+
return null;
|
|
15298
|
+
}
|
|
15299
|
+
function isBoundedInteger(value, min, max) {
|
|
15300
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
15301
|
+
}
|
|
15302
|
+
async function sha256Hex(text) {
|
|
15303
|
+
const bytes = new TextEncoder().encode(text);
|
|
15304
|
+
const buf = await crypto.subtle.digest("SHA-256", bytes);
|
|
15305
|
+
return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
15306
|
+
}
|
|
15233
15307
|
function simplePoolAdapter(sp) {
|
|
15234
15308
|
return {
|
|
15235
15309
|
subscribe(relays, filter, params) {
|
|
@@ -15254,11 +15328,14 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15254
15328
|
const clientSecretKey = options.clientSecretKey ?? generateSecretKey();
|
|
15255
15329
|
const clientPubkey = getPublicKey(clientSecretKey);
|
|
15256
15330
|
const clientInfo = options.clientInfo ?? { name: "kehto-cvm", version: "1.0.0" };
|
|
15331
|
+
const streamIdleTimeoutMs = options.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
15332
|
+
const streamProbeTimeoutMs = options.streamProbeTimeoutMs ?? DEFAULT_STREAM_PROBE_TIMEOUT_MS;
|
|
15257
15333
|
const sessions = /* @__PURE__ */ new Map();
|
|
15258
15334
|
const pending = /* @__PURE__ */ new Map();
|
|
15259
15335
|
const eventHandlers = /* @__PURE__ */ new Set();
|
|
15260
15336
|
const relayRefcount = /* @__PURE__ */ new Map();
|
|
15261
15337
|
const seenWraps = /* @__PURE__ */ new Set();
|
|
15338
|
+
const issuedTokens = /* @__PURE__ */ new Map();
|
|
15262
15339
|
let inbound = null;
|
|
15263
15340
|
let subscribedRelays = "";
|
|
15264
15341
|
function resolveRelays(server) {
|
|
@@ -15320,23 +15397,379 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15320
15397
|
} catch {
|
|
15321
15398
|
return;
|
|
15322
15399
|
}
|
|
15400
|
+
if (mcp.method === "notifications/progress") {
|
|
15401
|
+
const params = mcp.params ?? {};
|
|
15402
|
+
const cvm = params.cvm;
|
|
15403
|
+
if (cvm && typeof cvm.type === "string") {
|
|
15404
|
+
if (cvm.type === "oversized-transfer") {
|
|
15405
|
+
void handleOversizedFrame(serverPubkey, params, cvm);
|
|
15406
|
+
return;
|
|
15407
|
+
}
|
|
15408
|
+
if (cvm.type === "open-stream") {
|
|
15409
|
+
const entry = admitFrame(serverPubkey, params.progressToken);
|
|
15410
|
+
if (!entry) return;
|
|
15411
|
+
handleOpenStreamFrame(serverPubkey, entry, params, cvm, mcp);
|
|
15412
|
+
return;
|
|
15413
|
+
}
|
|
15414
|
+
}
|
|
15415
|
+
}
|
|
15416
|
+
routeMessage(serverPubkey, mcp);
|
|
15417
|
+
}
|
|
15418
|
+
async function publishStreamFrame(entry, stream, frame) {
|
|
15419
|
+
const session = sessions.get(entry.serverPubkey);
|
|
15420
|
+
if (!session) return;
|
|
15421
|
+
stream.localProgress += 1;
|
|
15422
|
+
await publishMcp(
|
|
15423
|
+
{ pubkey: entry.serverPubkey },
|
|
15424
|
+
session.relays,
|
|
15425
|
+
{
|
|
15426
|
+
jsonrpc: "2.0",
|
|
15427
|
+
method: "notifications/progress",
|
|
15428
|
+
params: {
|
|
15429
|
+
progressToken: stream.token,
|
|
15430
|
+
progress: stream.localProgress,
|
|
15431
|
+
cvm: { type: "open-stream", ...frame }
|
|
15432
|
+
}
|
|
15433
|
+
}
|
|
15434
|
+
);
|
|
15435
|
+
}
|
|
15436
|
+
function admitFrame(serverPubkey, token) {
|
|
15437
|
+
const tokenKey = progressTokenKey(token);
|
|
15438
|
+
if (!tokenKey) return null;
|
|
15439
|
+
const correlationId = issuedTokens.get(`${serverPubkey}:${tokenKey}`);
|
|
15440
|
+
if (!correlationId) return null;
|
|
15441
|
+
const entry = pending.get(correlationId);
|
|
15442
|
+
if (!entry || entry.serverPubkey !== serverPubkey) return null;
|
|
15443
|
+
return entry;
|
|
15444
|
+
}
|
|
15445
|
+
function releaseTransfer(entry) {
|
|
15446
|
+
if (!entry.oversized) return;
|
|
15447
|
+
clearTimeout(entry.oversized.expiryTimer);
|
|
15448
|
+
entry.oversized = void 0;
|
|
15449
|
+
}
|
|
15450
|
+
function releaseStream(entry) {
|
|
15451
|
+
if (!entry.stream) return;
|
|
15452
|
+
clearTimeout(entry.stream.idleTimer);
|
|
15453
|
+
if (entry.stream.probeTimer) clearTimeout(entry.stream.probeTimer);
|
|
15454
|
+
entry.stream = void 0;
|
|
15455
|
+
}
|
|
15456
|
+
function endStream(entry) {
|
|
15457
|
+
releaseStream(entry);
|
|
15458
|
+
entry.streamEnded = true;
|
|
15459
|
+
}
|
|
15460
|
+
function failStream(entry, reason) {
|
|
15461
|
+
const stream = entry.stream;
|
|
15462
|
+
if (!stream || entry.streamEnded) return;
|
|
15463
|
+
endStream(entry);
|
|
15464
|
+
void publishStreamFrame(entry, stream, { frameType: "abort", reason }).catch(() => {
|
|
15465
|
+
});
|
|
15466
|
+
}
|
|
15467
|
+
function resetStreamIdleTimer(entry, stream) {
|
|
15468
|
+
clearTimeout(stream.idleTimer);
|
|
15469
|
+
stream.idleTimer = setTimeout(() => onStreamIdle(entry), streamIdleTimeoutMs);
|
|
15470
|
+
}
|
|
15471
|
+
function onStreamIdle(entry) {
|
|
15472
|
+
const stream = entry.stream;
|
|
15473
|
+
if (!stream || entry.streamEnded) return;
|
|
15474
|
+
if (stream.pendingNonce !== null) return;
|
|
15475
|
+
const session = sessions.get(entry.serverPubkey);
|
|
15476
|
+
if (!session) {
|
|
15477
|
+
failStream(entry, "session closed");
|
|
15478
|
+
return;
|
|
15479
|
+
}
|
|
15480
|
+
stream.pendingNonce = nextStreamNonce();
|
|
15481
|
+
stream.probeTimer = setTimeout(() => failStream(entry, "probe timeout"), streamProbeTimeoutMs);
|
|
15482
|
+
void publishStreamFrame(entry, stream, { frameType: "ping", nonce: stream.pendingNonce }).catch(() => failStream(entry, "ping publication failed"));
|
|
15483
|
+
}
|
|
15484
|
+
function releaseFrameState(entry) {
|
|
15485
|
+
releaseTransfer(entry);
|
|
15486
|
+
releaseStream(entry);
|
|
15487
|
+
if (entry.tokenKey && issuedTokens.get(entry.tokenKey) === entry.correlationId) {
|
|
15488
|
+
issuedTokens.delete(entry.tokenKey);
|
|
15489
|
+
}
|
|
15490
|
+
entry.tokenKey = void 0;
|
|
15491
|
+
}
|
|
15492
|
+
function settlePending(correlationId) {
|
|
15493
|
+
const entry = pending.get(correlationId);
|
|
15494
|
+
if (!entry) return null;
|
|
15495
|
+
clearTimeout(entry.timer);
|
|
15496
|
+
pending.delete(correlationId);
|
|
15497
|
+
releaseFrameState(entry);
|
|
15498
|
+
return entry;
|
|
15499
|
+
}
|
|
15500
|
+
function countActiveTransfers() {
|
|
15501
|
+
let active = 0;
|
|
15502
|
+
for (const entry of pending.values()) {
|
|
15503
|
+
if (entry.oversized) active += 1;
|
|
15504
|
+
}
|
|
15505
|
+
return active;
|
|
15506
|
+
}
|
|
15507
|
+
async function handleOversizedFrame(serverPubkey, params, cvm) {
|
|
15508
|
+
const entry = admitFrame(serverPubkey, params.progressToken);
|
|
15509
|
+
if (!entry) return;
|
|
15510
|
+
const progress = Number(params.progress ?? NaN);
|
|
15511
|
+
if (!Number.isSafeInteger(progress) || progress < 0) return;
|
|
15512
|
+
if (cvm.frameType === "start") {
|
|
15513
|
+
const digest = typeof cvm.digest === "string" ? cvm.digest : "";
|
|
15514
|
+
const { totalBytes, totalChunks } = cvm;
|
|
15515
|
+
if (cvm.completionMode !== COMPLETION_MODE_RENDER) return;
|
|
15516
|
+
if (digest === "") return;
|
|
15517
|
+
if (!isBoundedInteger(totalChunks, 1, MAX_TRANSFER_CHUNKS)) return;
|
|
15518
|
+
if (!isBoundedInteger(totalBytes, 1, MAX_TRANSFER_BYTES)) return;
|
|
15519
|
+
if (progress > Number.MAX_SAFE_INTEGER - totalChunks - 1) return;
|
|
15520
|
+
if (!entry.oversized && countActiveTransfers() >= MAX_ACTIVE_TRANSFERS) return;
|
|
15521
|
+
releaseTransfer(entry);
|
|
15522
|
+
entry.oversized = {
|
|
15523
|
+
digest,
|
|
15524
|
+
totalBytes,
|
|
15525
|
+
totalChunks,
|
|
15526
|
+
startProgress: progress,
|
|
15527
|
+
acceptProgress: null,
|
|
15528
|
+
chunks: /* @__PURE__ */ new Map(),
|
|
15529
|
+
codeUnitsReceived: 0,
|
|
15530
|
+
// Hard expiry: progress frames extend the request deadline but never
|
|
15531
|
+
// how long buffers may be held.
|
|
15532
|
+
expiryTimer: setTimeout(() => releaseTransfer(entry), MAX_TRANSFER_LIFETIME_MS)
|
|
15533
|
+
};
|
|
15534
|
+
refreshPendingTimeout(entry);
|
|
15535
|
+
return;
|
|
15536
|
+
}
|
|
15537
|
+
const transfer = entry.oversized;
|
|
15538
|
+
if (!transfer) return;
|
|
15539
|
+
if (cvm.frameType === "accept") {
|
|
15540
|
+
if (progress !== transfer.startProgress + 1) {
|
|
15541
|
+
releaseTransfer(entry);
|
|
15542
|
+
return;
|
|
15543
|
+
}
|
|
15544
|
+
transfer.acceptProgress = progress;
|
|
15545
|
+
refreshPendingTimeout(entry);
|
|
15546
|
+
return;
|
|
15547
|
+
}
|
|
15548
|
+
if (cvm.frameType === "chunk") {
|
|
15549
|
+
if (typeof cvm.data !== "string" || progress <= transfer.startProgress) return;
|
|
15550
|
+
if (transfer.chunks.has(progress)) return;
|
|
15551
|
+
if (transfer.chunks.size >= transfer.totalChunks) {
|
|
15552
|
+
releaseTransfer(entry);
|
|
15553
|
+
return;
|
|
15554
|
+
}
|
|
15555
|
+
transfer.codeUnitsReceived += cvm.data.length;
|
|
15556
|
+
if (transfer.codeUnitsReceived > MAX_TRANSFER_BYTES) {
|
|
15557
|
+
releaseTransfer(entry);
|
|
15558
|
+
return;
|
|
15559
|
+
}
|
|
15560
|
+
transfer.chunks.set(progress, cvm.data);
|
|
15561
|
+
refreshPendingTimeout(entry);
|
|
15562
|
+
return;
|
|
15563
|
+
}
|
|
15564
|
+
if (cvm.frameType === "abort") {
|
|
15565
|
+
releaseTransfer(entry);
|
|
15566
|
+
return;
|
|
15567
|
+
}
|
|
15568
|
+
if (cvm.frameType === "end") {
|
|
15569
|
+
const message = await assembleTransfer(transfer, progress);
|
|
15570
|
+
releaseTransfer(entry);
|
|
15571
|
+
if (message) routeMessage(serverPubkey, message);
|
|
15572
|
+
return;
|
|
15573
|
+
}
|
|
15574
|
+
}
|
|
15575
|
+
async function assembleTransfer(transfer, endProgress) {
|
|
15576
|
+
const { startProgress, acceptProgress, totalChunks } = transfer;
|
|
15577
|
+
const hasCompleteRange = (first) => {
|
|
15578
|
+
for (let i4 = 0; i4 < totalChunks; i4++) {
|
|
15579
|
+
if (!transfer.chunks.has(first + i4)) return false;
|
|
15580
|
+
}
|
|
15581
|
+
return true;
|
|
15582
|
+
};
|
|
15583
|
+
const directStart = startProgress + 1;
|
|
15584
|
+
const acceptGatedStart = startProgress + 2;
|
|
15585
|
+
const firstProgress = hasCompleteRange(directStart) && endProgress === directStart + totalChunks ? directStart : acceptProgress === startProgress + 1 && hasCompleteRange(acceptGatedStart) && endProgress === acceptGatedStart + totalChunks ? acceptGatedStart : null;
|
|
15586
|
+
if (firstProgress === null) return null;
|
|
15587
|
+
let payload = "";
|
|
15588
|
+
for (let i4 = 0; i4 < totalChunks; i4++) {
|
|
15589
|
+
payload += transfer.chunks.get(firstProgress + i4);
|
|
15590
|
+
}
|
|
15591
|
+
if (new TextEncoder().encode(payload).byteLength !== transfer.totalBytes) return null;
|
|
15592
|
+
const expected = transfer.digest.startsWith(DIGEST_PREFIX) ? transfer.digest.slice(DIGEST_PREFIX.length) : transfer.digest;
|
|
15593
|
+
if (await sha256Hex(payload) !== expected) return null;
|
|
15594
|
+
try {
|
|
15595
|
+
return JSON.parse(payload);
|
|
15596
|
+
} catch {
|
|
15597
|
+
return null;
|
|
15598
|
+
}
|
|
15599
|
+
}
|
|
15600
|
+
function handleOpenStreamFrame(serverPubkey, entry, params, cvm, mcp) {
|
|
15601
|
+
const progress = Number(params.progress ?? NaN);
|
|
15602
|
+
if (!Number.isSafeInteger(progress) || progress < 0) return;
|
|
15603
|
+
if (entry.streamEnded) return;
|
|
15604
|
+
const stream = entry.stream;
|
|
15605
|
+
if (!stream) {
|
|
15606
|
+
if (cvm.frameType !== "start") return;
|
|
15607
|
+
entry.stream = {
|
|
15608
|
+
token: params.progressToken,
|
|
15609
|
+
lastProgress: progress,
|
|
15610
|
+
localProgress: 0,
|
|
15611
|
+
nextChunkIndex: 0,
|
|
15612
|
+
outOfOrder: /* @__PURE__ */ new Map(),
|
|
15613
|
+
outOfOrderCodeUnits: 0,
|
|
15614
|
+
pendingNonce: null,
|
|
15615
|
+
idleTimer: setTimeout(() => onStreamIdle(entry), streamIdleTimeoutMs),
|
|
15616
|
+
probeTimer: null
|
|
15617
|
+
};
|
|
15618
|
+
refreshPendingTimeout(entry);
|
|
15619
|
+
routeEvent(serverPubkey, mcp);
|
|
15620
|
+
return;
|
|
15621
|
+
}
|
|
15622
|
+
const admitOrderedActivity = () => {
|
|
15623
|
+
if (progress <= stream.lastProgress) {
|
|
15624
|
+
failStream(entry, "non-monotonic progress");
|
|
15625
|
+
return false;
|
|
15626
|
+
}
|
|
15627
|
+
stream.lastProgress = progress;
|
|
15628
|
+
resetStreamIdleTimer(entry, stream);
|
|
15629
|
+
refreshPendingTimeout(entry);
|
|
15630
|
+
return true;
|
|
15631
|
+
};
|
|
15632
|
+
switch (cvm.frameType) {
|
|
15633
|
+
case "start":
|
|
15634
|
+
failStream(entry, "duplicate start");
|
|
15635
|
+
return;
|
|
15636
|
+
case "accept":
|
|
15637
|
+
if (!admitOrderedActivity()) return;
|
|
15638
|
+
routeEvent(serverPubkey, mcp);
|
|
15639
|
+
return;
|
|
15640
|
+
case "chunk": {
|
|
15641
|
+
if (typeof cvm.data !== "string" || !isBoundedInteger(cvm.chunkIndex, 0, Number.MAX_SAFE_INTEGER)) {
|
|
15642
|
+
failStream(entry, "malformed chunk");
|
|
15643
|
+
return;
|
|
15644
|
+
}
|
|
15645
|
+
const index = cvm.chunkIndex;
|
|
15646
|
+
if (index < stream.nextChunkIndex) return;
|
|
15647
|
+
if (index > stream.nextChunkIndex) {
|
|
15648
|
+
const existing = stream.outOfOrder.get(index);
|
|
15649
|
+
if (existing) {
|
|
15650
|
+
if (existing.progress !== progress || JSON.stringify(existing.message) !== JSON.stringify(mcp)) {
|
|
15651
|
+
failStream(entry, "conflicting duplicate chunk");
|
|
15652
|
+
}
|
|
15653
|
+
return;
|
|
15654
|
+
}
|
|
15655
|
+
if (progress <= stream.lastProgress || stream.outOfOrder.size >= MAX_STREAM_BUFFERED_CHUNKS || stream.outOfOrderCodeUnits + cvm.data.length > MAX_STREAM_BUFFERED_CODE_UNITS) {
|
|
15656
|
+
failStream(entry, progress <= stream.lastProgress ? "non-monotonic progress" : "gap buffer exhausted");
|
|
15657
|
+
return;
|
|
15658
|
+
}
|
|
15659
|
+
stream.outOfOrder.set(index, { message: mcp, progress, codeUnits: cvm.data.length });
|
|
15660
|
+
stream.outOfOrderCodeUnits += cvm.data.length;
|
|
15661
|
+
resetStreamIdleTimer(entry, stream);
|
|
15662
|
+
refreshPendingTimeout(entry);
|
|
15663
|
+
return;
|
|
15664
|
+
}
|
|
15665
|
+
const ready = [
|
|
15666
|
+
{ message: mcp, progress, codeUnits: 0 }
|
|
15667
|
+
];
|
|
15668
|
+
let nextIndex = index + 1;
|
|
15669
|
+
while (stream.outOfOrder.has(nextIndex)) {
|
|
15670
|
+
ready.push(stream.outOfOrder.get(nextIndex));
|
|
15671
|
+
nextIndex += 1;
|
|
15672
|
+
}
|
|
15673
|
+
let previousProgress = stream.lastProgress;
|
|
15674
|
+
for (const item of ready) {
|
|
15675
|
+
if (item.progress <= previousProgress) {
|
|
15676
|
+
failStream(entry, "non-monotonic chunk order");
|
|
15677
|
+
return;
|
|
15678
|
+
}
|
|
15679
|
+
previousProgress = item.progress;
|
|
15680
|
+
}
|
|
15681
|
+
for (let bufferedIndex = index + 1; bufferedIndex < nextIndex; bufferedIndex++) {
|
|
15682
|
+
const buffered = stream.outOfOrder.get(bufferedIndex);
|
|
15683
|
+
stream.outOfOrder.delete(bufferedIndex);
|
|
15684
|
+
stream.outOfOrderCodeUnits -= buffered.codeUnits;
|
|
15685
|
+
}
|
|
15686
|
+
for (const item of ready) {
|
|
15687
|
+
routeEvent(serverPubkey, item.message);
|
|
15688
|
+
stream.nextChunkIndex += 1;
|
|
15689
|
+
}
|
|
15690
|
+
stream.lastProgress = previousProgress;
|
|
15691
|
+
resetStreamIdleTimer(entry, stream);
|
|
15692
|
+
refreshPendingTimeout(entry);
|
|
15693
|
+
return;
|
|
15694
|
+
}
|
|
15695
|
+
case "ping": {
|
|
15696
|
+
if (typeof cvm.nonce !== "string") return;
|
|
15697
|
+
if (new TextEncoder().encode(cvm.nonce).byteLength > MAX_PING_NONCE_BYTES) return;
|
|
15698
|
+
if (!admitOrderedActivity()) return;
|
|
15699
|
+
void publishStreamFrame(entry, stream, { frameType: "pong", nonce: cvm.nonce }).catch(() => {
|
|
15700
|
+
failStream(entry, "pong publication failed");
|
|
15701
|
+
});
|
|
15702
|
+
return;
|
|
15703
|
+
}
|
|
15704
|
+
case "pong": {
|
|
15705
|
+
if (stream.pendingNonce === null || cvm.nonce !== stream.pendingNonce) return;
|
|
15706
|
+
if (!admitOrderedActivity()) return;
|
|
15707
|
+
stream.pendingNonce = null;
|
|
15708
|
+
if (stream.probeTimer) clearTimeout(stream.probeTimer);
|
|
15709
|
+
stream.probeTimer = null;
|
|
15710
|
+
return;
|
|
15711
|
+
}
|
|
15712
|
+
case "close": {
|
|
15713
|
+
if (cvm.lastChunkIndex !== void 0) {
|
|
15714
|
+
const bound = cvm.lastChunkIndex;
|
|
15715
|
+
const complete = isBoundedInteger(bound, 0, Number.MAX_SAFE_INTEGER) && stream.nextChunkIndex === bound + 1 && stream.outOfOrder.size === 0;
|
|
15716
|
+
if (!complete) {
|
|
15717
|
+
failStream(entry, "close with unresolved chunk gaps");
|
|
15718
|
+
return;
|
|
15719
|
+
}
|
|
15720
|
+
}
|
|
15721
|
+
if (!admitOrderedActivity()) return;
|
|
15722
|
+
routeEvent(serverPubkey, mcp);
|
|
15723
|
+
endStream(entry);
|
|
15724
|
+
return;
|
|
15725
|
+
}
|
|
15726
|
+
case "abort":
|
|
15727
|
+
if (!admitOrderedActivity()) return;
|
|
15728
|
+
routeEvent(serverPubkey, mcp);
|
|
15729
|
+
endStream(entry);
|
|
15730
|
+
return;
|
|
15731
|
+
default:
|
|
15732
|
+
return;
|
|
15733
|
+
}
|
|
15734
|
+
}
|
|
15735
|
+
function refreshPendingTimeout(entry) {
|
|
15736
|
+
clearTimeout(entry.timer);
|
|
15737
|
+
entry.timer = setTimeout(() => {
|
|
15738
|
+
const settled = settlePending(entry.correlationId);
|
|
15739
|
+
settled?.reject(new Error("relay timeout"));
|
|
15740
|
+
}, entry.timeoutMs);
|
|
15741
|
+
}
|
|
15742
|
+
function routeMessage(serverPubkey, mcp) {
|
|
15323
15743
|
const id = mcp.id;
|
|
15324
15744
|
if (id != null && pending.has(String(id))) {
|
|
15325
15745
|
const entry = pending.get(String(id));
|
|
15326
15746
|
if (entry.serverPubkey !== serverPubkey) return;
|
|
15327
|
-
|
|
15328
|
-
|
|
15329
|
-
entry.resolve({ ...mcp, id: entry.originalId });
|
|
15747
|
+
const settled = settlePending(String(id));
|
|
15748
|
+
settled?.resolve({ ...mcp, id: entry.originalId });
|
|
15330
15749
|
return;
|
|
15331
15750
|
}
|
|
15751
|
+
routeEvent(serverPubkey, mcp);
|
|
15752
|
+
}
|
|
15753
|
+
function routeEvent(serverPubkey, mcp) {
|
|
15332
15754
|
if (mcp.method !== void 0 && sessions.has(serverPubkey)) {
|
|
15333
15755
|
const server = { pubkey: serverPubkey };
|
|
15334
15756
|
for (const handler of eventHandlers) handler(server, mcp);
|
|
15335
15757
|
}
|
|
15336
15758
|
}
|
|
15337
15759
|
async function publishMcp(server, relays, message) {
|
|
15760
|
+
const capabilityTags = [
|
|
15761
|
+
...encrypt22 ? [[SUPPORT_ENCRYPTION]] : [],
|
|
15762
|
+
...encrypt22 && wrapKind === KIND_GIFT_WRAP_EPHEMERAL ? [[SUPPORT_ENCRYPTION_EPHEMERAL]] : [],
|
|
15763
|
+
[SUPPORT_OVERSIZED_TRANSFER],
|
|
15764
|
+
[SUPPORT_OPEN_STREAM]
|
|
15765
|
+
];
|
|
15338
15766
|
const inner = finalizeEvent(
|
|
15339
|
-
{
|
|
15767
|
+
{
|
|
15768
|
+
kind: KIND_CVM,
|
|
15769
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
15770
|
+
tags: [["p", server.pubkey], ...capabilityTags],
|
|
15771
|
+
content: JSON.stringify(message)
|
|
15772
|
+
},
|
|
15340
15773
|
clientSecretKey
|
|
15341
15774
|
);
|
|
15342
15775
|
if (!encrypt22) {
|
|
@@ -15360,17 +15793,52 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15360
15793
|
function sendCorrelated(server, relays, message, timeout) {
|
|
15361
15794
|
const correlationId = nextCorrelationId();
|
|
15362
15795
|
const originalId = message.id;
|
|
15363
|
-
|
|
15796
|
+
let outgoing = { ...message, id: correlationId };
|
|
15797
|
+
let issuedToken;
|
|
15798
|
+
const params = message.params && typeof message.params === "object" ? message.params : void 0;
|
|
15799
|
+
const meta = params?._meta && typeof params._meta === "object" ? params._meta : void 0;
|
|
15800
|
+
const explicit = meta?.progressToken;
|
|
15801
|
+
if (message.method === "tools/call") {
|
|
15802
|
+
issuedToken = typeof explicit === "string" || typeof explicit === "number" ? explicit : correlationId;
|
|
15803
|
+
outgoing = {
|
|
15804
|
+
...outgoing,
|
|
15805
|
+
params: {
|
|
15806
|
+
...params,
|
|
15807
|
+
_meta: {
|
|
15808
|
+
...meta,
|
|
15809
|
+
progressToken: issuedToken
|
|
15810
|
+
}
|
|
15811
|
+
}
|
|
15812
|
+
};
|
|
15813
|
+
} else if (typeof explicit === "string" || typeof explicit === "number") {
|
|
15814
|
+
issuedToken = explicit;
|
|
15815
|
+
}
|
|
15816
|
+
const tokenKey = progressTokenKey(issuedToken);
|
|
15817
|
+
const issuedKey = tokenKey ? `${server.pubkey}:${tokenKey}` : null;
|
|
15818
|
+
if (issuedKey && issuedTokens.has(issuedKey)) {
|
|
15819
|
+
return Promise.reject(new Error("duplicate progress token"));
|
|
15820
|
+
}
|
|
15364
15821
|
return new Promise((resolve, reject2) => {
|
|
15365
|
-
const
|
|
15366
|
-
|
|
15367
|
-
reject2
|
|
15368
|
-
|
|
15369
|
-
|
|
15822
|
+
const entry = {
|
|
15823
|
+
resolve,
|
|
15824
|
+
reject: reject2,
|
|
15825
|
+
timer: setTimeout(() => {
|
|
15826
|
+
const settled = settlePending(correlationId);
|
|
15827
|
+
settled?.reject(new Error("relay timeout"));
|
|
15828
|
+
}, timeout),
|
|
15829
|
+
timeoutMs: timeout,
|
|
15830
|
+
originalId,
|
|
15831
|
+
serverPubkey: server.pubkey,
|
|
15832
|
+
correlationId
|
|
15833
|
+
};
|
|
15834
|
+
if (issuedKey) {
|
|
15835
|
+
entry.tokenKey = issuedKey;
|
|
15836
|
+
issuedTokens.set(issuedKey, correlationId);
|
|
15837
|
+
}
|
|
15838
|
+
pending.set(correlationId, entry);
|
|
15370
15839
|
void publishMcp(server, relays, outgoing).catch((err2) => {
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
reject2(err2 instanceof Error ? err2 : new Error("publish failed"));
|
|
15840
|
+
const settled = settlePending(correlationId);
|
|
15841
|
+
settled?.reject(err2 instanceof Error ? err2 : new Error("publish failed"));
|
|
15374
15842
|
});
|
|
15375
15843
|
});
|
|
15376
15844
|
}
|
|
@@ -15468,6 +15936,10 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15468
15936
|
if (!session) return;
|
|
15469
15937
|
sessions.delete(server.pubkey);
|
|
15470
15938
|
releaseRelays(session.relays);
|
|
15939
|
+
for (const [correlationId, entry] of pending) {
|
|
15940
|
+
if (entry.serverPubkey !== server.pubkey) continue;
|
|
15941
|
+
settlePending(correlationId)?.reject(new Error("server closed"));
|
|
15942
|
+
}
|
|
15471
15943
|
},
|
|
15472
15944
|
onEvent(handler) {
|
|
15473
15945
|
eventHandlers.add(handler);
|
|
@@ -15482,9 +15954,12 @@ function createNostrCvmTransport(options = {}) {
|
|
|
15482
15954
|
inbound = null;
|
|
15483
15955
|
for (const entry of pending.values()) {
|
|
15484
15956
|
clearTimeout(entry.timer);
|
|
15957
|
+
releaseTransfer(entry);
|
|
15958
|
+
releaseStream(entry);
|
|
15485
15959
|
entry.reject(new Error("transport disposed"));
|
|
15486
15960
|
}
|
|
15487
15961
|
pending.clear();
|
|
15962
|
+
issuedTokens.clear();
|
|
15488
15963
|
sessions.clear();
|
|
15489
15964
|
relayRefcount.clear();
|
|
15490
15965
|
eventHandlers.clear();
|
|
@@ -16389,14 +16864,14 @@ var CacheStorageNappletArtifactCache = class {
|
|
|
16389
16864
|
this.softCeilingBytes = options.softCeilingBytes ?? DEFAULT_SOFT_CEILING_BYTES;
|
|
16390
16865
|
this.hardCeilingBytes = options.hardCeilingBytes ?? DEFAULT_HARD_CEILING_BYTES;
|
|
16391
16866
|
}
|
|
16392
|
-
async readBlob(
|
|
16867
|
+
async readBlob(sha256Hex2) {
|
|
16393
16868
|
try {
|
|
16394
16869
|
const cache2 = await this.open();
|
|
16395
|
-
const response = await cache2.match(this.blobUrl(
|
|
16870
|
+
const response = await cache2.match(this.blobUrl(sha256Hex2));
|
|
16396
16871
|
if (!response) return void 0;
|
|
16397
16872
|
const bytes = await responseBytes(response);
|
|
16398
16873
|
const index = await this.readIndex(cache2);
|
|
16399
|
-
const entry = index.blobs[
|
|
16874
|
+
const entry = index.blobs[sha256Hex2];
|
|
16400
16875
|
if (entry) {
|
|
16401
16876
|
entry.lastAccessed = this.now();
|
|
16402
16877
|
index.updatedAt = entry.lastAccessed;
|
|
@@ -16404,20 +16879,20 @@ var CacheStorageNappletArtifactCache = class {
|
|
|
16404
16879
|
}
|
|
16405
16880
|
return bytes;
|
|
16406
16881
|
} catch (cause) {
|
|
16407
|
-
this.diagnostic({ type: "cache-read-failed", message: `failed to read blob ${
|
|
16882
|
+
this.diagnostic({ type: "cache-read-failed", message: `failed to read blob ${sha256Hex2}`, cause });
|
|
16408
16883
|
return void 0;
|
|
16409
16884
|
}
|
|
16410
16885
|
}
|
|
16411
|
-
async deleteBlob(
|
|
16886
|
+
async deleteBlob(sha256Hex2) {
|
|
16412
16887
|
try {
|
|
16413
16888
|
const cache2 = await this.open();
|
|
16414
|
-
await cache2.delete(this.blobUrl(
|
|
16889
|
+
await cache2.delete(this.blobUrl(sha256Hex2));
|
|
16415
16890
|
const index = await this.readIndex(cache2);
|
|
16416
|
-
delete index.blobs[
|
|
16891
|
+
delete index.blobs[sha256Hex2];
|
|
16417
16892
|
index.updatedAt = this.now();
|
|
16418
16893
|
await this.writeIndex(cache2, index);
|
|
16419
16894
|
} catch (cause) {
|
|
16420
|
-
this.diagnostic({ type: "cache-delete-failed", message: `failed to delete blob ${
|
|
16895
|
+
this.diagnostic({ type: "cache-delete-failed", message: `failed to delete blob ${sha256Hex2}`, cause });
|
|
16421
16896
|
}
|
|
16422
16897
|
}
|
|
16423
16898
|
async writeVerifiedResolution(input) {
|
|
@@ -16798,22 +17273,22 @@ function verifyManifestSignature(event) {
|
|
|
16798
17273
|
return false;
|
|
16799
17274
|
}
|
|
16800
17275
|
}
|
|
16801
|
-
function verifyBlobHash(bytes,
|
|
16802
|
-
return bytesToHex2(sha256(bytes)) ===
|
|
17276
|
+
function verifyBlobHash(bytes, sha256Hex2) {
|
|
17277
|
+
return bytesToHex2(sha256(bytes)) === sha256Hex2;
|
|
16803
17278
|
}
|
|
16804
17279
|
function stripTrailingSlash(url) {
|
|
16805
17280
|
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
16806
17281
|
}
|
|
16807
|
-
async function fetchBlob(servers,
|
|
17282
|
+
async function fetchBlob(servers, sha256Hex2, fetchBytes) {
|
|
16808
17283
|
for (const server of servers) {
|
|
16809
|
-
const url = `${stripTrailingSlash(server)}/${
|
|
17284
|
+
const url = `${stripTrailingSlash(server)}/${sha256Hex2}`;
|
|
16810
17285
|
try {
|
|
16811
17286
|
const bytes = await fetchBytes(url);
|
|
16812
|
-
if (verifyBlobHash(bytes,
|
|
17287
|
+
if (verifyBlobHash(bytes, sha256Hex2)) return bytes;
|
|
16813
17288
|
} catch {
|
|
16814
17289
|
}
|
|
16815
17290
|
}
|
|
16816
|
-
throw new NappletResolutionError("blob-unavailable", `no server served blob ${
|
|
17291
|
+
throw new NappletResolutionError("blob-unavailable", `no server served blob ${sha256Hex2}`);
|
|
16817
17292
|
}
|
|
16818
17293
|
var INDEX_PATHS = ["/index.html", "index.html", "/"];
|
|
16819
17294
|
function defaultDecode(bytes) {
|
|
@@ -20425,7 +20900,7 @@ async function resolvePajaPointer(value, options = {}) {
|
|
|
20425
20900
|
const resolved = await resolveNapplet({
|
|
20426
20901
|
event,
|
|
20427
20902
|
cache: await openNappletArtifactCache({ requireStorageEstimate: false }),
|
|
20428
|
-
fetchBlob: (
|
|
20903
|
+
fetchBlob: (sha256Hex2, servers) => fetchBlob([...servers, ...blossomServers], sha256Hex2, async (url) => {
|
|
20429
20904
|
const response = await (options.fetcher ?? fetch)(url);
|
|
20430
20905
|
if (!response.ok) throw new Error(`[paja-runtime] blob ${url}: ${response.status}`);
|
|
20431
20906
|
return new Uint8Array(await response.arrayBuffer());
|