mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Serialize nostr signEvent requests
This commit is contained in:
1 parent
745eb6ac48
commit
95bb462508
7 files changed
+155
-9
No files matched your search
+5
-4
@@ -173,6 +173,7 @@ import {
|
||||
import { createNip07Adapter } from "./adapters/nip07Adapter.js";
|
||||
import { createNsecAdapter } from "./adapters/nsecAdapter.js";
|
||||
import { createNip46Adapter } from "./adapters/nip46Adapter.js";
|
||||
import { queueSignEvent } from "./signRequestQueue.js";
|
||||
|
||||
function normalizeProfileFromEvent(event) {
|
||||
if (!event || !event.content) return null;
|
||||
@@ -4484,7 +4485,7 @@ export class NostrClient {
|
||||
|
||||
let signedEvent;
|
||||
try {
|
||||
signedEvent = await signer.signEvent(event);
|
||||
signedEvent = await queueSignEvent(signer, event);
|
||||
} catch (error) {
|
||||
return { ok: false, error: "signature-failed", details: error };
|
||||
}
|
||||
@@ -5065,7 +5066,7 @@ export class NostrClient {
|
||||
}
|
||||
|
||||
try {
|
||||
const signedEvent = await signer.signEvent(event);
|
||||
const signedEvent = await queueSignEvent(signer, event);
|
||||
devLogger.log("Signed edited event:", signedEvent);
|
||||
|
||||
const publishResults = await publishEventToRelays(
|
||||
@@ -5238,7 +5239,7 @@ export class NostrClient {
|
||||
}
|
||||
}
|
||||
|
||||
const signedEvent = await signer.signEvent(event);
|
||||
const signedEvent = await queueSignEvent(signer, event);
|
||||
const publishResults = await publishEventToRelays(
|
||||
this.pool,
|
||||
this.relays,
|
||||
@@ -5551,7 +5552,7 @@ export class NostrClient {
|
||||
: "Delete published video events",
|
||||
};
|
||||
|
||||
const signedDelete = await signer.signEvent(deleteEvent);
|
||||
const signedDelete = await queueSignEvent(signer, deleteEvent);
|
||||
const publishResults = await publishEventToRelays(
|
||||
this.pool,
|
||||
this.relays,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import { LRUCache } from "../utils/lruCache.js";
|
||||
import { CACHE_POLICIES } from "./cachePolicies.js";
|
||||
import { isSessionActor } from "./sessionActor.js";
|
||||
import { queueSignEvent } from "./signRequestQueue.js";
|
||||
|
||||
const COMMENT_EVENT_SCHEMA = getNostrEventSchema(NOTE_TYPES.VIDEO_COMMENT);
|
||||
const CACHE_POLICY = CACHE_POLICIES[NOTE_TYPES.VIDEO_COMMENT];
|
||||
@@ -1221,7 +1222,9 @@ export async function publishComment(
|
||||
}
|
||||
|
||||
try {
|
||||
signedEvent = await signer.signEvent(event);
|
||||
signedEvent = await queueSignEvent(signer, event, {
|
||||
timeoutMs: options?.timeoutMs,
|
||||
});
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
"[nostr] Failed to sign comment event with active signer:",
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { DEFAULT_NIP07_PERMISSION_METHODS } from "./nip07Permissions.js";
|
||||
import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import { logRebroadcastCountFailure } from "./countDiagnostics.js";
|
||||
import { queueSignEvent } from "./signRequestQueue.js";
|
||||
|
||||
const REBROADCAST_GUARD_PREFIX = "bitvid:rebroadcast:v1";
|
||||
const rebroadcastAttemptMemory = new Map();
|
||||
@@ -359,7 +360,9 @@ export async function signAndPublishEvent({
|
||||
}
|
||||
if (permissionResult.ok) {
|
||||
try {
|
||||
signedEvent = await signer.signEvent(event);
|
||||
signedEvent = await queueSignEvent(signer, event, {
|
||||
timeoutMs: options.timeoutMs,
|
||||
});
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
"[nostr] Failed to sign event with active signer:",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import { LRUCache } from "../utils/lruCache.js";
|
||||
import { CACHE_POLICIES } from "./cachePolicies.js";
|
||||
import { NOTE_TYPES } from "../nostrEventSchemas.js";
|
||||
import { queueSignEvent } from "./signRequestQueue.js";
|
||||
|
||||
const CACHE_POLICY = CACHE_POLICIES[NOTE_TYPES.VIDEO_REACTION];
|
||||
const reactionCache = new LRUCache({ maxSize: 100 });
|
||||
@@ -441,7 +442,9 @@ export async function publishVideoReaction(
|
||||
}
|
||||
if (permissionResult.ok) {
|
||||
try {
|
||||
signedEvent = await signer.signEvent(event);
|
||||
signedEvent = await queueSignEvent(signer, event, {
|
||||
timeoutMs: options?.timeoutMs,
|
||||
});
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
"[nostr] Failed to sign reaction event with active signer:",
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { devLogger } from "../utils/logger.js";
|
||||
|
||||
const DEFAULT_SIGN_TIMEOUT_MS = 20_000;
|
||||
const signQueues = new WeakMap();
|
||||
|
||||
const SIGN_ERROR_CODES = new Set([
|
||||
"permission-denied",
|
||||
"timeout",
|
||||
"not-capable",
|
||||
"signer-disconnected",
|
||||
]);
|
||||
|
||||
function resolveTimeoutMs(timeoutMs) {
|
||||
const candidate = Number(timeoutMs);
|
||||
if (Number.isFinite(candidate) && candidate > 0) {
|
||||
return Math.floor(candidate);
|
||||
}
|
||||
return DEFAULT_SIGN_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
function toError(value, message) {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
const error = new Error(message);
|
||||
error.details = value;
|
||||
return error;
|
||||
}
|
||||
|
||||
function normalizeSignError(error) {
|
||||
const normalized = toError(error, "signEvent failed");
|
||||
const existingCode = typeof normalized.code === "string" ? normalized.code : "";
|
||||
if (SIGN_ERROR_CODES.has(existingCode)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const message = typeof normalized.message === "string"
|
||||
? normalized.message.toLowerCase()
|
||||
: "";
|
||||
|
||||
if (
|
||||
existingCode === "extension-permission-denied" ||
|
||||
message.includes("permission") ||
|
||||
message.includes("denied") ||
|
||||
message.includes("rejected")
|
||||
) {
|
||||
normalized.code = "permission-denied";
|
||||
return normalized;
|
||||
}
|
||||
|
||||
if (
|
||||
existingCode === "nostr-extension-missing" ||
|
||||
message.includes("disconnected") ||
|
||||
message.includes("not connected") ||
|
||||
message.includes("connection closed") ||
|
||||
message.includes("extension missing")
|
||||
) {
|
||||
normalized.code = "signer-disconnected";
|
||||
return normalized;
|
||||
}
|
||||
|
||||
if (existingCode === "sign-event-unavailable") {
|
||||
normalized.code = "not-capable";
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function createSignError(code, originalError) {
|
||||
const error = toError(
|
||||
originalError,
|
||||
originalError?.message || `signEvent failed with ${code}`,
|
||||
);
|
||||
error.code = code;
|
||||
if (originalError && originalError !== error) {
|
||||
error.details = originalError;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
function runWithTimeout(operation, timeoutMs) {
|
||||
const resolvedTimeout = resolveTimeoutMs(timeoutMs);
|
||||
let timeoutId;
|
||||
let timedOut = false;
|
||||
const signPromise = Promise.resolve().then(operation);
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
timedOut = true;
|
||||
reject(createSignError("timeout", new Error(
|
||||
`signEvent timed out after ${resolvedTimeout}ms`,
|
||||
)));
|
||||
}, resolvedTimeout);
|
||||
});
|
||||
|
||||
return Promise.race([signPromise, timeoutPromise]).finally(() => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
if (timedOut) {
|
||||
void signPromise.catch((error) => {
|
||||
devLogger.warn("[nostr] signEvent resolved after timeout:", error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function queueSignEvent(signer, event, options = {}) {
|
||||
if (!signer || typeof signer !== "object") {
|
||||
throw createSignError("not-capable", new Error("signer-unavailable"));
|
||||
}
|
||||
|
||||
if (typeof signer.signEvent !== "function") {
|
||||
throw createSignError("not-capable", new Error("sign-event-unavailable"));
|
||||
}
|
||||
|
||||
const queue = signQueues.get(signer) || Promise.resolve();
|
||||
const runSign = async () => runWithTimeout(
|
||||
() => signer.signEvent(event),
|
||||
options.timeoutMs,
|
||||
);
|
||||
const queued = queue.then(runSign, runSign);
|
||||
signQueues.set(signer, queued.catch(() => {}));
|
||||
|
||||
try {
|
||||
return await queued;
|
||||
} catch (error) {
|
||||
throw normalizeSignError(error);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { RELAY_URLS } from "./toolkit.js";
|
||||
import { normalizePointerInput } from "./watchHistory.js";
|
||||
import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import { logViewCountFailure } from "./countDiagnostics.js";
|
||||
import { queueSignEvent } from "./signRequestQueue.js";
|
||||
|
||||
const VIEW_EVENT_GUARD_PREFIX = "bitvid:viewed";
|
||||
|
||||
@@ -793,7 +794,9 @@ export async function publishViewEvent(
|
||||
}
|
||||
if (permissionResult.ok) {
|
||||
try {
|
||||
signedEvent = await signer.signEvent(event);
|
||||
signedEvent = await queueSignEvent(signer, event, {
|
||||
timeoutMs: options?.timeoutMs,
|
||||
});
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
"[nostr] Failed to sign view event with active signer:",
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import { DEFAULT_NIP07_PERMISSION_METHODS } from "./nip07Permissions.js";
|
||||
import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import { profileCache } from "../state/profileCache.js";
|
||||
import { queueSignEvent } from "./signRequestQueue.js";
|
||||
|
||||
/**
|
||||
* Domain utilities for watch-history interactions. This module owns pointer
|
||||
@@ -1452,7 +1453,9 @@ class WatchHistoryManager {
|
||||
|
||||
const signEvent = async (event) => {
|
||||
if (activeSigner) {
|
||||
return activeSigner.signEvent(event);
|
||||
return queueSignEvent(activeSigner, event, {
|
||||
timeoutMs: options.timeoutMs,
|
||||
});
|
||||
}
|
||||
return this.deps.signEventWithPrivateKey(event, privateKey);
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user