Refactor storage

This commit is contained in:
Jon Staab
2025-09-30 16:11:49 -07:00
committed by hodlbod
parent 0a8c2faa74
commit a8d1c4bbbc
17 changed files with 488 additions and 711 deletions
+3 -3
View File
@@ -69,7 +69,7 @@
Goals
{#if $notifications.has(goalsPath)}
<div
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary-content"
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-neutral-content"
transition:fade>
</div>
{/if}
@@ -81,7 +81,7 @@
Threads
{#if $notifications.has(threadsPath)}
<div
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary-content"
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-neutral-content"
transition:fade>
</div>
{/if}
@@ -93,7 +93,7 @@
Calendar
{#if $notifications.has(calendarPath)}
<div
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary-content"
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-neutral-content"
transition:fade>
</div>
{/if}
@@ -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!)
+1 -2
View File
@@ -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"
@@ -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"
+3 -6
View File
@@ -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
+211
View File
@@ -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<TrustedEvent>("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<string>()
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<TrustedEvent>("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<string, Set<string>>()
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<Relay>("relays"))
return throttled(3000, relays).subscribe($relays => {
collectionStorageProvider.set("relays", $relays)
})
}
const syncHandles = async () => {
handles.set(await collectionStorageProvider.get<Handle>("handles"))
return onHandle(
batch(3000, async $handles => {
await collectionStorageProvider.add("handles", $handles)
}),
)
}
const syncZappers = async () => {
zappers.set(await collectionStorageProvider.get<Zapper>("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(),
])
+87 -73
View File
@@ -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 <T>(key: string): Promise<T | undefined> => {
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 <T>(key: string, value: T): Promise<void> => {
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 <T>(key: string): Promise<T[]> => {
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 <T>(key: string, value: T[]): Promise<void> => {
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 <T>(key: string, value: T[]): Promise<void> => {
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<void> => {
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<void>
sync(): Unsubscriber
}
export const getAllFromFile = async <T>(
filepath: string,
directory: Directory,
encoding: Encoding,
): Promise<T[]> => {
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<string, FilesystemStorageProvider>) => {
await Promise.all(Object.values(storageProviders).map(async provider => {
await provider.initializeState()
unsubscribers.push(provider.sync())
}))
}
export const clearFileStorage = async (): Promise<void> => {
await EventsStorageProvider.clearStorage()
await FreshnessStorageProvider.clearStorage()
await HandlesStorageProvider.clearStorage()
await PlaintextStorageProvider.clearStorage()
await RelaysStorageProvider.clearStorage()
await TrackerStorageProvider.clearStorage()
await ZappersStorageProvider.clearStorage()
}
export const collectionStorageProvider = new CollectionStorageProvider()
-102
View File
@@ -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<void> {
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<TrustedEvent[]> {
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<void> {
await Filesystem.deleteFile({path: EventsStorageProvider.filepath, directory: EventsStorageProvider.directory})
}
}
-45
View File
@@ -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<void> {
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<KV[]> {
return await getAllFromFile(FreshnessStorageProvider.filepath, FreshnessStorageProvider.directory, FreshnessStorageProvider.encoding)
}
async writeAll(items: Record<string, any>) {
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<void> {
await Filesystem.deleteFile({path: FreshnessStorageProvider.filepath, directory: FreshnessStorageProvider.directory})
}
}
-40
View File
@@ -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<void> {
handles.set(await this.getAll())
}
sync(): Unsubscriber {
return onHandle(batch(300, () => this.saveState()))
}
async getAll(): Promise<Handle[]> {
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<void> {
await Filesystem.deleteFile({path: HandlesStorageProvider.filepath, directory: HandlesStorageProvider.directory})
}
}
-45
View File
@@ -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<void> {
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<KV[]> {
return await getAllFromFile(PlaintextStorageProvider.filepath, PlaintextStorageProvider.directory, PlaintextStorageProvider.encoding)
}
async writeAll(items: Record<string, any>) {
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<void> {
await Filesystem.deleteFile({path: PlaintextStorageProvider.filepath, directory: PlaintextStorageProvider.directory})
}
}
-40
View File
@@ -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<void> {
relays.set(await this.getAll())
}
sync(): Unsubscriber {
return throttled(3000, relays).subscribe(() => this.saveState())
}
async getAll(): Promise<Relay[]> {
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<void> {
await Filesystem.deleteFile({path: RelaysStorageProvider.filepath, directory: RelaysStorageProvider.directory})
}
}
-80
View File
@@ -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<void> {
const relaysByid = new Map<string, Set<string>>()
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<Entry[]> {
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<void> {
await Filesystem.deleteFile({path: TrackerStorageProvider.filepath, directory: TrackerStorageProvider.directory})
}
}
-41
View File
@@ -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<void> {
zappers.set(await this.getAll())
}
sync(): Unsubscriber {
return onZapper(batch(300, () => this.saveState()))
}
async getAll(): Promise<Zapper[]> {
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<void> {
await Filesystem.deleteFile({path: ZappersStorageProvider.filepath, directory: ZappersStorageProvider.directory})
}
}
+15 -64
View File
@@ -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<void>())
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(