@vex-chat/libvex 6.6.4 → 6.8.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/Client.d.ts +11 -7
- package/dist/Client.d.ts.map +1 -1
- package/dist/Client.js +93 -81
- package/dist/Client.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/messageExtra.d.ts +114 -0
- package/dist/messageExtra.d.ts.map +1 -0
- package/dist/messageExtra.js +383 -0
- package/dist/messageExtra.js.map +1 -0
- package/dist/storage/schema.d.ts +1 -0
- package/dist/storage/schema.d.ts.map +1 -1
- package/dist/storage/sqlite.d.ts +1 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +68 -4
- package/dist/storage/sqlite.js.map +1 -1
- package/package.json +3 -3
- package/src/Client.ts +138 -112
- package/src/__tests__/harness/shared-suite.ts +38 -1
- package/src/__tests__/messageExtra.test.ts +97 -0
- package/src/index.ts +29 -0
- package/src/messageExtra.ts +585 -0
- package/src/storage/schema.ts +1 -0
- package/src/storage/sqlite.ts +82 -4
package/src/storage/sqlite.ts
CHANGED
|
@@ -54,6 +54,8 @@ import { type Kysely, sql } from "kysely";
|
|
|
54
54
|
import { effectiveMessageRetentionHintDays } from "../retention.js";
|
|
55
55
|
import { parseSkippedKeysStrict } from "../utils/ratchet.js";
|
|
56
56
|
|
|
57
|
+
const STORAGE_MESSAGE_BLOB_PREFIX = "vex-storage-message:1\n";
|
|
58
|
+
|
|
57
59
|
export class SqliteStorage extends EventEmitter implements Storage {
|
|
58
60
|
public ready = false;
|
|
59
61
|
/** 32-byte AES-256 (or nacl) key for local at-rest `secretbox` (see `XUtils.deriveLocalAtRestAesKey`). */
|
|
@@ -323,6 +325,7 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
323
325
|
.addColumn("sender", "text")
|
|
324
326
|
.addColumn("recipient", "text")
|
|
325
327
|
.addColumn("group", "text")
|
|
328
|
+
.addColumn("extra", "text")
|
|
326
329
|
.addColumn("mailID", "text")
|
|
327
330
|
.addColumn("message", "text")
|
|
328
331
|
.addColumn("direction", "text")
|
|
@@ -370,6 +373,7 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
370
373
|
)
|
|
371
374
|
.execute();
|
|
372
375
|
await this.ensureSessionRatchetColumns();
|
|
376
|
+
await this.ensureMessageExtraColumn();
|
|
373
377
|
await this.ensureRetentionHintColumn();
|
|
374
378
|
await this.ensureMessageMailIdIndex();
|
|
375
379
|
|
|
@@ -534,16 +538,17 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
534
538
|
return;
|
|
535
539
|
}
|
|
536
540
|
|
|
537
|
-
// Encrypt plaintext with at-rest key before saving to disk
|
|
541
|
+
// Encrypt plaintext with at-rest key before saving to disk.
|
|
542
|
+
const storedPlaintext = encodeStoredMessagePlaintext(message);
|
|
538
543
|
const fips = getCryptoProfile() === "fips";
|
|
539
544
|
const ct = fips
|
|
540
545
|
? await xSecretboxAsync(
|
|
541
|
-
XUtils.decodeUTF8(
|
|
546
|
+
XUtils.decodeUTF8(storedPlaintext),
|
|
542
547
|
XUtils.decodeHex(message.nonce),
|
|
543
548
|
this.atRestAesKey,
|
|
544
549
|
)
|
|
545
550
|
: xSecretbox(
|
|
546
|
-
XUtils.decodeUTF8(
|
|
551
|
+
XUtils.decodeUTF8(storedPlaintext),
|
|
547
552
|
XUtils.decodeHex(message.nonce),
|
|
548
553
|
this.atRestAesKey,
|
|
549
554
|
);
|
|
@@ -559,6 +564,7 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
559
564
|
authorID: message.authorID,
|
|
560
565
|
decrypted: message.decrypted ? 1 : 0,
|
|
561
566
|
direction: message.direction,
|
|
567
|
+
extra: null,
|
|
562
568
|
forward: message.forward ? 1 : 0,
|
|
563
569
|
group: message.group ?? null,
|
|
564
570
|
mailID: message.mailID,
|
|
@@ -733,6 +739,10 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
733
739
|
}
|
|
734
740
|
const direction =
|
|
735
741
|
msg.direction === "incoming" ? "incoming" : "outgoing";
|
|
742
|
+
const storedPlaintext = decodeStoredMessagePlaintext(
|
|
743
|
+
plaintext,
|
|
744
|
+
msg.extra,
|
|
745
|
+
);
|
|
736
746
|
const rowMessage: Message = {
|
|
737
747
|
authorID: msg.authorID,
|
|
738
748
|
decrypted: decryptedFlag,
|
|
@@ -740,7 +750,7 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
740
750
|
forward: msg.forward !== 0,
|
|
741
751
|
group: msg.group,
|
|
742
752
|
mailID: msg.mailID,
|
|
743
|
-
|
|
753
|
+
...storedPlaintext,
|
|
744
754
|
nonce: msg.nonce,
|
|
745
755
|
readerID: msg.readerID,
|
|
746
756
|
recipient: msg.recipient,
|
|
@@ -773,6 +783,16 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
773
783
|
};
|
|
774
784
|
}
|
|
775
785
|
|
|
786
|
+
private async ensureMessageExtraColumn(): Promise<void> {
|
|
787
|
+
try {
|
|
788
|
+
await sql
|
|
789
|
+
.raw("ALTER TABLE messages ADD COLUMN extra text")
|
|
790
|
+
.execute(this.db);
|
|
791
|
+
} catch {
|
|
792
|
+
// Existing databases may already have this column.
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
776
796
|
/** Speeds up mailID existence checks for saveMessage deduplication. */
|
|
777
797
|
private async ensureMessageMailIdIndex(): Promise<void> {
|
|
778
798
|
try {
|
|
@@ -965,3 +985,61 @@ export class SqliteStorage extends EventEmitter implements Storage {
|
|
|
965
985
|
});
|
|
966
986
|
}
|
|
967
987
|
}
|
|
988
|
+
|
|
989
|
+
function decodeStoredMessagePlaintext(
|
|
990
|
+
plaintext: string,
|
|
991
|
+
rowExtra: null | string,
|
|
992
|
+
): Pick<Message, "extra" | "message"> {
|
|
993
|
+
if (!plaintext.startsWith(STORAGE_MESSAGE_BLOB_PREFIX)) {
|
|
994
|
+
return {
|
|
995
|
+
...(rowExtra !== null ? { extra: rowExtra } : {}),
|
|
996
|
+
message: plaintext,
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
try {
|
|
1001
|
+
const raw = JSON.parse(
|
|
1002
|
+
plaintext.slice(STORAGE_MESSAGE_BLOB_PREFIX.length),
|
|
1003
|
+
) as unknown;
|
|
1004
|
+
if (!isJsonRecord(raw)) {
|
|
1005
|
+
return {
|
|
1006
|
+
...(rowExtra !== null ? { extra: rowExtra } : {}),
|
|
1007
|
+
message: plaintext,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
const message = raw["message"];
|
|
1011
|
+
if (typeof message !== "string") {
|
|
1012
|
+
return {
|
|
1013
|
+
...(rowExtra !== null ? { extra: rowExtra } : {}),
|
|
1014
|
+
message: plaintext,
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
const extra = raw["extra"];
|
|
1018
|
+
return {
|
|
1019
|
+
...(extra === null || typeof extra === "string" ? { extra } : {}),
|
|
1020
|
+
message,
|
|
1021
|
+
};
|
|
1022
|
+
} catch {
|
|
1023
|
+
return {
|
|
1024
|
+
...(rowExtra !== null ? { extra: rowExtra } : {}),
|
|
1025
|
+
message: plaintext,
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function encodeStoredMessagePlaintext(message: Message): string {
|
|
1031
|
+
if (message.extra === undefined) {
|
|
1032
|
+
return message.message;
|
|
1033
|
+
}
|
|
1034
|
+
return (
|
|
1035
|
+
STORAGE_MESSAGE_BLOB_PREFIX +
|
|
1036
|
+
JSON.stringify({
|
|
1037
|
+
extra: message.extra,
|
|
1038
|
+
message: message.message,
|
|
1039
|
+
})
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function isJsonRecord(value: unknown): value is Record<string, unknown> {
|
|
1044
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1045
|
+
}
|