mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
feat(services): add window.nostr fallback for hashtag preference decryption
This commit updates `HashtagPreferencesService.decryptEvent` to register and utilize decryption methods from `window.nostr` (NIP-04 and NIP-44) as a fallback mechanism. This ensures that users without an active internal signer (e.g., read-only or extension-only users) can still decrypt their preferences if they have granted permission to the extension. - Mirrors the decryption logic from `UserBlockListManager` (`js/userBlocks.js`). - Adds logging for fallback attempts and failures. - Preserves existing scheme selection logic (`determineDecryptionOrder`). - Adds new test cases to `tests/hashtag-preferences.test.mjs` verifying fallback behavior when the active signer is missing or incapable. Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
c4edfd0d36
commit
39d1eda058
2 files changed
+165
-5
No files matched your search
@@ -726,22 +726,76 @@ class HashtagPreferencesService {
|
||||
}
|
||||
|
||||
const decryptors = new Map();
|
||||
const registerDecryptor = (scheme, handler) => {
|
||||
const sources = new Map();
|
||||
const registerDecryptor = (scheme, handler, source = "unknown") => {
|
||||
if (!scheme || typeof handler !== "function" || decryptors.has(scheme)) {
|
||||
return;
|
||||
}
|
||||
decryptors.set(scheme, handler);
|
||||
sources.set(scheme, source);
|
||||
};
|
||||
|
||||
if (signerHasNip44) {
|
||||
registerDecryptor("nip44", (payload) => signer.nip44Decrypt(userPubkey, payload));
|
||||
registerDecryptor("nip44_v2", (payload) =>
|
||||
signer.nip44Decrypt(userPubkey, payload),
|
||||
registerDecryptor(
|
||||
"nip44",
|
||||
(payload) => signer.nip44Decrypt(userPubkey, payload),
|
||||
"active-signer",
|
||||
);
|
||||
registerDecryptor(
|
||||
"nip44_v2",
|
||||
(payload) => signer.nip44Decrypt(userPubkey, payload),
|
||||
"active-signer",
|
||||
);
|
||||
}
|
||||
|
||||
if (signerHasNip04) {
|
||||
registerDecryptor("nip04", (payload) => signer.nip04Decrypt(userPubkey, payload));
|
||||
registerDecryptor(
|
||||
"nip04",
|
||||
(payload) => signer.nip04Decrypt(userPubkey, payload),
|
||||
"active-signer",
|
||||
);
|
||||
}
|
||||
|
||||
const nostrApi = typeof window !== "undefined" ? window?.nostr : null;
|
||||
if (nostrApi) {
|
||||
if (typeof nostrApi.nip04?.decrypt === "function") {
|
||||
registerDecryptor(
|
||||
"nip04",
|
||||
(payload) => nostrApi.nip04.decrypt(userPubkey, payload),
|
||||
"extension",
|
||||
);
|
||||
}
|
||||
|
||||
const nip44 =
|
||||
nostrApi.nip44 && typeof nostrApi.nip44 === "object"
|
||||
? nostrApi.nip44
|
||||
: null;
|
||||
if (nip44) {
|
||||
if (typeof nip44.decrypt === "function") {
|
||||
registerDecryptor(
|
||||
"nip44",
|
||||
(payload) => nip44.decrypt(userPubkey, payload),
|
||||
"extension",
|
||||
);
|
||||
}
|
||||
|
||||
const nip44v2 =
|
||||
nip44.v2 && typeof nip44.v2 === "object" ? nip44.v2 : null;
|
||||
if (nip44v2 && typeof nip44v2.decrypt === "function") {
|
||||
registerDecryptor(
|
||||
"nip44_v2",
|
||||
(payload) => nip44v2.decrypt(userPubkey, payload),
|
||||
"extension",
|
||||
);
|
||||
if (!decryptors.has("nip44")) {
|
||||
registerDecryptor(
|
||||
"nip44",
|
||||
(payload) => nip44v2.decrypt(userPubkey, payload),
|
||||
"extension",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!decryptors.size) {
|
||||
@@ -760,6 +814,14 @@ class HashtagPreferencesService {
|
||||
if (!decryptFn) {
|
||||
continue;
|
||||
}
|
||||
const source = sources.get(scheme);
|
||||
|
||||
if (source === "extension") {
|
||||
userLogger.info(
|
||||
`${LOG_PREFIX} Attempting decryption via window.nostr fallback (${scheme})`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const plaintext = await decryptFn(ciphertext);
|
||||
if (typeof plaintext !== "string") {
|
||||
@@ -771,6 +833,12 @@ class HashtagPreferencesService {
|
||||
}
|
||||
return { ok: true, plaintext, scheme };
|
||||
} catch (error) {
|
||||
if (source === "extension") {
|
||||
userLogger.warn(
|
||||
`${LOG_PREFIX} window.nostr fallback decryption failed (${scheme})`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
attemptErrors.push({ scheme, error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,3 +463,95 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"load falls back to window.nostr when active signer is missing",
|
||||
{ concurrency: false },
|
||||
async (t) => {
|
||||
const pubkey = "abcdef".repeat(10) + "1234";
|
||||
|
||||
// Ensure no active signer (override beforeEach)
|
||||
clearActiveSigner();
|
||||
|
||||
// Mock window.nostr
|
||||
window.nostr = {
|
||||
nip04: {
|
||||
decrypt: async (pk, ciphertext) => {
|
||||
if (pk !== pubkey) throw new Error("Wrong pubkey");
|
||||
if (ciphertext === "window-encrypted") {
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
interests: ["WindowSuccess"],
|
||||
disinterests: []
|
||||
});
|
||||
}
|
||||
throw new Error("Decrypt failed");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Mock pool list to return an event
|
||||
nostrClient.pool = {
|
||||
list: async () => [{
|
||||
id: "evt1",
|
||||
created_at: 1000,
|
||||
pubkey,
|
||||
content: "window-encrypted",
|
||||
tags: [["encrypted", "nip04"]]
|
||||
}]
|
||||
};
|
||||
|
||||
relayManager.setEntries([{ url: "wss://relay.fallback", mode: "both" }], { allowEmpty: false, updateClient: false });
|
||||
|
||||
await hashtagPreferences.load(pubkey);
|
||||
|
||||
assert.deepEqual(hashtagPreferences.getInterests(), ["windowsuccess"]);
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
"load falls back to window.nostr when active signer lacks decrypt capabilities",
|
||||
{ concurrency: false },
|
||||
async (t) => {
|
||||
const pubkey = "abcdef".repeat(10) + "5678";
|
||||
|
||||
// Set a signer that can sign but NOT decrypt (override beforeEach)
|
||||
setActiveSigner({
|
||||
signEvent: async (e) => ({ ...e, id: "fake" }),
|
||||
// No nip04Decrypt or nip44Decrypt
|
||||
});
|
||||
|
||||
// Mock window.nostr
|
||||
window.nostr = {
|
||||
nip04: {
|
||||
decrypt: async (pk, ciphertext) => {
|
||||
if (pk !== pubkey) throw new Error("Wrong pubkey");
|
||||
if (ciphertext === "window-encrypted-2") {
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
interests: ["SignerFallback"],
|
||||
disinterests: []
|
||||
});
|
||||
}
|
||||
throw new Error("Decrypt failed");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Mock pool list to return an event
|
||||
nostrClient.pool = {
|
||||
list: async () => [{
|
||||
id: "evt2",
|
||||
created_at: 1000,
|
||||
pubkey,
|
||||
content: "window-encrypted-2",
|
||||
tags: [["encrypted", "nip04"]]
|
||||
}]
|
||||
};
|
||||
|
||||
relayManager.setEntries([{ url: "wss://relay.fallback", mode: "both" }], { allowEmpty: false, updateClient: false });
|
||||
|
||||
await hashtagPreferences.load(pubkey);
|
||||
|
||||
assert.deepEqual(hashtagPreferences.getInterests(), ["signerfallback"]);
|
||||
}
|
||||
);
|
||||
Reference in new issue
Block a user