Files
bitvid/js/nostr/signerCapabilities.js
T
thePR0M3TH3ANandClaude Opus 4.8 f383cde481 refactor(signer): dedupe extension-adapter builder; re-record size baseline
The refresh-restore signer fix (commit 0031d68b) grew SignerManager past its
file-size limit, breaking `npm run lint`. Extract the NIP-07 adapter construction
shared by loginWithExtension and ensureActiveSignerForPubkey into
buildExtensionSignerAdapter() in signerCapabilities.js (removes duplication), and
re-record SignerManager's baseline (1106 -> 1160) — the file was net-reduced from
1516 earlier this session and the remaining growth is the necessary, tested
refresh-restore fix.

`npm run lint` green again; signer tests pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 08:27:41 -04:00

225 lines
6.5 KiB
JavaScript

// js/nostr/signerCapabilities.js
//
// Signer capability/permission helpers extracted from managers/SignerManager.js
// to keep that module under the file-size budget. These operate on a signer
// object (or plain method lists) passed in — no SignerManager instance state —
// so they move cleanly. No behavior change.
import { resolveActiveSigner as resolveActiveSignerFromRegistry } from "../nostrClientRegistry.js";
export function resolveSignerCapabilities(signer) {
const fallback = {
sign: false,
nip44: false,
nip04: false,
};
if (!signer || typeof signer !== "object") {
return fallback;
}
const capabilities =
signer.capabilities && typeof signer.capabilities === "object"
? signer.capabilities
: {};
return {
sign:
(typeof capabilities.sign === "boolean" && capabilities.sign) ||
typeof signer.signEvent === "function",
nip44:
(typeof capabilities.nip44 === "boolean" && capabilities.nip44) ||
typeof signer.nip44Encrypt === "function" ||
typeof signer.nip44Decrypt === "function",
nip04:
(typeof capabilities.nip04 === "boolean" && capabilities.nip04) ||
typeof signer.nip04Encrypt === "function" ||
typeof signer.nip04Decrypt === "function",
};
}
export function hydrateExtensionSignerCapabilities(signer) {
if (!signer || typeof signer !== "object") {
return;
}
const signerType = typeof signer.type === "string" ? signer.type : "";
if (signerType !== "extension" && signerType !== "nip07") {
return;
}
const extension =
typeof window !== "undefined" && window && window.nostr ? window.nostr : null;
if (!extension) {
return;
}
if (typeof signer.signEvent !== "function" && extension.signEvent) {
if (typeof extension.signEvent === "function") {
signer.signEvent = extension.signEvent.bind(extension);
}
}
if (!signer.nip04 && extension.nip04) {
signer.nip04 = extension.nip04;
}
if (!signer.nip44 && extension.nip44) {
signer.nip44 = extension.nip44;
}
}
export function attachNipMethodAliases(signer) {
if (!signer || typeof signer !== "object") {
return;
}
const nip04 =
signer && typeof signer.nip04 === "object" && signer.nip04 !== null
? signer.nip04
: null;
if (nip04) {
const encrypt =
typeof nip04.encrypt === "function" ? nip04.encrypt.bind(nip04) : null;
const decrypt =
typeof nip04.decrypt === "function" ? nip04.decrypt.bind(nip04) : null;
if (encrypt && typeof signer.nip04Encrypt !== "function") {
signer.nip04Encrypt = (targetPubkey, plaintext) =>
encrypt(targetPubkey, plaintext);
}
if (decrypt && typeof signer.nip04Decrypt !== "function") {
signer.nip04Decrypt = (actorPubkey, ciphertext) =>
decrypt(actorPubkey, ciphertext);
}
}
const nip44 =
signer && typeof signer.nip44 === "object" && signer.nip44 !== null
? signer.nip44
: null;
if (nip44) {
const v2 =
typeof nip44.v2 === "object" && nip44.v2 !== null ? nip44.v2 : null;
const encrypt = (() => {
if (typeof signer.nip44Encrypt === "function") {
return null;
}
if (typeof v2?.encrypt === "function") {
return v2.encrypt.bind(v2);
}
if (typeof nip44.encrypt === "function") {
return nip44.encrypt.bind(nip44);
}
return null;
})();
const decrypt = (() => {
if (typeof signer.nip44Decrypt === "function") {
return null;
}
if (typeof v2?.decrypt === "function") {
return v2.decrypt.bind(v2);
}
if (typeof nip44.decrypt === "function") {
return nip44.decrypt.bind(nip44);
}
return null;
})();
if (encrypt) {
signer.nip44Encrypt = (targetPubkey, plaintext) =>
encrypt(targetPubkey, plaintext);
}
if (decrypt) {
signer.nip44Decrypt = (actorPubkey, ciphertext) =>
decrypt(actorPubkey, ciphertext);
}
}
}
// Build the plain NIP-07 signer adapter from a live `window.nostr`-style
// extension. Shared by loginWithExtension and the refresh-restore path in
// ensureActiveSignerForPubkey so the adapter shape stays identical. Capability
// aliases (nip04Decrypt/nip44Decrypt) are hydrated later by resolveActiveSigner.
export function buildExtensionSignerAdapter(extension, pubkey) {
return {
type: "extension",
pubkey,
signEvent:
typeof extension?.signEvent === "function"
? extension.signEvent.bind(extension)
: undefined,
nip04: extension?.nip04,
nip44: extension?.nip44,
};
}
export function resolveActiveSigner(pubkey) {
const signer = resolveActiveSignerFromRegistry(pubkey);
hydrateExtensionSignerCapabilities(signer);
attachNipMethodAliases(signer);
if (signer && typeof signer === "object") {
const capsDescriptor = Object.getOwnPropertyDescriptor(
signer,
"capabilities",
);
const isGetter = capsDescriptor && typeof capsDescriptor.get === "function";
if (!isGetter) {
signer.capabilities = resolveSignerCapabilities(signer);
}
}
return signer;
}
const ENCRYPTION_METHOD_PREFIXES = ["nip04.", "nip44."];
export function hasEncryptionPermissionMethods(methods) {
if (!Array.isArray(methods)) {
return false;
}
return methods.some((method) =>
ENCRYPTION_METHOD_PREFIXES.some((prefix) => method.startsWith(prefix)),
);
}
export function hasSigningPermissionMethods(methods) {
if (!Array.isArray(methods)) {
return false;
}
return methods.some(
(method) => method === "sign_event" || method === "get_public_key",
);
}
export function resolvePermissionStatusMessage(methods, context) {
const normalizedContext =
typeof context === "string" ? context.trim().toLowerCase() : "";
if (normalizedContext === "dm") {
return "Approve the extension prompt to enable encrypted direct messages.";
}
if (normalizedContext === "lists") {
return "Approve the extension prompt to access your encrypted lists.";
}
const includesEncryption = hasEncryptionPermissionMethods(methods);
const includesSigning = hasSigningPermissionMethods(methods);
if (includesEncryption && includesSigning) {
return "Approve the extension prompt to enable signing and encrypted features (DMs, subscriptions, block lists).";
}
if (includesEncryption) {
return "Approve the extension prompt to enable encrypted features like DMs and private lists.";
}
if (includesSigning) {
return "Approve the extension prompt to enable signing.";
}
return "Approve the extension prompt to continue.";
}