From 0cbc9874db5fcfc3a8b87d68893d9d1a2366e279 Mon Sep 17 00:00:00 2001 From: Jonathan Staab Date: Thu, 9 Feb 2023 08:58:06 -0600 Subject: [PATCH] Move rooms/messages over to localforage --- README.md | 4 +++ src/agent/data.js | 11 ++++---- src/agent/database.ts | 54 ++++++++++++++++++++++++++++++------- src/app/messages.js | 7 ++--- src/routes/Chat.svelte | 18 ++++++------- src/routes/ChatRoom.svelte | 8 +++--- src/routes/Logout.svelte | 4 ++- src/routes/Messages.svelte | 8 +++--- src/routes/RelayList.svelte | 4 +-- src/views/ChatEdit.svelte | 6 ++--- src/workers/database.js | 8 +++--- 11 files changed, 84 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 6ac3b951..fd014794 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,16 @@ If you like Coracle and want to support its development, you can donate sats via # Maintenance +- [ ] Stop using until to paginate, we skip a ton of stuff. Or use until per relay? + - Or just stop doing loading and listen +- [ ] If the latest message in a dm was the user, don't show notification - [ ] Normalize relay urls (lowercase, strip trailing slash) - [ ] Use nip 56 for reporting - https://github.com/nostr-protocol/nips/pull/205#issuecomment-1419234230 - [ ] Change network tab to list relays the user is connected to - [ ] Sync mentions box and in-reply mentions - [ ] Channels + - [ ] Damus has chats divided into DMs and requests - [ ] Ability to leave/mute DM conversation - [ ] Add petnames for channels - [ ] Add notifications for chat messages diff --git a/src/agent/data.js b/src/agent/data.js index 9da4a15e..2c553b0e 100644 --- a/src/agent/data.js +++ b/src/agent/data.js @@ -1,7 +1,7 @@ import Dexie, {liveQuery} from 'dexie' import {pick, isEmpty} from 'ramda' import {nip05} from 'nostr-tools' -import {noop, ensurePlural, switcherFn} from 'hurdak/lib/hurdak' +import {noop, createMap, ensurePlural, switcherFn} from 'hurdak/lib/hurdak' import {now} from 'src/util/misc' import {personKinds, Tags, roomAttrs, isRelay} from 'src/util/nostr' import database from 'src/agent/database' @@ -19,8 +19,6 @@ export const db = new Dexie('agent/data/db') db.version(13).stores({ relays: '++url, name', alerts: '++id, created_at', - messages: '++id, pubkey, recipient', - rooms: '++id, joined', }) export const updatePeople = async updates => { @@ -132,7 +130,7 @@ const processRoomEvents = async events => { continue } - const room = await db.table('rooms').get(roomId) + const room = await database.rooms.get(roomId) // Merge edits but don't let old ones override new ones if (room?.edited_at >= e.created_at) { @@ -156,7 +154,7 @@ const processRoomEvents = async events => { } if (!isEmpty(updates)) { - await db.table('rooms').bulkPut(Object.values(updates)) + await database.rooms.bulkPut(updates) } } @@ -165,8 +163,9 @@ const processMessages = async events => { .filter(e => e.kind === 4) .map(e => ({...e, recipient: Tags.from(e).type("p").values().first()})) + if (messages.length > 0) { - await db.table('messages').bulkPut(messages) + await database.messages.bulkPut(createMap('id', messages)) } } diff --git a/src/agent/database.ts b/src/agent/database.ts index 1cea434e..cd709406 100644 --- a/src/agent/database.ts +++ b/src/agent/database.ts @@ -1,3 +1,4 @@ +import {debounce} from 'throttle-debounce' import {is, prop, without} from 'ramda' import {writable} from 'svelte/store' import {switcherFn, ensurePlural, first} from 'hurdak/lib/hurdak' @@ -14,7 +15,8 @@ type Table = { name: string subscribe: (subscription: (value: any) => void) => (() => void) bulkPut: (data: object) => void - all: (where: object) => Promise + bulkPatch: (data: object) => void + all: (where?: object) => Promise get: (key: string) => any } @@ -29,7 +31,11 @@ class Channel { onMessage: (e: MessageEvent) => void constructor({onMessage}) { this.id = Math.random().toString().slice(2) - this.onMessage = e => onMessage(e.data as Message) + this.onMessage = e => { + if (e.data.channel === this.id) { + onMessage(e.data as Message) + } + } worker.addEventListener('message', this.onMessage) } @@ -70,9 +76,10 @@ const callLocalforage = async (storeName, method, ...args) => { const getItem = (storeName, ...args) => callLocalforage(storeName, 'getItem', ...args) const setItem = (storeName, ...args) => callLocalforage(storeName, 'setItem', ...args) const removeItem = (storeName, ...args) => callLocalforage(storeName, 'removeItem', ...args) -const length = (storeName) => callLocalforage(storeName, 'length') -const clear = (storeName) => callLocalforage(storeName, 'clear') -const keys = (storeName) => callLocalforage(storeName, 'keys') + +const length = storeName => callLocalforage(storeName, 'length') +const clear = storeName => callLocalforage(storeName, 'clear') +const keys = storeName => callLocalforage(storeName, 'keys') const iterate = (storeName, where = {}) => ({ [Symbol.asyncIterator]() { @@ -90,6 +97,9 @@ const iterate = (storeName, where = {}) => ({ promise.resolve() channel.close() }, + default: () => { + throw new Error(`Invalid topic ${m.topic}`) + }, }), }) @@ -147,7 +157,11 @@ const defineTable = (name: string): Table => { } } - const bulkPut = newData => { + const bulkPut = (newData: Record): void => { + if (is(Array, newData)) { + throw new Error(`Updates must be an object, not an array`) + } + setAndNotify({...data, ...newData}) // Sync to storage, keeping updates in order @@ -161,6 +175,19 @@ const defineTable = (name: string): Table => { }) as Promise } + const bulkPatch = (updates: Record): void => { + if (is(Array, updates)) { + throw new Error(`Updates must be an object, not an array`) + } + + const newData = {} + for (const [k, v] of Object.entries(updates)) { + newData[k] = {...data[k], ...v} + } + + bulkPut(newData) + } + const all = (where = {}) => asyncIterableToArray(iterate(name, where), prop('v')) const one = (where = {}) => first(all(where)) const get = k => data[k] @@ -175,12 +202,14 @@ const defineTable = (name: string): Table => { setAndNotify(initialData) })() - registry[name] = {name, subscribe, bulkPut, all, one, get} + registry[name] = {name, subscribe, bulkPut, bulkPatch, all, one, get} return registry[name] } const people = defineTable('people') +const rooms = defineTable('rooms') +const messages = defineTable('messages') // Helper to allow us to listen to changes of any given table @@ -218,10 +247,13 @@ const watch = (names, f) => { store.set(initialValue) } + // Debounce refresh so we don't get UI lag + const refresh = debounce(300, async () => store.set(await f(...tables))) + // Listen for changes - listener.subscribe(async name => { + listener.subscribe(name => { if (names.includes(name)) { - store.set(await f(...tables)) + refresh() } }) @@ -232,7 +264,9 @@ const watch = (names, f) => { const getPersonWithFallback = pubkey => people.get(pubkey) || {pubkey} +const clearAll = () => Promise.all(Object.keys(registry).map(clear)) + export default { getItem, setItem, removeItem, length, clear, keys, iterate, - watch, getPersonWithFallback, people, + watch, getPersonWithFallback, clearAll, people, rooms, messages, } diff --git a/src/app/messages.js b/src/app/messages.js index e850fec1..8b0a9fe0 100644 --- a/src/app/messages.js +++ b/src/app/messages.js @@ -1,7 +1,7 @@ import {pluck, reject} from 'ramda' import {get} from 'svelte/store' import {synced, now, timedelta, batch} from 'src/util/misc' -import {listen as _listen, db, user} from 'src/agent' +import {listen as _listen, database, user} from 'src/agent' import loaders from 'src/app/loaders' let listener @@ -23,10 +23,7 @@ const listen = async (relays, pubkey) => { const $user = get(user) // Reload annotated messages, don't alert about messages to self - const messages = reject( - e => e.pubkey === e.recipient, - await db.table('messages').toArray() - ) + const messages = reject(e => e.pubkey === e.recipient, await database.messages.all()) if (messages.length > 0) { await loaders.loadPeople(relays, pluck('pubkey', messages)) diff --git a/src/routes/Chat.svelte b/src/routes/Chat.svelte index 0f57515b..14ca4204 100644 --- a/src/routes/Chat.svelte +++ b/src/routes/Chat.svelte @@ -1,10 +1,10 @@