diff --git a/src/agent/database.ts b/src/agent/database.ts index 38db2294..4955eb9f 100644 --- a/src/agent/database.ts +++ b/src/agent/database.ts @@ -1,10 +1,10 @@ import type {Writable} from 'svelte/store' import {debounce} from 'throttle-debounce' -import {partition, is, prop, find, without, pluck, all, identity} from 'ramda' +import {omit, partition, is, find, without, pluck, all, identity} from 'ramda' import {writable, derived} from 'svelte/store' -import {switcherFn, createMap, ensurePlural} from 'hurdak/lib/hurdak' +import {createMap, ensurePlural} from 'hurdak/lib/hurdak' import {log, error} from 'src/util/logger' -import {defer, where, now, timedelta, asyncIterableToArray} from 'src/util/misc' +import {where, now, timedelta} from 'src/util/misc' // Types @@ -56,8 +56,8 @@ const call = (topic, payload): Promise => { }) } -const callLocalforage = async (storeName, method, ...args) => { - const message = await call('localforage.call', {storeName, method, args}) +const callLocalforage = async (method, ...args) => { + const message = await call('localforage.call', {method, args}) if (message.topic !== 'localforage.return') { throw new Error(`callLocalforage received invalid response: ${message}`) @@ -66,55 +66,6 @@ const callLocalforage = async (storeName, method, ...args) => { return message.payload } - -// Methods that proxy localforage - -const iterate = (storeName, where = {}) => ({ - [Symbol.asyncIterator]() { - let done = false - let promise = defer() - const messages = [] - const channel = new Channel({ - onMessage: m => switcherFn(m.topic, { - 'localforage.item': () => { - promise.resolve() - messages.push(m.payload) - }, - 'localforage.iterationComplete': () => { - done = true - promise.resolve() - channel.close() - }, - default: () => { - throw new Error(`Invalid topic ${m.topic}`) - }, - }), - }) - - channel.send('localforage.iterate', {storeName, where}) - - const next = async () => { - if (done) { - return {done} - } - - const [value] = messages.splice(0, 1) - - if (value) { - return {done, value} - } else { - promise = defer() - - await promise - - return next() - } - } - - return {next} - } -}) - // Local copy of data so we can provide a sync observable interface. The worker // is just for storing data and processing expensive queries @@ -145,7 +96,7 @@ class Table { ;(async () => { const t = Date.now() - this._setAndNotify(await this.opts.initialize(this)) + this._setAndNotify(await this.opts.initialize(this) || {}) const {length: recordsCount} = Object.keys(this.data) const timeElapsed = Date.now() - t @@ -155,6 +106,9 @@ class Table { this.ready.set(true) })() } + _persist = debounce(10_000, () => { + callLocalforage('setItem', this.name, this.data) + }) _setAndNotify(newData) { // Update our local copy this.data = newData @@ -163,6 +117,9 @@ class Table { for (const cb of this.listeners) { cb(this.data) } + + // Save to localstorage + this._persist() } subscribe(cb) { this.listeners.push(cb) @@ -179,8 +136,6 @@ class Table { } this._setAndNotify({...this.data, ...newData}) - - callLocalforage(this.name, 'setItems', newData) } async bulkPatch(updates: Record): Promise { if (is(Array, updates)) { @@ -195,7 +150,7 @@ class Table { this.bulkPut({...this.data, ...newData}) } async bulkRemove(keys) { - await callLocalforage(this.name, 'removeItems', keys) + this._setAndNotify(omit(keys, this.data)) } put(item) { return this.bulkPut(createMap(this.pk, [item])) @@ -206,18 +161,15 @@ class Table { remove(k) { return this.bulkRemove([k]) } - drop() { - return callLocalforage(this.name, 'dropInstance') + async drop() { + return callLocalforage('removeItem', this.name) } - dump() { - return callLocalforage(this.name, 'dump') + async dump() { + return callLocalforage('getItem', this.name) } toArray() { return Object.values(this.data) } - iter(spec = {}) { - return asyncIterableToArray(iterate(name, spec), prop('v')) - } all(spec = {}) { return this.toArray().filter(where(spec)) } @@ -238,7 +190,7 @@ const relays = new Table('relays', 'url') const routes = new Table('routes', 'id', { initialize: async table => { const isValid = r => r.last_seen > now() - timedelta(7, 'days') - const [valid, invalid] = partition(isValid, Object.values(await table.dump())) + const [valid, invalid] = partition(isValid, Object.values(await table.dump() || {})) // Delete stale routes asynchronously table.bulkRemove(pluck('id', invalid)) diff --git a/src/agent/workers/database.js b/src/agent/workers/database.js index 6af4818e..5c2b3635 100644 --- a/src/agent/workers/database.js +++ b/src/agent/workers/database.js @@ -1,83 +1,22 @@ import lf from 'localforage' import memoryStorageDriver from 'localforage-memoryStorageDriver' import {switcherFn} from 'hurdak/lib/hurdak' -import {error, warn} from 'src/util/logger' -import {where} from 'src/util/misc' +import {error} from 'src/util/logger' // Firefox private mode doesn't have access to any storage options lf.defineDriver(memoryStorageDriver) lf.setDriver([lf.INDEXEDDB, lf.WEBSQL, lf.LOCALSTORAGE, 'memoryStorageDriver']) -const stores = {} - -const getStore = storeName => { - if (!stores[storeName]) { - stores[storeName] = lf.createInstance({name: 'coracle', storeName}) - } - - if (stores[storeName].dropped) { - warn(`Dropped instance ${storeName} was requested`) - - return null - } - - return stores[storeName] -} - addEventListener('message', async ({data: {topic, payload, channel}}) => { const reply = (topic, payload) => postMessage({channel, topic, payload}) switcherFn(topic, { 'localforage.call': async () => { - const {storeName, method, args} = payload - const instance = getStore(storeName) + const {method, args} = payload - if (instance) { - const result = await switcherFn(method, { - dump: () => new Promise(resolve => { - const result = {} + const result = await lf[method](...args) - instance.iterate( - (v, k, i) => { result[k] = v }, - () => resolve(result), - ) - }), - setItems: async () => { - for (const [k, v] of Object.entries(args[0])) { - await instance.setItem(k, v) - } - }, - removeItems: async () => { - for (const k of args[0]) { - await instance.removeItem(k) - } - }, - drop: async () => { - instance.dropped = true - await instance.drop() - }, - default: () => instance[method](...args), - }) - - reply('localforage.return', result) - } - }, - 'localforage.iterate': async () => { - const matchesFilter = where(payload.where) - const instance = getStore(payload.storeName) - - if (instance) { - instance.iterate( - (v, k, i) => { - if (matchesFilter(v)) { - reply('localforage.item', {v, k, i}) - } - }, - () => { - reply('localforage.iterationComplete') - }, - ) - } + reply('localforage.return', result) }, default: () => { throw new Error(`invalid topic: ${topic}`) diff --git a/src/partials/PersonInfo.svelte b/src/partials/PersonInfo.svelte index 38cf64b8..22592907 100644 --- a/src/partials/PersonInfo.svelte +++ b/src/partials/PersonInfo.svelte @@ -5,7 +5,7 @@ import {renderContent, noEvent} from "src/util/html" import {displayPerson} from "src/util/nostr" import Anchor from 'src/partials/Anchor.svelte' - import {getPubkeyWriteRelays} from 'src/agent/relays' + import {getPubkeyWriteRelays, sampleRelays} from 'src/agent/relays' import user from 'src/agent/user' import {routes} from "src/app/ui" @@ -14,7 +14,7 @@ const {petnamePubkeys} = user const addPetname = pubkey => { - const [{url}] = getPubkeyWriteRelays(pubkey) + const [{url}] = sampleRelays(getPubkeyWriteRelays(pubkey)) user.addPetname(pubkey, url, displayPerson(person)) } diff --git a/src/routes/Logout.svelte b/src/routes/Logout.svelte index 89c9fdbd..fb7ac346 100644 --- a/src/routes/Logout.svelte +++ b/src/routes/Logout.svelte @@ -3,17 +3,13 @@ import Anchor from 'src/partials/Anchor.svelte' import Content from "src/partials/Content.svelte" import database from 'src/agent/database' - import pool from 'src/agent/pool' let confirmed = false const confirm = async () => { confirmed = true - await Promise.all([ - ...pool.getConnections().map(c => c.disconnect()), - database.dropAll(), - ]) + await database.dropAll() localStorage.clear() diff --git a/src/util/misc.ts b/src/util/misc.ts index 8479a673..9419a921 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -201,15 +201,6 @@ export const defer = () => { return Object.assign(p, {resolve, reject}) } -export const asyncIterableToArray = async (it, f = identity) => { - const result = [] - for await (const x of it) { - result.push(f(x)) - } - - return result -} - export const avg = xs => sum(xs) / xs.length export const where = filters =>