@fgrzl/fetch
Version:
A modern, type-safe HTTP client with middleware support for CSRF protection and authentication
2 lines • 13.4 kB
JavaScript
;var G=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var ee=Object.prototype.hasOwnProperty;var te=(r,e)=>{for(var t in e)G(r,t,{get:e[t],enumerable:!0})},re=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Z(e))!ee.call(r,o)&&o!==t&&G(r,o,{get:()=>e[o],enumerable:!(n=Y(e,o))||n.enumerable});return r};var ne=r=>re(G({},"__esModule",{value:!0}),r);var Re={};te(Re,{FetchClient:()=>C,FetchError:()=>O,HttpError:()=>z,NetworkError:()=>D,appendQueryParams:()=>q,buildQueryParams:()=>Q,createAuthenticationMiddleware:()=>F,createAuthorizationMiddleware:()=>k,createCacheMiddleware:()=>b,createLoggingMiddleware:()=>L,createRateLimitMiddleware:()=>T,createRetryMiddleware:()=>P,default:()=>we,useAuthentication:()=>w,useAuthorization:()=>j,useBasicStack:()=>_,useCSRF:()=>N,useCache:()=>A,useDevelopmentStack:()=>X,useLogging:()=>E,useProductionStack:()=>$,useRateLimit:()=>S,useRetry:()=>R});module.exports=ne(Re);var C=class{constructor(e={}){this.middlewares=[];this.credentials=e.credentials??"same-origin",this.baseUrl=e.baseUrl}use(e){return this.middlewares.push(e),this}setBaseUrl(e){return this.baseUrl=e,this}async request(e,t={}){let n=this.resolveUrl(e),o=0,s=async u=>{let d=u||{...t,url:n},h=d.url||n;if(o>=this.middlewares.length){let{url:l,...c}=d;return this.coreFetch(c,h)}let a=this.middlewares[o++];if(!a){let{url:l,...c}=d;return this.coreFetch(c,h)}return a(d,s)};return await s()}async coreFetch(e,t){try{let n={credentials:this.credentials,...e};if(n.headers instanceof Headers){let i={};n.headers.forEach((u,d)=>{i[d]=u}),n.headers=i}let o=await fetch(t,n),s=await this.parseResponse(o);return{data:o.ok?s:null,status:o.status,statusText:o.statusText,headers:o.headers,url:o.url,ok:o.ok,...o.ok?{}:{error:{message:o.statusText,body:s}}}}catch(n){if(n instanceof TypeError&&n.message.includes("fetch"))return{data:null,status:0,statusText:"Network Error",headers:new Headers,url:t,ok:!1,error:{message:"Failed to fetch",body:n}};throw n}}async parseResponse(e){let t=e.headers.get("content-type")||"";return t.includes("application/json")?e.json():t.includes("text/")?e.text():t.includes("application/octet-stream")||t.includes("image/")||t.includes("video/")||t.includes("audio/")?e.blob():e.body&&await e.text()||null}buildUrlWithParams(e,t){if(!t)return e;let n=this.resolveUrl(e);if(!n.startsWith("http://")&&!n.startsWith("https://")&&!n.startsWith("//")){let s=new URLSearchParams;Object.entries(t).forEach(([u,d])=>{d!=null&&s.set(u,String(d))});let i=s.toString();return i?`${n}?${i}`:n}let o=new URL(n);return Object.entries(t).forEach(([s,i])=>{i!=null&&o.searchParams.set(s,String(i))}),o.toString()}resolveUrl(e){if(e.startsWith("http://")||e.startsWith("https://")||e.startsWith("//")||!this.baseUrl)return e;try{let t=new URL(this.baseUrl);return new URL(e,t).toString()}catch{throw new Error(`Invalid URL: Unable to resolve "${e}" with baseUrl "${this.baseUrl}"`)}}head(e,t){let n=this.buildUrlWithParams(e,t);return this.request(n,{method:"HEAD"})}async headMetadata(e,t){let n=await this.head(e,t),o=n.headers.get("content-length"),s=n.headers.get("last-modified");return{...n,exists:n.ok,contentType:n.headers.get("content-type")||void 0,contentLength:o?parseInt(o,10):void 0,lastModified:s?new Date(s):void 0,etag:n.headers.get("etag")||void 0,cacheControl:n.headers.get("cache-control")||void 0}}get(e,t){let n=this.buildUrlWithParams(e,t);return this.request(n,{method:"GET"})}post(e,t,n){let o={"Content-Type":"application/json",...n??{}};return this.request(e,{method:"POST",headers:o,...t!==void 0?{body:JSON.stringify(t)}:{}})}put(e,t,n){let o={"Content-Type":"application/json",...n??{}};return this.request(e,{method:"PUT",headers:o,...t!==void 0?{body:JSON.stringify(t)}:{}})}patch(e,t,n){let o={"Content-Type":"application/json",...n??{}};return this.request(e,{method:"PATCH",headers:o,...t!==void 0?{body:JSON.stringify(t)}:{}})}del(e,t){let n=this.buildUrlWithParams(e,t);return this.request(n,{method:"DELETE"})}};function oe(r,e=[]){return e.some(t=>typeof t=="string"?r.includes(t):t.test(r))}function ie(r,e){return!e||e.length===0?!0:e.some(t=>typeof t=="string"?r.includes(t):t.test(r))}function F(r){let{tokenProvider:e,headerName:t="Authorization",tokenType:n="Bearer",skipPatterns:o=[],includePatterns:s}=r;return async(i,u)=>{let d=i.url||"",a=new URL(d).pathname;if(oe(a,o)||!ie(a,s))return u(i);try{let l=await e();if(!l)return u(i);let c=new Headers(i.headers);c.set(t,`${n} ${l}`);let p={...i,headers:c};return u(p)}catch{return u(i)}}}function w(r,e){return r.use(F(e))}function se(r={}){let{redirectPath:e="/login",returnUrlParam:t="return_url",includeReturnUrl:n=!0}=r;return()=>{let o=e;if(n&&typeof window<"u"){let s=encodeURIComponent(window.location.href),i=e.includes("?")?"&":"?";o=`${e}${i}${t}=${s}`}typeof window<"u"&&(window.location.href=o)}}function ae(r,e){return r||se(e)}function ce(r,e=[]){let t;try{t=new URL(r).pathname}catch{t=r}return e.some(n=>typeof n=="string"?t.includes(n):n.test(t))}function k(r={}){let{onUnauthorized:e,redirectConfig:t,onForbidden:n,skipPatterns:o=[],statusCodes:s=[401]}=r,i=ae(e,t);return async(u,d)=>{let h=u.url||"";if(ce(h,o))return d(u);let a=await d(u);if(s.includes(a.status))try{a.status===401&&i?await i(a,u):a.status===403&&n?await n(a,u):i&&await i(a,u)}catch(l){console.warn("Authorization handler failed:",l)}return a}}function j(r,e={}){return r.use(k(e))}var v=class{constructor(){this.cache=new Map}async get(e){let t=this.cache.get(e);return t?Date.now()>t.expiresAt?(this.cache.delete(e),null):t:null}async getWithExpiry(e){let t=this.cache.get(e);if(!t)return{entry:null,isExpired:!1};let n=Date.now()>t.expiresAt;return{entry:t,isExpired:n}}async set(e,t){this.cache.set(e,t)}async delete(e){this.cache.delete(e)}async clear(){this.cache.clear()}},ue=r=>{let e=r.url||"",t=r.method||"GET",n=r.headers?JSON.stringify(r.headers):"";return`${t}:${e}:${n}`};function de(r,e=[]){return e.some(t=>typeof t=="string"?r.includes(t):t.test(r))}function b(r={}){let{ttl:e=300*1e3,methods:t=["GET"],storage:n=new v,keyGenerator:o=ue,skipPatterns:s=[],staleWhileRevalidate:i=!1}=r;return async(u,d)=>{let h=(u.method||"GET").toUpperCase(),a=u.url||"";if(!t.includes(h)||de(a,s))return d(u);let l=o(u);try{let{entry:c,isExpired:p}=n.getWithExpiry?await n.getWithExpiry(l):await(async()=>({entry:await n.get(l),isExpired:!1}))();if(c&&!p)return{...c.response,headers:new Headers(c.response.headers),data:c.response.data};if(c&&i){let f={...c.response,headers:new Headers(c.response.headers),data:c.response.data};return p&&d(u).then(async y=>{let g={};y.headers.forEach((U,M)=>{g[M]=U});let x={response:{status:y.status,statusText:y.statusText,headers:g,data:y.data},timestamp:Date.now(),expiresAt:Date.now()+e};await n.set(l,x)}).catch(()=>{}),f}let m=await d(u);if(m.ok)try{let f={};m.headers.forEach((g,x)=>{f[x]=g});let y={response:{status:m.status,statusText:m.statusText,headers:f,data:m.data},timestamp:Date.now(),expiresAt:Date.now()+e};await n.set(l,y)}catch{}return m}catch(c){if(c&&typeof c=="object"&&"message"in c){let p=c.message;if(p.includes("Network")||p.includes("fetch"))throw c}return d(u)}}}function A(r,e={}){return r.use(b(e))}function le(r="XSRF-TOKEN"){if(typeof document>"u")return"";let e=`${r}=`,n=decodeURIComponent(document.cookie).split(";");for(let o of n){let s=o.trim();if(s.indexOf(e)===0)return s.substring(e.length)}return""}function he(r,e=[]){return e.some(t=>typeof t=="string"?r.includes(t):t.test(r))}function I(r={}){let{headerName:e="X-XSRF-TOKEN",cookieName:t="XSRF-TOKEN",protectedMethods:n=["POST","PUT","PATCH","DELETE"],skipPatterns:o=[],tokenProvider:s=()=>le(t)}=r;return async(i,u)=>{let d=(i.method||"GET").toUpperCase(),h=i.url||"";if(!n.includes(d)||he(h,o))return u(i);let a=s();if(!a)return u(i);let l=new Headers(i.headers);l.set(e,a);let c={...i,headers:l};return u(c)}}function N(r,e={}){return r.use(I(e))}var pe={debug:(r,e)=>console.debug(r,e),info:(r,e)=>console.info(r,e),warn:(r,e)=>console.warn(r,e),error:(r,e)=>console.error(r,e)},H={debug:0,info:1,warn:2,error:3},fe=r=>{let{method:e,url:t,status:n,duration:o}=r,s=`${e} ${t}`;return n&&(s+=` \u2192 ${n}`),o&&(s+=` (${o}ms)`),s};function me(r,e=[]){return e.some(t=>typeof t=="string"?r.includes(t):t.test(r))}function L(r={}){let{level:e="info",logger:t=pe,includeRequestHeaders:n=!1,includeResponseHeaders:o=!1,includeRequestBody:s=!1,includeResponseBody:i=!1,skipPatterns:u=[],formatter:d=fe}=r,h=H[e];return async(a,l)=>{let c=a.url||"",p=(a.method||"GET").toUpperCase();if(me(c,u))return l(a);let m=Date.now();if(H.debug>=h){let f=n?K(a.headers):void 0,y=s?a.body:void 0,g={level:"debug",timestamp:m,method:p,url:c,...f&&{requestHeaders:f},...y&&{requestBody:y}};t.debug(`\u2192 ${d(g)}`,g)}try{let f=await l(a),y=Date.now()-m,g=f.status>=400?"error":"info";if(H[g]>=h){let x=o?K(f.headers):void 0,U=i?f.data:void 0,M={level:g,timestamp:Date.now(),method:p,url:c,status:f.status,duration:y,...x?{responseHeaders:x}:{},...U!==void 0?{responseBody:U}:{}},B=`\u2190 ${d(M)}`;g==="error"?t.error(B,M):t.info(B,M)}return f}catch(f){let y=Date.now()-m;if(H.error>=h){let g={level:"error",timestamp:Date.now(),method:p,url:c,duration:y,error:f instanceof Error?f:new Error(String(f))};t.error(`\u2717 ${d(g)}`,g)}throw f}}}function K(r){if(!r)return;let e={};return r instanceof Headers?(r.forEach((t,n)=>{e[n]=t}),e):r}function E(r,e={}){return r.use(L(e))}var W=class{constructor(e,t,n=()=>Date.now()){this.maxTokens=e;this.refillRate=t;this.timeProvider=n;this.tokens=e,this.lastRefill=this.timeProvider()}tryConsume(){if(this.refill(),this.tokens>=1)return this.tokens--,{allowed:!0};let e=(1-this.tokens)/this.refillRate;return{allowed:!1,retryAfter:Math.ceil(e)}}refill(){let e=this.timeProvider(),n=(e-this.lastRefill)*this.refillRate;this.tokens=Math.min(this.maxTokens,this.tokens+n),this.lastRefill=e}};function T(r={}){let{maxRequests:e=60,windowMs:t=6e4,keyGenerator:n=()=>"default",skipPatterns:o=[],onRateLimitExceeded:s}=r,i=new Map,u=e/t;return async(d,h)=>{let a=d.url||"";if(o.some(m=>typeof m=="string"?a.includes(m):m.test(a)))return h(d);let l=n(d);i.has(l)||i.set(l,new W(e,u));let p=i.get(l).tryConsume();if(!p.allowed){if(s){let m=await s(p.retryAfter||0,d);if(m)return m}return{data:null,status:429,statusText:"Too Many Requests",headers:new Headers({"Retry-After":Math.ceil((p.retryAfter||0)/1e3).toString()}),url:d.url||"",ok:!1,error:{message:`Rate limit exceeded. Retry after ${p.retryAfter}ms`,body:{retryAfter:p.retryAfter}}}}return h(d)}}function S(r,e={}){return r.use(T(e))}var ge=r=>r.status===0||r.status>=500&&r.status<600,V=(r,e,t,n)=>{let o;switch(t){case"exponential":o=e*Math.pow(2,r-1);break;case"linear":o=e*r;break;case"fixed":default:o=e;break}return Math.min(o,n)},J=r=>new Promise(e=>setTimeout(e,r));function P(r={}){let{maxRetries:e=3,delay:t=1e3,backoff:n="exponential",maxDelay:o=3e4,shouldRetry:s=ge,onRetry:i}=r;return async(u,d)=>{let h,a=0;for(;a<=e;)try{let l=await d(u);if(l.ok||!s({status:l.status,ok:l.ok},a+1)||a>=e)return l;h=l,a++;let c=V(a,t,n,o);i&&i(a,c,{status:l.status,statusText:l.statusText}),await J(c)}catch(l){let c={data:null,status:0,statusText:"Network Error",headers:new Headers,url:u.url||"",ok:!1,error:{message:l instanceof Error?l.message:"Unknown error",body:l}};if(!s(c,a+1)||a>=e)return c;h=c,a++;let p=V(a,t,n,o);i&&i(a,p,{status:c.status,statusText:c.statusText}),await J(p)}return h}}function R(r,e={}){return r.use(P(e))}function $(r,e={}){let t=r;return e.auth&&(t=w(t,e.auth)),e.cache!==void 0&&(t=A(t,e.cache)),e.retry!==void 0&&(t=R(t,e.retry)),e.rateLimit!==void 0&&(t=S(t,e.rateLimit)),e.logging!==void 0&&(t=E(t,e.logging)),t}function X(r,e={}){let t=r;return e.auth&&(t=w(t,e.auth)),t=R(t,{maxRetries:1,delay:100}),t=E(t,{level:"debug",includeRequestHeaders:!0,includeResponseHeaders:!0,includeRequestBody:!0,includeResponseBody:!0}),t}function _(r,e){return R(w(r,e.auth),{maxRetries:2})}var O=class extends Error{constructor(e,t){super(e),this.name="FetchError",t!==void 0&&(this.cause=t)}},z=class extends O{constructor(e,t,n,o){super(`HTTP ${e} ${t} at ${o}`),this.name="HttpError",this.status=e,this.statusText=t,this.body=n}},D=class extends O{constructor(e,t,n){super(`Network error for ${t}: ${e}`,n),this.name="NetworkError"}};function Q(r){let e=new URLSearchParams;for(let[t,n]of Object.entries(r))n!==void 0&&(Array.isArray(n)?n.forEach(o=>{o!==void 0&&e.append(t,String(o))}):e.set(t,String(n)));return e.toString()}function q(r,e){let t=Q(e);if(!t)return r;let n=r.indexOf("#");if(n!==-1){let s=r.substring(0,n),i=r.substring(n),u=s.includes("?")?"&":"?";return`${s}${u}${t}${i}`}let o=r.includes("?")?"&":"?";return`${r}${o}${t}`}var ye=$(new C({credentials:"same-origin"}),{retry:{maxRetries:2,delay:1e3},cache:{ttl:300*1e3,methods:["GET"]},logging:{level:"info"},rateLimit:{maxRequests:100,windowMs:60*1e3}}),we=ye;0&&(module.exports={FetchClient,FetchError,HttpError,NetworkError,appendQueryParams,buildQueryParams,createAuthenticationMiddleware,createAuthorizationMiddleware,createCacheMiddleware,createLoggingMiddleware,createRateLimitMiddleware,createRetryMiddleware,useAuthentication,useAuthorization,useBasicStack,useCSRF,useCache,useDevelopmentStack,useLogging,useProductionStack,useRateLimit,useRetry});
//# sourceMappingURL=index.min.js.map