diff --git a/package.json b/package.json index 7de750cb..59f89338 100644 --- a/package.json +++ b/package.json @@ -59,17 +59,17 @@ "@types/throttle-debounce": "^5.0.2", "@vite-pwa/assets-generator": "^0.2.6", "@vite-pwa/sveltekit": "^0.6.6", - "@welshman/app": "^0.4.7", - "@welshman/content": "^0.4.7", - "@welshman/editor": "^0.4.7", - "@welshman/feeds": "^0.4.7", - "@welshman/lib": "^0.4.7", - "@welshman/net": "^0.4.7", - "@welshman/relay": "^0.4.7", - "@welshman/router": "^0.4.7", - "@welshman/signer": "^0.4.7", - "@welshman/store": "^0.4.7", - "@welshman/util": "^0.4.7", + "@welshman/app": "^0.5.0", + "@welshman/content": "^0.5.0", + "@welshman/editor": "^0.5.0", + "@welshman/feeds": "^0.5.0", + "@welshman/lib": "^0.5.0", + "@welshman/net": "^0.5.0", + "@welshman/relay": "^0.5.0", + "@welshman/router": "^0.5.0", + "@welshman/signer": "^0.5.0", + "@welshman/store": "^0.5.0", + "@welshman/util": "^0.5.0", "compressorjs": "^1.2.1", "daisyui": "^4.12.10", "date-picker-svelte": "^2.13.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b4d399d..9c550054 100644 Binary files a/pnpm-lock.yaml and b/pnpm-lock.yaml differ diff --git a/src/app/components/SpaceQuickLinks.svelte b/src/app/components/SpaceQuickLinks.svelte index 8c3fea88..f403d8a7 100644 --- a/src/app/components/SpaceQuickLinks.svelte +++ b/src/app/components/SpaceQuickLinks.svelte @@ -69,7 +69,7 @@ Goals {#if $notifications.has(goalsPath)}
{/if} @@ -81,7 +81,7 @@ Threads {#if $notifications.has(threadsPath)}
{/if} @@ -93,7 +93,7 @@ Calendar {#if $notifications.has(calendarPath)}
{/if} diff --git a/src/app/components/WalletAsReceivingAddress.svelte b/src/app/components/WalletAsReceivingAddress.svelte index a5f16fbf..2114e298 100644 --- a/src/app/components/WalletAsReceivingAddress.svelte +++ b/src/app/components/WalletAsReceivingAddress.svelte @@ -7,7 +7,7 @@ import {updateProfile} from "@app/core/commands" import {clearModals} from "@app/util/modal" import {userProfile, session} from "@welshman/app" - import {makeProfile, type NWCInfo} from "@welshman/util" + import {makeProfile} from "@welshman/util" const lud16 = getWalletAddress($session!.wallet!) diff --git a/src/app/components/WalletConnect.svelte b/src/app/components/WalletConnect.svelte index d6f97507..b8c6a38a 100644 --- a/src/app/components/WalletConnect.svelte +++ b/src/app/components/WalletConnect.svelte @@ -3,8 +3,7 @@ import {nwc} from "@getalby/sdk" import {sleep, assoc} from "@welshman/lib" import type {NWCInfo} from "@welshman/util" - import {pubkey, userProfile, updateSession, profilesByPubkey} from "@welshman/app" - import {makeProfile} from "@welshman/util" + import {pubkey, userProfile, updateSession} from "@welshman/app" import Link from "@lib/components/Link.svelte" import Cpu from "@assets/icons/cpu-bolt.svg?dataurl" import Lock from "@assets/icons/lock-keyhole.svg?dataurl" diff --git a/src/app/components/WalletUpdateReceivingAddress.svelte b/src/app/components/WalletUpdateReceivingAddress.svelte index f058eb12..0c941448 100644 --- a/src/app/components/WalletUpdateReceivingAddress.svelte +++ b/src/app/components/WalletUpdateReceivingAddress.svelte @@ -7,7 +7,6 @@ import ModalFooter from "@lib/components/ModalFooter.svelte" import Wallet from "@assets/icons/wallet.svg?dataurl" import CheckCircle from "@assets/icons/check-circle.svg?dataurl" - import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import {updateProfile} from "@app/core/commands" import {pushToast} from "@app/util/toast" diff --git a/src/app/core/commands.ts b/src/app/core/commands.ts index 4ca68e4a..7dd5c9be 100644 --- a/src/app/core/commands.ts +++ b/src/app/core/commands.ts @@ -84,7 +84,6 @@ import { userInboxRelaySelections, nip44EncryptToSelf, loadRelay, - clearStorage, dropSession, tagEventForComment, tagEventForQuote, @@ -111,7 +110,7 @@ import { } from "@app/core/state" import {loadAlertStatuses} from "@app/core/requests" import {platform, platformName, getPushInfo} from "@app/util/push" -import {clearFileStorage, preferencesStorageProvider} from "@src/lib/storage" +import {preferencesStorageProvider, collectionStorageProvider} from "@src/lib/storage" // Utils @@ -154,12 +153,10 @@ export const logout = async () => { dropSession($pubkey) } - await clearStorage() - localStorage.clear() - await preferencesStorageProvider.clear() - await clearFileStorage() + await preferencesStorageProvider.clear() + await collectionStorageProvider.clear() } // Synchronization diff --git a/src/app/util/storage.ts b/src/app/util/storage.ts new file mode 100644 index 00000000..be74144d --- /dev/null +++ b/src/app/util/storage.ts @@ -0,0 +1,211 @@ +import {on, throttle, fromPairs, batch, sortBy, concat} from "@welshman/lib" +import {throttled, freshness} from "@welshman/store" +import { + PROFILE, + FOLLOWS, + MUTES, + RELAYS, + BLOSSOM_SERVERS, + INBOX_RELAYS, + ROOMS, + APP_DATA, + ALERT_STATUS, + ALERT_EMAIL, + ALERT_WEB, + ALERT_IOS, + ALERT_ANDROID, + EVENT_TIME, + THREAD, + MESSAGE, + DIRECT_MESSAGE, + DIRECT_MESSAGE_FILE, +} from "@welshman/util" +import type {Zapper, TrustedEvent} from "@welshman/util" +import type {RepositoryUpdate} from "@welshman/relay" +import type {Handle, Relay} from "@welshman/app" +import { + plaintext, + tracker, + relays, + repository, + handles, + zappers, + onZapper, + onHandle, +} from "@welshman/app" +import {collectionStorageProvider} from "@lib/storage" + +const syncEvents = async () => { + repository.load(await collectionStorageProvider.get("events")) + + const rankEvent = (event: TrustedEvent) => { + switch (event.kind) { + case PROFILE: + return 1 + case FOLLOWS: + return 1 + case MUTES: + return 1 + case RELAYS: + return 1 + case BLOSSOM_SERVERS: + return 1 + case INBOX_RELAYS: + return 1 + case ROOMS: + return 1 + case APP_DATA: + return 1 + case ALERT_STATUS: + return 1 + case ALERT_EMAIL: + return 1 + case ALERT_WEB: + return 1 + case ALERT_IOS: + return 1 + case ALERT_ANDROID: + return 1 + case EVENT_TIME: + return 0.9 + case THREAD: + return 0.9 + case MESSAGE: + return 0.9 + case DIRECT_MESSAGE: + return 0.9 + case DIRECT_MESSAGE_FILE: + return 0.9 + default: + return 0 + } + } + + return on( + repository, + "update", + batch(3000, async (updates: RepositoryUpdate[]) => { + let added: TrustedEvent[] = [] + const removed = new Set() + + for (const update of updates) { + for (const event of update.added) { + if (rankEvent(event) > 0) { + added.push(event) + removed.delete(event.id) + } + } + + for (const id of update.removed) { + added = added.filter(event => !update.removed.has(event.id)) + removed.add(id) + } + } + + if (added.length > 0) { + let events = concat(await collectionStorageProvider.get("events"), added) + + // If we're well above our retention limit, drop lowest-ranked events + if (events.length > 15_000) { + events = sortBy(e => -rankEvent(e), events).slice(10_000) + } + + await collectionStorageProvider.set("events", events) + } + }), + ) +} + +const syncTracker = async () => { + const relaysById = new Map>() + + for (const [id, relays] of await collectionStorageProvider.get<[string, string[]]>("tracker")) { + relaysById.set(id, new Set(relays)) + } + + tracker.load(relaysById) + + let p = Promise.resolve() + + const updateOne = batch(3000, (ids: string[]) => { + p = p.then(() => { + collectionStorageProvider.add( + "tracker", + ids.map(id => [id, Array.from(tracker.getRelays(id))]), + ) + }) + }) + + const updateAll = throttle(3000, () => { + p = p.then(() => { + collectionStorageProvider.set("tracker", Array.from(tracker.relaysById.entries())) + }) + }) + + tracker.on("add", updateOne) + tracker.on("remove", updateOne) + tracker.on("load", updateAll) + tracker.on("clear", updateAll) + + return () => { + tracker.off("add", updateOne) + tracker.off("remove", updateOne) + tracker.off("load", updateAll) + tracker.off("clear", updateAll) + } +} + +const syncRelays = async () => { + relays.set(await collectionStorageProvider.get("relays")) + + return throttled(3000, relays).subscribe($relays => { + collectionStorageProvider.set("relays", $relays) + }) +} + +const syncHandles = async () => { + handles.set(await collectionStorageProvider.get("handles")) + + return onHandle( + batch(3000, async $handles => { + await collectionStorageProvider.add("handles", $handles) + }), + ) +} + +const syncZappers = async () => { + zappers.set(await collectionStorageProvider.get("zappers")) + + return onZapper( + batch(3000, async $zappers => { + await collectionStorageProvider.add("zappers", $zappers) + }), + ) +} + +const syncFreshness = async () => { + freshness.set(fromPairs(await collectionStorageProvider.get<[string, number]>("freshness"))) + + return throttled(3000, freshness).subscribe($freshness => { + collectionStorageProvider.set("freshness", Object.entries($freshness)) + }) +} + +const syncPlaintext = async () => { + plaintext.set(fromPairs(await collectionStorageProvider.get<[string, string]>("plaintext"))) + + return throttled(3000, plaintext).subscribe($plaintext => { + collectionStorageProvider.set("plaintext", Object.entries($plaintext)) + }) +} + +export const syncDataStores = () => + Promise.all([ + syncEvents(), + syncTracker(), + syncRelays(), + syncHandles(), + syncZappers(), + syncFreshness(), + syncPlaintext(), + ]) diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 02a879e2..b5abb458 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -1,17 +1,11 @@ +import {parseJson} from "@welshman/lib" import {type StorageProvider} from "@welshman/store" import {Preferences} from "@capacitor/preferences" -import type {Unsubscriber} from "svelte/store" -import {Encoding, Filesystem, type Directory} from "@capacitor/filesystem" -import {EventsStorageProvider} from "@lib/storage/events" -import {FreshnessStorageProvider} from "@lib/storage/freshness" -import {HandlesStorageProvider} from "@lib/storage/handles" -import {PlaintextStorageProvider} from "@lib/storage/plaintext" -import {RelaysStorageProvider} from "@lib/storage/relays" -import {TrackerStorageProvider} from "@lib/storage/tracker" -import {ZappersStorageProvider} from "@lib/storage/zappers" -import {repository, tracker, unsubscribers} from "@welshman/app" +import {Encoding, Filesystem, Directory} from "@capacitor/filesystem" export class PreferencesStorageProvider implements StorageProvider { + p = Promise.resolve() + get = async (key: string): Promise => { const result = await Preferences.get({key}) if (!result.value) return undefined @@ -22,74 +16,94 @@ export class PreferencesStorageProvider implements StorageProvider { } } - p = Promise.resolve() set = async (key: string, value: T): Promise => { - this.p = this.p.then(async () => await Preferences.set({key, value: JSON.stringify(value)})) + this.p = this.p.then(() => Preferences.set({key, value: JSON.stringify(value)})) + + await this.p + } + + clear = async () => { + this.p = this.p.then(() => Preferences.clear()) + + await this.p + } +} + +export const preferencesStorageProvider = new PreferencesStorageProvider() + +export class CollectionStorageProvider implements StorageProvider { + p = Promise.resolve() + + get = async (key: string): Promise => { + try { + const file = await Filesystem.readFile({ + path: key + ".json", + directory: Directory.Data, + encoding: Encoding.UTF8, + }) + + const items: T[] = [] + for (const line of file.data.toString().split("\n")) { + const item = parseJson(line) + + if (item) { + items.push(item) + } + } + + return items + } catch (err) { + // file doesn't exist, or isn't valid json + return [] + } + } + + set = async (key: string, value: T[]): Promise => { + this.p = this.p.then(async () => { + await Filesystem.writeFile({ + path: key + ".json", + directory: Directory.Data, + encoding: Encoding.UTF8, + data: value.map(v => JSON.stringify(v)).join("\n"), + }) + }) + + await this.p + } + + add = async (key: string, value: T[]): Promise => { + this.p = this.p.then(async () => { + await Filesystem.appendFile({ + path: key + ".json", + directory: Directory.Data, + encoding: Encoding.UTF8, + data: value.map(v => JSON.stringify(v)).join("\n"), + }) + }) + await this.p } clear = async (): Promise => { - await Preferences.clear() - this.p = Promise.resolve() + this.p = this.p.then(async () => { + try { + const res = await Filesystem.readdir({path: "./", directory: Directory.Data}) + + await Promise.all( + res.files.map(file => + Filesystem.deleteFile({ + path: file.name + ".json", + directory: Directory.Data, + }), + ), + ) + } catch (e) { + // Directory might not have been created + } + }) + + await this.p } } -// singleton instance of PreferencesStorageProvider -export const preferencesStorageProvider = new PreferencesStorageProvider() - -export interface FilesystemStorageProvider { - initializeState(): Promise - sync(): Unsubscriber -} - -export const getAllFromFile = async ( - filepath: string, - directory: Directory, - encoding: Encoding, -): Promise => { - try { - const contents = ( - await Filesystem.readFile({ - path: filepath, - directory, - encoding, - }) - ).data.toString() - - if (!contents || contents == "") { - return [] - } - - return JSON.parse(contents) - } catch (err) { - // file doesn't exist - return [] - } -} - -export const defaultStorageProviders = { - relays: new RelaysStorageProvider(), - handles: new RelaysStorageProvider(), - zappers: new ZappersStorageProvider(), - freshness: new FreshnessStorageProvider(), - plaintext: new PlaintextStorageProvider(), - tracker: new TrackerStorageProvider({tracker}), - events: new EventsStorageProvider({limit: 10_000, repository, rankEvent: () => 1}), -} - -export const initFileStorage = async (storageProviders: Record) => { - await Promise.all(Object.values(storageProviders).map(async provider => { - await provider.initializeState() - unsubscribers.push(provider.sync()) - })) -} - -export const clearFileStorage = async (): Promise => { - await EventsStorageProvider.clearStorage() - await FreshnessStorageProvider.clearStorage() - await HandlesStorageProvider.clearStorage() - await PlaintextStorageProvider.clearStorage() - await RelaysStorageProvider.clearStorage() - await TrackerStorageProvider.clearStorage() - await ZappersStorageProvider.clearStorage() -} \ No newline at end of file +export const collectionStorageProvider = new CollectionStorageProvider() diff --git a/src/lib/storage/events.ts b/src/lib/storage/events.ts deleted file mode 100644 index 9efbdfe1..00000000 --- a/src/lib/storage/events.ts +++ /dev/null @@ -1,102 +0,0 @@ -import {getAllFromFile, type FilesystemStorageProvider} from "@lib/storage" -import type {TrustedEvent} from "@welshman/util" -import type {Unsubscriber} from "svelte/store" -import {Filesystem, Directory, Encoding} from "@capacitor/filesystem" -import type {Repository, RepositoryUpdate} from "@welshman/relay" -import {on, sortBy} from "@welshman/lib" - -export class EventsStorageProvider implements FilesystemStorageProvider { - static filepath = "events.json" - static directory = Directory.Data - static encoding = Encoding.UTF8 - limit: number - repository: Repository - rankEvent: (event: TrustedEvent) => number - eventCount: number = 0 - isDeleting = false - - constructor({ - limit, - repository, - rankEvent, - }: { - limit: number - repository: Repository - rankEvent: (event: TrustedEvent) => number - }) { - this.limit = limit - this.repository = repository - this.rankEvent = rankEvent - } - - async initializeState(): Promise { - const events = await this.getAll() - this.eventCount = events.length - this.repository.load(events) - } - - sync(): Unsubscriber { - const onUpdate = async ({added, removed}: RepositoryUpdate) => { - // Only add events we want to keep - const keep = added.filter(e => this.rankEvent(e) > 0) - - // Add new events - if (keep.length > 0) { - await this.updateEvents(keep) - } - - // If we're well above our retention limit, drop lowest-ranked events - if (!this.isDeleting && this.eventCount > this.limit * 1.5) { - try { - this.isDeleting = true - - for (const event of sortBy(e => -this.rankEvent(e), await this.getAll()).slice( - this.limit, - )) { - removed.add(event.id) - } - - if (removed.size > 0) { - await this.deleteEvents(Array.from(removed)) - } - } finally { - this.isDeleting = false - } - } - - // Keep track of our total number of events. This isn't strictly accurate, but it's close enough - this.eventCount = this.eventCount + keep.length - removed.size - } - - return on(this.repository, "update", onUpdate) - } - - async getAll(): Promise { - return await getAllFromFile(EventsStorageProvider.filepath, EventsStorageProvider.directory, EventsStorageProvider.encoding) - } - - async writeAll(events: TrustedEvent[]) { - await Filesystem.writeFile({ - path: EventsStorageProvider.filepath, - directory: EventsStorageProvider.directory, - encoding: EventsStorageProvider.encoding, - data: JSON.stringify(events), - }) - } - - async updateEvents(events: TrustedEvent[]) { - const existing = await this.getAll() - const updated = existing.concat(events) - await this.writeAll(updated) - } - - async deleteEvents(ids: string[]) { - const existing = await this.getAll() - const updated = existing.filter(e => !ids.includes(e.id)) - await this.writeAll(updated) - } - - static async clearStorage(): Promise { - await Filesystem.deleteFile({path: EventsStorageProvider.filepath, directory: EventsStorageProvider.directory}) - } -} diff --git a/src/lib/storage/freshness.ts b/src/lib/storage/freshness.ts deleted file mode 100644 index ad8cb913..00000000 --- a/src/lib/storage/freshness.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {getAllFromFile, type FilesystemStorageProvider} from "@lib/storage" -import type {Unsubscriber} from "svelte/store" -import {Filesystem, Directory, Encoding} from "@capacitor/filesystem" -import {fromPairs} from "@welshman/lib" -import {freshness} from "@welshman/store" - -type KV = {key: string; value: any} - -export class FreshnessStorageProvider implements FilesystemStorageProvider { - static filepath = "freshness.json" - static directory = Directory.Data - static encoding = Encoding.UTF8 - - async initializeState(): Promise { - const items = await this.getAll() - freshness.set(fromPairs(items.map(item => [item.key, item.value]))) - } - - sync(): Unsubscriber { - const interval = setInterval(() => { - this.writeAll(freshness.get()) - }, 10_000) - - return () => clearInterval(interval) - } - - async getAll(): Promise { - return await getAllFromFile(FreshnessStorageProvider.filepath, FreshnessStorageProvider.directory, FreshnessStorageProvider.encoding) - } - - async writeAll(items: Record) { - const kvs = Object.entries(items).map(([key, value]) => ({key, value})) - - await Filesystem.writeFile({ - path: FreshnessStorageProvider.filepath, - directory: FreshnessStorageProvider.directory, - encoding: FreshnessStorageProvider.encoding, - data: JSON.stringify(kvs), - }) - } - - static async clearStorage(): Promise { - await Filesystem.deleteFile({path: FreshnessStorageProvider.filepath, directory: FreshnessStorageProvider.directory}) - } -} diff --git a/src/lib/storage/handles.ts b/src/lib/storage/handles.ts deleted file mode 100644 index 41ad520a..00000000 --- a/src/lib/storage/handles.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {getAllFromFile, type FilesystemStorageProvider} from "@lib/storage" -import {get, type Unsubscriber} from "svelte/store" -import {Filesystem, Directory, Encoding} from "@capacitor/filesystem" -import {batch} from "@welshman/lib" -import {handles, onHandle, type Handle} from "@welshman/app" - -export class HandlesStorageProvider implements FilesystemStorageProvider { - static filepath = "handles.json" - static directory = Directory.Data - static encoding = Encoding.UTF8 - - async initializeState(): Promise { - handles.set(await this.getAll()) - } - - sync(): Unsubscriber { - return onHandle(batch(300, () => this.saveState())) - } - - async getAll(): Promise { - return await getAllFromFile(HandlesStorageProvider.filepath, HandlesStorageProvider.directory, HandlesStorageProvider.encoding) - } - - async writeAll(handles: Handle[]) { - await Filesystem.writeFile({ - path: HandlesStorageProvider.filepath, - directory: HandlesStorageProvider.directory, - encoding: HandlesStorageProvider.encoding, - data: JSON.stringify(handles), - }) - } - - async saveState() { - await this.writeAll(get(handles)) - } - - static async clearStorage(): Promise { - await Filesystem.deleteFile({path: HandlesStorageProvider.filepath, directory: HandlesStorageProvider.directory}) - } -} diff --git a/src/lib/storage/plaintext.ts b/src/lib/storage/plaintext.ts deleted file mode 100644 index 01df9055..00000000 --- a/src/lib/storage/plaintext.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {getAllFromFile, type FilesystemStorageProvider} from "@lib/storage" -import type {Unsubscriber} from "svelte/store" -import {Filesystem, Directory, Encoding} from "@capacitor/filesystem" -import {fromPairs} from "@welshman/lib" -import {plaintext} from "@welshman/app" - -type KV = {key: string; value: any} - -export class PlaintextStorageProvider implements FilesystemStorageProvider { - static filepath = "plaintext.json" - static directory = Directory.Data - static encoding = Encoding.UTF8 - - async initializeState(): Promise { - const items = await this.getAll() - plaintext.set(fromPairs(items.map(item => [item.key, item.value]))) - } - - sync(): Unsubscriber { - const interval = setInterval(() => { - this.writeAll(plaintext.get()) - }, 10_000) - - return () => clearInterval(interval) - } - - async getAll(): Promise { - return await getAllFromFile(PlaintextStorageProvider.filepath, PlaintextStorageProvider.directory, PlaintextStorageProvider.encoding) - } - - async writeAll(items: Record) { - const kvs = Object.entries(items).map(([key, value]) => ({key, value})) - - await Filesystem.writeFile({ - path: PlaintextStorageProvider.filepath, - directory: PlaintextStorageProvider.directory, - encoding: PlaintextStorageProvider.encoding, - data: JSON.stringify(kvs), - }) - } - - static async clearStorage(): Promise { - await Filesystem.deleteFile({path: PlaintextStorageProvider.filepath, directory: PlaintextStorageProvider.directory}) - } -} diff --git a/src/lib/storage/relays.ts b/src/lib/storage/relays.ts deleted file mode 100644 index c4feb885..00000000 --- a/src/lib/storage/relays.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {getAllFromFile, type FilesystemStorageProvider} from "@lib/storage" -import type {Unsubscriber} from "svelte/store" -import {Filesystem, Directory, Encoding} from "@capacitor/filesystem" -import {relays, type Relay} from "@welshman/app" -import {throttled} from "@welshman/store" - -export class RelaysStorageProvider implements FilesystemStorageProvider { - static filepath = "relays.json" - static directory = Directory.Data - static encoding = Encoding.UTF8 - - async initializeState(): Promise { - relays.set(await this.getAll()) - } - - sync(): Unsubscriber { - return throttled(3000, relays).subscribe(() => this.saveState()) - } - - async getAll(): Promise { - return await getAllFromFile(RelaysStorageProvider.filepath, RelaysStorageProvider.directory, RelaysStorageProvider.encoding) - } - - async writeAll(relays: Relay[]) { - await Filesystem.writeFile({ - path: RelaysStorageProvider.filepath, - directory: RelaysStorageProvider.directory, - encoding: RelaysStorageProvider.encoding, - data: JSON.stringify(relays), - }) - } - - async saveState() { - await this.writeAll(relays.get()) - } - - static async clearStorage(): Promise { - await Filesystem.deleteFile({path: RelaysStorageProvider.filepath, directory: RelaysStorageProvider.directory}) - } -} diff --git a/src/lib/storage/tracker.ts b/src/lib/storage/tracker.ts deleted file mode 100644 index 59b46899..00000000 --- a/src/lib/storage/tracker.ts +++ /dev/null @@ -1,80 +0,0 @@ -import {getAllFromFile, type FilesystemStorageProvider} from "@lib/storage" -import type {Unsubscriber} from "svelte/store" -import {Filesystem, Directory, Encoding} from "@capacitor/filesystem" -import type {Tracker} from "@welshman/net" -import {call, on} from "@welshman/lib" - -type Entry = {id: string; relays: string[]} - -export class TrackerStorageProvider implements FilesystemStorageProvider { - static filepath = "tracker.json" - static directory = Directory.Data - static encoding = Encoding.UTF8 - tracker: Tracker - - constructor({tracker}: {tracker: Tracker}) { - this.tracker = tracker - } - - async initializeState(): Promise { - const relaysByid = new Map>() - - for (const {id, relays} of await this.getAll()) { - relaysByid.set(id, new Set(relays)) - } - - this.tracker.load(relaysByid) - } - - sync(): Unsubscriber { - const updateOne = async (id: string, relay: string) => { - const relays = new Set(await this.getAll()) - relays.add({id, relays: Array.from(this.tracker.getRelays(id))}) - await this.writeAll([...relays]) - } - - const updateAll = async () => { - await this.writeAll(Array.from(this.tracker.relaysById.entries()).map(([id, relays]) => ({ - id, - relays: Array.from(relays), - }))) - } - - const unsubscribers = [ - on(this.tracker, "add", updateOne), - on(this.tracker, "remove", updateOne), - on(this.tracker, "load", updateAll), - on(this.tracker, "clear", updateAll), - ] - - return () => { - unsubscribers.forEach(call) - } - } - - async getAll(): Promise { - return await getAllFromFile(TrackerStorageProvider.filepath, TrackerStorageProvider.directory, TrackerStorageProvider.encoding) - } - - async writeAll(relays: Entry[]) { - await Filesystem.writeFile({ - path: TrackerStorageProvider.filepath, - directory: TrackerStorageProvider.directory, - encoding: TrackerStorageProvider.encoding, - data: JSON.stringify(relays), - }) - } - - async saveState() { - await this.writeAll( - Array.from(this.tracker.relaysById.entries()).map(([id, relays]) => ({ - id, - relays: Array.from(relays), - })), - ) - } - - static async clearStorage(): Promise { - await Filesystem.deleteFile({path: TrackerStorageProvider.filepath, directory: TrackerStorageProvider.directory}) - } -} diff --git a/src/lib/storage/zappers.ts b/src/lib/storage/zappers.ts deleted file mode 100644 index 463b41ec..00000000 --- a/src/lib/storage/zappers.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {getAllFromFile, type FilesystemStorageProvider} from "@lib/storage" -import {get, type Unsubscriber} from "svelte/store" -import {Filesystem, Directory, Encoding} from "@capacitor/filesystem" -import {onZapper, zappers} from "@welshman/app" -import {batch} from "@welshman/lib" -import type {Zapper} from "@welshman/util" - -export class ZappersStorageProvider implements FilesystemStorageProvider { - static filepath = "zappers.json" - static directory = Directory.Data - static encoding = Encoding.UTF8 - - async initializeState(): Promise { - zappers.set(await this.getAll()) - } - - sync(): Unsubscriber { - return onZapper(batch(300, () => this.saveState())) - } - - async getAll(): Promise { - return await getAllFromFile(ZappersStorageProvider.filepath, ZappersStorageProvider.directory, ZappersStorageProvider.encoding) - } - - async writeAll(zappers: Zapper[]) { - await Filesystem.writeFile({ - path: ZappersStorageProvider.filepath, - directory: ZappersStorageProvider.directory, - encoding: ZappersStorageProvider.encoding, - data: JSON.stringify(zappers), - }) - } - - async saveState() { - await this.writeAll(get(zappers)) - } - - static async clearStorage(): Promise { - await Filesystem.deleteFile({path: ZappersStorageProvider.filepath, directory: ZappersStorageProvider.directory}) - } -} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 77af3367..d48063ec 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -24,27 +24,7 @@ WEEK, } from "@welshman/lib" import type {TrustedEvent, StampedEvent} from "@welshman/util" - import { - WRAP, - ALERT_STATUS, - ALERT_EMAIL, - ALERT_WEB, - ALERT_IOS, - ALERT_ANDROID, - EVENT_TIME, - APP_DATA, - THREAD, - MESSAGE, - INBOX_RELAYS, - DIRECT_MESSAGE, - DIRECT_MESSAGE_FILE, - MUTES, - FOLLOWS, - PROFILE, - RELAYS, - BLOSSOM_SERVERS, - ROOMS, - } from "@welshman/util" + import {WRAP} from "@welshman/util" import {Nip46Broker, makeSecret} from "@welshman/signer" import type {Socket, RelayMessage, ClientMessage} from "@welshman/net" import { @@ -61,7 +41,6 @@ } from "@welshman/net" import { loadRelay, - db, repository, pubkey, session, @@ -71,7 +50,6 @@ dropSession, loginWithNip01, loginWithNip46, - EventsStorageAdapter, loadRelaySelections, SignerLogEntryStatus, } from "@welshman/app" @@ -83,7 +61,7 @@ import * as net from "@welshman/net" import * as app from "@welshman/app" import {nsecDecode} from "@lib/util" - import {defaultStorageProviders, initFileStorage, preferencesStorageProvider} from "@lib/storage" + import {preferencesStorageProvider} from "@lib/storage" import AppContainer from "@app/components/AppContainer.svelte" import ModalContainer from "@app/components/ModalContainer.svelte" import {setupTracking} from "@app/util/tracking" @@ -105,11 +83,13 @@ import {initializePushNotifications} from "@app/util/push" import * as commands from "@app/core/commands" import * as requests from "@app/core/requests" - import * as notifications from "@app/util/notifications" import * as appState from "@app/core/state" - import {badgeCount, handleBadgeCountChanges} from "@app/util/notifications" + import * as notifications from "@app/util/notifications" + import * as storage from "@app/util/storage" import NewNotificationSound from "@src/app/components/NewNotificationSound.svelte" - import {EventsStorageProvider} from "@lib/storage/events" + + // Migration: delete old indexeddb database + indexedDB?.deleteDatabase('flotilla') // Migration: old nostrtalk instance used different sessions if ($session && !$signer) { @@ -123,6 +103,8 @@ const ready = $state(defer()) + let initialized = false + onMount(async () => { Object.assign(window, { get, @@ -240,7 +222,8 @@ document.documentElement.style["font-size"] = `${$userSettingsValues.font_size}rem` }) - if (!db) { + if (!initialized) { + initialized = true setupTracking() setupAnalytics() @@ -279,42 +262,10 @@ storage: preferencesStorageProvider, }) - await initFileStorage({...defaultStorageProviders, - events: new EventsStorageProvider({ - limit: 10_000, - repository, - rankEvent: (e: TrustedEvent) => { - if ( - [ - PROFILE, - FOLLOWS, - MUTES, - RELAYS, - BLOSSOM_SERVERS, - INBOX_RELAYS, - ROOMS, - APP_DATA, - ALERT_STATUS, - ALERT_EMAIL, - ALERT_WEB, - ALERT_IOS, - ALERT_ANDROID, - ].includes(e.kind) - ) { - return 1 - } - - if ( - [EVENT_TIME, THREAD, MESSAGE, DIRECT_MESSAGE, DIRECT_MESSAGE_FILE].includes(e.kind) - ) { - return 0.9 - } - - return 0 - }, - }), - }) + // Sync application data (relay, events, etc) + await storage.syncDataStores() + // Wait 300 ms for any throttled stores to finish sleep(300).then(() => ready.resolve()) defaultSocketPolicies.push( @@ -465,7 +416,7 @@ ) // subscribe to badge count for changes - badgeCount.subscribe(handleBadgeCountChanges) + notifications.badgeCount.subscribe(notifications.handleBadgeCountChanges) // Listen for signer errors, report to user via toast signerLog.subscribe(