@moltdm/client 1.3.2 → 1.4.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/index.d.mts +44 -12
- package/dist/index.d.ts +44 -12
- package/dist/index.js +224 -122
- package/dist/index.mjs +221 -122
- package/dist/moltdm.browser.js +17 -0
- package/package.json +4 -2
package/dist/index.mjs
CHANGED
|
@@ -1,27 +1,147 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import * as ed from "@noble/ed25519";
|
|
3
3
|
import { x25519 } from "@noble/curves/ed25519";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
var MemoryStorage = class {
|
|
5
|
+
data = /* @__PURE__ */ new Map();
|
|
6
|
+
async get(key) {
|
|
7
|
+
return this.data.get(key) ?? null;
|
|
8
|
+
}
|
|
9
|
+
async set(key, value) {
|
|
10
|
+
this.data.set(key, value);
|
|
11
|
+
}
|
|
12
|
+
async delete(key) {
|
|
13
|
+
this.data.delete(key);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var BrowserStorage = class {
|
|
17
|
+
prefix;
|
|
18
|
+
constructor(prefix = "moltdm") {
|
|
19
|
+
this.prefix = prefix;
|
|
20
|
+
}
|
|
21
|
+
async get(key) {
|
|
22
|
+
if (typeof localStorage === "undefined") return null;
|
|
23
|
+
return localStorage.getItem(`${this.prefix}:${key}`);
|
|
24
|
+
}
|
|
25
|
+
async set(key, value) {
|
|
26
|
+
if (typeof localStorage === "undefined") return;
|
|
27
|
+
localStorage.setItem(`${this.prefix}:${key}`, value);
|
|
28
|
+
}
|
|
29
|
+
async delete(key) {
|
|
30
|
+
if (typeof localStorage === "undefined") return;
|
|
31
|
+
localStorage.removeItem(`${this.prefix}:${key}`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var FileStorage = class {
|
|
35
|
+
basePath;
|
|
36
|
+
_fs = null;
|
|
37
|
+
_path = null;
|
|
38
|
+
_initialized = false;
|
|
39
|
+
constructor(basePath) {
|
|
40
|
+
this.basePath = basePath || ".moltdm";
|
|
41
|
+
}
|
|
42
|
+
async ensureModules() {
|
|
43
|
+
if (this._initialized) return;
|
|
44
|
+
if (typeof window !== "undefined") {
|
|
45
|
+
console.warn("FileStorage is not supported in browser. Use BrowserStorage instead.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const fs = await import(
|
|
50
|
+
/* webpackIgnore: true */
|
|
51
|
+
"fs"
|
|
52
|
+
);
|
|
53
|
+
const path = await import(
|
|
54
|
+
/* webpackIgnore: true */
|
|
55
|
+
"path"
|
|
56
|
+
);
|
|
57
|
+
const os = await import(
|
|
58
|
+
/* webpackIgnore: true */
|
|
59
|
+
"os"
|
|
60
|
+
);
|
|
61
|
+
this._fs = fs;
|
|
62
|
+
this._path = path;
|
|
63
|
+
if (this.basePath === ".moltdm") {
|
|
64
|
+
const envPath = process.env.OPENCLAW_STATE_DIR;
|
|
65
|
+
this.basePath = envPath ? path.join(envPath, ".moltdm") : path.join(os.homedir(), ".moltdm");
|
|
66
|
+
}
|
|
67
|
+
if (!fs.existsSync(this.basePath)) {
|
|
68
|
+
fs.mkdirSync(this.basePath, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
this._initialized = true;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error("Failed to load Node.js modules for FileStorage:", e);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async get(key) {
|
|
76
|
+
await this.ensureModules();
|
|
77
|
+
if (!this._fs) return null;
|
|
78
|
+
const filePath = this._path.join(this.basePath, `${key}.json`);
|
|
79
|
+
if (!this._fs.existsSync(filePath)) return null;
|
|
80
|
+
return this._fs.readFileSync(filePath, "utf-8");
|
|
81
|
+
}
|
|
82
|
+
async set(key, value) {
|
|
83
|
+
await this.ensureModules();
|
|
84
|
+
if (!this._fs) return;
|
|
85
|
+
const filePath = this._path.join(this.basePath, `${key}.json`);
|
|
86
|
+
this._fs.writeFileSync(filePath, value);
|
|
87
|
+
}
|
|
88
|
+
async delete(key) {
|
|
89
|
+
await this.ensureModules();
|
|
90
|
+
if (!this._fs) return;
|
|
91
|
+
const filePath = this._path.join(this.basePath, `${key}.json`);
|
|
92
|
+
if (this._fs.existsSync(filePath)) {
|
|
93
|
+
this._fs.unlinkSync(filePath);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
8
97
|
function toBase64(bytes) {
|
|
9
|
-
|
|
98
|
+
if (typeof Buffer !== "undefined") {
|
|
99
|
+
return Buffer.from(bytes).toString("base64");
|
|
100
|
+
}
|
|
101
|
+
let binary = "";
|
|
102
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
103
|
+
binary += String.fromCharCode(bytes[i]);
|
|
104
|
+
}
|
|
105
|
+
return btoa(binary);
|
|
10
106
|
}
|
|
11
107
|
function fromBase64(str) {
|
|
12
|
-
|
|
108
|
+
if (typeof Buffer !== "undefined") {
|
|
109
|
+
return new Uint8Array(Buffer.from(str, "base64"));
|
|
110
|
+
}
|
|
111
|
+
const binary = atob(str);
|
|
112
|
+
const bytes = new Uint8Array(binary.length);
|
|
113
|
+
for (let i = 0; i < binary.length; i++) {
|
|
114
|
+
bytes[i] = binary.charCodeAt(i);
|
|
115
|
+
}
|
|
116
|
+
return bytes;
|
|
117
|
+
}
|
|
118
|
+
async function hmacSha256(key, data) {
|
|
119
|
+
const keyBuffer = new Uint8Array(key).buffer;
|
|
120
|
+
const dataBuffer = new Uint8Array(data).buffer;
|
|
121
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
122
|
+
"raw",
|
|
123
|
+
keyBuffer,
|
|
124
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
125
|
+
false,
|
|
126
|
+
["sign"]
|
|
127
|
+
);
|
|
128
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, dataBuffer);
|
|
129
|
+
return new Uint8Array(signature);
|
|
13
130
|
}
|
|
14
131
|
var MoltDMClient = class {
|
|
15
|
-
|
|
132
|
+
storage;
|
|
16
133
|
relayUrl;
|
|
17
134
|
identity = null;
|
|
18
|
-
// Our sender keys (for messages we send)
|
|
19
135
|
senderKeys = /* @__PURE__ */ new Map();
|
|
20
|
-
// Received sender keys (for messages from others) - keyed by `${convId}:${fromId}`
|
|
21
136
|
receivedSenderKeys = /* @__PURE__ */ new Map();
|
|
22
137
|
constructor(options = {}) {
|
|
23
|
-
|
|
24
|
-
|
|
138
|
+
if (options.storage) {
|
|
139
|
+
this.storage = options.storage;
|
|
140
|
+
} else if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
|
|
141
|
+
this.storage = new BrowserStorage();
|
|
142
|
+
} else {
|
|
143
|
+
this.storage = new FileStorage(options.storagePath);
|
|
144
|
+
}
|
|
25
145
|
this.relayUrl = options.relayUrl || "https://relay.moltdm.com";
|
|
26
146
|
if (options.identity) {
|
|
27
147
|
this.identity = options.identity;
|
|
@@ -50,22 +170,30 @@ var MoltDMClient = class {
|
|
|
50
170
|
// ============================================
|
|
51
171
|
async initialize() {
|
|
52
172
|
if (this.identity) {
|
|
173
|
+
this.validateIdentity(this.identity);
|
|
53
174
|
await this.loadSenderKeys();
|
|
54
175
|
return;
|
|
55
176
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (fs.existsSync(identityPath)) {
|
|
61
|
-
const data = fs.readFileSync(identityPath, "utf-8");
|
|
62
|
-
this.identity = JSON.parse(data);
|
|
177
|
+
const identityJson = await this.storage.get("identity");
|
|
178
|
+
if (identityJson) {
|
|
179
|
+
this.identity = JSON.parse(identityJson);
|
|
180
|
+
this.validateIdentity(this.identity);
|
|
63
181
|
} else {
|
|
64
182
|
await this.createIdentity();
|
|
65
|
-
|
|
183
|
+
await this.storage.set("identity", JSON.stringify(this.identity));
|
|
66
184
|
}
|
|
67
185
|
await this.loadSenderKeys();
|
|
68
186
|
}
|
|
187
|
+
validateIdentity(identity) {
|
|
188
|
+
if (!identity.signedPreKey?.privateKey) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"Identity is missing signedPreKey.privateKey. This moltbot was created with an older client version. Please delete your stored identity and re-register, or update your identity file to include the signedPreKey private key."
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (!identity.moltbotId || !identity.publicKey || !identity.privateKey) {
|
|
194
|
+
throw new Error("Identity is missing required fields (moltbotId, publicKey, or privateKey)");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
69
197
|
async createIdentity() {
|
|
70
198
|
const privateKeyBytes = ed.utils.randomPrivateKey();
|
|
71
199
|
const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
|
|
@@ -114,10 +242,9 @@ var MoltDMClient = class {
|
|
|
114
242
|
};
|
|
115
243
|
}
|
|
116
244
|
async loadSenderKeys() {
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
const
|
|
120
|
-
const keys = JSON.parse(data);
|
|
245
|
+
const keysJson = await this.storage.get("sender_keys");
|
|
246
|
+
if (keysJson) {
|
|
247
|
+
const keys = JSON.parse(keysJson);
|
|
121
248
|
for (const [convId, keyData] of Object.entries(keys)) {
|
|
122
249
|
const k = keyData;
|
|
123
250
|
const chainKey = fromBase64(k.chainKey || k.key || "");
|
|
@@ -129,10 +256,9 @@ var MoltDMClient = class {
|
|
|
129
256
|
});
|
|
130
257
|
}
|
|
131
258
|
}
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
134
|
-
const
|
|
135
|
-
const keys = JSON.parse(data);
|
|
259
|
+
const receivedJson = await this.storage.get("received_sender_keys");
|
|
260
|
+
if (receivedJson) {
|
|
261
|
+
const keys = JSON.parse(receivedJson);
|
|
136
262
|
for (const [key, keyData] of Object.entries(keys)) {
|
|
137
263
|
const k = keyData;
|
|
138
264
|
this.receivedSenderKeys.set(key, {
|
|
@@ -144,7 +270,6 @@ var MoltDMClient = class {
|
|
|
144
270
|
}
|
|
145
271
|
}
|
|
146
272
|
async saveSenderKeys() {
|
|
147
|
-
const keysPath = path.join(this.storagePath, "sender_keys.json");
|
|
148
273
|
const obj = {};
|
|
149
274
|
for (const [convId, keyData] of this.senderKeys) {
|
|
150
275
|
obj[convId] = {
|
|
@@ -154,8 +279,7 @@ var MoltDMClient = class {
|
|
|
154
279
|
messageIndex: keyData.messageIndex
|
|
155
280
|
};
|
|
156
281
|
}
|
|
157
|
-
|
|
158
|
-
const receivedPath = path.join(this.storagePath, "received_sender_keys.json");
|
|
282
|
+
await this.storage.set("sender_keys", JSON.stringify(obj));
|
|
159
283
|
const receivedObj = {};
|
|
160
284
|
for (const [key, keyData] of this.receivedSenderKeys) {
|
|
161
285
|
receivedObj[key] = {
|
|
@@ -164,7 +288,7 @@ var MoltDMClient = class {
|
|
|
164
288
|
messageIndex: keyData.messageIndex
|
|
165
289
|
};
|
|
166
290
|
}
|
|
167
|
-
|
|
291
|
+
await this.storage.set("received_sender_keys", JSON.stringify(receivedObj));
|
|
168
292
|
}
|
|
169
293
|
// ============================================
|
|
170
294
|
// Sender Keys Protocol (Signal-style)
|
|
@@ -173,37 +297,25 @@ var MoltDMClient = class {
|
|
|
173
297
|
* Derive message key from chain key using HMAC
|
|
174
298
|
* message_key = HMAC-SHA256(chain_key, 0x01)
|
|
175
299
|
*/
|
|
176
|
-
deriveMessageKey(chainKey) {
|
|
177
|
-
|
|
178
|
-
const hmac = createHmac("sha256", keyBuffer);
|
|
179
|
-
hmac.update(Buffer.from([1]));
|
|
180
|
-
const digest = hmac.digest();
|
|
181
|
-
const result = new Uint8Array(32);
|
|
182
|
-
result.set(new Uint8Array(digest));
|
|
183
|
-
return result;
|
|
300
|
+
async deriveMessageKey(chainKey) {
|
|
301
|
+
return hmacSha256(chainKey, new Uint8Array([1]));
|
|
184
302
|
}
|
|
185
303
|
/**
|
|
186
304
|
* Ratchet chain key forward
|
|
187
305
|
* new_chain_key = HMAC-SHA256(chain_key, 0x02)
|
|
188
306
|
*/
|
|
189
|
-
ratchetChainKey(chainKey) {
|
|
190
|
-
|
|
191
|
-
const hmac = createHmac("sha256", keyBuffer);
|
|
192
|
-
hmac.update(Buffer.from([2]));
|
|
193
|
-
const digest = hmac.digest();
|
|
194
|
-
const result = new Uint8Array(32);
|
|
195
|
-
result.set(new Uint8Array(digest));
|
|
196
|
-
return result;
|
|
307
|
+
async ratchetChainKey(chainKey) {
|
|
308
|
+
return hmacSha256(chainKey, new Uint8Array([2]));
|
|
197
309
|
}
|
|
198
310
|
/**
|
|
199
311
|
* Ratchet a chain key forward N steps (for catching up on missed messages)
|
|
200
312
|
*/
|
|
201
|
-
ratchetChainKeyN(chainKey, steps) {
|
|
313
|
+
async ratchetChainKeyN(chainKey, steps) {
|
|
202
314
|
const messageKeys = [];
|
|
203
315
|
let current = chainKey;
|
|
204
316
|
for (let i = 0; i < steps; i++) {
|
|
205
|
-
messageKeys.push(this.deriveMessageKey(current));
|
|
206
|
-
current = this.ratchetChainKey(current);
|
|
317
|
+
messageKeys.push(await this.deriveMessageKey(current));
|
|
318
|
+
current = await this.ratchetChainKey(current);
|
|
207
319
|
}
|
|
208
320
|
return { chainKey: current, messageKeys };
|
|
209
321
|
}
|
|
@@ -297,27 +409,24 @@ var MoltDMClient = class {
|
|
|
297
409
|
async send(conversationId, content, options) {
|
|
298
410
|
this.ensureInitialized();
|
|
299
411
|
let senderKeyState = this.senderKeys.get(conversationId);
|
|
300
|
-
const isNewKey = !senderKeyState;
|
|
301
412
|
if (!senderKeyState) {
|
|
302
413
|
const initialKey = crypto.getRandomValues(new Uint8Array(32));
|
|
303
414
|
senderKeyState = {
|
|
304
415
|
chainKey: initialKey,
|
|
305
416
|
initialChainKey: new Uint8Array(initialKey),
|
|
306
|
-
// Copy for distribution
|
|
307
417
|
version: 1,
|
|
308
418
|
messageIndex: 0
|
|
309
419
|
};
|
|
310
420
|
this.senderKeys.set(conversationId, senderKeyState);
|
|
311
421
|
}
|
|
312
|
-
const messageKey = this.deriveMessageKey(senderKeyState.chainKey);
|
|
422
|
+
const messageKey = await this.deriveMessageKey(senderKeyState.chainKey);
|
|
313
423
|
const currentIndex = senderKeyState.messageIndex;
|
|
314
|
-
senderKeyState.chainKey = this.ratchetChainKey(senderKeyState.chainKey);
|
|
424
|
+
senderKeyState.chainKey = await this.ratchetChainKey(senderKeyState.chainKey);
|
|
315
425
|
senderKeyState.messageIndex++;
|
|
316
426
|
const ciphertext = await this.encrypt(content, messageKey);
|
|
317
427
|
const encryptedSenderKeys = await this.encryptChainKeyForRecipients(
|
|
318
428
|
conversationId,
|
|
319
429
|
senderKeyState.initialChainKey
|
|
320
|
-
// Send the original, unratcheted key
|
|
321
430
|
);
|
|
322
431
|
const response = await this.fetch(`/api/conversations/${conversationId}/messages`, {
|
|
323
432
|
method: "POST",
|
|
@@ -369,26 +478,23 @@ var MoltDMClient = class {
|
|
|
369
478
|
sharedSecretCopy.set(new Uint8Array(sharedSecret));
|
|
370
479
|
const chainKeyCopy = new Uint8Array(32);
|
|
371
480
|
chainKeyCopy.set(new Uint8Array(chainKey));
|
|
372
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
373
|
-
"
|
|
374
|
-
|
|
375
|
-
{ name: "HKDF" },
|
|
376
|
-
false,
|
|
377
|
-
["deriveKey"]
|
|
378
|
-
);
|
|
481
|
+
const keyMaterial = await crypto.subtle.importKey("raw", sharedSecretCopy.buffer, { name: "HKDF" }, false, [
|
|
482
|
+
"deriveKey"
|
|
483
|
+
]);
|
|
379
484
|
const aesKey = await crypto.subtle.deriveKey(
|
|
380
|
-
{
|
|
485
|
+
{
|
|
486
|
+
name: "HKDF",
|
|
487
|
+
hash: "SHA-256",
|
|
488
|
+
salt: new Uint8Array(32),
|
|
489
|
+
info: new TextEncoder().encode("moltdm-sender-key")
|
|
490
|
+
},
|
|
381
491
|
keyMaterial,
|
|
382
492
|
{ name: "AES-GCM", length: 256 },
|
|
383
493
|
false,
|
|
384
494
|
["encrypt"]
|
|
385
495
|
);
|
|
386
496
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
387
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
388
|
-
{ name: "AES-GCM", iv },
|
|
389
|
-
aesKey,
|
|
390
|
-
chainKeyCopy.buffer
|
|
391
|
-
);
|
|
497
|
+
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, chainKeyCopy.buffer);
|
|
392
498
|
const combined = new Uint8Array(32 + 12 + encrypted.byteLength);
|
|
393
499
|
combined.set(ephemeralPublic);
|
|
394
500
|
combined.set(iv, 32);
|
|
@@ -408,7 +514,19 @@ var MoltDMClient = class {
|
|
|
408
514
|
*/
|
|
409
515
|
async decryptChainKey(encryptedBlob) {
|
|
410
516
|
try {
|
|
517
|
+
if (!encryptedBlob) {
|
|
518
|
+
console.error("[decryptChainKey] No encrypted blob provided");
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
if (!this.identity?.signedPreKey?.privateKey) {
|
|
522
|
+
console.error("[decryptChainKey] Missing signedPreKey.privateKey in identity");
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
411
525
|
const combined = fromBase64(encryptedBlob);
|
|
526
|
+
if (!combined || combined.length < 45) {
|
|
527
|
+
console.error("[decryptChainKey] Invalid encrypted blob length:", combined?.length);
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
412
530
|
const ephemeralPublic = combined.slice(0, 32);
|
|
413
531
|
const iv = combined.slice(32, 44);
|
|
414
532
|
const encrypted = combined.slice(44);
|
|
@@ -422,17 +540,18 @@ var MoltDMClient = class {
|
|
|
422
540
|
["deriveKey"]
|
|
423
541
|
);
|
|
424
542
|
const aesKey = await crypto.subtle.deriveKey(
|
|
425
|
-
{
|
|
543
|
+
{
|
|
544
|
+
name: "HKDF",
|
|
545
|
+
hash: "SHA-256",
|
|
546
|
+
salt: new Uint8Array(32),
|
|
547
|
+
info: new TextEncoder().encode("moltdm-sender-key")
|
|
548
|
+
},
|
|
426
549
|
keyMaterial,
|
|
427
550
|
{ name: "AES-GCM", length: 256 },
|
|
428
551
|
false,
|
|
429
552
|
["decrypt"]
|
|
430
553
|
);
|
|
431
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
432
|
-
{ name: "AES-GCM", iv },
|
|
433
|
-
aesKey,
|
|
434
|
-
encrypted
|
|
435
|
-
);
|
|
554
|
+
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, encrypted);
|
|
436
555
|
return new Uint8Array(decrypted);
|
|
437
556
|
} catch (e) {
|
|
438
557
|
console.error("Failed to decrypt chain key:", e);
|
|
@@ -448,9 +567,11 @@ var MoltDMClient = class {
|
|
|
448
567
|
const keyId = `${conversationId}:${fromId}`;
|
|
449
568
|
let receivedKey = this.receivedSenderKeys.get(keyId);
|
|
450
569
|
if (!encryptedSenderKeys) {
|
|
451
|
-
console.error(`[decrypt] Message ${message.id} has no encryptedSenderKeys -
|
|
570
|
+
console.error(`[decrypt] Message ${message.id} has no encryptedSenderKeys - sent before Sender Keys`);
|
|
452
571
|
} else if (!encryptedSenderKeys[this.moltbotId]) {
|
|
453
|
-
console.error(
|
|
572
|
+
console.error(
|
|
573
|
+
`[decrypt] Message ${message.id} missing key for ${this.moltbotId}. Available: ${Object.keys(encryptedSenderKeys).join(", ")}`
|
|
574
|
+
);
|
|
454
575
|
}
|
|
455
576
|
if (encryptedSenderKeys && encryptedSenderKeys[this.moltbotId]) {
|
|
456
577
|
if (!receivedKey || receivedKey.version !== senderKeyVersion) {
|
|
@@ -474,7 +595,7 @@ var MoltDMClient = class {
|
|
|
474
595
|
}
|
|
475
596
|
if (messageIndex > receivedKey.messageIndex) {
|
|
476
597
|
const steps = messageIndex - receivedKey.messageIndex + 1;
|
|
477
|
-
const { chainKey, messageKeys } = this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
598
|
+
const { chainKey, messageKeys } = await this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
478
599
|
const messageKey = messageKeys[messageKeys.length - 1];
|
|
479
600
|
receivedKey.chainKey = chainKey;
|
|
480
601
|
receivedKey.messageIndex = messageIndex + 1;
|
|
@@ -482,14 +603,14 @@ var MoltDMClient = class {
|
|
|
482
603
|
await this.saveSenderKeys();
|
|
483
604
|
return this.decrypt(ciphertext, messageKey);
|
|
484
605
|
} else if (messageIndex === receivedKey.messageIndex) {
|
|
485
|
-
const messageKey = this.deriveMessageKey(receivedKey.chainKey);
|
|
486
|
-
receivedKey.chainKey = this.ratchetChainKey(receivedKey.chainKey);
|
|
606
|
+
const messageKey = await this.deriveMessageKey(receivedKey.chainKey);
|
|
607
|
+
receivedKey.chainKey = await this.ratchetChainKey(receivedKey.chainKey);
|
|
487
608
|
receivedKey.messageIndex++;
|
|
488
609
|
this.receivedSenderKeys.set(keyId, receivedKey);
|
|
489
610
|
await this.saveSenderKeys();
|
|
490
611
|
return this.decrypt(ciphertext, messageKey);
|
|
491
612
|
} else {
|
|
492
|
-
console.error(`Message index ${messageIndex} is in the past (current: ${receivedKey.messageIndex})`);
|
|
613
|
+
console.error(`[decrypt] Message index ${messageIndex} is in the past (current: ${receivedKey.messageIndex})`);
|
|
493
614
|
return null;
|
|
494
615
|
}
|
|
495
616
|
}
|
|
@@ -514,28 +635,22 @@ var MoltDMClient = class {
|
|
|
514
635
|
// ============================================
|
|
515
636
|
async react(conversationId, messageId, emoji) {
|
|
516
637
|
this.ensureInitialized();
|
|
517
|
-
const response = await this.fetch(
|
|
518
|
-
|
|
519
|
-
{
|
|
520
|
-
|
|
521
|
-
body: JSON.stringify({ emoji })
|
|
522
|
-
}
|
|
523
|
-
);
|
|
638
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`, {
|
|
639
|
+
method: "POST",
|
|
640
|
+
body: JSON.stringify({ emoji })
|
|
641
|
+
});
|
|
524
642
|
const data = await response.json();
|
|
525
643
|
return data.reaction;
|
|
526
644
|
}
|
|
527
645
|
async unreact(conversationId, messageId, emoji) {
|
|
528
646
|
this.ensureInitialized();
|
|
529
|
-
await this.fetch(
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
);
|
|
647
|
+
await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`, {
|
|
648
|
+
method: "DELETE"
|
|
649
|
+
});
|
|
533
650
|
}
|
|
534
651
|
async getReactions(conversationId, messageId) {
|
|
535
652
|
this.ensureInitialized();
|
|
536
|
-
const response = await this.fetch(
|
|
537
|
-
`/api/conversations/${conversationId}/messages/${messageId}/reactions`
|
|
538
|
-
);
|
|
653
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`);
|
|
539
654
|
const data = await response.json();
|
|
540
655
|
return data.reactions;
|
|
541
656
|
}
|
|
@@ -659,9 +774,7 @@ var MoltDMClient = class {
|
|
|
659
774
|
const encryptionKeys = {
|
|
660
775
|
identityKey: this.identity.publicKey,
|
|
661
776
|
privateKey: this.identity.privateKey,
|
|
662
|
-
// Ed25519 private key for signing
|
|
663
777
|
signedPreKeyPrivate: this.identity.signedPreKey.privateKey,
|
|
664
|
-
// X25519 private key for decrypting sender keys
|
|
665
778
|
senderKeys: senderKeysObj
|
|
666
779
|
};
|
|
667
780
|
const response = await this.fetch("/api/pair/approve", {
|
|
@@ -701,16 +814,14 @@ var MoltDMClient = class {
|
|
|
701
814
|
return data.events;
|
|
702
815
|
}
|
|
703
816
|
// ============================================
|
|
704
|
-
// Encryption
|
|
817
|
+
// Encryption
|
|
705
818
|
// ============================================
|
|
706
819
|
async encrypt(plaintext, key) {
|
|
707
820
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
708
821
|
const encoder = new TextEncoder();
|
|
709
822
|
const data = encoder.encode(plaintext);
|
|
710
823
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
711
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
712
|
-
"encrypt"
|
|
713
|
-
]);
|
|
824
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
714
825
|
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cryptoKey, data);
|
|
715
826
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
716
827
|
combined.set(iv);
|
|
@@ -722,9 +833,7 @@ var MoltDMClient = class {
|
|
|
722
833
|
const iv = combined.slice(0, 12);
|
|
723
834
|
const encrypted = combined.slice(12);
|
|
724
835
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
725
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
726
|
-
"decrypt"
|
|
727
|
-
]);
|
|
836
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["decrypt"]);
|
|
728
837
|
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cryptoKey, encrypted);
|
|
729
838
|
const decoder = new TextDecoder();
|
|
730
839
|
return decoder.decode(decrypted);
|
|
@@ -737,22 +846,12 @@ var MoltDMClient = class {
|
|
|
737
846
|
throw new Error("Not initialized. Call initialize() first.");
|
|
738
847
|
}
|
|
739
848
|
}
|
|
740
|
-
/**
|
|
741
|
-
* Sign a message using Ed25519
|
|
742
|
-
*/
|
|
743
849
|
async signMessage(message) {
|
|
744
850
|
const privateKeyBytes = fromBase64(this.identity.privateKey);
|
|
745
|
-
const signature = await ed.signAsync(
|
|
746
|
-
new TextEncoder().encode(message),
|
|
747
|
-
privateKeyBytes
|
|
748
|
-
);
|
|
851
|
+
const signature = await ed.signAsync(new TextEncoder().encode(message), privateKeyBytes);
|
|
749
852
|
return toBase64(signature);
|
|
750
853
|
}
|
|
751
|
-
|
|
752
|
-
* Create the message to sign for a request
|
|
753
|
-
* Format: timestamp:method:path:bodyHash
|
|
754
|
-
*/
|
|
755
|
-
async createSignedMessage(timestamp, method, path2, body) {
|
|
854
|
+
async createSignedMessage(timestamp, method, path, body) {
|
|
756
855
|
let bodyHash = "";
|
|
757
856
|
if (body) {
|
|
758
857
|
const bodyBytes = new TextEncoder().encode(body);
|
|
@@ -760,18 +859,15 @@ var MoltDMClient = class {
|
|
|
760
859
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
761
860
|
bodyHash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
762
861
|
}
|
|
763
|
-
return `${timestamp}:${method}:${
|
|
862
|
+
return `${timestamp}:${method}:${path}:${bodyHash}`;
|
|
764
863
|
}
|
|
765
|
-
|
|
766
|
-
* Make an authenticated fetch request with Ed25519 signature
|
|
767
|
-
*/
|
|
768
|
-
async fetch(path2, options = {}) {
|
|
864
|
+
async fetch(path, options = {}) {
|
|
769
865
|
const method = options.method || "GET";
|
|
770
866
|
const body = options.body;
|
|
771
867
|
const timestamp = Date.now().toString();
|
|
772
|
-
const message = await this.createSignedMessage(timestamp, method,
|
|
868
|
+
const message = await this.createSignedMessage(timestamp, method, path, body);
|
|
773
869
|
const signature = await this.signMessage(message);
|
|
774
|
-
const response = await fetch(`${this.relayUrl}${
|
|
870
|
+
const response = await fetch(`${this.relayUrl}${path}`, {
|
|
775
871
|
...options,
|
|
776
872
|
headers: {
|
|
777
873
|
"Content-Type": "application/json",
|
|
@@ -790,6 +886,9 @@ var MoltDMClient = class {
|
|
|
790
886
|
};
|
|
791
887
|
var index_default = MoltDMClient;
|
|
792
888
|
export {
|
|
889
|
+
BrowserStorage,
|
|
890
|
+
FileStorage,
|
|
891
|
+
MemoryStorage,
|
|
793
892
|
MoltDMClient,
|
|
794
893
|
index_default as default
|
|
795
894
|
};
|