@ecosy/core 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/http.d.ts CHANGED
@@ -33,6 +33,31 @@ export interface HttpRequest<Body = unknown, Params = Record<string, unknown>, Q
33
33
  query?: Query;
34
34
  params?: Params;
35
35
  signal?: AbortSignal;
36
+ /**
37
+ * Pass-through bag for `fetch`'s `RequestInit` options that the
38
+ * library does not manage itself (`credentials`, `cache`, `mode`,
39
+ * `redirect`, `referrer`, `referrerPolicy`, `integrity`, `keepalive`,
40
+ * `priority`, `duplex`) plus framework extensions (`next` on Next.js,
41
+ * `cf` on Cloudflare Workers, `dispatcher` on undici).
42
+ *
43
+ * Lib-level fields (`method`, `headers`, `body`, `signal`) cannot be
44
+ * overridden here — they are applied after this bag is spread.
45
+ * Unknown keys are silently dropped, so a compromised caller cannot
46
+ * smuggle arbitrary fields into `fetch`.
47
+ */
48
+ configs?: Record<string, unknown>;
49
+ }
50
+ /** Constructor options for {@link Http}. */
51
+ export interface HttpOptions {
52
+ /** Base URL prepended to every relative request path. */
53
+ baseURL?: string;
54
+ /**
55
+ * Additional origins (besides `baseURL`'s origin) that absolute URLs
56
+ * and redirect responses are permitted to reach. Any other origin is
57
+ * rejected before the request is sent and, for redirects, before the
58
+ * response body is returned.
59
+ */
60
+ allowedOrigins?: ReadonlyArray<string>;
36
61
  }
37
62
  /** Storage adapter interface for reading/writing auth tokens (e.g. `localStorage`). */
38
63
  export interface HttpStorage {
@@ -114,13 +139,23 @@ export declare class Endpoint {
114
139
  * ```
115
140
  */
116
141
  export declare class Http {
117
- private readonly baseURL;
118
142
  static readonly method: typeof HttpMethod;
119
143
  static readonly Endpoint: typeof Endpoint;
120
144
  static authTokenKey: string;
121
145
  static authHeaderKey: string;
122
146
  static authHeaderType: string;
123
147
  static authDetectToken: string[];
148
+ /**
149
+ * One-shot headers merged into the very next request across all
150
+ * instances, then cleared. Intended for request-scoped values like
151
+ * CSRF tokens or correlation IDs that callers don't want to thread
152
+ * through every call site.
153
+ *
154
+ * Note: this is a process-wide mutable static. In concurrent async
155
+ * contexts (e.g. multiple tenants sharing one process) it is the
156
+ * caller's responsibility to ensure the set → dispatch → reset
157
+ * sequence is not interleaved.
158
+ */
124
159
  static extraHeaders: Record<string, string> | null;
125
160
  static storage: HttpStorage | null;
126
161
  private defaultHeaders;
@@ -128,7 +163,34 @@ export declare class Http {
128
163
  private storage;
129
164
  private static interceptors;
130
165
  private interceptors;
131
- constructor(baseURL?: string | undefined);
166
+ private readonly baseURL;
167
+ private readonly allowedOrigins;
168
+ /**
169
+ * @param init - Either a base URL string (backwards-compatible form)
170
+ * or an {@link HttpOptions} object. Using the object form lets
171
+ * callers opt in to additional origins that absolute URLs and
172
+ * redirect responses are permitted to reach. Any other origin is
173
+ * rejected before the request is sent and, for redirects, before
174
+ * the response body is returned.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * new Http("https://api.example.com");
179
+ * new Http({
180
+ * baseURL: "https://api.example.com",
181
+ * allowedOrigins: ["https://cdn.example.com"],
182
+ * });
183
+ * ```
184
+ */
185
+ constructor(init?: string | HttpOptions);
186
+ /** Whether `origin` is this instance's baseURL origin or an explicitly allowed one. */
187
+ private isAllowedOrigin;
188
+ /**
189
+ * If a request was sent with credentials (Authorization / Cookie) and
190
+ * ended up at a different origin via redirect, refuse to return the
191
+ * response. Defends against token exfil via server-controlled 3xx.
192
+ */
193
+ private assertSameOriginResponse;
132
194
  /** Register a global interceptor (applies to all `Http` instances). */
133
195
  static on(...params: HttpInterceptorParameters): void;
134
196
  /** Remove a global interceptor. */
@@ -153,7 +215,18 @@ export declare class Http {
153
215
  getHeaders(headers?: Record<string, string>, isFormData?: boolean): {
154
216
  [x: string]: string;
155
217
  };
156
- /** Build the full URL from base URL, path, query string, and path params. */
218
+ /**
219
+ * Build the full URL from base URL, path, query string, and path params.
220
+ *
221
+ * Security rules applied here (see SECURITY audit):
222
+ * - Absolute URLs must use `http`/`https` and their origin must match
223
+ * `baseURL`'s origin or an entry in `allowedOrigins`.
224
+ * - Protocol-relative URLs (`//host/…`) are rejected — they silently
225
+ * flip the target host.
226
+ * - Path params (`{id}`) are URL-encoded by `interpolateURL`
227
+ * so a value of `"../admin"` cannot traverse the path.
228
+ * - Proto-pollution keys in `params` are stripped.
229
+ */
157
230
  getURL(options: HttpRequest): string;
158
231
  /** Serialize the request body (JSON, FormData, or binary). Returns `undefined` for bodyless methods. */
159
232
  getBody(options: HttpRequest): string | FormData | Uint8Array | ArrayBuffer | undefined;
package/dist/http.js CHANGED
@@ -1 +1 @@
1
- "use strict";var t=require("./utilities/filelist.js"),e=require("./utilities/flatten.js"),r=require("./utilities/formdata.js"),s=require("./utilities/sanitize-mime.js"),o=require("./serialize.js"),n=require("./env.js");const a=n.getEnv("API_URL","/"),i=n.getEnv("API_AUTH_TOKEN_KEY"),c=n.getEnv("API_AUTH_HEADER_KEY"),p=n.getEnv("API_AUTH_HEADER_TYPE");var d,u;exports.HttpMethod=void 0,(d=exports.HttpMethod||(exports.HttpMethod={})).GET="GET",d.POST="POST",d.PUT="PUT",d.DELETE="DELETE",d.PATCH="PATCH",d.HEAD="HEAD",d.OPTIONS="OPTIONS",exports.HttpUpload=void 0,(u=exports.HttpUpload||(exports.HttpUpload={})).UPLOAD="UPLOAD",u.RELATED="RELATED";class l{static register(t,e){return this.registered[t]=Object.assign({},e),this}static all(){return Object.assign({},this.registered)}}l.registered={};class h{constructor(t=a){this.baseURL=t,this.defaultHeaders={"Content-Type":"application/json"},this.method=h.method,this.storage=h.storage,this.interceptors={request:[],response:[],transform:[],error:[]}}static on(...t){const[e,r]=t,s=h.interceptors[e];s.includes(r)||s.push(r),h.interceptors[e]=s}static off(...t){const[e,r]=t,s=h.interceptors[e];h.interceptors[e]=s.filter(t=>t!==r)}setStorage(t){this.storage=t}isValidQuery(t){return"string"==typeof t||t instanceof URLSearchParams||(Array.isArray(t)?t.every(t=>Array.isArray(t)&&2===t.length&&"string"==typeof t[0]&&o.Serialize.Primitive.isPrimitive(t[1])):"object"==typeof t&&null!==t&&Object.values(t).every(t=>o.Serialize.Primitive.isPrimitive(t)))}getQuery(t){const{method:e,body:r,query:s}=t,n=e===exports.HttpMethod.GET&&this.isValidQuery(r)?r:s;return n&&"object"==typeof n?n instanceof URLSearchParams?n.toString():o.Serialize.queryString.stringify(n,{skipNull:!0,skipEmptyString:!0}):"string"==typeof n?n:""}addHeaders(t){this.defaultHeaders=Object.assign(Object.assign({},this.defaultHeaders),t)}on(...t){const[e,r]=t,s=this.interceptors[e];return s.includes(r)||s.push(r),this.interceptors[e]=s,this}off(...t){const[e,r]=t,s=this.interceptors[e];return this.interceptors[e]=s.filter(t=>t!==r),this}getToken(){const t=i||h.authTokenKey,e=c||h.authHeaderKey,r=p||h.authHeaderType;if(!t||!e)return{key:e,value:""};let s="";this.storage&&(s=this.storage.getItem(t)||"");const o={key:e,value:""};return s&&(o.value=s,r&&(o.value=`${r} ${s}`)),o}getHeaders(t={},e){const r=this.getToken();r.key in t&&t[r.key]||(t[r.key]=r.value),e&&"Content-Type"in t?delete t["Content-Type"]:t["Content-Type"]=t["Content-Type"]||this.defaultHeaders["Content-Type"]||"application/json";const s=Object.assign(Object.assign(Object.assign({},this.defaultHeaders),t),h.extraHeaders||{});return h.extraHeaders=null,s}getURL(t){var e;const{url:r,params:s={}}=t,n=this.getQuery(t);let a=r;if(r)if(r.match(/^https?:\/\//))a=r;else{a=`${null===(e=this.baseURL)||void 0===e?void 0:e.replace(/\/+$/,"")}/${r.replace(/^\/+/,"")}`}if(n){const t=a.includes("?")?"&":"?";a+=`${t}${n}`}return o.Serialize.interpolate(a,s)}getBody(t){const{method:e,body:s}=t;if(e!==exports.HttpMethod.GET&&e!==exports.HttpMethod.HEAD&&e!==exports.HttpMethod.OPTIONS&&e!==exports.HttpMethod.DELETE)return e!==exports.HttpMethod.POST&&e!==exports.HttpMethod.PUT&&e!==exports.HttpMethod.PATCH||!r.isFormData(s)?s instanceof Uint8Array||s instanceof ArrayBuffer?s:void 0!==s?JSON.stringify(s):void 0:s}async request(t){const{method:e=exports.HttpMethod.GET}=t;try{let s=Object.assign({},t);const o=[...h.interceptors.request,...this.interceptors.request];for(const t of o)s=await t(s);const n=this.getURL(s),a=this.getHeaders(s.headers||{});r.isFormData(s.body)&&"Content-Type"in a&&delete a["Content-Type"];const i=await fetch(n,{method:s.method||e,headers:a,body:this.getBody(s),signal:s.signal}),c=i.headers.get("Content-Type")||"";let p=null;p=c.includes("application/json")?await i.json():await i.text();const d=[...h.interceptors.transform,...this.interceptors.transform];let u=p;for(const t of d)u=await t(u);const l=[...h.interceptors.response,...this.interceptors.response];let g=i;for(const t of l)g=await t(g);const y={};g.headers.forEach((t,e)=>{y[e]=t});const T=g.ok;let f=null;if(!T){f=p.error||p;const t=[...h.interceptors.error,...this.interceptors.error];for(const e of t)f=await e(f)}return{data:u,success:T,error:T?null:f,status:g.status,statusText:g.statusText,headers:y}}catch(t){let e=t instanceof Error?t:new Error(String(t));const r=[...h.interceptors.error,...this.interceptors.error];for(const t of r)e=await t(e);return{data:null,status:0,statusText:"Error",headers:{},error:e,success:!1}}}get(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:exports.HttpMethod.GET,url:t,query:e}))}post(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:exports.HttpMethod.POST,url:t,body:e}))}put(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:exports.HttpMethod.PUT,url:t,body:e}))}patch(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:exports.HttpMethod.PATCH,url:t,body:e}))}delete(t,e){return this.request(Object.assign(Object.assign({},e),{method:exports.HttpMethod.DELETE,url:t}))}head(t,e){return this.request(Object.assign(Object.assign({},e),{method:exports.HttpMethod.HEAD,url:t}))}options(t,e){return this.request(Object.assign(Object.assign({},e),{method:exports.HttpMethod.OPTIONS,url:t}))}upload(s,o,n){const a=(null==n?void 0:n.body)?r.objectToFormData(n.body):new FormData;let i=(null==n?void 0:n.name)||"file";if(Array.isArray(o)?(i.endsWith("[]")&&(i=i.slice(0,-2)),o.forEach((t,e)=>{a.append(`${i}[${e}]`,t,t.name)})):t.isFileList(o)?(i.endsWith("[]")&&(i=i.slice(0,-2)),Array.from(o).forEach((t,e)=>{a.append(`${i}[${e}]`,t,t.name)})):a.append(i,o,o.name),null==n?void 0:n.body){const t=e.flatten(n.body);Object.entries(t).forEach(([t,e])=>{null!=e&&a.append(t,String(e))})}return(null==n?void 0:n.onProgress)&&"undefined"!=typeof XMLHttpRequest?new Promise(async t=>{try{let e=Object.assign(Object.assign({},n),{method:exports.HttpMethod.POST,url:s,body:a});const r=[...h.interceptors.request,...this.interceptors.request];for(const t of r)e=await t(e);const o=this.getURL(e),i=this.getHeaders(e.headers||{},!0),c=new XMLHttpRequest;c.upload.addEventListener("progress",t=>{var e;if(t.lengthComputable){const r=Math.round(t.loaded/t.total*100);null===(e=n.onProgress)||void 0===e||e.call(n,{loaded:t.loaded,total:t.total,percentage:r})}}),c.addEventListener("load",async()=>{try{const e=c.getResponseHeader("Content-Type")||"";let r=null;r=e.includes("application/json")?JSON.parse(c.responseText):c.responseText;const s=[...h.interceptors.transform,...this.interceptors.transform];let o=r;for(const t of s)o=await t(o);const n=[...h.interceptors.response,...this.interceptors.response];let a=new Response(c.responseText,{status:c.status,statusText:c.statusText,headers:i});for(const t of n)a=await t(a);const p={};a.headers.forEach((t,e)=>{p[e]=t});const d=a.ok;t({data:o,success:d,error:d?null:o,status:a.status,statusText:a.statusText,headers:p})}catch(e){const r=[...h.interceptors.error,...this.interceptors.error];for(const t of r)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}}),c.addEventListener("error",e=>{const r=[...h.interceptors.error,...this.interceptors.error];for(const t of r)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}),c.addEventListener("abort",()=>{t({data:null,status:0,statusText:"Aborted",headers:{},error:null,success:!1})}),c.open(e.method||exports.HttpMethod.POST,o,!0),Object.entries(i).forEach(([t,e])=>{"content-type"!==(null==t?void 0:t.toLowerCase())&&e&&c.setRequestHeader(t,e)}),c.send(e.body)}catch(e){const r=[...h.interceptors.error,...this.interceptors.error];for(const t of r)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}}):this.request({method:exports.HttpMethod.POST,url:s,body:a,headers:null==n?void 0:n.headers,params:null==n?void 0:n.params,signal:null==n?void 0:n.signal,query:null==n?void 0:n.query})}related(t,e,r){const o=s.sanitizeMime(r.contentType),n=s.sanitizeMime(r.metadataMimeType||"application/json"),a=`----related-boundary-${Date.now()}-${Math.random().toString(36).slice(2)}`,i=JSON.stringify(r.metadata),c=new TextEncoder,p=c.encode(`--${a}\r\nContent-Type: ${n}; charset=UTF-8\r\n\r\n`+i+"\r\n"),d=c.encode(`--${a}\r\nContent-Type: ${o}\r\nContent-Transfer-Encoding: binary\r\n\r\n`),u=c.encode(`\r\n--${a}--`),l=e instanceof Uint8Array?e:new Uint8Array(e),h=new Uint8Array(p.length+d.length+l.length+u.length);return[p,d,l,u].reduce((t,e)=>(h.set(e,t),t+e.length),0),this.request({method:exports.HttpMethod.POST,url:t,body:h,headers:Object.assign(Object.assign({},r.headers||{}),{"Content-Type":`multipart/related; boundary=${a}`}),params:r.params,signal:r.signal,query:r.query})}static createFactory(t={}){const r=t.http||new h(t.baseURL);return function(s,o){const n=e.flatten("function"==typeof t.endpoint?t.endpoint():t.endpoint||{})[s];return{key:s,fn:async(...e)=>{const s="function"==typeof t.storage?await t.storage():t.storage;switch(s&&r.setStorage(s),o){case exports.HttpMethod.POST:return await r.post(n,...e);case exports.HttpMethod.PUT:return await r.put(n,...e);case exports.HttpMethod.PATCH:return await r.patch(n,...e);case exports.HttpMethod.DELETE:return await r.delete(n,...e);case exports.HttpMethod.HEAD:return await r.head(n,...e);case exports.HttpMethod.OPTIONS:return await r.options(n,...e);case exports.HttpUpload.UPLOAD:return await r.upload(n,...e);case exports.HttpUpload.RELATED:return await r.related(n,...e);default:return await r.get(n,...e)}}}}}}h.method=exports.HttpMethod,h.Endpoint=l,h.authTokenKey="access_token",h.authHeaderKey="Authorization",h.authHeaderType="Bearer",h.authDetectToken=["localStorage","sessionStorage","cookie"],h.extraHeaders=null,h.storage=null,h.interceptors={request:[],response:[],transform:[],error:[]},exports.API_AUTH_HEADER_KEY=c,exports.API_AUTH_HEADER_TYPE=p,exports.API_AUTH_TOKEN_KEY=i,exports.DEFAULT_BASE_URL=a,exports.Endpoint=l,exports.Http=h;
1
+ "use strict";var t=require("./utilities/filelist.js"),e=require("./utilities/flatten.js"),r=require("./utilities/formdata.js"),s=require("./utilities/get.js"),o=require("./utilities/sanitize-mime.js"),n=require("./serialize.js"),i=require("./env.js");function a(t){try{return new URL(t).origin}catch(t){return null}}const c=new Set(["__proto__","constructor","prototype"]),d=new Set(["credentials","cache","mode","redirect","referrer","referrerPolicy","integrity","keepalive","priority","duplex","window","next","cf","dispatcher"]);function l(t){const e=Object.create(null);for(const r of Object.keys(t))c.has(r)||(e[r]=t[r]);return e}const p=i.getEnv("API_URL","/"),u=i.getEnv("API_AUTH_TOKEN_KEY"),h=i.getEnv("API_AUTH_HEADER_KEY"),g=i.getEnv("API_AUTH_HEADER_TYPE");var f,y;exports.HttpMethod=void 0,(f=exports.HttpMethod||(exports.HttpMethod={})).GET="GET",f.POST="POST",f.PUT="PUT",f.DELETE="DELETE",f.PATCH="PATCH",f.HEAD="HEAD",f.OPTIONS="OPTIONS",exports.HttpUpload=void 0,(y=exports.HttpUpload||(exports.HttpUpload={})).UPLOAD="UPLOAD",y.RELATED="RELATED";class T{static register(t,e){return this.registered[t]=Object.assign({},e),this}static all(){return Object.assign({},this.registered)}}T.registered={};class E{constructor(t){var e,r,s,o;this.defaultHeaders={"Content-Type":"application/json"},this.method=E.method,this.storage=E.storage,this.interceptors={request:[],response:[],transform:[],error:[]};const n="string"==typeof t||void 0===t?{baseURL:null!=t?t:p}:{baseURL:null!==(e=t.baseURL)&&void 0!==e?e:p,allowedOrigins:t.allowedOrigins};this.baseURL=null!==(s=null!==(r=n.baseURL)&&void 0!==r?r:p)&&void 0!==s?s:"/";const i=new Set;for(const t of null!==(o=n.allowedOrigins)&&void 0!==o?o:[]){const e=a(t);if(!e)throw new Error(`Http: invalid allowedOrigins entry: ${t}`);i.add(e)}const c=a(this.baseURL);c&&i.add(c),this.allowedOrigins=i}isAllowedOrigin(t){return 0===this.allowedOrigins.size||this.allowedOrigins.has(t)}assertSameOriginResponse(t,e,r){const s=Object.keys(r).some(t=>"authorization"===t.toLowerCase()&&!!r[t]),o=Object.keys(r).some(t=>"cookie"===t.toLowerCase()&&!!r[t]);if(!s&&!o)return;const n=a(t);if(!n)return;const i=e.url?a(e.url):null;if(i&&i!==n&&!this.isAllowedOrigin(i))throw new Error(`Http: credentialed request was redirected from ${n} to untrusted origin ${i}`)}static on(...t){const[e,r]=t,s=E.interceptors[e];s.includes(r)||s.push(r),E.interceptors[e]=s}static off(...t){const[e,r]=t,s=E.interceptors[e];E.interceptors[e]=s.filter(t=>t!==r)}setStorage(t){this.storage=t}isValidQuery(t){return"string"==typeof t||t instanceof URLSearchParams||(Array.isArray(t)?t.every(t=>Array.isArray(t)&&2===t.length&&"string"==typeof t[0]&&n.Serialize.Primitive.isPrimitive(t[1])):"object"==typeof t&&null!==t&&Object.values(t).every(t=>n.Serialize.Primitive.isPrimitive(t)))}getQuery(t){const{method:e,body:r,query:s}=t,o=e===exports.HttpMethod.GET&&this.isValidQuery(r)?r:s;if(!o||"object"!=typeof o)return"string"==typeof o?o:"";if(o instanceof URLSearchParams)return o.toString();const i=l(o);return n.Serialize.queryString.stringify(i,{skipNull:!0,skipEmptyString:!0})}addHeaders(t){this.defaultHeaders=Object.assign(Object.assign({},this.defaultHeaders),t)}on(...t){const[e,r]=t,s=this.interceptors[e];return s.includes(r)||s.push(r),this.interceptors[e]=s,this}off(...t){const[e,r]=t,s=this.interceptors[e];return this.interceptors[e]=s.filter(t=>t!==r),this}getToken(){const t=u||E.authTokenKey,e=h||E.authHeaderKey,r=g||E.authHeaderType;if(!t||!e)return{key:e,value:""};let s="";this.storage&&(s=this.storage.getItem(t)||"");const o={key:e,value:""};return s&&/^[\x21-\x7E]+$/.test(s)&&(o.value=r?`${r} ${s}`:s),o}getHeaders(t={},e){const r=this.getToken();r.key in t&&t[r.key]||(t[r.key]=r.value),e&&"Content-Type"in t?delete t["Content-Type"]:t["Content-Type"]=t["Content-Type"]||this.defaultHeaders["Content-Type"]||"application/json";const s=Object.assign(Object.assign(Object.assign({},this.defaultHeaders),t),E.extraHeaders||{});return E.extraHeaders=null,s}getURL(t){const{url:e="",params:r={}}=t,o=this.getQuery(t);let i;if(e){if(e.startsWith("//"))throw new Error(`Http: protocol-relative URLs are not allowed: ${e}`);if(/^[a-z][a-z0-9+.-]*:/i.test(e)){let t;try{t=new URL(e)}catch(t){throw new Error(`Http: invalid absolute URL: ${e}`)}if("http:"!==t.protocol&&"https:"!==t.protocol)throw new Error(`Http: unsupported URL scheme: ${t.protocol}`);if(!this.isAllowedOrigin(t.origin))throw new Error(`Http: URL origin '${t.origin}' is not in allowedOrigins`);i=t.toString()}else{const t=(this.baseURL||"/").replace(/\/+$/,""),r=e.replace(/^\/+/,"");i=t?`${t}/${r}`:`/${r}`}}else i=this.baseURL||"/";if(o){const t=i.includes("?")?"&":"?";i+=`${t}${o}`}if(!i.includes("{")||!i.includes("}"))return i;const a=Array.isArray(r)?r:l(r);return i.replace(/\{([a-zA-Z0-9_.-]+)\}/g,(t,e)=>{const r=s.get(a,e);return null==r||"object"==typeof r?"":n.Serialize.URL.encode(String(r))})}getBody(t){const{method:e,body:s}=t;if(e!==exports.HttpMethod.GET&&e!==exports.HttpMethod.HEAD&&e!==exports.HttpMethod.OPTIONS&&e!==exports.HttpMethod.DELETE)return e!==exports.HttpMethod.POST&&e!==exports.HttpMethod.PUT&&e!==exports.HttpMethod.PATCH||!r.isFormData(s)?s instanceof Uint8Array||s instanceof ArrayBuffer?s:void 0!==s?JSON.stringify(s):void 0:s}async request(t){const{method:e=exports.HttpMethod.GET}=t;try{let s=Object.assign({},t);const o=[...E.interceptors.request,...this.interceptors.request];for(const t of o)s=await t(s);const n=this.getURL(s),i=this.getHeaders(s.headers||{});r.isFormData(s.body)&&"Content-Type"in i&&delete i["Content-Type"];const a={};if(s.configs)for(const t of Object.keys(s.configs))d.has(t)&&(a[t]=s.configs[t]);const c=await fetch(n,Object.assign(Object.assign({},a),{method:s.method||e,headers:i,body:this.getBody(s),signal:s.signal}));this.assertSameOriginResponse(n,c,i);const l=c.headers.get("Content-Type")||"";let p=null;p=l.includes("application/json")?await c.json():await c.text();const u=[...E.interceptors.transform,...this.interceptors.transform];let h=p;for(const t of u)h=await t(h);const g=[...E.interceptors.response,...this.interceptors.response];let f=c;for(const t of g)f=await t(f);const y={};f.headers.forEach((t,e)=>{y[e]=t});const T=f.ok;let H=null;if(!T){H=p.error||p;const t=[...E.interceptors.error,...this.interceptors.error];for(const e of t)H=await e(H)}return{data:h,success:T,error:T?null:H,status:f.status,statusText:f.statusText,headers:y}}catch(t){let e=t instanceof Error?t:new Error(String(t));const r=[...E.interceptors.error,...this.interceptors.error];for(const t of r)e=await t(e);return{data:null,status:0,statusText:"Error",headers:{},error:e,success:!1}}}get(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:exports.HttpMethod.GET,url:t,query:e}))}post(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:exports.HttpMethod.POST,url:t,body:e}))}put(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:exports.HttpMethod.PUT,url:t,body:e}))}patch(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:exports.HttpMethod.PATCH,url:t,body:e}))}delete(t,e){return this.request(Object.assign(Object.assign({},e),{method:exports.HttpMethod.DELETE,url:t}))}head(t,e){return this.request(Object.assign(Object.assign({},e),{method:exports.HttpMethod.HEAD,url:t}))}options(t,e){return this.request(Object.assign(Object.assign({},e),{method:exports.HttpMethod.OPTIONS,url:t}))}upload(s,o,n){const i=(null==n?void 0:n.body)?r.objectToFormData(n.body):new FormData;let a=(null==n?void 0:n.name)||"file";if(Array.isArray(o)?(a.endsWith("[]")&&(a=a.slice(0,-2)),o.forEach((t,e)=>{i.append(`${a}[${e}]`,t,t.name)})):t.isFileList(o)?(a.endsWith("[]")&&(a=a.slice(0,-2)),Array.from(o).forEach((t,e)=>{i.append(`${a}[${e}]`,t,t.name)})):i.append(a,o,o.name),null==n?void 0:n.body){const t=e.flatten(n.body);Object.entries(t).forEach(([t,e])=>{null!=e&&i.append(t,String(e))})}return(null==n?void 0:n.onProgress)&&"undefined"!=typeof XMLHttpRequest?new Promise(async t=>{try{let e=Object.assign(Object.assign({},n),{method:exports.HttpMethod.POST,url:s,body:i});const r=[...E.interceptors.request,...this.interceptors.request];for(const t of r)e=await t(e);const o=this.getURL(e),a=this.getHeaders(e.headers||{},!0),c=new XMLHttpRequest;c.upload.addEventListener("progress",t=>{var e;if(t.lengthComputable){const r=Math.round(t.loaded/t.total*100);null===(e=n.onProgress)||void 0===e||e.call(n,{loaded:t.loaded,total:t.total,percentage:r})}}),c.addEventListener("load",async()=>{try{const e=c.getResponseHeader("Content-Type")||"";let r=null;r=e.includes("application/json")?JSON.parse(c.responseText):c.responseText;const s=[...E.interceptors.transform,...this.interceptors.transform];let o=r;for(const t of s)o=await t(o);const n=[...E.interceptors.response,...this.interceptors.response];let i=new Response(c.responseText,{status:c.status,statusText:c.statusText,headers:a});for(const t of n)i=await t(i);const d={};i.headers.forEach((t,e)=>{d[e]=t});const l=i.ok;t({data:o,success:l,error:l?null:o,status:i.status,statusText:i.statusText,headers:d})}catch(e){const r=[...E.interceptors.error,...this.interceptors.error];for(const t of r)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}}),c.addEventListener("error",e=>{const r=[...E.interceptors.error,...this.interceptors.error];for(const t of r)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}),c.addEventListener("abort",()=>{t({data:null,status:0,statusText:"Aborted",headers:{},error:null,success:!1})}),c.open(e.method||exports.HttpMethod.POST,o,!0),Object.entries(a).forEach(([t,e])=>{"content-type"!==(null==t?void 0:t.toLowerCase())&&e&&c.setRequestHeader(t,e)}),c.send(e.body)}catch(e){const r=[...E.interceptors.error,...this.interceptors.error];for(const t of r)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}}):this.request({method:exports.HttpMethod.POST,url:s,body:i,headers:null==n?void 0:n.headers,params:null==n?void 0:n.params,signal:null==n?void 0:n.signal,query:null==n?void 0:n.query})}related(t,e,r){const s=o.sanitizeMime(r.contentType),n=o.sanitizeMime(r.metadataMimeType||"application/json"),i=`----related-boundary-${Date.now()}-${Math.random().toString(36).slice(2)}`,a=JSON.stringify(r.metadata),c=new TextEncoder,d=c.encode(`--${i}\r\nContent-Type: ${n}; charset=UTF-8\r\n\r\n`+a+"\r\n"),l=c.encode(`--${i}\r\nContent-Type: ${s}\r\nContent-Transfer-Encoding: binary\r\n\r\n`),p=c.encode(`\r\n--${i}--`),u=e instanceof Uint8Array?e:new Uint8Array(e),h=new Uint8Array(d.length+l.length+u.length+p.length);return[d,l,u,p].reduce((t,e)=>(h.set(e,t),t+e.length),0),this.request({method:exports.HttpMethod.POST,url:t,body:h,headers:Object.assign(Object.assign({},r.headers||{}),{"Content-Type":`multipart/related; boundary=${i}`}),params:r.params,signal:r.signal,query:r.query})}static createFactory(t={}){const r=t.http||new E(t.baseURL);return function(s,o){const n=e.flatten("function"==typeof t.endpoint?t.endpoint():t.endpoint||{})[s];return{key:s,fn:async(...e)=>{const s="function"==typeof t.storage?await t.storage():t.storage;switch(s&&r.setStorage(s),o){case exports.HttpMethod.POST:return await r.post(n,...e);case exports.HttpMethod.PUT:return await r.put(n,...e);case exports.HttpMethod.PATCH:return await r.patch(n,...e);case exports.HttpMethod.DELETE:return await r.delete(n,...e);case exports.HttpMethod.HEAD:return await r.head(n,...e);case exports.HttpMethod.OPTIONS:return await r.options(n,...e);case exports.HttpUpload.UPLOAD:return await r.upload(n,...e);case exports.HttpUpload.RELATED:return await r.related(n,...e);default:return await r.get(n,...e)}}}}}}E.method=exports.HttpMethod,E.Endpoint=T,E.authTokenKey="access_token",E.authHeaderKey="Authorization",E.authHeaderType="Bearer",E.authDetectToken=["localStorage","sessionStorage","cookie"],E.extraHeaders=null,E.storage=null,E.interceptors={request:[],response:[],transform:[],error:[]},exports.API_AUTH_HEADER_KEY=h,exports.API_AUTH_HEADER_TYPE=g,exports.API_AUTH_TOKEN_KEY=u,exports.DEFAULT_BASE_URL=p,exports.Endpoint=T,exports.Http=E;
package/dist/http.mjs CHANGED
@@ -1 +1 @@
1
- import{isFileList as e}from"./utilities/filelist.mjs";import{flatten as t}from"./utilities/flatten.mjs";import{isFormData as r,objectToFormData as s}from"./utilities/formdata.mjs";import{sanitizeMime as n}from"./utilities/sanitize-mime.mjs";import{Serialize as o}from"./serialize.mjs";import{getEnv as a}from"./env.mjs";const i=a("API_URL","/"),c=a("API_AUTH_TOKEN_KEY"),u=a("API_AUTH_HEADER_KEY"),d=a("API_AUTH_HEADER_TYPE");var l,p;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE",e.PATCH="PATCH",e.HEAD="HEAD",e.OPTIONS="OPTIONS"}(l||(l={})),function(e){e.UPLOAD="UPLOAD",e.RELATED="RELATED"}(p||(p={}));class h{static register(e,t){return this.registered[e]=Object.assign({},t),this}static all(){return Object.assign({},this.registered)}}h.registered={};class f{constructor(e=i){this.baseURL=e,this.defaultHeaders={"Content-Type":"application/json"},this.method=f.method,this.storage=f.storage,this.interceptors={request:[],response:[],transform:[],error:[]}}static on(...e){const[t,r]=e,s=f.interceptors[t];s.includes(r)||s.push(r),f.interceptors[t]=s}static off(...e){const[t,r]=e,s=f.interceptors[t];f.interceptors[t]=s.filter(e=>e!==r)}setStorage(e){this.storage=e}isValidQuery(e){return"string"==typeof e||e instanceof URLSearchParams||(Array.isArray(e)?e.every(e=>Array.isArray(e)&&2===e.length&&"string"==typeof e[0]&&o.Primitive.isPrimitive(e[1])):"object"==typeof e&&null!==e&&Object.values(e).every(e=>o.Primitive.isPrimitive(e)))}getQuery(e){const{method:t,body:r,query:s}=e,n=t===l.GET&&this.isValidQuery(r)?r:s;return n&&"object"==typeof n?n instanceof URLSearchParams?n.toString():o.queryString.stringify(n,{skipNull:!0,skipEmptyString:!0}):"string"==typeof n?n:""}addHeaders(e){this.defaultHeaders=Object.assign(Object.assign({},this.defaultHeaders),e)}on(...e){const[t,r]=e,s=this.interceptors[t];return s.includes(r)||s.push(r),this.interceptors[t]=s,this}off(...e){const[t,r]=e,s=this.interceptors[t];return this.interceptors[t]=s.filter(e=>e!==r),this}getToken(){const e=c||f.authTokenKey,t=u||f.authHeaderKey,r=d||f.authHeaderType;if(!e||!t)return{key:t,value:""};let s="";this.storage&&(s=this.storage.getItem(e)||"");const n={key:t,value:""};return s&&(n.value=s,r&&(n.value=`${r} ${s}`)),n}getHeaders(e={},t){const r=this.getToken();r.key in e&&e[r.key]||(e[r.key]=r.value),t&&"Content-Type"in e?delete e["Content-Type"]:e["Content-Type"]=e["Content-Type"]||this.defaultHeaders["Content-Type"]||"application/json";const s=Object.assign(Object.assign(Object.assign({},this.defaultHeaders),e),f.extraHeaders||{});return f.extraHeaders=null,s}getURL(e){var t;const{url:r,params:s={}}=e,n=this.getQuery(e);let a=r;if(r)if(r.match(/^https?:\/\//))a=r;else{a=`${null===(t=this.baseURL)||void 0===t?void 0:t.replace(/\/+$/,"")}/${r.replace(/^\/+/,"")}`}if(n){const e=a.includes("?")?"&":"?";a+=`${e}${n}`}return o.interpolate(a,s)}getBody(e){const{method:t,body:s}=e;if(t!==l.GET&&t!==l.HEAD&&t!==l.OPTIONS&&t!==l.DELETE)return t!==l.POST&&t!==l.PUT&&t!==l.PATCH||!r(s)?s instanceof Uint8Array||s instanceof ArrayBuffer?s:void 0!==s?JSON.stringify(s):void 0:s}async request(e){const{method:t=l.GET}=e;try{let s=Object.assign({},e);const n=[...f.interceptors.request,...this.interceptors.request];for(const e of n)s=await e(s);const o=this.getURL(s),a=this.getHeaders(s.headers||{});r(s.body)&&"Content-Type"in a&&delete a["Content-Type"];const i=await fetch(o,{method:s.method||t,headers:a,body:this.getBody(s),signal:s.signal}),c=i.headers.get("Content-Type")||"";let u=null;u=c.includes("application/json")?await i.json():await i.text();const d=[...f.interceptors.transform,...this.interceptors.transform];let l=u;for(const e of d)l=await e(l);const p=[...f.interceptors.response,...this.interceptors.response];let h=i;for(const e of p)h=await e(h);const y={};h.headers.forEach((e,t)=>{y[t]=e});const g=h.ok;let T=null;if(!g){T=u.error||u;const e=[...f.interceptors.error,...this.interceptors.error];for(const t of e)T=await t(T)}return{data:l,success:g,error:g?null:T,status:h.status,statusText:h.statusText,headers:y}}catch(e){let t=e instanceof Error?e:new Error(String(e));const r=[...f.interceptors.error,...this.interceptors.error];for(const e of r)t=await e(t);return{data:null,status:0,statusText:"Error",headers:{},error:t,success:!1}}}get(e,t,r){return this.request(Object.assign(Object.assign({},r),{method:l.GET,url:e,query:t}))}post(e,t,r){return this.request(Object.assign(Object.assign({},r),{method:l.POST,url:e,body:t}))}put(e,t,r){return this.request(Object.assign(Object.assign({},r),{method:l.PUT,url:e,body:t}))}patch(e,t,r){return this.request(Object.assign(Object.assign({},r),{method:l.PATCH,url:e,body:t}))}delete(e,t){return this.request(Object.assign(Object.assign({},t),{method:l.DELETE,url:e}))}head(e,t){return this.request(Object.assign(Object.assign({},t),{method:l.HEAD,url:e}))}options(e,t){return this.request(Object.assign(Object.assign({},t),{method:l.OPTIONS,url:e}))}upload(r,n,o){const a=(null==o?void 0:o.body)?s(o.body):new FormData;let i=(null==o?void 0:o.name)||"file";if(Array.isArray(n)?(i.endsWith("[]")&&(i=i.slice(0,-2)),n.forEach((e,t)=>{a.append(`${i}[${t}]`,e,e.name)})):e(n)?(i.endsWith("[]")&&(i=i.slice(0,-2)),Array.from(n).forEach((e,t)=>{a.append(`${i}[${t}]`,e,e.name)})):a.append(i,n,n.name),null==o?void 0:o.body){const e=t(o.body);Object.entries(e).forEach(([e,t])=>{null!=t&&a.append(e,String(t))})}return(null==o?void 0:o.onProgress)&&"undefined"!=typeof XMLHttpRequest?new Promise(async e=>{try{let t=Object.assign(Object.assign({},o),{method:l.POST,url:r,body:a});const s=[...f.interceptors.request,...this.interceptors.request];for(const e of s)t=await e(t);const n=this.getURL(t),i=this.getHeaders(t.headers||{},!0),c=new XMLHttpRequest;c.upload.addEventListener("progress",e=>{var t;if(e.lengthComputable){const r=Math.round(e.loaded/e.total*100);null===(t=o.onProgress)||void 0===t||t.call(o,{loaded:e.loaded,total:e.total,percentage:r})}}),c.addEventListener("load",async()=>{try{const t=c.getResponseHeader("Content-Type")||"";let r=null;r=t.includes("application/json")?JSON.parse(c.responseText):c.responseText;const s=[...f.interceptors.transform,...this.interceptors.transform];let n=r;for(const e of s)n=await e(n);const o=[...f.interceptors.response,...this.interceptors.response];let a=new Response(c.responseText,{status:c.status,statusText:c.statusText,headers:i});for(const e of o)a=await e(a);const u={};a.headers.forEach((e,t)=>{u[t]=e});const d=a.ok;e({data:n,success:d,error:d?null:n,status:a.status,statusText:a.statusText,headers:u})}catch(t){const r=[...f.interceptors.error,...this.interceptors.error];for(const e of r)e(t);e({data:null,status:0,statusText:"Error",headers:{},error:t,success:!1})}}),c.addEventListener("error",t=>{const r=[...f.interceptors.error,...this.interceptors.error];for(const e of r)e(t);e({data:null,status:0,statusText:"Error",headers:{},error:t,success:!1})}),c.addEventListener("abort",()=>{e({data:null,status:0,statusText:"Aborted",headers:{},error:null,success:!1})}),c.open(t.method||l.POST,n,!0),Object.entries(i).forEach(([e,t])=>{"content-type"!==(null==e?void 0:e.toLowerCase())&&t&&c.setRequestHeader(e,t)}),c.send(t.body)}catch(t){const r=[...f.interceptors.error,...this.interceptors.error];for(const e of r)e(t);e({data:null,status:0,statusText:"Error",headers:{},error:t,success:!1})}}):this.request({method:l.POST,url:r,body:a,headers:null==o?void 0:o.headers,params:null==o?void 0:o.params,signal:null==o?void 0:o.signal,query:null==o?void 0:o.query})}related(e,t,r){const s=n(r.contentType),o=n(r.metadataMimeType||"application/json"),a=`----related-boundary-${Date.now()}-${Math.random().toString(36).slice(2)}`,i=JSON.stringify(r.metadata),c=new TextEncoder,u=c.encode(`--${a}\r\nContent-Type: ${o}; charset=UTF-8\r\n\r\n`+i+"\r\n"),d=c.encode(`--${a}\r\nContent-Type: ${s}\r\nContent-Transfer-Encoding: binary\r\n\r\n`),p=c.encode(`\r\n--${a}--`),h=t instanceof Uint8Array?t:new Uint8Array(t),f=new Uint8Array(u.length+d.length+h.length+p.length);return[u,d,h,p].reduce((e,t)=>(f.set(t,e),e+t.length),0),this.request({method:l.POST,url:e,body:f,headers:Object.assign(Object.assign({},r.headers||{}),{"Content-Type":`multipart/related; boundary=${a}`}),params:r.params,signal:r.signal,query:r.query})}static createFactory(e={}){const r=e.http||new f(e.baseURL);return function(s,n){const o=t("function"==typeof e.endpoint?e.endpoint():e.endpoint||{})[s];return{key:s,fn:async(...t)=>{const s="function"==typeof e.storage?await e.storage():e.storage;switch(s&&r.setStorage(s),n){case l.POST:return await r.post(o,...t);case l.PUT:return await r.put(o,...t);case l.PATCH:return await r.patch(o,...t);case l.DELETE:return await r.delete(o,...t);case l.HEAD:return await r.head(o,...t);case l.OPTIONS:return await r.options(o,...t);case p.UPLOAD:return await r.upload(o,...t);case p.RELATED:return await r.related(o,...t);default:return await r.get(o,...t)}}}}}}f.method=l,f.Endpoint=h,f.authTokenKey="access_token",f.authHeaderKey="Authorization",f.authHeaderType="Bearer",f.authDetectToken=["localStorage","sessionStorage","cookie"],f.extraHeaders=null,f.storage=null,f.interceptors={request:[],response:[],transform:[],error:[]};export{u as API_AUTH_HEADER_KEY,d as API_AUTH_HEADER_TYPE,c as API_AUTH_TOKEN_KEY,i as DEFAULT_BASE_URL,h as Endpoint,f as Http,l as HttpMethod,p as HttpUpload};
1
+ import{isFileList as e}from"./utilities/filelist.mjs";import{flatten as t}from"./utilities/flatten.mjs";import{isFormData as r,objectToFormData as s}from"./utilities/formdata.mjs";import{get as n}from"./utilities/get.mjs";import{sanitizeMime as o}from"./utilities/sanitize-mime.mjs";import{Serialize as i}from"./serialize.mjs";import{getEnv as a}from"./env.mjs";function c(e){try{return new URL(e).origin}catch(e){return null}}const l=new Set(["__proto__","constructor","prototype"]),u=new Set(["credentials","cache","mode","redirect","referrer","referrerPolicy","integrity","keepalive","priority","duplex","window","next","cf","dispatcher"]);function d(e){const t=Object.create(null);for(const r of Object.keys(e))l.has(r)||(t[r]=e[r]);return t}const p=a("API_URL","/"),h=a("API_AUTH_TOKEN_KEY"),f=a("API_AUTH_HEADER_KEY"),g=a("API_AUTH_HEADER_TYPE");var y,m;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE",e.PATCH="PATCH",e.HEAD="HEAD",e.OPTIONS="OPTIONS"}(y||(y={})),function(e){e.UPLOAD="UPLOAD",e.RELATED="RELATED"}(m||(m={}));class T{static register(e,t){return this.registered[e]=Object.assign({},t),this}static all(){return Object.assign({},this.registered)}}T.registered={};class O{constructor(e){var t,r,s,n;this.defaultHeaders={"Content-Type":"application/json"},this.method=O.method,this.storage=O.storage,this.interceptors={request:[],response:[],transform:[],error:[]};const o="string"==typeof e||void 0===e?{baseURL:null!=e?e:p}:{baseURL:null!==(t=e.baseURL)&&void 0!==t?t:p,allowedOrigins:e.allowedOrigins};this.baseURL=null!==(s=null!==(r=o.baseURL)&&void 0!==r?r:p)&&void 0!==s?s:"/";const i=new Set;for(const e of null!==(n=o.allowedOrigins)&&void 0!==n?n:[]){const t=c(e);if(!t)throw new Error(`Http: invalid allowedOrigins entry: ${e}`);i.add(t)}const a=c(this.baseURL);a&&i.add(a),this.allowedOrigins=i}isAllowedOrigin(e){return 0===this.allowedOrigins.size||this.allowedOrigins.has(e)}assertSameOriginResponse(e,t,r){const s=Object.keys(r).some(e=>"authorization"===e.toLowerCase()&&!!r[e]),n=Object.keys(r).some(e=>"cookie"===e.toLowerCase()&&!!r[e]);if(!s&&!n)return;const o=c(e);if(!o)return;const i=t.url?c(t.url):null;if(i&&i!==o&&!this.isAllowedOrigin(i))throw new Error(`Http: credentialed request was redirected from ${o} to untrusted origin ${i}`)}static on(...e){const[t,r]=e,s=O.interceptors[t];s.includes(r)||s.push(r),O.interceptors[t]=s}static off(...e){const[t,r]=e,s=O.interceptors[t];O.interceptors[t]=s.filter(e=>e!==r)}setStorage(e){this.storage=e}isValidQuery(e){return"string"==typeof e||e instanceof URLSearchParams||(Array.isArray(e)?e.every(e=>Array.isArray(e)&&2===e.length&&"string"==typeof e[0]&&i.Primitive.isPrimitive(e[1])):"object"==typeof e&&null!==e&&Object.values(e).every(e=>i.Primitive.isPrimitive(e)))}getQuery(e){const{method:t,body:r,query:s}=e,n=t===y.GET&&this.isValidQuery(r)?r:s;if(!n||"object"!=typeof n)return"string"==typeof n?n:"";if(n instanceof URLSearchParams)return n.toString();const o=d(n);return i.queryString.stringify(o,{skipNull:!0,skipEmptyString:!0})}addHeaders(e){this.defaultHeaders=Object.assign(Object.assign({},this.defaultHeaders),e)}on(...e){const[t,r]=e,s=this.interceptors[t];return s.includes(r)||s.push(r),this.interceptors[t]=s,this}off(...e){const[t,r]=e,s=this.interceptors[t];return this.interceptors[t]=s.filter(e=>e!==r),this}getToken(){const e=h||O.authTokenKey,t=f||O.authHeaderKey,r=g||O.authHeaderType;if(!e||!t)return{key:t,value:""};let s="";this.storage&&(s=this.storage.getItem(e)||"");const n={key:t,value:""};return s&&/^[\x21-\x7E]+$/.test(s)&&(n.value=r?`${r} ${s}`:s),n}getHeaders(e={},t){const r=this.getToken();r.key in e&&e[r.key]||(e[r.key]=r.value),t&&"Content-Type"in e?delete e["Content-Type"]:e["Content-Type"]=e["Content-Type"]||this.defaultHeaders["Content-Type"]||"application/json";const s=Object.assign(Object.assign(Object.assign({},this.defaultHeaders),e),O.extraHeaders||{});return O.extraHeaders=null,s}getURL(e){const{url:t="",params:r={}}=e,s=this.getQuery(e);let o;if(t){if(t.startsWith("//"))throw new Error(`Http: protocol-relative URLs are not allowed: ${t}`);if(/^[a-z][a-z0-9+.-]*:/i.test(t)){let e;try{e=new URL(t)}catch(e){throw new Error(`Http: invalid absolute URL: ${t}`)}if("http:"!==e.protocol&&"https:"!==e.protocol)throw new Error(`Http: unsupported URL scheme: ${e.protocol}`);if(!this.isAllowedOrigin(e.origin))throw new Error(`Http: URL origin '${e.origin}' is not in allowedOrigins`);o=e.toString()}else{const e=(this.baseURL||"/").replace(/\/+$/,""),r=t.replace(/^\/+/,"");o=e?`${e}/${r}`:`/${r}`}}else o=this.baseURL||"/";if(s){const e=o.includes("?")?"&":"?";o+=`${e}${s}`}if(!o.includes("{")||!o.includes("}"))return o;const a=Array.isArray(r)?r:d(r);return o.replace(/\{([a-zA-Z0-9_.-]+)\}/g,(e,t)=>{const r=n(a,t);return null==r||"object"==typeof r?"":i.URL.encode(String(r))})}getBody(e){const{method:t,body:s}=e;if(t!==y.GET&&t!==y.HEAD&&t!==y.OPTIONS&&t!==y.DELETE)return t!==y.POST&&t!==y.PUT&&t!==y.PATCH||!r(s)?s instanceof Uint8Array||s instanceof ArrayBuffer?s:void 0!==s?JSON.stringify(s):void 0:s}async request(e){const{method:t=y.GET}=e;try{let s=Object.assign({},e);const n=[...O.interceptors.request,...this.interceptors.request];for(const e of n)s=await e(s);const o=this.getURL(s),i=this.getHeaders(s.headers||{});r(s.body)&&"Content-Type"in i&&delete i["Content-Type"];const a={};if(s.configs)for(const e of Object.keys(s.configs))u.has(e)&&(a[e]=s.configs[e]);const c=await fetch(o,Object.assign(Object.assign({},a),{method:s.method||t,headers:i,body:this.getBody(s),signal:s.signal}));this.assertSameOriginResponse(o,c,i);const l=c.headers.get("Content-Type")||"";let d=null;d=l.includes("application/json")?await c.json():await c.text();const p=[...O.interceptors.transform,...this.interceptors.transform];let h=d;for(const e of p)h=await e(h);const f=[...O.interceptors.response,...this.interceptors.response];let g=c;for(const e of f)g=await e(g);const y={};g.headers.forEach((e,t)=>{y[t]=e});const m=g.ok;let T=null;if(!m){T=d.error||d;const e=[...O.interceptors.error,...this.interceptors.error];for(const t of e)T=await t(T)}return{data:h,success:m,error:m?null:T,status:g.status,statusText:g.statusText,headers:y}}catch(e){let t=e instanceof Error?e:new Error(String(e));const r=[...O.interceptors.error,...this.interceptors.error];for(const e of r)t=await e(t);return{data:null,status:0,statusText:"Error",headers:{},error:t,success:!1}}}get(e,t,r){return this.request(Object.assign(Object.assign({},r),{method:y.GET,url:e,query:t}))}post(e,t,r){return this.request(Object.assign(Object.assign({},r),{method:y.POST,url:e,body:t}))}put(e,t,r){return this.request(Object.assign(Object.assign({},r),{method:y.PUT,url:e,body:t}))}patch(e,t,r){return this.request(Object.assign(Object.assign({},r),{method:y.PATCH,url:e,body:t}))}delete(e,t){return this.request(Object.assign(Object.assign({},t),{method:y.DELETE,url:e}))}head(e,t){return this.request(Object.assign(Object.assign({},t),{method:y.HEAD,url:e}))}options(e,t){return this.request(Object.assign(Object.assign({},t),{method:y.OPTIONS,url:e}))}upload(r,n,o){const i=(null==o?void 0:o.body)?s(o.body):new FormData;let a=(null==o?void 0:o.name)||"file";if(Array.isArray(n)?(a.endsWith("[]")&&(a=a.slice(0,-2)),n.forEach((e,t)=>{i.append(`${a}[${t}]`,e,e.name)})):e(n)?(a.endsWith("[]")&&(a=a.slice(0,-2)),Array.from(n).forEach((e,t)=>{i.append(`${a}[${t}]`,e,e.name)})):i.append(a,n,n.name),null==o?void 0:o.body){const e=t(o.body);Object.entries(e).forEach(([e,t])=>{null!=t&&i.append(e,String(t))})}return(null==o?void 0:o.onProgress)&&"undefined"!=typeof XMLHttpRequest?new Promise(async e=>{try{let t=Object.assign(Object.assign({},o),{method:y.POST,url:r,body:i});const s=[...O.interceptors.request,...this.interceptors.request];for(const e of s)t=await e(t);const n=this.getURL(t),a=this.getHeaders(t.headers||{},!0),c=new XMLHttpRequest;c.upload.addEventListener("progress",e=>{var t;if(e.lengthComputable){const r=Math.round(e.loaded/e.total*100);null===(t=o.onProgress)||void 0===t||t.call(o,{loaded:e.loaded,total:e.total,percentage:r})}}),c.addEventListener("load",async()=>{try{const t=c.getResponseHeader("Content-Type")||"";let r=null;r=t.includes("application/json")?JSON.parse(c.responseText):c.responseText;const s=[...O.interceptors.transform,...this.interceptors.transform];let n=r;for(const e of s)n=await e(n);const o=[...O.interceptors.response,...this.interceptors.response];let i=new Response(c.responseText,{status:c.status,statusText:c.statusText,headers:a});for(const e of o)i=await e(i);const l={};i.headers.forEach((e,t)=>{l[t]=e});const u=i.ok;e({data:n,success:u,error:u?null:n,status:i.status,statusText:i.statusText,headers:l})}catch(t){const r=[...O.interceptors.error,...this.interceptors.error];for(const e of r)e(t);e({data:null,status:0,statusText:"Error",headers:{},error:t,success:!1})}}),c.addEventListener("error",t=>{const r=[...O.interceptors.error,...this.interceptors.error];for(const e of r)e(t);e({data:null,status:0,statusText:"Error",headers:{},error:t,success:!1})}),c.addEventListener("abort",()=>{e({data:null,status:0,statusText:"Aborted",headers:{},error:null,success:!1})}),c.open(t.method||y.POST,n,!0),Object.entries(a).forEach(([e,t])=>{"content-type"!==(null==e?void 0:e.toLowerCase())&&t&&c.setRequestHeader(e,t)}),c.send(t.body)}catch(t){const r=[...O.interceptors.error,...this.interceptors.error];for(const e of r)e(t);e({data:null,status:0,statusText:"Error",headers:{},error:t,success:!1})}}):this.request({method:y.POST,url:r,body:i,headers:null==o?void 0:o.headers,params:null==o?void 0:o.params,signal:null==o?void 0:o.signal,query:null==o?void 0:o.query})}related(e,t,r){const s=o(r.contentType),n=o(r.metadataMimeType||"application/json"),i=`----related-boundary-${Date.now()}-${Math.random().toString(36).slice(2)}`,a=JSON.stringify(r.metadata),c=new TextEncoder,l=c.encode(`--${i}\r\nContent-Type: ${n}; charset=UTF-8\r\n\r\n`+a+"\r\n"),u=c.encode(`--${i}\r\nContent-Type: ${s}\r\nContent-Transfer-Encoding: binary\r\n\r\n`),d=c.encode(`\r\n--${i}--`),p=t instanceof Uint8Array?t:new Uint8Array(t),h=new Uint8Array(l.length+u.length+p.length+d.length);return[l,u,p,d].reduce((e,t)=>(h.set(t,e),e+t.length),0),this.request({method:y.POST,url:e,body:h,headers:Object.assign(Object.assign({},r.headers||{}),{"Content-Type":`multipart/related; boundary=${i}`}),params:r.params,signal:r.signal,query:r.query})}static createFactory(e={}){const r=e.http||new O(e.baseURL);return function(s,n){const o=t("function"==typeof e.endpoint?e.endpoint():e.endpoint||{})[s];return{key:s,fn:async(...t)=>{const s="function"==typeof e.storage?await e.storage():e.storage;switch(s&&r.setStorage(s),n){case y.POST:return await r.post(o,...t);case y.PUT:return await r.put(o,...t);case y.PATCH:return await r.patch(o,...t);case y.DELETE:return await r.delete(o,...t);case y.HEAD:return await r.head(o,...t);case y.OPTIONS:return await r.options(o,...t);case m.UPLOAD:return await r.upload(o,...t);case m.RELATED:return await r.related(o,...t);default:return await r.get(o,...t)}}}}}}O.method=y,O.Endpoint=T,O.authTokenKey="access_token",O.authHeaderKey="Authorization",O.authHeaderType="Bearer",O.authDetectToken=["localStorage","sessionStorage","cookie"],O.extraHeaders=null,O.storage=null,O.interceptors={request:[],response:[],transform:[],error:[]};export{f as API_AUTH_HEADER_KEY,g as API_AUTH_HEADER_TYPE,h as API_AUTH_TOKEN_KEY,p as DEFAULT_BASE_URL,T as Endpoint,O as Http,y as HttpMethod,m as HttpUpload};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecosy/core",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A modular, tree-shakable collection of essential utilities, serialization primitives, and event-driven patterns for modern TypeScript applications",
5
5
  "license": "MIT",
6
6
  "publishConfig": {