@vex-chat/libvex 0.27.1 → 1.0.0-rc.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/Client.js CHANGED
@@ -1,61 +1,33 @@
1
- "use strict";
2
1
  // tslint:disable: no-empty-interface
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
6
- }) : (function(o, m, k, k2) {
7
- if (k2 === undefined) k2 = k;
8
- o[k2] = m[k];
9
- }));
10
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
11
- Object.defineProperty(o, "default", { enumerable: true, value: v });
12
- }) : function(o, v) {
13
- o["default"] = v;
14
- });
15
- var __importStar = (this && this.__importStar) || function (mod) {
16
- if (mod && mod.__esModule) return mod;
17
- var result = {};
18
- if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
19
- __setModuleDefault(result, mod);
20
- return result;
21
- };
22
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
23
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
24
- return new (P || (P = Promise))(function (resolve, reject) {
25
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
26
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
27
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
28
- step((generator = generator.apply(thisArg, _arguments || [])).next());
29
- });
30
- };
31
- var __importDefault = (this && this.__importDefault) || function (mod) {
32
- return (mod && mod.__esModule) ? mod : { "default": mod };
33
- };
34
- Object.defineProperty(exports, "__esModule", { value: true });
35
- exports.Client = void 0;
36
- const sleep_1 = require("@extrahash/sleep");
37
- const crypto_1 = require("@vex-chat/crypto");
38
- const types_1 = require("@vex-chat/types");
39
- const axios_1 = __importDefault(require("axios"));
40
- const browser_or_node_1 = require("browser-or-node");
41
- const btoa_1 = __importDefault(require("btoa"));
42
- const chalk_1 = __importDefault(require("chalk"));
43
- const events_1 = require("events");
44
- const msgpack_lite_1 = __importDefault(require("msgpack-lite"));
45
- const object_hash_1 = __importDefault(require("object-hash"));
46
- const os_1 = __importDefault(require("os"));
47
- const perf_hooks_1 = require("perf_hooks");
48
- const tweetnacl_1 = __importDefault(require("tweetnacl"));
49
- const uuid = __importStar(require("uuid"));
50
- const ws_1 = __importDefault(require("ws"));
51
- const Storage_1 = require("./Storage");
52
- const capitalize_1 = require("./utils/capitalize");
53
- const createLogger_1 = require("./utils/createLogger");
54
- const formatBytes_1 = require("./utils/formatBytes");
55
- const sqlSessionToCrypto_1 = require("./utils/sqlSessionToCrypto");
56
- const uint8uuid_1 = require("./utils/uint8uuid");
57
- axios_1.default.defaults.withCredentials = true;
58
- axios_1.default.defaults.responseType = "arraybuffer";
2
+ import { sleep } from "@extrahash/sleep";
3
+ import { xConcat, xConstants, xDH, xEncode, xHMAC, xKDF, XKeyConvert, xMakeNonce, xMnemonic, XUtils, } from "@vex-chat/crypto";
4
+ import { MailType } from "@vex-chat/types";
5
+ import ax, { AxiosError } from "axios";
6
+ import { isBrowser, isNode } from "browser-or-node";
7
+ import btoa from "btoa";
8
+ import chalk from "chalk";
9
+ import { EventEmitter } from "events";
10
+ import { Packr } from "msgpackr";
11
+ // useRecords:false emits standard msgpack (no nonstandard record extension).
12
+ // moreTypes:false keeps the extension set to what every other decoder understands.
13
+ // Packr.pack() returns Node Buffer, which axios sends correctly (plain Uint8Array
14
+ // would have its pool buffer sent in full — see axios issue #4068).
15
+ const msgpack = new Packr({ useRecords: false, moreTypes: false });
16
+ import objectHash from "object-hash";
17
+ import * as os from "node:os";
18
+ import { performance } from "node:perf_hooks";
19
+ import nacl from "tweetnacl";
20
+ import * as uuid from "uuid";
21
+ import winston from "winston";
22
+ import WebSocket from "ws";
23
+ import { Storage } from "./Storage.js";
24
+ import { capitalize } from "./utils/capitalize.js";
25
+ import { createLogger } from "./utils/createLogger.js";
26
+ import { formatBytes } from "./utils/formatBytes.js";
27
+ import { sqlSessionToCrypto } from "./utils/sqlSessionToCrypto.js";
28
+ import { uuidToUint8 } from "./utils/uint8uuid.js";
29
+ ax.defaults.withCredentials = true;
30
+ ax.defaults.responseType = "arraybuffer";
59
31
  const protocolMsgRegex = /��\w+:\w+��/g;
60
32
  /**
61
33
  * Client provides an interface for you to use a vex chat server and
@@ -106,253 +78,317 @@ const protocolMsgRegex = /��\w+:\w+��/g;
106
78
  *
107
79
  * @noInheritDoc
108
80
  */
109
- class Client extends events_1.EventEmitter {
110
- constructor(privateKey, options, storage) {
111
- super();
81
+ export class Client extends EventEmitter {
82
+ static loadKeyFile = XUtils.loadKeyFile;
83
+ static saveKeyFile = XUtils.saveKeyFile;
84
+ static create = async (privateKey, options, storage) => {
85
+ const client = new Client(privateKey, options, storage);
86
+ await client.init();
87
+ return client;
88
+ };
89
+ /**
90
+ * Generates an ed25519 secret key as a hex string.
91
+ *
92
+ * @returns - A secret key to use for the client. Save it permanently somewhere safe.
93
+ */
94
+ static generateSecretKey() {
95
+ return XUtils.encodeHex(nacl.sign.keyPair().secretKey);
96
+ }
97
+ /**
98
+ * Generates a random username using bip39.
99
+ *
100
+ * @returns - The username.
101
+ */
102
+ static randomUsername() {
103
+ const IKM = XUtils.decodeHex(XUtils.encodeHex(nacl.randomBytes(16)));
104
+ const mnemonic = xMnemonic(IKM).split(" ");
105
+ const addendum = XUtils.uint8ArrToNumber(nacl.randomBytes(1));
106
+ return (capitalize(mnemonic[0]) +
107
+ capitalize(mnemonic[1]) +
108
+ addendum.toString());
109
+ }
110
+ static getMnemonic(session) {
111
+ return xMnemonic(xKDF(XUtils.decodeHex(session.fingerprint)));
112
+ }
113
+ static deserializeExtra(type, extra) {
114
+ switch (type) {
115
+ case MailType.initial:
116
+ /* 32 bytes for signkey, 32 bytes for ephemeral key,
117
+ 68 bytes for AD, 6 bytes for otk index (empty for no otk) */
118
+ const signKey = extra.slice(0, 32);
119
+ const ephKey = extra.slice(32, 64);
120
+ const ad = extra.slice(96, 164);
121
+ const index = extra.slice(164, 170);
122
+ return [signKey, ephKey, ad, index];
123
+ case MailType.subsequent:
124
+ const publicKey = extra;
125
+ return [publicKey];
126
+ default:
127
+ return [];
128
+ }
129
+ }
130
+ /**
131
+ * The IUsers interface contains methods for dealing with users.
132
+ */
133
+ users = {
112
134
  /**
113
- * The IUsers interface contains methods for dealing with users.
135
+ * Retrieves a user's information by a string identifier.
136
+ * @param identifier: A userID, hex string public key, or a username.
137
+ *
138
+ * @returns - The user's IUser object, or null if the user does not exist.
114
139
  */
115
- this.users = {
116
- /**
117
- * Retrieves a user's information by a string identifier.
118
- * @param identifier: A userID, hex string public key, or a username.
119
- *
120
- * @returns - The user's IUser object, or null if the user does not exist.
121
- */
122
- retrieve: this.retrieveUserDBEntry.bind(this),
123
- /**
124
- * Retrieves the list of users you can currently access, or are already familiar with.
125
- *
126
- * @returns - The list of IUser objects.
127
- */
128
- familiars: this.getFamiliars.bind(this),
129
- };
130
- this.emoji = {
131
- create: this.uploadEmoji.bind(this),
132
- retrieveList: this.retrieveEmojiList.bind(this),
133
- retrieve: this.retrieveEmojiByID.bind(this),
134
- };
135
- this.me = {
136
- /**
137
- * Retrieves your user information
138
- *
139
- * @returns - The logged in user's IUser object.
140
- */
141
- user: this.getUser.bind(this),
142
- /**
143
- * Retrieves current device details
144
- *
145
- * @returns - The logged in device's IDevice object.
146
- */
147
- device: this.getDevice.bind(this),
148
- /**
149
- * Changes your avatar.
150
- */
151
- setAvatar: this.uploadAvatar.bind(this),
152
- };
153
- this.devices = {
154
- retrieve: this.getDeviceByID.bind(this),
155
- register: this.registerDevice.bind(this),
156
- delete: this.deleteDevice.bind(this),
157
- };
140
+ retrieve: this.retrieveUserDBEntry.bind(this),
158
141
  /**
159
- * The IMessages interface contains methods for dealing with messages.
142
+ * Retrieves the list of users you can currently access, or are already familiar with.
143
+ *
144
+ * @returns - The list of IUser objects.
160
145
  */
161
- this.files = {
162
- /**
163
- * Uploads an encrypted file and returns the details and the secret key.
164
- * @param file: The file as a Buffer.
165
- *
166
- * @returns Details of the file uploaded and the key to encrypt in the form [details, key].
167
- */
168
- create: this.createFile.bind(this),
169
- retrieve: this.retrieveFile.bind(this),
170
- };
146
+ familiars: this.getFamiliars.bind(this),
147
+ };
148
+ emoji = {
149
+ create: this.uploadEmoji.bind(this),
150
+ retrieveList: this.retrieveEmojiList.bind(this),
151
+ retrieve: this.retrieveEmojiByID.bind(this),
152
+ };
153
+ me = {
171
154
  /**
172
- * The IPermissions object contains all methods for dealing with permissions.
155
+ * Retrieves your user information
156
+ *
157
+ * @returns - The logged in user's IUser object.
173
158
  */
174
- this.permissions = {
175
- retrieve: this.getPermissions.bind(this),
176
- delete: this.deletePermission.bind(this),
177
- };
159
+ user: this.getUser.bind(this),
178
160
  /**
179
- * The IModeration object contains all methods for dealing with permissions.
161
+ * Retrieves current device details
162
+ *
163
+ * @returns - The logged in device's IDevice object.
180
164
  */
181
- this.moderation = {
182
- kick: this.kickUser.bind(this),
183
- fetchPermissionList: this.fetchPermissionList.bind(this),
184
- };
165
+ device: this.getDevice.bind(this),
185
166
  /**
186
- * The IInvites interface contains methods for dealing with invites.
167
+ * Changes your avatar.
187
168
  */
188
- this.invites = {
189
- create: this.createInvite.bind(this),
190
- redeem: this.redeemInvite.bind(this),
191
- retrieve: this.retrieveInvites.bind(this),
192
- };
169
+ setAvatar: this.uploadAvatar.bind(this),
170
+ };
171
+ devices = {
172
+ retrieve: this.getDeviceByID.bind(this),
173
+ register: this.registerDevice.bind(this),
174
+ delete: this.deleteDevice.bind(this),
175
+ };
176
+ /**
177
+ * The IMessages interface contains methods for dealing with messages.
178
+ */
179
+ files = {
193
180
  /**
194
- * The IMessages interface contains methods for dealing with messages.
181
+ * Uploads an encrypted file and returns the details and the secret key.
182
+ * @param file: The file as a Buffer.
183
+ *
184
+ * @returns Details of the file uploaded and the key to encrypt in the form [details, key].
195
185
  */
196
- this.messages = {
197
- /**
198
- * Send a direct message.
199
- * @param userID: The userID of the user to send a message to.
200
- * @param message: The message to send.
201
- */
202
- send: this.sendMessage.bind(this),
203
- /**
204
- * Send a group message to a channel.
205
- * @param channelID: The channelID of the channel to send a message to.
206
- * @param message: The message to send.
207
- */
208
- group: this.sendGroupMessage.bind(this),
209
- /**
210
- * Gets the message history with a specific userID.
211
- * @param userID: The userID of the user to retrieve message history for.
212
- *
213
- * @returns - The list of IMessage objects.
214
- */
215
- retrieve: this.getMessageHistory.bind(this),
216
- /**
217
- * Gets the group message history with a specific channelID.
218
- * @param chqnnelID: The channelID of the channel to retrieve message history for.
219
- *
220
- * @returns - The list of IMessage objects.
221
- */
222
- retrieveGroup: this.getGroupHistory.bind(this),
223
- delete: this.deleteHistory.bind(this),
224
- purge: this.purgeHistory.bind(this),
225
- };
186
+ create: this.createFile.bind(this),
187
+ retrieve: this.retrieveFile.bind(this),
188
+ };
189
+ /**
190
+ * The IPermissions object contains all methods for dealing with permissions.
191
+ */
192
+ permissions = {
193
+ retrieve: this.getPermissions.bind(this),
194
+ delete: this.deletePermission.bind(this),
195
+ };
196
+ /**
197
+ * The IModeration object contains all methods for dealing with permissions.
198
+ */
199
+ moderation = {
200
+ kick: this.kickUser.bind(this),
201
+ fetchPermissionList: this.fetchPermissionList.bind(this),
202
+ };
203
+ /**
204
+ * The IInvites interface contains methods for dealing with invites.
205
+ */
206
+ invites = {
207
+ create: this.createInvite.bind(this),
208
+ redeem: this.redeemInvite.bind(this),
209
+ retrieve: this.retrieveInvites.bind(this),
210
+ };
211
+ /**
212
+ * The IMessages interface contains methods for dealing with messages.
213
+ */
214
+ messages = {
226
215
  /**
227
- * The ISessions interface contains methods for dealing with encryption sessions.
216
+ * Send a direct message.
217
+ * @param userID: The userID of the user to send a message to.
218
+ * @param message: The message to send.
228
219
  */
229
- this.sessions = {
230
- /**
231
- * Gets all encryption sessions.
232
- *
233
- * @returns - The list of ISession encryption sessions.
234
- */
235
- retrieve: this.getSessionList.bind(this),
236
- /**
237
- * Returns a mnemonic for the session, to verify with the other user.
238
- * @param session the ISession object to get the mnemonic for.
239
- *
240
- * @returns - The mnemonic representation of the session.
241
- */
242
- verify: Client.getMnemonic,
243
- /**
244
- * Marks a mnemonic verified, implying that the the user has confirmed
245
- * that the session mnemonic matches with the other user.
246
- * @param sessionID the sessionID of the session to mark.
247
- * @param status Optionally, what to mark it as. Defaults to true.
248
- */
249
- markVerified: this.markSessionVerified.bind(this),
250
- };
251
- this.servers = {
252
- /**
253
- * Retrieves all servers the logged in user has access to.
254
- *
255
- * @returns - The list of IServer objects.
256
- */
257
- retrieve: this.getServerList.bind(this),
258
- /**
259
- * Retrieves server details by its unique serverID.
260
- *
261
- * @returns - The requested IServer object, or null if the id does not exist.
262
- */
263
- retrieveByID: this.getServerByID.bind(this),
264
- /**
265
- * Creates a new server.
266
- * @param name: The server name.
267
- *
268
- * @returns - The created IServer object.
269
- */
270
- create: this.createServer.bind(this),
271
- /**
272
- * Deletes a server.
273
- * @param serverID: The unique serverID to delete.
274
- */
275
- delete: this.deleteServer.bind(this),
276
- leave: this.leaveServer.bind(this),
277
- };
278
- this.channels = {
279
- /**
280
- * Retrieves all channels in a server.
281
- *
282
- * @returns - The list of IChannel objects.
283
- */
284
- retrieve: this.getChannelList.bind(this),
285
- /**
286
- * Retrieves channel details by its unique channelID.
287
- *
288
- * @returns - The list of IChannel objects.
289
- */
290
- retrieveByID: this.getChannelByID.bind(this),
291
- /**
292
- * Creates a new channel in a server.
293
- * @param name: The channel name.
294
- * @param serverID: The unique serverID to create the channel in.
295
- *
296
- * @returns - The created IChannel object.
297
- */
298
- create: this.createChannel.bind(this),
299
- /**
300
- * Deletes a channel.
301
- * @param channelID: The unique channelID to delete.
302
- */
303
- delete: this.deleteChannel.bind(this),
304
- /**
305
- * Retrieves a channel's userlist.
306
- * @param channelID: The channelID to retrieve userlist for.
307
- */
308
- userList: this.getUserList.bind(this),
309
- };
220
+ send: this.sendMessage.bind(this),
310
221
  /**
311
- * This is true if the client has ever been initialized. You can only initialize
312
- * a client once.
222
+ * Send a group message to a channel.
223
+ * @param channelID: The channelID of the channel to send a message to.
224
+ * @param message: The message to send.
313
225
  */
314
- this.hasInit = false;
226
+ group: this.sendGroupMessage.bind(this),
315
227
  /**
316
- * This is true if the client has ever logged in before. You can only login a client once.
228
+ * Gets the message history with a specific userID.
229
+ * @param userID: The userID of the user to retrieve message history for.
230
+ *
231
+ * @returns - The list of IMessage objects.
317
232
  */
318
- this.hasLoggedIn = false;
319
- this.sending = {};
320
- this.firstMailFetch = true;
321
- this.userRecords = {};
322
- this.deviceRecords = {};
323
- this.sessionRecords = {};
324
- this.isAlive = true;
325
- this.reading = false;
326
- this.fetchingMail = false;
327
- this.cookies = [];
328
- this.pingInterval = null;
329
- this.manuallyClosing = false;
330
- this.token = null;
331
- this.forwarded = [];
332
- this.log = createLogger_1.createLogger("client", options === null || options === void 0 ? void 0 : options.logLevel);
333
- this.prefixes = (options === null || options === void 0 ? void 0 : options.unsafeHttp) ? { HTTP: "http://", WS: "ws://" }
233
+ retrieve: this.getMessageHistory.bind(this),
234
+ /**
235
+ * Gets the group message history with a specific channelID.
236
+ * @param chqnnelID: The channelID of the channel to retrieve message history for.
237
+ *
238
+ * @returns - The list of IMessage objects.
239
+ */
240
+ retrieveGroup: this.getGroupHistory.bind(this),
241
+ delete: this.deleteHistory.bind(this),
242
+ purge: this.purgeHistory.bind(this),
243
+ };
244
+ /**
245
+ * The ISessions interface contains methods for dealing with encryption sessions.
246
+ */
247
+ sessions = {
248
+ /**
249
+ * Gets all encryption sessions.
250
+ *
251
+ * @returns - The list of ISession encryption sessions.
252
+ */
253
+ retrieve: this.getSessionList.bind(this),
254
+ /**
255
+ * Returns a mnemonic for the session, to verify with the other user.
256
+ * @param session the ISession object to get the mnemonic for.
257
+ *
258
+ * @returns - The mnemonic representation of the session.
259
+ */
260
+ verify: Client.getMnemonic,
261
+ /**
262
+ * Marks a mnemonic verified, implying that the the user has confirmed
263
+ * that the session mnemonic matches with the other user.
264
+ * @param sessionID the sessionID of the session to mark.
265
+ * @param status Optionally, what to mark it as. Defaults to true.
266
+ */
267
+ markVerified: this.markSessionVerified.bind(this),
268
+ };
269
+ servers = {
270
+ /**
271
+ * Retrieves all servers the logged in user has access to.
272
+ *
273
+ * @returns - The list of IServer objects.
274
+ */
275
+ retrieve: this.getServerList.bind(this),
276
+ /**
277
+ * Retrieves server details by its unique serverID.
278
+ *
279
+ * @returns - The requested IServer object, or null if the id does not exist.
280
+ */
281
+ retrieveByID: this.getServerByID.bind(this),
282
+ /**
283
+ * Creates a new server.
284
+ * @param name: The server name.
285
+ *
286
+ * @returns - The created IServer object.
287
+ */
288
+ create: this.createServer.bind(this),
289
+ /**
290
+ * Deletes a server.
291
+ * @param serverID: The unique serverID to delete.
292
+ */
293
+ delete: this.deleteServer.bind(this),
294
+ leave: this.leaveServer.bind(this),
295
+ };
296
+ channels = {
297
+ /**
298
+ * Retrieves all channels in a server.
299
+ *
300
+ * @returns - The list of IChannel objects.
301
+ */
302
+ retrieve: this.getChannelList.bind(this),
303
+ /**
304
+ * Retrieves channel details by its unique channelID.
305
+ *
306
+ * @returns - The list of IChannel objects.
307
+ */
308
+ retrieveByID: this.getChannelByID.bind(this),
309
+ /**
310
+ * Creates a new channel in a server.
311
+ * @param name: The channel name.
312
+ * @param serverID: The unique serverID to create the channel in.
313
+ *
314
+ * @returns - The created IChannel object.
315
+ */
316
+ create: this.createChannel.bind(this),
317
+ /**
318
+ * Deletes a channel.
319
+ * @param channelID: The unique channelID to delete.
320
+ */
321
+ delete: this.deleteChannel.bind(this),
322
+ /**
323
+ * Retrieves a channel's userlist.
324
+ * @param channelID: The channelID to retrieve userlist for.
325
+ */
326
+ userList: this.getUserList.bind(this),
327
+ };
328
+ /**
329
+ * This is true if the client has ever been initialized. You can only initialize
330
+ * a client once.
331
+ */
332
+ hasInit = false;
333
+ /**
334
+ * This is true if the client has ever logged in before. You can only login a client once.
335
+ */
336
+ hasLoggedIn = false;
337
+ sending = {};
338
+ database;
339
+ dbPath;
340
+ conn;
341
+ host;
342
+ firstMailFetch = true;
343
+ // these are created from one set of sign keys
344
+ signKeys;
345
+ idKeys;
346
+ xKeyRing;
347
+ user;
348
+ device;
349
+ userRecords = {};
350
+ deviceRecords = {};
351
+ sessionRecords = {};
352
+ isAlive = true;
353
+ reading = false;
354
+ fetchingMail = false;
355
+ cookies = [];
356
+ log;
357
+ pingInterval = null;
358
+ mailInterval;
359
+ manuallyClosing = false;
360
+ token = null;
361
+ forwarded = [];
362
+ prefixes;
363
+ constructor(privateKey, options, storage) {
364
+ super();
365
+ this.log = createLogger("client", options?.logLevel);
366
+ this.prefixes = options?.unsafeHttp
367
+ ? { HTTP: "http://", WS: "ws://" }
334
368
  : { HTTP: "https://", WS: "wss://" };
335
369
  this.signKeys = privateKey
336
- ? tweetnacl_1.default.sign.keyPair.fromSecretKey(crypto_1.XUtils.decodeHex(privateKey))
337
- : tweetnacl_1.default.sign.keyPair();
338
- this.idKeys = crypto_1.XKeyConvert.convertKeyPair(this.signKeys);
370
+ ? nacl.sign.keyPair.fromSecretKey(XUtils.decodeHex(privateKey))
371
+ : nacl.sign.keyPair();
372
+ this.idKeys = XKeyConvert.convertKeyPair(this.signKeys);
339
373
  if (!this.idKeys) {
340
374
  throw new Error("Could not convert key to X25519!");
341
375
  }
342
- this.host = (options === null || options === void 0 ? void 0 : options.host) || "api.vex.wtf";
343
- const dbFileName = (options === null || options === void 0 ? void 0 : options.inMemoryDb) ? ":memory:"
344
- : crypto_1.XUtils.encodeHex(this.signKeys.publicKey) + ".sqlite";
345
- this.dbPath = (options === null || options === void 0 ? void 0 : options.dbFolder) ? (options === null || options === void 0 ? void 0 : options.dbFolder) + "/" + dbFileName
376
+ this.host = options?.host || "api.vex.wtf";
377
+ const dbFileName = options?.inMemoryDb
378
+ ? ":memory:"
379
+ : XUtils.encodeHex(this.signKeys.publicKey) + ".sqlite";
380
+ this.dbPath = options?.dbFolder
381
+ ? options?.dbFolder + "/" + dbFileName
346
382
  : dbFileName;
347
383
  this.database = storage
348
384
  ? storage
349
- : new Storage_1.Storage(this.dbPath, crypto_1.XUtils.encodeHex(this.signKeys.secretKey), options);
385
+ : new Storage(this.dbPath, XUtils.encodeHex(this.signKeys.secretKey), options);
350
386
  this.database.on("error", (error) => {
351
387
  this.log.error(error.toString());
352
388
  this.close(true);
353
389
  });
354
390
  // we want to initialize this later with login()
355
- this.conn = new ws_1.default("ws://localhost:1234");
391
+ this.conn = new WebSocket("ws://localhost:1234");
356
392
  // silence the error for connecting to junk ws
357
393
  // tslint:disable-next-line: no-empty
358
394
  this.conn.onerror = () => { };
@@ -362,165 +398,114 @@ class Client extends events_1.EventEmitter {
362
398
  host: this.getHost(),
363
399
  dbPath: this.dbPath,
364
400
  environment: {
365
- isBrowser: browser_or_node_1.isBrowser,
366
- isNode: browser_or_node_1.isNode,
401
+ isBrowser,
402
+ isNode,
367
403
  },
368
404
  options,
369
405
  }, null, 4));
370
406
  }
371
- /**
372
- * Generates an ed25519 secret key as a hex string.
373
- *
374
- * @returns - A secret key to use for the client. Save it permanently somewhere safe.
375
- */
376
- static generateSecretKey() {
377
- return crypto_1.XUtils.encodeHex(tweetnacl_1.default.sign.keyPair().secretKey);
378
- }
379
- /**
380
- * Generates a random username using bip39.
381
- *
382
- * @returns - The username.
383
- */
384
- static randomUsername() {
385
- const IKM = crypto_1.XUtils.decodeHex(crypto_1.XUtils.encodeHex(tweetnacl_1.default.randomBytes(16)));
386
- const mnemonic = crypto_1.xMnemonic(IKM).split(" ");
387
- const addendum = crypto_1.XUtils.uint8ArrToNumber(tweetnacl_1.default.randomBytes(1));
388
- return (capitalize_1.capitalize(mnemonic[0]) +
389
- capitalize_1.capitalize(mnemonic[1]) +
390
- addendum.toString());
391
- }
392
- static getMnemonic(session) {
393
- return crypto_1.xMnemonic(crypto_1.xKDF(crypto_1.XUtils.decodeHex(session.fingerprint)));
394
- }
395
- static deserializeExtra(type, extra) {
396
- switch (type) {
397
- case types_1.XTypes.WS.MailType.initial:
398
- /* 32 bytes for signkey, 32 bytes for ephemeral key,
399
- 68 bytes for AD, 6 bytes for otk index (empty for no otk) */
400
- const signKey = extra.slice(0, 32);
401
- const ephKey = extra.slice(32, 64);
402
- const ad = extra.slice(96, 164);
403
- const index = extra.slice(164, 170);
404
- return [signKey, ephKey, ad, index];
405
- case types_1.XTypes.WS.MailType.subsequent:
406
- const publicKey = extra;
407
- return [publicKey];
408
- default:
409
- return [];
410
- }
411
- }
412
407
  getHost() {
413
408
  return this.prefixes.HTTP + this.host;
414
409
  }
415
410
  /**
416
411
  * Manually closes the client. Emits the closed event on successful shutdown.
417
412
  */
418
- close(muteEvent = false) {
419
- return __awaiter(this, void 0, void 0, function* () {
420
- this.manuallyClosing = true;
421
- this.log.info("Manually closing client.");
422
- this.conn.close();
423
- yield this.database.close();
424
- if (this.pingInterval) {
425
- clearInterval(this.pingInterval);
426
- }
427
- if (this.mailInterval) {
428
- clearInterval(this.mailInterval);
429
- }
430
- delete this.xKeyRing;
431
- if (!muteEvent) {
432
- this.emit("closed");
433
- }
434
- return;
435
- });
413
+ async close(muteEvent = false) {
414
+ this.manuallyClosing = true;
415
+ this.log.info("Manually closing client.");
416
+ this.conn.close();
417
+ await this.database.close();
418
+ if (this.pingInterval) {
419
+ clearInterval(this.pingInterval);
420
+ }
421
+ if (this.mailInterval) {
422
+ clearInterval(this.mailInterval);
423
+ }
424
+ delete this.xKeyRing;
425
+ if (!muteEvent) {
426
+ this.emit("closed");
427
+ }
428
+ return;
436
429
  }
437
430
  /**
438
431
  * Gets the hex string representations of the public and private keys.
439
432
  */
440
433
  getKeys() {
441
434
  return {
442
- public: crypto_1.XUtils.encodeHex(this.signKeys.publicKey),
443
- private: crypto_1.XUtils.encodeHex(this.signKeys.secretKey),
435
+ public: XUtils.encodeHex(this.signKeys.publicKey),
436
+ private: XUtils.encodeHex(this.signKeys.secretKey),
444
437
  };
445
438
  }
446
- login(username, password) {
447
- return __awaiter(this, void 0, void 0, function* () {
448
- try {
449
- const res = yield axios_1.default.post(this.getHost() + "/auth", msgpack_lite_1.default.encode({
450
- username,
451
- password,
452
- }), {
453
- headers: { "Content-Type": "application/msgpack" },
454
- });
455
- const { user, token, } = msgpack_lite_1.default.decode(Buffer.from(res.data));
456
- const cookies = res.headers["set-cookie"];
457
- if (cookies) {
458
- for (const cookie of cookies) {
459
- if (cookie.includes("auth")) {
460
- this.addCookie(cookie);
461
- }
439
+ async login(username, password) {
440
+ try {
441
+ const res = await ax.post(this.getHost() + "/auth", msgpack.encode({
442
+ username,
443
+ password,
444
+ }), {
445
+ headers: { "Content-Type": "application/msgpack" },
446
+ });
447
+ const { user, token, } = msgpack.decode(Buffer.from(res.data));
448
+ const cookies = res.headers["set-cookie"];
449
+ if (cookies) {
450
+ for (const cookie of cookies) {
451
+ if (cookie.includes("auth")) {
452
+ this.addCookie(cookie);
462
453
  }
463
454
  }
464
- this.setUser(user);
465
- this.token = token;
466
- }
467
- catch (err) {
468
- console.error(err.toString());
469
- return err;
470
455
  }
471
- return null;
472
- });
456
+ this.setUser(user);
457
+ this.token = token;
458
+ }
459
+ catch (err) {
460
+ console.error(err.toString());
461
+ return err;
462
+ }
463
+ return null;
473
464
  }
474
465
  /**
475
466
  * Returns the authorization cookie details. Throws if you don't have a
476
467
  * valid authorization cookie.
477
468
  */
478
- whoami() {
479
- return __awaiter(this, void 0, void 0, function* () {
480
- const res = yield axios_1.default.post(this.getHost() + "/whoami", null, {
481
- withCredentials: true,
482
- responseType: "arraybuffer",
483
- });
484
- const whoami = msgpack_lite_1.default.decode(Buffer.from(res.data));
485
- return whoami;
469
+ async whoami() {
470
+ const res = await ax.post(this.getHost() + "/whoami", null, {
471
+ withCredentials: true,
472
+ responseType: "arraybuffer",
486
473
  });
474
+ const whoami = msgpack.decode(Buffer.from(res.data));
475
+ return whoami;
487
476
  }
488
- logout() {
489
- return __awaiter(this, void 0, void 0, function* () {
490
- yield axios_1.default.post(this.getHost() + "/goodbye");
491
- });
477
+ async logout() {
478
+ await ax.post(this.getHost() + "/goodbye");
492
479
  }
493
480
  /**
494
481
  * Connects your device to the chat. You must have an valid authorization cookie.
495
482
  * You can check whoami() to see before calling connect().
496
483
  */
497
- connect() {
498
- return __awaiter(this, void 0, void 0, function* () {
499
- const { user, token } = yield this.whoami();
500
- this.token = token;
501
- if (!user || !token) {
502
- throw new Error("Auth cookie missing or expired. Log in again.");
503
- }
504
- this.setUser(user);
505
- this.device = yield this.retrieveOrCreateDevice();
506
- const connectToken = yield this.getToken("connect");
507
- if (!connectToken) {
508
- throw new Error("Couldn't get connect token.");
509
- }
510
- const signed = tweetnacl_1.default.sign(Uint8Array.from(uuid.parse(connectToken.key)), this.signKeys.secretKey);
511
- const res = yield axios_1.default.post(this.getHost() + "/device/" + this.device.deviceID + "/connect", msgpack_lite_1.default.encode({ signed }), { headers: { "Content-Type": "application/msgpack" } });
512
- const cookies = res.headers["set-cookie"];
513
- if (cookies) {
514
- for (const cookie of cookies) {
515
- if (cookie.includes("device")) {
516
- this.addCookie(cookie);
517
- }
484
+ async connect() {
485
+ const { user, token } = await this.whoami();
486
+ this.token = token;
487
+ if (!user || !token) {
488
+ throw new Error("Auth cookie missing or expired. Log in again.");
489
+ }
490
+ this.setUser(user);
491
+ this.device = await this.retrieveOrCreateDevice();
492
+ const connectToken = await this.getToken("connect");
493
+ if (!connectToken) {
494
+ throw new Error("Couldn't get connect token.");
495
+ }
496
+ const signed = nacl.sign(Uint8Array.from(uuid.parse(connectToken.key)), this.signKeys.secretKey);
497
+ const res = await ax.post(this.getHost() + "/device/" + this.device.deviceID + "/connect", msgpack.encode({ signed }), { headers: { "Content-Type": "application/msgpack" } });
498
+ const cookies = res.headers["set-cookie"];
499
+ if (cookies) {
500
+ for (const cookie of cookies) {
501
+ if (cookie.includes("device")) {
502
+ this.addCookie(cookie);
518
503
  }
519
504
  }
520
- this.log.info("Starting websocket.");
521
- this.initSocket();
522
- yield this.negotiateOTK();
523
- });
505
+ }
506
+ this.log.info("Starting websocket.");
507
+ this.initSocket();
508
+ await this.negotiateOTK();
524
509
  }
525
510
  /**
526
511
  * Registers a new account on the server.
@@ -530,519 +515,426 @@ class Client extends events_1.EventEmitter {
530
515
  *
531
516
  * @example [user, err] = await client.register("MyUsername");
532
517
  */
533
- register(username, password) {
534
- return __awaiter(this, void 0, void 0, function* () {
535
- while (!this.xKeyRing) {
536
- yield sleep_1.sleep(100);
518
+ async register(username, password) {
519
+ while (!this.xKeyRing) {
520
+ await sleep(100);
521
+ }
522
+ const regKey = await this.getToken("register");
523
+ if (regKey) {
524
+ const signKey = XUtils.encodeHex(this.signKeys.publicKey);
525
+ const signed = XUtils.encodeHex(nacl.sign(Uint8Array.from(uuid.parse(regKey.key)), this.signKeys.secretKey));
526
+ const regMsg = {
527
+ username,
528
+ signKey,
529
+ signed,
530
+ preKey: XUtils.encodeHex(this.xKeyRing.preKeys.keyPair.publicKey),
531
+ preKeySignature: XUtils.encodeHex(this.xKeyRing.preKeys.signature),
532
+ preKeyIndex: this.xKeyRing.preKeys.index,
533
+ password,
534
+ deviceName: `${os.platform()}`,
535
+ };
536
+ try {
537
+ const res = await ax.post(this.getHost() + "/register", msgpack.encode(regMsg), { headers: { "Content-Type": "application/msgpack" } });
538
+ this.setUser(msgpack.decode(Buffer.from(res.data)));
539
+ return [this.getUser(), null];
537
540
  }
538
- const regKey = yield this.getToken("register");
539
- if (regKey) {
540
- const signKey = crypto_1.XUtils.encodeHex(this.signKeys.publicKey);
541
- const signed = crypto_1.XUtils.encodeHex(tweetnacl_1.default.sign(Uint8Array.from(uuid.parse(regKey.key)), this.signKeys.secretKey));
542
- const regMsg = {
543
- username,
544
- signKey,
545
- signed,
546
- preKey: crypto_1.XUtils.encodeHex(this.xKeyRing.preKeys.keyPair.publicKey),
547
- preKeySignature: crypto_1.XUtils.encodeHex(this.xKeyRing.preKeys.signature),
548
- preKeyIndex: this.xKeyRing.preKeys.index,
549
- password,
550
- deviceName: `${os_1.default.platform()}`,
551
- };
552
- try {
553
- const res = yield axios_1.default.post(this.getHost() + "/register", msgpack_lite_1.default.encode(regMsg), { headers: { "Content-Type": "application/msgpack" } });
554
- this.setUser(msgpack_lite_1.default.decode(Buffer.from(res.data)));
555
- return [this.getUser(), null];
541
+ catch (err) {
542
+ if (err.response) {
543
+ return [null, new Error(err.response.data.error)];
556
544
  }
557
- catch (err) {
558
- if (err.response) {
559
- return [null, new Error(err.response.data.error)];
560
- }
561
- else {
562
- return [null, err];
563
- }
545
+ else {
546
+ return [null, err];
564
547
  }
565
548
  }
566
- else {
567
- return [null, new Error("Couldn't get regkey from server.")];
568
- }
569
- });
549
+ }
550
+ else {
551
+ return [null, new Error("Couldn't get regkey from server.")];
552
+ }
570
553
  }
571
554
  toString() {
572
- var _a, _b;
573
- return ((_a = this.user) === null || _a === void 0 ? void 0 : _a.username) + "<" + ((_b = this.device) === null || _b === void 0 ? void 0 : _b.deviceID) + ">";
555
+ return this.user?.username + "<" + this.device?.deviceID + ">";
574
556
  }
575
- redeemInvite(inviteID) {
576
- return __awaiter(this, void 0, void 0, function* () {
577
- const res = yield axios_1.default.patch(this.getHost() + "/invite/" + inviteID);
578
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
579
- });
557
+ async redeemInvite(inviteID) {
558
+ const res = await ax.patch(this.getHost() + "/invite/" + inviteID);
559
+ return msgpack.decode(Buffer.from(res.data));
580
560
  }
581
- retrieveInvites(serverID) {
582
- return __awaiter(this, void 0, void 0, function* () {
583
- const res = yield axios_1.default.get(this.getHost() + "/server/" + serverID + "/invites");
584
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
585
- });
561
+ async retrieveInvites(serverID) {
562
+ const res = await ax.get(this.getHost() + "/server/" + serverID + "/invites");
563
+ return msgpack.decode(Buffer.from(res.data));
586
564
  }
587
- createInvite(serverID, duration) {
588
- return __awaiter(this, void 0, void 0, function* () {
589
- const payload = {
590
- serverID,
591
- duration,
592
- };
593
- const res = yield axios_1.default.post(this.getHost() + "/server/" + serverID + "/invites", payload);
594
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
595
- });
565
+ async createInvite(serverID, duration) {
566
+ const payload = {
567
+ serverID,
568
+ duration,
569
+ };
570
+ const res = await ax.post(this.getHost() + "/server/" + serverID + "/invites", payload);
571
+ return msgpack.decode(Buffer.from(res.data));
596
572
  }
597
- retrieveEmojiList(serverID) {
598
- return __awaiter(this, void 0, void 0, function* () {
599
- const res = yield axios_1.default.get(this.getHost() + "/server/" + serverID + "/emoji");
600
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
601
- });
573
+ async retrieveEmojiList(serverID) {
574
+ const res = await ax.get(this.getHost() + "/server/" + serverID + "/emoji");
575
+ return msgpack.decode(Buffer.from(res.data));
602
576
  }
603
- retrieveEmojiByID(emojiID) {
604
- return __awaiter(this, void 0, void 0, function* () {
605
- const res = yield axios_1.default.get(this.getHost() + "/emoji/" + emojiID + "/details");
606
- // this is actually empty string
607
- if (!res.data) {
608
- return null;
609
- }
610
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
611
- });
577
+ async retrieveEmojiByID(emojiID) {
578
+ const res = await ax.get(this.getHost() + "/emoji/" + emojiID + "/details");
579
+ // this is actually empty string
580
+ if (!res.data) {
581
+ return null;
582
+ }
583
+ return msgpack.decode(Buffer.from(res.data));
612
584
  }
613
- leaveServer(serverID) {
614
- return __awaiter(this, void 0, void 0, function* () {
615
- const permissionList = yield this.permissions.retrieve();
616
- for (const permission of permissionList) {
617
- if (permission.resourceID === serverID) {
618
- yield this.deletePermission(permission.permissionID);
619
- }
585
+ async leaveServer(serverID) {
586
+ const permissionList = await this.permissions.retrieve();
587
+ for (const permission of permissionList) {
588
+ if (permission.resourceID === serverID) {
589
+ await this.deletePermission(permission.permissionID);
620
590
  }
621
- });
591
+ }
622
592
  }
623
- kickUser(userID, serverID) {
624
- return __awaiter(this, void 0, void 0, function* () {
625
- const permissionList = yield this.fetchPermissionList(serverID);
626
- for (const permission of permissionList) {
627
- if (userID === permission.userID) {
628
- yield this.deletePermission(permission.permissionID);
629
- return;
630
- }
593
+ async kickUser(userID, serverID) {
594
+ const permissionList = await this.fetchPermissionList(serverID);
595
+ for (const permission of permissionList) {
596
+ if (userID === permission.userID) {
597
+ await this.deletePermission(permission.permissionID);
598
+ return;
631
599
  }
632
- throw new Error("Couldn't kick user.");
633
- });
600
+ }
601
+ throw new Error("Couldn't kick user.");
634
602
  }
635
603
  addCookie(cookie) {
636
604
  if (!this.cookies.includes(cookie)) {
637
605
  this.cookies.push(cookie);
638
606
  this.log.info("cookies changed", this.getCookies());
639
- if (browser_or_node_1.isNode) {
640
- axios_1.default.defaults.headers.cookie = this.cookies.join(";");
607
+ if (isNode) {
608
+ ax.defaults.headers.cookie = this.cookies.join(";");
641
609
  }
642
610
  }
643
611
  }
644
612
  getCookies() {
645
613
  return this.cookies.join(";");
646
614
  }
647
- uploadEmoji(emoji, name, serverID) {
648
- return __awaiter(this, void 0, void 0, function* () {
649
- if (typeof FormData !== "undefined") {
650
- const fpayload = new FormData();
651
- fpayload.set("emoji", new Blob([emoji]));
652
- fpayload.set("name", name);
653
- try {
654
- const res = yield axios_1.default.post(this.getHost() + "/emoji/" + serverID, fpayload, {
655
- headers: { "Content-Type": "multipart/form-data" },
656
- onUploadProgress: (progressEvent) => {
657
- const percentCompleted = Math.round((progressEvent.loaded * 100) /
658
- progressEvent.total);
659
- const { loaded, total } = progressEvent;
660
- const progress = {
661
- direction: "upload",
662
- token: name,
663
- progress: percentCompleted,
664
- loaded,
665
- total,
666
- };
667
- this.emit("fileProgress", progress);
668
- },
669
- });
670
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
671
- }
672
- catch (err) {
673
- return null;
674
- }
675
- }
676
- const payload = {
677
- file: crypto_1.XUtils.encodeBase64(emoji),
678
- name,
679
- };
615
+ async uploadEmoji(emoji, name, serverID) {
616
+ if (typeof FormData !== "undefined") {
617
+ const fpayload = new FormData();
618
+ fpayload.set("emoji", new Blob([new Uint8Array(emoji)]));
619
+ fpayload.set("name", name);
680
620
  try {
681
- const res = yield axios_1.default.post(this.getHost() + "/emoji/" + serverID + "/json", msgpack_lite_1.default.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
682
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
621
+ const res = await ax.post(this.getHost() + "/emoji/" + serverID, fpayload, {
622
+ headers: { "Content-Type": "multipart/form-data" },
623
+ onUploadProgress: (progressEvent) => {
624
+ const percentCompleted = Math.round((progressEvent.loaded * 100) /
625
+ (progressEvent.total ?? 1));
626
+ const { loaded, total = 0 } = progressEvent;
627
+ const progress = {
628
+ direction: "upload",
629
+ token: name,
630
+ progress: percentCompleted,
631
+ loaded,
632
+ total,
633
+ };
634
+ this.emit("fileProgress", progress);
635
+ },
636
+ });
637
+ return msgpack.decode(Buffer.from(res.data));
683
638
  }
684
639
  catch (err) {
685
640
  return null;
686
641
  }
687
- });
642
+ }
643
+ const payload = {
644
+ file: XUtils.encodeBase64(emoji),
645
+ name,
646
+ };
647
+ try {
648
+ const res = await ax.post(this.getHost() + "/emoji/" + serverID + "/json", msgpack.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
649
+ return msgpack.decode(Buffer.from(res.data));
650
+ }
651
+ catch (err) {
652
+ return null;
653
+ }
688
654
  }
689
- retrieveOrCreateDevice() {
690
- var _a;
691
- return __awaiter(this, void 0, void 0, function* () {
692
- let device;
693
- try {
694
- const res = yield axios_1.default.get(this.prefixes.HTTP +
695
- this.host +
696
- "/device/" +
697
- crypto_1.XUtils.encodeHex(this.signKeys.publicKey));
698
- device = msgpack_lite_1.default.decode(Buffer.from(res.data));
699
- }
700
- catch (err) {
701
- this.log.error(err.toString());
702
- if (((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
703
- // just in case
704
- yield this.database.purgeKeyData();
705
- yield this.populateKeyRing();
706
- this.log.info("Attempting to register device.");
707
- const newDevice = yield this.registerDevice();
708
- if (newDevice) {
709
- device = newDevice;
710
- }
711
- else {
712
- throw new Error("Error registering device.");
713
- }
655
+ async retrieveOrCreateDevice() {
656
+ let device;
657
+ try {
658
+ const res = await ax.get(this.prefixes.HTTP +
659
+ this.host +
660
+ "/device/" +
661
+ XUtils.encodeHex(this.signKeys.publicKey));
662
+ device = msgpack.decode(Buffer.from(res.data));
663
+ }
664
+ catch (err) {
665
+ this.log.error(err.toString());
666
+ if (err.response?.status === 404) {
667
+ // just in case
668
+ await this.database.purgeKeyData();
669
+ await this.populateKeyRing();
670
+ this.log.info("Attempting to register device.");
671
+ const newDevice = await this.registerDevice();
672
+ if (newDevice) {
673
+ device = newDevice;
714
674
  }
715
675
  else {
716
- throw err;
676
+ throw new Error("Error registering device.");
717
677
  }
718
678
  }
719
- this.log.info("Got device " + JSON.stringify(device, null, 4));
720
- return device;
721
- });
722
- }
723
- registerDevice() {
724
- return __awaiter(this, void 0, void 0, function* () {
725
- while (!this.xKeyRing) {
726
- yield sleep_1.sleep(100);
727
- }
728
- const token = yield this.getToken("device");
729
- const [userDetails, err] = yield this.retrieveUserDBEntry(this.user.username);
730
- if (!userDetails) {
731
- throw new Error("Username not found " + this.user.username);
732
- }
733
- if (err) {
734
- throw err;
735
- }
736
- if (!token) {
737
- throw new Error("Couldn't fetch token.");
738
- }
739
- const signKey = this.getKeys().public;
740
- const signed = crypto_1.XUtils.encodeHex(tweetnacl_1.default.sign(Uint8Array.from(uuid.parse(token.key)), this.signKeys.secretKey));
741
- const devMsg = {
742
- username: userDetails.username,
743
- signKey,
744
- signed,
745
- preKey: crypto_1.XUtils.encodeHex(this.xKeyRing.preKeys.keyPair.publicKey),
746
- preKeySignature: crypto_1.XUtils.encodeHex(this.xKeyRing.preKeys.signature),
747
- preKeyIndex: this.xKeyRing.preKeys.index,
748
- deviceName: `${os_1.default.platform()}`,
749
- };
750
- try {
751
- const res = yield axios_1.default.post(this.prefixes.HTTP +
752
- this.host +
753
- "/user/" +
754
- userDetails.userID +
755
- "/devices", msgpack_lite_1.default.encode(devMsg), { headers: { "Content-Type": "application/msgpack" } });
756
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
757
- }
758
- catch (err) {
679
+ else {
759
680
  throw err;
760
681
  }
761
- });
682
+ }
683
+ this.log.info("Got device " + JSON.stringify(device, null, 4));
684
+ return device;
762
685
  }
763
- getToken(type) {
764
- return __awaiter(this, void 0, void 0, function* () {
765
- try {
766
- const res = yield axios_1.default.get(this.getHost() + "/token/" + type, {
767
- responseType: "arraybuffer",
768
- });
769
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
770
- }
771
- catch (err) {
772
- this.log.warn(err.toString());
773
- return null;
774
- }
775
- });
686
+ async registerDevice() {
687
+ while (!this.xKeyRing) {
688
+ await sleep(100);
689
+ }
690
+ const token = await this.getToken("device");
691
+ const [userDetails, err] = await this.retrieveUserDBEntry(this.user.username);
692
+ if (!userDetails) {
693
+ throw new Error("Username not found " + this.user.username);
694
+ }
695
+ if (err) {
696
+ throw err;
697
+ }
698
+ if (!token) {
699
+ throw new Error("Couldn't fetch token.");
700
+ }
701
+ const signKey = this.getKeys().public;
702
+ const signed = XUtils.encodeHex(nacl.sign(Uint8Array.from(uuid.parse(token.key)), this.signKeys.secretKey));
703
+ const devMsg = {
704
+ username: userDetails.username,
705
+ signKey,
706
+ signed,
707
+ preKey: XUtils.encodeHex(this.xKeyRing.preKeys.keyPair.publicKey),
708
+ preKeySignature: XUtils.encodeHex(this.xKeyRing.preKeys.signature),
709
+ preKeyIndex: this.xKeyRing.preKeys.index,
710
+ deviceName: `${os.platform()}`,
711
+ };
712
+ try {
713
+ const res = await ax.post(this.prefixes.HTTP +
714
+ this.host +
715
+ "/user/" +
716
+ userDetails.userID +
717
+ "/devices", msgpack.encode(devMsg), { headers: { "Content-Type": "application/msgpack" } });
718
+ return msgpack.decode(Buffer.from(res.data));
719
+ }
720
+ catch (err) {
721
+ throw err;
722
+ }
776
723
  }
777
- uploadAvatar(avatar) {
778
- return __awaiter(this, void 0, void 0, function* () {
779
- if (typeof FormData !== "undefined") {
780
- const fpayload = new FormData();
781
- fpayload.set("avatar", new Blob([avatar]));
782
- yield axios_1.default.post(this.prefixes.HTTP +
783
- this.host +
784
- "/avatar/" +
785
- this.me.user().userID, fpayload, {
786
- headers: { "Content-Type": "multipart/form-data" },
787
- onUploadProgress: (progressEvent) => {
788
- const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
789
- const { loaded, total } = progressEvent;
790
- const progress = {
791
- direction: "upload",
792
- token: this.getUser().userID,
793
- progress: percentCompleted,
794
- loaded,
795
- total,
796
- };
797
- this.emit("fileProgress", progress);
798
- },
799
- });
800
- return;
801
- }
802
- const payload = {
803
- file: crypto_1.XUtils.encodeBase64(avatar),
804
- };
805
- yield axios_1.default.post(this.prefixes.HTTP +
724
+ async getToken(type) {
725
+ try {
726
+ const res = await ax.get(this.getHost() + "/token/" + type, {
727
+ responseType: "arraybuffer",
728
+ });
729
+ return msgpack.decode(Buffer.from(res.data));
730
+ }
731
+ catch (err) {
732
+ this.log.warn(err.toString());
733
+ return null;
734
+ }
735
+ }
736
+ async uploadAvatar(avatar) {
737
+ if (typeof FormData !== "undefined") {
738
+ const fpayload = new FormData();
739
+ fpayload.set("avatar", new Blob([new Uint8Array(avatar)]));
740
+ await ax.post(this.prefixes.HTTP +
806
741
  this.host +
807
742
  "/avatar/" +
808
- this.me.user().userID +
809
- "/json", msgpack_lite_1.default.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
810
- });
743
+ this.me.user().userID, fpayload, {
744
+ headers: { "Content-Type": "multipart/form-data" },
745
+ onUploadProgress: (progressEvent) => {
746
+ const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total ?? 1));
747
+ const { loaded, total = 0 } = progressEvent;
748
+ const progress = {
749
+ direction: "upload",
750
+ token: this.getUser().userID,
751
+ progress: percentCompleted,
752
+ loaded,
753
+ total,
754
+ };
755
+ this.emit("fileProgress", progress);
756
+ },
757
+ });
758
+ return;
759
+ }
760
+ const payload = {
761
+ file: XUtils.encodeBase64(avatar),
762
+ };
763
+ await ax.post(this.prefixes.HTTP +
764
+ this.host +
765
+ "/avatar/" +
766
+ this.me.user().userID +
767
+ "/json", msgpack.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
811
768
  }
812
769
  /**
813
770
  * Gets a list of permissions for a server.
814
771
  *
815
772
  * @returns - The list of IPermissions objects.
816
773
  */
817
- fetchPermissionList(serverID) {
818
- return __awaiter(this, void 0, void 0, function* () {
819
- const res = yield axios_1.default.get(this.prefixes.HTTP +
820
- this.host +
821
- "/server/" +
822
- serverID +
823
- "/permissions");
824
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
825
- });
774
+ async fetchPermissionList(serverID) {
775
+ const res = await ax.get(this.prefixes.HTTP +
776
+ this.host +
777
+ "/server/" +
778
+ serverID +
779
+ "/permissions");
780
+ return msgpack.decode(Buffer.from(res.data));
826
781
  }
827
782
  /**
828
783
  * Gets all permissions for the logged in user.
829
784
  *
830
785
  * @returns - The list of IPermissions objects.
831
786
  */
832
- getPermissions() {
833
- return __awaiter(this, void 0, void 0, function* () {
834
- const res = yield axios_1.default.get(this.getHost() + "/user/" + this.getUser().userID + "/permissions");
835
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
836
- });
787
+ async getPermissions() {
788
+ const res = await ax.get(this.getHost() + "/user/" + this.getUser().userID + "/permissions");
789
+ return msgpack.decode(Buffer.from(res.data));
837
790
  }
838
- deletePermission(permissionID) {
839
- return __awaiter(this, void 0, void 0, function* () {
840
- yield axios_1.default.delete(this.getHost() + "/permission/" + permissionID);
841
- });
791
+ async deletePermission(permissionID) {
792
+ await ax.delete(this.getHost() + "/permission/" + permissionID);
842
793
  }
843
- retrieveFile(fileID, key) {
844
- return __awaiter(this, void 0, void 0, function* () {
845
- try {
846
- const detailsRes = yield axios_1.default.get(this.getHost() + "/file/" + fileID + "/details");
847
- const details = msgpack_lite_1.default.decode(Buffer.from(detailsRes.data));
848
- const res = yield axios_1.default.get(this.getHost() + "/file/" + fileID, {
849
- onDownloadProgress: (progressEvent) => {
850
- const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
851
- const { loaded, total } = progressEvent;
852
- const progress = {
853
- direction: "download",
854
- token: fileID,
855
- progress: percentCompleted,
856
- loaded,
857
- total,
858
- };
859
- this.emit("fileProgress", progress);
860
- },
861
- });
862
- const fileData = res.data;
863
- const decrypted = tweetnacl_1.default.secretbox.open(Uint8Array.from(Buffer.from(fileData)), crypto_1.XUtils.decodeHex(details.nonce), crypto_1.XUtils.decodeHex(key));
864
- if (decrypted) {
865
- const resp = {
866
- details,
867
- data: Buffer.from(decrypted),
794
+ async retrieveFile(fileID, key) {
795
+ try {
796
+ const detailsRes = await ax.get(this.getHost() + "/file/" + fileID + "/details");
797
+ const details = msgpack.decode(Buffer.from(detailsRes.data));
798
+ const res = await ax.get(this.getHost() + "/file/" + fileID, {
799
+ onDownloadProgress: (progressEvent) => {
800
+ const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total ?? 1));
801
+ const { loaded, total = 0 } = progressEvent;
802
+ const progress = {
803
+ direction: "download",
804
+ token: fileID,
805
+ progress: percentCompleted,
806
+ loaded,
807
+ total,
868
808
  };
869
- return resp;
870
- }
871
- throw new Error("Decryption failed.");
872
- }
873
- catch (err) {
874
- throw err;
809
+ this.emit("fileProgress", progress);
810
+ },
811
+ });
812
+ const fileData = res.data;
813
+ const decrypted = nacl.secretbox.open(Uint8Array.from(Buffer.from(fileData)), XUtils.decodeHex(details.nonce), XUtils.decodeHex(key));
814
+ if (decrypted) {
815
+ const resp = {
816
+ details,
817
+ data: Buffer.from(decrypted),
818
+ };
819
+ return resp;
875
820
  }
876
- });
821
+ throw new Error("Decryption failed.");
822
+ }
823
+ catch (err) {
824
+ throw err;
825
+ }
877
826
  }
878
- deleteServer(serverID) {
879
- return __awaiter(this, void 0, void 0, function* () {
880
- yield axios_1.default.delete(this.getHost() + "/server/" + serverID);
881
- });
827
+ async deleteServer(serverID) {
828
+ await ax.delete(this.getHost() + "/server/" + serverID);
882
829
  }
883
830
  /**
884
831
  * Initializes the keyring. This must be called before anything else.
885
832
  */
886
- init() {
887
- return __awaiter(this, void 0, void 0, function* () {
888
- if (this.hasInit) {
889
- return new Error("You should only call init() once.");
833
+ async init() {
834
+ if (this.hasInit) {
835
+ return new Error("You should only call init() once.");
836
+ }
837
+ this.hasInit = true;
838
+ await this.populateKeyRing();
839
+ this.on("message", async (message) => {
840
+ if (message.direction === "outgoing" && !message.forward) {
841
+ this.forward(message);
842
+ }
843
+ if (message.direction === "incoming" &&
844
+ message.recipient === message.sender) {
845
+ return;
890
846
  }
891
- this.hasInit = true;
892
- yield this.populateKeyRing();
893
- this.on("message", (message) => __awaiter(this, void 0, void 0, function* () {
894
- if (message.direction === "outgoing" && !message.forward) {
895
- this.forward(message);
896
- }
897
- if (message.direction === "incoming" &&
898
- message.recipient === message.sender) {
899
- return;
900
- }
901
- yield this.database.saveMessage(message);
902
- }));
903
- this.emit("ready");
847
+ await this.database.saveMessage(message);
904
848
  });
849
+ this.emit("ready");
905
850
  }
906
- deleteChannel(channelID) {
907
- return __awaiter(this, void 0, void 0, function* () {
908
- yield axios_1.default.delete(this.getHost() + "/channel/" + channelID);
909
- });
851
+ async deleteChannel(channelID) {
852
+ await ax.delete(this.getHost() + "/channel/" + channelID);
910
853
  }
911
854
  // returns the file details and the encryption key
912
- createFile(file) {
913
- return __awaiter(this, void 0, void 0, function* () {
914
- this.log.info("Creating file, size: " + formatBytes_1.formatBytes(Buffer.byteLength(file)));
915
- const nonce = crypto_1.xMakeNonce();
916
- const key = tweetnacl_1.default.box.keyPair();
917
- const box = tweetnacl_1.default.secretbox(Uint8Array.from(file), nonce, key.secretKey);
918
- this.log.info("Encrypted size: " + formatBytes_1.formatBytes(Buffer.byteLength(box)));
919
- if (typeof FormData !== "undefined") {
920
- const fpayload = new FormData();
921
- fpayload.set("owner", this.getDevice().deviceID);
922
- fpayload.set("nonce", crypto_1.XUtils.encodeHex(nonce));
923
- fpayload.set("file", new Blob([box]));
924
- const fres = yield axios_1.default.post(this.getHost() + "/file", fpayload, {
925
- headers: { "Content-Type": "multipart/form-data" },
926
- onUploadProgress: (progressEvent) => {
927
- const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
928
- const { loaded, total } = progressEvent;
929
- const progress = {
930
- direction: "upload",
931
- token: crypto_1.XUtils.encodeHex(nonce),
932
- progress: percentCompleted,
933
- loaded,
934
- total,
935
- };
936
- this.emit("fileProgress", progress);
937
- },
938
- });
939
- const fcreatedFile = msgpack_lite_1.default.decode(Buffer.from(fres.data));
940
- return [fcreatedFile, crypto_1.XUtils.encodeHex(key.secretKey)];
941
- }
942
- const payload = {
943
- owner: this.getDevice().deviceID,
944
- nonce: crypto_1.XUtils.encodeHex(nonce),
945
- file: crypto_1.XUtils.encodeBase64(box),
946
- };
947
- const res = yield axios_1.default.post(this.getHost() + "/file/json", msgpack_lite_1.default.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
948
- const createdFile = msgpack_lite_1.default.decode(Buffer.from(res.data));
949
- return [createdFile, crypto_1.XUtils.encodeHex(key.secretKey)];
950
- });
855
+ async createFile(file) {
856
+ this.log.info("Creating file, size: " + formatBytes(Buffer.byteLength(file)));
857
+ const nonce = xMakeNonce();
858
+ const key = nacl.box.keyPair();
859
+ const box = nacl.secretbox(Uint8Array.from(file), nonce, key.secretKey);
860
+ this.log.info("Encrypted size: " + formatBytes(Buffer.byteLength(box)));
861
+ if (typeof FormData !== "undefined") {
862
+ const fpayload = new FormData();
863
+ fpayload.set("owner", this.getDevice().deviceID);
864
+ fpayload.set("nonce", XUtils.encodeHex(nonce));
865
+ fpayload.set("file", new Blob([new Uint8Array(box)]));
866
+ const fres = await ax.post(this.getHost() + "/file", fpayload, {
867
+ headers: { "Content-Type": "multipart/form-data" },
868
+ onUploadProgress: (progressEvent) => {
869
+ const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total ?? 1));
870
+ const { loaded, total = 0 } = progressEvent;
871
+ const progress = {
872
+ direction: "upload",
873
+ token: XUtils.encodeHex(nonce),
874
+ progress: percentCompleted,
875
+ loaded,
876
+ total,
877
+ };
878
+ this.emit("fileProgress", progress);
879
+ },
880
+ });
881
+ const fcreatedFile = msgpack.decode(Buffer.from(fres.data));
882
+ return [fcreatedFile, XUtils.encodeHex(key.secretKey)];
883
+ }
884
+ const payload = {
885
+ owner: this.getDevice().deviceID,
886
+ nonce: XUtils.encodeHex(nonce),
887
+ file: XUtils.encodeBase64(box),
888
+ };
889
+ const res = await ax.post(this.getHost() + "/file/json", msgpack.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
890
+ const createdFile = msgpack.decode(Buffer.from(res.data));
891
+ return [createdFile, XUtils.encodeHex(key.secretKey)];
951
892
  }
952
- getUserList(channelID) {
953
- return __awaiter(this, void 0, void 0, function* () {
954
- const res = yield axios_1.default.post(this.getHost() + "/userList/" + channelID);
955
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
956
- });
893
+ async getUserList(channelID) {
894
+ const res = await ax.post(this.getHost() + "/userList/" + channelID);
895
+ return msgpack.decode(Buffer.from(res.data));
957
896
  }
958
- markSessionVerified(sessionID) {
959
- return __awaiter(this, void 0, void 0, function* () {
960
- return this.database.markSessionVerified(sessionID);
961
- });
897
+ async markSessionVerified(sessionID) {
898
+ return this.database.markSessionVerified(sessionID);
962
899
  }
963
- getGroupHistory(channelID) {
964
- return __awaiter(this, void 0, void 0, function* () {
965
- const messages = yield this.database.getGroupHistory(channelID);
966
- return messages;
967
- });
900
+ async getGroupHistory(channelID) {
901
+ const messages = await this.database.getGroupHistory(channelID);
902
+ return messages;
968
903
  }
969
- deleteHistory(channelOrUserID, olderThan) {
970
- return __awaiter(this, void 0, void 0, function* () {
971
- yield this.database.deleteHistory(channelOrUserID, olderThan);
972
- });
904
+ async deleteHistory(channelOrUserID, olderThan) {
905
+ await this.database.deleteHistory(channelOrUserID, olderThan);
973
906
  }
974
- purgeHistory() {
975
- return __awaiter(this, void 0, void 0, function* () {
976
- yield this.database.purgeHistory();
977
- });
907
+ async purgeHistory() {
908
+ await this.database.purgeHistory();
978
909
  }
979
- getMessageHistory(userID) {
980
- return __awaiter(this, void 0, void 0, function* () {
981
- const messages = yield this.database.getMessageHistory(userID);
982
- return messages;
983
- });
910
+ async getMessageHistory(userID) {
911
+ const messages = await this.database.getMessageHistory(userID);
912
+ return messages;
984
913
  }
985
- sendMessage(userID, message) {
986
- var _a, _b;
987
- return __awaiter(this, void 0, void 0, function* () {
988
- try {
989
- const [userEntry, err] = yield this.retrieveUserDBEntry(userID);
990
- if (err) {
991
- throw err;
992
- }
993
- if (!userEntry) {
994
- throw new Error("Couldn't get user entry.");
995
- }
996
- let deviceList = yield this.getUserDeviceList(userID);
997
- if (!deviceList) {
998
- let retries = 0;
999
- while (!deviceList) {
1000
- deviceList = yield this.getUserDeviceList(userID);
1001
- retries++;
1002
- if (retries > 3) {
1003
- throw new Error("Couldn't get device list.");
1004
- }
1005
- }
1006
- }
1007
- const mailID = uuid.v4();
1008
- const promises = [];
1009
- for (const device of deviceList) {
1010
- promises.push(this.sendMail(device, userEntry, crypto_1.XUtils.decodeUTF8(message), null, mailID, false));
1011
- }
1012
- Promise.allSettled(promises).then((results) => {
1013
- for (const result of results) {
1014
- const { status } = result;
1015
- if (status === "rejected") {
1016
- this.log.warn("Message failed.");
1017
- this.log.warn(result);
1018
- }
1019
- }
1020
- });
1021
- }
1022
- catch (err) {
1023
- this.log.error("Message " + (((_a = err.message) === null || _a === void 0 ? void 0 : _a.mailID) || "") + " threw exception.");
1024
- this.log.error(err.toString());
1025
- if ((_b = err.message) === null || _b === void 0 ? void 0 : _b.mailID) {
1026
- yield this.database.deleteMessage(err.message.mailID);
1027
- }
914
+ async sendMessage(userID, message) {
915
+ try {
916
+ const [userEntry, err] = await this.retrieveUserDBEntry(userID);
917
+ if (err) {
1028
918
  throw err;
1029
919
  }
1030
- });
1031
- }
1032
- sendGroupMessage(channelID, message) {
1033
- return __awaiter(this, void 0, void 0, function* () {
1034
- const userList = yield this.getUserList(channelID);
1035
- for (const user of userList) {
1036
- this.userRecords[user.userID] = user;
920
+ if (!userEntry) {
921
+ throw new Error("Couldn't get user entry.");
922
+ }
923
+ let deviceList = await this.getUserDeviceList(userID);
924
+ if (!deviceList) {
925
+ let retries = 0;
926
+ while (!deviceList) {
927
+ deviceList = await this.getUserDeviceList(userID);
928
+ retries++;
929
+ if (retries > 3) {
930
+ throw new Error("Couldn't get device list.");
931
+ }
932
+ }
1037
933
  }
1038
- this.log.info("Sending to userlist:\n" + JSON.stringify(userList, null, 4));
1039
934
  const mailID = uuid.v4();
1040
935
  const promises = [];
1041
- const userIDs = [...new Set(userList.map((user) => user.userID))];
1042
- const devices = yield this.getMultiUserDeviceList(userIDs);
1043
- this.log.info("Retrieved devicelist:\n" + JSON.stringify(devices, null, 4));
1044
- for (const device of devices) {
1045
- promises.push(this.sendMail(device, this.userRecords[device.owner], crypto_1.XUtils.decodeUTF8(message), uint8uuid_1.uuidToUint8(channelID), mailID, false));
936
+ for (const device of deviceList) {
937
+ promises.push(this.sendMail(device, userEntry, XUtils.decodeUTF8(message), null, mailID, false));
1046
938
  }
1047
939
  Promise.allSettled(promises).then((results) => {
1048
940
  for (const result of results) {
@@ -1053,102 +945,131 @@ class Client extends events_1.EventEmitter {
1053
945
  }
1054
946
  }
1055
947
  });
1056
- });
1057
- }
1058
- createServer(name) {
1059
- return __awaiter(this, void 0, void 0, function* () {
1060
- const res = yield axios_1.default.post(this.getHost() + "/server/" + btoa_1.default(name));
1061
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
1062
- });
1063
- }
1064
- forward(message) {
1065
- return __awaiter(this, void 0, void 0, function* () {
1066
- const copy = Object.assign({}, message);
1067
- if (this.forwarded.includes(copy.mailID)) {
1068
- return;
1069
- }
1070
- this.forwarded.push(copy.mailID);
1071
- if (this.forwarded.length > 1000) {
1072
- this.forwarded.shift();
1073
- }
1074
- const msgBytes = Uint8Array.from(msgpack_lite_1.default.encode(copy));
1075
- const devices = yield this.getUserDeviceList(this.getUser().userID);
1076
- this.log.info("Forwarding to my other devices, deviceList length is " + (devices === null || devices === void 0 ? void 0 : devices.length));
1077
- if (!devices) {
1078
- throw new Error("Couldn't get own devices.");
1079
- }
1080
- const promises = [];
1081
- for (const device of devices) {
1082
- if (device.deviceID !== this.getDevice().deviceID) {
1083
- promises.push(this.sendMail(device, this.getUser(), msgBytes, null, copy.mailID, true));
1084
- }
948
+ }
949
+ catch (err) {
950
+ this.log.error("Message " + (err.message?.mailID || "") + " threw exception.");
951
+ this.log.error(err.toString());
952
+ if (err.message?.mailID) {
953
+ await this.database.deleteMessage(err.message.mailID);
1085
954
  }
1086
- Promise.allSettled(promises).then((results) => {
1087
- for (const result of results) {
1088
- const { status } = result;
1089
- if (status === "rejected") {
1090
- this.log.warn("Message failed.");
1091
- this.log.warn(result);
1092
- }
955
+ throw err;
956
+ }
957
+ }
958
+ async sendGroupMessage(channelID, message) {
959
+ const userList = await this.getUserList(channelID);
960
+ for (const user of userList) {
961
+ this.userRecords[user.userID] = user;
962
+ }
963
+ this.log.info("Sending to userlist:\n" + JSON.stringify(userList, null, 4));
964
+ const mailID = uuid.v4();
965
+ const promises = [];
966
+ const userIDs = [...new Set(userList.map((user) => user.userID))];
967
+ const devices = await this.getMultiUserDeviceList(userIDs);
968
+ this.log.info("Retrieved devicelist:\n" + JSON.stringify(devices, null, 4));
969
+ for (const device of devices) {
970
+ promises.push(this.sendMail(device, this.userRecords[device.owner], XUtils.decodeUTF8(message), uuidToUint8(channelID), mailID, false));
971
+ }
972
+ Promise.allSettled(promises).then((results) => {
973
+ for (const result of results) {
974
+ const { status } = result;
975
+ if (status === "rejected") {
976
+ this.log.warn("Message failed.");
977
+ this.log.warn(result);
1093
978
  }
1094
- });
979
+ }
1095
980
  });
1096
981
  }
1097
- /* Sends encrypted mail to a user. */
1098
- sendMail(device, user, msg, group, mailID, forward, retry = false) {
1099
- return __awaiter(this, void 0, void 0, function* () {
1100
- while (this.sending[device.deviceID] !== undefined) {
1101
- this.log.warn("Sending in progress to device ID " +
1102
- device.deviceID +
1103
- ", waiting.");
1104
- yield sleep_1.sleep(100);
1105
- }
1106
- this.log.info("Sending mail to user: \n" + JSON.stringify(user, null, 4));
1107
- this.log.info("Sending mail to device:\n " +
1108
- JSON.stringify(device.deviceID, null, 4));
1109
- this.sending[device.deviceID] = device;
1110
- const session = yield this.database.getSessionByDeviceID(device.deviceID);
1111
- if (!session || retry) {
1112
- this.log.info("Creating new session for " + device.deviceID);
1113
- yield this.createSession(device, user, msg, group, mailID, forward);
1114
- return;
982
+ async createServer(name) {
983
+ const res = await ax.post(this.getHost() + "/server/" + btoa(name));
984
+ return msgpack.decode(Buffer.from(res.data));
985
+ }
986
+ async forward(message) {
987
+ const copy = { ...message };
988
+ if (this.forwarded.includes(copy.mailID)) {
989
+ return;
990
+ }
991
+ this.forwarded.push(copy.mailID);
992
+ if (this.forwarded.length > 1000) {
993
+ this.forwarded.shift();
994
+ }
995
+ const msgBytes = Uint8Array.from(msgpack.encode(copy));
996
+ const devices = await this.getUserDeviceList(this.getUser().userID);
997
+ this.log.info("Forwarding to my other devices, deviceList length is " +
998
+ devices?.length);
999
+ if (!devices) {
1000
+ throw new Error("Couldn't get own devices.");
1001
+ }
1002
+ const promises = [];
1003
+ for (const device of devices) {
1004
+ if (device.deviceID !== this.getDevice().deviceID) {
1005
+ promises.push(this.sendMail(device, this.getUser(), msgBytes, null, copy.mailID, true));
1115
1006
  }
1116
- else {
1117
- this.log.info("Found existing session for " + device.deviceID);
1007
+ }
1008
+ Promise.allSettled(promises).then((results) => {
1009
+ for (const result of results) {
1010
+ const { status } = result;
1011
+ if (status === "rejected") {
1012
+ this.log.warn("Message failed.");
1013
+ this.log.warn(result);
1014
+ }
1118
1015
  }
1119
- const nonce = crypto_1.xMakeNonce();
1120
- const cipher = tweetnacl_1.default.secretbox(msg, nonce, session.SK);
1121
- const extra = session.publicKey;
1122
- const mail = {
1123
- mailType: types_1.XTypes.WS.MailType.subsequent,
1124
- mailID: mailID || uuid.v4(),
1125
- recipient: device.deviceID,
1126
- cipher,
1127
- nonce,
1128
- extra,
1129
- sender: this.getDevice().deviceID,
1130
- group,
1131
- forward,
1132
- authorID: this.getUser().userID,
1133
- readerID: session.userID,
1134
- };
1135
- const msgb = {
1136
- transmissionID: uuid.v4(),
1137
- type: "resource",
1138
- resourceType: "mail",
1139
- action: "CREATE",
1140
- data: mail,
1141
- };
1142
- const hmac = crypto_1.xHMAC(mail, session.SK);
1143
- this.log.info("Mail hash: " + object_hash_1.default(mail));
1144
- this.log.info("Calculated hmac: " + crypto_1.XUtils.encodeHex(hmac));
1145
- const outMsg = forward
1146
- ? Object.assign(Object.assign({}, msgpack_lite_1.default.decode(msg)), { forward: true }) : {
1016
+ });
1017
+ }
1018
+ /* Sends encrypted mail to a user. */
1019
+ async sendMail(device, user, msg, group, mailID, forward, retry = false) {
1020
+ while (this.sending[device.deviceID] !== undefined) {
1021
+ this.log.warn("Sending in progress to device ID " +
1022
+ device.deviceID +
1023
+ ", waiting.");
1024
+ await sleep(100);
1025
+ }
1026
+ this.log.info("Sending mail to user: \n" + JSON.stringify(user, null, 4));
1027
+ this.log.info("Sending mail to device:\n " +
1028
+ JSON.stringify(device.deviceID, null, 4));
1029
+ this.sending[device.deviceID] = device;
1030
+ const session = await this.database.getSessionByDeviceID(device.deviceID);
1031
+ if (!session || retry) {
1032
+ this.log.info("Creating new session for " + device.deviceID);
1033
+ await this.createSession(device, user, msg, group, mailID, forward);
1034
+ return;
1035
+ }
1036
+ else {
1037
+ this.log.info("Found existing session for " + device.deviceID);
1038
+ }
1039
+ const nonce = xMakeNonce();
1040
+ const cipher = nacl.secretbox(msg, nonce, session.SK);
1041
+ const extra = session.publicKey;
1042
+ const mail = {
1043
+ mailType: MailType.subsequent,
1044
+ mailID: mailID || uuid.v4(),
1045
+ recipient: device.deviceID,
1046
+ cipher,
1047
+ nonce,
1048
+ extra,
1049
+ sender: this.getDevice().deviceID,
1050
+ group,
1051
+ forward,
1052
+ authorID: this.getUser().userID,
1053
+ readerID: session.userID,
1054
+ };
1055
+ const msgb = {
1056
+ transmissionID: uuid.v4(),
1057
+ type: "resource",
1058
+ resourceType: "mail",
1059
+ action: "CREATE",
1060
+ data: mail,
1061
+ };
1062
+ const hmac = xHMAC(mail, session.SK);
1063
+ this.log.info("Mail hash: " + objectHash(mail));
1064
+ this.log.info("Calculated hmac: " + XUtils.encodeHex(hmac));
1065
+ const outMsg = forward
1066
+ ? { ...msgpack.decode(msg), forward: true }
1067
+ : {
1147
1068
  mailID: mail.mailID,
1148
1069
  sender: mail.sender,
1149
1070
  recipient: mail.recipient,
1150
- nonce: crypto_1.XUtils.encodeHex(mail.nonce),
1151
- message: crypto_1.XUtils.encodeUTF8(msg),
1071
+ nonce: XUtils.encodeHex(mail.nonce),
1072
+ message: XUtils.encodeUTF8(msg),
1152
1073
  direction: "outgoing",
1153
1074
  timestamp: new Date(Date.now()),
1154
1075
  decrypted: true,
@@ -1157,142 +1078,121 @@ class Client extends events_1.EventEmitter {
1157
1078
  authorID: mail.authorID,
1158
1079
  readerID: mail.readerID,
1159
1080
  };
1160
- this.emit("message", outMsg);
1161
- yield new Promise((res, rej) => {
1162
- const callback = (packedMsg) => __awaiter(this, void 0, void 0, function* () {
1163
- const [header, receivedMsg] = crypto_1.XUtils.unpackMessage(packedMsg);
1164
- if (receivedMsg.transmissionID === msgb.transmissionID) {
1165
- this.conn.off("message", callback);
1166
- if (receivedMsg.type === "success") {
1167
- res(receivedMsg.data);
1168
- }
1169
- else {
1170
- rej({
1171
- error: receivedMsg,
1172
- message: outMsg,
1173
- });
1174
- }
1081
+ this.emit("message", outMsg);
1082
+ await new Promise((res, rej) => {
1083
+ const callback = async (packedMsg) => {
1084
+ const [header, receivedMsg] = XUtils.unpackMessage(packedMsg);
1085
+ if (receivedMsg.transmissionID === msgb.transmissionID) {
1086
+ this.conn.off("message", callback);
1087
+ if (receivedMsg.type === "success") {
1088
+ res(receivedMsg.data);
1175
1089
  }
1176
- });
1177
- this.conn.on("message", callback);
1178
- this.send(msgb, hmac);
1179
- });
1180
- delete this.sending[device.deviceID];
1181
- });
1182
- }
1183
- getSessionList() {
1184
- return __awaiter(this, void 0, void 0, function* () {
1185
- return this.database.getAllSessions();
1090
+ else {
1091
+ rej({
1092
+ error: receivedMsg,
1093
+ message: outMsg,
1094
+ });
1095
+ }
1096
+ }
1097
+ };
1098
+ this.conn.on("message", callback);
1099
+ this.send(msgb, hmac);
1186
1100
  });
1101
+ delete this.sending[device.deviceID];
1187
1102
  }
1188
- getServerList() {
1189
- return __awaiter(this, void 0, void 0, function* () {
1190
- const res = yield axios_1.default.get(this.getHost() + "/user/" + this.getUser().userID + "/servers");
1191
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
1192
- });
1103
+ async getSessionList() {
1104
+ return this.database.getAllSessions();
1193
1105
  }
1194
- createChannel(name, serverID) {
1195
- return __awaiter(this, void 0, void 0, function* () {
1196
- const body = { name };
1197
- const res = yield axios_1.default.post(this.getHost() + "/server/" + serverID + "/channels", msgpack_lite_1.default.encode(body), { headers: { "Content-Type": "application/msgpack" } });
1198
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
1199
- });
1106
+ async getServerList() {
1107
+ const res = await ax.get(this.getHost() + "/user/" + this.getUser().userID + "/servers");
1108
+ return msgpack.decode(Buffer.from(res.data));
1200
1109
  }
1201
- getDeviceByID(deviceID) {
1202
- return __awaiter(this, void 0, void 0, function* () {
1203
- if (this.deviceRecords[deviceID]) {
1204
- this.log.info("Found device in local cache.");
1205
- return this.deviceRecords[deviceID];
1206
- }
1207
- const device = yield this.database.getDevice(deviceID);
1208
- if (device) {
1209
- this.log.info("Found device in local db.");
1210
- this.deviceRecords[deviceID] = device;
1211
- return device;
1212
- }
1213
- try {
1214
- const res = yield axios_1.default.get(this.getHost() + "/device/" + deviceID);
1215
- this.log.info("Retrieved device from server.");
1216
- const fetchedDevice = msgpack_lite_1.default.decode(Buffer.from(res.data));
1217
- this.deviceRecords[deviceID] = fetchedDevice;
1218
- yield this.database.saveDevice(fetchedDevice);
1219
- return fetchedDevice;
1220
- }
1221
- catch (err) {
1222
- return null;
1223
- }
1224
- });
1110
+ async createChannel(name, serverID) {
1111
+ const body = { name };
1112
+ const res = await ax.post(this.getHost() + "/server/" + serverID + "/channels", msgpack.encode(body), { headers: { "Content-Type": "application/msgpack" } });
1113
+ return msgpack.decode(Buffer.from(res.data));
1225
1114
  }
1226
- deleteDevice(deviceID) {
1227
- return __awaiter(this, void 0, void 0, function* () {
1228
- if (deviceID === this.getDevice().deviceID) {
1229
- throw new Error("You can't delete the device you're logged in to.");
1230
- }
1231
- yield axios_1.default.delete(this.prefixes.HTTP +
1232
- this.host +
1233
- "/user/" +
1234
- this.getUser().userID +
1235
- "/devices/" +
1236
- deviceID);
1237
- });
1115
+ async getDeviceByID(deviceID) {
1116
+ if (this.deviceRecords[deviceID]) {
1117
+ this.log.info("Found device in local cache.");
1118
+ return this.deviceRecords[deviceID];
1119
+ }
1120
+ const device = await this.database.getDevice(deviceID);
1121
+ if (device) {
1122
+ this.log.info("Found device in local db.");
1123
+ this.deviceRecords[deviceID] = device;
1124
+ return device;
1125
+ }
1126
+ try {
1127
+ const res = await ax.get(this.getHost() + "/device/" + deviceID);
1128
+ this.log.info("Retrieved device from server.");
1129
+ const fetchedDevice = msgpack.decode(Buffer.from(res.data));
1130
+ this.deviceRecords[deviceID] = fetchedDevice;
1131
+ await this.database.saveDevice(fetchedDevice);
1132
+ return fetchedDevice;
1133
+ }
1134
+ catch (err) {
1135
+ return null;
1136
+ }
1238
1137
  }
1239
- getMultiUserDeviceList(userIDs) {
1240
- return __awaiter(this, void 0, void 0, function* () {
1241
- try {
1242
- const res = yield axios_1.default.post(this.getHost() + "/deviceList", msgpack_lite_1.default.encode(userIDs), { headers: { "Content-Type": "application/msgpack" } });
1243
- const devices = msgpack_lite_1.default.decode(Buffer.from(res.data));
1244
- for (const device of devices) {
1245
- this.deviceRecords[device.deviceID] = device;
1246
- }
1247
- return devices;
1248
- }
1249
- catch (err) {
1250
- return [];
1138
+ async deleteDevice(deviceID) {
1139
+ if (deviceID === this.getDevice().deviceID) {
1140
+ throw new Error("You can't delete the device you're logged in to.");
1141
+ }
1142
+ await ax.delete(this.prefixes.HTTP +
1143
+ this.host +
1144
+ "/user/" +
1145
+ this.getUser().userID +
1146
+ "/devices/" +
1147
+ deviceID);
1148
+ }
1149
+ async getMultiUserDeviceList(userIDs) {
1150
+ try {
1151
+ const res = await ax.post(this.getHost() + "/deviceList", msgpack.encode(userIDs), { headers: { "Content-Type": "application/msgpack" } });
1152
+ const devices = msgpack.decode(Buffer.from(res.data));
1153
+ for (const device of devices) {
1154
+ this.deviceRecords[device.deviceID] = device;
1251
1155
  }
1252
- });
1156
+ return devices;
1157
+ }
1158
+ catch (err) {
1159
+ return [];
1160
+ }
1253
1161
  }
1254
- getUserDeviceList(userID) {
1255
- return __awaiter(this, void 0, void 0, function* () {
1256
- try {
1257
- const res = yield axios_1.default.get(this.getHost() + "/user/" + userID + "/devices");
1258
- const devices = msgpack_lite_1.default.decode(Buffer.from(res.data));
1259
- for (const device of devices) {
1260
- this.deviceRecords[device.deviceID] = device;
1261
- }
1262
- return devices;
1263
- }
1264
- catch (err) {
1265
- return null;
1162
+ async getUserDeviceList(userID) {
1163
+ try {
1164
+ const res = await ax.get(this.getHost() + "/user/" + userID + "/devices");
1165
+ const devices = msgpack.decode(Buffer.from(res.data));
1166
+ for (const device of devices) {
1167
+ this.deviceRecords[device.deviceID] = device;
1266
1168
  }
1267
- });
1169
+ return devices;
1170
+ }
1171
+ catch (err) {
1172
+ return null;
1173
+ }
1268
1174
  }
1269
- getServerByID(serverID) {
1270
- return __awaiter(this, void 0, void 0, function* () {
1271
- try {
1272
- const res = yield axios_1.default.get(this.getHost() + "/server/" + serverID);
1273
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
1274
- }
1275
- catch (err) {
1276
- return null;
1277
- }
1278
- });
1175
+ async getServerByID(serverID) {
1176
+ try {
1177
+ const res = await ax.get(this.getHost() + "/server/" + serverID);
1178
+ return msgpack.decode(Buffer.from(res.data));
1179
+ }
1180
+ catch (err) {
1181
+ return null;
1182
+ }
1279
1183
  }
1280
- getChannelByID(channelID) {
1281
- return __awaiter(this, void 0, void 0, function* () {
1282
- try {
1283
- const res = yield axios_1.default.get(this.getHost() + "/channel/" + channelID);
1284
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
1285
- }
1286
- catch (err) {
1287
- return null;
1288
- }
1289
- });
1184
+ async getChannelByID(channelID) {
1185
+ try {
1186
+ const res = await ax.get(this.getHost() + "/channel/" + channelID);
1187
+ return msgpack.decode(Buffer.from(res.data));
1188
+ }
1189
+ catch (err) {
1190
+ return null;
1191
+ }
1290
1192
  }
1291
- getChannelList(serverID) {
1292
- return __awaiter(this, void 0, void 0, function* () {
1293
- const res = yield axios_1.default.get(this.getHost() + "/server/" + serverID + "/channels");
1294
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
1295
- });
1193
+ async getChannelList(serverID) {
1194
+ const res = await ax.get(this.getHost() + "/server/" + serverID + "/channels");
1195
+ return msgpack.decode(Buffer.from(res.data));
1296
1196
  }
1297
1197
  /* Get the currently logged in user. You cannot call this until
1298
1198
  after the auth event is emitted. */
@@ -1314,139 +1214,135 @@ class Client extends events_1.EventEmitter {
1314
1214
  /* Retrieves the userID with the user identifier.
1315
1215
  user identifier is checked for userID, then signkey,
1316
1216
  and finally falls back to username. */
1317
- retrieveUserDBEntry(userIdentifier) {
1318
- return __awaiter(this, void 0, void 0, function* () {
1319
- if (this.userRecords[userIdentifier]) {
1320
- return [this.userRecords[userIdentifier], null];
1321
- }
1322
- try {
1323
- const res = yield axios_1.default.get(this.getHost() + "/user/" + userIdentifier);
1324
- const userRecord = msgpack_lite_1.default.decode(Buffer.from(res.data));
1325
- this.userRecords[userIdentifier] = userRecord;
1326
- return [userRecord, null];
1327
- }
1328
- catch (err) {
1329
- return [null, err];
1330
- }
1331
- });
1217
+ async retrieveUserDBEntry(userIdentifier) {
1218
+ if (this.userRecords[userIdentifier]) {
1219
+ return [this.userRecords[userIdentifier], null];
1220
+ }
1221
+ try {
1222
+ const res = await ax.get(this.getHost() + "/user/" + userIdentifier);
1223
+ const userRecord = msgpack.decode(Buffer.from(res.data));
1224
+ this.userRecords[userIdentifier] = userRecord;
1225
+ return [userRecord, null];
1226
+ }
1227
+ catch (err) {
1228
+ return [null, err];
1229
+ }
1332
1230
  }
1333
1231
  /* Retrieves the current list of users you have sessions with. */
1334
- getFamiliars() {
1335
- return __awaiter(this, void 0, void 0, function* () {
1336
- const sessions = yield this.database.getAllSessions();
1337
- const familiars = [];
1338
- for (const session of sessions) {
1339
- const [user, err] = yield this.retrieveUserDBEntry(session.userID);
1340
- if (user) {
1341
- familiars.push(user);
1342
- }
1232
+ async getFamiliars() {
1233
+ const sessions = await this.database.getAllSessions();
1234
+ const familiars = [];
1235
+ for (const session of sessions) {
1236
+ const [user, err] = await this.retrieveUserDBEntry(session.userID);
1237
+ if (user) {
1238
+ familiars.push(user);
1343
1239
  }
1344
- return familiars;
1345
- });
1240
+ }
1241
+ return familiars;
1346
1242
  }
1347
- createSession(device, user, message, group,
1243
+ async createSession(device, user, message, group,
1348
1244
  /* this is passed through if the first message is
1349
1245
  part of a group message */
1350
1246
  mailID, forward) {
1351
- var _a;
1352
- return __awaiter(this, void 0, void 0, function* () {
1353
- let keyBundle;
1354
- this.log.info("Requesting key bundle for device: " +
1355
- JSON.stringify(device, null, 4));
1356
- try {
1357
- keyBundle = yield this.retrieveKeyBundle(device.deviceID);
1358
- }
1359
- catch (err) {
1360
- this.log.warn("Couldn't get key bundle:", err);
1361
- return;
1362
- }
1363
- this.log.warn(this.toString() +
1364
- " retrieved keybundle #" + ((_a = keyBundle.otk) === null || _a === void 0 ? void 0 : _a.index.toString()) +
1365
- " for " +
1366
- device.deviceID);
1367
- // my keys
1368
- const IK_A = this.xKeyRing.identityKeys.secretKey;
1369
- const IK_AP = this.xKeyRing.identityKeys.publicKey;
1370
- const EK_A = this.xKeyRing.ephemeralKeys.secretKey;
1371
- // their keys
1372
- const IK_B = crypto_1.XKeyConvert.convertPublicKey(keyBundle.signKey);
1373
- const SPK_B = keyBundle.preKey.publicKey;
1374
- const OPK_B = keyBundle.otk ? keyBundle.otk.publicKey : null;
1375
- // diffie hellman functions
1376
- const DH1 = crypto_1.xDH(IK_A, SPK_B);
1377
- const DH2 = crypto_1.xDH(EK_A, IK_B);
1378
- const DH3 = crypto_1.xDH(EK_A, SPK_B);
1379
- const DH4 = OPK_B ? crypto_1.xDH(EK_A, OPK_B) : null;
1380
- // initial key material
1381
- const IKM = DH4 ? crypto_1.xConcat(DH1, DH2, DH3, DH4) : crypto_1.xConcat(DH1, DH2, DH3);
1382
- // one time key index
1383
- const IDX = keyBundle.otk
1384
- ? crypto_1.XUtils.numberToUint8Arr(keyBundle.otk.index)
1385
- : crypto_1.XUtils.numberToUint8Arr(0);
1386
- // shared secret key
1387
- const SK = crypto_1.xKDF(IKM);
1388
- this.log.info("Obtained SK, " + crypto_1.XUtils.encodeHex(SK));
1389
- const PK = tweetnacl_1.default.box.keyPair.fromSecretKey(SK).publicKey;
1390
- this.log.info(this.toString() +
1391
- " Obtained PK for " +
1392
- device.deviceID +
1393
- " " +
1394
- crypto_1.XUtils.encodeHex(PK));
1395
- const AD = crypto_1.xConcat(crypto_1.xEncode(crypto_1.xConstants.CURVE, IK_AP), crypto_1.xEncode(crypto_1.xConstants.CURVE, IK_B));
1396
- const nonce = crypto_1.xMakeNonce();
1397
- const cipher = tweetnacl_1.default.secretbox(message, nonce, SK);
1398
- this.log.info("Encrypted ciphertext.");
1399
- /* 32 bytes for signkey, 32 bytes for ephemeral key,
1400
- 68 bytes for AD, 6 bytes for otk index (empty for no otk) */
1401
- const extra = crypto_1.xConcat(this.signKeys.publicKey, this.xKeyRing.ephemeralKeys.publicKey, PK, AD, IDX);
1402
- const mail = {
1403
- mailType: types_1.XTypes.WS.MailType.initial,
1404
- mailID: mailID || uuid.v4(),
1405
- recipient: device.deviceID,
1406
- cipher,
1407
- nonce,
1408
- extra,
1409
- sender: this.getDevice().deviceID,
1410
- group,
1411
- forward,
1412
- authorID: this.getUser().userID,
1413
- readerID: user.userID,
1414
- };
1415
- const hmac = crypto_1.xHMAC(mail, SK);
1416
- this.log.info("Mail hash: " + object_hash_1.default(mail));
1417
- this.log.info("Generated hmac: " + crypto_1.XUtils.encodeHex(hmac));
1418
- const msg = {
1419
- transmissionID: uuid.v4(),
1420
- type: "resource",
1421
- resourceType: "mail",
1422
- action: "CREATE",
1423
- data: mail,
1424
- };
1425
- // discard the ephemeral keys
1426
- this.newEphemeralKeys();
1427
- // save the encryption session
1428
- this.log.info("Saving new session.");
1429
- const sessionEntry = {
1430
- verified: false,
1431
- sessionID: uuid.v4(),
1432
- userID: user.userID,
1433
- mode: "initiator",
1434
- SK: crypto_1.XUtils.encodeHex(SK),
1435
- publicKey: crypto_1.XUtils.encodeHex(PK),
1436
- lastUsed: new Date(Date.now()),
1437
- fingerprint: crypto_1.XUtils.encodeHex(AD),
1438
- deviceID: device.deviceID,
1439
- };
1440
- yield this.database.saveSession(sessionEntry);
1441
- this.emit("session", sessionEntry, user);
1442
- // emit the message
1443
- const emitMsg = forward
1444
- ? Object.assign(Object.assign({}, msgpack_lite_1.default.decode(message)), { forward: true }) : {
1445
- nonce: crypto_1.XUtils.encodeHex(mail.nonce),
1247
+ let keyBundle;
1248
+ this.log.info("Requesting key bundle for device: " +
1249
+ JSON.stringify(device, null, 4));
1250
+ try {
1251
+ keyBundle = await this.retrieveKeyBundle(device.deviceID);
1252
+ }
1253
+ catch (err) {
1254
+ this.log.warn("Couldn't get key bundle:", err);
1255
+ return;
1256
+ }
1257
+ this.log.warn(this.toString() +
1258
+ " retrieved keybundle #" +
1259
+ keyBundle.otk?.index.toString() +
1260
+ " for " +
1261
+ device.deviceID);
1262
+ // my keys
1263
+ const IK_A = this.xKeyRing.identityKeys.secretKey;
1264
+ const IK_AP = this.xKeyRing.identityKeys.publicKey;
1265
+ const EK_A = this.xKeyRing.ephemeralKeys.secretKey;
1266
+ // their keys
1267
+ const IK_B = XKeyConvert.convertPublicKey(keyBundle.signKey);
1268
+ const SPK_B = keyBundle.preKey.publicKey;
1269
+ const OPK_B = keyBundle.otk ? keyBundle.otk.publicKey : null;
1270
+ // diffie hellman functions
1271
+ const DH1 = xDH(IK_A, SPK_B);
1272
+ const DH2 = xDH(EK_A, IK_B);
1273
+ const DH3 = xDH(EK_A, SPK_B);
1274
+ const DH4 = OPK_B ? xDH(EK_A, OPK_B) : null;
1275
+ // initial key material
1276
+ const IKM = DH4 ? xConcat(DH1, DH2, DH3, DH4) : xConcat(DH1, DH2, DH3);
1277
+ // one time key index
1278
+ const IDX = keyBundle.otk
1279
+ ? XUtils.numberToUint8Arr(keyBundle.otk.index)
1280
+ : XUtils.numberToUint8Arr(0);
1281
+ // shared secret key
1282
+ const SK = xKDF(IKM);
1283
+ this.log.info("Obtained SK, " + XUtils.encodeHex(SK));
1284
+ const PK = nacl.box.keyPair.fromSecretKey(SK).publicKey;
1285
+ this.log.info(this.toString() +
1286
+ " Obtained PK for " +
1287
+ device.deviceID +
1288
+ " " +
1289
+ XUtils.encodeHex(PK));
1290
+ const AD = xConcat(xEncode(xConstants.CURVE, IK_AP), xEncode(xConstants.CURVE, IK_B));
1291
+ const nonce = xMakeNonce();
1292
+ const cipher = nacl.secretbox(message, nonce, SK);
1293
+ this.log.info("Encrypted ciphertext.");
1294
+ /* 32 bytes for signkey, 32 bytes for ephemeral key,
1295
+ 68 bytes for AD, 6 bytes for otk index (empty for no otk) */
1296
+ const extra = xConcat(this.signKeys.publicKey, this.xKeyRing.ephemeralKeys.publicKey, PK, AD, IDX);
1297
+ const mail = {
1298
+ mailType: MailType.initial,
1299
+ mailID: mailID || uuid.v4(),
1300
+ recipient: device.deviceID,
1301
+ cipher,
1302
+ nonce,
1303
+ extra,
1304
+ sender: this.getDevice().deviceID,
1305
+ group,
1306
+ forward,
1307
+ authorID: this.getUser().userID,
1308
+ readerID: user.userID,
1309
+ };
1310
+ const hmac = xHMAC(mail, SK);
1311
+ this.log.info("Mail hash: " + objectHash(mail));
1312
+ this.log.info("Generated hmac: " + XUtils.encodeHex(hmac));
1313
+ const msg = {
1314
+ transmissionID: uuid.v4(),
1315
+ type: "resource",
1316
+ resourceType: "mail",
1317
+ action: "CREATE",
1318
+ data: mail,
1319
+ };
1320
+ // discard the ephemeral keys
1321
+ this.newEphemeralKeys();
1322
+ // save the encryption session
1323
+ this.log.info("Saving new session.");
1324
+ const sessionEntry = {
1325
+ verified: false,
1326
+ sessionID: uuid.v4(),
1327
+ userID: user.userID,
1328
+ mode: "initiator",
1329
+ SK: XUtils.encodeHex(SK),
1330
+ publicKey: XUtils.encodeHex(PK),
1331
+ lastUsed: new Date(Date.now()),
1332
+ fingerprint: XUtils.encodeHex(AD),
1333
+ deviceID: device.deviceID,
1334
+ };
1335
+ await this.database.saveSession(sessionEntry);
1336
+ this.emit("session", sessionEntry, user);
1337
+ // emit the message
1338
+ const emitMsg = forward
1339
+ ? { ...msgpack.decode(message), forward: true }
1340
+ : {
1341
+ nonce: XUtils.encodeHex(mail.nonce),
1446
1342
  mailID: mail.mailID,
1447
1343
  sender: mail.sender,
1448
1344
  recipient: mail.recipient,
1449
- message: crypto_1.XUtils.encodeUTF8(message),
1345
+ message: XUtils.encodeUTF8(message),
1450
1346
  direction: "outgoing",
1451
1347
  timestamp: new Date(Date.now()),
1452
1348
  decrypted: true,
@@ -1455,30 +1351,29 @@ class Client extends events_1.EventEmitter {
1455
1351
  authorID: mail.authorID,
1456
1352
  readerID: mail.readerID,
1457
1353
  };
1458
- this.emit("message", emitMsg);
1459
- // send mail and wait for response
1460
- yield new Promise((res, rej) => {
1461
- const callback = (packedMsg) => {
1462
- const [header, receivedMsg] = crypto_1.XUtils.unpackMessage(packedMsg);
1463
- if (receivedMsg.transmissionID === msg.transmissionID) {
1464
- this.conn.off("message", callback);
1465
- if (receivedMsg.type === "success") {
1466
- res(receivedMsg.data);
1467
- }
1468
- else {
1469
- rej({
1470
- error: receivedMsg,
1471
- message: emitMsg,
1472
- });
1473
- }
1354
+ this.emit("message", emitMsg);
1355
+ // send mail and wait for response
1356
+ await new Promise((res, rej) => {
1357
+ const callback = (packedMsg) => {
1358
+ const [header, receivedMsg] = XUtils.unpackMessage(packedMsg);
1359
+ if (receivedMsg.transmissionID === msg.transmissionID) {
1360
+ this.conn.off("message", callback);
1361
+ if (receivedMsg.type === "success") {
1362
+ res(receivedMsg.data);
1474
1363
  }
1475
- };
1476
- this.conn.on("message", callback);
1477
- this.send(msg, hmac);
1478
- this.log.info("Mail sent.");
1479
- });
1480
- delete this.sending[device.deviceID];
1364
+ else {
1365
+ rej({
1366
+ error: receivedMsg,
1367
+ message: emitMsg,
1368
+ });
1369
+ }
1370
+ }
1371
+ };
1372
+ this.conn.on("message", callback);
1373
+ this.send(msg, hmac);
1374
+ this.log.info("Mail sent.");
1481
1375
  });
1376
+ delete this.sending[device.deviceID];
1482
1377
  }
1483
1378
  sendReceipt(nonce) {
1484
1379
  const receipt = {
@@ -1488,91 +1383,93 @@ class Client extends events_1.EventEmitter {
1488
1383
  };
1489
1384
  this.send(receipt);
1490
1385
  }
1491
- getSessionByPubkey(publicKey) {
1492
- return __awaiter(this, void 0, void 0, function* () {
1493
- const strPubKey = crypto_1.XUtils.encodeHex(publicKey);
1494
- if (this.sessionRecords[strPubKey]) {
1495
- return this.sessionRecords[strPubKey];
1496
- }
1497
- const session = yield this.database.getSessionByPublicKey(publicKey);
1498
- if (session) {
1499
- this.sessionRecords[strPubKey] = session;
1500
- }
1501
- return session;
1502
- });
1503
- }
1504
- readMail(header, mail, timestamp) {
1505
- return __awaiter(this, void 0, void 0, function* () {
1506
- this.sendReceipt(mail.nonce);
1507
- let timeout = 1;
1508
- while (this.reading) {
1509
- yield sleep_1.sleep(timeout);
1510
- timeout *= 2;
1511
- }
1512
- this.reading = true;
1513
- const healSession = () => __awaiter(this, void 0, void 0, function* () {
1386
+ async getSessionByPubkey(publicKey) {
1387
+ const strPubKey = XUtils.encodeHex(publicKey);
1388
+ if (this.sessionRecords[strPubKey]) {
1389
+ return this.sessionRecords[strPubKey];
1390
+ }
1391
+ const session = await this.database.getSessionByPublicKey(publicKey);
1392
+ if (session) {
1393
+ this.sessionRecords[strPubKey] = session;
1394
+ }
1395
+ return session;
1396
+ }
1397
+ async readMail(header, mail, timestamp) {
1398
+ this.sendReceipt(mail.nonce);
1399
+ let timeout = 1;
1400
+ while (this.reading) {
1401
+ await sleep(timeout);
1402
+ timeout *= 2;
1403
+ }
1404
+ this.reading = true;
1405
+ try {
1406
+ const healSession = async () => {
1514
1407
  this.log.info("Requesting retry of " + mail.mailID);
1515
- const deviceEntry = yield this.getDeviceByID(mail.sender);
1516
- const [user, err] = yield this.retrieveUserDBEntry(mail.authorID);
1408
+ const deviceEntry = await this.getDeviceByID(mail.sender);
1409
+ const [user, err] = await this.retrieveUserDBEntry(mail.authorID);
1517
1410
  if (deviceEntry && user) {
1518
- this.createSession(deviceEntry, user, crypto_1.XUtils.decodeUTF8(`��RETRY_REQUEST:${mail.mailID}��`), mail.group, uuid.v4(), false);
1411
+ this.createSession(deviceEntry, user, XUtils.decodeUTF8(`��RETRY_REQUEST:${mail.mailID}��`), mail.group, uuid.v4(), false);
1519
1412
  }
1520
- });
1413
+ };
1521
1414
  this.log.info("Received mail from " + mail.sender);
1522
1415
  switch (mail.mailType) {
1523
- case types_1.XTypes.WS.MailType.subsequent:
1416
+ case MailType.subsequent:
1524
1417
  const [publicKey] = Client.deserializeExtra(mail.mailType, mail.extra);
1525
- let session = yield this.getSessionByPubkey(publicKey);
1418
+ let session = await this.getSessionByPubkey(publicKey);
1526
1419
  let retries = 0;
1527
1420
  while (!session) {
1528
1421
  if (retries > 3) {
1529
1422
  break;
1530
1423
  }
1531
- session = yield this.getSessionByPubkey(publicKey);
1424
+ session = await this.getSessionByPubkey(publicKey);
1532
1425
  retries++;
1533
1426
  return;
1534
1427
  }
1535
1428
  if (!session) {
1536
1429
  this.log.warn("Couldn't find session public key " +
1537
- crypto_1.XUtils.encodeHex(publicKey));
1430
+ XUtils.encodeHex(publicKey));
1538
1431
  healSession();
1539
1432
  return;
1540
1433
  }
1541
1434
  this.log.info("Session found for " + mail.sender);
1542
- this.log.info("Mail nonce " + crypto_1.XUtils.encodeHex(mail.nonce));
1543
- const HMAC = crypto_1.xHMAC(mail, session.SK);
1544
- this.log.info("Mail hash: " + object_hash_1.default(mail));
1545
- this.log.info("Calculated hmac: " + crypto_1.XUtils.encodeHex(HMAC));
1546
- if (!crypto_1.XUtils.bytesEqual(HMAC, header)) {
1435
+ this.log.info("Mail nonce " + XUtils.encodeHex(mail.nonce));
1436
+ const HMAC = xHMAC(mail, session.SK);
1437
+ this.log.info("Mail hash: " + objectHash(mail));
1438
+ this.log.info("Calculated hmac: " + XUtils.encodeHex(HMAC));
1439
+ if (!XUtils.bytesEqual(HMAC, header)) {
1547
1440
  this.log.warn("Message authentication failed (HMAC does not match).");
1548
1441
  healSession();
1549
1442
  return;
1550
1443
  }
1551
- const decrypted = tweetnacl_1.default.secretbox.open(mail.cipher, mail.nonce, session.SK);
1444
+ const decrypted = nacl.secretbox.open(mail.cipher, mail.nonce, session.SK);
1552
1445
  if (decrypted) {
1553
1446
  this.log.info("Decryption successful.");
1554
1447
  let plaintext = "";
1555
1448
  if (!mail.forward) {
1556
- plaintext = crypto_1.XUtils.encodeUTF8(decrypted);
1449
+ plaintext = XUtils.encodeUTF8(decrypted);
1557
1450
  }
1558
1451
  // emit the message
1559
1452
  const message = mail.forward
1560
- ? Object.assign(Object.assign({}, msgpack_lite_1.default.decode(decrypted)), { forward: true }) : {
1561
- nonce: crypto_1.XUtils.encodeHex(mail.nonce),
1562
- mailID: mail.mailID,
1563
- sender: mail.sender,
1564
- recipient: mail.recipient,
1565
- message: crypto_1.XUtils.encodeUTF8(decrypted),
1566
- direction: "incoming",
1567
- timestamp: new Date(timestamp),
1568
- decrypted: true,
1569
- group: mail.group
1570
- ? uuid.stringify(mail.group)
1571
- : null,
1572
- forward: mail.forward,
1573
- authorID: mail.authorID,
1574
- readerID: mail.readerID,
1575
- };
1453
+ ? {
1454
+ ...msgpack.decode(decrypted),
1455
+ forward: true,
1456
+ }
1457
+ : {
1458
+ nonce: XUtils.encodeHex(mail.nonce),
1459
+ mailID: mail.mailID,
1460
+ sender: mail.sender,
1461
+ recipient: mail.recipient,
1462
+ message: XUtils.encodeUTF8(decrypted),
1463
+ direction: "incoming",
1464
+ timestamp: new Date(timestamp),
1465
+ decrypted: true,
1466
+ group: mail.group
1467
+ ? uuid.stringify(mail.group)
1468
+ : null,
1469
+ forward: mail.forward,
1470
+ authorID: mail.authorID,
1471
+ readerID: mail.readerID,
1472
+ };
1576
1473
  this.emit("message", message);
1577
1474
  this.database.markSessionUsed(session.sessionID);
1578
1475
  }
@@ -1581,7 +1478,7 @@ class Client extends events_1.EventEmitter {
1581
1478
  healSession();
1582
1479
  // emit the message
1583
1480
  const message = {
1584
- nonce: crypto_1.XUtils.encodeHex(mail.nonce),
1481
+ nonce: XUtils.encodeHex(mail.nonce),
1585
1482
  mailID: mail.mailID,
1586
1483
  sender: mail.sender,
1587
1484
  recipient: mail.recipient,
@@ -1597,33 +1494,33 @@ class Client extends events_1.EventEmitter {
1597
1494
  this.emit("message", message);
1598
1495
  }
1599
1496
  break;
1600
- case types_1.XTypes.WS.MailType.initial:
1497
+ case MailType.initial:
1601
1498
  this.log.info("Initiating new session.");
1602
- const [signKey, ephKey, assocData, indexBytes,] = Client.deserializeExtra(types_1.XTypes.WS.MailType.initial, mail.extra);
1603
- const preKeyIndex = crypto_1.XUtils.uint8ArrToNumber(indexBytes);
1499
+ const [signKey, ephKey, assocData, indexBytes,] = Client.deserializeExtra(MailType.initial, mail.extra);
1500
+ const preKeyIndex = XUtils.uint8ArrToNumber(indexBytes);
1604
1501
  this.log.info(this.toString() + " otk #" + preKeyIndex + " indicated");
1605
1502
  const otk = preKeyIndex === 0
1606
1503
  ? null
1607
- : yield this.database.getOneTimeKey(preKeyIndex);
1504
+ : await this.database.getOneTimeKey(preKeyIndex);
1608
1505
  if (otk) {
1609
1506
  this.log.info("otk #" +
1610
- JSON.stringify(otk === null || otk === void 0 ? void 0 : otk.index) +
1507
+ JSON.stringify(otk?.index) +
1611
1508
  " retrieved from database.");
1612
1509
  }
1613
- this.log.info("signKey: " + crypto_1.XUtils.encodeHex(signKey));
1614
- this.log.info("preKey: " + crypto_1.XUtils.encodeHex(ephKey));
1510
+ this.log.info("signKey: " + XUtils.encodeHex(signKey));
1511
+ this.log.info("preKey: " + XUtils.encodeHex(ephKey));
1615
1512
  if (otk) {
1616
- this.log.info("OTK: " + crypto_1.XUtils.encodeHex(otk.keyPair.publicKey));
1513
+ this.log.info("OTK: " + XUtils.encodeHex(otk.keyPair.publicKey));
1617
1514
  }
1618
- if ((otk === null || otk === void 0 ? void 0 : otk.index) !== preKeyIndex && preKeyIndex !== 0) {
1515
+ if (otk?.index !== preKeyIndex && preKeyIndex !== 0) {
1619
1516
  this.log.warn("OTK index mismatch, received " +
1620
- JSON.stringify(otk === null || otk === void 0 ? void 0 : otk.index) +
1517
+ JSON.stringify(otk?.index) +
1621
1518
  ", expected " +
1622
1519
  preKeyIndex.toString());
1623
1520
  return;
1624
1521
  }
1625
1522
  // their public keys
1626
- const IK_A = crypto_1.XKeyConvert.convertPublicKey(signKey);
1523
+ const IK_A = XKeyConvert.convertPublicKey(signKey);
1627
1524
  const EK_A = ephKey;
1628
1525
  // my private keys
1629
1526
  const IK_B = this.xKeyRing.identityKeys.secretKey;
@@ -1631,71 +1528,72 @@ class Client extends events_1.EventEmitter {
1631
1528
  const SPK_B = this.xKeyRing.preKeys.keyPair.secretKey;
1632
1529
  const OPK_B = otk ? otk.keyPair.secretKey : null;
1633
1530
  // diffie hellman functions
1634
- const DH1 = crypto_1.xDH(SPK_B, IK_A);
1635
- const DH2 = crypto_1.xDH(IK_B, EK_A);
1636
- const DH3 = crypto_1.xDH(SPK_B, EK_A);
1637
- const DH4 = OPK_B ? crypto_1.xDH(OPK_B, EK_A) : null;
1531
+ const DH1 = xDH(SPK_B, IK_A);
1532
+ const DH2 = xDH(IK_B, EK_A);
1533
+ const DH3 = xDH(SPK_B, EK_A);
1534
+ const DH4 = OPK_B ? xDH(OPK_B, EK_A) : null;
1638
1535
  // initial key material
1639
1536
  const IKM = DH4
1640
- ? crypto_1.xConcat(DH1, DH2, DH3, DH4)
1641
- : crypto_1.xConcat(DH1, DH2, DH3);
1537
+ ? xConcat(DH1, DH2, DH3, DH4)
1538
+ : xConcat(DH1, DH2, DH3);
1642
1539
  // shared secret key
1643
- const SK = crypto_1.xKDF(IKM);
1540
+ const SK = xKDF(IKM);
1644
1541
  this.log.info("Obtained SK for " +
1645
1542
  mail.sender +
1646
1543
  ", " +
1647
- crypto_1.XUtils.encodeHex(SK));
1544
+ XUtils.encodeHex(SK));
1648
1545
  // shared public key
1649
- const PK = tweetnacl_1.default.box.keyPair.fromSecretKey(SK).publicKey;
1546
+ const PK = nacl.box.keyPair.fromSecretKey(SK).publicKey;
1650
1547
  this.log.info(this.toString() +
1651
1548
  "Obtained PK for " +
1652
1549
  mail.sender +
1653
1550
  " " +
1654
- crypto_1.XUtils.encodeHex(PK));
1655
- const hmac = crypto_1.xHMAC(mail, SK);
1656
- this.log.info("Mail hash: " + object_hash_1.default(mail));
1657
- this.log.info("Calculated hmac: " + crypto_1.XUtils.encodeHex(hmac));
1551
+ XUtils.encodeHex(PK));
1552
+ const hmac = xHMAC(mail, SK);
1553
+ this.log.info("Mail hash: " + objectHash(mail));
1554
+ this.log.info("Calculated hmac: " + XUtils.encodeHex(hmac));
1658
1555
  // associated data
1659
- const AD = crypto_1.xConcat(crypto_1.xEncode(crypto_1.xConstants.CURVE, IK_A), crypto_1.xEncode(crypto_1.xConstants.CURVE, IK_BP));
1660
- if (!crypto_1.XUtils.bytesEqual(hmac, header)) {
1556
+ const AD = xConcat(xEncode(xConstants.CURVE, IK_A), xEncode(xConstants.CURVE, IK_BP));
1557
+ if (!XUtils.bytesEqual(hmac, header)) {
1661
1558
  console.warn("Mail authentication failed (HMAC did not match).");
1662
1559
  console.warn(mail);
1663
1560
  return;
1664
1561
  }
1665
1562
  this.log.info("Mail authenticated successfully.");
1666
- const unsealed = tweetnacl_1.default.secretbox.open(mail.cipher, mail.nonce, SK);
1563
+ const unsealed = nacl.secretbox.open(mail.cipher, mail.nonce, SK);
1667
1564
  if (unsealed) {
1668
1565
  this.log.info("Decryption successful.");
1669
1566
  let plaintext = "";
1670
1567
  if (!mail.forward) {
1671
- plaintext = crypto_1.XUtils.encodeUTF8(unsealed);
1568
+ plaintext = XUtils.encodeUTF8(unsealed);
1672
1569
  }
1673
1570
  // emit the message
1674
1571
  const message = mail.forward
1675
- ? Object.assign(Object.assign({}, msgpack_lite_1.default.decode(unsealed)), { forward: true }) : {
1676
- nonce: crypto_1.XUtils.encodeHex(mail.nonce),
1677
- mailID: mail.mailID,
1678
- sender: mail.sender,
1679
- recipient: mail.recipient,
1680
- message: plaintext,
1681
- direction: "incoming",
1682
- timestamp: new Date(timestamp),
1683
- decrypted: true,
1684
- group: mail.group
1685
- ? uuid.stringify(mail.group)
1686
- : null,
1687
- forward: mail.forward,
1688
- authorID: mail.authorID,
1689
- readerID: mail.readerID,
1690
- };
1572
+ ? { ...msgpack.decode(unsealed), forward: true }
1573
+ : {
1574
+ nonce: XUtils.encodeHex(mail.nonce),
1575
+ mailID: mail.mailID,
1576
+ sender: mail.sender,
1577
+ recipient: mail.recipient,
1578
+ message: plaintext,
1579
+ direction: "incoming",
1580
+ timestamp: new Date(timestamp),
1581
+ decrypted: true,
1582
+ group: mail.group
1583
+ ? uuid.stringify(mail.group)
1584
+ : null,
1585
+ forward: mail.forward,
1586
+ authorID: mail.authorID,
1587
+ readerID: mail.readerID,
1588
+ };
1691
1589
  this.emit("message", message);
1692
1590
  // discard onetimekey
1693
- yield this.database.deleteOneTimeKey(preKeyIndex);
1694
- const deviceEntry = yield this.getDeviceByID(mail.sender);
1591
+ await this.database.deleteOneTimeKey(preKeyIndex);
1592
+ const deviceEntry = await this.getDeviceByID(mail.sender);
1695
1593
  if (!deviceEntry) {
1696
1594
  throw new Error("Couldn't get device entry.");
1697
1595
  }
1698
- const [userEntry, userErr] = yield this.retrieveUserDBEntry(deviceEntry.owner);
1596
+ const [userEntry, userErr] = await this.retrieveUserDBEntry(deviceEntry.owner);
1699
1597
  if (!userEntry) {
1700
1598
  throw new Error("Couldn't get user entry.");
1701
1599
  }
@@ -1707,14 +1605,14 @@ class Client extends events_1.EventEmitter {
1707
1605
  sessionID: uuid.v4(),
1708
1606
  userID: userEntry.userID,
1709
1607
  mode: "receiver",
1710
- SK: crypto_1.XUtils.encodeHex(SK),
1711
- publicKey: crypto_1.XUtils.encodeHex(PK),
1608
+ SK: XUtils.encodeHex(SK),
1609
+ publicKey: XUtils.encodeHex(PK),
1712
1610
  lastUsed: new Date(Date.now()),
1713
- fingerprint: crypto_1.XUtils.encodeHex(AD),
1611
+ fingerprint: XUtils.encodeHex(AD),
1714
1612
  deviceID: mail.sender,
1715
1613
  };
1716
- yield this.database.saveSession(newSession);
1717
- let [user, err] = yield this.retrieveUserDBEntry(newSession.userID);
1614
+ await this.database.saveSession(newSession);
1615
+ let [user, err] = await this.retrieveUserDBEntry(newSession.userID);
1718
1616
  if (user) {
1719
1617
  this.emit("session", newSession, user);
1720
1618
  }
@@ -1722,7 +1620,7 @@ class Client extends events_1.EventEmitter {
1722
1620
  let failed = 1;
1723
1621
  // retry a couple times
1724
1622
  while (!user) {
1725
- [user, err] = yield this.retrieveUserDBEntry(newSession.userID);
1623
+ [user, err] = await this.retrieveUserDBEntry(newSession.userID);
1726
1624
  failed++;
1727
1625
  if (failed > 3) {
1728
1626
  this.log.warn("Couldn't retrieve user entry.");
@@ -1739,74 +1637,72 @@ class Client extends events_1.EventEmitter {
1739
1637
  this.log.warn("Unsupported MailType:", mail.mailType);
1740
1638
  break;
1741
1639
  }
1640
+ }
1641
+ finally {
1742
1642
  this.reading = false;
1743
- });
1643
+ }
1744
1644
  }
1745
1645
  newEphemeralKeys() {
1746
- this.xKeyRing.ephemeralKeys = tweetnacl_1.default.box.keyPair();
1646
+ this.xKeyRing.ephemeralKeys = nacl.box.keyPair();
1747
1647
  }
1748
1648
  createPreKey() {
1749
- const preKeyPair = tweetnacl_1.default.box.keyPair();
1649
+ const preKeyPair = nacl.box.keyPair();
1750
1650
  const preKeys = {
1751
1651
  keyPair: preKeyPair,
1752
- signature: tweetnacl_1.default.sign(crypto_1.xEncode(crypto_1.xConstants.CURVE, preKeyPair.publicKey), this.signKeys.secretKey),
1652
+ signature: nacl.sign(xEncode(xConstants.CURVE, preKeyPair.publicKey), this.signKeys.secretKey),
1753
1653
  };
1754
1654
  return preKeys;
1755
1655
  }
1756
- handleNotify(msg) {
1757
- return __awaiter(this, void 0, void 0, function* () {
1758
- switch (msg.event) {
1759
- case "mail":
1760
- this.log.info("Server has informed us of new mail.");
1761
- yield this.getMail();
1762
- this.fetchingMail = false;
1763
- break;
1764
- case "permission":
1765
- this.emit("permission", msg.data);
1766
- break;
1767
- case "retryRequest":
1768
- const messageID = msg.data;
1769
- break;
1770
- default:
1771
- this.log.info("Unsupported notification event " + msg.event);
1772
- break;
1773
- }
1774
- });
1656
+ async handleNotify(msg) {
1657
+ switch (msg.event) {
1658
+ case "mail":
1659
+ this.log.info("Server has informed us of new mail.");
1660
+ await this.getMail();
1661
+ this.fetchingMail = false;
1662
+ break;
1663
+ case "permission":
1664
+ this.emit("permission", msg.data);
1665
+ break;
1666
+ case "retryRequest":
1667
+ const messageID = msg.data;
1668
+ break;
1669
+ default:
1670
+ this.log.info("Unsupported notification event " + msg.event);
1671
+ break;
1672
+ }
1775
1673
  }
1776
- populateKeyRing() {
1777
- return __awaiter(this, void 0, void 0, function* () {
1778
- // we've checked in the constructor that these exist
1779
- const identityKeys = this.idKeys;
1780
- let preKeys = yield this.database.getPreKeys();
1781
- if (!preKeys) {
1782
- this.log.warn("No prekeys found in database, creating a new one.");
1783
- preKeys = this.createPreKey();
1784
- yield this.database.savePreKeys([preKeys], false);
1785
- }
1786
- const sessions = yield this.database.getAllSessions();
1787
- for (const session of sessions) {
1788
- this.sessionRecords[session.publicKey] = sqlSessionToCrypto_1.sqlSessionToCrypto(session);
1789
- }
1790
- const ephemeralKeys = tweetnacl_1.default.box.keyPair();
1791
- this.xKeyRing = {
1792
- identityKeys,
1793
- preKeys,
1794
- ephemeralKeys,
1795
- };
1796
- this.log.info("Keyring populated:\n" +
1797
- JSON.stringify({
1798
- signKey: crypto_1.XUtils.encodeHex(this.signKeys.publicKey),
1799
- preKey: crypto_1.XUtils.encodeHex(preKeys.keyPair.publicKey),
1800
- ephemeralKey: crypto_1.XUtils.encodeHex(ephemeralKeys.publicKey),
1801
- }, null, 4));
1802
- });
1674
+ async populateKeyRing() {
1675
+ // we've checked in the constructor that these exist
1676
+ const identityKeys = this.idKeys;
1677
+ let preKeys = await this.database.getPreKeys();
1678
+ if (!preKeys) {
1679
+ this.log.warn("No prekeys found in database, creating a new one.");
1680
+ preKeys = this.createPreKey();
1681
+ await this.database.savePreKeys([preKeys], false);
1682
+ }
1683
+ const sessions = await this.database.getAllSessions();
1684
+ for (const session of sessions) {
1685
+ this.sessionRecords[session.publicKey] = sqlSessionToCrypto(session);
1686
+ }
1687
+ const ephemeralKeys = nacl.box.keyPair();
1688
+ this.xKeyRing = {
1689
+ identityKeys,
1690
+ preKeys,
1691
+ ephemeralKeys,
1692
+ };
1693
+ this.log.info("Keyring populated:\n" +
1694
+ JSON.stringify({
1695
+ signKey: XUtils.encodeHex(this.signKeys.publicKey),
1696
+ preKey: XUtils.encodeHex(preKeys.keyPair.publicKey),
1697
+ ephemeralKey: XUtils.encodeHex(ephemeralKeys.publicKey),
1698
+ }, null, 4));
1803
1699
  }
1804
1700
  initSocket() {
1805
1701
  try {
1806
1702
  if (!this.token) {
1807
1703
  throw new Error("No token found, did you call login()?");
1808
1704
  }
1809
- this.conn = new ws_1.default(this.prefixes.WS + this.host + "/socket", { headers: { Cookie: "auth=" + this.token } });
1705
+ this.conn = new WebSocket(this.prefixes.WS + this.host + "/socket", { headers: { Cookie: "auth=" + this.token } });
1810
1706
  this.conn.on("open", () => {
1811
1707
  this.log.info("Connection opened.");
1812
1708
  this.pingInterval = setInterval(this.ping.bind(this), 15000);
@@ -1820,10 +1716,10 @@ class Client extends events_1.EventEmitter {
1820
1716
  this.conn.on("error", (error) => {
1821
1717
  throw error;
1822
1718
  });
1823
- this.conn.on("message", (message) => __awaiter(this, void 0, void 0, function* () {
1824
- const [header, msg] = crypto_1.XUtils.unpackMessage(message);
1825
- this.log.debug(chalk_1.default.red.bold("INH ") + crypto_1.XUtils.encodeHex(header));
1826
- this.log.debug(chalk_1.default.red.bold("IN ") + JSON.stringify(msg, null, 4));
1719
+ this.conn.on("message", async (message) => {
1720
+ const [header, msg] = XUtils.unpackMessage(message);
1721
+ this.log.debug(chalk.red.bold("INH ") + XUtils.encodeHex(header));
1722
+ this.log.debug(chalk.red.bold("IN ") + JSON.stringify(msg, null, 4));
1827
1723
  switch (msg.type) {
1828
1724
  case "ping":
1829
1725
  this.pong(msg.transmissionID);
@@ -1854,7 +1750,7 @@ class Client extends events_1.EventEmitter {
1854
1750
  this.log.info("Unsupported message " + msg.type);
1855
1751
  break;
1856
1752
  }
1857
- }));
1753
+ });
1858
1754
  }
1859
1755
  catch (err) {
1860
1756
  throw new Error("Error initiating websocket connection " + err.toString());
@@ -1863,161 +1759,135 @@ class Client extends events_1.EventEmitter {
1863
1759
  setAlive(status) {
1864
1760
  this.isAlive = status;
1865
1761
  }
1866
- postAuth() {
1867
- return __awaiter(this, void 0, void 0, function* () {
1868
- let count = 0;
1869
- while (true) {
1870
- try {
1871
- yield this.getMail();
1872
- count++;
1873
- this.fetchingMail = false;
1874
- if (count > 10) {
1875
- this.negotiateOTK();
1876
- count = 0;
1877
- }
1878
- }
1879
- catch (err) {
1880
- this.log.warn("Problem fetching mail" + err.toString());
1881
- }
1882
- yield sleep_1.sleep(1000 * 60);
1883
- }
1884
- });
1885
- }
1886
- getMail() {
1887
- return __awaiter(this, void 0, void 0, function* () {
1888
- while (this.fetchingMail) {
1889
- yield sleep_1.sleep(500);
1890
- }
1891
- this.fetchingMail = true;
1892
- let firstFetch = false;
1893
- if (this.firstMailFetch) {
1894
- firstFetch = true;
1895
- this.firstMailFetch = false;
1896
- }
1897
- if (firstFetch) {
1898
- this.emit("decryptingMail");
1899
- }
1900
- this.log.info("fetching mail for device " + this.getDevice().deviceID);
1762
+ async postAuth() {
1763
+ let count = 0;
1764
+ while (true) {
1901
1765
  try {
1902
- const res = yield axios_1.default.post(this.getHost() +
1903
- "/device/" +
1904
- this.getDevice().deviceID +
1905
- "/mail");
1906
- const inbox = msgpack_lite_1.default
1907
- .decode(Buffer.from(res.data))
1908
- .sort((a, b) => b[2].getTime() - a[2].getTime());
1909
- for (const mailDetails of inbox) {
1910
- const [mailHeader, mailBody, timestamp] = mailDetails;
1911
- try {
1912
- yield this.readMail(mailHeader, mailBody, timestamp.toString());
1913
- }
1914
- catch (err) {
1915
- console.warn(err.toString());
1916
- }
1766
+ await this.getMail();
1767
+ count++;
1768
+ this.fetchingMail = false;
1769
+ if (count > 10) {
1770
+ this.negotiateOTK();
1771
+ count = 0;
1917
1772
  }
1918
1773
  }
1919
1774
  catch (err) {
1920
- console.warn(err.toString());
1775
+ this.log.warn("Problem fetching mail" + err.toString());
1921
1776
  }
1922
- this.fetchingMail = false;
1923
- });
1924
- }
1925
- /* header is 32 bytes and is either empty
1926
- or contains an HMAC of the message with
1927
- a derived SK */
1928
- send(msg, header) {
1929
- return __awaiter(this, void 0, void 0, function* () {
1930
- let i = 0;
1931
- while (this.conn.readyState !== 1) {
1932
- yield sleep_1.sleep(i);
1933
- i *= 2;
1934
- }
1935
- this.log.debug(chalk_1.default.red.bold("OUTH ") +
1936
- crypto_1.XUtils.encodeHex(header || crypto_1.XUtils.emptyHeader()));
1937
- this.log.debug(chalk_1.default.red.bold("OUT ") + JSON.stringify(msg, null, 4));
1938
- this.conn.send(crypto_1.XUtils.packMessage(msg, header));
1939
- });
1940
- }
1941
- retrieveKeyBundle(deviceID) {
1942
- return __awaiter(this, void 0, void 0, function* () {
1943
- const res = yield axios_1.default.post(this.getHost() + "/device/" + deviceID + "/keyBundle");
1944
- return msgpack_lite_1.default.decode(Buffer.from(res.data));
1945
- });
1777
+ await sleep(1000 * 60);
1778
+ }
1946
1779
  }
1947
- getOTKCount() {
1948
- return __awaiter(this, void 0, void 0, function* () {
1949
- const res = yield axios_1.default.get(this.getHost() +
1780
+ async getMail() {
1781
+ while (this.fetchingMail) {
1782
+ await sleep(500);
1783
+ }
1784
+ this.fetchingMail = true;
1785
+ let firstFetch = false;
1786
+ if (this.firstMailFetch) {
1787
+ firstFetch = true;
1788
+ this.firstMailFetch = false;
1789
+ }
1790
+ if (firstFetch) {
1791
+ this.emit("decryptingMail");
1792
+ }
1793
+ this.log.info("fetching mail for device " + this.getDevice().deviceID);
1794
+ try {
1795
+ const res = await ax.post(this.getHost() +
1950
1796
  "/device/" +
1951
1797
  this.getDevice().deviceID +
1952
- "/otk/count");
1953
- return msgpack_lite_1.default.decode(Buffer.from(res.data)).count;
1954
- });
1955
- }
1956
- submitOTK(amount) {
1957
- return __awaiter(this, void 0, void 0, function* () {
1958
- const otks = [];
1959
- const t0 = perf_hooks_1.performance.now();
1960
- for (let i = 0; i < amount; i++) {
1961
- otks[i] = this.createPreKey();
1798
+ "/mail");
1799
+ const inbox = msgpack.decode(Buffer.from(res.data)).sort((a, b) => b[2].getTime() - a[2].getTime());
1800
+ for (const mailDetails of inbox) {
1801
+ const [mailHeader, mailBody, timestamp] = mailDetails;
1802
+ try {
1803
+ await this.readMail(mailHeader, mailBody, timestamp.toString());
1804
+ }
1805
+ catch (err) {
1806
+ console.warn(err.toString());
1807
+ }
1962
1808
  }
1963
- const t1 = perf_hooks_1.performance.now();
1964
- this.log.info("Generated " + amount + " one time keys in " + (t1 - t0) + " ms.");
1965
- const savedKeys = yield this.database.savePreKeys(otks, true);
1966
- yield axios_1.default.post(this.getHost() + "/device/" + this.getDevice().deviceID + "/otk", msgpack_lite_1.default.encode(savedKeys.map((key) => this.censorPreKey(key))), {
1967
- headers: { "Content-Type": "application/msgpack" },
1968
- });
1969
- });
1809
+ }
1810
+ catch (err) {
1811
+ console.warn(err.toString());
1812
+ }
1813
+ this.fetchingMail = false;
1970
1814
  }
1971
- negotiateOTK() {
1972
- return __awaiter(this, void 0, void 0, function* () {
1973
- const otkCount = yield this.getOTKCount();
1974
- this.log.info("Server reported OTK: " + otkCount.toString());
1975
- const needs = crypto_1.xConstants.MIN_OTK_SUPPLY - otkCount;
1976
- if (needs === 0) {
1977
- this.log.info("Server otk supply full.");
1978
- return;
1979
- }
1980
- yield this.submitOTK(needs);
1815
+ /* header is 32 bytes and is either empty
1816
+ or contains an HMAC of the message with
1817
+ a derived SK */
1818
+ async send(msg, header) {
1819
+ let i = 0;
1820
+ while (this.conn.readyState !== 1) {
1821
+ await sleep(i);
1822
+ i *= 2;
1823
+ }
1824
+ this.log.debug(chalk.red.bold("OUTH ") +
1825
+ XUtils.encodeHex(header || XUtils.emptyHeader()));
1826
+ this.log.debug(chalk.red.bold("OUT ") + JSON.stringify(msg, null, 4));
1827
+ this.conn.send(XUtils.packMessage(msg, header));
1828
+ }
1829
+ async retrieveKeyBundle(deviceID) {
1830
+ const res = await ax.post(this.getHost() + "/device/" + deviceID + "/keyBundle");
1831
+ return msgpack.decode(Buffer.from(res.data));
1832
+ }
1833
+ async getOTKCount() {
1834
+ const res = await ax.get(this.getHost() +
1835
+ "/device/" +
1836
+ this.getDevice().deviceID +
1837
+ "/otk/count");
1838
+ return msgpack.decode(Buffer.from(res.data)).count;
1839
+ }
1840
+ async submitOTK(amount) {
1841
+ const otks = [];
1842
+ const t0 = performance.now();
1843
+ for (let i = 0; i < amount; i++) {
1844
+ otks[i] = this.createPreKey();
1845
+ }
1846
+ const t1 = performance.now();
1847
+ this.log.info("Generated " + amount + " one time keys in " + (t1 - t0) + " ms.");
1848
+ const savedKeys = await this.database.savePreKeys(otks, true);
1849
+ await ax.post(this.getHost() + "/device/" + this.getDevice().deviceID + "/otk", msgpack.encode(savedKeys.map((key) => this.censorPreKey(key))), {
1850
+ headers: { "Content-Type": "application/msgpack" },
1981
1851
  });
1982
1852
  }
1853
+ async negotiateOTK() {
1854
+ const otkCount = await this.getOTKCount();
1855
+ this.log.info("Server reported OTK: " + otkCount.toString());
1856
+ const needs = xConstants.MIN_OTK_SUPPLY - otkCount;
1857
+ if (needs === 0) {
1858
+ this.log.info("Server otk supply full.");
1859
+ return;
1860
+ }
1861
+ await this.submitOTK(needs);
1862
+ }
1983
1863
  respond(msg) {
1984
1864
  const response = {
1985
1865
  transmissionID: msg.transmissionID,
1986
1866
  type: "response",
1987
- signed: tweetnacl_1.default.sign(msg.challenge, this.signKeys.secretKey),
1867
+ signed: nacl.sign(msg.challenge, this.signKeys.secretKey),
1988
1868
  };
1989
1869
  this.send(response);
1990
1870
  }
1991
1871
  pong(transmissionID) {
1992
1872
  this.send({ transmissionID, type: "pong" });
1993
1873
  }
1994
- ping() {
1995
- return __awaiter(this, void 0, void 0, function* () {
1996
- if (!this.isAlive) {
1997
- this.log.warn("Ping failed.");
1998
- }
1999
- this.setAlive(false);
2000
- this.send({ transmissionID: uuid.v4(), type: "ping" });
2001
- });
1874
+ async ping() {
1875
+ if (!this.isAlive) {
1876
+ this.log.warn("Ping failed.");
1877
+ }
1878
+ this.setAlive(false);
1879
+ this.send({ transmissionID: uuid.v4(), type: "ping" });
2002
1880
  }
2003
1881
  censorPreKey(preKey) {
2004
1882
  if (!preKey.index) {
2005
1883
  throw new Error("Key index is required.");
2006
1884
  }
2007
1885
  return {
2008
- publicKey: crypto_1.XUtils.decodeHex(preKey.publicKey),
2009
- signature: crypto_1.XUtils.decodeHex(preKey.signature),
1886
+ publicKey: XUtils.decodeHex(preKey.publicKey),
1887
+ signature: XUtils.decodeHex(preKey.signature),
2010
1888
  index: preKey.index,
2011
1889
  deviceID: this.getDevice().deviceID,
2012
1890
  };
2013
1891
  }
2014
1892
  }
2015
- exports.Client = Client;
2016
- Client.loadKeyFile = crypto_1.XUtils.loadKeyFile;
2017
- Client.saveKeyFile = crypto_1.XUtils.saveKeyFile;
2018
- Client.create = (privateKey, options, storage) => __awaiter(void 0, void 0, void 0, function* () {
2019
- const client = new Client(privateKey, options, storage);
2020
- yield client.init();
2021
- return client;
2022
- });
2023
1893
  //# sourceMappingURL=Client.js.map