mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-28 00:12:25 +00:00
Merge pull request #1011 from PR0M3TH3AN/codex/enhance-encryption-support-for-subscriptions
Prefer NIP-44 v2 for private lists
This commit is contained in:
6 files changed
+944
-109
No files matched your search
@@ -1361,6 +1361,7 @@ export function buildSubscriptionListEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
content,
|
||||
encryption,
|
||||
}) {
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.SUBSCRIPTION_LIST);
|
||||
const tags = [];
|
||||
@@ -1369,6 +1370,9 @@ export function buildSubscriptionListEvent({
|
||||
if (identifierName && identifierValue) {
|
||||
tags.push([identifierName, identifierValue]);
|
||||
}
|
||||
if (typeof encryption === "string" && encryption) {
|
||||
tags.push(["encrypted", encryption]);
|
||||
}
|
||||
appendSchemaTags(tags, schema);
|
||||
return {
|
||||
kind: schema?.kind ?? 30002,
|
||||
@@ -1383,6 +1387,7 @@ export function buildBlockListEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
content,
|
||||
encryption,
|
||||
}) {
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.USER_BLOCK_LIST);
|
||||
const tags = [];
|
||||
@@ -1391,6 +1396,9 @@ export function buildBlockListEvent({
|
||||
if (identifierName && identifierValue) {
|
||||
tags.push([identifierName, identifierValue]);
|
||||
}
|
||||
if (typeof encryption === "string" && encryption) {
|
||||
tags.push(["encrypted", encryption]);
|
||||
}
|
||||
appendSchemaTags(tags, schema);
|
||||
return {
|
||||
kind: schema?.kind ?? 30002,
|
||||
|
||||
@@ -102,6 +102,16 @@ function normalizeTag(input) {
|
||||
return trimmed.toLowerCase();
|
||||
}
|
||||
|
||||
function normalizeEncryptionToken(value) {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/g, "");
|
||||
}
|
||||
|
||||
function extractEncryptionHints(event) {
|
||||
if (!event || typeof event !== "object") {
|
||||
return [];
|
||||
@@ -131,16 +141,18 @@ function extractEncryptionHints(event) {
|
||||
}
|
||||
const parts = rawValue
|
||||
.split(/[\s,]+/)
|
||||
.map((part) => part.trim().toLowerCase())
|
||||
.map((part) => normalizeEncryptionToken(part))
|
||||
.filter(Boolean);
|
||||
for (const part of parts) {
|
||||
if (part.startsWith("nip44")) {
|
||||
if (/^nip44(?:[_-]?v2|v2)/.test(part)) {
|
||||
pushUnique("nip44_v2");
|
||||
} else {
|
||||
pushUnique("nip44");
|
||||
}
|
||||
} else if (part === "nip04" || part === "nip-04") {
|
||||
if (part === "nip44v2" || part === "nip44v02") {
|
||||
pushUnique("nip44_v2");
|
||||
continue;
|
||||
}
|
||||
if (part === "nip44") {
|
||||
pushUnique("nip44");
|
||||
continue;
|
||||
}
|
||||
if (part === "nip04" || part === "nip4") {
|
||||
pushUnique("nip04");
|
||||
}
|
||||
}
|
||||
@@ -171,7 +183,7 @@ function determineDecryptionOrder(event, availableSchemes) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const fallback of ["nip04", "nip44", "nip44_v2"]) {
|
||||
for (const fallback of ["nip44_v2", "nip44", "nip04"]) {
|
||||
if (availableSet.has(fallback) && !prioritized.includes(fallback)) {
|
||||
prioritized.push(fallback);
|
||||
}
|
||||
|
||||
+82
-22
@@ -36,6 +36,16 @@ function normalizeHexPubkey(value) {
|
||||
return trimmed ? trimmed.toLowerCase() : "";
|
||||
}
|
||||
|
||||
function normalizeEncryptionToken(value) {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/g, "");
|
||||
}
|
||||
|
||||
function extractEncryptionHints(event) {
|
||||
if (!event || typeof event !== "object") {
|
||||
return [];
|
||||
@@ -65,16 +75,18 @@ function extractEncryptionHints(event) {
|
||||
}
|
||||
const parts = rawValue
|
||||
.split(/[\s,]+/)
|
||||
.map((part) => part.trim().toLowerCase())
|
||||
.map((part) => normalizeEncryptionToken(part))
|
||||
.filter(Boolean);
|
||||
for (const part of parts) {
|
||||
if (part.startsWith("nip44")) {
|
||||
if (/^nip44(?:[_-]?v2|v2)/.test(part)) {
|
||||
pushUnique("nip44_v2");
|
||||
} else {
|
||||
pushUnique("nip44");
|
||||
}
|
||||
} else if (part === "nip04" || part === "nip-04") {
|
||||
if (part === "nip44v2" || part === "nip44v02") {
|
||||
pushUnique("nip44_v2");
|
||||
continue;
|
||||
}
|
||||
if (part === "nip44") {
|
||||
pushUnique("nip44");
|
||||
continue;
|
||||
}
|
||||
if (part === "nip04" || part === "nip4") {
|
||||
pushUnique("nip04");
|
||||
}
|
||||
}
|
||||
@@ -105,7 +117,7 @@ function determineDecryptionOrder(event, availableSchemes) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const fallback of ["nip04", "nip44", "nip44_v2"]) {
|
||||
for (const fallback of ["nip44_v2", "nip44", "nip04"]) {
|
||||
if (availableSet.has(fallback) && !prioritized.includes(fallback)) {
|
||||
prioritized.push(fallback);
|
||||
}
|
||||
@@ -516,12 +528,6 @@ class SubscriptionsManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof signer.nip04Encrypt !== "function") {
|
||||
const error = new Error("NIP-04 encryption is required to update subscriptions.");
|
||||
error.code = "nip04-missing";
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (typeof signer.signEvent !== "function") {
|
||||
const error = new Error("Active signer missing signEvent support.");
|
||||
error.code = "sign-event-missing";
|
||||
@@ -531,8 +537,33 @@ class SubscriptionsManager {
|
||||
const plainObj = { subPubkeys: Array.from(this.subscribedPubkeys) };
|
||||
const plainStr = JSON.stringify(plainObj);
|
||||
|
||||
const encryptors = [];
|
||||
const registerEncryptor = (scheme, handler) => {
|
||||
if (!scheme || typeof handler !== "function") {
|
||||
return;
|
||||
}
|
||||
encryptors.push({ scheme, handler });
|
||||
};
|
||||
|
||||
if (typeof signer.nip44Encrypt === "function") {
|
||||
registerEncryptor("nip44_v2", (value) => signer.nip44Encrypt(userPubkey, value));
|
||||
registerEncryptor("nip44", (value) => signer.nip44Encrypt(userPubkey, value));
|
||||
}
|
||||
|
||||
if (typeof signer.nip04Encrypt === "function") {
|
||||
registerEncryptor("nip04", (value) => signer.nip04Encrypt(userPubkey, value));
|
||||
}
|
||||
|
||||
if (!encryptors.length) {
|
||||
const error = new Error(
|
||||
"An encryption-capable signer is required to update subscriptions.",
|
||||
);
|
||||
error.code = "subscriptions-missing-encryptor";
|
||||
throw error;
|
||||
}
|
||||
|
||||
/*
|
||||
* The subscription list is stored as a NIP-04 message to self, so both
|
||||
* The subscription list is stored as an encrypted message to self, so both
|
||||
* encryption and decryption intentionally use the user's own pubkey.
|
||||
* Extensions are expected to support this encrypt-to-self flow; altering
|
||||
* the target would break loadSubscriptions, which decrypts with the same
|
||||
@@ -540,17 +571,46 @@ class SubscriptionsManager {
|
||||
* need a parallel read path and should not overwrite this behavior.
|
||||
*/
|
||||
let cipherText = "";
|
||||
try {
|
||||
cipherText = await signer.nip04Encrypt(userPubkey, plainStr);
|
||||
} catch (err) {
|
||||
userLogger.error("Encryption failed:", err);
|
||||
throw err;
|
||||
let encryptionScheme = "";
|
||||
const seenSchemes = new Set();
|
||||
const encryptionErrors = [];
|
||||
|
||||
for (const candidate of encryptors) {
|
||||
if (seenSchemes.has(candidate.scheme)) {
|
||||
continue;
|
||||
}
|
||||
seenSchemes.add(candidate.scheme);
|
||||
try {
|
||||
const encrypted = await candidate.handler(plainStr);
|
||||
if (typeof encrypted === "string" && encrypted) {
|
||||
cipherText = encrypted;
|
||||
encryptionScheme = candidate.scheme;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
encryptionErrors.push({ scheme: candidate.scheme, error });
|
||||
}
|
||||
}
|
||||
|
||||
if (!cipherText) {
|
||||
const error = new Error("Failed to encrypt subscription list payload.");
|
||||
error.code = "subscriptions-encrypt-failed";
|
||||
error.cause = encryptionErrors;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const encryptionTagValue =
|
||||
encryptionScheme === "nip44_v2"
|
||||
? "nip44_v2"
|
||||
: encryptionScheme === "nip44"
|
||||
? "nip44"
|
||||
: undefined;
|
||||
|
||||
const evt = buildSubscriptionListEvent({
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: cipherText
|
||||
content: cipherText,
|
||||
encryption: encryptionTagValue,
|
||||
});
|
||||
|
||||
let signedEvent;
|
||||
|
||||
+336
-78
@@ -68,6 +68,97 @@ const BLOCKLIST_STORAGE_PREFIX = "bitvid:user-blocks";
|
||||
const BLOCKLIST_SEEDED_KEY_PREFIX = `${BLOCKLIST_STORAGE_PREFIX}:seeded:v1`;
|
||||
const BLOCKLIST_REMOVALS_KEY_PREFIX = `${BLOCKLIST_STORAGE_PREFIX}:removals:v1`;
|
||||
|
||||
function normalizeEncryptionToken(value) {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/g, "");
|
||||
}
|
||||
|
||||
function extractEncryptionHints(event) {
|
||||
if (!event || typeof event !== "object") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tags = Array.isArray(event.tags) ? event.tags : [];
|
||||
const hints = [];
|
||||
|
||||
const pushUnique = (scheme) => {
|
||||
if (!scheme || hints.includes(scheme)) {
|
||||
return;
|
||||
}
|
||||
hints.push(scheme);
|
||||
};
|
||||
|
||||
for (const tag of tags) {
|
||||
if (!Array.isArray(tag) || tag.length < 2) {
|
||||
continue;
|
||||
}
|
||||
const name = typeof tag[0] === "string" ? tag[0].trim().toLowerCase() : "";
|
||||
if (name !== "encrypted" && name !== "encryption") {
|
||||
continue;
|
||||
}
|
||||
const rawValue = typeof tag[1] === "string" ? tag[1] : "";
|
||||
if (!rawValue) {
|
||||
continue;
|
||||
}
|
||||
const parts = rawValue
|
||||
.split(/[\s,]+/)
|
||||
.map((part) => normalizeEncryptionToken(part))
|
||||
.filter(Boolean);
|
||||
|
||||
for (const part of parts) {
|
||||
if (part === "nip44v2" || part === "nip44v02") {
|
||||
pushUnique("nip44_v2");
|
||||
continue;
|
||||
}
|
||||
if (part === "nip44") {
|
||||
pushUnique("nip44");
|
||||
continue;
|
||||
}
|
||||
if (part === "nip04" || part === "nip4") {
|
||||
pushUnique("nip04");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hints;
|
||||
}
|
||||
|
||||
function determineDecryptionOrder(event, availableSchemes) {
|
||||
const available = Array.isArray(availableSchemes) ? availableSchemes : [];
|
||||
const availableSet = new Set(available);
|
||||
const prioritized = [];
|
||||
|
||||
const hints = extractEncryptionHints(event);
|
||||
const aliasMap = {
|
||||
nip04: ["nip04"],
|
||||
nip44: ["nip44", "nip44_v2"],
|
||||
nip44_v2: ["nip44_v2", "nip44"],
|
||||
};
|
||||
|
||||
for (const hint of hints) {
|
||||
const candidates = Array.isArray(aliasMap[hint]) ? aliasMap[hint] : [hint];
|
||||
for (const candidate of candidates) {
|
||||
if (availableSet.has(candidate) && !prioritized.includes(candidate)) {
|
||||
prioritized.push(candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const fallback of ["nip44_v2", "nip44", "nip04"]) {
|
||||
if (availableSet.has(fallback) && !prioritized.includes(fallback)) {
|
||||
prioritized.push(fallback);
|
||||
}
|
||||
}
|
||||
|
||||
return prioritized.length ? prioritized : available;
|
||||
}
|
||||
|
||||
function decodeNpubToHex(npub) {
|
||||
if (typeof npub !== "string") {
|
||||
return null;
|
||||
@@ -343,56 +434,109 @@ class UserBlockListManager {
|
||||
});
|
||||
};
|
||||
|
||||
const activeSigner = getActiveSigner();
|
||||
const signerDecryptor =
|
||||
activeSigner && typeof activeSigner.nip04Decrypt === "function"
|
||||
? activeSigner.nip04Decrypt
|
||||
: null;
|
||||
const resolveDecryptors = async (event) => {
|
||||
const signer = getActiveSigner();
|
||||
const signerHasNip04 = typeof signer?.nip04Decrypt === "function";
|
||||
const signerHasNip44 = typeof signer?.nip44Decrypt === "function";
|
||||
|
||||
let extensionDecryptor =
|
||||
typeof window?.nostr?.nip04?.decrypt === "function"
|
||||
? (pubkey, payload) => window.nostr.nip04.decrypt(pubkey, payload)
|
||||
: null;
|
||||
const hints = extractEncryptionHints(event);
|
||||
const requiresNip44 = hints.includes("nip44") || hints.includes("nip44_v2");
|
||||
const requiresNip04 = !hints.length || hints.includes("nip04");
|
||||
|
||||
if (!signerDecryptor && !extensionDecryptor) {
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[UserBlockList] Unable to load block list via extension decryptor; permissions denied.",
|
||||
permissionResult.error,
|
||||
);
|
||||
this.reset();
|
||||
this.loaded = true;
|
||||
const error =
|
||||
permissionResult.error instanceof Error
|
||||
? permissionResult.error
|
||||
: new Error(
|
||||
"Extension permissions are required to use the browser decryptor.",
|
||||
);
|
||||
emitStatus({ status: "error", error, decryptor: "extension" });
|
||||
emitStatus({ status: "settled" });
|
||||
return;
|
||||
let permissionError = null;
|
||||
if ((!signerHasNip44 && requiresNip44) || (!signerHasNip04 && requiresNip04)) {
|
||||
try {
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
if (!permissionResult?.ok) {
|
||||
permissionError =
|
||||
permissionResult?.error instanceof Error
|
||||
? permissionResult.error
|
||||
: new Error(
|
||||
"Extension permissions are required to use the browser decryptor.",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
permissionError = error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
}
|
||||
|
||||
extensionDecryptor =
|
||||
typeof window?.nostr?.nip04?.decrypt === "function"
|
||||
? (pubkey, payload) => window.nostr.nip04.decrypt(pubkey, payload)
|
||||
: null;
|
||||
}
|
||||
const decryptors = new Map();
|
||||
const sources = new Map();
|
||||
const registerDecryptor = (scheme, handler, source) => {
|
||||
if (!scheme || typeof handler !== "function" || decryptors.has(scheme)) {
|
||||
return;
|
||||
}
|
||||
decryptors.set(scheme, handler);
|
||||
sources.set(scheme, source || "unknown");
|
||||
};
|
||||
|
||||
if (!signerDecryptor && !extensionDecryptor) {
|
||||
userLogger.warn(
|
||||
"[UserBlockList] No nip04 decryptor available; treating block list as empty.",
|
||||
);
|
||||
this.reset();
|
||||
this.loaded = true;
|
||||
const error = new Error(
|
||||
"No NIP-04 decryptor is available to load the block list.",
|
||||
);
|
||||
emitStatus({ status: "error", error, decryptor: "unavailable" });
|
||||
emitStatus({ status: "settled" });
|
||||
return;
|
||||
}
|
||||
if (signerHasNip44) {
|
||||
registerDecryptor(
|
||||
"nip44",
|
||||
(payload) => signer.nip44Decrypt(normalized, payload),
|
||||
"active-signer",
|
||||
);
|
||||
registerDecryptor(
|
||||
"nip44_v2",
|
||||
(payload) => signer.nip44Decrypt(normalized, payload),
|
||||
"active-signer",
|
||||
);
|
||||
}
|
||||
|
||||
if (signerHasNip04) {
|
||||
registerDecryptor(
|
||||
"nip04",
|
||||
(payload) => signer.nip04Decrypt(normalized, 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(normalized, 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(normalized, 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(normalized, payload),
|
||||
"extension",
|
||||
);
|
||||
if (!decryptors.has("nip44")) {
|
||||
registerDecryptor(
|
||||
"nip44",
|
||||
(payload) => nip44v2.decrypt(normalized, payload),
|
||||
"extension",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
decryptors,
|
||||
sources,
|
||||
permissionError,
|
||||
order: determineDecryptionOrder(event, Array.from(decryptors.keys())),
|
||||
};
|
||||
};
|
||||
|
||||
try {
|
||||
const filter = {
|
||||
@@ -573,36 +717,92 @@ class UserBlockListManager {
|
||||
return;
|
||||
}
|
||||
|
||||
let decrypted = "";
|
||||
const decryptPath = signerDecryptor
|
||||
? "active-signer"
|
||||
: extensionDecryptor
|
||||
? "extension"
|
||||
: "unavailable";
|
||||
try {
|
||||
if (signerDecryptor) {
|
||||
decrypted = await signerDecryptor(normalized, newest.content);
|
||||
} else if (extensionDecryptor) {
|
||||
decrypted = await extensionDecryptor(normalized, newest.content);
|
||||
} else {
|
||||
throw new Error("nip04-unavailable");
|
||||
const {
|
||||
decryptors,
|
||||
order,
|
||||
sources,
|
||||
permissionError,
|
||||
} = await resolveDecryptors(newest);
|
||||
|
||||
if (!decryptors.size) {
|
||||
if (permissionError) {
|
||||
userLogger.warn(
|
||||
"[UserBlockList] Unable to load block list via extension decryptor; permissions denied.",
|
||||
permissionError,
|
||||
);
|
||||
this.reset();
|
||||
this.loaded = true;
|
||||
emitStatus({ status: "error", error: permissionError, decryptor: "extension" });
|
||||
emitStatus({ status: "settled" });
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
userLogger.warn(
|
||||
"[UserBlockList] No decryptors available; treating block list as empty.",
|
||||
);
|
||||
this.reset();
|
||||
this.loaded = true;
|
||||
const error = new Error(
|
||||
"No decryptors are available to load the block list.",
|
||||
);
|
||||
emitStatus({ status: "error", error, decryptor: "unavailable" });
|
||||
emitStatus({ status: "settled" });
|
||||
return;
|
||||
}
|
||||
|
||||
let decrypted = "";
|
||||
let schemeUsed = "";
|
||||
let schemeSource = "unavailable";
|
||||
const attemptErrors = [];
|
||||
|
||||
for (const scheme of order) {
|
||||
const handler = decryptors.get(scheme);
|
||||
if (typeof handler !== "function") {
|
||||
continue;
|
||||
}
|
||||
const sourceLabel = sources.get(scheme) || "unknown";
|
||||
try {
|
||||
const plaintext = await handler(newest.content);
|
||||
if (typeof plaintext !== "string") {
|
||||
throw new Error("Decryption returned a non-string payload.");
|
||||
}
|
||||
decrypted = plaintext;
|
||||
schemeUsed = scheme;
|
||||
schemeSource = sourceLabel;
|
||||
break;
|
||||
} catch (error) {
|
||||
attemptErrors.push({ scheme, source: sourceLabel, error });
|
||||
}
|
||||
}
|
||||
|
||||
if (!decrypted) {
|
||||
const error = new Error(
|
||||
"Failed to decrypt block list with available schemes.",
|
||||
);
|
||||
error.code = "block-list-decrypt-failed";
|
||||
if (attemptErrors.length) {
|
||||
error.cause = attemptErrors;
|
||||
}
|
||||
const lastAttempt = attemptErrors.length
|
||||
? attemptErrors[attemptErrors.length - 1]
|
||||
: null;
|
||||
const decryptPath = lastAttempt?.source || "unavailable";
|
||||
userLogger.error(
|
||||
`[UserBlockList] Failed to decrypt block list via ${decryptPath}:`,
|
||||
err,
|
||||
`[UserBlockList] Failed to decrypt block list via available schemes (last: ${decryptPath}).`,
|
||||
error,
|
||||
);
|
||||
applyBlockedPubkeys([], {
|
||||
source,
|
||||
reason: "decrypt-error",
|
||||
event: newest,
|
||||
error: err,
|
||||
error,
|
||||
decryptor: decryptPath,
|
||||
errors: attemptErrors,
|
||||
});
|
||||
emitStatus({
|
||||
status: "error",
|
||||
event: newest,
|
||||
error: err,
|
||||
error,
|
||||
source,
|
||||
decryptor: decryptPath,
|
||||
});
|
||||
@@ -629,12 +829,16 @@ class UserBlockListManager {
|
||||
source,
|
||||
reason: "applied",
|
||||
event: newest,
|
||||
scheme: schemeUsed,
|
||||
decryptor: schemeSource,
|
||||
});
|
||||
emitStatus({
|
||||
status: "applied",
|
||||
event: newest,
|
||||
blockedPubkeys: Array.from(this.blockedPubkeys),
|
||||
source,
|
||||
scheme: schemeUsed,
|
||||
decryptor: schemeSource,
|
||||
});
|
||||
} catch (err) {
|
||||
userLogger.error("[UserBlockList] Failed to parse block list:", err);
|
||||
@@ -643,8 +847,17 @@ class UserBlockListManager {
|
||||
reason: "parse-error",
|
||||
event: newest,
|
||||
error: err,
|
||||
decryptor: schemeSource,
|
||||
scheme: schemeUsed,
|
||||
});
|
||||
emitStatus({
|
||||
status: "error",
|
||||
event: newest,
|
||||
error: err,
|
||||
source,
|
||||
decryptor: schemeSource,
|
||||
scheme: schemeUsed,
|
||||
});
|
||||
emitStatus({ status: "error", event: newest, error: err, source });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1107,14 +1320,6 @@ class UserBlockListManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof signer.nip04Encrypt !== "function") {
|
||||
const err = new Error(
|
||||
"NIP-04 encryption is required to update the block list."
|
||||
);
|
||||
err.code = "nip04-missing";
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (typeof signer.signEvent !== "function") {
|
||||
const err = new Error("Active signer missing signEvent support.");
|
||||
err.code = "sign-event-missing";
|
||||
@@ -1133,19 +1338,72 @@ class UserBlockListManager {
|
||||
};
|
||||
const plaintext = JSON.stringify(payload);
|
||||
|
||||
let cipherText = "";
|
||||
try {
|
||||
cipherText = await signer.nip04Encrypt(normalized, plaintext);
|
||||
} catch (error) {
|
||||
const err = new Error("Failed to encrypt block list.");
|
||||
err.code = "nip04-missing";
|
||||
const encryptors = [];
|
||||
const registerEncryptor = (scheme, handler) => {
|
||||
if (!scheme || typeof handler !== "function") {
|
||||
return;
|
||||
}
|
||||
encryptors.push({ scheme, handler });
|
||||
};
|
||||
|
||||
if (typeof signer.nip44Encrypt === "function") {
|
||||
registerEncryptor("nip44_v2", (value) => signer.nip44Encrypt(normalized, value));
|
||||
registerEncryptor("nip44", (value) => signer.nip44Encrypt(normalized, value));
|
||||
}
|
||||
|
||||
if (typeof signer.nip04Encrypt === "function") {
|
||||
registerEncryptor("nip04", (value) => signer.nip04Encrypt(normalized, value));
|
||||
}
|
||||
|
||||
if (!encryptors.length) {
|
||||
const err = new Error(
|
||||
"An encryption-capable signer is required to update the block list."
|
||||
);
|
||||
err.code = "block-list-missing-encryptor";
|
||||
throw err;
|
||||
}
|
||||
|
||||
let cipherText = "";
|
||||
let encryptionScheme = "";
|
||||
const seenSchemes = new Set();
|
||||
const encryptionErrors = [];
|
||||
|
||||
for (const candidate of encryptors) {
|
||||
if (seenSchemes.has(candidate.scheme)) {
|
||||
continue;
|
||||
}
|
||||
seenSchemes.add(candidate.scheme);
|
||||
try {
|
||||
const encrypted = await candidate.handler(plaintext);
|
||||
if (typeof encrypted === "string" && encrypted) {
|
||||
cipherText = encrypted;
|
||||
encryptionScheme = candidate.scheme;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
encryptionErrors.push({ scheme: candidate.scheme, error });
|
||||
}
|
||||
}
|
||||
|
||||
if (!cipherText) {
|
||||
const err = new Error("Failed to encrypt block list.");
|
||||
err.code = "block-list-encrypt-failed";
|
||||
err.cause = encryptionErrors;
|
||||
throw err;
|
||||
}
|
||||
|
||||
const encryptionTagValue =
|
||||
encryptionScheme === "nip44_v2"
|
||||
? "nip44_v2"
|
||||
: encryptionScheme === "nip44"
|
||||
? "nip44"
|
||||
: undefined;
|
||||
|
||||
const event = buildBlockListEvent({
|
||||
pubkey: normalized,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: cipherText,
|
||||
encryption: encryptionTagValue,
|
||||
});
|
||||
|
||||
const signedEvent = await signer.signEvent(event);
|
||||
|
||||
@@ -311,6 +311,88 @@ test("loadSubscriptions handles nip44.v2 decryptors", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("loadSubscriptions prefers nip44 decryptors when both are available", async () => {
|
||||
const SubscriptionsManager = subscriptions.constructor;
|
||||
const manager = new SubscriptionsManager();
|
||||
|
||||
const originalRelays = Array.isArray(nostrClient.relays)
|
||||
? [...nostrClient.relays]
|
||||
: nostrClient.relays;
|
||||
const originalWriteRelays = Array.isArray(nostrClient.writeRelays)
|
||||
? [...nostrClient.writeRelays]
|
||||
: nostrClient.writeRelays;
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalEnsurePermissions = nostrClient.ensureExtensionPermissions;
|
||||
|
||||
const hadWindow = typeof globalThis.window !== "undefined";
|
||||
if (!hadWindow) {
|
||||
globalThis.window = {};
|
||||
}
|
||||
const originalWindowNostr = globalThis.window.nostr;
|
||||
|
||||
const relayUrls = ["wss://relay-nip44-pref.example"];
|
||||
nostrClient.relays = relayUrls;
|
||||
nostrClient.writeRelays = relayUrls;
|
||||
|
||||
const event = {
|
||||
id: "event-nip44-pref",
|
||||
created_at: 700,
|
||||
content: "cipher-nip44-pref",
|
||||
tags: [["encrypted", "nip44_v2"]],
|
||||
};
|
||||
|
||||
nostrClient.pool = {
|
||||
list() {
|
||||
return Promise.resolve([event]);
|
||||
},
|
||||
};
|
||||
|
||||
nostrClient.ensureExtensionPermissions = async () => ({ ok: true });
|
||||
|
||||
const decryptCalls = { nip04: 0, nip44: 0 };
|
||||
globalThis.window.nostr = {
|
||||
nip04: {
|
||||
async decrypt() {
|
||||
decryptCalls.nip04 += 1;
|
||||
return JSON.stringify({ subPubkeys: ["pub-nip04"] });
|
||||
},
|
||||
},
|
||||
nip44: {
|
||||
v2: {
|
||||
async decrypt(_pubkey, ciphertext) {
|
||||
decryptCalls.nip44 += 1;
|
||||
return JSON.stringify({ subPubkeys: ["pub-nip44-pref"], hint: ciphertext });
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await manager.loadSubscriptions("user-pubkey-123");
|
||||
|
||||
assert.deepEqual(
|
||||
Array.from(manager.subscribedPubkeys),
|
||||
["pub-nip44-pref"],
|
||||
"nip44 decrypted subscriptions should populate the set",
|
||||
);
|
||||
assert.equal(decryptCalls.nip44, 1, "nip44 decryptor should be used");
|
||||
assert.equal(decryptCalls.nip04, 0, "nip04 decryptor should be skipped");
|
||||
} finally {
|
||||
nostrClient.relays = originalRelays;
|
||||
nostrClient.writeRelays = originalWriteRelays;
|
||||
nostrClient.pool = originalPool;
|
||||
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
|
||||
if (typeof originalWindowNostr === "undefined") {
|
||||
delete globalThis.window.nostr;
|
||||
} else {
|
||||
globalThis.window.nostr = originalWindowNostr;
|
||||
}
|
||||
if (!hadWindow) {
|
||||
delete globalThis.window;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
"loadSubscriptions uses active signer decryptors without requesting extension permissions",
|
||||
async () => {
|
||||
@@ -651,6 +733,183 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test("publishSubscriptionList prefers nip44 encryption when available", async () => {
|
||||
const SubscriptionsManager = subscriptions.constructor;
|
||||
const manager = new SubscriptionsManager();
|
||||
|
||||
const originalRelays = Array.isArray(nostrClient.relays)
|
||||
? [...nostrClient.relays]
|
||||
: nostrClient.relays;
|
||||
const originalWriteRelays = Array.isArray(nostrClient.writeRelays)
|
||||
? [...nostrClient.writeRelays]
|
||||
: nostrClient.writeRelays;
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalEnsurePermissions = nostrClient.ensureExtensionPermissions;
|
||||
const originalSigner = getActiveSigner();
|
||||
|
||||
const relayUrls = ["wss://relay-prefer-nip44.example"];
|
||||
nostrClient.relays = relayUrls;
|
||||
nostrClient.writeRelays = relayUrls;
|
||||
|
||||
let permissionCalls = 0;
|
||||
nostrClient.ensureExtensionPermissions = async () => {
|
||||
permissionCalls += 1;
|
||||
return { ok: true };
|
||||
};
|
||||
|
||||
const publishCalls = [];
|
||||
nostrClient.pool = {
|
||||
publish(urls, event) {
|
||||
publishCalls.push({ urls, event });
|
||||
return {
|
||||
on(eventName, handler) {
|
||||
if (eventName === "ok") {
|
||||
handler();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const encryptCalls = { nip44: 0, nip04: 0 };
|
||||
const signedEvents = [];
|
||||
setActiveSigner({
|
||||
type: "nsec",
|
||||
pubkey: "user-pubkey-123",
|
||||
async nip44Encrypt(pubkey, plaintext) {
|
||||
encryptCalls.nip44 += 1;
|
||||
assert.equal(pubkey, "user-pubkey-123");
|
||||
assert.equal(
|
||||
plaintext,
|
||||
JSON.stringify({ subPubkeys: ["pub-one", "pub-two"] }),
|
||||
);
|
||||
return "cipher-nip44";
|
||||
},
|
||||
async nip04Encrypt() {
|
||||
encryptCalls.nip04 += 1;
|
||||
throw new Error("nip04 should not be used when nip44 is available");
|
||||
},
|
||||
async signEvent(event) {
|
||||
signedEvents.push(event);
|
||||
return { ...event, id: "signed-nip44-event" };
|
||||
},
|
||||
});
|
||||
|
||||
manager.subscribedPubkeys = new Set(["pub-one", "pub-two"]);
|
||||
|
||||
try {
|
||||
await manager.publishSubscriptionList("user-pubkey-123");
|
||||
|
||||
assert.equal(permissionCalls, 0, "should not request extension permissions");
|
||||
assert.equal(encryptCalls.nip44, 1, "nip44Encrypt should be used once");
|
||||
assert.equal(encryptCalls.nip04, 0, "nip04Encrypt should not be invoked");
|
||||
assert.equal(signedEvents.length, 1, "signEvent should be called once");
|
||||
const signedEvent = signedEvents[0];
|
||||
const encryptedTags = signedEvent.tags.filter((tag) => tag[0] === "encrypted");
|
||||
assert.deepEqual(
|
||||
encryptedTags,
|
||||
[["encrypted", "nip44_v2"]],
|
||||
"signed event should advertise nip44_v2 encryption",
|
||||
);
|
||||
assert.equal(publishCalls.length, relayUrls.length, "event should publish to relays");
|
||||
assert.equal(manager.subsEventId, "signed-nip44-event");
|
||||
} finally {
|
||||
nostrClient.relays = originalRelays;
|
||||
nostrClient.writeRelays = originalWriteRelays;
|
||||
nostrClient.pool = originalPool;
|
||||
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
|
||||
setActiveSigner(originalSigner);
|
||||
}
|
||||
});
|
||||
|
||||
test("publishSubscriptionList falls back to nip04 when nip44 fails", async () => {
|
||||
const SubscriptionsManager = subscriptions.constructor;
|
||||
const manager = new SubscriptionsManager();
|
||||
|
||||
const originalRelays = Array.isArray(nostrClient.relays)
|
||||
? [...nostrClient.relays]
|
||||
: nostrClient.relays;
|
||||
const originalWriteRelays = Array.isArray(nostrClient.writeRelays)
|
||||
? [...nostrClient.writeRelays]
|
||||
: nostrClient.writeRelays;
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalEnsurePermissions = nostrClient.ensureExtensionPermissions;
|
||||
const originalSigner = getActiveSigner();
|
||||
|
||||
const relayUrls = ["wss://relay-fallback-nip04.example"];
|
||||
nostrClient.relays = relayUrls;
|
||||
nostrClient.writeRelays = relayUrls;
|
||||
|
||||
nostrClient.ensureExtensionPermissions = async () => ({ ok: true });
|
||||
|
||||
const publishCalls = [];
|
||||
nostrClient.pool = {
|
||||
publish(urls, event) {
|
||||
publishCalls.push({ urls, event });
|
||||
return {
|
||||
on(eventName, handler) {
|
||||
if (eventName === "ok") {
|
||||
handler();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const encryptCalls = { nip44: 0, nip04: 0 };
|
||||
const signedEvents = [];
|
||||
setActiveSigner({
|
||||
type: "nsec",
|
||||
pubkey: "user-pubkey-123",
|
||||
async nip44Encrypt() {
|
||||
encryptCalls.nip44 += 1;
|
||||
throw new Error("simulated nip44 failure");
|
||||
},
|
||||
async nip04Encrypt(pubkey, plaintext) {
|
||||
encryptCalls.nip04 += 1;
|
||||
assert.equal(pubkey, "user-pubkey-123");
|
||||
assert.equal(
|
||||
plaintext,
|
||||
JSON.stringify({ subPubkeys: ["pub-three"] }),
|
||||
);
|
||||
return "cipher-nip04";
|
||||
},
|
||||
async signEvent(event) {
|
||||
signedEvents.push(event);
|
||||
return { ...event, id: "signed-nip04-event" };
|
||||
},
|
||||
});
|
||||
|
||||
manager.subscribedPubkeys = new Set(["pub-three"]);
|
||||
|
||||
try {
|
||||
await manager.publishSubscriptionList("user-pubkey-123");
|
||||
|
||||
assert.ok(
|
||||
encryptCalls.nip44 >= 1,
|
||||
"nip44Encrypt should be attempted before falling back",
|
||||
);
|
||||
assert.equal(encryptCalls.nip04, 1, "nip04Encrypt should handle fallback");
|
||||
const signedEvent = signedEvents[0];
|
||||
const encryptedTags = signedEvent.tags.filter((tag) => tag[0] === "encrypted");
|
||||
assert.equal(
|
||||
encryptedTags.length,
|
||||
0,
|
||||
"fallback nip04 encryption should not advertise a nip44 tag",
|
||||
);
|
||||
assert.equal(publishCalls.length, relayUrls.length, "event should publish to relays");
|
||||
assert.equal(manager.subsEventId, "signed-nip04-event");
|
||||
} finally {
|
||||
nostrClient.relays = originalRelays;
|
||||
nostrClient.writeRelays = originalWriteRelays;
|
||||
nostrClient.pool = originalPool;
|
||||
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
|
||||
setActiveSigner(originalSigner);
|
||||
}
|
||||
});
|
||||
|
||||
test("renderSameGridStyle shows empty state message", async () => {
|
||||
const SubscriptionsManager = subscriptions.constructor;
|
||||
|
||||
|
||||
@@ -633,3 +633,241 @@ await (async () => {
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
const actor = "1".repeat(64);
|
||||
const blocked = "2".repeat(64);
|
||||
|
||||
const UserBlockListManager = userBlocks.constructor;
|
||||
const manager = new UserBlockListManager();
|
||||
|
||||
const originalRelays = Array.isArray(nostrClient.relays)
|
||||
? [...nostrClient.relays]
|
||||
: nostrClient.relays;
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalEnsurePermissions = nostrClient.ensureExtensionPermissions;
|
||||
const originalSigner = getActiveSigner();
|
||||
|
||||
const relayUrls = ["wss://relay-nip44-blocklist.example"];
|
||||
nostrClient.relays = relayUrls;
|
||||
nostrClient.ensureExtensionPermissions = async () => ({ ok: true });
|
||||
|
||||
const publishCalls = [];
|
||||
nostrClient.pool = {
|
||||
publish(urls, event) {
|
||||
publishCalls.push({ urls, event });
|
||||
return {
|
||||
on(eventName, handler) {
|
||||
if (eventName === "ok") {
|
||||
handler();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const encryptCalls = { nip44: 0, nip04: 0 };
|
||||
const signedEvents = [];
|
||||
setActiveSigner({
|
||||
type: "nsec",
|
||||
pubkey: actor,
|
||||
async nip44Encrypt(pubkey, plaintext) {
|
||||
encryptCalls.nip44 += 1;
|
||||
assert.equal(pubkey, actor);
|
||||
assert.equal(
|
||||
plaintext,
|
||||
JSON.stringify({ blockedPubkeys: [blocked] }),
|
||||
);
|
||||
return "cipher-nip44";
|
||||
},
|
||||
async nip04Encrypt() {
|
||||
encryptCalls.nip04 += 1;
|
||||
throw new Error("nip04 should not be used when nip44 is available");
|
||||
},
|
||||
async signEvent(event) {
|
||||
signedEvents.push(event);
|
||||
return { ...event, id: "signed-nip44-block-event" };
|
||||
},
|
||||
});
|
||||
|
||||
manager.blockedPubkeys = new Set([blocked]);
|
||||
manager.publishMuteListSnapshot = async () => null;
|
||||
manager.loadBlocks = async () => {};
|
||||
|
||||
try {
|
||||
const result = await manager.publishBlockList(actor);
|
||||
|
||||
assert.equal(result.id, "signed-nip44-block-event");
|
||||
assert.equal(encryptCalls.nip44, 1, "nip44Encrypt should be used once");
|
||||
assert.equal(encryptCalls.nip04, 0, "nip04Encrypt should not be invoked");
|
||||
assert.equal(signedEvents.length, 1, "signEvent should be called once");
|
||||
const encryptedTags = signedEvents[0].tags.filter((tag) => tag[0] === "encrypted");
|
||||
assert.deepEqual(
|
||||
encryptedTags,
|
||||
[["encrypted", "nip44_v2"]],
|
||||
"block list event should advertise nip44_v2 encryption",
|
||||
);
|
||||
assert.equal(publishCalls.length, relayUrls.length, "event should publish to relays");
|
||||
} finally {
|
||||
nostrClient.relays = originalRelays;
|
||||
nostrClient.pool = originalPool;
|
||||
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
|
||||
setActiveSigner(originalSigner);
|
||||
}
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
const actor = "3".repeat(64);
|
||||
const blocked = "4".repeat(64);
|
||||
|
||||
const UserBlockListManager = userBlocks.constructor;
|
||||
const manager = new UserBlockListManager();
|
||||
|
||||
const originalRelays = Array.isArray(nostrClient.relays)
|
||||
? [...nostrClient.relays]
|
||||
: nostrClient.relays;
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalEnsurePermissions = nostrClient.ensureExtensionPermissions;
|
||||
const originalSigner = getActiveSigner();
|
||||
|
||||
nostrClient.relays = ["wss://relay-nip04-fallback.example"];
|
||||
nostrClient.ensureExtensionPermissions = async () => ({ ok: true });
|
||||
|
||||
const publishCalls = [];
|
||||
nostrClient.pool = {
|
||||
publish(urls, event) {
|
||||
publishCalls.push({ urls, event });
|
||||
return {
|
||||
on(eventName, handler) {
|
||||
if (eventName === "ok") {
|
||||
handler();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const encryptCalls = { nip44: 0, nip04: 0 };
|
||||
const signedEvents = [];
|
||||
setActiveSigner({
|
||||
type: "nsec",
|
||||
pubkey: actor,
|
||||
async nip44Encrypt() {
|
||||
encryptCalls.nip44 += 1;
|
||||
throw new Error("simulated nip44 failure");
|
||||
},
|
||||
async nip04Encrypt(pubkey, plaintext) {
|
||||
encryptCalls.nip04 += 1;
|
||||
assert.equal(pubkey, actor);
|
||||
assert.equal(
|
||||
plaintext,
|
||||
JSON.stringify({ blockedPubkeys: [blocked] }),
|
||||
);
|
||||
return "cipher-nip04";
|
||||
},
|
||||
async signEvent(event) {
|
||||
signedEvents.push(event);
|
||||
return { ...event, id: "signed-nip04-block-event" };
|
||||
},
|
||||
});
|
||||
|
||||
manager.blockedPubkeys = new Set([blocked]);
|
||||
manager.publishMuteListSnapshot = async () => null;
|
||||
manager.loadBlocks = async () => {};
|
||||
|
||||
try {
|
||||
const result = await manager.publishBlockList(actor);
|
||||
|
||||
assert.equal(result.id, "signed-nip04-block-event");
|
||||
assert.ok(
|
||||
encryptCalls.nip44 >= 1,
|
||||
"nip44Encrypt should be attempted before falling back",
|
||||
);
|
||||
assert.equal(encryptCalls.nip04, 1, "nip04Encrypt should handle fallback");
|
||||
const encryptedTags = signedEvents[0].tags.filter((tag) => tag[0] === "encrypted");
|
||||
assert.equal(
|
||||
encryptedTags.length,
|
||||
0,
|
||||
"fallback nip04 encryption should not advertise a nip44 tag",
|
||||
);
|
||||
assert.equal(publishCalls.length, 1, "event should publish to the available relay");
|
||||
} finally {
|
||||
nostrClient.relays = originalRelays;
|
||||
nostrClient.pool = originalPool;
|
||||
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
|
||||
setActiveSigner(originalSigner);
|
||||
}
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
const actor = "5".repeat(64);
|
||||
const blocked = "6".repeat(64);
|
||||
|
||||
const UserBlockListManager = userBlocks.constructor;
|
||||
const manager = new UserBlockListManager();
|
||||
|
||||
const originalNostr = window.nostr;
|
||||
const originalRelays = Array.isArray(nostrClient.relays)
|
||||
? [...nostrClient.relays]
|
||||
: nostrClient.relays;
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalEnsurePermissions = nostrClient.ensureExtensionPermissions;
|
||||
const originalSigner = getActiveSigner();
|
||||
|
||||
clearActiveSigner();
|
||||
|
||||
const relayUrls = ["wss://relay-nip44-read.example"];
|
||||
nostrClient.relays = relayUrls;
|
||||
nostrClient.ensureExtensionPermissions = async () => ({ ok: true });
|
||||
|
||||
const decryptCalls = { nip44: 0, nip04: 0 };
|
||||
window.nostr = {
|
||||
nip04: {
|
||||
async decrypt() {
|
||||
decryptCalls.nip04 += 1;
|
||||
return JSON.stringify({ blockedPubkeys: [blocked] });
|
||||
},
|
||||
},
|
||||
nip44: {
|
||||
v2: {
|
||||
async decrypt(_pubkey, ciphertext) {
|
||||
decryptCalls.nip44 += 1;
|
||||
return JSON.stringify({ blockedPubkeys: [blocked], hint: ciphertext });
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
nostrClient.pool = {
|
||||
list() {
|
||||
return Promise.resolve([
|
||||
{
|
||||
id: "event-nip44-read",
|
||||
created_at: 9_000,
|
||||
pubkey: actor,
|
||||
content: "cipher-nip44-read",
|
||||
tags: [["encrypted", "nip44_v2"]],
|
||||
},
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await manager.loadBlocks(actor);
|
||||
|
||||
assert.equal(decryptCalls.nip44, 1, "nip44 decryptor should be preferred");
|
||||
assert.equal(decryptCalls.nip04, 0, "nip04 decryptor should not be used");
|
||||
} finally {
|
||||
window.nostr = originalNostr;
|
||||
nostrClient.relays = originalRelays;
|
||||
nostrClient.pool = originalPool;
|
||||
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
|
||||
if (originalSigner) {
|
||||
setActiveSigner(originalSigner);
|
||||
} else {
|
||||
clearActiveSigner();
|
||||
}
|
||||
}
|
||||
})();
|
||||
Reference in new issue
Block a user