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

People not removed in an exclude should be able to post on the new epoch #78

Merged
merged 20 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
64 changes: 64 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,70 @@ module.exports = {
if (err) return cb(clarify(err, 'Error finding or creating additions feed when starting ssb-tribes2'))
return cb()
})

// look for new epochs that we're added to
ssb.metafeeds.findOrCreate((err, myRoot) => {
// prettier-ignore
if (err) return cb(clarify(err, 'todo'))
Powersource marked this conversation as resolved.
Show resolved Hide resolved

pull(
ssb.db.query(
// TODO: does this output new stuff if we accept an invite to an old epoch and then find additions to newer epochs?
where(and(isDecrypted('box2'), type('group/add-member'))),
live({ old: true }),
toPullStream()
Powersource marked this conversation as resolved.
Show resolved Hide resolved
),
// groups/epochs we're added to
pull.filter((msg) => msg.value?.content?.recps?.includes(myRoot.id)),
// to find new epochs we only check groups we've accepted the invite to
paraMap((msg, cb) => {
pull(
ssb.box2.listGroupIds(),
pull.collect((err, groupIds) => {
// prettier-ignore
if (err) return cb(clarify(err, 'todo'))

if (groupIds.includes(msg.value?.content?.recps?.[0])) {
return cb(null, msg)
} else {
return cb()
}
})
)
}),
Copy link
Member

Choose a reason for hiding this comment

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

paraMap(fn) === pull.asyncMap(fn)
You need to add a "width" for it to be running in parallel e.g. run at most 4 at once:

Suggested change
}),
}, 4),

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

pull.filter(Boolean),
pull.drain(
(msg) => {
const groupId = msg.value?.content?.recps?.[0]
Copy link
Member

Choose a reason for hiding this comment

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

🔥 we should get rid of this with isGroupAdd waaay further up in the stream.
Then we never have to do this oh... do we have this thing or not? That's the whole reason I got into JSON-Schema.
Like in the line below

const newKey = Buffer.from(msg.value?.content?.groupKey, 'base64')

if groupKey is not there this code is gonna blow up in your face

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done


const newKey = Buffer.from(msg.value?.content?.groupKey, 'base64')
ssb.box2.addGroupInfo(groupId, { key: newKey }, (err) => {
// prettier-ignore
if (err) return cb(clarify(err, 'todo'))

const newKeyPick = {
mixmix marked this conversation as resolved.
Show resolved Hide resolved
key: newKey,
scheme: keySchemes.private_group,
}
// TODO: naively guessing that this is the latest key for now
ssb.box2.pickGroupWriteKey(groupId, newKeyPick, (err) => {
Powersource marked this conversation as resolved.
Show resolved Hide resolved
// prettier-ignore
if (err) return cb(clarify(err, "todo"))

ssb.db.reindexEncrypted((err) => {
// prettier-ignore
if (err) cb(clarify(err, 'todo'))
})
})
})
},
(err) => {
// prettier-ignore
if (err) return cb(clarify(err, 'todo'))
}
)
)
})
}

return {
Expand Down
120 changes: 120 additions & 0 deletions test/exclude-members.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { fromMessageSigil } = require('ssb-uri2')
const Testbot = require('./helpers/testbot')
const replicate = require('./helpers/replicate')
const countGroupFeeds = require('./helpers/count-group-feeds')
const pull = require('pull-stream')

test('add and remove a person, post on the new feed', async (t) => {
// Alice's feeds should look like
Expand Down Expand Up @@ -154,3 +155,122 @@ test('add and remove a person, post on the new feed', async (t) => {
await p(alice.close)(true)
await p(bob.close)(true)
})

test("If you're not the excluder nor the excludee then you should still be in the group and have access to the new epoch", async (t) => {
// alice creates the group. adds bob and carol. removes bob.
// bob gets added and is removed
// carol stays in the group
const alice = Testbot({
keys: ssbKeys.generate(null, 'alice'),
mfSeed: Buffer.from(
'000000000000000000000000000000000000000000000000000000000000a1ce',
'hex'
),
})
const bob = Testbot({
keys: ssbKeys.generate(null, 'bob'),
mfSeed: Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000b0b',
'hex'
),
})
const carol = Testbot({
keys: ssbKeys.generate(null, 'carol'),
mfSeed: Buffer.from(
'00000000000000000000000000000000000000000000000000000000000ca201',
'hex'
),
})

await alice.tribes2.start()
await bob.tribes2.start()
await carol.tribes2.start()
t.pass('tribes2 started for everyone')

await p(alice.metafeeds.findOrCreate)()
const bobRoot = await p(bob.metafeeds.findOrCreate)()
const carolRoot = await p(carol.metafeeds.findOrCreate)()

await replicate(alice, bob)
await replicate(alice, carol)
await replicate(bob, carol)
t.pass('everyone replicates their trees')

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

await replicate(alice, carol)

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

await replicate(alice, carol)

await carol.tribes2.acceptInvite(groupId)

await replicate(alice, carol)

const {
value: { author: firstFeedId },
} = await carol.tribes2
.publish({
type: 'test',
text: 'first post',
recps: [groupId],
})
.catch((err) => t.error(err, 'carol failed to publish on first feed'))

await replicate(alice, carol)

await alice.tribes2
.excludeMembers(groupId, [bobRoot.id])
.catch((err) => t.error(err, 'remove member fail'))
Powersource marked this conversation as resolved.
Show resolved Hide resolved

await replicate(alice, carol)

// TODO: maybe remove?
await p(setTimeout)(5000)

const {
value: { author: secondFeedId },
} = await carol.tribes2
.publish({
type: 'test',
text: 'second post',
recps: [groupId],
})
.catch(t.fail)

t.notEquals(
secondFeedId,
firstFeedId,
'feed for second publish is different to first publish'
)
Powersource marked this conversation as resolved.
Show resolved Hide resolved

const { writeKey: writeKey2 } = await carol.tribes2.get(groupId)

const branches = await pull(
carol.metafeeds.branchStream({ root: carolRoot, old: true, live: false }),
pull.collectAsPromise()
)
Powersource marked this conversation as resolved.
Show resolved Hide resolved

const groupFeedPurposes = branches
.filter((branch) => branch.length === 4)
.map((branch) => branch[3])
.filter((feed) => feed.recps && feed.purpose.length === 44)
.map((feed) => feed.purpose)

t.true(
groupFeedPurposes.includes(writeKey1.key.toString('base64')),
'Carol has a feed for the old key'
)
t.true(
groupFeedPurposes.includes(writeKey2.key.toString('base64')),
'Carol has a feed for the new key'
)

await p(alice.close)(true)
await p(bob.close)(true)
})