@nexusm/sdk 5.1.0 → 5.2.0
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/index.d.mts +20 -20
- package/dist/index.d.ts +20 -20
- package/dist/index.js +55 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -163,6 +163,12 @@ var ValidationError = class extends ApiError {
|
|
|
163
163
|
this.details = details;
|
|
164
164
|
}
|
|
165
165
|
};
|
|
166
|
+
var UpstreamInterceptError = class extends ApiError {
|
|
167
|
+
constructor(message, statusCode, response) {
|
|
168
|
+
super(message, statusCode, response, "NEXUS_UPSTREAM_INTERCEPT");
|
|
169
|
+
this.name = "UpstreamInterceptError";
|
|
170
|
+
}
|
|
171
|
+
};
|
|
166
172
|
var NotFoundError = class extends ApiError {
|
|
167
173
|
constructor(message, response) {
|
|
168
174
|
super(message, 404, response, "NEXUS_NOT_FOUND_ERROR");
|
|
@@ -574,6 +580,35 @@ var OfflineQueue = class {
|
|
|
574
580
|
};
|
|
575
581
|
|
|
576
582
|
// src/http/client.ts
|
|
583
|
+
function redirectHost(location) {
|
|
584
|
+
if (typeof location !== "string" || location === "") return "an unknown host";
|
|
585
|
+
try {
|
|
586
|
+
return new URL(location).host;
|
|
587
|
+
} catch {
|
|
588
|
+
return "an unparseable location";
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function upstreamRedirectError(response, url) {
|
|
592
|
+
const headers = response.headers ?? {};
|
|
593
|
+
const host = redirectHost(headers.location ?? headers.Location);
|
|
594
|
+
return new UpstreamInterceptError(
|
|
595
|
+
`Request to ${url ?? "the API"} was redirected (HTTP ${response.status}) to ${host} instead of being answered by Nexus. This is typically an expired or missing edge credential (e.g. a Cloudflare Access service token) \u2014 the API itself was never reached.`,
|
|
596
|
+
response.status,
|
|
597
|
+
response.data
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
function assertNotIntercepted(response) {
|
|
601
|
+
if (response.config?.responseType === "text") return;
|
|
602
|
+
const headers = response.headers ?? {};
|
|
603
|
+
const raw = headers["content-type"] ?? headers["Content-Type"];
|
|
604
|
+
if (typeof raw !== "string" || raw === "") return;
|
|
605
|
+
if (raw.toLowerCase().includes("json")) return;
|
|
606
|
+
throw new UpstreamInterceptError(
|
|
607
|
+
`Request to ${response.config?.url ?? "the API"} returned HTTP ${response.status} with content-type "${raw}" where JSON was expected. Something between this client and Nexus answered the request (auth edge, proxy, or captive portal); treating it as data would look like an empty result.`,
|
|
608
|
+
response.status,
|
|
609
|
+
response.data
|
|
610
|
+
);
|
|
611
|
+
}
|
|
577
612
|
var HttpClient = class {
|
|
578
613
|
/**
|
|
579
614
|
* Create a new HTTP client.
|
|
@@ -591,7 +626,14 @@ var HttpClient = class {
|
|
|
591
626
|
}
|
|
592
627
|
this.axios = axios.create({
|
|
593
628
|
baseURL: config.baseUrl,
|
|
594
|
-
timeout: config.timeout
|
|
629
|
+
timeout: config.timeout,
|
|
630
|
+
// Never follow redirects (Kairos#66 / Aether#372). An auth edge such as
|
|
631
|
+
// Cloudflare Access answers an unauthenticated call with 302 → its own
|
|
632
|
+
// login page; following it yields a 200 with HTML, which is
|
|
633
|
+
// indistinguishable from an empty result to the caller. With
|
|
634
|
+
// maxRedirects: 0 the 3xx fails axios' status validation and reaches the
|
|
635
|
+
// error interceptor, which turns it into an UpstreamInterceptError.
|
|
636
|
+
maxRedirects: 0
|
|
595
637
|
});
|
|
596
638
|
this.setupRequestInterceptor();
|
|
597
639
|
this.setupResponseInterceptor();
|
|
@@ -806,8 +848,11 @@ var HttpClient = class {
|
|
|
806
848
|
*/
|
|
807
849
|
setupResponseInterceptor() {
|
|
808
850
|
this.axios.interceptors.response.use(
|
|
809
|
-
// Success handler -- pass through
|
|
810
|
-
(response) =>
|
|
851
|
+
// Success handler -- pass through, except for a 2xx that is not JSON.
|
|
852
|
+
(response) => {
|
|
853
|
+
assertNotIntercepted(response);
|
|
854
|
+
return response;
|
|
855
|
+
},
|
|
811
856
|
// Error handler -- normalise into NexusError hierarchy
|
|
812
857
|
(error) => {
|
|
813
858
|
if (axios.isCancel(error)) {
|
|
@@ -821,6 +866,11 @@ var HttpClient = class {
|
|
|
821
866
|
)
|
|
822
867
|
);
|
|
823
868
|
}
|
|
869
|
+
if (error.response && error.response.status >= 300 && error.response.status < 400) {
|
|
870
|
+
return Promise.reject(
|
|
871
|
+
upstreamRedirectError(error.response, error.config?.url)
|
|
872
|
+
);
|
|
873
|
+
}
|
|
824
874
|
if (error.response) {
|
|
825
875
|
const apiError = ApiError.fromResponse(error.response);
|
|
826
876
|
const reqUrl = error.config?.url ?? "";
|
|
@@ -1476,6 +1526,7 @@ export {
|
|
|
1476
1526
|
RateLimitError,
|
|
1477
1527
|
TenantService,
|
|
1478
1528
|
TimeoutError,
|
|
1529
|
+
UpstreamInterceptError,
|
|
1479
1530
|
ValidationError,
|
|
1480
1531
|
apiKeyCreateSchema,
|
|
1481
1532
|
contextRequestSchema,
|