@vex-chat/libvex 6.6.1 → 6.6.3
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/README.md +1 -1
- package/dist/Client.d.ts +3 -14
- package/dist/Client.d.ts.map +1 -1
- package/dist/Client.js +88 -105
- package/dist/Client.js.map +1 -1
- package/dist/codec.js +1 -1
- package/dist/codecs.d.ts +3 -6
- package/dist/codecs.d.ts.map +1 -1
- package/dist/codecs.js +6 -9
- package/dist/codecs.js.map +1 -1
- package/dist/http.d.ts +83 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +368 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
- package/src/Client.ts +127 -139
- package/src/__tests__/harness/shared-suite.ts +33 -3
- package/src/__tests__/http.test.ts +98 -0
- package/src/codec.ts +1 -1
- package/src/codecs.ts +6 -9
- package/src/http.ts +563 -0
- package/src/index.ts +6 -0
- package/dist/storage/node/http-agents.d.ts +0 -20
- package/dist/storage/node/http-agents.d.ts.map +0 -1
- package/dist/storage/node/http-agents.js +0 -26
- package/dist/storage/node/http-agents.js.map +0 -1
- package/src/storage/node/http-agents.ts +0 -39
package/dist/Client.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { enterCryptoProfileScope, getCryptoProfile, leaveCryptoProfileScope, setCryptoProfile, xBoxKeyPairAsync, xBoxKeyPairFromSecretAsync, xConcat, xConstants, xDHAsync, xEcdhKeyPairFromEcdsaKeyPairAsync, xEncode, xHMAC, xKDF, XKeyConvert, xMakeNonce, xMnemonic, xRandomBytes, xSecretboxAsync, xSecretboxOpenAsync, xSignAsync, xSignKeyPair, xSignKeyPairAsync, xSignKeyPairFromSecret, xSignKeyPairFromSecretAsync, XUtils, } from "@vex-chat/crypto";
|
|
7
7
|
import { MailType, MailWSSchema, PermissionSchema, WSMessageSchema, } from "@vex-chat/types";
|
|
8
|
-
import axios, { isAxiosError } from "axios";
|
|
9
8
|
import { EventEmitter } from "eventemitter3";
|
|
10
9
|
import * as uuid from "uuid";
|
|
11
10
|
import { z } from "zod/v4";
|
|
11
|
+
import { createFetchHttpClient, isHttpError, } from "./http.js";
|
|
12
12
|
import { clampLocalMessageRetentionDays, formatVexRetentionEnvelope, stripVexRetentionEnvelope, } from "./retention.js";
|
|
13
13
|
import { WebSocketAdapter, WebSocketNotOpenError, } from "./transport/websocket.js";
|
|
14
14
|
import { decodeFipsInitialExtraV1, encodeFipsInitialExtraV1, fipsP256AdFromIdentityPubs, fipsP256PreKeySignPayload, isFipsInitialExtraV1, } from "./utils/fipsMailExtra.js";
|
|
@@ -195,10 +195,22 @@ function spireErrorBodyMessage(data, max = 8_000) {
|
|
|
195
195
|
return t.length > max ? t.slice(0, max) + "…" : t;
|
|
196
196
|
}
|
|
197
197
|
import { msgpack } from "./codec.js";
|
|
198
|
-
import { ActionTokenCodec, AuthResponseCodec, ChannelArrayCodec, ChannelCodec, ConnectResponseCodec,
|
|
198
|
+
import { ActionTokenCodec, AuthResponseCodec, ChannelArrayCodec, ChannelCodec, ConnectResponseCodec, decodeHttpResponse, DeviceArrayCodec, DeviceChallengeCodec, DeviceCodec, DeviceRegistrationResultCodec, EmojiArrayCodec, EmojiCodec, FileSQLCodec, InviteArrayCodec, InviteCodec, KeyBundleCodec, OtkCountCodec, PasskeyArrayCodec, PasskeyAuthFinishResponseCodec, PasskeyCodec, PasskeyOptionsCodec, PendingDeviceRequestArrayCodec, PendingDeviceRequestCodec, PermissionArrayCodec, PermissionCodec, RegisterPendingApprovalCodec, RegisterResponseCodec, ServerArrayCodec, ServerChannelBootstrapCodec, ServerCodec, UserArrayCodec, UserCodec, WhoamiCodec, } from "./codecs.js";
|
|
199
199
|
import { sqlSessionToCrypto } from "./utils/sqlSessionToCrypto.js";
|
|
200
200
|
import { uuidToUint8 } from "./utils/uint8uuid.js";
|
|
201
201
|
const _protocolMsgRegex = /��\w+:\w+��/g;
|
|
202
|
+
const NotificationSubscriptionSchema = z.object({
|
|
203
|
+
channel: z.literal("expo"),
|
|
204
|
+
createdAt: z.string(),
|
|
205
|
+
deviceID: z.string(),
|
|
206
|
+
enabled: z.boolean(),
|
|
207
|
+
events: z.array(z.string()),
|
|
208
|
+
platform: z.string().nullable(),
|
|
209
|
+
subscriptionID: z.string(),
|
|
210
|
+
token: z.string(),
|
|
211
|
+
updatedAt: z.string(),
|
|
212
|
+
userID: z.string(),
|
|
213
|
+
});
|
|
202
214
|
function compareInboxEntries(a, b) {
|
|
203
215
|
const timeCmp = a[2].localeCompare(b[2]);
|
|
204
216
|
if (timeCmp !== 0) {
|
|
@@ -585,7 +597,7 @@ export class Client {
|
|
|
585
597
|
forwarded = new Set();
|
|
586
598
|
host;
|
|
587
599
|
http;
|
|
588
|
-
/** Cancels in-flight
|
|
600
|
+
/** Cancels in-flight HTTP work on `close()` so `postAuth`/`getMail` cannot hang forever. */
|
|
589
601
|
httpAbortController = new AbortController();
|
|
590
602
|
idKeys;
|
|
591
603
|
isAlive = true;
|
|
@@ -593,11 +605,6 @@ export class Client {
|
|
|
593
605
|
localRetentionPurgeTimer = null;
|
|
594
606
|
mailInterval;
|
|
595
607
|
manuallyClosing = false;
|
|
596
|
-
/**
|
|
597
|
-
* Node-only: per-client HTTP(S) agents (see `init()` + `storage/node/http-agents`).
|
|
598
|
-
* Dropped on `close()` so idle keep-alive sockets do not keep the process alive.
|
|
599
|
-
*/
|
|
600
|
-
nodeHttpAgents;
|
|
601
608
|
/* Retrieves the userID with the user identifier.
|
|
602
609
|
user identifier is checked for userID, then signkey,
|
|
603
610
|
and finally falls back to username. */
|
|
@@ -658,7 +665,7 @@ export class Client {
|
|
|
658
665
|
this.database.on("error", (_error) => {
|
|
659
666
|
void this.close(true);
|
|
660
667
|
});
|
|
661
|
-
this.http =
|
|
668
|
+
this.http = createFetchHttpClient({
|
|
662
669
|
responseType: "arraybuffer",
|
|
663
670
|
signal: this.httpAbortController.signal,
|
|
664
671
|
});
|
|
@@ -830,27 +837,6 @@ export class Client {
|
|
|
830
837
|
return undefined;
|
|
831
838
|
}
|
|
832
839
|
}
|
|
833
|
-
/**
|
|
834
|
-
* True when running under Node (has `process.versions`).
|
|
835
|
-
* Uses indirect lookup so the bare `process` global never appears in
|
|
836
|
-
* source that the platform-guard plugin scans.
|
|
837
|
-
*/
|
|
838
|
-
static isNodeRuntime() {
|
|
839
|
-
try {
|
|
840
|
-
const g = Object.getOwnPropertyDescriptor(globalThis, "\u0070rocess");
|
|
841
|
-
if (!g)
|
|
842
|
-
return false;
|
|
843
|
-
const proc = typeof g.get === "function" ? g.get() : g.value;
|
|
844
|
-
if (typeof proc !== "object" || proc === null) {
|
|
845
|
-
return false;
|
|
846
|
-
}
|
|
847
|
-
return ("versions" in proc &&
|
|
848
|
-
typeof proc.versions === "object");
|
|
849
|
-
}
|
|
850
|
-
catch {
|
|
851
|
-
return false;
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
840
|
/**
|
|
855
841
|
* Closes the client — disconnects the WebSocket, shuts down storage,
|
|
856
842
|
* and emits `closed` unless `muteEvent` is `true`.
|
|
@@ -867,11 +853,6 @@ export class Client {
|
|
|
867
853
|
this.httpAbortController.abort();
|
|
868
854
|
this.socket.close();
|
|
869
855
|
await this.database.close();
|
|
870
|
-
if (this.nodeHttpAgents) {
|
|
871
|
-
this.nodeHttpAgents.http.destroy();
|
|
872
|
-
this.nodeHttpAgents.https.destroy();
|
|
873
|
-
delete this.nodeHttpAgents;
|
|
874
|
-
}
|
|
875
856
|
if (this.pingInterval) {
|
|
876
857
|
clearInterval(this.pingInterval);
|
|
877
858
|
}
|
|
@@ -909,7 +890,7 @@ export class Client {
|
|
|
909
890
|
}
|
|
910
891
|
const signedAsync = await xSignAsync(Uint8Array.from(uuid.parse(connectToken.key)), this.signKeys.secretKey);
|
|
911
892
|
const res = await this.http.post(this.getHost() + "/device/" + this.device.deviceID + "/connect", msgpack.encode({ signed: signedAsync }), { headers: { "Content-Type": "application/msgpack" } });
|
|
912
|
-
const { deviceToken } =
|
|
893
|
+
const { deviceToken } = decodeHttpResponse(ConnectResponseCodec, res.data);
|
|
913
894
|
this.http.defaults.headers.common["X-Device-Token"] = deviceToken;
|
|
914
895
|
this.autoReconnectEnabled = true;
|
|
915
896
|
this.initSocket();
|
|
@@ -978,14 +959,14 @@ export class Client {
|
|
|
978
959
|
}), {
|
|
979
960
|
headers: { "Content-Type": "application/msgpack" },
|
|
980
961
|
});
|
|
981
|
-
const { token, user } =
|
|
962
|
+
const { token, user } = decodeHttpResponse(AuthResponseCodec, res.data);
|
|
982
963
|
this.setUser(user);
|
|
983
964
|
this.token = token;
|
|
984
965
|
this.http.defaults.headers.common.Authorization = `Bearer ${token}`;
|
|
985
966
|
return { ok: true };
|
|
986
967
|
}
|
|
987
968
|
catch (err) {
|
|
988
|
-
if (
|
|
969
|
+
if (isHttpError(err) && err.response) {
|
|
989
970
|
return {
|
|
990
971
|
error: spireErrorBodyMessage(err.response.data),
|
|
991
972
|
ok: false,
|
|
@@ -1014,10 +995,10 @@ export class Client {
|
|
|
1014
995
|
deviceID: id,
|
|
1015
996
|
signKey: signKeyHex,
|
|
1016
997
|
}), { headers: { "Content-Type": "application/msgpack" } });
|
|
1017
|
-
const { challenge, challengeID } =
|
|
998
|
+
const { challenge, challengeID } = decodeHttpResponse(DeviceChallengeCodec, challengeRes.data);
|
|
1018
999
|
const signed = XUtils.encodeHex(await xSignAsync(XUtils.decodeHex(challenge), this.signKeys.secretKey));
|
|
1019
1000
|
const verifyRes = await this.http.post(this.getHost() + "/auth/device/verify", msgpack.encode({ challengeID, signed }), { headers: { "Content-Type": "application/msgpack" } });
|
|
1020
|
-
const { token, user } =
|
|
1001
|
+
const { token, user } = decodeHttpResponse(AuthResponseCodec, verifyRes.data);
|
|
1021
1002
|
this.setUser(user);
|
|
1022
1003
|
this.token = token;
|
|
1023
1004
|
this.http.defaults.headers.common.Authorization = `Bearer ${token}`;
|
|
@@ -1128,7 +1109,7 @@ export class Client {
|
|
|
1128
1109
|
let didDecodeRegisterResponse = false;
|
|
1129
1110
|
let pendingApproval = null;
|
|
1130
1111
|
try {
|
|
1131
|
-
const { device, token, user } =
|
|
1112
|
+
const { device, token, user } = decodeHttpResponse(RegisterResponseCodec, res.data);
|
|
1132
1113
|
this.device = device;
|
|
1133
1114
|
this.setUser(user);
|
|
1134
1115
|
this.token = token;
|
|
@@ -1140,7 +1121,7 @@ export class Client {
|
|
|
1140
1121
|
}
|
|
1141
1122
|
if (!didDecodeRegisterResponse) {
|
|
1142
1123
|
try {
|
|
1143
|
-
pendingApproval =
|
|
1124
|
+
pendingApproval = decodeHttpResponse(RegisterPendingApprovalCodec, res.data);
|
|
1144
1125
|
}
|
|
1145
1126
|
catch {
|
|
1146
1127
|
// fall through to legacy decode path
|
|
@@ -1158,7 +1139,7 @@ export class Client {
|
|
|
1158
1139
|
}),
|
|
1159
1140
|
];
|
|
1160
1141
|
}
|
|
1161
|
-
const legacyUser =
|
|
1142
|
+
const legacyUser = decodeHttpResponse(UserCodec, res.data);
|
|
1162
1143
|
this.setUser(legacyUser);
|
|
1163
1144
|
// Legacy servers require /auth after /register to get a JWT.
|
|
1164
1145
|
const loginResult = await this.login(resolvedUsername, resolvedPassword);
|
|
@@ -1173,7 +1154,7 @@ export class Client {
|
|
|
1173
1154
|
return [this.getUser(), null];
|
|
1174
1155
|
}
|
|
1175
1156
|
catch (err) {
|
|
1176
|
-
if (
|
|
1157
|
+
if (isHttpError(err) && err.response) {
|
|
1177
1158
|
return [
|
|
1178
1159
|
null,
|
|
1179
1160
|
new Error(spireErrorBodyMessage(err.response.data)),
|
|
@@ -1213,7 +1194,7 @@ export class Client {
|
|
|
1213
1194
|
"/device/" +
|
|
1214
1195
|
this.getDevice().deviceID +
|
|
1215
1196
|
"/notifications/subscriptions", input, { responseType: "json" });
|
|
1216
|
-
return response.data;
|
|
1197
|
+
return NotificationSubscriptionSchema.parse(response.data);
|
|
1217
1198
|
}
|
|
1218
1199
|
/**
|
|
1219
1200
|
* Triggers an immediate inbox sync by fetching `/mail` once.
|
|
@@ -1254,7 +1235,7 @@ export class Client {
|
|
|
1254
1235
|
*/
|
|
1255
1236
|
async whoami() {
|
|
1256
1237
|
const res = await this.http.post(this.getHost() + "/whoami");
|
|
1257
|
-
const whoami =
|
|
1238
|
+
const whoami = decodeHttpResponse(WhoamiCodec, res.data);
|
|
1258
1239
|
return whoami;
|
|
1259
1240
|
}
|
|
1260
1241
|
async abortPendingDeviceRegistration(args) {
|
|
@@ -1299,16 +1280,16 @@ export class Client {
|
|
|
1299
1280
|
"/devices/requests/" +
|
|
1300
1281
|
requestID +
|
|
1301
1282
|
"/approve", msgpack.encode({ signed }), { headers: { "Content-Type": "application/msgpack" } });
|
|
1302
|
-
return
|
|
1283
|
+
return decodeHttpResponse(DeviceCodec, response.data);
|
|
1303
1284
|
}
|
|
1304
1285
|
async beginPasskeyAuthentication(username) {
|
|
1305
1286
|
const response = await this.http.post(this.getHost() + "/auth/passkey/begin", msgpack.encode({ username }), { headers: { "Content-Type": "application/msgpack" } });
|
|
1306
|
-
return
|
|
1287
|
+
return decodeHttpResponse(PasskeyOptionsCodec, response.data);
|
|
1307
1288
|
}
|
|
1308
1289
|
async beginPasskeyRegistration(name) {
|
|
1309
1290
|
const userID = this.getUser().userID;
|
|
1310
1291
|
const response = await this.http.post(this.getHost() + "/user/" + userID + "/passkeys/register/begin", msgpack.encode({ name }), { headers: { "Content-Type": "application/msgpack" } });
|
|
1311
|
-
return
|
|
1292
|
+
return decodeHttpResponse(PasskeyOptionsCodec, response.data);
|
|
1312
1293
|
}
|
|
1313
1294
|
censorPreKey(preKey) {
|
|
1314
1295
|
if (!preKey.index) {
|
|
@@ -1324,7 +1305,7 @@ export class Client {
|
|
|
1324
1305
|
async createChannel(name, serverID) {
|
|
1325
1306
|
const body = { name };
|
|
1326
1307
|
const res = await this.http.post(this.getHost() + "/server/" + serverID + "/channels", msgpack.encode(body), { headers: { "Content-Type": "application/msgpack" } });
|
|
1327
|
-
return
|
|
1308
|
+
return decodeHttpResponse(ChannelCodec, res.data);
|
|
1328
1309
|
}
|
|
1329
1310
|
// returns the file details and the encryption key
|
|
1330
1311
|
async createFile(file) {
|
|
@@ -1334,7 +1315,19 @@ export class Client {
|
|
|
1334
1315
|
? xRandomBytes(32)
|
|
1335
1316
|
: (await xBoxKeyPairAsync()).secretKey;
|
|
1336
1317
|
const box = await xSecretboxAsync(Uint8Array.from(file), nonce, fileKey);
|
|
1337
|
-
|
|
1318
|
+
const canUseMultipart = typeof FormData !== "undefined" &&
|
|
1319
|
+
(() => {
|
|
1320
|
+
try {
|
|
1321
|
+
// React Native/Hermes can expose Blob/FormData but
|
|
1322
|
+
// reject ArrayBufferView-backed blobs at runtime.
|
|
1323
|
+
void new Blob([new Uint8Array([1, 2, 3])]);
|
|
1324
|
+
return true;
|
|
1325
|
+
}
|
|
1326
|
+
catch {
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
})();
|
|
1330
|
+
if (canUseMultipart) {
|
|
1338
1331
|
const fpayload = new FormData();
|
|
1339
1332
|
fpayload.set("owner", this.getDevice().deviceID);
|
|
1340
1333
|
fpayload.set("nonce", XUtils.encodeHex(nonce));
|
|
@@ -1355,7 +1348,7 @@ export class Client {
|
|
|
1355
1348
|
this.emitter.emit("fileProgress", progress);
|
|
1356
1349
|
},
|
|
1357
1350
|
});
|
|
1358
|
-
const fcreatedFile =
|
|
1351
|
+
const fcreatedFile = decodeHttpResponse(FileSQLCodec, fres.data);
|
|
1359
1352
|
return [fcreatedFile, XUtils.encodeHex(fileKey)];
|
|
1360
1353
|
}
|
|
1361
1354
|
const payload = {
|
|
@@ -1364,7 +1357,7 @@ export class Client {
|
|
|
1364
1357
|
owner: this.getDevice().deviceID,
|
|
1365
1358
|
};
|
|
1366
1359
|
const res = await this.http.post(this.getHost() + "/file/json", msgpack.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
|
|
1367
|
-
const createdFile =
|
|
1360
|
+
const createdFile = decodeHttpResponse(FileSQLCodec, res.data);
|
|
1368
1361
|
return [createdFile, XUtils.encodeHex(fileKey)];
|
|
1369
1362
|
});
|
|
1370
1363
|
}
|
|
@@ -1374,7 +1367,7 @@ export class Client {
|
|
|
1374
1367
|
serverID,
|
|
1375
1368
|
};
|
|
1376
1369
|
const res = await this.http.post(this.getHost() + "/server/" + serverID + "/invites", msgpack.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
|
|
1377
|
-
return
|
|
1370
|
+
return decodeHttpResponse(InviteCodec, res.data);
|
|
1378
1371
|
}
|
|
1379
1372
|
async createPreKey() {
|
|
1380
1373
|
const preKeyPair = await xBoxKeyPairAsync();
|
|
@@ -1388,7 +1381,7 @@ export class Client {
|
|
|
1388
1381
|
}
|
|
1389
1382
|
async createServer(name) {
|
|
1390
1383
|
const res = await this.http.post(this.getHost() + "/server/" + globalThis.btoa(name));
|
|
1391
|
-
return
|
|
1384
|
+
return decodeHttpResponse(ServerCodec, res.data);
|
|
1392
1385
|
}
|
|
1393
1386
|
async createSession(device, user, message, group,
|
|
1394
1387
|
/* this is passed through if the first message is
|
|
@@ -1584,7 +1577,7 @@ export class Client {
|
|
|
1584
1577
|
await this.http.delete(this.getHost() + "/server/" + serverID);
|
|
1585
1578
|
}
|
|
1586
1579
|
deviceListFailureDetail(err) {
|
|
1587
|
-
if (!
|
|
1580
|
+
if (!isHttpError(err)) {
|
|
1588
1581
|
return "";
|
|
1589
1582
|
}
|
|
1590
1583
|
const st = err.response?.status;
|
|
@@ -1607,7 +1600,7 @@ export class Client {
|
|
|
1607
1600
|
"/server/" +
|
|
1608
1601
|
serverID +
|
|
1609
1602
|
"/permissions");
|
|
1610
|
-
return
|
|
1603
|
+
return decodeHttpResponse(PermissionArrayCodec, res.data);
|
|
1611
1604
|
}
|
|
1612
1605
|
async fetchUser(userIdentifier) {
|
|
1613
1606
|
// Usernames are case-insensitive at the protocol level, so
|
|
@@ -1631,19 +1624,19 @@ export class Client {
|
|
|
1631
1624
|
}
|
|
1632
1625
|
try {
|
|
1633
1626
|
const res = await this.http.get(this.getHost() + "/user/" + cacheKey);
|
|
1634
|
-
const userRecord =
|
|
1627
|
+
const userRecord = decodeHttpResponse(UserCodec, res.data);
|
|
1635
1628
|
this.userRecords[cacheKey] = userRecord;
|
|
1636
1629
|
this.notFoundUsers.delete(cacheKey);
|
|
1637
1630
|
return [userRecord, null];
|
|
1638
1631
|
}
|
|
1639
1632
|
catch (err) {
|
|
1640
|
-
if (
|
|
1633
|
+
if (isHttpError(err) && err.response?.status === 404) {
|
|
1641
1634
|
// Definitive: user doesn't exist — cache and don't retry
|
|
1642
1635
|
this.notFoundUsers.set(cacheKey, Date.now());
|
|
1643
1636
|
return [null, err];
|
|
1644
1637
|
}
|
|
1645
1638
|
// Transient (5xx, network error) — don't cache, caller can retry
|
|
1646
|
-
return [null,
|
|
1639
|
+
return [null, isHttpError(err) ? err : null];
|
|
1647
1640
|
}
|
|
1648
1641
|
}
|
|
1649
1642
|
async fetchUserDeviceListOnce(userID) {
|
|
@@ -1651,7 +1644,7 @@ export class Client {
|
|
|
1651
1644
|
return [];
|
|
1652
1645
|
}
|
|
1653
1646
|
const res = await this.http.get(this.getHost() + "/user/" + userID + "/devices");
|
|
1654
|
-
const devices =
|
|
1647
|
+
const devices = decodeHttpResponse(DeviceArrayCodec, res.data);
|
|
1655
1648
|
for (const device of devices) {
|
|
1656
1649
|
this.deviceRecords[device.deviceID] = device;
|
|
1657
1650
|
}
|
|
@@ -1698,7 +1691,7 @@ export class Client {
|
|
|
1698
1691
|
*/
|
|
1699
1692
|
async finishPasskeyAuthentication(args) {
|
|
1700
1693
|
const response = await this.http.post(this.getHost() + "/auth/passkey/finish", msgpack.encode(args), { headers: { "Content-Type": "application/msgpack" } });
|
|
1701
|
-
const decoded =
|
|
1694
|
+
const decoded = decodeHttpResponse(PasskeyAuthFinishResponseCodec, response.data);
|
|
1702
1695
|
this.setUser(decoded.user);
|
|
1703
1696
|
this.token = decoded.token;
|
|
1704
1697
|
this.http.defaults.headers.common.Authorization = `Bearer ${decoded.token}`;
|
|
@@ -1707,7 +1700,7 @@ export class Client {
|
|
|
1707
1700
|
async finishPasskeyRegistration(args) {
|
|
1708
1701
|
const userID = this.getUser().userID;
|
|
1709
1702
|
const response = await this.http.post(this.getHost() + "/user/" + userID + "/passkeys/register/finish", msgpack.encode(args), { headers: { "Content-Type": "application/msgpack" } });
|
|
1710
|
-
return
|
|
1703
|
+
return decodeHttpResponse(PasskeyCodec, response.data);
|
|
1711
1704
|
}
|
|
1712
1705
|
async forward(message) {
|
|
1713
1706
|
if (this.isManualCloseInFlight()) {
|
|
@@ -1741,7 +1734,7 @@ export class Client {
|
|
|
1741
1734
|
async getChannelByID(channelID) {
|
|
1742
1735
|
try {
|
|
1743
1736
|
const res = await this.http.get(this.getHost() + "/channel/" + channelID);
|
|
1744
|
-
return
|
|
1737
|
+
return decodeHttpResponse(ChannelCodec, res.data);
|
|
1745
1738
|
}
|
|
1746
1739
|
catch (_err) {
|
|
1747
1740
|
return null;
|
|
@@ -1749,7 +1742,7 @@ export class Client {
|
|
|
1749
1742
|
}
|
|
1750
1743
|
async getChannelList(serverID) {
|
|
1751
1744
|
const res = await this.http.get(this.getHost() + "/server/" + serverID + "/channels");
|
|
1752
|
-
return
|
|
1745
|
+
return decodeHttpResponse(ChannelArrayCodec, res.data);
|
|
1753
1746
|
}
|
|
1754
1747
|
getDevice() {
|
|
1755
1748
|
if (!this.device) {
|
|
@@ -1768,7 +1761,7 @@ export class Client {
|
|
|
1768
1761
|
}
|
|
1769
1762
|
try {
|
|
1770
1763
|
const res = await this.http.get(this.getHost() + "/device/" + deviceID);
|
|
1771
|
-
const fetchedDevice =
|
|
1764
|
+
const fetchedDevice = decodeHttpResponse(DeviceCodec, res.data);
|
|
1772
1765
|
this.deviceRecords[deviceID] = fetchedDevice;
|
|
1773
1766
|
await this.database.saveDevice(fetchedDevice);
|
|
1774
1767
|
return fetchedDevice;
|
|
@@ -1785,10 +1778,10 @@ export class Client {
|
|
|
1785
1778
|
this.getUser().userID +
|
|
1786
1779
|
"/devices/requests/" +
|
|
1787
1780
|
requestID);
|
|
1788
|
-
return
|
|
1781
|
+
return decodeHttpResponse(PendingDeviceRequestCodec, response.data);
|
|
1789
1782
|
}
|
|
1790
1783
|
catch (err) {
|
|
1791
|
-
if (
|
|
1784
|
+
if (isHttpError(err) && err.response?.status === 404) {
|
|
1792
1785
|
return null;
|
|
1793
1786
|
}
|
|
1794
1787
|
throw err;
|
|
@@ -1885,7 +1878,7 @@ export class Client {
|
|
|
1885
1878
|
async getMultiUserDeviceList(userIDs) {
|
|
1886
1879
|
try {
|
|
1887
1880
|
const res = await this.http.post(this.getHost() + "/deviceList", msgpack.encode(userIDs), { headers: { "Content-Type": "application/msgpack" } });
|
|
1888
|
-
const devices =
|
|
1881
|
+
const devices = decodeHttpResponse(DeviceArrayCodec, res.data);
|
|
1889
1882
|
for (const device of devices) {
|
|
1890
1883
|
this.deviceRecords[device.deviceID] = device;
|
|
1891
1884
|
}
|
|
@@ -1900,7 +1893,7 @@ export class Client {
|
|
|
1900
1893
|
"/device/" +
|
|
1901
1894
|
this.getDevice().deviceID +
|
|
1902
1895
|
"/otk/count");
|
|
1903
|
-
return
|
|
1896
|
+
return decodeHttpResponse(OtkCountCodec, res.data).count;
|
|
1904
1897
|
}
|
|
1905
1898
|
/**
|
|
1906
1899
|
* Gets all permissions for the logged in user.
|
|
@@ -1909,12 +1902,12 @@ export class Client {
|
|
|
1909
1902
|
*/
|
|
1910
1903
|
async getPermissions() {
|
|
1911
1904
|
const res = await this.http.get(this.getHost() + "/user/" + this.getUser().userID + "/permissions");
|
|
1912
|
-
return
|
|
1905
|
+
return decodeHttpResponse(PermissionArrayCodec, res.data);
|
|
1913
1906
|
}
|
|
1914
1907
|
async getServerByID(serverID) {
|
|
1915
1908
|
try {
|
|
1916
1909
|
const res = await this.http.get(this.getHost() + "/server/" + serverID);
|
|
1917
|
-
return
|
|
1910
|
+
return decodeHttpResponse(ServerCodec, res.data);
|
|
1918
1911
|
}
|
|
1919
1912
|
catch (_err) {
|
|
1920
1913
|
return null;
|
|
@@ -1925,11 +1918,11 @@ export class Client {
|
|
|
1925
1918
|
"/user/" +
|
|
1926
1919
|
this.getUser().userID +
|
|
1927
1920
|
"/servers/bootstrap");
|
|
1928
|
-
return
|
|
1921
|
+
return decodeHttpResponse(ServerChannelBootstrapCodec, res.data);
|
|
1929
1922
|
}
|
|
1930
1923
|
async getServerList() {
|
|
1931
1924
|
const res = await this.http.get(this.getHost() + "/user/" + this.getUser().userID + "/servers");
|
|
1932
|
-
return
|
|
1925
|
+
return decodeHttpResponse(ServerArrayCodec, res.data);
|
|
1933
1926
|
}
|
|
1934
1927
|
async getSessionByPubkey(publicKey) {
|
|
1935
1928
|
const strPubKey = XUtils.encodeHex(publicKey);
|
|
@@ -1950,7 +1943,7 @@ export class Client {
|
|
|
1950
1943
|
const res = await this.http.get(this.getHost() + "/token/" + type, {
|
|
1951
1944
|
responseType: "arraybuffer",
|
|
1952
1945
|
});
|
|
1953
|
-
return
|
|
1946
|
+
return decodeHttpResponse(ActionTokenCodec, res.data);
|
|
1954
1947
|
}
|
|
1955
1948
|
catch {
|
|
1956
1949
|
return null;
|
|
@@ -1980,7 +1973,7 @@ export class Client {
|
|
|
1980
1973
|
}
|
|
1981
1974
|
async getUserList(channelID) {
|
|
1982
1975
|
const res = await this.http.post(this.getHost() + "/userList/" + channelID);
|
|
1983
|
-
return
|
|
1976
|
+
return decodeHttpResponse(UserArrayCodec, res.data);
|
|
1984
1977
|
}
|
|
1985
1978
|
async handleNotify(msg) {
|
|
1986
1979
|
switch (msg.event) {
|
|
@@ -2044,12 +2037,6 @@ export class Client {
|
|
|
2044
2037
|
throw new Error("You should only call init() once.");
|
|
2045
2038
|
}
|
|
2046
2039
|
this.hasInit = true;
|
|
2047
|
-
if (Client.isNodeRuntime()) {
|
|
2048
|
-
const { attachNodeAgentsToAxios, createNodeHttpAgents } = await import("./storage/node/http-agents.js");
|
|
2049
|
-
const agents = createNodeHttpAgents();
|
|
2050
|
-
this.nodeHttpAgents = agents;
|
|
2051
|
-
attachNodeAgentsToAxios(this.http, agents);
|
|
2052
|
-
}
|
|
2053
2040
|
await this.populateKeyRing();
|
|
2054
2041
|
this.emitter.on("message", this.onInternalMessage);
|
|
2055
2042
|
void this.runLocalRetentionPurge();
|
|
@@ -2182,7 +2169,7 @@ export class Client {
|
|
|
2182
2169
|
"/user/" +
|
|
2183
2170
|
this.getUser().userID +
|
|
2184
2171
|
"/devices/requests");
|
|
2185
|
-
return
|
|
2172
|
+
return decodeHttpResponse(PendingDeviceRequestArrayCodec, response.data);
|
|
2186
2173
|
}
|
|
2187
2174
|
/**
|
|
2188
2175
|
* Lists every device the current account owns.
|
|
@@ -2193,12 +2180,12 @@ export class Client {
|
|
|
2193
2180
|
async listDevices() {
|
|
2194
2181
|
const userID = this.getUser().userID;
|
|
2195
2182
|
const res = await this.http.get(this.getHost() + "/user/" + userID + "/devices");
|
|
2196
|
-
return
|
|
2183
|
+
return decodeHttpResponse(DeviceArrayCodec, res.data);
|
|
2197
2184
|
}
|
|
2198
2185
|
async listPasskeys() {
|
|
2199
2186
|
const userID = this.getUser().userID;
|
|
2200
2187
|
const response = await this.http.get(this.getHost() + "/user/" + userID + "/passkeys");
|
|
2201
|
-
return
|
|
2188
|
+
return decodeHttpResponse(PasskeyArrayCodec, response.data);
|
|
2202
2189
|
}
|
|
2203
2190
|
async markSessionVerified(sessionID) {
|
|
2204
2191
|
return this.database.markSessionVerified(sessionID);
|
|
@@ -2249,7 +2236,7 @@ export class Client {
|
|
|
2249
2236
|
"/passkey/devices/requests/" +
|
|
2250
2237
|
requestID +
|
|
2251
2238
|
"/approve");
|
|
2252
|
-
return
|
|
2239
|
+
return decodeHttpResponse(DeviceCodec, response.data);
|
|
2253
2240
|
}
|
|
2254
2241
|
async passkeyDeleteDevice(deviceID) {
|
|
2255
2242
|
const userID = this.getUser().userID;
|
|
@@ -2258,7 +2245,7 @@ export class Client {
|
|
|
2258
2245
|
async passkeyListDevices() {
|
|
2259
2246
|
const userID = this.getUser().userID;
|
|
2260
2247
|
const response = await this.http.get(this.getHost() + "/user/" + userID + "/passkey/devices");
|
|
2261
|
-
return
|
|
2248
|
+
return decodeHttpResponse(DeviceArrayCodec, response.data);
|
|
2262
2249
|
}
|
|
2263
2250
|
async passkeyRejectDeviceRequest(requestID) {
|
|
2264
2251
|
const userID = this.getUser().userID;
|
|
@@ -2310,10 +2297,10 @@ export class Client {
|
|
|
2310
2297
|
"/user/devices/requests/" +
|
|
2311
2298
|
args.requestID +
|
|
2312
2299
|
"/poll", msgpack.encode({ signed }), { headers: { "Content-Type": "application/msgpack" } });
|
|
2313
|
-
return
|
|
2300
|
+
return decodeHttpResponse(PendingDeviceRequestCodec, response.data);
|
|
2314
2301
|
}
|
|
2315
2302
|
catch (err) {
|
|
2316
|
-
if (
|
|
2303
|
+
if (isHttpError(err) && err.response?.status === 404) {
|
|
2317
2304
|
return null;
|
|
2318
2305
|
}
|
|
2319
2306
|
throw err;
|
|
@@ -2930,7 +2917,7 @@ export class Client {
|
|
|
2930
2917
|
}
|
|
2931
2918
|
async redeemInvite(inviteID) {
|
|
2932
2919
|
const res = await this.http.patch(this.getHost() + "/invite/" + inviteID);
|
|
2933
|
-
return
|
|
2920
|
+
return decodeHttpResponse(PermissionCodec, res.data);
|
|
2934
2921
|
}
|
|
2935
2922
|
registerDecryptFailure(mail) {
|
|
2936
2923
|
const count = (this.decryptFailureCounts.get(mail.mailID) ?? 0) + 1;
|
|
@@ -2969,7 +2956,7 @@ export class Client {
|
|
|
2969
2956
|
"/user/" +
|
|
2970
2957
|
userDetails.userID +
|
|
2971
2958
|
"/devices", msgpack.encode(devMsg), { headers: { "Content-Type": "application/msgpack" } });
|
|
2972
|
-
return
|
|
2959
|
+
return decodeHttpResponse(DeviceRegistrationResultCodec, res.data);
|
|
2973
2960
|
}
|
|
2974
2961
|
async rejectDeviceRequest(requestID) {
|
|
2975
2962
|
await this.http.post(this.prefixes.HTTP +
|
|
@@ -2996,22 +2983,18 @@ export class Client {
|
|
|
2996
2983
|
}
|
|
2997
2984
|
async retrieveEmojiByID(emojiID) {
|
|
2998
2985
|
const res = await this.http.get(this.getHost() + "/emoji/" + emojiID + "/details");
|
|
2999
|
-
|
|
3000
|
-
return null;
|
|
3001
|
-
}
|
|
3002
|
-
return decodeAxios(EmojiCodec, res.data);
|
|
2986
|
+
return decodeHttpResponse(EmojiCodec, res.data);
|
|
3003
2987
|
}
|
|
3004
2988
|
async retrieveEmojiList(serverID) {
|
|
3005
2989
|
const res = await this.http.get(this.getHost() + "/server/" + serverID + "/emoji");
|
|
3006
|
-
return
|
|
2990
|
+
return decodeHttpResponse(EmojiArrayCodec, res.data);
|
|
3007
2991
|
}
|
|
3008
2992
|
async retrieveFile(fileID, key) {
|
|
3009
2993
|
const detailsRes = await this.http.get(this.getHost() + "/file/" + fileID + "/details");
|
|
3010
|
-
const details =
|
|
2994
|
+
const details = decodeHttpResponse(FileSQLCodec, detailsRes.data);
|
|
3011
2995
|
const res = await this.http.get(this.getHost() + "/file/" + fileID, {
|
|
3012
2996
|
onDownloadProgress: (progressEvent) => {
|
|
3013
|
-
const percentCompleted = Math.round((progressEvent.loaded * 100) /
|
|
3014
|
-
(progressEvent.total ?? 1));
|
|
2997
|
+
const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total ?? 1));
|
|
3015
2998
|
const { loaded, total = 0 } = progressEvent;
|
|
3016
2999
|
const progress = {
|
|
3017
3000
|
direction: "download",
|
|
@@ -3035,11 +3018,11 @@ export class Client {
|
|
|
3035
3018
|
}
|
|
3036
3019
|
async retrieveInvites(serverID) {
|
|
3037
3020
|
const res = await this.http.get(this.getHost() + "/server/" + serverID + "/invites");
|
|
3038
|
-
return
|
|
3021
|
+
return decodeHttpResponse(InviteArrayCodec, res.data);
|
|
3039
3022
|
}
|
|
3040
3023
|
async retrieveKeyBundle(deviceID) {
|
|
3041
3024
|
const res = await this.http.post(this.getHost() + "/device/" + deviceID + "/keyBundle");
|
|
3042
|
-
return
|
|
3025
|
+
return decodeHttpResponse(KeyBundleCodec, res.data);
|
|
3043
3026
|
}
|
|
3044
3027
|
async retrieveOrCreateDevice() {
|
|
3045
3028
|
let device;
|
|
@@ -3048,10 +3031,10 @@ export class Client {
|
|
|
3048
3031
|
this.host +
|
|
3049
3032
|
"/device/" +
|
|
3050
3033
|
XUtils.encodeHex(this.signKeys.publicKey));
|
|
3051
|
-
device =
|
|
3034
|
+
device = decodeHttpResponse(DeviceCodec, res.data);
|
|
3052
3035
|
}
|
|
3053
3036
|
catch (err) {
|
|
3054
|
-
if (
|
|
3037
|
+
if (isHttpError(err) && err.response?.status === 404) {
|
|
3055
3038
|
await this.database.purgeKeyData();
|
|
3056
3039
|
await this.populateKeyRing();
|
|
3057
3040
|
const newDevice = await this.registerDevice();
|
|
@@ -3634,7 +3617,7 @@ export class Client {
|
|
|
3634
3617
|
this.emitter.emit("fileProgress", progress);
|
|
3635
3618
|
},
|
|
3636
3619
|
});
|
|
3637
|
-
return
|
|
3620
|
+
return decodeHttpResponse(EmojiCodec, res.data);
|
|
3638
3621
|
}
|
|
3639
3622
|
catch (_err) {
|
|
3640
3623
|
return null;
|
|
@@ -3646,7 +3629,7 @@ export class Client {
|
|
|
3646
3629
|
};
|
|
3647
3630
|
try {
|
|
3648
3631
|
const res = await this.http.post(this.getHost() + "/emoji/" + serverID + "/json", msgpack.encode(payload), { headers: { "Content-Type": "application/msgpack" } });
|
|
3649
|
-
return
|
|
3632
|
+
return decodeHttpResponse(EmojiCodec, res.data);
|
|
3650
3633
|
}
|
|
3651
3634
|
catch (_err) {
|
|
3652
3635
|
return null;
|