UNPKG

fexios

Version:

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

420 lines (419 loc) 14.3 kB
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