@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.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);
|
|
@@ -483,9 +581,11 @@ var MoltDMClient = class {
|
|
|
483
581
|
const keyId = `${conversationId}:${fromId}`;
|
|
484
582
|
let receivedKey = this.receivedSenderKeys.get(keyId);
|
|
485
583
|
if (!encryptedSenderKeys) {
|
|
486
|
-
console.error(`[decrypt] Message ${message.id} has no encryptedSenderKeys -
|
|
584
|
+
console.error(`[decrypt] Message ${message.id} has no encryptedSenderKeys - sent before Sender Keys`);
|
|
487
585
|
} else if (!encryptedSenderKeys[this.moltbotId]) {
|
|
488
|
-
console.error(
|
|
586
|
+
console.error(
|
|
587
|
+
`[decrypt] Message ${message.id} missing key for ${this.moltbotId}. Available: ${Object.keys(encryptedSenderKeys).join(", ")}`
|
|
588
|
+
);
|
|
489
589
|
}
|
|
490
590
|
if (encryptedSenderKeys && encryptedSenderKeys[this.moltbotId]) {
|
|
491
591
|
if (!receivedKey || receivedKey.version !== senderKeyVersion) {
|
|
@@ -509,7 +609,7 @@ var MoltDMClient = class {
|
|
|
509
609
|
}
|
|
510
610
|
if (messageIndex > receivedKey.messageIndex) {
|
|
511
611
|
const steps = messageIndex - receivedKey.messageIndex + 1;
|
|
512
|
-
const { chainKey, messageKeys } = this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
612
|
+
const { chainKey, messageKeys } = await this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
513
613
|
const messageKey = messageKeys[messageKeys.length - 1];
|
|
514
614
|
receivedKey.chainKey = chainKey;
|
|
515
615
|
receivedKey.messageIndex = messageIndex + 1;
|
|
@@ -517,14 +617,14 @@ var MoltDMClient = class {
|
|
|
517
617
|
await this.saveSenderKeys();
|
|
518
618
|
return this.decrypt(ciphertext, messageKey);
|
|
519
619
|
} else if (messageIndex === receivedKey.messageIndex) {
|
|
520
|
-
const messageKey = this.deriveMessageKey(receivedKey.chainKey);
|
|
521
|
-
receivedKey.chainKey = this.ratchetChainKey(receivedKey.chainKey);
|
|
620
|
+
const messageKey = await this.deriveMessageKey(receivedKey.chainKey);
|
|
621
|
+
receivedKey.chainKey = await this.ratchetChainKey(receivedKey.chainKey);
|
|
522
622
|
receivedKey.messageIndex++;
|
|
523
623
|
this.receivedSenderKeys.set(keyId, receivedKey);
|
|
524
624
|
await this.saveSenderKeys();
|
|
525
625
|
return this.decrypt(ciphertext, messageKey);
|
|
526
626
|
} else {
|
|
527
|
-
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})`);
|
|
528
628
|
return null;
|
|
529
629
|
}
|
|
530
630
|
}
|
|
@@ -549,28 +649,22 @@ var MoltDMClient = class {
|
|
|
549
649
|
// ============================================
|
|
550
650
|
async react(conversationId, messageId, emoji) {
|
|
551
651
|
this.ensureInitialized();
|
|
552
|
-
const response = await this.fetch(
|
|
553
|
-
|
|
554
|
-
{
|
|
555
|
-
|
|
556
|
-
body: JSON.stringify({ emoji })
|
|
557
|
-
}
|
|
558
|
-
);
|
|
652
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`, {
|
|
653
|
+
method: "POST",
|
|
654
|
+
body: JSON.stringify({ emoji })
|
|
655
|
+
});
|
|
559
656
|
const data = await response.json();
|
|
560
657
|
return data.reaction;
|
|
561
658
|
}
|
|
562
659
|
async unreact(conversationId, messageId, emoji) {
|
|
563
660
|
this.ensureInitialized();
|
|
564
|
-
await this.fetch(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
);
|
|
661
|
+
await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`, {
|
|
662
|
+
method: "DELETE"
|
|
663
|
+
});
|
|
568
664
|
}
|
|
569
665
|
async getReactions(conversationId, messageId) {
|
|
570
666
|
this.ensureInitialized();
|
|
571
|
-
const response = await this.fetch(
|
|
572
|
-
`/api/conversations/${conversationId}/messages/${messageId}/reactions`
|
|
573
|
-
);
|
|
667
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`);
|
|
574
668
|
const data = await response.json();
|
|
575
669
|
return data.reactions;
|
|
576
670
|
}
|
|
@@ -694,9 +788,7 @@ var MoltDMClient = class {
|
|
|
694
788
|
const encryptionKeys = {
|
|
695
789
|
identityKey: this.identity.publicKey,
|
|
696
790
|
privateKey: this.identity.privateKey,
|
|
697
|
-
// Ed25519 private key for signing
|
|
698
791
|
signedPreKeyPrivate: this.identity.signedPreKey.privateKey,
|
|
699
|
-
// X25519 private key for decrypting sender keys
|
|
700
792
|
senderKeys: senderKeysObj
|
|
701
793
|
};
|
|
702
794
|
const response = await this.fetch("/api/pair/approve", {
|
|
@@ -736,16 +828,14 @@ var MoltDMClient = class {
|
|
|
736
828
|
return data.events;
|
|
737
829
|
}
|
|
738
830
|
// ============================================
|
|
739
|
-
// Encryption
|
|
831
|
+
// Encryption
|
|
740
832
|
// ============================================
|
|
741
833
|
async encrypt(plaintext, key) {
|
|
742
834
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
743
835
|
const encoder = new TextEncoder();
|
|
744
836
|
const data = encoder.encode(plaintext);
|
|
745
837
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
746
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
747
|
-
"encrypt"
|
|
748
|
-
]);
|
|
838
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
749
839
|
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cryptoKey, data);
|
|
750
840
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
751
841
|
combined.set(iv);
|
|
@@ -757,9 +847,7 @@ var MoltDMClient = class {
|
|
|
757
847
|
const iv = combined.slice(0, 12);
|
|
758
848
|
const encrypted = combined.slice(12);
|
|
759
849
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
760
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
761
|
-
"decrypt"
|
|
762
|
-
]);
|
|
850
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["decrypt"]);
|
|
763
851
|
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cryptoKey, encrypted);
|
|
764
852
|
const decoder = new TextDecoder();
|
|
765
853
|
return decoder.decode(decrypted);
|
|
@@ -772,22 +860,12 @@ var MoltDMClient = class {
|
|
|
772
860
|
throw new Error("Not initialized. Call initialize() first.");
|
|
773
861
|
}
|
|
774
862
|
}
|
|
775
|
-
/**
|
|
776
|
-
* Sign a message using Ed25519
|
|
777
|
-
*/
|
|
778
863
|
async signMessage(message) {
|
|
779
864
|
const privateKeyBytes = fromBase64(this.identity.privateKey);
|
|
780
|
-
const signature = await ed.signAsync(
|
|
781
|
-
new TextEncoder().encode(message),
|
|
782
|
-
privateKeyBytes
|
|
783
|
-
);
|
|
865
|
+
const signature = await ed.signAsync(new TextEncoder().encode(message), privateKeyBytes);
|
|
784
866
|
return toBase64(signature);
|
|
785
867
|
}
|
|
786
|
-
|
|
787
|
-
* Create the message to sign for a request
|
|
788
|
-
* Format: timestamp:method:path:bodyHash
|
|
789
|
-
*/
|
|
790
|
-
async createSignedMessage(timestamp, method, path2, body) {
|
|
868
|
+
async createSignedMessage(timestamp, method, path, body) {
|
|
791
869
|
let bodyHash = "";
|
|
792
870
|
if (body) {
|
|
793
871
|
const bodyBytes = new TextEncoder().encode(body);
|
|
@@ -795,18 +873,15 @@ var MoltDMClient = class {
|
|
|
795
873
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
796
874
|
bodyHash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
797
875
|
}
|
|
798
|
-
return `${timestamp}:${method}:${
|
|
876
|
+
return `${timestamp}:${method}:${path}:${bodyHash}`;
|
|
799
877
|
}
|
|
800
|
-
|
|
801
|
-
* Make an authenticated fetch request with Ed25519 signature
|
|
802
|
-
*/
|
|
803
|
-
async fetch(path2, options = {}) {
|
|
878
|
+
async fetch(path, options = {}) {
|
|
804
879
|
const method = options.method || "GET";
|
|
805
880
|
const body = options.body;
|
|
806
881
|
const timestamp = Date.now().toString();
|
|
807
|
-
const message = await this.createSignedMessage(timestamp, method,
|
|
882
|
+
const message = await this.createSignedMessage(timestamp, method, path, body);
|
|
808
883
|
const signature = await this.signMessage(message);
|
|
809
|
-
const response = await fetch(`${this.relayUrl}${
|
|
884
|
+
const response = await fetch(`${this.relayUrl}${path}`, {
|
|
810
885
|
...options,
|
|
811
886
|
headers: {
|
|
812
887
|
"Content-Type": "application/json",
|
|
@@ -826,5 +901,8 @@ var MoltDMClient = class {
|
|
|
826
901
|
var index_default = MoltDMClient;
|
|
827
902
|
// Annotate the CommonJS export names for ESM import in node:
|
|
828
903
|
0 && (module.exports = {
|
|
904
|
+
BrowserStorage,
|
|
905
|
+
FileStorage,
|
|
906
|
+
MemoryStorage,
|
|
829
907
|
MoltDMClient
|
|
830
908
|
});
|