Merge pull request #205 from PR0M3TH3AN/codex/fix-console-warnings-and-errors

Handle nostr readiness and count fallback
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-01 11:56:18 -04:00
commit 2f1193dbf2
2 files changed
+147 -16

No files matched your search

+45 -13
View File
@@ -194,8 +194,27 @@ function clearLegacyStorageFor(listKey) {
}
}
function ensureNostrReady() {
if (!nostrClient || !nostrClient.pool) {
async function ensureNostrReady() {
if (!nostrClient) {
throw createError(
"nostr-unavailable",
"Nostr relay pool is not initialized."
);
}
if (typeof nostrClient.waitForPool === "function") {
try {
await nostrClient.waitForPool();
} catch (error) {
throw createError(
"nostr-unavailable",
"Nostr relay pool is not initialized.",
error
);
}
}
if (!nostrClient.pool) {
throw createError(
"nostr-unavailable",
"Nostr relay pool is not initialized."
@@ -257,7 +276,7 @@ function extractNpubsFromEvent(event) {
}
async function loadNostrList(identifier) {
const relays = ensureNostrReady();
const relays = await ensureNostrReady();
const dTagValue = `${ADMIN_LIST_NAMESPACE}:${identifier}`;
const filter = {
kinds: [30000],
@@ -401,14 +420,27 @@ function publishToRelay(url, signedEvent, listKey) {
}
if (typeof pub.on === "function") {
pub.on("ok", () => finalize(true));
pub.on("seen", () => finalize(true));
pub.on("failed", (reason) => {
const error =
reason instanceof Error ? reason : new Error(String(reason || "failed"));
finalize(false, error);
});
return;
try {
pub.on("ok", () => finalize(true));
pub.on("seen", () => finalize(true));
pub.on("failed", (reason) => {
const error =
reason instanceof Error
? reason
: new Error(String(reason || "failed"));
finalize(false, error);
});
return;
} catch (listenerError) {
if (isDevMode) {
console.warn(
`[adminListStore] Failed to wire publish listeners for ${url}:`,
listenerError
);
}
finalize(true);
return;
}
}
if (typeof pub.then === "function") {
@@ -433,7 +465,7 @@ function publishToRelay(url, signedEvent, listKey) {
}
async function persistNostrState(actorNpub, updates = {}) {
ensureNostrReady();
await ensureNostrReady();
const extension = window?.nostr;
if (!extension || typeof extension.signEvent !== "function") {
throw createError(
@@ -510,7 +542,7 @@ async function persistNostrState(actorNpub, updates = {}) {
throw createError("signature-failed", "Failed to sign admin list event.", error);
}
const relays = ensureNostrReady();
const relays = await ensureNostrReady();
const publishResults = await Promise.all(
relays.map((url) => publishToRelay(url, signedEvent, listKey))
);
+102 -3
View File
@@ -36,6 +36,7 @@ const NIP07_LOGIN_TIMEOUT_ERROR_MESSAGE =
"Timed out waiting for the NIP-07 extension. Check the extension prompt and try again.";
const SESSION_ACTOR_STORAGE_KEY = "bitvid:sessionActor:v1";
const WATCH_HISTORY_CACHE_STORAGE_KEY = "bitvid:watchHistoryCache:v1";
const COUNT_UNSUPPORTED_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes
// To limit error spam
let errorLogCount = 0;
@@ -55,6 +56,36 @@ function logErrorOnce(message, eventContent = null) {
}
}
function createDeferred() {
let resolveFn;
let rejectFn;
let state = "pending";
const promise = new Promise((resolve, reject) => {
resolveFn = (value) => {
if (state !== "pending") {
return;
}
state = "fulfilled";
resolve(value);
};
rejectFn = (error) => {
if (state !== "pending") {
return;
}
state = "rejected";
reject(error);
};
});
return {
promise,
resolve: resolveFn,
reject: rejectFn,
isSettled: () => state !== "pending",
getState: () => state,
};
}
function withNip07Timeout(operation) {
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
@@ -1108,6 +1139,9 @@ class NostrClient {
this.watchHistoryRepublishTimers = new Map();
this.watchHistoryCacheTtlMs = WATCH_HISTORY_CACHE_TTL_MS;
this.countRequestCounter = 0;
this.countUnsupportedRelays = new Map();
this._poolReadyDeferred = createDeferred();
}
restoreLocalData() {
@@ -3597,13 +3631,19 @@ class NostrClient {
.filter((r) => r.success)
.map((r) => r.url);
if (successfulRelays.length === 0) {
throw new Error("No relays connected");
const error = new Error("No relays connected");
this._poolReadyDeferred.reject(error);
this.pool = null;
throw error;
}
if (isDevMode) {
console.log(`Connected to ${successfulRelays.length} relay(s)`);
}
this._poolReadyDeferred.resolve();
} catch (err) {
console.error("Nostr init failed:", err);
this._poolReadyDeferred.reject(err);
this.pool = null;
throw err;
}
}
@@ -4699,6 +4739,46 @@ class NostrClient {
return normalized;
}
markCountUnsupported(relayUrl, ttlMs = COUNT_UNSUPPORTED_COOLDOWN_MS) {
const normalizedUrl = typeof relayUrl === "string" ? relayUrl.trim() : "";
if (!normalizedUrl) {
return;
}
const now = Date.now();
const expiry = now + Math.max(0, Number(ttlMs) || 0);
this.countUnsupportedRelays.set(normalizedUrl, expiry);
}
isCountTemporarilyUnsupported(relayUrl) {
const normalizedUrl = typeof relayUrl === "string" ? relayUrl.trim() : "";
if (!normalizedUrl) {
return false;
}
const expiry = this.countUnsupportedRelays.get(normalizedUrl);
if (!expiry) {
return false;
}
if (Date.now() >= expiry) {
this.countUnsupportedRelays.delete(normalizedUrl);
return false;
}
return true;
}
waitForPool(timeoutMs) {
if (this.pool && this._poolReadyDeferred.isSettled()) {
return Promise.resolve();
}
const promise = this._poolReadyDeferred.promise;
const numericTimeout = Number(timeoutMs);
if (!Number.isFinite(numericTimeout) || numericTimeout <= 0) {
return promise;
}
return withRequestTimeout(promise, numericTimeout, null, "Timed out waiting for Nostr pool readiness");
}
generateCountRequestId(prefix = "count") {
this.countRequestCounter += 1;
if (this.countRequestCounter > Number.MAX_SAFE_INTEGER - 1) {
@@ -4818,6 +4898,7 @@ class NostrClient {
} else if (typeof relay.count === "function") {
countPromise = relay.count(normalizedFilters, { id: requestId });
} else {
this.markCountUnsupported(normalizedUrl);
throw new Error(
`[nostr] Relay ${normalizedUrl} does not support COUNT frames.`
);
@@ -4854,6 +4935,13 @@ class NostrClient {
const perRelayResults = await Promise.all(
relayList.map(async (url) => {
if (this.isCountTemporarilyUnsupported(url)) {
return {
url,
ok: false,
error: new Error("COUNT unsupported (cached)"),
};
}
try {
const frame = await this.sendRawCountFrame(url, normalizedFilters, {
timeoutMs: options.timeoutMs,
@@ -4861,8 +4949,19 @@ class NostrClient {
const count = this.extractCountValue(frame?.[2]);
return { url, ok: true, frame, count };
} catch (error) {
if (isDevMode) {
console.warn(`[nostr] COUNT request failed on ${url}:`, error);
if (
error &&
typeof error.message === "string" &&
error.message.includes("does not support COUNT frames")
) {
this.markCountUnsupported(url);
logErrorOnce(`[nostr] COUNT disabled for ${url} (no support).`);
} else if (isDevMode) {
const message =
error && typeof error.message === "string"
? error.message
: String(error);
logErrorOnce(`[nostr] COUNT request failed on ${url}: ${message}`);
}
return { url, ok: false, error };
}