16 changed files with 705 additions and 514 deletions
@ -0,0 +1,271 @@
@@ -0,0 +1,271 @@
|
||||
import { tagNameEquals } from '@/lib/tag' |
||||
import { Event, kinds } from 'nostr-tools' |
||||
|
||||
type TValue<T = any> = { |
||||
key: string |
||||
value: T |
||||
addedAt: number |
||||
} |
||||
|
||||
const StoreNames = { |
||||
PROFILE_EVENTS: 'profileEvents', |
||||
RELAY_LIST_EVENTS: 'relayListEvents', |
||||
FOLLOW_LIST_EVENTS: 'followListEvents', |
||||
MUTE_LIST_EVENTS: 'muteListEvents', |
||||
MUTE_DECRYPTED_TAGS: 'muteDecryptedTags', |
||||
RELAY_INFO_EVENTS: 'relayInfoEvents' |
||||
} |
||||
|
||||
class IndexedDbService { |
||||
static instance: IndexedDbService |
||||
static getInstance(): IndexedDbService { |
||||
if (!IndexedDbService.instance) { |
||||
IndexedDbService.instance = new IndexedDbService() |
||||
IndexedDbService.instance.init() |
||||
} |
||||
return IndexedDbService.instance |
||||
} |
||||
|
||||
private db: IDBDatabase | null = null |
||||
private initPromise: Promise<void> | null = null |
||||
|
||||
init(): Promise<void> { |
||||
if (!this.initPromise) { |
||||
this.initPromise = new Promise((resolve, reject) => { |
||||
const request = window.indexedDB.open('jumble', 2) |
||||
|
||||
request.onerror = (event) => { |
||||
reject(event) |
||||
} |
||||
|
||||
request.onsuccess = () => { |
||||
this.db = request.result |
||||
resolve() |
||||
} |
||||
|
||||
request.onupgradeneeded = () => { |
||||
this.db = request.result |
||||
if (!this.db.objectStoreNames.contains(StoreNames.PROFILE_EVENTS)) { |
||||
this.db.createObjectStore(StoreNames.PROFILE_EVENTS, { keyPath: 'key' }) |
||||
} |
||||
if (!this.db.objectStoreNames.contains(StoreNames.RELAY_LIST_EVENTS)) { |
||||
this.db.createObjectStore(StoreNames.RELAY_LIST_EVENTS, { keyPath: 'key' }) |
||||
} |
||||
if (!this.db.objectStoreNames.contains(StoreNames.FOLLOW_LIST_EVENTS)) { |
||||
this.db.createObjectStore(StoreNames.FOLLOW_LIST_EVENTS, { keyPath: 'key' }) |
||||
} |
||||
if (!this.db.objectStoreNames.contains(StoreNames.MUTE_LIST_EVENTS)) { |
||||
this.db.createObjectStore(StoreNames.MUTE_LIST_EVENTS, { keyPath: 'key' }) |
||||
} |
||||
if (!this.db.objectStoreNames.contains(StoreNames.MUTE_DECRYPTED_TAGS)) { |
||||
this.db.createObjectStore(StoreNames.MUTE_DECRYPTED_TAGS, { keyPath: 'key' }) |
||||
} |
||||
if (!this.db.objectStoreNames.contains(StoreNames.RELAY_INFO_EVENTS)) { |
||||
this.db.createObjectStore(StoreNames.RELAY_INFO_EVENTS, { keyPath: 'key' }) |
||||
} |
||||
} |
||||
}) |
||||
setTimeout(() => this.cleanUp(), 1000 * 60) // 1 minute
|
||||
} |
||||
return this.initPromise |
||||
} |
||||
|
||||
async putReplaceableEvent(event: Event): Promise<boolean> { |
||||
const storeName = this.getStoreNameByKind(event.kind) |
||||
if (!storeName) { |
||||
return Promise.reject('store name not found') |
||||
} |
||||
await this.initPromise |
||||
return new Promise((resolve, reject) => { |
||||
if (!this.db) { |
||||
return reject('database not initialized') |
||||
} |
||||
const transaction = this.db.transaction(storeName, 'readwrite') |
||||
const store = transaction.objectStore(storeName) |
||||
|
||||
const getRequest = store.get(event.pubkey) |
||||
getRequest.onsuccess = () => { |
||||
const oldValue = getRequest.result as TValue<Event> | undefined |
||||
if (oldValue && oldValue.value.created_at >= event.created_at) { |
||||
return resolve(false) |
||||
} |
||||
const putRequest = store.put(this.formatValue(event.pubkey, event)) |
||||
putRequest.onsuccess = () => { |
||||
resolve(true) |
||||
} |
||||
|
||||
putRequest.onerror = (event) => { |
||||
reject(event) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
async getReplaceableEvent(pubkey: string, kind: number): Promise<Event | undefined> { |
||||
const storeName = this.getStoreNameByKind(kind) |
||||
if (!storeName) { |
||||
return Promise.reject('store name not found') |
||||
} |
||||
await this.initPromise |
||||
return new Promise((resolve, reject) => { |
||||
if (!this.db) { |
||||
return reject('database not initialized') |
||||
} |
||||
const transaction = this.db.transaction(storeName, 'readonly') |
||||
const store = transaction.objectStore(storeName) |
||||
const request = store.get(pubkey) |
||||
|
||||
request.onsuccess = () => { |
||||
resolve((request.result as TValue<Event>)?.value) |
||||
} |
||||
|
||||
request.onerror = (event) => { |
||||
reject(event) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
async getMuteDecryptedTags(id: string): Promise<string[][]> { |
||||
await this.initPromise |
||||
return new Promise((resolve, reject) => { |
||||
if (!this.db) { |
||||
return reject('database not initialized') |
||||
} |
||||
const transaction = this.db.transaction(StoreNames.MUTE_DECRYPTED_TAGS, 'readonly') |
||||
const store = transaction.objectStore(StoreNames.MUTE_DECRYPTED_TAGS) |
||||
const request = store.get(id) |
||||
|
||||
request.onsuccess = () => { |
||||
resolve((request.result as TValue<string[][]>)?.value) |
||||
} |
||||
|
||||
request.onerror = (event) => { |
||||
reject(event) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
async putMuteDecryptedTags(id: string, tags: string[][]): Promise<void> { |
||||
await this.initPromise |
||||
return new Promise((resolve, reject) => { |
||||
if (!this.db) { |
||||
return reject('database not initialized') |
||||
} |
||||
const transaction = this.db.transaction(StoreNames.MUTE_DECRYPTED_TAGS, 'readwrite') |
||||
const store = transaction.objectStore(StoreNames.MUTE_DECRYPTED_TAGS) |
||||
|
||||
const putRequest = store.put(this.formatValue(id, tags)) |
||||
putRequest.onsuccess = () => { |
||||
resolve() |
||||
} |
||||
|
||||
putRequest.onerror = (event) => { |
||||
reject(event) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
async getAllRelayInfoEvents(): Promise<Event[]> { |
||||
await this.initPromise |
||||
return new Promise((resolve, reject) => { |
||||
if (!this.db) { |
||||
return reject('database not initialized') |
||||
} |
||||
const transaction = this.db.transaction(StoreNames.RELAY_INFO_EVENTS, 'readonly') |
||||
const store = transaction.objectStore(StoreNames.RELAY_INFO_EVENTS) |
||||
const request = store.getAll() |
||||
|
||||
request.onsuccess = () => { |
||||
resolve((request.result as TValue<Event>[])?.map((item) => item.value)) |
||||
} |
||||
|
||||
request.onerror = (event) => { |
||||
reject(event) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
async putRelayInfoEvent(event: Event): Promise<void> { |
||||
await this.initPromise |
||||
return new Promise((resolve, reject) => { |
||||
if (!this.db) { |
||||
return reject('database not initialized') |
||||
} |
||||
const dValue = event.tags.find(tagNameEquals('d'))?.[1] |
||||
if (!dValue) { |
||||
return resolve() |
||||
} |
||||
const transaction = this.db.transaction(StoreNames.RELAY_INFO_EVENTS, 'readwrite') |
||||
const store = transaction.objectStore(StoreNames.RELAY_INFO_EVENTS) |
||||
|
||||
const putRequest = store.put(this.formatValue(dValue, event)) |
||||
putRequest.onsuccess = () => { |
||||
resolve() |
||||
} |
||||
|
||||
putRequest.onerror = (event) => { |
||||
reject(event) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
private getStoreNameByKind(kind: number): string | undefined { |
||||
switch (kind) { |
||||
case kinds.Metadata: |
||||
return StoreNames.PROFILE_EVENTS |
||||
case kinds.RelayList: |
||||
return StoreNames.RELAY_LIST_EVENTS |
||||
case kinds.Contacts: |
||||
return StoreNames.FOLLOW_LIST_EVENTS |
||||
case kinds.Mutelist: |
||||
return StoreNames.MUTE_LIST_EVENTS |
||||
default: |
||||
return undefined |
||||
} |
||||
} |
||||
|
||||
private formatValue<T>(key: string, value: T): TValue<T> { |
||||
return { |
||||
key, |
||||
value, |
||||
addedAt: Date.now() |
||||
} |
||||
} |
||||
|
||||
private async cleanUp() { |
||||
await this.initPromise |
||||
if (!this.db) { |
||||
return |
||||
} |
||||
|
||||
const expirationTimestamp = Date.now() - 1000 * 60 * 60 * 24 // 1 day
|
||||
const transaction = this.db!.transaction(Object.values(StoreNames), 'readwrite') |
||||
await Promise.allSettled( |
||||
Object.values(StoreNames).map((storeName) => { |
||||
return new Promise<void>((resolve, reject) => { |
||||
const store = transaction.objectStore(storeName) |
||||
const request = store.openCursor() |
||||
request.onsuccess = (event) => { |
||||
const cursor = (event.target as IDBRequest).result |
||||
if (cursor) { |
||||
const value: TValue = cursor.value |
||||
if (value.addedAt < expirationTimestamp) { |
||||
cursor.delete() |
||||
} |
||||
cursor.continue() |
||||
} else { |
||||
resolve() |
||||
} |
||||
} |
||||
|
||||
request.onerror = (event) => { |
||||
reject(event) |
||||
} |
||||
}) |
||||
}) |
||||
) |
||||
} |
||||
} |
||||
|
||||
const instance = IndexedDbService.getInstance() |
||||
export default instance |
||||
@ -0,0 +1,215 @@
@@ -0,0 +1,215 @@
|
||||
import { StorageKey } from '@/constants' |
||||
import { isSameAccount } from '@/lib/account' |
||||
import { randomString } from '@/lib/random' |
||||
import { |
||||
TAccount, |
||||
TAccountPointer, |
||||
TFeedType, |
||||
TNoteListMode, |
||||
TRelaySet, |
||||
TThemeSetting |
||||
} from '@/types' |
||||
|
||||
const DEFAULT_RELAY_SETS: TRelaySet[] = [ |
||||
{ |
||||
id: randomString(), |
||||
name: 'Global', |
||||
relayUrls: ['wss://relay.damus.io/', 'wss://nos.lol/'] |
||||
}, |
||||
{ |
||||
id: randomString(), |
||||
name: 'Safer Global', |
||||
relayUrls: ['wss://nostr.wine/', 'wss://pyramid.fiatjaf.com/'] |
||||
}, |
||||
{ |
||||
id: randomString(), |
||||
name: 'Short Notes', |
||||
relayUrls: ['wss://140.f7z.io/'] |
||||
}, |
||||
{ |
||||
id: randomString(), |
||||
name: 'News', |
||||
relayUrls: ['wss://news.utxo.one/'] |
||||
}, |
||||
{ |
||||
id: randomString(), |
||||
name: 'Algo', |
||||
relayUrls: ['wss://algo.utxo.one'] |
||||
} |
||||
] |
||||
|
||||
class LocalStorageService { |
||||
static instance: LocalStorageService |
||||
|
||||
private relaySets: TRelaySet[] = [] |
||||
private activeRelaySetId: string | null = null |
||||
private feedType: TFeedType = 'relays' |
||||
private themeSetting: TThemeSetting = 'system' |
||||
private accounts: TAccount[] = [] |
||||
private currentAccount: TAccount | null = null |
||||
private noteListMode: TNoteListMode = 'posts' |
||||
|
||||
constructor() { |
||||
if (!LocalStorageService.instance) { |
||||
this.init() |
||||
LocalStorageService.instance = this |
||||
} |
||||
return LocalStorageService.instance |
||||
} |
||||
|
||||
init() { |
||||
this.themeSetting = |
||||
(window.localStorage.getItem(StorageKey.THEME_SETTING) as TThemeSetting) ?? 'system' |
||||
const accountsStr = window.localStorage.getItem(StorageKey.ACCOUNTS) |
||||
this.accounts = accountsStr ? JSON.parse(accountsStr) : [] |
||||
const currentAccountStr = window.localStorage.getItem(StorageKey.CURRENT_ACCOUNT) |
||||
this.currentAccount = currentAccountStr ? JSON.parse(currentAccountStr) : null |
||||
const feedType = window.localStorage.getItem(StorageKey.FEED_TYPE) |
||||
if (feedType && ['following', 'relays'].includes(feedType)) { |
||||
this.feedType = feedType as 'following' | 'relays' |
||||
} else { |
||||
this.feedType = 'relays' |
||||
} |
||||
const noteListModeStr = window.localStorage.getItem(StorageKey.NOTE_LIST_MODE) |
||||
this.noteListMode = |
||||
noteListModeStr && ['posts', 'postsAndReplies', 'pictures'].includes(noteListModeStr) |
||||
? (noteListModeStr as TNoteListMode) |
||||
: 'posts' |
||||
|
||||
const relaySetsStr = window.localStorage.getItem(StorageKey.RELAY_SETS) |
||||
if (!relaySetsStr) { |
||||
let relaySets: TRelaySet[] = [] |
||||
const legacyRelayGroupsStr = window.localStorage.getItem('relayGroups') |
||||
if (legacyRelayGroupsStr) { |
||||
const legacyRelayGroups = JSON.parse(legacyRelayGroupsStr) |
||||
relaySets = legacyRelayGroups.map((group: any) => { |
||||
return { |
||||
id: randomString(), |
||||
name: group.groupName, |
||||
relayUrls: group.relayUrls |
||||
} |
||||
}) |
||||
} |
||||
if (!relaySets.length) { |
||||
relaySets = DEFAULT_RELAY_SETS |
||||
} |
||||
const activeRelaySetId = relaySets[0].id |
||||
window.localStorage.setItem(StorageKey.RELAY_SETS, JSON.stringify(relaySets)) |
||||
window.localStorage.setItem(StorageKey.ACTIVE_RELAY_SET_ID, activeRelaySetId) |
||||
this.relaySets = relaySets |
||||
this.activeRelaySetId = activeRelaySetId |
||||
} else { |
||||
this.relaySets = JSON.parse(relaySetsStr) |
||||
this.activeRelaySetId = window.localStorage.getItem(StorageKey.ACTIVE_RELAY_SET_ID) ?? null |
||||
} |
||||
|
||||
// Clean up deprecated data
|
||||
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP) |
||||
window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP) |
||||
window.localStorage.removeItem(StorageKey.ACCOUNT_RELAY_LIST_EVENT_MAP) |
||||
window.localStorage.removeItem(StorageKey.ACCOUNT_MUTE_LIST_EVENT_MAP) |
||||
window.localStorage.removeItem(StorageKey.ACCOUNT_MUTE_DECRYPTED_TAGS_MAP) |
||||
} |
||||
|
||||
getRelaySets() { |
||||
return this.relaySets |
||||
} |
||||
|
||||
setRelaySets(relaySets: TRelaySet[]) { |
||||
this.relaySets = relaySets |
||||
window.localStorage.setItem(StorageKey.RELAY_SETS, JSON.stringify(this.relaySets)) |
||||
} |
||||
|
||||
getActiveRelaySetId() { |
||||
return this.activeRelaySetId |
||||
} |
||||
|
||||
setActiveRelaySetId(id: string | null) { |
||||
this.activeRelaySetId = id |
||||
if (id) { |
||||
window.localStorage.setItem(StorageKey.ACTIVE_RELAY_SET_ID, id) |
||||
} else { |
||||
window.localStorage.removeItem(StorageKey.ACTIVE_RELAY_SET_ID) |
||||
} |
||||
} |
||||
|
||||
getFeedType() { |
||||
return this.feedType |
||||
} |
||||
|
||||
setFeedType(feedType: TFeedType) { |
||||
this.feedType = feedType |
||||
window.localStorage.setItem(StorageKey.FEED_TYPE, this.feedType) |
||||
} |
||||
|
||||
getThemeSetting() { |
||||
return this.themeSetting |
||||
} |
||||
|
||||
setThemeSetting(themeSetting: TThemeSetting) { |
||||
window.localStorage.setItem(StorageKey.THEME_SETTING, themeSetting) |
||||
this.themeSetting = themeSetting |
||||
} |
||||
|
||||
getNoteListMode() { |
||||
return this.noteListMode |
||||
} |
||||
|
||||
setNoteListMode(mode: TNoteListMode) { |
||||
window.localStorage.setItem(StorageKey.NOTE_LIST_MODE, mode) |
||||
this.noteListMode = mode |
||||
} |
||||
|
||||
getAccounts() { |
||||
return this.accounts |
||||
} |
||||
|
||||
findAccount(account: TAccountPointer) { |
||||
return this.accounts.find((act) => isSameAccount(act, account)) |
||||
} |
||||
|
||||
getCurrentAccount() { |
||||
return this.currentAccount |
||||
} |
||||
|
||||
getAccountNsec(pubkey: string) { |
||||
const account = this.accounts.find((act) => act.pubkey === pubkey && act.signerType === 'nsec') |
||||
return account?.nsec |
||||
} |
||||
|
||||
getAccountNcryptsec(pubkey: string) { |
||||
const account = this.accounts.find( |
||||
(act) => act.pubkey === pubkey && act.signerType === 'ncryptsec' |
||||
) |
||||
return account?.ncryptsec |
||||
} |
||||
|
||||
addAccount(account: TAccount) { |
||||
if (this.accounts.find((act) => isSameAccount(act, account))) { |
||||
return |
||||
} |
||||
this.accounts.push(account) |
||||
window.localStorage.setItem(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) |
||||
return account |
||||
} |
||||
|
||||
removeAccount(account: TAccount) { |
||||
this.accounts = this.accounts.filter((act) => !isSameAccount(act, account)) |
||||
window.localStorage.setItem(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) |
||||
} |
||||
|
||||
switchAccount(account: TAccount | null) { |
||||
if (isSameAccount(this.currentAccount, account)) { |
||||
return |
||||
} |
||||
const act = this.accounts.find((act) => isSameAccount(act, account)) |
||||
if (!act) { |
||||
return |
||||
} |
||||
this.currentAccount = act |
||||
window.localStorage.setItem(StorageKey.CURRENT_ACCOUNT, JSON.stringify(act)) |
||||
} |
||||
} |
||||
|
||||
const instance = new LocalStorageService() |
||||
export default instance |
||||
@ -1,370 +0,0 @@
@@ -1,370 +0,0 @@
|
||||
import { StorageKey } from '@/constants' |
||||
import { isSameAccount } from '@/lib/account' |
||||
import { randomString } from '@/lib/random' |
||||
import { |
||||
TAccount, |
||||
TAccountPointer, |
||||
TFeedType, |
||||
TNoteListMode, |
||||
TRelaySet, |
||||
TThemeSetting |
||||
} from '@/types' |
||||
import { Event } from 'nostr-tools' |
||||
|
||||
const DEFAULT_RELAY_SETS: TRelaySet[] = [ |
||||
{ |
||||
id: randomString(), |
||||
name: 'Global', |
||||
relayUrls: ['wss://relay.damus.io/', 'wss://nos.lol/'] |
||||
}, |
||||
{ |
||||
id: randomString(), |
||||
name: 'Safer Global', |
||||
relayUrls: ['wss://nostr.wine/', 'wss://pyramid.fiatjaf.com/'] |
||||
}, |
||||
{ |
||||
id: randomString(), |
||||
name: 'Short Notes', |
||||
relayUrls: ['wss://140.f7z.io/'] |
||||
}, |
||||
{ |
||||
id: randomString(), |
||||
name: 'News', |
||||
relayUrls: ['wss://news.utxo.one/'] |
||||
}, |
||||
{ |
||||
id: randomString(), |
||||
name: 'Algo', |
||||
relayUrls: ['wss://algo.utxo.one'] |
||||
} |
||||
] |
||||
|
||||
class StorageService { |
||||
static instance: StorageService |
||||
|
||||
private relaySets: TRelaySet[] = [] |
||||
private activeRelaySetId: string | null = null |
||||
private feedType: TFeedType = 'relays' |
||||
private themeSetting: TThemeSetting = 'system' |
||||
private accounts: TAccount[] = [] |
||||
private currentAccount: TAccount | null = null |
||||
private noteListMode: TNoteListMode = 'posts' |
||||
private accountRelayListEventMap: Record<string, Event | undefined> = {} // pubkey -> relayListEvent
|
||||
private accountFollowListEventMap: Record<string, Event | undefined> = {} // pubkey -> followListEvent
|
||||
private accountMuteListEventMap: Record<string, Event | undefined> = {} // pubkey -> muteListEvent
|
||||
private accountMuteDecryptedTagsMap: Record< |
||||
string, |
||||
{ id: string; tags: string[][] } | undefined |
||||
> = {} // pubkey -> { id, tags }
|
||||
private accountProfileEventMap: Record<string, Event | undefined> = {} // pubkey -> profileEvent
|
||||
|
||||
constructor() { |
||||
if (!StorageService.instance) { |
||||
this.init() |
||||
StorageService.instance = this |
||||
} |
||||
return StorageService.instance |
||||
} |
||||
|
||||
init() { |
||||
this.themeSetting = |
||||
(window.localStorage.getItem(StorageKey.THEME_SETTING) as TThemeSetting) ?? 'system' |
||||
const accountsStr = window.localStorage.getItem(StorageKey.ACCOUNTS) |
||||
this.accounts = accountsStr ? JSON.parse(accountsStr) : [] |
||||
const currentAccountStr = window.localStorage.getItem(StorageKey.CURRENT_ACCOUNT) |
||||
this.currentAccount = currentAccountStr ? JSON.parse(currentAccountStr) : null |
||||
const feedType = window.localStorage.getItem(StorageKey.FEED_TYPE) |
||||
if (feedType && ['following', 'relays'].includes(feedType)) { |
||||
this.feedType = feedType as 'following' | 'relays' |
||||
} else { |
||||
this.feedType = 'relays' |
||||
} |
||||
const noteListModeStr = window.localStorage.getItem(StorageKey.NOTE_LIST_MODE) |
||||
this.noteListMode = |
||||
noteListModeStr && ['posts', 'postsAndReplies', 'pictures'].includes(noteListModeStr) |
||||
? (noteListModeStr as TNoteListMode) |
||||
: 'posts' |
||||
|
||||
const accountRelayListEventMapStr = window.localStorage.getItem( |
||||
StorageKey.ACCOUNT_RELAY_LIST_EVENT_MAP |
||||
) |
||||
this.accountRelayListEventMap = accountRelayListEventMapStr |
||||
? JSON.parse(accountRelayListEventMapStr) |
||||
: {} |
||||
const accountFollowListEventMapStr = window.localStorage.getItem( |
||||
StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP |
||||
) |
||||
this.accountFollowListEventMap = accountFollowListEventMapStr |
||||
? JSON.parse(accountFollowListEventMapStr) |
||||
: {} |
||||
const accountMuteListEventMapStr = window.localStorage.getItem( |
||||
StorageKey.ACCOUNT_MUTE_LIST_EVENT_MAP |
||||
) |
||||
this.accountMuteListEventMap = accountMuteListEventMapStr |
||||
? JSON.parse(accountMuteListEventMapStr) |
||||
: {} |
||||
const accountMuteDecryptedTagsMapStr = window.localStorage.getItem( |
||||
StorageKey.ACCOUNT_MUTE_DECRYPTED_TAGS_MAP |
||||
) |
||||
this.accountMuteDecryptedTagsMap = accountMuteDecryptedTagsMapStr |
||||
? JSON.parse(accountMuteDecryptedTagsMapStr) |
||||
: {} |
||||
const accountProfileEventMapStr = window.localStorage.getItem( |
||||
StorageKey.ACCOUNT_PROFILE_EVENT_MAP |
||||
) |
||||
this.accountProfileEventMap = accountProfileEventMapStr |
||||
? JSON.parse(accountProfileEventMapStr) |
||||
: {} |
||||
|
||||
const relaySetsStr = window.localStorage.getItem(StorageKey.RELAY_SETS) |
||||
if (!relaySetsStr) { |
||||
let relaySets: TRelaySet[] = [] |
||||
const legacyRelayGroupsStr = window.localStorage.getItem('relayGroups') |
||||
if (legacyRelayGroupsStr) { |
||||
const legacyRelayGroups = JSON.parse(legacyRelayGroupsStr) |
||||
relaySets = legacyRelayGroups.map((group: any) => { |
||||
return { |
||||
id: randomString(), |
||||
name: group.groupName, |
||||
relayUrls: group.relayUrls |
||||
} |
||||
}) |
||||
} |
||||
if (!relaySets.length) { |
||||
relaySets = DEFAULT_RELAY_SETS |
||||
} |
||||
const activeRelaySetId = relaySets[0].id |
||||
window.localStorage.setItem(StorageKey.RELAY_SETS, JSON.stringify(relaySets)) |
||||
window.localStorage.setItem(StorageKey.ACTIVE_RELAY_SET_ID, activeRelaySetId) |
||||
this.relaySets = relaySets |
||||
this.activeRelaySetId = activeRelaySetId |
||||
} else { |
||||
this.relaySets = JSON.parse(relaySetsStr) |
||||
this.activeRelaySetId = window.localStorage.getItem(StorageKey.ACTIVE_RELAY_SET_ID) ?? null |
||||
} |
||||
} |
||||
|
||||
getRelaySets() { |
||||
return this.relaySets |
||||
} |
||||
|
||||
setRelaySets(relaySets: TRelaySet[]) { |
||||
this.relaySets = relaySets |
||||
window.localStorage.setItem(StorageKey.RELAY_SETS, JSON.stringify(this.relaySets)) |
||||
} |
||||
|
||||
getActiveRelaySetId() { |
||||
return this.activeRelaySetId |
||||
} |
||||
|
||||
setActiveRelaySetId(id: string | null) { |
||||
this.activeRelaySetId = id |
||||
if (id) { |
||||
window.localStorage.setItem(StorageKey.ACTIVE_RELAY_SET_ID, id) |
||||
} else { |
||||
window.localStorage.removeItem(StorageKey.ACTIVE_RELAY_SET_ID) |
||||
} |
||||
} |
||||
|
||||
getFeedType() { |
||||
return this.feedType |
||||
} |
||||
|
||||
setFeedType(feedType: TFeedType) { |
||||
this.feedType = feedType |
||||
window.localStorage.setItem(StorageKey.FEED_TYPE, this.feedType) |
||||
} |
||||
|
||||
getThemeSetting() { |
||||
return this.themeSetting |
||||
} |
||||
|
||||
setThemeSetting(themeSetting: TThemeSetting) { |
||||
window.localStorage.setItem(StorageKey.THEME_SETTING, themeSetting) |
||||
this.themeSetting = themeSetting |
||||
} |
||||
|
||||
getNoteListMode() { |
||||
return this.noteListMode |
||||
} |
||||
|
||||
setNoteListMode(mode: TNoteListMode) { |
||||
window.localStorage.setItem(StorageKey.NOTE_LIST_MODE, mode) |
||||
this.noteListMode = mode |
||||
} |
||||
|
||||
getAccounts() { |
||||
return this.accounts |
||||
} |
||||
|
||||
findAccount(account: TAccountPointer) { |
||||
return this.accounts.find((act) => isSameAccount(act, account)) |
||||
} |
||||
|
||||
getCurrentAccount() { |
||||
return this.currentAccount |
||||
} |
||||
|
||||
getAccountNsec(pubkey: string) { |
||||
const account = this.accounts.find((act) => act.pubkey === pubkey && act.signerType === 'nsec') |
||||
return account?.nsec |
||||
} |
||||
|
||||
getAccountNcryptsec(pubkey: string) { |
||||
const account = this.accounts.find( |
||||
(act) => act.pubkey === pubkey && act.signerType === 'ncryptsec' |
||||
) |
||||
return account?.ncryptsec |
||||
} |
||||
|
||||
addAccount(account: TAccount) { |
||||
if (this.accounts.find((act) => isSameAccount(act, account))) { |
||||
return |
||||
} |
||||
this.accounts.push(account) |
||||
window.localStorage.setItem(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) |
||||
return account |
||||
} |
||||
|
||||
removeAccount(account: TAccount) { |
||||
this.accounts = this.accounts.filter((act) => !isSameAccount(act, account)) |
||||
delete this.accountFollowListEventMap[account.pubkey] |
||||
delete this.accountRelayListEventMap[account.pubkey] |
||||
delete this.accountMuteListEventMap[account.pubkey] |
||||
delete this.accountMuteDecryptedTagsMap[account.pubkey] |
||||
delete this.accountProfileEventMap[account.pubkey] |
||||
window.localStorage.setItem(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) |
||||
window.localStorage.setItem( |
||||
StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP, |
||||
JSON.stringify(this.accountFollowListEventMap) |
||||
) |
||||
window.localStorage.setItem( |
||||
StorageKey.ACCOUNT_MUTE_LIST_EVENT_MAP, |
||||
JSON.stringify(this.accountMuteListEventMap) |
||||
) |
||||
window.localStorage.setItem( |
||||
StorageKey.ACCOUNT_MUTE_DECRYPTED_TAGS_MAP, |
||||
JSON.stringify(this.accountMuteDecryptedTagsMap) |
||||
) |
||||
window.localStorage.setItem( |
||||
StorageKey.ACCOUNT_RELAY_LIST_EVENT_MAP, |
||||
JSON.stringify(this.accountRelayListEventMap) |
||||
) |
||||
window.localStorage.setItem( |
||||
StorageKey.ACCOUNT_PROFILE_EVENT_MAP, |
||||
JSON.stringify(this.accountProfileEventMap) |
||||
) |
||||
} |
||||
|
||||
switchAccount(account: TAccount | null) { |
||||
if (isSameAccount(this.currentAccount, account)) { |
||||
return |
||||
} |
||||
const act = this.accounts.find((act) => isSameAccount(act, account)) |
||||
if (!act) { |
||||
return |
||||
} |
||||
this.currentAccount = act |
||||
window.localStorage.setItem(StorageKey.CURRENT_ACCOUNT, JSON.stringify(act)) |
||||
} |
||||
|
||||
getAccountRelayListEvent(pubkey: string) { |
||||
return this.accountRelayListEventMap[pubkey] |
||||
} |
||||
|
||||
setAccountRelayListEvent(relayListEvent: Event) { |
||||
const pubkey = relayListEvent.pubkey |
||||
if ( |
||||
this.accountRelayListEventMap[pubkey] && |
||||
this.accountRelayListEventMap[pubkey].created_at > relayListEvent.created_at |
||||
) { |
||||
return false |
||||
} |
||||
this.accountRelayListEventMap[pubkey] = relayListEvent |
||||
window.localStorage.setItem( |
||||
StorageKey.ACCOUNT_RELAY_LIST_EVENT_MAP, |
||||
JSON.stringify(this.accountRelayListEventMap) |
||||
) |
||||
return true |
||||
} |
||||
|
||||
getAccountFollowListEvent(pubkey: string) { |
||||
return this.accountFollowListEventMap[pubkey] |
||||
} |
||||
|
||||
setAccountFollowListEvent(followListEvent: Event) { |
||||
const pubkey = followListEvent.pubkey |
||||
if ( |
||||
this.accountFollowListEventMap[pubkey] && |
||||
this.accountFollowListEventMap[pubkey].created_at > followListEvent.created_at |
||||
) { |
||||
return false |
||||
} |
||||
this.accountFollowListEventMap[pubkey] = followListEvent |
||||
window.localStorage.setItem( |
||||
StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP, |
||||
JSON.stringify(this.accountFollowListEventMap) |
||||
) |
||||
return true |
||||
} |
||||
|
||||
getAccountMuteListEvent(pubkey: string) { |
||||
return this.accountMuteListEventMap[pubkey] |
||||
} |
||||
|
||||
setAccountMuteListEvent(muteListEvent: Event) { |
||||
const pubkey = muteListEvent.pubkey |
||||
if ( |
||||
this.accountMuteListEventMap[pubkey] && |
||||
this.accountMuteListEventMap[pubkey].created_at > muteListEvent.created_at |
||||
) { |
||||
return false |
||||
} |
||||
this.accountMuteListEventMap[pubkey] = muteListEvent |
||||
window.localStorage.setItem( |
||||
StorageKey.ACCOUNT_MUTE_LIST_EVENT_MAP, |
||||
JSON.stringify(this.accountMuteListEventMap) |
||||
) |
||||
return true |
||||
} |
||||
|
||||
getAccountMuteDecryptedTags(muteListEvent: Event) { |
||||
const stored = this.accountMuteDecryptedTagsMap[muteListEvent.pubkey] |
||||
if (stored && stored.id === muteListEvent.id) { |
||||
return stored.tags |
||||
} |
||||
return null |
||||
} |
||||
|
||||
setAccountMuteDecryptedTags(muteListEvent: Event, tags: string[][]) { |
||||
this.accountMuteDecryptedTagsMap[muteListEvent.pubkey] = { id: muteListEvent.id, tags } |
||||
window.localStorage.setItem( |
||||
StorageKey.ACCOUNT_MUTE_DECRYPTED_TAGS_MAP, |
||||
JSON.stringify(this.accountMuteDecryptedTagsMap) |
||||
) |
||||
} |
||||
|
||||
getAccountProfileEvent(pubkey: string) { |
||||
return this.accountProfileEventMap[pubkey] |
||||
} |
||||
|
||||
setAccountProfileEvent(profileEvent: Event) { |
||||
const pubkey = profileEvent.pubkey |
||||
if ( |
||||
this.accountProfileEventMap[pubkey] && |
||||
this.accountProfileEventMap[pubkey].created_at > profileEvent.created_at |
||||
) { |
||||
return false |
||||
} |
||||
this.accountProfileEventMap[pubkey] = profileEvent |
||||
window.localStorage.setItem( |
||||
StorageKey.ACCOUNT_PROFILE_EVENT_MAP, |
||||
JSON.stringify(this.accountProfileEventMap) |
||||
) |
||||
return true |
||||
} |
||||
} |
||||
|
||||
const instance = new StorageService() |
||||
|
||||
export default instance |
||||
Loading…
Reference in new issue