@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/src/Client.ts CHANGED
@@ -39,7 +39,6 @@ import type {
39
39
  SessionSQL,
40
40
  } from "@vex-chat/types";
41
41
  import type { ClientMessage } from "@vex-chat/types";
42
- import type { AxiosInstance } from "axios";
43
42
 
44
43
  import {
45
44
  type CryptoProfile,
@@ -76,11 +75,16 @@ import {
76
75
  WSMessageSchema,
77
76
  } from "@vex-chat/types";
78
77
 
79
- import axios, { type AxiosError, isAxiosError } from "axios";
80
78
  import { EventEmitter } from "eventemitter3";
81
79
  import * as uuid from "uuid";
82
80
  import { z } from "zod/v4";
83
81
 
82
+ import {
83
+ createFetchHttpClient,
84
+ type FetchHttpClient,
85
+ type HttpError,
86
+ isHttpError,
87
+ } from "./http.js";
84
88
  import {
85
89
  clampLocalMessageRetentionDays,
86
90
  formatVexRetentionEnvelope,
@@ -317,7 +321,7 @@ import {
317
321
  ChannelArrayCodec,
318
322
  ChannelCodec,
319
323
  ConnectResponseCodec,
320
- decodeAxios,
324
+ decodeHttpResponse,
321
325
  DeviceArrayCodec,
322
326
  DeviceChallengeCodec,
323
327
  DeviceCodec,
@@ -666,6 +670,20 @@ export interface NotificationSubscription {
666
670
  userID: string;
667
671
  }
668
672
 
673
+ const NotificationSubscriptionSchema: z.ZodType<NotificationSubscription> =
674
+ z.object({
675
+ channel: z.literal("expo"),
676
+ createdAt: z.string(),
677
+ deviceID: z.string(),
678
+ enabled: z.boolean(),
679
+ events: z.array(z.string()),
680
+ platform: z.string().nullable(),
681
+ subscriptionID: z.string(),
682
+ token: z.string(),
683
+ updatedAt: z.string(),
684
+ userID: z.string(),
685
+ });
686
+
669
687
  export interface NotificationSubscriptionInput {
670
688
  channel: "expo";
671
689
  events?: string[];
@@ -1032,7 +1050,7 @@ export interface Users {
1032
1050
  /**
1033
1051
  * Looks up a user by user ID, username, or signing key.
1034
1052
  */
1035
- retrieve: (userID: string) => Promise<[null | User, AxiosError | null]>;
1053
+ retrieve: (userID: string) => Promise<[null | User, HttpError | null]>;
1036
1054
  }
1037
1055
 
1038
1056
  /**
@@ -1439,8 +1457,8 @@ export class Client {
1439
1457
  private firstMailFetch = true;
1440
1458
  private readonly forwarded = new Set<string>();
1441
1459
  private readonly host: string;
1442
- private readonly http: AxiosInstance;
1443
- /** Cancels in-flight axios work on `close()` so `postAuth`/`getMail` cannot hang forever. */
1460
+ private readonly http: FetchHttpClient;
1461
+ /** Cancels in-flight HTTP work on `close()` so `postAuth`/`getMail` cannot hang forever. */
1444
1462
  private readonly httpAbortController = new AbortController();
1445
1463
  private readonly idKeys: KeyPair | null;
1446
1464
  private isAlive: boolean = true;
@@ -1451,14 +1469,6 @@ export class Client {
1451
1469
  private readonly mailInterval?: NodeJS.Timeout;
1452
1470
 
1453
1471
  private manuallyClosing: boolean = false;
1454
- /**
1455
- * Node-only: per-client HTTP(S) agents (see `init()` + `storage/node/http-agents`).
1456
- * Dropped on `close()` so idle keep-alive sockets do not keep the process alive.
1457
- */
1458
- private nodeHttpAgents?: {
1459
- http: { destroy(): void };
1460
- https: { destroy(): void };
1461
- };
1462
1472
  /* Retrieves the userID with the user identifier.
1463
1473
  user identifier is checked for userID, then signkey,
1464
1474
  and finally falls back to username. */
@@ -1544,7 +1554,7 @@ export class Client {
1544
1554
  void this.close(true);
1545
1555
  });
1546
1556
 
1547
- this.http = axios.create({
1557
+ this.http = createFetchHttpClient({
1548
1558
  responseType: "arraybuffer",
1549
1559
  signal: this.httpAbortController.signal,
1550
1560
  });
@@ -1754,32 +1764,6 @@ export class Client {
1754
1764
  }
1755
1765
  }
1756
1766
 
1757
- /**
1758
- * True when running under Node (has `process.versions`).
1759
- * Uses indirect lookup so the bare `process` global never appears in
1760
- * source that the platform-guard plugin scans.
1761
- */
1762
- private static isNodeRuntime(): boolean {
1763
- try {
1764
- const g = Object.getOwnPropertyDescriptor(
1765
- globalThis,
1766
- "\u0070rocess",
1767
- );
1768
- if (!g) return false;
1769
- const proc: unknown =
1770
- typeof g.get === "function" ? g.get() : g.value;
1771
- if (typeof proc !== "object" || proc === null) {
1772
- return false;
1773
- }
1774
- return (
1775
- "versions" in proc &&
1776
- typeof (proc as { versions?: unknown }).versions === "object"
1777
- );
1778
- } catch {
1779
- return false;
1780
- }
1781
- }
1782
-
1783
1767
  /**
1784
1768
  * Closes the client — disconnects the WebSocket, shuts down storage,
1785
1769
  * and emits `closed` unless `muteEvent` is `true`.
@@ -1797,12 +1781,6 @@ export class Client {
1797
1781
  this.socket.close();
1798
1782
  await this.database.close();
1799
1783
 
1800
- if (this.nodeHttpAgents) {
1801
- this.nodeHttpAgents.http.destroy();
1802
- this.nodeHttpAgents.https.destroy();
1803
- delete this.nodeHttpAgents;
1804
- }
1805
-
1806
1784
  if (this.pingInterval) {
1807
1785
  clearInterval(this.pingInterval);
1808
1786
  }
@@ -1855,7 +1833,10 @@ export class Client {
1855
1833
  msgpack.encode({ signed: signedAsync }),
1856
1834
  { headers: { "Content-Type": "application/msgpack" } },
1857
1835
  );
1858
- const { deviceToken } = decodeAxios(ConnectResponseCodec, res.data);
1836
+ const { deviceToken } = decodeHttpResponse(
1837
+ ConnectResponseCodec,
1838
+ res.data,
1839
+ );
1859
1840
  this.http.defaults.headers.common["X-Device-Token"] = deviceToken;
1860
1841
 
1861
1842
  this.autoReconnectEnabled = true;
@@ -1937,14 +1918,17 @@ export class Client {
1937
1918
  headers: { "Content-Type": "application/msgpack" },
1938
1919
  },
1939
1920
  );
1940
- const { token, user } = decodeAxios(AuthResponseCodec, res.data);
1921
+ const { token, user } = decodeHttpResponse(
1922
+ AuthResponseCodec,
1923
+ res.data,
1924
+ );
1941
1925
 
1942
1926
  this.setUser(user);
1943
1927
  this.token = token;
1944
1928
  this.http.defaults.headers.common.Authorization = `Bearer ${token}`;
1945
1929
  return { ok: true };
1946
1930
  } catch (err: unknown) {
1947
- if (isAxiosError(err) && err.response) {
1931
+ if (isHttpError(err) && err.response) {
1948
1932
  return {
1949
1933
  error: spireErrorBodyMessage(err.response.data),
1950
1934
  ok: false,
@@ -1979,7 +1963,7 @@ export class Client {
1979
1963
  }),
1980
1964
  { headers: { "Content-Type": "application/msgpack" } },
1981
1965
  );
1982
- const { challenge, challengeID } = decodeAxios(
1966
+ const { challenge, challengeID } = decodeHttpResponse(
1983
1967
  DeviceChallengeCodec,
1984
1968
  challengeRes.data,
1985
1969
  );
@@ -1996,7 +1980,7 @@ export class Client {
1996
1980
  msgpack.encode({ challengeID, signed }),
1997
1981
  { headers: { "Content-Type": "application/msgpack" } },
1998
1982
  );
1999
- const { token, user } = decodeAxios(
1983
+ const { token, user } = decodeHttpResponse(
2000
1984
  AuthResponseCodec,
2001
1985
  verifyRes.data,
2002
1986
  );
@@ -2150,7 +2134,7 @@ export class Client {
2150
2134
  let didDecodeRegisterResponse = false;
2151
2135
  let pendingApproval: null | PendingDeviceRegistration = null;
2152
2136
  try {
2153
- const { device, token, user } = decodeAxios(
2137
+ const { device, token, user } = decodeHttpResponse(
2154
2138
  RegisterResponseCodec,
2155
2139
  res.data,
2156
2140
  );
@@ -2165,7 +2149,7 @@ export class Client {
2165
2149
 
2166
2150
  if (!didDecodeRegisterResponse) {
2167
2151
  try {
2168
- pendingApproval = decodeAxios(
2152
+ pendingApproval = decodeHttpResponse(
2169
2153
  RegisterPendingApprovalCodec,
2170
2154
  res.data,
2171
2155
  );
@@ -2186,7 +2170,7 @@ export class Client {
2186
2170
  }),
2187
2171
  ];
2188
2172
  }
2189
- const legacyUser = decodeAxios(UserCodec, res.data);
2173
+ const legacyUser = decodeHttpResponse(UserCodec, res.data);
2190
2174
  this.setUser(legacyUser);
2191
2175
 
2192
2176
  // Legacy servers require /auth after /register to get a JWT.
@@ -2206,7 +2190,7 @@ export class Client {
2206
2190
  }
2207
2191
  return [this.getUser(), null];
2208
2192
  } catch (err: unknown) {
2209
- if (isAxiosError(err) && err.response) {
2193
+ if (isHttpError(err) && err.response) {
2210
2194
  return [
2211
2195
  null,
2212
2196
  new Error(spireErrorBodyMessage(err.response.data)),
@@ -2246,7 +2230,7 @@ export class Client {
2246
2230
  public async subscribeNotifications(
2247
2231
  input: NotificationSubscriptionInput,
2248
2232
  ): Promise<NotificationSubscription> {
2249
- const response = await this.http.post<NotificationSubscription>(
2233
+ const response = await this.http.post(
2250
2234
  this.getHost() +
2251
2235
  "/device/" +
2252
2236
  this.getDevice().deviceID +
@@ -2254,7 +2238,7 @@ export class Client {
2254
2238
  input,
2255
2239
  { responseType: "json" },
2256
2240
  );
2257
- return response.data;
2241
+ return NotificationSubscriptionSchema.parse(response.data);
2258
2242
  }
2259
2243
 
2260
2244
  /**
@@ -2309,7 +2293,7 @@ export class Client {
2309
2293
  }> {
2310
2294
  const res = await this.http.post(this.getHost() + "/whoami");
2311
2295
 
2312
- const whoami = decodeAxios(WhoamiCodec, res.data);
2296
+ const whoami = decodeHttpResponse(WhoamiCodec, res.data);
2313
2297
  return whoami;
2314
2298
  }
2315
2299
 
@@ -2380,7 +2364,7 @@ export class Client {
2380
2364
  msgpack.encode({ signed }),
2381
2365
  { headers: { "Content-Type": "application/msgpack" } },
2382
2366
  );
2383
- return decodeAxios(DeviceCodec, response.data);
2367
+ return decodeHttpResponse(DeviceCodec, response.data);
2384
2368
  }
2385
2369
 
2386
2370
  private async beginPasskeyAuthentication(username: string): Promise<{
@@ -2393,7 +2377,7 @@ export class Client {
2393
2377
  msgpack.encode({ username }),
2394
2378
  { headers: { "Content-Type": "application/msgpack" } },
2395
2379
  );
2396
- return decodeAxios(PasskeyOptionsCodec, response.data);
2380
+ return decodeHttpResponse(PasskeyOptionsCodec, response.data);
2397
2381
  }
2398
2382
 
2399
2383
  private async beginPasskeyRegistration(name: string): Promise<{
@@ -2407,7 +2391,7 @@ export class Client {
2407
2391
  msgpack.encode({ name }),
2408
2392
  { headers: { "Content-Type": "application/msgpack" } },
2409
2393
  );
2410
- return decodeAxios(PasskeyOptionsCodec, response.data);
2394
+ return decodeHttpResponse(PasskeyOptionsCodec, response.data);
2411
2395
  }
2412
2396
 
2413
2397
  private censorPreKey(preKey: PreKeysSQL): PreKeysWS {
@@ -2432,7 +2416,7 @@ export class Client {
2432
2416
  msgpack.encode(body),
2433
2417
  { headers: { "Content-Type": "application/msgpack" } },
2434
2418
  );
2435
- return decodeAxios(ChannelCodec, res.data);
2419
+ return decodeHttpResponse(ChannelCodec, res.data);
2436
2420
  }
2437
2421
 
2438
2422
  // returns the file details and the encryption key
@@ -2449,7 +2433,20 @@ export class Client {
2449
2433
  fileKey,
2450
2434
  );
2451
2435
 
2452
- if (typeof FormData !== "undefined") {
2436
+ const canUseMultipart =
2437
+ typeof FormData !== "undefined" &&
2438
+ (() => {
2439
+ try {
2440
+ // React Native/Hermes can expose Blob/FormData but
2441
+ // reject ArrayBufferView-backed blobs at runtime.
2442
+ void new Blob([new Uint8Array([1, 2, 3])]);
2443
+ return true;
2444
+ } catch {
2445
+ return false;
2446
+ }
2447
+ })();
2448
+
2449
+ if (canUseMultipart) {
2453
2450
  const fpayload = new FormData();
2454
2451
  fpayload.set("owner", this.getDevice().deviceID);
2455
2452
  fpayload.set("nonce", XUtils.encodeHex(nonce));
@@ -2477,7 +2474,10 @@ export class Client {
2477
2474
  },
2478
2475
  },
2479
2476
  );
2480
- const fcreatedFile = decodeAxios(FileSQLCodec, fres.data);
2477
+ const fcreatedFile = decodeHttpResponse(
2478
+ FileSQLCodec,
2479
+ fres.data,
2480
+ );
2481
2481
 
2482
2482
  return [fcreatedFile, XUtils.encodeHex(fileKey)];
2483
2483
  }
@@ -2496,7 +2496,7 @@ export class Client {
2496
2496
  msgpack.encode(payload),
2497
2497
  { headers: { "Content-Type": "application/msgpack" } },
2498
2498
  );
2499
- const createdFile = decodeAxios(FileSQLCodec, res.data);
2499
+ const createdFile = decodeHttpResponse(FileSQLCodec, res.data);
2500
2500
 
2501
2501
  return [createdFile, XUtils.encodeHex(fileKey)];
2502
2502
  });
@@ -2514,7 +2514,7 @@ export class Client {
2514
2514
  { headers: { "Content-Type": "application/msgpack" } },
2515
2515
  );
2516
2516
 
2517
- return decodeAxios(InviteCodec, res.data);
2517
+ return decodeHttpResponse(InviteCodec, res.data);
2518
2518
  }
2519
2519
 
2520
2520
  private async createPreKey(): Promise<UnsavedPreKey> {
@@ -2533,7 +2533,7 @@ export class Client {
2533
2533
  const res = await this.http.post(
2534
2534
  this.getHost() + "/server/" + globalThis.btoa(name),
2535
2535
  );
2536
- return decodeAxios(ServerCodec, res.data);
2536
+ return decodeHttpResponse(ServerCodec, res.data);
2537
2537
  }
2538
2538
 
2539
2539
  private async createSession(
@@ -2794,7 +2794,7 @@ export class Client {
2794
2794
  await this.http.delete(this.getHost() + "/server/" + serverID);
2795
2795
  }
2796
2796
  private deviceListFailureDetail(err: unknown): string {
2797
- if (!isAxiosError(err)) {
2797
+ if (!isHttpError(err)) {
2798
2798
  return "";
2799
2799
  }
2800
2800
  const st = err.response?.status;
@@ -2819,12 +2819,12 @@ export class Client {
2819
2819
  serverID +
2820
2820
  "/permissions",
2821
2821
  );
2822
- return decodeAxios(PermissionArrayCodec, res.data);
2822
+ return decodeHttpResponse(PermissionArrayCodec, res.data);
2823
2823
  }
2824
2824
 
2825
2825
  private async fetchUser(
2826
2826
  userIdentifier: string,
2827
- ): Promise<[null | User, AxiosError | null]> {
2827
+ ): Promise<[null | User, HttpError | null]> {
2828
2828
  // Usernames are case-insensitive at the protocol level, so
2829
2829
  // canonicalize the *cache key* to lowercase for username
2830
2830
  // lookups. Without this we'd accumulate duplicate
@@ -2851,18 +2851,18 @@ export class Client {
2851
2851
  const res = await this.http.get(
2852
2852
  this.getHost() + "/user/" + cacheKey,
2853
2853
  );
2854
- const userRecord = decodeAxios(UserCodec, res.data);
2854
+ const userRecord = decodeHttpResponse(UserCodec, res.data);
2855
2855
  this.userRecords[cacheKey] = userRecord;
2856
2856
  this.notFoundUsers.delete(cacheKey);
2857
2857
  return [userRecord, null];
2858
2858
  } catch (err: unknown) {
2859
- if (isAxiosError(err) && err.response?.status === 404) {
2859
+ if (isHttpError(err) && err.response?.status === 404) {
2860
2860
  // Definitive: user doesn't exist — cache and don't retry
2861
2861
  this.notFoundUsers.set(cacheKey, Date.now());
2862
2862
  return [null, err];
2863
2863
  }
2864
2864
  // Transient (5xx, network error) — don't cache, caller can retry
2865
- return [null, isAxiosError(err) ? err : null];
2865
+ return [null, isHttpError(err) ? err : null];
2866
2866
  }
2867
2867
  }
2868
2868
 
@@ -2873,7 +2873,7 @@ export class Client {
2873
2873
  const res = await this.http.get(
2874
2874
  this.getHost() + "/user/" + userID + "/devices",
2875
2875
  );
2876
- const devices = decodeAxios(DeviceArrayCodec, res.data);
2876
+ const devices = decodeHttpResponse(DeviceArrayCodec, res.data);
2877
2877
  for (const device of devices) {
2878
2878
  this.deviceRecords[device.deviceID] = device;
2879
2879
  }
@@ -2936,7 +2936,7 @@ export class Client {
2936
2936
  msgpack.encode(args),
2937
2937
  { headers: { "Content-Type": "application/msgpack" } },
2938
2938
  );
2939
- const decoded = decodeAxios(
2939
+ const decoded = decodeHttpResponse(
2940
2940
  PasskeyAuthFinishResponseCodec,
2941
2941
  response.data,
2942
2942
  );
@@ -2957,7 +2957,7 @@ export class Client {
2957
2957
  msgpack.encode(args),
2958
2958
  { headers: { "Content-Type": "application/msgpack" } },
2959
2959
  );
2960
- return decodeAxios(PasskeyCodec, response.data);
2960
+ return decodeHttpResponse(PasskeyCodec, response.data);
2961
2961
  }
2962
2962
 
2963
2963
  private async forward(message: Message) {
@@ -3007,7 +3007,7 @@ export class Client {
3007
3007
  const res = await this.http.get(
3008
3008
  this.getHost() + "/channel/" + channelID,
3009
3009
  );
3010
- return decodeAxios(ChannelCodec, res.data);
3010
+ return decodeHttpResponse(ChannelCodec, res.data);
3011
3011
  } catch (_err: unknown) {
3012
3012
  return null;
3013
3013
  }
@@ -3017,7 +3017,7 @@ export class Client {
3017
3017
  const res = await this.http.get(
3018
3018
  this.getHost() + "/server/" + serverID + "/channels",
3019
3019
  );
3020
- return decodeAxios(ChannelArrayCodec, res.data);
3020
+ return decodeHttpResponse(ChannelArrayCodec, res.data);
3021
3021
  }
3022
3022
 
3023
3023
  private getDevice(): Device {
@@ -3043,7 +3043,7 @@ export class Client {
3043
3043
  const res = await this.http.get(
3044
3044
  this.getHost() + "/device/" + deviceID,
3045
3045
  );
3046
- const fetchedDevice = decodeAxios(DeviceCodec, res.data);
3046
+ const fetchedDevice = decodeHttpResponse(DeviceCodec, res.data);
3047
3047
  this.deviceRecords[deviceID] = fetchedDevice;
3048
3048
  await this.database.saveDevice(fetchedDevice);
3049
3049
  return fetchedDevice;
@@ -3064,9 +3064,9 @@ export class Client {
3064
3064
  "/devices/requests/" +
3065
3065
  requestID,
3066
3066
  );
3067
- return decodeAxios(PendingDeviceRequestCodec, response.data);
3067
+ return decodeHttpResponse(PendingDeviceRequestCodec, response.data);
3068
3068
  } catch (err: unknown) {
3069
- if (isAxiosError(err) && err.response?.status === 404) {
3069
+ if (isHttpError(err) && err.response?.status === 404) {
3070
3070
  return null;
3071
3071
  }
3072
3072
  throw err;
@@ -3112,7 +3112,7 @@ export class Client {
3112
3112
  }
3113
3113
 
3114
3114
  try {
3115
- const res = await this.http.post<ArrayBuffer>(
3115
+ const res = await this.http.post(
3116
3116
  this.getHost() +
3117
3117
  "/device/" +
3118
3118
  this.getDevice().deviceID +
@@ -3186,7 +3186,7 @@ export class Client {
3186
3186
  msgpack.encode(userIDs),
3187
3187
  { headers: { "Content-Type": "application/msgpack" } },
3188
3188
  );
3189
- const devices = decodeAxios(DeviceArrayCodec, res.data);
3189
+ const devices = decodeHttpResponse(DeviceArrayCodec, res.data);
3190
3190
  for (const device of devices) {
3191
3191
  this.deviceRecords[device.deviceID] = device;
3192
3192
  }
@@ -3204,7 +3204,7 @@ export class Client {
3204
3204
  this.getDevice().deviceID +
3205
3205
  "/otk/count",
3206
3206
  );
3207
- return decodeAxios(OtkCountCodec, res.data).count;
3207
+ return decodeHttpResponse(OtkCountCodec, res.data).count;
3208
3208
  }
3209
3209
 
3210
3210
  /**
@@ -3216,7 +3216,7 @@ export class Client {
3216
3216
  const res = await this.http.get(
3217
3217
  this.getHost() + "/user/" + this.getUser().userID + "/permissions",
3218
3218
  );
3219
- return decodeAxios(PermissionArrayCodec, res.data);
3219
+ return decodeHttpResponse(PermissionArrayCodec, res.data);
3220
3220
  }
3221
3221
 
3222
3222
  private async getServerByID(serverID: string): Promise<null | Server> {
@@ -3224,7 +3224,7 @@ export class Client {
3224
3224
  const res = await this.http.get(
3225
3225
  this.getHost() + "/server/" + serverID,
3226
3226
  );
3227
- return decodeAxios(ServerCodec, res.data);
3227
+ return decodeHttpResponse(ServerCodec, res.data);
3228
3228
  } catch (_err: unknown) {
3229
3229
  return null;
3230
3230
  }
@@ -3237,14 +3237,14 @@ export class Client {
3237
3237
  this.getUser().userID +
3238
3238
  "/servers/bootstrap",
3239
3239
  );
3240
- return decodeAxios(ServerChannelBootstrapCodec, res.data);
3240
+ return decodeHttpResponse(ServerChannelBootstrapCodec, res.data);
3241
3241
  }
3242
3242
 
3243
3243
  private async getServerList(): Promise<Server[]> {
3244
3244
  const res = await this.http.get(
3245
3245
  this.getHost() + "/user/" + this.getUser().userID + "/servers",
3246
3246
  );
3247
- return decodeAxios(ServerArrayCodec, res.data);
3247
+ return decodeHttpResponse(ServerArrayCodec, res.data);
3248
3248
  }
3249
3249
 
3250
3250
  private async getSessionByPubkey(publicKey: Uint8Array) {
@@ -3277,7 +3277,7 @@ export class Client {
3277
3277
  const res = await this.http.get(this.getHost() + "/token/" + type, {
3278
3278
  responseType: "arraybuffer",
3279
3279
  });
3280
- return decodeAxios(ActionTokenCodec, res.data);
3280
+ return decodeHttpResponse(ActionTokenCodec, res.data);
3281
3281
  } catch {
3282
3282
  return null;
3283
3283
  }
@@ -3312,7 +3312,7 @@ export class Client {
3312
3312
  const res = await this.http.post(
3313
3313
  this.getHost() + "/userList/" + channelID,
3314
3314
  );
3315
- return decodeAxios(UserArrayCodec, res.data);
3315
+ return decodeHttpResponse(UserArrayCodec, res.data);
3316
3316
  }
3317
3317
 
3318
3318
  private async handleNotify(msg: NotifyMsg) {
@@ -3385,14 +3385,6 @@ export class Client {
3385
3385
  }
3386
3386
  this.hasInit = true;
3387
3387
 
3388
- if (Client.isNodeRuntime()) {
3389
- const { attachNodeAgentsToAxios, createNodeHttpAgents } =
3390
- await import("./storage/node/http-agents.js");
3391
- const agents = createNodeHttpAgents();
3392
- this.nodeHttpAgents = agents;
3393
- attachNodeAgentsToAxios(this.http, agents);
3394
- }
3395
-
3396
3388
  await this.populateKeyRing();
3397
3389
  this.emitter.on("message", this.onInternalMessage);
3398
3390
  void this.runLocalRetentionPurge();
@@ -3546,7 +3538,10 @@ export class Client {
3546
3538
  this.getUser().userID +
3547
3539
  "/devices/requests",
3548
3540
  );
3549
- return decodeAxios(PendingDeviceRequestArrayCodec, response.data);
3541
+ return decodeHttpResponse(
3542
+ PendingDeviceRequestArrayCodec,
3543
+ response.data,
3544
+ );
3550
3545
  }
3551
3546
 
3552
3547
  /**
@@ -3560,7 +3555,7 @@ export class Client {
3560
3555
  const res = await this.http.get(
3561
3556
  this.getHost() + "/user/" + userID + "/devices",
3562
3557
  );
3563
- return decodeAxios(DeviceArrayCodec, res.data);
3558
+ return decodeHttpResponse(DeviceArrayCodec, res.data);
3564
3559
  }
3565
3560
 
3566
3561
  private async listPasskeys(): Promise<Passkey[]> {
@@ -3568,7 +3563,7 @@ export class Client {
3568
3563
  const response = await this.http.get(
3569
3564
  this.getHost() + "/user/" + userID + "/passkeys",
3570
3565
  );
3571
- return decodeAxios(PasskeyArrayCodec, response.data);
3566
+ return decodeHttpResponse(PasskeyArrayCodec, response.data);
3572
3567
  }
3573
3568
 
3574
3569
  private async markSessionVerified(sessionID: string) {
@@ -3634,7 +3629,7 @@ export class Client {
3634
3629
  requestID +
3635
3630
  "/approve",
3636
3631
  );
3637
- return decodeAxios(DeviceCodec, response.data);
3632
+ return decodeHttpResponse(DeviceCodec, response.data);
3638
3633
  }
3639
3634
 
3640
3635
  private async passkeyDeleteDevice(deviceID: string): Promise<void> {
@@ -3649,7 +3644,7 @@ export class Client {
3649
3644
  const response = await this.http.get(
3650
3645
  this.getHost() + "/user/" + userID + "/passkey/devices",
3651
3646
  );
3652
- return decodeAxios(DeviceArrayCodec, response.data);
3647
+ return decodeHttpResponse(DeviceArrayCodec, response.data);
3653
3648
  }
3654
3649
 
3655
3650
  private async passkeyRejectDeviceRequest(requestID: string): Promise<void> {
@@ -3716,9 +3711,9 @@ export class Client {
3716
3711
  msgpack.encode({ signed }),
3717
3712
  { headers: { "Content-Type": "application/msgpack" } },
3718
3713
  );
3719
- return decodeAxios(PendingDeviceRequestCodec, response.data);
3714
+ return decodeHttpResponse(PendingDeviceRequestCodec, response.data);
3720
3715
  } catch (err: unknown) {
3721
- if (isAxiosError(err) && err.response?.status === 404) {
3716
+ if (isHttpError(err) && err.response?.status === 404) {
3722
3717
  return null;
3723
3718
  }
3724
3719
  throw err;
@@ -4548,7 +4543,7 @@ export class Client {
4548
4543
  const res = await this.http.patch(
4549
4544
  this.getHost() + "/invite/" + inviteID,
4550
4545
  );
4551
- return decodeAxios(PermissionCodec, res.data);
4546
+ return decodeHttpResponse(PermissionCodec, res.data);
4552
4547
  }
4553
4548
 
4554
4549
  private registerDecryptFailure(mail: MailWS): number {
@@ -4603,7 +4598,7 @@ export class Client {
4603
4598
  msgpack.encode(devMsg),
4604
4599
  { headers: { "Content-Type": "application/msgpack" } },
4605
4600
  );
4606
- return decodeAxios(DeviceRegistrationResultCodec, res.data);
4601
+ return decodeHttpResponse(DeviceRegistrationResultCodec, res.data);
4607
4602
  }
4608
4603
 
4609
4604
  private async rejectDeviceRequest(requestID: string): Promise<void> {
@@ -4640,17 +4635,14 @@ export class Client {
4640
4635
  const res = await this.http.get(
4641
4636
  this.getHost() + "/emoji/" + emojiID + "/details",
4642
4637
  );
4643
- if (!res.data) {
4644
- return null;
4645
- }
4646
- return decodeAxios(EmojiCodec, res.data);
4638
+ return decodeHttpResponse(EmojiCodec, res.data);
4647
4639
  }
4648
4640
 
4649
4641
  private async retrieveEmojiList(serverID: string): Promise<Emoji[]> {
4650
4642
  const res = await this.http.get(
4651
4643
  this.getHost() + "/server/" + serverID + "/emoji",
4652
4644
  );
4653
- return decodeAxios(EmojiArrayCodec, res.data);
4645
+ return decodeHttpResponse(EmojiArrayCodec, res.data);
4654
4646
  }
4655
4647
 
4656
4648
  private async retrieveFile(
@@ -4660,28 +4652,24 @@ export class Client {
4660
4652
  const detailsRes = await this.http.get(
4661
4653
  this.getHost() + "/file/" + fileID + "/details",
4662
4654
  );
4663
- const details = decodeAxios(FileSQLCodec, detailsRes.data);
4655
+ const details = decodeHttpResponse(FileSQLCodec, detailsRes.data);
4664
4656
 
4665
- const res = await this.http.get<ArrayBuffer>(
4666
- this.getHost() + "/file/" + fileID,
4667
- {
4668
- onDownloadProgress: (progressEvent) => {
4669
- const percentCompleted = Math.round(
4670
- (progressEvent.loaded * 100) /
4671
- (progressEvent.total ?? 1),
4672
- );
4673
- const { loaded, total = 0 } = progressEvent;
4674
- const progress: FileProgress = {
4675
- direction: "download",
4676
- loaded,
4677
- progress: percentCompleted,
4678
- token: fileID,
4679
- total,
4680
- };
4681
- this.emitter.emit("fileProgress", progress);
4682
- },
4657
+ const res = await this.http.get(this.getHost() + "/file/" + fileID, {
4658
+ onDownloadProgress: (progressEvent) => {
4659
+ const percentCompleted = Math.round(
4660
+ (progressEvent.loaded * 100) / (progressEvent.total ?? 1),
4661
+ );
4662
+ const { loaded, total = 0 } = progressEvent;
4663
+ const progress: FileProgress = {
4664
+ direction: "download",
4665
+ loaded,
4666
+ progress: percentCompleted,
4667
+ token: fileID,
4668
+ total,
4669
+ };
4670
+ this.emitter.emit("fileProgress", progress);
4683
4671
  },
4684
- );
4672
+ });
4685
4673
  const fileData = res.data;
4686
4674
 
4687
4675
  const decrypted = await xSecretboxOpenAsync(
@@ -4703,14 +4691,14 @@ export class Client {
4703
4691
  const res = await this.http.get(
4704
4692
  this.getHost() + "/server/" + serverID + "/invites",
4705
4693
  );
4706
- return decodeAxios(InviteArrayCodec, res.data);
4694
+ return decodeHttpResponse(InviteArrayCodec, res.data);
4707
4695
  }
4708
4696
 
4709
4697
  private async retrieveKeyBundle(deviceID: string): Promise<KeyBundle> {
4710
4698
  const res = await this.http.post(
4711
4699
  this.getHost() + "/device/" + deviceID + "/keyBundle",
4712
4700
  );
4713
- return decodeAxios(KeyBundleCodec, res.data);
4701
+ return decodeHttpResponse(KeyBundleCodec, res.data);
4714
4702
  }
4715
4703
 
4716
4704
  private async retrieveOrCreateDevice(): Promise<Device> {
@@ -4722,9 +4710,9 @@ export class Client {
4722
4710
  "/device/" +
4723
4711
  XUtils.encodeHex(this.signKeys.publicKey),
4724
4712
  );
4725
- device = decodeAxios(DeviceCodec, res.data);
4713
+ device = decodeHttpResponse(DeviceCodec, res.data);
4726
4714
  } catch (err: unknown) {
4727
- if (isAxiosError(err) && err.response?.status === 404) {
4715
+ if (isHttpError(err) && err.response?.status === 404) {
4728
4716
  await this.database.purgeKeyData();
4729
4717
  await this.populateKeyRing();
4730
4718
 
@@ -5477,7 +5465,7 @@ export class Client {
5477
5465
  },
5478
5466
  },
5479
5467
  );
5480
- return decodeAxios(EmojiCodec, res.data);
5468
+ return decodeHttpResponse(EmojiCodec, res.data);
5481
5469
  } catch (_err: unknown) {
5482
5470
  return null;
5483
5471
  }
@@ -5493,7 +5481,7 @@ export class Client {
5493
5481
  msgpack.encode(payload),
5494
5482
  { headers: { "Content-Type": "application/msgpack" } },
5495
5483
  );
5496
- return decodeAxios(EmojiCodec, res.data);
5484
+ return decodeHttpResponse(EmojiCodec, res.data);
5497
5485
  } catch (_err: unknown) {
5498
5486
  return null;
5499
5487
  }