mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Merge pull request #761 from PR0M3TH3AN/codex/add-signer-registry-to-nostr.js
Refactor Nostr signer integration
This commit is contained in:
6 files changed
+466
-229
No files matched your search
@@ -42,6 +42,31 @@ When managing relay metadata, use `buildRelayListEvent` so the resulting
|
||||
replaceable event follows NIP-65 (`kind:10002`) with `"r"` tags describing the
|
||||
read/write split.
|
||||
|
||||
### Active signer registry
|
||||
|
||||
Authentication providers should register their capabilities with the
|
||||
`nostrClient` after login so every publish helper can reuse them:
|
||||
|
||||
```js
|
||||
import { setActiveSigner } from "./nostr.js";
|
||||
|
||||
setActiveSigner({
|
||||
type: "extension", // optional label, used to request NIP-07 permissions
|
||||
pubkey, // hex or npub of the active account
|
||||
signEvent: (event) => extension.signEvent(event),
|
||||
nip04Encrypt: (targetHex, plaintext) => extension.nip04.encrypt(targetHex, plaintext),
|
||||
nip04Decrypt: (actorHex, ciphertext) => extension.nip04.decrypt(actorHex, ciphertext),
|
||||
nip44Encrypt: (targetHex, plaintext) => extension.nip44.encrypt(targetHex, plaintext),
|
||||
nip44Decrypt: (actorHex, ciphertext) => extension.nip44.decrypt(actorHex, ciphertext),
|
||||
});
|
||||
```
|
||||
|
||||
`setActiveSigner` accepts any object that implements the subset of capabilities
|
||||
you support. `nostrClient` will prefer the registered signer for signing and
|
||||
encryption before falling back to session actors. Call `clearActiveSigner()` on
|
||||
logout if your integration manages session state manually; the built-in logout
|
||||
handler already does this for the default extension flow.
|
||||
|
||||
### Accessing raw events
|
||||
|
||||
`NostrClient` now keeps a lightweight cache of parsed videos alongside a
|
||||
|
||||
+307
-163
@@ -57,6 +57,8 @@ export const DEFAULT_RELAY_URLS = Object.freeze([
|
||||
|
||||
const RELAY_URLS = Array.from(DEFAULT_RELAY_URLS);
|
||||
|
||||
let activeSigner = null;
|
||||
|
||||
const EVENTS_CACHE_STORAGE_KEY = "bitvid:eventsCache:v1";
|
||||
const LEGACY_EVENTS_STORAGE_KEY = "bitvidEvents";
|
||||
const EVENTS_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
||||
@@ -2432,6 +2434,119 @@ function normalizeActorKey(actor) {
|
||||
return trimmed.toLowerCase();
|
||||
}
|
||||
|
||||
export function getActiveSigner() {
|
||||
return activeSigner;
|
||||
}
|
||||
|
||||
export function clearActiveSigner() {
|
||||
activeSigner = null;
|
||||
return activeSigner;
|
||||
}
|
||||
|
||||
export function setActiveSigner(candidate = null) {
|
||||
if (!candidate || typeof candidate !== "object") {
|
||||
activeSigner = null;
|
||||
return activeSigner;
|
||||
}
|
||||
|
||||
const pubkeyInput =
|
||||
typeof candidate.pubkey === "string"
|
||||
? candidate.pubkey
|
||||
: typeof candidate.pubKey === "string"
|
||||
? candidate.pubKey
|
||||
: "";
|
||||
const normalizedPubkey = normalizeActorKey(pubkeyInput);
|
||||
|
||||
let signEvent = null;
|
||||
if (typeof candidate.signEvent === "function") {
|
||||
signEvent = candidate.signEvent;
|
||||
}
|
||||
|
||||
let nip04Encrypt = null;
|
||||
if (typeof candidate.nip04Encrypt === "function") {
|
||||
nip04Encrypt = candidate.nip04Encrypt;
|
||||
} else if (candidate.nip04 && typeof candidate.nip04.encrypt === "function") {
|
||||
nip04Encrypt = candidate.nip04.encrypt.bind(candidate.nip04);
|
||||
}
|
||||
|
||||
let nip04Decrypt = null;
|
||||
if (typeof candidate.nip04Decrypt === "function") {
|
||||
nip04Decrypt = candidate.nip04Decrypt;
|
||||
} else if (candidate.nip04 && typeof candidate.nip04.decrypt === "function") {
|
||||
nip04Decrypt = candidate.nip04.decrypt.bind(candidate.nip04);
|
||||
}
|
||||
|
||||
let nip44Encrypt = null;
|
||||
if (typeof candidate.nip44Encrypt === "function") {
|
||||
nip44Encrypt = candidate.nip44Encrypt;
|
||||
} else if (candidate.nip44 && typeof candidate.nip44.encrypt === "function") {
|
||||
nip44Encrypt = candidate.nip44.encrypt.bind(candidate.nip44);
|
||||
} else if (
|
||||
candidate.nip44?.v2 &&
|
||||
typeof candidate.nip44.v2.encrypt === "function"
|
||||
) {
|
||||
nip44Encrypt = candidate.nip44.v2.encrypt.bind(candidate.nip44.v2);
|
||||
}
|
||||
|
||||
let nip44Decrypt = null;
|
||||
if (typeof candidate.nip44Decrypt === "function") {
|
||||
nip44Decrypt = candidate.nip44Decrypt;
|
||||
} else if (candidate.nip44 && typeof candidate.nip44.decrypt === "function") {
|
||||
nip44Decrypt = candidate.nip44.decrypt.bind(candidate.nip44);
|
||||
} else if (
|
||||
candidate.nip44?.v2 &&
|
||||
typeof candidate.nip44.v2.decrypt === "function"
|
||||
) {
|
||||
nip44Decrypt = candidate.nip44.v2.decrypt.bind(candidate.nip44.v2);
|
||||
}
|
||||
|
||||
const signerType =
|
||||
typeof candidate.type === "string" && candidate.type.trim()
|
||||
? candidate.type.trim()
|
||||
: null;
|
||||
|
||||
activeSigner = {
|
||||
type: signerType,
|
||||
pubkey: normalizedPubkey,
|
||||
signEvent,
|
||||
nip04Encrypt,
|
||||
nip04Decrypt,
|
||||
nip44Encrypt,
|
||||
nip44Decrypt,
|
||||
};
|
||||
|
||||
return activeSigner;
|
||||
}
|
||||
|
||||
function resolveActiveSigner(targetActor) {
|
||||
const signer = getActiveSigner();
|
||||
if (!signer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedTarget = normalizeActorKey(targetActor);
|
||||
const normalizedSigner = normalizeActorKey(signer.pubkey);
|
||||
if (normalizedTarget && normalizedSigner && normalizedTarget !== normalizedSigner) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof signer.signEvent !== "function" &&
|
||||
typeof signer.nip04Encrypt !== "function" &&
|
||||
typeof signer.nip04Decrypt !== "function" &&
|
||||
typeof signer.nip44Encrypt !== "function" &&
|
||||
typeof signer.nip44Decrypt !== "function"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return signer;
|
||||
}
|
||||
|
||||
function shouldRequestExtensionPermissions(signer) {
|
||||
return signer?.type === "extension";
|
||||
}
|
||||
|
||||
function canonicalizeWatchHistoryItems(rawItems, maxItems = WATCH_HISTORY_MAX_ITEMS) {
|
||||
const seen = new Map();
|
||||
if (Array.isArray(rawItems)) {
|
||||
@@ -3642,13 +3757,13 @@ export class NostrClient {
|
||||
typeof this.pubkey === "string" && this.pubkey
|
||||
? this.pubkey.toLowerCase()
|
||||
: "";
|
||||
const extension = window?.nostr;
|
||||
const canSignWithExtension =
|
||||
const activeSigner = resolveActiveSigner(this.pubkey);
|
||||
const canSignWithActiveSigner =
|
||||
!!normalizedLogged &&
|
||||
extension &&
|
||||
typeof extension.signEvent === "function";
|
||||
activeSigner &&
|
||||
typeof activeSigner.signEvent === "function";
|
||||
|
||||
if (!forceRenew && canSignWithExtension) {
|
||||
if (!forceRenew && canSignWithActiveSigner) {
|
||||
return normalizedLogged;
|
||||
}
|
||||
|
||||
@@ -4283,21 +4398,29 @@ export class NostrClient {
|
||||
}
|
||||
|
||||
const actorPubkey = resolvedActor || actorKey;
|
||||
const extension = window?.nostr;
|
||||
const extensionActorKey = normalizeActorKey(this.pubkey);
|
||||
const canUseExtensionSign =
|
||||
extension &&
|
||||
typeof extension.signEvent === "function" &&
|
||||
actorKey === extensionActorKey;
|
||||
const normalizedLogged = normalizeActorKey(this.pubkey);
|
||||
const signer = resolveActiveSigner(actorKey);
|
||||
const canUseActiveSignerSign =
|
||||
normalizedLogged &&
|
||||
normalizedLogged === actorKey &&
|
||||
signer &&
|
||||
typeof signer.signEvent === "function";
|
||||
const useActiveSignerEncrypt =
|
||||
canUseActiveSignerSign &&
|
||||
signer &&
|
||||
typeof signer.nip04Encrypt === "function";
|
||||
const activeSigner = canUseActiveSignerSign ? signer : null;
|
||||
const encryptionSigner = useActiveSignerEncrypt ? signer : null;
|
||||
|
||||
const useExtensionEncrypt =
|
||||
canUseExtensionSign &&
|
||||
extension &&
|
||||
extension.nip04 &&
|
||||
typeof extension.nip04.encrypt === "function";
|
||||
if (
|
||||
(canUseActiveSignerSign || useActiveSignerEncrypt) &&
|
||||
shouldRequestExtensionPermissions(signer)
|
||||
) {
|
||||
await this.ensureExtensionPermissions(DEFAULT_NIP07_PERMISSION_METHODS);
|
||||
}
|
||||
|
||||
let privateKey = "";
|
||||
if (!canUseExtensionSign) {
|
||||
if (!canUseActiveSignerSign) {
|
||||
if (!this.sessionActor || this.sessionActor.pubkey !== actorKey) {
|
||||
const ensured = await this.ensureSessionActor();
|
||||
if (normalizeActorKey(ensured) !== actorKey) {
|
||||
@@ -4313,10 +4436,6 @@ export class NostrClient {
|
||||
}
|
||||
}
|
||||
|
||||
if (useExtensionEncrypt) {
|
||||
await this.ensureExtensionPermissions(DEFAULT_NIP07_PERMISSION_METHODS);
|
||||
}
|
||||
|
||||
const canonicalItems = canonicalizeWatchHistoryItems(
|
||||
Array.isArray(rawItems) ? rawItems : [],
|
||||
WATCH_HISTORY_MAX_ITEMS,
|
||||
@@ -4387,8 +4506,8 @@ export class NostrClient {
|
||||
};
|
||||
|
||||
const encryptChunk = async (plaintext) => {
|
||||
if (useExtensionEncrypt) {
|
||||
return extension.nip04.encrypt(actorPubkey, plaintext);
|
||||
if (encryptionSigner) {
|
||||
return encryptionSigner.nip04Encrypt(actorPubkey, plaintext);
|
||||
}
|
||||
const tools = await ensureNip04Tools();
|
||||
if (!tools?.nip04 || typeof tools.nip04.encrypt !== "function") {
|
||||
@@ -4398,8 +4517,8 @@ export class NostrClient {
|
||||
};
|
||||
|
||||
const signEvent = async (event) => {
|
||||
if (canUseExtensionSign) {
|
||||
return extension.signEvent(event);
|
||||
if (activeSigner) {
|
||||
return activeSigner.signEvent(event);
|
||||
}
|
||||
return signEventWithPrivateKey(event, privateKey);
|
||||
};
|
||||
@@ -4843,13 +4962,24 @@ export class NostrClient {
|
||||
|
||||
const actorKeyIsHex = /^[0-9a-f]{64}$/.test(actorKey);
|
||||
|
||||
const normalizedLogged = normalizeActorKey(this.pubkey);
|
||||
const signer = resolveActiveSigner(actorKey);
|
||||
const canUseActiveSignerDecrypt =
|
||||
normalizedLogged &&
|
||||
normalizedLogged === actorKey &&
|
||||
signer &&
|
||||
typeof signer.nip04Decrypt === "function";
|
||||
const decryptSigner = canUseActiveSignerDecrypt ? signer : null;
|
||||
|
||||
if (decryptSigner && shouldRequestExtensionPermissions(decryptSigner)) {
|
||||
await this.ensureExtensionPermissions(DEFAULT_NIP07_PERMISSION_METHODS);
|
||||
}
|
||||
|
||||
devLogger.info("[nostr] Fetching watch history from relays.", {
|
||||
actor: resolvedActor,
|
||||
forceRefresh: options.forceRefresh === true,
|
||||
});
|
||||
|
||||
const extension = window?.nostr;
|
||||
|
||||
const existingEntry = this.watchHistoryCache.get(actorKey);
|
||||
const now = Date.now();
|
||||
const ttl = this.getWatchHistoryCacheTtlMs();
|
||||
@@ -5122,26 +5252,24 @@ export class NostrClient {
|
||||
expectedPlaintextFormat:
|
||||
"JSON string with { version, items, snapshot, chunkIndex, totalChunks }",
|
||||
});
|
||||
const extensionDecrypt =
|
||||
extension &&
|
||||
typeof extension?.nip04?.decrypt === "function" &&
|
||||
normalizeActorKey(this.pubkey) === actorKey;
|
||||
if (extensionDecrypt) {
|
||||
await this.ensureExtensionPermissions(DEFAULT_NIP07_PERMISSION_METHODS);
|
||||
if (decryptSigner) {
|
||||
devLogger.info(
|
||||
"[nostr] Using logged in user's extension key to decrypt watch history chunk.",
|
||||
"[nostr] Using active signer to decrypt watch history chunk.",
|
||||
{
|
||||
actorKey,
|
||||
chunkIdentifier: context.chunkIdentifier ?? null,
|
||||
eventId: context.eventId ?? null,
|
||||
actorKey,
|
||||
chunkIdentifier: context.chunkIdentifier ?? null,
|
||||
eventId: context.eventId ?? null,
|
||||
},
|
||||
);
|
||||
const plaintext = await decryptSigner.nip04Decrypt(actorKey, ciphertext);
|
||||
devLogger.info(
|
||||
"[nostr] Successfully decrypted watch history chunk via active signer.",
|
||||
{
|
||||
actorKey,
|
||||
chunkIdentifier: context.chunkIdentifier ?? null,
|
||||
eventId: context.eventId ?? null,
|
||||
},
|
||||
);
|
||||
const plaintext = await extension.nip04.decrypt(actorKey, ciphertext);
|
||||
devLogger.info("[nostr] Successfully decrypted watch history chunk via extension key.", {
|
||||
actorKey,
|
||||
chunkIdentifier: context.chunkIdentifier ?? null,
|
||||
eventId: context.eventId ?? null,
|
||||
});
|
||||
return plaintext;
|
||||
}
|
||||
if (!this.sessionActor || this.sessionActor.pubkey !== actorKey) {
|
||||
@@ -5841,28 +5969,33 @@ export class NostrClient {
|
||||
|
||||
let signedEvent = null;
|
||||
|
||||
if (
|
||||
const signer = resolveActiveSigner(actorPubkey);
|
||||
const canUseActiveSigner =
|
||||
normalizedActor &&
|
||||
normalizedActor === normalizedLogged &&
|
||||
window?.nostr &&
|
||||
typeof window.nostr.signEvent === "function"
|
||||
) {
|
||||
const permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
signer &&
|
||||
typeof signer.signEvent === "function";
|
||||
|
||||
if (canUseActiveSigner) {
|
||||
let permissionResult = { ok: true };
|
||||
if (shouldRequestExtensionPermissions(signer)) {
|
||||
permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
}
|
||||
if (permissionResult.ok) {
|
||||
try {
|
||||
signedEvent = await window.nostr.signEvent(event);
|
||||
signedEvent = await signer.signEvent(event);
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
"[nostr] Failed to sign view event with extension:",
|
||||
"[nostr] Failed to sign view event with active signer:",
|
||||
error,
|
||||
);
|
||||
return { ok: false, error: "signing-failed", details: error };
|
||||
}
|
||||
} else {
|
||||
userLogger.warn(
|
||||
"[nostr] Extension permissions missing; signing view event with session key.",
|
||||
"[nostr] Active signer permissions missing; signing view event with session key.",
|
||||
permissionResult.error,
|
||||
);
|
||||
}
|
||||
@@ -6163,6 +6296,17 @@ export class NostrClient {
|
||||
this.pubkey = pubkey;
|
||||
devLogger.log("Logged in with extension. Pubkey:", this.pubkey);
|
||||
|
||||
setActiveSigner({
|
||||
type: "extension",
|
||||
pubkey,
|
||||
signEvent:
|
||||
typeof extension.signEvent === "function"
|
||||
? extension.signEvent.bind(extension)
|
||||
: null,
|
||||
nip04: extension.nip04,
|
||||
nip44: extension.nip44,
|
||||
});
|
||||
|
||||
const postLoginPermissions = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
@@ -6181,6 +6325,7 @@ export class NostrClient {
|
||||
|
||||
logout() {
|
||||
this.pubkey = null;
|
||||
clearActiveSigner();
|
||||
for (const timer of this.watchHistoryRepublishTimers.values()) {
|
||||
if (timer && typeof timer.timer === "number") {
|
||||
clearTimeout(timer.timer);
|
||||
@@ -6222,33 +6367,41 @@ export class NostrClient {
|
||||
return { ok: false, error: "nostr-uninitialized" };
|
||||
}
|
||||
|
||||
const extension = window?.nostr;
|
||||
if (!extension) {
|
||||
return { ok: false, error: "nostr-extension-missing" };
|
||||
const activeSignerCandidate =
|
||||
typeof actorPubkeyOverride === "string" && actorPubkeyOverride.trim()
|
||||
? resolveActiveSigner(actorPubkeyOverride)
|
||||
: resolveActiveSigner(this.pubkey);
|
||||
const baseActiveSigner = activeSignerCandidate || getActiveSigner();
|
||||
const signer =
|
||||
typeof actorPubkeyOverride === "string" && actorPubkeyOverride.trim()
|
||||
? activeSignerCandidate
|
||||
: baseActiveSigner
|
||||
? resolveActiveSigner(baseActiveSigner.pubkey || this.pubkey)
|
||||
: null;
|
||||
|
||||
if (!signer || typeof signer.signEvent !== "function") {
|
||||
return { ok: false, error: "sign-event-unavailable" };
|
||||
}
|
||||
|
||||
const permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[nostr] Cannot send direct message without extension permissions.",
|
||||
permissionResult.error,
|
||||
);
|
||||
return {
|
||||
ok: false,
|
||||
error: "extension-permission-denied",
|
||||
details: permissionResult.error,
|
||||
};
|
||||
}
|
||||
|
||||
const nip04 = extension.nip04;
|
||||
if (!nip04 || typeof nip04.encrypt !== "function") {
|
||||
if (typeof signer.nip04Encrypt !== "function") {
|
||||
return { ok: false, error: "nip04-unavailable" };
|
||||
}
|
||||
|
||||
if (typeof extension.signEvent !== "function") {
|
||||
return { ok: false, error: "sign-event-unavailable" };
|
||||
if (shouldRequestExtensionPermissions(signer)) {
|
||||
const permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[nostr] Cannot send direct message without extension permissions.",
|
||||
permissionResult.error,
|
||||
);
|
||||
return {
|
||||
ok: false,
|
||||
error: "extension-permission-denied",
|
||||
details: permissionResult.error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let actorHex =
|
||||
@@ -6260,15 +6413,8 @@ export class NostrClient {
|
||||
actorHex = this.pubkey.trim();
|
||||
}
|
||||
|
||||
if (!actorHex && typeof extension.getPublicKey === "function") {
|
||||
try {
|
||||
actorHex = await extension.getPublicKey();
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[nostr] Failed to fetch actor pubkey from extension:",
|
||||
error
|
||||
);
|
||||
}
|
||||
if (!actorHex && typeof signer?.pubkey === "string") {
|
||||
actorHex = signer.pubkey;
|
||||
}
|
||||
|
||||
if (!actorHex) {
|
||||
@@ -6282,7 +6428,7 @@ export class NostrClient {
|
||||
|
||||
let ciphertext = "";
|
||||
try {
|
||||
ciphertext = await nip04.encrypt(targetHex, trimmedMessage);
|
||||
ciphertext = await signer.nip04Encrypt(targetHex, trimmedMessage);
|
||||
} catch (error) {
|
||||
return { ok: false, error: "encryption-failed", details: error };
|
||||
}
|
||||
@@ -6297,7 +6443,7 @@ export class NostrClient {
|
||||
|
||||
let signedEvent;
|
||||
try {
|
||||
signedEvent = await extension.signEvent(event);
|
||||
signedEvent = await signer.signEvent(event);
|
||||
} catch (error) {
|
||||
return { ok: false, error: "signature-failed", details: error };
|
||||
}
|
||||
@@ -6334,12 +6480,6 @@ export class NostrClient {
|
||||
relaysOverride = null,
|
||||
} = {}
|
||||
) {
|
||||
const extension = window?.nostr;
|
||||
const extensionSigner =
|
||||
extension && typeof extension.signEvent === "function"
|
||||
? extension.signEvent.bind(extension)
|
||||
: null;
|
||||
|
||||
const normalizedEventPubkey =
|
||||
event && typeof event.pubkey === "string"
|
||||
? event.pubkey.toLowerCase()
|
||||
@@ -6351,30 +6491,35 @@ export class NostrClient {
|
||||
normalizedLogged &&
|
||||
normalizedEventPubkey !== normalizedLogged;
|
||||
|
||||
const signer = resolveActiveSigner(normalizedEventPubkey || this.pubkey);
|
||||
const canUseActiveSigner =
|
||||
!usingSessionActor &&
|
||||
signer &&
|
||||
typeof signer.signEvent === "function";
|
||||
|
||||
let eventToSign = event;
|
||||
let signedEvent = null;
|
||||
let signerPubkey = null;
|
||||
|
||||
if (
|
||||
extensionSigner &&
|
||||
normalizedEventPubkey &&
|
||||
normalizedEventPubkey === normalizedLogged
|
||||
) {
|
||||
const permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
if (canUseActiveSigner) {
|
||||
let permissionResult = { ok: true };
|
||||
if (shouldRequestExtensionPermissions(signer)) {
|
||||
permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
}
|
||||
if (permissionResult.ok) {
|
||||
try {
|
||||
signedEvent = await extensionSigner(event);
|
||||
signedEvent = await signer.signEvent(event);
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
"[nostr] Failed to sign event with extension:",
|
||||
"[nostr] Failed to sign event with active signer:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
userLogger.warn(
|
||||
"[nostr] Extension permissions missing; falling back to session signer.",
|
||||
"[nostr] Active signer permissions missing; falling back to session signer.",
|
||||
permissionResult.error,
|
||||
);
|
||||
}
|
||||
@@ -6918,31 +7063,35 @@ export class NostrClient {
|
||||
devLogger.log("Creating edited event with root ID:", oldRootId);
|
||||
devLogger.log("Event content:", event.content);
|
||||
|
||||
const extensionSigner = window?.nostr?.signEvent;
|
||||
if (typeof extensionSigner !== "function") {
|
||||
const error = new Error("A NIP-07 extension with signEvent support is required to edit videos.");
|
||||
const signer = resolveActiveSigner(userPubkeyLower);
|
||||
if (!signer || typeof signer.signEvent !== "function") {
|
||||
const error = new Error(
|
||||
"An active signer with signEvent support is required to edit videos.",
|
||||
);
|
||||
error.code = "nostr-extension-missing";
|
||||
throw error;
|
||||
}
|
||||
|
||||
const permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[nostr] Extension permissions denied while editing a video.",
|
||||
permissionResult.error,
|
||||
if (shouldRequestExtensionPermissions(signer)) {
|
||||
const permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
const error = new Error(
|
||||
"The NIP-07 extension must grant decrypt and sign permissions before editing a video.",
|
||||
);
|
||||
error.code = "extension-permission-denied";
|
||||
error.cause = permissionResult.error;
|
||||
throw error;
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[nostr] Signer permissions denied while editing a video.",
|
||||
permissionResult.error,
|
||||
);
|
||||
const error = new Error(
|
||||
"The active signer must grant decrypt and sign permissions before editing a video.",
|
||||
);
|
||||
error.code = "extension-permission-denied";
|
||||
error.cause = permissionResult.error;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const signedEvent = await extensionSigner(event);
|
||||
const signedEvent = await signer.signEvent(event);
|
||||
devLogger.log("Signed edited event:", signedEvent);
|
||||
|
||||
const publishResults = await publishEventToRelays(
|
||||
@@ -7083,36 +7232,34 @@ export class NostrClient {
|
||||
content: JSON.stringify(contentObject),
|
||||
};
|
||||
|
||||
const extension = window?.nostr;
|
||||
const extensionSigner =
|
||||
extension && typeof extension.signEvent === "function"
|
||||
? extension.signEvent.bind(extension)
|
||||
: null;
|
||||
if (typeof extensionSigner !== "function") {
|
||||
const signer = resolveActiveSigner(pubkey);
|
||||
if (!signer || typeof signer.signEvent !== "function") {
|
||||
const error = new Error(
|
||||
"A NIP-07 extension with signEvent support is required to revert videos.",
|
||||
"An active signer with signEvent support is required to revert videos.",
|
||||
);
|
||||
error.code = "nostr-extension-missing";
|
||||
throw error;
|
||||
}
|
||||
|
||||
const permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[nostr] Extension permissions denied while reverting a video.",
|
||||
permissionResult.error,
|
||||
if (shouldRequestExtensionPermissions(signer)) {
|
||||
const permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
const error = new Error(
|
||||
"The NIP-07 extension must grant decrypt and sign permissions before reverting a video.",
|
||||
);
|
||||
error.code = "extension-permission-denied";
|
||||
error.cause = permissionResult.error;
|
||||
throw error;
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[nostr] Signer permissions denied while reverting a video.",
|
||||
permissionResult.error,
|
||||
);
|
||||
const error = new Error(
|
||||
"The active signer must grant decrypt and sign permissions before reverting a video.",
|
||||
);
|
||||
error.code = "extension-permission-denied";
|
||||
error.cause = permissionResult.error;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const signedEvent = await extensionSigner(event);
|
||||
const signedEvent = await signer.signEvent(event);
|
||||
const publishResults = await publishEventToRelays(
|
||||
this.pool,
|
||||
this.relays,
|
||||
@@ -7381,34 +7528,31 @@ export class NostrClient {
|
||||
|
||||
const deleteSummaries = [];
|
||||
if (identifierRecords.length) {
|
||||
const extension = window?.nostr;
|
||||
const extensionSigner =
|
||||
extension && typeof extension.signEvent === "function"
|
||||
? extension.signEvent.bind(extension)
|
||||
: null;
|
||||
|
||||
if (typeof extensionSigner !== "function") {
|
||||
const signer = resolveActiveSigner(pubkey);
|
||||
if (!signer || typeof signer.signEvent !== "function") {
|
||||
const error = new Error(
|
||||
"A NIP-07 extension with signEvent support is required to delete videos.",
|
||||
"An active signer with signEvent support is required to delete videos.",
|
||||
);
|
||||
error.code = "nostr-extension-missing";
|
||||
throw error;
|
||||
}
|
||||
|
||||
const permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[nostr] Extension permissions denied while deleting videos.",
|
||||
permissionResult.error,
|
||||
if (shouldRequestExtensionPermissions(signer)) {
|
||||
const permissionResult = await this.ensureExtensionPermissions(
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
);
|
||||
const error = new Error(
|
||||
"The NIP-07 extension must grant decrypt and sign permissions before deleting a video.",
|
||||
);
|
||||
error.code = "extension-permission-denied";
|
||||
error.cause = permissionResult.error;
|
||||
throw error;
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[nostr] Signer permissions denied while deleting videos.",
|
||||
permissionResult.error,
|
||||
);
|
||||
const error = new Error(
|
||||
"The active signer must grant decrypt and sign permissions before deleting a video.",
|
||||
);
|
||||
error.code = "extension-permission-denied";
|
||||
error.cause = permissionResult.error;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const chunkSize = 100;
|
||||
@@ -7426,7 +7570,7 @@ export class NostrClient {
|
||||
: "Delete published video events",
|
||||
};
|
||||
|
||||
const signedDelete = await extensionSigner(deleteEvent);
|
||||
const signedDelete = await signer.signEvent(deleteEvent);
|
||||
const publishResults = await publishEventToRelays(
|
||||
this.pool,
|
||||
this.relays,
|
||||
|
||||
+21
-17
@@ -1,6 +1,7 @@
|
||||
import { isDevMode } from "./config.js";
|
||||
import {
|
||||
DEFAULT_RELAY_URLS,
|
||||
getActiveSigner,
|
||||
nostrClient,
|
||||
requestDefaultExtensionPermissions,
|
||||
} from "./nostr.js";
|
||||
@@ -606,28 +607,31 @@ class RelayPreferencesManager {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[RelayPreferencesManager] Extension permissions denied while publishing relay preferences.",
|
||||
permissionResult.error,
|
||||
);
|
||||
const signer = getActiveSigner();
|
||||
if (!signer || typeof signer.signEvent !== "function") {
|
||||
const error = new Error(
|
||||
"The NIP-07 extension must allow signing before publishing relay preferences.",
|
||||
);
|
||||
error.code = "extension-permission-denied";
|
||||
error.cause = permissionResult.error;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!window?.nostr?.signEvent) {
|
||||
const error = new Error(
|
||||
"A NIP-07 extension is required to publish relay preferences."
|
||||
"An active signer with signEvent support is required to publish relay preferences."
|
||||
);
|
||||
error.code = "nostr-extension-missing";
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (signer.type === "extension") {
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[RelayPreferencesManager] Signer permissions denied while publishing relay preferences.",
|
||||
permissionResult.error,
|
||||
);
|
||||
const error = new Error(
|
||||
"The active signer must allow signing before publishing relay preferences.",
|
||||
);
|
||||
error.code = "extension-permission-denied";
|
||||
error.cause = permissionResult.error;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nostrClient?.pool) {
|
||||
const error = new Error(
|
||||
"Nostr is not connected yet. Please try again once relays are ready."
|
||||
@@ -649,7 +653,7 @@ class RelayPreferencesManager {
|
||||
relays: entries,
|
||||
});
|
||||
|
||||
const signedEvent = await window.nostr.signEvent(event);
|
||||
const signedEvent = await signer.signEvent(event);
|
||||
const targets = this.getPublishTargets(options?.relayUrls);
|
||||
|
||||
if (!targets.length) {
|
||||
|
||||
+68
-32
@@ -1,5 +1,6 @@
|
||||
// js/subscriptions.js
|
||||
import {
|
||||
getActiveSigner,
|
||||
nostrClient,
|
||||
convertEventToVideo as sharedConvertEventToVideo,
|
||||
requestDefaultExtensionPermissions,
|
||||
@@ -286,14 +287,6 @@ class SubscriptionsManager {
|
||||
return { ok: false, error };
|
||||
}
|
||||
|
||||
const nostrApi =
|
||||
typeof window !== "undefined" && window && window.nostr ? window.nostr : null;
|
||||
if (!nostrApi) {
|
||||
const error = new Error("Nostr extension is unavailable for decrypting subscriptions.");
|
||||
error.code = "nostr-extension-missing";
|
||||
return { ok: false, error };
|
||||
}
|
||||
|
||||
const decryptors = new Map();
|
||||
const registerDecryptor = (scheme, handler) => {
|
||||
if (!scheme || typeof handler !== "function" || decryptors.has(scheme)) {
|
||||
@@ -302,28 +295,48 @@ class SubscriptionsManager {
|
||||
decryptors.set(scheme, handler);
|
||||
};
|
||||
|
||||
if (nostrApi.nip04 && typeof nostrApi.nip04.decrypt === "function") {
|
||||
registerDecryptor("nip04", (payload) => nostrApi.nip04.decrypt(userPubkey, payload));
|
||||
const signer = getActiveSigner();
|
||||
if (signer?.nip04Decrypt) {
|
||||
registerDecryptor("nip04", (payload) => signer.nip04Decrypt(userPubkey, payload));
|
||||
}
|
||||
|
||||
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));
|
||||
if (signer?.nip44Decrypt) {
|
||||
registerDecryptor("nip44", (payload) => signer.nip44Decrypt(userPubkey, payload));
|
||||
}
|
||||
|
||||
const nostrApi =
|
||||
typeof window !== "undefined" && window && window.nostr ? window.nostr : null;
|
||||
if (nostrApi) {
|
||||
if (nostrApi.nip04 && typeof nostrApi.nip04.decrypt === "function") {
|
||||
registerDecryptor("nip04", (payload) =>
|
||||
nostrApi.nip04.decrypt(userPubkey, payload)
|
||||
);
|
||||
}
|
||||
|
||||
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));
|
||||
if (!decryptors.has("nip44")) {
|
||||
registerDecryptor("nip44", (payload) => nip44v2.decrypt(userPubkey, payload));
|
||||
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));
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
if (!decryptors.has("nip44")) {
|
||||
registerDecryptor("nip44", (payload) => nip44v2.decrypt(userPubkey, payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!decryptors.size) {
|
||||
const error = new Error("No compatible decryption helpers are available.");
|
||||
error.code = "subscriptions-no-decryptors";
|
||||
const error = new Error(
|
||||
"No active signer or extension decryptors are available for subscriptions."
|
||||
);
|
||||
error.code = "nostr-extension-missing";
|
||||
return { ok: false, error };
|
||||
}
|
||||
|
||||
@@ -413,17 +426,40 @@ class SubscriptionsManager {
|
||||
throw new Error("No pubkey => cannot publish subscription list.");
|
||||
}
|
||||
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[SubscriptionsManager] Extension permissions denied while updating subscriptions.",
|
||||
permissionResult.error,
|
||||
);
|
||||
const signer = getActiveSigner();
|
||||
if (!signer) {
|
||||
const error = new Error(
|
||||
"The NIP-07 extension must allow encryption and signing before updating subscriptions.",
|
||||
"An active signer is required to update subscriptions."
|
||||
);
|
||||
error.code = "extension-permission-denied";
|
||||
error.cause = permissionResult.error;
|
||||
error.code = "signer-missing";
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (signer.type === "extension") {
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[SubscriptionsManager] Signer permissions denied while updating subscriptions.",
|
||||
permissionResult.error,
|
||||
);
|
||||
const error = new Error(
|
||||
"The active signer must allow encryption and signing before updating subscriptions.",
|
||||
);
|
||||
error.code = "extension-permission-denied";
|
||||
error.cause = permissionResult.error;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -440,7 +476,7 @@ class SubscriptionsManager {
|
||||
*/
|
||||
let cipherText = "";
|
||||
try {
|
||||
cipherText = await window.nostr.nip04.encrypt(userPubkey, plainStr);
|
||||
cipherText = await signer.nip04Encrypt(userPubkey, plainStr);
|
||||
} catch (err) {
|
||||
userLogger.error("Encryption failed:", err);
|
||||
throw err;
|
||||
@@ -454,7 +490,7 @@ class SubscriptionsManager {
|
||||
|
||||
let signedEvent;
|
||||
try {
|
||||
signedEvent = await window.nostr.signEvent(evt);
|
||||
signedEvent = await signer.signEvent(evt);
|
||||
} catch (signErr) {
|
||||
userLogger.error("Failed to sign subscription list:", signErr);
|
||||
throw signErr;
|
||||
|
||||
+38
-16
@@ -1,5 +1,6 @@
|
||||
// js/userBlocks.js
|
||||
import {
|
||||
getActiveSigner,
|
||||
nostrClient,
|
||||
requestDefaultExtensionPermissions,
|
||||
} from "./nostr.js";
|
||||
@@ -548,9 +549,19 @@ class UserBlockListManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeSigner = getActiveSigner();
|
||||
const decryptWithActiveSigner =
|
||||
activeSigner && typeof activeSigner.nip04Decrypt === "function";
|
||||
|
||||
let decrypted = "";
|
||||
try {
|
||||
decrypted = await window.nostr.nip04.decrypt(normalized, newest.content);
|
||||
if (decryptWithActiveSigner) {
|
||||
decrypted = await activeSigner.nip04Decrypt(normalized, newest.content);
|
||||
} else if (window?.nostr?.nip04?.decrypt) {
|
||||
decrypted = await window.nostr.nip04.decrypt(normalized, newest.content);
|
||||
} else {
|
||||
throw new Error("nip04-unavailable");
|
||||
}
|
||||
} catch (err) {
|
||||
userLogger.error("[UserBlockList] Failed to decrypt block list:", err);
|
||||
applyBlockedPubkeys([], {
|
||||
@@ -905,21 +916,32 @@ class UserBlockListManager {
|
||||
|
||||
onStatus?.({ status: "publishing" });
|
||||
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[UserBlockList] Extension permissions denied while updating the block list.",
|
||||
permissionResult.error,
|
||||
);
|
||||
const signer = getActiveSigner();
|
||||
if (!signer) {
|
||||
const err = new Error(
|
||||
"The NIP-07 extension must allow encryption and signing before updating the block list.",
|
||||
"An active signer is required to update the block list."
|
||||
);
|
||||
err.code = "extension-permission-denied";
|
||||
err.cause = permissionResult.error;
|
||||
err.code = "signer-missing";
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!window?.nostr?.nip04?.encrypt) {
|
||||
if (signer.type === "extension") {
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
if (!permissionResult.ok) {
|
||||
userLogger.warn(
|
||||
"[UserBlockList] Signer permissions denied while updating the block list.",
|
||||
permissionResult.error,
|
||||
);
|
||||
const err = new Error(
|
||||
"The active signer must allow encryption and signing before updating the block list.",
|
||||
);
|
||||
err.code = "extension-permission-denied";
|
||||
err.cause = permissionResult.error;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof signer.nip04Encrypt !== "function") {
|
||||
const err = new Error(
|
||||
"NIP-04 encryption is required to update the block list."
|
||||
);
|
||||
@@ -927,9 +949,9 @@ class UserBlockListManager {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (typeof window.nostr.signEvent !== "function") {
|
||||
const err = new Error("Nostr extension missing signEvent support.");
|
||||
err.code = "nip04-missing";
|
||||
if (typeof signer.signEvent !== "function") {
|
||||
const err = new Error("Active signer missing signEvent support.");
|
||||
err.code = "sign-event-missing";
|
||||
throw err;
|
||||
}
|
||||
|
||||
@@ -947,7 +969,7 @@ class UserBlockListManager {
|
||||
|
||||
let cipherText = "";
|
||||
try {
|
||||
cipherText = await window.nostr.nip04.encrypt(normalized, plaintext);
|
||||
cipherText = await signer.nip04Encrypt(normalized, plaintext);
|
||||
} catch (error) {
|
||||
const err = new Error("Failed to encrypt block list.");
|
||||
err.code = "nip04-missing";
|
||||
@@ -960,7 +982,7 @@ class UserBlockListManager {
|
||||
content: cipherText,
|
||||
});
|
||||
|
||||
const signedEvent = await window.nostr.signEvent(event);
|
||||
const signedEvent = await signer.signEvent(event);
|
||||
|
||||
const publishResults = await publishEventToRelays(
|
||||
nostrClient.pool,
|
||||
|
||||
@@ -52,7 +52,7 @@ const {
|
||||
} = await import(
|
||||
"../js/adminListStore.js"
|
||||
);
|
||||
const { nostrClient } = await import("../js/nostr.js");
|
||||
const { nostrClient, setActiveSigner } = await import("../js/nostr.js");
|
||||
const {
|
||||
ADMIN_SUPER_NPUB,
|
||||
ADMIN_LIST_NAMESPACE,
|
||||
@@ -290,6 +290,12 @@ globalThis.window.nostr.signEvent = async (event) => ({
|
||||
id: "signed-event",
|
||||
});
|
||||
|
||||
setActiveSigner({
|
||||
pubkey: superHex,
|
||||
signEvent: globalThis.window.nostr.signEvent,
|
||||
type: "test",
|
||||
});
|
||||
|
||||
resetListRegistry();
|
||||
|
||||
registerListEvent({
|
||||
|
||||
Reference in new issue
Block a user