Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test adding someone back into the group #111

Merged
merged 18 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)),
Comment on lines 448 to +452
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but you could do it will pull-streams tidily too

pull.flatten()
pull.map(readKey => readKey.gey.toString('base64')),
pull.drain(
  myReadKeys.add,
  err => ...
)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just seems slightly more complex?

(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 .",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, I've been missing this - I caught an "unused var" the other day, or maybe undefined var.
Anyway, I think this should be part of npm test script

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my editor's been running it automatically on save but realized we didn't have an official way of doing it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added to npm test

"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)
Powersource marked this conversation as resolved.
Show resolved Hide resolved

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)
Powersource marked this conversation as resolved.
Show resolved Hide resolved

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