@river-build/sdk
Version:
For more details, visit the following resources:
174 lines • 9.05 kB
JavaScript
/**
* @group main
*/
import { dlog } from '@river-build/dlog';
import { isDefined } from '../../check';
import { makeTestClient, makeUniqueSpaceStreamId, waitFor } from '../testUtils';
import { makeUniqueChannelStreamId } from '../../id';
import { SyncState } from '../../syncedStreamsLoop';
const log = dlog('csb:test:decryptionExtensions');
describe('ClientDecryptionExtensions', () => {
let clients = [];
const makeAndStartClient = async (opts) => {
const client = await makeTestClient(opts);
await client.initializeUser();
client.startSync();
await waitFor(() => expect(client.streams.syncState).toBe(SyncState.Syncing));
log('started client', client.userId, client.signerContext);
clients.push(client);
return client;
};
const sendMessage = async (client, streamId, body) => {
await client.waitForStream(streamId);
await client.sendMessage(streamId, body);
};
const getDecryptedChannelMessages = (stream) => {
return stream.view.timeline
.map((e) => {
// for tests, return decrypted content
if (e.decryptedContent?.kind === 'channelMessage' &&
e.decryptedContent.content.payload?.case === 'post' &&
e.decryptedContent.content.payload?.value?.content?.case === 'text') {
return e.decryptedContent.content.payload?.value?.content?.value?.body;
}
// and local content
if (e.localEvent?.channelMessage?.payload?.case === 'post' &&
e.localEvent?.channelMessage?.payload?.value?.content?.case === 'text') {
return e.localEvent?.channelMessage?.payload?.value?.content?.value?.body;
}
return undefined;
})
.filter(isDefined);
};
const waitForMessages = async (client, streamId, bodys) => {
log('waitForMessages', client.userId, streamId, bodys);
const stream = await client.waitForStream(streamId);
return waitFor(() => {
const messages = getDecryptedChannelMessages(stream);
expect(messages).toEqual(bodys);
}, { timeoutMS: 15000 });
};
const getDecryptionErrors = async (client, streamId) => {
const stream = client.stream(streamId) ?? (await client.waitForStream(streamId));
return stream.view.timeline
.map((e) => {
if (e.decryptedContentError) {
return e.decryptedContentError;
}
return undefined;
})
.filter(isDefined);
};
const waitForDecryptionErrors = async (client, streamId, count) => {
log('waitForDecryptionErrors', client.userId, streamId, count);
return waitFor(async () => {
const errors = await getDecryptionErrors(client, streamId);
expect(errors.length).toEqual(count);
}, { timeoutMS: 10000 });
};
beforeEach(async () => { });
afterEach(async () => {
for (const client of clients) {
await client.stop();
}
clients = [];
});
test('shareKeysWithNewDevices', async () => {
const bob1 = await makeAndStartClient({ deviceId: 'bob1' });
const alice1 = await makeAndStartClient({ deviceId: 'alice1' });
// create a dm and send a message
const { streamId } = await bob1.createDMChannel(alice1.userId);
await sendMessage(bob1, streamId, 'hello');
await expect(alice1.waitForStream(streamId)).resolves.not.toThrow();
// wait for the message to arrive and decrypt
await expect(waitForMessages(alice1, streamId, ['hello'])).resolves.not.toThrow();
// boot up alice on a second device
const alice2 = await makeAndStartClient({
context: alice1.signerContext,
deviceId: 'alice2',
});
// This wait takes over 5s
await expect(alice2.waitForStream(streamId)).resolves.not.toThrow();
// alice gets keys sent via new device message
await expect(waitForMessages(alice2, streamId, ['hello'])).resolves.not.toThrow();
// stop alice2 so she's offline
await alice2.stop();
// send a second message
const bob2 = await makeAndStartClient({
context: bob1.signerContext,
deviceId: 'bob2',
});
await expect(bob2.waitForStream(streamId)).resolves.not.toThrow();
await sendMessage(bob2, streamId, 'whats up');
// the message should get decrypted on alice1
await expect(waitForMessages(bob1, streamId, ['hello', 'whats up'])).resolves.not.toThrow();
await expect(waitForMessages(alice1, streamId, ['hello', 'whats up'])).resolves.not.toThrow();
// start alice2 back up
const alice2_restarted = await makeAndStartClient({
context: alice1.signerContext,
deviceId: 'alice2',
});
await expect(alice2_restarted.waitForStream(streamId)).resolves.not.toThrow();
// she should have the keys because bob2 should share with existing memebers
await expect(waitForMessages(alice2_restarted, streamId, ['hello', 'whats up'])).resolves.not.toThrow();
});
// users aren't online at the same time
test('bobIsntOnlineToShareKeys', async () => {
// have two people come up, go offline, then two more people come up
const bob1 = await makeAndStartClient({ deviceId: 'bob1' });
const spaceId = makeUniqueSpaceStreamId();
await bob1.createSpace(spaceId);
const channelId = makeUniqueChannelStreamId(spaceId);
await bob1.createChannel(spaceId, 'bob1sChannel', '', channelId);
await sendMessage(bob1, channelId, 'its bob');
await bob1.stop();
// alice comes up, joins the space and channel, and sends a message
const alice1 = await makeAndStartClient({ deviceId: 'alice1' });
await alice1.joinStream(spaceId);
await alice1.joinStream(channelId);
await expect(waitForDecryptionErrors(alice1, channelId, 1)).resolves.not.toThrow(); // alice should see a decryption error
await expect(waitForMessages(alice1, channelId, [])).resolves.not.toThrow(); // alice doesn't see the messsage if bob isn't online to send keys
await sendMessage(alice1, channelId, 'its alice');
await expect(waitForMessages(alice1, channelId, ['its alice'])).resolves.not.toThrow(); // alice doesn't see the messsage if bob isn't online to send keys
// bob comes back online, same device
const bob1IsBack = await makeAndStartClient({
context: bob1.signerContext,
deviceId: 'bob1',
});
await expect(waitForMessages(bob1IsBack, channelId, ['its bob', 'its alice'])).resolves.not.toThrow();
// alice should get keys and decrypt bobs message
await expect(waitForMessages(alice1, channelId, ['its bob', 'its alice'])).resolves.not.toThrow();
});
test('shareKeysInMultipleStreamsToSameDevice', async () => {
const bob1 = await makeAndStartClient({ deviceId: 'bob1' });
const alice1 = await makeAndStartClient({ deviceId: 'alice1' });
const spaceId = makeUniqueSpaceStreamId();
await bob1.createSpace(spaceId);
const channel1StreamId = makeUniqueChannelStreamId(spaceId);
const channel2StreamId = makeUniqueChannelStreamId(spaceId);
await bob1.createChannel(spaceId, 'channel1', '', channel1StreamId);
await bob1.createChannel(spaceId, 'channel2', '', channel2StreamId);
await sendMessage(bob1, channel1StreamId, 'hello channel 1');
await sendMessage(bob1, channel2StreamId, 'hello channel 2');
await expect(alice1.joinStream(spaceId)).resolves.not.toThrow();
await expect(alice1.joinStream(channel1StreamId)).resolves.not.toThrow();
await expect(alice1.joinStream(channel2StreamId)).resolves.not.toThrow();
// wait for the message to arrive and decrypt
await expect(waitForMessages(alice1, channel1StreamId, ['hello channel 1'])).resolves.not.toThrow();
await expect(waitForMessages(alice1, channel2StreamId, ['hello channel 2'])).resolves.not.toThrow();
// stop bob to simplify test
await bob1.stop();
// boot up alice on a second device
const alice2 = await makeAndStartClient({
context: alice1.signerContext,
deviceId: 'alice2',
});
// This wait takes over 5s, we should address
await expect(alice2.waitForStream(channel1StreamId)).resolves.not.toThrow();
await expect(alice2.waitForStream(channel2StreamId)).resolves.not.toThrow();
// alice gets keys sent via new device message
await expect(waitForMessages(alice2, channel1StreamId, ['hello channel 1'])).resolves.not.toThrow();
await expect(waitForMessages(alice2, channel2StreamId, ['hello channel 2'])).resolves.not.toThrow();
});
});
//# sourceMappingURL=clientDecryptionExtensions.test.js.map