fexios
Version:
Fetch based HTTP client with similar API to axios for browser and Node.js
420 lines (419 loc) • 14.3 kB
JavaScript
var y = /* @__PURE__ */ ((s) => (s.BODY_USED = "BODY_USED", s.NO_BODY_READER = "NO_BODY_READER", s.TIMEOUT = "TIMEOUT", s.NETWORK_ERROR = "NETWORK_ERROR", s.BODY_NOT_ALLOWED = "BODY_NOT_ALLOWED", s.HOOK_CONTEXT_CHANGED = "HOOK_CONTEXT_CHANGED", s.ABORTED_BY_HOOK = "ABORTED_BY_HOOK", s.INVALID_HOOK_CALLBACK = "INVALID_HOOK_CALLBACK", s.UNEXPECTED_HOOK_RETURN = "UNEXPECTED_HOOK_RETURN", s))(y || {});
class d extends Error {
constructor(r, t, e, o) {
super(t, o), this.code = r, this.context = e, this.name = "FexiosError";
}
}
class H extends d {
constructor(r, t, e) {
super(t.statusText, r, void 0, e), this.response = t, this.name = "FexiosResponseError";
}
}
const K = (s) => !(s instanceof H) && s instanceof d;
function j(s, r = 1024) {
if (!(s instanceof Uint8Array))
throw new TypeError("Input must be a Uint8Array");
const t = s.slice(0, r), e = new TextDecoder("utf-8", { fatal: !0 });
try {
const o = e.decode(t), n = /[\x00-\x08\x0E-\x1F\x7F]/g, h = o.match(n);
return !(h && h.length / o.length > 0.1);
} catch {
return !1;
}
}
function S(s) {
if (typeof s != "object" || s === null || Object.prototype.toString.call(s) !== "[object Object]")
return !1;
const r = Object.getPrototypeOf(s);
return r === Object.prototype || r === null;
}
function U(s, r = {}) {
const t = {};
return Object.entries(s).forEach(([e, o]) => {
o != null && (r.dropEmptyString && o === "" || (t[e] = o));
}), t;
}
class E {
constructor(r, t, e) {
this.rawResponse = r, this.data = t, this.ok = r.ok, this.status = r.status, this.statusText = r.statusText, this.headers = r.headers, Object.entries(e || {}).forEach(([o, n]) => {
this[o] = n;
});
}
}
async function I(s, r, t) {
var l;
if (s.bodyUsed)
throw new d(
y.BODY_USED,
"Response body has already been used or locked"
);
const e = s.headers.get("content-type") || "", o = Number(s.headers.get("content-length")) || 0, n = (i, c) => c === "json" || i.startsWith("application/json"), h = (i, c, a) => a === "blob" || i.startsWith("image/") || i.startsWith("video/") || i.startsWith("audio/") || !j(c);
if ((s.status === 101 || s.status === 426 || s.headers.get("upgrade")) && typeof globalThis.WebSocket < "u") {
const i = new WebSocket(s.url);
return await new Promise((c, a) => {
i.onopen = c, i.onerror = a;
}), new E(s, i, {
ok: !0,
status: 101,
statusText: "Switching Protocols"
});
} else if (e.startsWith("text/event-stream") && !["text", "json"].includes(r || "") && typeof globalThis.EventSource < "u") {
const i = new EventSource(s.url);
return await new Promise((c, a) => {
i.onopen = c, i.onerror = a;
}), new E(s, i);
} else {
if (r === "stream")
return new E(
s,
s.body
);
{
const c = (l = s.clone().body) == null ? void 0 : l.getReader();
if (!c)
throw new d(
y.NO_BODY_READER,
"Failed to get ReadableStream from response body"
);
let a = new Uint8Array();
for (; ; ) {
const { done: O, value: b } = await c.read();
if (O) break;
if (b && (a = new Uint8Array([...a, ...b]), t && o > 0)) {
const m = Math.min(a.length / o, 1);
t(m, a);
}
}
const f = new E(s, void 0);
if (h(e, a, r) ? f.data = new Blob([a], {
type: s.headers.get("content-type") || void 0
}) : f.data = new TextDecoder().decode(a), n(e, r))
try {
f.data = JSON.parse(f.data);
} catch {
}
if (typeof f.data == "string" && r !== "text") {
const O = f.data.trim(), b = O[0], m = O[O.length - 1];
if (b === "{" && m === "}" || b === "[" && m === "]")
try {
f.data = JSON.parse(f.data);
} catch {
}
}
if (typeof f.data > "u" && (f.data = a.length > 0 ? a : void 0), f.ok)
return f;
throw new H(
`Request failed with status code ${s.status}`,
f
);
}
}
}
class P {
/**
* Build URLSearchParams from a record object with proper array handling
* @param query - The query object containing key-value pairs
* @returns URLSearchParams instance
*/
static makeSearchParams(r) {
const t = new URLSearchParams();
return Object.entries(r).forEach(([e, o]) => {
Array.isArray(o) ? o.forEach((n) => t.append(e, String(n))) : t.set(e, String(o));
}), t;
}
/**
* 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(r) {
return this.makeSearchParams(r).toString();
}
}
function C(s) {
return s && s.__esModule && Object.prototype.hasOwnProperty.call(s, "default") ? s.default : s;
}
var D, A;
function B() {
if (A) return D;
A = 1;
function s(r) {
var t = this.constructor.prototype[r], e = function() {
return t.apply(e, arguments);
};
return Object.setPrototypeOf(e, this.constructor.prototype), Object.getOwnPropertyNames(t).forEach(function(o) {
Object.defineProperty(e, o, Object.getOwnPropertyDescriptor(t, o));
}), e;
}
return s.prototype = Object.create(Function.prototype), D = s, D;
}
var q = B();
const W = /* @__PURE__ */ C(q);
class T extends W {
constructor(r = {}) {
super("request"), this.baseConfigs = r, 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 = T.create, this.dropUndefinedAndNull = U, this.checkIsPlainObject = S, this.ALL_METHODS.forEach(this.createMethodShortcut.bind(this));
}
async request(r, t) {
var m, R, k, L;
let e = t = t || {};
typeof r == "string" || r instanceof URL ? e.url = r.toString() : typeof r == "object" && (e = { ...r, ...e }), e = await this.emit("beforeInit", e);
const o = t.baseURL || this.baseConfigs.baseURL || ((m = globalThis.location) == null ? void 0 : m.href), n = o ? new URL(o, (R = globalThis.location) == null ? void 0 : R.href) : void 0, h = new URL(e.url.toString(), n);
e.url = h.href, e.baseURL = n ? n.href : h.origin, e.headers = this.mergeHeaders(
this.baseConfigs.headers,
t.headers
);
const l = n == null ? void 0 : n.searchParams, i = new URLSearchParams(h.searchParams);
if (h.search = "", e.url = h.href, e.query = this.mergeQuery(
l,
// baseURL query (lowest priority)
this.baseConfigs.query,
// defaultOptions (baseOptions)
i,
// requestURL query (urlParams)
t.query
// requestOptions (highest priority)
), h.search = P.makeQueryString(e.query), e.url = h.toString(), this.METHODS_WITHOUT_BODY.includes(
(k = e.method) == null ? void 0 : k.toLocaleLowerCase()
) && e.body)
throw new d(
y.BODY_NOT_ALLOWED,
`Request method "${e.method}" does not allow body`
);
e = await this.emit("beforeRequest", e);
let c;
typeof e.body < "u" && e.body !== null && (e.body instanceof Blob || e.body instanceof FormData || e.body instanceof URLSearchParams ? c = e.body : typeof e.body == "object" && e.body !== null ? (c = JSON.stringify(e.body), e.headers["content-type"] = "application/json") : c = e.body), !((L = t.headers) != null && L["content-type"]) && c && (c instanceof FormData || c instanceof URLSearchParams ? delete e.headers["content-type"] : typeof c == "string" && typeof e.body == "object" ? e.headers["content-type"] = "application/json" : c instanceof Blob && (e.headers["content-type"] = c.type)), e.body = c, e = await this.emit("afterBodyTransformed", e);
const a = e.abortController || globalThis.AbortController ? new AbortController() : void 0, f = 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 = f, e = await this.emit("beforeActualFetch", e);
const O = e.timeout || this.baseConfigs.timeout || 60 * 1e3;
if (e.url.startsWith("ws"))
try {
const u = new WebSocket(e.url);
return await new Promise((g, p) => {
const w = setTimeout(() => {
p(
new d(
y.TIMEOUT,
`WebSocket connection timed out after ${O}ms`,
e
)
);
}, O);
u.onopen = () => {
clearTimeout(w), g();
}, u.onerror = (_) => {
clearTimeout(w), p(
new d(
y.NETWORK_ERROR,
"WebSocket connection failed",
e
)
);
}, u.onclose = (_) => {
_.code !== 1e3 && (clearTimeout(w), p(
new d(
y.NETWORK_ERROR,
`WebSocket closed with code ${_.code}`,
e
)
));
};
}), e.rawResponse = new Response(), e.response = new E(e.rawResponse, u, {
ok: !0,
status: 101,
statusText: "Switching Protocols"
}), e.data = u, e.headers = new Headers(), this.emit("afterResponse", e);
} catch (u) {
throw u instanceof d ? u : new d(
y.NETWORK_ERROR,
`WebSocket creation failed: ${u}`,
e
);
}
let b;
try {
a && (b = setTimeout(() => {
a.abort();
}, O));
const u = await fetch(e.rawRequest).catch((g) => {
throw b && clearTimeout(b), a != null && a.signal.aborted ? new d(
y.TIMEOUT,
`Request timed out after ${O}ms`,
e
) : new d(y.NETWORK_ERROR, g.message, e);
});
return b && clearTimeout(b), e.rawResponse = u, e.response = await I(
u,
e.responseType,
(g, p) => {
var w;
(w = t == null ? void 0 : t.onProgress) == null || w.call(t, g, p);
}
), e.data = e.response.data, e.headers = e.response.headers, this.emit("afterResponse", e);
} catch (u) {
throw b && clearTimeout(b), u;
}
}
mergeQuery(r, ...t) {
const e = {}, o = (n) => {
n && (S(n) ? Object.entries(n).forEach(([h, l]) => {
l == null ? delete e[h] : Array.isArray(l) ? (h.endsWith("[]"), e[h] = l.map(String)) : e[h] = String(l);
}) : new URLSearchParams(n).forEach((l, i) => {
e[i] = l;
}));
};
return o(r), t.forEach(o), e;
}
mergeHeaders(r, ...t) {
const e = {}, o = new Headers(r);
for (const n of t) {
if (n == null) continue;
if (S(n)) {
const l = U(n);
if (Object.keys(l).length === 0) continue;
new Headers(l).forEach((c, a) => {
o.set(a, c);
});
} else
new Headers(n).forEach((i, c) => {
o.set(c, i);
});
}
return o.forEach((n, h) => {
e[h] = n;
}), e;
}
async emit(r, t) {
const e = this.hooks.filter((o) => o.event === r);
try {
let o = 0;
for (const n of e) {
const h = `${r}#${n.action.name || `anonymous#${o}`}`, l = Symbol("FexiosHookContext");
t[l] = l;
const i = await n.action.call(this, t);
if (i === !1)
throw new d(
y.ABORTED_BY_HOOK,
`Request aborted by hook "${h}"`,
t
);
if (typeof i == "object" && i[l] === l)
t = i;
else {
const c = globalThis["".concat("console")];
try {
throw new d(
y.HOOK_CONTEXT_CHANGED,
`Hook "${h}" should return the original FexiosContext or return false to abort the request, but got "${i}".`
);
} catch (a) {
c.warn(a.stack || a);
}
}
delete t[l], o++;
}
} catch (o) {
return Promise.reject(o);
}
return t;
}
on(r, t, e = !1) {
if (typeof t != "function")
throw new d(
y.INVALID_HOOK_CALLBACK,
`Hook should be a function, but got "${typeof t}"`
);
return this.hooks[e ? "unshift" : "push"]({
event: r,
action: t
}), this;
}
off(r, t) {
return r === "*" || !r ? this.hooks = this.hooks.filter((e) => e.action !== t) : this.hooks = this.hooks.filter(
(e) => e.event !== r || e.action !== t
), this;
}
createInterceptor(r) {
return {
handlers: () => this.hooks.filter((t) => t.event === r).map((t) => t.action),
use: (t, e = !1) => this.on(r, t, e),
clear: () => {
this.hooks = this.hooks.filter((t) => t.event !== r);
}
};
}
createMethodShortcut(r) {
return Object.defineProperty(this, r, {
value: (t, e, o) => (this.METHODS_WITHOUT_BODY.includes(
r.toLocaleLowerCase()
) ? o = e : (o = o || {}, o.body = e), this.request(t, {
...o,
method: r
}))
}), this;
}
extends(r) {
const t = new T({ ...this.baseConfigs, ...r });
return t.hooks = [...this.hooks], t;
}
static create(r) {
return new T(r);
}
}
/**
* 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 x = T.create, N = x();
typeof globalThis < "u" ? globalThis.fexios = N : typeof window < "u" && (window.fexios = N);
export {
T as Fexios,
d as FexiosError,
y as FexiosErrorCodes,
P as FexiosQueryBuilder,
E as FexiosResponse,
H as FexiosResponseError,
j as checkIfTextData,
S as checkIsPlainObject,
x as createFexios,
N as default,
U as dropUndefinedAndNull,
N as fexios,
K as isFexiosError,
I as resolveResponseBody
};
//# sourceMappingURL=index.js.map