mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-19 11:43:35 +00:00
Switch from multiple instances of localforage to dumping everything into one key per table. Fix logout as well.
This commit is contained in:
parent
0d97800713
commit
3ea222b120
@ -1,10 +1,10 @@
|
|||||||
import type {Writable} from 'svelte/store'
|
import type {Writable} from 'svelte/store'
|
||||||
import {debounce} from 'throttle-debounce'
|
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 {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 {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
|
// Types
|
||||||
|
|
||||||
@ -56,8 +56,8 @@ const call = (topic, payload): Promise<Message> => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const callLocalforage = async (storeName, method, ...args) => {
|
const callLocalforage = async (method, ...args) => {
|
||||||
const message = await call('localforage.call', {storeName, method, args})
|
const message = await call('localforage.call', {method, args})
|
||||||
|
|
||||||
if (message.topic !== 'localforage.return') {
|
if (message.topic !== 'localforage.return') {
|
||||||
throw new Error(`callLocalforage received invalid response: ${message}`)
|
throw new Error(`callLocalforage received invalid response: ${message}`)
|
||||||
@ -66,55 +66,6 @@ const callLocalforage = async (storeName, method, ...args) => {
|
|||||||
return message.payload
|
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
|
// Local copy of data so we can provide a sync observable interface. The worker
|
||||||
// is just for storing data and processing expensive queries
|
// is just for storing data and processing expensive queries
|
||||||
|
|
||||||
@ -145,7 +96,7 @@ class Table {
|
|||||||
;(async () => {
|
;(async () => {
|
||||||
const t = Date.now()
|
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 {length: recordsCount} = Object.keys(this.data)
|
||||||
const timeElapsed = Date.now() - t
|
const timeElapsed = Date.now() - t
|
||||||
@ -155,6 +106,9 @@ class Table {
|
|||||||
this.ready.set(true)
|
this.ready.set(true)
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
|
_persist = debounce(10_000, () => {
|
||||||
|
callLocalforage('setItem', this.name, this.data)
|
||||||
|
})
|
||||||
_setAndNotify(newData) {
|
_setAndNotify(newData) {
|
||||||
// Update our local copy
|
// Update our local copy
|
||||||
this.data = newData
|
this.data = newData
|
||||||
@ -163,6 +117,9 @@ class Table {
|
|||||||
for (const cb of this.listeners) {
|
for (const cb of this.listeners) {
|
||||||
cb(this.data)
|
cb(this.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save to localstorage
|
||||||
|
this._persist()
|
||||||
}
|
}
|
||||||
subscribe(cb) {
|
subscribe(cb) {
|
||||||
this.listeners.push(cb)
|
this.listeners.push(cb)
|
||||||
@ -179,8 +136,6 @@ class Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._setAndNotify({...this.data, ...newData})
|
this._setAndNotify({...this.data, ...newData})
|
||||||
|
|
||||||
callLocalforage(this.name, 'setItems', newData)
|
|
||||||
}
|
}
|
||||||
async bulkPatch(updates: Record<string, object>): Promise<void> {
|
async bulkPatch(updates: Record<string, object>): Promise<void> {
|
||||||
if (is(Array, updates)) {
|
if (is(Array, updates)) {
|
||||||
@ -195,7 +150,7 @@ class Table {
|
|||||||
this.bulkPut({...this.data, ...newData})
|
this.bulkPut({...this.data, ...newData})
|
||||||
}
|
}
|
||||||
async bulkRemove(keys) {
|
async bulkRemove(keys) {
|
||||||
await callLocalforage(this.name, 'removeItems', keys)
|
this._setAndNotify(omit(keys, this.data))
|
||||||
}
|
}
|
||||||
put(item) {
|
put(item) {
|
||||||
return this.bulkPut(createMap(this.pk, [item]))
|
return this.bulkPut(createMap(this.pk, [item]))
|
||||||
@ -206,18 +161,15 @@ class Table {
|
|||||||
remove(k) {
|
remove(k) {
|
||||||
return this.bulkRemove([k])
|
return this.bulkRemove([k])
|
||||||
}
|
}
|
||||||
drop() {
|
async drop() {
|
||||||
return callLocalforage(this.name, 'dropInstance')
|
return callLocalforage('removeItem', this.name)
|
||||||
}
|
}
|
||||||
dump() {
|
async dump() {
|
||||||
return callLocalforage(this.name, 'dump')
|
return callLocalforage('getItem', this.name)
|
||||||
}
|
}
|
||||||
toArray() {
|
toArray() {
|
||||||
return Object.values(this.data)
|
return Object.values(this.data)
|
||||||
}
|
}
|
||||||
iter(spec = {}) {
|
|
||||||
return asyncIterableToArray(iterate(name, spec), prop('v'))
|
|
||||||
}
|
|
||||||
all(spec = {}) {
|
all(spec = {}) {
|
||||||
return this.toArray().filter(where(spec))
|
return this.toArray().filter(where(spec))
|
||||||
}
|
}
|
||||||
@ -238,7 +190,7 @@ const relays = new Table('relays', 'url')
|
|||||||
const routes = new Table('routes', 'id', {
|
const routes = new Table('routes', 'id', {
|
||||||
initialize: async table => {
|
initialize: async table => {
|
||||||
const isValid = r => r.last_seen > now() - timedelta(7, 'days')
|
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
|
// Delete stale routes asynchronously
|
||||||
table.bulkRemove(pluck('id', invalid))
|
table.bulkRemove(pluck('id', invalid))
|
||||||
|
@ -1,83 +1,22 @@
|
|||||||
import lf from 'localforage'
|
import lf from 'localforage'
|
||||||
import memoryStorageDriver from 'localforage-memoryStorageDriver'
|
import memoryStorageDriver from 'localforage-memoryStorageDriver'
|
||||||
import {switcherFn} from 'hurdak/lib/hurdak'
|
import {switcherFn} from 'hurdak/lib/hurdak'
|
||||||
import {error, warn} from 'src/util/logger'
|
import {error} from 'src/util/logger'
|
||||||
import {where} from 'src/util/misc'
|
|
||||||
|
|
||||||
// Firefox private mode doesn't have access to any storage options
|
// Firefox private mode doesn't have access to any storage options
|
||||||
lf.defineDriver(memoryStorageDriver)
|
lf.defineDriver(memoryStorageDriver)
|
||||||
lf.setDriver([lf.INDEXEDDB, lf.WEBSQL, lf.LOCALSTORAGE, '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}}) => {
|
addEventListener('message', async ({data: {topic, payload, channel}}) => {
|
||||||
const reply = (topic, payload) => postMessage({channel, topic, payload})
|
const reply = (topic, payload) => postMessage({channel, topic, payload})
|
||||||
|
|
||||||
switcherFn(topic, {
|
switcherFn(topic, {
|
||||||
'localforage.call': async () => {
|
'localforage.call': async () => {
|
||||||
const {storeName, method, args} = payload
|
const {method, args} = payload
|
||||||
const instance = getStore(storeName)
|
|
||||||
|
|
||||||
if (instance) {
|
const result = await lf[method](...args)
|
||||||
const result = await switcherFn(method, {
|
|
||||||
dump: () => new Promise(resolve => {
|
|
||||||
const result = {}
|
|
||||||
|
|
||||||
instance.iterate(
|
reply('localforage.return', result)
|
||||||
(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')
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
default: () => {
|
default: () => {
|
||||||
throw new Error(`invalid topic: ${topic}`)
|
throw new Error(`invalid topic: ${topic}`)
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import {renderContent, noEvent} from "src/util/html"
|
import {renderContent, noEvent} from "src/util/html"
|
||||||
import {displayPerson} from "src/util/nostr"
|
import {displayPerson} from "src/util/nostr"
|
||||||
import Anchor from 'src/partials/Anchor.svelte'
|
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 user from 'src/agent/user'
|
||||||
import {routes} from "src/app/ui"
|
import {routes} from "src/app/ui"
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
const {petnamePubkeys} = user
|
const {petnamePubkeys} = user
|
||||||
|
|
||||||
const addPetname = pubkey => {
|
const addPetname = pubkey => {
|
||||||
const [{url}] = getPubkeyWriteRelays(pubkey)
|
const [{url}] = sampleRelays(getPubkeyWriteRelays(pubkey))
|
||||||
|
|
||||||
user.addPetname(pubkey, url, displayPerson(person))
|
user.addPetname(pubkey, url, displayPerson(person))
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,13 @@
|
|||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
import Content from "src/partials/Content.svelte"
|
import Content from "src/partials/Content.svelte"
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import pool from 'src/agent/pool'
|
|
||||||
|
|
||||||
let confirmed = false
|
let confirmed = false
|
||||||
|
|
||||||
const confirm = async () => {
|
const confirm = async () => {
|
||||||
confirmed = true
|
confirmed = true
|
||||||
|
|
||||||
await Promise.all([
|
await database.dropAll()
|
||||||
...pool.getConnections().map(c => c.disconnect()),
|
|
||||||
database.dropAll(),
|
|
||||||
])
|
|
||||||
|
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
|
|
||||||
|
@ -201,15 +201,6 @@ export const defer = () => {
|
|||||||
return Object.assign(p, {resolve, reject})
|
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 avg = xs => sum(xs) / xs.length
|
||||||
|
|
||||||
export const where = filters =>
|
export const where = filters =>
|
||||||
|
Loading…
Reference in New Issue
Block a user