@gryt/core 0.12.0 → 0.13.0
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.
- package/dist/index.d.ts +1 -1
- package/dist/mls/dmDriver.d.ts +1 -1
- package/dist/mls/dmDriver.js +77 -8
- package/dist/mls/interfaces.d.ts +22 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -14,4 +14,4 @@ export { createMlsDmDriver, MlsDriverError, type MlsDriverErrorCode } from "./ml
|
|
|
14
14
|
export { mlsPinsFromPeerPins } from "./mls/pins.js";
|
|
15
15
|
export { decodeMlsDmContent, encodeMlsDmContent, MLS_DM_CONTENT_VERSION, type MlsDmContent, readMlsDmContent, } from "./mls/content.js";
|
|
16
16
|
export { applyMlsReaction, mlsReactionAction, type MlsReaction } from "./mls/reactions.js";
|
|
17
|
-
export type { DmSealingMode, MlsClaimedKeyPackage, MlsDecryptedMessage, MlsDeviceRecord, MlsDeviceRef, MlsDmDriver, MlsDmDriverOptions, MlsDmEvents, MlsGroupRecord, MlsGroupView, MlsKeyPackageRecord, MlsLogEntry, MlsPins, MlsRefusal, MlsReply, MlsServerCapability, MlsStateStore, MlsTransport, MlsWelcomeDelivery, } from "./mls/interfaces.js";
|
|
17
|
+
export type { DmSealingMode, MlsClaimedKeyPackage, MlsDecryptedMessage, MlsDeviceRecord, MlsDeviceRef, MlsDmDriver, MlsDmDriverOptions, MlsDmEvents, MlsGroupRecord, MlsGroupView, MlsKeyPackageRecord, MlsListedDevice, MlsLogEntry, MlsOwnDevice, MlsPins, MlsRefusal, MlsReply, MlsServerCapability, MlsStateStore, MlsTransport, MlsWelcomeDelivery, } from "./mls/interfaces.js";
|
package/dist/mls/dmDriver.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MlsDmDriver, MlsDmDriverOptions, MlsRefusal } from "./interfaces.js";
|
|
2
|
-
export type MlsDriverErrorCode = "refused" | "no_device" | "waiting_for_welcome" | "not_in_group" | "unexpected_member" | "peer_unverified" | "too_many_attachments" | "commit_retries";
|
|
2
|
+
export type MlsDriverErrorCode = "refused" | "no_device" | "waiting_for_welcome" | "not_in_group" | "unexpected_member" | "peer_unverified" | "too_many_attachments" | "commit_retries" | "device_removed";
|
|
3
3
|
export declare class MlsDriverError extends Error {
|
|
4
4
|
code: MlsDriverErrorCode;
|
|
5
5
|
/** The server's refusal, when there was one. */
|
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, readMlsKeyPackage, removeMlsMembers, } from "@gryt/crypto";
|
|
1
|
+
import { addMlsMembers, asIdentityScope, base64UrlDecode, createMlsGroup, decodeMlsGroupState, encodeMlsGroupState, encryptMlsMessage, forgetMlsSecrets, generateMlsKeyPackage, joinMlsGroup, mlsGroupInfo, mlsGroupMembers, mlsWelcomeRefs, processMlsMessage, readDeviceCertificate, readMlsKeyPackage, removeMlsMembers, } from "@gryt/crypto";
|
|
2
2
|
/** Five devices per person per server (design, section 1). The server holds the same line. */
|
|
3
3
|
const MLS_MAX_DEVICES_PER_PERSON = 5;
|
|
4
4
|
const COMMIT_ATTEMPTS = 5;
|
|
@@ -55,7 +55,39 @@ function keyedQueue() {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
export function createMlsDmDriver(options) {
|
|
58
|
-
const {
|
|
58
|
+
const { store, pins, events, serverUserId: self, capability } = options;
|
|
59
|
+
/* Once the server says `device_removed`, nothing goes to it again from this driver:
|
|
60
|
+
no sync, and above all no publish that could register this device back. */
|
|
61
|
+
let removed = false;
|
|
62
|
+
const removedError = (r) => new MlsDriverError("device_removed", "This device was removed from encrypted messages on this server.", r);
|
|
63
|
+
function stopOnRemoval(call) {
|
|
64
|
+
return async (req) => {
|
|
65
|
+
if (removed)
|
|
66
|
+
throw removedError();
|
|
67
|
+
const r = await call(req);
|
|
68
|
+
if (!r.ok && r.error === "device_removed") {
|
|
69
|
+
if (!removed) {
|
|
70
|
+
removed = true;
|
|
71
|
+
events.onDeviceRemoved?.();
|
|
72
|
+
}
|
|
73
|
+
throw removedError(r);
|
|
74
|
+
}
|
|
75
|
+
return r;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const raw = options.transport;
|
|
79
|
+
const transport = {
|
|
80
|
+
publishKeyPackages: stopOnRemoval((req) => raw.publishKeyPackages(req)),
|
|
81
|
+
claimKeyPackages: stopOnRemoval((req) => raw.claimKeyPackages(req)),
|
|
82
|
+
listDevices: stopOnRemoval((req) => raw.listDevices(req)),
|
|
83
|
+
removeDevice: stopOnRemoval((req) => raw.removeDevice(req)),
|
|
84
|
+
createGroup: stopOnRemoval((req) => raw.createGroup(req)),
|
|
85
|
+
commit: stopOnRemoval((req) => raw.commit(req)),
|
|
86
|
+
send: stopOnRemoval((req) => raw.send(req)),
|
|
87
|
+
fetchLog: stopOnRemoval((req) => raw.fetchLog(req)),
|
|
88
|
+
sync: stopOnRemoval((req) => raw.sync(req)),
|
|
89
|
+
ackWelcomes: stopOnRemoval((req) => raw.ackWelcomes(req)),
|
|
90
|
+
};
|
|
59
91
|
const scope = asIdentityScope(options.scope);
|
|
60
92
|
const trusts = new Map();
|
|
61
93
|
/** What the engine asks about every leaf: is it somebody in this conversation, by pin. */
|
|
@@ -579,6 +611,9 @@ export function createMlsDmDriver(options) {
|
|
|
579
611
|
/** Adds wait for the next send; a device that's gone is removed straight away. */
|
|
580
612
|
async function handleDevicesChanged(push) {
|
|
581
613
|
reconciled.clear();
|
|
614
|
+
// One of yours changed, and it may be this one: a sync is how the server says so.
|
|
615
|
+
if (push.serverUserId === self && registered)
|
|
616
|
+
await transport.sync({ deviceId: (await ownDevice()).deviceId });
|
|
582
617
|
for (const rec of await store.listGroups()) {
|
|
583
618
|
await run(rec.conversationId, async () => {
|
|
584
619
|
const open = await load(rec.conversationId);
|
|
@@ -593,6 +628,45 @@ export function createMlsDmDriver(options) {
|
|
|
593
628
|
});
|
|
594
629
|
}
|
|
595
630
|
}
|
|
631
|
+
/** Names from certificates under your own person key: this device's, then your leaves in each group. */
|
|
632
|
+
async function ownDeviceNames(d, wanted) {
|
|
633
|
+
const mine = readDeviceCertificate(d.certificate, scope);
|
|
634
|
+
const names = new Map([[mine.deviceId, mine.deviceName]]);
|
|
635
|
+
const unnamed = () => [...wanted].some((id) => !names.has(id));
|
|
636
|
+
for (const rec of await store.listGroups()) {
|
|
637
|
+
if (!unnamed())
|
|
638
|
+
break;
|
|
639
|
+
let members;
|
|
640
|
+
try {
|
|
641
|
+
members = mlsGroupMembers(decodeMlsGroupState(rec.state, trustFor(rec.conversationId)), scope);
|
|
642
|
+
}
|
|
643
|
+
catch {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
for (const { certificate: c } of members) {
|
|
647
|
+
if (wanted.has(c.deviceId) && !names.has(c.deviceId) && sameBytes(c.personPublicKey, mine.personPublicKey)) {
|
|
648
|
+
names.set(c.deviceId, c.deviceName);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return names;
|
|
653
|
+
}
|
|
654
|
+
async function ownDevices() {
|
|
655
|
+
const r = await transport.listDevices({});
|
|
656
|
+
if (!r.ok)
|
|
657
|
+
throw refused("Listing devices", r);
|
|
658
|
+
const d = await ownDevice();
|
|
659
|
+
const listed = r.devices.filter((x) => x.serverUserId === self);
|
|
660
|
+
const names = await ownDeviceNames(d, new Set(listed.map((x) => x.deviceId)));
|
|
661
|
+
return listed.map((x) => ({
|
|
662
|
+
serverUserId: x.serverUserId,
|
|
663
|
+
deviceId: x.deviceId,
|
|
664
|
+
name: names.get(x.deviceId) ?? null,
|
|
665
|
+
addedAt: x.createdAt ?? null,
|
|
666
|
+
lastSeenAt: x.lastSeenAt ?? null,
|
|
667
|
+
thisDevice: x.deviceId === d.deviceId,
|
|
668
|
+
}));
|
|
669
|
+
}
|
|
596
670
|
return {
|
|
597
671
|
async start() {
|
|
598
672
|
await ownDevice();
|
|
@@ -613,12 +687,7 @@ export function createMlsDmDriver(options) {
|
|
|
613
687
|
handleMessage,
|
|
614
688
|
handleWelcome,
|
|
615
689
|
handleDevicesChanged,
|
|
616
|
-
|
|
617
|
-
const r = await transport.listDevices({});
|
|
618
|
-
if (!r.ok)
|
|
619
|
-
throw refused("Listing devices", r);
|
|
620
|
-
return r.devices;
|
|
621
|
-
},
|
|
690
|
+
ownDevices,
|
|
622
691
|
async removeOwnDevice(deviceId) {
|
|
623
692
|
const r = await transport.removeDevice({ deviceId });
|
|
624
693
|
if (!r.ok)
|
package/dist/mls/interfaces.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export interface MlsDeviceRef {
|
|
|
13
13
|
serverUserId: string;
|
|
14
14
|
deviceId: string;
|
|
15
15
|
}
|
|
16
|
+
/** A row of `mls:devices`. Your own rows say when the server first and last saw each one. */
|
|
17
|
+
export interface MlsListedDevice extends MlsDeviceRef {
|
|
18
|
+
createdAt?: string;
|
|
19
|
+
lastSeenAt?: string;
|
|
20
|
+
}
|
|
16
21
|
/** The server's view of a group. `groupId` is lower-case hex. */
|
|
17
22
|
export interface MlsGroupView {
|
|
18
23
|
conversationId: string;
|
|
@@ -71,7 +76,7 @@ export interface MlsTransport {
|
|
|
71
76
|
listDevices(req: {
|
|
72
77
|
conversationId?: string;
|
|
73
78
|
}): Promise<MlsReply<{
|
|
74
|
-
devices:
|
|
79
|
+
devices: MlsListedDevice[];
|
|
75
80
|
}>>;
|
|
76
81
|
removeDevice(req: {
|
|
77
82
|
deviceId: string;
|
|
@@ -232,6 +237,11 @@ export interface MlsDmEvents {
|
|
|
232
237
|
conversationId: string;
|
|
233
238
|
groupId: string;
|
|
234
239
|
}): void;
|
|
240
|
+
/**
|
|
241
|
+
* The server says this device was removed here (`device_removed`). The driver has stopped for
|
|
242
|
+
* good. Wipe this server's MLS state, and don't make a new device until the person asks.
|
|
243
|
+
*/
|
|
244
|
+
onDeviceRemoved?(): void;
|
|
235
245
|
}
|
|
236
246
|
/** Decision 4, per DM. `refused` never falls back to version 1. */
|
|
237
247
|
export type DmSealingMode = {
|
|
@@ -243,6 +253,15 @@ export type DmSealingMode = {
|
|
|
243
253
|
kind: "refused";
|
|
244
254
|
reason: "peer_left_mls" | "server_dropped_mls" | "no_own_device";
|
|
245
255
|
};
|
|
256
|
+
/** One of your own devices on this server, for a device list. */
|
|
257
|
+
export interface MlsOwnDevice extends MlsDeviceRef {
|
|
258
|
+
/** From its device certificate, found in this device or a group you share. Null when none shows it. */
|
|
259
|
+
name: string | null;
|
|
260
|
+
/** ISO times from the server, or null from a server that doesn't send them. */
|
|
261
|
+
addedAt: string | null;
|
|
262
|
+
lastSeenAt: string | null;
|
|
263
|
+
thisDevice: boolean;
|
|
264
|
+
}
|
|
246
265
|
export interface MlsDmDriverOptions {
|
|
247
266
|
transport: MlsTransport;
|
|
248
267
|
store: MlsStateStore;
|
|
@@ -272,7 +291,8 @@ export interface MlsDmDriver {
|
|
|
272
291
|
handleDevicesChanged(push: {
|
|
273
292
|
serverUserId: string;
|
|
274
293
|
}): Promise<void>;
|
|
275
|
-
|
|
294
|
+
/** Oldest first, as the server lists them. */
|
|
295
|
+
ownDevices(): Promise<MlsOwnDevice[]>;
|
|
276
296
|
/** Your own devices only. Every group you share drops it on the next pass. */
|
|
277
297
|
removeOwnDevice(deviceId: string): Promise<void>;
|
|
278
298
|
}
|
package/package.json
CHANGED