UNPKG

fexios

Version:

Fetch based HTTP client with similar API to axios for browser and Node.js

519 lines (518 loc) 16.7 kB
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