mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
131 lines
3.3 KiB
JavaScript
131 lines
3.3 KiB
JavaScript
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);
|
|
}
|
|
}
|