Skip to content

Commit

Permalink
Merge pull request #111 from ssbc/add-back
Browse files Browse the repository at this point in the history
Test adding someone back into the group
  • Loading branch information
Powersource authored May 18, 2023
2 parents ae0d64a + 07d785a commit be604f9
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ module.exports = {
sourceType: 'module',
},
rules: {
'no-console': ['error', { allow: ['error'] }],
'no-console': ['error', { allow: ['error', 'time', 'timeEnd'] }],
},
}
30 changes: 18 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,23 +404,25 @@ module.exports = {
function listInvites() {
const deferredSource = pullDefer.source()

getMyGroups((err, myGroups) => {
getMyReadKeys((err, myReadKeys) => {
// prettier-ignore
if (err) return deferredSource.abort(clarify(err, 'Failed to list group IDs when listing invites'))
if (err) return deferredSource.abort(clarify(err, 'Failed to list group readKeys when listing invites'))

const source = pull(
// get all the groupIds we've heard of from invites
// get all the additions we've heard of
ssb.db.query(
where(and(isDecrypted('box2'), type('group/add-member'))),
toPullStream()
),
pull.filter((msg) => isAddMember(msg)),
pull.map((msg) => msg.value.content.recps[0]),
pull.unique(),
pull.map((msg) => msg.value.content),

// drop those we're a part of already
pull.filter((groupId) => !myGroups.has(groupId)),
// drop those we're grabbed secrets from already (in case we've been in the group before)
pull.filter((content) => !myReadKeys.has(content.secret)),

// groupId
pull.map((content) => content.recps[0]),
pull.unique(),
// gather all the data required for each group-invite
pull.asyncMap(getGroupInviteData)
)
Expand All @@ -432,8 +434,8 @@ module.exports = {

// listInvites helpers

function getMyGroups(cb) {
const myGroups = new Set()
function getMyReadKeys(cb) {
const myReadKeys = new Set()

pull(
// TODO replace with pull.values (unless want "round-robbin" sampling)
Expand All @@ -442,11 +444,15 @@ module.exports = {
ssb.box2.listGroupIds({ excluded: true }),
]),
pull.flatten(),
pull.asyncMap((groupId, cb) => ssb.box2.getGroupInfo(groupId, cb)),
pull.drain(
(groupId) => myGroups.add(groupId),
(groupInfo) =>
groupInfo.readKeys
.map((readKey) => readKey.key.toString('base64'))
.forEach((readKeyString) => myReadKeys.add(readKeyString)),
(err) => {
if (err) return cb(err)
return cb(null, myGroups)
return cb(null, myReadKeys)
}
)
)
Expand Down Expand Up @@ -550,7 +556,7 @@ module.exports = {
// prettier-ignore
if (err) return cb(clarify(err, 'Error finding or creating additions feed when starting ssb-tribes2'))
cb(null)
startListeners(ssb, getPreferredEpoch, console.error)
startListeners(ssb, console.error)
})
}

Expand Down
1 change: 0 additions & 1 deletion lib/epochs.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,6 @@ function Epochs(ssb) {
const epochId = msg.value.content.tangles.members.root
// check if this epoch is in the reduce tangle
const epochNode = reduce.graph.getNode(epochId)
if (!epochNode) console.log('unknown epoch', epochId)
if (!epochNode) return cb(null, false)

getMembers(epochId, (err, res) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/operators.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function seekFirstRecp(buffer, start, p) {
if (p < 0) return -1

let pValueFirstRecp
const error = iterate(buffer, p, (_, pointer, key) => {
const error = iterate(buffer, p, (_, pointer) => {
pValueFirstRecp = pointer
return true
})
Expand Down
23 changes: 20 additions & 3 deletions listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ const {
const pull = require('pull-stream')
const paraMap = require('pull-paramap')
const clarify = require('clarify-error')
const Epochs = require('./lib/epochs')

module.exports = function startListeners(ssb, onError) {
const { getTipEpochs, getPreferredEpoch } = Epochs(ssb)

module.exports = function startListeners(ssb, getPreferredEpoch, onError) {
let isClosed = false
ssb.close.hook((close, args) => {
isClosed = true
Expand All @@ -52,9 +55,23 @@ module.exports = function startListeners(ssb, getPreferredEpoch, onError) {
pull.drain(
(msg) => {
const groupId = msg.value.content.recps[0]
ssb.box2.excludeGroupInfo(groupId, (err) => {
getTipEpochs(groupId, (err, tipEpochs) => {
// prettier-ignore
if (err && !isClosed) return onError(clarify(err, 'Error on excluding group info after finding exclusion of ourselves'))
if (err) return onError(clarify(err, 'Error on getting tip epochs after finding exclusion of ourselves'))

const excludeEpochRootId = msg.value.content.tangles.members.root

const excludeIsInTipEpoch = tipEpochs
.map((tip) => tip.id)
.includes(excludeEpochRootId)

// ignore the exclude if it's an old one (we were added back to the group later)
if (!excludeIsInTipEpoch) return

ssb.box2.excludeGroupInfo(groupId, (err) => {
// prettier-ignore
if (err) return onError(clarify(err, 'Error on excluding group info after finding exclusion of ourselves'))
})
})
},
(err) => {
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"ssb"
],
"engines": {
"node": ">=12"
"node": ">=16"
},
"main": "index.js",
"files": [
Expand Down Expand Up @@ -78,7 +78,8 @@
"test:raw": "tape 'test/**/*.test.js'",
"test:only": "if grep -r --exclude-dir=node_modules --exclude-dir=.git --color 'test\\.only' ; then exit 1; fi",
"test:bail": "npm run test:raw | tap-arc --bail",
"test": "npm run test:raw | tap-arc && npm run test:only",
"test": "npm run test:raw | tap-arc && npm run test:only && npm run lint",
"lint": "eslint .",
"format-code": "prettier --write \"**/*.js\"",
"format-code-staged": "pretty-quick --staged --pattern \"**/*.js\"",
"coverage": "c8 --reporter=lcov npm run test"
Expand Down
95 changes: 95 additions & 0 deletions test/add-member.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,98 @@ test('addMembers adds to all the tip epochs and gives keys to all the old epochs
.then(() => t.pass('clients close'))
.catch((err) => t.error(err))
})

test('can add someone back into a group', async (t) => {
const alice = Testbot({
keys: ssbKeys.generate(null, 'alice'),
mfSeed: Buffer.from(
'000000000000000000000000000000000000000000000000000000000000a1ce',
'hex'
),
})
const bobConfig = {
name: 'bobrestart',
keys: ssbKeys.generate(null, 'bob'),
mfSeed: Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000b0b',
'hex'
),
}
let bob = Testbot(bobConfig)

await Promise.all([alice.tribes2.start(), bob.tribes2.start()])

const bobRoot = await p(bob.metafeeds.findOrCreate)()

await replicate(alice, bob).catch(t.error)

const { id: groupId } = await alice.tribes2
.create()
.catch((err) => t.error(err, 'alice failed to create group'))

await alice.tribes2
.addMembers(groupId, [bobRoot.id])
.then(() => t.pass('added bob'))
.catch((err) => t.error(err, 'add bob fail'))

await replicate(alice, bob).catch(t.error)

await bob.tribes2.acceptInvite(groupId)

await replicate(alice, bob).catch(t.error)

await alice.tribes2
.excludeMembers(groupId, [bobRoot.id])
.then(() => t.pass('alice excluded bob'))
.catch((err) => t.error(err, 'remove member fail'))

await replicate(alice, bob).catch(t.error)

await alice.tribes2
.addMembers(groupId, [bobRoot.id])
.then(() => t.pass('added bob back in again'))
.catch((err) => t.error(err, 'add bob back fail'))

await replicate(alice, bob).catch(t.error)

const invites = await pull(bob.tribes2.listInvites(), pull.collectAsPromise())
t.equal(invites.length, 1, 'got a reinvite')
t.equal(invites[0].id, groupId, 'got invite to correct group')
await bob.tribes2.acceptInvite(groupId).catch(t.error)

async function verifyInGroup(peer) {
const noInvites = await pull(
bob.tribes2.listInvites(),
pull.collectAsPromise()
)
t.deepEqual(noInvites, [], "we used the invite so there aren't any left")

await peer.tribes2
.acceptInvite(groupId)
.then(() => t.fail('consumed invite twice'))
.catch(() => t.pass("can't consume invite twice"))

const list = await pull(peer.tribes2.list(), pull.collectAsPromise())
t.equal(list.length, 1, 'one group')
t.equal(list[0].id, groupId, 'id in list is correct')

const group = await bob.tribes2.get(groupId)
t.notEqual(group.writeKey, undefined, 'bob got writeKey back')
t.equal(group.excluded, undefined, 'bob is not excluded')
}

await verifyInGroup(bob)

await p(setTimeout)(500)
await p(bob.close)(true).then(() => t.pass("bob's client was closed"))
bob = Testbot({
...bobConfig,
rimraf: false,
})
t.pass('bob got a new client')
await bob.tribes2.start().then(() => t.pass('bob restarted'))

await verifyInGroup(bob)

await Promise.all([p(alice.close)(true), p(bob.close)(true)])
})
2 changes: 1 addition & 1 deletion test/lib/epochs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ test('lib/epochs (getMissingMembers)', async (t) => {
const rootFeeds = await Promise.all(
peers.map((peer) => p(peer.metafeeds.findOrCreate)())
)
const [aliceId, bobId, oscarId] = rootFeeds.map((feed) => feed.id)
const [, bobId, oscarId] = rootFeeds.map((feed) => feed.id)

const group = await run('alice creates a group', alice.tribes2.create({}))
await sync('to get Additions feeds')
Expand Down
5 changes: 1 addition & 4 deletions test/list-members.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,7 @@ test('listMembers works with exclusion', async (t) => {
const msg =
"Bob gets an error when trying to list members of the group he's excluded from"
await pull(bob.tribes2.listMembers(groupId), pull.collectAsPromise())
.then((res) => {
console.log(res)
t.fail(msg)
})
.then(() => t.fail(msg))
.catch(() => t.pass(msg))

await p(setTimeout)(500)
Expand Down

0 comments on commit be604f9

Please sign in to comment.