@moltdm/client 1.3.2 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +44 -12
- package/dist/index.d.ts +44 -12
- package/dist/index.js +224 -122
- package/dist/index.mjs +221 -122
- package/dist/moltdm.browser.js +17 -0
- package/package.json +4 -2
package/dist/index.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;
|
|
@@ -85,22 +208,30 @@ var MoltDMClient = class {
|
|
|
85
208
|
// ============================================
|
|
86
209
|
async initialize() {
|
|
87
210
|
if (this.identity) {
|
|
211
|
+
this.validateIdentity(this.identity);
|
|
88
212
|
await this.loadSenderKeys();
|
|
89
213
|
return;
|
|
90
214
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (fs.existsSync(identityPath)) {
|
|
96
|
-
const data = fs.readFileSync(identityPath, "utf-8");
|
|
97
|
-
this.identity = JSON.parse(data);
|
|
215
|
+
const identityJson = await this.storage.get("identity");
|
|
216
|
+
if (identityJson) {
|
|
217
|
+
this.identity = JSON.parse(identityJson);
|
|
218
|
+
this.validateIdentity(this.identity);
|
|
98
219
|
} else {
|
|
99
220
|
await this.createIdentity();
|
|
100
|
-
|
|
221
|
+
await this.storage.set("identity", JSON.stringify(this.identity));
|
|
101
222
|
}
|
|
102
223
|
await this.loadSenderKeys();
|
|
103
224
|
}
|
|
225
|
+
validateIdentity(identity) {
|
|
226
|
+
if (!identity.signedPreKey?.privateKey) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
"Identity is missing signedPreKey.privateKey. This moltbot was created with an older client version. Please delete your stored identity and re-register, or update your identity file to include the signedPreKey private key."
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
if (!identity.moltbotId || !identity.publicKey || !identity.privateKey) {
|
|
232
|
+
throw new Error("Identity is missing required fields (moltbotId, publicKey, or privateKey)");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
104
235
|
async createIdentity() {
|
|
105
236
|
const privateKeyBytes = ed.utils.randomPrivateKey();
|
|
106
237
|
const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
|
|
@@ -149,10 +280,9 @@ var MoltDMClient = class {
|
|
|
149
280
|
};
|
|
150
281
|
}
|
|
151
282
|
async loadSenderKeys() {
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
154
|
-
const
|
|
155
|
-
const keys = JSON.parse(data);
|
|
283
|
+
const keysJson = await this.storage.get("sender_keys");
|
|
284
|
+
if (keysJson) {
|
|
285
|
+
const keys = JSON.parse(keysJson);
|
|
156
286
|
for (const [convId, keyData] of Object.entries(keys)) {
|
|
157
287
|
const k = keyData;
|
|
158
288
|
const chainKey = fromBase64(k.chainKey || k.key || "");
|
|
@@ -164,10 +294,9 @@ var MoltDMClient = class {
|
|
|
164
294
|
});
|
|
165
295
|
}
|
|
166
296
|
}
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
const
|
|
170
|
-
const keys = JSON.parse(data);
|
|
297
|
+
const receivedJson = await this.storage.get("received_sender_keys");
|
|
298
|
+
if (receivedJson) {
|
|
299
|
+
const keys = JSON.parse(receivedJson);
|
|
171
300
|
for (const [key, keyData] of Object.entries(keys)) {
|
|
172
301
|
const k = keyData;
|
|
173
302
|
this.receivedSenderKeys.set(key, {
|
|
@@ -179,7 +308,6 @@ var MoltDMClient = class {
|
|
|
179
308
|
}
|
|
180
309
|
}
|
|
181
310
|
async saveSenderKeys() {
|
|
182
|
-
const keysPath = path.join(this.storagePath, "sender_keys.json");
|
|
183
311
|
const obj = {};
|
|
184
312
|
for (const [convId, keyData] of this.senderKeys) {
|
|
185
313
|
obj[convId] = {
|
|
@@ -189,8 +317,7 @@ var MoltDMClient = class {
|
|
|
189
317
|
messageIndex: keyData.messageIndex
|
|
190
318
|
};
|
|
191
319
|
}
|
|
192
|
-
|
|
193
|
-
const receivedPath = path.join(this.storagePath, "received_sender_keys.json");
|
|
320
|
+
await this.storage.set("sender_keys", JSON.stringify(obj));
|
|
194
321
|
const receivedObj = {};
|
|
195
322
|
for (const [key, keyData] of this.receivedSenderKeys) {
|
|
196
323
|
receivedObj[key] = {
|
|
@@ -199,7 +326,7 @@ var MoltDMClient = class {
|
|
|
199
326
|
messageIndex: keyData.messageIndex
|
|
200
327
|
};
|
|
201
328
|
}
|
|
202
|
-
|
|
329
|
+
await this.storage.set("received_sender_keys", JSON.stringify(receivedObj));
|
|
203
330
|
}
|
|
204
331
|
// ============================================
|
|
205
332
|
// Sender Keys Protocol (Signal-style)
|
|
@@ -208,37 +335,25 @@ var MoltDMClient = class {
|
|
|
208
335
|
* Derive message key from chain key using HMAC
|
|
209
336
|
* message_key = HMAC-SHA256(chain_key, 0x01)
|
|
210
337
|
*/
|
|
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;
|
|
338
|
+
async deriveMessageKey(chainKey) {
|
|
339
|
+
return hmacSha256(chainKey, new Uint8Array([1]));
|
|
219
340
|
}
|
|
220
341
|
/**
|
|
221
342
|
* Ratchet chain key forward
|
|
222
343
|
* new_chain_key = HMAC-SHA256(chain_key, 0x02)
|
|
223
344
|
*/
|
|
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;
|
|
345
|
+
async ratchetChainKey(chainKey) {
|
|
346
|
+
return hmacSha256(chainKey, new Uint8Array([2]));
|
|
232
347
|
}
|
|
233
348
|
/**
|
|
234
349
|
* Ratchet a chain key forward N steps (for catching up on missed messages)
|
|
235
350
|
*/
|
|
236
|
-
ratchetChainKeyN(chainKey, steps) {
|
|
351
|
+
async ratchetChainKeyN(chainKey, steps) {
|
|
237
352
|
const messageKeys = [];
|
|
238
353
|
let current = chainKey;
|
|
239
354
|
for (let i = 0; i < steps; i++) {
|
|
240
|
-
messageKeys.push(this.deriveMessageKey(current));
|
|
241
|
-
current = this.ratchetChainKey(current);
|
|
355
|
+
messageKeys.push(await this.deriveMessageKey(current));
|
|
356
|
+
current = await this.ratchetChainKey(current);
|
|
242
357
|
}
|
|
243
358
|
return { chainKey: current, messageKeys };
|
|
244
359
|
}
|
|
@@ -332,27 +447,24 @@ var MoltDMClient = class {
|
|
|
332
447
|
async send(conversationId, content, options) {
|
|
333
448
|
this.ensureInitialized();
|
|
334
449
|
let senderKeyState = this.senderKeys.get(conversationId);
|
|
335
|
-
const isNewKey = !senderKeyState;
|
|
336
450
|
if (!senderKeyState) {
|
|
337
451
|
const initialKey = crypto.getRandomValues(new Uint8Array(32));
|
|
338
452
|
senderKeyState = {
|
|
339
453
|
chainKey: initialKey,
|
|
340
454
|
initialChainKey: new Uint8Array(initialKey),
|
|
341
|
-
// Copy for distribution
|
|
342
455
|
version: 1,
|
|
343
456
|
messageIndex: 0
|
|
344
457
|
};
|
|
345
458
|
this.senderKeys.set(conversationId, senderKeyState);
|
|
346
459
|
}
|
|
347
|
-
const messageKey = this.deriveMessageKey(senderKeyState.chainKey);
|
|
460
|
+
const messageKey = await this.deriveMessageKey(senderKeyState.chainKey);
|
|
348
461
|
const currentIndex = senderKeyState.messageIndex;
|
|
349
|
-
senderKeyState.chainKey = this.ratchetChainKey(senderKeyState.chainKey);
|
|
462
|
+
senderKeyState.chainKey = await this.ratchetChainKey(senderKeyState.chainKey);
|
|
350
463
|
senderKeyState.messageIndex++;
|
|
351
464
|
const ciphertext = await this.encrypt(content, messageKey);
|
|
352
465
|
const encryptedSenderKeys = await this.encryptChainKeyForRecipients(
|
|
353
466
|
conversationId,
|
|
354
467
|
senderKeyState.initialChainKey
|
|
355
|
-
// Send the original, unratcheted key
|
|
356
468
|
);
|
|
357
469
|
const response = await this.fetch(`/api/conversations/${conversationId}/messages`, {
|
|
358
470
|
method: "POST",
|
|
@@ -404,26 +516,23 @@ var MoltDMClient = class {
|
|
|
404
516
|
sharedSecretCopy.set(new Uint8Array(sharedSecret));
|
|
405
517
|
const chainKeyCopy = new Uint8Array(32);
|
|
406
518
|
chainKeyCopy.set(new Uint8Array(chainKey));
|
|
407
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
408
|
-
"
|
|
409
|
-
|
|
410
|
-
{ name: "HKDF" },
|
|
411
|
-
false,
|
|
412
|
-
["deriveKey"]
|
|
413
|
-
);
|
|
519
|
+
const keyMaterial = await crypto.subtle.importKey("raw", sharedSecretCopy.buffer, { name: "HKDF" }, false, [
|
|
520
|
+
"deriveKey"
|
|
521
|
+
]);
|
|
414
522
|
const aesKey = await crypto.subtle.deriveKey(
|
|
415
|
-
{
|
|
523
|
+
{
|
|
524
|
+
name: "HKDF",
|
|
525
|
+
hash: "SHA-256",
|
|
526
|
+
salt: new Uint8Array(32),
|
|
527
|
+
info: new TextEncoder().encode("moltdm-sender-key")
|
|
528
|
+
},
|
|
416
529
|
keyMaterial,
|
|
417
530
|
{ name: "AES-GCM", length: 256 },
|
|
418
531
|
false,
|
|
419
532
|
["encrypt"]
|
|
420
533
|
);
|
|
421
534
|
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
|
-
);
|
|
535
|
+
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, chainKeyCopy.buffer);
|
|
427
536
|
const combined = new Uint8Array(32 + 12 + encrypted.byteLength);
|
|
428
537
|
combined.set(ephemeralPublic);
|
|
429
538
|
combined.set(iv, 32);
|
|
@@ -443,7 +552,19 @@ var MoltDMClient = class {
|
|
|
443
552
|
*/
|
|
444
553
|
async decryptChainKey(encryptedBlob) {
|
|
445
554
|
try {
|
|
555
|
+
if (!encryptedBlob) {
|
|
556
|
+
console.error("[decryptChainKey] No encrypted blob provided");
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
if (!this.identity?.signedPreKey?.privateKey) {
|
|
560
|
+
console.error("[decryptChainKey] Missing signedPreKey.privateKey in identity");
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
446
563
|
const combined = fromBase64(encryptedBlob);
|
|
564
|
+
if (!combined || combined.length < 45) {
|
|
565
|
+
console.error("[decryptChainKey] Invalid encrypted blob length:", combined?.length);
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
447
568
|
const ephemeralPublic = combined.slice(0, 32);
|
|
448
569
|
const iv = combined.slice(32, 44);
|
|
449
570
|
const encrypted = combined.slice(44);
|
|
@@ -457,17 +578,18 @@ var MoltDMClient = class {
|
|
|
457
578
|
["deriveKey"]
|
|
458
579
|
);
|
|
459
580
|
const aesKey = await crypto.subtle.deriveKey(
|
|
460
|
-
{
|
|
581
|
+
{
|
|
582
|
+
name: "HKDF",
|
|
583
|
+
hash: "SHA-256",
|
|
584
|
+
salt: new Uint8Array(32),
|
|
585
|
+
info: new TextEncoder().encode("moltdm-sender-key")
|
|
586
|
+
},
|
|
461
587
|
keyMaterial,
|
|
462
588
|
{ name: "AES-GCM", length: 256 },
|
|
463
589
|
false,
|
|
464
590
|
["decrypt"]
|
|
465
591
|
);
|
|
466
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
467
|
-
{ name: "AES-GCM", iv },
|
|
468
|
-
aesKey,
|
|
469
|
-
encrypted
|
|
470
|
-
);
|
|
592
|
+
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, encrypted);
|
|
471
593
|
return new Uint8Array(decrypted);
|
|
472
594
|
} catch (e) {
|
|
473
595
|
console.error("Failed to decrypt chain key:", e);
|
|
@@ -483,9 +605,11 @@ var MoltDMClient = class {
|
|
|
483
605
|
const keyId = `${conversationId}:${fromId}`;
|
|
484
606
|
let receivedKey = this.receivedSenderKeys.get(keyId);
|
|
485
607
|
if (!encryptedSenderKeys) {
|
|
486
|
-
console.error(`[decrypt] Message ${message.id} has no encryptedSenderKeys -
|
|
608
|
+
console.error(`[decrypt] Message ${message.id} has no encryptedSenderKeys - sent before Sender Keys`);
|
|
487
609
|
} else if (!encryptedSenderKeys[this.moltbotId]) {
|
|
488
|
-
console.error(
|
|
610
|
+
console.error(
|
|
611
|
+
`[decrypt] Message ${message.id} missing key for ${this.moltbotId}. Available: ${Object.keys(encryptedSenderKeys).join(", ")}`
|
|
612
|
+
);
|
|
489
613
|
}
|
|
490
614
|
if (encryptedSenderKeys && encryptedSenderKeys[this.moltbotId]) {
|
|
491
615
|
if (!receivedKey || receivedKey.version !== senderKeyVersion) {
|
|
@@ -509,7 +633,7 @@ var MoltDMClient = class {
|
|
|
509
633
|
}
|
|
510
634
|
if (messageIndex > receivedKey.messageIndex) {
|
|
511
635
|
const steps = messageIndex - receivedKey.messageIndex + 1;
|
|
512
|
-
const { chainKey, messageKeys } = this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
636
|
+
const { chainKey, messageKeys } = await this.ratchetChainKeyN(receivedKey.chainKey, steps);
|
|
513
637
|
const messageKey = messageKeys[messageKeys.length - 1];
|
|
514
638
|
receivedKey.chainKey = chainKey;
|
|
515
639
|
receivedKey.messageIndex = messageIndex + 1;
|
|
@@ -517,14 +641,14 @@ var MoltDMClient = class {
|
|
|
517
641
|
await this.saveSenderKeys();
|
|
518
642
|
return this.decrypt(ciphertext, messageKey);
|
|
519
643
|
} else if (messageIndex === receivedKey.messageIndex) {
|
|
520
|
-
const messageKey = this.deriveMessageKey(receivedKey.chainKey);
|
|
521
|
-
receivedKey.chainKey = this.ratchetChainKey(receivedKey.chainKey);
|
|
644
|
+
const messageKey = await this.deriveMessageKey(receivedKey.chainKey);
|
|
645
|
+
receivedKey.chainKey = await this.ratchetChainKey(receivedKey.chainKey);
|
|
522
646
|
receivedKey.messageIndex++;
|
|
523
647
|
this.receivedSenderKeys.set(keyId, receivedKey);
|
|
524
648
|
await this.saveSenderKeys();
|
|
525
649
|
return this.decrypt(ciphertext, messageKey);
|
|
526
650
|
} else {
|
|
527
|
-
console.error(`Message index ${messageIndex} is in the past (current: ${receivedKey.messageIndex})`);
|
|
651
|
+
console.error(`[decrypt] Message index ${messageIndex} is in the past (current: ${receivedKey.messageIndex})`);
|
|
528
652
|
return null;
|
|
529
653
|
}
|
|
530
654
|
}
|
|
@@ -549,28 +673,22 @@ var MoltDMClient = class {
|
|
|
549
673
|
// ============================================
|
|
550
674
|
async react(conversationId, messageId, emoji) {
|
|
551
675
|
this.ensureInitialized();
|
|
552
|
-
const response = await this.fetch(
|
|
553
|
-
|
|
554
|
-
{
|
|
555
|
-
|
|
556
|
-
body: JSON.stringify({ emoji })
|
|
557
|
-
}
|
|
558
|
-
);
|
|
676
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`, {
|
|
677
|
+
method: "POST",
|
|
678
|
+
body: JSON.stringify({ emoji })
|
|
679
|
+
});
|
|
559
680
|
const data = await response.json();
|
|
560
681
|
return data.reaction;
|
|
561
682
|
}
|
|
562
683
|
async unreact(conversationId, messageId, emoji) {
|
|
563
684
|
this.ensureInitialized();
|
|
564
|
-
await this.fetch(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
);
|
|
685
|
+
await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`, {
|
|
686
|
+
method: "DELETE"
|
|
687
|
+
});
|
|
568
688
|
}
|
|
569
689
|
async getReactions(conversationId, messageId) {
|
|
570
690
|
this.ensureInitialized();
|
|
571
|
-
const response = await this.fetch(
|
|
572
|
-
`/api/conversations/${conversationId}/messages/${messageId}/reactions`
|
|
573
|
-
);
|
|
691
|
+
const response = await this.fetch(`/api/conversations/${conversationId}/messages/${messageId}/reactions`);
|
|
574
692
|
const data = await response.json();
|
|
575
693
|
return data.reactions;
|
|
576
694
|
}
|
|
@@ -694,9 +812,7 @@ var MoltDMClient = class {
|
|
|
694
812
|
const encryptionKeys = {
|
|
695
813
|
identityKey: this.identity.publicKey,
|
|
696
814
|
privateKey: this.identity.privateKey,
|
|
697
|
-
// Ed25519 private key for signing
|
|
698
815
|
signedPreKeyPrivate: this.identity.signedPreKey.privateKey,
|
|
699
|
-
// X25519 private key for decrypting sender keys
|
|
700
816
|
senderKeys: senderKeysObj
|
|
701
817
|
};
|
|
702
818
|
const response = await this.fetch("/api/pair/approve", {
|
|
@@ -736,16 +852,14 @@ var MoltDMClient = class {
|
|
|
736
852
|
return data.events;
|
|
737
853
|
}
|
|
738
854
|
// ============================================
|
|
739
|
-
// Encryption
|
|
855
|
+
// Encryption
|
|
740
856
|
// ============================================
|
|
741
857
|
async encrypt(plaintext, key) {
|
|
742
858
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
743
859
|
const encoder = new TextEncoder();
|
|
744
860
|
const data = encoder.encode(plaintext);
|
|
745
861
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
746
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
747
|
-
"encrypt"
|
|
748
|
-
]);
|
|
862
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
749
863
|
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cryptoKey, data);
|
|
750
864
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
751
865
|
combined.set(iv);
|
|
@@ -757,9 +871,7 @@ var MoltDMClient = class {
|
|
|
757
871
|
const iv = combined.slice(0, 12);
|
|
758
872
|
const encrypted = combined.slice(12);
|
|
759
873
|
const keyBuffer = new Uint8Array(key).buffer;
|
|
760
|
-
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, [
|
|
761
|
-
"decrypt"
|
|
762
|
-
]);
|
|
874
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["decrypt"]);
|
|
763
875
|
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cryptoKey, encrypted);
|
|
764
876
|
const decoder = new TextDecoder();
|
|
765
877
|
return decoder.decode(decrypted);
|
|
@@ -772,22 +884,12 @@ var MoltDMClient = class {
|
|
|
772
884
|
throw new Error("Not initialized. Call initialize() first.");
|
|
773
885
|
}
|
|
774
886
|
}
|
|
775
|
-
/**
|
|
776
|
-
* Sign a message using Ed25519
|
|
777
|
-
*/
|
|
778
887
|
async signMessage(message) {
|
|
779
888
|
const privateKeyBytes = fromBase64(this.identity.privateKey);
|
|
780
|
-
const signature = await ed.signAsync(
|
|
781
|
-
new TextEncoder().encode(message),
|
|
782
|
-
privateKeyBytes
|
|
783
|
-
);
|
|
889
|
+
const signature = await ed.signAsync(new TextEncoder().encode(message), privateKeyBytes);
|
|
784
890
|
return toBase64(signature);
|
|
785
891
|
}
|
|
786
|
-
|
|
787
|
-
* Create the message to sign for a request
|
|
788
|
-
* Format: timestamp:method:path:bodyHash
|
|
789
|
-
*/
|
|
790
|
-
async createSignedMessage(timestamp, method, path2, body) {
|
|
892
|
+
async createSignedMessage(timestamp, method, path, body) {
|
|
791
893
|
let bodyHash = "";
|
|
792
894
|
if (body) {
|
|
793
895
|
const bodyBytes = new TextEncoder().encode(body);
|
|
@@ -795,18 +897,15 @@ var MoltDMClient = class {
|
|
|
795
897
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
796
898
|
bodyHash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
797
899
|
}
|
|
798
|
-
return `${timestamp}:${method}:${
|
|
900
|
+
return `${timestamp}:${method}:${path}:${bodyHash}`;
|
|
799
901
|
}
|
|
800
|
-
|
|
801
|
-
* Make an authenticated fetch request with Ed25519 signature
|
|
802
|
-
*/
|
|
803
|
-
async fetch(path2, options = {}) {
|
|
902
|
+
async fetch(path, options = {}) {
|
|
804
903
|
const method = options.method || "GET";
|
|
805
904
|
const body = options.body;
|
|
806
905
|
const timestamp = Date.now().toString();
|
|
807
|
-
const message = await this.createSignedMessage(timestamp, method,
|
|
906
|
+
const message = await this.createSignedMessage(timestamp, method, path, body);
|
|
808
907
|
const signature = await this.signMessage(message);
|
|
809
|
-
const response = await fetch(`${this.relayUrl}${
|
|
908
|
+
const response = await fetch(`${this.relayUrl}${path}`, {
|
|
810
909
|
...options,
|
|
811
910
|
headers: {
|
|
812
911
|
"Content-Type": "application/json",
|
|
@@ -826,5 +925,8 @@ var MoltDMClient = class {
|
|
|
826
925
|
var index_default = MoltDMClient;
|
|
827
926
|
// Annotate the CommonJS export names for ESM import in node:
|
|
828
927
|
0 && (module.exports = {
|
|
928
|
+
BrowserStorage,
|
|
929
|
+
FileStorage,
|
|
930
|
+
MemoryStorage,
|
|
829
931
|
MoltDMClient
|
|
830
932
|
});
|