From eb2f4ebdc1519124c6debdeee22919c88639c440 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Wed, 22 Feb 2023 20:38:16 +0100 Subject: [PATCH 01/37] Add some basic exclusion code --- index.js | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/index.js b/index.js index 4db8dac..db53993 100644 --- a/index.js +++ b/index.js @@ -369,6 +369,52 @@ module.exports = { }) } + function excludeMembers(groupId, feedIds, opts = {}, cb) { + if (cb === undefined) + return promisify(excludeMembers)(groupId, feedIds, opts) + + // TODO: should probably check this against a spec of its own + // TODO: should add members tangle to this. should we add opts to publish()? { spec, tangles, keys} + publish( + { + type: 'group/exclude', + excludes: feedIds, + recps: [groupId], + }, + (err, exclusionMsg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publish exclude msg')) + + pull( + listMembers(groupId), + pull.collect((err, beforeMembers) => { + const remainingMembers = beforeMembers.filter( + (member) => !feedIds.includes(member) + ) + + const newGroupKey = new SecretKey() + const newKeyContent = { + type: 'group/move-epoch', + secret: newGroupKey.toString('base64'), + recps: [groupId, ...remainingMembers], + } + // TODO: loop if many members + publish(newKeyContent, (err) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) + + console.log('added people to new epoch') + + // TODO: create feed for the new epoch + // either createGroupWithoutMembers(myRoot, cb) { but with an arg for the secret + // or findOrCreateGroupFeed(null, function gotGroupFeed(err, groupFeed) { but we need to duplicate more code (maybe premature to worry about tho) + }) + }) + ) + } + ) + } + function publish(content, cb) { if (cb === undefined) return promisify(publish)(content) @@ -522,6 +568,7 @@ module.exports = { get, list, addMembers, + excludeMembers, publish, listMembers, listInvites, From 7ab7f2671cbaee60ef01ad97585279054b0e76fd Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Thu, 23 Feb 2023 11:35:05 +0100 Subject: [PATCH 02/37] Add comment --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index db53993..26b9307 100644 --- a/index.js +++ b/index.js @@ -392,6 +392,7 @@ module.exports = { (member) => !feedIds.includes(member) ) + // TODO: add this key to ourselves. step 1. make sure we ourselves can post to the new feed. step 2 and later: other people can post on the new key const newGroupKey = new SecretKey() const newKeyContent = { type: 'group/move-epoch', From 7ddc5ef4a7642138d32e0a0005361d1cad8f1f93 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Tue, 7 Mar 2023 14:40:39 +0100 Subject: [PATCH 03/37] Try to create new feed --- index.js | 75 +++++++++++++++++++++++++++------------ lib/get-group-tangle.js | 3 +- lib/meta-feed-helpers.js | 4 +-- package.json | 2 +- test/list-and-get.test.js | 16 ++++----- 5 files changed, 65 insertions(+), 35 deletions(-) diff --git a/index.js b/index.js index ee189d7..1e27e6f 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,7 @@ const { }, } = require('private-group-spec') const { fromMessageSigil, isBendyButtV1FeedSSBURI } = require('ssb-uri2') +const { SecretKey } = require('ssb-private-group-keys') const buildGroupId = require('./lib/build-group-id') const AddGroupTangle = require('./lib/add-group-tangle') const publishAndPrune = require('./lib/prune-publish') @@ -42,13 +43,13 @@ module.exports = { secretKeyFromString, findOrCreateAdditionsFeed, findOrCreateGroupFeed, - findOrCreateGroupWithoutMembers, + findOrCreateEpochWithoutMembers, } = MetaFeedHelpers(ssb) function create(opts = {}, cb) { if (cb === undefined) return promisify(create)(opts) - findOrCreateGroupWithoutMembers((err, group) => { + findOrCreateEpochWithoutMembers((err, group) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to create group init message when creating a group')) @@ -61,7 +62,8 @@ module.exports = { groupInitMsg, groupKey: secret.toBuffer(), }), - secret: secret.toBuffer(), + writeKey: secret.toBuffer(), + readKeys: [secret.toBuffer()], root: fromMessageSigil(groupInitMsg.key), subfeed: groupFeed.keys, } @@ -84,14 +86,15 @@ module.exports = { function get(id, cb) { if (cb === undefined) return promisify(get)(id) - ssb.box2.getGroupKeyInfo(id, (err, info) => { + ssb.box2.getGroupInfo(id, (err, info) => { if (err) return cb(clarify(err, 'Failed to get group details')) if (!info) return cb(new Error(`Couldn't find group with id ${id}`)) cb(null, { id, - secret: info.key, + writeKey: info.writeKey, + readKeys: info.readKeys, root: info.root, }) }) @@ -115,14 +118,14 @@ module.exports = { return cb(new Error('addMembers only supports bendybutt-v1 feed IDs')) } - get(groupId, (err, { secret, root }) => { + get(groupId, (err, { writeKey, root }) => { // prettier-ignore if (err) return cb(clarify(err, `Failed to get group details when adding members`)) const content = { type: 'group/add-member', version: 'v2', - secret: secret.toString('base64'), + secret: writeKey.toString('base64'), root, tangles: { members: { @@ -182,23 +185,49 @@ module.exports = { (member) => !feedIds.includes(member) ) - // TODO: add this key to ourselves. step 1. make sure we ourselves can post to the new feed. step 2 and later: other people can post on the new key - const newGroupKey = new SecretKey() - const newKeyContent = { - type: 'group/move-epoch', - secret: newGroupKey.toString('base64'), - recps: [groupId, ...remainingMembers], - } - // TODO: loop if many members - publish(newKeyContent, (err) => { + findOrCreateEpochWithoutMembers((err, group) => { // prettier-ignore - if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) + if (err) return cb(clarify(err, 'Failed to create group init message when creating a group')) + + const { groupInitMsg, groupFeed, myRoot } = group + + const newGroupKey = secretKeyFromString(groupFeed.purpose) - console.log('added people to new epoch') + const data = { + id: buildGroupId({ + groupInitMsg, + groupKey: newGroupKey.toBuffer(), + }), + root: fromMessageSigil(groupInitMsg.key), + subfeed: groupFeed.keys, + } + + // TODO: shouldn't be add, should be update or something + ssb.box2.addGroupInfo(data.id, { + key: newGroupKey, + root: data.root, + }) - // TODO: create feed for the new epoch - // either createGroupWithoutMembers(myRoot, cb) { but with an arg for the secret - // or findOrCreateGroupFeed(null, function gotGroupFeed(err, groupFeed) { but we need to duplicate more code (maybe premature to worry about tho) + // TODO: add this key to ourselves. step 1. make sure we ourselves can post to the new feed. step 2 and later: other people can post on the new key + const newKeyContent = { + type: 'group/move-epoch', + secret: newGroupKey.toString('base64'), + exclusion: fromMessageSigil(exclusionMsg.key), + // TODO: how to have the group id in the recps but encrypt to the new key. do we need to put the new key in box2 before this? + // maybe we should create the new feed first :thinking: then we'll have the key safely saved there + recps: [groupId, ...remainingMembers], + } + // TODO: loop if many members + publish(newKeyContent, (err) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) + + console.log('added people to new epoch') + + // TODO: create feed for the new epoch + // either createGroupWithoutMembers(myRoot, cb) { but with an arg for the secret + // or findOrCreateGroupFeed(null, function gotGroupFeed(err, groupFeed) { but we need to duplicate more code (maybe premature to worry about tho) + }) }) }) ) @@ -224,11 +253,11 @@ module.exports = { if (!contentSpec(content)) return cb(new Error(contentSpec.errorsString)) - get(groupId, (err, { secret }) => { + get(groupId, (err, { writeKey }) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to get group details when publishing to a group')) - findOrCreateGroupFeed(secret, (err, groupFeed) => { + findOrCreateGroupFeed(writeKey, (err, groupFeed) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create group feed when publishing to a group')) diff --git a/lib/get-group-tangle.js b/lib/get-group-tangle.js index da887a7..f28effc 100644 --- a/lib/get-group-tangle.js +++ b/lib/get-group-tangle.js @@ -21,7 +21,7 @@ module.exports = function GetGroupTangle(server) { return cb(new Error(`get-group-tangle expects valid groupId, got: ${groupId}`)) } - server.box2.getGroupKeyInfo(groupId, (err, info) => { + server.box2.getGroupInfo(groupId, (err, info) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to get group key info when getting group tangle')) @@ -30,6 +30,7 @@ module.exports = function GetGroupTangle(server) { } getUpdates(info.root, (err, msgs) => { + // prettier-ignore if (err) return cb(clarify(err, 'Failed to read updates when getting group tangle')) const nodes = msgs.map((msg) => ({ diff --git a/lib/meta-feed-helpers.js b/lib/meta-feed-helpers.js index 6d1a269..59186b0 100644 --- a/lib/meta-feed-helpers.js +++ b/lib/meta-feed-helpers.js @@ -169,7 +169,7 @@ module.exports = (ssb) => { /** more specifically: a group that has never had any members. i.e. either * 1. newly created but tribes2 crashed before we had time to add ourselves to it. in that case find and return it. if we can't find such a group then * 2. freshly create a new group, and return */ - function findOrCreateGroupWithoutMembers(cb) { + function findOrCreateEpochWithoutMembers(cb) { ssb.metafeeds.findOrCreate(function gotRoot(err, myRoot) { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create root feed when creating a group')) @@ -244,6 +244,6 @@ module.exports = (ssb) => { findOrCreateFromSecret, findOrCreateGroupFeed, createGroupWithoutMembers, - findOrCreateGroupWithoutMembers, + findOrCreateEpochWithoutMembers, } } diff --git a/package.json b/package.json index 4ad3c7c..a43e1ad 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "pull-paramap": "^1.2.2", "pull-stream": "^3.7.0", "ssb-bfe": "^3.7.0", - "ssb-box2": "^5.0.0", + "ssb-box2": "^6.0.0", "ssb-crut": "^4.6.1", "ssb-db2": "^6.3.0", "ssb-meta-feeds": "^0.38.2", diff --git a/test/list-and-get.test.js b/test/list-and-get.test.js index 0c76cbb..d41c761 100644 --- a/test/list-and-get.test.js +++ b/test/list-and-get.test.js @@ -23,11 +23,7 @@ test('tribes.list + tribes.get', (t) => { pull.collect(async (err, list) => { t.error(err, 'no error') - const expectedGroup = { - secret: group.secret, - root: group.root, - id: group.id, - } + const expectedGroup = group t.deepEqual(list, [expectedGroup], 'lists group ids') @@ -63,14 +59,18 @@ test('tribes.list + tribes.get', (t) => { test('get', async (t) => { const ssb = Testbot() - const { id, secret, root } = await ssb.tribes2.create().catch(t.error) + const { id, writeKey, readKeys, root } = await ssb.tribes2 + .create() + .catch(t.error) const group = await ssb.tribes2.get(id) t.equal(id, group.id) t.true(isIdentityGroupSSBURI(group.id)) - t.true(Buffer.isBuffer(group.secret)) - t.equal(secret, group.secret) + t.true(Buffer.isBuffer(group.writeKey.key)) + t.equal(writeKey.key, group.writeKey.key) + t.true(Buffer.isBuffer(group.readKeys[0].key)) + t.equal(readKeys[0].key, group.readKeys[0].key) t.true(isClassicMessageSSBURI(group.root), 'has root') t.equal(root, group.root) From 5bcba48ef5685f2566180793a813aa422b11d378 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Sun, 12 Mar 2023 21:17:11 +0100 Subject: [PATCH 04/37] Remove old todo comments --- index.js | 120 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 64 insertions(+), 56 deletions(-) diff --git a/index.js b/index.js index 730de13..38784cc 100644 --- a/index.js +++ b/index.js @@ -22,7 +22,6 @@ const { keySchemes, } = require('private-group-spec') const { fromMessageSigil, isBendyButtV1FeedSSBURI } = require('ssb-uri2') -const { SecretKey } = require('ssb-private-group-keys') const buildGroupId = require('./lib/build-group-id') const AddTangles = require('./lib/add-tangles') const publishAndPrune = require('./lib/prune-publish') @@ -164,73 +163,82 @@ module.exports = { if (cb === undefined) return promisify(excludeMembers)(groupId, feedIds, opts) + const excludeContent = { + type: 'group/exclude', + excludes: feedIds, + recps: [groupId], + } + // TODO: should probably check this against a spec of its own // TODO: should add members tangle to this. should we add opts to publish()? { spec, tangles, keys} - publish( - { - type: 'group/exclude', - excludes: feedIds, - recps: [groupId], - }, - (err, exclusionMsg) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to publish exclude msg')) + publish(excludeContent, (err, exclusionMsg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publish exclude msg')) - pull( - listMembers(groupId), - pull.collect((err, beforeMembers) => { - const remainingMembers = beforeMembers.filter( - (member) => !feedIds.includes(member) - ) + pull( + listMembers(groupId), + pull.collect((err, beforeMembers) => { + const remainingMembers = beforeMembers.filter( + (member) => !feedIds.includes(member) + ) - findOrCreateEpochWithoutMembers((err, group) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to create group init message when creating a group')) + findOrCreateEpochWithoutMembers((err, group) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to create group init message when creating a group')) - const { groupInitMsg, groupFeed, myRoot } = group + const { groupInitMsg, groupFeed, myRoot } = group - const newGroupKey = secretKeyFromString(groupFeed.purpose) + const newGroupKey = secretKeyFromString(groupFeed.purpose) - const data = { - id: buildGroupId({ - groupInitMsg, - groupKey: newGroupKey.toBuffer(), - }), - root: fromMessageSigil(groupInitMsg.key), - subfeed: groupFeed.keys, - } + const data = { + id: buildGroupId({ + groupInitMsg, + groupKey: newGroupKey.toBuffer(), + }), + root: fromMessageSigil(groupInitMsg.key), + subfeed: groupFeed.keys, + } - // TODO: shouldn't be add, should be update or something - ssb.box2.addGroupInfo(data.id, { + ssb.box2.addGroupInfo( + data.id, + { key: newGroupKey, root: data.root, - }) - - // TODO: add this key to ourselves. step 1. make sure we ourselves can post to the new feed. step 2 and later: other people can post on the new key - const newKeyContent = { - type: 'group/move-epoch', - secret: newGroupKey.toString('base64'), - exclusion: fromMessageSigil(exclusionMsg.key), - // TODO: how to have the group id in the recps but encrypt to the new key. do we need to put the new key in box2 before this? - // maybe we should create the new feed first :thinking: then we'll have the key safely saved there - recps: [groupId, ...remainingMembers], + }, + (err) => { + ssb.box2.pickGroupWriteKey( + data.id, + { + key: newGroupKey, + scheme: keySchemes.private_group, + }, + (err) => { + const newKeyContent = { + type: 'group/move-epoch', + secret: newGroupKey.toString('base64'), + exclusion: fromMessageSigil(exclusionMsg.key), + // TODO: maybe we should create the new feed first :thinking: then we'll have the key safely saved there + recps: [groupId, ...remainingMembers], + } + // TODO: loop if many members + publish(newKeyContent, (err) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) + + console.log('added people to new epoch') + + // TODO: create feed for the new epoch + // either createGroupWithoutMembers(myRoot, cb) { but with an arg for the secret + // or findOrCreateGroupFeed(null, function gotGroupFeed(err, groupFeed) { but we need to duplicate more code (maybe premature to worry about tho) + }) + } + ) } - // TODO: loop if many members - publish(newKeyContent, (err) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) - - console.log('added people to new epoch') - - // TODO: create feed for the new epoch - // either createGroupWithoutMembers(myRoot, cb) { but with an arg for the secret - // or findOrCreateGroupFeed(null, function gotGroupFeed(err, groupFeed) { but we need to duplicate more code (maybe premature to worry about tho) - }) - }) + ) }) - ) - } - ) + }) + ) + }) } function publish(content, cb) { From c607e2fff7a85a34aa6dd58df4a051de498bdd87 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Mon, 13 Mar 2023 16:16:14 +0100 Subject: [PATCH 05/37] Start to add test --- index.js | 62 +++++++++++++++------------------ test/exclude-members.test.js | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 test/exclude-members.test.js diff --git a/index.js b/index.js index 38784cc..4c8e31c 100644 --- a/index.js +++ b/index.js @@ -199,42 +199,36 @@ module.exports = { subfeed: groupFeed.keys, } - ssb.box2.addGroupInfo( - data.id, - { + const addInfo = { + key: newGroupKey, + root: data.root, + } + ssb.box2.addGroupInfo(data.id, addInfo, (err) => { + const newKey = { key: newGroupKey, - root: data.root, - }, - (err) => { - ssb.box2.pickGroupWriteKey( - data.id, - { - key: newGroupKey, - scheme: keySchemes.private_group, - }, - (err) => { - const newKeyContent = { - type: 'group/move-epoch', - secret: newGroupKey.toString('base64'), - exclusion: fromMessageSigil(exclusionMsg.key), - // TODO: maybe we should create the new feed first :thinking: then we'll have the key safely saved there - recps: [groupId, ...remainingMembers], - } - // TODO: loop if many members - publish(newKeyContent, (err) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) - - console.log('added people to new epoch') - - // TODO: create feed for the new epoch - // either createGroupWithoutMembers(myRoot, cb) { but with an arg for the secret - // or findOrCreateGroupFeed(null, function gotGroupFeed(err, groupFeed) { but we need to duplicate more code (maybe premature to worry about tho) - }) - } - ) + scheme: keySchemes.private_group, } - ) + ssb.box2.pickGroupWriteKey(data.id, newKey, (err) => { + const newKeyContent = { + type: 'group/move-epoch', + secret: newGroupKey.toString('base64'), + exclusion: fromMessageSigil(exclusionMsg.key), + // TODO: maybe we should create the new feed first :thinking: then we'll have the key safely saved there + recps: [groupId, ...remainingMembers], + } + // TODO: loop if many members + publish(newKeyContent, (err) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) + + console.log('added people to new epoch') + + // TODO: create feed for the new epoch + // either createGroupWithoutMembers(myRoot, cb) { but with an arg for the secret + // or findOrCreateGroupFeed(null, function gotGroupFeed(err, groupFeed) { but we need to duplicate more code (maybe premature to worry about tho) + }) + }) + }) }) }) ) diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js new file mode 100644 index 0000000..0f89d86 --- /dev/null +++ b/test/exclude-members.test.js @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2022 Andre 'Staltz' Medeiros +// +// SPDX-License-Identifier: CC0-1.0 + +const test = require('tape') +const pull = require('pull-stream') +const { promisify: p } = require('util') +const ssbKeys = require('ssb-keys') +const Testbot = require('./helpers/testbot') +const replicate = require('./helpers/replicate') + +test('add and remove a person, post on the new feed', async (t) => { + 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' + ), + }) + + await alice.tribes2.start() + await bob.tribes2.start() + t.pass('tribes2 started for both alice and bob') + + await p(alice.metafeeds.findOrCreate)() + const bobRoot = await p(bob.metafeeds.findOrCreate)() + + await replicate(alice, bob) + t.pass('alice and bob replicate their trees') + + const { id: groupId } = await alice.tribes2.create().catch((err) => { + console.error('alice failed to create group', err) + t.fail(err) + }) + t.pass('alice created a group') + + await alice.tribes2.addMembers(groupId, [bobRoot.id]).catch((err) => { + console.error('add member fail', err) + t.fail(err) + }) + t.pass('alice added bob to the group') + + await alice.tribes2.excludeMembers(groupId, [bobRoot.id]).catch((err) => { + console.error('remove member fail', err) + t.fail(err) + }) + + await alice.tribes2 + .publish({ + type: 'test', + text: 'post', + recps: [groupId], + }) + .catch(t.fail) + + // TODO: verify that message was published on the new feed + + await p(alice.close)(true) + await p(bob.close)(true) +}) From e854e1a90c35ca5654afab1c7d78a67a4bd737e8 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Mon, 13 Mar 2023 17:08:24 +0100 Subject: [PATCH 06/37] Add opts to publish --- index.js | 24 ++++++++++++++---------- test/get-tangle.test.js | 12 ++++++++---- test/prune-publish.test.js | 1 + 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index 4c8e31c..7cdbd1b 100644 --- a/index.js +++ b/index.js @@ -168,10 +168,9 @@ module.exports = { excludes: feedIds, recps: [groupId], } - - // TODO: should probably check this against a spec of its own - // TODO: should add members tangle to this. should we add opts to publish()? { spec, tangles, keys} - publish(excludeContent, (err, exclusionMsg) => { + // TODO: spec + const excludeOpts = { tangles: ['group', 'members'], spec: () => true } + publish(excludeContent, excludeOpts, (err, exclusionMsg) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to publish exclude msg')) @@ -213,11 +212,11 @@ module.exports = { type: 'group/move-epoch', secret: newGroupKey.toString('base64'), exclusion: fromMessageSigil(exclusionMsg.key), - // TODO: maybe we should create the new feed first :thinking: then we'll have the key safely saved there recps: [groupId, ...remainingMembers], } // TODO: loop if many members - publish(newKeyContent, (err) => { + // TODO: spec + publish(newKeyContent, { spec: () => true }, (err) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) @@ -226,6 +225,8 @@ module.exports = { // TODO: create feed for the new epoch // either createGroupWithoutMembers(myRoot, cb) { but with an arg for the secret // or findOrCreateGroupFeed(null, function gotGroupFeed(err, groupFeed) { but we need to duplicate more code (maybe premature to worry about tho) + + return cb() }) }) }) @@ -235,22 +236,25 @@ module.exports = { }) } - function publish(content, cb) { - if (cb === undefined) return promisify(publish)(content) + function publish(content, opts, cb) { + if (cb === undefined) return promisify(publish)(content, opts) if (!content) return cb(new Error('Missing content')) + const isSpec = opts?.spec ?? isContent + const tangles = opts?.tangles ?? ['group'] + const recps = content.recps if (!recps || !Array.isArray(recps) || recps.length < 1) { return cb(new Error('Missing recps')) } const groupId = recps[0] - addTangles(content, ['group'], (err, content) => { + addTangles(content, tangles, (err, content) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to add group tangle when publishing to a group')) - if (!isContent(content)) return cb(new Error(isContent.errorsString)) + if (!isSpec(content)) return cb(new Error(isSpec.errorsString)) get(groupId, (err, { writeKey }) => { // prettier-ignore diff --git a/test/get-tangle.test.js b/test/get-tangle.test.js index a0ad533..c30ab67 100644 --- a/test/get-tangle.test.js +++ b/test/get-tangle.test.js @@ -63,7 +63,7 @@ test('get-tangle unit test', (t) => { recps: [group.id], } - server.tribes2.publish(content, (err, msg) => { + server.tribes2.publish(content, null, (err, msg) => { t.error(err, 'no error') getTangle(group.id, (err, { root, previous }) => { @@ -74,7 +74,7 @@ test('get-tangle unit test', (t) => { 'adding message to root' ) - server.tribes2.publish(content, (err, msg) => { + server.tribes2.publish(content, null, (err, msg) => { t.error(err, 'no error') getTangle(group.id, (err, { root, previous }) => { @@ -110,7 +110,11 @@ test(`get-tangle-${n}-publishes`, (t) => { pull.values(publishArray), paraMap( (value, cb) => - server.tribes2.publish({ type: 'memo', value, recps: [groupId] }, cb), + server.tribes2.publish( + { type: 'memo', value, recps: [groupId] }, + null, + cb + ), 4 ), paraMap((msg, cb) => server.db.getMsg(msg.key, cb), 10), @@ -149,7 +153,7 @@ test('get-tangle', (t) => { } ssb.db.onMsgAdded((lastMsgAfterCreate) => { - ssb.tribes2.publish(content, (err, msg) => { + ssb.tribes2.publish(content, null, (err, msg) => { t.error(err, 'publish a message') ssb.db.get(msg.key, (err, A) => { diff --git a/test/prune-publish.test.js b/test/prune-publish.test.js index e04eeb2..e92545f 100644 --- a/test/prune-publish.test.js +++ b/test/prune-publish.test.js @@ -72,6 +72,7 @@ test('publish many messages that might need pruning', (t) => { new Promise((res, rej) => { ssb.tribes2.publish( { type: 'potato', content: value, recps: [group.id] }, + null, (err, msg) => { if (err) return rej(err) return res(msg) From 75f054c9f663fa1d71ea3fac23fcb2608793d8b5 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Mon, 13 Mar 2023 19:33:15 +0100 Subject: [PATCH 07/37] Let publish handle creating the new feed --- index.js | 72 ++++++++++++------------------- test/create.test.js | 15 +------ test/exclude-members.test.js | 21 ++++++++- test/helpers/count-group-feeds.js | 14 ++++++ 4 files changed, 63 insertions(+), 59 deletions(-) create mode 100644 test/helpers/count-group-feeds.js diff --git a/index.js b/index.js index 7cdbd1b..efac4d9 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,7 @@ const { }, keySchemes, } = require('private-group-spec') +const { SecretKey } = require('ssb-private-group-keys') const { fromMessageSigil, isBendyButtV1FeedSSBURI } = require('ssb-uri2') const buildGroupId = require('./lib/build-group-id') const AddTangles = require('./lib/add-tangles') @@ -177,60 +178,42 @@ module.exports = { pull( listMembers(groupId), pull.collect((err, beforeMembers) => { + if (err) return cb(err) + const remainingMembers = beforeMembers.filter( (member) => !feedIds.includes(member) ) + const newGroupKey = new SecretKey() + const addInfo = { key: newGroupKey.toBuffer() } - findOrCreateEpochWithoutMembers((err, group) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to create group init message when creating a group')) - - const { groupInitMsg, groupFeed, myRoot } = group + ssb.box2.addGroupInfo(groupId, addInfo, (err) => { + if (err) return cb(err) - const newGroupKey = secretKeyFromString(groupFeed.purpose) - - const data = { - id: buildGroupId({ - groupInitMsg, - groupKey: newGroupKey.toBuffer(), - }), - root: fromMessageSigil(groupInitMsg.key), - subfeed: groupFeed.keys, + const newKey = { + key: newGroupKey.toBuffer(), + scheme: keySchemes.private_group, } - - const addInfo = { - key: newGroupKey, - root: data.root, - } - ssb.box2.addGroupInfo(data.id, addInfo, (err) => { - const newKey = { - key: newGroupKey, - scheme: keySchemes.private_group, + ssb.box2.pickGroupWriteKey(groupId, newKey, (err) => { + if (err) return cb(err) + + const newKeyContent = { + type: 'group/move-epoch', + secret: newGroupKey.toString('base64'), + exclusion: fromMessageSigil(exclusionMsg.key), + recps: [groupId, ...remainingMembers], } - ssb.box2.pickGroupWriteKey(data.id, newKey, (err) => { - const newKeyContent = { - type: 'group/move-epoch', - secret: newGroupKey.toString('base64'), - exclusion: fromMessageSigil(exclusionMsg.key), - recps: [groupId, ...remainingMembers], - } - // TODO: loop if many members - // TODO: spec - publish(newKeyContent, { spec: () => true }, (err) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) - - console.log('added people to new epoch') - - // TODO: create feed for the new epoch - // either createGroupWithoutMembers(myRoot, cb) { but with an arg for the secret - // or findOrCreateGroupFeed(null, function gotGroupFeed(err, groupFeed) { but we need to duplicate more code (maybe premature to worry about tho) - - return cb() - }) + // TODO: loop if many members + // TODO: spec + // TODO: post this on old feed + publish(newKeyContent, { spec: () => true }, (err) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) + + return cb() }) }) }) + //}) }) ) }) @@ -260,6 +243,7 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, 'Failed to get group details when publishing to a group')) + console.log('posting using', writeKey.key) findOrCreateGroupFeed(writeKey.key, (err, groupFeed) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create group feed when publishing to a group')) diff --git a/test/create.test.js b/test/create.test.js index 3c10937..87945b1 100644 --- a/test/create.test.js +++ b/test/create.test.js @@ -9,8 +9,8 @@ const { keySchemes } = require('private-group-spec') const Ref = require('ssb-ref') const { promisify: p } = require('util') const { where, type, toPromise } = require('ssb-db2/operators') -const pull = require('pull-stream') const Testbot = require('./helpers/testbot') +const countGroupFeeds = require('./helpers/count-group-feeds') test('create', async (t) => { const ssb = Testbot() @@ -104,19 +104,6 @@ test('root message is encrypted', async (t) => { await p(alice.close)(true) }) -function countGroupFeeds(server, cb) { - pull( - server.metafeeds.branchStream({ old: true, live: false }), - pull.filter((branch) => branch.length === 4), - pull.map((branch) => branch[3]), - pull.filter((feed) => feed.recps), - pull.collect((err, feeds) => { - if (err) return cb(err) - return cb(null, feeds.length) - }) - ) -} - function createEmptyGroupFeed({ server, root }, cb) { const secret = new SecretKey() server.metafeeds.findOrCreate( diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index 0f89d86..c2786be 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -3,11 +3,11 @@ // SPDX-License-Identifier: CC0-1.0 const test = require('tape') -const pull = require('pull-stream') const { promisify: p } = require('util') const ssbKeys = require('ssb-keys') const Testbot = require('./helpers/testbot') const replicate = require('./helpers/replicate') +const countGroupFeeds = require('./helpers/count-group-feeds') test('add and remove a person, post on the new feed', async (t) => { const alice = Testbot({ @@ -47,11 +47,24 @@ test('add and remove a person, post on the new feed', async (t) => { }) t.pass('alice added bob to the group') + t.equals( + await p(countGroupFeeds)(alice), + 1, + 'before exclude alice has 1 group feed' + ) + await alice.tribes2.excludeMembers(groupId, [bobRoot.id]).catch((err) => { console.error('remove member fail', err) t.fail(err) }) + // TODO: will be 2 if we post an init message on the new group feed + t.equals( + await p(countGroupFeeds)(alice), + 1, + 'after exclude alice has some number of group feeds?' + ) + await alice.tribes2 .publish({ type: 'test', @@ -60,6 +73,12 @@ test('add and remove a person, post on the new feed', async (t) => { }) .catch(t.fail) + t.equals( + await p(countGroupFeeds)(alice), + 2, + 'alice has 2 group feeds after publishing on the new feed' + ) + // TODO: verify that message was published on the new feed await p(alice.close)(true) diff --git a/test/helpers/count-group-feeds.js b/test/helpers/count-group-feeds.js new file mode 100644 index 0000000..f891130 --- /dev/null +++ b/test/helpers/count-group-feeds.js @@ -0,0 +1,14 @@ +const pull = require('pull-stream') + +module.exports = function countGroupFeeds(server, cb) { + pull( + server.metafeeds.branchStream({ old: true, live: false }), + pull.filter((branch) => branch.length === 4), + pull.map((branch) => branch[3]), + pull.filter((feed) => feed.recps), + pull.collect((err, feeds) => { + if (err) return cb(err) + return cb(null, feeds.length) + }) + ) +} From 5c2cb46c6510c6aa5d40c528c011e6b0a6f7a4d9 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Mon, 13 Mar 2023 19:35:29 +0100 Subject: [PATCH 08/37] Remove spec todos --- index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.js b/index.js index efac4d9..02f47bc 100644 --- a/index.js +++ b/index.js @@ -169,7 +169,6 @@ module.exports = { excludes: feedIds, recps: [groupId], } - // TODO: spec const excludeOpts = { tangles: ['group', 'members'], spec: () => true } publish(excludeContent, excludeOpts, (err, exclusionMsg) => { // prettier-ignore @@ -203,7 +202,6 @@ module.exports = { recps: [groupId, ...remainingMembers], } // TODO: loop if many members - // TODO: spec // TODO: post this on old feed publish(newKeyContent, { spec: () => true }, (err) => { // prettier-ignore From 2c93839e3e99bfc28ce052af003cc704044c70a4 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Mon, 13 Mar 2023 19:36:46 +0100 Subject: [PATCH 09/37] Add license to count-group-feeds --- test/helpers/count-group-feeds.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/helpers/count-group-feeds.js b/test/helpers/count-group-feeds.js index f891130..ed5536b 100644 --- a/test/helpers/count-group-feeds.js +++ b/test/helpers/count-group-feeds.js @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2022 Andre 'Staltz' Medeiros +// +// SPDX-License-Identifier: CC0-1.0 + const pull = require('pull-stream') module.exports = function countGroupFeeds(server, cb) { From 6fbe83849dcd1b449e2fc5125b2ed95bdacaf1d4 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Mon, 13 Mar 2023 19:41:25 +0100 Subject: [PATCH 10/37] Remove console log --- index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.js b/index.js index 02f47bc..9c1b9ab 100644 --- a/index.js +++ b/index.js @@ -211,7 +211,6 @@ module.exports = { }) }) }) - //}) }) ) }) @@ -241,7 +240,6 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, 'Failed to get group details when publishing to a group')) - console.log('posting using', writeKey.key) findOrCreateGroupFeed(writeKey.key, (err, groupFeed) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create group feed when publishing to a group')) From 1ddb4e92b5b94444c9f61446325eef53f7bea100 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Wed, 15 Mar 2023 19:06:06 +0100 Subject: [PATCH 11/37] Post init message on new epoch --- index.js | 99 ++++++++++++++++++++++-------------- lib/add-tangles.js | 1 + test/exclude-members.test.js | 7 ++- 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/index.js b/index.js index 9c1b9ab..2957dc1 100644 --- a/index.js +++ b/index.js @@ -164,55 +164,76 @@ module.exports = { if (cb === undefined) return promisify(excludeMembers)(groupId, feedIds, opts) - const excludeContent = { - type: 'group/exclude', - excludes: feedIds, - recps: [groupId], - } - const excludeOpts = { tangles: ['group', 'members'], spec: () => true } - publish(excludeContent, excludeOpts, (err, exclusionMsg) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to publish exclude msg')) + ssb.metafeeds.findOrCreate(function gotRoot(err, myRoot) { + if (err) return cb(err) - pull( - listMembers(groupId), - pull.collect((err, beforeMembers) => { - if (err) return cb(err) - - const remainingMembers = beforeMembers.filter( - (member) => !feedIds.includes(member) - ) - const newGroupKey = new SecretKey() - const addInfo = { key: newGroupKey.toBuffer() } + const excludeContent = { + type: 'group/exclude', + excludes: feedIds, + recps: [groupId], + } + const excludeOpts = { tangles: ['group', 'members'], spec: () => true } + publish(excludeContent, excludeOpts, (err, exclusionMsg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publish exclude msg')) - ssb.box2.addGroupInfo(groupId, addInfo, (err) => { + pull( + listMembers(groupId), + pull.collect((err, beforeMembers) => { if (err) return cb(err) - const newKey = { - key: newGroupKey.toBuffer(), - scheme: keySchemes.private_group, - } - ssb.box2.pickGroupWriteKey(groupId, newKey, (err) => { + const remainingMembers = beforeMembers.filter( + (member) => !feedIds.includes(member) + ) + const newGroupKey = new SecretKey() + const addInfo = { key: newGroupKey.toBuffer() } + + ssb.box2.addGroupInfo(groupId, addInfo, (err) => { if (err) return cb(err) - const newKeyContent = { - type: 'group/move-epoch', - secret: newGroupKey.toString('base64'), - exclusion: fromMessageSigil(exclusionMsg.key), - recps: [groupId, ...remainingMembers], + const newKey = { + key: newGroupKey.toBuffer(), + scheme: keySchemes.private_group, } - // TODO: loop if many members - // TODO: post this on old feed - publish(newKeyContent, { spec: () => true }, (err) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) - - return cb() + ssb.box2.pickGroupWriteKey(groupId, newKey, (err) => { + if (err) return cb(err) + + const newEpochContent = { + type: 'group/init', + version: 'v2', + groupKey: newGroupKey.toString('base64'), + tangles: { + members: { root: null, previous: null }, + }, + recps: [groupId, myRoot.id], + } + const newTangleOpts = { + tangles: ['group', 'epoch'], + spec: () => true, + } + publish(newEpochContent, newTangleOpts, (err) => { + if (err) return cb(err) + + const newKeyContent = { + type: 'group/move-epoch', + secret: newGroupKey.toString('base64'), + exclusion: fromMessageSigil(exclusionMsg.key), + recps: [groupId, ...remainingMembers], + } + // TODO: loop if many members + // TODO: post this on old feed + publish(newKeyContent, { spec: () => true }, (err) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) + + return cb() + }) + }) }) }) }) - }) - ) + ) + }) }) } diff --git a/lib/add-tangles.js b/lib/add-tangles.js index a556eae..86963e0 100644 --- a/lib/add-tangles.js +++ b/lib/add-tangles.js @@ -11,6 +11,7 @@ module.exports = function AddTangles(server) { const getTangle = { group: GetTangle(server, 'group'), members: GetTangle(server, 'members'), + epoch: GetTangle(server, 'epoch'), } function addSomeTangles(content, tangles, cb) { diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index c2786be..4c2d30a 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -58,11 +58,10 @@ test('add and remove a person, post on the new feed', async (t) => { t.fail(err) }) - // TODO: will be 2 if we post an init message on the new group feed t.equals( await p(countGroupFeeds)(alice), - 1, - 'after exclude alice has some number of group feeds?' + 2, + 'after exclude alice has 2 group feeds' ) await alice.tribes2 @@ -76,7 +75,7 @@ test('add and remove a person, post on the new feed', async (t) => { t.equals( await p(countGroupFeeds)(alice), 2, - 'alice has 2 group feeds after publishing on the new feed' + 'alice still has 2 group feeds after publishing on the new feed' ) // TODO: verify that message was published on the new feed From 8ac8f62a05ee331f41aacab32cf82472fbfeb305 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Wed, 15 Mar 2023 20:42:20 +0100 Subject: [PATCH 12/37] Fix publish usage in test --- test/get-tangle.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/get-tangle.test.js b/test/get-tangle.test.js index c30ab67..cba0a6b 100644 --- a/test/get-tangle.test.js +++ b/test/get-tangle.test.js @@ -218,10 +218,10 @@ test('get-tangle with branch', async (t) => { recps: [group.id], }) - await p(alice.tribes2.publish)(content()).catch(t.fail) + await alice.tribes2.publish(content()).catch(t.fail) t.pass('alice published a message') - await p(bob.tribes2.publish)(content()).catch(t.fail) + await bob.tribes2.publish(content()).catch(t.fail) t.pass('bob published a message') // Then Bob shares his message with Alice From ade87ffa85f2855424540907fbac74662f94fbd0 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Wed, 15 Mar 2023 22:34:50 +0100 Subject: [PATCH 13/37] Start checking that messages look correct --- test/exclude-members.test.js | 84 ++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index 4c2d30a..9adfe27 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -5,11 +5,26 @@ const test = require('tape') const { promisify: p } = require('util') const ssbKeys = require('ssb-keys') +const { + where, + and, + count, + isDecrypted, + type, + author, + toCallback, + toPullStream, + toPromise, +} = require('ssb-db2/operators') +const pull = require('pull-stream') const Testbot = require('./helpers/testbot') const replicate = require('./helpers/replicate') const countGroupFeeds = require('./helpers/count-group-feeds') test('add and remove a person, post on the new feed', async (t) => { + // feeds should look like + // first: initGroup->addAlice->addBob->excludeBob->reAddAlice + // second: initEpoch->post const alice = Testbot({ keys: ssbKeys.generate(null, 'alice'), mfSeed: Buffer.from( @@ -35,7 +50,11 @@ test('add and remove a person, post on the new feed', async (t) => { await replicate(alice, bob) t.pass('alice and bob replicate their trees') - const { id: groupId } = await alice.tribes2.create().catch((err) => { + const { + id: groupId, + writeKey: writeKey1, + subfeed: { id: firstFeedId }, + } = await alice.tribes2.create().catch((err) => { console.error('alice failed to create group', err) t.fail(err) }) @@ -64,7 +83,9 @@ test('add and remove a person, post on the new feed', async (t) => { 'after exclude alice has 2 group feeds' ) - await alice.tribes2 + const { + value: { author: secondFeedId }, + } = await alice.tribes2 .publish({ type: 'test', text: 'post', @@ -78,7 +99,64 @@ test('add and remove a person, post on the new feed', async (t) => { 'alice still has 2 group feeds after publishing on the new feed' ) - // TODO: verify that message was published on the new feed + t.notEquals( + secondFeedId, + firstFeedId, + 'feed for publish is different to initial feed' + ) + + const { writeKey: writeKey2 } = await alice.tribes2.get(groupId) + + t.false(writeKey1.key.equals(writeKey2.key), "there's a new key for writing") + + const msgsFromFirst = await alice.db.query( + where(author(firstFeedId)), + toPromise() + ) + + const firstContents = msgsFromFirst.map((msg) => msg.value.content) + + t.equal(firstContents.length, 5, '5 messages on first feed') + + const firstInit = firstContents[0] + + t.equal(firstInit.type, 'group/init') + t.equal(firstInit.groupKey, writeKey1.key.toString('base64')) + + //const addAlice = firstContents[1] + //TODO + + //const addBob = firstContents[2] + //TODO + + //const excludeMsg = firstContents[3] + + // TODO: test excludeMsg once we use the correct format + + //const reinviteMsg = firstContents[4] + + // TODO: test reinviteMsg once we use the correct format (add-member) + + const msgsFromSecond = await alice.db.query( + where(author(secondFeedId)), + toPromise() + ) + + const secondContents = msgsFromSecond.map((msg) => msg.value.content) + console.log({ firstContents, secondContents }) + + t.equal(secondContents.length, 2, '2 messages on second (new) feed') + + const secondInit = secondContents[0] + + t.equal(secondInit.type, 'group/init') + t.equal(secondInit.version, 'v2') + t.equal(secondInit.groupKey, writeKey2.key.toString('base64')) + // TODO: test epoch tangle + + const post = secondContents[1] + + t.equal(post.text, 'post', 'found post on second feed') await p(alice.close)(true) await p(bob.close)(true) From 0d2d713788b12ffdc54ee5587bfd1b12ec8f3708 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Thu, 16 Mar 2023 11:24:03 +0100 Subject: [PATCH 14/37] Figure out that additions are on the additions feed --- test/exclude-members.test.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index 9adfe27..321a8f4 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -23,8 +23,9 @@ const countGroupFeeds = require('./helpers/count-group-feeds') test('add and remove a person, post on the new feed', async (t) => { // feeds should look like - // first: initGroup->addAlice->addBob->excludeBob->reAddAlice + // first: initGroup->excludeBob->reAddAlice // second: initEpoch->post + // additions: addAlice->addBob (not checking this here) const alice = Testbot({ keys: ssbKeys.generate(null, 'alice'), mfSeed: Buffer.from( @@ -116,24 +117,18 @@ test('add and remove a person, post on the new feed', async (t) => { const firstContents = msgsFromFirst.map((msg) => msg.value.content) - t.equal(firstContents.length, 5, '5 messages on first feed') + t.equal(firstContents.length, 3, '3 messages on first feed') const firstInit = firstContents[0] t.equal(firstInit.type, 'group/init') t.equal(firstInit.groupKey, writeKey1.key.toString('base64')) - //const addAlice = firstContents[1] - //TODO - - //const addBob = firstContents[2] - //TODO - - //const excludeMsg = firstContents[3] + //const excludeMsg = firstContents[1] // TODO: test excludeMsg once we use the correct format - //const reinviteMsg = firstContents[4] + //const reinviteMsg = firstContents[2] // TODO: test reinviteMsg once we use the correct format (add-member) @@ -143,7 +138,6 @@ test('add and remove a person, post on the new feed', async (t) => { ) const secondContents = msgsFromSecond.map((msg) => msg.value.content) - console.log({ firstContents, secondContents }) t.equal(secondContents.length, 2, '2 messages on second (new) feed') From e8a81fe1b1a5a39be8d80be2e339544c444ebee5 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Thu, 16 Mar 2023 11:54:07 +0100 Subject: [PATCH 15/37] Post re-add messages on old feed --- index.js | 128 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 73 insertions(+), 55 deletions(-) diff --git a/index.js b/index.js index 2957dc1..7e1f4ab 100644 --- a/index.js +++ b/index.js @@ -167,72 +167,90 @@ module.exports = { ssb.metafeeds.findOrCreate(function gotRoot(err, myRoot) { if (err) return cb(err) - const excludeContent = { - type: 'group/exclude', - excludes: feedIds, - recps: [groupId], - } - const excludeOpts = { tangles: ['group', 'members'], spec: () => true } - publish(excludeContent, excludeOpts, (err, exclusionMsg) => { + get(groupId, (err, { writeKey: oldWriteKey }) => { // prettier-ignore - if (err) return cb(clarify(err, 'Failed to publish exclude msg')) - - pull( - listMembers(groupId), - pull.collect((err, beforeMembers) => { - if (err) return cb(err) - - const remainingMembers = beforeMembers.filter( - (member) => !feedIds.includes(member) - ) - const newGroupKey = new SecretKey() - const addInfo = { key: newGroupKey.toBuffer() } + if (err) return cb(err) - ssb.box2.addGroupInfo(groupId, addInfo, (err) => { - if (err) return cb(err) + findOrCreateGroupFeed(oldWriteKey.key, (err, oldGroupFeed) => { + // prettier-ignore + if (err) return cb(err) + + const excludeContent = { + type: 'group/exclude', + excludes: feedIds, + recps: [groupId], + } + const excludeOpts = { + tangles: ['group', 'members'], + spec: () => true, + } + publish(excludeContent, excludeOpts, (err, exclusionMsg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publish exclude msg')) - const newKey = { - key: newGroupKey.toBuffer(), - scheme: keySchemes.private_group, - } - ssb.box2.pickGroupWriteKey(groupId, newKey, (err) => { + pull( + listMembers(groupId), + pull.collect((err, beforeMembers) => { if (err) return cb(err) - const newEpochContent = { - type: 'group/init', - version: 'v2', - groupKey: newGroupKey.toString('base64'), - tangles: { - members: { root: null, previous: null }, - }, - recps: [groupId, myRoot.id], - } - const newTangleOpts = { - tangles: ['group', 'epoch'], - spec: () => true, - } - publish(newEpochContent, newTangleOpts, (err) => { + const remainingMembers = beforeMembers.filter( + (member) => !feedIds.includes(member) + ) + const newGroupKey = new SecretKey() + const addInfo = { key: newGroupKey.toBuffer() } + + ssb.box2.addGroupInfo(groupId, addInfo, (err) => { if (err) return cb(err) - const newKeyContent = { - type: 'group/move-epoch', - secret: newGroupKey.toString('base64'), - exclusion: fromMessageSigil(exclusionMsg.key), - recps: [groupId, ...remainingMembers], + const newKey = { + key: newGroupKey.toBuffer(), + scheme: keySchemes.private_group, } - // TODO: loop if many members - // TODO: post this on old feed - publish(newKeyContent, { spec: () => true }, (err) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) - - return cb() + ssb.box2.pickGroupWriteKey(groupId, newKey, (err) => { + if (err) return cb(err) + + const newEpochContent = { + type: 'group/init', + version: 'v2', + groupKey: newGroupKey.toString('base64'), + tangles: { + members: { root: null, previous: null }, + }, + recps: [groupId, myRoot.id], + } + const newTangleOpts = { + tangles: ['group', 'epoch'], + spec: () => true, + } + publish(newEpochContent, newTangleOpts, (err) => { + if (err) return cb(err) + + const reAddContent = { + type: 'group/move-epoch', + secret: newGroupKey.toString('base64'), + exclusion: fromMessageSigil(exclusionMsg.key), + recps: [groupId, ...remainingMembers], + } + const reAddOpts = { + // the re-adding needs to be published on the old + // feed so that the additions feed is not spammed, + // while people need to still be able to find it + feedKeys: oldGroupFeed.keys, + spec: () => true, + } + publish(reAddContent, reAddOpts, (err) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) + + return cb() + }) + }) }) }) }) - }) + ) }) - ) + }) }) }) } @@ -265,7 +283,7 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create group feed when publishing to a group')) - publishAndPrune(ssb, content, groupFeed.keys, cb) + publishAndPrune(ssb, content, opts?.feedKeys ?? groupFeed.keys, cb) }) }) }) From 5be659c905a27c06b12f125f1dfeb832d0ef8b1f Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Thu, 16 Mar 2023 18:49:12 +0100 Subject: [PATCH 16/37] Use addMembers to re-add members --- index.js | 32 +++++++++++++++++--------------- test/exclude-members.test.js | 21 ++++++--------------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/index.js b/index.js index 7e1f4ab..e19e1ff 100644 --- a/index.js +++ b/index.js @@ -153,7 +153,13 @@ module.exports = { if (!isAddMember(content)) return cb(new Error(isAddMember.errorsString)) - publishAndPrune(ssb, content, additionsFeed.keys, cb) + // TODO: use publish for this whole section instead? + publishAndPrune( + ssb, + content, + opts.feedKeys ?? additionsFeed.keys, + cb + ) }) }) }) @@ -184,7 +190,7 @@ module.exports = { tangles: ['group', 'members'], spec: () => true, } - publish(excludeContent, excludeOpts, (err, exclusionMsg) => { + publish(excludeContent, excludeOpts, (err) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to publish exclude msg')) @@ -225,25 +231,21 @@ module.exports = { publish(newEpochContent, newTangleOpts, (err) => { if (err) return cb(err) - const reAddContent = { - type: 'group/move-epoch', - secret: newGroupKey.toString('base64'), - exclusion: fromMessageSigil(exclusionMsg.key), - recps: [groupId, ...remainingMembers], - } const reAddOpts = { // the re-adding needs to be published on the old // feed so that the additions feed is not spammed, // while people need to still be able to find it feedKeys: oldGroupFeed.keys, - spec: () => true, } - publish(reAddContent, reAddOpts, (err) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to tell people about new epoch')) - - return cb() - }) + addMembers( + groupId, + remainingMembers, + reAddOpts, + (err) => { + if (err) return cb(err) + return cb() + } + ) }) }) }) diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index 321a8f4..0d31c9b 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -5,18 +5,7 @@ const test = require('tape') const { promisify: p } = require('util') const ssbKeys = require('ssb-keys') -const { - where, - and, - count, - isDecrypted, - type, - author, - toCallback, - toPullStream, - toPromise, -} = require('ssb-db2/operators') -const pull = require('pull-stream') +const { where, author, toPromise } = require('ssb-db2/operators') const Testbot = require('./helpers/testbot') const replicate = require('./helpers/replicate') const countGroupFeeds = require('./helpers/count-group-feeds') @@ -45,7 +34,7 @@ test('add and remove a person, post on the new feed', async (t) => { await bob.tribes2.start() t.pass('tribes2 started for both alice and bob') - await p(alice.metafeeds.findOrCreate)() + const aliceRoot = await p(alice.metafeeds.findOrCreate)() const bobRoot = await p(bob.metafeeds.findOrCreate)() await replicate(alice, bob) @@ -128,9 +117,11 @@ test('add and remove a person, post on the new feed', async (t) => { // TODO: test excludeMsg once we use the correct format - //const reinviteMsg = firstContents[2] + const reinviteMsg = firstContents[2] - // TODO: test reinviteMsg once we use the correct format (add-member) + t.equal(reinviteMsg.type, 'group/add-member') + t.deepEqual(reinviteMsg.recps, [groupId, aliceRoot.id]) + // TODO: check members tangle const msgsFromSecond = await alice.db.query( where(author(secondFeedId)), From cd8910a99808df556f1dd1dc05056d80d97f4d68 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Thu, 16 Mar 2023 18:56:39 +0100 Subject: [PATCH 17/37] Use publish in addMembers --- index.js | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index e19e1ff..b509c12 100644 --- a/index.js +++ b/index.js @@ -146,21 +146,15 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create additions feed when adding members')) - addTangles(content, ['group', 'members'], (err, content) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to add group tangles when adding members')) - - if (!isAddMember(content)) - return cb(new Error(isAddMember.errorsString)) - - // TODO: use publish for this whole section instead? - publishAndPrune( - ssb, - content, - opts.feedKeys ?? additionsFeed.keys, - cb - ) - }) + publish( + content, + { + spec: isAddMember, + tangles: ['group', 'members'], + feedKeys: opts.feedKeys ?? additionsFeed.keys, + }, + cb + ) }) }) }) From d8baa13aea94e8ab6e52c14236b7cf6d9e466c62 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Thu, 16 Mar 2023 19:05:31 +0100 Subject: [PATCH 18/37] Always have publish add the group tangle at least --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index b509c12..1095d83 100644 --- a/index.js +++ b/index.js @@ -150,7 +150,7 @@ module.exports = { content, { spec: isAddMember, - tangles: ['group', 'members'], + tangles: ['members'], feedKeys: opts.feedKeys ?? additionsFeed.keys, }, cb @@ -181,7 +181,7 @@ module.exports = { recps: [groupId], } const excludeOpts = { - tangles: ['group', 'members'], + tangles: ['members'], spec: () => true, } publish(excludeContent, excludeOpts, (err) => { @@ -219,7 +219,7 @@ module.exports = { recps: [groupId, myRoot.id], } const newTangleOpts = { - tangles: ['group', 'epoch'], + tangles: ['epoch'], spec: () => true, } publish(newEpochContent, newTangleOpts, (err) => { @@ -257,7 +257,7 @@ module.exports = { if (!content) return cb(new Error('Missing content')) const isSpec = opts?.spec ?? isContent - const tangles = opts?.tangles ?? ['group'] + const tangles = ['group', ...(opts?.tangles ?? [])] const recps = content.recps if (!recps || !Array.isArray(recps) || recps.length < 1) { From 91f7182ee40065c2e1bdce4fe02e9b116a29a21b Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Thu, 16 Mar 2023 19:22:51 +0100 Subject: [PATCH 19/37] Test exclude message --- test/exclude-members.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index 0d31c9b..7133e2e 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -113,9 +113,12 @@ test('add and remove a person, post on the new feed', async (t) => { t.equal(firstInit.type, 'group/init') t.equal(firstInit.groupKey, writeKey1.key.toString('base64')) - //const excludeMsg = firstContents[1] + const excludeMsg = firstContents[1] - // TODO: test excludeMsg once we use the correct format + t.equal(excludeMsg.type, 'group/exclude') + t.deepEqual(excludeMsg.excludes, [bobRoot.id]) + t.deepEqual(excludeMsg.recps, [groupId]) + // TODO: check members tangle const reinviteMsg = firstContents[2] @@ -137,6 +140,7 @@ test('add and remove a person, post on the new feed', async (t) => { t.equal(secondInit.type, 'group/init') t.equal(secondInit.version, 'v2') t.equal(secondInit.groupKey, writeKey2.key.toString('base64')) + t.deepEqual(secondInit.tangles.members, { root: null, previous: null }) // TODO: test epoch tangle const post = secondContents[1] From 6a47f71b6de53218c8a72f1cdfae41f8335400ab Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Fri, 17 Mar 2023 12:33:52 +0100 Subject: [PATCH 20/37] Test member tangle for exclude --- lib/get-tangle.js | 9 +++++++-- test/exclude-members.test.js | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/get-tangle.js b/lib/get-tangle.js index 3e17792..1f0aa0f 100644 --- a/lib/get-tangle.js +++ b/lib/get-tangle.js @@ -6,12 +6,17 @@ const pull = require('pull-stream') const Reduce = require('@tangle/reduce') const Strategy = require('@tangle/strategy') const clarify = require('clarify-error') -const { isIdentityGroupSSBURI } = require('ssb-uri2') +const { isIdentityGroupSSBURI, fromMessageSigil } = require('ssb-uri2') // for figuring out what "previous" should be for the group const strategy = new Strategy({}) +function toUri(link) { + if (typeof link !== 'string') return link + return link.startsWith('%') ? fromMessageSigil(link) : link +} + /** `server` is the ssb server you're using. `tangle` is the name of the tangle in the group you're looking for, e.g. "group" or "members" */ module.exports = function GetTangle(server, tangle) { const getUpdates = GetUpdates(server, tangle) @@ -35,7 +40,7 @@ module.exports = function GetTangle(server, tangle) { if (err) return cb(clarify(err, 'Failed to read updates when getting tangle')) const nodes = msgs.map((msg) => ({ - key: msg.key, + key: toUri(msg.key), previous: msg.value.content.tangles[tangle].previous, })) // NOTE: getUpdates query does not get root node diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index 7133e2e..562f3ad 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -6,6 +6,7 @@ const test = require('tape') const { promisify: p } = require('util') const ssbKeys = require('ssb-keys') const { where, author, toPromise } = require('ssb-db2/operators') +const { fromMessageSigil } = require('ssb-uri2') const Testbot = require('./helpers/testbot') const replicate = require('./helpers/replicate') const countGroupFeeds = require('./helpers/count-group-feeds') @@ -42,6 +43,7 @@ test('add and remove a person, post on the new feed', async (t) => { const { id: groupId, + root, writeKey: writeKey1, subfeed: { id: firstFeedId }, } = await alice.tribes2.create().catch((err) => { @@ -50,10 +52,12 @@ test('add and remove a person, post on the new feed', async (t) => { }) t.pass('alice created a group') - await alice.tribes2.addMembers(groupId, [bobRoot.id]).catch((err) => { - console.error('add member fail', err) - t.fail(err) - }) + const addBobMsg = await alice.tribes2 + .addMembers(groupId, [bobRoot.id]) + .catch((err) => { + console.error('add member fail', err) + t.fail(err) + }) t.pass('alice added bob to the group') t.equals( @@ -118,7 +122,10 @@ test('add and remove a person, post on the new feed', async (t) => { t.equal(excludeMsg.type, 'group/exclude') t.deepEqual(excludeMsg.excludes, [bobRoot.id]) t.deepEqual(excludeMsg.recps, [groupId]) - // TODO: check members tangle + t.deepEqual(excludeMsg.tangles.members, { + root, + previous: [fromMessageSigil(addBobMsg.key)], + }) const reinviteMsg = firstContents[2] From 3dfe59f53ae7603b2856210cbc9a243262a55d75 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Fri, 17 Mar 2023 13:37:49 +0100 Subject: [PATCH 21/37] Clarify exclusion errors --- index.js | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 1095d83..a2121d5 100644 --- a/index.js +++ b/index.js @@ -153,7 +153,11 @@ module.exports = { tangles: ['members'], feedKeys: opts.feedKeys ?? additionsFeed.keys, }, - cb + (err, msg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publish add-member message')) + return cb(null, msg) + } ) }) }) @@ -165,15 +169,16 @@ module.exports = { return promisify(excludeMembers)(groupId, feedIds, opts) ssb.metafeeds.findOrCreate(function gotRoot(err, myRoot) { - if (err) return cb(err) + // prettier-ignore + if (err) return cb(clarify(err, "Couldn't get own root when excluding members")) get(groupId, (err, { writeKey: oldWriteKey }) => { // prettier-ignore - if (err) return cb(err) + if (err) return cb(clarify(err, "Couldn't get old key when excluding members")) findOrCreateGroupFeed(oldWriteKey.key, (err, oldGroupFeed) => { // prettier-ignore - if (err) return cb(err) + if (err) return cb(clarify(err, "Couldn't get the old group feed when excluding members")) const excludeContent = { type: 'group/exclude', @@ -191,7 +196,8 @@ module.exports = { pull( listMembers(groupId), pull.collect((err, beforeMembers) => { - if (err) return cb(err) + // prettier-ignore + if (err) return cb(clarify(err, "Couldn't get old member list when excluding members")) const remainingMembers = beforeMembers.filter( (member) => !feedIds.includes(member) @@ -200,14 +206,16 @@ module.exports = { const addInfo = { key: newGroupKey.toBuffer() } ssb.box2.addGroupInfo(groupId, addInfo, (err) => { - if (err) return cb(err) + // prettier-ignore + if (err) return cb(clarify(err, "Couldn't store new key when excluding members")) const newKey = { key: newGroupKey.toBuffer(), scheme: keySchemes.private_group, } ssb.box2.pickGroupWriteKey(groupId, newKey, (err) => { - if (err) return cb(err) + // prettier-ignore + if (err) return cb(clarify(err, "Couldn't switch to new key for writing when excluding members")) const newEpochContent = { type: 'group/init', @@ -223,7 +231,8 @@ module.exports = { spec: () => true, } publish(newEpochContent, newTangleOpts, (err) => { - if (err) return cb(err) + // prettier-ignore + if (err) return cb(clarify(err, "Couldn't post init msg on new epoch when excluding members")) const reAddOpts = { // the re-adding needs to be published on the old @@ -236,7 +245,8 @@ module.exports = { remainingMembers, reAddOpts, (err) => { - if (err) return cb(err) + // prettier-ignore + if (err) return cb(clarify(err, "Couldn't re-add remaining members when excluding members")) return cb() } ) @@ -279,7 +289,16 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create group feed when publishing to a group')) - publishAndPrune(ssb, content, opts?.feedKeys ?? groupFeed.keys, cb) + publishAndPrune( + ssb, + content, + opts?.feedKeys ?? groupFeed.keys, + (err, msg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publishAndPrune when publishing a group message')) + return cb(null, msg) + } + ) }) }) }) From 641ee1eb43e4d2238b9d0153c753205c11647c20 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Fri, 17 Mar 2023 14:46:27 +0100 Subject: [PATCH 22/37] Document excludeMembers --- README.md | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index abdb0a9..9a2aa76 100644 --- a/README.md +++ b/README.md @@ -107,16 +107,23 @@ NOTE: If `create` finds an empty (i.e. seemingly unused) group feed, it will sta - `opts` _Object_ - currently empty, but will be used in the future to specify details like whether the group has an admin subgroup, etc. - `cb` _Function_ - callback function of signature `(err, group)` where `group` is an object containing: - - `id` _CloakedId_ - a cipherlink that's safe to use publicly to name the group, and is used in `recps` to trigger encrypting messages to that group, encoded as an ssb-uri + + - `id` _GroupUri_ - a cipherlink that's safe to use publicly to name the group, and is used in `recps` to trigger encrypting messages to that group, encoded as an ssb-uri - `subfeed` _Keys_ - the keys of the subfeed you should publish group data to - - `secret` _Buffer_ - the symmetric key used by the group for encryption + - `writeKey` _GroupKey_ - the current key used for publishing new messages to the group. It is one of the `readKeys`. + - `readKeys` _[GroupKey]_ - an array of all keys used to read messages for this group. - `root` _MessagedId_ - the MessageId of the `group/init` message of the group, encoded as an ssb-uri. + where _GroupKey_ is an object on the format + + - `key` _Buffer_ - the symmetric key used by the group for encryption + - `scheme` _String_ - the scheme for this key + ### `ssb.tribes2.get(groupId, cb)` Gets information about a specific group. -- `groupId` _CloakedId_ - the public-safe cipherlink which identifies the group +- `groupId` _GroupUri_ - the public-safe cipherlink which identifies the group - `cb` _Function_ - callback function of signature `(err, group)` where `group` is an object on the same format as the `group` object returned by #create ### `ssb.tribes2.list({ live }) => source` @@ -124,19 +131,34 @@ Gets information about a specific group. Creates a pull-stream source which emits `group` data of each private group you're a part of. If `live` is true then it also outputs all new groups you join. (Same format as `group` object returned by #create) -### `ssb.tribes2.addMembers(groupId, feedIds, cb)` +### `ssb.tribes2.addMembers(groupId, feedIds, opts, cb)` -Publish `group/add-member` messages to a group of peers, which gives them all the details they need -to join the group. +Publish `group/add-member` messages to a group of peers, which gives them all the details they need to join the group. Newly added members will need to accept the invite using `acceptInvite()` before they start replicating the group. -- `groupId` _CloakedId_ - the public-safe cipherlink which identifies the group (same as in #create) -- `feedIds` _[FeedId]_ - an Array of 1-16 different ids for peers (accepts ssb-uri or sigil feed ids) +- `groupId` _GroupUri_ - the public-safe cipherlink which identifies the group (same as in #create) +- `feedIds` _[FeedId]_ - an Array of 1-15 different ids for peers (accepts ssb-uri or sigil feed ids) +- `opts` _Object_ - with the options: + - `text` _String_ - A piece of text attached to the addition. Visible to the whole group and the newly added people. + - `feedKeys` _Keys_ - By default the addition is published to the feed with the purpose "group/additions" but using this option you can provide keys for another feed to publish on. Note that this doesn't affect the encryption used. - `cb` _Function_ - a callback of signature `(err, msg)` -### `ssb.tribes2.publish(content, cb)` +### `ssb.tribes2.excludeMembers(groupId, feedIds, opts, cb) + +Excludes some current members of the group, by creating a new key and group feed and reinviting everyone to that key except for the excluded members. + +- `groupId` _GroupUri_ - the public-safe cipherlink which identifies the group (same as in #create) +- `feedIds` _[FeedId]_ - an Array of 1-15 different ids for peers (accepts ssb-uri or sigil feed ids) +- `opts` _Object_ - placeholder for future options. +- `cb` _Function_ - a callback of signature `(err)` + +### `ssb.tribes2.publish(content, opts, cb)` Publishes any kind of message encrypted to the group. The function wraps `ssb.db.create()` but handles adding tangles and using the correct encryption for the `content.recps` that you've provided. Mutates `content`. +- `opts` _Object_ - with the options: + - `spec` _Function_ - the `is-my-ssb-valid`/`is-my-json-valid`-based validator that you want to check this message against before publishing. By default uses the `content` validator from `private-group-spec`. + - `tangles` _[String]_ - by default `publish` always adds the `group` tangle to messages, but using this option you can ask it to add additional tangles. Currently only supports a few tangles that are core to groups. + - `feedKeys` _Keys_ - By default the message is published to the currently used group feed (current epoch) but using this option you can provide keys for another feed to publish on. Note that this doesn't affect the encryption used. - `cb` _Function_ - a callback of signature `(err, msg)` ### `ssb.tribes2.listMembers(groupId, { live }) => source` From 591ebbf1bbbf91afe95aa1845dd9f83cc40fabe0 Mon Sep 17 00:00:00 2001 From: mix irving Date: Mon, 20 Mar 2023 21:37:31 +1300 Subject: [PATCH 23/37] remove un-necessary lookups of feeds for create + exclude members --- index.js | 64 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/index.js b/index.js index a2121d5..005bb11 100644 --- a/index.js +++ b/index.js @@ -142,23 +142,33 @@ module.exports = { if (opts.text) content.text = opts.text + if (opts.feedKeys) { + const options = { + spec: isAddMember, + tangles: ['members'], + feedKeys: opts.feedKeys, + } + publish(content, options, (err, msg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publish add-member message')) + return cb(null, msg) + }) + return + } + findOrCreateAdditionsFeed((err, additionsFeed) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create additions feed when adding members')) - - publish( - content, - { - spec: isAddMember, - tangles: ['members'], - feedKeys: opts.feedKeys ?? additionsFeed.keys, - }, - (err, msg) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to publish add-member message')) - return cb(null, msg) - } - ) + const options = { + spec: isAddMember, + tangles: ['members'], + feedKeys: additionsFeed.keys, + } + publish(content, options, (err, msg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publish add-member message')) + return cb(null, msg) + }) }) }) }) @@ -261,7 +271,7 @@ module.exports = { }) } - function publish(content, opts, cb) { + function publish(content, opts = {}, cb) { if (cb === undefined) return promisify(publish)(content, opts) if (!content) return cb(new Error('Missing content')) @@ -285,20 +295,24 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, 'Failed to get group details when publishing to a group')) + if (opts.feedKeys) { + publishAndPrune(ssb, content, opts.feedKeys, (err, msg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publishAndPrune when publishing a group message')) + return cb(null, msg) + }) + return + } + findOrCreateGroupFeed(writeKey.key, (err, groupFeed) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create group feed when publishing to a group')) - publishAndPrune( - ssb, - content, - opts?.feedKeys ?? groupFeed.keys, - (err, msg) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to publishAndPrune when publishing a group message')) - return cb(null, msg) - } - ) + publishAndPrune(ssb, content, groupFeed.keys, (err, msg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publishAndPrune when publishing a group message')) + return cb(null, msg) + }) }) }) }) From 74baa4acdfd7da77ffe1c51d021bcd631d13b1fb Mon Sep 17 00:00:00 2001 From: mix irving Date: Mon, 20 Mar 2023 22:00:41 +1300 Subject: [PATCH 24/37] fix crash resistence for create --- index.js | 4 ++-- lib/meta-feed-helpers.js | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 005bb11..2d5d879 100644 --- a/index.js +++ b/index.js @@ -44,14 +44,14 @@ module.exports = { secretKeyFromString, findOrCreateAdditionsFeed, findOrCreateGroupFeed, - findOrCreateEpochWithoutMembers, + findOrCreateGroupWithoutMembers, getRootFeedIdFromMsgId, } = MetaFeedHelpers(ssb) function create(opts = {}, cb) { if (cb === undefined) return promisify(create)(opts) - findOrCreateEpochWithoutMembers((err, group) => { + findOrCreateGroupWithoutMembers((err, group) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to create group init message when creating a group')) diff --git a/lib/meta-feed-helpers.js b/lib/meta-feed-helpers.js index 657ef0b..e69bcc3 100644 --- a/lib/meta-feed-helpers.js +++ b/lib/meta-feed-helpers.js @@ -171,19 +171,24 @@ module.exports = (ssb) => { /** more specifically: a group that has never had any members. i.e. either * 1. newly created but tribes2 crashed before we had time to add ourselves to it. in that case find and return it. if we can't find such a group then * 2. freshly create a new group, and return */ - function findOrCreateEpochWithoutMembers(cb) { + function findOrCreateGroupWithoutMembers(cb) { ssb.metafeeds.findOrCreate(function gotRoot(err, myRoot) { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create root feed when creating a group')) - // find groups without any group/add-member messages + // crash resistence: look for groups without any group/add-member messages let foundMemberlessGroup = false pull( ssb.db.query( where(and(isDecrypted('box2'), type('group/init'))), toPullStream() ), + // find only init for epoch zero + pull.filter(rootMsg => rootMsg.value.content?.tangles?.group?.root === null), + + // see if there are and members added pull.asyncMap((rootMsg, cb) => { + // return rootMsg if empty group, otherwise return false let foundMember = false pull( ssb.db.query( @@ -208,6 +213,7 @@ module.exports = (ssb) => { ) }), pull.filter(Boolean), + pull.take(1), pull.drain( (rootMsg) => { @@ -230,9 +236,9 @@ module.exports = (ssb) => { (err) => { // prettier-ignore if (err) return cb(clarify(err, "errored trying to find potential memberless feed")) - else if (!foundMemberlessGroup) { - return createGroupWithoutMembers(myRoot, cb) - } + else if (!foundMemberlessGroup) { + return createGroupWithoutMembers(myRoot, cb) + } } ) ) @@ -259,8 +265,8 @@ module.exports = (ssb) => { findEmptyGroupFeed, findOrCreateFromSecret, findOrCreateGroupFeed, + findOrCreateGroupWithoutMembers, createGroupWithoutMembers, - findOrCreateEpochWithoutMembers, getRootFeedIdFromMsgId, } } From b8ee79982032118210efff62523890a08732ddfd Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Mon, 20 Mar 2023 10:25:05 +0100 Subject: [PATCH 25/37] Fix message sigils in tests --- index.js | 4 ++-- test/get-tangle.test.js | 35 ++++++++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 2d5d879..4dbba30 100644 --- a/index.js +++ b/index.js @@ -271,7 +271,7 @@ module.exports = { }) } - function publish(content, opts = {}, cb) { + function publish(content, opts, cb) { if (cb === undefined) return promisify(publish)(content, opts) if (!content) return cb(new Error('Missing content')) @@ -295,7 +295,7 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, 'Failed to get group details when publishing to a group')) - if (opts.feedKeys) { + if (opts?.feedKeys) { publishAndPrune(ssb, content, opts.feedKeys, (err, msg) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to publishAndPrune when publishing a group message')) diff --git a/test/get-tangle.test.js b/test/get-tangle.test.js index cba0a6b..3ea97e0 100644 --- a/test/get-tangle.test.js +++ b/test/get-tangle.test.js @@ -13,6 +13,7 @@ const { toPullStream, where, } = require('ssb-db2/operators') +const { fromMessageSigil } = require('ssb-uri2') const GetTangle = require('../lib/get-tangle') const Testbot = require('./helpers/testbot') @@ -44,7 +45,7 @@ test('get-tangle unit test', (t) => { descending(), toPullStream() ), - pull.map((m) => m.key), + pull.map((m) => fromMessageSigil(m.key)), pull.take(1), pull.collect((err, keys) => { t.error(err, 'no error') @@ -70,7 +71,7 @@ test('get-tangle unit test', (t) => { t.error(err, 'no error') t.deepEqual( { root, previous }, - { root: rootKey, previous: [msg.key] }, + { root: rootKey, previous: [fromMessageSigil(msg.key)] }, 'adding message to root' ) @@ -81,7 +82,10 @@ test('get-tangle unit test', (t) => { t.error(err, 'no error') t.deepEqual( { root, previous }, - { root: rootKey, previous: [msg.key] }, + { + root: rootKey, + previous: [fromMessageSigil(msg.key)], + }, 'adding message to tip' ) server.close(true, t.end) @@ -162,7 +166,10 @@ test('get-tangle', (t) => { t.deepEqual( A.content.tangles.group, // actual // last message is the admin adding themselves to the group they just created i.e. not the root msg - { root: groupRoot, previous: [lastMsgAfterCreate.kvt.key] }, // expected + { + root: groupRoot, + previous: [fromMessageSigil(lastMsgAfterCreate.kvt.key)], + }, // expected 'auto adds group tangle (auto added tangles.group)' ) @@ -209,7 +216,11 @@ test('get-tangle with branch', async (t) => { const bobTangle = await p(getBobGroupTangle)(group.id).catch(t.fail) t.deepEqual(aliceTangle, bobTangle, 'tangles should match') t.deepEqual(aliceTangle.root, group.root, 'the root is the groupId') - t.deepEqual(aliceTangle.previous, [invite.key], 'previous is the invite key') + t.deepEqual( + aliceTangle.previous, + [fromMessageSigil(invite.key)], + 'previous is the invite key' + ) // Alice and Bob will both publish a message const content = () => ({ @@ -275,8 +286,14 @@ test('members tangle works', async (t) => { const groupTangle = await p(getGroup)(group.id) const membersTangle = await p(getMembers)(group.id) - const expectedGroupTangle = { root: group.root, previous: [bobPost.key] } - const expectedMembersTangle = { root: group.root, previous: [bobInvite.key] } + const expectedGroupTangle = { + root: group.root, + previous: [fromMessageSigil(bobPost.key)], + } + const expectedMembersTangle = { + root: group.root, + previous: [fromMessageSigil(bobInvite.key)], + } t.deepEquals(groupTangle, expectedGroupTangle, 'group tangle is correct') t.deepEquals( membersTangle, @@ -314,7 +331,7 @@ test('members tangle works', async (t) => { newGroupTangle, { root: group.root, - previous: [carolInviteEnc.key], + previous: [fromMessageSigil(carolInviteEnc.key)], }, 'got correct updated group tangle' ) @@ -322,7 +339,7 @@ test('members tangle works', async (t) => { newMembersTangle, { root: group.root, - previous: [carolInviteEnc.key], + previous: [fromMessageSigil(carolInviteEnc.key)], }, 'got correct updated members tangle' ) From b725ee52686b46ec2df25d80fd93d57ed1d62d35 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Mon, 20 Mar 2023 16:12:16 +0100 Subject: [PATCH 26/37] Deduplicate publish feedKeys code --- index.js | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/index.js b/index.js index 4dbba30..7f81b94 100644 --- a/index.js +++ b/index.js @@ -142,27 +142,18 @@ module.exports = { if (opts.text) content.text = opts.text - if (opts.feedKeys) { - const options = { - spec: isAddMember, - tangles: ['members'], - feedKeys: opts.feedKeys, - } - publish(content, options, (err, msg) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to publish add-member message')) - return cb(null, msg) - }) - return - } + const getFeed = opts?.feedKeys + ? (cb) => cb() + : findOrCreateAdditionsFeed - findOrCreateAdditionsFeed((err, additionsFeed) => { + getFeed((err, additionsFeed) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create additions feed when adding members')) + const options = { spec: isAddMember, tangles: ['members'], - feedKeys: additionsFeed.keys, + feedKeys: opts?.feedKeys ?? additionsFeed.keys, } publish(content, options, (err, msg) => { // prettier-ignore @@ -295,24 +286,24 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, 'Failed to get group details when publishing to a group')) - if (opts?.feedKeys) { - publishAndPrune(ssb, content, opts.feedKeys, (err, msg) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to publishAndPrune when publishing a group message')) - return cb(null, msg) - }) - return - } + const getFeed = opts?.feedKeys + ? (_, cb) => cb() + : findOrCreateGroupFeed - findOrCreateGroupFeed(writeKey.key, (err, groupFeed) => { + getFeed(writeKey.key, (err, groupFeed) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create group feed when publishing to a group')) - publishAndPrune(ssb, content, groupFeed.keys, (err, msg) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to publishAndPrune when publishing a group message')) - return cb(null, msg) - }) + publishAndPrune( + ssb, + content, + opts?.feedKeys ?? groupFeed.keys, + (err, msg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publishAndPrune when publishing a group message')) + return cb(null, msg) + } + ) }) }) }) From 34daa39292c243115e82935dc404649ab3ff4f16 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Tue, 21 Mar 2023 19:13:22 +0100 Subject: [PATCH 27/37] Remove incorrect 'pass' assertions --- test/exclude-members.test.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index 562f3ad..fdc6319 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -46,19 +46,13 @@ test('add and remove a person, post on the new feed', async (t) => { root, writeKey: writeKey1, subfeed: { id: firstFeedId }, - } = await alice.tribes2.create().catch((err) => { - console.error('alice failed to create group', err) - t.fail(err) - }) - t.pass('alice created a group') + } = await alice.tribes2 + .create() + .catch((err) => t.error(err, 'alice failed to create group')) const addBobMsg = await alice.tribes2 .addMembers(groupId, [bobRoot.id]) - .catch((err) => { - console.error('add member fail', err) - t.fail(err) - }) - t.pass('alice added bob to the group') + .catch((err) => t.error(err, 'add member fail')) t.equals( await p(countGroupFeeds)(alice), @@ -67,8 +61,7 @@ test('add and remove a person, post on the new feed', async (t) => { ) await alice.tribes2.excludeMembers(groupId, [bobRoot.id]).catch((err) => { - console.error('remove member fail', err) - t.fail(err) + t.error(err, 'remove member fail') }) t.equals( From af04b2e892d2a2e9cdcebfba1bbab31f820ac9eb Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Tue, 21 Mar 2023 19:15:44 +0100 Subject: [PATCH 28/37] Remove brackets from arrow fn --- test/exclude-members.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index fdc6319..482851d 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -60,9 +60,9 @@ test('add and remove a person, post on the new feed', async (t) => { 'before exclude alice has 1 group feed' ) - await alice.tribes2.excludeMembers(groupId, [bobRoot.id]).catch((err) => { - t.error(err, 'remove member fail') - }) + await alice.tribes2 + .excludeMembers(groupId, [bobRoot.id]) + .catch((err) => t.error(err, 'remove member fail')) t.equals( await p(countGroupFeeds)(alice), From 1e7026efe3cd9db87f01c496ec56783a71623645 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Tue, 21 Mar 2023 19:17:49 +0100 Subject: [PATCH 29/37] Test epoch tangle --- test/exclude-members.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index 482851d..224483f 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -141,7 +141,11 @@ test('add and remove a person, post on the new feed', async (t) => { t.equal(secondInit.version, 'v2') t.equal(secondInit.groupKey, writeKey2.key.toString('base64')) t.deepEqual(secondInit.tangles.members, { root: null, previous: null }) - // TODO: test epoch tangle + t.deepEqual( + secondInit.tangles.epoch, + { root, previous: [root] }, + 'epoch tangle is correct on new epoch init' + ) const post = secondContents[1] From 215ae03b9758b71c3c055cbb7641dde8b9d8d005 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Tue, 21 Mar 2023 19:27:10 +0100 Subject: [PATCH 30/37] spec->isValid --- README.md | 2 +- index.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9a2aa76..ee505f0 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ Excludes some current members of the group, by creating a new key and group feed Publishes any kind of message encrypted to the group. The function wraps `ssb.db.create()` but handles adding tangles and using the correct encryption for the `content.recps` that you've provided. Mutates `content`. - `opts` _Object_ - with the options: - - `spec` _Function_ - the `is-my-ssb-valid`/`is-my-json-valid`-based validator that you want to check this message against before publishing. By default uses the `content` validator from `private-group-spec`. + - `isValid` _Function_ - a validator (typically `is-my-ssb-valid`/`is-my-json-valid`-based) that you want to check this message against before publishing. Have the function return false if the message is invalid and the message won't be published. By default uses the `content` validator from `private-group-spec`. - `tangles` _[String]_ - by default `publish` always adds the `group` tangle to messages, but using this option you can ask it to add additional tangles. Currently only supports a few tangles that are core to groups. - `feedKeys` _Keys_ - By default the message is published to the currently used group feed (current epoch) but using this option you can provide keys for another feed to publish on. Note that this doesn't affect the encryption used. - `cb` _Function_ - a callback of signature `(err, msg)` diff --git a/index.js b/index.js index 7f81b94..8c432db 100644 --- a/index.js +++ b/index.js @@ -151,7 +151,7 @@ module.exports = { if (err) return cb(clarify(err, 'Failed to find or create additions feed when adding members')) const options = { - spec: isAddMember, + isValid: isAddMember, tangles: ['members'], feedKeys: opts?.feedKeys ?? additionsFeed.keys, } @@ -188,7 +188,7 @@ module.exports = { } const excludeOpts = { tangles: ['members'], - spec: () => true, + isValid: () => true, } publish(excludeContent, excludeOpts, (err) => { // prettier-ignore @@ -229,7 +229,7 @@ module.exports = { } const newTangleOpts = { tangles: ['epoch'], - spec: () => true, + isValid: () => true, } publish(newEpochContent, newTangleOpts, (err) => { // prettier-ignore @@ -267,7 +267,7 @@ module.exports = { if (!content) return cb(new Error('Missing content')) - const isSpec = opts?.spec ?? isContent + const isValid = opts?.isValid ?? isContent const tangles = ['group', ...(opts?.tangles ?? [])] const recps = content.recps @@ -280,7 +280,7 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, 'Failed to add group tangle when publishing to a group')) - if (!isSpec(content)) return cb(new Error(isSpec.errorsString)) + if (!isValid(content)) return cb(new Error(isValid.errorsString)) get(groupId, (err, { writeKey }) => { // prettier-ignore From b1389791187cddbfd294f7566ddffc64becc7a35 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Tue, 21 Mar 2023 19:40:37 +0100 Subject: [PATCH 31/37] Add default error on failed validation --- index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 8c432db..5176a2e 100644 --- a/index.js +++ b/index.js @@ -280,7 +280,10 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, 'Failed to add group tangle when publishing to a group')) - if (!isValid(content)) return cb(new Error(isValid.errorsString)) + if (!isValid(content)) + return cb( + new Error(isValid.errorsString ?? 'content failed validation') + ) get(groupId, (err, { writeKey }) => { // prettier-ignore From 96a195b2578368d613969db8a2e8a82a0655fa54 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Tue, 21 Mar 2023 19:47:40 +0100 Subject: [PATCH 32/37] Clean up feedKeys opts logic --- index.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 5176a2e..b6b99eb 100644 --- a/index.js +++ b/index.js @@ -143,7 +143,7 @@ module.exports = { if (opts.text) content.text = opts.text const getFeed = opts?.feedKeys - ? (cb) => cb() + ? (cb) => cb(null, { keys: opts.feedKeys }) : findOrCreateAdditionsFeed getFeed((err, additionsFeed) => { @@ -153,7 +153,7 @@ module.exports = { const options = { isValid: isAddMember, tangles: ['members'], - feedKeys: opts?.feedKeys ?? additionsFeed.keys, + feedKeys: additionsFeed.keys, } publish(content, options, (err, msg) => { // prettier-ignore @@ -290,23 +290,18 @@ module.exports = { if (err) return cb(clarify(err, 'Failed to get group details when publishing to a group')) const getFeed = opts?.feedKeys - ? (_, cb) => cb() + ? (_, cb) => cb(null, { keys: opts.feedKeys }) : findOrCreateGroupFeed getFeed(writeKey.key, (err, groupFeed) => { // prettier-ignore if (err) return cb(clarify(err, 'Failed to find or create group feed when publishing to a group')) - publishAndPrune( - ssb, - content, - opts?.feedKeys ?? groupFeed.keys, - (err, msg) => { - // prettier-ignore - if (err) return cb(clarify(err, 'Failed to publishAndPrune when publishing a group message')) - return cb(null, msg) - } - ) + publishAndPrune(ssb, content, groupFeed.keys, (err, msg) => { + // prettier-ignore + if (err) return cb(clarify(err, 'Failed to publishAndPrune when publishing a group message')) + return cb(null, msg) + }) }) }) }) From 138da88656e137a6fcb5864709e55be5f5c2dd6c Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Tue, 21 Mar 2023 19:49:54 +0100 Subject: [PATCH 33/37] Default value on destructure Co-authored-by: mix irving --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index b6b99eb..b74824e 100644 --- a/index.js +++ b/index.js @@ -173,7 +173,7 @@ module.exports = { // prettier-ignore if (err) return cb(clarify(err, "Couldn't get own root when excluding members")) - get(groupId, (err, { writeKey: oldWriteKey }) => { + get(groupId, (err, { writeKey: oldWriteKey } = {}) => { // prettier-ignore if (err) return cb(clarify(err, "Couldn't get old key when excluding members")) From ef40b289226e408b7a59df4584739d8e22bfe116 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Wed, 22 Mar 2023 13:06:19 +0100 Subject: [PATCH 34/37] Remove cipherlink term MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Staltz --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee505f0..0def017 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ NOTE: If `create` finds an empty (i.e. seemingly unused) group feed, it will sta - `opts` _Object_ - currently empty, but will be used in the future to specify details like whether the group has an admin subgroup, etc. - `cb` _Function_ - callback function of signature `(err, group)` where `group` is an object containing: - - `id` _GroupUri_ - a cipherlink that's safe to use publicly to name the group, and is used in `recps` to trigger encrypting messages to that group, encoded as an ssb-uri + - `id` _GroupUri_ - an SSB URI that's safe to use publicly to name the group, and is used in `recps` to trigger encrypting messages to that group - `subfeed` _Keys_ - the keys of the subfeed you should publish group data to - `writeKey` _GroupKey_ - the current key used for publishing new messages to the group. It is one of the `readKeys`. - `readKeys` _[GroupKey]_ - an array of all keys used to read messages for this group. From d69ec72f6d5a6bcc984414c4c52f1867e55ad53e Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Wed, 22 Mar 2023 13:07:30 +0100 Subject: [PATCH 35/37] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Staltz --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0def017..139330f 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ NOTE: If `create` finds an empty (i.e. seemingly unused) group feed, it will sta - `readKeys` _[GroupKey]_ - an array of all keys used to read messages for this group. - `root` _MessagedId_ - the MessageId of the `group/init` message of the group, encoded as an ssb-uri. - where _GroupKey_ is an object on the format + where _GroupKey_ is an object of the format - `key` _Buffer_ - the symmetric key used by the group for encryption - `scheme` _String_ - the scheme for this key @@ -123,7 +123,7 @@ NOTE: If `create` finds an empty (i.e. seemingly unused) group feed, it will sta Gets information about a specific group. -- `groupId` _GroupUri_ - the public-safe cipherlink which identifies the group +- `groupId` _GroupUri_ - the public-safe SSB URI which identifies the group - `cb` _Function_ - callback function of signature `(err, group)` where `group` is an object on the same format as the `group` object returned by #create ### `ssb.tribes2.list({ live }) => source` @@ -135,7 +135,7 @@ Creates a pull-stream source which emits `group` data of each private group you' Publish `group/add-member` messages to a group of peers, which gives them all the details they need to join the group. Newly added members will need to accept the invite using `acceptInvite()` before they start replicating the group. -- `groupId` _GroupUri_ - the public-safe cipherlink which identifies the group (same as in #create) +- `groupId` _GroupUri_ - the public-safe SSB URI which identifies the group (same as in #create) - `feedIds` _[FeedId]_ - an Array of 1-15 different ids for peers (accepts ssb-uri or sigil feed ids) - `opts` _Object_ - with the options: - `text` _String_ - A piece of text attached to the addition. Visible to the whole group and the newly added people. @@ -146,7 +146,7 @@ Publish `group/add-member` messages to a group of peers, which gives them all th Excludes some current members of the group, by creating a new key and group feed and reinviting everyone to that key except for the excluded members. -- `groupId` _GroupUri_ - the public-safe cipherlink which identifies the group (same as in #create) +- `groupId` _GroupUri_ - the public-safe SSB URI which identifies the group (same as in #create) - `feedIds` _[FeedId]_ - an Array of 1-15 different ids for peers (accepts ssb-uri or sigil feed ids) - `opts` _Object_ - placeholder for future options. - `cb` _Function_ - a callback of signature `(err)` From 7fb846f15b22a4f5de1971e96da7035dbe0481d6 Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Wed, 22 Mar 2023 13:08:20 +0100 Subject: [PATCH 36/37] Update comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Staltz --- test/exclude-members.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/exclude-members.test.js b/test/exclude-members.test.js index 224483f..2c8f5c6 100644 --- a/test/exclude-members.test.js +++ b/test/exclude-members.test.js @@ -12,7 +12,7 @@ const replicate = require('./helpers/replicate') const countGroupFeeds = require('./helpers/count-group-feeds') test('add and remove a person, post on the new feed', async (t) => { - // feeds should look like + // Alice's feeds should look like // first: initGroup->excludeBob->reAddAlice // second: initEpoch->post // additions: addAlice->addBob (not checking this here) From c21c8e2273fd11422df1f73168d735816e755fbb Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Wed, 22 Mar 2023 13:12:52 +0100 Subject: [PATCH 37/37] Make addMembers feedKeys opt private --- README.md | 1 - index.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 139330f..6fcc682 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,6 @@ Publish `group/add-member` messages to a group of peers, which gives them all th - `feedIds` _[FeedId]_ - an Array of 1-15 different ids for peers (accepts ssb-uri or sigil feed ids) - `opts` _Object_ - with the options: - `text` _String_ - A piece of text attached to the addition. Visible to the whole group and the newly added people. - - `feedKeys` _Keys_ - By default the addition is published to the feed with the purpose "group/additions" but using this option you can provide keys for another feed to publish on. Note that this doesn't affect the encryption used. - `cb` _Function_ - a callback of signature `(err, msg)` ### `ssb.tribes2.excludeMembers(groupId, feedIds, opts, cb) diff --git a/index.js b/index.js index b74824e..4bb5575 100644 --- a/index.js +++ b/index.js @@ -142,8 +142,8 @@ module.exports = { if (opts.text) content.text = opts.text - const getFeed = opts?.feedKeys - ? (cb) => cb(null, { keys: opts.feedKeys }) + const getFeed = opts?._feedKeys + ? (cb) => cb(null, { keys: opts._feedKeys }) : findOrCreateAdditionsFeed getFeed((err, additionsFeed) => { @@ -239,7 +239,7 @@ module.exports = { // the re-adding needs to be published on the old // feed so that the additions feed is not spammed, // while people need to still be able to find it - feedKeys: oldGroupFeed.keys, + _feedKeys: oldGroupFeed.keys, } addMembers( groupId,