@gryt/core 0.17.1 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -264,6 +264,29 @@ export class FakeDeliveryService {
|
|
|
264
264
|
this.welcomes = this.welcomes.filter((w) => !(w.serverUserId === me && w.deviceId === deviceId && welcomeIds.includes(w.welcomeId)));
|
|
265
265
|
return { ok: true, deleted: before - this.welcomes.length };
|
|
266
266
|
},
|
|
267
|
+
putGroupInfo: async ({ conversationId, deviceId, groupInfo }) => {
|
|
268
|
+
await tick();
|
|
269
|
+
if (!member(conversationId))
|
|
270
|
+
return fail("not_found", "No such conversation.");
|
|
271
|
+
if (!this.isDevice(me, deviceId))
|
|
272
|
+
return fail("unknown_device", "Publish KeyPackages from this device first.");
|
|
273
|
+
const g = this.groups.get(conversationId);
|
|
274
|
+
if (!g)
|
|
275
|
+
return fail("no_group", "This conversation has no group yet.");
|
|
276
|
+
g.info = { epoch: g.epoch, data: groupInfo.slice() };
|
|
277
|
+
return { ok: true };
|
|
278
|
+
},
|
|
279
|
+
getGroupInfo: async ({ conversationId, deviceId }) => {
|
|
280
|
+
await tick();
|
|
281
|
+
if (!member(conversationId))
|
|
282
|
+
return fail("not_found", "No such conversation.");
|
|
283
|
+
if (!this.isDevice(me, deviceId))
|
|
284
|
+
return fail("unknown_device", "Publish KeyPackages from this device first.");
|
|
285
|
+
const g = this.groups.get(conversationId);
|
|
286
|
+
if (!g?.info || g.info.epoch !== g.epoch)
|
|
287
|
+
return fail("no_group_info", "There's nothing to join from yet.");
|
|
288
|
+
return { ok: true, epoch: g.info.epoch, groupInfo: g.info.data.slice() };
|
|
289
|
+
},
|
|
267
290
|
requestAdd: async ({ conversationId, deviceId }) => {
|
|
268
291
|
await tick();
|
|
269
292
|
if (!member(conversationId))
|
package/dist/mls/dmDriver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addMlsMembers, asIdentityScope, base64UrlDecode, createMlsGroup, decodeMlsGroupState, encodeMlsGroupState, encryptMlsMessage, forgetMlsSecrets, generateMlsKeyPackage, joinMlsGroup, mlsGroupInfo, mlsGroupMembers, mlsWelcomeRefs, processMlsMessage, readDeviceCertificate, readMlsKeyPackage, removeMlsMembers, } from "@gryt/crypto";
|
|
1
|
+
import { addMlsMembers, asIdentityScope, base64UrlDecode, createMlsGroup, decodeMlsGroupState, encodeMlsGroupState, encryptMlsMessage, forgetMlsSecrets, generateMlsKeyPackage, joinMlsGroup, joinMlsGroupExternally, mlsGroupInfo, mlsGroupInfoForJoining, mlsGroupMembers, mlsWelcomeRefs, processMlsMessage, readDeviceCertificate, readMlsKeyPackage, removeMlsMembers, } from "@gryt/crypto";
|
|
2
2
|
/** Devices per person per server. Must match the server's MLS_MAX_DEVICES, or extra devices are left out quietly. */
|
|
3
3
|
const MLS_MAX_DEVICES_PER_PERSON = 100;
|
|
4
4
|
const COMMIT_ATTEMPTS = 5;
|
|
@@ -90,6 +90,8 @@ export function createMlsDmDriver(options) {
|
|
|
90
90
|
}
|
|
91
91
|
const raw = options.transport;
|
|
92
92
|
const rawRequestAdd = raw.requestAdd?.bind(raw);
|
|
93
|
+
const rawPutGroupInfo = raw.putGroupInfo?.bind(raw);
|
|
94
|
+
const rawGetGroupInfo = raw.getGroupInfo?.bind(raw);
|
|
93
95
|
const transport = {
|
|
94
96
|
publishKeyPackages: stopOnRemoval((req) => raw.publishKeyPackages(req)),
|
|
95
97
|
claimKeyPackages: stopOnRemoval((req) => raw.claimKeyPackages(req)),
|
|
@@ -102,6 +104,8 @@ export function createMlsDmDriver(options) {
|
|
|
102
104
|
sync: stopOnRemoval((req) => raw.sync(req)),
|
|
103
105
|
ackWelcomes: stopOnRemoval((req) => raw.ackWelcomes(req)),
|
|
104
106
|
...(rawRequestAdd ? { requestAdd: stopOnRemoval(rawRequestAdd) } : {}),
|
|
107
|
+
...(rawPutGroupInfo ? { putGroupInfo: stopOnRemoval(rawPutGroupInfo) } : {}),
|
|
108
|
+
...(rawGetGroupInfo ? { getGroupInfo: stopOnRemoval(rawGetGroupInfo) } : {}),
|
|
105
109
|
};
|
|
106
110
|
const scope = asIdentityScope(options.scope);
|
|
107
111
|
const trusts = new Map();
|
|
@@ -277,7 +281,7 @@ export function createMlsDmDriver(options) {
|
|
|
277
281
|
}
|
|
278
282
|
else {
|
|
279
283
|
try {
|
|
280
|
-
const r = await processMlsMessage(open.state, e.data);
|
|
284
|
+
const r = await processMlsMessage(open.state, e.data, scope);
|
|
281
285
|
if (r.kind === "handshake" && r.removed) {
|
|
282
286
|
await store.deleteGroup(open.rec.conversationId);
|
|
283
287
|
left.add(open.rec.conversationId);
|
|
@@ -384,6 +388,7 @@ export function createMlsDmDriver(options) {
|
|
|
384
388
|
forgetMlsSecrets(c.consumed);
|
|
385
389
|
open.rec = { ...open.rec, pending: undefined };
|
|
386
390
|
await save(open);
|
|
391
|
+
void publishGroupInfo(open);
|
|
387
392
|
return;
|
|
388
393
|
}
|
|
389
394
|
open.rec = { ...open.rec, pending: undefined };
|
|
@@ -526,6 +531,53 @@ export function createMlsDmDriver(options) {
|
|
|
526
531
|
welcomeWaiters.set(conversationId, [...(welcomeWaiters.get(conversationId) ?? []), done]);
|
|
527
532
|
});
|
|
528
533
|
}
|
|
534
|
+
/** For the next device of either person to join by itself. An old server has nowhere to keep it. */
|
|
535
|
+
async function publishGroupInfo(open) {
|
|
536
|
+
if (!transport.putGroupInfo)
|
|
537
|
+
return;
|
|
538
|
+
try {
|
|
539
|
+
const groupInfo = await mlsGroupInfoForJoining(open.state);
|
|
540
|
+
await transport.putGroupInfo({ conversationId: open.rec.conversationId, deviceId: (await ownDevice()).deviceId, groupInfo });
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
// A stale epoch or an old server: the next commit uploads one again.
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
/* Joins from the GroupInfo a member left, with nobody else online (GRYT-1859). False when
|
|
547
|
+
there's none, it's behind, or this server can't: then the device asks to be added. */
|
|
548
|
+
async function joinByItself(conversationId) {
|
|
549
|
+
if (!transport.getGroupInfo)
|
|
550
|
+
return false;
|
|
551
|
+
const d = await ownDevice();
|
|
552
|
+
const got = await transport.getGroupInfo({ conversationId, deviceId: d.deviceId }).catch(() => null);
|
|
553
|
+
if (!got?.ok)
|
|
554
|
+
return false;
|
|
555
|
+
return run(conversationId, async () => {
|
|
556
|
+
if (await store.loadGroup(conversationId))
|
|
557
|
+
return true;
|
|
558
|
+
let joined;
|
|
559
|
+
try {
|
|
560
|
+
joined = await joinMlsGroupExternally({ groupInfo: got.groupInfo, device: d, trust: trustFor(conversationId) });
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
const r = await transport.commit({ conversationId, deviceId: d.deviceId, commit: joined.commit });
|
|
566
|
+
if (!r.ok)
|
|
567
|
+
return false;
|
|
568
|
+
const open = {
|
|
569
|
+
rec: { conversationId, groupId: groupIdOf(joined.state), state: new Uint8Array(), cursor: r.seq, joinedEpoch: epochOf(joined.state) },
|
|
570
|
+
state: joined.state,
|
|
571
|
+
};
|
|
572
|
+
await save(open);
|
|
573
|
+
await notePeople(conversationId, joined.state);
|
|
574
|
+
reconciled.delete(conversationId);
|
|
575
|
+
left.delete(conversationId);
|
|
576
|
+
events.onJoined?.({ conversationId, groupId: open.rec.groupId });
|
|
577
|
+
void publishGroupInfo(open);
|
|
578
|
+
return true;
|
|
579
|
+
});
|
|
580
|
+
}
|
|
529
581
|
/* Only a member can add a device, so one that's out asks the members' apps to. An old
|
|
530
582
|
server doesn't answer, which is why nothing waits on it. */
|
|
531
583
|
async function askToBeAdded(conversationId, force = false) {
|
|
@@ -543,7 +595,10 @@ export function createMlsDmDriver(options) {
|
|
|
543
595
|
return;
|
|
544
596
|
void store
|
|
545
597
|
.loadGroup(conversationId)
|
|
546
|
-
.then((rec) =>
|
|
598
|
+
.then(async (rec) => {
|
|
599
|
+
if (!rec && !(await joinByItself(conversationId)))
|
|
600
|
+
await askToBeAdded(conversationId);
|
|
601
|
+
})
|
|
547
602
|
.catch(() => undefined);
|
|
548
603
|
}, NOT_ADDED_GRACE_MS);
|
|
549
604
|
}
|
|
@@ -556,6 +611,8 @@ export function createMlsDmDriver(options) {
|
|
|
556
611
|
if (unjoinable.has(conversationId)) {
|
|
557
612
|
throw new MlsDriverError("peer_unverified", "This device was added, and a member's key isn't pinned here to join with.");
|
|
558
613
|
}
|
|
614
|
+
if (await joinByItself(conversationId))
|
|
615
|
+
return;
|
|
559
616
|
void askToBeAdded(conversationId).catch(() => undefined);
|
|
560
617
|
await waitForWelcome(conversationId);
|
|
561
618
|
}
|
package/dist/mls/interfaces.d.ts
CHANGED
|
@@ -146,6 +146,19 @@ export interface MlsTransport {
|
|
|
146
146
|
conversationId: string;
|
|
147
147
|
deviceId: string;
|
|
148
148
|
}): Promise<MlsReply<object>>;
|
|
149
|
+
/** The group's GroupInfo for its current epoch, so a device of a member can join by itself (GRYT-1859). */
|
|
150
|
+
putGroupInfo?(req: {
|
|
151
|
+
conversationId: string;
|
|
152
|
+
deviceId: string;
|
|
153
|
+
groupInfo: Uint8Array;
|
|
154
|
+
}): Promise<MlsReply<object>>;
|
|
155
|
+
getGroupInfo?(req: {
|
|
156
|
+
conversationId: string;
|
|
157
|
+
deviceId: string;
|
|
158
|
+
}): Promise<MlsReply<{
|
|
159
|
+
epoch: number;
|
|
160
|
+
groupInfo: Uint8Array;
|
|
161
|
+
}>>;
|
|
149
162
|
}
|
|
150
163
|
/** `server:info.mls`. Absent means the server can't do MLS. */
|
|
151
164
|
export interface MlsServerCapability {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gryt/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "Gryt's shared application logic: one implementation of the things the desktop and mobile apps both do.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@gryt/crypto": "0.
|
|
46
|
+
"@gryt/crypto": "0.11.0",
|
|
47
47
|
"@noble/hashes": "^2.3.0"
|
|
48
48
|
}
|
|
49
49
|
}
|