mirror of
https://github.com/styppo/hamstr.git
synced 2024-10-18 13:33:22 +00:00
run pouchdb on a web worker.
This commit is contained in:
parent
171e8b3530
commit
ff232bb017
@ -8,8 +8,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.vue ./",
|
||||
"dev": "quasar dev --port 3001",
|
||||
"build": "quasar build",
|
||||
"publish": "quasar build && netlify deploy --dir=dist/spa/ --prod"
|
||||
"publish": "rm -r dist/spa && quasar build && netlify deploy --dir=dist/spa/ --prod"
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.0.0",
|
||||
|
@ -4,7 +4,6 @@
|
||||
<q-form @submit="sendPost">
|
||||
<q-input
|
||||
v-model="text"
|
||||
dense
|
||||
autogrow
|
||||
autofocus
|
||||
label="Say something"
|
||||
|
@ -2,7 +2,6 @@
|
||||
<q-form class="px-24" @submit="sendReply">
|
||||
<q-input
|
||||
v-model="text"
|
||||
dense
|
||||
autogrow
|
||||
autofocus
|
||||
label="Reply to this note"
|
||||
|
389
src/db.js
389
src/db.js
@ -1,381 +1,96 @@
|
||||
/* global emit */
|
||||
const worker = new Worker(new URL('./worker-db.js', import.meta.url))
|
||||
|
||||
import PouchDB from 'pouchdb-core'
|
||||
import PouchDBUpsert from 'pouchdb-upsert'
|
||||
import PouchDBMapReduce from 'pouchdb-mapreduce'
|
||||
import PouchDBAdapterIDB from 'pouchdb-adapter-idb'
|
||||
const hub = {}
|
||||
|
||||
PouchDB.plugin(PouchDBAdapterIDB).plugin(PouchDBMapReduce).plugin(PouchDBUpsert)
|
||||
worker.onmessage = ev => {
|
||||
let {id, success, error, data, stream} = JSON.parse(ev.data)
|
||||
|
||||
// instantiate db (every doc will be an event, that's it)
|
||||
// ~
|
||||
export const db = new PouchDB('nostr-events', {
|
||||
auto_compaction: true,
|
||||
revs_limit: 1
|
||||
})
|
||||
window.db = db
|
||||
|
||||
// db schema (views)
|
||||
// ~
|
||||
const DESIGN_VERSION = 3
|
||||
db.upsert('_design/main', current => {
|
||||
if (current && current.version >= DESIGN_VERSION) return false
|
||||
|
||||
return {
|
||||
version: DESIGN_VERSION,
|
||||
views: {
|
||||
profiles: {
|
||||
map: function (event) {
|
||||
if (event.kind === 0) {
|
||||
emit(event.pubkey)
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
homefeed: {
|
||||
map: function (event) {
|
||||
if (event.kind === 1) {
|
||||
emit(event.created_at)
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
mentions: {
|
||||
map: function (event) {
|
||||
if (event.kind === 1) {
|
||||
for (var i = 0; i < event.tags.length; i++) {
|
||||
var tag = event.tags[i]
|
||||
if (tag[0] === 'p') emit([tag[1], event.created_at])
|
||||
if (tag[0] === 'e') emit([tag[1], event.created_at])
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
messages: {
|
||||
map: function (event) {
|
||||
if (event.kind === 4) {
|
||||
for (var i = 0; i < event.tags.length; i++) {
|
||||
var tag = event.tags[i]
|
||||
if (tag[0] === 'p') {
|
||||
emit([tag[1], event.created_at])
|
||||
break
|
||||
}
|
||||
}
|
||||
emit([event.pubkey, event.created_at])
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
contactlists: {
|
||||
map: function (event) {
|
||||
if (event.kind === 3) {
|
||||
emit(event.pubkey)
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
followers: {
|
||||
map: function (event) {
|
||||
if (event.kind === 3) {
|
||||
for (let i = 0; i < event.tags.length; i++) {
|
||||
var tag = event.tags[i]
|
||||
if (tag.length >= 2 && tag[0] === 'p') {
|
||||
emit(tag[1], event.pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
petnames: {
|
||||
map: function (event) {
|
||||
if (event.kind === 3) {
|
||||
for (let i = 0; i < event.tags.length; i++) {
|
||||
var tag = event.tags[i]
|
||||
if (tag.length >= 4 && tag[0] === 'p') {
|
||||
emit(tag[1], [event.pubkey, tag[3]])
|
||||
}
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
}
|
||||
}
|
||||
if (stream) {
|
||||
console.log('db ~>>', id, data)
|
||||
hub[id](data)
|
||||
return
|
||||
}
|
||||
}).then(() => {
|
||||
// cleanup old views after a design doc change
|
||||
db.viewCleanup().then(r => console.log('view cleanup done', r))
|
||||
})
|
||||
|
||||
// delete old events after the first 1000 (this is slow, so do it after a while)
|
||||
//
|
||||
setTimeout(async () => {
|
||||
let result = await db.query('main/homefeed', {
|
||||
descending: true,
|
||||
skip: 1000,
|
||||
include_docs: true
|
||||
if (!success) {
|
||||
hub[id].reject(new Error(error))
|
||||
delete hub[id]
|
||||
return
|
||||
}
|
||||
|
||||
console.log('db ->', id, data)
|
||||
hub[id].resolve(data)
|
||||
delete hub[id]
|
||||
}
|
||||
|
||||
function call(name, args) {
|
||||
let id = name + ' ' + Math.random().toString().slice(-4)
|
||||
console.log('db <-', id, name, args)
|
||||
worker.postMessage(JSON.stringify({id, name, args}))
|
||||
return new Promise((resolve, reject) => {
|
||||
hub[id] = {resolve, reject}
|
||||
})
|
||||
result.rows.forEach(row => db.remove(row.doc))
|
||||
}, 1000 * 60 * 15 /* 15 minutes */)
|
||||
}
|
||||
|
||||
// general function for saving an event, with granular logic for each kind
|
||||
//
|
||||
export async function dbSave(event) {
|
||||
switch (event.kind) {
|
||||
case 0: {
|
||||
// first check if we don't have a newer metadata for this user
|
||||
let current = await dbGetProfile(event.pubkey)
|
||||
if (current && current.created_at >= event.created_at) {
|
||||
// don't save
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
case 1:
|
||||
break
|
||||
case 2:
|
||||
break
|
||||
case 3: {
|
||||
// first check if we don't have a newer contact list for this user
|
||||
let current = await dbGetContactList(event.pubkey)
|
||||
if (current && current.created_at >= event.created_at) {
|
||||
// don't save
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
case 4: {
|
||||
// cleanup extra fields if somehow they manage to get in here (they shouldn't)
|
||||
delete event.appended
|
||||
delete event.plaintext
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
event._id = event.id
|
||||
|
||||
try {
|
||||
await db.put(event)
|
||||
} catch (err) {
|
||||
if (err.name !== 'conflict') {
|
||||
console.error('unexpected error saving event', event, err)
|
||||
function stream(name, args, callback) {
|
||||
let id = name + ' ' + Math.random().toString().slice(-4)
|
||||
console.log('db <-', id, args)
|
||||
worker.postMessage(JSON.stringify({id, name, args, stream: true}))
|
||||
return {
|
||||
cancel() {
|
||||
worker.postMessage(JSON.stringify({id, cancel: true}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// db queries
|
||||
// ~
|
||||
export async function eraseDatabase() {
|
||||
return call('eraseDatabase', [])
|
||||
}
|
||||
export async function dbSave(event) {
|
||||
return call('dbSave', [event])
|
||||
}
|
||||
export async function dbGetHomeFeedNotes(
|
||||
limit = 50,
|
||||
since = Math.round(Date.now() / 1000)
|
||||
) {
|
||||
let result = await db.query('main/homefeed', {
|
||||
include_docs: true,
|
||||
descending: true,
|
||||
limit,
|
||||
startkey: since
|
||||
})
|
||||
return result.rows.map(r => r.doc)
|
||||
return call('dbGetHomeFeedNotes', [limit, since])
|
||||
}
|
||||
|
||||
export function onNewHomeFeedNote(callback = () => {}) {
|
||||
// listen for changes
|
||||
let changes = db.changes({
|
||||
live: true,
|
||||
since: 'now',
|
||||
include_docs: true,
|
||||
filter: '_view',
|
||||
view: 'main/homefeed'
|
||||
})
|
||||
|
||||
changes.on('change', change => callback(change.doc))
|
||||
|
||||
return changes
|
||||
return stream('onNewHomeFeedNote', [], callback)
|
||||
}
|
||||
|
||||
export async function dbGetChats(ourPubKey) {
|
||||
let result = await db.query('main/messages')
|
||||
|
||||
let chats = result.rows
|
||||
.map(r => r.key)
|
||||
.reduce((acc, [peer, date]) => {
|
||||
acc[peer] = acc[peer] || 0
|
||||
if (date > acc[peer]) acc[peer] = date
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
delete chats[ourPubKey]
|
||||
|
||||
return Object.entries(chats)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([peer, lastMessage]) => ({peer, lastMessage}))
|
||||
return call('dbGetChats', [ourPubKey])
|
||||
}
|
||||
|
||||
export async function dbGetMessages(
|
||||
peerPubKey,
|
||||
limit = 50,
|
||||
since = Math.round(Date.now() / 1000)
|
||||
) {
|
||||
let result = await db.query('main/messages', {
|
||||
include_docs: true,
|
||||
descending: true,
|
||||
startkey: [peerPubKey, since],
|
||||
endkey: [peerPubKey, 0],
|
||||
limit
|
||||
})
|
||||
return result.rows
|
||||
.map(r => r.doc)
|
||||
.reverse()
|
||||
.reduce((acc, event) => {
|
||||
if (!acc.length) return [event]
|
||||
let last = acc[acc.length - 1]
|
||||
if (
|
||||
last.pubkey === event.pubkey &&
|
||||
last.created_at + 120 >= event.created_at
|
||||
) {
|
||||
last.appended = last.appended || []
|
||||
last.appended.push(event)
|
||||
} else {
|
||||
acc.push(event)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
return call('dbGetMessages', [peerPubKey, limit, since])
|
||||
}
|
||||
|
||||
export function onNewMessage(peerPubKey, callback = () => {}) {
|
||||
// listen for changes
|
||||
let changes = db.changes({
|
||||
live: true,
|
||||
since: 'now',
|
||||
include_docs: true,
|
||||
filter: '_view',
|
||||
view: 'main/messages'
|
||||
})
|
||||
|
||||
changes.on('change', change => {
|
||||
if (
|
||||
change.doc.pubkey === peerPubKey ||
|
||||
change.doc.tags.find(([t, v]) => t === 'p' && v === peerPubKey)
|
||||
) {
|
||||
callback(change.doc)
|
||||
}
|
||||
})
|
||||
|
||||
return changes
|
||||
return stream('onNewMessage', [], callback)
|
||||
}
|
||||
|
||||
export async function dbGetEvent(id) {
|
||||
try {
|
||||
return await db.get(id)
|
||||
} catch (err) {
|
||||
if (err.name === 'not_found') return null
|
||||
else throw err
|
||||
}
|
||||
return call('dbGetEvent', [id])
|
||||
}
|
||||
|
||||
export async function dbGetMentions(ourPubKey, limit = 40, since, until) {
|
||||
let result = await db.query('main/mentions', {
|
||||
include_docs: true,
|
||||
descending: true,
|
||||
startkey: [ourPubKey, until],
|
||||
endkey: [ourPubKey, since],
|
||||
limit
|
||||
})
|
||||
return result.rows.map(r => r.doc)
|
||||
return call('dbGetMentions', [ourPubKey, limit, since, until])
|
||||
}
|
||||
|
||||
export function onNewMention(ourPubKey, callback = () => {}) {
|
||||
// listen for changes
|
||||
let changes = db.changes({
|
||||
live: true,
|
||||
since: 'now',
|
||||
include_docs: true,
|
||||
filter: '_view',
|
||||
view: 'main/mentions'
|
||||
})
|
||||
|
||||
changes.on('change', change => {
|
||||
if (change.doc.tags.find(([t, v]) => t === 'p' && v === ourPubKey)) {
|
||||
callback(change.doc)
|
||||
}
|
||||
})
|
||||
|
||||
return changes
|
||||
return stream('onNewMention', [ourPubKey], callback)
|
||||
}
|
||||
|
||||
export function onNewAnyMessage(callback = () => {}) {
|
||||
// listen for changes
|
||||
let changes = db.changes({
|
||||
live: true,
|
||||
since: 'now',
|
||||
include_docs: true,
|
||||
filter: '_view',
|
||||
view: 'main/messages'
|
||||
})
|
||||
|
||||
changes.on('change', change => {
|
||||
callback(change.doc)
|
||||
})
|
||||
|
||||
return changes
|
||||
return stream('onNewAnyMessage', [], callback)
|
||||
}
|
||||
|
||||
export async function dbGetUnreadNotificationsCount(ourPubKey, since) {
|
||||
let result = await db.query('main/mentions', {
|
||||
include_docs: false,
|
||||
descending: true,
|
||||
startkey: [ourPubKey, {}],
|
||||
endkey: [ourPubKey, since]
|
||||
})
|
||||
return result.rows.length
|
||||
return call('dbGetUnreadNotificationsCount', [ourPubKey, since])
|
||||
}
|
||||
|
||||
export async function dbGetUnreadMessages(pubkey, since) {
|
||||
let result = await db.query('main/messages', {
|
||||
include_docs: false,
|
||||
descending: true,
|
||||
startkey: [pubkey, {}],
|
||||
endkey: [pubkey, since]
|
||||
})
|
||||
return result.rows.length
|
||||
return call('dbGetUnreadMessages', [pubkey, since])
|
||||
}
|
||||
|
||||
export async function dbGetProfile(pubkey) {
|
||||
let result = await db.query('main/profiles', {
|
||||
include_docs: true,
|
||||
key: pubkey
|
||||
})
|
||||
switch (result.rows.length) {
|
||||
case 0:
|
||||
return null
|
||||
case 1:
|
||||
return result.rows[0].doc
|
||||
default: {
|
||||
let sorted = result.rows.sort(
|
||||
(a, b) => (b.doc?.created_at || 0) - (a.doc?.created_at || 0)
|
||||
)
|
||||
sorted
|
||||
.slice(1)
|
||||
.filter(row => row.doc)
|
||||
.forEach(row => db.remove(row.doc))
|
||||
return sorted[0].doc
|
||||
}
|
||||
}
|
||||
return call('dbGetProfile', [pubkey])
|
||||
}
|
||||
|
||||
export async function dbGetContactList(pubkey) {
|
||||
let result = await db.query('main/contactlists', {
|
||||
include_docs: true,
|
||||
key: pubkey
|
||||
})
|
||||
switch (result.rows.length) {
|
||||
case 0:
|
||||
return null
|
||||
case 1:
|
||||
return result.rows[0].doc
|
||||
default: {
|
||||
let sorted = result.rows.sort(
|
||||
(a, b) => (b.doc?.created_at || 0) - (a.doc?.created_at || 0)
|
||||
)
|
||||
sorted
|
||||
.slice(1)
|
||||
.filter(row => row.doc)
|
||||
.forEach(row => db.remove(row.doc))
|
||||
return sorted[0].doc
|
||||
}
|
||||
}
|
||||
return call('dbGetContactList', [pubkey])
|
||||
}
|
||||
|
@ -35,7 +35,7 @@
|
||||
<q-separator />
|
||||
<div class="my-8">
|
||||
<div class="text-lg p-4">Relays</div>
|
||||
<q-list class="mb-3" dense>
|
||||
<q-list class="mb-3">
|
||||
<q-item v-for="(opts, url) in $store.state.relays" :key="url">
|
||||
<q-item-section class="text-slate-800">
|
||||
<div class="flex-inline">
|
||||
@ -71,13 +71,7 @@
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-form @submit="addRelay">
|
||||
<q-input
|
||||
v-model="addingRelay"
|
||||
class="mx-3"
|
||||
filled
|
||||
dense
|
||||
label="Add a relay"
|
||||
>
|
||||
<q-input v-model="addingRelay" class="mx-3" filled label="Add a relay">
|
||||
<template #append>
|
||||
<q-btn
|
||||
label="Add"
|
||||
@ -150,7 +144,7 @@ import {LocalStorage} from 'quasar'
|
||||
import {nextTick} from 'vue'
|
||||
|
||||
import helpersMixin from '../utils/mixin'
|
||||
import {db} from '../db'
|
||||
import {eraseDatabase} from '../db'
|
||||
|
||||
export default {
|
||||
name: 'Settings',
|
||||
@ -239,7 +233,7 @@ export default {
|
||||
})
|
||||
.onOk(async () => {
|
||||
LocalStorage.clear()
|
||||
await db.destroy()
|
||||
await eraseDatabase()
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
|
419
src/worker-db.js
Normal file
419
src/worker-db.js
Normal file
@ -0,0 +1,419 @@
|
||||
/* global emit */
|
||||
|
||||
import PouchDB from 'pouchdb-core'
|
||||
import PouchDBUpsert from 'pouchdb-upsert'
|
||||
import PouchDBMapReduce from 'pouchdb-mapreduce'
|
||||
import PouchDBAdapterIDB from 'pouchdb-adapter-idb'
|
||||
|
||||
PouchDB.plugin(PouchDBAdapterIDB).plugin(PouchDBMapReduce).plugin(PouchDBUpsert)
|
||||
|
||||
// instantiate db (every doc will be an event, that's it)
|
||||
// ~
|
||||
const db = new PouchDB('nostr-events', {
|
||||
auto_compaction: true,
|
||||
revs_limit: 1
|
||||
})
|
||||
|
||||
// db schema (views)
|
||||
// ~
|
||||
const DESIGN_VERSION = 3
|
||||
db.upsert('_design/main', current => {
|
||||
if (current && current.version >= DESIGN_VERSION) return false
|
||||
|
||||
return {
|
||||
version: DESIGN_VERSION,
|
||||
views: {
|
||||
profiles: {
|
||||
map: function (event) {
|
||||
if (event.kind === 0) {
|
||||
emit(event.pubkey)
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
homefeed: {
|
||||
map: function (event) {
|
||||
if (event.kind === 1) {
|
||||
emit(event.created_at)
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
mentions: {
|
||||
map: function (event) {
|
||||
if (event.kind === 1) {
|
||||
for (var i = 0; i < event.tags.length; i++) {
|
||||
var tag = event.tags[i]
|
||||
if (tag[0] === 'p') emit([tag[1], event.created_at])
|
||||
if (tag[0] === 'e') emit([tag[1], event.created_at])
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
messages: {
|
||||
map: function (event) {
|
||||
if (event.kind === 4) {
|
||||
for (var i = 0; i < event.tags.length; i++) {
|
||||
var tag = event.tags[i]
|
||||
if (tag[0] === 'p') {
|
||||
emit([tag[1], event.created_at])
|
||||
break
|
||||
}
|
||||
}
|
||||
emit([event.pubkey, event.created_at])
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
contactlists: {
|
||||
map: function (event) {
|
||||
if (event.kind === 3) {
|
||||
emit(event.pubkey)
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
followers: {
|
||||
map: function (event) {
|
||||
if (event.kind === 3) {
|
||||
for (let i = 0; i < event.tags.length; i++) {
|
||||
var tag = event.tags[i]
|
||||
if (tag.length >= 2 && tag[0] === 'p') {
|
||||
emit(tag[1], event.pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
},
|
||||
petnames: {
|
||||
map: function (event) {
|
||||
if (event.kind === 3) {
|
||||
for (let i = 0; i < event.tags.length; i++) {
|
||||
var tag = event.tags[i]
|
||||
if (tag.length >= 4 && tag[0] === 'p') {
|
||||
emit(tag[1], [event.pubkey, tag[3]])
|
||||
}
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
// cleanup old views after a design doc change
|
||||
db.viewCleanup().then(r => console.log('view cleanup done', r))
|
||||
})
|
||||
|
||||
// delete old events after the first 1000 (this is slow, so do it after a while)
|
||||
//
|
||||
setTimeout(async () => {
|
||||
let result = await db.query('main/homefeed', {
|
||||
descending: true,
|
||||
skip: 1000,
|
||||
include_docs: true
|
||||
})
|
||||
result.rows.forEach(row => db.remove(row.doc))
|
||||
}, 1000 * 60 * 15 /* 15 minutes */)
|
||||
|
||||
const methods = {
|
||||
// delete everything
|
||||
//
|
||||
async eraseDatabase() {
|
||||
return await db.destroy()
|
||||
},
|
||||
|
||||
// general function for saving an event, with granular logic for each kind
|
||||
//
|
||||
async dbSave(event) {
|
||||
switch (event.kind) {
|
||||
case 0: {
|
||||
// first check if we don't have a newer metadata for this user
|
||||
let current = await methods.dbGetProfile(event.pubkey)
|
||||
if (current && current.created_at >= event.created_at) {
|
||||
// don't save
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
case 1:
|
||||
break
|
||||
case 2:
|
||||
break
|
||||
case 3: {
|
||||
// first check if we don't have a newer contact list for this user
|
||||
let current = await methods.dbGetContactList(event.pubkey)
|
||||
if (current && current.created_at >= event.created_at) {
|
||||
// don't save
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
case 4: {
|
||||
// cleanup extra fields if somehow they manage to get in here (they shouldn't)
|
||||
delete event.appended
|
||||
delete event.plaintext
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
event._id = event.id
|
||||
|
||||
try {
|
||||
await db.put(event)
|
||||
} catch (err) {
|
||||
if (err.name !== 'conflict') {
|
||||
console.error('unexpected error saving event', event, err)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// db queries
|
||||
// ~
|
||||
async dbGetHomeFeedNotes(limit = 50, since = Math.round(Date.now() / 1000)) {
|
||||
let result = await db.query('main/homefeed', {
|
||||
include_docs: true,
|
||||
descending: true,
|
||||
limit,
|
||||
startkey: since
|
||||
})
|
||||
return result.rows.map(r => r.doc)
|
||||
},
|
||||
|
||||
onNewHomeFeedNote(callback = () => {}) {
|
||||
// listen for changes
|
||||
let changes = db.changes({
|
||||
live: true,
|
||||
since: 'now',
|
||||
include_docs: true,
|
||||
filter: '_view',
|
||||
view: 'main/homefeed'
|
||||
})
|
||||
|
||||
changes.on('change', change => callback(change.doc))
|
||||
|
||||
return changes
|
||||
},
|
||||
|
||||
async dbGetChats(ourPubKey) {
|
||||
let result = await db.query('main/messages')
|
||||
|
||||
let chats = result.rows
|
||||
.map(r => r.key)
|
||||
.reduce((acc, [peer, date]) => {
|
||||
acc[peer] = acc[peer] || 0
|
||||
if (date > acc[peer]) acc[peer] = date
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
delete chats[ourPubKey]
|
||||
|
||||
return Object.entries(chats)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([peer, lastMessage]) => ({peer, lastMessage}))
|
||||
},
|
||||
|
||||
async dbGetMessages(
|
||||
peerPubKey,
|
||||
limit = 50,
|
||||
since = Math.round(Date.now() / 1000)
|
||||
) {
|
||||
let result = await db.query('main/messages', {
|
||||
include_docs: true,
|
||||
descending: true,
|
||||
startkey: [peerPubKey, since],
|
||||
endkey: [peerPubKey, 0],
|
||||
limit
|
||||
})
|
||||
return result.rows
|
||||
.map(r => r.doc)
|
||||
.reverse()
|
||||
.reduce((acc, event) => {
|
||||
if (!acc.length) return [event]
|
||||
let last = acc[acc.length - 1]
|
||||
if (
|
||||
last.pubkey === event.pubkey &&
|
||||
last.created_at + 120 >= event.created_at
|
||||
) {
|
||||
last.appended = last.appended || []
|
||||
last.appended.push(event)
|
||||
} else {
|
||||
acc.push(event)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
},
|
||||
|
||||
onNewMessage(peerPubKey, callback = () => {}) {
|
||||
// listen for changes
|
||||
let changes = db.changes({
|
||||
live: true,
|
||||
since: 'now',
|
||||
include_docs: true,
|
||||
filter: '_view',
|
||||
view: 'main/messages'
|
||||
})
|
||||
|
||||
changes.on('change', change => {
|
||||
if (
|
||||
change.doc.pubkey === peerPubKey ||
|
||||
change.doc.tags.find(([t, v]) => t === 'p' && v === peerPubKey)
|
||||
) {
|
||||
callback(change.doc)
|
||||
}
|
||||
})
|
||||
|
||||
return changes
|
||||
},
|
||||
|
||||
async dbGetEvent(id) {
|
||||
try {
|
||||
return await db.get(id)
|
||||
} catch (err) {
|
||||
if (err.name === 'not_found') return null
|
||||
else throw err
|
||||
}
|
||||
},
|
||||
|
||||
async dbGetMentions(ourPubKey, limit = 40, since, until) {
|
||||
let result = await db.query('main/mentions', {
|
||||
include_docs: true,
|
||||
descending: true,
|
||||
startkey: [ourPubKey, until],
|
||||
endkey: [ourPubKey, since],
|
||||
limit
|
||||
})
|
||||
return result.rows.map(r => r.doc)
|
||||
},
|
||||
|
||||
onNewMention(ourPubKey, callback = () => {}) {
|
||||
// listen for changes
|
||||
let changes = db.changes({
|
||||
live: true,
|
||||
since: 'now',
|
||||
include_docs: true,
|
||||
filter: '_view',
|
||||
view: 'main/mentions'
|
||||
})
|
||||
|
||||
changes.on('change', change => {
|
||||
if (change.doc.tags.find(([t, v]) => t === 'p' && v === ourPubKey)) {
|
||||
callback(change.doc)
|
||||
}
|
||||
})
|
||||
|
||||
return changes
|
||||
},
|
||||
|
||||
onNewAnyMessage(callback = () => {}) {
|
||||
// listen for changes
|
||||
let changes = db.changes({
|
||||
live: true,
|
||||
since: 'now',
|
||||
include_docs: true,
|
||||
filter: '_view',
|
||||
view: 'main/messages'
|
||||
})
|
||||
|
||||
changes.on('change', change => {
|
||||
callback(change.doc)
|
||||
})
|
||||
|
||||
return changes
|
||||
},
|
||||
|
||||
async dbGetUnreadNotificationsCount(ourPubKey, since) {
|
||||
let result = await db.query('main/mentions', {
|
||||
include_docs: false,
|
||||
descending: true,
|
||||
startkey: [ourPubKey, {}],
|
||||
endkey: [ourPubKey, since]
|
||||
})
|
||||
return result.rows.length
|
||||
},
|
||||
|
||||
async dbGetUnreadMessages(pubkey, since) {
|
||||
let result = await db.query('main/messages', {
|
||||
include_docs: false,
|
||||
descending: true,
|
||||
startkey: [pubkey, {}],
|
||||
endkey: [pubkey, since]
|
||||
})
|
||||
return result.rows.length
|
||||
},
|
||||
|
||||
async dbGetProfile(pubkey) {
|
||||
let result = await db.query('main/profiles', {
|
||||
include_docs: true,
|
||||
key: pubkey
|
||||
})
|
||||
switch (result.rows.length) {
|
||||
case 0:
|
||||
return null
|
||||
case 1:
|
||||
return result.rows[0].doc
|
||||
default: {
|
||||
let sorted = result.rows.sort(
|
||||
(a, b) => (b.doc?.created_at || 0) - (a.doc?.created_at || 0)
|
||||
)
|
||||
sorted
|
||||
.slice(1)
|
||||
.filter(row => row.doc)
|
||||
.forEach(row => db.remove(row.doc))
|
||||
return sorted[0].doc
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async dbGetContactList(pubkey) {
|
||||
let result = await db.query('main/contactlists', {
|
||||
include_docs: true,
|
||||
key: pubkey
|
||||
})
|
||||
switch (result.rows.length) {
|
||||
case 0:
|
||||
return null
|
||||
case 1:
|
||||
return result.rows[0].doc
|
||||
default: {
|
||||
let sorted = result.rows.sort(
|
||||
(a, b) => (b.doc?.created_at || 0) - (a.doc?.created_at || 0)
|
||||
)
|
||||
sorted
|
||||
.slice(1)
|
||||
.filter(row => row.doc)
|
||||
.forEach(row => db.remove(row.doc))
|
||||
return sorted[0].doc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var streams = {}
|
||||
|
||||
self.onmessage = async function (ev) {
|
||||
let {name, args, id, stream, cancel} = JSON.parse(ev.data)
|
||||
|
||||
if (stream) {
|
||||
let changes = methods[name](...args, data => {
|
||||
self.postMessage(
|
||||
JSON.stringify({
|
||||
id,
|
||||
data,
|
||||
stream: true
|
||||
})
|
||||
)
|
||||
})
|
||||
streams[id] = changes
|
||||
} else if (cancel) {
|
||||
streams[id].cancel()
|
||||
delete streams[id]
|
||||
} else {
|
||||
var reply = {id}
|
||||
try {
|
||||
let data = await methods[name](...args)
|
||||
reply.success = true
|
||||
reply.data = data
|
||||
} catch (err) {
|
||||
reply.success = false
|
||||
reply.error = err.message
|
||||
}
|
||||
|
||||
self.postMessage(JSON.stringify(reply))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user