Files
bitvid/js/nostrClientRegistry.js
google-labs-jules[bot]andPR0M3TH3AN ab8c46d54d Refactor publishVideo logic and fix signer tests
- Extracted prepareVideoPublishPayload and prepareVideoMirrorOptions to js/nostr/videoPayloadBuilder.js
- Created js/utils/cryptoUtils.js and js/utils/mime.js
- Updated js/nostr/client.js to use new builder and helpers
- Fixed SignerManager permission hydration and session actor logic
- Fixed nostrClientRegistry update logic
- Fixed unit tests: nostr-private-key-signer, integration-remote-flow, dmSignalEvents

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-09 21:32:29 +00:00

272 lines
6.7 KiB
JavaScript

// js/nostrClientRegistry.js
let registeredNostrClient = null;
let requestPermissionsDelegate = null;
const registeredSigners = new Map();
let activeSigner = null;
let activeSignerPubkey = "";
const activeSignerListeners = new Set();
const globalScope =
typeof window !== "undefined"
? window
: typeof globalThis !== "undefined"
? globalThis
: null;
function normalizePubkey(pubkey) {
return typeof pubkey === "string" ? pubkey.trim().toLowerCase() : "";
}
function emitSignerEvent(type, detail) {
if (!globalScope || typeof globalScope.dispatchEvent !== "function") {
return;
}
let event = null;
if (typeof globalScope.CustomEvent === "function") {
event = new globalScope.CustomEvent(type, { detail });
} else if (typeof Event === "function") {
event = new Event(type);
event.detail = detail;
}
if (event) {
globalScope.dispatchEvent(event);
}
}
function hasPermissionChanges(previousMeta, nextMeta) {
const previous = previousMeta?.permissions ?? null;
const next = nextMeta?.permissions ?? null;
try {
return JSON.stringify(previous) !== JSON.stringify(next);
} catch (error) {
return previous !== next;
}
}
function updateActiveSigner(nextSigner, nextPubkey) {
const normalizedPubkey = normalizePubkey(nextPubkey);
const previousSigner = activeSigner;
const previousPubkey = activeSignerPubkey;
activeSigner = nextSigner || null;
activeSignerPubkey = normalizedPubkey;
if (
previousSigner !== activeSigner ||
previousPubkey !== activeSignerPubkey
) {
const payload = {
previousSigner,
previousPubkey,
signer: activeSigner,
pubkey: activeSignerPubkey,
};
activeSignerListeners.forEach((listener) => {
try {
listener(payload);
} catch (error) {
// Intentionally swallow listener errors to avoid breaking signer updates.
}
});
}
}
export function registerNostrClient(client, options = {}) {
registeredNostrClient = client || null;
if (typeof options.requestPermissions === "function") {
requestPermissionsDelegate = options.requestPermissions;
return;
}
if (
registeredNostrClient &&
typeof registeredNostrClient.ensureExtensionPermissions === "function"
) {
requestPermissionsDelegate = (...args) =>
registeredNostrClient.ensureExtensionPermissions(...args);
return;
}
requestPermissionsDelegate = null;
}
export function getRegisteredNostrClient() {
return registeredNostrClient;
}
export function clearNostrClientRegistration() {
registeredNostrClient = null;
requestPermissionsDelegate = null;
}
export function requestDefaultExtensionPermissions(...args) {
if (typeof requestPermissionsDelegate === "function") {
try {
return requestPermissionsDelegate(...args);
} catch (error) {
return Promise.resolve({ ok: false, error });
}
}
const error = new Error("Nostr client is not registered.");
return Promise.resolve({ ok: false, error });
}
export function registerSigner(pubkey, signer, meta = {}) {
const normalized = normalizePubkey(pubkey);
if (!normalized) {
return null;
}
const previous = registeredSigners.get(normalized) || null;
if (!signer || typeof signer !== "object") {
if (previous) {
registeredSigners.delete(normalized);
emitSignerEvent("signer:disconnected", {
pubkey: normalized,
signer: previous.signer,
meta: previous.meta,
});
if (activeSignerPubkey === normalized) {
updateActiveSigner(null, "");
}
}
return null;
}
const sanitizedMeta =
meta && typeof meta === "object" ? { ...meta } : {};
registeredSigners.set(normalized, { signer, meta: sanitizedMeta });
if (!previous) {
emitSignerEvent("signer:connected", {
pubkey: normalized,
signer,
meta: sanitizedMeta,
});
} else if (hasPermissionChanges(previous.meta, sanitizedMeta)) {
emitSignerEvent("signer:permissions:changed", {
pubkey: normalized,
signer,
previousMeta: previous.meta,
meta: sanitizedMeta,
});
}
if (activeSignerPubkey === normalized) {
updateActiveSigner(signer, normalized);
}
return signer;
}
export function setActiveSigner(signerOrPubkey) {
if (!signerOrPubkey) {
updateActiveSigner(null, "");
return null;
}
if (typeof signerOrPubkey === "string") {
const normalized = normalizePubkey(signerOrPubkey);
const entry = registeredSigners.get(normalized);
if (entry) {
updateActiveSigner(entry.signer, normalized);
return entry.signer;
}
// If not found, do not clear active signer just yet?
// Or should we?
updateActiveSigner(null, normalized);
return null;
}
if (typeof signerOrPubkey === "object") {
const pubkey = normalizePubkey(signerOrPubkey.pubkey);
if (pubkey) {
// Force update of the registry for this pubkey
registeredSigners.set(pubkey, { signer: signerOrPubkey, meta: {} });
// emitSignerEvent("signer:connected", ...); // Skip for now to keep diff small
}
updateActiveSigner(signerOrPubkey, pubkey);
return signerOrPubkey;
}
return null;
}
export function clearActiveSigner() {
updateActiveSigner(null, "");
}
export function getActiveSigner() {
return activeSigner;
}
export function logoutSigner(pubkey) {
const normalized = normalizePubkey(pubkey);
if (normalized) {
const entry = registeredSigners.get(normalized);
if (entry?.signer && typeof entry.signer.destroy === "function") {
try {
entry.signer.destroy();
} catch (error) {
// Ignore signer teardown failures.
}
}
registerSigner(normalized, null);
return;
}
if (activeSigner) {
const activePubkey = normalizePubkey(activeSigner.pubkey || activeSignerPubkey);
if (activePubkey) {
logoutSigner(activePubkey);
return;
}
}
updateActiveSigner(null, "");
}
export function resolveActiveSigner(pubkey) {
const normalized = normalizePubkey(pubkey);
if (normalized) {
const entry = registeredSigners.get(normalized);
if (entry?.signer) {
return entry.signer;
}
if (
activeSigner &&
typeof activeSigner.pubkey === "string" &&
normalizePubkey(activeSigner.pubkey) === normalized
) {
return activeSigner;
}
}
return activeSigner;
}
export function listRegisteredSigners() {
return Array.from(registeredSigners.entries()).map(([pubkey, entry]) => ({
pubkey,
signer: entry.signer,
meta: entry.meta,
}));
}
export function onActiveSignerChanged(callback) {
if (typeof callback === "function") {
activeSignerListeners.add(callback);
}
}
export function offActiveSignerChanged(callback) {
activeSignerListeners.delete(callback);
}