diff --git a/src/main.tsx b/src/main.tsx index 3a25c089..ba79658f 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -10,6 +10,7 @@ import { createRoot } from 'react-dom/client' import App from './App.tsx' import { ErrorBoundary } from './components/ErrorBoundary.tsx' import { publishMonitorAnnouncementOnce } from './services/nip66-monitor' +import storage from './services/local-storage.service' declare global { interface Window { @@ -34,6 +35,7 @@ async function bootstrap() { } catch { window.__RUNTIME_CONFIG__ = {} } + await storage.initAsync() publishMonitorAnnouncementOnce() createRoot(document.getElementById('root')!).render( diff --git a/src/services/gif.service.ts b/src/services/gif.service.ts index 9a702494..1047fa74 100644 --- a/src/services/gif.service.ts +++ b/src/services/gif.service.ts @@ -8,6 +8,7 @@ import { normalizeUrl } from '@/lib/url' import { kinds } from 'nostr-tools' import type { Event as NEvent } from 'nostr-tools' import client from './client.service' +import indexedDb from './indexed-db.service' export interface GifMetadata { url: string @@ -179,9 +180,7 @@ function parseGifFromEvent(event: NEvent): GifMetadata | null { } } -const CACHE_MAX_AGE_MS = 5 * 60 * 1000 // 5 minutes in-memory cache -let cachedGifs: GifMetadata[] = [] -let cacheTime = 0 +const CACHE_MAX_AGE_MS = 5 * 60 * 1000 // 5 minutes; cache lives in IndexedDB /** * Fetch GIFs from Nostr kind 1063 (NIP-94) events on GIF relays. @@ -197,9 +196,11 @@ export async function fetchGifs( extraReadRelayUrls: string[] = [], userPubkey: string | null = null ): Promise { - const useCache = !forceRefresh && cachedGifs.length > 0 && Date.now() - cacheTime < CACHE_MAX_AGE_MS - if (useCache && !searchQuery) { - return cachedGifs.slice(0, limit) + if (!forceRefresh && !searchQuery) { + const cached = await indexedDb.getGifCache() + if (cached && cached.gifs.length > 0 && Date.now() - cached.cachedAt < CACHE_MAX_AGE_MS) { + return cached.gifs.slice(0, limit) as GifMetadata[] + } } const readUrls = [ @@ -263,8 +264,7 @@ export async function fetchGifs( const result = gifs.slice(0, limit) if (result.length > 0 && !searchQuery) { - cachedGifs = result - cacheTime = Date.now() + await indexedDb.setGifCache(result, Date.now()) } return result diff --git a/src/services/indexed-db.service.ts b/src/services/indexed-db.service.ts index 60a18f75..e9e0e233 100644 --- a/src/services/indexed-db.service.ts +++ b/src/services/indexed-db.service.ts @@ -40,11 +40,15 @@ export const StoreNames = { /** NIP-66: per-relay discovery cache (key = relay URL, value = { discovery, cachedAt }). */ NIP66_DISCOVERY: 'nip66Discovery', /** NIP-A3 payment targets (kind 10133). */ - PAYMENT_INFO_EVENTS: 'paymentInfoEvents' + PAYMENT_INFO_EVENTS: 'paymentInfoEvents', + /** Cached GIF list (parsed from kind 1063 + 1/1111). Key: 'gifList', value: { gifs, cachedAt }. */ + GIF_CACHE: 'gifCache', + /** App settings (replaces in-memory/localStorage for persisted settings). Key: setting key, value: string. */ + SETTINGS: 'settings' } /** Schema version we expect. When adding stores or migrations, bump this. */ -const DB_VERSION = 21 +const DB_VERSION = 22 /** Max age for profile and payment info cache before we refetch (5 min). */ const PROFILE_AND_PAYMENT_CACHE_MAX_AGE_MS = 5 * 60 * 1000 @@ -204,6 +208,12 @@ class IndexedDbService { if (!db.objectStoreNames.contains(StoreNames.PAYMENT_INFO_EVENTS)) { db.createObjectStore(StoreNames.PAYMENT_INFO_EVENTS, { keyPath: 'key' }) } + if (!db.objectStoreNames.contains(StoreNames.GIF_CACHE)) { + db.createObjectStore(StoreNames.GIF_CACHE, { keyPath: 'key' }) + } + if (!db.objectStoreNames.contains(StoreNames.SETTINGS)) { + db.createObjectStore(StoreNames.SETTINGS, { keyPath: 'key' }) + } } } ); @@ -1596,7 +1606,7 @@ class IndexedDbService { async clearRssFeedItems(): Promise { await this.initPromise const storeName = StoreNames.RSS_FEED_ITEMS - + if (!this.db || !this.db.objectStoreNames.contains(storeName)) { return } @@ -1605,16 +1615,120 @@ class IndexedDbService { const transaction = this.db!.transaction(storeName, 'readwrite') const store = transaction.objectStore(storeName) const request = store.clear() - + request.onsuccess = () => { resolve() } - + request.onerror = () => { reject(request.error) } }) } + + private static readonly GIF_CACHE_KEY = 'gifList' + + /** + * Get cached GIF list from IndexedDB. Returns null if missing or store unavailable. + */ + async getGifCache(): Promise<{ gifs: { url: string; fallbackUrl?: string; eventId: string; pubkey: string; createdAt: number }[]; cachedAt: number } | null> { + await this.initPromise + if (!this.db || !this.db.objectStoreNames.contains(StoreNames.GIF_CACHE)) { + return null + } + return new Promise((resolve) => { + const transaction = this.db!.transaction(StoreNames.GIF_CACHE, 'readonly') + const store = transaction.objectStore(StoreNames.GIF_CACHE) + const request = store.get(IndexedDbService.GIF_CACHE_KEY) + request.onsuccess = () => { + const row = request.result as { key: string; value: { gifs: unknown[]; cachedAt: number } } | undefined + if (row?.value?.gifs && typeof row.value.cachedAt === 'number') { + resolve({ gifs: row.value.gifs as { url: string; fallbackUrl?: string; eventId: string; pubkey: string; createdAt: number }[], cachedAt: row.value.cachedAt }) + } else { + resolve(null) + } + } + request.onerror = () => resolve(null) + }) + } + + /** + * Write GIF list cache to IndexedDB. + */ + async setGifCache(gifs: { url: string; fallbackUrl?: string; eventId: string; pubkey: string; createdAt: number }[], cachedAt: number): Promise { + await this.initPromise + if (!this.db || !this.db.objectStoreNames.contains(StoreNames.GIF_CACHE)) { + return + } + return new Promise((resolve, reject) => { + const transaction = this.db!.transaction(StoreNames.GIF_CACHE, 'readwrite') + const store = transaction.objectStore(StoreNames.GIF_CACHE) + store.put({ key: IndexedDbService.GIF_CACHE_KEY, value: { gifs, cachedAt } }) + transaction.oncomplete = () => resolve() + transaction.onerror = () => reject(transaction.error) + }) + } + + /** + * Get a single setting value from IndexedDB. Returns null if missing. + */ + async getSetting(key: string): Promise { + await this.initPromise + if (!this.db || !this.db.objectStoreNames.contains(StoreNames.SETTINGS)) { + return null + } + return new Promise((resolve) => { + const transaction = this.db!.transaction(StoreNames.SETTINGS, 'readonly') + const store = transaction.objectStore(StoreNames.SETTINGS) + const request = store.get(key) + request.onsuccess = () => { + const row = request.result as { key: string; value: string } | undefined + resolve(row?.value ?? null) + } + request.onerror = () => resolve(null) + }) + } + + /** + * Get all settings from IndexedDB as a key -> value map. + */ + async getAllSettings(): Promise> { + await this.initPromise + if (!this.db || !this.db.objectStoreNames.contains(StoreNames.SETTINGS)) { + return {} + } + return new Promise((resolve, reject) => { + const transaction = this.db!.transaction(StoreNames.SETTINGS, 'readonly') + const store = transaction.objectStore(StoreNames.SETTINGS) + const request = store.getAll() + request.onsuccess = () => { + const rows = (request.result || []) as { key: string; value: string }[] + const out: Record = {} + rows.forEach((r) => { + if (r.key != null && r.value != null) out[r.key] = r.value + }) + resolve(out) + } + request.onerror = () => reject(request.error) + }) + } + + /** + * Set a setting in IndexedDB. + */ + async setSetting(key: string, value: string): Promise { + await this.initPromise + if (!this.db || !this.db.objectStoreNames.contains(StoreNames.SETTINGS)) { + return + } + return new Promise((resolve, reject) => { + const transaction = this.db!.transaction(StoreNames.SETTINGS, 'readwrite') + const store = transaction.objectStore(StoreNames.SETTINGS) + store.put({ key, value }) + transaction.oncomplete = () => resolve() + transaction.onerror = () => reject(transaction.error) + }) + } } const instance = IndexedDbService.getInstance() diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index e5ac6a1d..69a0840f 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -21,6 +21,49 @@ import { TRelaySet, TThemeSetting, } from '@/types' +import indexedDb from './indexed-db.service' + +/** Keys we persist to IndexedDB (and migrate from localStorage when IDB is empty). */ +const SETTINGS_KEYS = [ + StorageKey.RELAY_SETS, + StorageKey.THEME_SETTING, + StorageKey.FONT_SIZE, + StorageKey.NOTE_LIST_MODE, + StorageKey.ACCOUNTS, + StorageKey.CURRENT_ACCOUNT, + StorageKey.DEFAULT_ZAP_SATS, + StorageKey.DEFAULT_ZAP_COMMENT, + StorageKey.QUICK_ZAP, + StorageKey.ZAP_REPLY_THRESHOLD, + StorageKey.LAST_READ_NOTIFICATION_TIME_MAP, + StorageKey.ACCOUNT_FEED_INFO_MAP, + StorageKey.AUTOPLAY, + StorageKey.HIDE_UNTRUSTED_INTERACTIONS, + StorageKey.HIDE_UNTRUSTED_NOTIFICATIONS, + StorageKey.HIDE_UNTRUSTED_NOTES, + StorageKey.MEDIA_UPLOAD_SERVICE_CONFIG_MAP, + StorageKey.DEFAULT_SHOW_NSFW, + StorageKey.DISMISSED_TOO_MANY_RELAYS_ALERT, + StorageKey.SHOW_KINDS, + StorageKey.SHOW_KINDS_VERSION, + StorageKey.SHOW_KIND_1_OPs, + StorageKey.SHOW_KIND_1_REPLIES, + StorageKey.SHOW_KIND_1111, + StorageKey.HIDE_CONTENT_MENTIONING_MUTED_USERS, + StorageKey.NOTIFICATION_LIST_STYLE, + StorageKey.MEDIA_AUTO_LOAD_POLICY, + StorageKey.SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS, + StorageKey.SHOW_RECOMMENDED_RELAYS_PANEL, + StorageKey.ADD_RANDOM_RELAYS_TO_PUBLISH, + StorageKey.DEFAULT_EXPIRATION_ENABLED, + StorageKey.DEFAULT_EXPIRATION_MONTHS, + StorageKey.DEFAULT_QUIET_ENABLED, + StorageKey.DEFAULT_QUIET_DAYS, + StorageKey.RESPECT_QUIET_TAGS, + StorageKey.GLOBAL_QUIET_MODE, + StorageKey.SHOW_RSS_FEED, + StorageKey.PANE_MODE +] as const class LocalStorageService { static instance: LocalStorageService @@ -107,7 +150,7 @@ class LocalStorageService { if (!relaySets.length) { relaySets = [] } - window.localStorage.setItem(StorageKey.RELAY_SETS, JSON.stringify(relaySets)) + this.persistSetting(StorageKey.RELAY_SETS, JSON.stringify(relaySets)) this.relaySets = relaySets } else { this.relaySets = JSON.parse(relaySetsStr) @@ -246,8 +289,8 @@ class LocalStorageService { } this.showKinds = showKinds } - window.localStorage.setItem(StorageKey.SHOW_KINDS, JSON.stringify(this.showKinds)) - window.localStorage.setItem(StorageKey.SHOW_KINDS_VERSION, '7') + this.persistSetting(StorageKey.SHOW_KINDS, JSON.stringify(this.showKinds)) + this.persistSetting(StorageKey.SHOW_KINDS_VERSION, '7') // Feed filter: kind 1 OPs, kind 1 replies, kind 1111 (migrate from legacy showRepliesAndComments if set) const showKind1OPsStr = window.localStorage.getItem(StorageKey.SHOW_KIND_1_OPs) @@ -346,13 +389,133 @@ class LocalStorageService { window.localStorage.removeItem(StorageKey.FEED_TYPE) } + /** Persist a setting to both localStorage and IndexedDB (source of truth is IndexedDB). */ + private persistSetting(key: string, value: string): void { + window.localStorage.setItem(key, value) + indexedDb.setSetting(key, value).catch(() => {}) + } + + private initPromise: Promise | null = null + + /** + * Async init: hydrate from IndexedDB when available, otherwise migrate localStorage into IndexedDB. + * Call this before app render so settings are read from IndexedDB. + */ + async initAsync(): Promise { + if (this.initPromise) return this.initPromise + this.initPromise = (async () => { + await indexedDb.init() + const all = await indexedDb.getAllSettings() + if (Object.keys(all).length > 0) { + this.applySettings(all) + } else { + await this.migrateToIdb() + } + })() + return this.initPromise + } + + private async migrateToIdb(): Promise { + for (const key of SETTINGS_KEYS) { + const value = window.localStorage.getItem(key) + if (value != null) await indexedDb.setSetting(key, value) + } + } + + private applySettings(record: Record): void { + const get = (k: string) => record[k] ?? window.localStorage.getItem(k) + if (get(StorageKey.THEME_SETTING) != null) { + this.themeSetting = (get(StorageKey.THEME_SETTING) as TThemeSetting) ?? this.themeSetting + } + if (get(StorageKey.FONT_SIZE) != null) { + this.fontSize = (get(StorageKey.FONT_SIZE) as TFontSize) ?? this.fontSize + } + const noteListModeStr = get(StorageKey.NOTE_LIST_MODE) + if (noteListModeStr != null && ['posts', 'postsAndReplies', 'pictures'].includes(noteListModeStr)) { + this.noteListMode = noteListModeStr as TNoteListMode + } + const accountsStr = get(StorageKey.ACCOUNTS) + if (accountsStr != null) this.accounts = JSON.parse(accountsStr) as TAccount[] + const currentAccountStr = get(StorageKey.CURRENT_ACCOUNT) + if (currentAccountStr != null) this.currentAccount = JSON.parse(currentAccountStr) as TAccount | null + const lastReadStr = get(StorageKey.LAST_READ_NOTIFICATION_TIME_MAP) + if (lastReadStr != null) this.lastReadNotificationTimeMap = JSON.parse(lastReadStr) as Record + const relaySetsStr = get(StorageKey.RELAY_SETS) + if (relaySetsStr != null) this.relaySets = JSON.parse(relaySetsStr) as TRelaySet[] + const defaultZapSatsStr = get(StorageKey.DEFAULT_ZAP_SATS) + if (defaultZapSatsStr != null) { + const num = parseInt(defaultZapSatsStr) + if (!isNaN(num)) this.defaultZapSats = num + } + const defaultZapCommentStr = get(StorageKey.DEFAULT_ZAP_COMMENT) + if (defaultZapCommentStr != null) this.defaultZapComment = defaultZapCommentStr + const quickZapStr = get(StorageKey.QUICK_ZAP) + if (quickZapStr != null) this.quickZap = quickZapStr === 'true' + const zapReplyStr = get(StorageKey.ZAP_REPLY_THRESHOLD) + if (zapReplyStr != null) { + const num = parseInt(zapReplyStr) + if (!isNaN(num)) this.zapReplyThreshold = num + } + const accountFeedInfoStr = get(StorageKey.ACCOUNT_FEED_INFO_MAP) + if (accountFeedInfoStr != null) this.accountFeedInfoMap = JSON.parse(accountFeedInfoStr) as Record + this.autoplay = get(StorageKey.AUTOPLAY) !== 'false' + const hideInteractions = get(StorageKey.HIDE_UNTRUSTED_INTERACTIONS) + if (hideInteractions != null) this.hideUntrustedInteractions = hideInteractions === 'true' + const hideNotifications = get(StorageKey.HIDE_UNTRUSTED_NOTIFICATIONS) + if (hideNotifications != null) this.hideUntrustedNotifications = hideNotifications === 'true' + const hideNotes = get(StorageKey.HIDE_UNTRUSTED_NOTES) + if (hideNotes != null) this.hideUntrustedNotes = hideNotes === 'true' + const mediaConfigStr = get(StorageKey.MEDIA_UPLOAD_SERVICE_CONFIG_MAP) + if (mediaConfigStr != null) this.mediaUploadServiceConfigMap = JSON.parse(mediaConfigStr) as Record + this.defaultShowNsfw = get(StorageKey.DEFAULT_SHOW_NSFW) === 'true' + this.dismissedTooManyRelaysAlert = get(StorageKey.DISMISSED_TOO_MANY_RELAYS_ALERT) === 'true' + this.showRecommendedRelaysPanel = get(StorageKey.SHOW_RECOMMENDED_RELAYS_PANEL) === 'true' + this.addRandomRelaysToPublish = get(StorageKey.ADD_RANDOM_RELAYS_TO_PUBLISH) === 'true' + const showKindsStr = get(StorageKey.SHOW_KINDS) + if (showKindsStr != null) this.showKinds = JSON.parse(showKindsStr) as number[] + const showKind1OPsStr = get(StorageKey.SHOW_KIND_1_OPs) + if (showKind1OPsStr != null) this.showKind1OPs = showKind1OPsStr === 'true' + const showKind1RepliesStr = get(StorageKey.SHOW_KIND_1_REPLIES) + if (showKind1RepliesStr != null) this.showKind1Replies = showKind1RepliesStr === 'true' + const showKind1111Str = get(StorageKey.SHOW_KIND_1111) + if (showKind1111Str != null) this.showKind1111 = showKind1111Str === 'true' + this.hideContentMentioningMutedUsers = get(StorageKey.HIDE_CONTENT_MENTIONING_MUTED_USERS) === 'true' + const notifStyle = get(StorageKey.NOTIFICATION_LIST_STYLE) + if (notifStyle != null) this.notificationListStyle = notifStyle === NOTIFICATION_LIST_STYLE.COMPACT ? NOTIFICATION_LIST_STYLE.COMPACT : NOTIFICATION_LIST_STYLE.DETAILED + const mediaPolicy = get(StorageKey.MEDIA_AUTO_LOAD_POLICY) + if (mediaPolicy != null && Object.values(MEDIA_AUTO_LOAD_POLICY).includes(mediaPolicy as TMediaAutoLoadPolicy)) { + this.mediaAutoLoadPolicy = mediaPolicy as TMediaAutoLoadPolicy + } + const shownWalletStr = get(StorageKey.SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS) + if (shownWalletStr != null) this.shownCreateWalletGuideToastPubkeys = new Set(JSON.parse(shownWalletStr) as string[]) + this.defaultExpirationEnabled = get(StorageKey.DEFAULT_EXPIRATION_ENABLED) === 'true' + const defaultExpirationMonthsStr = get(StorageKey.DEFAULT_EXPIRATION_MONTHS) + if (defaultExpirationMonthsStr != null) { + const num = parseInt(defaultExpirationMonthsStr) + if (!isNaN(num) && num >= 0) this.defaultExpirationMonths = num + } + this.defaultQuietEnabled = get(StorageKey.DEFAULT_QUIET_ENABLED) === 'true' + const defaultQuietDaysStr = get(StorageKey.DEFAULT_QUIET_DAYS) + if (defaultQuietDaysStr != null) { + const num = parseInt(defaultQuietDaysStr) + if (!isNaN(num) && num >= 0) this.defaultQuietDays = num + } + const respectQuietStr = get(StorageKey.RESPECT_QUIET_TAGS) + if (respectQuietStr != null) this.respectQuietTags = respectQuietStr === 'true' + this.globalQuietMode = get(StorageKey.GLOBAL_QUIET_MODE) === 'true' + const showRssStr = get(StorageKey.SHOW_RSS_FEED) + if (showRssStr != null) this.showRssFeed = showRssStr === 'true' + const paneStr = get(StorageKey.PANE_MODE) + if (paneStr != null && (paneStr === 'single' || paneStr === 'double')) this.panelMode = paneStr + } + getRelaySets() { return this.relaySets } setRelaySets(relaySets: TRelaySet[]) { this.relaySets = relaySets - window.localStorage.setItem(StorageKey.RELAY_SETS, JSON.stringify(this.relaySets)) + this.persistSetting(StorageKey.RELAY_SETS, JSON.stringify(this.relaySets)) } getThemeSetting() { @@ -360,7 +523,7 @@ class LocalStorageService { } setThemeSetting(themeSetting: TThemeSetting) { - window.localStorage.setItem(StorageKey.THEME_SETTING, themeSetting) + this.persistSetting(StorageKey.THEME_SETTING, themeSetting) this.themeSetting = themeSetting } @@ -369,7 +532,7 @@ class LocalStorageService { } setFontSize(fontSize: TFontSize) { - window.localStorage.setItem(StorageKey.FONT_SIZE, fontSize) + this.persistSetting(StorageKey.FONT_SIZE, fontSize) this.fontSize = fontSize } @@ -378,7 +541,7 @@ class LocalStorageService { } setNoteListMode(mode: TNoteListMode) { - window.localStorage.setItem(StorageKey.NOTE_LIST_MODE, mode) + this.persistSetting(StorageKey.NOTE_LIST_MODE, mode) this.noteListMode = mode } @@ -413,13 +576,13 @@ class LocalStorageService { } else { this.accounts.push(account) } - window.localStorage.setItem(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) + this.persistSetting(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) return this.accounts } removeAccount(account: TAccount) { this.accounts = this.accounts.filter((act) => !isSameAccount(act, account)) - window.localStorage.setItem(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) + this.persistSetting(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) return this.accounts } @@ -432,7 +595,7 @@ class LocalStorageService { return } this.currentAccount = act - window.localStorage.setItem(StorageKey.CURRENT_ACCOUNT, JSON.stringify(act)) + this.persistSetting(StorageKey.CURRENT_ACCOUNT, JSON.stringify(act)) } getDefaultZapSats() { @@ -441,7 +604,7 @@ class LocalStorageService { setDefaultZapSats(sats: number) { this.defaultZapSats = sats - window.localStorage.setItem(StorageKey.DEFAULT_ZAP_SATS, sats.toString()) + this.persistSetting(StorageKey.DEFAULT_ZAP_SATS, sats.toString()) } getDefaultZapComment() { @@ -450,7 +613,7 @@ class LocalStorageService { setDefaultZapComment(comment: string) { this.defaultZapComment = comment - window.localStorage.setItem(StorageKey.DEFAULT_ZAP_COMMENT, comment) + this.persistSetting(StorageKey.DEFAULT_ZAP_COMMENT, comment) } getQuickZap() { @@ -459,7 +622,7 @@ class LocalStorageService { setQuickZap(quickZap: boolean) { this.quickZap = quickZap - window.localStorage.setItem(StorageKey.QUICK_ZAP, quickZap.toString()) + this.persistSetting(StorageKey.QUICK_ZAP, quickZap.toString()) } getZapReplyThreshold() { @@ -468,7 +631,7 @@ class LocalStorageService { setZapReplyThreshold(sats: number) { this.zapReplyThreshold = sats - window.localStorage.setItem(StorageKey.ZAP_REPLY_THRESHOLD, sats.toString()) + this.persistSetting(StorageKey.ZAP_REPLY_THRESHOLD, sats.toString()) } getLastReadNotificationTime(pubkey: string) { @@ -477,7 +640,7 @@ class LocalStorageService { setLastReadNotificationTime(pubkey: string, time: number) { this.lastReadNotificationTimeMap[pubkey] = time - window.localStorage.setItem( + this.persistSetting( StorageKey.LAST_READ_NOTIFICATION_TIME_MAP, JSON.stringify(this.lastReadNotificationTimeMap) ) @@ -489,7 +652,7 @@ class LocalStorageService { setFeedInfo(info: TFeedInfo, pubkey?: string | null) { this.accountFeedInfoMap[pubkey ?? 'default'] = info - window.localStorage.setItem( + this.persistSetting( StorageKey.ACCOUNT_FEED_INFO_MAP, JSON.stringify(this.accountFeedInfoMap) ) @@ -501,7 +664,7 @@ class LocalStorageService { setAutoplay(autoplay: boolean) { this.autoplay = autoplay - window.localStorage.setItem(StorageKey.AUTOPLAY, autoplay.toString()) + this.persistSetting(StorageKey.AUTOPLAY, autoplay.toString()) } getHideUntrustedInteractions() { @@ -510,7 +673,7 @@ class LocalStorageService { setHideUntrustedInteractions(hideUntrustedInteractions: boolean) { this.hideUntrustedInteractions = hideUntrustedInteractions - window.localStorage.setItem( + this.persistSetting( StorageKey.HIDE_UNTRUSTED_INTERACTIONS, hideUntrustedInteractions.toString() ) @@ -522,7 +685,7 @@ class LocalStorageService { setHideUntrustedNotifications(hideUntrustedNotifications: boolean) { this.hideUntrustedNotifications = hideUntrustedNotifications - window.localStorage.setItem( + this.persistSetting( StorageKey.HIDE_UNTRUSTED_NOTIFICATIONS, hideUntrustedNotifications.toString() ) @@ -534,7 +697,7 @@ class LocalStorageService { setHideUntrustedNotes(hideUntrustedNotes: boolean) { this.hideUntrustedNotes = hideUntrustedNotes - window.localStorage.setItem(StorageKey.HIDE_UNTRUSTED_NOTES, hideUntrustedNotes.toString()) + this.persistSetting(StorageKey.HIDE_UNTRUSTED_NOTES, hideUntrustedNotes.toString()) } getMediaUploadServiceConfig(pubkey?: string | null): TMediaUploadServiceConfig { @@ -550,7 +713,7 @@ class LocalStorageService { config: TMediaUploadServiceConfig ): TMediaUploadServiceConfig { this.mediaUploadServiceConfigMap[pubkey] = config - window.localStorage.setItem( + this.persistSetting( StorageKey.MEDIA_UPLOAD_SERVICE_CONFIG_MAP, JSON.stringify(this.mediaUploadServiceConfigMap) ) @@ -563,7 +726,7 @@ class LocalStorageService { setDefaultShowNsfw(defaultShowNsfw: boolean) { this.defaultShowNsfw = defaultShowNsfw - window.localStorage.setItem(StorageKey.DEFAULT_SHOW_NSFW, defaultShowNsfw.toString()) + this.persistSetting(StorageKey.DEFAULT_SHOW_NSFW, defaultShowNsfw.toString()) } getDismissedTooManyRelaysAlert() { @@ -572,7 +735,7 @@ class LocalStorageService { setDismissedTooManyRelaysAlert(dismissed: boolean) { this.dismissedTooManyRelaysAlert = dismissed - window.localStorage.setItem(StorageKey.DISMISSED_TOO_MANY_RELAYS_ALERT, dismissed.toString()) + this.persistSetting(StorageKey.DISMISSED_TOO_MANY_RELAYS_ALERT, dismissed.toString()) } getShowRecommendedRelaysPanel() { @@ -581,7 +744,7 @@ class LocalStorageService { setShowRecommendedRelaysPanel(show: boolean) { this.showRecommendedRelaysPanel = show - window.localStorage.setItem(StorageKey.SHOW_RECOMMENDED_RELAYS_PANEL, show.toString()) + this.persistSetting(StorageKey.SHOW_RECOMMENDED_RELAYS_PANEL, show.toString()) } getAddRandomRelaysToPublish(): boolean { @@ -590,7 +753,7 @@ class LocalStorageService { setAddRandomRelaysToPublish(value: boolean) { this.addRandomRelaysToPublish = value - window.localStorage.setItem(StorageKey.ADD_RANDOM_RELAYS_TO_PUBLISH, value.toString()) + this.persistSetting(StorageKey.ADD_RANDOM_RELAYS_TO_PUBLISH, value.toString()) } getShowKinds() { @@ -599,7 +762,7 @@ class LocalStorageService { setShowKinds(newKinds: number[]) { this.showKinds = newKinds - window.localStorage.setItem(StorageKey.SHOW_KINDS, JSON.stringify(newKinds)) + this.persistSetting(StorageKey.SHOW_KINDS, JSON.stringify(newKinds)) } getShowKind1OPs(): boolean { @@ -608,7 +771,7 @@ class LocalStorageService { setShowKind1OPs(value: boolean) { this.showKind1OPs = value - window.localStorage.setItem(StorageKey.SHOW_KIND_1_OPs, value.toString()) + this.persistSetting(StorageKey.SHOW_KIND_1_OPs, value.toString()) } getShowKind1Replies(): boolean { @@ -617,7 +780,7 @@ class LocalStorageService { setShowKind1Replies(value: boolean) { this.showKind1Replies = value - window.localStorage.setItem(StorageKey.SHOW_KIND_1_REPLIES, value.toString()) + this.persistSetting(StorageKey.SHOW_KIND_1_REPLIES, value.toString()) } getShowKind1111(): boolean { @@ -626,7 +789,7 @@ class LocalStorageService { setShowKind1111(value: boolean) { this.showKind1111 = value - window.localStorage.setItem(StorageKey.SHOW_KIND_1111, value.toString()) + this.persistSetting(StorageKey.SHOW_KIND_1111, value.toString()) } getHideContentMentioningMutedUsers() { @@ -635,7 +798,7 @@ class LocalStorageService { setHideContentMentioningMutedUsers(hide: boolean) { this.hideContentMentioningMutedUsers = hide - window.localStorage.setItem(StorageKey.HIDE_CONTENT_MENTIONING_MUTED_USERS, hide.toString()) + this.persistSetting(StorageKey.HIDE_CONTENT_MENTIONING_MUTED_USERS, hide.toString()) } getNotificationListStyle() { @@ -644,7 +807,7 @@ class LocalStorageService { setNotificationListStyle(style: TNotificationStyle) { this.notificationListStyle = style - window.localStorage.setItem(StorageKey.NOTIFICATION_LIST_STYLE, style) + this.persistSetting(StorageKey.NOTIFICATION_LIST_STYLE, style) } getMediaAutoLoadPolicy() { @@ -653,7 +816,7 @@ class LocalStorageService { setMediaAutoLoadPolicy(policy: TMediaAutoLoadPolicy) { this.mediaAutoLoadPolicy = policy - window.localStorage.setItem(StorageKey.MEDIA_AUTO_LOAD_POLICY, policy) + this.persistSetting(StorageKey.MEDIA_AUTO_LOAD_POLICY, policy) } hasShownCreateWalletGuideToast(pubkey: string) { @@ -665,7 +828,7 @@ class LocalStorageService { return } this.shownCreateWalletGuideToastPubkeys.add(pubkey) - window.localStorage.setItem( + this.persistSetting( StorageKey.SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS, JSON.stringify(Array.from(this.shownCreateWalletGuideToastPubkeys)) ) @@ -678,7 +841,7 @@ class LocalStorageService { setDefaultExpirationEnabled(enabled: boolean) { this.defaultExpirationEnabled = enabled - window.localStorage.setItem(StorageKey.DEFAULT_EXPIRATION_ENABLED, enabled.toString()) + this.persistSetting(StorageKey.DEFAULT_EXPIRATION_ENABLED, enabled.toString()) } getDefaultExpirationMonths() { @@ -688,7 +851,7 @@ class LocalStorageService { setDefaultExpirationMonths(months: number) { if (Number.isInteger(months) && months >= 0) { this.defaultExpirationMonths = months - window.localStorage.setItem(StorageKey.DEFAULT_EXPIRATION_MONTHS, months.toString()) + this.persistSetting(StorageKey.DEFAULT_EXPIRATION_MONTHS, months.toString()) } } @@ -699,7 +862,7 @@ class LocalStorageService { setDefaultQuietEnabled(enabled: boolean) { this.defaultQuietEnabled = enabled - window.localStorage.setItem(StorageKey.DEFAULT_QUIET_ENABLED, enabled.toString()) + this.persistSetting(StorageKey.DEFAULT_QUIET_ENABLED, enabled.toString()) } getDefaultQuietDays() { @@ -709,7 +872,7 @@ class LocalStorageService { setDefaultQuietDays(days: number) { if (Number.isInteger(days) && days >= 0) { this.defaultQuietDays = days - window.localStorage.setItem(StorageKey.DEFAULT_QUIET_DAYS, days.toString()) + this.persistSetting(StorageKey.DEFAULT_QUIET_DAYS, days.toString()) } } @@ -719,7 +882,7 @@ class LocalStorageService { setRespectQuietTags(respect: boolean) { this.respectQuietTags = respect - window.localStorage.setItem(StorageKey.RESPECT_QUIET_TAGS, respect.toString()) + this.persistSetting(StorageKey.RESPECT_QUIET_TAGS, respect.toString()) } getGlobalQuietMode() { @@ -728,7 +891,7 @@ class LocalStorageService { setGlobalQuietMode(enabled: boolean) { this.globalQuietMode = enabled - window.localStorage.setItem(StorageKey.GLOBAL_QUIET_MODE, enabled.toString()) + this.persistSetting(StorageKey.GLOBAL_QUIET_MODE, enabled.toString()) } getShowRssFeed() { @@ -737,7 +900,7 @@ class LocalStorageService { setShowRssFeed(show: boolean) { this.showRssFeed = show - window.localStorage.setItem(StorageKey.SHOW_RSS_FEED, show.toString()) + this.persistSetting(StorageKey.SHOW_RSS_FEED, show.toString()) } getPanelMode(): 'single' | 'double' { @@ -746,7 +909,7 @@ class LocalStorageService { setPanelMode(mode: 'single' | 'double') { this.panelMode = mode - window.localStorage.setItem(StorageKey.PANE_MODE, mode) + this.persistSetting(StorageKey.PANE_MODE, mode) } }