fexios
Version:
Fetch based HTTP client with similar API to axios for browser and Node.js
519 lines (518 loc) • 16.7 kB
JavaScript
var O = /* @__PURE__ */ ((t) => (t.BODY_USED = "BODY_USED", t.NO_BODY_READER = "NO_BODY_READER", t.TIMEOUT = "TIMEOUT", t.NETWORK_ERROR = "NETWORK_ERROR", t.BODY_NOT_ALLOWED = "BODY_NOT_ALLOWED", t.HOOK_CONTEXT_CHANGED = "HOOK_CONTEXT_CHANGED", t.ABORTED_BY_HOOK = "ABORTED_BY_HOOK", t.INVALID_HOOK_CALLBACK = "INVALID_HOOK_CALLBACK", t.UNEXPECTED_HOOK_RETURN = "UNEXPECTED_HOOK_RETURN", t))(O || {});
class u extends Error {
constructor(s, r, e, o) {
super(r, o), this.code = s, this.context = e, this.name = "FexiosError";
}
}
class A extends u {
constructor(s, r, e) {
super(r.statusText, s, void 0, e), this.response = r, this.name = "FexiosResponseError";
}
}
const Q = (t) => !(t instanceof A) && t instanceof u;
function H(t, s = 2048) {
if (!(t instanceof Uint8Array))
throw new TypeError("Input must be a Uint8Array");
if (t.length === 0)
return !0;
const r = Math.min(
Math.max(t.length, 256),
s
), e = t.slice(0, r);
if (N(e))
return !1;
const o = j(e);
if (o.nullByteRatio > 0.05 || o.highByteRatio > 0.95)
return !1;
const i = ["utf-8", "utf-16le", "utf-16be", "iso-8859-1"];
let n = -1, f = !1;
for (const c of i)
try {
const a = new TextDecoder(c, { fatal: !0 }).decode(e), l = I(a);
l > n && (n = l, f = l > 0.7);
} catch {
continue;
}
return f;
}
function N(t) {
if (t.length < 4) return !1;
const s = [
[137, 80, 78, 71],
// PNG
[255, 216, 255],
// JPEG
[71, 73, 70],
// GIF
[37, 80, 68, 70],
// PDF
[80, 75, 3, 4],
// ZIP/Office documents
[80, 75, 5, 6],
// ZIP empty archive
[80, 75, 7, 8],
// ZIP spanned archive
[127, 69, 76, 70],
// ELF executable
[77, 90],
// Windows executable
[202, 254, 186, 190],
// Java class file
[0, 0, 1, 0],
// ICO
[82, 73, 70, 70]
// RIFF (AVI, WAV, etc.)
];
for (const r of s)
if (t.length >= r.length) {
let e = !0;
for (let o = 0; o < r.length; o++)
if (t[o] !== r[o]) {
e = !1;
break;
}
if (e) return !0;
}
return !1;
}
function j(t) {
let s = 0, r = 0, e = 0;
for (const o of t)
o === 0 && s++, o > 127 && r++, (o < 32 && o !== 9 && o !== 10 && o !== 13 || o === 127) && e++;
return {
nullByteRatio: s / t.length,
highByteRatio: r / t.length,
controlCharRatio: e / t.length
};
}
function I(t) {
if (t.length === 0) return 1;
let s = 1, r = 0;
for (let o = 0; o < t.length; o++) {
const n = t[o].charCodeAt(0);
n >= 32 && n <= 126 || n === 9 || n === 10 || n === 13 || n === 32 ? r++ : n > 127 && n < 65534 ? !P(n) && !W(n) && r++ : s -= 0.1;
}
const e = r / t.length;
return s *= e, q(t) && (s *= 1.1), Math.max(0, Math.min(1, s));
}
function P(t) {
return t >= 0 && t <= 31 || t >= 127 && t <= 159;
}
function W(t) {
return t >= 57344 && t <= 63743 || t >= 983040 && t <= 1048573 || t >= 1048576 && t <= 1114109;
}
function q(t) {
return [
/\b\w+\b/,
// Words
/[.!?]+\s/,
// Sentence endings
/\s+/,
// Whitespace
/[a-zA-Z]{3,}/,
// English words
/[\u4e00-\u9fa5]+/,
// Chinese characters
/\d+/
// Numbers
].some((r) => r.test(t));
}
function _(t) {
if (typeof t != "object" || t === null || Object.prototype.toString.call(t) !== "[object Object]")
return !1;
const s = Object.getPrototypeOf(t);
return s === Object.prototype || s === null;
}
function U(t, s = {}) {
const r = {};
return Object.entries(t).forEach(([e, o]) => {
o != null && (s.dropEmptyString && o === "" || (r[e] = o));
}), r;
}
class p {
constructor(s, r, e) {
this.rawResponse = s, this.data = r, this.ok = s.ok, this.status = s.status, this.statusText = s.statusText, this.headers = s.headers, Object.entries(e || {}).forEach(([o, i]) => {
this[o] = i;
});
}
}
async function K(t, s, r) {
var f;
if (t.bodyUsed)
throw new u(
O.BODY_USED,
"Response body has already been used or locked"
);
const e = t.headers.get("content-type") || "", o = Number(t.headers.get("content-length")) || 0, i = (c, h) => h === "json" || c.startsWith("application/json"), n = (c, h, a) => a === "blob" || c.startsWith("image/") && !c.startsWith("image/svg") || c.startsWith("video/") || c.startsWith("audio/") || !H(h);
if ((t.status === 101 || t.status === 426 || t.headers.get("upgrade")) && typeof globalThis.WebSocket < "u") {
const c = new WebSocket(t.url);
return await new Promise((h, a) => {
c.onopen = h, c.onerror = a;
}), new p(t, c, {
ok: !0,
status: 101,
statusText: "Switching Protocols"
});
} else if (e.startsWith("text/event-stream") && !["text", "json"].includes(s || "") && typeof globalThis.EventSource < "u") {
const c = new EventSource(t.url);
return await new Promise((h, a) => {
c.onopen = h, c.onerror = a;
}), new p(t, c);
} else {
if (s === "stream")
return new p(
t,
t.body
);
{
const h = (f = t.clone().body) == null ? void 0 : f.getReader();
if (!h)
throw new u(
O.NO_BODY_READER,
"Failed to get ReadableStream from response body"
);
let a = new Uint8Array();
for (; ; ) {
const { done: b, value: y } = await h.read();
if (b) break;
if (y && (a = new Uint8Array([...a, ...y]), r && o > 0)) {
const g = Math.min(a.length / o, 1);
r(g, a);
}
}
const l = new p(t, void 0);
if (s === "arrayBuffer")
return l.data = a.buffer, l;
if (i(e, s))
try {
const b = new TextDecoder().decode(a);
l.data = JSON.parse(b);
} catch {
}
if (typeof l.data != "string" && n(e, a, s) ? l.data = new Blob([a], {
type: t.headers.get("content-type") || void 0
}) : l.data = new TextDecoder().decode(a), typeof l.data == "string" && s !== "text") {
const b = l.data.trim(), y = b[0], g = b[b.length - 1];
if (y === "{" && g === "}" || y === "[" && g === "]")
try {
l.data = JSON.parse(l.data);
} catch {
}
}
if (typeof l.data > "u" && (l.data = a.length > 0 ? a : void 0), l.ok)
return l;
throw new A(
`Request failed with status code ${t.status}`,
l
);
}
}
}
class x {
/**
* Build URLSearchParams from a record object with proper array handling
* @param query - The query object containing key-value pairs
* @returns URLSearchParams instance
*/
static makeSearchParams(s) {
const r = new URLSearchParams();
return Object.entries(s).forEach(([e, o]) => {
Array.isArray(o) ? o.forEach((i) => r.append(e, String(i))) : r.set(e, String(o));
}), r;
}
/**
* Build query string from a record object with proper array handling
* @param query - The query object containing key-value pairs
* @returns URL-encoded query string
*/
static makeQueryString(s) {
return this.makeSearchParams(s).toString();
}
}
function M(t) {
return t && t.__esModule && Object.prototype.hasOwnProperty.call(t, "default") ? t.default : t;
}
var D, B;
function Y() {
if (B) return D;
B = 1;
function t(s) {
var r = this.constructor.prototype[s], e = function() {
return r.apply(e, arguments);
};
return Object.setPrototypeOf(e, this.constructor.prototype), Object.getOwnPropertyNames(r).forEach(function(o) {
Object.defineProperty(e, o, Object.getOwnPropertyDescriptor(r, o));
}), e;
}
return t.prototype = Object.create(Function.prototype), D = t, D;
}
var v = Y();
const $ = /* @__PURE__ */ M(v);
class E extends $ {
constructor(s = {}) {
super("request"), this.baseConfigs = s, this.hooks = [], this.DEFAULT_CONFIGS = {
baseURL: "",
timeout: 60 * 1e3,
credentials: "same-origin",
headers: {},
query: {},
responseType: void 0
}, this.ALL_METHODS = [
"get",
"post",
"put",
"patch",
"delete",
"head",
"options",
"trace"
], this.METHODS_WITHOUT_BODY = [
"get",
"head",
"options",
"trace"
], this.interceptors = {
request: this.createInterceptor("beforeRequest"),
response: this.createInterceptor("afterResponse")
}, this.create = E.create, this.dropUndefinedAndNull = U, this.checkIsPlainObject = _, this.ALL_METHODS.forEach(this.createMethodShortcut.bind(this));
}
async request(s, r) {
var g, R, k, L;
let e = r = r || {};
typeof s == "string" || s instanceof URL ? e.url = s.toString() : typeof s == "object" && (e = { ...s, ...e }), e = await this.emit("beforeInit", e);
const o = r.baseURL || this.baseConfigs.baseURL || ((g = globalThis.location) == null ? void 0 : g.href), i = o ? new URL(o, (R = globalThis.location) == null ? void 0 : R.href) : void 0, n = new URL(e.url.toString(), i);
e.url = n.href, e.baseURL = i ? i.href : n.origin, e.headers = this.mergeHeaders(
this.baseConfigs.headers,
r.headers
);
const f = i == null ? void 0 : i.searchParams, c = new URLSearchParams(n.searchParams);
if (n.search = "", e.url = n.href, e.query = this.mergeQuery(
f,
// baseURL query (lowest priority)
this.baseConfigs.query,
// defaultOptions (baseOptions)
c,
// requestURL query (urlParams)
r.query
// requestOptions (highest priority)
), n.search = x.makeQueryString(e.query), e.url = n.toString(), this.METHODS_WITHOUT_BODY.includes(
(k = e.method) == null ? void 0 : k.toLocaleLowerCase()
) && e.body)
throw new u(
O.BODY_NOT_ALLOWED,
`Request method "${e.method}" does not allow body`
);
e = await this.emit("beforeRequest", e);
let h;
typeof e.body < "u" && e.body !== null && (e.body instanceof Blob || e.body instanceof FormData || e.body instanceof URLSearchParams ? h = e.body : typeof e.body == "object" && e.body !== null ? (h = JSON.stringify(e.body), e.headers["content-type"] = "application/json") : h = e.body), !((L = r.headers) != null && L["content-type"]) && h && (h instanceof FormData || h instanceof URLSearchParams ? delete e.headers["content-type"] : typeof h == "string" && typeof e.body == "object" ? e.headers["content-type"] = "application/json" : h instanceof Blob && (e.headers["content-type"] = h.type)), e.body = h, e = await this.emit("afterBodyTransformed", e);
const a = e.abortController || globalThis.AbortController ? new AbortController() : void 0, l = new Request(e.url, {
method: e.method || "GET",
credentials: e.credentials,
cache: e.cache,
mode: e.mode,
headers: e.headers,
body: e.body,
signal: a == null ? void 0 : a.signal
});
e.rawRequest = l, e = await this.emit("beforeActualFetch", e);
const b = e.timeout || this.baseConfigs.timeout || 60 * 1e3;
if (e.url.startsWith("ws"))
try {
const d = new WebSocket(e.url);
return await new Promise((w, T) => {
const m = setTimeout(() => {
T(
new u(
O.TIMEOUT,
`WebSocket connection timed out after ${b}ms`,
e
)
);
}, b);
d.onopen = () => {
clearTimeout(m), w();
}, d.onerror = (S) => {
clearTimeout(m), T(
new u(
O.NETWORK_ERROR,
"WebSocket connection failed",
e
)
);
}, d.onclose = (S) => {
S.code !== 1e3 && (clearTimeout(m), T(
new u(
O.NETWORK_ERROR,
`WebSocket closed with code ${S.code}`,
e
)
));
};
}), e.rawResponse = new Response(), e.response = new p(e.rawResponse, d, {
ok: !0,
status: 101,
statusText: "Switching Protocols"
}), e.data = d, e.headers = new Headers(), this.emit("afterResponse", e);
} catch (d) {
throw d instanceof u ? d : new u(
O.NETWORK_ERROR,
`WebSocket creation failed: ${d}`,
e
);
}
let y;
try {
a && (y = setTimeout(() => {
a.abort();
}, b));
const d = await fetch(e.rawRequest).catch((w) => {
throw y && clearTimeout(y), a != null && a.signal.aborted ? new u(
O.TIMEOUT,
`Request timed out after ${b}ms`,
e
) : new u(O.NETWORK_ERROR, w.message, e);
});
return y && clearTimeout(y), e.rawResponse = d, e.response = await K(
d,
e.responseType,
(w, T) => {
var m;
(m = r == null ? void 0 : r.onProgress) == null || m.call(r, w, T);
}
), e.data = e.response.data, e.headers = e.response.headers, this.emit("afterResponse", e);
} catch (d) {
throw y && clearTimeout(y), d;
}
}
mergeQuery(s, ...r) {
const e = {}, o = (i) => {
i && (_(i) ? Object.entries(i).forEach(([n, f]) => {
f == null ? delete e[n] : Array.isArray(f) ? (n.endsWith("[]"), e[n] = f.map(String)) : e[n] = String(f);
}) : new URLSearchParams(i).forEach((f, c) => {
e[c] = f;
}));
};
return o(s), r.forEach(o), e;
}
mergeHeaders(s, ...r) {
const e = {}, o = new Headers(s);
for (const i of r) {
if (i == null) continue;
if (_(i)) {
const f = U(i);
if (Object.keys(f).length === 0) continue;
new Headers(f).forEach((h, a) => {
o.set(a, h);
});
} else
new Headers(i).forEach((c, h) => {
o.set(h, c);
});
}
return o.forEach((i, n) => {
e[n] = i;
}), e;
}
async emit(s, r) {
const e = this.hooks.filter((o) => o.event === s);
try {
let o = 0;
for (const i of e) {
const n = `${s}#${i.action.name || `anonymous#${o}`}`, f = Symbol("FexiosHookContext");
r[f] = f;
const c = await i.action.call(this, r);
if (c === !1)
throw new u(
O.ABORTED_BY_HOOK,
`Request aborted by hook "${n}"`,
r
);
if (typeof c == "object" && c[f] === f)
r = c;
else {
const h = globalThis["".concat("console")];
try {
throw new u(
O.HOOK_CONTEXT_CHANGED,
`Hook "${n}" should return the original FexiosContext or return false to abort the request, but got "${c}".`
);
} catch (a) {
h.warn(a.stack || a);
}
}
delete r[f], o++;
}
} catch (o) {
return Promise.reject(o);
}
return r;
}
on(s, r, e = !1) {
if (typeof r != "function")
throw new u(
O.INVALID_HOOK_CALLBACK,
`Hook should be a function, but got "${typeof r}"`
);
return this.hooks[e ? "unshift" : "push"]({
event: s,
action: r
}), this;
}
off(s, r) {
return s === "*" || !s ? this.hooks = this.hooks.filter((e) => e.action !== r) : this.hooks = this.hooks.filter(
(e) => e.event !== s || e.action !== r
), this;
}
createInterceptor(s) {
return {
handlers: () => this.hooks.filter((r) => r.event === s).map((r) => r.action),
use: (r, e = !1) => this.on(s, r, e),
clear: () => {
this.hooks = this.hooks.filter((r) => r.event !== s);
}
};
}
createMethodShortcut(s) {
return Object.defineProperty(this, s, {
value: (r, e, o) => (this.METHODS_WITHOUT_BODY.includes(
s.toLocaleLowerCase()
) ? o = e : (o = o || {}, o.body = e), this.request(r, {
...o,
method: s
}))
}), this;
}
extends(s) {
const r = new E({ ...this.baseConfigs, ...s });
return r.hooks = [...this.hooks], r;
}
static create(s) {
return new E(s);
}
}
/**
* Fexios
* @desc Fetch based HTTP client with similar API to axios for browser and Node.js
*
* @license MIT
* @author dragon-fish <dragon-fish@qq.com>
*/
const F = E.create, C = F();
typeof globalThis < "u" ? globalThis.fexios = C : typeof window < "u" && (window.fexios = C);
export {
E as Fexios,
u as FexiosError,
O as FexiosErrorCodes,
x as FexiosQueryBuilder,
p as FexiosResponse,
A as FexiosResponseError,
H as checkIfTextData,
_ as checkIsPlainObject,
F as createFexios,
C as default,
U as dropUndefinedAndNull,
C as fexios,
Q as isFexiosError,
K as resolveResponseBody
};
//# sourceMappingURL=index.js.map