@vex-chat/libvex 1.1.0 → 4.0.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.
Files changed (149) hide show
  1. package/README.md +104 -41
  2. package/dist/Client.d.ts +473 -560
  3. package/dist/Client.d.ts.map +1 -0
  4. package/dist/Client.js +1486 -1551
  5. package/dist/Client.js.map +1 -1
  6. package/dist/Storage.d.ts +111 -0
  7. package/dist/Storage.d.ts.map +1 -0
  8. package/dist/Storage.js +2 -0
  9. package/dist/Storage.js.map +1 -0
  10. package/dist/__tests__/harness/memory-storage.d.ts +29 -27
  11. package/dist/__tests__/harness/memory-storage.d.ts.map +1 -0
  12. package/dist/__tests__/harness/memory-storage.js +120 -109
  13. package/dist/__tests__/harness/memory-storage.js.map +1 -1
  14. package/dist/codec.d.ts +44 -0
  15. package/dist/codec.d.ts.map +1 -0
  16. package/dist/codec.js +51 -0
  17. package/dist/codec.js.map +1 -0
  18. package/dist/codecs.d.ts +201 -0
  19. package/dist/codecs.d.ts.map +1 -0
  20. package/dist/codecs.js +67 -0
  21. package/dist/codecs.js.map +1 -0
  22. package/dist/index.d.ts +6 -5
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +1 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/keystore/memory.d.ts +5 -4
  27. package/dist/keystore/memory.d.ts.map +1 -0
  28. package/dist/keystore/memory.js +9 -7
  29. package/dist/keystore/memory.js.map +1 -1
  30. package/dist/keystore/node.d.ts +8 -6
  31. package/dist/keystore/node.d.ts.map +1 -0
  32. package/dist/keystore/node.js +47 -22
  33. package/dist/keystore/node.js.map +1 -1
  34. package/dist/preset/common.d.ts +7 -0
  35. package/dist/preset/common.d.ts.map +1 -0
  36. package/dist/preset/common.js +2 -0
  37. package/dist/preset/common.js.map +1 -0
  38. package/dist/preset/node.d.ts +4 -7
  39. package/dist/preset/node.d.ts.map +1 -0
  40. package/dist/preset/node.js +4 -11
  41. package/dist/preset/node.js.map +1 -1
  42. package/dist/preset/test.d.ts +4 -5
  43. package/dist/preset/test.d.ts.map +1 -0
  44. package/dist/preset/test.js +3 -20
  45. package/dist/preset/test.js.map +1 -1
  46. package/dist/storage/node.d.ts +3 -3
  47. package/dist/storage/node.d.ts.map +1 -0
  48. package/dist/storage/node.js +4 -10
  49. package/dist/storage/node.js.map +1 -1
  50. package/dist/storage/schema.d.ts +55 -54
  51. package/dist/storage/schema.d.ts.map +1 -0
  52. package/dist/storage/sqlite.d.ts +41 -28
  53. package/dist/storage/sqlite.d.ts.map +1 -0
  54. package/dist/storage/sqlite.js +339 -297
  55. package/dist/storage/sqlite.js.map +1 -1
  56. package/dist/transport/types.d.ts +17 -16
  57. package/dist/transport/types.d.ts.map +1 -0
  58. package/dist/transport/websocket.d.ts +26 -0
  59. package/dist/transport/websocket.d.ts.map +1 -0
  60. package/dist/transport/websocket.js +83 -0
  61. package/dist/transport/websocket.js.map +1 -0
  62. package/dist/types/crypto.d.ts +38 -0
  63. package/dist/types/crypto.d.ts.map +1 -0
  64. package/dist/types/crypto.js +9 -0
  65. package/dist/types/crypto.js.map +1 -0
  66. package/dist/types/identity.d.ts +22 -0
  67. package/dist/types/identity.d.ts.map +1 -0
  68. package/dist/types/identity.js +6 -0
  69. package/dist/types/identity.js.map +1 -0
  70. package/dist/types/index.d.ts +3 -0
  71. package/dist/types/index.d.ts.map +1 -0
  72. package/dist/types/index.js +2 -0
  73. package/dist/types/index.js.map +1 -0
  74. package/dist/utils/capitalize.d.ts +1 -0
  75. package/dist/utils/capitalize.d.ts.map +1 -0
  76. package/dist/utils/formatBytes.d.ts +1 -0
  77. package/dist/utils/formatBytes.d.ts.map +1 -0
  78. package/dist/utils/formatBytes.js +3 -1
  79. package/dist/utils/formatBytes.js.map +1 -1
  80. package/dist/utils/sqlSessionToCrypto.d.ts +4 -2
  81. package/dist/utils/sqlSessionToCrypto.d.ts.map +1 -0
  82. package/dist/utils/sqlSessionToCrypto.js +5 -5
  83. package/dist/utils/sqlSessionToCrypto.js.map +1 -1
  84. package/dist/utils/uint8uuid.d.ts +1 -4
  85. package/dist/utils/uint8uuid.d.ts.map +1 -0
  86. package/dist/utils/uint8uuid.js +1 -7
  87. package/dist/utils/uint8uuid.js.map +1 -1
  88. package/package.json +74 -91
  89. package/src/Client.ts +3086 -0
  90. package/{dist/IStorage.d.ts → src/Storage.ts} +70 -62
  91. package/src/__tests__/codec.test.ts +256 -0
  92. package/src/__tests__/ghost.png +0 -0
  93. package/src/__tests__/harness/fixtures.ts +22 -0
  94. package/src/__tests__/harness/memory-storage.ts +254 -0
  95. package/src/__tests__/harness/platform-transports.ts +4 -0
  96. package/src/__tests__/harness/poison-node-imports.ts +107 -0
  97. package/src/__tests__/harness/shared-suite.ts +426 -0
  98. package/src/__tests__/platform-browser.test.ts +14 -0
  99. package/src/__tests__/platform-node.test.ts +9 -0
  100. package/src/__tests__/triggered.png +0 -0
  101. package/src/codec.ts +68 -0
  102. package/src/codecs.ts +101 -0
  103. package/src/index.ts +40 -0
  104. package/src/keystore/memory.ts +30 -0
  105. package/src/keystore/node.ts +102 -0
  106. package/src/preset/common.ts +7 -0
  107. package/src/preset/node.ts +18 -0
  108. package/src/preset/test.ts +20 -0
  109. package/src/storage/node.ts +22 -0
  110. package/src/storage/schema.ts +94 -0
  111. package/src/storage/sqlite.ts +655 -0
  112. package/src/transport/types.ts +22 -0
  113. package/src/transport/websocket.ts +106 -0
  114. package/src/types/crypto.ts +42 -0
  115. package/src/types/identity.ts +23 -0
  116. package/src/types/index.ts +9 -0
  117. package/src/utils/capitalize.ts +6 -0
  118. package/src/utils/formatBytes.ts +15 -0
  119. package/src/utils/sqlSessionToCrypto.ts +16 -0
  120. package/src/utils/uint8uuid.ts +7 -0
  121. package/dist/IStorage.js +0 -2
  122. package/dist/IStorage.js.map +0 -1
  123. package/dist/keystore/types.d.ts +0 -4
  124. package/dist/keystore/types.js +0 -2
  125. package/dist/keystore/types.js.map +0 -1
  126. package/dist/preset/expo.d.ts +0 -2
  127. package/dist/preset/expo.js +0 -39
  128. package/dist/preset/expo.js.map +0 -1
  129. package/dist/preset/tauri.d.ts +0 -2
  130. package/dist/preset/tauri.js +0 -36
  131. package/dist/preset/tauri.js.map +0 -1
  132. package/dist/preset/types.d.ts +0 -14
  133. package/dist/preset/types.js +0 -2
  134. package/dist/preset/types.js.map +0 -1
  135. package/dist/storage/expo.d.ts +0 -3
  136. package/dist/storage/expo.js +0 -18
  137. package/dist/storage/expo.js.map +0 -1
  138. package/dist/storage/tauri.d.ts +0 -3
  139. package/dist/storage/tauri.js +0 -21
  140. package/dist/storage/tauri.js.map +0 -1
  141. package/dist/transport/browser.d.ts +0 -17
  142. package/dist/transport/browser.js +0 -56
  143. package/dist/transport/browser.js.map +0 -1
  144. package/dist/utils/constants.d.ts +0 -8
  145. package/dist/utils/constants.js +0 -9
  146. package/dist/utils/constants.js.map +0 -1
  147. package/dist/utils/createLogger.d.ts +0 -5
  148. package/dist/utils/createLogger.js +0 -27
  149. package/dist/utils/createLogger.js.map +0 -1
@@ -0,0 +1,102 @@
1
+ import type { KeyStore, StoredCredentials } from "../types/index.js";
2
+
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+
6
+ /**
7
+ * File-backed KeyStore for Node.js (CLI tools, bots, integration tests).
8
+ *
9
+ * Stores credentials as encrypted files on disk using XUtils.encryptKeyData.
10
+ * Node-only — imports node:fs.
11
+ */
12
+ import { XUtils } from "@vex-chat/crypto";
13
+
14
+ export class NodeKeyStore implements KeyStore {
15
+ private readonly dir: string;
16
+ private readonly passphrase: string;
17
+
18
+ constructor(passphrase: string, dir: string = ".") {
19
+ if (!passphrase) {
20
+ throw new Error(
21
+ "NodeKeyStore requires a non-empty passphrase. " +
22
+ "The caller must supply a passphrase sourced from user input, OS keychain, or similar.",
23
+ );
24
+ }
25
+ this.passphrase = passphrase;
26
+ this.dir = dir;
27
+ }
28
+
29
+ clear(username: string): Promise<void> {
30
+ try {
31
+ fs.unlinkSync(this.filePath(username));
32
+ } catch {
33
+ // File may not exist
34
+ }
35
+ return Promise.resolve();
36
+ }
37
+
38
+ load(username?: string): Promise<null | StoredCredentials> {
39
+ if (username) {
40
+ return Promise.resolve(this.readFile(this.filePath(username)));
41
+ }
42
+ // Find most recent .vex file in the directory
43
+ try {
44
+ const files = fs
45
+ .readdirSync(this.dir)
46
+ .filter((f) => f.endsWith(".vex"))
47
+ .map((f) => ({
48
+ mtime: fs.statSync(path.join(this.dir, f)).mtimeMs,
49
+ name: f,
50
+ }))
51
+ .sort((a, b) => b.mtime - a.mtime);
52
+ if (files.length === 0) return Promise.resolve(null);
53
+ const newest = files[0];
54
+ if (!newest) return Promise.resolve(null);
55
+ return Promise.resolve(
56
+ this.readFile(path.join(this.dir, newest.name)),
57
+ );
58
+ } catch {
59
+ return Promise.resolve(null);
60
+ }
61
+ }
62
+
63
+ save(creds: StoredCredentials): Promise<void> {
64
+ const data = JSON.stringify(creds);
65
+ const encrypted = XUtils.encryptKeyData(this.passphrase, data);
66
+ fs.writeFileSync(this.filePath(creds.username), encrypted);
67
+ return Promise.resolve();
68
+ }
69
+
70
+ private filePath(username: string): string {
71
+ return path.join(this.dir, `${username}.vex`);
72
+ }
73
+
74
+ private readFile(filePath: string): null | StoredCredentials {
75
+ try {
76
+ const data = fs.readFileSync(filePath);
77
+ const decrypted = XUtils.decryptKeyData(
78
+ new Uint8Array(data),
79
+ this.passphrase,
80
+ );
81
+ const parsed: unknown = JSON.parse(decrypted);
82
+ if (isStoredCredentials(parsed)) {
83
+ return parsed;
84
+ }
85
+ return null;
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+ }
91
+
92
+ function isStoredCredentials(value: unknown): value is StoredCredentials {
93
+ if (typeof value !== "object" || value === null) return false;
94
+ return (
95
+ "username" in value &&
96
+ typeof value.username === "string" &&
97
+ "deviceID" in value &&
98
+ typeof value.deviceID === "string" &&
99
+ "deviceKey" in value &&
100
+ typeof value.deviceKey === "string"
101
+ );
102
+ }
@@ -0,0 +1,7 @@
1
+ import type { Storage } from "../Storage.js";
2
+
3
+ /** Internal preset interface used by nodePreset and testPreset. */
4
+ export interface PlatformPreset {
5
+ createStorage(dbName: string, privateKey: string): Promise<Storage>;
6
+ deviceName: string;
7
+ }
@@ -0,0 +1,18 @@
1
+ import type { Storage } from "../Storage.js";
2
+ /**
3
+ * Platform preset for Node.js (CLI tools, bots, tests).
4
+ *
5
+ * - WebSocket: native global (Node 22+)
6
+ * - Storage: Kysely + better-sqlite3
7
+ */
8
+ import type { PlatformPreset } from "./common.js";
9
+
10
+ export function nodePreset(): PlatformPreset {
11
+ return {
12
+ async createStorage(dbName, privateKey): Promise<Storage> {
13
+ const { createNodeStorage } = await import("../storage/node.js");
14
+ return createNodeStorage(dbName, privateKey);
15
+ },
16
+ deviceName: process.platform,
17
+ };
18
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Platform preset for tests — no I/O, no platform dependencies.
3
+ *
4
+ * - WebSocket: native global (Node 22+)
5
+ * - Storage: in-memory (no persistence)
6
+ */
7
+ import type { PlatformPreset } from "./common.js";
8
+
9
+ export function testPreset(): PlatformPreset {
10
+ return {
11
+ async createStorage(_dbName, privateKey) {
12
+ const { MemoryStorage } =
13
+ await import("../__tests__/harness/memory-storage.js");
14
+ const storage = new MemoryStorage(privateKey);
15
+ await storage.init();
16
+ return storage;
17
+ },
18
+ deviceName: "test",
19
+ };
20
+ }
@@ -0,0 +1,22 @@
1
+ import type { Storage } from "../Storage.js";
2
+ import type { ClientDatabase } from "./schema.js";
3
+
4
+ import BetterSqlite3 from "better-sqlite3";
5
+ /**
6
+ * Node.js storage factory — creates SqliteStorage with better-sqlite3 dialect.
7
+ * Node-only — imports better-sqlite3 which is a native addon.
8
+ */
9
+ import { Kysely, SqliteDialect } from "kysely";
10
+
11
+ import { SqliteStorage } from "./sqlite.js";
12
+
13
+ export function createNodeStorage(dbPath: string, SK: string): Storage {
14
+ const db = new Kysely<ClientDatabase>({
15
+ dialect: new SqliteDialect({
16
+ database: new BetterSqlite3(dbPath),
17
+ }),
18
+ });
19
+ const storage = new SqliteStorage(db, SK);
20
+ void storage.init();
21
+ return storage;
22
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Kysely typed table interfaces for the client-side SQLite database.
3
+ */
4
+ import type {
5
+ ColumnType,
6
+ Generated,
7
+ Insertable,
8
+ Selectable,
9
+ Updateable,
10
+ } from "kysely";
11
+
12
+ export interface ClientDatabase {
13
+ devices: DevicesTable;
14
+ messages: MessagesTable;
15
+ oneTimeKeys: OneTimeKeysTable;
16
+ preKeys: PreKeysTable;
17
+ sessions: SessionsTable;
18
+ }
19
+
20
+ export type DeviceRow = Selectable<DevicesTable>;
21
+
22
+ export type DeviceUpdate = Updateable<DevicesTable>;
23
+
24
+ export type MessageRow = Selectable<MessagesTable>;
25
+
26
+ export type MessageUpdate = Updateable<MessagesTable>;
27
+
28
+ export type NewDevice = Insertable<DevicesTable>;
29
+
30
+ // ── Row utility types ────────────────────────────────────────────────────────
31
+
32
+ export type NewMessage = Insertable<MessagesTable>;
33
+ export type NewOneTimeKey = Insertable<OneTimeKeysTable>;
34
+ export type NewPreKey = Insertable<PreKeysTable>;
35
+
36
+ export type NewSession = Insertable<SessionsTable>;
37
+ export type OneTimeKeyRow = Selectable<OneTimeKeysTable>;
38
+ export type PreKeyRow = Selectable<PreKeysTable>;
39
+
40
+ export type SessionRow = Selectable<SessionsTable>;
41
+ export type SessionUpdate = Updateable<SessionsTable>;
42
+ interface DevicesTable {
43
+ deleted: number;
44
+ deviceID: string;
45
+ lastLogin: string;
46
+ name: string;
47
+ owner: string;
48
+ signKey: string;
49
+ }
50
+
51
+ interface MessagesTable {
52
+ authorID: string;
53
+ decrypted: number;
54
+ direction: string;
55
+ forward: number;
56
+ group: null | string;
57
+ mailID: string;
58
+ message: string;
59
+ nonce: string;
60
+ readerID: string;
61
+ recipient: string;
62
+ sender: string;
63
+ timestamp: string;
64
+ }
65
+ interface OneTimeKeysTable {
66
+ deviceID: ColumnType<string, string | undefined, string>;
67
+ index: Generated<number>;
68
+ keyID: ColumnType<string, string | undefined, string>;
69
+ privateKey: string;
70
+ publicKey: string;
71
+ signature: string;
72
+ userID: ColumnType<string, string | undefined, string>;
73
+ }
74
+
75
+ interface PreKeysTable {
76
+ deviceID: ColumnType<string, string | undefined, string>;
77
+ index: Generated<number>;
78
+ keyID: ColumnType<string, string | undefined, string>;
79
+ privateKey: string;
80
+ publicKey: string;
81
+ signature: string;
82
+ userID: ColumnType<string, string | undefined, string>;
83
+ }
84
+ interface SessionsTable {
85
+ deviceID: string;
86
+ fingerprint: string;
87
+ lastUsed: string;
88
+ mode: string;
89
+ publicKey: string;
90
+ sessionID: string;
91
+ SK: string;
92
+ userID: string;
93
+ verified: number;
94
+ }