Handle missing signer when decrypting watch history

This commit is contained in:
thePR0M3TH3AN committed 2025-10-01 20:50:08 -04:00
1 parent db0461e9c7
commit 61c8b3a8ee
2 files changed
+148 -3

No files matched your search

+75 -3
View File
@@ -1778,6 +1778,9 @@ class NostrClient {
delivered: previousEntry?.delivered instanceof Set
? new Set(previousEntry.delivered)
: new Set(),
chunkNeedsSigner: previousEntry?.chunkNeedsSigner instanceof Set
? new Set(previousEntry.chunkNeedsSigner)
: new Set(),
};
const validKeys = new Set(entry.items.map((item) => pointerKey(item)));
@@ -1801,6 +1804,11 @@ class NostrClient {
entry.delivered.delete(key);
}
}
for (const key of [...entry.chunkNeedsSigner]) {
if (!validKeys.has(key)) {
entry.chunkNeedsSigner.delete(key);
}
}
const previousChunkPointers = previousEntry?.chunkPointers;
const headPointer = previousChunkPointers?.head
@@ -2077,12 +2085,39 @@ class NostrClient {
}
const normalizedLogged =
typeof this.pubkey === "string" ? this.pubkey.toLowerCase() : "";
typeof this.pubkey === "string" && this.pubkey.trim()
? this.pubkey.trim().toLowerCase()
: "";
const pointerAuthor =
typeof pointerEvent?.pubkey === "string" && pointerEvent.pubkey.trim()
? pointerEvent.pubkey.trim().toLowerCase()
: "";
const hasExtensionDecrypt =
typeof window?.nostr?.nip04?.decrypt === "function";
const shouldDeferMissingSigner =
normalizedActor &&
normalizedActor === normalizedLogged &&
pointerAuthor &&
pointerAuthor === normalizedActor &&
!hasExtensionDecrypt;
if (shouldDeferMissingSigner) {
fallbackPayload.needsSignerRetry = true;
const warningMeta = {
actor: normalizedActor,
};
if (typeof pointerEvent?.id === "string" && pointerEvent.id) {
warningMeta.event = pointerEvent.id;
}
console.warn("[nostr] watch-history-decrypt-missing-signer", warningMeta);
return fallbackPayload;
}
if (
normalizedActor &&
normalizedActor === normalizedLogged &&
window?.nostr?.nip04?.decrypt
hasExtensionDecrypt
) {
try {
const plaintext = await window.nostr.nip04.decrypt(
@@ -3932,7 +3967,11 @@ class NostrClient {
indexEvent: null,
chunkAddresses: new Set(),
expectedChunkIdentifiers: new Set(),
needsSignerRetry: new Set(),
};
if (!(bucket.needsSignerRetry instanceof Set)) {
bucket.needsSignerRetry = new Set();
}
bucket.chunks.set(chunkIndex, {
event,
@@ -3941,6 +3980,7 @@ class NostrClient {
snapshot: snapshotId,
chunkIndex,
totalChunks,
needsSignerRetry: !!payload?.needsSignerRetry,
},
created_at: typeof event.created_at === "number" ? event.created_at : 0,
});
@@ -3951,6 +3991,11 @@ class NostrClient {
bucket.latestCreatedAt = createdAt;
}
bucket.version = Math.max(bucket.version, payload.version || 0);
if (payload?.needsSignerRetry) {
bucket.needsSignerRetry.add(chunkIndex);
} else {
bucket.needsSignerRetry.delete(chunkIndex);
}
const chunkAddress = eventToAddressPointer(event);
if (chunkAddress) {
bucket.chunkAddresses.add(chunkAddress);
@@ -3971,7 +4016,11 @@ class NostrClient {
indexEvent: null,
chunkAddresses: new Set(),
expectedChunkIdentifiers: new Set(),
needsSignerRetry: new Set(),
};
if (!(bucket.needsSignerRetry instanceof Set)) {
bucket.needsSignerRetry = new Set();
}
if (meta.event) {
const createdAt =
@@ -4060,6 +4109,9 @@ class NostrClient {
expectedChunkIdentifiers: bucket.expectedChunkIdentifiers instanceof Set
? new Set(bucket.expectedChunkIdentifiers)
: new Set(),
needsSignerRetry: bucket.needsSignerRetry instanceof Set
? new Set(bucket.needsSignerRetry)
: new Set(),
};
if (!fallbackSnapshot) {
@@ -4124,6 +4176,12 @@ class NostrClient {
entry.chunkEvents.clear();
}
if (!(entry.chunkNeedsSigner instanceof Set)) {
entry.chunkNeedsSigner = new Set();
} else {
entry.chunkNeedsSigner.clear();
}
const chunkPointerItemsForEntry = [];
const chunkPointerKeys = new Set();
let indexPointerForEntry = null;
@@ -4178,8 +4236,22 @@ class NostrClient {
continue;
}
const clonedChunkEvent = cloneEventForCache(chunk.event);
if (clonedChunkEvent) {
const chunkPayloadIndex = Number.isFinite(chunk.payload?.chunkIndex)
? Math.max(0, Math.floor(chunk.payload.chunkIndex))
: null;
const flaggedBySnapshot =
chunkPayloadIndex !== null &&
effectiveSnapshot?.needsSignerRetry instanceof Set &&
effectiveSnapshot.needsSignerRetry.has(chunkPayloadIndex);
const needsSignerRetry =
!!chunk.payload?.needsSignerRetry || flaggedBySnapshot;
if (needsSignerRetry) {
entry.chunkNeedsSigner.add(key);
entry.resolved.delete(key);
} else if (clonedChunkEvent) {
entry.chunkEvents.set(key, clonedChunkEvent);
} else {
entry.chunkEvents.set(key, chunk.event);
}
chunkPointerKeys.add(key);
chunkPointerItemsForEntry.push(pointer);
+73
View File
@@ -1381,6 +1381,79 @@ try {
}
}
const missingSignerClient = createDecryptClient(LOGGED_ACTOR);
missingSignerClient.pubkey = LOGGED_ACTOR;
missingSignerClient.sessionActor = null;
let missingSignerEnsureCalls = 0;
missingSignerClient.ensureSessionActor = async () => {
missingSignerEnsureCalls += 1;
return LOGGED_ACTOR;
};
const priorMissingSignerNostr = globalThis.window.nostr;
const priorMissingSignerSessionDecrypt = globalThis.window.NostrTools.nip04.decrypt;
let missingSignerSessionDecryptCalls = 0;
try {
delete globalThis.window.nostr;
globalThis.window.NostrTools.nip04.decrypt = async () => {
missingSignerSessionDecryptCalls += 1;
return JSON.stringify(encryptedPayload);
};
const missingSignerChunkEvent = {
...encryptedChunkEvent,
pubkey: LOGGED_ACTOR,
id: "missing-signer-event",
};
const missingSignerResult = await missingSignerClient.decryptWatchHistoryEvent(
missingSignerChunkEvent,
LOGGED_ACTOR
);
assert.equal(
missingSignerEnsureCalls,
0,
"missing signer should not trigger session actor ensure"
);
assert.equal(
missingSignerSessionDecryptCalls,
0,
"missing signer should skip session decrypt fallback"
);
assert.equal(
missingSignerResult.needsSignerRetry,
true,
"missing signer fallback should mark retry flag"
);
assert.deepEqual(
missingSignerResult.items.map((item) => ({
type: item.type,
value: item.value,
relay: item.relay || null,
})),
encryptedPayload.items.map((item) => ({
type: item.type,
value: item.value,
relay: item.relay || null,
})),
"missing signer fallback should preserve pointer metadata"
);
} finally {
if (priorMissingSignerNostr === undefined) {
delete globalThis.window.nostr;
} else {
globalThis.window.nostr = priorMissingSignerNostr;
}
if (priorMissingSignerSessionDecrypt === undefined) {
delete globalThis.window.NostrTools.nip04.decrypt;
} else {
globalThis.window.NostrTools.nip04.decrypt = priorMissingSignerSessionDecrypt;
}
}
const unsortedVideos = [
{ id: "alpha", watchHistory: { watchedAt: 1_700_000_000_500 } },
{ id: "bravo", watchHistory: { watchedAt: 1_700_000_010_000 } },