@vex-chat/libvex 6.8.0 → 7.0.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.
- package/dist/Client.d.ts +24 -8
- package/dist/Client.d.ts.map +1 -1
- package/dist/Client.js +220 -124
- package/dist/Client.js.map +1 -1
- package/dist/Storage.d.ts +6 -0
- package/dist/Storage.d.ts.map +1 -1
- package/dist/__tests__/harness/memory-storage.d.ts +2 -1
- package/dist/__tests__/harness/memory-storage.d.ts.map +1 -1
- package/dist/__tests__/harness/memory-storage.js +27 -0
- package/dist/__tests__/harness/memory-storage.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/messageExtra.d.ts +18 -0
- package/dist/messageExtra.d.ts.map +1 -1
- package/dist/messageExtra.js +104 -0
- package/dist/messageExtra.js.map +1 -1
- package/dist/storage/sqlite.d.ts +2 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +47 -1
- package/dist/storage/sqlite.js.map +1 -1
- package/package.json +4 -4
- package/src/Client.ts +336 -169
- package/src/Storage.ts +11 -0
- package/src/__tests__/harness/memory-storage.ts +42 -1
- package/src/__tests__/messageExtra.test.ts +38 -0
- package/src/index.ts +6 -1
- package/src/messageExtra.ts +145 -0
- package/src/storage/sqlite.ts +65 -3
package/src/Storage.ts
CHANGED
|
@@ -24,6 +24,11 @@ import type { EventEmitter } from "eventemitter3";
|
|
|
24
24
|
* - Managing prekeys / one-time keys used for session setup
|
|
25
25
|
* - Emitting lifecycle events (`ready`, `error`)
|
|
26
26
|
*/
|
|
27
|
+
export interface MessageUpdatePatch {
|
|
28
|
+
extra?: null | string | undefined;
|
|
29
|
+
message?: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
export interface Storage extends EventEmitter {
|
|
28
33
|
/** Closes storage resources (connections, handles, transactions, etc.). */
|
|
29
34
|
close: () => Promise<void>;
|
|
@@ -131,6 +136,12 @@ export interface Storage extends EventEmitter {
|
|
|
131
136
|
preKeys: UnsavedPreKey[],
|
|
132
137
|
oneTime: boolean,
|
|
133
138
|
) => Promise<PreKeysSQL[]>;
|
|
139
|
+
|
|
134
140
|
/** Persists an encryption session. */
|
|
135
141
|
saveSession: (session: Session) => Promise<void>;
|
|
142
|
+
/** Updates locally persisted message plaintext and/or encrypted extra by `mailID`. */
|
|
143
|
+
updateMessage: (
|
|
144
|
+
mailID: string,
|
|
145
|
+
patch: MessageUpdatePatch,
|
|
146
|
+
) => Promise<boolean>;
|
|
136
147
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Message } from "../../index.js";
|
|
8
|
-
import type { Storage } from "../../Storage.js";
|
|
8
|
+
import type { MessageUpdatePatch, Storage } from "../../Storage.js";
|
|
9
9
|
import type {
|
|
10
10
|
PreKeysCrypto,
|
|
11
11
|
SessionCrypto,
|
|
@@ -276,6 +276,47 @@ export class MemoryStorage extends EventEmitter implements Storage {
|
|
|
276
276
|
return Promise.resolve();
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
async updateMessage(
|
|
280
|
+
mailID: string,
|
|
281
|
+
patch: MessageUpdatePatch,
|
|
282
|
+
): Promise<boolean> {
|
|
283
|
+
const idx = this.messages.findIndex((m) => m.mailID === mailID);
|
|
284
|
+
if (idx < 0) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
if (
|
|
288
|
+
patch.message === undefined &&
|
|
289
|
+
!Object.prototype.hasOwnProperty.call(patch, "extra")
|
|
290
|
+
) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const current = this.messages[idx];
|
|
294
|
+
if (!current) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
const next = { ...current };
|
|
298
|
+
if (Object.prototype.hasOwnProperty.call(patch, "extra")) {
|
|
299
|
+
next.extra = patch.extra;
|
|
300
|
+
}
|
|
301
|
+
if (patch.message !== undefined) {
|
|
302
|
+
const fips = getCryptoProfile() === "fips";
|
|
303
|
+
const ct = fips
|
|
304
|
+
? await xSecretboxAsync(
|
|
305
|
+
XUtils.decodeUTF8(patch.message),
|
|
306
|
+
XUtils.decodeHex(current.nonce),
|
|
307
|
+
this.atRestAesKey,
|
|
308
|
+
)
|
|
309
|
+
: xSecretbox(
|
|
310
|
+
XUtils.decodeUTF8(patch.message),
|
|
311
|
+
XUtils.decodeHex(current.nonce),
|
|
312
|
+
this.atRestAesKey,
|
|
313
|
+
);
|
|
314
|
+
next.message = XUtils.encodeHex(ct);
|
|
315
|
+
}
|
|
316
|
+
this.messages[idx] = next;
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
279
320
|
private async decryptMessage(msg: Message): Promise<Message> {
|
|
280
321
|
const copy = { ...msg };
|
|
281
322
|
if (copy.decrypted) {
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
createMessageDeleteBatchEventExtra,
|
|
10
|
+
createMessageDeleteEventExtra,
|
|
9
11
|
createMessageEmbedExtra,
|
|
12
|
+
createMessageUpdateEventExtra,
|
|
10
13
|
type MessageEmbed,
|
|
11
14
|
parseMessageExtra,
|
|
12
15
|
serializeMessageExtra,
|
|
@@ -36,6 +39,7 @@ describe("message extra", () => {
|
|
|
36
39
|
},
|
|
37
40
|
],
|
|
38
41
|
display: "decorate",
|
|
42
|
+
iconAttachment: imageAttachment,
|
|
39
43
|
kind: "git.workflow",
|
|
40
44
|
title: "CI finished",
|
|
41
45
|
version: 1,
|
|
@@ -79,6 +83,8 @@ describe("message extra", () => {
|
|
|
79
83
|
const parsed = parseMessageExtra(
|
|
80
84
|
JSON.stringify({
|
|
81
85
|
embed: { display: "replace", kind: 123 },
|
|
86
|
+
messageDeleteEvent: { action: "delete" },
|
|
87
|
+
messageUpdateEvent: { action: "update", targetMailID: "m1" },
|
|
82
88
|
reactionEvent: { action: "toggle", targetMailID: "m1" },
|
|
83
89
|
vendor: { ok: true },
|
|
84
90
|
version: 999,
|
|
@@ -91,6 +97,38 @@ describe("message extra", () => {
|
|
|
91
97
|
});
|
|
92
98
|
});
|
|
93
99
|
|
|
100
|
+
it("serializes and parses message update and delete events", () => {
|
|
101
|
+
const updateExtra = createMessageUpdateEventExtra(
|
|
102
|
+
"m-target",
|
|
103
|
+
"edited text",
|
|
104
|
+
);
|
|
105
|
+
const deleteExtra = createMessageDeleteEventExtra("m-target");
|
|
106
|
+
|
|
107
|
+
expect(parseMessageExtra(updateExtra).messageUpdateEvent).toEqual({
|
|
108
|
+
action: "update",
|
|
109
|
+
message: "edited text",
|
|
110
|
+
targetMailID: "m-target",
|
|
111
|
+
});
|
|
112
|
+
expect(parseMessageExtra(deleteExtra).messageDeleteEvent).toEqual({
|
|
113
|
+
action: "delete",
|
|
114
|
+
targetMailID: "m-target",
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("serializes and parses batched message delete events", () => {
|
|
119
|
+
const extra = createMessageDeleteBatchEventExtra([
|
|
120
|
+
"m-first",
|
|
121
|
+
"m-second",
|
|
122
|
+
"m-first",
|
|
123
|
+
"",
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
expect(parseMessageExtra(extra).messageDeleteEvent).toEqual({
|
|
127
|
+
action: "delete",
|
|
128
|
+
targetMailIDs: ["m-first", "m-second"],
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
94
132
|
it("returns null for an empty serialized extra", () => {
|
|
95
133
|
expect(serializeMessageExtra({ version: 1 })).toBeNull();
|
|
96
134
|
});
|
package/src/index.ts
CHANGED
|
@@ -49,13 +49,17 @@ export type {
|
|
|
49
49
|
HttpResponse,
|
|
50
50
|
} from "./http.js";
|
|
51
51
|
export {
|
|
52
|
+
createMessageDeleteBatchEventExtra,
|
|
53
|
+
createMessageDeleteEventExtra,
|
|
52
54
|
createMessageEmbedExtra,
|
|
55
|
+
createMessageUpdateEventExtra,
|
|
53
56
|
MESSAGE_EXTRA_VERSION,
|
|
54
57
|
parseMessageExtra,
|
|
55
58
|
serializeMessageExtra,
|
|
56
59
|
} from "./messageExtra.js";
|
|
57
60
|
export type {
|
|
58
61
|
EncryptedFileAttachmentReference,
|
|
62
|
+
MessageDeleteEvent,
|
|
59
63
|
MessageEmbed,
|
|
60
64
|
MessageEmbedAction,
|
|
61
65
|
MessageEmbedBlock,
|
|
@@ -75,13 +79,14 @@ export type {
|
|
|
75
79
|
MessageExtra,
|
|
76
80
|
MessageReaction,
|
|
77
81
|
MessageReactionEvent,
|
|
82
|
+
MessageUpdateEvent,
|
|
78
83
|
} from "./messageExtra.js";
|
|
79
84
|
export {
|
|
80
85
|
clampLocalMessageRetentionDays,
|
|
81
86
|
effectiveMessageRetentionHintDays,
|
|
82
87
|
MAX_LOCAL_MESSAGE_RETENTION_DAYS,
|
|
83
88
|
} from "./retention.js";
|
|
84
|
-
export type { Storage } from "./Storage.js";
|
|
89
|
+
export type { MessageUpdatePatch, Storage } from "./Storage.js";
|
|
85
90
|
export type {
|
|
86
91
|
KeyPair,
|
|
87
92
|
KeyStore,
|
package/src/messageExtra.ts
CHANGED
|
@@ -14,12 +14,20 @@ export interface EncryptedFileAttachmentReference {
|
|
|
14
14
|
key: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface MessageDeleteEvent {
|
|
18
|
+
action: "delete";
|
|
19
|
+
deletedAt?: string | undefined;
|
|
20
|
+
targetMailID?: string | undefined;
|
|
21
|
+
targetMailIDs?: string[] | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
export interface MessageEmbed {
|
|
18
25
|
actions?: MessageEmbedAction[] | undefined;
|
|
19
26
|
blocks?: MessageEmbedBlock[] | undefined;
|
|
20
27
|
display: MessageEmbedDisplay;
|
|
21
28
|
fields?: MessageEmbedField[] | undefined;
|
|
22
29
|
icon?: string | undefined;
|
|
30
|
+
iconAttachment?: EncryptedFileAttachmentReference | undefined;
|
|
23
31
|
kind: string;
|
|
24
32
|
source?: MessageEmbedSource | undefined;
|
|
25
33
|
subtitle?: string | undefined;
|
|
@@ -132,6 +140,8 @@ export type MessageEmoji =
|
|
|
132
140
|
export interface MessageExtra {
|
|
133
141
|
[key: string]: unknown;
|
|
134
142
|
embed?: MessageEmbed | undefined;
|
|
143
|
+
messageDeleteEvent?: MessageDeleteEvent | undefined;
|
|
144
|
+
messageUpdateEvent?: MessageUpdateEvent | undefined;
|
|
135
145
|
reactionEvent?: MessageReactionEvent | undefined;
|
|
136
146
|
reactions?: MessageReaction[] | undefined;
|
|
137
147
|
version: typeof MESSAGE_EXTRA_VERSION;
|
|
@@ -148,6 +158,41 @@ export interface MessageReactionEvent {
|
|
|
148
158
|
targetMailID: string;
|
|
149
159
|
}
|
|
150
160
|
|
|
161
|
+
export interface MessageUpdateEvent {
|
|
162
|
+
action: "update";
|
|
163
|
+
editedAt?: string | undefined;
|
|
164
|
+
message: string;
|
|
165
|
+
targetMailID: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function createMessageDeleteBatchEventExtra(
|
|
169
|
+
targetMailIDs: string[],
|
|
170
|
+
currentExtra?: null | string,
|
|
171
|
+
): null | string {
|
|
172
|
+
return serializeMessageExtra({
|
|
173
|
+
...parseMessageExtra(currentExtra),
|
|
174
|
+
messageDeleteEvent: {
|
|
175
|
+
action: "delete",
|
|
176
|
+
targetMailIDs,
|
|
177
|
+
},
|
|
178
|
+
version: MESSAGE_EXTRA_VERSION,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function createMessageDeleteEventExtra(
|
|
183
|
+
targetMailID: string,
|
|
184
|
+
currentExtra?: null | string,
|
|
185
|
+
): null | string {
|
|
186
|
+
return serializeMessageExtra({
|
|
187
|
+
...parseMessageExtra(currentExtra),
|
|
188
|
+
messageDeleteEvent: {
|
|
189
|
+
action: "delete",
|
|
190
|
+
targetMailID,
|
|
191
|
+
},
|
|
192
|
+
version: MESSAGE_EXTRA_VERSION,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
151
196
|
export function createMessageEmbedExtra(
|
|
152
197
|
embed: MessageEmbed,
|
|
153
198
|
currentExtra?: null | string,
|
|
@@ -159,6 +204,22 @@ export function createMessageEmbedExtra(
|
|
|
159
204
|
});
|
|
160
205
|
}
|
|
161
206
|
|
|
207
|
+
export function createMessageUpdateEventExtra(
|
|
208
|
+
targetMailID: string,
|
|
209
|
+
message: string,
|
|
210
|
+
currentExtra?: null | string,
|
|
211
|
+
): null | string {
|
|
212
|
+
return serializeMessageExtra({
|
|
213
|
+
...parseMessageExtra(currentExtra),
|
|
214
|
+
messageUpdateEvent: {
|
|
215
|
+
action: "update",
|
|
216
|
+
message,
|
|
217
|
+
targetMailID,
|
|
218
|
+
},
|
|
219
|
+
version: MESSAGE_EXTRA_VERSION,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
162
223
|
export function parseMessageExtra(
|
|
163
224
|
extra: null | string | undefined,
|
|
164
225
|
): MessageExtra {
|
|
@@ -173,17 +234,27 @@ export function parseMessageExtra(
|
|
|
173
234
|
}
|
|
174
235
|
const rest: Record<string, unknown> = { ...raw };
|
|
175
236
|
delete rest["embed"];
|
|
237
|
+
delete rest["messageDeleteEvent"];
|
|
238
|
+
delete rest["messageUpdateEvent"];
|
|
176
239
|
delete rest["reactionEvent"];
|
|
177
240
|
delete rest["reactions"];
|
|
178
241
|
delete rest["version"];
|
|
179
242
|
|
|
180
243
|
const embed = parseMessageEmbed(raw["embed"]);
|
|
244
|
+
const messageDeleteEvent = parseMessageDeleteEvent(
|
|
245
|
+
raw["messageDeleteEvent"],
|
|
246
|
+
);
|
|
247
|
+
const messageUpdateEvent = parseMessageUpdateEvent(
|
|
248
|
+
raw["messageUpdateEvent"],
|
|
249
|
+
);
|
|
181
250
|
const reactionEvent = parseMessageReactionEvent(raw["reactionEvent"]);
|
|
182
251
|
const reactions = parseMessageReactions(raw["reactions"]);
|
|
183
252
|
|
|
184
253
|
return {
|
|
185
254
|
...rest,
|
|
186
255
|
...(embed ? { embed } : {}),
|
|
256
|
+
...(messageDeleteEvent ? { messageDeleteEvent } : {}),
|
|
257
|
+
...(messageUpdateEvent ? { messageUpdateEvent } : {}),
|
|
187
258
|
...(reactionEvent ? { reactionEvent } : {}),
|
|
188
259
|
...(reactions.length > 0 ? { reactions } : {}),
|
|
189
260
|
version: MESSAGE_EXTRA_VERSION,
|
|
@@ -262,6 +333,12 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
262
333
|
|
|
263
334
|
function normalizeMessageExtra(extra: MessageExtra): MessageExtra {
|
|
264
335
|
const embed = parseMessageEmbed(extra.embed);
|
|
336
|
+
const messageDeleteEvent = parseMessageDeleteEvent(
|
|
337
|
+
extra.messageDeleteEvent,
|
|
338
|
+
);
|
|
339
|
+
const messageUpdateEvent = parseMessageUpdateEvent(
|
|
340
|
+
extra.messageUpdateEvent,
|
|
341
|
+
);
|
|
265
342
|
const reactionEvent = parseMessageReactionEvent(extra.reactionEvent);
|
|
266
343
|
const reactions = parseMessageReactions(extra.reactions);
|
|
267
344
|
const normalized: MessageExtra = {
|
|
@@ -274,6 +351,16 @@ function normalizeMessageExtra(extra: MessageExtra): MessageExtra {
|
|
|
274
351
|
} else {
|
|
275
352
|
delete normalized.embed;
|
|
276
353
|
}
|
|
354
|
+
if (messageDeleteEvent) {
|
|
355
|
+
normalized.messageDeleteEvent = messageDeleteEvent;
|
|
356
|
+
} else {
|
|
357
|
+
delete normalized.messageDeleteEvent;
|
|
358
|
+
}
|
|
359
|
+
if (messageUpdateEvent) {
|
|
360
|
+
normalized.messageUpdateEvent = messageUpdateEvent;
|
|
361
|
+
} else {
|
|
362
|
+
delete normalized.messageUpdateEvent;
|
|
363
|
+
}
|
|
277
364
|
if (reactionEvent) {
|
|
278
365
|
normalized.reactionEvent = reactionEvent;
|
|
279
366
|
} else {
|
|
@@ -311,6 +398,44 @@ function parseAttachment(
|
|
|
311
398
|
};
|
|
312
399
|
}
|
|
313
400
|
|
|
401
|
+
function parseMessageDeleteEvent(value: unknown): MessageDeleteEvent | null {
|
|
402
|
+
if (!isRecord(value) || value["action"] !== "delete") {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
const targetMailID =
|
|
406
|
+
typeof value["targetMailID"] === "string" &&
|
|
407
|
+
value["targetMailID"].length > 0
|
|
408
|
+
? value["targetMailID"]
|
|
409
|
+
: undefined;
|
|
410
|
+
const targetMailIDs = parseMessageDeleteTargets(value["targetMailIDs"]);
|
|
411
|
+
if (!targetMailID && targetMailIDs.length === 0) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const event: MessageDeleteEvent = {
|
|
415
|
+
action: "delete",
|
|
416
|
+
...(targetMailID ? { targetMailID } : {}),
|
|
417
|
+
...(targetMailIDs.length > 0 ? { targetMailIDs } : {}),
|
|
418
|
+
};
|
|
419
|
+
copyOptionalString(event, value, "deletedAt");
|
|
420
|
+
return event;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function parseMessageDeleteTargets(value: unknown): string[] {
|
|
424
|
+
if (!Array.isArray(value)) {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
const targets: string[] = [];
|
|
428
|
+
const seen = new Set<string>();
|
|
429
|
+
for (const item of value) {
|
|
430
|
+
if (typeof item !== "string" || item.length === 0 || seen.has(item)) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
seen.add(item);
|
|
434
|
+
targets.push(item);
|
|
435
|
+
}
|
|
436
|
+
return targets;
|
|
437
|
+
}
|
|
438
|
+
|
|
314
439
|
function parseMessageEmbed(value: unknown): MessageEmbed | null {
|
|
315
440
|
if (!isRecord(value)) return null;
|
|
316
441
|
const display = value["display"];
|
|
@@ -331,6 +456,8 @@ function parseMessageEmbed(value: unknown): MessageEmbed | null {
|
|
|
331
456
|
version: MESSAGE_EXTRA_VERSION,
|
|
332
457
|
};
|
|
333
458
|
copyOptionalString(embed, value, "icon");
|
|
459
|
+
const iconAttachment = parseAttachment(value["iconAttachment"]);
|
|
460
|
+
if (iconAttachment) embed.iconAttachment = iconAttachment;
|
|
334
461
|
copyOptionalString(embed, value, "subtitle");
|
|
335
462
|
copyOptionalBoolean(embed, value, "suppressLinkPreview");
|
|
336
463
|
copyOptionalString(embed, value, "timestamp");
|
|
@@ -583,3 +710,21 @@ function parseMessageReactions(value: unknown): MessageReaction[] {
|
|
|
583
710
|
return reaction ? [reaction] : [];
|
|
584
711
|
});
|
|
585
712
|
}
|
|
713
|
+
|
|
714
|
+
function parseMessageUpdateEvent(value: unknown): MessageUpdateEvent | null {
|
|
715
|
+
if (
|
|
716
|
+
!isRecord(value) ||
|
|
717
|
+
value["action"] !== "update" ||
|
|
718
|
+
typeof value["message"] !== "string" ||
|
|
719
|
+
typeof value["targetMailID"] !== "string"
|
|
720
|
+
) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
const event: MessageUpdateEvent = {
|
|
724
|
+
action: "update",
|
|
725
|
+
message: value["message"],
|
|
726
|
+
targetMailID: value["targetMailID"],
|
|
727
|
+
};
|
|
728
|
+
copyOptionalString(event, value, "editedAt");
|
|
729
|
+
return event;
|
|
730
|
+
}
|
package/src/storage/sqlite.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Message } from "../index.js";
|
|
8
|
-
import type { Storage } from "../Storage.js";
|
|
8
|
+
import type { MessageUpdatePatch, Storage } from "../Storage.js";
|
|
9
9
|
import type {
|
|
10
10
|
PreKeysCrypto,
|
|
11
11
|
SessionCrypto,
|
|
@@ -597,8 +597,6 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
597
597
|
}
|
|
598
598
|
}
|
|
599
599
|
|
|
600
|
-
// ── Devices ──────────────────────────────────────────────────────────────
|
|
601
|
-
|
|
602
600
|
async savePreKeys(
|
|
603
601
|
preKeys: UnsavedPreKey[],
|
|
604
602
|
oneTime: boolean,
|
|
@@ -637,6 +635,8 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
637
635
|
return saved;
|
|
638
636
|
}
|
|
639
637
|
|
|
638
|
+
// ── Devices ──────────────────────────────────────────────────────────────
|
|
639
|
+
|
|
640
640
|
async saveSession(session: SessionSQL): Promise<void> {
|
|
641
641
|
if (this.closing) {
|
|
642
642
|
return;
|
|
@@ -703,6 +703,68 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
703
703
|
}
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
+
async updateMessage(
|
|
707
|
+
mailID: string,
|
|
708
|
+
patch: MessageUpdatePatch,
|
|
709
|
+
): Promise<boolean> {
|
|
710
|
+
if (this.isClosingNow()) {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
await this.untilReady();
|
|
714
|
+
if (
|
|
715
|
+
patch.message === undefined &&
|
|
716
|
+
!Object.prototype.hasOwnProperty.call(patch, "extra")
|
|
717
|
+
) {
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const row = await this.db
|
|
722
|
+
.selectFrom("messages")
|
|
723
|
+
.selectAll()
|
|
724
|
+
.where("mailID", "=", mailID)
|
|
725
|
+
.executeTakeFirst();
|
|
726
|
+
if (!row) {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const current = (await this.decryptMessagesAsync([row]))[0];
|
|
731
|
+
if (!current) {
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
const next: Message = {
|
|
735
|
+
...current,
|
|
736
|
+
...(patch.message !== undefined ? { message: patch.message } : {}),
|
|
737
|
+
...(Object.prototype.hasOwnProperty.call(patch, "extra")
|
|
738
|
+
? { extra: patch.extra }
|
|
739
|
+
: {}),
|
|
740
|
+
};
|
|
741
|
+
const storedPlaintext = encodeStoredMessagePlaintext(next);
|
|
742
|
+
const fips = getCryptoProfile() === "fips";
|
|
743
|
+
const ct = fips
|
|
744
|
+
? await xSecretboxAsync(
|
|
745
|
+
XUtils.decodeUTF8(storedPlaintext),
|
|
746
|
+
XUtils.decodeHex(row.nonce),
|
|
747
|
+
this.atRestAesKey,
|
|
748
|
+
)
|
|
749
|
+
: xSecretbox(
|
|
750
|
+
XUtils.decodeUTF8(storedPlaintext),
|
|
751
|
+
XUtils.decodeHex(row.nonce),
|
|
752
|
+
this.atRestAesKey,
|
|
753
|
+
);
|
|
754
|
+
if (this.isClosingNow()) {
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
const result = await this.db
|
|
758
|
+
.updateTable("messages")
|
|
759
|
+
.set({
|
|
760
|
+
extra: null,
|
|
761
|
+
message: XUtils.encodeHex(ct),
|
|
762
|
+
})
|
|
763
|
+
.where("mailID", "=", mailID)
|
|
764
|
+
.executeTakeFirst();
|
|
765
|
+
return Number(result.numUpdatedRows) > 0;
|
|
766
|
+
}
|
|
767
|
+
|
|
706
768
|
// ── Purge ────────────────────────────────────────────────────────────────
|
|
707
769
|
|
|
708
770
|
private async decryptMessagesAsync(
|