@ecosy/core 0.3.0 → 0.3.2

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,40 @@ 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>;
61
+ /**
62
+ * Default `configs` merged into every request sent through this
63
+ * instance. Same shape and allowlist as {@link HttpRequest.configs}:
64
+ * `credentials`, `cache`, `mode`, `redirect`, `referrer`,
65
+ * `referrerPolicy`, `integrity`, `keepalive`, `priority`, `duplex`,
66
+ * plus framework extensions (`next`, `cf`, `dispatcher`). Per-call
67
+ * `configs` override these.
68
+ */
69
+ configs?: Record<string, unknown>;
36
70
  }
37
71
  /** Storage adapter interface for reading/writing auth tokens (e.g. `localStorage`). */
38
72
  export interface HttpStorage {
@@ -114,13 +148,23 @@ export declare class Endpoint {
114
148
  * ```
115
149
  */
116
150
  export declare class Http {
117
- private readonly baseURL;
118
151
  static readonly method: typeof HttpMethod;
119
152
  static readonly Endpoint: typeof Endpoint;
120
153
  static authTokenKey: string;
121
154
  static authHeaderKey: string;
122
155
  static authHeaderType: string;
123
156
  static authDetectToken: string[];
157
+ /**
158
+ * One-shot headers merged into the very next request across all
159
+ * instances, then cleared. Intended for request-scoped values like
160
+ * CSRF tokens or correlation IDs that callers don't want to thread
161
+ * through every call site.
162
+ *
163
+ * Note: this is a process-wide mutable static. In concurrent async
164
+ * contexts (e.g. multiple tenants sharing one process) it is the
165
+ * caller's responsibility to ensure the set → dispatch → reset
166
+ * sequence is not interleaved.
167
+ */
124
168
  static extraHeaders: Record<string, string> | null;
125
169
  static storage: HttpStorage | null;
126
170
  private defaultHeaders;
@@ -128,7 +172,35 @@ export declare class Http {
128
172
  private storage;
129
173
  private static interceptors;
130
174
  private interceptors;
131
- constructor(baseURL?: string | undefined);
175
+ private readonly baseURL;
176
+ private readonly allowedOrigins;
177
+ private readonly defaultConfigs;
178
+ /**
179
+ * @param init - Either a base URL string (backwards-compatible form)
180
+ * or an {@link HttpOptions} object. Using the object form lets
181
+ * callers opt in to additional origins that absolute URLs and
182
+ * redirect responses are permitted to reach. Any other origin is
183
+ * rejected before the request is sent and, for redirects, before
184
+ * the response body is returned.
185
+ *
186
+ * @example
187
+ * ```ts
188
+ * new Http("https://api.example.com");
189
+ * new Http({
190
+ * baseURL: "https://api.example.com",
191
+ * allowedOrigins: ["https://cdn.example.com"],
192
+ * });
193
+ * ```
194
+ */
195
+ constructor(init?: string | HttpOptions);
196
+ /** Whether `origin` is this instance's baseURL origin or an explicitly allowed one. */
197
+ private isAllowedOrigin;
198
+ /**
199
+ * If a request was sent with credentials (Authorization / Cookie) and
200
+ * ended up at a different origin via redirect, refuse to return the
201
+ * response. Defends against token exfil via server-controlled 3xx.
202
+ */
203
+ private assertSameOriginResponse;
132
204
  /** Register a global interceptor (applies to all `Http` instances). */
133
205
  static on(...params: HttpInterceptorParameters): void;
134
206
  /** Remove a global interceptor. */
@@ -153,7 +225,18 @@ export declare class Http {
153
225
  getHeaders(headers?: Record<string, string>, isFormData?: boolean): {
154
226
  [x: string]: string;
155
227
  };
156
- /** Build the full URL from base URL, path, query string, and path params. */
228
+ /**
229
+ * Build the full URL from base URL, path, query string, and path params.
230
+ *
231
+ * Security rules applied here (see SECURITY audit):
232
+ * - Absolute URLs must use `http`/`https` and their origin must match
233
+ * `baseURL`'s origin or an entry in `allowedOrigins`.
234
+ * - Protocol-relative URLs (`//host/…`) are rejected — they silently
235
+ * flip the target host.
236
+ * - Path params (`{id}`) are URL-encoded by `interpolateURL`
237
+ * so a value of `"../admin"` cannot traverse the path.
238
+ * - Proto-pollution keys in `params` are stripped.
239
+ */
157
240
  getURL(options: HttpRequest): string;
158
241
  /** Serialize the request body (JSON, FormData, or binary). Returns `undefined` for bodyless methods. */
159
242
  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))d.has(r)&&(e[r]=t[r]);return e}function p(t){const e=Object.create(null);for(const r of Object.keys(t))c.has(r)||(e[r]=t[r]);return e}const u=i.getEnv("API_URL","/"),h=i.getEnv("API_AUTH_TOKEN_KEY"),g=i.getEnv("API_AUTH_HEADER_KEY"),f=i.getEnv("API_AUTH_HEADER_TYPE");var y,T;exports.HttpMethod=void 0,(y=exports.HttpMethod||(exports.HttpMethod={})).GET="GET",y.POST="POST",y.PUT="PUT",y.DELETE="DELETE",y.PATCH="PATCH",y.HEAD="HEAD",y.OPTIONS="OPTIONS",exports.HttpUpload=void 0,(T=exports.HttpUpload||(exports.HttpUpload={})).UPLOAD="UPLOAD",T.RELATED="RELATED";class E{static register(t,e){return this.registered[t]=Object.assign({},e),this}static all(){return Object.assign({},this.registered)}}E.registered={};class H{constructor(t){var e,r,s;this.defaultHeaders={"Content-Type":"application/json"},this.method=H.method,this.storage=H.storage,this.interceptors={request:[],response:[],transform:[],error:[]};const o="string"==typeof t||void 0===t?{baseURL:null!=t?t:u}:t;this.baseURL=null!==(r=null!==(e=o.baseURL)&&void 0!==e?e:u)&&void 0!==r?r:"/";const n=new Set;for(const t of null!==(s=o.allowedOrigins)&&void 0!==s?s:[]){const e=a(t);if(!e)throw new Error(`Http: invalid allowedOrigins entry: ${t}`);n.add(e)}const i=a(this.baseURL);i&&n.add(i),this.allowedOrigins=n,this.defaultConfigs=o.configs?l(o.configs):{}}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=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]&&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=p(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=h||H.authTokenKey,e=g||H.authHeaderKey,r=f||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&&/^[\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),H.extraHeaders||{});return H.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:p(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=[...H.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=Object.assign(Object.assign({},this.defaultConfigs),s.configs?l(s.configs):{}),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 d=c.headers.get("Content-Type")||"";let p=null;p=d.includes("application/json")?await c.json():await c.text();const u=[...H.interceptors.transform,...this.interceptors.transform];let h=p;for(const t of u)h=await t(h);const g=[...H.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 E=null;if(!T){E=p.error||p;const t=[...H.interceptors.error,...this.interceptors.error];for(const e of t)E=await e(E)}return{data:h,success:T,error:T?null:E,status:f.status,statusText:f.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 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=[...H.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=[...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 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=[...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(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=[...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: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 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=E,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=g,exports.API_AUTH_HEADER_TYPE=f,exports.API_AUTH_TOKEN_KEY=h,exports.DEFAULT_BASE_URL=u,exports.Endpoint=E,exports.Http=H;
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 t}from"./utilities/filelist.mjs";import{flatten as e}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(t){try{return new URL(t).origin}catch(t){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(t){const e=Object.create(null);for(const r of Object.keys(t))u.has(r)&&(e[r]=t[r]);return e}function p(t){const e=Object.create(null);for(const r of Object.keys(t))l.has(r)||(e[r]=t[r]);return e}const h=a("API_URL","/"),f=a("API_AUTH_TOKEN_KEY"),g=a("API_AUTH_HEADER_KEY"),y=a("API_AUTH_HEADER_TYPE");var m,T;!function(t){t.GET="GET",t.POST="POST",t.PUT="PUT",t.DELETE="DELETE",t.PATCH="PATCH",t.HEAD="HEAD",t.OPTIONS="OPTIONS"}(m||(m={})),function(t){t.UPLOAD="UPLOAD",t.RELATED="RELATED"}(T||(T={}));class O{static register(t,e){return this.registered[t]=Object.assign({},e),this}static all(){return Object.assign({},this.registered)}}O.registered={};class b{constructor(t){var e,r,s;this.defaultHeaders={"Content-Type":"application/json"},this.method=b.method,this.storage=b.storage,this.interceptors={request:[],response:[],transform:[],error:[]};const n="string"==typeof t||void 0===t?{baseURL:null!=t?t:h}:t;this.baseURL=null!==(r=null!==(e=n.baseURL)&&void 0!==e?e:h)&&void 0!==r?r:"/";const o=new Set;for(const t of null!==(s=n.allowedOrigins)&&void 0!==s?s:[]){const e=c(t);if(!e)throw new Error(`Http: invalid allowedOrigins entry: ${t}`);o.add(e)}const i=c(this.baseURL);i&&o.add(i),this.allowedOrigins=o,this.defaultConfigs=n.configs?d(n.configs):{}}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]),n=Object.keys(r).some(t=>"cookie"===t.toLowerCase()&&!!r[t]);if(!s&&!n)return;const o=c(t);if(!o)return;const i=e.url?c(e.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(...t){const[e,r]=t,s=b.interceptors[e];s.includes(r)||s.push(r),b.interceptors[e]=s}static off(...t){const[e,r]=t,s=b.interceptors[e];b.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]&&i.Primitive.isPrimitive(t[1])):"object"==typeof t&&null!==t&&Object.values(t).every(t=>i.Primitive.isPrimitive(t)))}getQuery(t){const{method:e,body:r,query:s}=t,n=e===m.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=p(n);return i.queryString.stringify(o,{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=f||b.authTokenKey,e=g||b.authHeaderKey,r=y||b.authHeaderType;if(!t||!e)return{key:e,value:""};let s="";this.storage&&(s=this.storage.getItem(t)||"");const n={key:e,value:""};return s&&/^[\x21-\x7E]+$/.test(s)&&(n.value=r?`${r} ${s}`:s),n}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),b.extraHeaders||{});return b.extraHeaders=null,s}getURL(t){const{url:e="",params:r={}}=t,s=this.getQuery(t);let o;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`);o=t.toString()}else{const t=(this.baseURL||"/").replace(/\/+$/,""),r=e.replace(/^\/+/,"");o=t?`${t}/${r}`:`/${r}`}}else o=this.baseURL||"/";if(s){const t=o.includes("?")?"&":"?";o+=`${t}${s}`}if(!o.includes("{")||!o.includes("}"))return o;const a=Array.isArray(r)?r:p(r);return o.replace(/\{([a-zA-Z0-9_.-]+)\}/g,(t,e)=>{const r=n(a,e);return null==r||"object"==typeof r?"":i.URL.encode(String(r))})}getBody(t){const{method:e,body:s}=t;if(e!==m.GET&&e!==m.HEAD&&e!==m.OPTIONS&&e!==m.DELETE)return e!==m.POST&&e!==m.PUT&&e!==m.PATCH||!r(s)?s instanceof Uint8Array||s instanceof ArrayBuffer?s:void 0!==s?JSON.stringify(s):void 0:s}async request(t){const{method:e=m.GET}=t;try{let s=Object.assign({},t);const n=[...b.interceptors.request,...this.interceptors.request];for(const t of n)s=await t(s);const o=this.getURL(s),i=this.getHeaders(s.headers||{});r(s.body)&&"Content-Type"in i&&delete i["Content-Type"];const a=Object.assign(Object.assign({},this.defaultConfigs),s.configs?d(s.configs):{}),c=await fetch(o,Object.assign(Object.assign({},a),{method:s.method||e,headers:i,body:this.getBody(s),signal:s.signal}));this.assertSameOriginResponse(o,c,i);const l=c.headers.get("Content-Type")||"";let u=null;u=l.includes("application/json")?await c.json():await c.text();const p=[...b.interceptors.transform,...this.interceptors.transform];let h=u;for(const t of p)h=await t(h);const f=[...b.interceptors.response,...this.interceptors.response];let g=c;for(const t of f)g=await t(g);const y={};g.headers.forEach((t,e)=>{y[e]=t});const m=g.ok;let T=null;if(!m){T=u.error||u;const t=[...b.interceptors.error,...this.interceptors.error];for(const e of t)T=await e(T)}return{data:h,success:m,error:m?null:T,status:g.status,statusText:g.statusText,headers:y}}catch(t){let e=t instanceof Error?t:new Error(String(t));const r=[...b.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:m.GET,url:t,query:e}))}post(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:m.POST,url:t,body:e}))}put(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:m.PUT,url:t,body:e}))}patch(t,e,r){return this.request(Object.assign(Object.assign({},r),{method:m.PATCH,url:t,body:e}))}delete(t,e){return this.request(Object.assign(Object.assign({},e),{method:m.DELETE,url:t}))}head(t,e){return this.request(Object.assign(Object.assign({},e),{method:m.HEAD,url:t}))}options(t,e){return this.request(Object.assign(Object.assign({},e),{method:m.OPTIONS,url:t}))}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((t,e)=>{i.append(`${a}[${e}]`,t,t.name)})):t(n)?(a.endsWith("[]")&&(a=a.slice(0,-2)),Array.from(n).forEach((t,e)=>{i.append(`${a}[${e}]`,t,t.name)})):i.append(a,n,n.name),null==o?void 0:o.body){const t=e(o.body);Object.entries(t).forEach(([t,e])=>{null!=e&&i.append(t,String(e))})}return(null==o?void 0:o.onProgress)&&"undefined"!=typeof XMLHttpRequest?new Promise(async t=>{try{let e=Object.assign(Object.assign({},o),{method:m.POST,url:r,body:i});const s=[...b.interceptors.request,...this.interceptors.request];for(const t of s)e=await t(e);const n=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=o.onProgress)||void 0===e||e.call(o,{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=[...b.interceptors.transform,...this.interceptors.transform];let n=r;for(const t of s)n=await t(n);const o=[...b.interceptors.response,...this.interceptors.response];let i=new Response(c.responseText,{status:c.status,statusText:c.statusText,headers:a});for(const t of o)i=await t(i);const l={};i.headers.forEach((t,e)=>{l[e]=t});const u=i.ok;t({data:n,success:u,error:u?null:n,status:i.status,statusText:i.statusText,headers:l})}catch(e){const r=[...b.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=[...b.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||m.POST,n,!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=[...b.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:m.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(t,e,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=e instanceof Uint8Array?e:new Uint8Array(e),h=new Uint8Array(l.length+u.length+p.length+d.length);return[l,u,p,d].reduce((t,e)=>(h.set(e,t),t+e.length),0),this.request({method:m.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 b(t.baseURL);return function(s,n){const o=e("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),n){case m.POST:return await r.post(o,...e);case m.PUT:return await r.put(o,...e);case m.PATCH:return await r.patch(o,...e);case m.DELETE:return await r.delete(o,...e);case m.HEAD:return await r.head(o,...e);case m.OPTIONS:return await r.options(o,...e);case T.UPLOAD:return await r.upload(o,...e);case T.RELATED:return await r.related(o,...e);default:return await r.get(o,...e)}}}}}}b.method=m,b.Endpoint=O,b.authTokenKey="access_token",b.authHeaderKey="Authorization",b.authHeaderType="Bearer",b.authDetectToken=["localStorage","sessionStorage","cookie"],b.extraHeaders=null,b.storage=null,b.interceptors={request:[],response:[],transform:[],error:[]};export{g as API_AUTH_HEADER_KEY,y as API_AUTH_HEADER_TYPE,f as API_AUTH_TOKEN_KEY,h as DEFAULT_BASE_URL,O as Endpoint,b as Http,m as HttpMethod,T 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.2",
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": {