Refactor numeric constants to centralized module

- Introduce `js/constants.js` with semantic time constants (e.g., `ONE_MINUTE_MS`, `STANDARD_TIMEOUT_MS`) and feature-specific TTLs.
- Export `KIND_MUTE_LIST` from `js/nostrEventSchemas.js` to standardize NIP-51 mute list kind usage.
- Refactor `js/state/cache.js`, `js/nostr/cachePolicies.js`, `js/channelProfile.js`, and various services to use centralized constants instead of hardcoded magic numbers.
- Update `js/nostr/relayConstants.js` to derive values from the shared time constants.
- Replace duplicate definitions of timeouts and retry delays across the codebase.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-11 03:27:26 +00:00
1 parent 83d8023767
commit c47392aba6
17 files changed
+92 -47

No files matched your search

+3 -3
View File
@@ -9,7 +9,7 @@
* Methods use `this` which is bound to the Application instance.
*/
import { FEED_TYPES } from "../constants.js";
import { FEED_TYPES, STANDARD_TIMEOUT_MS, MEDIUM_TIMEOUT_MS } from "../constants.js";
/**
* @param {object} deps - Injected dependencies.
@@ -926,7 +926,7 @@ export function createFeedCoordinator(deps) {
// Give the app a grace period to establish initial connections before complaining.
const now = Date.now();
const GRACE_PERIOD_MS = 10000;
const GRACE_PERIOD_MS = STANDARD_TIMEOUT_MS;
if (now - (this.appStartedAt || now) < GRACE_PERIOD_MS) {
return false;
}
@@ -941,7 +941,7 @@ export function createFeedCoordinator(deps) {
return false;
}
const cooldownMs = 30000;
const cooldownMs = MEDIUM_TIMEOUT_MS;
if (now - (this.lastRelayHealthWarningAt || 0) < cooldownMs) {
return true;
}
+2 -1
View File
@@ -26,6 +26,7 @@ import {
DEFAULT_BLUR_THRESHOLD,
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
FIVE_MINUTES_MS,
} from "./constants.js";
import {
applyModerationContextDatasets,
@@ -1310,7 +1311,7 @@ let currentProfileLoadToken = 0;
const FALLBACK_CHANNEL_BANNER = "assets/jpg/bitvid.jpg";
const FALLBACK_CHANNEL_AVATAR = "assets/svg/default-profile.svg";
const PROFILE_EVENT_CACHE_TTL_MS = 5 * 60 * 1000;
const PROFILE_EVENT_CACHE_TTL_MS = FIVE_MINUTES_MS;
const channelProfileMetadataCache = new Map();
+21
View File
@@ -9,6 +9,27 @@ import {
IS_DEV_MODE,
} from "./config.js";
// Time constants in milliseconds
export const ONE_SECOND_MS = 1000;
export const ONE_MINUTE_MS = 60 * ONE_SECOND_MS;
export const FIVE_MINUTES_MS = 5 * ONE_MINUTE_MS;
export const TEN_MINUTES_MS = 10 * ONE_MINUTE_MS;
export const FORTY_FIVE_MINUTES_MS = 45 * ONE_MINUTE_MS;
export const ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
export const ONE_DAY_MS = 24 * ONE_HOUR_MS;
// Specific timeouts and TTLs
export const SHORT_TIMEOUT_MS = 5000;
export const STANDARD_TIMEOUT_MS = 10000;
export const MEDIUM_TIMEOUT_MS = 30000;
export const LONG_TIMEOUT_MS = 60000;
export const DEFAULT_CACHE_TTL_MS = FIVE_MINUTES_MS;
export const PROFILE_CACHE_TTL_MS = TEN_MINUTES_MS;
export const URL_HEALTH_TTL_MS = FORTY_FIVE_MINUTES_MS;
export const URL_HEALTH_RETRY_MS = FIVE_MINUTES_MS;
export const URL_PROBE_TIMEOUT_MS = 8 * ONE_SECOND_MS;
function coerceNonNegativeInteger(value, fallback) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) {
+2 -1
View File
@@ -13,6 +13,7 @@ import {
getViewportCenter,
PRIORITY_BASELINE,
} from "./gridHealthLogic.js";
import { FIVE_MINUTES_MS } from "./constants.js";
const badgeUpdateListeners = new Set();
@@ -227,7 +228,7 @@ function queueProbe(magnet, cacheKey, priority = 0, webSeeds = []) {
return job;
}
const PROBE_CACHE_TTL_MS = 5 * 60 * 1000;
const PROBE_CACHE_TTL_MS = FIVE_MINUTES_MS;
const PROBE_TIMEOUT_MS = 20000;
const PROBE_CONCURRENCY = 96;
const PROBE_POLL_COUNT = 3;
+5 -4
View File
@@ -1,5 +1,6 @@
import { NOTE_TYPES } from "../nostrEventSchemas.js";
import { WATCH_HISTORY_LIST_IDENTIFIER } from "../config.js";
import { FIVE_MINUTES_MS, TEN_MINUTES_MS, ONE_DAY_MS } from "../constants.js";
export const STORAGE_TIERS = Object.freeze({
MEMORY: "memory",
@@ -15,13 +16,13 @@ export const MERGE_STRATEGIES = Object.freeze({
export const CACHE_POLICIES = Object.freeze({
[NOTE_TYPES.VIDEO_POST]: {
storage: STORAGE_TIERS.INDEXED_DB,
ttl: 10 * 60 * 1000, // 10 minutes
ttl: TEN_MINUTES_MS,
addressing: "kind:pubkey:d",
merge: MERGE_STRATEGIES.REPLACEABLE,
},
[NOTE_TYPES.WATCH_HISTORY]: {
storage: STORAGE_TIERS.LOCAL_STORAGE,
ttl: 24 * 60 * 60 * 1000, // 24 hours
ttl: ONE_DAY_MS,
addressing: "kind:pubkey:d",
defaultDTag: WATCH_HISTORY_LIST_IDENTIFIER,
merge: MERGE_STRATEGIES.APPEND_ONLY, // Bucketed append
@@ -64,13 +65,13 @@ export const CACHE_POLICIES = Object.freeze({
},
[NOTE_TYPES.VIDEO_COMMENT]: {
storage: STORAGE_TIERS.MEMORY,
ttl: 5 * 60 * 1000, // 5 minutes
ttl: FIVE_MINUTES_MS,
addressing: "id",
merge: MERGE_STRATEGIES.APPEND_ONLY,
},
[NOTE_TYPES.VIDEO_REACTION]: {
storage: STORAGE_TIERS.MEMORY,
ttl: 5 * 60 * 1000, // 5 minutes
ttl: FIVE_MINUTES_MS,
addressing: "id",
merge: MERGE_STRATEGIES.APPEND_ONLY,
},
+6 -5
View File
@@ -26,6 +26,7 @@ import {
RELAY_FAILURE_WINDOW_THRESHOLD,
RELAY_SUMMARY_LOG_INTERVAL_MS,
} from "../relayConstants.js";
import { LONG_TIMEOUT_MS } from "../../constants.js";
function withRequestTimeout(promise, timeoutMs, onTimeout, message = "Request timed out") {
const resolvedTimeout = Number(timeoutMs);
@@ -210,7 +211,7 @@ export class ConnectionManager {
if (result.success) {
this.clearRelayBackoff(result.url);
} else {
this.markRelayUnreachable(result.url, 60000, {
this.markRelayUnreachable(result.url, LONG_TIMEOUT_MS, {
reason: "connect-timeout",
});
if (isDevMode) {
@@ -367,7 +368,7 @@ export class ConnectionManager {
return filtered.length;
}
markRelayUnreachable(url, ttlMs = 60000, { reason = null } = {}) {
markRelayUnreachable(url, ttlMs = LONG_TIMEOUT_MS, { reason = null } = {}) {
if (!url || typeof url !== "string") {
return;
}
@@ -722,7 +723,7 @@ export class ConnectionManager {
`Failed to connect to relay ${normalizedUrl}`
);
connectError.code = "relay-connect-failed";
this.markRelayUnreachable(normalizedUrl, 60000, {
this.markRelayUnreachable(normalizedUrl, LONG_TIMEOUT_MS, {
reason: "connect-failed",
});
throw connectError;
@@ -733,7 +734,7 @@ export class ConnectionManager {
`Relay ${normalizedUrl} is unavailable for COUNT requests.`
);
relayError.code = "relay-unavailable";
this.markRelayUnreachable(normalizedUrl, 60000, {
this.markRelayUnreachable(normalizedUrl, LONG_TIMEOUT_MS, {
reason: "relay-unavailable",
});
throw relayError;
@@ -880,7 +881,7 @@ export class ConnectionManager {
// because it might still be good for subscriptions.
// We only trip the circuit breaker for hard errors.
if (!isTimeout) {
this.markRelayUnreachable(url, 60000, {
this.markRelayUnreachable(url, LONG_TIMEOUT_MS, {
reason: "count-error",
});
} else if (isDevMode) {
+15 -6
View File
@@ -2,14 +2,23 @@
// Canonical relay timing and circuit-breaker constants.
// Import from here — do NOT duplicate these values in other modules.
export const RELAY_CONNECT_TIMEOUT_MS = 5000;
import {
SHORT_TIMEOUT_MS,
LONG_TIMEOUT_MS,
ONE_SECOND_MS,
TEN_MINUTES_MS,
FIVE_MINUTES_MS,
MEDIUM_TIMEOUT_MS,
} from "../constants.js";
export const RELAY_CONNECT_TIMEOUT_MS = SHORT_TIMEOUT_MS;
export const RELAY_RECONNECT_BASE_DELAY_MS = 2000;
export const RELAY_RECONNECT_MAX_DELAY_MS = 60000;
export const RELAY_RECONNECT_MAX_DELAY_MS = LONG_TIMEOUT_MS;
export const RELAY_RECONNECT_MAX_ATTEMPTS = 5;
export const RELAY_BACKOFF_BASE_DELAY_MS = 1000;
export const RELAY_BACKOFF_BASE_DELAY_MS = ONE_SECOND_MS;
export const RELAY_BACKOFF_MAX_DELAY_MS = 8000;
export const RELAY_CIRCUIT_BREAKER_THRESHOLD = 3;
export const RELAY_CIRCUIT_BREAKER_COOLDOWN_MS = 10 * 60 * 1000;
export const RELAY_FAILURE_WINDOW_MS = 5 * 60 * 1000;
export const RELAY_CIRCUIT_BREAKER_COOLDOWN_MS = TEN_MINUTES_MS;
export const RELAY_FAILURE_WINDOW_MS = FIVE_MINUTES_MS;
export const RELAY_FAILURE_WINDOW_THRESHOLD = 3;
export const RELAY_SUMMARY_LOG_INTERVAL_MS = 30000;
export const RELAY_SUMMARY_LOG_INTERVAL_MS = MEDIUM_TIMEOUT_MS;
+5 -3
View File
@@ -50,6 +50,8 @@ export const NOTE_TYPES = Object.freeze({
CHAT_MESSAGE: "chatMessage",
});
export const KIND_MUTE_LIST = 10000;
export const SUBSCRIPTION_LIST_IDENTIFIER = "subscriptions";
export const BLOCK_LIST_IDENTIFIER = "user-blocks";
export const ADMIN_LIST_IDENTIFIERS = Object.freeze({
@@ -540,7 +542,7 @@ const BASE_SCHEMAS = {
[NOTE_TYPES.USER_BLOCK_LIST]: {
type: NOTE_TYPES.USER_BLOCK_LIST,
label: "User block list (legacy)",
kind: 10000,
kind: KIND_MUTE_LIST,
identifierTag: {
name: "d",
value: BLOCK_LIST_IDENTIFIER,
@@ -616,7 +618,7 @@ const BASE_SCHEMAS = {
[NOTE_TYPES.MUTE_LIST]: {
type: NOTE_TYPES.MUTE_LIST,
label: "Mute list",
kind: 10000,
kind: KIND_MUTE_LIST,
participantTagName: "p",
appendTags: DEFAULT_APPEND_TAGS,
content: {
@@ -1704,7 +1706,7 @@ export function buildMuteListEvent(params) {
}
const event = {
kind: schema?.kind ?? 10000,
kind: schema?.kind ?? KIND_MUTE_LIST,
pubkey,
created_at,
tags,
+2 -1
View File
@@ -6,9 +6,10 @@ import { setProfileCacheEntry } from "../state/cache.js";
import { nostrToolsReady } from "../nostrToolsBootstrap.js";
import { userLogger } from "../utils/logger.js";
import { HEX64_REGEX } from "../utils/hex.js";
import { TEN_MINUTES_MS } from "../constants.js";
const CACHE_TTL_MS = 10 * 60 * 1000;
const CACHE_TTL_MS = TEN_MINUTES_MS;
let cachedAddress = null;
+2 -1
View File
@@ -4,8 +4,9 @@ import {
fetchPayServiceData as defaultFetchPayServiceData,
validateInvoiceAmount,
} from "./lnurl.js";
import { FIVE_MINUTES_MS } from "../constants.js";
const METADATA_CACHE_TTL_MS = 5 * 60 * 1000;
const METADATA_CACHE_TTL_MS = FIVE_MINUTES_MS;
const lightningMetadataCache = new Map();
const lightningMetadataByUrl = new Map();
+2 -2
View File
@@ -4,7 +4,7 @@ import logger, { devLogger, userLogger } from "../utils/logger.js";
import { normalizeHexId, normalizeHexPubkey } from "../utils/hex.js";
import { buildVideoAddressPointer } from "../utils/videoPointer.js";
import { COMMENT_EVENT_KIND } from "../nostr/commentEvents.js";
import { FEATURE_IMPROVED_COMMENT_FETCHING } from "../constants.js";
import { FEATURE_IMPROVED_COMMENT_FETCHING, FIVE_MINUTES_MS } from "../constants.js";
const ROOT_PARENT_KEY = "__root__";
const DEFAULT_INITIAL_LIMIT = 40;
@@ -12,7 +12,7 @@ const DEFAULT_HYDRATION_DEBOUNCE_MS = 25;
const PROFILE_FETCH_MAX_ATTEMPTS = 3;
const PROFILE_FETCH_BACKOFF_MS = 50;
const COMMENT_CACHE_PREFIX = "bitvid:comments:";
const COMMENT_CACHE_TTL_MS = 5 * 60 * 1000;
const COMMENT_CACHE_TTL_MS = FIVE_MINUTES_MS;
const COMMENT_CACHE_VERSION = 2;
function toPositiveInteger(value, fallback) {
+3 -2
View File
@@ -1,7 +1,8 @@
import { devLogger } from "../utils/logger.js";
import { FIVE_MINUTES_MS, ONE_MINUTE_MS } from "../constants.js";
const DEFAULT_IDF_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
const DEFAULT_HISTORY_REFRESH_INTERVAL_MS = 60 * 1000;
const DEFAULT_IDF_REFRESH_INTERVAL_MS = FIVE_MINUTES_MS;
const DEFAULT_HISTORY_REFRESH_INTERVAL_MS = ONE_MINUTE_MS;
const DEFAULT_REFRESH_DEBOUNCE_MS = 200;
let workerInstance = null;
+4 -4
View File
@@ -6,7 +6,7 @@ import {
import { publishEventToRelays, assertAnyRelayAccepted } from "../nostrPublish.js";
import { accessControl } from "../accessControl.js";
import { userBlocks, USER_BLOCK_EVENTS } from "../userBlocks.js";
import { buildReportEvent } from "../nostrEventSchemas.js";
import { buildReportEvent, KIND_MUTE_LIST } from "../nostrEventSchemas.js";
import logger from "../utils/logger.js";
/**
@@ -1182,7 +1182,7 @@ export class ModerationService {
this.log(`[moderationService] Refreshing mute subscriptions for ${authors.length} authors in ${chunks.length} batches`);
for (const chunk of chunks) {
const filter = { kinds: [10000], authors: chunk };
const filter = { kinds: [KIND_MUTE_LIST], authors: chunk };
try {
const sub = this.nostrClient.pool.sub(relays, [filter]);
sub.on("event", (event) => {
@@ -1347,7 +1347,7 @@ export class ModerationService {
return;
}
if (!event || event.kind !== 10000) {
if (!event || event.kind !== KIND_MUTE_LIST) {
this.replaceTrustedMuteList(owner, new Set(), { createdAt: 0, eventId: "" });
return;
}
@@ -1387,7 +1387,7 @@ export class ModerationService {
}
ingestTrustedMuteEvent(event) {
if (!event || event.kind !== 10000) {
if (!event || event.kind !== KIND_MUTE_LIST) {
return;
}
+4 -3
View File
@@ -1,9 +1,10 @@
import { devLogger, userLogger } from "../utils/logger.js";
import { FIVE_MINUTES_MS, SHORT_TIMEOUT_MS, LONG_TIMEOUT_MS } from "../constants.js";
const TELEMETRY_STORAGE_KEY = "bitvid:relay-health-telemetry-opt-in";
const PERSISTENT_FAILURE_THRESHOLD = 3;
const USER_LOG_COOLDOWN_MS = 5 * 60 * 1000;
const DEFAULT_TIMEOUT_MS = 5000;
const USER_LOG_COOLDOWN_MS = FIVE_MINUTES_MS;
const DEFAULT_TIMEOUT_MS = SHORT_TIMEOUT_MS;
function resolveLogger(logger) {
if (logger && logger.dev && logger.user) {
@@ -154,7 +155,7 @@ class RelayHealthService {
state.lastErrorAt = now;
if (this.nostrClient && typeof this.nostrClient.markRelayUnreachable === "function") {
this.nostrClient.markRelayUnreachable(relayUrl, 60000, {
this.nostrClient.markRelayUnreachable(relayUrl, LONG_TIMEOUT_MS, {
reason: "relay-health-failed",
});
}
+6 -4
View File
@@ -5,6 +5,10 @@ import {
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
getTrustedMuteHideThreshold,
getTrustedSpamHideThreshold,
PROFILE_CACHE_TTL_MS,
URL_HEALTH_TTL_MS,
URL_HEALTH_RETRY_MS,
URL_PROBE_TIMEOUT_MS,
} from "../constants.js";
import { userLogger } from "../utils/logger.js";
import { sanitizeProfileMediaUrl } from "../utils/profileMedia.js";
@@ -17,14 +21,12 @@ import createProfileSettingsStore from "./profileSettingsStore.js";
const PROFILE_CACHE_STORAGE_KEY = "bitvid:profileCache:v1";
const PROFILE_CACHE_VERSION = 1;
const PROFILE_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
const SAVED_PROFILES_STORAGE_KEY = "bitvid:savedProfiles:v1";
const SAVED_PROFILES_STORAGE_VERSION = 1;
const URL_HEALTH_TTL_MS = 45 * 60 * 1000; // 45 minutes
const URL_HEALTH_TIMEOUT_RETRY_MS = 5 * 60 * 1000; // 5 minutes
export const URL_PROBE_TIMEOUT_MS = 8 * 1000; // 8 seconds
const URL_HEALTH_TIMEOUT_RETRY_MS = URL_HEALTH_RETRY_MS;
export { URL_PROBE_TIMEOUT_MS };
const URL_PROBE_TIMEOUT_RETRY_MS = 15 * 1000; // 15 seconds
+8 -6
View File
@@ -10,9 +10,11 @@ import {
buildBlockListEvent,
buildMuteListEvent,
BLOCK_LIST_IDENTIFIER,
KIND_MUTE_LIST,
} from "./nostrEventSchemas.js";
import { CACHE_POLICIES, STORAGE_TIERS } from "./nostr/cachePolicies.js";
import { devLogger, userLogger } from "./utils/logger.js";
import { STANDARD_TIMEOUT_MS } from "./constants.js";
import {
publishEventToRelays,
assertAnyRelayAccepted,
@@ -86,7 +88,7 @@ const BACKGROUND_BLOCKLIST_TIMEOUT_MS = 6000;
// PERF: Reduced from 20s/15s to 10s/8s — the signer is now guaranteed to be
// ready before list loading starts (authSessionCoordinator waits for the
// permission pre-grant), so decryption should succeed quickly.
const DECRYPT_TIMEOUT_MS = 10000;
const DECRYPT_TIMEOUT_MS = STANDARD_TIMEOUT_MS;
const BACKGROUND_DECRYPT_TIMEOUT_MS = 8000;
// PERF: Reduced from 10s to 3s for faster recovery during login.
const DECRYPT_RETRY_DELAY_MS = 3000;
@@ -420,7 +422,7 @@ function isUserBlockListEvent(event) {
return false;
}
if (event.kind === 10000) {
if (event.kind === KIND_MUTE_LIST) {
return true;
}
@@ -755,7 +757,7 @@ class UserBlockListManager {
this.blockListSubscriptionKey = key;
const filters = [
{ kinds: [10000], authors: [normalized] },
{ kinds: [KIND_MUTE_LIST], authors: [normalized] },
{ kinds: [30002], authors: [normalized], "#d": [BLOCK_LIST_IDENTIFIER] },
];
@@ -1261,7 +1263,7 @@ class UserBlockListManager {
// Standard Mute: Kind 10000 WITHOUT d=user-blocks
const standardMuteEvents = validEvents
.filter((e) => e.kind === 10000 && !isTaggedBlockListEvent(e))
.filter((e) => e.kind === KIND_MUTE_LIST && !isTaggedBlockListEvent(e))
.sort((a, b) => (b?.created_at || 0) - (a?.created_at || 0));
const newestLegacy = legacyBlockEvents[0] || null;
@@ -1549,7 +1551,7 @@ class UserBlockListManager {
// Legacy variants are fetched in parallel but processed separately to avoid blocking.
const standardFetchPromise = nostrClient.fetchListIncrementally({
kind: 10000,
kind: KIND_MUTE_LIST,
pubkey: normalized,
relayUrls: relays,
since: fetchSince,
@@ -1558,7 +1560,7 @@ class UserBlockListManager {
const legacyFetchPromise = Promise.all([
nostrClient.fetchListIncrementally({
kind: 10000,
kind: KIND_MUTE_LIST,
pubkey: normalized,
dTag: BLOCK_LIST_IDENTIFIER,
relayUrls: relays,
+2 -1
View File
@@ -16,10 +16,11 @@ import {
} from "./config.js";
import { getApplication } from "./applicationContext.js";
import { devLogger, userLogger } from "./utils/logger.js";
import { ONE_MINUTE_MS } from "./constants.js";
const LOCAL_STORAGE_QUEUE_KEY = "bitvid:watch-history:queue:v1";
const SESSION_STORAGE_VERSION = 1;
const POINTER_THROTTLE_MS = 60 * 1000;
const POINTER_THROTTLE_MS = ONE_MINUTE_MS;
const state = {
restored: false,
queues: new Map(),