diff --git a/package-lock.json b/package-lock.json index 3531c414..3675d110 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "jumble-imwald", - "version": "18.0.0", + "version": "18.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jumble-imwald", - "version": "18.0.0", + "version": "18.0.1", "license": "MIT", "dependencies": { "@asciidoctor/core": "^3.0.4", diff --git a/package.json b/package.json index ba389d73..12475512 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jumble-imwald", - "version": "18.0.0", + "version": "18.0.1", "description": "A user-friendly Nostr client focused on relay feed browsing and relay discovery, forked from Jumble", "private": true, "type": "module", diff --git a/src/lib/logger.ts b/src/lib/logger.ts index 40318ece..be8693c2 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -61,8 +61,11 @@ class Logger { private formatMessage(level: LogLevel, message: string, ...args: any[]): [string, ...any[]] { const timestamp = new Date().toISOString().substring(11, 23) // HH:mm:ss.SSS - const caller = this.getCallerInfo() - const prefix = `[${timestamp}] [${level.toUpperCase()}] [${caller}]` + // Stack capture is expensive (main-thread jank, especially on mobile). Only when deep debug is on. + const caller = this.config.enableDebug ? this.getCallerInfo() : '' + const prefix = caller + ? `[${timestamp}] [${level.toUpperCase()}] [${caller}]` + : `[${timestamp}] [${level.toUpperCase()}]` return [`${prefix} ${message}`, ...args] } diff --git a/src/main.tsx b/src/main.tsx index 91e1ca45..8ea75212 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -30,21 +30,25 @@ const SESSION_STORAGE_KEY = 'jumble:session' async function bootstrap() { // Always defined: fetch does not throw on 4xx/5xx, so non-OK responses must not leave this unset. window.__RUNTIME_CONFIG__ = {} - try { - const r = await fetch('/config.json') - if (r.ok) { - window.__RUNTIME_CONFIG__ = (await r.json()) as { NIP66_MONITOR_NPUB?: string } - } - } catch { - window.__RUNTIME_CONFIG__ = {} - } + await Promise.all([ + storage.initAsync(), + (async () => { + try { + const r = await fetch('/config.json') + if (r.ok) { + window.__RUNTIME_CONFIG__ = (await r.json()) as { NIP66_MONITOR_NPUB?: string } + } + } catch { + window.__RUNTIME_CONFIG__ = {} + } + })() + ]) // Mark session storage as used so it's visible in DevTools; VersionUpdateBanner and NotePage also use it. try { sessionStorage.setItem(SESSION_STORAGE_KEY, String(Date.now())) } catch { // ignore quota or private browsing } - await storage.initAsync() publishMonitorAnnouncementOnce() createRoot(document.getElementById('root')!).render( diff --git a/src/services/client.service.ts b/src/services/client.service.ts index aefbaa25..1fba2b81 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -97,7 +97,13 @@ class ClientService extends EventTarget { async init() { await indexedDb.iterateProfileEvents((profileEvent) => this.addUsernameToIndex(profileEvent)) - this.fetchNip66RelayDiscovery().catch(() => {}) + // Defer NIP-66 discovery so the first WebSocket slots go to login, relay list, and feed — not background search. + const runNip66 = () => this.fetchNip66RelayDiscovery().catch(() => {}) + if (typeof requestIdleCallback !== 'undefined') { + requestIdleCallback(() => runNip66(), { timeout: 8000 }) + } else { + setTimeout(runNip66, 2500) + } } /** NIP-66: fetch relay discovery events (30166) in background to supplement search/NIP support. */ @@ -1291,7 +1297,7 @@ class ClientService extends EventTarget { const isExternalSearch = eoseTimeout > 1000 // Consider it external search if timeout > 1s if (isExternalSearch) { - logger.info('query: Starting external relay search', { + logger.debug('query: Starting external relay search', { relayCount: urls.length, relays: urls, eoseTimeout, @@ -1301,7 +1307,7 @@ class ClientService extends EventTarget { } /** Once one relay returns results, give others this long (ms) then resolve with what we have */ - const FIRST_RESULT_GRACE_MS = 2000 + const FIRST_RESULT_GRACE_MS = 1200 return await new Promise((resolve) => { const events: NEvent[] = [] @@ -1331,7 +1337,7 @@ class ClientService extends EventTarget { } const duration = eoseTime ? Date.now() - eoseTime : 0 if (isExternalSearch) { - logger.info('query: Resolving external search', { + logger.debug('query: Resolving external search', { eventsFound: events.length, eventCount, allEosed, @@ -1346,7 +1352,7 @@ class ClientService extends EventTarget { onevent(evt) { eventCount++ if (isExternalSearch && eventCount <= 3) { - logger.info('query: Received event', { + logger.debug('query: Received event', { eventId: evt.id.substring(0, 8), eventCount, timeSinceEose: eoseTime ? Date.now() - eoseTime : null @@ -1393,7 +1399,7 @@ class ClientService extends EventTarget { allEosed = true eoseTime = Date.now() if (isExternalSearch) { - logger.info('query: Received EOSE from all relays', { + logger.debug('query: Received EOSE from all relays', { eventsSoFar: events.length, eventCount, willWait: eoseTimeout @@ -1418,7 +1424,7 @@ class ClientService extends EventTarget { }, onclose: (url, reason) => { if (isExternalSearch) { - logger.info('query: Relay connection closed', { url, reason, eventsSoFar: events.length, allEosed }) + logger.debug('query: Relay connection closed', { url, reason, eventsSoFar: events.length, allEosed }) } // If we've received EOSE, we have a timeout set - let it handle resolution // This gives searchable relays time to search their databases @@ -1447,7 +1453,7 @@ class ClientService extends EventTarget { // Fallback timeout: resolve after globalTimeout to prevent hanging globalTimeoutId = setTimeout(() => { if (isExternalSearch) { - logger.info('query: Global timeout reached', { + logger.debug('query: Global timeout reached', { eventsFound: events.length, eventCount, allEosed