@gcmdev/faxios 0.2.0 → 0.2.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/LICENSE +8 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/adapters/fetch-helpers.d.ts +2 -1
- package/dist/lib/adapters/fetch-helpers.d.ts.map +1 -0
- package/dist/lib/adapters/fetch.d.ts +1 -0
- package/dist/lib/adapters/fetch.d.ts.map +1 -0
- package/dist/lib/adapters/fetch.js +86 -26
- package/dist/lib/adapters/fetch.js.map +1 -1
- package/dist/lib/cancel/CanceledError.d.ts +2 -1
- package/dist/lib/cancel/CanceledError.d.ts.map +1 -0
- package/dist/lib/cancel/isCancel.d.ts +1 -0
- package/dist/lib/cancel/isCancel.d.ts.map +1 -0
- package/dist/lib/core/Faxios.d.ts +4 -3
- package/dist/lib/core/Faxios.d.ts.map +1 -0
- package/dist/lib/core/Faxios.js +8 -4
- package/dist/lib/core/Faxios.js.map +1 -1
- package/dist/lib/core/FaxiosError.d.ts +3 -2
- package/dist/lib/core/FaxiosError.d.ts.map +1 -0
- package/dist/lib/core/FaxiosHeaders.d.ts +9 -8
- package/dist/lib/core/FaxiosHeaders.d.ts.map +1 -0
- package/dist/lib/core/buildFullPath.d.ts +1 -0
- package/dist/lib/core/buildFullPath.d.ts.map +1 -0
- package/dist/lib/core/define.d.ts +20 -3
- package/dist/lib/core/define.d.ts.map +1 -0
- package/dist/lib/core/define.js.map +1 -1
- package/dist/lib/core/dispatchRequest.d.ts +2 -1
- package/dist/lib/core/dispatchRequest.d.ts.map +1 -0
- package/dist/lib/core/mergeConfig.d.ts +3 -2
- package/dist/lib/core/mergeConfig.d.ts.map +1 -0
- package/dist/lib/core/mergeConfig.js +40 -2
- package/dist/lib/core/mergeConfig.js.map +1 -1
- package/dist/lib/core/prepareRequest.d.ts +1 -0
- package/dist/lib/core/prepareRequest.d.ts.map +1 -0
- package/dist/lib/core/route.d.ts +10 -1
- package/dist/lib/core/route.d.ts.map +1 -0
- package/dist/lib/core/route.js +2 -5
- package/dist/lib/core/route.js.map +1 -1
- package/dist/lib/core/settle.d.ts +1 -0
- package/dist/lib/core/settle.d.ts.map +1 -0
- package/dist/lib/core/transformData.d.ts +3 -2
- package/dist/lib/core/transformData.d.ts.map +1 -0
- package/dist/lib/core/validateSchema.d.ts +3 -2
- package/dist/lib/core/validateSchema.d.ts.map +1 -0
- package/dist/lib/defaults/index.d.ts +1 -0
- package/dist/lib/defaults/index.d.ts.map +1 -0
- package/dist/lib/defaults/transitional.d.ts +1 -0
- package/dist/lib/defaults/transitional.d.ts.map +1 -0
- package/dist/lib/env/data.d.ts +2 -1
- package/dist/lib/env/data.d.ts.map +1 -0
- package/dist/lib/env/data.js +1 -1
- package/dist/lib/faxios.d.ts +59 -38
- package/dist/lib/faxios.d.ts.map +1 -0
- package/dist/lib/faxios.js.map +1 -1
- package/dist/lib/helpers/FaxiosURLSearchParams.d.ts +1 -0
- package/dist/lib/helpers/FaxiosURLSearchParams.d.ts.map +1 -0
- package/dist/lib/helpers/HttpStatusCode.d.ts +1 -0
- package/dist/lib/helpers/HttpStatusCode.d.ts.map +1 -0
- package/dist/lib/helpers/buildURL.d.ts +1 -0
- package/dist/lib/helpers/buildURL.d.ts.map +1 -0
- package/dist/lib/helpers/cloneConfig.d.ts +1 -1
- package/dist/lib/helpers/cloneConfig.d.ts.map +1 -0
- package/dist/lib/helpers/cloneConfig.js +2 -2
- package/dist/lib/helpers/cloneConfig.js.map +1 -1
- package/dist/lib/helpers/combineURLs.d.ts +1 -0
- package/dist/lib/helpers/combineURLs.d.ts.map +1 -0
- package/dist/lib/helpers/composeSignals.d.ts +1 -0
- package/dist/lib/helpers/composeSignals.d.ts.map +1 -0
- package/dist/lib/helpers/cookies.d.ts +2 -1
- package/dist/lib/helpers/cookies.d.ts.map +1 -0
- package/dist/lib/helpers/estimateDataURLDecodedBytes.d.ts +1 -0
- package/dist/lib/helpers/estimateDataURLDecodedBytes.d.ts.map +1 -0
- package/dist/lib/helpers/formDataToJSON.d.ts +2 -1
- package/dist/lib/helpers/formDataToJSON.d.ts.map +1 -0
- package/dist/lib/helpers/formDataToJSON.js +1 -1
- package/dist/lib/helpers/formDataToJSON.js.map +1 -1
- package/dist/lib/helpers/isAbsoluteURL.d.ts +1 -0
- package/dist/lib/helpers/isAbsoluteURL.d.ts.map +1 -0
- package/dist/lib/helpers/isFaxiosError.d.ts +1 -0
- package/dist/lib/helpers/isFaxiosError.d.ts.map +1 -0
- package/dist/lib/helpers/isURLSameOrigin.d.ts +1 -0
- package/dist/lib/helpers/isURLSameOrigin.d.ts.map +1 -0
- package/dist/lib/helpers/parseHeaders.d.ts +2 -1
- package/dist/lib/helpers/parseHeaders.d.ts.map +1 -0
- package/dist/lib/helpers/progressEventReducer.d.ts +1 -0
- package/dist/lib/helpers/progressEventReducer.d.ts.map +1 -0
- package/dist/lib/helpers/sanitizeHeaderValue.d.ts +1 -0
- package/dist/lib/helpers/sanitizeHeaderValue.d.ts.map +1 -0
- package/dist/lib/helpers/speedometer.d.ts +1 -0
- package/dist/lib/helpers/speedometer.d.ts.map +1 -0
- package/dist/lib/helpers/substitutePathParams.d.ts +1 -0
- package/dist/lib/helpers/substitutePathParams.d.ts.map +1 -0
- package/dist/lib/helpers/substitutePathParams.js +11 -3
- package/dist/lib/helpers/substitutePathParams.js.map +1 -1
- package/dist/lib/helpers/throttle.d.ts +1 -0
- package/dist/lib/helpers/throttle.d.ts.map +1 -0
- package/dist/lib/helpers/toFormData.d.ts +1 -0
- package/dist/lib/helpers/toFormData.d.ts.map +1 -0
- package/dist/lib/helpers/toFormData.js +4 -2
- package/dist/lib/helpers/toFormData.js.map +1 -1
- package/dist/lib/helpers/toURLEncodedForm.d.ts +1 -0
- package/dist/lib/helpers/toURLEncodedForm.d.ts.map +1 -0
- package/dist/lib/helpers/trackStream.d.ts +1 -0
- package/dist/lib/helpers/trackStream.d.ts.map +1 -0
- package/dist/lib/helpers/validator.d.ts +2 -1
- package/dist/lib/helpers/validator.d.ts.map +1 -0
- package/dist/lib/platform.d.ts +1 -0
- package/dist/lib/platform.d.ts.map +1 -0
- package/dist/lib/plugins/attempt.d.ts +1 -0
- package/dist/lib/plugins/attempt.d.ts.map +1 -0
- package/dist/lib/plugins/authBearer.d.ts +10 -5
- package/dist/lib/plugins/authBearer.d.ts.map +1 -0
- package/dist/lib/plugins/authBearer.js +6 -0
- package/dist/lib/plugins/authBearer.js.map +1 -1
- package/dist/lib/plugins/definePlugin.d.ts +4 -3
- package/dist/lib/plugins/definePlugin.d.ts.map +1 -0
- package/dist/lib/plugins/retry.d.ts +24 -23
- package/dist/lib/plugins/retry.d.ts.map +1 -0
- package/dist/lib/plugins/timing.d.ts +9 -8
- package/dist/lib/plugins/timing.d.ts.map +1 -0
- package/dist/lib/types/standard-schema.d.ts +7 -6
- package/dist/lib/types/standard-schema.d.ts.map +1 -0
- package/dist/lib/types.d.ts +104 -92
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +5 -3
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +9 -2
- package/dist/lib/utils.js.map +1 -1
- package/package.json +8 -4
- package/skills/configure-requests/SKILL.md +53 -8
- package/skills/configure-requests/references/plugins.md +54 -6
- package/src/index.ts +57 -0
- package/src/lib/adapters/fetch-helpers.ts +167 -0
- package/src/lib/adapters/fetch.ts +871 -0
- package/src/lib/cancel/CanceledError.ts +23 -0
- package/src/lib/cancel/isCancel.ts +10 -0
- package/src/lib/core/Faxios.ts +517 -0
- package/src/lib/core/FaxiosError.ts +283 -0
- package/src/lib/core/FaxiosHeaders.ts +529 -0
- package/src/lib/core/buildFullPath.ts +51 -0
- package/src/lib/core/define.ts +127 -0
- package/src/lib/core/dispatchRequest.ts +171 -0
- package/src/lib/core/mergeConfig.ts +213 -0
- package/src/lib/core/prepareRequest.ts +155 -0
- package/src/lib/core/route.ts +65 -0
- package/src/lib/core/settle.ts +20 -0
- package/src/lib/core/transformData.ts +23 -0
- package/src/lib/core/validateSchema.ts +53 -0
- package/src/lib/defaults/index.ts +221 -0
- package/src/lib/defaults/transitional.ts +9 -0
- package/src/lib/env/data.ts +1 -0
- package/src/lib/faxios.ts +306 -0
- package/src/lib/helpers/FaxiosURLSearchParams.ts +52 -0
- package/src/lib/helpers/HttpStatusCode.ts +77 -0
- package/src/lib/helpers/buildURL.ts +77 -0
- package/src/lib/helpers/cloneConfig.ts +22 -0
- package/src/lib/helpers/combineURLs.ts +11 -0
- package/src/lib/helpers/composeSignals.ts +74 -0
- package/src/lib/helpers/cookies.ts +68 -0
- package/src/lib/helpers/deprecatedMethod.ts +32 -0
- package/src/lib/helpers/estimateDataURLDecodedBytes.ts +95 -0
- package/src/lib/helpers/formDataToJSON.ts +122 -0
- package/src/lib/helpers/isAbsoluteURL.ts +13 -0
- package/src/lib/helpers/isFaxiosError.ts +19 -0
- package/src/lib/helpers/isURLSameOrigin.ts +16 -0
- package/src/lib/helpers/parseHeaders.ts +76 -0
- package/src/lib/helpers/progressEventReducer.ts +57 -0
- package/src/lib/helpers/sanitizeHeaderValue.ts +61 -0
- package/src/lib/helpers/speedometer.ts +55 -0
- package/src/lib/helpers/substitutePathParams.ts +23 -0
- package/src/lib/helpers/throttle.ts +45 -0
- package/src/lib/helpers/toFormData.ts +298 -0
- package/src/lib/helpers/toURLEncodedForm.ts +14 -0
- package/src/lib/helpers/trackStream.ts +123 -0
- package/src/lib/helpers/validator.ts +114 -0
- package/src/lib/platform.ts +44 -0
- package/src/lib/plugins/attempt.ts +3 -0
- package/src/lib/plugins/authBearer.ts +129 -0
- package/src/lib/plugins/definePlugin.ts +34 -0
- package/src/lib/plugins/retry.ts +245 -0
- package/src/lib/plugins/timing.ts +76 -0
- package/src/lib/types/standard-schema.ts +58 -0
- package/src/lib/types.ts +496 -0
- package/src/lib/utils.ts +941 -0
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
import FaxiosError from "../core/FaxiosError.js";
|
|
2
|
+
import FaxiosHeaders from "../core/FaxiosHeaders.js";
|
|
3
|
+
import prepareRequest from "../core/prepareRequest.js";
|
|
4
|
+
import { settle } from "../core/settle.js";
|
|
5
|
+
import { VERSION } from "../env/data.js";
|
|
6
|
+
import composeSignals from "../helpers/composeSignals.js";
|
|
7
|
+
import estimateDataURLDecodedBytes from "../helpers/estimateDataURLDecodedBytes.js";
|
|
8
|
+
import {
|
|
9
|
+
asyncDecorator,
|
|
10
|
+
progressEventDecorator,
|
|
11
|
+
progressEventReducer
|
|
12
|
+
} from "../helpers/progressEventReducer.js";
|
|
13
|
+
import { toByteStringHeaderObject } from "../helpers/sanitizeHeaderValue.js";
|
|
14
|
+
import { trackStream } from "../helpers/trackStream.js";
|
|
15
|
+
import platform from "../platform.js";
|
|
16
|
+
import type {
|
|
17
|
+
FaxiosRequestHeaders,
|
|
18
|
+
InternalFaxiosRequestConfig
|
|
19
|
+
} from "../types.js";
|
|
20
|
+
import utils from "../utils.js";
|
|
21
|
+
import { checkDeclaredContentLength, checkMaterializedSize, cleanFormDataContentType, encodeBodyIfNeeded, handleFetchCaughtError } from "./fetch-helpers.js";
|
|
22
|
+
import type { ComposedSignal, PendingBodyErrorRef } from "./fetch-helpers.js";
|
|
23
|
+
|
|
24
|
+
// btoa is a global in Node 16+ and browsers; accessed via globalThis for no-DOM lib compat
|
|
25
|
+
const _btoa: (data: string) => string = (
|
|
26
|
+
globalThis as unknown as Record<string, unknown>
|
|
27
|
+
)["btoa"] as (data: string) => string;
|
|
28
|
+
type AnyConstructor = new (...args: Array<unknown>) => unknown;
|
|
29
|
+
type FetchFn = (input: unknown, init?: unknown) => Promise<AnyResponse>;
|
|
30
|
+
type AnyResponse = {
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
33
|
+
body: unknown;
|
|
34
|
+
headers: {
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
get: (name: string) => null | string;
|
|
37
|
+
has: (name: string) => boolean;
|
|
38
|
+
};
|
|
39
|
+
status: number;
|
|
40
|
+
statusText: string;
|
|
41
|
+
};
|
|
42
|
+
type AnyRequest = {
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
45
|
+
body: null | { cancel: () => Promise<void>; };
|
|
46
|
+
headers: {
|
|
47
|
+
get: (name: string) => null | string;
|
|
48
|
+
has: (name: string) => boolean;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
type AnyReadableStream = {
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
cancel: () => Promise<void>;
|
|
54
|
+
};
|
|
55
|
+
type AnyTextEncoder = { encode: (str: string) => Uint8Array; };
|
|
56
|
+
|
|
57
|
+
const DEFAULT_CHUNK_SIZE = 64 * 1024;
|
|
58
|
+
|
|
59
|
+
const { isFunction } = utils;
|
|
60
|
+
|
|
61
|
+
// Web stream bodies this adapter has sent. fetch can read a stream without leaving it locked,
|
|
62
|
+
// and there's no portable "disturbed" check, so a second send is caught here.
|
|
63
|
+
const sentStreamBodies = new WeakSet<object>();
|
|
64
|
+
|
|
65
|
+
// A stream body can be read once. Middleware that calls next() again after a try read it
|
|
66
|
+
// would send an empty body (Node stream) or get an uncoded TypeError from fetch (web stream).
|
|
67
|
+
const rejectUsedStreamBody = (data: unknown, config: InternalFaxiosRequestConfig): void => {
|
|
68
|
+
const body = data as { bodyUsed?: unknown; destroyed?: unknown; locked?: unknown; readableDidRead?: unknown; readableEnded?: unknown; };
|
|
69
|
+
const isWebStream = utils.isReadableStream!(data);
|
|
70
|
+
if (
|
|
71
|
+
(isWebStream && (body.locked === true || sentStreamBodies.has(body))) ||
|
|
72
|
+
((utils.isRequest!(data) || utils.isResponse!(data)) && body.bodyUsed === true) ||
|
|
73
|
+
(utils.isStream(data) && (body.readableDidRead === true || body.readableEnded === true || body.destroyed === true))
|
|
74
|
+
) {
|
|
75
|
+
throw new FaxiosError(
|
|
76
|
+
"Request body stream was already used and can't be sent again; pass a new body for each try",
|
|
77
|
+
FaxiosError.ERR_BAD_REQUEST,
|
|
78
|
+
config
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (isWebStream) sentStreamBodies.add(body);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Server runtimes default User-Agent to their own (e.g. "node"), so name faxios there.
|
|
85
|
+
// Browsers send their own User-Agent; setting one makes every cross-origin request
|
|
86
|
+
// need a CORS preflight that most APIs don't allow.
|
|
87
|
+
const setsUserAgent = !platform.hasStandardBrowserEnv && !platform.hasStandardBrowserWebWorkerEnv;
|
|
88
|
+
const applyDefaultUserAgent = (headers: FaxiosRequestHeaders): void => {
|
|
89
|
+
if (setsUserAgent) headers.set("User-Agent", "faxios/" + VERSION, false);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Encode a UTF-8 string to a Latin-1 byte string for use with btoa().
|
|
94
|
+
* This is a modern replacement for the deprecated unescape(encodeURIComponent(str)) pattern.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} str The string to encode
|
|
97
|
+
*
|
|
98
|
+
* @returns {string} UTF-8 bytes as a Latin-1 string
|
|
99
|
+
*/
|
|
100
|
+
const encodeUTF8 = (str: string): string =>
|
|
101
|
+
encodeURIComponent(str).replace(
|
|
102
|
+
/%([0-9A-F]{2})/gi,
|
|
103
|
+
(_: string, hex: string) => String.fromCharCode(parseInt(hex, 16))
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Node's WHATWG URL parser returns `username` and `password` percent-encoded.
|
|
107
|
+
// Decode before composing the `auth` option so credentials such as
|
|
108
|
+
// `my%40email.com:pass` are sent as `my@email.com:pass`. Falls back to the
|
|
109
|
+
// original value for malformed input so a bad encoding never throws.
|
|
110
|
+
const decodeURIComponentSafe = (value: unknown): unknown => {
|
|
111
|
+
if (!utils.isString(value)) {
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
return decodeURIComponent(value as string);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const test = (
|
|
124
|
+
fn: (...args: Array<unknown>) => unknown,
|
|
125
|
+
...args: Array<unknown>
|
|
126
|
+
): boolean => {
|
|
127
|
+
try {
|
|
128
|
+
return !!fn(...args);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const maybeWithAuthCredentials = (url: string): boolean => {
|
|
136
|
+
const protocolIndex = url.indexOf("://");
|
|
137
|
+
const urlToCheck = protocolIndex !== -1 ? url.slice(protocolIndex + 3) : url;
|
|
138
|
+
// Only `@` indicates credentials in the authority; `:` alone is a port separator.
|
|
139
|
+
return urlToCheck.includes("@");
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// RequestInit members faxios sets itself; the same keys in `fetchOptions` are ignored.
|
|
143
|
+
const FAXIOS_INIT_KEYS: Record<string, true> = {
|
|
144
|
+
body: true,
|
|
145
|
+
credentials: true,
|
|
146
|
+
duplex: true,
|
|
147
|
+
headers: true,
|
|
148
|
+
method: true,
|
|
149
|
+
signal: true,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Spec defaults written as own init members when the caller sets none. undici converts the init
|
|
153
|
+
// into a plain dictionary and reads absent members from it, so an absent member still picks up a
|
|
154
|
+
// polluted Object.prototype value; an own default leaves nothing to inherit.
|
|
155
|
+
const REQUEST_INIT_DEFAULTS: Record<string, unknown> = {
|
|
156
|
+
cache: "default",
|
|
157
|
+
integrity: "",
|
|
158
|
+
keepalive: false,
|
|
159
|
+
mode: "cors",
|
|
160
|
+
redirect: "follow",
|
|
161
|
+
referrer: "about:client",
|
|
162
|
+
referrerPolicy: "",
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Where undici (Node's fetch) keeps its global dispatcher.
|
|
166
|
+
const UNDICI_GLOBAL_DISPATCHER = Symbol.for("undici.globalDispatcher.1");
|
|
167
|
+
|
|
168
|
+
type FetchEnv = {
|
|
169
|
+
fetch?: unknown;
|
|
170
|
+
Request?: unknown;
|
|
171
|
+
Response?: unknown;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// eslint-disable-next-line sonarjs/function-return-type
|
|
175
|
+
const factory = (env: FetchEnv) => {
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
177
|
+
const globalObject: Record<string, unknown> = utils.global ?? globalThis;
|
|
178
|
+
const ReadableStream = globalObject["ReadableStream"] as
|
|
179
|
+
| (AnyConstructor & { prototype: AnyReadableStream; })
|
|
180
|
+
| undefined;
|
|
181
|
+
const TextEncoder = globalObject["TextEncoder"] as
|
|
182
|
+
| (new () => AnyTextEncoder)
|
|
183
|
+
| undefined;
|
|
184
|
+
|
|
185
|
+
// `undefined` falls back to the global constructor; `null` disables it.
|
|
186
|
+
const envFetch = env.fetch as FetchFn | undefined;
|
|
187
|
+
const Request = (env.Request === undefined ? globalObject["Request"] : env.Request) as AnyConstructor | undefined;
|
|
188
|
+
const Response = (env.Response === undefined ? globalObject["Response"] : env.Response) as AnyConstructor | undefined;
|
|
189
|
+
const _globalFetch = (globalThis as unknown as Record<string, unknown>)[
|
|
190
|
+
"fetch"
|
|
191
|
+
];
|
|
192
|
+
const isFetchSupported = envFetch
|
|
193
|
+
? isFunction(envFetch)
|
|
194
|
+
: isFunction(_globalFetch);
|
|
195
|
+
const isRequestSupported = isFunction(Request);
|
|
196
|
+
const isResponseSupported = isFunction(Response);
|
|
197
|
+
|
|
198
|
+
if (!isFetchSupported) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Only members this runtime's Request exposes, so an engine that rejects members it doesn't
|
|
203
|
+
// implement (Cloudflare Workers) is never sent one.
|
|
204
|
+
const requestInitDefaults = Object.entries(REQUEST_INIT_DEFAULTS).filter(([ key ]) =>
|
|
205
|
+
isRequestSupported && utils.hasOwnInPrototypeChain((Request as AnyConstructor).prototype, key));
|
|
206
|
+
|
|
207
|
+
// Null prototype: WebIDL reads every RequestInit member with Get, which walks the prototype
|
|
208
|
+
// chain, so a plain object literal would let a polluted Object.prototype.redirect, .integrity,
|
|
209
|
+
// .mode or undici .dispatcher apply to every request. Every own enumerable key of fetchOptions
|
|
210
|
+
// is copied, so runtime extensions (Next.js `next`, Workers `cf`, Bun `tls`) still pass through;
|
|
211
|
+
// inherited keys never do. The caller sets signal, method, headers, body, duplex, credentials.
|
|
212
|
+
const initFromFetchOptions = (fetchOptions: unknown): Record<string, unknown> => {
|
|
213
|
+
const init: Record<string, unknown> = Object.create(null);
|
|
214
|
+
for (const [ key, value ] of requestInitDefaults) init[key] = value;
|
|
215
|
+
// Node's global fetch reads `dispatcher` the same way; pin it to undici's own global one.
|
|
216
|
+
if (!envFetch && utils.hasOwnProp(globalThis, UNDICI_GLOBAL_DISPATCHER)) {
|
|
217
|
+
init["dispatcher"] = (globalThis as Record<PropertyKey, unknown>)[UNDICI_GLOBAL_DISPATCHER];
|
|
218
|
+
}
|
|
219
|
+
if (utils.isObject(fetchOptions)) {
|
|
220
|
+
for (const key of Object.keys(fetchOptions)) {
|
|
221
|
+
if (!utils.hasOwnProp(FAXIOS_INIT_KEYS, key)) init[key] = (fetchOptions as Record<string, unknown>)[key];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return init;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const isReadableStreamSupported = isFunction(ReadableStream);
|
|
228
|
+
|
|
229
|
+
const encodeText: (str: string) => Promise<Uint8Array> | Uint8Array =
|
|
230
|
+
typeof TextEncoder === "function"
|
|
231
|
+
? (
|
|
232
|
+
(encoder: AnyTextEncoder) => (str: string) =>
|
|
233
|
+
encoder.encode(str)
|
|
234
|
+
)(new TextEncoder())
|
|
235
|
+
: async (str: string) =>
|
|
236
|
+
new Uint8Array(
|
|
237
|
+
await (
|
|
238
|
+
new (Request as AnyConstructor)(str) as AnyRequest
|
|
239
|
+
).arrayBuffer()
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// A Node stream body reaches fetch twice: once in `new Request(url, init)` and again in
|
|
243
|
+
// `fetch(request, init)`. undici (Node 26.11+) starts reading an async iterable in both,
|
|
244
|
+
// so the first chunk goes to the discarded copy. One web stream is read only once.
|
|
245
|
+
// Web stream bodies must yield bytes, so string chunks (e.g. `Readable.from(["a"])`) are encoded.
|
|
246
|
+
const from = (ReadableStream as undefined | { from?: (source: unknown) => unknown; })?.from;
|
|
247
|
+
const streamFrom = typeof from === "function" ? from.bind(ReadableStream) : undefined;
|
|
248
|
+
async function* toByteChunks(source: AsyncIterable<unknown>) {
|
|
249
|
+
for await (const chunk of source) yield typeof chunk === "string" ? await encodeText(chunk) : chunk;
|
|
250
|
+
}
|
|
251
|
+
const toWebStream = (body: unknown): unknown =>
|
|
252
|
+
(streamFrom && utils.isStream(body) && !utils.isFormData(body)
|
|
253
|
+
? streamFrom(toByteChunks(body as AsyncIterable<unknown>))
|
|
254
|
+
: body);
|
|
255
|
+
|
|
256
|
+
const supportsRequestStream =
|
|
257
|
+
isRequestSupported &&
|
|
258
|
+
isReadableStreamSupported &&
|
|
259
|
+
test(() => {
|
|
260
|
+
let duplexAccessed = false;
|
|
261
|
+
|
|
262
|
+
const request = new (Request as AnyConstructor)(platform.origin, {
|
|
263
|
+
body: new (ReadableStream as AnyConstructor)(),
|
|
264
|
+
method: "POST",
|
|
265
|
+
get duplex() {
|
|
266
|
+
duplexAccessed = true;
|
|
267
|
+
return "half";
|
|
268
|
+
},
|
|
269
|
+
}) as AnyRequest;
|
|
270
|
+
|
|
271
|
+
const hasContentType = request.headers.has("Content-Type");
|
|
272
|
+
|
|
273
|
+
if (request.body != null) {
|
|
274
|
+
request.body.cancel().catch(() => {});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
278
|
+
return duplexAccessed && !hasContentType;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const supportsResponseStream =
|
|
282
|
+
isResponseSupported &&
|
|
283
|
+
isReadableStreamSupported &&
|
|
284
|
+
test(() =>
|
|
285
|
+
utils.isReadableStream!(
|
|
286
|
+
(new (Response as AnyConstructor)("") as AnyResponse).body
|
|
287
|
+
)
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const resolvers: Record<
|
|
291
|
+
string,
|
|
292
|
+
((res: AnyResponse, config?: unknown) => unknown) | false
|
|
293
|
+
> = {
|
|
294
|
+
stream: supportsResponseStream && ((res: AnyResponse) => res.body),
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
298
|
+
isFetchSupported &&
|
|
299
|
+
(() => {
|
|
300
|
+
[ "text", "arrayBuffer", "blob", "formData", "stream" ].forEach(type => {
|
|
301
|
+
!resolvers[type] &&
|
|
302
|
+
(resolvers[type] = (res: AnyResponse, config?: unknown) => {
|
|
303
|
+
const method = (res as Record<string, unknown>)[type];
|
|
304
|
+
|
|
305
|
+
if (method) {
|
|
306
|
+
return (method as (this: AnyResponse) => unknown).call(res);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
throw new FaxiosError(
|
|
310
|
+
`Response type '${type}' is not supported`,
|
|
311
|
+
FaxiosError.ERR_NOT_SUPPORT,
|
|
312
|
+
config as InternalFaxiosRequestConfig
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
})();
|
|
317
|
+
|
|
318
|
+
const getBodyLength = async (body: unknown): Promise<number | undefined> => {
|
|
319
|
+
if (body == null) {
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (utils.isBlob(body)) {
|
|
324
|
+
return (body as { size: number; }).size;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (utils.isSpecCompliantForm(body)) {
|
|
328
|
+
const _request = new (Request as AnyConstructor)(platform.origin, {
|
|
329
|
+
method: "POST",
|
|
330
|
+
body,
|
|
331
|
+
}) as AnyRequest;
|
|
332
|
+
return (await _request.arrayBuffer()).byteLength;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (utils.isArrayBufferView(body) || utils.isArrayBuffer(body)) {
|
|
336
|
+
return (body as ArrayBuffer | ArrayBufferView).byteLength;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (utils.isURLSearchParams(body)) {
|
|
340
|
+
body = body + "";
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (utils.isString(body)) {
|
|
344
|
+
return (await encodeText(body as string)).byteLength;
|
|
345
|
+
}
|
|
346
|
+
return undefined;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const resolveBodyLength = async (
|
|
350
|
+
headers: FaxiosRequestHeaders,
|
|
351
|
+
body: unknown
|
|
352
|
+
): Promise<number | undefined> => {
|
|
353
|
+
const length = utils.toFiniteNumber(headers.getContentLength());
|
|
354
|
+
|
|
355
|
+
return length == null ? getBodyLength(body) : length;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const normalizeCredentials = (wc: boolean | string): string => {
|
|
359
|
+
if (utils.isString(wc)) return wc as string;
|
|
360
|
+
return wc ? "include" : "omit";
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// HTTP basic authentication: extract auth from config or URL, set Authorization header,
|
|
364
|
+
// strip credentials from URL. Returns the (possibly updated) URL.
|
|
365
|
+
const applyAuthToRequest = (
|
|
366
|
+
url: string,
|
|
367
|
+
headers: FaxiosRequestHeaders,
|
|
368
|
+
own: (key: string) => unknown
|
|
369
|
+
): string => {
|
|
370
|
+
let auth: undefined | { password: unknown; username: unknown; };
|
|
371
|
+
const configAuth = own("auth");
|
|
372
|
+
if (configAuth) {
|
|
373
|
+
auth = {
|
|
374
|
+
username: utils.getSafeProp(configAuth, "username") || "",
|
|
375
|
+
password: utils.getSafeProp(configAuth, "password") || "",
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (maybeWithAuthCredentials(url)) {
|
|
379
|
+
const parsedURL = new URL(url, platform.origin);
|
|
380
|
+
if (!auth && (parsedURL.username || parsedURL.password)) {
|
|
381
|
+
auth = {
|
|
382
|
+
username: decodeURIComponentSafe(parsedURL.username),
|
|
383
|
+
password: decodeURIComponentSafe(parsedURL.password),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (parsedURL.username || parsedURL.password) {
|
|
387
|
+
parsedURL.username = "";
|
|
388
|
+
parsedURL.password = "";
|
|
389
|
+
url = parsedURL.href;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (auth) {
|
|
393
|
+
headers.delete("authorization");
|
|
394
|
+
headers.set(
|
|
395
|
+
"Authorization",
|
|
396
|
+
"Basic " +
|
|
397
|
+
_btoa(
|
|
398
|
+
encodeUTF8(
|
|
399
|
+
String(auth.username || "") + ":" + String(auth.password || "")
|
|
400
|
+
)
|
|
401
|
+
)
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
return url;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// Pre-flight size checks: data: URL content-length and known-size body maxBodyLength.
|
|
408
|
+
// Returns requestContentLength if computed, else undefined.
|
|
409
|
+
const checkPreflightLimits = async (
|
|
410
|
+
url: string,
|
|
411
|
+
data: unknown,
|
|
412
|
+
method: string,
|
|
413
|
+
hasMaxContentLength: boolean,
|
|
414
|
+
maxContentLength: number,
|
|
415
|
+
hasMaxBodyLength: boolean,
|
|
416
|
+
maxBodyLength: number,
|
|
417
|
+
config: InternalFaxiosRequestConfig,
|
|
418
|
+
request: unknown
|
|
419
|
+
): Promise<number | undefined> => {
|
|
420
|
+
// Enforce maxContentLength for data: URLs up-front so we never materialize
|
|
421
|
+
// an oversized payload. The HTTP adapter applies the same check.
|
|
422
|
+
if (
|
|
423
|
+
hasMaxContentLength &&
|
|
424
|
+
typeof url === "string" &&
|
|
425
|
+
url.startsWith("data:")
|
|
426
|
+
) {
|
|
427
|
+
const estimated = estimateDataURLDecodedBytes(url);
|
|
428
|
+
if (estimated > maxContentLength) {
|
|
429
|
+
throw new FaxiosError(
|
|
430
|
+
"maxContentLength size of " + maxContentLength + " exceeded",
|
|
431
|
+
FaxiosError.ERR_BAD_RESPONSE,
|
|
432
|
+
config,
|
|
433
|
+
request
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// Enforce maxBodyLength against known-size bodies before dispatch using
|
|
438
|
+
// the body's actual size — never a caller-declared Content-Length.
|
|
439
|
+
if (hasMaxBodyLength && method !== "get" && method !== "head") {
|
|
440
|
+
const outboundLength = await getBodyLength(data);
|
|
441
|
+
if (typeof outboundLength === "number" && isFinite(outboundLength)) {
|
|
442
|
+
if (outboundLength > maxBodyLength) {
|
|
443
|
+
throw new FaxiosError(
|
|
444
|
+
"Request body larger than maxBodyLength limit",
|
|
445
|
+
FaxiosError.ERR_BAD_REQUEST,
|
|
446
|
+
config,
|
|
447
|
+
request
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
return outboundLength;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return undefined;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// Wrap a Request body with upload progress tracking and optional maxBodyLength enforcement.
|
|
457
|
+
const applyUploadProgress = (
|
|
458
|
+
_request: AnyRequest,
|
|
459
|
+
data: unknown,
|
|
460
|
+
headers: FaxiosRequestHeaders,
|
|
461
|
+
requestContentLength: number | undefined,
|
|
462
|
+
onUploadProgress: unknown,
|
|
463
|
+
_trackStream: (stream: unknown, onProgress?: (bytes: number) => void, flush?: () => void) => unknown
|
|
464
|
+
): unknown => {
|
|
465
|
+
let contentTypeHeader: null | string;
|
|
466
|
+
if (
|
|
467
|
+
utils.isFormData(data) &&
|
|
468
|
+
(contentTypeHeader = _request.headers.get("content-type"))
|
|
469
|
+
) {
|
|
470
|
+
headers.setContentType(contentTypeHeader);
|
|
471
|
+
}
|
|
472
|
+
if (_request.body) {
|
|
473
|
+
const [ onProgress, flush ] = onUploadProgress
|
|
474
|
+
? progressEventDecorator(
|
|
475
|
+
requestContentLength,
|
|
476
|
+
progressEventReducer(
|
|
477
|
+
asyncDecorator(
|
|
478
|
+
onUploadProgress as (...args: Array<unknown>) => unknown
|
|
479
|
+
),
|
|
480
|
+
false
|
|
481
|
+
)
|
|
482
|
+
)
|
|
483
|
+
: [];
|
|
484
|
+
return _trackStream(_request.body, onProgress, flush);
|
|
485
|
+
}
|
|
486
|
+
return data;
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// Set up request body streaming with upload progress and/or maxBodyLength enforcement.
|
|
490
|
+
const buildUploadStream = async (
|
|
491
|
+
data: unknown,
|
|
492
|
+
url: string,
|
|
493
|
+
method: string,
|
|
494
|
+
headers: FaxiosRequestHeaders,
|
|
495
|
+
onUploadProgress: unknown,
|
|
496
|
+
mustEnforceStreamBody: boolean,
|
|
497
|
+
requestContentLength: number | undefined,
|
|
498
|
+
hasMaxBodyLength: boolean,
|
|
499
|
+
maxBodyLength: number,
|
|
500
|
+
config: InternalFaxiosRequestConfig,
|
|
501
|
+
request: unknown,
|
|
502
|
+
pendingBodyErrorRef: PendingBodyErrorRef
|
|
503
|
+
): Promise<{ data: unknown; requestContentLength: number | undefined; }> => {
|
|
504
|
+
const _makeBodyLengthError = () =>
|
|
505
|
+
new FaxiosError(
|
|
506
|
+
"Request body larger than maxBodyLength limit",
|
|
507
|
+
FaxiosError.ERR_BAD_REQUEST,
|
|
508
|
+
config,
|
|
509
|
+
request
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
const _trackStream = (
|
|
513
|
+
stream: unknown,
|
|
514
|
+
onProgress?: (bytes: number) => void,
|
|
515
|
+
flush?: () => void
|
|
516
|
+
) =>
|
|
517
|
+
trackStream(
|
|
518
|
+
stream,
|
|
519
|
+
DEFAULT_CHUNK_SIZE,
|
|
520
|
+
(loadedBytes: number) => {
|
|
521
|
+
if (hasMaxBodyLength && loadedBytes > maxBodyLength) {
|
|
522
|
+
pendingBodyErrorRef.value = _makeBodyLengthError();
|
|
523
|
+
throw pendingBodyErrorRef.value;
|
|
524
|
+
}
|
|
525
|
+
onProgress && onProgress(loadedBytes);
|
|
526
|
+
},
|
|
527
|
+
flush
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
if (
|
|
531
|
+
supportsRequestStream &&
|
|
532
|
+
method !== "get" &&
|
|
533
|
+
method !== "head" &&
|
|
534
|
+
(onUploadProgress || mustEnforceStreamBody)
|
|
535
|
+
) {
|
|
536
|
+
requestContentLength =
|
|
537
|
+
requestContentLength == null
|
|
538
|
+
? await resolveBodyLength(headers, data)
|
|
539
|
+
: requestContentLength;
|
|
540
|
+
|
|
541
|
+
// A declared length of 0 is only trusted to skip the wrap when we are
|
|
542
|
+
// not enforcing a stream limit (which must not rely on that header).
|
|
543
|
+
if (requestContentLength !== 0 || mustEnforceStreamBody) {
|
|
544
|
+
const _request = new (Request as AnyConstructor)(url, {
|
|
545
|
+
method: "POST",
|
|
546
|
+
body: data,
|
|
547
|
+
duplex: "half",
|
|
548
|
+
}) as AnyRequest;
|
|
549
|
+
data = applyUploadProgress(_request, data, headers, requestContentLength, onUploadProgress, _trackStream);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
else if (
|
|
553
|
+
mustEnforceStreamBody &&
|
|
554
|
+
!isRequestSupported &&
|
|
555
|
+
isReadableStreamSupported &&
|
|
556
|
+
method !== "get" &&
|
|
557
|
+
method !== "head"
|
|
558
|
+
) {
|
|
559
|
+
data = _trackStream(data);
|
|
560
|
+
}
|
|
561
|
+
else if (
|
|
562
|
+
mustEnforceStreamBody &&
|
|
563
|
+
isRequestSupported &&
|
|
564
|
+
!supportsRequestStream &&
|
|
565
|
+
method !== "get" &&
|
|
566
|
+
method !== "head"
|
|
567
|
+
) {
|
|
568
|
+
throw new FaxiosError(
|
|
569
|
+
"Stream request bodies are not supported by the current fetch implementation",
|
|
570
|
+
FaxiosError.ERR_NOT_SUPPORT,
|
|
571
|
+
config,
|
|
572
|
+
request
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return { data, requestContentLength };
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// Wrap the response body stream with download progress tracking and maxContentLength enforcement.
|
|
580
|
+
const buildDownloadStream = (
|
|
581
|
+
response: AnyResponse,
|
|
582
|
+
responseHeaders: FaxiosHeaders,
|
|
583
|
+
onDownloadProgress: unknown,
|
|
584
|
+
hasMaxContentLength: boolean,
|
|
585
|
+
maxContentLength: number,
|
|
586
|
+
isStreamResponse: boolean,
|
|
587
|
+
unsubscribe: (() => void) | false | undefined,
|
|
588
|
+
config: InternalFaxiosRequestConfig,
|
|
589
|
+
request: unknown
|
|
590
|
+
): AnyResponse => {
|
|
591
|
+
const needsWrapping = onDownloadProgress || hasMaxContentLength || (isStreamResponse && unsubscribe);
|
|
592
|
+
if (!supportsResponseStream || !response.body || !needsWrapping) {
|
|
593
|
+
return response;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const options: Record<string, unknown> = {};
|
|
597
|
+
[ "status", "statusText", "headers" ].forEach(prop => {
|
|
598
|
+
options[prop] = response[prop];
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
const responseContentLength = utils.toFiniteNumber(
|
|
602
|
+
(responseHeaders.getContentLength as () => unknown)()
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
const [ onProgress, flush ] = onDownloadProgress
|
|
606
|
+
? progressEventDecorator(
|
|
607
|
+
responseContentLength,
|
|
608
|
+
progressEventReducer(
|
|
609
|
+
asyncDecorator(
|
|
610
|
+
onDownloadProgress as (...args: Array<unknown>) => unknown
|
|
611
|
+
),
|
|
612
|
+
true
|
|
613
|
+
)
|
|
614
|
+
)
|
|
615
|
+
: [];
|
|
616
|
+
|
|
617
|
+
let bytesRead = 0;
|
|
618
|
+
const onChunkProgress = (loadedBytes: number): void => {
|
|
619
|
+
if (hasMaxContentLength) {
|
|
620
|
+
bytesRead = loadedBytes;
|
|
621
|
+
if (bytesRead > maxContentLength) {
|
|
622
|
+
throw new FaxiosError(
|
|
623
|
+
"maxContentLength size of " + maxContentLength + " exceeded",
|
|
624
|
+
FaxiosError.ERR_BAD_RESPONSE,
|
|
625
|
+
config,
|
|
626
|
+
request
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
onProgress && onProgress(loadedBytes);
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
return new (Response as AnyConstructor)(
|
|
634
|
+
trackStream(
|
|
635
|
+
response.body,
|
|
636
|
+
DEFAULT_CHUNK_SIZE,
|
|
637
|
+
onChunkProgress,
|
|
638
|
+
() => {
|
|
639
|
+
flush && flush();
|
|
640
|
+
unsubscribe && unsubscribe();
|
|
641
|
+
}
|
|
642
|
+
),
|
|
643
|
+
options
|
|
644
|
+
) as AnyResponse;
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
const unsubscribeOnComplete = (isStreamResponse: boolean, unsubscribe: (() => void) | undefined) => {
|
|
648
|
+
if (!isStreamResponse) unsubscribe?.();
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
return async (config: InternalFaxiosRequestConfig) => {
|
|
652
|
+
const _resolved = prepareRequest(config) as InternalFaxiosRequestConfig & {
|
|
653
|
+
fetchOptions?: Record<string, unknown>;
|
|
654
|
+
withCredentials?: boolean | string;
|
|
655
|
+
};
|
|
656
|
+
let {
|
|
657
|
+
url,
|
|
658
|
+
method,
|
|
659
|
+
data,
|
|
660
|
+
signal,
|
|
661
|
+
timeout,
|
|
662
|
+
timeoutErrorMessage,
|
|
663
|
+
onDownloadProgress,
|
|
664
|
+
onUploadProgress,
|
|
665
|
+
responseType,
|
|
666
|
+
fetchOptions,
|
|
667
|
+
maxContentLength,
|
|
668
|
+
maxBodyLength,
|
|
669
|
+
} = _resolved;
|
|
670
|
+
let headers = _resolved.headers;
|
|
671
|
+
let withCredentials: boolean | string =
|
|
672
|
+
_resolved.withCredentials ?? "same-origin";
|
|
673
|
+
|
|
674
|
+
const hasMaxContentLength =
|
|
675
|
+
utils.isNumber(maxContentLength) && maxContentLength! > -1;
|
|
676
|
+
const hasMaxBodyLength =
|
|
677
|
+
utils.isNumber(maxBodyLength) && maxBodyLength! > -1;
|
|
678
|
+
const own = (key: string): unknown =>
|
|
679
|
+
utils.hasOwnProp(config, key)
|
|
680
|
+
? (config as unknown as Record<string, unknown>)[key]
|
|
681
|
+
: undefined;
|
|
682
|
+
|
|
683
|
+
const _fetch: FetchFn = (envFetch || (globalThis as unknown as Record<string, unknown>)["fetch"]) as FetchFn;
|
|
684
|
+
|
|
685
|
+
if (timeout != null && !Number.isFinite(Number(timeout))) {
|
|
686
|
+
throw new FaxiosError(
|
|
687
|
+
"error trying to parse `config.timeout` to int",
|
|
688
|
+
FaxiosError.ERR_BAD_OPTION_VALUE,
|
|
689
|
+
config
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
responseType = (
|
|
694
|
+
responseType ? (responseType + "").toLowerCase() : "text"
|
|
695
|
+
) as typeof responseType;
|
|
696
|
+
|
|
697
|
+
const composedSignal = composeSignals(
|
|
698
|
+
[ signal ],
|
|
699
|
+
timeout,
|
|
700
|
+
timeoutErrorMessage
|
|
701
|
+
) as ComposedSignal | undefined;
|
|
702
|
+
|
|
703
|
+
const unsubscribe = composedSignal?.unsubscribe?.bind(composedSignal);
|
|
704
|
+
|
|
705
|
+
let request: null | Request = null;
|
|
706
|
+
const pendingBodyErrorRef: PendingBodyErrorRef = { value: null };
|
|
707
|
+
|
|
708
|
+
try {
|
|
709
|
+
rejectUsedStreamBody(data, config);
|
|
710
|
+
|
|
711
|
+
url = applyAuthToRequest(url!, headers, own);
|
|
712
|
+
|
|
713
|
+
const preflightContentLength = await checkPreflightLimits(
|
|
714
|
+
url,
|
|
715
|
+
data,
|
|
716
|
+
method!,
|
|
717
|
+
hasMaxContentLength,
|
|
718
|
+
maxContentLength!,
|
|
719
|
+
hasMaxBodyLength,
|
|
720
|
+
maxBodyLength!,
|
|
721
|
+
config,
|
|
722
|
+
request
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
// A streamed body under maxBodyLength must be counted as fetch consumes
|
|
726
|
+
// it; its size is never trusted from a caller-declared Content-Length.
|
|
727
|
+
const isStreamLike = utils.isReadableStream!(data) || utils.isStream(data);
|
|
728
|
+
const mustEnforceStreamBody = hasMaxBodyLength && isStreamLike;
|
|
729
|
+
|
|
730
|
+
({ data } = await buildUploadStream(
|
|
731
|
+
data,
|
|
732
|
+
url,
|
|
733
|
+
method!,
|
|
734
|
+
headers,
|
|
735
|
+
onUploadProgress,
|
|
736
|
+
mustEnforceStreamBody,
|
|
737
|
+
preflightContentLength,
|
|
738
|
+
hasMaxBodyLength,
|
|
739
|
+
maxBodyLength!,
|
|
740
|
+
config,
|
|
741
|
+
request,
|
|
742
|
+
pendingBodyErrorRef
|
|
743
|
+
));
|
|
744
|
+
|
|
745
|
+
withCredentials = normalizeCredentials(withCredentials);
|
|
746
|
+
|
|
747
|
+
// Cloudflare Workers throws when credentials are defined
|
|
748
|
+
// see https://github.com/cloudflare/workerd/issues/902
|
|
749
|
+
const isCredentialsSupported =
|
|
750
|
+
isRequestSupported &&
|
|
751
|
+
Request != null &&
|
|
752
|
+
"credentials" in (Request.prototype as object);
|
|
753
|
+
|
|
754
|
+
cleanFormDataContentType(data, headers);
|
|
755
|
+
|
|
756
|
+
applyDefaultUserAgent(headers);
|
|
757
|
+
|
|
758
|
+
const serializedHeaders = toByteStringHeaderObject(headers.normalize(false));
|
|
759
|
+
|
|
760
|
+
// Browsers auto-add "text/plain;charset=UTF-8" for string bodies when no Content-Type is set.
|
|
761
|
+
// Encoding as Uint8Array prevents the browser from injecting a content-type.
|
|
762
|
+
// Case-insensitive check: toByteStringHeaderObject may normalize to lowercase.
|
|
763
|
+
data = toWebStream(await encodeBodyIfNeeded(data, serializedHeaders, encodeText));
|
|
764
|
+
|
|
765
|
+
const resolvedOptions = initFromFetchOptions(fetchOptions);
|
|
766
|
+
resolvedOptions["signal"] = composedSignal;
|
|
767
|
+
resolvedOptions["method"] = (method as string).toUpperCase();
|
|
768
|
+
resolvedOptions["headers"] = serializedHeaders;
|
|
769
|
+
resolvedOptions["body"] = data;
|
|
770
|
+
resolvedOptions["duplex"] = "half";
|
|
771
|
+
resolvedOptions["credentials"] = isCredentialsSupported ? withCredentials : undefined;
|
|
772
|
+
|
|
773
|
+
request = isRequestSupported
|
|
774
|
+
? new (Request as AnyConstructor)(url, resolvedOptions) as Request
|
|
775
|
+
: null;
|
|
776
|
+
|
|
777
|
+
// Pass resolvedOptions as the fetch init even when a Request object is supplied, so the
|
|
778
|
+
// same null-prototype members apply to both calls.
|
|
779
|
+
let response = await (isRequestSupported
|
|
780
|
+
? _fetch(request, resolvedOptions)
|
|
781
|
+
: _fetch(url, resolvedOptions));
|
|
782
|
+
|
|
783
|
+
const responseHeaders = FaxiosHeaders.from(response.headers);
|
|
784
|
+
|
|
785
|
+
checkDeclaredContentLength(responseHeaders, hasMaxContentLength, maxContentLength!, config, request);
|
|
786
|
+
|
|
787
|
+
const isStreamResponse =
|
|
788
|
+
supportsResponseStream &&
|
|
789
|
+
(responseType === "stream" || responseType === "response");
|
|
790
|
+
|
|
791
|
+
response = buildDownloadStream(
|
|
792
|
+
response,
|
|
793
|
+
responseHeaders,
|
|
794
|
+
onDownloadProgress,
|
|
795
|
+
hasMaxContentLength,
|
|
796
|
+
maxContentLength!,
|
|
797
|
+
isStreamResponse,
|
|
798
|
+
unsubscribe,
|
|
799
|
+
config,
|
|
800
|
+
request
|
|
801
|
+
);
|
|
802
|
+
|
|
803
|
+
responseType = responseType || "text";
|
|
804
|
+
|
|
805
|
+
const resolverKey = utils.findKey(resolvers, responseType) || "text";
|
|
806
|
+
const resolver = resolvers[resolverKey];
|
|
807
|
+
const responseData = await (resolver && resolver(response, config));
|
|
808
|
+
|
|
809
|
+
checkMaterializedSize(
|
|
810
|
+
responseData,
|
|
811
|
+
hasMaxContentLength,
|
|
812
|
+
maxContentLength!,
|
|
813
|
+
isStreamResponse,
|
|
814
|
+
supportsResponseStream,
|
|
815
|
+
config,
|
|
816
|
+
request
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
unsubscribeOnComplete(isStreamResponse, unsubscribe);
|
|
820
|
+
|
|
821
|
+
return await new Promise((resolve, reject) => {
|
|
822
|
+
settle(resolve, reject, {
|
|
823
|
+
data: responseData,
|
|
824
|
+
headers: FaxiosHeaders.from(response.headers),
|
|
825
|
+
status: response.status,
|
|
826
|
+
statusText: response.statusText,
|
|
827
|
+
config,
|
|
828
|
+
request,
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
catch (err) {
|
|
833
|
+
unsubscribe && unsubscribe();
|
|
834
|
+
throw handleFetchCaughtError(err, composedSignal, pendingBodyErrorRef, config, request);
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
const seedCache = new Map<unknown, unknown>();
|
|
840
|
+
|
|
841
|
+
export const getFetch = (config?: { env?: Record<string, unknown>; }) => {
|
|
842
|
+
// Own reads only: a polluted Object.prototype.fetch must never become the transport.
|
|
843
|
+
const env = config && utils.hasOwnProp(config, "env") ? config.env : undefined;
|
|
844
|
+
const own = (key: keyof FetchEnv): unknown => (env && utils.hasOwnProp(env, key) ? env[key] : undefined);
|
|
845
|
+
const fetch = own("fetch");
|
|
846
|
+
const Request = own("Request");
|
|
847
|
+
const Response = own("Response");
|
|
848
|
+
const seeds: Array<unknown> = [ Request, Response, fetch ];
|
|
849
|
+
|
|
850
|
+
const len = seeds.length;
|
|
851
|
+
let i = len;
|
|
852
|
+
let seed: unknown;
|
|
853
|
+
let target: unknown;
|
|
854
|
+
let map: Map<unknown, unknown> = seedCache;
|
|
855
|
+
|
|
856
|
+
while (i--) {
|
|
857
|
+
seed = seeds[i];
|
|
858
|
+
target = map.get(seed);
|
|
859
|
+
|
|
860
|
+
if (target === undefined) {
|
|
861
|
+
target = i ? new Map<unknown, unknown>() : factory({ fetch, Request, Response });
|
|
862
|
+
map.set(seed, target);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
map = target as Map<unknown, unknown>;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
return target;
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
getFetch();
|