diff --git a/js/app/feedCoordinator.js b/js/app/feedCoordinator.js index 106201b0..616a2e5d 100644 --- a/js/app/feedCoordinator.js +++ b/js/app/feedCoordinator.js @@ -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; } diff --git a/js/channelProfile.js b/js/channelProfile.js index fbe4667c..129cea21 100644 --- a/js/channelProfile.js +++ b/js/channelProfile.js @@ -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(); diff --git a/js/constants.js b/js/constants.js index eedfe2f5..0deab851 100644 --- a/js/constants.js +++ b/js/constants.js @@ -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)) { diff --git a/js/gridHealth.js b/js/gridHealth.js index 19669701..f5bedf8e 100644 --- a/js/gridHealth.js +++ b/js/gridHealth.js @@ -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; diff --git a/js/nostr/cachePolicies.js b/js/nostr/cachePolicies.js index d269a72b..c8978ff0 100644 --- a/js/nostr/cachePolicies.js +++ b/js/nostr/cachePolicies.js @@ -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, }, diff --git a/js/nostr/managers/ConnectionManager.js b/js/nostr/managers/ConnectionManager.js index 85ce904f..35fff540 100644 --- a/js/nostr/managers/ConnectionManager.js +++ b/js/nostr/managers/ConnectionManager.js @@ -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) { diff --git a/js/nostr/relayConstants.js b/js/nostr/relayConstants.js index 7701095c..e7f8c572 100644 --- a/js/nostr/relayConstants.js +++ b/js/nostr/relayConstants.js @@ -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; diff --git a/js/nostrEventSchemas.js b/js/nostrEventSchemas.js index 307b6985..93abdd73 100644 --- a/js/nostrEventSchemas.js +++ b/js/nostrEventSchemas.js @@ -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, diff --git a/js/payments/platformAddress.js b/js/payments/platformAddress.js index 5399ac2f..842ee9be 100644 --- a/js/payments/platformAddress.js +++ b/js/payments/platformAddress.js @@ -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; diff --git a/js/payments/zapSharedState.js b/js/payments/zapSharedState.js index 9ac0fef4..833edf53 100644 --- a/js/payments/zapSharedState.js +++ b/js/payments/zapSharedState.js @@ -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(); diff --git a/js/services/commentThreadService.js b/js/services/commentThreadService.js index 6018980d..e361248c 100644 --- a/js/services/commentThreadService.js +++ b/js/services/commentThreadService.js @@ -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) { diff --git a/js/services/exploreDataService.js b/js/services/exploreDataService.js index c8cb38b7..4556b0c3 100644 --- a/js/services/exploreDataService.js +++ b/js/services/exploreDataService.js @@ -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; diff --git a/js/services/moderationService.js b/js/services/moderationService.js index a90b8844..a5ae291b 100644 --- a/js/services/moderationService.js +++ b/js/services/moderationService.js @@ -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; } diff --git a/js/services/relayHealthService.js b/js/services/relayHealthService.js index 47ae5061..2aacc856 100644 --- a/js/services/relayHealthService.js +++ b/js/services/relayHealthService.js @@ -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", }); } diff --git a/js/state/cache.js b/js/state/cache.js index b399633c..2fca6225 100644 --- a/js/state/cache.js +++ b/js/state/cache.js @@ -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 diff --git a/js/userBlocks.js b/js/userBlocks.js index 83723837..fa458cfa 100644 --- a/js/userBlocks.js +++ b/js/userBlocks.js @@ -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, diff --git a/js/watchHistoryService.js b/js/watchHistoryService.js index cfecf734..8d5e750a 100644 --- a/js/watchHistoryService.js +++ b/js/watchHistoryService.js @@ -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(),