@moltdm/client 1.3.1 → 1.4.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.mts +43 -12
- package/dist/index.d.ts +43 -12
- package/dist/index.js +206 -121
- package/dist/index.mjs +203 -121
- package/dist/moltdm.browser.js +17 -0
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -30,33 +30,156 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
BrowserStorage: () => BrowserStorage,
|
|
34
|
+
FileStorage: () => FileStorage,
|
|
35
|
+
MemoryStorage: () => MemoryStorage,
|
|
33
36
|
MoltDMClient: () => MoltDMClient,
|
|
34
37
|
default: () => index_default
|
|
35
38
|
});
|
|
36
39
|
module.exports = __toCommonJS(index_exports);
|
|
37
40
|
var ed = __toESM(require("@noble/ed25519"));
|
|
38
41
|
var import_ed25519 = require("@noble/curves/ed25519");
|
|
39
|
-
var
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
var MemoryStorage = class {
|
|
43
|
+
data = /* @__PURE__ */ new Map();
|
|
44
|
+
async get(key) {
|
|
45
|
+
return this.data.get(key) ?? null;
|
|
46
|
+
}
|
|
47
|
+
async set(key, value) {
|
|
48
|
+
this.data.set(key, value);
|
|
49
|
+
}
|
|
50
|
+
async delete(key) {
|
|
51
|
+
this.data.delete(key);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var BrowserStorage = class {
|
|
55
|
+
prefix;
|
|
56
|
+
constructor(prefix = "moltdm") {
|
|
57
|
+
this.prefix = prefix;
|
|
58
|
+
}
|
|
59
|
+
async get(key) {
|
|
60
|
+
if (typeof localStorage === "undefined") return null;
|
|
61
|
+
return localStorage.getItem(`${this.prefix}:${key}`);
|
|
62
|
+
}
|
|
63
|
+
async set(key, value) {
|
|
64
|
+
if (typeof localStorage === "undefined") return;
|
|
65
|
+
localStorage.setItem(`${this.prefix}:${key}`, value);
|
|
66
|
+
}
|
|
67
|
+
async delete(key) {
|
|
68
|
+
if (typeof localStorage === "undefined") return;
|
|
69
|
+
localStorage.removeItem(`${this.prefix}:${key}`);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var FileStorage = class {
|
|
73
|
+
basePath;
|
|
74
|
+
_fs = null;
|
|
75
|
+
_path = null;
|
|
76
|
+
_initialized = false;
|
|
77
|
+
constructor(basePath) {
|
|
78
|
+
this.basePath = basePath || ".moltdm";
|
|
79
|
+
}
|
|
80
|
+
async ensureModules() {
|
|
81
|
+
if (this._initialized) return;
|
|
82
|
+
if (typeof window !== "undefined") {
|
|
83
|
+
console.warn("FileStorage is not supported in browser. Use BrowserStorage instead.");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const fs = await import(
|
|
88
|
+
/* webpackIgnore: true */
|
|
89
|
+
"fs"
|
|
90
|
+
);
|
|
91
|
+
const path = await import(
|
|
92
|
+
/* webpackIgnore: true */
|
|
93
|
+
"path"
|
|
94
|
+
);
|
|
95
|
+
const os = await import(
|
|
96
|
+
/* webpackIgnore: true */
|
|
97
|
+
"os"
|
|
98
|
+
);
|
|
99
|
+
this._fs = fs;
|
|
100
|
+
this._path = path;
|
|
101
|
+
if (this.basePath === ".moltdm") {
|
|
102
|
+
const envPath = process.env.OPENCLAW_STATE_DIR;
|
|
103
|
+
this.basePath = envPath ? path.join(envPath, ".moltdm") : path.join(os.homedir(), ".moltdm");
|
|
104
|
+
}
|
|
105
|
+
if (!fs.existsSync(this.basePath)) {
|
|
106
|
+
fs.mkdirSync(this.basePath, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
this._initialized = true;
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.error("Failed to load Node.js modules for FileStorage:", e);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async get(key) {
|
|
114
|
+
await this.ensureModules();
|
|
115
|
+
if (!this._fs) return null;
|
|
116
|
+
const filePath = this._path.join(this.basePath, `${key}.json`);
|
|
117
|
+
if (!this._fs.existsSync(filePath)) return null;
|
|
118
|
+
return this._fs.readFileSync(filePath, "utf-8");
|
|
119
|
+
}
|
|
120
|
+
async set(key, value) {
|
|
121
|
+
await this.ensureModules();
|
|
122
|
+
if (!this._fs) return;
|
|
123
|
+
const filePath = this._path.join(this.basePath, `${key}.json`);
|
|
124
|
+
this._fs.writeFileSync(filePath, value);
|
|
125
|
+
}
|
|
126
|
+
async delete(key) {
|
|
127
|
+
await this.ensureModules();
|
|
128
|
+
if (!this._fs) return;
|
|
129
|
+
const filePath = this._path.join(this.basePath, `${key}.json`);
|
|
130
|
+
if (this._fs.existsSync(filePath)) {
|
|
131
|
+
this._fs.unlinkSync(filePath);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
43
135
|
function toBase64(bytes) {
|
|
44
|
-
|
|
136
|
+
if (typeof Buffer !== "undefined") {
|
|
137
|
+
return Buffer.from(bytes).toString("base64");
|
|
138
|
+
}
|
|
139
|
+
let binary = "";
|
|
140
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
141
|
+
binary += String.fromCharCode(bytes[i]);
|
|
142
|
+
}
|
|
143
|
+
return btoa(binary);
|
|
45
144
|
}
|
|
46
145
|
function fromBase64(str) {
|
|
47
|
-
|
|
146
|
+
if (typeof Buffer !== "undefined") {
|
|
147
|
+
return new Uint8Array(Buffer.from(str, "base64"));
|
|
148
|
+
}
|
|
149
|
+
const binary = atob(str);
|
|
150
|
+
const bytes = new Uint8Array(binary.length);
|
|
151
|
+
for (let i = 0; i < binary.length; i++) {
|
|
152
|
+
bytes[i] = binary.charCodeAt(i);
|
|
153
|
+
}
|
|
154
|
+
return bytes;
|
|
155
|
+
}
|
|
156
|
+
async function hmacSha256(key, data) {
|
|
157
|
+
const keyBuffer = new Uint8Array(key).buffer;
|
|
158
|
+
const dataBuffer = new Uint8Array(data).buffer;
|
|
159
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
160
|
+
"raw",
|
|
161
|
+
keyBuffer,
|
|
162
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
163
|
+
false,
|
|
164
|
+
["sign"]
|
|
165
|
+
);
|
|
166
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, dataBuffer);
|
|
167
|
+
return new Uint8Array(signature);
|
|
48
168
|
}
|
|
49
169
|
var MoltDMClient = class {
|
|
50
|
-
|
|
170
|
+
storage;
|
|
51
171
|
relayUrl;
|
|
52
172
|
identity = null;
|
|
53
|
-
// Our sender keys (for messages we send)
|
|
54
173
|
senderKeys = /* @__PURE__ */ new Map();
|
|
55
|
-
// Received sender keys (for messages from others) - keyed by `${convId}:${fromId}`
|
|
56
174
|
receivedSenderKeys = /* @__PURE__ */ new Map();
|
|
57
175
|
constructor(options = {}) {
|
|
58
|
-
|
|
59
|
-
|
|
176
|
+
if (options.storage) {
|
|
177
|
+
this.storage = options.storage;
|
|
178
|
+
} else if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
|
|
179
|
+
this.storage = new BrowserStorage();
|
|
180
|
+
} else {
|
|
181
|
+
this.storage = new FileStorage(options.storagePath);
|
|
182
|
+
}
|
|
60
183
|
this.relayUrl = options.relayUrl || "https://relay.moltdm.com";
|
|
61
184
|
if (options.identity) {
|
|
62
185
|
this.identity = options.identity;
|
|
@@ -88,16 +211,12 @@ var MoltDMClient = class {
|
|
|
88
211
|
await this.loadSenderKeys();
|
|
89
212
|
return;
|
|
90
213
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const identityPath = path.join(this.storagePath, "identity.json");
|
|
95
|
-
if (fs.existsSync(identityPath)) {
|
|
96
|
-
const data = fs.readFileSync(identityPath, "utf-8");
|
|
97
|
-
this.identity = JSON.parse(data);
|
|
214
|
+
const identityJson = await this.storage.get("identity");
|
|
215
|
+
if (identityJson) {
|
|
216
|
+
this.identity = JSON.parse(identityJson);
|
|
98
217
|
} else {
|
|
99
218
|
await this.createIdentity();
|
|
100
|
-
|
|
219
|
+
await this.storage.set("identity", JSON.stringify(this.identity));
|
|
101
220
|
}
|
|
102
221
|
await this.loadSenderKeys();
|
|
103
222
|
}
|
|
@@ -149,10 +268,9 @@ var MoltDMClient = class {
|
|
|
149
268
|
};
|
|
150
269
|
}
|
|
151
270
|
async loadSenderKeys() {
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
154
|
-
const
|
|
155
|
-
const keys = JSON.parse(data);
|
|
271
|
+
const keysJson = await this.storage.get("sender_keys");
|
|
272
|
+
if (keysJson) {
|
|
273
|
+
const keys = JSON.parse(keysJson);
|
|
156
274
|
for (const [convId, keyData] of Object.entries(keys)) {
|
|
157
275
|
const k = keyData;
|
|
158
276
|
const chainKey = fromBase64(k.chainKey || k.key || "");
|
|
@@ -164,10 +282,9 @@ var MoltDMClient = class {
|
|
|
164
282
|
});
|
|
165
283
|
}
|
|
166
284
|
}
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
const
|
|
170
|
-
const keys = JSON.parse(data);
|
|
285
|
+
const receivedJson = await this.storage.get("received_sender_keys");
|
|
286
|
+
if (receivedJson) {
|
|
287
|
+
const keys = JSON.parse(receivedJson);
|
|
171
288
|
for (const [key, keyData] of Object.entries(keys)) {
|
|
172
289
|
const k = keyData;
|
|
173
290
|
this.receivedSenderKeys.set(key, {
|
|
@@ -179,7 +296,6 @@ var MoltDMClient = class {
|
|
|
179
296
|
}
|
|
180
297
|
}
|
|
181
298
|
async saveSenderKeys() {
|
|
182
|
-
const keysPath = path.join(this.storagePath, "sender_keys.json");
|
|
183
299
|
const obj = {};
|
|
184
300
|
for (const [convId, keyData] of this.senderKeys) {
|
|
185
301
|
obj[convId] = {
|
|
@@ -189,8 +305,7 @@ var MoltDMClient = class {
|
|
|
189
305
|
messageIndex: keyData.messageIndex
|
|
190
306
|
};
|
|
191
307
|
}
|
|
192
|
-
|
|
193
|
-
const receivedPath = path.join(this.storagePath, "received_sender_keys.json");
|
|
308
|
+
await this.storage.set("sender_keys", JSON.stringify(obj));
|
|
194
309
|
const receivedObj = {};
|
|
195
310
|
for (const [key, keyData] of this.receivedSenderKeys) {
|
|
196
311
|
receivedObj[key] = {
|
|
@@ -199,7 +314,7 @@ var MoltDMClient = class {
|
|
|
199
314
|
messageIndex: keyData.messageIndex
|
|
200
315
|
};
|
|
201
316
|
}
|
|
202
|
-
|
|
317
|
+
await this.storage.set("received_sender_keys", JSON.stringify(receivedObj));
|
|
203
318
|
}
|
|
204
319
|
// ============================================
|
|
205
320
|
// Sender Keys Protocol (Signal-style)
|
|
@@ -208,37 +323,25 @@ var MoltDMClient = class {
|
|
|
208
323
|
* Derive message key from chain key using HMAC
|
|
209
324
|
* message_key = HMAC-SHA256(chain_key, 0x01)
|
|
210
325
|
*/
|
|
211
|
-
deriveMessageKey(chainKey) {
|
|
212
|
-
|
|
213
|
-
const hmac = (0, import_crypto.createHmac)("sha256", keyBuffer);
|
|
214
|
-
hmac.update(Buffer.from([1]));
|
|
215
|
-
const digest = hmac.digest();
|
|
216
|
-
const result = new Uint8Array(32);
|
|
217
|
-
result.set(new Uint8Array(digest));
|
|
218
|
-
return result;
|
|
326
|
+
async deriveMessageKey(chainKey) {
|
|
327
|
+
return hmacSha256(chainKey, new Uint8Array([1]));
|
|
219
328
|
}
|
|
220
329
|
/**
|
|
221
330
|
* Ratchet chain key forward
|
|
222
331
|
* new_chain_key = HMAC-SHA256(chain_key, 0x02)
|
|
223
332
|
*/
|
|
224
|
-
ratchetChainKey(chainKey) {
|
|
225
|
-
|
|
226
|
-
const hmac = (0, import_crypto.createHmac)("sha256", keyBuffer);
|
|
227
|
-
hmac.update(Buffer.from([2]));
|
|
228
|
-
const digest = hmac.digest();
|
|
229
|
-
const result = new Uint8Array(32);
|
|
230
|
-
result.set(new Uint8Array(digest));
|
|
231
|
-
return result;
|
|
333
|
+
async ratchetChainKey(chainKey) {
|
|
334
|
+
return hmacSha256(chainKey, new Uint8Array([2]));
|
|
232
335
|
}
|
|
233
336
|
/**
|
|
234
337
|
* Ratchet a chain key forward N steps (for catching up on missed messages)
|
|
235
338
|
*/
|
|
236
|
-
ratchetChainKeyN(chainKey, steps) {
|
|
339
|
+
async ratchetChainKeyN(chainKey, steps) {
|
|
237
340
|
const messageKeys = [];
|
|
238
341
|
let current = chainKey;
|
|
239
342
|
for (let i = 0; i < steps; i++) {
|
|
240
|
-
messageKeys.push(this.deriveMessageKey(current));
|
|
241
|
-
current = this.ratchetChainKey(current);
|
|
343
|
+
messageKeys.push(await this.deriveMessageKey(current));
|
|
344
|
+
current = await this.ratchetChainKey(current);
|
|
242
345
|
}
|
|
243
346
|
return { chainKey: current, messageKeys };
|
|
244
347
|
}
|
|
@@ -332,27 +435,24 @@ var MoltDMClient = class {
|
|
|
332
435
|
async send(conversationId, content, options) {
|
|
333
436
|
this.ensureInitialized();
|
|
334
437
|
let senderKeyState = this.senderKeys.get(conversationId);
|
|
335
|
-
const isNewKey = !senderKeyState;
|
|
336
438
|
if (!senderKeyState) {
|
|
337
439
|
const initialKey = crypto.getRandomValues(new Uint8Array(32));
|
|
338
440
|
senderKeyState = {
|
|
339
441
|
chainKey: initialKey,
|
|
340
442
|
initialChainKey: new Uint8Array(initialKey),
|
|
341
|
-
// Copy for distribution
|
|
342
443
|
version: 1,
|
|
343
444
|
messageIndex: 0
|
|
344
445
|
};
|
|
345
446
|
this.senderKeys.set(conversationId, senderKeyState);
|
|
346
447
|
}
|
|
347
|
-
const messageKey = this.deriveMessageKey(senderKeyState.chainKey);
|
|
448
|
+
const messageKey = await this.deriveMessageKey(senderKeyState.chainKey);
|
|
348
449
|
const currentIndex = senderKeyState.messageIndex;
|
|
349
|
-
senderKeyState.chainKey = this.ratchetChainKey(senderKeyState.chainKey);
|
|
450
|
+
senderKeyState.chainKey = await this.ratchetChainKey(senderKeyState.chainKey);
|
|
350
451
|
senderKeyState.messageIndex++;
|
|
351
452
|
const ciphertext = await this.encrypt(content, messageKey);
|
|
352
453
|
const encryptedSenderKeys = await this.encryptChainKeyForRecipients(
|
|
353
454
|
conversationId,
|
|
354
455
|
senderKeyState.initialChainKey
|
|
355
|
-
// Send the original, unratcheted key
|
|
356
456
|
);
|
|
357
457
|
const response = await this.fetch(`/api/conversations/${conversationId}/messages`, {
|
|
358
458
|
method: "POST",
|
|
@@ -404,26 +504,23 @@ var MoltDMClient = class {
|
|
|
404
504
|
sharedSecretCopy.set(new Uint8Array(sharedSecret));
|
|
405
505
|
const chainKeyCopy = new Uint8Array(32);
|
|
406
506
|
chainKeyCopy.set(new Uint8Array(chainKey));
|
|
407
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
408
|
-
"
|
|
409
|
-
|
|
410
|
-
{ name: "HKDF" },
|
|
411
|
-
false,
|
|
412
|
-
["deriveKey"]
|
|
413
|
-
);
|
|
507
|
+
const keyMaterial = await crypto.subtle.importKey("raw", sharedSecretCopy.buffer, { name: "HKDF" }, false, [
|
|
508
|
+
"deriveKey"
|
|
509
|
+
]);
|
|
414
510
|
const aesKey = await crypto.subtle.deriveKey(
|
|
415
|
-
{
|
|
511
|
+
{
|
|
512
|
+
name: "HKDF",
|
|
513
|
+
hash: "SHA-256",
|
|
514
|
+
salt: new Uint8Array(32),
|
|
515
|
+
info: new TextEncoder().encode("moltdm-sender-key")
|
|
516
|
+
},
|
|
416
517
|
keyMaterial,
|
|
417
518
|
{ name: "AES-GCM", length: 256 },
|
|
418
519
|
false,
|
|
419
520
|
["encrypt"]
|
|
420
521
|
);
|
|
421
522
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
422
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
423
|
-
{ name: "AES-GCM", iv },
|
|
424
|
-
aesKey,
|
|
425
|
-
chainKeyCopy.buffer
|
|
426
|
-
);
|
|
523
|
+
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, chainKeyCopy.buffer);
|
|
427
524
|
const combined = new Uint8Array(32 + 12 + encrypted.byteLength);
|
|
428
525
|
combined.set(ephemeralPublic);
|
|
429
526
|
combined.set(iv, 32);
|
|
@@ -457,17 +554,18 @@ var MoltDMClient = class {
|
|
|
457
554
|
["deriveKey"]
|
|
458
555
|
);
|
|
459
556
|
const aesKey = await crypto.subtle.deriveKey(
|
|
460
|
-
{
|
|
557
|
+
{
|
|
558
|
+
name: "HKDF",
|
|
559
|
+
hash: "SHA-256",
|
|
560
|
+
salt: new Uint8Array(32),
|
|
561
|
+
info: new TextEncoder().encode("moltdm-sender-key")
|
|
562
|
+
},
|
|
461
563
|
keyMaterial,
|
|
462
564
|
{ name: "AES-GCM", length: 256 },
|
|
463
565
|
false,
|
|
464
566
|
["decrypt"]
|
|
465
567
|
);
|
|
466
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
467
|
-
{ name: "AES-GCM", iv },
|
|
468
|
-
aesKey,
|
|
469
|
-
encrypted
|
|
470
|
-
);
|
|
568
|
+
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, encrypted);
|
|
471
569
|
return new Uint8Array(decrypted);
|
|
472
570
|
} catch (e) {
|
|
473
571
|
console.error("Failed to decrypt chain key:", e);
|
|
@@ -482,6 +580,13 @@ var MoltDMClient = class {
|
|
|
482
580
|
const { conversationId, fromId, ciphertext, senderKeyVersion, messageIndex, encryptedSenderKeys } = message;
|
|
483
581
|
const keyId = `${conversationId}:${fromId}`;
|
|
484
582
|
let receivedKey = this.receivedSenderKeys.get(keyId);
|
|
583
|
+
if (!encryptedSenderKeys) {
|
|
584
|
+
console.error(`[decrypt] Message ${message.id} has no encryptedSenderKeys - sent before Sender Keys`);
|
|
585
|
+
} else if (!encryptedSenderKeys[this.moltbotId]) {
|
|
586
|
+
console.error(
|
|
587
|
+
`[decrypt] Message ${message.id} missing key for ${this.moltbotId}. Available: ${Object.keys(encryptedSenderKeys).join(", ")}`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
485
590
|
if (encryptedSenderKeys && encryptedSenderKeys[this.moltbotId]) {
|
|
486
591
|
if (!receivedKey || receivedKey.version !== senderKeyVersion) {
|
|
487
592
|
const chainKey = await this.decryptChainKey(encryptedSenderKeys[this.moltbotId]);
|
|
@@ -493,16 +598,18 @@ var MoltDMClient = class {
|
|
|
493
598
|
};
|
|
494
599
|
this.receivedSenderKeys.set(keyId, receivedKey);
|
|
495
600
|
await this.saveSenderKeys();
|
|
601
|
+
} else {
|
|
602
|
+
console.error(`[decrypt] Failed to decrypt chain key for ${keyId}`);
|
|
496
603
|
}
|
|
497
604
|
}
|
|
498
605
|
}
|
|
499
606
|
if (!receivedKey) {
|
|
500
|
-
console.error(`No sender key for ${keyId}`);
|
|
607
|
+
console.error(`[decrypt] No sender key for ${keyId}`);
|
|
501
608
|
return null;
|
|
502
609
|
}
|
|
503
610
|
if (messageIndex > receivedKey.messageIndex) {
|
|
504
611
|
const steps = messageIndex - receivedKey.messageIndex + 1;
|
|
505
|
-
const { chainKey, messageKeys } = this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
612
|
+
const { chainKey, messageKeys } = await this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
506
613
|
const messageKey = messageKeys[messageKeys.length - 1];
|
|
507
614
|
receivedKey.chainKey = chainKey;
|
|
508
615
|
receivedKey.messageIndex = messageIndex + 1;
|
|
@@ -510,14 +617,14 @@ var MoltDMClient = class {
|
|
|
510
617
|
await this.saveSenderKeys();
|
|
511
618
|
return this.decrypt(ciphertext, messageKey);
|
|
512
619
|
} else if (messageIndex === receivedKey.messageIndex) {
|
|
513
|
-
const messageKey = this.deriveMessageKey(receivedKey.chainKey);
|
|
514
|
-
receivedKey.chainKey = this.ratchetChainKey(receivedKey.chainKey);
|
|
620
|
+
const messageKey = await this.deriveMessageKey(receivedKey.chainKey);
|
|
621
|
+
receivedKey.chainKey = await this.ratchetChainKey(receivedKey.chainKey);
|
|
515
622
|
receivedKey.messageIndex++;
|
|
516
623
|
this.receivedSenderKeys.set(keyId, receivedKey);
|
|
517
624
|
await this.saveSenderKeys();
|
|
518
625
|
return this.decrypt(ciphertext, messageKey);
|
|
519
626
|
} else {
|
|
520
|
-
console.error(`Message index ${messageIndex} is in the past (current: ${receivedKey.messageIndex})`);
|
|
627
|
+
console.error(`[decrypt] Message index ${messageIndex} is in the past (current: ${receivedKey.messageIndex})`);
|
|
521
628
|
return null;
|
|
522
629
|
}
|
|
523
630
|
}
|
|
@@ -542,28 +649,22 @@ var MoltDMClient = class {
|
|
|
542
649
|
// ============================================
|
|
543
650
|
async react(conversationId, messageId, emoji) {
|
|
544
651
|
this.ensureInitialized();
|
|
545
|
-
const response = await this.fetch(
|
|
546
|
-
|
|
547
|
-
{
|
|
548
|
-
|
|
549
|
-
body: JSON.stringify({ emoji })
|
|
550
|
-
}
|
|
551
|
-
);
|
|
652
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`, {
|
|
653
|
+
method: "POST",
|
|
654
|
+
body: JSON.stringify({ emoji })
|
|
655
|
+
});
|
|
552
656
|
const data = await response.json();
|
|
553
657
|
return data.reaction;
|
|
554
658
|
}
|
|
555
659
|
async unreact(conversationId, messageId, emoji) {
|
|
556
660
|
this.ensureInitialized();
|
|
557
|
-
await this.fetch(
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
);
|
|
661
|
+
await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`, {
|
|
662
|
+
method: "DELETE"
|
|
663
|
+
});
|
|
561
664
|
}
|
|
562
665
|
async getReactions(conversationId, messageId) {
|
|
563
666
|
this.ensureInitialized();
|
|
564
|
-
const response = await this.fetch(
|
|
565
|
-
`/api/conversations/${conversationId}/messages/${messageId}/reactions`
|
|
566
|
-
);
|
|
667
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`);
|
|
567
668
|
const data = await response.json();
|
|
568
669
|
return data.reactions;
|
|
569
670
|
}
|
|
@@ -687,9 +788,7 @@ var MoltDMClient = class {
|
|
|
687
788
|
const encryptionKeys = {
|
|
688
789
|
identityKey: this.identity.publicKey,
|
|
689
790
|
privateKey: this.identity.privateKey,
|
|
690
|
-
// Ed25519 private key for signing
|
|
691
791
|
signedPreKeyPrivate: this.identity.signedPreKey.privateKey,
|
|
692
|
-
// X25519 private key for decrypting sender keys
|
|
693
792
|
senderKeys: senderKeysObj
|
|
694
793
|
};
|
|
695
794
|
const response = await this.fetch("/api/pair/approve", {
|
|
@@ -729,16 +828,14 @@ var MoltDMClient = class {
|
|
|
729
828
|
return data.events;
|
|
730
829
|
}
|
|
731
830
|
// ============================================
|
|
732
|
-
// Encryption
|
|
831
|
+
// Encryption
|
|
733
832
|
// ============================================
|
|
734
833
|
async encrypt(plaintext, key) {
|
|
735
834
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
736
835
|
const encoder = new TextEncoder();
|
|
737
836
|
const data = encoder.encode(plaintext);
|
|
738
837
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
739
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
740
|
-
"encrypt"
|
|
741
|
-
]);
|
|
838
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
742
839
|
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cryptoKey, data);
|
|
743
840
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
744
841
|
combined.set(iv);
|
|
@@ -750,9 +847,7 @@ var MoltDMClient = class {
|
|
|
750
847
|
const iv = combined.slice(0, 12);
|
|
751
848
|
const encrypted = combined.slice(12);
|
|
752
849
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
753
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
754
|
-
"decrypt"
|
|
755
|
-
]);
|
|
850
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["decrypt"]);
|
|
756
851
|
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cryptoKey, encrypted);
|
|
757
852
|
const decoder = new TextDecoder();
|
|
758
853
|
return decoder.decode(decrypted);
|
|
@@ -765,22 +860,12 @@ var MoltDMClient = class {
|
|
|
765
860
|
throw new Error("Not initialized. Call initialize() first.");
|
|
766
861
|
}
|
|
767
862
|
}
|
|
768
|
-
/**
|
|
769
|
-
* Sign a message using Ed25519
|
|
770
|
-
*/
|
|
771
863
|
async signMessage(message) {
|
|
772
864
|
const privateKeyBytes = fromBase64(this.identity.privateKey);
|
|
773
|
-
const signature = await ed.signAsync(
|
|
774
|
-
new TextEncoder().encode(message),
|
|
775
|
-
privateKeyBytes
|
|
776
|
-
);
|
|
865
|
+
const signature = await ed.signAsync(new TextEncoder().encode(message), privateKeyBytes);
|
|
777
866
|
return toBase64(signature);
|
|
778
867
|
}
|
|
779
|
-
|
|
780
|
-
* Create the message to sign for a request
|
|
781
|
-
* Format: timestamp:method:path:bodyHash
|
|
782
|
-
*/
|
|
783
|
-
async createSignedMessage(timestamp, method, path2, body) {
|
|
868
|
+
async createSignedMessage(timestamp, method, path, body) {
|
|
784
869
|
let bodyHash = "";
|
|
785
870
|
if (body) {
|
|
786
871
|
const bodyBytes = new TextEncoder().encode(body);
|
|
@@ -788,18 +873,15 @@ var MoltDMClient = class {
|
|
|
788
873
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
789
874
|
bodyHash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
790
875
|
}
|
|
791
|
-
return `${timestamp}:${method}:${
|
|
876
|
+
return `${timestamp}:${method}:${path}:${bodyHash}`;
|
|
792
877
|
}
|
|
793
|
-
|
|
794
|
-
* Make an authenticated fetch request with Ed25519 signature
|
|
795
|
-
*/
|
|
796
|
-
async fetch(path2, options = {}) {
|
|
878
|
+
async fetch(path, options = {}) {
|
|
797
879
|
const method = options.method || "GET";
|
|
798
880
|
const body = options.body;
|
|
799
881
|
const timestamp = Date.now().toString();
|
|
800
|
-
const message = await this.createSignedMessage(timestamp, method,
|
|
882
|
+
const message = await this.createSignedMessage(timestamp, method, path, body);
|
|
801
883
|
const signature = await this.signMessage(message);
|
|
802
|
-
const response = await fetch(`${this.relayUrl}${
|
|
884
|
+
const response = await fetch(`${this.relayUrl}${path}`, {
|
|
803
885
|
...options,
|
|
804
886
|
headers: {
|
|
805
887
|
"Content-Type": "application/json",
|
|
@@ -819,5 +901,8 @@ var MoltDMClient = class {
|
|
|
819
901
|
var index_default = MoltDMClient;
|
|
820
902
|
// Annotate the CommonJS export names for ESM import in node:
|
|
821
903
|
0 && (module.exports = {
|
|
904
|
+
BrowserStorage,
|
|
905
|
+
FileStorage,
|
|
906
|
+
MemoryStorage,
|
|
822
907
|
MoltDMClient
|
|
823
908
|
});
|