Chunk watch history payloads and rebuild fetch pipeline

This commit is contained in:
thePR0M3TH3AN committed 2025-09-30 14:34:41 -04:00
1 parent dfa260c2d0
commit 034c4c3761
4 files changed
+636 -167

No files matched your search

+90 -41
View File
@@ -24,8 +24,11 @@ const { nostrClient } = await import("../js/nostr.js");
function createPublishingClient(actorPubkey) {
const client = new nostrClient.constructor();
const publishedEvents = [];
const payloads = [];
client.pool = {
publish(urls, event) {
publishedEvents.push(event);
return {
on(eventName, handler) {
if (eventName === "ok" || eventName === "seen") {
@@ -39,14 +42,14 @@ function createPublishingClient(actorPubkey) {
client.pubkey = "";
client.sessionActor = { pubkey: actorPubkey, privateKey: "unit-secret" };
client.ensureSessionActor = async () => actorPubkey;
client.encryptWatchHistoryPayload = async () => ({
ok: true,
ciphertext: "ciphertext",
});
client.encryptWatchHistoryPayload = async (_actor, payload) => {
payloads.push(payload);
return { ok: true, ciphertext: JSON.stringify(payload) };
};
client.persistWatchHistoryEntry = () => {};
client.cancelWatchHistoryRepublish = () => {};
client.scheduleWatchHistoryRepublish = () => {};
return client;
return { client, publishedEvents, payloads };
}
function createDecryptClient(actorPubkey) {
@@ -57,56 +60,102 @@ function createDecryptClient(actorPubkey) {
}
const ACTOR = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const POINTER_A = {
type: "a",
value: `30078:${ACTOR}:watch-history`,
relay: "wss://relay.unit",
};
const POINTER_E = { type: "e", value: "event-id-123", relay: null };
const publishingClient = createPublishingClient(ACTOR);
const { client: publishingClient, publishedEvents, payloads } =
createPublishingClient(ACTOR);
publishingClient.watchHistoryPayloadMaxBytes = 400;
publishingClient.watchHistoryFetchEventLimit = 10;
const longPointers = Array.from({ length: 8 }, (_, index) => {
const relay = index % 3 === 0 ? "wss://relay.unit" : null;
if (index % 2 === 0) {
return {
type: "e",
value: `event-${index}-${"x".repeat(90)}`,
relay,
};
}
return {
type: "a",
value: `30078:${ACTOR}:history-${index}-${"y".repeat(60)}`,
relay,
};
});
const publishResult = await publishingClient.publishWatchHistorySnapshot(
ACTOR,
[POINTER_A, POINTER_E]
longPointers
);
assert.equal(publishResult.ok, true, "publish should report success");
assert(publishResult.events.length > 1, "snapshot should chunk into multiple events");
assert.equal(
publishResult.events.length,
payloads.length,
"each chunk should produce an encryption payload"
);
const pointerTags = publishResult.event.tags.filter((tag) =>
tag && (tag[0] === "a" || tag[0] === "e")
payloads.forEach((payload) => {
const size = JSON.stringify(payload).length;
assert(
size <= publishingClient.watchHistoryPayloadMaxBytes,
`payload size ${size} should respect cap`
);
assert.equal(payload.version, 2, "chunk payload should use version 2");
});
const snapshotIds = new Set(
publishResult.events.map((event) =>
event.tags.find((tag) => Array.isArray(tag) && tag[0] === "snapshot")?.[1]
)
);
assert.equal(snapshotIds.size, 1, "all chunks should share a snapshot id");
const headTag = publishResult.event.tags.find(
(tag) => Array.isArray(tag) && tag[0] === "head"
);
assert(headTag, "head chunk should include head tag");
delete globalThis.window.nostr;
if (!globalThis.window.NostrTools.nip04) {
globalThis.window.NostrTools.nip04 = {};
}
globalThis.window.NostrTools.nip04.decrypt = async (
_priv,
_pub,
ciphertext
) => ciphertext;
const decryptClient = createDecryptClient(ACTOR);
decryptClient.relays = ["wss://unit.test"];
decryptClient.watchHistoryFetchEventLimit = 10;
decryptClient.pool = {
list: async () => publishedEvents,
};
const fetched = await decryptClient.fetchWatchHistory(ACTOR);
assert.equal(
fetched.items.length,
longPointers.length,
"fetch should reassemble all chunked items"
);
assert.deepEqual(pointerTags, [
["a", POINTER_A.value, POINTER_A.relay],
["e", POINTER_E.value],
]);
assert.deepEqual(
publishResult.items.map((item) => ({
fetched.items.map((item) => ({
type: item.type,
value: item.value,
relay: item.relay || null,
})),
[
{ type: "a", value: POINTER_A.value, relay: POINTER_A.relay },
{ type: "e", value: POINTER_E.value, relay: null },
]
longPointers.map((item) => ({
type: item.type,
value: item.value,
relay: item.relay || null,
})),
"chunked fetch should preserve pointer order"
);
delete globalThis.window.nostr;
delete globalThis.window.NostrTools.nip04;
const decryptClient = createDecryptClient(ACTOR);
const decrypted = await decryptClient.decryptWatchHistoryEvent(
publishResult.event,
ACTOR
);
assert.deepEqual(decrypted, {
version: 0,
items: [
{ type: "a", value: POINTER_A.value, relay: POINTER_A.relay },
{ type: "e", value: POINTER_E.value, relay: null },
],
});
console.log("watch history tests passed");