Browse Source

cache relay pulse

imwald
Silberengel 1 month ago
parent
commit
b9763eaeed
  1. 34
      package-lock.json
  2. 3
      package.json
  3. 38
      src/lib/relay-pulse-active-npubs-cache.ts
  4. 32
      src/providers/FavoriteRelaysActivityProvider.tsx

34
package-lock.json generated

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
{
"name": "jumble-imwald",
"version": "19.4.0",
"version": "20.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "jumble-imwald",
"version": "19.4.0",
"version": "20.0.0",
"license": "MIT",
"dependencies": {
"@asciidoctor/core": "^3.0.4",
@ -4892,9 +4892,9 @@ @@ -4892,9 +4892,9 @@
"license": "MIT"
},
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@ -12576,9 +12576,9 @@ @@ -12576,9 +12576,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
@ -15008,9 +15008,9 @@ @@ -15008,9 +15008,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
@ -15707,9 +15707,9 @@ @@ -15707,9 +15707,9 @@
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@ -15802,9 +15802,9 @@ @@ -15802,9 +15802,9 @@
}
},
"node_modules/vitest/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {

3
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "jumble-imwald",
"version": "19.4.0",
"version": "20.0.0",
"description": "A user-friendly Nostr client focused on relay feed browsing and relay discovery, forked from Jumble",
"private": true,
"type": "module",
@ -14,6 +14,7 @@ @@ -14,6 +14,7 @@
"homepage": "https://github.com/Silberengel/jumble",
"scripts": {
"dev": "vite --host",
"dev:refresh": "rm -rf node_modules/.vite && vite --host",
"build": "tsc -b && vite build",
"lint": "eslint .",
"format": "prettier --write .",

38
src/lib/relay-pulse-active-npubs-cache.ts

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
import logger from '@/lib/logger'
/** One row per browser; overwritten whenever a new active-npub list is fetched for the same relay + viewer scope. */
export type RelayPulseActiveNpubsCacheRow = {
relayKey: string
viewerPubkey: string | null
orderedPubkeys: string[]
lastFetchedAtMs: number
}
const STORAGE_KEY = 'jumble.relayPulse.activeNpubs.v1'
export function readRelayPulseActiveNpubsCache(
relayKey: string,
viewerPubkey: string | null
): Pick<RelayPulseActiveNpubsCacheRow, 'orderedPubkeys' | 'lastFetchedAtMs'> | null {
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return null
const data = JSON.parse(raw) as unknown
if (!data || typeof data !== 'object') return null
const o = data as Record<string, unknown>
if (o.relayKey !== relayKey || o.viewerPubkey !== viewerPubkey) return null
if (!Array.isArray(o.orderedPubkeys) || typeof o.lastFetchedAtMs !== 'number') return null
const orderedPubkeys = o.orderedPubkeys.filter((x): x is string => typeof x === 'string')
return { orderedPubkeys, lastFetchedAtMs: o.lastFetchedAtMs }
} catch {
return null
}
}
export function writeRelayPulseActiveNpubsCache(row: RelayPulseActiveNpubsCacheRow): void {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(row))
} catch (e) {
logger.debug('[RelayPulseActiveNpubsCache] write failed', { error: e })
}
}

32
src/providers/FavoriteRelaysActivityProvider.tsx

@ -1,5 +1,9 @@ @@ -1,5 +1,9 @@
import logger from '@/lib/logger'
import { getFavoritesFeedRelayUrls } from '@/lib/favorites-feed-relays'
import {
readRelayPulseActiveNpubsCache,
writeRelayPulseActiveNpubsCache
} from '@/lib/relay-pulse-active-npubs-cache'
import { hexPubkeysEqual, normalizeHexPubkey, userIdToPubkey } from '@/lib/pubkey'
import { getPubkeysFromPTags } from '@/lib/tag'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
@ -92,8 +96,15 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R @@ -92,8 +96,15 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R
setLoading(false)
setRelayActivityReady(true)
const now = Date.now()
setOrderedPubkeys([])
lastCompletedFetchAtRef.current = now
setLastFetchedAtMs(now)
writeRelayPulseActiveNpubsCache({
relayKey,
viewerPubkey: viewerPubkey ?? null,
orderedPubkeys: [],
lastFetchedAtMs: now
})
return
}
setLoading(true)
@ -109,9 +120,16 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R @@ -109,9 +120,16 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R
}
)
const now = Date.now()
setOrderedPubkeys(aggregatePubkeysByRecency(events))
const nextPubkeys = aggregatePubkeysByRecency(events)
setOrderedPubkeys(nextPubkeys)
lastCompletedFetchAtRef.current = now
setLastFetchedAtMs(now)
writeRelayPulseActiveNpubsCache({
relayKey,
viewerPubkey: viewerPubkey ?? null,
orderedPubkeys: nextPubkeys,
lastFetchedAtMs: now
})
} catch (error) {
logger.debug('[FavoriteRelaysActivity] fetch failed', { error, useDefaultRelays })
if (!useDefaultRelays && favoriteRelays.length > 0) {
@ -122,7 +140,7 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R @@ -122,7 +140,7 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R
setRelayActivityReady(true)
}
},
[favoriteRelays, blockedRelays]
[favoriteRelays, blockedRelays, relayKey, viewerPubkey]
)
const fetchRef = useRef(fetchActive)
@ -160,6 +178,16 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R @@ -160,6 +178,16 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R
prevViewerRef.current = viewerPubkey ?? undefined
}, [viewerPubkey, resetForRefetch])
/** Restore last successful relay-pulse author list from localStorage (same relay set + viewer). */
useEffect(() => {
const row = readRelayPulseActiveNpubsCache(relayKey, viewerPubkey ?? null)
if (!row) return
setOrderedPubkeys(row.orderedPubkeys)
setLastFetchedAtMs(row.lastFetchedAtMs)
setRelayActivityReady(true)
lastCompletedFetchAtRef.current = row.lastFetchedAtMs
}, [relayKey, viewerPubkey])
/** When follow list from context is empty but we have a logged-in viewer, try IndexedDB cache.
* Fixes race where pulse data arrives before NostrProvider has hydrated follow list from cache. */
useEffect(() => {

Loading…
Cancel
Save