@vex-chat/libvex 1.0.1 → 1.0.2
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.d.ts +12 -8
- package/dist/Client.js +131 -74
- package/dist/Client.js.map +1 -1
- package/dist/IStorage.d.ts +1 -1
- package/dist/IStorage.js +1 -1
- package/dist/IStorage.js.map +1 -1
- package/dist/__tests__/harness/memory-storage.d.ts +45 -0
- package/dist/__tests__/harness/memory-storage.js +181 -0
- package/dist/__tests__/harness/memory-storage.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/keystore/memory.d.ts +11 -0
- package/dist/keystore/memory.js +18 -0
- package/dist/keystore/memory.js.map +1 -0
- package/dist/keystore/node.d.ts +10 -0
- package/dist/keystore/node.js +64 -0
- package/dist/keystore/node.js.map +1 -0
- package/dist/keystore/types.d.ts +4 -0
- package/dist/keystore/types.js +2 -0
- package/dist/keystore/types.js.map +1 -0
- package/dist/preset/expo.d.ts +2 -0
- package/dist/preset/expo.js +37 -0
- package/dist/preset/expo.js.map +1 -0
- package/dist/preset/node.d.ts +12 -0
- package/dist/preset/node.js +16 -0
- package/dist/preset/node.js.map +1 -0
- package/dist/preset/tauri.d.ts +2 -0
- package/dist/preset/tauri.js +35 -0
- package/dist/preset/tauri.js.map +1 -0
- package/dist/preset/test.d.ts +10 -0
- package/dist/preset/test.js +28 -0
- package/dist/preset/test.js.map +1 -0
- package/dist/preset/types.d.ts +13 -0
- package/dist/preset/types.js +2 -0
- package/dist/preset/types.js.map +1 -0
- package/dist/storage/expo.d.ts +3 -0
- package/dist/storage/expo.js +18 -0
- package/dist/storage/expo.js.map +1 -0
- package/dist/storage/node.d.ts +3 -0
- package/dist/storage/node.js +24 -0
- package/dist/storage/node.js.map +1 -0
- package/dist/storage/schema.d.ts +79 -0
- package/dist/storage/schema.js +2 -0
- package/dist/storage/schema.js.map +1 -0
- package/dist/{Storage.d.ts → storage/sqlite.d.ts} +19 -21
- package/dist/storage/sqlite.js +487 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/storage/tauri.d.ts +3 -0
- package/dist/storage/tauri.js +21 -0
- package/dist/storage/tauri.js.map +1 -0
- package/dist/transport/browser.d.ts +17 -0
- package/dist/transport/browser.js +56 -0
- package/dist/transport/browser.js.map +1 -0
- package/dist/transport/types.d.ts +20 -0
- package/dist/transport/types.js +2 -0
- package/dist/transport/types.js.map +1 -0
- package/package.json +102 -14
- package/dist/Storage.js +0 -444
- package/dist/Storage.js.map +0 -1
package/dist/Client.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IChannel, IDevice, IEmoji, IFileResponse, IFileSQL, IInvite, IPermission, IServer, ISessionSQL } from "@vex-chat/types";
|
|
2
2
|
import { AxiosError } from "axios";
|
|
3
|
-
import { EventEmitter } from "
|
|
3
|
+
import { EventEmitter } from "eventemitter3";
|
|
4
|
+
import type { IClientAdapters } from "./transport/types.js";
|
|
4
5
|
import type { IStorage } from "./IStorage.js";
|
|
5
6
|
/**
|
|
6
7
|
* IMessage is a chat message.
|
|
@@ -176,7 +177,7 @@ export interface IMe {
|
|
|
176
177
|
/** Returns metadata for the currently authenticated device. */
|
|
177
178
|
device: () => IDevice;
|
|
178
179
|
/** Uploads and sets a new avatar image for the current user. */
|
|
179
|
-
setAvatar: (avatar:
|
|
180
|
+
setAvatar: (avatar: Uint8Array) => Promise<void>;
|
|
180
181
|
}
|
|
181
182
|
/**
|
|
182
183
|
* @ignore
|
|
@@ -292,7 +293,7 @@ export interface IDevices {
|
|
|
292
293
|
*/
|
|
293
294
|
export interface IFiles {
|
|
294
295
|
/** Uploads and encrypts a file. */
|
|
295
|
-
create: (file:
|
|
296
|
+
create: (file: Uint8Array) => Promise<[IFileSQL, string]>;
|
|
296
297
|
/** Downloads and decrypts a file using a file ID and key. */
|
|
297
298
|
retrieve: (fileID: string, key: string) => Promise<IFileResponse | null>;
|
|
298
299
|
}
|
|
@@ -301,7 +302,7 @@ export interface IFiles {
|
|
|
301
302
|
*/
|
|
302
303
|
export interface IEmojis {
|
|
303
304
|
/** Uploads a custom emoji to a server. */
|
|
304
|
-
create: (emoji:
|
|
305
|
+
create: (emoji: Uint8Array, name: string, serverID: string) => Promise<IEmoji | null>;
|
|
305
306
|
/** Lists emojis available on a server. */
|
|
306
307
|
retrieveList: (serverID: string) => Promise<IEmoji[]>;
|
|
307
308
|
/** Fetches one emoji's metadata by ID. */
|
|
@@ -340,6 +341,8 @@ export interface IClientOptions {
|
|
|
340
341
|
unsafeHttp?: boolean;
|
|
341
342
|
/** Whether local message history should be persisted by default storage. */
|
|
342
343
|
saveHistory?: boolean;
|
|
344
|
+
/** Platform-specific adapters (WebSocket + Logger). When omitted, defaults to Node.js ws. */
|
|
345
|
+
adapters?: IClientAdapters;
|
|
343
346
|
}
|
|
344
347
|
export declare interface Client {
|
|
345
348
|
/**
|
|
@@ -518,17 +521,17 @@ export declare interface Client {
|
|
|
518
521
|
*/
|
|
519
522
|
export declare class Client extends EventEmitter {
|
|
520
523
|
/**
|
|
521
|
-
*
|
|
524
|
+
* Encrypts a secret key with a password.
|
|
522
525
|
*
|
|
523
526
|
* Pass-through utility from `@vex-chat/crypto`.
|
|
524
527
|
*/
|
|
525
|
-
static
|
|
528
|
+
static encryptKeyData: (password: string, keyToSave: string, iterationOverride?: number) => Uint8Array;
|
|
526
529
|
/**
|
|
527
|
-
*
|
|
530
|
+
* Decrypts a secret key from encrypted data produced by encryptKeyData().
|
|
528
531
|
*
|
|
529
532
|
* Pass-through utility from `@vex-chat/crypto`.
|
|
530
533
|
*/
|
|
531
|
-
static
|
|
534
|
+
static decryptKeyData: (keyData: Uint8Array, password: string) => string;
|
|
532
535
|
/**
|
|
533
536
|
* Creates and initializes a client in one step.
|
|
534
537
|
*
|
|
@@ -641,6 +644,7 @@ export declare class Client extends EventEmitter {
|
|
|
641
644
|
private dbPath;
|
|
642
645
|
private conn;
|
|
643
646
|
private host;
|
|
647
|
+
private adapters;
|
|
644
648
|
private firstMailFetch;
|
|
645
649
|
private signKeys;
|
|
646
650
|
private idKeys;
|
package/dist/Client.js
CHANGED
|
@@ -4,25 +4,30 @@ import { xConcat, xConstants, xDH, xEncode, xHMAC, xKDF, XKeyConvert, xMakeNonce
|
|
|
4
4
|
import { MailType } from "@vex-chat/types";
|
|
5
5
|
import ax, { AxiosError } from "axios";
|
|
6
6
|
import { isBrowser, isNode } from "browser-or-node";
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import { EventEmitter } from "
|
|
7
|
+
// btoa is native in browsers and Node 16+
|
|
8
|
+
import pc from "picocolors";
|
|
9
|
+
import { EventEmitter } from "eventemitter3";
|
|
10
10
|
import { Packr } from "msgpackr";
|
|
11
11
|
// useRecords:false emits standard msgpack (no nonstandard record extension).
|
|
12
12
|
// moreTypes:false keeps the extension set to what every other decoder understands.
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
13
|
+
const _packr = new Packr({ useRecords: false, moreTypes: false });
|
|
14
|
+
// Packr.encode() returns a subarray of its internal pool buffer.
|
|
15
|
+
// In browsers, XMLHttpRequest.send() sends the full underlying ArrayBuffer,
|
|
16
|
+
// not just the slice — corrupting the payload (axios issue #4068).
|
|
17
|
+
// Wrap encode to always return a fresh copy.
|
|
18
|
+
const msgpack = {
|
|
19
|
+
encode: (value) => {
|
|
20
|
+
const packed = _packr.encode(value);
|
|
21
|
+
return new Uint8Array(packed.buffer.slice(packed.byteOffset, packed.byteOffset + packed.byteLength));
|
|
22
|
+
},
|
|
23
|
+
decode: _packr.decode.bind(_packr),
|
|
24
|
+
};
|
|
16
25
|
import objectHash from "object-hash";
|
|
17
|
-
import * as os from "node:os";
|
|
18
|
-
import { performance } from "node:perf_hooks";
|
|
19
26
|
import nacl from "tweetnacl";
|
|
20
27
|
import * as uuid from "uuid";
|
|
21
|
-
|
|
22
|
-
import WebSocket from "ws";
|
|
23
|
-
import { Storage } from "./Storage.js";
|
|
28
|
+
// Storage (Kysely/better-sqlite3) loaded lazily — only when no external storage is provided.
|
|
24
29
|
import { capitalize } from "./utils/capitalize.js";
|
|
25
|
-
|
|
30
|
+
// createLogger (winston) loaded lazily — only in Node when no adapter logger is provided.
|
|
26
31
|
import { formatBytes } from "./utils/formatBytes.js";
|
|
27
32
|
import { sqlSessionToCrypto } from "./utils/sqlSessionToCrypto.js";
|
|
28
33
|
import { uuidToUint8 } from "./utils/uint8uuid.js";
|
|
@@ -78,17 +83,17 @@ const protocolMsgRegex = /��\w+:\w+��/g;
|
|
|
78
83
|
*/
|
|
79
84
|
export class Client extends EventEmitter {
|
|
80
85
|
/**
|
|
81
|
-
*
|
|
86
|
+
* Encrypts a secret key with a password.
|
|
82
87
|
*
|
|
83
88
|
* Pass-through utility from `@vex-chat/crypto`.
|
|
84
89
|
*/
|
|
85
|
-
static
|
|
90
|
+
static encryptKeyData = XUtils.encryptKeyData;
|
|
86
91
|
/**
|
|
87
|
-
*
|
|
92
|
+
* Decrypts a secret key from encrypted data produced by encryptKeyData().
|
|
88
93
|
*
|
|
89
94
|
* Pass-through utility from `@vex-chat/crypto`.
|
|
90
95
|
*/
|
|
91
|
-
static
|
|
96
|
+
static decryptKeyData = XUtils.decryptKeyData;
|
|
92
97
|
/**
|
|
93
98
|
* Creates and initializes a client in one step.
|
|
94
99
|
*
|
|
@@ -102,7 +107,36 @@ export class Client extends EventEmitter {
|
|
|
102
107
|
* ```
|
|
103
108
|
*/
|
|
104
109
|
static create = async (privateKey, options, storage) => {
|
|
105
|
-
|
|
110
|
+
let opts = options;
|
|
111
|
+
if (!opts?.adapters) {
|
|
112
|
+
const { default: WebSocket } = await import("ws");
|
|
113
|
+
const { createLogger: makeLog } = await import("./utils/createLogger.js");
|
|
114
|
+
opts = {
|
|
115
|
+
...opts,
|
|
116
|
+
adapters: {
|
|
117
|
+
logger: makeLog("libvex", opts?.logLevel),
|
|
118
|
+
WebSocket: WebSocket,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Lazily create Node Storage only on the Node path (no adapters).
|
|
123
|
+
// When adapters are provided (browser/RN), the caller must supply storage
|
|
124
|
+
// via PlatformPreset.createStorage() — there is no Node fallback.
|
|
125
|
+
let resolvedStorage = storage;
|
|
126
|
+
if (!resolvedStorage) {
|
|
127
|
+
if (opts?.adapters) {
|
|
128
|
+
throw new Error("No storage provided. When using adapters (browser/RN), pass storage from your PlatformPreset.");
|
|
129
|
+
}
|
|
130
|
+
const { createNodeStorage } = await import("./storage/node.js");
|
|
131
|
+
const dbFileName = opts?.inMemoryDb
|
|
132
|
+
? ":memory:"
|
|
133
|
+
: XUtils.encodeHex(nacl.sign.keyPair.fromSecretKey(XUtils.decodeHex(privateKey || "")).publicKey) + ".sqlite";
|
|
134
|
+
const dbPath = opts?.dbFolder
|
|
135
|
+
? opts.dbFolder + "/" + dbFileName
|
|
136
|
+
: dbFileName;
|
|
137
|
+
resolvedStorage = createNodeStorage(dbPath, privateKey || XUtils.encodeHex(nacl.sign.keyPair().secretKey));
|
|
138
|
+
}
|
|
139
|
+
const client = new Client(privateKey, opts, resolvedStorage);
|
|
106
140
|
await client.init();
|
|
107
141
|
return client;
|
|
108
142
|
};
|
|
@@ -397,6 +431,7 @@ export class Client extends EventEmitter {
|
|
|
397
431
|
dbPath;
|
|
398
432
|
conn;
|
|
399
433
|
host;
|
|
434
|
+
adapters;
|
|
400
435
|
firstMailFetch = true;
|
|
401
436
|
// these are created from one set of sign keys
|
|
402
437
|
signKeys;
|
|
@@ -420,7 +455,12 @@ export class Client extends EventEmitter {
|
|
|
420
455
|
prefixes;
|
|
421
456
|
constructor(privateKey, options, storage) {
|
|
422
457
|
super();
|
|
423
|
-
this.log =
|
|
458
|
+
this.log = options?.adapters?.logger ?? {
|
|
459
|
+
info() { },
|
|
460
|
+
warn() { },
|
|
461
|
+
error() { },
|
|
462
|
+
debug() { },
|
|
463
|
+
};
|
|
424
464
|
this.prefixes = options?.unsafeHttp
|
|
425
465
|
? { HTTP: "http://", WS: "ws://" }
|
|
426
466
|
: { HTTP: "https://", WS: "wss://" };
|
|
@@ -438,17 +478,20 @@ export class Client extends EventEmitter {
|
|
|
438
478
|
this.dbPath = options?.dbFolder
|
|
439
479
|
? options?.dbFolder + "/" + dbFileName
|
|
440
480
|
: dbFileName;
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
481
|
+
if (!storage) {
|
|
482
|
+
throw new Error("No storage provided. Use Client.create() which resolves storage automatically.");
|
|
483
|
+
}
|
|
484
|
+
this.database = storage;
|
|
444
485
|
this.database.on("error", (error) => {
|
|
445
486
|
this.log.error(error.toString());
|
|
446
487
|
this.close(true);
|
|
447
488
|
});
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
489
|
+
if (!options?.adapters) {
|
|
490
|
+
throw new Error("No adapters provided. Use Client.create() which resolves adapters automatically.");
|
|
491
|
+
}
|
|
492
|
+
this.adapters = options.adapters;
|
|
493
|
+
// Placeholder connection — replaced by initSocket() during connect()
|
|
494
|
+
this.conn = new this.adapters.WebSocket("ws://localhost:1234");
|
|
452
495
|
this.conn.onerror = () => { };
|
|
453
496
|
this.log.info("Client debug information: " +
|
|
454
497
|
JSON.stringify({
|
|
@@ -523,7 +566,7 @@ export class Client extends EventEmitter {
|
|
|
523
566
|
}), {
|
|
524
567
|
headers: { "Content-Type": "application/msgpack" },
|
|
525
568
|
});
|
|
526
|
-
const { user, token } = msgpack.decode(
|
|
569
|
+
const { user, token } = msgpack.decode(new Uint8Array(res.data));
|
|
527
570
|
const cookies = res.headers["set-cookie"];
|
|
528
571
|
if (cookies) {
|
|
529
572
|
for (const cookie of cookies) {
|
|
@@ -561,7 +604,7 @@ export class Client extends EventEmitter {
|
|
|
561
604
|
withCredentials: true,
|
|
562
605
|
responseType: "arraybuffer",
|
|
563
606
|
});
|
|
564
|
-
const whoami = msgpack.decode(
|
|
607
|
+
const whoami = msgpack.decode(new Uint8Array(res.data));
|
|
565
608
|
return whoami;
|
|
566
609
|
}
|
|
567
610
|
/**
|
|
@@ -624,11 +667,13 @@ export class Client extends EventEmitter {
|
|
|
624
667
|
preKeySignature: XUtils.encodeHex(this.xKeyRing.preKeys.signature),
|
|
625
668
|
preKeyIndex: this.xKeyRing.preKeys.index,
|
|
626
669
|
password,
|
|
627
|
-
deviceName:
|
|
670
|
+
deviceName: typeof navigator !== "undefined"
|
|
671
|
+
? navigator.userAgent.slice(0, 64)
|
|
672
|
+
: "node",
|
|
628
673
|
};
|
|
629
674
|
try {
|
|
630
675
|
const res = await ax.post(this.getHost() + "/register", msgpack.encode(regMsg), { headers: { "Content-Type": "application/msgpack" } });
|
|
631
|
-
this.setUser(msgpack.decode(
|
|
676
|
+
this.setUser(msgpack.decode(new Uint8Array(res.data)));
|
|
632
677
|
return [this.getUser(), null];
|
|
633
678
|
}
|
|
634
679
|
catch (err) {
|
|
@@ -652,11 +697,11 @@ export class Client extends EventEmitter {
|
|
|
652
697
|
}
|
|
653
698
|
async redeemInvite(inviteID) {
|
|
654
699
|
const res = await ax.patch(this.getHost() + "/invite/" + inviteID);
|
|
655
|
-
return msgpack.decode(
|
|
700
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
656
701
|
}
|
|
657
702
|
async retrieveInvites(serverID) {
|
|
658
703
|
const res = await ax.get(this.getHost() + "/server/" + serverID + "/invites");
|
|
659
|
-
return msgpack.decode(
|
|
704
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
660
705
|
}
|
|
661
706
|
async createInvite(serverID, duration) {
|
|
662
707
|
const payload = {
|
|
@@ -664,11 +709,11 @@ export class Client extends EventEmitter {
|
|
|
664
709
|
duration,
|
|
665
710
|
};
|
|
666
711
|
const res = await ax.post(this.getHost() + "/server/" + serverID + "/invites", payload);
|
|
667
|
-
return msgpack.decode(
|
|
712
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
668
713
|
}
|
|
669
714
|
async retrieveEmojiList(serverID) {
|
|
670
715
|
const res = await ax.get(this.getHost() + "/server/" + serverID + "/emoji");
|
|
671
|
-
return msgpack.decode(
|
|
716
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
672
717
|
}
|
|
673
718
|
async retrieveEmojiByID(emojiID) {
|
|
674
719
|
const res = await ax.get(this.getHost() + "/emoji/" + emojiID + "/details");
|
|
@@ -676,7 +721,7 @@ export class Client extends EventEmitter {
|
|
|
676
721
|
if (!res.data) {
|
|
677
722
|
return null;
|
|
678
723
|
}
|
|
679
|
-
return msgpack.decode(
|
|
724
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
680
725
|
}
|
|
681
726
|
async leaveServer(serverID) {
|
|
682
727
|
const permissionList = await this.permissions.retrieve();
|
|
@@ -730,7 +775,7 @@ export class Client extends EventEmitter {
|
|
|
730
775
|
this.emit("fileProgress", progress);
|
|
731
776
|
},
|
|
732
777
|
});
|
|
733
|
-
return msgpack.decode(
|
|
778
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
734
779
|
}
|
|
735
780
|
catch (err) {
|
|
736
781
|
return null;
|
|
@@ -742,7 +787,7 @@ export class Client extends EventEmitter {
|
|
|
742
787
|
};
|
|
743
788
|
try {
|
|
744
789
|
const res = await ax.post(this.getHost() + "/emoji/" + serverID + "/json", msgpack.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
|
|
745
|
-
return msgpack.decode(
|
|
790
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
746
791
|
}
|
|
747
792
|
catch (err) {
|
|
748
793
|
return null;
|
|
@@ -755,7 +800,7 @@ export class Client extends EventEmitter {
|
|
|
755
800
|
this.host +
|
|
756
801
|
"/device/" +
|
|
757
802
|
XUtils.encodeHex(this.signKeys.publicKey));
|
|
758
|
-
device = msgpack.decode(
|
|
803
|
+
device = msgpack.decode(new Uint8Array(res.data));
|
|
759
804
|
}
|
|
760
805
|
catch (err) {
|
|
761
806
|
this.log.error(err.toString());
|
|
@@ -803,7 +848,9 @@ export class Client extends EventEmitter {
|
|
|
803
848
|
preKey: XUtils.encodeHex(this.xKeyRing.preKeys.keyPair.publicKey),
|
|
804
849
|
preKeySignature: XUtils.encodeHex(this.xKeyRing.preKeys.signature),
|
|
805
850
|
preKeyIndex: this.xKeyRing.preKeys.index,
|
|
806
|
-
deviceName:
|
|
851
|
+
deviceName: typeof navigator !== "undefined"
|
|
852
|
+
? navigator.userAgent.slice(0, 64)
|
|
853
|
+
: "node",
|
|
807
854
|
};
|
|
808
855
|
try {
|
|
809
856
|
const res = await ax.post(this.prefixes.HTTP +
|
|
@@ -811,7 +858,7 @@ export class Client extends EventEmitter {
|
|
|
811
858
|
"/user/" +
|
|
812
859
|
userDetails.userID +
|
|
813
860
|
"/devices", msgpack.encode(devMsg), { headers: { "Content-Type": "application/msgpack" } });
|
|
814
|
-
return msgpack.decode(
|
|
861
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
815
862
|
}
|
|
816
863
|
catch (err) {
|
|
817
864
|
throw err;
|
|
@@ -822,7 +869,7 @@ export class Client extends EventEmitter {
|
|
|
822
869
|
const res = await ax.get(this.getHost() + "/token/" + type, {
|
|
823
870
|
responseType: "arraybuffer",
|
|
824
871
|
});
|
|
825
|
-
return msgpack.decode(
|
|
872
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
826
873
|
}
|
|
827
874
|
catch (err) {
|
|
828
875
|
this.log.warn(err.toString());
|
|
@@ -874,7 +921,7 @@ export class Client extends EventEmitter {
|
|
|
874
921
|
"/server/" +
|
|
875
922
|
serverID +
|
|
876
923
|
"/permissions");
|
|
877
|
-
return msgpack.decode(
|
|
924
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
878
925
|
}
|
|
879
926
|
/**
|
|
880
927
|
* Gets all permissions for the logged in user.
|
|
@@ -883,7 +930,7 @@ export class Client extends EventEmitter {
|
|
|
883
930
|
*/
|
|
884
931
|
async getPermissions() {
|
|
885
932
|
const res = await ax.get(this.getHost() + "/user/" + this.getUser().userID + "/permissions");
|
|
886
|
-
return msgpack.decode(
|
|
933
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
887
934
|
}
|
|
888
935
|
async deletePermission(permissionID) {
|
|
889
936
|
await ax.delete(this.getHost() + "/permission/" + permissionID);
|
|
@@ -891,7 +938,7 @@ export class Client extends EventEmitter {
|
|
|
891
938
|
async retrieveFile(fileID, key) {
|
|
892
939
|
try {
|
|
893
940
|
const detailsRes = await ax.get(this.getHost() + "/file/" + fileID + "/details");
|
|
894
|
-
const details = msgpack.decode(
|
|
941
|
+
const details = msgpack.decode(new Uint8Array(detailsRes.data));
|
|
895
942
|
const res = await ax.get(this.getHost() + "/file/" + fileID, {
|
|
896
943
|
onDownloadProgress: (progressEvent) => {
|
|
897
944
|
const percentCompleted = Math.round((progressEvent.loaded * 100) /
|
|
@@ -908,11 +955,11 @@ export class Client extends EventEmitter {
|
|
|
908
955
|
},
|
|
909
956
|
});
|
|
910
957
|
const fileData = res.data;
|
|
911
|
-
const decrypted = nacl.secretbox.open(Uint8Array
|
|
958
|
+
const decrypted = nacl.secretbox.open(new Uint8Array(fileData), XUtils.decodeHex(details.nonce), XUtils.decodeHex(key));
|
|
912
959
|
if (decrypted) {
|
|
913
960
|
const resp = {
|
|
914
961
|
details,
|
|
915
|
-
data:
|
|
962
|
+
data: new Uint8Array(decrypted),
|
|
916
963
|
};
|
|
917
964
|
return resp;
|
|
918
965
|
}
|
|
@@ -930,7 +977,7 @@ export class Client extends EventEmitter {
|
|
|
930
977
|
*/
|
|
931
978
|
async init() {
|
|
932
979
|
if (this.hasInit) {
|
|
933
|
-
|
|
980
|
+
throw new Error("You should only call init() once.");
|
|
934
981
|
}
|
|
935
982
|
this.hasInit = true;
|
|
936
983
|
await this.populateKeyRing();
|
|
@@ -951,11 +998,11 @@ export class Client extends EventEmitter {
|
|
|
951
998
|
}
|
|
952
999
|
// returns the file details and the encryption key
|
|
953
1000
|
async createFile(file) {
|
|
954
|
-
this.log.info("Creating file, size: " + formatBytes(
|
|
1001
|
+
this.log.info("Creating file, size: " + formatBytes(file.byteLength));
|
|
955
1002
|
const nonce = xMakeNonce();
|
|
956
1003
|
const key = nacl.box.keyPair();
|
|
957
1004
|
const box = nacl.secretbox(Uint8Array.from(file), nonce, key.secretKey);
|
|
958
|
-
this.log.info("Encrypted size: " + formatBytes(
|
|
1005
|
+
this.log.info("Encrypted size: " + formatBytes(box.byteLength));
|
|
959
1006
|
if (typeof FormData !== "undefined") {
|
|
960
1007
|
const fpayload = new FormData();
|
|
961
1008
|
fpayload.set("owner", this.getDevice().deviceID);
|
|
@@ -977,7 +1024,7 @@ export class Client extends EventEmitter {
|
|
|
977
1024
|
this.emit("fileProgress", progress);
|
|
978
1025
|
},
|
|
979
1026
|
});
|
|
980
|
-
const fcreatedFile = msgpack.decode(
|
|
1027
|
+
const fcreatedFile = msgpack.decode(new Uint8Array(fres.data));
|
|
981
1028
|
return [fcreatedFile, XUtils.encodeHex(key.secretKey)];
|
|
982
1029
|
}
|
|
983
1030
|
const payload = {
|
|
@@ -986,12 +1033,12 @@ export class Client extends EventEmitter {
|
|
|
986
1033
|
file: XUtils.encodeBase64(box),
|
|
987
1034
|
};
|
|
988
1035
|
const res = await ax.post(this.getHost() + "/file/json", msgpack.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
|
|
989
|
-
const createdFile = msgpack.decode(
|
|
1036
|
+
const createdFile = msgpack.decode(new Uint8Array(res.data));
|
|
990
1037
|
return [createdFile, XUtils.encodeHex(key.secretKey)];
|
|
991
1038
|
}
|
|
992
1039
|
async getUserList(channelID) {
|
|
993
1040
|
const res = await ax.post(this.getHost() + "/userList/" + channelID);
|
|
994
|
-
return msgpack.decode(
|
|
1041
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
995
1042
|
}
|
|
996
1043
|
async markSessionVerified(sessionID) {
|
|
997
1044
|
return this.database.markSessionVerified(sessionID);
|
|
@@ -1040,7 +1087,7 @@ export class Client extends EventEmitter {
|
|
|
1040
1087
|
const { status } = result;
|
|
1041
1088
|
if (status === "rejected") {
|
|
1042
1089
|
this.log.warn("Message failed.");
|
|
1043
|
-
this.log.warn(result);
|
|
1090
|
+
this.log.warn(JSON.stringify(result));
|
|
1044
1091
|
}
|
|
1045
1092
|
}
|
|
1046
1093
|
});
|
|
@@ -1073,14 +1120,14 @@ export class Client extends EventEmitter {
|
|
|
1073
1120
|
const { status } = result;
|
|
1074
1121
|
if (status === "rejected") {
|
|
1075
1122
|
this.log.warn("Message failed.");
|
|
1076
|
-
this.log.warn(result);
|
|
1123
|
+
this.log.warn(JSON.stringify(result));
|
|
1077
1124
|
}
|
|
1078
1125
|
}
|
|
1079
1126
|
});
|
|
1080
1127
|
}
|
|
1081
1128
|
async createServer(name) {
|
|
1082
|
-
const res = await ax.post(this.getHost() + "/server/" + btoa(name));
|
|
1083
|
-
return msgpack.decode(
|
|
1129
|
+
const res = await ax.post(this.getHost() + "/server/" + globalThis.btoa(name));
|
|
1130
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
1084
1131
|
}
|
|
1085
1132
|
async forward(message) {
|
|
1086
1133
|
const copy = { ...message };
|
|
@@ -1109,7 +1156,7 @@ export class Client extends EventEmitter {
|
|
|
1109
1156
|
const { status } = result;
|
|
1110
1157
|
if (status === "rejected") {
|
|
1111
1158
|
this.log.warn("Message failed.");
|
|
1112
|
-
this.log.warn(result);
|
|
1159
|
+
this.log.warn(JSON.stringify(result));
|
|
1113
1160
|
}
|
|
1114
1161
|
}
|
|
1115
1162
|
});
|
|
@@ -1204,12 +1251,12 @@ export class Client extends EventEmitter {
|
|
|
1204
1251
|
}
|
|
1205
1252
|
async getServerList() {
|
|
1206
1253
|
const res = await ax.get(this.getHost() + "/user/" + this.getUser().userID + "/servers");
|
|
1207
|
-
return msgpack.decode(
|
|
1254
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
1208
1255
|
}
|
|
1209
1256
|
async createChannel(name, serverID) {
|
|
1210
1257
|
const body = { name };
|
|
1211
1258
|
const res = await ax.post(this.getHost() + "/server/" + serverID + "/channels", msgpack.encode(body), { headers: { "Content-Type": "application/msgpack" } });
|
|
1212
|
-
return msgpack.decode(
|
|
1259
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
1213
1260
|
}
|
|
1214
1261
|
async getDeviceByID(deviceID) {
|
|
1215
1262
|
if (this.deviceRecords[deviceID]) {
|
|
@@ -1225,7 +1272,7 @@ export class Client extends EventEmitter {
|
|
|
1225
1272
|
try {
|
|
1226
1273
|
const res = await ax.get(this.getHost() + "/device/" + deviceID);
|
|
1227
1274
|
this.log.info("Retrieved device from server.");
|
|
1228
|
-
const fetchedDevice = msgpack.decode(
|
|
1275
|
+
const fetchedDevice = msgpack.decode(new Uint8Array(res.data));
|
|
1229
1276
|
this.deviceRecords[deviceID] = fetchedDevice;
|
|
1230
1277
|
await this.database.saveDevice(fetchedDevice);
|
|
1231
1278
|
return fetchedDevice;
|
|
@@ -1248,7 +1295,7 @@ export class Client extends EventEmitter {
|
|
|
1248
1295
|
async getMultiUserDeviceList(userIDs) {
|
|
1249
1296
|
try {
|
|
1250
1297
|
const res = await ax.post(this.getHost() + "/deviceList", msgpack.encode(userIDs), { headers: { "Content-Type": "application/msgpack" } });
|
|
1251
|
-
const devices = msgpack.decode(
|
|
1298
|
+
const devices = msgpack.decode(new Uint8Array(res.data));
|
|
1252
1299
|
for (const device of devices) {
|
|
1253
1300
|
this.deviceRecords[device.deviceID] = device;
|
|
1254
1301
|
}
|
|
@@ -1261,7 +1308,7 @@ export class Client extends EventEmitter {
|
|
|
1261
1308
|
async getUserDeviceList(userID) {
|
|
1262
1309
|
try {
|
|
1263
1310
|
const res = await ax.get(this.getHost() + "/user/" + userID + "/devices");
|
|
1264
|
-
const devices = msgpack.decode(
|
|
1311
|
+
const devices = msgpack.decode(new Uint8Array(res.data));
|
|
1265
1312
|
for (const device of devices) {
|
|
1266
1313
|
this.deviceRecords[device.deviceID] = device;
|
|
1267
1314
|
}
|
|
@@ -1274,7 +1321,7 @@ export class Client extends EventEmitter {
|
|
|
1274
1321
|
async getServerByID(serverID) {
|
|
1275
1322
|
try {
|
|
1276
1323
|
const res = await ax.get(this.getHost() + "/server/" + serverID);
|
|
1277
|
-
return msgpack.decode(
|
|
1324
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
1278
1325
|
}
|
|
1279
1326
|
catch (err) {
|
|
1280
1327
|
return null;
|
|
@@ -1283,7 +1330,7 @@ export class Client extends EventEmitter {
|
|
|
1283
1330
|
async getChannelByID(channelID) {
|
|
1284
1331
|
try {
|
|
1285
1332
|
const res = await ax.get(this.getHost() + "/channel/" + channelID);
|
|
1286
|
-
return msgpack.decode(
|
|
1333
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
1287
1334
|
}
|
|
1288
1335
|
catch (err) {
|
|
1289
1336
|
return null;
|
|
@@ -1291,7 +1338,7 @@ export class Client extends EventEmitter {
|
|
|
1291
1338
|
}
|
|
1292
1339
|
async getChannelList(serverID) {
|
|
1293
1340
|
const res = await ax.get(this.getHost() + "/server/" + serverID + "/channels");
|
|
1294
|
-
return msgpack.decode(
|
|
1341
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
1295
1342
|
}
|
|
1296
1343
|
/* Get the currently logged in user. You cannot call this until
|
|
1297
1344
|
after the auth event is emitted. */
|
|
@@ -1319,7 +1366,7 @@ export class Client extends EventEmitter {
|
|
|
1319
1366
|
}
|
|
1320
1367
|
try {
|
|
1321
1368
|
const res = await ax.get(this.getHost() + "/user/" + userIdentifier);
|
|
1322
|
-
const userRecord = msgpack.decode(
|
|
1369
|
+
const userRecord = msgpack.decode(new Uint8Array(res.data));
|
|
1323
1370
|
this.userRecords[userIdentifier] = userRecord;
|
|
1324
1371
|
return [userRecord, null];
|
|
1325
1372
|
}
|
|
@@ -1804,13 +1851,23 @@ export class Client extends EventEmitter {
|
|
|
1804
1851
|
if (!this.token) {
|
|
1805
1852
|
throw new Error("No token found, did you call login()?");
|
|
1806
1853
|
}
|
|
1807
|
-
|
|
1854
|
+
const wsUrl = this.prefixes.WS + this.host + "/socket";
|
|
1855
|
+
// Pass cookie as option — Node ws forwards it as an upgrade header.
|
|
1856
|
+
// Browser/RN WebSocket ignores the options object entirely,
|
|
1857
|
+
// so the cookie never reaches the server on those platforms.
|
|
1858
|
+
this.conn = new this.adapters.WebSocket(wsUrl, {
|
|
1859
|
+
headers: { Cookie: "auth=" + this.token },
|
|
1860
|
+
});
|
|
1808
1861
|
this.conn.on("open", () => {
|
|
1809
1862
|
this.log.info("Connection opened.");
|
|
1810
1863
|
this.pingInterval = setInterval(this.ping.bind(this), 15000);
|
|
1811
1864
|
});
|
|
1812
1865
|
this.conn.on("close", () => {
|
|
1813
1866
|
this.log.info("Connection closed.");
|
|
1867
|
+
if (this.pingInterval) {
|
|
1868
|
+
clearInterval(this.pingInterval);
|
|
1869
|
+
this.pingInterval = null;
|
|
1870
|
+
}
|
|
1814
1871
|
if (!this.manuallyClosing) {
|
|
1815
1872
|
this.emit("disconnect");
|
|
1816
1873
|
}
|
|
@@ -1820,8 +1877,8 @@ export class Client extends EventEmitter {
|
|
|
1820
1877
|
});
|
|
1821
1878
|
this.conn.on("message", async (message) => {
|
|
1822
1879
|
const [header, msg] = XUtils.unpackMessage(message);
|
|
1823
|
-
this.log.debug(
|
|
1824
|
-
this.log.debug(
|
|
1880
|
+
this.log.debug(pc.red(pc.bold("INH ") + XUtils.encodeHex(header)));
|
|
1881
|
+
this.log.debug(pc.red(pc.bold("IN ") + JSON.stringify(msg, null, 4)));
|
|
1825
1882
|
switch (msg.type) {
|
|
1826
1883
|
case "ping":
|
|
1827
1884
|
this.pong(msg.transmissionID);
|
|
@@ -1899,7 +1956,7 @@ export class Client extends EventEmitter {
|
|
|
1899
1956
|
this.getDevice().deviceID +
|
|
1900
1957
|
"/mail");
|
|
1901
1958
|
const inbox = msgpack
|
|
1902
|
-
.decode(
|
|
1959
|
+
.decode(new Uint8Array(res.data))
|
|
1903
1960
|
.sort((a, b) => b[2].getTime() - a[2].getTime());
|
|
1904
1961
|
for (const mailDetails of inbox) {
|
|
1905
1962
|
const [mailHeader, mailBody, timestamp] = mailDetails;
|
|
@@ -1925,21 +1982,21 @@ export class Client extends EventEmitter {
|
|
|
1925
1982
|
await sleep(i);
|
|
1926
1983
|
i *= 2;
|
|
1927
1984
|
}
|
|
1928
|
-
this.log.debug(
|
|
1929
|
-
XUtils.encodeHex(header || XUtils.emptyHeader()));
|
|
1930
|
-
this.log.debug(
|
|
1985
|
+
this.log.debug(pc.red(pc.bold("OUTH ") +
|
|
1986
|
+
XUtils.encodeHex(header || XUtils.emptyHeader())));
|
|
1987
|
+
this.log.debug(pc.red(pc.bold("OUT ") + JSON.stringify(msg, null, 4)));
|
|
1931
1988
|
this.conn.send(XUtils.packMessage(msg, header));
|
|
1932
1989
|
}
|
|
1933
1990
|
async retrieveKeyBundle(deviceID) {
|
|
1934
1991
|
const res = await ax.post(this.getHost() + "/device/" + deviceID + "/keyBundle");
|
|
1935
|
-
return msgpack.decode(
|
|
1992
|
+
return msgpack.decode(new Uint8Array(res.data));
|
|
1936
1993
|
}
|
|
1937
1994
|
async getOTKCount() {
|
|
1938
1995
|
const res = await ax.get(this.getHost() +
|
|
1939
1996
|
"/device/" +
|
|
1940
1997
|
this.getDevice().deviceID +
|
|
1941
1998
|
"/otk/count");
|
|
1942
|
-
return msgpack.decode(
|
|
1999
|
+
return msgpack.decode(new Uint8Array(res.data)).count;
|
|
1943
2000
|
}
|
|
1944
2001
|
async submitOTK(amount) {
|
|
1945
2002
|
const otks = [];
|