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(