Files
bitvid/tests/dm-decrypt-context.test.mjs
T
thePR0M3TH3ANandClaude Opus 4.8 7951fd0f09 fix(dm): can't open conversations + "helpers unavailable" on a working signer
After switching to a responsive signer (nos2x), two DM-specific bugs surfaced.

1. Clicking a conversation in the list threw
   "this.controller.handleDmConversationSelect is not a function"
   (ProfileDirectMessageRenderer onSelectConversation). The method exists on
   ProfileDirectMessageActions and the controller delegates ~15 sibling Actions
   methods — but the handleDmConversationSelect delegation was missing. Add it.

2. Loading a conversation's messages threw "DM decryption helpers are
   unavailable" even though the signer decrypts blocks/subscriptions/hashtags
   fine. buildDmDecryptContext gated decryptor registration on (a) the dynamic
   `capabilities` getter (which probes window.nostr and false-negatives on lazy
   injection / nos2x's module shape) and (b) the permission-probe result. The
   list services already learned to register by METHOD EXISTENCE and validate at
   call time; align the DM path to do the same so a present, working signer
   never yields zero decryptors. The permission probe still runs.

Tests: tests/dm-decrypt-context.test.mjs (cheat-resistant — a signer whose
capabilities getter reports false and whose permission probe returns ok:false
must still register nip04+nip44 decryptors). Full unit + dm:unit + dm:integration
suites green. KNOWN_BUGS #0.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-16 14:52:11 -04:00

63 lines
2.3 KiB
JavaScript

// Scenario (SCN-dm-context-method-existence):
// Given an active NIP-07 signer that EXPOSES nip04Decrypt/nip44Decrypt methods
// but whose dynamic `capabilities` getter reports them false (the real-env
// nos2x case — lazy/odd module shape) AND whose extension permission probe
// returns ok:false,
// When buildDmDecryptContext runs for a DM load,
// Then it must still register the signer's decryptors (by method existence,
// like the list services do) — otherwise listDirectMessages throws
// "DM decryption helpers are unavailable" even though the signer decrypts
// everything else fine (KNOWN_BUGS #0).
import test from "node:test";
import assert from "node:assert/strict";
if (!globalThis.WebSocket) {
globalThis.WebSocket = class MockWebSocket {};
}
const { NostrClient } = await import("../js/nostr/client.js");
test("buildDmDecryptContext registers decryptors by method existence, not capabilities/permission gate", async () => {
const client = new NostrClient();
const actor = "a".repeat(64);
client.pubkey = actor;
const fakeSigner = {
type: "nip07",
pubkey: actor,
nip04Decrypt: async () => "plaintext",
nip44Decrypt: async () => "plaintext",
// The dynamic capabilities getter that returned false negatives and made
// the DM path register zero decryptors.
get capabilities() {
return { sign: true, nip04: false, nip44: false };
},
};
// Active signer resolves to our fake regardless of how it's looked up.
client.signerManager.resolveActiveSigner = () => fakeSigner;
client.signerManager.getActiveSigner = () => fakeSigner;
// Permission probe reports NOT ok — must NOT block decryptor registration.
client.ensureExtensionPermissions = async () => ({
ok: false,
error: new Error("permission-not-granted"),
});
const context = await client.buildDmDecryptContext(actor);
const schemes = context.decryptors.map((d) => d.scheme);
assert.ok(
schemes.includes("nip44"),
"nip44 decryptor must be registered from the signer method",
);
assert.ok(
schemes.includes("nip04"),
"nip04 decryptor must be registered from the signer method",
);
assert.ok(
context.decryptors.length >= 2,
`expected at least the two signer decryptors, got ${context.decryptors.length}`,
);
});