@moltdm/client 1.3.2 → 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 +200 -122
- package/dist/index.mjs +197 -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;
|
|
@@ -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);
|
|
@@ -448,9 +543,11 @@ var MoltDMClient = class {
|
|
|
448
543
|
const keyId = `${conversationId}:${fromId}`;
|
|
449
544
|
let receivedKey = this.receivedSenderKeys.get(keyId);
|
|
450
545
|
if (!encryptedSenderKeys) {
|
|
451
|
-
console.error(`[decrypt] Message ${message.id} has no encryptedSenderKeys -
|
|
546
|
+
console.error(`[decrypt] Message ${message.id} has no encryptedSenderKeys - sent before Sender Keys`);
|
|
452
547
|
} else if (!encryptedSenderKeys[this.moltbotId]) {
|
|
453
|
-
console.error(
|
|
548
|
+
console.error(
|
|
549
|
+
`[decrypt] Message ${message.id} missing key for ${this.moltbotId}. Available: ${Object.keys(encryptedSenderKeys).join(", ")}`
|
|
550
|
+
);
|
|
454
551
|
}
|
|
455
552
|
if (encryptedSenderKeys && encryptedSenderKeys[this.moltbotId]) {
|
|
456
553
|
if (!receivedKey || receivedKey.version !== senderKeyVersion) {
|
|
@@ -474,7 +571,7 @@ var MoltDMClient = class {
|
|
|
474
571
|
}
|
|
475
572
|
if (messageIndex > receivedKey.messageIndex) {
|
|
476
573
|
const steps = messageIndex - receivedKey.messageIndex + 1;
|
|
477
|
-
const { chainKey, messageKeys } = this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
574
|
+
const { chainKey, messageKeys } = await this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
478
575
|
const messageKey = messageKeys[messageKeys.length - 1];
|
|
479
576
|
receivedKey.chainKey = chainKey;
|
|
480
577
|
receivedKey.messageIndex = messageIndex + 1;
|
|
@@ -482,14 +579,14 @@ var MoltDMClient = class {
|
|
|
482
579
|
await this.saveSenderKeys();
|
|
483
580
|
return this.decrypt(ciphertext, messageKey);
|
|
484
581
|
} else if (messageIndex === receivedKey.messageIndex) {
|
|
485
|
-
const messageKey = this.deriveMessageKey(receivedKey.chainKey);
|
|
486
|
-
receivedKey.chainKey = this.ratchetChainKey(receivedKey.chainKey);
|
|
582
|
+
const messageKey = await this.deriveMessageKey(receivedKey.chainKey);
|
|
583
|
+
receivedKey.chainKey = await this.ratchetChainKey(receivedKey.chainKey);
|
|
487
584
|
receivedKey.messageIndex++;
|
|
488
585
|
this.receivedSenderKeys.set(keyId, receivedKey);
|
|
489
586
|
await this.saveSenderKeys();
|
|
490
587
|
return this.decrypt(ciphertext, messageKey);
|
|
491
588
|
} else {
|
|
492
|
-
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})`);
|
|
493
590
|
return null;
|
|
494
591
|
}
|
|
495
592
|
}
|
|
@@ -514,28 +611,22 @@ var MoltDMClient = class {
|
|
|
514
611
|
// ============================================
|
|
515
612
|
async react(conversationId, messageId, emoji) {
|
|
516
613
|
this.ensureInitialized();
|
|
517
|
-
const response = await this.fetch(
|
|
518
|
-
|
|
519
|
-
{
|
|
520
|
-
|
|
521
|
-
body: JSON.stringify({ emoji })
|
|
522
|
-
}
|
|
523
|
-
);
|
|
614
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`, {
|
|
615
|
+
method: "POST",
|
|
616
|
+
body: JSON.stringify({ emoji })
|
|
617
|
+
});
|
|
524
618
|
const data = await response.json();
|
|
525
619
|
return data.reaction;
|
|
526
620
|
}
|
|
527
621
|
async unreact(conversationId, messageId, emoji) {
|
|
528
622
|
this.ensureInitialized();
|
|
529
|
-
await this.fetch(
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
);
|
|
623
|
+
await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`, {
|
|
624
|
+
method: "DELETE"
|
|
625
|
+
});
|
|
533
626
|
}
|
|
534
627
|
async getReactions(conversationId, messageId) {
|
|
535
628
|
this.ensureInitialized();
|
|
536
|
-
const response = await this.fetch(
|
|
537
|
-
`/api/conversations/${conversationId}/messages/${messageId}/reactions`
|
|
538
|
-
);
|
|
629
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`);
|
|
539
630
|
const data = await response.json();
|
|
540
631
|
return data.reactions;
|
|
541
632
|
}
|
|
@@ -659,9 +750,7 @@ var MoltDMClient = class {
|
|
|
659
750
|
const encryptionKeys = {
|
|
660
751
|
identityKey: this.identity.publicKey,
|
|
661
752
|
privateKey: this.identity.privateKey,
|
|
662
|
-
// Ed25519 private key for signing
|
|
663
753
|
signedPreKeyPrivate: this.identity.signedPreKey.privateKey,
|
|
664
|
-
// X25519 private key for decrypting sender keys
|
|
665
754
|
senderKeys: senderKeysObj
|
|
666
755
|
};
|
|
667
756
|
const response = await this.fetch("/api/pair/approve", {
|
|
@@ -701,16 +790,14 @@ var MoltDMClient = class {
|
|
|
701
790
|
return data.events;
|
|
702
791
|
}
|
|
703
792
|
// ============================================
|
|
704
|
-
// Encryption
|
|
793
|
+
// Encryption
|
|
705
794
|
// ============================================
|
|
706
795
|
async encrypt(plaintext, key) {
|
|
707
796
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
708
797
|
const encoder = new TextEncoder();
|
|
709
798
|
const data = encoder.encode(plaintext);
|
|
710
799
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
711
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
712
|
-
"encrypt"
|
|
713
|
-
]);
|
|
800
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
714
801
|
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cryptoKey, data);
|
|
715
802
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
716
803
|
combined.set(iv);
|
|
@@ -722,9 +809,7 @@ var MoltDMClient = class {
|
|
|
722
809
|
const iv = combined.slice(0, 12);
|
|
723
810
|
const encrypted = combined.slice(12);
|
|
724
811
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
725
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
726
|
-
"decrypt"
|
|
727
|
-
]);
|
|
812
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["decrypt"]);
|
|
728
813
|
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cryptoKey, encrypted);
|
|
729
814
|
const decoder = new TextDecoder();
|
|
730
815
|
return decoder.decode(decrypted);
|
|
@@ -737,22 +822,12 @@ var MoltDMClient = class {
|
|
|
737
822
|
throw new Error("Not initialized. Call initialize() first.");
|
|
738
823
|
}
|
|
739
824
|
}
|
|
740
|
-
/**
|
|
741
|
-
* Sign a message using Ed25519
|
|
742
|
-
*/
|
|
743
825
|
async signMessage(message) {
|
|
744
826
|
const privateKeyBytes = fromBase64(this.identity.privateKey);
|
|
745
|
-
const signature = await ed.signAsync(
|
|
746
|
-
new TextEncoder().encode(message),
|
|
747
|
-
privateKeyBytes
|
|
748
|
-
);
|
|
827
|
+
const signature = await ed.signAsync(new TextEncoder().encode(message), privateKeyBytes);
|
|
749
828
|
return toBase64(signature);
|
|
750
829
|
}
|
|
751
|
-
|
|
752
|
-
* Create the message to sign for a request
|
|
753
|
-
* Format: timestamp:method:path:bodyHash
|
|
754
|
-
*/
|
|
755
|
-
async createSignedMessage(timestamp, method, path2, body) {
|
|
830
|
+
async createSignedMessage(timestamp, method, path, body) {
|
|
756
831
|
let bodyHash = "";
|
|
757
832
|
if (body) {
|
|
758
833
|
const bodyBytes = new TextEncoder().encode(body);
|
|
@@ -760,18 +835,15 @@ var MoltDMClient = class {
|
|
|
760
835
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
761
836
|
bodyHash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
762
837
|
}
|
|
763
|
-
return `${timestamp}:${method}:${
|
|
838
|
+
return `${timestamp}:${method}:${path}:${bodyHash}`;
|
|
764
839
|
}
|
|
765
|
-
|
|
766
|
-
* Make an authenticated fetch request with Ed25519 signature
|
|
767
|
-
*/
|
|
768
|
-
async fetch(path2, options = {}) {
|
|
840
|
+
async fetch(path, options = {}) {
|
|
769
841
|
const method = options.method || "GET";
|
|
770
842
|
const body = options.body;
|
|
771
843
|
const timestamp = Date.now().toString();
|
|
772
|
-
const message = await this.createSignedMessage(timestamp, method,
|
|
844
|
+
const message = await this.createSignedMessage(timestamp, method, path, body);
|
|
773
845
|
const signature = await this.signMessage(message);
|
|
774
|
-
const response = await fetch(`${this.relayUrl}${
|
|
846
|
+
const response = await fetch(`${this.relayUrl}${path}`, {
|
|
775
847
|
...options,
|
|
776
848
|
headers: {
|
|
777
849
|
"Content-Type": "application/json",
|
|
@@ -790,6 +862,9 @@ var MoltDMClient = class {
|
|
|
790
862
|
};
|
|
791
863
|
var index_default = MoltDMClient;
|
|
792
864
|
export {
|
|
865
|
+
BrowserStorage,
|
|
866
|
+
FileStorage,
|
|
867
|
+
MemoryStorage,
|
|
793
868
|
MoltDMClient,
|
|
794
869
|
index_default as default
|
|
795
870
|
};
|