@vex-chat/libvex 6.6.0 → 6.6.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.
@@ -32,8 +32,7 @@ import type { Storage } from "../../Storage.js";
32
32
 
33
33
  import { getCryptoProfile, setCryptoProfile } from "@vex-chat/crypto";
34
34
 
35
- import { isAxiosError } from "axios";
36
-
35
+ import { isHttpError } from "../../http.js";
37
36
  import { Client } from "../../index.js";
38
37
 
39
38
  import { testFile, testImage } from "./fixtures.js";
@@ -685,7 +684,7 @@ async function withTransientRetry<T>(fn: () => Promise<T>): Promise<T> {
685
684
  } catch (e) {
686
685
  last = e;
687
686
  const transient =
688
- isAxiosError(e) &&
687
+ isHttpError(e) &&
689
688
  (e.response?.status === 502 || e.response?.status === 503);
690
689
  if (transient && i < attempts - 1) {
691
690
  await new Promise((r) => setTimeout(r, 400 * (i + 1)));
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Copyright (c) 2020-2026 Vex Heavy Industries LLC
3
+ * Licensed under AGPL-3.0. See LICENSE for details.
4
+ * Commercial licenses available at vex.wtf
5
+ */
6
+
7
+ import { afterEach, describe, expect, it } from "vitest";
8
+
9
+ import { createFetchHttpClient, isHttpError } from "../http.js";
10
+
11
+ const originalFetch = globalThis.fetch;
12
+
13
+ afterEach(() => {
14
+ globalThis.fetch = originalFetch;
15
+ });
16
+
17
+ describe("FetchHttpClient", () => {
18
+ it("preserves status metadata for empty JSON error responses", async () => {
19
+ globalThis.fetch = () =>
20
+ Promise.resolve(
21
+ new Response(null, {
22
+ status: 401,
23
+ statusText: "Unauthorized",
24
+ }),
25
+ );
26
+
27
+ const client = createFetchHttpClient();
28
+ const err = await captureError(() =>
29
+ client.post(
30
+ "https://example.test/device/id/notifications",
31
+ {},
32
+ { responseType: "json" },
33
+ ),
34
+ );
35
+
36
+ expect(isHttpError(err)).toBe(true);
37
+ if (!isHttpError(err)) {
38
+ throw err;
39
+ }
40
+ expect(err.response?.status).toBe(401);
41
+ expect(err.response?.statusText).toBe("Unauthorized");
42
+ expect(err.response?.data).toBeNull();
43
+ expect(err.config.method).toBe("POST");
44
+ });
45
+
46
+ it("preserves status metadata for non-JSON error responses", async () => {
47
+ globalThis.fetch = () =>
48
+ Promise.resolve(
49
+ new Response("plain failure", {
50
+ headers: { "Content-Type": "text/plain" },
51
+ status: 400,
52
+ statusText: "Bad Request",
53
+ }),
54
+ );
55
+
56
+ const client = createFetchHttpClient();
57
+ const err = await captureError(() =>
58
+ client.get("https://example.test/status", {
59
+ responseType: "json",
60
+ }),
61
+ );
62
+
63
+ expect(isHttpError(err)).toBe(true);
64
+ if (!isHttpError(err)) {
65
+ throw err;
66
+ }
67
+ expect(err.response?.status).toBe(400);
68
+ expect(err.response?.data).toBe("plain failure");
69
+ });
70
+
71
+ it("emits a final upload progress event for FormData payloads", async () => {
72
+ globalThis.fetch = () =>
73
+ Promise.resolve(new Response(new ArrayBuffer(0), { status: 200 }));
74
+
75
+ const client = createFetchHttpClient();
76
+ const events: { loaded: number; total?: number }[] = [];
77
+ const payload = new FormData();
78
+ payload.set("file", new Blob([new Uint8Array([1, 2, 3])]));
79
+ payload.set("name", "ok");
80
+
81
+ await client.post("https://example.test/file", payload, {
82
+ onUploadProgress: (event) => {
83
+ events.push(event);
84
+ },
85
+ });
86
+
87
+ expect(events).toEqual([{ loaded: 5, total: 5 }]);
88
+ });
89
+ });
90
+
91
+ async function captureError(fn: () => Promise<unknown>): Promise<unknown> {
92
+ try {
93
+ await fn();
94
+ } catch (err: unknown) {
95
+ return err;
96
+ }
97
+ throw new Error("Expected function to throw");
98
+ }
package/src/codec.ts CHANGED
@@ -59,7 +59,7 @@ function msgpackDecode(data: Uint8Array): unknown {
59
59
  /**
60
60
  * Encode a value to msgpack. Returns a fresh Uint8Array copy
61
61
  * (not a subarray of the internal pool buffer) to avoid browser
62
- * XMLHttpRequest.send() corruption (axios issue #4068).
62
+ * transport corruption when a runtime reuses pooled buffers.
63
63
  */
64
64
  function msgpackEncode(value: unknown): Uint8Array {
65
65
  const packed = _packr.encode(value);
package/src/codecs.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * Pre-built codec instances for every HTTP response type.
9
9
  *
10
10
  * Usage: import { UserCodec } from "./codecs.js";
11
- * const data = decodeAxios(UserCodec, res.data);
11
+ * const data = decodeHttpResponse(UserCodec, res.data);
12
12
  *
13
13
  * decode() returns typed data without runtime validation (SDK trusts server).
14
14
  * For trust boundary validation, use codec.decodeSafe() directly.
@@ -190,18 +190,15 @@ export const PasskeyAuthFinishResponseCodec = createCodec(
190
190
  }),
191
191
  );
192
192
 
193
- // ── Helper: decode axios response buffer ────────────────────────────────────
193
+ // ── Helper: decode binary HTTP response buffer ──────────────────────────────
194
194
 
195
195
  /**
196
- * Decode an axios arraybuffer response with a typed codec.
196
+ * Decode a binary HTTP response with a typed codec.
197
197
  * Uses decodeSafe (Zod-validated) so schema mismatches surface immediately.
198
198
  */
199
- export function decodeAxios<T>(
199
+ export function decodeHttpResponse<T>(
200
200
  codec: { decodeSafe: (data: Uint8Array) => T },
201
- /**
202
- * Accepts `unknown` because axios types its `responseType: 'arraybuffer'`
203
- * responses as `any`. At runtime this is always an `ArrayBuffer`.
204
- */
201
+ /** Accepts `unknown` so callers can pass transport response data directly. */
205
202
  data: unknown,
206
203
  ): T {
207
204
  if (data instanceof Uint8Array) {
@@ -210,5 +207,5 @@ export function decodeAxios<T>(
210
207
  if (data instanceof ArrayBuffer) {
211
208
  return codec.decodeSafe(new Uint8Array(data));
212
209
  }
213
- throw new Error("Expected Uint8Array or ArrayBuffer from axios response");
210
+ throw new Error("Expected Uint8Array or ArrayBuffer from HTTP response");
214
211
  }