mirror of https://codeberg.org/pzp/pzp-sync.git
realtime sync
This commit is contained in:
parent
e9971410eb
commit
54aa67a08f
|
@ -340,7 +340,7 @@ class Algorithm {
|
||||||
* @param {Range} myWantRange
|
* @param {Range} myWantRange
|
||||||
* @returns {Array<Msg>}
|
* @returns {Array<Msg>}
|
||||||
*/
|
*/
|
||||||
#filterReceivedMsgs(rootID, msgs, myWantRange) {
|
filterReceivedMsgs(rootID, msgs, myWantRange) {
|
||||||
const [minWant, maxWant] = myWantRange
|
const [minWant, maxWant] = myWantRange
|
||||||
|
|
||||||
const validNewMsgs = msgs
|
const validNewMsgs = msgs
|
||||||
|
@ -351,9 +351,9 @@ class Algorithm {
|
||||||
return false // the rootMsg is the only acceptable depth-zero msg
|
return false // the rootMsg is the only acceptable depth-zero msg
|
||||||
}
|
}
|
||||||
if (!msg.data) {
|
if (!msg.data) {
|
||||||
return depth <= maxWant
|
return true
|
||||||
} else {
|
} else {
|
||||||
return minWant <= depth && depth <= maxWant
|
return minWant <= depth
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
|
@ -369,6 +369,13 @@ class Algorithm {
|
||||||
return validNewMsgs
|
return validNewMsgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<Msg>} msgs
|
||||||
|
*/
|
||||||
|
getMsgIDs(msgs) {
|
||||||
|
return msgs.map((msg) => MsgV4.getMsgID(msg))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes the new msgs and adds them to the database. Also performs pruning as
|
* Takes the new msgs and adds them to the database. Also performs pruning as
|
||||||
* post-processing.
|
* post-processing.
|
||||||
|
@ -376,15 +383,12 @@ class Algorithm {
|
||||||
* @param {string} rootID
|
* @param {string} rootID
|
||||||
* @param {Array<Msg>} newMsgs
|
* @param {Array<Msg>} newMsgs
|
||||||
* @param {Goal} goal
|
* @param {Goal} goal
|
||||||
* @param {Range} myWantRange
|
|
||||||
*/
|
*/
|
||||||
async commit(rootID, newMsgs, goal, myWantRange) {
|
async commit(rootID, newMsgs, goal) {
|
||||||
const validNewMsgs = this.#filterReceivedMsgs(rootID, newMsgs, myWantRange)
|
|
||||||
|
|
||||||
// TODO: Simulate adding this whole tangle, and check if it's valid
|
// TODO: Simulate adding this whole tangle, and check if it's valid
|
||||||
|
|
||||||
// Add new messages
|
// Add new messages
|
||||||
for (const msg of validNewMsgs) {
|
for (const msg of newMsgs) {
|
||||||
try {
|
try {
|
||||||
if (msg.metadata.account === 'self') {
|
if (msg.metadata.account === 'self') {
|
||||||
await p(this.#peer.db.add)(msg, null /* infer tangleID */)
|
await p(this.#peer.db.add)(msg, null /* infer tangleID */)
|
||||||
|
|
|
@ -51,7 +51,8 @@ function initSync(peer, config) {
|
||||||
function createStream(remoteId, iamClient) {
|
function createStream(remoteId, iamClient) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
debug('Opening a stream with remote %s %s', iamClient ? 'server' : 'client', remoteId)
|
debug('Opening a stream with remote %s %s', iamClient ? 'server' : 'client', remoteId)
|
||||||
const stream = new SyncStream(peer.shse.pubkey, debug, peer.goals, algo)
|
const { shse, db, goals } = peer
|
||||||
|
const stream = new SyncStream(shse.pubkey, debug, db, goals, algo)
|
||||||
streams.push(stream)
|
streams.push(stream)
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
|
|
115
lib/stream.js
115
lib/stream.js
|
@ -4,6 +4,8 @@ const { isEmptyRange } = require('./range')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {ReturnType<import('ppppp-goals').init>} PPPPPGoals
|
* @typedef {ReturnType<import('ppppp-goals').init>} PPPPPGoals
|
||||||
|
* @typedef {ReturnType<import('ppppp-db').init>} PPPPPDB
|
||||||
|
* @typedef {import('ppppp-db').RecPresent} Rec
|
||||||
* @typedef {import('ppppp-db/msg-v4').Msg} Msg
|
* @typedef {import('ppppp-db/msg-v4').Msg} Msg
|
||||||
* @typedef {import('./range').Range} Range
|
* @typedef {import('./range').Range} Range
|
||||||
* @typedef {import('./algorithm')} Algorithm
|
* @typedef {import('./algorithm')} Algorithm
|
||||||
|
@ -28,6 +30,8 @@ class SyncStream extends Pipeable {
|
||||||
#debug
|
#debug
|
||||||
/** @type {Set<string>} Set of tangleId */
|
/** @type {Set<string>} Set of tangleId */
|
||||||
#requested
|
#requested
|
||||||
|
/** @type {PPPPPDB} */
|
||||||
|
#db
|
||||||
/** @type {PPPPPGoals} */
|
/** @type {PPPPPGoals} */
|
||||||
#goals
|
#goals
|
||||||
/**
|
/**
|
||||||
|
@ -60,15 +64,17 @@ class SyncStream extends Pipeable {
|
||||||
* @type {Map<string, Set<string>>}
|
* @type {Map<string, Set<string>>}
|
||||||
*/
|
*/
|
||||||
#sendableMsgs
|
#sendableMsgs
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
#realtimeSyncing
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param {string} localId
|
* @param {string} localId
|
||||||
* @param {CallableFunction} debug
|
* @param {CallableFunction} debug
|
||||||
|
* @param {PPPPPDB} db
|
||||||
* @param {PPPPPGoals} goals
|
* @param {PPPPPGoals} goals
|
||||||
* @param {Algorithm} algo
|
* @param {Algorithm} algo
|
||||||
*/
|
*/
|
||||||
constructor(localId, debug, goals, algo) {
|
constructor(localId, debug, db, goals, algo) {
|
||||||
super()
|
super()
|
||||||
this.paused = false // TODO: should we start as paused=true?
|
this.paused = false // TODO: should we start as paused=true?
|
||||||
this.ended = false
|
this.ended = false
|
||||||
|
@ -76,9 +82,11 @@ class SyncStream extends Pipeable {
|
||||||
this.source = this.sink = null
|
this.source = this.sink = null
|
||||||
this.#myId = localId.slice(0, 6)
|
this.#myId = localId.slice(0, 6)
|
||||||
this.#debug = debug
|
this.#debug = debug
|
||||||
|
this.#db = db
|
||||||
this.#goals = goals
|
this.#goals = goals
|
||||||
this.#algo = algo
|
this.#algo = algo
|
||||||
this.#requested = new Set()
|
this.#requested = new Set()
|
||||||
|
this.#realtimeSyncing = new Set()
|
||||||
this.#localHave = new Map()
|
this.#localHave = new Map()
|
||||||
this.#localWant = new Map()
|
this.#localWant = new Map()
|
||||||
this.#remoteHave = new Map()
|
this.#remoteHave = new Map()
|
||||||
|
@ -99,6 +107,23 @@ class SyncStream extends Pipeable {
|
||||||
this.resume()
|
this.resume()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Setup real-time syncing
|
||||||
|
this.#db.onRecordAdded((/** @type {Rec} */ { id: msgID, msg }) => {
|
||||||
|
const tangleIDs = [msgID].concat(Object.keys(msg.metadata.tangles))
|
||||||
|
for (const id of tangleIDs) {
|
||||||
|
if (this.#realtimeSyncing.has(id)) {
|
||||||
|
if (this.#receivableMsgs.has(msgID)) continue
|
||||||
|
if (this.#receivableMsgs.get(id)?.has(msgID)) continue
|
||||||
|
if (this.#sendableMsgs.has(msgID)) continue
|
||||||
|
if (this.#sendableMsgs.get(id)?.has(msgID)) continue
|
||||||
|
this.sink.write({ id, phase: 9, payload: [msg] })
|
||||||
|
// prettier-ignore
|
||||||
|
this.#debug('%s Stream OUTr: sent msg %s in %s', this.#myId, msgID, id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#canSend() {
|
#canSend() {
|
||||||
|
@ -135,9 +160,9 @@ class SyncStream extends Pipeable {
|
||||||
#sendLocalHave(id) {
|
#sendLocalHave(id) {
|
||||||
const localHaveRange = this.#algo.haveRange(id)
|
const localHaveRange = this.#algo.haveRange(id)
|
||||||
this.#localHave.set(id, localHaveRange)
|
this.#localHave.set(id, localHaveRange)
|
||||||
// prettier-ignore
|
|
||||||
this.#debug('%s Stream OUT1: send local have-range %o for %s', this.#myId, localHaveRange, id)
|
|
||||||
this.sink.write({ id, phase: 1, payload: localHaveRange })
|
this.sink.write({ id, phase: 1, payload: localHaveRange })
|
||||||
|
// prettier-ignore
|
||||||
|
this.#debug('%s Stream OUT1: sent local have-range %o for %s', this.#myId, localHaveRange, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -153,9 +178,9 @@ class SyncStream extends Pipeable {
|
||||||
const wantRange = this.#algo.wantRange(haveRange, remoteHaveRange, goal)
|
const wantRange = this.#algo.wantRange(haveRange, remoteHaveRange, goal)
|
||||||
this.#localHave.set(id, haveRange)
|
this.#localHave.set(id, haveRange)
|
||||||
this.#localWant.set(id, wantRange)
|
this.#localWant.set(id, wantRange)
|
||||||
// prettier-ignore
|
|
||||||
this.#debug('%s Stream OUT2: send local have-range %o and want-range %o for %s', this.#myId, haveRange, wantRange, id)
|
|
||||||
this.sink.write({ id, phase: 2, payload: { haveRange, wantRange } })
|
this.sink.write({ id, phase: 2, payload: { haveRange, wantRange } })
|
||||||
|
// prettier-ignore
|
||||||
|
this.#debug('%s Stream OUT2: sent local have-range %o and want-range %o for %s', this.#myId, haveRange, wantRange, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,7 +205,7 @@ class SyncStream extends Pipeable {
|
||||||
payload: { bloom: localBloom0, wantRange: localWant },
|
payload: { bloom: localBloom0, wantRange: localWant },
|
||||||
})
|
})
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
this.#debug('%s Stream OUT3: send local want-range %o and bloom round 0 for %s', this.#myId, localWant, id)
|
this.#debug('%s Stream OUT3: sent local want-range %o and bloom round 0 for %s', this.#myId, localWant, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,7 +233,7 @@ class SyncStream extends Pipeable {
|
||||||
payload: { bloom: localBloom, msgIDs: msgIDsForThem },
|
payload: { bloom: localBloom, msgIDs: msgIDsForThem },
|
||||||
})
|
})
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
this.#debug('%s Stream OUT4: send bloom round 0 plus msgIDs in %s: %o', this.#myId, id, msgIDsForThem)
|
this.#debug('%s Stream OUT4: sent bloom round 0 plus msgIDs in %s: %o', this.#myId, id, msgIDsForThem)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -241,7 +266,7 @@ class SyncStream extends Pipeable {
|
||||||
payload: { bloom: localBloom, msgIDs: msgIDsForThem },
|
payload: { bloom: localBloom, msgIDs: msgIDsForThem },
|
||||||
})
|
})
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
this.#debug('%s Stream OUT%s: send bloom round %s plus msgIDs in %s: %o', this.#myId, phase, round, id, msgIDsForThem)
|
this.#debug('%s Stream OUT%s: sent bloom round %s plus msgIDs in %s: %o', this.#myId, phase, round, id, msgIDsForThem)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -274,7 +299,7 @@ class SyncStream extends Pipeable {
|
||||||
payload: { bloom: localBloom, msgIDs: msgIDsForThem },
|
payload: { bloom: localBloom, msgIDs: msgIDsForThem },
|
||||||
})
|
})
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
this.#debug('%s Stream OUT%s: send bloom round %s plus msgIDs in %s: %o', this.#myId, phase, round, id, msgIDsForThem)
|
this.#debug('%s Stream OUT%s: sent bloom round %s plus msgIDs in %s: %o', this.#myId, phase, round, id, msgIDsForThem)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -310,7 +335,10 @@ class SyncStream extends Pipeable {
|
||||||
payload: { msgs, bloom: localBloom },
|
payload: { msgs, bloom: localBloom },
|
||||||
})
|
})
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
this.#debug('%s Stream OUT8: send bloom round %s plus %s msgs in %s', this.#myId, round, msgs.length, id)
|
this.#debug('%s Stream OUT8: sent bloom round %s plus %s msgs in %s', this.#myId, round, msgs.length, id)
|
||||||
|
if (!this.#realtimeSyncing.has(id) && !isEmptyRange(remoteWantRange)) {
|
||||||
|
this.#realtimeSyncing.add(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -335,28 +363,13 @@ class SyncStream extends Pipeable {
|
||||||
const tangleMsgs = this.#algo.getTangleMsgs(id, msgIDs)
|
const tangleMsgs = this.#algo.getTangleMsgs(id, msgIDs)
|
||||||
const accountMsgs = this.#algo.filterAndFetchAccountMsgs(msgIDs)
|
const accountMsgs = this.#algo.filterAndFetchAccountMsgs(msgIDs)
|
||||||
const msgs = accountMsgs.concat(tangleMsgs)
|
const msgs = accountMsgs.concat(tangleMsgs)
|
||||||
// prettier-ignore
|
|
||||||
this.#debug('%s Stream OUT9: send %s msgs in %s', this.#myId, msgs.length, id)
|
|
||||||
this.sink.write({ id, phase: 9, payload: msgs })
|
this.sink.write({ id, phase: 9, payload: msgs })
|
||||||
|
|
||||||
const goal = this.#goals.get(id)
|
|
||||||
if (!goal) throw new Error(`No goal found for "${id}"`)
|
|
||||||
const localWantRange = this.#localWant.get(id)
|
|
||||||
if (!localWantRange) throw new Error(`Local want-range not set for ${id}`)
|
|
||||||
this.#requested.delete(id)
|
|
||||||
this.#localHave.delete(id)
|
|
||||||
this.#localWant.delete(id)
|
|
||||||
this.#remoteHave.delete(id)
|
|
||||||
this.#remoteWant.delete(id)
|
|
||||||
this.#receivableMsgs.delete(id)
|
|
||||||
this.#sendableMsgs.delete(id)
|
|
||||||
if (msgsForMe.length === 0) return
|
|
||||||
try {
|
|
||||||
this.#algo.commit(id, msgsForMe, goal, localWantRange)
|
|
||||||
} catch (err) {
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
this.#debug('%s Stream could not commit received messages, because: %s', this.#myId, err)
|
this.#debug('%s Stream OUT9: sent %s msgs in %s', this.#myId, msgs.length, id)
|
||||||
|
if (!this.#realtimeSyncing.has(id) && !isEmptyRange(remoteWantRange)) {
|
||||||
|
this.#realtimeSyncing.add(id)
|
||||||
}
|
}
|
||||||
|
this.#consumeMissingMsgs(id, msgsForMe)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -365,24 +378,34 @@ class SyncStream extends Pipeable {
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
#consumeMissingMsgs(id, msgsForMe) {
|
#consumeMissingMsgs(id, msgsForMe) {
|
||||||
// prettier-ignore
|
|
||||||
this.#debug('%s Stream IN9: got %s msgs in %s', this.#myId, msgsForMe.length, id)
|
|
||||||
const localWantRange = this.#localWant.get(id)
|
|
||||||
|
|
||||||
this.#requested.delete(id)
|
this.#requested.delete(id)
|
||||||
this.#localHave.delete(id)
|
this.#localHave.delete(id)
|
||||||
this.#localWant.delete(id)
|
|
||||||
this.#remoteHave.delete(id)
|
this.#remoteHave.delete(id)
|
||||||
this.#remoteWant.delete(id)
|
this.#remoteWant.delete(id)
|
||||||
this.#receivableMsgs.delete(id)
|
|
||||||
this.#sendableMsgs.delete(id)
|
|
||||||
|
|
||||||
if (msgsForMe.length === 0) return
|
if (msgsForMe.length === 0) return
|
||||||
const goal = this.#goals.get(id)
|
const goal = this.#goals.get(id)
|
||||||
if (!goal) throw new Error(`No goal found for "${id}"`)
|
if (!goal) {
|
||||||
if (!localWantRange) throw new Error(`Local want-range not set for "${id}"`)
|
this.#debug('%s Stream exception: no goal found for %s', this.#myId, id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const localWantRange = this.#localWant.get(id)
|
||||||
|
if (!localWantRange) {
|
||||||
|
// prettier-ignore
|
||||||
|
this.#debug('%s Stream exception: local want-range not set for %s', this.#myId, id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const validMsgs = this.#algo.filterReceivedMsgs(
|
||||||
|
id,
|
||||||
|
msgsForMe,
|
||||||
|
localWantRange
|
||||||
|
)
|
||||||
|
const validMsgIDs = this.#algo.getMsgIDs(validMsgs)
|
||||||
|
this.#updateReceivableMsgs(id, validMsgIDs)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.#algo.commit(id, msgsForMe, goal, localWantRange)
|
this.#algo.commit(id, validMsgs, goal)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
this.#debug('%s Stream could not commit received messages, because: %s', this.#myId, err)
|
this.#debug('%s Stream could not commit received messages, because: %s', this.#myId, err)
|
||||||
|
@ -400,9 +423,14 @@ class SyncStream extends Pipeable {
|
||||||
const accountMsgs = this.#algo.getAccountMsgsFor(tangleMsgs)
|
const accountMsgs = this.#algo.getAccountMsgsFor(tangleMsgs)
|
||||||
for (const msg of accountMsgs) msgs.push(msg)
|
for (const msg of accountMsgs) msgs.push(msg)
|
||||||
for (const msg of tangleMsgs) msgs.push(msg)
|
for (const msg of tangleMsgs) msgs.push(msg)
|
||||||
// prettier-ignore
|
const msgIDs = this.#algo.getMsgIDs(msgs)
|
||||||
this.#debug('%s Stream OUT9: send %s msgs in %s', this.#myId, msgs.length, id)
|
this.#updateSendableMsgs(id, msgIDs)
|
||||||
this.sink.write({ id, phase: 9, payload: msgs })
|
this.sink.write({ id, phase: 9, payload: msgs })
|
||||||
|
// prettier-ignore
|
||||||
|
this.#debug('%s Stream OUT9: sent %s msgs in %s', this.#myId, msgs.length, id)
|
||||||
|
if (!this.#realtimeSyncing.has(id) && !isEmptyRange(remoteWantRange)) {
|
||||||
|
this.#realtimeSyncing.add(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// source method
|
// source method
|
||||||
|
@ -422,6 +450,7 @@ class SyncStream extends Pipeable {
|
||||||
write(data) {
|
write(data) {
|
||||||
const { id, phase, payload } = data
|
const { id, phase, payload } = data
|
||||||
|
|
||||||
|
// TODO: validate that each data objects has the exact correct shape
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case 0: {
|
case 0: {
|
||||||
return this.#sendLocalHave(id)
|
return this.#sendLocalHave(id)
|
||||||
|
@ -471,6 +500,8 @@ class SyncStream extends Pipeable {
|
||||||
return this.#sendMissingMsgsRes(id, 2, bloom, msgs)
|
return this.#sendMissingMsgsRes(id, 2, bloom, msgs)
|
||||||
}
|
}
|
||||||
case 9: {
|
case 9: {
|
||||||
|
// prettier-ignore
|
||||||
|
this.#debug('%s Stream IN9: got %s msgs in %s', this.#myId, payload.length, id)
|
||||||
return this.#consumeMissingMsgs(id, payload)
|
return this.#consumeMissingMsgs(id, payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
const test = require('node:test')
|
||||||
|
const assert = require('node:assert')
|
||||||
|
const p = require('node:util').promisify
|
||||||
|
const { createPeer } = require('./util')
|
||||||
|
|
||||||
|
test('sync feed msgs in realtime after the 9 rounds', async (t) => {
|
||||||
|
const alice = createPeer({ name: 'alice' })
|
||||||
|
const bob = createPeer({ name: 'bob' })
|
||||||
|
|
||||||
|
await alice.db.loaded()
|
||||||
|
await bob.db.loaded()
|
||||||
|
|
||||||
|
const bobID = await p(bob.db.account.create)({
|
||||||
|
subdomain: 'account',
|
||||||
|
_nonce: 'bob',
|
||||||
|
})
|
||||||
|
|
||||||
|
await p(bob.db.feed.publish)({
|
||||||
|
account: bobID,
|
||||||
|
domain: 'post',
|
||||||
|
data: { text: 'm0' },
|
||||||
|
})
|
||||||
|
assert('bob published post 0')
|
||||||
|
|
||||||
|
const bobPostsID = bob.db.feed.getID(bobID, 'post')
|
||||||
|
|
||||||
|
{
|
||||||
|
const arr = [...alice.db.msgs()]
|
||||||
|
.filter((msg) => msg.metadata.account === bobID && msg.data)
|
||||||
|
.map((msg) => msg.data.text)
|
||||||
|
assert.deepEqual(arr, [], 'alice has no posts from bob')
|
||||||
|
}
|
||||||
|
|
||||||
|
bob.goals.set(bobPostsID, 'all')
|
||||||
|
alice.goals.set(bobPostsID, 'all')
|
||||||
|
|
||||||
|
const remoteAlice = await p(bob.connect)(alice.getAddress())
|
||||||
|
assert('bob connected to alice')
|
||||||
|
|
||||||
|
bob.sync.start()
|
||||||
|
await p(setTimeout)(1000)
|
||||||
|
assert('sync!')
|
||||||
|
|
||||||
|
{
|
||||||
|
const arr = [...alice.db.msgs()]
|
||||||
|
.filter((msg) => msg.metadata.account === bobID && msg.data)
|
||||||
|
.map((msg) => msg.data.text)
|
||||||
|
assert.deepEqual(arr, ['m0'], 'alice has post 0 from bob')
|
||||||
|
}
|
||||||
|
|
||||||
|
await p(bob.db.feed.publish)({
|
||||||
|
account: bobID,
|
||||||
|
domain: 'post',
|
||||||
|
data: { text: 'm1' },
|
||||||
|
})
|
||||||
|
assert('bob published post 1')
|
||||||
|
|
||||||
|
await p(bob.db.feed.publish)({
|
||||||
|
account: bobID,
|
||||||
|
domain: 'post',
|
||||||
|
data: { text: 'm2' },
|
||||||
|
})
|
||||||
|
assert('bob published post 2')
|
||||||
|
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const arr = [...alice.db.msgs()]
|
||||||
|
.filter((msg) => msg.metadata.account === bobID && msg.data)
|
||||||
|
.map((msg) => msg.data.text)
|
||||||
|
if (arr.length < 3) {
|
||||||
|
await p(setTimeout)(200)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
assert.deepEqual(arr, ['m0', 'm1', 'm2'], 'alice has posts 0..2 from bob')
|
||||||
|
}
|
||||||
|
|
||||||
|
await p(remoteAlice.close)(true)
|
||||||
|
await p(alice.close)(true)
|
||||||
|
await p(bob.close)(true)
|
||||||
|
})
|
Loading…
Reference in New Issue