mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-10 10:57:04 +00:00
Add indexeddb storage
This commit is contained in:
6
package-lock.json
generated
6
package-lock.json
generated
@@ -18,6 +18,7 @@
|
|||||||
"@welshman/store": "^0.0.2",
|
"@welshman/store": "^0.0.2",
|
||||||
"@welshman/util": "^0.0.25",
|
"@welshman/util": "^0.0.25",
|
||||||
"daisyui": "^4.12.10",
|
"daisyui": "^4.12.10",
|
||||||
|
"idb": "^8.0.0",
|
||||||
"nostr-tools": "^2.7.2",
|
"nostr-tools": "^2.7.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||||
"throttle-debounce": "^5.0.2"
|
"throttle-debounce": "^5.0.2"
|
||||||
@@ -2724,6 +2725,11 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/idb": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw=="
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"@welshman/store": "^0.0.2",
|
"@welshman/store": "^0.0.2",
|
||||||
"@welshman/util": "^0.0.25",
|
"@welshman/util": "^0.0.25",
|
||||||
"daisyui": "^4.12.10",
|
"daisyui": "^4.12.10",
|
||||||
|
"idb": "^8.0.0",
|
||||||
"nostr-tools": "^2.7.2",
|
"nostr-tools": "^2.7.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||||
"throttle-debounce": "^5.0.2"
|
"throttle-debounce": "^5.0.2"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {derived} from "svelte/store"
|
import {derived, writable} from "svelte/store"
|
||||||
import {memoize, assoc} from '@welshman/lib'
|
import {memoize, assoc} from '@welshman/lib'
|
||||||
import type {CustomEvent} from '@welshman/util'
|
import type {CustomEvent} from '@welshman/util'
|
||||||
import {Repository, createEvent, Relay} from "@welshman/util"
|
import {Repository, createEvent, Relay} from "@welshman/util"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
const tryLogin = async () => {
|
const tryLogin = async () => {
|
||||||
const secret = makeSecret()
|
const secret = makeSecret()
|
||||||
const handle = await loadHandle(`${username}@${handler.domain}`)
|
const handle = await loadHandle(`${username}@${handler.domain}`)
|
||||||
|
console.log(handle)
|
||||||
|
|
||||||
if (!handle?.pubkey) {
|
if (!handle?.pubkey) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
|
|||||||
209
src/app/state.ts
209
src/app/state.ts
@@ -8,36 +8,46 @@ import type {SubscribeRequest} from '@welshman/net'
|
|||||||
import {publish, subscribe} from '@welshman/net'
|
import {publish, subscribe} from '@welshman/net'
|
||||||
import {decrypt} from '@welshman/signer'
|
import {decrypt} from '@welshman/signer'
|
||||||
import {deriveEvents, deriveEventsMapped, getter, withGetter} from "@welshman/store"
|
import {deriveEvents, deriveEventsMapped, getter, withGetter} from "@welshman/store"
|
||||||
import {synced, parseJson} from '@lib/util'
|
import {parseJson} from '@lib/util'
|
||||||
import type {Session, Handle, Relay} from '@app/types'
|
import type {Session, Handle, Relay} from '@app/types'
|
||||||
import {INDEXER_RELAYS, DUFFLEPUD_URL, repository, pk, getSession, getSigner, signer} from "@app/base"
|
import {INDEXER_RELAYS, DUFFLEPUD_URL, repository, pk, getSession, getSigner, signer} from "@app/base"
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
export const createCollection = <T>({
|
export const createCollection = <T>({
|
||||||
|
name,
|
||||||
store,
|
store,
|
||||||
getKey,
|
getKey,
|
||||||
isStale,
|
|
||||||
load,
|
load,
|
||||||
}: {
|
}: {
|
||||||
|
name: string,
|
||||||
store: Readable<T[]>,
|
store: Readable<T[]>,
|
||||||
getKey: (item: T) => string,
|
getKey: (item: T) => string,
|
||||||
isStale: (item: T) => boolean,
|
|
||||||
load: (key: string, ...args: any) => Promise<any>
|
load: (key: string, ...args: any) => Promise<any>
|
||||||
}) => {
|
}) => {
|
||||||
const indexStore = derived(store, $items => indexBy(getKey, $items))
|
const indexStore = derived(store, $items => indexBy(getKey, $items))
|
||||||
const getIndex = getter(indexStore)
|
const getIndex = getter(indexStore)
|
||||||
|
|
||||||
const getItem = (key: string) => getIndex().get(key)
|
const getItem = (key: string) => getIndex().get(key)
|
||||||
|
const pending = new Map<string, Promise<Maybe<T>>>
|
||||||
|
|
||||||
const loadItem = async (key: string, ...args: any[]) => {
|
const loadItem = async (key: string, ...args: any[]) => {
|
||||||
const item = getIndex().get(key)
|
if (getFreshness(name, key) > now() - 3600) {
|
||||||
|
return getIndex().get(key)
|
||||||
if (item && !isStale(item)) {
|
|
||||||
return item
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await load(key, ...args)
|
if (pending.has(key)) {
|
||||||
|
await pending.get(key)
|
||||||
|
} else {
|
||||||
|
setFreshness(name, key, now())
|
||||||
|
|
||||||
|
const promise = load(key, ...args)
|
||||||
|
|
||||||
|
pending.set(key, promise)
|
||||||
|
|
||||||
|
await promise
|
||||||
|
|
||||||
|
pending.delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
return getIndex().get(key)
|
return getIndex().get(key)
|
||||||
}
|
}
|
||||||
@@ -61,9 +71,7 @@ export const load = (request: SubscribeRequest) =>
|
|||||||
new Promise<Maybe<CustomEvent>>(resolve => {
|
new Promise<Maybe<CustomEvent>>(resolve => {
|
||||||
const sub = subscribe({closeOnEose: true, timeout: 3000, delay: 50, ...request})
|
const sub = subscribe({closeOnEose: true, timeout: 3000, delay: 50, ...request})
|
||||||
|
|
||||||
sub.emitter.on('event', (url: string, event: SignedEvent) => {
|
sub.emitter.on('event', (url: string, e: SignedEvent) => {
|
||||||
const e: CustomEvent = {...event, fetched_at: now()}
|
|
||||||
|
|
||||||
repository.publish(e)
|
repository.publish(e)
|
||||||
sub.close()
|
sub.close()
|
||||||
resolve(e)
|
resolve(e)
|
||||||
@@ -104,6 +112,25 @@ export const updateList = async (kind: number, modifyTags: ModifyTags) => {
|
|||||||
await publish({event, relays})
|
await publish({event, relays})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Freshness
|
||||||
|
|
||||||
|
export const freshness = withGetter(writable<Record<string, number>>({}))
|
||||||
|
|
||||||
|
export const getFreshnessKey = (ns: string, key: string) => `${ns}:${key}`
|
||||||
|
|
||||||
|
export const getFreshness = (ns: string, key: string) => freshness.get()[getFreshnessKey(ns, key)] || 0
|
||||||
|
|
||||||
|
export const setFreshness = (ns: string, key: string, ts: number) => freshness.update(assoc(getFreshnessKey(ns, key), ts))
|
||||||
|
|
||||||
|
export const setFreshnessBulk = (ns: string, updates: Record<string, number>) =>
|
||||||
|
freshness.update($freshness => {
|
||||||
|
for (const [key, ts] of Object.entries(updates)) {
|
||||||
|
$freshness[key] = ts
|
||||||
|
}
|
||||||
|
|
||||||
|
return $freshness
|
||||||
|
})
|
||||||
|
|
||||||
// Plaintext
|
// Plaintext
|
||||||
|
|
||||||
export const plaintext = withGetter(writable<Record<string, string>>({}))
|
export const plaintext = withGetter(writable<Record<string, string>>({}))
|
||||||
@@ -138,23 +165,23 @@ export const {
|
|||||||
loadItem: loadRelay,
|
loadItem: loadRelay,
|
||||||
// getItem: getRelay,
|
// getItem: getRelay,
|
||||||
} = createCollection({
|
} = createCollection({
|
||||||
store: relays,
|
name: 'relays',
|
||||||
getKey: (relay: Relay) => relay.url,
|
store: relays,
|
||||||
isStale: (relay: Relay) => relay.fetched_at < now() - 3600,
|
getKey: (relay: Relay) => relay.url,
|
||||||
load: batcher(800, async (urls: string[]) => {
|
load: batcher(800, async (urls: string[]) => {
|
||||||
const urlSet = new Set(urls)
|
const urlSet = new Set(urls)
|
||||||
const res = await postJson(`${DUFFLEPUD_URL}/relay/info`, {urls: Array.from(urlSet)})
|
const res = await postJson(`${DUFFLEPUD_URL}/relay/info`, {urls: Array.from(urlSet)})
|
||||||
const index = indexBy((item: any) => item.url, res?.data || [])
|
const index = indexBy((item: any) => item.url, res?.data || [])
|
||||||
const items: Relay[] = urls.map(url => {
|
const items: Relay[] = urls.map(url => {
|
||||||
const {info = {}} = index.get(url) || {}
|
const {info = {}} = index.get(url) || {}
|
||||||
|
|
||||||
return {...info, url, fetched_at: now()}
|
return {...info, url}
|
||||||
})
|
})
|
||||||
|
|
||||||
relays.update($relays => uniqBy($relay => $relay.url, [...$relays, ...items]))
|
relays.update($relays => uniqBy($relay => $relay.url, [...$relays, ...items]))
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handles
|
// Handles
|
||||||
@@ -162,29 +189,29 @@ export const {
|
|||||||
export const handles = writable<Handle[]>([])
|
export const handles = writable<Handle[]>([])
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
indexStore: handlesByPubkey,
|
indexStore: handlesByNip05,
|
||||||
getIndex: getHandlesByPubkey,
|
getIndex: getHandlesByNip05,
|
||||||
deriveItem: deriveHandle,
|
deriveItem: deriveHandle,
|
||||||
loadItem: loadHandle,
|
loadItem: loadHandle,
|
||||||
// getItem: getHandle,
|
// getItem: getHandle,
|
||||||
} = createCollection({
|
} = createCollection({
|
||||||
store: handles,
|
name: 'handles',
|
||||||
getKey: (handle: Handle) => handle.pubkey,
|
store: handles,
|
||||||
isStale: (handle: Handle) => handle.fetched_at < now() - 3600,
|
getKey: (handle: Handle) => handle.nip05,
|
||||||
load: batcher(800, async (nip05s: string[]) => {
|
load: batcher(800, async (nip05s: string[]) => {
|
||||||
const nip05Set = new Set(nip05s)
|
const nip05Set = new Set(nip05s)
|
||||||
const res = await postJson(`${DUFFLEPUD_URL}/handle/info`, {handles: Array.from(nip05Set)})
|
const res = await postJson(`${DUFFLEPUD_URL}/handle/info`, {handles: Array.from(nip05Set)})
|
||||||
const index = indexBy((item: any) => item.handle, res?.data || [])
|
const index = indexBy((item: any) => item.handle, res?.data || [])
|
||||||
const items: Handle[] = nip05s.map(nip05 => {
|
const items: Handle[] = nip05s.map(nip05 => {
|
||||||
const {info = {}} = index.get(nip05) || {}
|
const {info = {}} = index.get(nip05) || {}
|
||||||
|
|
||||||
return {...info, nip05, fetched_at: now()}
|
return {...info, nip05}
|
||||||
})
|
})
|
||||||
|
|
||||||
handles.update($handles => uniqBy($handle => $handle.nip05, [...$handles, ...items]))
|
handles.update($handles => uniqBy($handle => $handle.nip05, [...$handles, ...items]))
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Profiles
|
// Profiles
|
||||||
@@ -203,15 +230,15 @@ export const {
|
|||||||
loadItem: loadProfile,
|
loadItem: loadProfile,
|
||||||
// getItem: getProfile,
|
// getItem: getProfile,
|
||||||
} = createCollection({
|
} = createCollection({
|
||||||
store: profiles,
|
name: 'profiles',
|
||||||
getKey: profile => profile.event.pubkey,
|
store: profiles,
|
||||||
isStale: (profile: PublishedProfile) => profile.event.fetched_at < now() - 3600,
|
getKey: profile => profile.event.pubkey,
|
||||||
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
|
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
|
||||||
load({
|
load({
|
||||||
...request,
|
...request,
|
||||||
relays: [...relays, ...INDEXER_RELAYS],
|
relays: [...relays, ...INDEXER_RELAYS],
|
||||||
filters: [{kinds: [PROFILE], authors: [pubkey]}],
|
filters: [{kinds: [PROFILE], authors: [pubkey]}],
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Relay selections
|
// Relay selections
|
||||||
@@ -231,15 +258,15 @@ export const {
|
|||||||
loadItem: loadRelaySelections,
|
loadItem: loadRelaySelections,
|
||||||
// getItem: getRelaySelections,
|
// getItem: getRelaySelections,
|
||||||
} = createCollection({
|
} = createCollection({
|
||||||
store: relaySelections,
|
name: 'relaySelections',
|
||||||
getKey: relaySelections => relaySelections.pubkey,
|
store: relaySelections,
|
||||||
isStale: (relaySelections: CustomEvent) => relaySelections.fetched_at < now() - 3600,
|
getKey: relaySelections => relaySelections.pubkey,
|
||||||
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
|
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
|
||||||
load({
|
load({
|
||||||
...request,
|
...request,
|
||||||
relays: [...relays, ...INDEXER_RELAYS],
|
relays: [...relays, ...INDEXER_RELAYS],
|
||||||
filters: [{kinds: [RELAYS], authors: [pubkey]}],
|
filters: [{kinds: [RELAYS], authors: [pubkey]}],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Follows
|
// Follows
|
||||||
@@ -263,15 +290,15 @@ export const {
|
|||||||
loadItem: loadFollows,
|
loadItem: loadFollows,
|
||||||
// getItem: getFollows,
|
// getItem: getFollows,
|
||||||
} = createCollection({
|
} = createCollection({
|
||||||
store: follows,
|
name: 'follows',
|
||||||
getKey: follows => follows.event.pubkey,
|
store: follows,
|
||||||
isStale: (follows: PublishedList) => follows.event.fetched_at < now() - 3600,
|
getKey: follows => follows.event.pubkey,
|
||||||
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
|
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
|
||||||
load({
|
load({
|
||||||
...request,
|
...request,
|
||||||
relays: [...relays, ...INDEXER_RELAYS],
|
relays: [...relays, ...INDEXER_RELAYS],
|
||||||
filters: [{kinds: [FOLLOWS], authors: [pubkey]}],
|
filters: [{kinds: [FOLLOWS], authors: [pubkey]}],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mutes
|
// Mutes
|
||||||
@@ -295,15 +322,15 @@ export const {
|
|||||||
loadItem: loadMutes,
|
loadItem: loadMutes,
|
||||||
// getItem: getMutes,
|
// getItem: getMutes,
|
||||||
} = createCollection({
|
} = createCollection({
|
||||||
store: mutes,
|
name: 'mutes',
|
||||||
getKey: mute => mute.event.pubkey,
|
store: mutes,
|
||||||
isStale: (mutes: PublishedList) => mutes.event.fetched_at < now() - 3600,
|
getKey: mute => mute.event.pubkey,
|
||||||
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
|
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
|
||||||
load({
|
load({
|
||||||
...request,
|
...request,
|
||||||
relays: [...relays, ...INDEXER_RELAYS],
|
relays: [...relays, ...INDEXER_RELAYS],
|
||||||
filters: [{kinds: [MUTES], authors: [pubkey]}],
|
filters: [{kinds: [MUTES], authors: [pubkey]}],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Groups
|
// Groups
|
||||||
@@ -360,18 +387,18 @@ export const {
|
|||||||
loadItem: loadGroup,
|
loadItem: loadGroup,
|
||||||
// getItem: getGroup,
|
// getItem: getGroup,
|
||||||
} = createCollection({
|
} = createCollection({
|
||||||
store: groups,
|
name: 'groups',
|
||||||
getKey: (group: PublishedGroup) => group.nom,
|
store: groups,
|
||||||
isStale: (group: PublishedGroup) => group.event.fetched_at < now() - 3600,
|
getKey: (group: PublishedGroup) => group.nom,
|
||||||
load: (nom: string, relays: string[] = [], request: Partial<SubscribeRequest> = {}) =>
|
load: (nom: string, relays: string[] = [], request: Partial<SubscribeRequest> = {}) =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
...relays.map(loadRelay),
|
...relays.map(loadRelay),
|
||||||
load({
|
load({
|
||||||
...request,
|
...request,
|
||||||
relays,
|
relays,
|
||||||
filters: [{kinds: [GROUP_META], '#d': [nom]}],
|
filters: [{kinds: [GROUP_META], '#d': [nom]}],
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
// Qualified groups
|
// Qualified groups
|
||||||
@@ -433,9 +460,9 @@ export const {
|
|||||||
loadItem: loadGroupMembership,
|
loadItem: loadGroupMembership,
|
||||||
// getItem: getGroupMembership,
|
// getItem: getGroupMembership,
|
||||||
} = createCollection({
|
} = createCollection({
|
||||||
|
name: 'groupMemberships',
|
||||||
store: groupMemberships,
|
store: groupMemberships,
|
||||||
getKey: groupMembership => groupMembership.event.pubkey,
|
getKey: groupMembership => groupMembership.event.pubkey,
|
||||||
isStale: (groupMembership: PublishedGroupMembership) => groupMembership.event.fetched_at < now() - 3600,
|
|
||||||
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
|
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
|
||||||
load({
|
load({
|
||||||
...request,
|
...request,
|
||||||
|
|||||||
123
src/app/storage.ts
Normal file
123
src/app/storage.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import {openDB, deleteDB} from "idb"
|
||||||
|
import type {IDBPDatabase} from "idb"
|
||||||
|
import {throttle} from 'throttle-debounce'
|
||||||
|
import {writable} from 'svelte/store'
|
||||||
|
import type {Unsubscriber, Writable} from 'svelte/store'
|
||||||
|
import {isNil, randomInt} from '@welshman/lib'
|
||||||
|
import {withGetter} from '@welshman/store'
|
||||||
|
import {getJson, setJson} from '@lib/util'
|
||||||
|
import {pk, sessions, repository} from '@app/base'
|
||||||
|
|
||||||
|
export type Item = Record<string, any>
|
||||||
|
|
||||||
|
export type IndexedDbAdapter = {
|
||||||
|
keyPath: string
|
||||||
|
store: Writable<Item[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export let db: IDBPDatabase
|
||||||
|
|
||||||
|
export const dead = withGetter(writable(false))
|
||||||
|
|
||||||
|
export const subs: Unsubscriber[] = []
|
||||||
|
|
||||||
|
export const DB_NAME = "flotilla"
|
||||||
|
|
||||||
|
export const DB_VERSION = 1
|
||||||
|
|
||||||
|
export const getAll = async (name: string) => {
|
||||||
|
const tx = db.transaction(name, "readwrite")
|
||||||
|
const store = tx.objectStore(name)
|
||||||
|
const result = await store.getAll()
|
||||||
|
|
||||||
|
await tx.done
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export const bulkPut = async (name: string, data: any[]) => {
|
||||||
|
const tx = db.transaction(name, "readwrite")
|
||||||
|
const store = tx.objectStore(name)
|
||||||
|
|
||||||
|
await Promise.all(data.map(item => store.put(item)))
|
||||||
|
await tx.done
|
||||||
|
}
|
||||||
|
|
||||||
|
export const bulkDelete = async (name: string, ids: string[]) => {
|
||||||
|
const tx = db.transaction(name, "readwrite")
|
||||||
|
const store = tx.objectStore(name)
|
||||||
|
|
||||||
|
await Promise.all(ids.map(id => store.delete(id)))
|
||||||
|
await tx.done
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initIndexedDbAdapter = async (name: string, adapter: IndexedDbAdapter) => {
|
||||||
|
let copy = await getAll(name)
|
||||||
|
|
||||||
|
adapter.store.set(copy)
|
||||||
|
|
||||||
|
adapter.store.subscribe(
|
||||||
|
throttle(randomInt(3000, 5000), async (data: Item[]) => {
|
||||||
|
if (dead.get()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevIds = new Set(copy.map(item => item[adapter.keyPath]))
|
||||||
|
const currentIds = new Set(data.map(item => item[adapter.keyPath]))
|
||||||
|
const newRecords = data.filter(r => !prevIds.has(r[adapter.keyPath]))
|
||||||
|
const removedRecords = copy.filter(r => !currentIds.has(r[adapter.keyPath]))
|
||||||
|
|
||||||
|
copy = data
|
||||||
|
|
||||||
|
if (newRecords.length > 0) {
|
||||||
|
await bulkPut(name, newRecords)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedRecords.length > 0) {
|
||||||
|
await bulkDelete(name, removedRecords.map(item => item[adapter.keyPath]))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initStorage = async (adapters: Record<string, IndexedDbAdapter>) => {
|
||||||
|
if (!window.indexedDB) return
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", () => closeStorage())
|
||||||
|
|
||||||
|
db = await openDB(DB_NAME, DB_VERSION, {
|
||||||
|
upgrade(db: IDBPDatabase) {
|
||||||
|
const names = Object.keys(adapters)
|
||||||
|
|
||||||
|
for (const name of db.objectStoreNames) {
|
||||||
|
if (!names.includes(name)) {
|
||||||
|
db.deleteObjectStore(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [name, {keyPath}] of Object.entries(adapters)) {
|
||||||
|
try {
|
||||||
|
db.createObjectStore(name, {keyPath})
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(adapters)
|
||||||
|
.map(([name, config]) => initIndexedDbAdapter(name, config))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const closeStorage = async () => {
|
||||||
|
dead.set(true)
|
||||||
|
subs.forEach(unsub => unsub())
|
||||||
|
await db?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clearStorage = async () => {
|
||||||
|
await closeStorage()
|
||||||
|
await deleteDB(DB_NAME)
|
||||||
|
}
|
||||||
@@ -9,20 +9,11 @@ export type Session = {
|
|||||||
handler?: Nip46Handler
|
handler?: Nip46Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Relay = RelayProfile & {
|
export type Relay = RelayProfile
|
||||||
fetched_at: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Handle = {
|
export type Handle = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
nip05: string
|
nip05: string
|
||||||
nip46: string[]
|
nip46: string[]
|
||||||
relays: string[]
|
relays: string[]
|
||||||
fetched_at: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@welshman/util' {
|
|
||||||
interface CustomEvent extends TrustedEvent {
|
|
||||||
fetched_at: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import "@src/app.css"
|
import "@src/app.css"
|
||||||
import {onMount} from 'svelte'
|
import {onMount} from 'svelte'
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
|
import {createEventStore} from '@welshman/store'
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
import ModalBox from "@lib/components/ModalBox.svelte"
|
import ModalBox from "@lib/components/ModalBox.svelte"
|
||||||
import Toast from "@app/components/Toast.svelte"
|
import Toast from "@app/components/Toast.svelte"
|
||||||
@@ -9,8 +10,11 @@
|
|||||||
import PrimaryNav from "@app/components/PrimaryNav.svelte"
|
import PrimaryNav from "@app/components/PrimaryNav.svelte"
|
||||||
import SecondaryNav from "@app/components/SecondaryNav.svelte"
|
import SecondaryNav from "@app/components/SecondaryNav.svelte"
|
||||||
import {modals, clearModal} from "@app/modal"
|
import {modals, clearModal} from "@app/modal"
|
||||||
import {session} from "@app/base"
|
import {session, sessions, pk, repository} from "@app/base"
|
||||||
|
import {plaintext, relays, handles} from "@app/state"
|
||||||
|
import {initStorage} from "@app/storage"
|
||||||
|
|
||||||
|
let ready: Promise<void>
|
||||||
let dialog: HTMLDialogElement
|
let dialog: HTMLDialogElement
|
||||||
let prev: any
|
let prev: any
|
||||||
|
|
||||||
@@ -31,6 +35,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
ready = initStorage({
|
||||||
|
events: {
|
||||||
|
keyPath: 'id',
|
||||||
|
store: createEventStore(repository),
|
||||||
|
},
|
||||||
|
relays: {
|
||||||
|
keyPath: 'url',
|
||||||
|
store: relays,
|
||||||
|
},
|
||||||
|
handles: {
|
||||||
|
keyPath: 'nip05',
|
||||||
|
store: handles,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
dialog.addEventListener('close', () => {
|
dialog.addEventListener('close', () => {
|
||||||
if (modal) {
|
if (modal) {
|
||||||
clearModal()
|
clearModal()
|
||||||
@@ -39,26 +58,30 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div data-theme="dark">
|
{#await ready}
|
||||||
<div class="flex h-screen">
|
<div data-theme="dark" />
|
||||||
<PrimaryNav />
|
{:then}
|
||||||
<SecondaryNav />
|
<div data-theme="dark">
|
||||||
<div class="flex-grow bg-base-200">
|
<div class="flex h-screen">
|
||||||
<slot />
|
<PrimaryNav />
|
||||||
|
<SecondaryNav />
|
||||||
|
<div class="flex-grow bg-base-200">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<dialog bind:this={dialog} class="modal modal-bottom sm:modal-middle !z-modal">
|
||||||
|
{#if prev}
|
||||||
|
{#key prev}
|
||||||
|
<ModalBox {...prev} />
|
||||||
|
{/key}
|
||||||
|
<Toast />
|
||||||
|
{/if}
|
||||||
|
{#if $session}
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button />
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
</dialog>
|
||||||
|
<Toast />
|
||||||
</div>
|
</div>
|
||||||
<dialog bind:this={dialog} class="modal modal-bottom sm:modal-middle !z-modal">
|
{/await}
|
||||||
{#if prev}
|
|
||||||
{#key prev}
|
|
||||||
<ModalBox {...prev} />
|
|
||||||
{/key}
|
|
||||||
<Toast />
|
|
||||||
{/if}
|
|
||||||
{#if $session}
|
|
||||||
<form method="dialog" class="modal-backdrop">
|
|
||||||
<button />
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
</dialog>
|
|
||||||
<Toast />
|
|
||||||
</div>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user