@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.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;
|
|
@@ -53,16 +173,12 @@ var MoltDMClient = class {
|
|
|
53
173
|
await this.loadSenderKeys();
|
|
54
174
|
return;
|
|
55
175
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const identityPath = path.join(this.storagePath, "identity.json");
|
|
60
|
-
if (fs.existsSync(identityPath)) {
|
|
61
|
-
const data = fs.readFileSync(identityPath, "utf-8");
|
|
62
|
-
this.identity = JSON.parse(data);
|
|
176
|
+
const identityJson = await this.storage.get("identity");
|
|
177
|
+
if (identityJson) {
|
|
178
|
+
this.identity = JSON.parse(identityJson);
|
|
63
179
|
} else {
|
|
64
180
|
await this.createIdentity();
|
|
65
|
-
|
|
181
|
+
await this.storage.set("identity", JSON.stringify(this.identity));
|
|
66
182
|
}
|
|
67
183
|
await this.loadSenderKeys();
|
|
68
184
|
}
|
|
@@ -114,10 +230,9 @@ var MoltDMClient = class {
|
|
|
114
230
|
};
|
|
115
231
|
}
|
|
116
232
|
async loadSenderKeys() {
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
const
|
|
120
|
-
const keys = JSON.parse(data);
|
|
233
|
+
const keysJson = await this.storage.get("sender_keys");
|
|
234
|
+
if (keysJson) {
|
|
235
|
+
const keys = JSON.parse(keysJson);
|
|
121
236
|
for (const [convId, keyData] of Object.entries(keys)) {
|
|
122
237
|
const k = keyData;
|
|
123
238
|
const chainKey = fromBase64(k.chainKey || k.key || "");
|
|
@@ -129,10 +244,9 @@ var MoltDMClient = class {
|
|
|
129
244
|
});
|
|
130
245
|
}
|
|
131
246
|
}
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
134
|
-
const
|
|
135
|
-
const keys = JSON.parse(data);
|
|
247
|
+
const receivedJson = await this.storage.get("received_sender_keys");
|
|
248
|
+
if (receivedJson) {
|
|
249
|
+
const keys = JSON.parse(receivedJson);
|
|
136
250
|
for (const [key, keyData] of Object.entries(keys)) {
|
|
137
251
|
const k = keyData;
|
|
138
252
|
this.receivedSenderKeys.set(key, {
|
|
@@ -144,7 +258,6 @@ var MoltDMClient = class {
|
|
|
144
258
|
}
|
|
145
259
|
}
|
|
146
260
|
async saveSenderKeys() {
|
|
147
|
-
const keysPath = path.join(this.storagePath, "sender_keys.json");
|
|
148
261
|
const obj = {};
|
|
149
262
|
for (const [convId, keyData] of this.senderKeys) {
|
|
150
263
|
obj[convId] = {
|
|
@@ -154,8 +267,7 @@ var MoltDMClient = class {
|
|
|
154
267
|
messageIndex: keyData.messageIndex
|
|
155
268
|
};
|
|
156
269
|
}
|
|
157
|
-
|
|
158
|
-
const receivedPath = path.join(this.storagePath, "received_sender_keys.json");
|
|
270
|
+
await this.storage.set("sender_keys", JSON.stringify(obj));
|
|
159
271
|
const receivedObj = {};
|
|
160
272
|
for (const [key, keyData] of this.receivedSenderKeys) {
|
|
161
273
|
receivedObj[key] = {
|
|
@@ -164,7 +276,7 @@ var MoltDMClient = class {
|
|
|
164
276
|
messageIndex: keyData.messageIndex
|
|
165
277
|
};
|
|
166
278
|
}
|
|
167
|
-
|
|
279
|
+
await this.storage.set("received_sender_keys", JSON.stringify(receivedObj));
|
|
168
280
|
}
|
|
169
281
|
// ============================================
|
|
170
282
|
// Sender Keys Protocol (Signal-style)
|
|
@@ -173,37 +285,25 @@ var MoltDMClient = class {
|
|
|
173
285
|
* Derive message key from chain key using HMAC
|
|
174
286
|
* message_key = HMAC-SHA256(chain_key, 0x01)
|
|
175
287
|
*/
|
|
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;
|
|
288
|
+
async deriveMessageKey(chainKey) {
|
|
289
|
+
return hmacSha256(chainKey, new Uint8Array([1]));
|
|
184
290
|
}
|
|
185
291
|
/**
|
|
186
292
|
* Ratchet chain key forward
|
|
187
293
|
* new_chain_key = HMAC-SHA256(chain_key, 0x02)
|
|
188
294
|
*/
|
|
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;
|
|
295
|
+
async ratchetChainKey(chainKey) {
|
|
296
|
+
return hmacSha256(chainKey, new Uint8Array([2]));
|
|
197
297
|
}
|
|
198
298
|
/**
|
|
199
299
|
* Ratchet a chain key forward N steps (for catching up on missed messages)
|
|
200
300
|
*/
|
|
201
|
-
ratchetChainKeyN(chainKey, steps) {
|
|
301
|
+
async ratchetChainKeyN(chainKey, steps) {
|
|
202
302
|
const messageKeys = [];
|
|
203
303
|
let current = chainKey;
|
|
204
304
|
for (let i = 0; i < steps; i++) {
|
|
205
|
-
messageKeys.push(this.deriveMessageKey(current));
|
|
206
|
-
current = this.ratchetChainKey(current);
|
|
305
|
+
messageKeys.push(await this.deriveMessageKey(current));
|
|
306
|
+
current = await this.ratchetChainKey(current);
|
|
207
307
|
}
|
|
208
308
|
return { chainKey: current, messageKeys };
|
|
209
309
|
}
|
|
@@ -297,27 +397,24 @@ var MoltDMClient = class {
|
|
|
297
397
|
async send(conversationId, content, options) {
|
|
298
398
|
this.ensureInitialized();
|
|
299
399
|
let senderKeyState = this.senderKeys.get(conversationId);
|
|
300
|
-
const isNewKey = !senderKeyState;
|
|
301
400
|
if (!senderKeyState) {
|
|
302
401
|
const initialKey = crypto.getRandomValues(new Uint8Array(32));
|
|
303
402
|
senderKeyState = {
|
|
304
403
|
chainKey: initialKey,
|
|
305
404
|
initialChainKey: new Uint8Array(initialKey),
|
|
306
|
-
// Copy for distribution
|
|
307
405
|
version: 1,
|
|
308
406
|
messageIndex: 0
|
|
309
407
|
};
|
|
310
408
|
this.senderKeys.set(conversationId, senderKeyState);
|
|
311
409
|
}
|
|
312
|
-
const messageKey = this.deriveMessageKey(senderKeyState.chainKey);
|
|
410
|
+
const messageKey = await this.deriveMessageKey(senderKeyState.chainKey);
|
|
313
411
|
const currentIndex = senderKeyState.messageIndex;
|
|
314
|
-
senderKeyState.chainKey = this.ratchetChainKey(senderKeyState.chainKey);
|
|
412
|
+
senderKeyState.chainKey = await this.ratchetChainKey(senderKeyState.chainKey);
|
|
315
413
|
senderKeyState.messageIndex++;
|
|
316
414
|
const ciphertext = await this.encrypt(content, messageKey);
|
|
317
415
|
const encryptedSenderKeys = await this.encryptChainKeyForRecipients(
|
|
318
416
|
conversationId,
|
|
319
417
|
senderKeyState.initialChainKey
|
|
320
|
-
// Send the original, unratcheted key
|
|
321
418
|
);
|
|
322
419
|
const response = await this.fetch(`/api/conversations/${conversationId}/messages`, {
|
|
323
420
|
method: "POST",
|
|
@@ -369,26 +466,23 @@ var MoltDMClient = class {
|
|
|
369
466
|
sharedSecretCopy.set(new Uint8Array(sharedSecret));
|
|
370
467
|
const chainKeyCopy = new Uint8Array(32);
|
|
371
468
|
chainKeyCopy.set(new Uint8Array(chainKey));
|
|
372
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
373
|
-
"
|
|
374
|
-
|
|
375
|
-
{ name: "HKDF" },
|
|
376
|
-
false,
|
|
377
|
-
["deriveKey"]
|
|
378
|
-
);
|
|
469
|
+
const keyMaterial = await crypto.subtle.importKey("raw", sharedSecretCopy.buffer, { name: "HKDF" }, false, [
|
|
470
|
+
"deriveKey"
|
|
471
|
+
]);
|
|
379
472
|
const aesKey = await crypto.subtle.deriveKey(
|
|
380
|
-
{
|
|
473
|
+
{
|
|
474
|
+
name: "HKDF",
|
|
475
|
+
hash: "SHA-256",
|
|
476
|
+
salt: new Uint8Array(32),
|
|
477
|
+
info: new TextEncoder().encode("moltdm-sender-key")
|
|
478
|
+
},
|
|
381
479
|
keyMaterial,
|
|
382
480
|
{ name: "AES-GCM", length: 256 },
|
|
383
481
|
false,
|
|
384
482
|
["encrypt"]
|
|
385
483
|
);
|
|
386
484
|
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
|
-
);
|
|
485
|
+
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, chainKeyCopy.buffer);
|
|
392
486
|
const combined = new Uint8Array(32 + 12 + encrypted.byteLength);
|
|
393
487
|
combined.set(ephemeralPublic);
|
|
394
488
|
combined.set(iv, 32);
|
|
@@ -422,17 +516,18 @@ var MoltDMClient = class {
|
|
|
422
516
|
["deriveKey"]
|
|
423
517
|
);
|
|
424
518
|
const aesKey = await crypto.subtle.deriveKey(
|
|
425
|
-
{
|
|
519
|
+
{
|
|
520
|
+
name: "HKDF",
|
|
521
|
+
hash: "SHA-256",
|
|
522
|
+
salt: new Uint8Array(32),
|
|
523
|
+
info: new TextEncoder().encode("moltdm-sender-key")
|
|
524
|
+
},
|
|
426
525
|
keyMaterial,
|
|
427
526
|
{ name: "AES-GCM", length: 256 },
|
|
428
527
|
false,
|
|
429
528
|
["decrypt"]
|
|
430
529
|
);
|
|
431
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
432
|
-
{ name: "AES-GCM", iv },
|
|
433
|
-
aesKey,
|
|
434
|
-
encrypted
|
|
435
|
-
);
|
|
530
|
+
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, encrypted);
|
|
436
531
|
return new Uint8Array(decrypted);
|
|
437
532
|
} catch (e) {
|
|
438
533
|
console.error("Failed to decrypt chain key:", e);
|
|
@@ -447,6 +542,13 @@ var MoltDMClient = class {
|
|
|
447
542
|
const { conversationId, fromId, ciphertext, senderKeyVersion, messageIndex, encryptedSenderKeys } = message;
|
|
448
543
|
const keyId = `${conversationId}:${fromId}`;
|
|
449
544
|
let receivedKey = this.receivedSenderKeys.get(keyId);
|
|
545
|
+
if (!encryptedSenderKeys) {
|
|
546
|
+
console.error(`[decrypt] Message ${message.id} has no encryptedSenderKeys - sent before Sender Keys`);
|
|
547
|
+
} else if (!encryptedSenderKeys[this.moltbotId]) {
|
|
548
|
+
console.error(
|
|
549
|
+
`[decrypt] Message ${message.id} missing key for ${this.moltbotId}. Available: ${Object.keys(encryptedSenderKeys).join(", ")}`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
450
552
|
if (encryptedSenderKeys && encryptedSenderKeys[this.moltbotId]) {
|
|
451
553
|
if (!receivedKey || receivedKey.version !== senderKeyVersion) {
|
|
452
554
|
const chainKey = await this.decryptChainKey(encryptedSenderKeys[this.moltbotId]);
|
|
@@ -458,16 +560,18 @@ var MoltDMClient = class {
|
|
|
458
560
|
};
|
|
459
561
|
this.receivedSenderKeys.set(keyId, receivedKey);
|
|
460
562
|
await this.saveSenderKeys();
|
|
563
|
+
} else {
|
|
564
|
+
console.error(`[decrypt] Failed to decrypt chain key for ${keyId}`);
|
|
461
565
|
}
|
|
462
566
|
}
|
|
463
567
|
}
|
|
464
568
|
if (!receivedKey) {
|
|
465
|
-
console.error(`No sender key for ${keyId}`);
|
|
569
|
+
console.error(`[decrypt] No sender key for ${keyId}`);
|
|
466
570
|
return null;
|
|
467
571
|
}
|
|
468
572
|
if (messageIndex > receivedKey.messageIndex) {
|
|
469
573
|
const steps = messageIndex - receivedKey.messageIndex + 1;
|
|
470
|
-
const { chainKey, messageKeys } = this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
574
|
+
const { chainKey, messageKeys } = await this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
471
575
|
const messageKey = messageKeys[messageKeys.length - 1];
|
|
472
576
|
receivedKey.chainKey = chainKey;
|
|
473
577
|
receivedKey.messageIndex = messageIndex + 1;
|
|
@@ -475,14 +579,14 @@ var MoltDMClient = class {
|
|
|
475
579
|
await this.saveSenderKeys();
|
|
476
580
|
return this.decrypt(ciphertext, messageKey);
|
|
477
581
|
} else if (messageIndex === receivedKey.messageIndex) {
|
|
478
|
-
const messageKey = this.deriveMessageKey(receivedKey.chainKey);
|
|
479
|
-
receivedKey.chainKey = this.ratchetChainKey(receivedKey.chainKey);
|
|
582
|
+
const messageKey = await this.deriveMessageKey(receivedKey.chainKey);
|
|
583
|
+
receivedKey.chainKey = await this.ratchetChainKey(receivedKey.chainKey);
|
|
480
584
|
receivedKey.messageIndex++;
|
|
481
585
|
this.receivedSenderKeys.set(keyId, receivedKey);
|
|
482
586
|
await this.saveSenderKeys();
|
|
483
587
|
return this.decrypt(ciphertext, messageKey);
|
|
484
588
|
} else {
|
|
485
|
-
console.error(`Message index ${messageIndex} is in the past (current: ${receivedKey.messageIndex})`);
|
|
589
|
+
console.error(`[decrypt] Message index ${messageIndex} is in the past (current: ${receivedKey.messageIndex})`);
|
|
486
590
|
return null;
|
|
487
591
|
}
|
|
488
592
|
}
|
|
@@ -507,28 +611,22 @@ var MoltDMClient = class {
|
|
|
507
611
|
// ============================================
|
|
508
612
|
async react(conversationId, messageId, emoji) {
|
|
509
613
|
this.ensureInitialized();
|
|
510
|
-
const response = await this.fetch(
|
|
511
|
-
|
|
512
|
-
{
|
|
513
|
-
|
|
514
|
-
body: JSON.stringify({ emoji })
|
|
515
|
-
}
|
|
516
|
-
);
|
|
614
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`, {
|
|
615
|
+
method: "POST",
|
|
616
|
+
body: JSON.stringify({ emoji })
|
|
617
|
+
});
|
|
517
618
|
const data = await response.json();
|
|
518
619
|
return data.reaction;
|
|
519
620
|
}
|
|
520
621
|
async unreact(conversationId, messageId, emoji) {
|
|
521
622
|
this.ensureInitialized();
|
|
522
|
-
await this.fetch(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
);
|
|
623
|
+
await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`, {
|
|
624
|
+
method: "DELETE"
|
|
625
|
+
});
|
|
526
626
|
}
|
|
527
627
|
async getReactions(conversationId, messageId) {
|
|
528
628
|
this.ensureInitialized();
|
|
529
|
-
const response = await this.fetch(
|
|
530
|
-
`/api/conversations/${conversationId}/messages/${messageId}/reactions`
|
|
531
|
-
);
|
|
629
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`);
|
|
532
630
|
const data = await response.json();
|
|
533
631
|
return data.reactions;
|
|
534
632
|
}
|
|
@@ -652,9 +750,7 @@ var MoltDMClient = class {
|
|
|
652
750
|
const encryptionKeys = {
|
|
653
751
|
identityKey: this.identity.publicKey,
|
|
654
752
|
privateKey: this.identity.privateKey,
|
|
655
|
-
// Ed25519 private key for signing
|
|
656
753
|
signedPreKeyPrivate: this.identity.signedPreKey.privateKey,
|
|
657
|
-
// X25519 private key for decrypting sender keys
|
|
658
754
|
senderKeys: senderKeysObj
|
|
659
755
|
};
|
|
660
756
|
const response = await this.fetch("/api/pair/approve", {
|
|
@@ -694,16 +790,14 @@ var MoltDMClient = class {
|
|
|
694
790
|
return data.events;
|
|
695
791
|
}
|
|
696
792
|
// ============================================
|
|
697
|
-
// Encryption
|
|
793
|
+
// Encryption
|
|
698
794
|
// ============================================
|
|
699
795
|
async encrypt(plaintext, key) {
|
|
700
796
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
701
797
|
const encoder = new TextEncoder();
|
|
702
798
|
const data = encoder.encode(plaintext);
|
|
703
799
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
704
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
705
|
-
"encrypt"
|
|
706
|
-
]);
|
|
800
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
707
801
|
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cryptoKey, data);
|
|
708
802
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
709
803
|
combined.set(iv);
|
|
@@ -715,9 +809,7 @@ var MoltDMClient = class {
|
|
|
715
809
|
const iv = combined.slice(0, 12);
|
|
716
810
|
const encrypted = combined.slice(12);
|
|
717
811
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
718
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
719
|
-
"decrypt"
|
|
720
|
-
]);
|
|
812
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["decrypt"]);
|
|
721
813
|
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cryptoKey, encrypted);
|
|
722
814
|
const decoder = new TextDecoder();
|
|
723
815
|
return decoder.decode(decrypted);
|
|
@@ -730,22 +822,12 @@ var MoltDMClient = class {
|
|
|
730
822
|
throw new Error("Not initialized. Call initialize() first.");
|
|
731
823
|
}
|
|
732
824
|
}
|
|
733
|
-
/**
|
|
734
|
-
* Sign a message using Ed25519
|
|
735
|
-
*/
|
|
736
825
|
async signMessage(message) {
|
|
737
826
|
const privateKeyBytes = fromBase64(this.identity.privateKey);
|
|
738
|
-
const signature = await ed.signAsync(
|
|
739
|
-
new TextEncoder().encode(message),
|
|
740
|
-
privateKeyBytes
|
|
741
|
-
);
|
|
827
|
+
const signature = await ed.signAsync(new TextEncoder().encode(message), privateKeyBytes);
|
|
742
828
|
return toBase64(signature);
|
|
743
829
|
}
|
|
744
|
-
|
|
745
|
-
* Create the message to sign for a request
|
|
746
|
-
* Format: timestamp:method:path:bodyHash
|
|
747
|
-
*/
|
|
748
|
-
async createSignedMessage(timestamp, method, path2, body) {
|
|
830
|
+
async createSignedMessage(timestamp, method, path, body) {
|
|
749
831
|
let bodyHash = "";
|
|
750
832
|
if (body) {
|
|
751
833
|
const bodyBytes = new TextEncoder().encode(body);
|
|
@@ -753,18 +835,15 @@ var MoltDMClient = class {
|
|
|
753
835
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
754
836
|
bodyHash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
755
837
|
}
|
|
756
|
-
return `${timestamp}:${method}:${
|
|
838
|
+
return `${timestamp}:${method}:${path}:${bodyHash}`;
|
|
757
839
|
}
|
|
758
|
-
|
|
759
|
-
* Make an authenticated fetch request with Ed25519 signature
|
|
760
|
-
*/
|
|
761
|
-
async fetch(path2, options = {}) {
|
|
840
|
+
async fetch(path, options = {}) {
|
|
762
841
|
const method = options.method || "GET";
|
|
763
842
|
const body = options.body;
|
|
764
843
|
const timestamp = Date.now().toString();
|
|
765
|
-
const message = await this.createSignedMessage(timestamp, method,
|
|
844
|
+
const message = await this.createSignedMessage(timestamp, method, path, body);
|
|
766
845
|
const signature = await this.signMessage(message);
|
|
767
|
-
const response = await fetch(`${this.relayUrl}${
|
|
846
|
+
const response = await fetch(`${this.relayUrl}${path}`, {
|
|
768
847
|
...options,
|
|
769
848
|
headers: {
|
|
770
849
|
"Content-Type": "application/json",
|
|
@@ -783,6 +862,9 @@ var MoltDMClient = class {
|
|
|
783
862
|
};
|
|
784
863
|
var index_default = MoltDMClient;
|
|
785
864
|
export {
|
|
865
|
+
BrowserStorage,
|
|
866
|
+
FileStorage,
|
|
867
|
+
MemoryStorage,
|
|
786
868
|
MoltDMClient,
|
|
787
869
|
index_default as default
|
|
788
870
|
};
|