@neabyte/fetch
Advanced tools
@@ -1,1 +0,1 @@ | ||
| "use strict";Object.defineProperty(exports,"__esModule",{value:!0});class FetchError extends Error{status;data;url;constructor(message,status,data,url){super(message),this.name="FetchError",this.status=status,this.data=data,this.url=url}}const headers_CONTENT_LENGTH="content-length",headers_CONTENT_TYPE="content-type",schemes_HTTP="http://",schemes_HTTPS="https://",contentTypes_APPLICATION_JSON="application/json",contentTypes_APPLICATION_OCTET_STREAM="application/octet-stream",contentTypes_APPLICATION_URL_ENCODED="application/x-www-form-urlencoded",contentTypes_TEXT_PREFIX="text/",contentTypes_TEXT_PLAIN="text/plain",httpMethods_GET="GET",httpMethods_POST="POST",httpMethods_PUT="PUT",httpMethods_PATCH="PATCH",httpMethods_DELETE="DELETE",httpMethods_HEAD="HEAD",httpMethods_OPTIONS="OPTIONS",errorMessages_ABORTED="Request timeout - operation exceeded the specified timeout duration",errorMessages_BALANCER_ENDPOINTS_REQUIRED="Balancer endpoints are required and must be a non-empty array",errorMessages_BALANCER_STRATEGY_INVALID='Balancer strategy must be either "fastest" or "parallel"',errorMessages_FILENAME_REQUIRED="Filename is required when download is enabled",errorMessages_FILENAME_UNDEFINED="Filename is undefined",errorMessages_FORWARDER_ENDPOINTS_REQUIRED="Forwarder endpoints are required and must be a non-empty array",errorMessages_PROGRESS_CALLBACK_UNDEFINED="Progress callback is undefined",errorMessages_RESPONSE_BODY_NULL="Response body is null",errorMessages_RESPONSE_TOO_LARGE="Response too large",errorMessages_RETRIES_NON_NEGATIVE="Retries must be a non-negative number",errorMessages_STREAM_PARSE_PREFIX="Failed to parse streaming response: ",errorMessages_TIMEOUT_NON_NEGATIVE="Timeout must be a non-negative number",errorMessages_UNKNOWN_ERROR="Unknown error",errorMessages_URL_INVALID="URL must be a non-empty string",defaults_BASE_URL="",defaults_DOWNLOAD=!1,defaults_RESPONSE_TYPE="auto",defaults_RETRIES=1,defaults_STREAM=!1,defaults_TIMEOUT_MS=3e4,misc_NEWLINE="\n",misc_ABORT_ERROR_NAME="AbortError",misc_MAX_NDJSON_BUFFER_BYTES=10485760,retryDelays_BASE_DELAY_MS=1e3,retryDelays_MAX_DELAY_MS=1e4,forwarderDefaults={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function parseJsonWithFallback(response){try{return await response.json()}catch(jsonError){try{return await response.text()}catch(textError){return}}}function isJsonContentType(contentType){return!0===contentType?.includes(contentTypes_APPLICATION_JSON)}function isTextContentType(contentType){return!0===contentType?.includes(contentTypes_TEXT_PREFIX)}async function parseResponseByType(response,config,method){const contentType=response.headers.get(headers_CONTENT_TYPE);if(method!==httpMethods_HEAD){if("auto"!==config.responseType)switch(config.responseType){case"json":return parseJsonWithFallback(response);case"text":return await response.text();case"buffer":{const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}case"blob":return await response.blob();default:return async function(response){const contentType=response.headers.get(headers_CONTENT_TYPE);if(null===contentType)try{return await response.text()}catch{const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}if(contentType.includes(contentTypes_APPLICATION_JSON))return await response.json();if(contentType.includes(contentTypes_TEXT_PREFIX))return await response.text();const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}(response)}return async function(response,contentType,method){if(method===httpMethods_HEAD)return;if(isJsonContentType(contentType))return parseJsonWithFallback(response);if(isTextContentType(contentType))return await response.text();const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}(response,contentType,method)}}async function parseResponseWithProgress(response,config,url,method){return config.onProgress?async function(response,config,url,method){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?(()=>{const parsed=parseInt(contentLength,10);return Number.isNaN(parsed)?0:parsed})():0;if(null===response.body)return parseResponseByType(response,{responseType:"auto"},method);const reader=response.body.getReader(),chunks=[];let received=0;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value&&(chunks.push(value),received+=value.length),received>misc_MAX_NDJSON_BUFFER_BYTES)throw new FetchError(errorMessages_RESPONSE_TOO_LARGE,void 0,void 0,url);if(total>0&&void 0!==config.onProgress){const percentage=Math.round(received/total*100);config.onProgress(percentage)}}if(0===chunks.length)return await parseResponseByType(response,{responseType:"auto"},method);const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);let position=0;for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;const blob=new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM}),newResponse=new Response(blob,{status:response.status,statusText:response.statusText,headers:response.headers});return await parseResponseByType(newResponse,{responseType:"auto"},method)}finally{reader.releaseLock()}}(response,{onProgress:config.onProgress},url,method):parseResponseByType(response,{responseType:config.responseType},method)}class RetryAfterParser{static parseRetryAfterMs(retryAfter){const trimmed=retryAfter.trim();if(""===trimmed)return null;const seconds=Number(trimmed);if(!Number.isNaN(seconds)&&seconds>=0)return Math.floor(1e3*seconds);try{const date=new Date(trimmed);if(Number.isNaN(date.getTime()))return null;const now=new Date,delayMs=date.getTime()-now.getTime();return delayMs>0?delayMs:0}catch{return null}}static async honorRetryAfterIfPresent(error){const retryAfterHeader=error instanceof Error&&"retryAfter"in error?error.retryAfter:void 0;if(void 0===retryAfterHeader)return!1;const delayMs=this.parseRetryAfterMs(retryAfterHeader);return null!==delayMs&&(await new Promise(resolve=>setTimeout(resolve,delayMs)),!0)}}async function honorRetryAfterIfPresent(error){return RetryAfterParser.honorRetryAfterIfPresent(error)}class RateLimiter{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(bytesToTransfer,maxRateBps){if(maxRateBps<=0)return 0;const elapsedMs=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(maxRateBps/4,256),this.tokens=this.maxTokens,this.refillRate=maxRateBps/1e3);const tokensToAdd=elapsedMs*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+tokensToAdd),this.tokens>=bytesToTransfer)return this.tokens-=bytesToTransfer,this.transferredBytes+=bytesToTransfer,0;const delayMs=(bytesToTransfer-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=bytesToTransfer,Math.min(delayMs,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(bytesToTransfer,maxRateBps){const delayMs=this.calculateDelay(bytesToTransfer,maxRateBps);delayMs>0&&await new Promise(resolve=>setTimeout(resolve,delayMs))}}function isValidURL(url){if(!url||"string"!=typeof url)return!1;try{const parsedUrl=new URL(url);if(!["http:","https:"].includes(parsedUrl.protocol))return!1;if(!parsedUrl.hostname||0===parsedUrl.hostname.length)return!1;if(url.endsWith(":"))return!1;if(parsedUrl.port){const port=parseInt(parsedUrl.port,10);if(isNaN(port)||port<1||port>65535)return!1}return!0}catch{return!1}}function cleanupController(controller){controller.timeoutId&&clearTimeout(controller.timeoutId)}function waitForRetry(attempt,baseDelay=retryDelays_BASE_DELAY_MS){const rawDelay=Math.min(baseDelay*Math.pow(2,attempt),retryDelays_MAX_DELAY_MS),jitter=.75+.5*function(){const cryptoMaybe=globalThis.crypto;if(void 0!==cryptoMaybe&&"object"==typeof cryptoMaybe&&"function"==typeof cryptoMaybe.getRandomValues){const cryptoObj=cryptoMaybe,buf=new Uint32Array(1);cryptoObj.getRandomValues(buf);const value=(buf.at(0)??0)/4294967295;return Number.isFinite(value)?value:.5}const now=Date.now();return(4294967295&(now^now>>>3))%1e6/1e6}(),delay=Math.round(rawDelay*jitter);return new Promise(resolve=>setTimeout(resolve,delay))}function shouldRetry(error,attempt,maxRetries){if(attempt>=maxRetries)return!1;const errorName=error?.name;return!(error instanceof Error&&error.name===misc_ABORT_ERROR_NAME||errorName===misc_ABORT_ERROR_NAME)&&(!(error instanceof FetchError&&error.data?.name===misc_ABORT_ERROR_NAME)&&!(error instanceof FetchError&&void 0!==error.status&&error.status>=400&&error.status<500))}function createHeaders(headers={}){return new Headers(headers)}class BalancerHandler{static validateBalancerConfig(config){if(!config.endpoints?.length||!Array.isArray(config.endpoints))throw new FetchError(errorMessages_BALANCER_ENDPOINTS_REQUIRED,void 0,void 0,"");if(!config.strategy||!["fastest","parallel"].includes(config.strategy))throw new FetchError(errorMessages_BALANCER_STRATEGY_INVALID,void 0,void 0,"");for(const endpoint of config.endpoints)if(!isValidURL(endpoint))throw new FetchError(`Invalid balancer endpoint URL: ${endpoint}`,void 0,void 0,endpoint)}static async executeWithBalancer(url,config,executeRequest){return this.validateBalancerConfig(config),"fastest"===config.strategy?this.executeFastestStrategy(url,config,executeRequest):this.executeParallelStrategy(url,config,executeRequest)}static async executeFastestStrategy(url,config,executeRequest){const errors=[];for(const endpoint of config.endpoints){const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);if(result.success)return result.data;errors.push({endpoint:endpoint,error:result.error})}const lastError=errors[errors.length-1];if(lastError?.error instanceof FetchError)throw lastError.error;throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}static async executeParallelStrategy(url,config,executeRequest){const promises=config.endpoints.map(async endpoint=>{const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);return result.success?{success:!0,data:result.data,endpoint:endpoint}:{success:!1,error:result.error,endpoint:endpoint}}),results=await Promise.allSettled(promises).then(settledResults=>settledResults.map(result=>"fulfilled"===result.status?result.value:{success:!1,error:result.reason,endpoint:"unknown"})),successfulResults=results.filter(result=>result.success);if(0===successfulResults.length){const errors=results.filter(result=>!result.success).map(result=>({endpoint:result.endpoint,error:result.error}));throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}return successfulResults.map(result=>result.data)}static buildFullUrl(endpoint,url){const baseUrl=endpoint.endsWith("/")?endpoint.slice(0,-1):endpoint;if(!url||""===url.trim())return baseUrl;if(url.startsWith("http://")||url.startsWith("https://"))return url;return`${baseUrl}${url.startsWith("/")?url:`/${url}`}`}}async function handleDownload(response,config){if(void 0===config.filename||""===config.filename.trim())throw new Error(errorMessages_FILENAME_REQUIRED);void 0!==config.onProgress&&null!==response.body?await async function(response,config){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?parseInt(contentLength,10):0;if(total>0)await async function(response,config,total){if(null===response.body)throw new Error(errorMessages_RESPONSE_BODY_NULL);if(void 0===config.onProgress)throw new Error(errorMessages_PROGRESS_CALLBACK_UNDEFINED);if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);let received=0;const chunks=[],rateLimiter=new RateLimiter,reader=response.body.getReader(),processingChunkSize=128;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value)for(let i=0;i<value.length;i+=processingChunkSize){const chunk=value.slice(i,i+processingChunkSize);received=await processChunk(chunk,config,rateLimiter,chunks,received,total)}}const blob=function(chunks,received,response){let position=0;const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;return new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM})}(chunks,received,response);await saveBlob(blob,config.filename)}finally{reader.releaseLock()}}(response,config,total);else{if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);await handleDirectDownload(response,config.filename)}}(response,config):await handleDirectDownload(response,config.filename)}async function processChunk(chunk,config,rateLimiter,chunks,received,total){void 0!==config.maxRate&&config.maxRate>0&&await rateLimiter.throttle(chunk.length,config.maxRate),chunks.push(chunk);const newReceived=received+chunk.length,percentage=Math.round(newReceived/total*100);return config.onProgress?.(percentage),newReceived}async function handleDirectDownload(response,filename){const blob=await response.blob();await saveBlob(blob,filename)}async function saveBlob(blob,filename){if("undefined"!=typeof window&&"undefined"!=typeof document){const objectUrl=URL.createObjectURL(blob),link=document.createElement("a");link.href=objectUrl,link.download=filename;try{document.body.appendChild(link),link.click()}finally{document.body.contains(link)&&document.body.removeChild(link),URL.revokeObjectURL(objectUrl)}}else{const{writeFileSync:writeFileSync}=await import("node:fs"),buffer=await blob.arrayBuffer();writeFileSync(filename,Buffer.from(buffer))}}function processRequestBody(body,headers,maxRate,onProgress){if(function(body){return body instanceof FormData||body instanceof URLSearchParams||body instanceof Blob||body instanceof ArrayBuffer||body instanceof Uint8Array||"string"==typeof body}(body))return{body:body,needsDuplex:!1};if("object"==typeof body&&null!=body){headers["Content-Type"]??=contentTypes_APPLICATION_JSON;const serializedBody=function(body,headers){let result;const contentType=headers["Content-Type"];if(void 0===contentType||contentType===contentTypes_APPLICATION_JSON)result=JSON.stringify(body);else if(contentType.includes(contentTypes_APPLICATION_URL_ENCODED)){const params=function(body){const formData=new URLSearchParams;for(const[key,value]of Object.entries(body))if(null!=value){let stringValue;stringValue="object"==typeof value?JSON.stringify(value):"string"==typeof value?value:"number"==typeof value||"boolean"==typeof value?String(value):JSON.stringify(value),formData.append(key,stringValue)}return formData}(body);result=params.toString()}else result=(contentType.includes(contentTypes_TEXT_PLAIN),JSON.stringify(body));return result}(body,headers);return{body:serializedBody,needsDuplex:!1}}const stringBody=String(body);return headers["Content-Type"]??=contentTypes_TEXT_PLAIN,{body:stringBody,needsDuplex:!1}}class ErrorHandler{static normalizeExecuteError(error,fullUrl){const name=error?.name;return name===misc_ABORT_ERROR_NAME?{success:!1,error:new FetchError(errorMessages_ABORTED,void 0,error,fullUrl)}:{success:!1,error:error}}static async getErrorData(response){return async function(response){try{const contentType=response.headers.get(headers_CONTENT_TYPE);return null===contentType?null:isJsonContentType(contentType)?await response.json():await response.text()}catch{return null}}(response)}static createHttpError(response,fullUrl,errorData){const retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);return null!==retryAfter&&(err.retryAfter=retryAfter),err}}class ForwarderHandler{static validateForwarderConfig(config){if(!config.forwarders?.length||!Array.isArray(config.forwarders))throw new FetchError(errorMessages_FORWARDER_ENDPOINTS_REQUIRED,void 0,void 0,"");for(const forwarder of config.forwarders)if(!isValidURL(forwarder.url))throw new FetchError(`Invalid forwarder endpoint URL: ${forwarder.url}`,void 0,void 0,forwarder.url)}static forwardResponse(responseData,config,executeRequest){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(results=>{this.logForwarderResults(results)}).catch(error=>{}),Promise.resolve()}static forwardResponseWithCallback(responseData,config,executeRequest,onComplete){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(settledResults=>{const results=settledResults.map(result=>"fulfilled"===result.status?result.value:{endpoint:"unknown",success:!1,error:result.reason});onComplete?onComplete(results):this.logForwarderResults(settledResults)}),Promise.resolve()}static logForwarderResults(results){const successful=results.filter(result=>"fulfilled"===result.status&&result.value.success).length;results.length-successful>0&&results.forEach(result=>{("fulfilled"!==result.status||result.value.success)&&result.status})}static async executeForwarderWithRetries(forwarder,responseData,executeRequest){const{retries:retries=forwarderDefaults.RETRIES,timeout:timeout=forwarderDefaults.TIMEOUT_MS,url:url,method:method,headers:headers,body:forwarderBody}=forwarder;let body;body="function"==typeof forwarderBody?forwarderBody(responseData):void 0!==forwarderBody?forwarderBody:responseData;try{return{success:!0,data:await RetryHandler.executeWithRetries(url,{retries:retries,timeout:timeout,download:!1},()=>executeRequest(url,method,body,headers),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}}static createConfig(urls,method=forwarderDefaults.METHOD,headers,timeout,retries){return{forwarders:urls.map(url=>({method:method,url:url,...headers?{headers:headers}:{},...void 0!==timeout?{timeout:timeout}:{},...void 0!==retries?{retries:retries}:{}}))}}static createConfigFromForwarders(forwarders){return{forwarders:forwarders}}}class RetryHandler{static async executeWithRetries(fullUrl,config,executeRequest,honorRetryAfter){let lastError=null;for(let attempt=0;attempt<=config.retries;attempt++){const result=await executeRequest();if(result.success)return result.data;if(lastError=result.error,shouldRetry(result.error,attempt,config.retries)){if(await honorRetryAfter(result.error))continue;await waitForRetry(attempt);continue}break}if(lastError instanceof FetchError)throw lastError;throw new FetchError(lastError instanceof Error?lastError.message:errorMessages_UNKNOWN_ERROR,void 0,lastError,fullUrl)}}class StreamHandler{static createStreamIterator(response,url){const bodyStream=response.body;if(!bodyStream)throw new FetchError(errorMessages_RESPONSE_BODY_NULL,void 0,null,url);return{async*[Symbol.asyncIterator](){const reader=bodyStream.getReader();try{const contentType=response.headers.get(headers_CONTENT_TYPE),isJson=isJsonContentType(contentType),isText=isTextContentType(contentType);if(isJson)return void(yield*StreamHandler.iterateNdjson(reader,url));if(isText)return void(yield*StreamHandler.iterateText(reader));yield*StreamHandler.iterateBinary(reader)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}finally{reader.releaseLock()}}}}static async readDecodedChunk(reader,decoder){const{done:done,value:value}=await reader.read();return done?null:void 0===value?"":decoder.decode(value,{stream:!0})}static async*iterateText(reader){const decoder=new TextDecoder;for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value){const chunk=decoder.decode(value,{stream:!0});chunk.length>0&&(yield chunk)}}}static async*iterateNdjson(reader,url){let buffer="";const decoder=new TextDecoder;for(;;){const chunk=await StreamHandler.readDecodedChunk(reader,decoder);if(null===chunk)break;buffer+=chunk,yield*StreamHandler.yieldCompleteJsonLines(buffer,newBuffer=>{buffer=newBuffer},url)}const trimmed=buffer.trim();trimmed.length>0&&(yield*StreamHandler.safeParseJsonLine(trimmed,url))}static*yieldCompleteJsonLines(buffer,setBuffer,url){let newlineIndex;for(;-1!==(newlineIndex=buffer.indexOf(misc_NEWLINE));){const line=buffer.slice(0,newlineIndex).trim();setBuffer(buffer=buffer.slice(newlineIndex+misc_NEWLINE.length)),0!==line.length&&(yield*StreamHandler.safeParseJsonLine(line,url))}}static*safeParseJsonLine(line,url){try{yield JSON.parse(line)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}}static async*iterateBinary(reader){for(;;){const{done:done,value:value}=await reader.read();if(done)break;void 0!==value&&(yield value)}}}exports.BalancerHandler=BalancerHandler,exports.ForwarderHandler=ForwarderHandler,exports.createHeaders=createHeaders,exports.default=class{static defaultConfig={timeout:defaults_TIMEOUT_MS,retries:defaults_RETRIES,headers:{"Content-Type":contentTypes_APPLICATION_JSON},baseURL:defaults_BASE_URL,stream:defaults_STREAM,download:defaults_DOWNLOAD,responseType:defaults_RESPONSE_TYPE};static async get(url,options={}){return this.request(httpMethods_GET,url,options)}static async post(url,body,options={}){return this.request(httpMethods_POST,url,this.createRequestOptions(options,body))}static async put(url,body,options={}){return this.request(httpMethods_PUT,url,this.createRequestOptions(options,body))}static async patch(url,body,options={}){return this.request(httpMethods_PATCH,url,this.createRequestOptions(options,body))}static async delete(url,options={}){return this.request(httpMethods_DELETE,url,options)}static async head(url,options={}){return this.request(httpMethods_HEAD,url,options)}static async options(url,options={}){return this.request(httpMethods_OPTIONS,url,options)}static createRequestOptions(options,body){return{...options,...void 0!==body?{body:body}:{}}}static async request(method,url,options={}){if("string"!=typeof url||""===url.trim())throw new FetchError(errorMessages_URL_INVALID,void 0,void 0,url);const config={...this.defaultConfig,...options};if(config.retries<0)throw new FetchError(errorMessages_RETRIES_NON_NEGATIVE,void 0,void 0,url);if(config.timeout<0)throw new FetchError(errorMessages_TIMEOUT_NON_NEGATIVE,void 0,void 0,url);if(config.download&&(void 0===config.filename||""===config.filename.trim()))throw new FetchError(errorMessages_FILENAME_REQUIRED,void 0,void 0,url);if(config.balancer){const response=await this.executeWithBalancer(method,url,config);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}const fullUrl=function(url,baseURL){if(url.startsWith(schemes_HTTP)||url.startsWith(schemes_HTTPS))return url;if(baseURL)return`${baseURL.endsWith("/")?baseURL.slice(0,-1):baseURL}${url.startsWith("/")?url:`/${url}`}`;return url}(url,config.baseURL),response=await RetryHandler.executeWithRetries(fullUrl,{retries:config.retries,timeout:config.timeout,download:config.download,...void 0!==config.filename?{filename:config.filename}:{}},()=>this.executeRequest(method,fullUrl,config),honorRetryAfterIfPresent);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}static async executeRequest(method,fullUrl,config){let controller;try{config.signal||(controller=function(timeout){const controller=new AbortController;if(timeout>0){const timeoutId=setTimeout(()=>{controller.abort()},timeout);controller.timeoutId=timeoutId}return controller}(config.timeout));const fetchOptions=function(method,config,controller){const headers={...config.headers};void 0!==config.body&&(config.body instanceof FormData?delete headers["Content-Type"]:config.body instanceof URLSearchParams&&(headers["Content-Type"]=contentTypes_APPLICATION_URL_ENCODED));const requestInit={method:method,headers:createHeaders(headers)},methodsWithBody=[httpMethods_POST,httpMethods_PUT,httpMethods_PATCH];if(void 0!==config.body&&methodsWithBody.includes(method)){const bodyResult=processRequestBody(config.body,config.headers,config.maxRate);requestInit.body=bodyResult.body,bodyResult.needsDuplex&&(requestInit.duplex="half")}return config.signal?requestInit.signal=config.signal:controller&&(requestInit.signal=controller.signal),requestInit}(method,config,controller),response=await globalThis.fetch(fullUrl,fetchOptions);if(controller&&cleanupController(controller),!response.ok){const errorData=await ErrorHandler.getErrorData(response),retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);throw null!==retryAfter&&(err.retryAfter=retryAfter),err}if(config.stream){return{success:!0,data:StreamHandler.createStreamIterator(response,fullUrl)}}if(config.download)return await this.handleDownloadResponse(response,config);if(method===httpMethods_HEAD||method===httpMethods_OPTIONS)return{success:!0,data:void 0};const parseConfig={responseType:config.responseType};void 0!==config.onProgress&&(parseConfig.onProgress=config.onProgress);return{success:!0,data:await parseResponseWithProgress(response,parseConfig,fullUrl,method)}}catch(error){return controller&&cleanupController(controller),ErrorHandler.normalizeExecuteError(error,fullUrl)}}static async handleDownloadResponse(response,config){const downloadConfig={};void 0!==config.filename&&(downloadConfig.filename=config.filename),void 0!==config.maxRate&&(downloadConfig.maxRate=config.maxRate),void 0!==config.onProgress&&(downloadConfig.onProgress=config.onProgress),await handleDownload(response,downloadConfig);const contentLength=response.headers.get(headers_CONTENT_LENGTH),contentType=response.headers.get(headers_CONTENT_TYPE);return{success:!0,data:{filename:config.filename??"download",size:null!==contentLength?parseInt(contentLength,10):0,type:contentType??"application/octet-stream",status:response.status,ok:response.ok}}}static async executeWithBalancer(method,url,config){const{balancer:balancer,...baseConfig}=config;return await BalancerHandler.executeWithBalancer(url,balancer,async endpoint=>{try{return{success:!0,data:await RetryHandler.executeWithRetries(endpoint,{retries:baseConfig.retries,timeout:baseConfig.timeout,download:baseConfig.download,...void 0!==baseConfig.filename?{filename:baseConfig.filename}:{}},()=>this.executeRequest(method,endpoint,baseConfig),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}})}static async forwardResponse(responseData,config){const{forwarder:forwarder,...baseConfig}=config,forwarderConfig=ForwarderHandler.createConfigFromForwarders(forwarder);await ForwarderHandler.forwardResponse(responseData,forwarderConfig,(endpoint,forwardMethod,body,headers)=>this.executeRequest(forwardMethod,endpoint,{...baseConfig,body:body,headers:headers??{}}))}}; | ||
| "use strict";Object.defineProperty(exports,"__esModule",{value:!0});class FetchError extends Error{status;data;url;constructor(message,status,data,url){super(message),this.name="FetchError",this.status=status,this.data=data,this.url=url}}const headers_CONTENT_LENGTH="content-length",headers_CONTENT_TYPE="content-type",schemes_HTTP="http://",schemes_HTTPS="https://",contentTypes_APPLICATION_JSON="application/json",contentTypes_APPLICATION_OCTET_STREAM="application/octet-stream",contentTypes_APPLICATION_URL_ENCODED="application/x-www-form-urlencoded",contentTypes_TEXT_PREFIX="text/",contentTypes_TEXT_PLAIN="text/plain",httpMethods_GET="GET",httpMethods_POST="POST",httpMethods_PUT="PUT",httpMethods_PATCH="PATCH",httpMethods_DELETE="DELETE",httpMethods_HEAD="HEAD",httpMethods_OPTIONS="OPTIONS",httpMethods_TRACE="TRACE",errorMessages_ABORTED="Request timeout - operation exceeded the specified timeout duration",errorMessages_BALANCER_ENDPOINTS_REQUIRED="Balancer endpoints are required and must be a non-empty array",errorMessages_BALANCER_STRATEGY_INVALID='Balancer strategy must be either "fastest" or "parallel"',errorMessages_FILENAME_REQUIRED="Filename is required when download is enabled",errorMessages_FILENAME_UNDEFINED="Filename is undefined",errorMessages_FORWARDER_ENDPOINTS_REQUIRED="Forwarder endpoints are required and must be a non-empty array",errorMessages_PROGRESS_CALLBACK_UNDEFINED="Progress callback is undefined",errorMessages_RESPONSE_BODY_NULL="Response body is null",errorMessages_RESPONSE_TOO_LARGE="Response too large",errorMessages_RETRIES_NON_NEGATIVE="Retries must be a non-negative number",errorMessages_STREAM_PARSE_PREFIX="Failed to parse streaming response: ",errorMessages_TIMEOUT_NON_NEGATIVE="Timeout must be a non-negative number",errorMessages_UNKNOWN_ERROR="Unknown error",errorMessages_URL_INVALID="URL must be a non-empty string",defaults_BASE_URL="",defaults_DOWNLOAD=!1,defaults_RESPONSE_TYPE="auto",defaults_RETRIES=1,defaults_STREAM=!1,defaults_TIMEOUT_MS=3e4,misc_NEWLINE="\n",misc_ABORT_ERROR_NAME="AbortError",misc_MAX_NDJSON_BUFFER_BYTES=10485760,retryDelays_BASE_DELAY_MS=1e3,retryDelays_MAX_DELAY_MS=1e4,forwarderDefaults={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function parseJsonWithFallback(response){try{return await response.json()}catch(jsonError){try{return await response.text()}catch(textError){return}}}function isJsonContentType(contentType){return!0===contentType?.includes(contentTypes_APPLICATION_JSON)}function isTextContentType(contentType){return!0===contentType?.includes(contentTypes_TEXT_PREFIX)}async function parseResponseByType(response,config,method){const contentType=response.headers.get(headers_CONTENT_TYPE);if(method!==httpMethods_HEAD){if("auto"!==config.responseType)switch(config.responseType){case"json":return parseJsonWithFallback(response);case"text":return await response.text();case"buffer":{const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}case"blob":return await response.blob();default:return async function(response){const contentType=response.headers.get(headers_CONTENT_TYPE);if(null===contentType)try{return await response.text()}catch{const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}if(contentType.includes(contentTypes_APPLICATION_JSON))return await response.json();if(contentType.includes(contentTypes_TEXT_PREFIX))return await response.text();const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}(response)}return async function(response,contentType,method){if(method===httpMethods_HEAD)return;if(isJsonContentType(contentType))return parseJsonWithFallback(response);if(isTextContentType(contentType))return await response.text();const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}(response,contentType,method)}}async function parseResponseWithProgress(response,config,url,method){return config.onProgress?async function(response,config,url,method){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?(()=>{const parsed=parseInt(contentLength,10);return Number.isNaN(parsed)?0:parsed})():0;if(null===response.body)return parseResponseByType(response,{responseType:"auto"},method);const reader=response.body.getReader(),chunks=[];let received=0;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value&&(chunks.push(value),received+=value.length),received>misc_MAX_NDJSON_BUFFER_BYTES)throw new FetchError(errorMessages_RESPONSE_TOO_LARGE,void 0,void 0,url);if(total>0&&void 0!==config.onProgress){const percentage=Math.round(received/total*100);config.onProgress(percentage)}}if(0===chunks.length)return await parseResponseByType(response,{responseType:"auto"},method);const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);let position=0;for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;const blob=new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM}),newResponse=new Response(blob,{status:response.status,statusText:response.statusText,headers:response.headers});return await parseResponseByType(newResponse,{responseType:"auto"},method)}finally{reader.releaseLock()}}(response,{onProgress:config.onProgress},url,method):parseResponseByType(response,{responseType:config.responseType},method)}class RetryAfterParser{static parseRetryAfterMs(retryAfter){const trimmed=retryAfter.trim();if(""===trimmed)return null;const seconds=Number(trimmed);if(!Number.isNaN(seconds)&&seconds>=0)return Math.floor(1e3*seconds);try{const date=new Date(trimmed);if(Number.isNaN(date.getTime()))return null;const now=new Date,delayMs=date.getTime()-now.getTime();return delayMs>0?delayMs:0}catch{return null}}static async honorRetryAfterIfPresent(error){const retryAfterHeader=error instanceof Error&&"retryAfter"in error?error.retryAfter:void 0;if(void 0===retryAfterHeader)return!1;const delayMs=this.parseRetryAfterMs(retryAfterHeader);return null!==delayMs&&(await new Promise(resolve=>setTimeout(resolve,delayMs)),!0)}}async function honorRetryAfterIfPresent(error){return RetryAfterParser.honorRetryAfterIfPresent(error)}class RateLimiter{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(bytesToTransfer,maxRateBps){if(maxRateBps<=0)return 0;const elapsedMs=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(maxRateBps/4,256),this.tokens=this.maxTokens,this.refillRate=maxRateBps/1e3);const tokensToAdd=elapsedMs*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+tokensToAdd),this.tokens>=bytesToTransfer)return this.tokens-=bytesToTransfer,this.transferredBytes+=bytesToTransfer,0;const delayMs=(bytesToTransfer-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=bytesToTransfer,Math.min(delayMs,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(bytesToTransfer,maxRateBps){const delayMs=this.calculateDelay(bytesToTransfer,maxRateBps);delayMs>0&&await new Promise(resolve=>setTimeout(resolve,delayMs))}}function isValidURL(url){if(!url||"string"!=typeof url)return!1;try{const parsedUrl=new URL(url);if(!["http:","https:"].includes(parsedUrl.protocol))return!1;if(!parsedUrl.hostname||0===parsedUrl.hostname.length)return!1;if(url.endsWith(":"))return!1;if(parsedUrl.port){const port=parseInt(parsedUrl.port,10);if(isNaN(port)||port<1||port>65535)return!1}return!0}catch{return!1}}function cleanupController(controller){controller.timeoutId&&clearTimeout(controller.timeoutId)}function waitForRetry(attempt,baseDelay=retryDelays_BASE_DELAY_MS){const rawDelay=Math.min(baseDelay*Math.pow(2,attempt),retryDelays_MAX_DELAY_MS),jitter=.75+.5*function(){const cryptoMaybe=globalThis.crypto;if(void 0!==cryptoMaybe&&"object"==typeof cryptoMaybe&&"function"==typeof cryptoMaybe.getRandomValues){const cryptoObj=cryptoMaybe,buf=new Uint32Array(1);cryptoObj.getRandomValues(buf);const value=(buf.at(0)??0)/4294967295;return Number.isFinite(value)?value:.5}const now=Date.now();return(4294967295&(now^now>>>3))%1e6/1e6}(),delay=Math.round(rawDelay*jitter);return new Promise(resolve=>setTimeout(resolve,delay))}function shouldRetry(error,attempt,maxRetries){if(attempt>=maxRetries)return!1;const errorName=error?.name;return!(error instanceof Error&&error.name===misc_ABORT_ERROR_NAME||errorName===misc_ABORT_ERROR_NAME)&&(!(error instanceof FetchError&&error.data?.name===misc_ABORT_ERROR_NAME)&&!(error instanceof FetchError&&void 0!==error.status&&error.status>=400&&error.status<500))}function createHeaders(headers={}){return new Headers(headers)}class BalancerHandler{static validateBalancerConfig(config){if(!config.endpoints?.length||!Array.isArray(config.endpoints))throw new FetchError(errorMessages_BALANCER_ENDPOINTS_REQUIRED,void 0,void 0,"");if(!config.strategy||!["fastest","parallel"].includes(config.strategy))throw new FetchError(errorMessages_BALANCER_STRATEGY_INVALID,void 0,void 0,"");for(const endpoint of config.endpoints)if(!isValidURL(endpoint))throw new FetchError(`Invalid balancer endpoint URL: ${endpoint}`,void 0,void 0,endpoint)}static async executeWithBalancer(url,config,executeRequest){return this.validateBalancerConfig(config),"fastest"===config.strategy?this.executeFastestStrategy(url,config,executeRequest):this.executeParallelStrategy(url,config,executeRequest)}static async executeFastestStrategy(url,config,executeRequest){const errors=[];for(const endpoint of config.endpoints){const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);if(result.success)return result.data;errors.push({endpoint:endpoint,error:result.error})}const lastError=errors[errors.length-1];if(lastError?.error instanceof FetchError)throw lastError.error;throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}static async executeParallelStrategy(url,config,executeRequest){const promises=config.endpoints.map(async endpoint=>{const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);return result.success?{success:!0,data:result.data,endpoint:endpoint}:{success:!1,error:result.error,endpoint:endpoint}}),results=await Promise.allSettled(promises).then(settledResults=>settledResults.map(result=>"fulfilled"===result.status?result.value:{success:!1,error:result.reason,endpoint:"unknown"})),successfulResults=results.filter(result=>result.success);if(0===successfulResults.length){const errors=results.filter(result=>!result.success).map(result=>({endpoint:result.endpoint,error:result.error}));throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}return successfulResults.map(result=>result.data)}static buildFullUrl(endpoint,url){const baseUrl=endpoint.endsWith("/")?endpoint.slice(0,-1):endpoint;if(!url||""===url.trim())return baseUrl;if(url.startsWith("http://")||url.startsWith("https://"))return url;return`${baseUrl}${url.startsWith("/")?url:`/${url}`}`}}async function handleDownload(response,config){if(void 0===config.filename||""===config.filename.trim())throw new Error(errorMessages_FILENAME_REQUIRED);void 0!==config.onProgress&&null!==response.body?await async function(response,config){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?parseInt(contentLength,10):0;if(total>0)await async function(response,config,total){if(null===response.body)throw new Error(errorMessages_RESPONSE_BODY_NULL);if(void 0===config.onProgress)throw new Error(errorMessages_PROGRESS_CALLBACK_UNDEFINED);if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);let received=0;const chunks=[],rateLimiter=new RateLimiter,reader=response.body.getReader(),processingChunkSize=128;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value)for(let i=0;i<value.length;i+=processingChunkSize){const chunk=value.slice(i,i+processingChunkSize);received=await processChunk(chunk,config,rateLimiter,chunks,received,total)}}const blob=function(chunks,received,response){let position=0;const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;return new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM})}(chunks,received,response);await saveBlob(blob,config.filename)}finally{reader.releaseLock()}}(response,config,total);else{if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);await handleDirectDownload(response,config.filename)}}(response,config):await handleDirectDownload(response,config.filename)}async function processChunk(chunk,config,rateLimiter,chunks,received,total){void 0!==config.maxRate&&config.maxRate>0&&await rateLimiter.throttle(chunk.length,config.maxRate),chunks.push(chunk);const newReceived=received+chunk.length,percentage=Math.round(newReceived/total*100);return config.onProgress?.(percentage),newReceived}async function handleDirectDownload(response,filename){const blob=await response.blob();await saveBlob(blob,filename)}async function saveBlob(blob,filename){if("undefined"!=typeof window&&"undefined"!=typeof document){const objectUrl=URL.createObjectURL(blob),link=document.createElement("a");link.href=objectUrl,link.download=filename;try{document.body.appendChild(link),link.click()}finally{document.body.contains(link)&&document.body.removeChild(link),URL.revokeObjectURL(objectUrl)}}else{const{writeFileSync:writeFileSync}=await import("node:fs"),buffer=await blob.arrayBuffer();writeFileSync(filename,Buffer.from(buffer))}}function processRequestBody(body,headers,maxRate,onProgress){if(function(body){return body instanceof FormData||body instanceof URLSearchParams||body instanceof Blob||body instanceof ArrayBuffer||body instanceof Uint8Array||"string"==typeof body}(body))return{body:body,needsDuplex:!1};if("object"==typeof body&&null!=body){headers["Content-Type"]??=contentTypes_APPLICATION_JSON;const serializedBody=function(body,headers){let result;const contentType=headers["Content-Type"];if(void 0===contentType||contentType===contentTypes_APPLICATION_JSON)result=JSON.stringify(body);else if(contentType.includes(contentTypes_APPLICATION_URL_ENCODED)){const params=function(body){const formData=new URLSearchParams;for(const[key,value]of Object.entries(body))if(null!=value){let stringValue;stringValue="object"==typeof value?JSON.stringify(value):"string"==typeof value?value:"number"==typeof value||"boolean"==typeof value?String(value):JSON.stringify(value),formData.append(key,stringValue)}return formData}(body);result=params.toString()}else result=(contentType.includes(contentTypes_TEXT_PLAIN),JSON.stringify(body));return result}(body,headers);return{body:serializedBody,needsDuplex:!1}}const stringBody=String(body);return headers["Content-Type"]??=contentTypes_TEXT_PLAIN,{body:stringBody,needsDuplex:!1}}class ErrorHandler{static normalizeExecuteError(error,fullUrl){const name=error?.name;return name===misc_ABORT_ERROR_NAME?{success:!1,error:new FetchError(errorMessages_ABORTED,void 0,error,fullUrl)}:{success:!1,error:error}}static async getErrorData(response){return async function(response){try{const contentType=response.headers.get(headers_CONTENT_TYPE);return null===contentType?null:isJsonContentType(contentType)?await response.json():await response.text()}catch{return null}}(response)}static createHttpError(response,fullUrl,errorData){const retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);return null!==retryAfter&&(err.retryAfter=retryAfter),err}}class ForwarderHandler{static validateForwarderConfig(config){if(!config.forwarders?.length||!Array.isArray(config.forwarders))throw new FetchError(errorMessages_FORWARDER_ENDPOINTS_REQUIRED,void 0,void 0,"");for(const forwarder of config.forwarders)if(!isValidURL(forwarder.url))throw new FetchError(`Invalid forwarder endpoint URL: ${forwarder.url}`,void 0,void 0,forwarder.url)}static forwardResponse(responseData,config,executeRequest){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(results=>{this.logForwarderResults(results)}).catch(error=>{}),Promise.resolve()}static forwardResponseWithCallback(responseData,config,executeRequest,onComplete){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(settledResults=>{const results=settledResults.map(result=>"fulfilled"===result.status?result.value:{endpoint:"unknown",success:!1,error:result.reason});onComplete?onComplete(results):this.logForwarderResults(settledResults)}),Promise.resolve()}static logForwarderResults(results){const successful=results.filter(result=>"fulfilled"===result.status&&result.value.success).length;results.length-successful>0&&results.forEach(result=>{("fulfilled"!==result.status||result.value.success)&&result.status})}static async executeForwarderWithRetries(forwarder,responseData,executeRequest){const{retries:retries=forwarderDefaults.RETRIES,timeout:timeout=forwarderDefaults.TIMEOUT_MS,url:url,method:method,headers:headers,body:forwarderBody}=forwarder;let body;body="function"==typeof forwarderBody?forwarderBody(responseData):void 0!==forwarderBody?forwarderBody:responseData;try{return{success:!0,data:await RetryHandler.executeWithRetries(url,{retries:retries,timeout:timeout,download:!1},()=>executeRequest(url,method,body,headers),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}}static createConfig(urls,method=forwarderDefaults.METHOD,headers,timeout,retries){return{forwarders:urls.map(url=>({method:method,url:url,...headers?{headers:headers}:{},...void 0!==timeout?{timeout:timeout}:{},...void 0!==retries?{retries:retries}:{}}))}}static createConfigFromForwarders(forwarders){return{forwarders:forwarders}}}class RetryHandler{static async executeWithRetries(fullUrl,config,executeRequest,honorRetryAfter){let lastError=null;for(let attempt=0;attempt<=config.retries;attempt++){const result=await executeRequest();if(result.success)return result.data;if(lastError=result.error,shouldRetry(result.error,attempt,config.retries)){if(await honorRetryAfter(result.error))continue;await waitForRetry(attempt);continue}break}if(lastError instanceof FetchError)throw lastError;throw new FetchError(lastError instanceof Error?lastError.message:errorMessages_UNKNOWN_ERROR,void 0,lastError,fullUrl)}}class StreamHandler{static createStreamIterator(response,url){const bodyStream=response.body;if(!bodyStream)throw new FetchError(errorMessages_RESPONSE_BODY_NULL,void 0,null,url);return{async*[Symbol.asyncIterator](){const reader=bodyStream.getReader();try{const contentType=response.headers.get(headers_CONTENT_TYPE),isJson=isJsonContentType(contentType),isText=isTextContentType(contentType);if(isJson)return void(yield*StreamHandler.iterateNdjson(reader,url));if(isText)return void(yield*StreamHandler.iterateText(reader));yield*StreamHandler.iterateBinary(reader)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}finally{reader.releaseLock()}}}}static async readDecodedChunk(reader,decoder){const{done:done,value:value}=await reader.read();return done?null:void 0===value?"":decoder.decode(value,{stream:!0})}static async*iterateText(reader){const decoder=new TextDecoder;for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value){const chunk=decoder.decode(value,{stream:!0});chunk.length>0&&(yield chunk)}}}static async*iterateNdjson(reader,url){let buffer="";const decoder=new TextDecoder;for(;;){const chunk=await StreamHandler.readDecodedChunk(reader,decoder);if(null===chunk)break;buffer+=chunk,yield*StreamHandler.yieldCompleteJsonLines(buffer,newBuffer=>{buffer=newBuffer},url)}const trimmed=buffer.trim();trimmed.length>0&&(yield*StreamHandler.safeParseJsonLine(trimmed,url))}static*yieldCompleteJsonLines(buffer,setBuffer,url){let newlineIndex;for(;-1!==(newlineIndex=buffer.indexOf(misc_NEWLINE));){const line=buffer.slice(0,newlineIndex).trim();setBuffer(buffer=buffer.slice(newlineIndex+misc_NEWLINE.length)),0!==line.length&&(yield*StreamHandler.safeParseJsonLine(line,url))}}static*safeParseJsonLine(line,url){try{yield JSON.parse(line)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}}static async*iterateBinary(reader){for(;;){const{done:done,value:value}=await reader.read();if(done)break;void 0!==value&&(yield value)}}}exports.BalancerHandler=BalancerHandler,exports.ForwarderHandler=ForwarderHandler,exports.createHeaders=createHeaders,exports.default=class{static defaultConfig={timeout:defaults_TIMEOUT_MS,retries:defaults_RETRIES,headers:{"Content-Type":contentTypes_APPLICATION_JSON},baseURL:defaults_BASE_URL,stream:defaults_STREAM,download:defaults_DOWNLOAD,responseType:defaults_RESPONSE_TYPE};static async get(url,options={}){return this.request(httpMethods_GET,url,options)}static async post(url,body,options={}){return this.request(httpMethods_POST,url,this.createRequestOptions(options,body))}static async put(url,body,options={}){return this.request(httpMethods_PUT,url,this.createRequestOptions(options,body))}static async patch(url,body,options={}){return this.request(httpMethods_PATCH,url,this.createRequestOptions(options,body))}static async delete(url,options={}){return this.request(httpMethods_DELETE,url,options)}static async head(url,options={}){return this.request(httpMethods_HEAD,url,options)}static async options(url,options={}){return this.request(httpMethods_OPTIONS,url,options)}static async trace(url,options={}){return this.request(httpMethods_TRACE,url,options)}static createRequestOptions(options,body){return{...options,...void 0!==body?{body:body}:{}}}static async request(method,url,options={}){if("string"!=typeof url||""===url.trim())throw new FetchError(errorMessages_URL_INVALID,void 0,void 0,url);const config={...this.defaultConfig,...options};if(config.retries<0)throw new FetchError(errorMessages_RETRIES_NON_NEGATIVE,void 0,void 0,url);if(config.timeout<0)throw new FetchError(errorMessages_TIMEOUT_NON_NEGATIVE,void 0,void 0,url);if(config.download&&(void 0===config.filename||""===config.filename.trim()))throw new FetchError(errorMessages_FILENAME_REQUIRED,void 0,void 0,url);if(config.balancer){const response=await this.executeWithBalancer(method,url,config);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}const fullUrl=function(url,baseURL){if(url.startsWith(schemes_HTTP)||url.startsWith(schemes_HTTPS))return url;if(baseURL)return`${baseURL.endsWith("/")?baseURL.slice(0,-1):baseURL}${url.startsWith("/")?url:`/${url}`}`;return url}(url,config.baseURL),response=await RetryHandler.executeWithRetries(fullUrl,{retries:config.retries,timeout:config.timeout,download:config.download,...void 0!==config.filename?{filename:config.filename}:{}},()=>this.executeRequest(method,fullUrl,config),honorRetryAfterIfPresent);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}static async executeRequest(method,fullUrl,config){let controller;try{config.signal||(controller=function(timeout){const controller=new AbortController;if(timeout>0){const timeoutId=setTimeout(()=>{controller.abort()},timeout);controller.timeoutId=timeoutId}return controller}(config.timeout));const fetchOptions=function(method,config,controller){const headers={...config.headers};void 0!==config.body&&(config.body instanceof FormData?delete headers["Content-Type"]:config.body instanceof URLSearchParams&&(headers["Content-Type"]=contentTypes_APPLICATION_URL_ENCODED));const requestInit={method:method,headers:createHeaders(headers)},methodsWithBody=[httpMethods_POST,httpMethods_PUT,httpMethods_PATCH];if(void 0!==config.body&&methodsWithBody.includes(method)){const bodyResult=processRequestBody(config.body,config.headers,config.maxRate);requestInit.body=bodyResult.body,bodyResult.needsDuplex&&(requestInit.duplex="half")}return config.signal?requestInit.signal=config.signal:controller&&(requestInit.signal=controller.signal),requestInit}(method,config,controller),response=await globalThis.fetch(fullUrl,fetchOptions);if(controller&&cleanupController(controller),!response.ok){const errorData=await ErrorHandler.getErrorData(response),retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);throw null!==retryAfter&&(err.retryAfter=retryAfter),err}if(config.stream){return{success:!0,data:StreamHandler.createStreamIterator(response,fullUrl)}}if(config.download)return await this.handleDownloadResponse(response,config);if(method===httpMethods_HEAD||method===httpMethods_OPTIONS)return{success:!0,data:void 0};const parseConfig={responseType:config.responseType};void 0!==config.onProgress&&(parseConfig.onProgress=config.onProgress);return{success:!0,data:await parseResponseWithProgress(response,parseConfig,fullUrl,method)}}catch(error){return controller&&cleanupController(controller),ErrorHandler.normalizeExecuteError(error,fullUrl)}}static async handleDownloadResponse(response,config){const downloadConfig={};void 0!==config.filename&&(downloadConfig.filename=config.filename),void 0!==config.maxRate&&(downloadConfig.maxRate=config.maxRate),void 0!==config.onProgress&&(downloadConfig.onProgress=config.onProgress),await handleDownload(response,downloadConfig);const contentLength=response.headers.get(headers_CONTENT_LENGTH),contentType=response.headers.get(headers_CONTENT_TYPE);return{success:!0,data:{filename:config.filename??"download",size:null!==contentLength?parseInt(contentLength,10):0,type:contentType??"application/octet-stream",status:response.status,ok:response.ok}}}static async executeWithBalancer(method,url,config){const{balancer:balancer,...baseConfig}=config;return await BalancerHandler.executeWithBalancer(url,balancer,async endpoint=>{try{return{success:!0,data:await RetryHandler.executeWithRetries(endpoint,{retries:baseConfig.retries,timeout:baseConfig.timeout,download:baseConfig.download,...void 0!==baseConfig.filename?{filename:baseConfig.filename}:{}},()=>this.executeRequest(method,endpoint,baseConfig),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}})}static async forwardResponse(responseData,config){const{forwarder:forwarder,...baseConfig}=config,forwarderConfig=ForwarderHandler.createConfigFromForwarders(forwarder);await ForwarderHandler.forwardResponse(responseData,forwarderConfig,(endpoint,forwardMethod,body,headers)=>this.executeRequest(forwardMethod,endpoint,{...baseConfig,body:body,headers:headers??{}}))}}; |
@@ -1,1 +0,1 @@ | ||
| "use strict";Object.defineProperty(exports,"__esModule",{value:!0});class FetchError extends Error{status;data;url;constructor(e,t,r,n){super(e),this.name="FetchError",this.status=t,this.data=r,this.url=n}}const e="content-length",t="content-type",r="application/json",n="application/octet-stream",s="application/x-www-form-urlencoded",a="text/",o="text/plain",i="POST",c="PATCH",u="HEAD",l="OPTIONS",d="Filename is required when download is enabled",f="Filename is undefined",h="Response body is null",y="Failed to parse streaming response: ",w="Unknown error",p="AbortError",m={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function g(e){try{return await e.json()}catch(t){try{return await e.text()}catch(e){return}}}function b(e){return!0===e?.includes(r)}function v(e){return!0===e?.includes(a)}async function R(e,n,s){const o=e.headers.get(t);if(s!==u){if("auto"!==n.responseType)switch(n.responseType){case"json":return g(e);case"text":return await e.text();case"buffer":{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}case"blob":return await e.blob();default:return async function(e){const n=e.headers.get(t);if(null===n)try{return await e.text()}catch{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}if(n.includes(r))return await e.json();if(n.includes(a))return await e.text();const s=await e.arrayBuffer();return Object.assign(s,{length:s.byteLength})}(e)}return async function(e,t,r){if(r===u)return;if(b(t))return g(e);if(v(t))return await e.text();const n=await e.arrayBuffer();return Object.assign(n,{length:n.byteLength})}(e,o,s)}}async function x(r,s,a,o){return s.onProgress?async function(r,s,a,o){const i=r.headers.get(e),c=null!==i?(()=>{const e=parseInt(i,10);return Number.isNaN(e)?0:e})():0;if(null===r.body)return R(r,{responseType:"auto"},o);const u=r.body.getReader(),l=[];let d=0;try{for(;;){const{done:e,value:t}=await u.read();if(e)break;if(void 0!==t&&(l.push(t),d+=t.length),d>10485760)throw new FetchError("Response too large",void 0,void 0,a);if(c>0&&void 0!==s.onProgress){const e=Math.round(d/c*100);s.onProgress(e)}}if(0===l.length)return await R(r,{responseType:"auto"},o);const e=new ArrayBuffer(d),i=new Uint8Array(e);let f=0;for(const e of l)i.set(e,f),f+=e.length;const h=new Blob([e],{type:r.headers.get(t)??n}),y=new Response(h,{status:r.status,statusText:r.statusText,headers:r.headers});return await R(y,{responseType:"auto"},o)}finally{u.releaseLock()}}(r,{onProgress:s.onProgress},a,o):R(r,{responseType:s.responseType},o)}class T{static parseRetryAfterMs(e){const t=e.trim();if(""===t)return null;const r=+t;if(!Number.isNaN(r)&&r>=0)return Math.floor(1e3*r);try{const e=new Date(t);if(Number.isNaN(e.getTime()))return null;const r=new Date,n=e.getTime()-r.getTime();return n>0?n:0}catch{return null}}static async honorRetryAfterIfPresent(e){const t=e instanceof Error&&"retryAfter"in e?e.retryAfter:void 0;if(void 0===t)return!1;const r=this.parseRetryAfterMs(t);return null!==r&&(await new Promise(e=>setTimeout(e,r)),!0)}}async function P(e){return T.honorRetryAfterIfPresent(e)}class k{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(e,t){if(0>=t)return 0;const r=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(t/4,256),this.tokens=this.maxTokens,this.refillRate=t/1e3);const n=r*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+n),this.tokens>=e)return this.tokens-=e,this.transferredBytes+=e,0;const s=(e-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=e,Math.min(s,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(e,t){const r=this.calculateDelay(e,t);r>0&&await new Promise(e=>setTimeout(e,r))}}function E(e){if(!e||"string"!=typeof e)return!1;try{const t=new URL(e);if(!["http:","https:"].includes(t.protocol))return!1;if(!t.hostname||0===t.hostname.length)return!1;if(e.endsWith(":"))return!1;if(t.port){const e=parseInt(t.port,10);if(isNaN(e)||1>e||e>65535)return!1}return!0}catch{return!1}}function F(e){e.timeoutId&&clearTimeout(e.timeoutId)}function A(e,t=1e3){const r=Math.min(t*Math.pow(2,e),1e4),n=.75+.5*function(){const e=globalThis.crypto;if(void 0!==e&&"object"==typeof e&&"function"==typeof e.getRandomValues){const t=e,r=new Uint32Array(1);t.getRandomValues(r);const n=(r.at(0)??0)/4294967295;return Number.isFinite(n)?n:.5}const t=Date.now();return(4294967295&(t^t>>>3))%1e6/1e6}(),s=Math.round(r*n);return new Promise(e=>setTimeout(e,s))}function B(e,t,r){if(t>=r)return!1;const n=e?.name;return!(e instanceof Error&&e.name===p||n===p||e instanceof FetchError&&e.data?.name===p||e instanceof FetchError&&void 0!==e.status&&e.status>=400&&500>e.status)}function createHeaders(e={}){return new Headers(e)}class C{static validateBalancerConfig(e){if(!e.endpoints?.length||!Array.isArray(e.endpoints))throw new FetchError("Balancer endpoints are required and must be a non-empty array",void 0,void 0,"");if(!e.strategy||!["fastest","parallel"].includes(e.strategy))throw new FetchError('Balancer strategy must be either "fastest" or "parallel"',void 0,void 0,"");for(const t of e.endpoints)if(!E(t))throw new FetchError("Invalid balancer endpoint URL: "+t,void 0,void 0,t)}static async executeWithBalancer(e,t,r){return this.validateBalancerConfig(t),"fastest"===t.strategy?this.executeFastestStrategy(e,t,r):this.executeParallelStrategy(e,t,r)}static async executeFastestStrategy(e,t,r){const n=[];for(const s of t.endpoints){const t=this.buildFullUrl(s,e),a=await r(t);if(a.success)return a.data;n.push({endpoint:s,error:a.error})}const s=n[n.length-1];if(s?.error instanceof FetchError)throw s.error;throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,n,t.endpoints[0])}static async executeParallelStrategy(e,t,r){const n=t.endpoints.map(async t=>{const n=this.buildFullUrl(t,e),s=await r(n);return s.success?{success:!0,data:s.data,endpoint:t}:{success:!1,error:s.error,endpoint:t}}),s=await Promise.allSettled(n).then(e=>e.map(e=>"fulfilled"===e.status?e.value:{success:!1,error:e.reason,endpoint:"unknown"})),a=s.filter(e=>e.success);if(0===a.length){const e=s.filter(e=>!e.success).map(e=>({endpoint:e.endpoint,error:e.error}));throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,e,t.endpoints[0])}return a.map(e=>e.data)}static buildFullUrl(e,t){const r=e.endsWith("/")?e.slice(0,-1):e;return t&&""!==t.trim()?t.startsWith("http://")||t.startsWith("https://")?t:`${r}${t.startsWith("/")?t:"/"+t}`:r}}async function S(e,t,r,n,s,a){void 0!==t.maxRate&&t.maxRate>0&&await r.throttle(e.length,t.maxRate),n.push(e);const o=s+e.length,i=Math.round(o/a*100);return t.onProgress?.(i),o}async function L(e,t){const r=await e.blob();await O(r,t)}async function O(e,t){if("undefined"!=typeof window&&"undefined"!=typeof document){const r=URL.createObjectURL(e),n=document.createElement("a");n.href=r,n.download=t;try{document.body.appendChild(n),n.click()}finally{document.body.contains(n)&&document.body.removeChild(n),URL.revokeObjectURL(r)}}else{const{writeFileSync:r}=await import("node:fs"),n=await e.arrayBuffer();r(t,Buffer.from(n))}}class D{static normalizeExecuteError(e,t){const r=e?.name;return r===p?{success:!1,error:new FetchError("Request timeout - operation exceeded the specified timeout duration",void 0,e,t)}:{success:!1,error:e}}static async getErrorData(e){return async function(e){try{const r=e.headers.get(t);return null===r?null:b(r)?await e.json():await e.text()}catch{return null}}(e)}static createHttpError(e,t,r){const n=e.headers.get("Retry-After"),s=new FetchError(`HTTP ${e.status}: ${e.statusText}`,e.status,r,t);return null!==n&&(s.retryAfter=n),s}}class U{static validateForwarderConfig(e){if(!e.forwarders?.length||!Array.isArray(e.forwarders))throw new FetchError("Forwarder endpoints are required and must be a non-empty array",void 0,void 0,"");for(const t of e.forwarders)if(!E(t.url))throw new FetchError("Invalid forwarder endpoint URL: "+t.url,void 0,void 0,t.url)}static forwardResponse(e,t,r){this.validateForwarderConfig(t);const n=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(n).then(e=>{this.logForwarderResults(e)}).catch(e=>{}),Promise.resolve()}static forwardResponseWithCallback(e,t,r,n){this.validateForwarderConfig(t);const s=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(s).then(e=>{const t=e.map(e=>"fulfilled"===e.status?e.value:{endpoint:"unknown",success:!1,error:e.reason});n?n(t):this.logForwarderResults(e)}),Promise.resolve()}static logForwarderResults(e){const t=e.filter(e=>"fulfilled"===e.status&&e.value.success).length;e.length-t>0&&e.forEach(e=>{("fulfilled"!==e.status||e.value.success)&&e.status})}static async executeForwarderWithRetries(e,t,r){const{retries:n=m.RETRIES,timeout:s=m.TIMEOUT_MS,url:a,method:o,headers:i,body:c}=e;let u;u="function"==typeof c?c(t):void 0!==c?c:t;try{return{success:!0,data:await W.executeWithRetries(a,{retries:n,timeout:s,download:!1},()=>r(a,o,u,i),P)}}catch(e){return{success:!1,error:e}}}static createConfig(e,t=m.METHOD,r,n,s){return{forwarders:e.map(e=>({method:t,url:e,...r?{headers:r}:{},...void 0!==n?{timeout:n}:{},...void 0!==s?{retries:s}:{}}))}}static createConfigFromForwarders(e){return{forwarders:e}}}class W{static async executeWithRetries(e,t,r,n){let s=null;for(let e=0;e<=t.retries;e++){const a=await r();if(a.success)return a.data;if(s=a.error,!B(a.error,e,t.retries))break;await n(a.error)||await A(e)}if(s instanceof FetchError)throw s;throw new FetchError(s instanceof Error?s.message:w,void 0,s,e)}}class q{static createStreamIterator(e,r){const n=e.body;if(!n)throw new FetchError(h,void 0,null,r);return{async*[Symbol.asyncIterator](){const s=n.getReader();try{const n=e.headers.get(t),a=b(n),o=v(n);if(a)return void(yield*q.iterateNdjson(s,r));if(o)return void(yield*q.iterateText(s));yield*q.iterateBinary(s)}catch(e){throw new FetchError(`${y}${e instanceof Error?e.message:w}`,void 0,e,r)}finally{s.releaseLock()}}}}static async readDecodedChunk(e,t){const{done:r,value:n}=await e.read();return r?null:void 0===n?"":t.decode(n,{stream:!0})}static async*iterateText(e){const t=new TextDecoder;for(;;){const{done:r,value:n}=await e.read();if(r)break;if(void 0!==n){const e=t.decode(n,{stream:!0});e.length>0&&(yield e)}}}static async*iterateNdjson(e,t){let r="";const n=new TextDecoder;for(;;){const s=await q.readDecodedChunk(e,n);if(null===s)break;r+=s,yield*q.yieldCompleteJsonLines(r,e=>{r=e},t)}const s=r.trim();s.length>0&&(yield*q.safeParseJsonLine(s,t))}static*yieldCompleteJsonLines(e,t,r){let n;for(;-1!==(n=e.indexOf("\n"));){const s=e.slice(0,n).trim();t(e=e.slice(n+1)),0!==s.length&&(yield*q.safeParseJsonLine(s,r))}}static*safeParseJsonLine(e,t){try{yield JSON.parse(e)}catch(e){throw new FetchError(`${y}${e instanceof Error?e.message:w}`,void 0,e,t)}}static async*iterateBinary(e){for(;;){const{done:t,value:r}=await e.read();if(t)break;void 0!==r&&(yield r)}}}exports.BalancerHandler=C,exports.ForwarderHandler=U,exports.createHeaders=createHeaders,exports.default=class{static defaultConfig={timeout:3e4,retries:1,headers:{"Content-Type":r},baseURL:"",stream:!1,download:!1,responseType:"auto"};static async get(e,t={}){return this.request("GET",e,t)}static async post(e,t,r={}){return this.request(i,e,this.createRequestOptions(r,t))}static async put(e,t,r={}){return this.request("PUT",e,this.createRequestOptions(r,t))}static async patch(e,t,r={}){return this.request(c,e,this.createRequestOptions(r,t))}static async delete(e,t={}){return this.request("DELETE",e,t)}static async head(e,t={}){return this.request(u,e,t)}static async options(e,t={}){return this.request(l,e,t)}static createRequestOptions(e,t){return{...e,...void 0!==t?{body:t}:{}}}static async request(e,t,r={}){if("string"!=typeof t||""===t.trim())throw new FetchError("URL must be a non-empty string",void 0,void 0,t);const n={...this.defaultConfig,...r};if(0>n.retries)throw new FetchError("Retries must be a non-negative number",void 0,void 0,t);if(0>n.timeout)throw new FetchError("Timeout must be a non-negative number",void 0,void 0,t);if(n.download&&(void 0===n.filename||""===n.filename.trim()))throw new FetchError(d,void 0,void 0,t);if(n.balancer){const r=await this.executeWithBalancer(e,t,n);return n.forwarder&&n.forwarder.length>0&&(U.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(r,n)),r}const s=function(e,t){return e.startsWith("http://")||e.startsWith("https://")?e:t?`${t.endsWith("/")?t.slice(0,-1):t}${e.startsWith("/")?e:"/"+e}`:e}(t,n.baseURL),a=await W.executeWithRetries(s,{retries:n.retries,timeout:n.timeout,download:n.download,...void 0!==n.filename?{filename:n.filename}:{}},()=>this.executeRequest(e,s,n),P);return n.forwarder&&n.forwarder.length>0&&(U.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(a,n)),a}static async executeRequest(e,t,n){let a;try{n.signal||(a=function(e){const t=new AbortController;if(e>0){const r=setTimeout(()=>{t.abort()},e);t.timeoutId=r}return t}(n.timeout));const d=function(e,t,n){const a={...t.headers};void 0!==t.body&&(t.body instanceof FormData?delete a["Content-Type"]:t.body instanceof URLSearchParams&&(a["Content-Type"]=s));const u={method:e,headers:createHeaders(a)},l=[i,"PUT",c];if(void 0!==t.body&&l.includes(e)){const e=function(e,t){if(function(e){return e instanceof FormData||e instanceof URLSearchParams||e instanceof Blob||e instanceof ArrayBuffer||e instanceof Uint8Array||"string"==typeof e}(e))return{body:e,needsDuplex:!1};if("object"==typeof e&&null!=e){t["Content-Type"]??=r;const n=function(e,t){let n;const a=t["Content-Type"];if(void 0===a||a===r)n=JSON.stringify(e);else if(a.includes(s)){const t=function(e){const t=new URLSearchParams;for(const[r,n]of Object.entries(e))if(null!=n){let e;e="object"==typeof n?JSON.stringify(n):"string"==typeof n?n:"number"==typeof n||"boolean"==typeof n?n+"":JSON.stringify(n),t.append(r,e)}return t}(e);n=t.toString()}else a.includes(o),n=JSON.stringify(e);return n}(e,t);return{body:n,needsDuplex:!1}}const n=e+"";return t["Content-Type"]??=o,{body:n,needsDuplex:!1}}(t.body,t.headers,t.maxRate);u.body=e.body,e.needsDuplex&&(u.duplex="half")}return t.signal?u.signal=t.signal:n&&(u.signal=n.signal),u}(e,n,a),f=await globalThis.fetch(t,d);if(a&&F(a),!f.ok){const e=await D.getErrorData(f),r=f.headers.get("Retry-After"),n=new FetchError(`HTTP ${f.status}: ${f.statusText}`,f.status,e,t);throw null!==r&&(n.retryAfter=r),n}if(n.stream)return{success:!0,data:q.createStreamIterator(f,t)};if(n.download)return await this.handleDownloadResponse(f,n);if(e===u||e===l)return{success:!0,data:void 0};const h={responseType:n.responseType};return void 0!==n.onProgress&&(h.onProgress=n.onProgress),{success:!0,data:await x(f,h,t,e)}}catch(e){return a&&F(a),D.normalizeExecuteError(e,t)}}static async handleDownloadResponse(r,s){const a={};void 0!==s.filename&&(a.filename=s.filename),void 0!==s.maxRate&&(a.maxRate=s.maxRate),void 0!==s.onProgress&&(a.onProgress=s.onProgress),await async function(r,s){if(void 0===s.filename||""===s.filename.trim())throw Error(d);void 0!==s.onProgress&&null!==r.body?await async function(r,s){const a=r.headers.get(e),o=null!==a?parseInt(a,10):0;if(o>0)await async function(e,r,s){if(null===e.body)throw Error(h);if(void 0===r.onProgress)throw Error("Progress callback is undefined");if(void 0===r.filename)throw Error(f);let a=0;const o=[],i=new k,c=e.body.getReader();try{for(;;){const{done:e,value:t}=await c.read();if(e)break;if(void 0!==t)for(let e=0;e<t.length;e+=128){const n=t.slice(e,e+128);a=await S(n,r,i,o,a,s)}}const u=function(e,r,s){let a=0;const o=new ArrayBuffer(r),i=new Uint8Array(o);for(const t of e)i.set(t,a),a+=t.length;return new Blob([o],{type:s.headers.get(t)??n})}(o,a,e);await O(u,r.filename)}finally{c.releaseLock()}}(r,s,o);else{if(void 0===s.filename)throw Error(f);await L(r,s.filename)}}(r,s):await L(r,s.filename)}(r,a);const o=r.headers.get(e),i=r.headers.get(t);return{success:!0,data:{filename:s.filename??"download",size:null!==o?parseInt(o,10):0,type:i??"application/octet-stream",status:r.status,ok:r.ok}}}static async executeWithBalancer(e,t,r){const{balancer:n,...s}=r;return await C.executeWithBalancer(t,n,async t=>{try{return{success:!0,data:await W.executeWithRetries(t,{retries:s.retries,timeout:s.timeout,download:s.download,...void 0!==s.filename?{filename:s.filename}:{}},()=>this.executeRequest(e,t,s),P)}}catch(e){return{success:!1,error:e}}})}static async forwardResponse(e,t){const{forwarder:r,...n}=t,s=U.createConfigFromForwarders(r);await U.forwardResponse(e,s,(e,t,r,s)=>this.executeRequest(t,e,{...n,body:r,headers:s??{}}))}}; | ||
| "use strict";Object.defineProperty(exports,"__esModule",{value:!0});class FetchError extends Error{status;data;url;constructor(e,t,r,n){super(e),this.name="FetchError",this.status=t,this.data=r,this.url=n}}const e="content-length",t="content-type",r="application/json",n="application/octet-stream",s="application/x-www-form-urlencoded",a="text/",o="text/plain",i="POST",c="PATCH",u="HEAD",l="OPTIONS",d="Filename is required when download is enabled",f="Filename is undefined",h="Response body is null",y="Failed to parse streaming response: ",w="Unknown error",p="AbortError",m={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function g(e){try{return await e.json()}catch(t){try{return await e.text()}catch(e){return}}}function b(e){return!0===e?.includes(r)}function v(e){return!0===e?.includes(a)}async function R(e,n,s){const o=e.headers.get(t);if(s!==u){if("auto"!==n.responseType)switch(n.responseType){case"json":return g(e);case"text":return await e.text();case"buffer":{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}case"blob":return await e.blob();default:return async function(e){const n=e.headers.get(t);if(null===n)try{return await e.text()}catch{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}if(n.includes(r))return await e.json();if(n.includes(a))return await e.text();const s=await e.arrayBuffer();return Object.assign(s,{length:s.byteLength})}(e)}return async function(e,t,r){if(r===u)return;if(b(t))return g(e);if(v(t))return await e.text();const n=await e.arrayBuffer();return Object.assign(n,{length:n.byteLength})}(e,o,s)}}async function x(r,s,a,o){return s.onProgress?async function(r,s,a,o){const i=r.headers.get(e),c=null!==i?(()=>{const e=parseInt(i,10);return Number.isNaN(e)?0:e})():0;if(null===r.body)return R(r,{responseType:"auto"},o);const u=r.body.getReader(),l=[];let d=0;try{for(;;){const{done:e,value:t}=await u.read();if(e)break;if(void 0!==t&&(l.push(t),d+=t.length),d>10485760)throw new FetchError("Response too large",void 0,void 0,a);if(c>0&&void 0!==s.onProgress){const e=Math.round(d/c*100);s.onProgress(e)}}if(0===l.length)return await R(r,{responseType:"auto"},o);const e=new ArrayBuffer(d),i=new Uint8Array(e);let f=0;for(const e of l)i.set(e,f),f+=e.length;const h=new Blob([e],{type:r.headers.get(t)??n}),y=new Response(h,{status:r.status,statusText:r.statusText,headers:r.headers});return await R(y,{responseType:"auto"},o)}finally{u.releaseLock()}}(r,{onProgress:s.onProgress},a,o):R(r,{responseType:s.responseType},o)}class T{static parseRetryAfterMs(e){const t=e.trim();if(""===t)return null;const r=+t;if(!Number.isNaN(r)&&r>=0)return Math.floor(1e3*r);try{const e=new Date(t);if(Number.isNaN(e.getTime()))return null;const r=new Date,n=e.getTime()-r.getTime();return n>0?n:0}catch{return null}}static async honorRetryAfterIfPresent(e){const t=e instanceof Error&&"retryAfter"in e?e.retryAfter:void 0;if(void 0===t)return!1;const r=this.parseRetryAfterMs(t);return null!==r&&(await new Promise(e=>setTimeout(e,r)),!0)}}async function P(e){return T.honorRetryAfterIfPresent(e)}class k{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(e,t){if(0>=t)return 0;const r=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(t/4,256),this.tokens=this.maxTokens,this.refillRate=t/1e3);const n=r*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+n),this.tokens>=e)return this.tokens-=e,this.transferredBytes+=e,0;const s=(e-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=e,Math.min(s,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(e,t){const r=this.calculateDelay(e,t);r>0&&await new Promise(e=>setTimeout(e,r))}}function E(e){if(!e||"string"!=typeof e)return!1;try{const t=new URL(e);if(!["http:","https:"].includes(t.protocol))return!1;if(!t.hostname||0===t.hostname.length)return!1;if(e.endsWith(":"))return!1;if(t.port){const e=parseInt(t.port,10);if(isNaN(e)||1>e||e>65535)return!1}return!0}catch{return!1}}function F(e){e.timeoutId&&clearTimeout(e.timeoutId)}function A(e,t=1e3){const r=Math.min(t*Math.pow(2,e),1e4),n=.75+.5*function(){const e=globalThis.crypto;if(void 0!==e&&"object"==typeof e&&"function"==typeof e.getRandomValues){const t=e,r=new Uint32Array(1);t.getRandomValues(r);const n=(r.at(0)??0)/4294967295;return Number.isFinite(n)?n:.5}const t=Date.now();return(4294967295&(t^t>>>3))%1e6/1e6}(),s=Math.round(r*n);return new Promise(e=>setTimeout(e,s))}function C(e,t,r){if(t>=r)return!1;const n=e?.name;return!(e instanceof Error&&e.name===p||n===p||e instanceof FetchError&&e.data?.name===p||e instanceof FetchError&&void 0!==e.status&&e.status>=400&&500>e.status)}function createHeaders(e={}){return new Headers(e)}class B{static validateBalancerConfig(e){if(!e.endpoints?.length||!Array.isArray(e.endpoints))throw new FetchError("Balancer endpoints are required and must be a non-empty array",void 0,void 0,"");if(!e.strategy||!["fastest","parallel"].includes(e.strategy))throw new FetchError('Balancer strategy must be either "fastest" or "parallel"',void 0,void 0,"");for(const t of e.endpoints)if(!E(t))throw new FetchError("Invalid balancer endpoint URL: "+t,void 0,void 0,t)}static async executeWithBalancer(e,t,r){return this.validateBalancerConfig(t),"fastest"===t.strategy?this.executeFastestStrategy(e,t,r):this.executeParallelStrategy(e,t,r)}static async executeFastestStrategy(e,t,r){const n=[];for(const s of t.endpoints){const t=this.buildFullUrl(s,e),a=await r(t);if(a.success)return a.data;n.push({endpoint:s,error:a.error})}const s=n[n.length-1];if(s?.error instanceof FetchError)throw s.error;throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,n,t.endpoints[0])}static async executeParallelStrategy(e,t,r){const n=t.endpoints.map(async t=>{const n=this.buildFullUrl(t,e),s=await r(n);return s.success?{success:!0,data:s.data,endpoint:t}:{success:!1,error:s.error,endpoint:t}}),s=await Promise.allSettled(n).then(e=>e.map(e=>"fulfilled"===e.status?e.value:{success:!1,error:e.reason,endpoint:"unknown"})),a=s.filter(e=>e.success);if(0===a.length){const e=s.filter(e=>!e.success).map(e=>({endpoint:e.endpoint,error:e.error}));throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,e,t.endpoints[0])}return a.map(e=>e.data)}static buildFullUrl(e,t){const r=e.endsWith("/")?e.slice(0,-1):e;return t&&""!==t.trim()?t.startsWith("http://")||t.startsWith("https://")?t:`${r}${t.startsWith("/")?t:"/"+t}`:r}}async function S(e,t,r,n,s,a){void 0!==t.maxRate&&t.maxRate>0&&await r.throttle(e.length,t.maxRate),n.push(e);const o=s+e.length,i=Math.round(o/a*100);return t.onProgress?.(i),o}async function L(e,t){const r=await e.blob();await O(r,t)}async function O(e,t){if("undefined"!=typeof window&&"undefined"!=typeof document){const r=URL.createObjectURL(e),n=document.createElement("a");n.href=r,n.download=t;try{document.body.appendChild(n),n.click()}finally{document.body.contains(n)&&document.body.removeChild(n),URL.revokeObjectURL(r)}}else{const{writeFileSync:r}=await import("node:fs"),n=await e.arrayBuffer();r(t,Buffer.from(n))}}class D{static normalizeExecuteError(e,t){const r=e?.name;return r===p?{success:!1,error:new FetchError("Request timeout - operation exceeded the specified timeout duration",void 0,e,t)}:{success:!1,error:e}}static async getErrorData(e){return async function(e){try{const r=e.headers.get(t);return null===r?null:b(r)?await e.json():await e.text()}catch{return null}}(e)}static createHttpError(e,t,r){const n=e.headers.get("Retry-After"),s=new FetchError(`HTTP ${e.status}: ${e.statusText}`,e.status,r,t);return null!==n&&(s.retryAfter=n),s}}class U{static validateForwarderConfig(e){if(!e.forwarders?.length||!Array.isArray(e.forwarders))throw new FetchError("Forwarder endpoints are required and must be a non-empty array",void 0,void 0,"");for(const t of e.forwarders)if(!E(t.url))throw new FetchError("Invalid forwarder endpoint URL: "+t.url,void 0,void 0,t.url)}static forwardResponse(e,t,r){this.validateForwarderConfig(t);const n=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(n).then(e=>{this.logForwarderResults(e)}).catch(e=>{}),Promise.resolve()}static forwardResponseWithCallback(e,t,r,n){this.validateForwarderConfig(t);const s=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(s).then(e=>{const t=e.map(e=>"fulfilled"===e.status?e.value:{endpoint:"unknown",success:!1,error:e.reason});n?n(t):this.logForwarderResults(e)}),Promise.resolve()}static logForwarderResults(e){const t=e.filter(e=>"fulfilled"===e.status&&e.value.success).length;e.length-t>0&&e.forEach(e=>{("fulfilled"!==e.status||e.value.success)&&e.status})}static async executeForwarderWithRetries(e,t,r){const{retries:n=m.RETRIES,timeout:s=m.TIMEOUT_MS,url:a,method:o,headers:i,body:c}=e;let u;u="function"==typeof c?c(t):void 0!==c?c:t;try{return{success:!0,data:await q.executeWithRetries(a,{retries:n,timeout:s,download:!1},()=>r(a,o,u,i),P)}}catch(e){return{success:!1,error:e}}}static createConfig(e,t=m.METHOD,r,n,s){return{forwarders:e.map(e=>({method:t,url:e,...r?{headers:r}:{},...void 0!==n?{timeout:n}:{},...void 0!==s?{retries:s}:{}}))}}static createConfigFromForwarders(e){return{forwarders:e}}}class q{static async executeWithRetries(e,t,r,n){let s=null;for(let e=0;e<=t.retries;e++){const a=await r();if(a.success)return a.data;if(s=a.error,!C(a.error,e,t.retries))break;await n(a.error)||await A(e)}if(s instanceof FetchError)throw s;throw new FetchError(s instanceof Error?s.message:w,void 0,s,e)}}class W{static createStreamIterator(e,r){const n=e.body;if(!n)throw new FetchError(h,void 0,null,r);return{async*[Symbol.asyncIterator](){const s=n.getReader();try{const n=e.headers.get(t),a=b(n),o=v(n);if(a)return void(yield*W.iterateNdjson(s,r));if(o)return void(yield*W.iterateText(s));yield*W.iterateBinary(s)}catch(e){throw new FetchError(`${y}${e instanceof Error?e.message:w}`,void 0,e,r)}finally{s.releaseLock()}}}}static async readDecodedChunk(e,t){const{done:r,value:n}=await e.read();return r?null:void 0===n?"":t.decode(n,{stream:!0})}static async*iterateText(e){const t=new TextDecoder;for(;;){const{done:r,value:n}=await e.read();if(r)break;if(void 0!==n){const e=t.decode(n,{stream:!0});e.length>0&&(yield e)}}}static async*iterateNdjson(e,t){let r="";const n=new TextDecoder;for(;;){const s=await W.readDecodedChunk(e,n);if(null===s)break;r+=s,yield*W.yieldCompleteJsonLines(r,e=>{r=e},t)}const s=r.trim();s.length>0&&(yield*W.safeParseJsonLine(s,t))}static*yieldCompleteJsonLines(e,t,r){let n;for(;-1!==(n=e.indexOf("\n"));){const s=e.slice(0,n).trim();t(e=e.slice(n+1)),0!==s.length&&(yield*W.safeParseJsonLine(s,r))}}static*safeParseJsonLine(e,t){try{yield JSON.parse(e)}catch(e){throw new FetchError(`${y}${e instanceof Error?e.message:w}`,void 0,e,t)}}static async*iterateBinary(e){for(;;){const{done:t,value:r}=await e.read();if(t)break;void 0!==r&&(yield r)}}}exports.BalancerHandler=B,exports.ForwarderHandler=U,exports.createHeaders=createHeaders,exports.default=class{static defaultConfig={timeout:3e4,retries:1,headers:{"Content-Type":r},baseURL:"",stream:!1,download:!1,responseType:"auto"};static async get(e,t={}){return this.request("GET",e,t)}static async post(e,t,r={}){return this.request(i,e,this.createRequestOptions(r,t))}static async put(e,t,r={}){return this.request("PUT",e,this.createRequestOptions(r,t))}static async patch(e,t,r={}){return this.request(c,e,this.createRequestOptions(r,t))}static async delete(e,t={}){return this.request("DELETE",e,t)}static async head(e,t={}){return this.request(u,e,t)}static async options(e,t={}){return this.request(l,e,t)}static async trace(e,t={}){return this.request("TRACE",e,t)}static createRequestOptions(e,t){return{...e,...void 0!==t?{body:t}:{}}}static async request(e,t,r={}){if("string"!=typeof t||""===t.trim())throw new FetchError("URL must be a non-empty string",void 0,void 0,t);const n={...this.defaultConfig,...r};if(0>n.retries)throw new FetchError("Retries must be a non-negative number",void 0,void 0,t);if(0>n.timeout)throw new FetchError("Timeout must be a non-negative number",void 0,void 0,t);if(n.download&&(void 0===n.filename||""===n.filename.trim()))throw new FetchError(d,void 0,void 0,t);if(n.balancer){const r=await this.executeWithBalancer(e,t,n);return n.forwarder&&n.forwarder.length>0&&(U.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(r,n)),r}const s=function(e,t){return e.startsWith("http://")||e.startsWith("https://")?e:t?`${t.endsWith("/")?t.slice(0,-1):t}${e.startsWith("/")?e:"/"+e}`:e}(t,n.baseURL),a=await q.executeWithRetries(s,{retries:n.retries,timeout:n.timeout,download:n.download,...void 0!==n.filename?{filename:n.filename}:{}},()=>this.executeRequest(e,s,n),P);return n.forwarder&&n.forwarder.length>0&&(U.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(a,n)),a}static async executeRequest(e,t,n){let a;try{n.signal||(a=function(e){const t=new AbortController;if(e>0){const r=setTimeout(()=>{t.abort()},e);t.timeoutId=r}return t}(n.timeout));const d=function(e,t,n){const a={...t.headers};void 0!==t.body&&(t.body instanceof FormData?delete a["Content-Type"]:t.body instanceof URLSearchParams&&(a["Content-Type"]=s));const u={method:e,headers:createHeaders(a)},l=[i,"PUT",c];if(void 0!==t.body&&l.includes(e)){const e=function(e,t){if(function(e){return e instanceof FormData||e instanceof URLSearchParams||e instanceof Blob||e instanceof ArrayBuffer||e instanceof Uint8Array||"string"==typeof e}(e))return{body:e,needsDuplex:!1};if("object"==typeof e&&null!=e){t["Content-Type"]??=r;const n=function(e,t){let n;const a=t["Content-Type"];if(void 0===a||a===r)n=JSON.stringify(e);else if(a.includes(s)){const t=function(e){const t=new URLSearchParams;for(const[r,n]of Object.entries(e))if(null!=n){let e;e="object"==typeof n?JSON.stringify(n):"string"==typeof n?n:"number"==typeof n||"boolean"==typeof n?n+"":JSON.stringify(n),t.append(r,e)}return t}(e);n=t.toString()}else a.includes(o),n=JSON.stringify(e);return n}(e,t);return{body:n,needsDuplex:!1}}const n=e+"";return t["Content-Type"]??=o,{body:n,needsDuplex:!1}}(t.body,t.headers,t.maxRate);u.body=e.body,e.needsDuplex&&(u.duplex="half")}return t.signal?u.signal=t.signal:n&&(u.signal=n.signal),u}(e,n,a),f=await globalThis.fetch(t,d);if(a&&F(a),!f.ok){const e=await D.getErrorData(f),r=f.headers.get("Retry-After"),n=new FetchError(`HTTP ${f.status}: ${f.statusText}`,f.status,e,t);throw null!==r&&(n.retryAfter=r),n}if(n.stream)return{success:!0,data:W.createStreamIterator(f,t)};if(n.download)return await this.handleDownloadResponse(f,n);if(e===u||e===l)return{success:!0,data:void 0};const h={responseType:n.responseType};return void 0!==n.onProgress&&(h.onProgress=n.onProgress),{success:!0,data:await x(f,h,t,e)}}catch(e){return a&&F(a),D.normalizeExecuteError(e,t)}}static async handleDownloadResponse(r,s){const a={};void 0!==s.filename&&(a.filename=s.filename),void 0!==s.maxRate&&(a.maxRate=s.maxRate),void 0!==s.onProgress&&(a.onProgress=s.onProgress),await async function(r,s){if(void 0===s.filename||""===s.filename.trim())throw Error(d);void 0!==s.onProgress&&null!==r.body?await async function(r,s){const a=r.headers.get(e),o=null!==a?parseInt(a,10):0;if(o>0)await async function(e,r,s){if(null===e.body)throw Error(h);if(void 0===r.onProgress)throw Error("Progress callback is undefined");if(void 0===r.filename)throw Error(f);let a=0;const o=[],i=new k,c=e.body.getReader();try{for(;;){const{done:e,value:t}=await c.read();if(e)break;if(void 0!==t)for(let e=0;e<t.length;e+=128){const n=t.slice(e,e+128);a=await S(n,r,i,o,a,s)}}const u=function(e,r,s){let a=0;const o=new ArrayBuffer(r),i=new Uint8Array(o);for(const t of e)i.set(t,a),a+=t.length;return new Blob([o],{type:s.headers.get(t)??n})}(o,a,e);await O(u,r.filename)}finally{c.releaseLock()}}(r,s,o);else{if(void 0===s.filename)throw Error(f);await L(r,s.filename)}}(r,s):await L(r,s.filename)}(r,a);const o=r.headers.get(e),i=r.headers.get(t);return{success:!0,data:{filename:s.filename??"download",size:null!==o?parseInt(o,10):0,type:i??"application/octet-stream",status:r.status,ok:r.ok}}}static async executeWithBalancer(e,t,r){const{balancer:n,...s}=r;return await B.executeWithBalancer(t,n,async t=>{try{return{success:!0,data:await q.executeWithRetries(t,{retries:s.retries,timeout:s.timeout,download:s.download,...void 0!==s.filename?{filename:s.filename}:{}},()=>this.executeRequest(e,t,s),P)}}catch(e){return{success:!1,error:e}}})}static async forwardResponse(e,t){const{forwarder:r,...n}=t,s=U.createConfigFromForwarders(r);await U.forwardResponse(e,s,(e,t,r,s)=>this.executeRequest(t,e,{...n,body:r,headers:s??{}}))}}; |
+1
-0
@@ -87,2 +87,3 @@ type FetchResponse<T> = T | AsyncIterable<T> | undefined; | ||
| static options<T = unknown>(url: string, options?: FetchOptions): Promise<FetchResponse<T> | FetchResponse<T[]>>; | ||
| static trace<T = unknown>(url: string, options?: FetchOptions): Promise<FetchResponse<T> | FetchResponse<T[]>>; | ||
| private static createRequestOptions; | ||
@@ -89,0 +90,0 @@ private static request; |
@@ -1,1 +0,1 @@ | ||
| class FetchError extends Error{status;data;url;constructor(message,status,data,url){super(message),this.name="FetchError",this.status=status,this.data=data,this.url=url}}const headers_CONTENT_LENGTH="content-length",headers_CONTENT_TYPE="content-type",schemes_HTTP="http://",schemes_HTTPS="https://",contentTypes_APPLICATION_JSON="application/json",contentTypes_APPLICATION_OCTET_STREAM="application/octet-stream",contentTypes_APPLICATION_URL_ENCODED="application/x-www-form-urlencoded",contentTypes_TEXT_PREFIX="text/",contentTypes_TEXT_PLAIN="text/plain",httpMethods_GET="GET",httpMethods_POST="POST",httpMethods_PUT="PUT",httpMethods_PATCH="PATCH",httpMethods_DELETE="DELETE",httpMethods_HEAD="HEAD",httpMethods_OPTIONS="OPTIONS",errorMessages_ABORTED="Request timeout - operation exceeded the specified timeout duration",errorMessages_BALANCER_ENDPOINTS_REQUIRED="Balancer endpoints are required and must be a non-empty array",errorMessages_BALANCER_STRATEGY_INVALID='Balancer strategy must be either "fastest" or "parallel"',errorMessages_FILENAME_REQUIRED="Filename is required when download is enabled",errorMessages_FILENAME_UNDEFINED="Filename is undefined",errorMessages_FORWARDER_ENDPOINTS_REQUIRED="Forwarder endpoints are required and must be a non-empty array",errorMessages_PROGRESS_CALLBACK_UNDEFINED="Progress callback is undefined",errorMessages_RESPONSE_BODY_NULL="Response body is null",errorMessages_RESPONSE_TOO_LARGE="Response too large",errorMessages_RETRIES_NON_NEGATIVE="Retries must be a non-negative number",errorMessages_STREAM_PARSE_PREFIX="Failed to parse streaming response: ",errorMessages_TIMEOUT_NON_NEGATIVE="Timeout must be a non-negative number",errorMessages_UNKNOWN_ERROR="Unknown error",errorMessages_URL_INVALID="URL must be a non-empty string",defaults_BASE_URL="",defaults_DOWNLOAD=!1,defaults_RESPONSE_TYPE="auto",defaults_RETRIES=1,defaults_STREAM=!1,defaults_TIMEOUT_MS=3e4,misc_NEWLINE="\n",misc_ABORT_ERROR_NAME="AbortError",misc_MAX_NDJSON_BUFFER_BYTES=10485760,retryDelays_BASE_DELAY_MS=1e3,retryDelays_MAX_DELAY_MS=1e4,forwarderDefaults={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function parseJsonWithFallback(response){try{return await response.json()}catch(jsonError){try{return await response.text()}catch(textError){return}}}function isJsonContentType(contentType){return!0===contentType?.includes(contentTypes_APPLICATION_JSON)}function isTextContentType(contentType){return!0===contentType?.includes(contentTypes_TEXT_PREFIX)}async function parseResponseByType(response,config,method){const contentType=response.headers.get(headers_CONTENT_TYPE);if(method!==httpMethods_HEAD){if("auto"!==config.responseType)switch(config.responseType){case"json":return parseJsonWithFallback(response);case"text":return await response.text();case"buffer":{const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}case"blob":return await response.blob();default:return async function(response){const contentType=response.headers.get(headers_CONTENT_TYPE);if(null===contentType)try{return await response.text()}catch{const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}if(contentType.includes(contentTypes_APPLICATION_JSON))return await response.json();if(contentType.includes(contentTypes_TEXT_PREFIX))return await response.text();const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}(response)}return async function(response,contentType,method){if(method===httpMethods_HEAD)return;if(isJsonContentType(contentType))return parseJsonWithFallback(response);if(isTextContentType(contentType))return await response.text();const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}(response,contentType,method)}}async function parseResponseWithProgress(response,config,url,method){return config.onProgress?async function(response,config,url,method){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?(()=>{const parsed=parseInt(contentLength,10);return Number.isNaN(parsed)?0:parsed})():0;if(null===response.body)return parseResponseByType(response,{responseType:"auto"},method);const reader=response.body.getReader(),chunks=[];let received=0;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value&&(chunks.push(value),received+=value.length),received>misc_MAX_NDJSON_BUFFER_BYTES)throw new FetchError(errorMessages_RESPONSE_TOO_LARGE,void 0,void 0,url);if(total>0&&void 0!==config.onProgress){const percentage=Math.round(received/total*100);config.onProgress(percentage)}}if(0===chunks.length)return await parseResponseByType(response,{responseType:"auto"},method);const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);let position=0;for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;const blob=new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM}),newResponse=new Response(blob,{status:response.status,statusText:response.statusText,headers:response.headers});return await parseResponseByType(newResponse,{responseType:"auto"},method)}finally{reader.releaseLock()}}(response,{onProgress:config.onProgress},url,method):parseResponseByType(response,{responseType:config.responseType},method)}class RetryAfterParser{static parseRetryAfterMs(retryAfter){const trimmed=retryAfter.trim();if(""===trimmed)return null;const seconds=Number(trimmed);if(!Number.isNaN(seconds)&&seconds>=0)return Math.floor(1e3*seconds);try{const date=new Date(trimmed);if(Number.isNaN(date.getTime()))return null;const now=new Date,delayMs=date.getTime()-now.getTime();return delayMs>0?delayMs:0}catch{return null}}static async honorRetryAfterIfPresent(error){const retryAfterHeader=error instanceof Error&&"retryAfter"in error?error.retryAfter:void 0;if(void 0===retryAfterHeader)return!1;const delayMs=this.parseRetryAfterMs(retryAfterHeader);return null!==delayMs&&(await new Promise(resolve=>setTimeout(resolve,delayMs)),!0)}}async function honorRetryAfterIfPresent(error){return RetryAfterParser.honorRetryAfterIfPresent(error)}class RateLimiter{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(bytesToTransfer,maxRateBps){if(maxRateBps<=0)return 0;const elapsedMs=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(maxRateBps/4,256),this.tokens=this.maxTokens,this.refillRate=maxRateBps/1e3);const tokensToAdd=elapsedMs*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+tokensToAdd),this.tokens>=bytesToTransfer)return this.tokens-=bytesToTransfer,this.transferredBytes+=bytesToTransfer,0;const delayMs=(bytesToTransfer-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=bytesToTransfer,Math.min(delayMs,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(bytesToTransfer,maxRateBps){const delayMs=this.calculateDelay(bytesToTransfer,maxRateBps);delayMs>0&&await new Promise(resolve=>setTimeout(resolve,delayMs))}}function isValidURL(url){if(!url||"string"!=typeof url)return!1;try{const parsedUrl=new URL(url);if(!["http:","https:"].includes(parsedUrl.protocol))return!1;if(!parsedUrl.hostname||0===parsedUrl.hostname.length)return!1;if(url.endsWith(":"))return!1;if(parsedUrl.port){const port=parseInt(parsedUrl.port,10);if(isNaN(port)||port<1||port>65535)return!1}return!0}catch{return!1}}function cleanupController(controller){controller.timeoutId&&clearTimeout(controller.timeoutId)}function waitForRetry(attempt,baseDelay=retryDelays_BASE_DELAY_MS){const rawDelay=Math.min(baseDelay*Math.pow(2,attempt),retryDelays_MAX_DELAY_MS),jitter=.75+.5*function(){const cryptoMaybe=globalThis.crypto;if(void 0!==cryptoMaybe&&"object"==typeof cryptoMaybe&&"function"==typeof cryptoMaybe.getRandomValues){const cryptoObj=cryptoMaybe,buf=new Uint32Array(1);cryptoObj.getRandomValues(buf);const value=(buf.at(0)??0)/4294967295;return Number.isFinite(value)?value:.5}const now=Date.now();return(4294967295&(now^now>>>3))%1e6/1e6}(),delay=Math.round(rawDelay*jitter);return new Promise(resolve=>setTimeout(resolve,delay))}function shouldRetry(error,attempt,maxRetries){if(attempt>=maxRetries)return!1;const errorName=error?.name;return!(error instanceof Error&&error.name===misc_ABORT_ERROR_NAME||errorName===misc_ABORT_ERROR_NAME)&&(!(error instanceof FetchError&&error.data?.name===misc_ABORT_ERROR_NAME)&&!(error instanceof FetchError&&void 0!==error.status&&error.status>=400&&error.status<500))}function createHeaders(headers={}){return new Headers(headers)}class BalancerHandler{static validateBalancerConfig(config){if(!config.endpoints?.length||!Array.isArray(config.endpoints))throw new FetchError(errorMessages_BALANCER_ENDPOINTS_REQUIRED,void 0,void 0,"");if(!config.strategy||!["fastest","parallel"].includes(config.strategy))throw new FetchError(errorMessages_BALANCER_STRATEGY_INVALID,void 0,void 0,"");for(const endpoint of config.endpoints)if(!isValidURL(endpoint))throw new FetchError(`Invalid balancer endpoint URL: ${endpoint}`,void 0,void 0,endpoint)}static async executeWithBalancer(url,config,executeRequest){return this.validateBalancerConfig(config),"fastest"===config.strategy?this.executeFastestStrategy(url,config,executeRequest):this.executeParallelStrategy(url,config,executeRequest)}static async executeFastestStrategy(url,config,executeRequest){const errors=[];for(const endpoint of config.endpoints){const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);if(result.success)return result.data;errors.push({endpoint:endpoint,error:result.error})}const lastError=errors[errors.length-1];if(lastError?.error instanceof FetchError)throw lastError.error;throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}static async executeParallelStrategy(url,config,executeRequest){const promises=config.endpoints.map(async endpoint=>{const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);return result.success?{success:!0,data:result.data,endpoint:endpoint}:{success:!1,error:result.error,endpoint:endpoint}}),results=await Promise.allSettled(promises).then(settledResults=>settledResults.map(result=>"fulfilled"===result.status?result.value:{success:!1,error:result.reason,endpoint:"unknown"})),successfulResults=results.filter(result=>result.success);if(0===successfulResults.length){const errors=results.filter(result=>!result.success).map(result=>({endpoint:result.endpoint,error:result.error}));throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}return successfulResults.map(result=>result.data)}static buildFullUrl(endpoint,url){const baseUrl=endpoint.endsWith("/")?endpoint.slice(0,-1):endpoint;if(!url||""===url.trim())return baseUrl;if(url.startsWith("http://")||url.startsWith("https://"))return url;return`${baseUrl}${url.startsWith("/")?url:`/${url}`}`}}async function handleDownload(response,config){if(void 0===config.filename||""===config.filename.trim())throw new Error(errorMessages_FILENAME_REQUIRED);void 0!==config.onProgress&&null!==response.body?await async function(response,config){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?parseInt(contentLength,10):0;if(total>0)await async function(response,config,total){if(null===response.body)throw new Error(errorMessages_RESPONSE_BODY_NULL);if(void 0===config.onProgress)throw new Error(errorMessages_PROGRESS_CALLBACK_UNDEFINED);if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);let received=0;const chunks=[],rateLimiter=new RateLimiter,reader=response.body.getReader(),processingChunkSize=128;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value)for(let i=0;i<value.length;i+=processingChunkSize){const chunk=value.slice(i,i+processingChunkSize);received=await processChunk(chunk,config,rateLimiter,chunks,received,total)}}const blob=function(chunks,received,response){let position=0;const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;return new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM})}(chunks,received,response);await saveBlob(blob,config.filename)}finally{reader.releaseLock()}}(response,config,total);else{if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);await handleDirectDownload(response,config.filename)}}(response,config):await handleDirectDownload(response,config.filename)}async function processChunk(chunk,config,rateLimiter,chunks,received,total){void 0!==config.maxRate&&config.maxRate>0&&await rateLimiter.throttle(chunk.length,config.maxRate),chunks.push(chunk);const newReceived=received+chunk.length,percentage=Math.round(newReceived/total*100);return config.onProgress?.(percentage),newReceived}async function handleDirectDownload(response,filename){const blob=await response.blob();await saveBlob(blob,filename)}async function saveBlob(blob,filename){if("undefined"!=typeof window&&"undefined"!=typeof document){const objectUrl=URL.createObjectURL(blob),link=document.createElement("a");link.href=objectUrl,link.download=filename;try{document.body.appendChild(link),link.click()}finally{document.body.contains(link)&&document.body.removeChild(link),URL.revokeObjectURL(objectUrl)}}else{const{writeFileSync:writeFileSync}=await import("node:fs"),buffer=await blob.arrayBuffer();writeFileSync(filename,Buffer.from(buffer))}}function processRequestBody(body,headers,maxRate,onProgress){if(function(body){return body instanceof FormData||body instanceof URLSearchParams||body instanceof Blob||body instanceof ArrayBuffer||body instanceof Uint8Array||"string"==typeof body}(body))return{body:body,needsDuplex:!1};if("object"==typeof body&&null!=body){headers["Content-Type"]??=contentTypes_APPLICATION_JSON;const serializedBody=function(body,headers){let result;const contentType=headers["Content-Type"];if(void 0===contentType||contentType===contentTypes_APPLICATION_JSON)result=JSON.stringify(body);else if(contentType.includes(contentTypes_APPLICATION_URL_ENCODED)){const params=function(body){const formData=new URLSearchParams;for(const[key,value]of Object.entries(body))if(null!=value){let stringValue;stringValue="object"==typeof value?JSON.stringify(value):"string"==typeof value?value:"number"==typeof value||"boolean"==typeof value?String(value):JSON.stringify(value),formData.append(key,stringValue)}return formData}(body);result=params.toString()}else result=(contentType.includes(contentTypes_TEXT_PLAIN),JSON.stringify(body));return result}(body,headers);return{body:serializedBody,needsDuplex:!1}}const stringBody=String(body);return headers["Content-Type"]??=contentTypes_TEXT_PLAIN,{body:stringBody,needsDuplex:!1}}class ErrorHandler{static normalizeExecuteError(error,fullUrl){const name=error?.name;return name===misc_ABORT_ERROR_NAME?{success:!1,error:new FetchError(errorMessages_ABORTED,void 0,error,fullUrl)}:{success:!1,error:error}}static async getErrorData(response){return async function(response){try{const contentType=response.headers.get(headers_CONTENT_TYPE);return null===contentType?null:isJsonContentType(contentType)?await response.json():await response.text()}catch{return null}}(response)}static createHttpError(response,fullUrl,errorData){const retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);return null!==retryAfter&&(err.retryAfter=retryAfter),err}}class ForwarderHandler{static validateForwarderConfig(config){if(!config.forwarders?.length||!Array.isArray(config.forwarders))throw new FetchError(errorMessages_FORWARDER_ENDPOINTS_REQUIRED,void 0,void 0,"");for(const forwarder of config.forwarders)if(!isValidURL(forwarder.url))throw new FetchError(`Invalid forwarder endpoint URL: ${forwarder.url}`,void 0,void 0,forwarder.url)}static forwardResponse(responseData,config,executeRequest){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(results=>{this.logForwarderResults(results)}).catch(error=>{}),Promise.resolve()}static forwardResponseWithCallback(responseData,config,executeRequest,onComplete){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(settledResults=>{const results=settledResults.map(result=>"fulfilled"===result.status?result.value:{endpoint:"unknown",success:!1,error:result.reason});onComplete?onComplete(results):this.logForwarderResults(settledResults)}),Promise.resolve()}static logForwarderResults(results){const successful=results.filter(result=>"fulfilled"===result.status&&result.value.success).length;results.length-successful>0&&results.forEach(result=>{("fulfilled"!==result.status||result.value.success)&&result.status})}static async executeForwarderWithRetries(forwarder,responseData,executeRequest){const{retries:retries=forwarderDefaults.RETRIES,timeout:timeout=forwarderDefaults.TIMEOUT_MS,url:url,method:method,headers:headers,body:forwarderBody}=forwarder;let body;body="function"==typeof forwarderBody?forwarderBody(responseData):void 0!==forwarderBody?forwarderBody:responseData;try{return{success:!0,data:await RetryHandler.executeWithRetries(url,{retries:retries,timeout:timeout,download:!1},()=>executeRequest(url,method,body,headers),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}}static createConfig(urls,method=forwarderDefaults.METHOD,headers,timeout,retries){return{forwarders:urls.map(url=>({method:method,url:url,...headers?{headers:headers}:{},...void 0!==timeout?{timeout:timeout}:{},...void 0!==retries?{retries:retries}:{}}))}}static createConfigFromForwarders(forwarders){return{forwarders:forwarders}}}class RetryHandler{static async executeWithRetries(fullUrl,config,executeRequest,honorRetryAfter){let lastError=null;for(let attempt=0;attempt<=config.retries;attempt++){const result=await executeRequest();if(result.success)return result.data;if(lastError=result.error,shouldRetry(result.error,attempt,config.retries)){if(await honorRetryAfter(result.error))continue;await waitForRetry(attempt);continue}break}if(lastError instanceof FetchError)throw lastError;throw new FetchError(lastError instanceof Error?lastError.message:errorMessages_UNKNOWN_ERROR,void 0,lastError,fullUrl)}}class StreamHandler{static createStreamIterator(response,url){const bodyStream=response.body;if(!bodyStream)throw new FetchError(errorMessages_RESPONSE_BODY_NULL,void 0,null,url);return{async*[Symbol.asyncIterator](){const reader=bodyStream.getReader();try{const contentType=response.headers.get(headers_CONTENT_TYPE),isJson=isJsonContentType(contentType),isText=isTextContentType(contentType);if(isJson)return void(yield*StreamHandler.iterateNdjson(reader,url));if(isText)return void(yield*StreamHandler.iterateText(reader));yield*StreamHandler.iterateBinary(reader)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}finally{reader.releaseLock()}}}}static async readDecodedChunk(reader,decoder){const{done:done,value:value}=await reader.read();return done?null:void 0===value?"":decoder.decode(value,{stream:!0})}static async*iterateText(reader){const decoder=new TextDecoder;for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value){const chunk=decoder.decode(value,{stream:!0});chunk.length>0&&(yield chunk)}}}static async*iterateNdjson(reader,url){let buffer="";const decoder=new TextDecoder;for(;;){const chunk=await StreamHandler.readDecodedChunk(reader,decoder);if(null===chunk)break;buffer+=chunk,yield*StreamHandler.yieldCompleteJsonLines(buffer,newBuffer=>{buffer=newBuffer},url)}const trimmed=buffer.trim();trimmed.length>0&&(yield*StreamHandler.safeParseJsonLine(trimmed,url))}static*yieldCompleteJsonLines(buffer,setBuffer,url){let newlineIndex;for(;-1!==(newlineIndex=buffer.indexOf(misc_NEWLINE));){const line=buffer.slice(0,newlineIndex).trim();setBuffer(buffer=buffer.slice(newlineIndex+misc_NEWLINE.length)),0!==line.length&&(yield*StreamHandler.safeParseJsonLine(line,url))}}static*safeParseJsonLine(line,url){try{yield JSON.parse(line)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}}static async*iterateBinary(reader){for(;;){const{done:done,value:value}=await reader.read();if(done)break;void 0!==value&&(yield value)}}}class FetchClient{static defaultConfig={timeout:defaults_TIMEOUT_MS,retries:defaults_RETRIES,headers:{"Content-Type":contentTypes_APPLICATION_JSON},baseURL:defaults_BASE_URL,stream:defaults_STREAM,download:defaults_DOWNLOAD,responseType:defaults_RESPONSE_TYPE};static async get(url,options={}){return this.request(httpMethods_GET,url,options)}static async post(url,body,options={}){return this.request(httpMethods_POST,url,this.createRequestOptions(options,body))}static async put(url,body,options={}){return this.request(httpMethods_PUT,url,this.createRequestOptions(options,body))}static async patch(url,body,options={}){return this.request(httpMethods_PATCH,url,this.createRequestOptions(options,body))}static async delete(url,options={}){return this.request(httpMethods_DELETE,url,options)}static async head(url,options={}){return this.request(httpMethods_HEAD,url,options)}static async options(url,options={}){return this.request(httpMethods_OPTIONS,url,options)}static createRequestOptions(options,body){return{...options,...void 0!==body?{body:body}:{}}}static async request(method,url,options={}){if("string"!=typeof url||""===url.trim())throw new FetchError(errorMessages_URL_INVALID,void 0,void 0,url);const config={...this.defaultConfig,...options};if(config.retries<0)throw new FetchError(errorMessages_RETRIES_NON_NEGATIVE,void 0,void 0,url);if(config.timeout<0)throw new FetchError(errorMessages_TIMEOUT_NON_NEGATIVE,void 0,void 0,url);if(config.download&&(void 0===config.filename||""===config.filename.trim()))throw new FetchError(errorMessages_FILENAME_REQUIRED,void 0,void 0,url);if(config.balancer){const response=await this.executeWithBalancer(method,url,config);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}const fullUrl=function(url,baseURL){if(url.startsWith(schemes_HTTP)||url.startsWith(schemes_HTTPS))return url;if(baseURL)return`${baseURL.endsWith("/")?baseURL.slice(0,-1):baseURL}${url.startsWith("/")?url:`/${url}`}`;return url}(url,config.baseURL),response=await RetryHandler.executeWithRetries(fullUrl,{retries:config.retries,timeout:config.timeout,download:config.download,...void 0!==config.filename?{filename:config.filename}:{}},()=>this.executeRequest(method,fullUrl,config),honorRetryAfterIfPresent);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}static async executeRequest(method,fullUrl,config){let controller;try{config.signal||(controller=function(timeout){const controller=new AbortController;if(timeout>0){const timeoutId=setTimeout(()=>{controller.abort()},timeout);controller.timeoutId=timeoutId}return controller}(config.timeout));const fetchOptions=function(method,config,controller){const headers={...config.headers};void 0!==config.body&&(config.body instanceof FormData?delete headers["Content-Type"]:config.body instanceof URLSearchParams&&(headers["Content-Type"]=contentTypes_APPLICATION_URL_ENCODED));const requestInit={method:method,headers:createHeaders(headers)},methodsWithBody=[httpMethods_POST,httpMethods_PUT,httpMethods_PATCH];if(void 0!==config.body&&methodsWithBody.includes(method)){const bodyResult=processRequestBody(config.body,config.headers,config.maxRate);requestInit.body=bodyResult.body,bodyResult.needsDuplex&&(requestInit.duplex="half")}return config.signal?requestInit.signal=config.signal:controller&&(requestInit.signal=controller.signal),requestInit}(method,config,controller),response=await globalThis.fetch(fullUrl,fetchOptions);if(controller&&cleanupController(controller),!response.ok){const errorData=await ErrorHandler.getErrorData(response),retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);throw null!==retryAfter&&(err.retryAfter=retryAfter),err}if(config.stream){return{success:!0,data:StreamHandler.createStreamIterator(response,fullUrl)}}if(config.download)return await this.handleDownloadResponse(response,config);if(method===httpMethods_HEAD||method===httpMethods_OPTIONS)return{success:!0,data:void 0};const parseConfig={responseType:config.responseType};void 0!==config.onProgress&&(parseConfig.onProgress=config.onProgress);return{success:!0,data:await parseResponseWithProgress(response,parseConfig,fullUrl,method)}}catch(error){return controller&&cleanupController(controller),ErrorHandler.normalizeExecuteError(error,fullUrl)}}static async handleDownloadResponse(response,config){const downloadConfig={};void 0!==config.filename&&(downloadConfig.filename=config.filename),void 0!==config.maxRate&&(downloadConfig.maxRate=config.maxRate),void 0!==config.onProgress&&(downloadConfig.onProgress=config.onProgress),await handleDownload(response,downloadConfig);const contentLength=response.headers.get(headers_CONTENT_LENGTH),contentType=response.headers.get(headers_CONTENT_TYPE);return{success:!0,data:{filename:config.filename??"download",size:null!==contentLength?parseInt(contentLength,10):0,type:contentType??"application/octet-stream",status:response.status,ok:response.ok}}}static async executeWithBalancer(method,url,config){const{balancer:balancer,...baseConfig}=config;return await BalancerHandler.executeWithBalancer(url,balancer,async endpoint=>{try{return{success:!0,data:await RetryHandler.executeWithRetries(endpoint,{retries:baseConfig.retries,timeout:baseConfig.timeout,download:baseConfig.download,...void 0!==baseConfig.filename?{filename:baseConfig.filename}:{}},()=>this.executeRequest(method,endpoint,baseConfig),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}})}static async forwardResponse(responseData,config){const{forwarder:forwarder,...baseConfig}=config,forwarderConfig=ForwarderHandler.createConfigFromForwarders(forwarder);await ForwarderHandler.forwardResponse(responseData,forwarderConfig,(endpoint,forwardMethod,body,headers)=>this.executeRequest(forwardMethod,endpoint,{...baseConfig,body:body,headers:headers??{}}))}}export{BalancerHandler,ForwarderHandler,createHeaders,FetchClient as default}; | ||
| class FetchError extends Error{status;data;url;constructor(message,status,data,url){super(message),this.name="FetchError",this.status=status,this.data=data,this.url=url}}const headers_CONTENT_LENGTH="content-length",headers_CONTENT_TYPE="content-type",schemes_HTTP="http://",schemes_HTTPS="https://",contentTypes_APPLICATION_JSON="application/json",contentTypes_APPLICATION_OCTET_STREAM="application/octet-stream",contentTypes_APPLICATION_URL_ENCODED="application/x-www-form-urlencoded",contentTypes_TEXT_PREFIX="text/",contentTypes_TEXT_PLAIN="text/plain",httpMethods_GET="GET",httpMethods_POST="POST",httpMethods_PUT="PUT",httpMethods_PATCH="PATCH",httpMethods_DELETE="DELETE",httpMethods_HEAD="HEAD",httpMethods_OPTIONS="OPTIONS",httpMethods_TRACE="TRACE",errorMessages_ABORTED="Request timeout - operation exceeded the specified timeout duration",errorMessages_BALANCER_ENDPOINTS_REQUIRED="Balancer endpoints are required and must be a non-empty array",errorMessages_BALANCER_STRATEGY_INVALID='Balancer strategy must be either "fastest" or "parallel"',errorMessages_FILENAME_REQUIRED="Filename is required when download is enabled",errorMessages_FILENAME_UNDEFINED="Filename is undefined",errorMessages_FORWARDER_ENDPOINTS_REQUIRED="Forwarder endpoints are required and must be a non-empty array",errorMessages_PROGRESS_CALLBACK_UNDEFINED="Progress callback is undefined",errorMessages_RESPONSE_BODY_NULL="Response body is null",errorMessages_RESPONSE_TOO_LARGE="Response too large",errorMessages_RETRIES_NON_NEGATIVE="Retries must be a non-negative number",errorMessages_STREAM_PARSE_PREFIX="Failed to parse streaming response: ",errorMessages_TIMEOUT_NON_NEGATIVE="Timeout must be a non-negative number",errorMessages_UNKNOWN_ERROR="Unknown error",errorMessages_URL_INVALID="URL must be a non-empty string",defaults_BASE_URL="",defaults_DOWNLOAD=!1,defaults_RESPONSE_TYPE="auto",defaults_RETRIES=1,defaults_STREAM=!1,defaults_TIMEOUT_MS=3e4,misc_NEWLINE="\n",misc_ABORT_ERROR_NAME="AbortError",misc_MAX_NDJSON_BUFFER_BYTES=10485760,retryDelays_BASE_DELAY_MS=1e3,retryDelays_MAX_DELAY_MS=1e4,forwarderDefaults={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function parseJsonWithFallback(response){try{return await response.json()}catch(jsonError){try{return await response.text()}catch(textError){return}}}function isJsonContentType(contentType){return!0===contentType?.includes(contentTypes_APPLICATION_JSON)}function isTextContentType(contentType){return!0===contentType?.includes(contentTypes_TEXT_PREFIX)}async function parseResponseByType(response,config,method){const contentType=response.headers.get(headers_CONTENT_TYPE);if(method!==httpMethods_HEAD){if("auto"!==config.responseType)switch(config.responseType){case"json":return parseJsonWithFallback(response);case"text":return await response.text();case"buffer":{const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}case"blob":return await response.blob();default:return async function(response){const contentType=response.headers.get(headers_CONTENT_TYPE);if(null===contentType)try{return await response.text()}catch{const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}if(contentType.includes(contentTypes_APPLICATION_JSON))return await response.json();if(contentType.includes(contentTypes_TEXT_PREFIX))return await response.text();const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}(response)}return async function(response,contentType,method){if(method===httpMethods_HEAD)return;if(isJsonContentType(contentType))return parseJsonWithFallback(response);if(isTextContentType(contentType))return await response.text();const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}(response,contentType,method)}}async function parseResponseWithProgress(response,config,url,method){return config.onProgress?async function(response,config,url,method){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?(()=>{const parsed=parseInt(contentLength,10);return Number.isNaN(parsed)?0:parsed})():0;if(null===response.body)return parseResponseByType(response,{responseType:"auto"},method);const reader=response.body.getReader(),chunks=[];let received=0;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value&&(chunks.push(value),received+=value.length),received>misc_MAX_NDJSON_BUFFER_BYTES)throw new FetchError(errorMessages_RESPONSE_TOO_LARGE,void 0,void 0,url);if(total>0&&void 0!==config.onProgress){const percentage=Math.round(received/total*100);config.onProgress(percentage)}}if(0===chunks.length)return await parseResponseByType(response,{responseType:"auto"},method);const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);let position=0;for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;const blob=new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM}),newResponse=new Response(blob,{status:response.status,statusText:response.statusText,headers:response.headers});return await parseResponseByType(newResponse,{responseType:"auto"},method)}finally{reader.releaseLock()}}(response,{onProgress:config.onProgress},url,method):parseResponseByType(response,{responseType:config.responseType},method)}class RetryAfterParser{static parseRetryAfterMs(retryAfter){const trimmed=retryAfter.trim();if(""===trimmed)return null;const seconds=Number(trimmed);if(!Number.isNaN(seconds)&&seconds>=0)return Math.floor(1e3*seconds);try{const date=new Date(trimmed);if(Number.isNaN(date.getTime()))return null;const now=new Date,delayMs=date.getTime()-now.getTime();return delayMs>0?delayMs:0}catch{return null}}static async honorRetryAfterIfPresent(error){const retryAfterHeader=error instanceof Error&&"retryAfter"in error?error.retryAfter:void 0;if(void 0===retryAfterHeader)return!1;const delayMs=this.parseRetryAfterMs(retryAfterHeader);return null!==delayMs&&(await new Promise(resolve=>setTimeout(resolve,delayMs)),!0)}}async function honorRetryAfterIfPresent(error){return RetryAfterParser.honorRetryAfterIfPresent(error)}class RateLimiter{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(bytesToTransfer,maxRateBps){if(maxRateBps<=0)return 0;const elapsedMs=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(maxRateBps/4,256),this.tokens=this.maxTokens,this.refillRate=maxRateBps/1e3);const tokensToAdd=elapsedMs*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+tokensToAdd),this.tokens>=bytesToTransfer)return this.tokens-=bytesToTransfer,this.transferredBytes+=bytesToTransfer,0;const delayMs=(bytesToTransfer-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=bytesToTransfer,Math.min(delayMs,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(bytesToTransfer,maxRateBps){const delayMs=this.calculateDelay(bytesToTransfer,maxRateBps);delayMs>0&&await new Promise(resolve=>setTimeout(resolve,delayMs))}}function isValidURL(url){if(!url||"string"!=typeof url)return!1;try{const parsedUrl=new URL(url);if(!["http:","https:"].includes(parsedUrl.protocol))return!1;if(!parsedUrl.hostname||0===parsedUrl.hostname.length)return!1;if(url.endsWith(":"))return!1;if(parsedUrl.port){const port=parseInt(parsedUrl.port,10);if(isNaN(port)||port<1||port>65535)return!1}return!0}catch{return!1}}function cleanupController(controller){controller.timeoutId&&clearTimeout(controller.timeoutId)}function waitForRetry(attempt,baseDelay=retryDelays_BASE_DELAY_MS){const rawDelay=Math.min(baseDelay*Math.pow(2,attempt),retryDelays_MAX_DELAY_MS),jitter=.75+.5*function(){const cryptoMaybe=globalThis.crypto;if(void 0!==cryptoMaybe&&"object"==typeof cryptoMaybe&&"function"==typeof cryptoMaybe.getRandomValues){const cryptoObj=cryptoMaybe,buf=new Uint32Array(1);cryptoObj.getRandomValues(buf);const value=(buf.at(0)??0)/4294967295;return Number.isFinite(value)?value:.5}const now=Date.now();return(4294967295&(now^now>>>3))%1e6/1e6}(),delay=Math.round(rawDelay*jitter);return new Promise(resolve=>setTimeout(resolve,delay))}function shouldRetry(error,attempt,maxRetries){if(attempt>=maxRetries)return!1;const errorName=error?.name;return!(error instanceof Error&&error.name===misc_ABORT_ERROR_NAME||errorName===misc_ABORT_ERROR_NAME)&&(!(error instanceof FetchError&&error.data?.name===misc_ABORT_ERROR_NAME)&&!(error instanceof FetchError&&void 0!==error.status&&error.status>=400&&error.status<500))}function createHeaders(headers={}){return new Headers(headers)}class BalancerHandler{static validateBalancerConfig(config){if(!config.endpoints?.length||!Array.isArray(config.endpoints))throw new FetchError(errorMessages_BALANCER_ENDPOINTS_REQUIRED,void 0,void 0,"");if(!config.strategy||!["fastest","parallel"].includes(config.strategy))throw new FetchError(errorMessages_BALANCER_STRATEGY_INVALID,void 0,void 0,"");for(const endpoint of config.endpoints)if(!isValidURL(endpoint))throw new FetchError(`Invalid balancer endpoint URL: ${endpoint}`,void 0,void 0,endpoint)}static async executeWithBalancer(url,config,executeRequest){return this.validateBalancerConfig(config),"fastest"===config.strategy?this.executeFastestStrategy(url,config,executeRequest):this.executeParallelStrategy(url,config,executeRequest)}static async executeFastestStrategy(url,config,executeRequest){const errors=[];for(const endpoint of config.endpoints){const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);if(result.success)return result.data;errors.push({endpoint:endpoint,error:result.error})}const lastError=errors[errors.length-1];if(lastError?.error instanceof FetchError)throw lastError.error;throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}static async executeParallelStrategy(url,config,executeRequest){const promises=config.endpoints.map(async endpoint=>{const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);return result.success?{success:!0,data:result.data,endpoint:endpoint}:{success:!1,error:result.error,endpoint:endpoint}}),results=await Promise.allSettled(promises).then(settledResults=>settledResults.map(result=>"fulfilled"===result.status?result.value:{success:!1,error:result.reason,endpoint:"unknown"})),successfulResults=results.filter(result=>result.success);if(0===successfulResults.length){const errors=results.filter(result=>!result.success).map(result=>({endpoint:result.endpoint,error:result.error}));throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}return successfulResults.map(result=>result.data)}static buildFullUrl(endpoint,url){const baseUrl=endpoint.endsWith("/")?endpoint.slice(0,-1):endpoint;if(!url||""===url.trim())return baseUrl;if(url.startsWith("http://")||url.startsWith("https://"))return url;return`${baseUrl}${url.startsWith("/")?url:`/${url}`}`}}async function handleDownload(response,config){if(void 0===config.filename||""===config.filename.trim())throw new Error(errorMessages_FILENAME_REQUIRED);void 0!==config.onProgress&&null!==response.body?await async function(response,config){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?parseInt(contentLength,10):0;if(total>0)await async function(response,config,total){if(null===response.body)throw new Error(errorMessages_RESPONSE_BODY_NULL);if(void 0===config.onProgress)throw new Error(errorMessages_PROGRESS_CALLBACK_UNDEFINED);if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);let received=0;const chunks=[],rateLimiter=new RateLimiter,reader=response.body.getReader(),processingChunkSize=128;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value)for(let i=0;i<value.length;i+=processingChunkSize){const chunk=value.slice(i,i+processingChunkSize);received=await processChunk(chunk,config,rateLimiter,chunks,received,total)}}const blob=function(chunks,received,response){let position=0;const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;return new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM})}(chunks,received,response);await saveBlob(blob,config.filename)}finally{reader.releaseLock()}}(response,config,total);else{if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);await handleDirectDownload(response,config.filename)}}(response,config):await handleDirectDownload(response,config.filename)}async function processChunk(chunk,config,rateLimiter,chunks,received,total){void 0!==config.maxRate&&config.maxRate>0&&await rateLimiter.throttle(chunk.length,config.maxRate),chunks.push(chunk);const newReceived=received+chunk.length,percentage=Math.round(newReceived/total*100);return config.onProgress?.(percentage),newReceived}async function handleDirectDownload(response,filename){const blob=await response.blob();await saveBlob(blob,filename)}async function saveBlob(blob,filename){if("undefined"!=typeof window&&"undefined"!=typeof document){const objectUrl=URL.createObjectURL(blob),link=document.createElement("a");link.href=objectUrl,link.download=filename;try{document.body.appendChild(link),link.click()}finally{document.body.contains(link)&&document.body.removeChild(link),URL.revokeObjectURL(objectUrl)}}else{const{writeFileSync:writeFileSync}=await import("node:fs"),buffer=await blob.arrayBuffer();writeFileSync(filename,Buffer.from(buffer))}}function processRequestBody(body,headers,maxRate,onProgress){if(function(body){return body instanceof FormData||body instanceof URLSearchParams||body instanceof Blob||body instanceof ArrayBuffer||body instanceof Uint8Array||"string"==typeof body}(body))return{body:body,needsDuplex:!1};if("object"==typeof body&&null!=body){headers["Content-Type"]??=contentTypes_APPLICATION_JSON;const serializedBody=function(body,headers){let result;const contentType=headers["Content-Type"];if(void 0===contentType||contentType===contentTypes_APPLICATION_JSON)result=JSON.stringify(body);else if(contentType.includes(contentTypes_APPLICATION_URL_ENCODED)){const params=function(body){const formData=new URLSearchParams;for(const[key,value]of Object.entries(body))if(null!=value){let stringValue;stringValue="object"==typeof value?JSON.stringify(value):"string"==typeof value?value:"number"==typeof value||"boolean"==typeof value?String(value):JSON.stringify(value),formData.append(key,stringValue)}return formData}(body);result=params.toString()}else result=(contentType.includes(contentTypes_TEXT_PLAIN),JSON.stringify(body));return result}(body,headers);return{body:serializedBody,needsDuplex:!1}}const stringBody=String(body);return headers["Content-Type"]??=contentTypes_TEXT_PLAIN,{body:stringBody,needsDuplex:!1}}class ErrorHandler{static normalizeExecuteError(error,fullUrl){const name=error?.name;return name===misc_ABORT_ERROR_NAME?{success:!1,error:new FetchError(errorMessages_ABORTED,void 0,error,fullUrl)}:{success:!1,error:error}}static async getErrorData(response){return async function(response){try{const contentType=response.headers.get(headers_CONTENT_TYPE);return null===contentType?null:isJsonContentType(contentType)?await response.json():await response.text()}catch{return null}}(response)}static createHttpError(response,fullUrl,errorData){const retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);return null!==retryAfter&&(err.retryAfter=retryAfter),err}}class ForwarderHandler{static validateForwarderConfig(config){if(!config.forwarders?.length||!Array.isArray(config.forwarders))throw new FetchError(errorMessages_FORWARDER_ENDPOINTS_REQUIRED,void 0,void 0,"");for(const forwarder of config.forwarders)if(!isValidURL(forwarder.url))throw new FetchError(`Invalid forwarder endpoint URL: ${forwarder.url}`,void 0,void 0,forwarder.url)}static forwardResponse(responseData,config,executeRequest){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(results=>{this.logForwarderResults(results)}).catch(error=>{}),Promise.resolve()}static forwardResponseWithCallback(responseData,config,executeRequest,onComplete){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(settledResults=>{const results=settledResults.map(result=>"fulfilled"===result.status?result.value:{endpoint:"unknown",success:!1,error:result.reason});onComplete?onComplete(results):this.logForwarderResults(settledResults)}),Promise.resolve()}static logForwarderResults(results){const successful=results.filter(result=>"fulfilled"===result.status&&result.value.success).length;results.length-successful>0&&results.forEach(result=>{("fulfilled"!==result.status||result.value.success)&&result.status})}static async executeForwarderWithRetries(forwarder,responseData,executeRequest){const{retries:retries=forwarderDefaults.RETRIES,timeout:timeout=forwarderDefaults.TIMEOUT_MS,url:url,method:method,headers:headers,body:forwarderBody}=forwarder;let body;body="function"==typeof forwarderBody?forwarderBody(responseData):void 0!==forwarderBody?forwarderBody:responseData;try{return{success:!0,data:await RetryHandler.executeWithRetries(url,{retries:retries,timeout:timeout,download:!1},()=>executeRequest(url,method,body,headers),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}}static createConfig(urls,method=forwarderDefaults.METHOD,headers,timeout,retries){return{forwarders:urls.map(url=>({method:method,url:url,...headers?{headers:headers}:{},...void 0!==timeout?{timeout:timeout}:{},...void 0!==retries?{retries:retries}:{}}))}}static createConfigFromForwarders(forwarders){return{forwarders:forwarders}}}class RetryHandler{static async executeWithRetries(fullUrl,config,executeRequest,honorRetryAfter){let lastError=null;for(let attempt=0;attempt<=config.retries;attempt++){const result=await executeRequest();if(result.success)return result.data;if(lastError=result.error,shouldRetry(result.error,attempt,config.retries)){if(await honorRetryAfter(result.error))continue;await waitForRetry(attempt);continue}break}if(lastError instanceof FetchError)throw lastError;throw new FetchError(lastError instanceof Error?lastError.message:errorMessages_UNKNOWN_ERROR,void 0,lastError,fullUrl)}}class StreamHandler{static createStreamIterator(response,url){const bodyStream=response.body;if(!bodyStream)throw new FetchError(errorMessages_RESPONSE_BODY_NULL,void 0,null,url);return{async*[Symbol.asyncIterator](){const reader=bodyStream.getReader();try{const contentType=response.headers.get(headers_CONTENT_TYPE),isJson=isJsonContentType(contentType),isText=isTextContentType(contentType);if(isJson)return void(yield*StreamHandler.iterateNdjson(reader,url));if(isText)return void(yield*StreamHandler.iterateText(reader));yield*StreamHandler.iterateBinary(reader)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}finally{reader.releaseLock()}}}}static async readDecodedChunk(reader,decoder){const{done:done,value:value}=await reader.read();return done?null:void 0===value?"":decoder.decode(value,{stream:!0})}static async*iterateText(reader){const decoder=new TextDecoder;for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value){const chunk=decoder.decode(value,{stream:!0});chunk.length>0&&(yield chunk)}}}static async*iterateNdjson(reader,url){let buffer="";const decoder=new TextDecoder;for(;;){const chunk=await StreamHandler.readDecodedChunk(reader,decoder);if(null===chunk)break;buffer+=chunk,yield*StreamHandler.yieldCompleteJsonLines(buffer,newBuffer=>{buffer=newBuffer},url)}const trimmed=buffer.trim();trimmed.length>0&&(yield*StreamHandler.safeParseJsonLine(trimmed,url))}static*yieldCompleteJsonLines(buffer,setBuffer,url){let newlineIndex;for(;-1!==(newlineIndex=buffer.indexOf(misc_NEWLINE));){const line=buffer.slice(0,newlineIndex).trim();setBuffer(buffer=buffer.slice(newlineIndex+misc_NEWLINE.length)),0!==line.length&&(yield*StreamHandler.safeParseJsonLine(line,url))}}static*safeParseJsonLine(line,url){try{yield JSON.parse(line)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}}static async*iterateBinary(reader){for(;;){const{done:done,value:value}=await reader.read();if(done)break;void 0!==value&&(yield value)}}}class FetchClient{static defaultConfig={timeout:defaults_TIMEOUT_MS,retries:defaults_RETRIES,headers:{"Content-Type":contentTypes_APPLICATION_JSON},baseURL:defaults_BASE_URL,stream:defaults_STREAM,download:defaults_DOWNLOAD,responseType:defaults_RESPONSE_TYPE};static async get(url,options={}){return this.request(httpMethods_GET,url,options)}static async post(url,body,options={}){return this.request(httpMethods_POST,url,this.createRequestOptions(options,body))}static async put(url,body,options={}){return this.request(httpMethods_PUT,url,this.createRequestOptions(options,body))}static async patch(url,body,options={}){return this.request(httpMethods_PATCH,url,this.createRequestOptions(options,body))}static async delete(url,options={}){return this.request(httpMethods_DELETE,url,options)}static async head(url,options={}){return this.request(httpMethods_HEAD,url,options)}static async options(url,options={}){return this.request(httpMethods_OPTIONS,url,options)}static async trace(url,options={}){return this.request(httpMethods_TRACE,url,options)}static createRequestOptions(options,body){return{...options,...void 0!==body?{body:body}:{}}}static async request(method,url,options={}){if("string"!=typeof url||""===url.trim())throw new FetchError(errorMessages_URL_INVALID,void 0,void 0,url);const config={...this.defaultConfig,...options};if(config.retries<0)throw new FetchError(errorMessages_RETRIES_NON_NEGATIVE,void 0,void 0,url);if(config.timeout<0)throw new FetchError(errorMessages_TIMEOUT_NON_NEGATIVE,void 0,void 0,url);if(config.download&&(void 0===config.filename||""===config.filename.trim()))throw new FetchError(errorMessages_FILENAME_REQUIRED,void 0,void 0,url);if(config.balancer){const response=await this.executeWithBalancer(method,url,config);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}const fullUrl=function(url,baseURL){if(url.startsWith(schemes_HTTP)||url.startsWith(schemes_HTTPS))return url;if(baseURL)return`${baseURL.endsWith("/")?baseURL.slice(0,-1):baseURL}${url.startsWith("/")?url:`/${url}`}`;return url}(url,config.baseURL),response=await RetryHandler.executeWithRetries(fullUrl,{retries:config.retries,timeout:config.timeout,download:config.download,...void 0!==config.filename?{filename:config.filename}:{}},()=>this.executeRequest(method,fullUrl,config),honorRetryAfterIfPresent);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}static async executeRequest(method,fullUrl,config){let controller;try{config.signal||(controller=function(timeout){const controller=new AbortController;if(timeout>0){const timeoutId=setTimeout(()=>{controller.abort()},timeout);controller.timeoutId=timeoutId}return controller}(config.timeout));const fetchOptions=function(method,config,controller){const headers={...config.headers};void 0!==config.body&&(config.body instanceof FormData?delete headers["Content-Type"]:config.body instanceof URLSearchParams&&(headers["Content-Type"]=contentTypes_APPLICATION_URL_ENCODED));const requestInit={method:method,headers:createHeaders(headers)},methodsWithBody=[httpMethods_POST,httpMethods_PUT,httpMethods_PATCH];if(void 0!==config.body&&methodsWithBody.includes(method)){const bodyResult=processRequestBody(config.body,config.headers,config.maxRate);requestInit.body=bodyResult.body,bodyResult.needsDuplex&&(requestInit.duplex="half")}return config.signal?requestInit.signal=config.signal:controller&&(requestInit.signal=controller.signal),requestInit}(method,config,controller),response=await globalThis.fetch(fullUrl,fetchOptions);if(controller&&cleanupController(controller),!response.ok){const errorData=await ErrorHandler.getErrorData(response),retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);throw null!==retryAfter&&(err.retryAfter=retryAfter),err}if(config.stream){return{success:!0,data:StreamHandler.createStreamIterator(response,fullUrl)}}if(config.download)return await this.handleDownloadResponse(response,config);if(method===httpMethods_HEAD||method===httpMethods_OPTIONS)return{success:!0,data:void 0};const parseConfig={responseType:config.responseType};void 0!==config.onProgress&&(parseConfig.onProgress=config.onProgress);return{success:!0,data:await parseResponseWithProgress(response,parseConfig,fullUrl,method)}}catch(error){return controller&&cleanupController(controller),ErrorHandler.normalizeExecuteError(error,fullUrl)}}static async handleDownloadResponse(response,config){const downloadConfig={};void 0!==config.filename&&(downloadConfig.filename=config.filename),void 0!==config.maxRate&&(downloadConfig.maxRate=config.maxRate),void 0!==config.onProgress&&(downloadConfig.onProgress=config.onProgress),await handleDownload(response,downloadConfig);const contentLength=response.headers.get(headers_CONTENT_LENGTH),contentType=response.headers.get(headers_CONTENT_TYPE);return{success:!0,data:{filename:config.filename??"download",size:null!==contentLength?parseInt(contentLength,10):0,type:contentType??"application/octet-stream",status:response.status,ok:response.ok}}}static async executeWithBalancer(method,url,config){const{balancer:balancer,...baseConfig}=config;return await BalancerHandler.executeWithBalancer(url,balancer,async endpoint=>{try{return{success:!0,data:await RetryHandler.executeWithRetries(endpoint,{retries:baseConfig.retries,timeout:baseConfig.timeout,download:baseConfig.download,...void 0!==baseConfig.filename?{filename:baseConfig.filename}:{}},()=>this.executeRequest(method,endpoint,baseConfig),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}})}static async forwardResponse(responseData,config){const{forwarder:forwarder,...baseConfig}=config,forwarderConfig=ForwarderHandler.createConfigFromForwarders(forwarder);await ForwarderHandler.forwardResponse(responseData,forwarderConfig,(endpoint,forwardMethod,body,headers)=>this.executeRequest(forwardMethod,endpoint,{...baseConfig,body:body,headers:headers??{}}))}}export{BalancerHandler,ForwarderHandler,createHeaders,FetchClient as default}; |
@@ -1,1 +0,1 @@ | ||
| class FetchError extends Error{status;data;url;constructor(e,t,r,n){super(e),this.name="FetchError",this.status=t,this.data=r,this.url=n}}const e="content-length",t="content-type",r="application/json",n="application/octet-stream",s="application/x-www-form-urlencoded",a="text/",o="text/plain",i="POST",c="PATCH",u="HEAD",l="OPTIONS",d="Filename is required when download is enabled",f="Filename is undefined",h="Response body is null",y="Failed to parse streaming response: ",w="Unknown error",m="AbortError",p={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function g(e){try{return await e.json()}catch(t){try{return await e.text()}catch(e){return}}}function b(e){return!0===e?.includes(r)}function v(e){return!0===e?.includes(a)}async function R(e,n,s){const o=e.headers.get(t);if(s!==u){if("auto"!==n.responseType)switch(n.responseType){case"json":return g(e);case"text":return await e.text();case"buffer":{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}case"blob":return await e.blob();default:return async function(e){const n=e.headers.get(t);if(null===n)try{return await e.text()}catch{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}if(n.includes(r))return await e.json();if(n.includes(a))return await e.text();const s=await e.arrayBuffer();return Object.assign(s,{length:s.byteLength})}(e)}return async function(e,t,r){if(r===u)return;if(b(t))return g(e);if(v(t))return await e.text();const n=await e.arrayBuffer();return Object.assign(n,{length:n.byteLength})}(e,o,s)}}async function T(r,s,a,o){return s.onProgress?async function(r,s,a,o){const i=r.headers.get(e),c=null!==i?(()=>{const e=parseInt(i,10);return Number.isNaN(e)?0:e})():0;if(null===r.body)return R(r,{responseType:"auto"},o);const u=r.body.getReader(),l=[];let d=0;try{for(;;){const{done:e,value:t}=await u.read();if(e)break;if(void 0!==t&&(l.push(t),d+=t.length),d>10485760)throw new FetchError("Response too large",void 0,void 0,a);if(c>0&&void 0!==s.onProgress){const e=Math.round(d/c*100);s.onProgress(e)}}if(0===l.length)return await R(r,{responseType:"auto"},o);const e=new ArrayBuffer(d),i=new Uint8Array(e);let f=0;for(const e of l)i.set(e,f),f+=e.length;const h=new Blob([e],{type:r.headers.get(t)??n}),y=new Response(h,{status:r.status,statusText:r.statusText,headers:r.headers});return await R(y,{responseType:"auto"},o)}finally{u.releaseLock()}}(r,{onProgress:s.onProgress},a,o):R(r,{responseType:s.responseType},o)}class x{static parseRetryAfterMs(e){const t=e.trim();if(""===t)return null;const r=+t;if(!Number.isNaN(r)&&r>=0)return Math.floor(1e3*r);try{const e=new Date(t);if(Number.isNaN(e.getTime()))return null;const r=new Date,n=e.getTime()-r.getTime();return n>0?n:0}catch{return null}}static async honorRetryAfterIfPresent(e){const t=e instanceof Error&&"retryAfter"in e?e.retryAfter:void 0;if(void 0===t)return!1;const r=this.parseRetryAfterMs(t);return null!==r&&(await new Promise(e=>setTimeout(e,r)),!0)}}async function P(e){return x.honorRetryAfterIfPresent(e)}class k{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(e,t){if(0>=t)return 0;const r=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(t/4,256),this.tokens=this.maxTokens,this.refillRate=t/1e3);const n=r*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+n),this.tokens>=e)return this.tokens-=e,this.transferredBytes+=e,0;const s=(e-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=e,Math.min(s,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(e,t){const r=this.calculateDelay(e,t);r>0&&await new Promise(e=>setTimeout(e,r))}}function E(e){if(!e||"string"!=typeof e)return!1;try{const t=new URL(e);if(!["http:","https:"].includes(t.protocol))return!1;if(!t.hostname||0===t.hostname.length)return!1;if(e.endsWith(":"))return!1;if(t.port){const e=parseInt(t.port,10);if(isNaN(e)||1>e||e>65535)return!1}return!0}catch{return!1}}function F(e){e.timeoutId&&clearTimeout(e.timeoutId)}function A(e,t=1e3){const r=Math.min(t*Math.pow(2,e),1e4),n=.75+.5*function(){const e=globalThis.crypto;if(void 0!==e&&"object"==typeof e&&"function"==typeof e.getRandomValues){const t=e,r=new Uint32Array(1);t.getRandomValues(r);const n=(r.at(0)??0)/4294967295;return Number.isFinite(n)?n:.5}const t=Date.now();return(4294967295&(t^t>>>3))%1e6/1e6}(),s=Math.round(r*n);return new Promise(e=>setTimeout(e,s))}function C(e,t,r){if(t>=r)return!1;const n=e?.name;return!(e instanceof Error&&e.name===m||n===m||e instanceof FetchError&&e.data?.name===m||e instanceof FetchError&&void 0!==e.status&&e.status>=400&&500>e.status)}function createHeaders(e={}){return new Headers(e)}class S{static validateBalancerConfig(e){if(!e.endpoints?.length||!Array.isArray(e.endpoints))throw new FetchError("Balancer endpoints are required and must be a non-empty array",void 0,void 0,"");if(!e.strategy||!["fastest","parallel"].includes(e.strategy))throw new FetchError('Balancer strategy must be either "fastest" or "parallel"',void 0,void 0,"");for(const t of e.endpoints)if(!E(t))throw new FetchError("Invalid balancer endpoint URL: "+t,void 0,void 0,t)}static async executeWithBalancer(e,t,r){return this.validateBalancerConfig(t),"fastest"===t.strategy?this.executeFastestStrategy(e,t,r):this.executeParallelStrategy(e,t,r)}static async executeFastestStrategy(e,t,r){const n=[];for(const s of t.endpoints){const t=this.buildFullUrl(s,e),a=await r(t);if(a.success)return a.data;n.push({endpoint:s,error:a.error})}const s=n[n.length-1];if(s?.error instanceof FetchError)throw s.error;throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,n,t.endpoints[0])}static async executeParallelStrategy(e,t,r){const n=t.endpoints.map(async t=>{const n=this.buildFullUrl(t,e),s=await r(n);return s.success?{success:!0,data:s.data,endpoint:t}:{success:!1,error:s.error,endpoint:t}}),s=await Promise.allSettled(n).then(e=>e.map(e=>"fulfilled"===e.status?e.value:{success:!1,error:e.reason,endpoint:"unknown"})),a=s.filter(e=>e.success);if(0===a.length){const e=s.filter(e=>!e.success).map(e=>({endpoint:e.endpoint,error:e.error}));throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,e,t.endpoints[0])}return a.map(e=>e.data)}static buildFullUrl(e,t){const r=e.endsWith("/")?e.slice(0,-1):e;return t&&""!==t.trim()?t.startsWith("http://")||t.startsWith("https://")?t:`${r}${t.startsWith("/")?t:"/"+t}`:r}}async function B(e,t,r,n,s,a){void 0!==t.maxRate&&t.maxRate>0&&await r.throttle(e.length,t.maxRate),n.push(e);const o=s+e.length,i=Math.round(o/a*100);return t.onProgress?.(i),o}async function L(e,t){const r=await e.blob();await D(r,t)}async function D(e,t){if("undefined"!=typeof window&&"undefined"!=typeof document){const r=URL.createObjectURL(e),n=document.createElement("a");n.href=r,n.download=t;try{document.body.appendChild(n),n.click()}finally{document.body.contains(n)&&document.body.removeChild(n),URL.revokeObjectURL(r)}}else{const{writeFileSync:r}=await import("node:fs"),n=await e.arrayBuffer();r(t,Buffer.from(n))}}class O{static normalizeExecuteError(e,t){const r=e?.name;return r===m?{success:!1,error:new FetchError("Request timeout - operation exceeded the specified timeout duration",void 0,e,t)}:{success:!1,error:e}}static async getErrorData(e){return async function(e){try{const r=e.headers.get(t);return null===r?null:b(r)?await e.json():await e.text()}catch{return null}}(e)}static createHttpError(e,t,r){const n=e.headers.get("Retry-After"),s=new FetchError(`HTTP ${e.status}: ${e.statusText}`,e.status,r,t);return null!==n&&(s.retryAfter=n),s}}class U{static validateForwarderConfig(e){if(!e.forwarders?.length||!Array.isArray(e.forwarders))throw new FetchError("Forwarder endpoints are required and must be a non-empty array",void 0,void 0,"");for(const t of e.forwarders)if(!E(t.url))throw new FetchError("Invalid forwarder endpoint URL: "+t.url,void 0,void 0,t.url)}static forwardResponse(e,t,r){this.validateForwarderConfig(t);const n=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(n).then(e=>{this.logForwarderResults(e)}).catch(e=>{}),Promise.resolve()}static forwardResponseWithCallback(e,t,r,n){this.validateForwarderConfig(t);const s=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(s).then(e=>{const t=e.map(e=>"fulfilled"===e.status?e.value:{endpoint:"unknown",success:!1,error:e.reason});n?n(t):this.logForwarderResults(e)}),Promise.resolve()}static logForwarderResults(e){const t=e.filter(e=>"fulfilled"===e.status&&e.value.success).length;e.length-t>0&&e.forEach(e=>{("fulfilled"!==e.status||e.value.success)&&e.status})}static async executeForwarderWithRetries(e,t,r){const{retries:n=p.RETRIES,timeout:s=p.TIMEOUT_MS,url:a,method:o,headers:i,body:c}=e;let u;u="function"==typeof c?c(t):void 0!==c?c:t;try{return{success:!0,data:await W.executeWithRetries(a,{retries:n,timeout:s,download:!1},()=>r(a,o,u,i),P)}}catch(e){return{success:!1,error:e}}}static createConfig(e,t=p.METHOD,r,n,s){return{forwarders:e.map(e=>({method:t,url:e,...r?{headers:r}:{},...void 0!==n?{timeout:n}:{},...void 0!==s?{retries:s}:{}}))}}static createConfigFromForwarders(e){return{forwarders:e}}}class W{static async executeWithRetries(e,t,r,n){let s=null;for(let e=0;e<=t.retries;e++){const a=await r();if(a.success)return a.data;if(s=a.error,!C(a.error,e,t.retries))break;await n(a.error)||await A(e)}if(s instanceof FetchError)throw s;throw new FetchError(s instanceof Error?s.message:w,void 0,s,e)}}class q{static createStreamIterator(e,r){const n=e.body;if(!n)throw new FetchError(h,void 0,null,r);return{async*[Symbol.asyncIterator](){const s=n.getReader();try{const n=e.headers.get(t),a=b(n),o=v(n);if(a)return void(yield*q.iterateNdjson(s,r));if(o)return void(yield*q.iterateText(s));yield*q.iterateBinary(s)}catch(e){throw new FetchError(`${y}${e instanceof Error?e.message:w}`,void 0,e,r)}finally{s.releaseLock()}}}}static async readDecodedChunk(e,t){const{done:r,value:n}=await e.read();return r?null:void 0===n?"":t.decode(n,{stream:!0})}static async*iterateText(e){const t=new TextDecoder;for(;;){const{done:r,value:n}=await e.read();if(r)break;if(void 0!==n){const e=t.decode(n,{stream:!0});e.length>0&&(yield e)}}}static async*iterateNdjson(e,t){let r="";const n=new TextDecoder;for(;;){const s=await q.readDecodedChunk(e,n);if(null===s)break;r+=s,yield*q.yieldCompleteJsonLines(r,e=>{r=e},t)}const s=r.trim();s.length>0&&(yield*q.safeParseJsonLine(s,t))}static*yieldCompleteJsonLines(e,t,r){let n;for(;-1!==(n=e.indexOf("\n"));){const s=e.slice(0,n).trim();t(e=e.slice(n+1)),0!==s.length&&(yield*q.safeParseJsonLine(s,r))}}static*safeParseJsonLine(e,t){try{yield JSON.parse(e)}catch(e){throw new FetchError(`${y}${e instanceof Error?e.message:w}`,void 0,e,t)}}static async*iterateBinary(e){for(;;){const{done:t,value:r}=await e.read();if(t)break;void 0!==r&&(yield r)}}}class N{static defaultConfig={timeout:3e4,retries:1,headers:{"Content-Type":r},baseURL:"",stream:!1,download:!1,responseType:"auto"};static async get(e,t={}){return this.request("GET",e,t)}static async post(e,t,r={}){return this.request(i,e,this.createRequestOptions(r,t))}static async put(e,t,r={}){return this.request("PUT",e,this.createRequestOptions(r,t))}static async patch(e,t,r={}){return this.request(c,e,this.createRequestOptions(r,t))}static async delete(e,t={}){return this.request("DELETE",e,t)}static async head(e,t={}){return this.request(u,e,t)}static async options(e,t={}){return this.request(l,e,t)}static createRequestOptions(e,t){return{...e,...void 0!==t?{body:t}:{}}}static async request(e,t,r={}){if("string"!=typeof t||""===t.trim())throw new FetchError("URL must be a non-empty string",void 0,void 0,t);const n={...this.defaultConfig,...r};if(0>n.retries)throw new FetchError("Retries must be a non-negative number",void 0,void 0,t);if(0>n.timeout)throw new FetchError("Timeout must be a non-negative number",void 0,void 0,t);if(n.download&&(void 0===n.filename||""===n.filename.trim()))throw new FetchError(d,void 0,void 0,t);if(n.balancer){const r=await this.executeWithBalancer(e,t,n);return n.forwarder&&n.forwarder.length>0&&(U.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(r,n)),r}const s=function(e,t){return e.startsWith("http://")||e.startsWith("https://")?e:t?`${t.endsWith("/")?t.slice(0,-1):t}${e.startsWith("/")?e:"/"+e}`:e}(t,n.baseURL),a=await W.executeWithRetries(s,{retries:n.retries,timeout:n.timeout,download:n.download,...void 0!==n.filename?{filename:n.filename}:{}},()=>this.executeRequest(e,s,n),P);return n.forwarder&&n.forwarder.length>0&&(U.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(a,n)),a}static async executeRequest(e,t,n){let a;try{n.signal||(a=function(e){const t=new AbortController;if(e>0){const r=setTimeout(()=>{t.abort()},e);t.timeoutId=r}return t}(n.timeout));const d=function(e,t,n){const a={...t.headers};void 0!==t.body&&(t.body instanceof FormData?delete a["Content-Type"]:t.body instanceof URLSearchParams&&(a["Content-Type"]=s));const u={method:e,headers:createHeaders(a)},l=[i,"PUT",c];if(void 0!==t.body&&l.includes(e)){const e=function(e,t){if(function(e){return e instanceof FormData||e instanceof URLSearchParams||e instanceof Blob||e instanceof ArrayBuffer||e instanceof Uint8Array||"string"==typeof e}(e))return{body:e,needsDuplex:!1};if("object"==typeof e&&null!=e){t["Content-Type"]??=r;const n=function(e,t){let n;const a=t["Content-Type"];if(void 0===a||a===r)n=JSON.stringify(e);else if(a.includes(s)){const t=function(e){const t=new URLSearchParams;for(const[r,n]of Object.entries(e))if(null!=n){let e;e="object"==typeof n?JSON.stringify(n):"string"==typeof n?n:"number"==typeof n||"boolean"==typeof n?n+"":JSON.stringify(n),t.append(r,e)}return t}(e);n=t.toString()}else a.includes(o),n=JSON.stringify(e);return n}(e,t);return{body:n,needsDuplex:!1}}const n=e+"";return t["Content-Type"]??=o,{body:n,needsDuplex:!1}}(t.body,t.headers,t.maxRate);u.body=e.body,e.needsDuplex&&(u.duplex="half")}return t.signal?u.signal=t.signal:n&&(u.signal=n.signal),u}(e,n,a),f=await globalThis.fetch(t,d);if(a&&F(a),!f.ok){const e=await O.getErrorData(f),r=f.headers.get("Retry-After"),n=new FetchError(`HTTP ${f.status}: ${f.statusText}`,f.status,e,t);throw null!==r&&(n.retryAfter=r),n}if(n.stream)return{success:!0,data:q.createStreamIterator(f,t)};if(n.download)return await this.handleDownloadResponse(f,n);if(e===u||e===l)return{success:!0,data:void 0};const h={responseType:n.responseType};return void 0!==n.onProgress&&(h.onProgress=n.onProgress),{success:!0,data:await T(f,h,t,e)}}catch(e){return a&&F(a),O.normalizeExecuteError(e,t)}}static async handleDownloadResponse(r,s){const a={};void 0!==s.filename&&(a.filename=s.filename),void 0!==s.maxRate&&(a.maxRate=s.maxRate),void 0!==s.onProgress&&(a.onProgress=s.onProgress),await async function(r,s){if(void 0===s.filename||""===s.filename.trim())throw Error(d);void 0!==s.onProgress&&null!==r.body?await async function(r,s){const a=r.headers.get(e),o=null!==a?parseInt(a,10):0;if(o>0)await async function(e,r,s){if(null===e.body)throw Error(h);if(void 0===r.onProgress)throw Error("Progress callback is undefined");if(void 0===r.filename)throw Error(f);let a=0;const o=[],i=new k,c=e.body.getReader();try{for(;;){const{done:e,value:t}=await c.read();if(e)break;if(void 0!==t)for(let e=0;e<t.length;e+=128){const n=t.slice(e,e+128);a=await B(n,r,i,o,a,s)}}const u=function(e,r,s){let a=0;const o=new ArrayBuffer(r),i=new Uint8Array(o);for(const t of e)i.set(t,a),a+=t.length;return new Blob([o],{type:s.headers.get(t)??n})}(o,a,e);await D(u,r.filename)}finally{c.releaseLock()}}(r,s,o);else{if(void 0===s.filename)throw Error(f);await L(r,s.filename)}}(r,s):await L(r,s.filename)}(r,a);const o=r.headers.get(e),i=r.headers.get(t);return{success:!0,data:{filename:s.filename??"download",size:null!==o?parseInt(o,10):0,type:i??"application/octet-stream",status:r.status,ok:r.ok}}}static async executeWithBalancer(e,t,r){const{balancer:n,...s}=r;return await S.executeWithBalancer(t,n,async t=>{try{return{success:!0,data:await W.executeWithRetries(t,{retries:s.retries,timeout:s.timeout,download:s.download,...void 0!==s.filename?{filename:s.filename}:{}},()=>this.executeRequest(e,t,s),P)}}catch(e){return{success:!1,error:e}}})}static async forwardResponse(e,t){const{forwarder:r,...n}=t,s=U.createConfigFromForwarders(r);await U.forwardResponse(e,s,(e,t,r,s)=>this.executeRequest(t,e,{...n,body:r,headers:s??{}}))}}export{S as BalancerHandler,U as ForwarderHandler,createHeaders,N as default}; | ||
| class FetchError extends Error{status;data;url;constructor(e,t,r,n){super(e),this.name="FetchError",this.status=t,this.data=r,this.url=n}}const e="content-length",t="content-type",r="application/json",n="application/octet-stream",s="application/x-www-form-urlencoded",a="text/",o="text/plain",i="POST",c="PATCH",u="HEAD",l="OPTIONS",d="Filename is required when download is enabled",f="Filename is undefined",h="Response body is null",y="Failed to parse streaming response: ",w="Unknown error",m="AbortError",p={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function g(e){try{return await e.json()}catch(t){try{return await e.text()}catch(e){return}}}function b(e){return!0===e?.includes(r)}function v(e){return!0===e?.includes(a)}async function R(e,n,s){const o=e.headers.get(t);if(s!==u){if("auto"!==n.responseType)switch(n.responseType){case"json":return g(e);case"text":return await e.text();case"buffer":{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}case"blob":return await e.blob();default:return async function(e){const n=e.headers.get(t);if(null===n)try{return await e.text()}catch{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}if(n.includes(r))return await e.json();if(n.includes(a))return await e.text();const s=await e.arrayBuffer();return Object.assign(s,{length:s.byteLength})}(e)}return async function(e,t,r){if(r===u)return;if(b(t))return g(e);if(v(t))return await e.text();const n=await e.arrayBuffer();return Object.assign(n,{length:n.byteLength})}(e,o,s)}}async function T(r,s,a,o){return s.onProgress?async function(r,s,a,o){const i=r.headers.get(e),c=null!==i?(()=>{const e=parseInt(i,10);return Number.isNaN(e)?0:e})():0;if(null===r.body)return R(r,{responseType:"auto"},o);const u=r.body.getReader(),l=[];let d=0;try{for(;;){const{done:e,value:t}=await u.read();if(e)break;if(void 0!==t&&(l.push(t),d+=t.length),d>10485760)throw new FetchError("Response too large",void 0,void 0,a);if(c>0&&void 0!==s.onProgress){const e=Math.round(d/c*100);s.onProgress(e)}}if(0===l.length)return await R(r,{responseType:"auto"},o);const e=new ArrayBuffer(d),i=new Uint8Array(e);let f=0;for(const e of l)i.set(e,f),f+=e.length;const h=new Blob([e],{type:r.headers.get(t)??n}),y=new Response(h,{status:r.status,statusText:r.statusText,headers:r.headers});return await R(y,{responseType:"auto"},o)}finally{u.releaseLock()}}(r,{onProgress:s.onProgress},a,o):R(r,{responseType:s.responseType},o)}class x{static parseRetryAfterMs(e){const t=e.trim();if(""===t)return null;const r=+t;if(!Number.isNaN(r)&&r>=0)return Math.floor(1e3*r);try{const e=new Date(t);if(Number.isNaN(e.getTime()))return null;const r=new Date,n=e.getTime()-r.getTime();return n>0?n:0}catch{return null}}static async honorRetryAfterIfPresent(e){const t=e instanceof Error&&"retryAfter"in e?e.retryAfter:void 0;if(void 0===t)return!1;const r=this.parseRetryAfterMs(t);return null!==r&&(await new Promise(e=>setTimeout(e,r)),!0)}}async function P(e){return x.honorRetryAfterIfPresent(e)}class k{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(e,t){if(0>=t)return 0;const r=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(t/4,256),this.tokens=this.maxTokens,this.refillRate=t/1e3);const n=r*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+n),this.tokens>=e)return this.tokens-=e,this.transferredBytes+=e,0;const s=(e-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=e,Math.min(s,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(e,t){const r=this.calculateDelay(e,t);r>0&&await new Promise(e=>setTimeout(e,r))}}function E(e){if(!e||"string"!=typeof e)return!1;try{const t=new URL(e);if(!["http:","https:"].includes(t.protocol))return!1;if(!t.hostname||0===t.hostname.length)return!1;if(e.endsWith(":"))return!1;if(t.port){const e=parseInt(t.port,10);if(isNaN(e)||1>e||e>65535)return!1}return!0}catch{return!1}}function F(e){e.timeoutId&&clearTimeout(e.timeoutId)}function A(e,t=1e3){const r=Math.min(t*Math.pow(2,e),1e4),n=.75+.5*function(){const e=globalThis.crypto;if(void 0!==e&&"object"==typeof e&&"function"==typeof e.getRandomValues){const t=e,r=new Uint32Array(1);t.getRandomValues(r);const n=(r.at(0)??0)/4294967295;return Number.isFinite(n)?n:.5}const t=Date.now();return(4294967295&(t^t>>>3))%1e6/1e6}(),s=Math.round(r*n);return new Promise(e=>setTimeout(e,s))}function C(e,t,r){if(t>=r)return!1;const n=e?.name;return!(e instanceof Error&&e.name===m||n===m||e instanceof FetchError&&e.data?.name===m||e instanceof FetchError&&void 0!==e.status&&e.status>=400&&500>e.status)}function createHeaders(e={}){return new Headers(e)}class S{static validateBalancerConfig(e){if(!e.endpoints?.length||!Array.isArray(e.endpoints))throw new FetchError("Balancer endpoints are required and must be a non-empty array",void 0,void 0,"");if(!e.strategy||!["fastest","parallel"].includes(e.strategy))throw new FetchError('Balancer strategy must be either "fastest" or "parallel"',void 0,void 0,"");for(const t of e.endpoints)if(!E(t))throw new FetchError("Invalid balancer endpoint URL: "+t,void 0,void 0,t)}static async executeWithBalancer(e,t,r){return this.validateBalancerConfig(t),"fastest"===t.strategy?this.executeFastestStrategy(e,t,r):this.executeParallelStrategy(e,t,r)}static async executeFastestStrategy(e,t,r){const n=[];for(const s of t.endpoints){const t=this.buildFullUrl(s,e),a=await r(t);if(a.success)return a.data;n.push({endpoint:s,error:a.error})}const s=n[n.length-1];if(s?.error instanceof FetchError)throw s.error;throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,n,t.endpoints[0])}static async executeParallelStrategy(e,t,r){const n=t.endpoints.map(async t=>{const n=this.buildFullUrl(t,e),s=await r(n);return s.success?{success:!0,data:s.data,endpoint:t}:{success:!1,error:s.error,endpoint:t}}),s=await Promise.allSettled(n).then(e=>e.map(e=>"fulfilled"===e.status?e.value:{success:!1,error:e.reason,endpoint:"unknown"})),a=s.filter(e=>e.success);if(0===a.length){const e=s.filter(e=>!e.success).map(e=>({endpoint:e.endpoint,error:e.error}));throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,e,t.endpoints[0])}return a.map(e=>e.data)}static buildFullUrl(e,t){const r=e.endsWith("/")?e.slice(0,-1):e;return t&&""!==t.trim()?t.startsWith("http://")||t.startsWith("https://")?t:`${r}${t.startsWith("/")?t:"/"+t}`:r}}async function B(e,t,r,n,s,a){void 0!==t.maxRate&&t.maxRate>0&&await r.throttle(e.length,t.maxRate),n.push(e);const o=s+e.length,i=Math.round(o/a*100);return t.onProgress?.(i),o}async function L(e,t){const r=await e.blob();await D(r,t)}async function D(e,t){if("undefined"!=typeof window&&"undefined"!=typeof document){const r=URL.createObjectURL(e),n=document.createElement("a");n.href=r,n.download=t;try{document.body.appendChild(n),n.click()}finally{document.body.contains(n)&&document.body.removeChild(n),URL.revokeObjectURL(r)}}else{const{writeFileSync:r}=await import("node:fs"),n=await e.arrayBuffer();r(t,Buffer.from(n))}}class O{static normalizeExecuteError(e,t){const r=e?.name;return r===m?{success:!1,error:new FetchError("Request timeout - operation exceeded the specified timeout duration",void 0,e,t)}:{success:!1,error:e}}static async getErrorData(e){return async function(e){try{const r=e.headers.get(t);return null===r?null:b(r)?await e.json():await e.text()}catch{return null}}(e)}static createHttpError(e,t,r){const n=e.headers.get("Retry-After"),s=new FetchError(`HTTP ${e.status}: ${e.statusText}`,e.status,r,t);return null!==n&&(s.retryAfter=n),s}}class U{static validateForwarderConfig(e){if(!e.forwarders?.length||!Array.isArray(e.forwarders))throw new FetchError("Forwarder endpoints are required and must be a non-empty array",void 0,void 0,"");for(const t of e.forwarders)if(!E(t.url))throw new FetchError("Invalid forwarder endpoint URL: "+t.url,void 0,void 0,t.url)}static forwardResponse(e,t,r){this.validateForwarderConfig(t);const n=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(n).then(e=>{this.logForwarderResults(e)}).catch(e=>{}),Promise.resolve()}static forwardResponseWithCallback(e,t,r,n){this.validateForwarderConfig(t);const s=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(s).then(e=>{const t=e.map(e=>"fulfilled"===e.status?e.value:{endpoint:"unknown",success:!1,error:e.reason});n?n(t):this.logForwarderResults(e)}),Promise.resolve()}static logForwarderResults(e){const t=e.filter(e=>"fulfilled"===e.status&&e.value.success).length;e.length-t>0&&e.forEach(e=>{("fulfilled"!==e.status||e.value.success)&&e.status})}static async executeForwarderWithRetries(e,t,r){const{retries:n=p.RETRIES,timeout:s=p.TIMEOUT_MS,url:a,method:o,headers:i,body:c}=e;let u;u="function"==typeof c?c(t):void 0!==c?c:t;try{return{success:!0,data:await q.executeWithRetries(a,{retries:n,timeout:s,download:!1},()=>r(a,o,u,i),P)}}catch(e){return{success:!1,error:e}}}static createConfig(e,t=p.METHOD,r,n,s){return{forwarders:e.map(e=>({method:t,url:e,...r?{headers:r}:{},...void 0!==n?{timeout:n}:{},...void 0!==s?{retries:s}:{}}))}}static createConfigFromForwarders(e){return{forwarders:e}}}class q{static async executeWithRetries(e,t,r,n){let s=null;for(let e=0;e<=t.retries;e++){const a=await r();if(a.success)return a.data;if(s=a.error,!C(a.error,e,t.retries))break;await n(a.error)||await A(e)}if(s instanceof FetchError)throw s;throw new FetchError(s instanceof Error?s.message:w,void 0,s,e)}}class W{static createStreamIterator(e,r){const n=e.body;if(!n)throw new FetchError(h,void 0,null,r);return{async*[Symbol.asyncIterator](){const s=n.getReader();try{const n=e.headers.get(t),a=b(n),o=v(n);if(a)return void(yield*W.iterateNdjson(s,r));if(o)return void(yield*W.iterateText(s));yield*W.iterateBinary(s)}catch(e){throw new FetchError(`${y}${e instanceof Error?e.message:w}`,void 0,e,r)}finally{s.releaseLock()}}}}static async readDecodedChunk(e,t){const{done:r,value:n}=await e.read();return r?null:void 0===n?"":t.decode(n,{stream:!0})}static async*iterateText(e){const t=new TextDecoder;for(;;){const{done:r,value:n}=await e.read();if(r)break;if(void 0!==n){const e=t.decode(n,{stream:!0});e.length>0&&(yield e)}}}static async*iterateNdjson(e,t){let r="";const n=new TextDecoder;for(;;){const s=await W.readDecodedChunk(e,n);if(null===s)break;r+=s,yield*W.yieldCompleteJsonLines(r,e=>{r=e},t)}const s=r.trim();s.length>0&&(yield*W.safeParseJsonLine(s,t))}static*yieldCompleteJsonLines(e,t,r){let n;for(;-1!==(n=e.indexOf("\n"));){const s=e.slice(0,n).trim();t(e=e.slice(n+1)),0!==s.length&&(yield*W.safeParseJsonLine(s,r))}}static*safeParseJsonLine(e,t){try{yield JSON.parse(e)}catch(e){throw new FetchError(`${y}${e instanceof Error?e.message:w}`,void 0,e,t)}}static async*iterateBinary(e){for(;;){const{done:t,value:r}=await e.read();if(t)break;void 0!==r&&(yield r)}}}class N{static defaultConfig={timeout:3e4,retries:1,headers:{"Content-Type":r},baseURL:"",stream:!1,download:!1,responseType:"auto"};static async get(e,t={}){return this.request("GET",e,t)}static async post(e,t,r={}){return this.request(i,e,this.createRequestOptions(r,t))}static async put(e,t,r={}){return this.request("PUT",e,this.createRequestOptions(r,t))}static async patch(e,t,r={}){return this.request(c,e,this.createRequestOptions(r,t))}static async delete(e,t={}){return this.request("DELETE",e,t)}static async head(e,t={}){return this.request(u,e,t)}static async options(e,t={}){return this.request(l,e,t)}static async trace(e,t={}){return this.request("TRACE",e,t)}static createRequestOptions(e,t){return{...e,...void 0!==t?{body:t}:{}}}static async request(e,t,r={}){if("string"!=typeof t||""===t.trim())throw new FetchError("URL must be a non-empty string",void 0,void 0,t);const n={...this.defaultConfig,...r};if(0>n.retries)throw new FetchError("Retries must be a non-negative number",void 0,void 0,t);if(0>n.timeout)throw new FetchError("Timeout must be a non-negative number",void 0,void 0,t);if(n.download&&(void 0===n.filename||""===n.filename.trim()))throw new FetchError(d,void 0,void 0,t);if(n.balancer){const r=await this.executeWithBalancer(e,t,n);return n.forwarder&&n.forwarder.length>0&&(U.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(r,n)),r}const s=function(e,t){return e.startsWith("http://")||e.startsWith("https://")?e:t?`${t.endsWith("/")?t.slice(0,-1):t}${e.startsWith("/")?e:"/"+e}`:e}(t,n.baseURL),a=await q.executeWithRetries(s,{retries:n.retries,timeout:n.timeout,download:n.download,...void 0!==n.filename?{filename:n.filename}:{}},()=>this.executeRequest(e,s,n),P);return n.forwarder&&n.forwarder.length>0&&(U.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(a,n)),a}static async executeRequest(e,t,n){let a;try{n.signal||(a=function(e){const t=new AbortController;if(e>0){const r=setTimeout(()=>{t.abort()},e);t.timeoutId=r}return t}(n.timeout));const d=function(e,t,n){const a={...t.headers};void 0!==t.body&&(t.body instanceof FormData?delete a["Content-Type"]:t.body instanceof URLSearchParams&&(a["Content-Type"]=s));const u={method:e,headers:createHeaders(a)},l=[i,"PUT",c];if(void 0!==t.body&&l.includes(e)){const e=function(e,t){if(function(e){return e instanceof FormData||e instanceof URLSearchParams||e instanceof Blob||e instanceof ArrayBuffer||e instanceof Uint8Array||"string"==typeof e}(e))return{body:e,needsDuplex:!1};if("object"==typeof e&&null!=e){t["Content-Type"]??=r;const n=function(e,t){let n;const a=t["Content-Type"];if(void 0===a||a===r)n=JSON.stringify(e);else if(a.includes(s)){const t=function(e){const t=new URLSearchParams;for(const[r,n]of Object.entries(e))if(null!=n){let e;e="object"==typeof n?JSON.stringify(n):"string"==typeof n?n:"number"==typeof n||"boolean"==typeof n?n+"":JSON.stringify(n),t.append(r,e)}return t}(e);n=t.toString()}else a.includes(o),n=JSON.stringify(e);return n}(e,t);return{body:n,needsDuplex:!1}}const n=e+"";return t["Content-Type"]??=o,{body:n,needsDuplex:!1}}(t.body,t.headers,t.maxRate);u.body=e.body,e.needsDuplex&&(u.duplex="half")}return t.signal?u.signal=t.signal:n&&(u.signal=n.signal),u}(e,n,a),f=await globalThis.fetch(t,d);if(a&&F(a),!f.ok){const e=await O.getErrorData(f),r=f.headers.get("Retry-After"),n=new FetchError(`HTTP ${f.status}: ${f.statusText}`,f.status,e,t);throw null!==r&&(n.retryAfter=r),n}if(n.stream)return{success:!0,data:W.createStreamIterator(f,t)};if(n.download)return await this.handleDownloadResponse(f,n);if(e===u||e===l)return{success:!0,data:void 0};const h={responseType:n.responseType};return void 0!==n.onProgress&&(h.onProgress=n.onProgress),{success:!0,data:await T(f,h,t,e)}}catch(e){return a&&F(a),O.normalizeExecuteError(e,t)}}static async handleDownloadResponse(r,s){const a={};void 0!==s.filename&&(a.filename=s.filename),void 0!==s.maxRate&&(a.maxRate=s.maxRate),void 0!==s.onProgress&&(a.onProgress=s.onProgress),await async function(r,s){if(void 0===s.filename||""===s.filename.trim())throw Error(d);void 0!==s.onProgress&&null!==r.body?await async function(r,s){const a=r.headers.get(e),o=null!==a?parseInt(a,10):0;if(o>0)await async function(e,r,s){if(null===e.body)throw Error(h);if(void 0===r.onProgress)throw Error("Progress callback is undefined");if(void 0===r.filename)throw Error(f);let a=0;const o=[],i=new k,c=e.body.getReader();try{for(;;){const{done:e,value:t}=await c.read();if(e)break;if(void 0!==t)for(let e=0;e<t.length;e+=128){const n=t.slice(e,e+128);a=await B(n,r,i,o,a,s)}}const u=function(e,r,s){let a=0;const o=new ArrayBuffer(r),i=new Uint8Array(o);for(const t of e)i.set(t,a),a+=t.length;return new Blob([o],{type:s.headers.get(t)??n})}(o,a,e);await D(u,r.filename)}finally{c.releaseLock()}}(r,s,o);else{if(void 0===s.filename)throw Error(f);await L(r,s.filename)}}(r,s):await L(r,s.filename)}(r,a);const o=r.headers.get(e),i=r.headers.get(t);return{success:!0,data:{filename:s.filename??"download",size:null!==o?parseInt(o,10):0,type:i??"application/octet-stream",status:r.status,ok:r.ok}}}static async executeWithBalancer(e,t,r){const{balancer:n,...s}=r;return await S.executeWithBalancer(t,n,async t=>{try{return{success:!0,data:await q.executeWithRetries(t,{retries:s.retries,timeout:s.timeout,download:s.download,...void 0!==s.filename?{filename:s.filename}:{}},()=>this.executeRequest(e,t,s),P)}}catch(e){return{success:!1,error:e}}})}static async forwardResponse(e,t){const{forwarder:r,...n}=t,s=U.createConfigFromForwarders(r);await U.forwardResponse(e,s,(e,t,r,s)=>this.executeRequest(t,e,{...n,body:r,headers:s??{}}))}}export{S as BalancerHandler,U as ForwarderHandler,createHeaders,N as default}; |
@@ -1,1 +0,1 @@ | ||
| !function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?factory(exports):"function"==typeof define&&define.amd?define(["exports"],factory):factory((global="undefined"!=typeof globalThis?globalThis:global||self).Fetch={})}(this,function(exports){"use strict";class FetchError extends Error{status;data;url;constructor(message,status,data,url){super(message),this.name="FetchError",this.status=status,this.data=data,this.url=url}}const headers_CONTENT_LENGTH="content-length",headers_CONTENT_TYPE="content-type",schemes_HTTP="http://",schemes_HTTPS="https://",contentTypes_APPLICATION_JSON="application/json",contentTypes_APPLICATION_OCTET_STREAM="application/octet-stream",contentTypes_APPLICATION_URL_ENCODED="application/x-www-form-urlencoded",contentTypes_TEXT_PREFIX="text/",contentTypes_TEXT_PLAIN="text/plain",httpMethods_GET="GET",httpMethods_POST="POST",httpMethods_PUT="PUT",httpMethods_PATCH="PATCH",httpMethods_DELETE="DELETE",httpMethods_HEAD="HEAD",httpMethods_OPTIONS="OPTIONS",errorMessages_ABORTED="Request timeout - operation exceeded the specified timeout duration",errorMessages_BALANCER_ENDPOINTS_REQUIRED="Balancer endpoints are required and must be a non-empty array",errorMessages_BALANCER_STRATEGY_INVALID='Balancer strategy must be either "fastest" or "parallel"',errorMessages_FILENAME_REQUIRED="Filename is required when download is enabled",errorMessages_FILENAME_UNDEFINED="Filename is undefined",errorMessages_FORWARDER_ENDPOINTS_REQUIRED="Forwarder endpoints are required and must be a non-empty array",errorMessages_PROGRESS_CALLBACK_UNDEFINED="Progress callback is undefined",errorMessages_RESPONSE_BODY_NULL="Response body is null",errorMessages_RESPONSE_TOO_LARGE="Response too large",errorMessages_RETRIES_NON_NEGATIVE="Retries must be a non-negative number",errorMessages_STREAM_PARSE_PREFIX="Failed to parse streaming response: ",errorMessages_TIMEOUT_NON_NEGATIVE="Timeout must be a non-negative number",errorMessages_UNKNOWN_ERROR="Unknown error",errorMessages_URL_INVALID="URL must be a non-empty string",defaults_BASE_URL="",defaults_DOWNLOAD=!1,defaults_RESPONSE_TYPE="auto",defaults_RETRIES=1,defaults_STREAM=!1,defaults_TIMEOUT_MS=3e4,misc_NEWLINE="\n",misc_ABORT_ERROR_NAME="AbortError",misc_MAX_NDJSON_BUFFER_BYTES=10485760,retryDelays_BASE_DELAY_MS=1e3,retryDelays_MAX_DELAY_MS=1e4,forwarderDefaults={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function parseJsonWithFallback(response){try{return await response.json()}catch(jsonError){try{return await response.text()}catch(textError){return}}}function isJsonContentType(contentType){return!0===contentType?.includes(contentTypes_APPLICATION_JSON)}function isTextContentType(contentType){return!0===contentType?.includes(contentTypes_TEXT_PREFIX)}async function parseResponseByType(response,config,method){const contentType=response.headers.get(headers_CONTENT_TYPE);if(method!==httpMethods_HEAD){if("auto"!==config.responseType)switch(config.responseType){case"json":return parseJsonWithFallback(response);case"text":return await response.text();case"buffer":{const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}case"blob":return await response.blob();default:return async function(response){const contentType=response.headers.get(headers_CONTENT_TYPE);if(null===contentType)try{return await response.text()}catch{const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}if(contentType.includes(contentTypes_APPLICATION_JSON))return await response.json();if(contentType.includes(contentTypes_TEXT_PREFIX))return await response.text();const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}(response)}return async function(response,contentType,method){if(method===httpMethods_HEAD)return;if(isJsonContentType(contentType))return parseJsonWithFallback(response);if(isTextContentType(contentType))return await response.text();const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}(response,contentType,method)}}async function parseResponseWithProgress(response,config,url,method){return config.onProgress?async function(response,config,url,method){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?(()=>{const parsed=parseInt(contentLength,10);return Number.isNaN(parsed)?0:parsed})():0;if(null===response.body)return parseResponseByType(response,{responseType:"auto"},method);const reader=response.body.getReader(),chunks=[];let received=0;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value&&(chunks.push(value),received+=value.length),received>misc_MAX_NDJSON_BUFFER_BYTES)throw new FetchError(errorMessages_RESPONSE_TOO_LARGE,void 0,void 0,url);if(total>0&&void 0!==config.onProgress){const percentage=Math.round(received/total*100);config.onProgress(percentage)}}if(0===chunks.length)return await parseResponseByType(response,{responseType:"auto"},method);const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);let position=0;for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;const blob=new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM}),newResponse=new Response(blob,{status:response.status,statusText:response.statusText,headers:response.headers});return await parseResponseByType(newResponse,{responseType:"auto"},method)}finally{reader.releaseLock()}}(response,{onProgress:config.onProgress},url,method):parseResponseByType(response,{responseType:config.responseType},method)}class RetryAfterParser{static parseRetryAfterMs(retryAfter){const trimmed=retryAfter.trim();if(""===trimmed)return null;const seconds=Number(trimmed);if(!Number.isNaN(seconds)&&seconds>=0)return Math.floor(1e3*seconds);try{const date=new Date(trimmed);if(Number.isNaN(date.getTime()))return null;const now=new Date,delayMs=date.getTime()-now.getTime();return delayMs>0?delayMs:0}catch{return null}}static async honorRetryAfterIfPresent(error){const retryAfterHeader=error instanceof Error&&"retryAfter"in error?error.retryAfter:void 0;if(void 0===retryAfterHeader)return!1;const delayMs=this.parseRetryAfterMs(retryAfterHeader);return null!==delayMs&&(await new Promise(resolve=>setTimeout(resolve,delayMs)),!0)}}async function honorRetryAfterIfPresent(error){return RetryAfterParser.honorRetryAfterIfPresent(error)}class RateLimiter{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(bytesToTransfer,maxRateBps){if(maxRateBps<=0)return 0;const elapsedMs=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(maxRateBps/4,256),this.tokens=this.maxTokens,this.refillRate=maxRateBps/1e3);const tokensToAdd=elapsedMs*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+tokensToAdd),this.tokens>=bytesToTransfer)return this.tokens-=bytesToTransfer,this.transferredBytes+=bytesToTransfer,0;const delayMs=(bytesToTransfer-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=bytesToTransfer,Math.min(delayMs,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(bytesToTransfer,maxRateBps){const delayMs=this.calculateDelay(bytesToTransfer,maxRateBps);delayMs>0&&await new Promise(resolve=>setTimeout(resolve,delayMs))}}function isValidURL(url){if(!url||"string"!=typeof url)return!1;try{const parsedUrl=new URL(url);if(!["http:","https:"].includes(parsedUrl.protocol))return!1;if(!parsedUrl.hostname||0===parsedUrl.hostname.length)return!1;if(url.endsWith(":"))return!1;if(parsedUrl.port){const port=parseInt(parsedUrl.port,10);if(isNaN(port)||port<1||port>65535)return!1}return!0}catch{return!1}}function cleanupController(controller){controller.timeoutId&&clearTimeout(controller.timeoutId)}function waitForRetry(attempt,baseDelay=retryDelays_BASE_DELAY_MS){const rawDelay=Math.min(baseDelay*Math.pow(2,attempt),retryDelays_MAX_DELAY_MS),jitter=.75+.5*function(){const cryptoMaybe=globalThis.crypto;if(void 0!==cryptoMaybe&&"object"==typeof cryptoMaybe&&"function"==typeof cryptoMaybe.getRandomValues){const cryptoObj=cryptoMaybe,buf=new Uint32Array(1);cryptoObj.getRandomValues(buf);const value=(buf.at(0)??0)/4294967295;return Number.isFinite(value)?value:.5}const now=Date.now();return(4294967295&(now^now>>>3))%1e6/1e6}(),delay=Math.round(rawDelay*jitter);return new Promise(resolve=>setTimeout(resolve,delay))}function shouldRetry(error,attempt,maxRetries){if(attempt>=maxRetries)return!1;const errorName=error?.name;return!(error instanceof Error&&error.name===misc_ABORT_ERROR_NAME||errorName===misc_ABORT_ERROR_NAME)&&(!(error instanceof FetchError&&error.data?.name===misc_ABORT_ERROR_NAME)&&!(error instanceof FetchError&&void 0!==error.status&&error.status>=400&&error.status<500))}function createHeaders(headers={}){return new Headers(headers)}class BalancerHandler{static validateBalancerConfig(config){if(!config.endpoints?.length||!Array.isArray(config.endpoints))throw new FetchError(errorMessages_BALANCER_ENDPOINTS_REQUIRED,void 0,void 0,"");if(!config.strategy||!["fastest","parallel"].includes(config.strategy))throw new FetchError(errorMessages_BALANCER_STRATEGY_INVALID,void 0,void 0,"");for(const endpoint of config.endpoints)if(!isValidURL(endpoint))throw new FetchError(`Invalid balancer endpoint URL: ${endpoint}`,void 0,void 0,endpoint)}static async executeWithBalancer(url,config,executeRequest){return this.validateBalancerConfig(config),"fastest"===config.strategy?this.executeFastestStrategy(url,config,executeRequest):this.executeParallelStrategy(url,config,executeRequest)}static async executeFastestStrategy(url,config,executeRequest){const errors=[];for(const endpoint of config.endpoints){const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);if(result.success)return result.data;errors.push({endpoint:endpoint,error:result.error})}const lastError=errors[errors.length-1];if(lastError?.error instanceof FetchError)throw lastError.error;throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}static async executeParallelStrategy(url,config,executeRequest){const promises=config.endpoints.map(async endpoint=>{const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);return result.success?{success:!0,data:result.data,endpoint:endpoint}:{success:!1,error:result.error,endpoint:endpoint}}),results=await Promise.allSettled(promises).then(settledResults=>settledResults.map(result=>"fulfilled"===result.status?result.value:{success:!1,error:result.reason,endpoint:"unknown"})),successfulResults=results.filter(result=>result.success);if(0===successfulResults.length){const errors=results.filter(result=>!result.success).map(result=>({endpoint:result.endpoint,error:result.error}));throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}return successfulResults.map(result=>result.data)}static buildFullUrl(endpoint,url){const baseUrl=endpoint.endsWith("/")?endpoint.slice(0,-1):endpoint;if(!url||""===url.trim())return baseUrl;if(url.startsWith("http://")||url.startsWith("https://"))return url;return`${baseUrl}${url.startsWith("/")?url:`/${url}`}`}}async function handleDownload(response,config){if(void 0===config.filename||""===config.filename.trim())throw new Error(errorMessages_FILENAME_REQUIRED);void 0!==config.onProgress&&null!==response.body?await async function(response,config){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?parseInt(contentLength,10):0;if(total>0)await async function(response,config,total){if(null===response.body)throw new Error(errorMessages_RESPONSE_BODY_NULL);if(void 0===config.onProgress)throw new Error(errorMessages_PROGRESS_CALLBACK_UNDEFINED);if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);let received=0;const chunks=[],rateLimiter=new RateLimiter,reader=response.body.getReader(),processingChunkSize=128;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value)for(let i=0;i<value.length;i+=processingChunkSize){const chunk=value.slice(i,i+processingChunkSize);received=await processChunk(chunk,config,rateLimiter,chunks,received,total)}}const blob=function(chunks,received,response){let position=0;const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;return new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM})}(chunks,received,response);await saveBlob(blob,config.filename)}finally{reader.releaseLock()}}(response,config,total);else{if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);await handleDirectDownload(response,config.filename)}}(response,config):await handleDirectDownload(response,config.filename)}async function processChunk(chunk,config,rateLimiter,chunks,received,total){void 0!==config.maxRate&&config.maxRate>0&&await rateLimiter.throttle(chunk.length,config.maxRate),chunks.push(chunk);const newReceived=received+chunk.length,percentage=Math.round(newReceived/total*100);return config.onProgress?.(percentage),newReceived}async function handleDirectDownload(response,filename){const blob=await response.blob();await saveBlob(blob,filename)}async function saveBlob(blob,filename){if("undefined"!=typeof window&&"undefined"!=typeof document){const objectUrl=URL.createObjectURL(blob),link=document.createElement("a");link.href=objectUrl,link.download=filename;try{document.body.appendChild(link),link.click()}finally{document.body.contains(link)&&document.body.removeChild(link),URL.revokeObjectURL(objectUrl)}}else{const{writeFileSync:writeFileSync}=await import("node:fs"),buffer=await blob.arrayBuffer();writeFileSync(filename,Buffer.from(buffer))}}function processRequestBody(body,headers,maxRate,onProgress){if(function(body){return body instanceof FormData||body instanceof URLSearchParams||body instanceof Blob||body instanceof ArrayBuffer||body instanceof Uint8Array||"string"==typeof body}(body))return{body:body,needsDuplex:!1};if("object"==typeof body&&null!=body){headers["Content-Type"]??=contentTypes_APPLICATION_JSON;const serializedBody=function(body,headers){let result;const contentType=headers["Content-Type"];if(void 0===contentType||contentType===contentTypes_APPLICATION_JSON)result=JSON.stringify(body);else if(contentType.includes(contentTypes_APPLICATION_URL_ENCODED)){const params=function(body){const formData=new URLSearchParams;for(const[key,value]of Object.entries(body))if(null!=value){let stringValue;stringValue="object"==typeof value?JSON.stringify(value):"string"==typeof value?value:"number"==typeof value||"boolean"==typeof value?String(value):JSON.stringify(value),formData.append(key,stringValue)}return formData}(body);result=params.toString()}else result=(contentType.includes(contentTypes_TEXT_PLAIN),JSON.stringify(body));return result}(body,headers);return{body:serializedBody,needsDuplex:!1}}const stringBody=String(body);return headers["Content-Type"]??=contentTypes_TEXT_PLAIN,{body:stringBody,needsDuplex:!1}}class ErrorHandler{static normalizeExecuteError(error,fullUrl){const name=error?.name;return name===misc_ABORT_ERROR_NAME?{success:!1,error:new FetchError(errorMessages_ABORTED,void 0,error,fullUrl)}:{success:!1,error:error}}static async getErrorData(response){return async function(response){try{const contentType=response.headers.get(headers_CONTENT_TYPE);return null===contentType?null:isJsonContentType(contentType)?await response.json():await response.text()}catch{return null}}(response)}static createHttpError(response,fullUrl,errorData){const retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);return null!==retryAfter&&(err.retryAfter=retryAfter),err}}class ForwarderHandler{static validateForwarderConfig(config){if(!config.forwarders?.length||!Array.isArray(config.forwarders))throw new FetchError(errorMessages_FORWARDER_ENDPOINTS_REQUIRED,void 0,void 0,"");for(const forwarder of config.forwarders)if(!isValidURL(forwarder.url))throw new FetchError(`Invalid forwarder endpoint URL: ${forwarder.url}`,void 0,void 0,forwarder.url)}static forwardResponse(responseData,config,executeRequest){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(results=>{this.logForwarderResults(results)}).catch(error=>{}),Promise.resolve()}static forwardResponseWithCallback(responseData,config,executeRequest,onComplete){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(settledResults=>{const results=settledResults.map(result=>"fulfilled"===result.status?result.value:{endpoint:"unknown",success:!1,error:result.reason});onComplete?onComplete(results):this.logForwarderResults(settledResults)}),Promise.resolve()}static logForwarderResults(results){const successful=results.filter(result=>"fulfilled"===result.status&&result.value.success).length;results.length-successful>0&&results.forEach(result=>{("fulfilled"!==result.status||result.value.success)&&result.status})}static async executeForwarderWithRetries(forwarder,responseData,executeRequest){const{retries:retries=forwarderDefaults.RETRIES,timeout:timeout=forwarderDefaults.TIMEOUT_MS,url:url,method:method,headers:headers,body:forwarderBody}=forwarder;let body;body="function"==typeof forwarderBody?forwarderBody(responseData):void 0!==forwarderBody?forwarderBody:responseData;try{return{success:!0,data:await RetryHandler.executeWithRetries(url,{retries:retries,timeout:timeout,download:!1},()=>executeRequest(url,method,body,headers),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}}static createConfig(urls,method=forwarderDefaults.METHOD,headers,timeout,retries){return{forwarders:urls.map(url=>({method:method,url:url,...headers?{headers:headers}:{},...void 0!==timeout?{timeout:timeout}:{},...void 0!==retries?{retries:retries}:{}}))}}static createConfigFromForwarders(forwarders){return{forwarders:forwarders}}}class RetryHandler{static async executeWithRetries(fullUrl,config,executeRequest,honorRetryAfter){let lastError=null;for(let attempt=0;attempt<=config.retries;attempt++){const result=await executeRequest();if(result.success)return result.data;if(lastError=result.error,shouldRetry(result.error,attempt,config.retries)){if(await honorRetryAfter(result.error))continue;await waitForRetry(attempt);continue}break}if(lastError instanceof FetchError)throw lastError;throw new FetchError(lastError instanceof Error?lastError.message:errorMessages_UNKNOWN_ERROR,void 0,lastError,fullUrl)}}class StreamHandler{static createStreamIterator(response,url){const bodyStream=response.body;if(!bodyStream)throw new FetchError(errorMessages_RESPONSE_BODY_NULL,void 0,null,url);return{async*[Symbol.asyncIterator](){const reader=bodyStream.getReader();try{const contentType=response.headers.get(headers_CONTENT_TYPE),isJson=isJsonContentType(contentType),isText=isTextContentType(contentType);if(isJson)return void(yield*StreamHandler.iterateNdjson(reader,url));if(isText)return void(yield*StreamHandler.iterateText(reader));yield*StreamHandler.iterateBinary(reader)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}finally{reader.releaseLock()}}}}static async readDecodedChunk(reader,decoder){const{done:done,value:value}=await reader.read();return done?null:void 0===value?"":decoder.decode(value,{stream:!0})}static async*iterateText(reader){const decoder=new TextDecoder;for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value){const chunk=decoder.decode(value,{stream:!0});chunk.length>0&&(yield chunk)}}}static async*iterateNdjson(reader,url){let buffer="";const decoder=new TextDecoder;for(;;){const chunk=await StreamHandler.readDecodedChunk(reader,decoder);if(null===chunk)break;buffer+=chunk,yield*StreamHandler.yieldCompleteJsonLines(buffer,newBuffer=>{buffer=newBuffer},url)}const trimmed=buffer.trim();trimmed.length>0&&(yield*StreamHandler.safeParseJsonLine(trimmed,url))}static*yieldCompleteJsonLines(buffer,setBuffer,url){let newlineIndex;for(;-1!==(newlineIndex=buffer.indexOf(misc_NEWLINE));){const line=buffer.slice(0,newlineIndex).trim();setBuffer(buffer=buffer.slice(newlineIndex+misc_NEWLINE.length)),0!==line.length&&(yield*StreamHandler.safeParseJsonLine(line,url))}}static*safeParseJsonLine(line,url){try{yield JSON.parse(line)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}}static async*iterateBinary(reader){for(;;){const{done:done,value:value}=await reader.read();if(done)break;void 0!==value&&(yield value)}}}exports.BalancerHandler=BalancerHandler,exports.ForwarderHandler=ForwarderHandler,exports.createHeaders=createHeaders,exports.default=class{static defaultConfig={timeout:defaults_TIMEOUT_MS,retries:defaults_RETRIES,headers:{"Content-Type":contentTypes_APPLICATION_JSON},baseURL:defaults_BASE_URL,stream:defaults_STREAM,download:defaults_DOWNLOAD,responseType:defaults_RESPONSE_TYPE};static async get(url,options={}){return this.request(httpMethods_GET,url,options)}static async post(url,body,options={}){return this.request(httpMethods_POST,url,this.createRequestOptions(options,body))}static async put(url,body,options={}){return this.request(httpMethods_PUT,url,this.createRequestOptions(options,body))}static async patch(url,body,options={}){return this.request(httpMethods_PATCH,url,this.createRequestOptions(options,body))}static async delete(url,options={}){return this.request(httpMethods_DELETE,url,options)}static async head(url,options={}){return this.request(httpMethods_HEAD,url,options)}static async options(url,options={}){return this.request(httpMethods_OPTIONS,url,options)}static createRequestOptions(options,body){return{...options,...void 0!==body?{body:body}:{}}}static async request(method,url,options={}){if("string"!=typeof url||""===url.trim())throw new FetchError(errorMessages_URL_INVALID,void 0,void 0,url);const config={...this.defaultConfig,...options};if(config.retries<0)throw new FetchError(errorMessages_RETRIES_NON_NEGATIVE,void 0,void 0,url);if(config.timeout<0)throw new FetchError(errorMessages_TIMEOUT_NON_NEGATIVE,void 0,void 0,url);if(config.download&&(void 0===config.filename||""===config.filename.trim()))throw new FetchError(errorMessages_FILENAME_REQUIRED,void 0,void 0,url);if(config.balancer){const response=await this.executeWithBalancer(method,url,config);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}const fullUrl=function(url,baseURL){if(url.startsWith(schemes_HTTP)||url.startsWith(schemes_HTTPS))return url;if(baseURL)return`${baseURL.endsWith("/")?baseURL.slice(0,-1):baseURL}${url.startsWith("/")?url:`/${url}`}`;return url}(url,config.baseURL),response=await RetryHandler.executeWithRetries(fullUrl,{retries:config.retries,timeout:config.timeout,download:config.download,...void 0!==config.filename?{filename:config.filename}:{}},()=>this.executeRequest(method,fullUrl,config),honorRetryAfterIfPresent);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}static async executeRequest(method,fullUrl,config){let controller;try{config.signal||(controller=function(timeout){const controller=new AbortController;if(timeout>0){const timeoutId=setTimeout(()=>{controller.abort()},timeout);controller.timeoutId=timeoutId}return controller}(config.timeout));const fetchOptions=function(method,config,controller){const headers={...config.headers};void 0!==config.body&&(config.body instanceof FormData?delete headers["Content-Type"]:config.body instanceof URLSearchParams&&(headers["Content-Type"]=contentTypes_APPLICATION_URL_ENCODED));const requestInit={method:method,headers:createHeaders(headers)},methodsWithBody=[httpMethods_POST,httpMethods_PUT,httpMethods_PATCH];if(void 0!==config.body&&methodsWithBody.includes(method)){const bodyResult=processRequestBody(config.body,config.headers,config.maxRate);requestInit.body=bodyResult.body,bodyResult.needsDuplex&&(requestInit.duplex="half")}return config.signal?requestInit.signal=config.signal:controller&&(requestInit.signal=controller.signal),requestInit}(method,config,controller),response=await globalThis.fetch(fullUrl,fetchOptions);if(controller&&cleanupController(controller),!response.ok){const errorData=await ErrorHandler.getErrorData(response),retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);throw null!==retryAfter&&(err.retryAfter=retryAfter),err}if(config.stream){return{success:!0,data:StreamHandler.createStreamIterator(response,fullUrl)}}if(config.download)return await this.handleDownloadResponse(response,config);if(method===httpMethods_HEAD||method===httpMethods_OPTIONS)return{success:!0,data:void 0};const parseConfig={responseType:config.responseType};void 0!==config.onProgress&&(parseConfig.onProgress=config.onProgress);return{success:!0,data:await parseResponseWithProgress(response,parseConfig,fullUrl,method)}}catch(error){return controller&&cleanupController(controller),ErrorHandler.normalizeExecuteError(error,fullUrl)}}static async handleDownloadResponse(response,config){const downloadConfig={};void 0!==config.filename&&(downloadConfig.filename=config.filename),void 0!==config.maxRate&&(downloadConfig.maxRate=config.maxRate),void 0!==config.onProgress&&(downloadConfig.onProgress=config.onProgress),await handleDownload(response,downloadConfig);const contentLength=response.headers.get(headers_CONTENT_LENGTH),contentType=response.headers.get(headers_CONTENT_TYPE);return{success:!0,data:{filename:config.filename??"download",size:null!==contentLength?parseInt(contentLength,10):0,type:contentType??"application/octet-stream",status:response.status,ok:response.ok}}}static async executeWithBalancer(method,url,config){const{balancer:balancer,...baseConfig}=config;return await BalancerHandler.executeWithBalancer(url,balancer,async endpoint=>{try{return{success:!0,data:await RetryHandler.executeWithRetries(endpoint,{retries:baseConfig.retries,timeout:baseConfig.timeout,download:baseConfig.download,...void 0!==baseConfig.filename?{filename:baseConfig.filename}:{}},()=>this.executeRequest(method,endpoint,baseConfig),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}})}static async forwardResponse(responseData,config){const{forwarder:forwarder,...baseConfig}=config,forwarderConfig=ForwarderHandler.createConfigFromForwarders(forwarder);await ForwarderHandler.forwardResponse(responseData,forwarderConfig,(endpoint,forwardMethod,body,headers)=>this.executeRequest(forwardMethod,endpoint,{...baseConfig,body:body,headers:headers??{}}))}},Object.defineProperty(exports,"__esModule",{value:!0})}); | ||
| !function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?factory(exports):"function"==typeof define&&define.amd?define(["exports"],factory):factory((global="undefined"!=typeof globalThis?globalThis:global||self).Fetch={})}(this,function(exports){"use strict";class FetchError extends Error{status;data;url;constructor(message,status,data,url){super(message),this.name="FetchError",this.status=status,this.data=data,this.url=url}}const headers_CONTENT_LENGTH="content-length",headers_CONTENT_TYPE="content-type",schemes_HTTP="http://",schemes_HTTPS="https://",contentTypes_APPLICATION_JSON="application/json",contentTypes_APPLICATION_OCTET_STREAM="application/octet-stream",contentTypes_APPLICATION_URL_ENCODED="application/x-www-form-urlencoded",contentTypes_TEXT_PREFIX="text/",contentTypes_TEXT_PLAIN="text/plain",httpMethods_GET="GET",httpMethods_POST="POST",httpMethods_PUT="PUT",httpMethods_PATCH="PATCH",httpMethods_DELETE="DELETE",httpMethods_HEAD="HEAD",httpMethods_OPTIONS="OPTIONS",httpMethods_TRACE="TRACE",errorMessages_ABORTED="Request timeout - operation exceeded the specified timeout duration",errorMessages_BALANCER_ENDPOINTS_REQUIRED="Balancer endpoints are required and must be a non-empty array",errorMessages_BALANCER_STRATEGY_INVALID='Balancer strategy must be either "fastest" or "parallel"',errorMessages_FILENAME_REQUIRED="Filename is required when download is enabled",errorMessages_FILENAME_UNDEFINED="Filename is undefined",errorMessages_FORWARDER_ENDPOINTS_REQUIRED="Forwarder endpoints are required and must be a non-empty array",errorMessages_PROGRESS_CALLBACK_UNDEFINED="Progress callback is undefined",errorMessages_RESPONSE_BODY_NULL="Response body is null",errorMessages_RESPONSE_TOO_LARGE="Response too large",errorMessages_RETRIES_NON_NEGATIVE="Retries must be a non-negative number",errorMessages_STREAM_PARSE_PREFIX="Failed to parse streaming response: ",errorMessages_TIMEOUT_NON_NEGATIVE="Timeout must be a non-negative number",errorMessages_UNKNOWN_ERROR="Unknown error",errorMessages_URL_INVALID="URL must be a non-empty string",defaults_BASE_URL="",defaults_DOWNLOAD=!1,defaults_RESPONSE_TYPE="auto",defaults_RETRIES=1,defaults_STREAM=!1,defaults_TIMEOUT_MS=3e4,misc_NEWLINE="\n",misc_ABORT_ERROR_NAME="AbortError",misc_MAX_NDJSON_BUFFER_BYTES=10485760,retryDelays_BASE_DELAY_MS=1e3,retryDelays_MAX_DELAY_MS=1e4,forwarderDefaults={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function parseJsonWithFallback(response){try{return await response.json()}catch(jsonError){try{return await response.text()}catch(textError){return}}}function isJsonContentType(contentType){return!0===contentType?.includes(contentTypes_APPLICATION_JSON)}function isTextContentType(contentType){return!0===contentType?.includes(contentTypes_TEXT_PREFIX)}async function parseResponseByType(response,config,method){const contentType=response.headers.get(headers_CONTENT_TYPE);if(method!==httpMethods_HEAD){if("auto"!==config.responseType)switch(config.responseType){case"json":return parseJsonWithFallback(response);case"text":return await response.text();case"buffer":{const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}case"blob":return await response.blob();default:return async function(response){const contentType=response.headers.get(headers_CONTENT_TYPE);if(null===contentType)try{return await response.text()}catch{const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}if(contentType.includes(contentTypes_APPLICATION_JSON))return await response.json();if(contentType.includes(contentTypes_TEXT_PREFIX))return await response.text();const arrayBuffer=await response.arrayBuffer();return Object.assign(arrayBuffer,{length:arrayBuffer.byteLength})}(response)}return async function(response,contentType,method){if(method===httpMethods_HEAD)return;if(isJsonContentType(contentType))return parseJsonWithFallback(response);if(isTextContentType(contentType))return await response.text();const buffer=await response.arrayBuffer();return Object.assign(buffer,{length:buffer.byteLength})}(response,contentType,method)}}async function parseResponseWithProgress(response,config,url,method){return config.onProgress?async function(response,config,url,method){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?(()=>{const parsed=parseInt(contentLength,10);return Number.isNaN(parsed)?0:parsed})():0;if(null===response.body)return parseResponseByType(response,{responseType:"auto"},method);const reader=response.body.getReader(),chunks=[];let received=0;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value&&(chunks.push(value),received+=value.length),received>misc_MAX_NDJSON_BUFFER_BYTES)throw new FetchError(errorMessages_RESPONSE_TOO_LARGE,void 0,void 0,url);if(total>0&&void 0!==config.onProgress){const percentage=Math.round(received/total*100);config.onProgress(percentage)}}if(0===chunks.length)return await parseResponseByType(response,{responseType:"auto"},method);const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);let position=0;for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;const blob=new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM}),newResponse=new Response(blob,{status:response.status,statusText:response.statusText,headers:response.headers});return await parseResponseByType(newResponse,{responseType:"auto"},method)}finally{reader.releaseLock()}}(response,{onProgress:config.onProgress},url,method):parseResponseByType(response,{responseType:config.responseType},method)}class RetryAfterParser{static parseRetryAfterMs(retryAfter){const trimmed=retryAfter.trim();if(""===trimmed)return null;const seconds=Number(trimmed);if(!Number.isNaN(seconds)&&seconds>=0)return Math.floor(1e3*seconds);try{const date=new Date(trimmed);if(Number.isNaN(date.getTime()))return null;const now=new Date,delayMs=date.getTime()-now.getTime();return delayMs>0?delayMs:0}catch{return null}}static async honorRetryAfterIfPresent(error){const retryAfterHeader=error instanceof Error&&"retryAfter"in error?error.retryAfter:void 0;if(void 0===retryAfterHeader)return!1;const delayMs=this.parseRetryAfterMs(retryAfterHeader);return null!==delayMs&&(await new Promise(resolve=>setTimeout(resolve,delayMs)),!0)}}async function honorRetryAfterIfPresent(error){return RetryAfterParser.honorRetryAfterIfPresent(error)}class RateLimiter{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(bytesToTransfer,maxRateBps){if(maxRateBps<=0)return 0;const elapsedMs=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(maxRateBps/4,256),this.tokens=this.maxTokens,this.refillRate=maxRateBps/1e3);const tokensToAdd=elapsedMs*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+tokensToAdd),this.tokens>=bytesToTransfer)return this.tokens-=bytesToTransfer,this.transferredBytes+=bytesToTransfer,0;const delayMs=(bytesToTransfer-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=bytesToTransfer,Math.min(delayMs,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(bytesToTransfer,maxRateBps){const delayMs=this.calculateDelay(bytesToTransfer,maxRateBps);delayMs>0&&await new Promise(resolve=>setTimeout(resolve,delayMs))}}function isValidURL(url){if(!url||"string"!=typeof url)return!1;try{const parsedUrl=new URL(url);if(!["http:","https:"].includes(parsedUrl.protocol))return!1;if(!parsedUrl.hostname||0===parsedUrl.hostname.length)return!1;if(url.endsWith(":"))return!1;if(parsedUrl.port){const port=parseInt(parsedUrl.port,10);if(isNaN(port)||port<1||port>65535)return!1}return!0}catch{return!1}}function cleanupController(controller){controller.timeoutId&&clearTimeout(controller.timeoutId)}function waitForRetry(attempt,baseDelay=retryDelays_BASE_DELAY_MS){const rawDelay=Math.min(baseDelay*Math.pow(2,attempt),retryDelays_MAX_DELAY_MS),jitter=.75+.5*function(){const cryptoMaybe=globalThis.crypto;if(void 0!==cryptoMaybe&&"object"==typeof cryptoMaybe&&"function"==typeof cryptoMaybe.getRandomValues){const cryptoObj=cryptoMaybe,buf=new Uint32Array(1);cryptoObj.getRandomValues(buf);const value=(buf.at(0)??0)/4294967295;return Number.isFinite(value)?value:.5}const now=Date.now();return(4294967295&(now^now>>>3))%1e6/1e6}(),delay=Math.round(rawDelay*jitter);return new Promise(resolve=>setTimeout(resolve,delay))}function shouldRetry(error,attempt,maxRetries){if(attempt>=maxRetries)return!1;const errorName=error?.name;return!(error instanceof Error&&error.name===misc_ABORT_ERROR_NAME||errorName===misc_ABORT_ERROR_NAME)&&(!(error instanceof FetchError&&error.data?.name===misc_ABORT_ERROR_NAME)&&!(error instanceof FetchError&&void 0!==error.status&&error.status>=400&&error.status<500))}function createHeaders(headers={}){return new Headers(headers)}class BalancerHandler{static validateBalancerConfig(config){if(!config.endpoints?.length||!Array.isArray(config.endpoints))throw new FetchError(errorMessages_BALANCER_ENDPOINTS_REQUIRED,void 0,void 0,"");if(!config.strategy||!["fastest","parallel"].includes(config.strategy))throw new FetchError(errorMessages_BALANCER_STRATEGY_INVALID,void 0,void 0,"");for(const endpoint of config.endpoints)if(!isValidURL(endpoint))throw new FetchError(`Invalid balancer endpoint URL: ${endpoint}`,void 0,void 0,endpoint)}static async executeWithBalancer(url,config,executeRequest){return this.validateBalancerConfig(config),"fastest"===config.strategy?this.executeFastestStrategy(url,config,executeRequest):this.executeParallelStrategy(url,config,executeRequest)}static async executeFastestStrategy(url,config,executeRequest){const errors=[];for(const endpoint of config.endpoints){const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);if(result.success)return result.data;errors.push({endpoint:endpoint,error:result.error})}const lastError=errors[errors.length-1];if(lastError?.error instanceof FetchError)throw lastError.error;throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}static async executeParallelStrategy(url,config,executeRequest){const promises=config.endpoints.map(async endpoint=>{const fullUrl=this.buildFullUrl(endpoint,url),result=await executeRequest(fullUrl);return result.success?{success:!0,data:result.data,endpoint:endpoint}:{success:!1,error:result.error,endpoint:endpoint}}),results=await Promise.allSettled(promises).then(settledResults=>settledResults.map(result=>"fulfilled"===result.status?result.value:{success:!1,error:result.reason,endpoint:"unknown"})),successfulResults=results.filter(result=>result.success);if(0===successfulResults.length){const errors=results.filter(result=>!result.success).map(result=>({endpoint:result.endpoint,error:result.error}));throw new FetchError(`All ${config.endpoints.length} balancer endpoints failed`,void 0,errors,config.endpoints[0])}return successfulResults.map(result=>result.data)}static buildFullUrl(endpoint,url){const baseUrl=endpoint.endsWith("/")?endpoint.slice(0,-1):endpoint;if(!url||""===url.trim())return baseUrl;if(url.startsWith("http://")||url.startsWith("https://"))return url;return`${baseUrl}${url.startsWith("/")?url:`/${url}`}`}}async function handleDownload(response,config){if(void 0===config.filename||""===config.filename.trim())throw new Error(errorMessages_FILENAME_REQUIRED);void 0!==config.onProgress&&null!==response.body?await async function(response,config){const contentLength=response.headers.get(headers_CONTENT_LENGTH),total=null!==contentLength?parseInt(contentLength,10):0;if(total>0)await async function(response,config,total){if(null===response.body)throw new Error(errorMessages_RESPONSE_BODY_NULL);if(void 0===config.onProgress)throw new Error(errorMessages_PROGRESS_CALLBACK_UNDEFINED);if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);let received=0;const chunks=[],rateLimiter=new RateLimiter,reader=response.body.getReader(),processingChunkSize=128;try{for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value)for(let i=0;i<value.length;i+=processingChunkSize){const chunk=value.slice(i,i+processingChunkSize);received=await processChunk(chunk,config,rateLimiter,chunks,received,total)}}const blob=function(chunks,received,response){let position=0;const buffer=new ArrayBuffer(received),allChunks=new Uint8Array(buffer);for(const chunk of chunks)allChunks.set(chunk,position),position+=chunk.length;return new Blob([buffer],{type:response.headers.get(headers_CONTENT_TYPE)??contentTypes_APPLICATION_OCTET_STREAM})}(chunks,received,response);await saveBlob(blob,config.filename)}finally{reader.releaseLock()}}(response,config,total);else{if(void 0===config.filename)throw new Error(errorMessages_FILENAME_UNDEFINED);await handleDirectDownload(response,config.filename)}}(response,config):await handleDirectDownload(response,config.filename)}async function processChunk(chunk,config,rateLimiter,chunks,received,total){void 0!==config.maxRate&&config.maxRate>0&&await rateLimiter.throttle(chunk.length,config.maxRate),chunks.push(chunk);const newReceived=received+chunk.length,percentage=Math.round(newReceived/total*100);return config.onProgress?.(percentage),newReceived}async function handleDirectDownload(response,filename){const blob=await response.blob();await saveBlob(blob,filename)}async function saveBlob(blob,filename){if("undefined"!=typeof window&&"undefined"!=typeof document){const objectUrl=URL.createObjectURL(blob),link=document.createElement("a");link.href=objectUrl,link.download=filename;try{document.body.appendChild(link),link.click()}finally{document.body.contains(link)&&document.body.removeChild(link),URL.revokeObjectURL(objectUrl)}}else{const{writeFileSync:writeFileSync}=await import("node:fs"),buffer=await blob.arrayBuffer();writeFileSync(filename,Buffer.from(buffer))}}function processRequestBody(body,headers,maxRate,onProgress){if(function(body){return body instanceof FormData||body instanceof URLSearchParams||body instanceof Blob||body instanceof ArrayBuffer||body instanceof Uint8Array||"string"==typeof body}(body))return{body:body,needsDuplex:!1};if("object"==typeof body&&null!=body){headers["Content-Type"]??=contentTypes_APPLICATION_JSON;const serializedBody=function(body,headers){let result;const contentType=headers["Content-Type"];if(void 0===contentType||contentType===contentTypes_APPLICATION_JSON)result=JSON.stringify(body);else if(contentType.includes(contentTypes_APPLICATION_URL_ENCODED)){const params=function(body){const formData=new URLSearchParams;for(const[key,value]of Object.entries(body))if(null!=value){let stringValue;stringValue="object"==typeof value?JSON.stringify(value):"string"==typeof value?value:"number"==typeof value||"boolean"==typeof value?String(value):JSON.stringify(value),formData.append(key,stringValue)}return formData}(body);result=params.toString()}else result=(contentType.includes(contentTypes_TEXT_PLAIN),JSON.stringify(body));return result}(body,headers);return{body:serializedBody,needsDuplex:!1}}const stringBody=String(body);return headers["Content-Type"]??=contentTypes_TEXT_PLAIN,{body:stringBody,needsDuplex:!1}}class ErrorHandler{static normalizeExecuteError(error,fullUrl){const name=error?.name;return name===misc_ABORT_ERROR_NAME?{success:!1,error:new FetchError(errorMessages_ABORTED,void 0,error,fullUrl)}:{success:!1,error:error}}static async getErrorData(response){return async function(response){try{const contentType=response.headers.get(headers_CONTENT_TYPE);return null===contentType?null:isJsonContentType(contentType)?await response.json():await response.text()}catch{return null}}(response)}static createHttpError(response,fullUrl,errorData){const retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);return null!==retryAfter&&(err.retryAfter=retryAfter),err}}class ForwarderHandler{static validateForwarderConfig(config){if(!config.forwarders?.length||!Array.isArray(config.forwarders))throw new FetchError(errorMessages_FORWARDER_ENDPOINTS_REQUIRED,void 0,void 0,"");for(const forwarder of config.forwarders)if(!isValidURL(forwarder.url))throw new FetchError(`Invalid forwarder endpoint URL: ${forwarder.url}`,void 0,void 0,forwarder.url)}static forwardResponse(responseData,config,executeRequest){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(results=>{this.logForwarderResults(results)}).catch(error=>{}),Promise.resolve()}static forwardResponseWithCallback(responseData,config,executeRequest,onComplete){this.validateForwarderConfig(config);const forwardPromises=config.forwarders.map(async forwarder=>{try{const result=await this.executeForwarderWithRetries(forwarder,responseData,executeRequest);return{endpoint:forwarder.url,success:result.success,error:result.success?void 0:result.error}}catch(error){return{endpoint:forwarder.url,success:!1,error:error}}});return Promise.allSettled(forwardPromises).then(settledResults=>{const results=settledResults.map(result=>"fulfilled"===result.status?result.value:{endpoint:"unknown",success:!1,error:result.reason});onComplete?onComplete(results):this.logForwarderResults(settledResults)}),Promise.resolve()}static logForwarderResults(results){const successful=results.filter(result=>"fulfilled"===result.status&&result.value.success).length;results.length-successful>0&&results.forEach(result=>{("fulfilled"!==result.status||result.value.success)&&result.status})}static async executeForwarderWithRetries(forwarder,responseData,executeRequest){const{retries:retries=forwarderDefaults.RETRIES,timeout:timeout=forwarderDefaults.TIMEOUT_MS,url:url,method:method,headers:headers,body:forwarderBody}=forwarder;let body;body="function"==typeof forwarderBody?forwarderBody(responseData):void 0!==forwarderBody?forwarderBody:responseData;try{return{success:!0,data:await RetryHandler.executeWithRetries(url,{retries:retries,timeout:timeout,download:!1},()=>executeRequest(url,method,body,headers),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}}static createConfig(urls,method=forwarderDefaults.METHOD,headers,timeout,retries){return{forwarders:urls.map(url=>({method:method,url:url,...headers?{headers:headers}:{},...void 0!==timeout?{timeout:timeout}:{},...void 0!==retries?{retries:retries}:{}}))}}static createConfigFromForwarders(forwarders){return{forwarders:forwarders}}}class RetryHandler{static async executeWithRetries(fullUrl,config,executeRequest,honorRetryAfter){let lastError=null;for(let attempt=0;attempt<=config.retries;attempt++){const result=await executeRequest();if(result.success)return result.data;if(lastError=result.error,shouldRetry(result.error,attempt,config.retries)){if(await honorRetryAfter(result.error))continue;await waitForRetry(attempt);continue}break}if(lastError instanceof FetchError)throw lastError;throw new FetchError(lastError instanceof Error?lastError.message:errorMessages_UNKNOWN_ERROR,void 0,lastError,fullUrl)}}class StreamHandler{static createStreamIterator(response,url){const bodyStream=response.body;if(!bodyStream)throw new FetchError(errorMessages_RESPONSE_BODY_NULL,void 0,null,url);return{async*[Symbol.asyncIterator](){const reader=bodyStream.getReader();try{const contentType=response.headers.get(headers_CONTENT_TYPE),isJson=isJsonContentType(contentType),isText=isTextContentType(contentType);if(isJson)return void(yield*StreamHandler.iterateNdjson(reader,url));if(isText)return void(yield*StreamHandler.iterateText(reader));yield*StreamHandler.iterateBinary(reader)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}finally{reader.releaseLock()}}}}static async readDecodedChunk(reader,decoder){const{done:done,value:value}=await reader.read();return done?null:void 0===value?"":decoder.decode(value,{stream:!0})}static async*iterateText(reader){const decoder=new TextDecoder;for(;;){const{done:done,value:value}=await reader.read();if(done)break;if(void 0!==value){const chunk=decoder.decode(value,{stream:!0});chunk.length>0&&(yield chunk)}}}static async*iterateNdjson(reader,url){let buffer="";const decoder=new TextDecoder;for(;;){const chunk=await StreamHandler.readDecodedChunk(reader,decoder);if(null===chunk)break;buffer+=chunk,yield*StreamHandler.yieldCompleteJsonLines(buffer,newBuffer=>{buffer=newBuffer},url)}const trimmed=buffer.trim();trimmed.length>0&&(yield*StreamHandler.safeParseJsonLine(trimmed,url))}static*yieldCompleteJsonLines(buffer,setBuffer,url){let newlineIndex;for(;-1!==(newlineIndex=buffer.indexOf(misc_NEWLINE));){const line=buffer.slice(0,newlineIndex).trim();setBuffer(buffer=buffer.slice(newlineIndex+misc_NEWLINE.length)),0!==line.length&&(yield*StreamHandler.safeParseJsonLine(line,url))}}static*safeParseJsonLine(line,url){try{yield JSON.parse(line)}catch(error){throw new FetchError(`${errorMessages_STREAM_PARSE_PREFIX}${error instanceof Error?error.message:errorMessages_UNKNOWN_ERROR}`,void 0,error,url)}}static async*iterateBinary(reader){for(;;){const{done:done,value:value}=await reader.read();if(done)break;void 0!==value&&(yield value)}}}exports.BalancerHandler=BalancerHandler,exports.ForwarderHandler=ForwarderHandler,exports.createHeaders=createHeaders,exports.default=class{static defaultConfig={timeout:defaults_TIMEOUT_MS,retries:defaults_RETRIES,headers:{"Content-Type":contentTypes_APPLICATION_JSON},baseURL:defaults_BASE_URL,stream:defaults_STREAM,download:defaults_DOWNLOAD,responseType:defaults_RESPONSE_TYPE};static async get(url,options={}){return this.request(httpMethods_GET,url,options)}static async post(url,body,options={}){return this.request(httpMethods_POST,url,this.createRequestOptions(options,body))}static async put(url,body,options={}){return this.request(httpMethods_PUT,url,this.createRequestOptions(options,body))}static async patch(url,body,options={}){return this.request(httpMethods_PATCH,url,this.createRequestOptions(options,body))}static async delete(url,options={}){return this.request(httpMethods_DELETE,url,options)}static async head(url,options={}){return this.request(httpMethods_HEAD,url,options)}static async options(url,options={}){return this.request(httpMethods_OPTIONS,url,options)}static async trace(url,options={}){return this.request(httpMethods_TRACE,url,options)}static createRequestOptions(options,body){return{...options,...void 0!==body?{body:body}:{}}}static async request(method,url,options={}){if("string"!=typeof url||""===url.trim())throw new FetchError(errorMessages_URL_INVALID,void 0,void 0,url);const config={...this.defaultConfig,...options};if(config.retries<0)throw new FetchError(errorMessages_RETRIES_NON_NEGATIVE,void 0,void 0,url);if(config.timeout<0)throw new FetchError(errorMessages_TIMEOUT_NON_NEGATIVE,void 0,void 0,url);if(config.download&&(void 0===config.filename||""===config.filename.trim()))throw new FetchError(errorMessages_FILENAME_REQUIRED,void 0,void 0,url);if(config.balancer){const response=await this.executeWithBalancer(method,url,config);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}const fullUrl=function(url,baseURL){if(url.startsWith(schemes_HTTP)||url.startsWith(schemes_HTTPS))return url;if(baseURL)return`${baseURL.endsWith("/")?baseURL.slice(0,-1):baseURL}${url.startsWith("/")?url:`/${url}`}`;return url}(url,config.baseURL),response=await RetryHandler.executeWithRetries(fullUrl,{retries:config.retries,timeout:config.timeout,download:config.download,...void 0!==config.filename?{filename:config.filename}:{}},()=>this.executeRequest(method,fullUrl,config),honorRetryAfterIfPresent);return config.forwarder&&config.forwarder.length>0&&(ForwarderHandler.validateForwarderConfig({forwarders:config.forwarder}),await this.forwardResponse(response,config)),response}static async executeRequest(method,fullUrl,config){let controller;try{config.signal||(controller=function(timeout){const controller=new AbortController;if(timeout>0){const timeoutId=setTimeout(()=>{controller.abort()},timeout);controller.timeoutId=timeoutId}return controller}(config.timeout));const fetchOptions=function(method,config,controller){const headers={...config.headers};void 0!==config.body&&(config.body instanceof FormData?delete headers["Content-Type"]:config.body instanceof URLSearchParams&&(headers["Content-Type"]=contentTypes_APPLICATION_URL_ENCODED));const requestInit={method:method,headers:createHeaders(headers)},methodsWithBody=[httpMethods_POST,httpMethods_PUT,httpMethods_PATCH];if(void 0!==config.body&&methodsWithBody.includes(method)){const bodyResult=processRequestBody(config.body,config.headers,config.maxRate);requestInit.body=bodyResult.body,bodyResult.needsDuplex&&(requestInit.duplex="half")}return config.signal?requestInit.signal=config.signal:controller&&(requestInit.signal=controller.signal),requestInit}(method,config,controller),response=await globalThis.fetch(fullUrl,fetchOptions);if(controller&&cleanupController(controller),!response.ok){const errorData=await ErrorHandler.getErrorData(response),retryAfter=response.headers.get("Retry-After"),err=new FetchError(`HTTP ${response.status}: ${response.statusText}`,response.status,errorData,fullUrl);throw null!==retryAfter&&(err.retryAfter=retryAfter),err}if(config.stream){return{success:!0,data:StreamHandler.createStreamIterator(response,fullUrl)}}if(config.download)return await this.handleDownloadResponse(response,config);if(method===httpMethods_HEAD||method===httpMethods_OPTIONS)return{success:!0,data:void 0};const parseConfig={responseType:config.responseType};void 0!==config.onProgress&&(parseConfig.onProgress=config.onProgress);return{success:!0,data:await parseResponseWithProgress(response,parseConfig,fullUrl,method)}}catch(error){return controller&&cleanupController(controller),ErrorHandler.normalizeExecuteError(error,fullUrl)}}static async handleDownloadResponse(response,config){const downloadConfig={};void 0!==config.filename&&(downloadConfig.filename=config.filename),void 0!==config.maxRate&&(downloadConfig.maxRate=config.maxRate),void 0!==config.onProgress&&(downloadConfig.onProgress=config.onProgress),await handleDownload(response,downloadConfig);const contentLength=response.headers.get(headers_CONTENT_LENGTH),contentType=response.headers.get(headers_CONTENT_TYPE);return{success:!0,data:{filename:config.filename??"download",size:null!==contentLength?parseInt(contentLength,10):0,type:contentType??"application/octet-stream",status:response.status,ok:response.ok}}}static async executeWithBalancer(method,url,config){const{balancer:balancer,...baseConfig}=config;return await BalancerHandler.executeWithBalancer(url,balancer,async endpoint=>{try{return{success:!0,data:await RetryHandler.executeWithRetries(endpoint,{retries:baseConfig.retries,timeout:baseConfig.timeout,download:baseConfig.download,...void 0!==baseConfig.filename?{filename:baseConfig.filename}:{}},()=>this.executeRequest(method,endpoint,baseConfig),honorRetryAfterIfPresent)}}catch(error){return{success:!1,error:error}}})}static async forwardResponse(responseData,config){const{forwarder:forwarder,...baseConfig}=config,forwarderConfig=ForwarderHandler.createConfigFromForwarders(forwarder);await ForwarderHandler.forwardResponse(responseData,forwarderConfig,(endpoint,forwardMethod,body,headers)=>this.executeRequest(forwardMethod,endpoint,{...baseConfig,body:body,headers:headers??{}}))}},Object.defineProperty(exports,"__esModule",{value:!0})}); |
@@ -1,1 +0,1 @@ | ||
| !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Fetch={})}(this,function(e){"use strict";class FetchError extends Error{status;data;url;constructor(e,t,r,n){super(e),this.name="FetchError",this.status=t,this.data=r,this.url=n}}const t="content-length",r="content-type",n="application/json",s="application/octet-stream",a="application/x-www-form-urlencoded",o="text/",i="text/plain",c="POST",u="PATCH",l="HEAD",d="OPTIONS",f="Filename is required when download is enabled",h="Filename is undefined",y="Response body is null",w="Failed to parse streaming response: ",p="Unknown error",m="AbortError",g={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function b(e){try{return await e.json()}catch(t){try{return await e.text()}catch(e){return}}}function v(e){return!0===e?.includes(n)}function R(e){return!0===e?.includes(o)}async function T(e,t,s){const a=e.headers.get(r);if(s!==l){if("auto"!==t.responseType)switch(t.responseType){case"json":return b(e);case"text":return await e.text();case"buffer":{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}case"blob":return await e.blob();default:return async function(e){const t=e.headers.get(r);if(null===t)try{return await e.text()}catch{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}if(t.includes(n))return await e.json();if(t.includes(o))return await e.text();const s=await e.arrayBuffer();return Object.assign(s,{length:s.byteLength})}(e)}return async function(e,t,r){if(r===l)return;if(v(t))return b(e);if(R(t))return await e.text();const n=await e.arrayBuffer();return Object.assign(n,{length:n.byteLength})}(e,a,s)}}async function x(e,n,a,o){return n.onProgress?async function(e,n,a,o){const i=e.headers.get(t),c=null!==i?(()=>{const e=parseInt(i,10);return Number.isNaN(e)?0:e})():0;if(null===e.body)return T(e,{responseType:"auto"},o);const u=e.body.getReader(),l=[];let d=0;try{for(;;){const{done:e,value:t}=await u.read();if(e)break;if(void 0!==t&&(l.push(t),d+=t.length),d>10485760)throw new FetchError("Response too large",void 0,void 0,a);if(c>0&&void 0!==n.onProgress){const e=Math.round(d/c*100);n.onProgress(e)}}if(0===l.length)return await T(e,{responseType:"auto"},o);const t=new ArrayBuffer(d),i=new Uint8Array(t);let f=0;for(const e of l)i.set(e,f),f+=e.length;const h=new Blob([t],{type:e.headers.get(r)??s}),y=new Response(h,{status:e.status,statusText:e.statusText,headers:e.headers});return await T(y,{responseType:"auto"},o)}finally{u.releaseLock()}}(e,{onProgress:n.onProgress},a,o):T(e,{responseType:n.responseType},o)}class P{static parseRetryAfterMs(e){const t=e.trim();if(""===t)return null;const r=+t;if(!Number.isNaN(r)&&r>=0)return Math.floor(1e3*r);try{const e=new Date(t);if(Number.isNaN(e.getTime()))return null;const r=new Date,n=e.getTime()-r.getTime();return n>0?n:0}catch{return null}}static async honorRetryAfterIfPresent(e){const t=e instanceof Error&&"retryAfter"in e?e.retryAfter:void 0;if(void 0===t)return!1;const r=this.parseRetryAfterMs(t);return null!==r&&(await new Promise(e=>setTimeout(e,r)),!0)}}async function k(e){return P.honorRetryAfterIfPresent(e)}class E{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(e,t){if(0>=t)return 0;const r=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(t/4,256),this.tokens=this.maxTokens,this.refillRate=t/1e3);const n=r*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+n),this.tokens>=e)return this.tokens-=e,this.transferredBytes+=e,0;const s=(e-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=e,Math.min(s,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(e,t){const r=this.calculateDelay(e,t);r>0&&await new Promise(e=>setTimeout(e,r))}}function F(e){if(!e||"string"!=typeof e)return!1;try{const t=new URL(e);if(!["http:","https:"].includes(t.protocol))return!1;if(!t.hostname||0===t.hostname.length)return!1;if(e.endsWith(":"))return!1;if(t.port){const e=parseInt(t.port,10);if(isNaN(e)||1>e||e>65535)return!1}return!0}catch{return!1}}function A(e){e.timeoutId&&clearTimeout(e.timeoutId)}function B(e,t=1e3){const r=Math.min(t*Math.pow(2,e),1e4),n=.75+.5*function(){const e=globalThis.crypto;if(void 0!==e&&"object"==typeof e&&"function"==typeof e.getRandomValues){const t=e,r=new Uint32Array(1);t.getRandomValues(r);const n=(r.at(0)??0)/4294967295;return Number.isFinite(n)?n:.5}const t=Date.now();return(4294967295&(t^t>>>3))%1e6/1e6}(),s=Math.round(r*n);return new Promise(e=>setTimeout(e,s))}function C(e,t,r){if(t>=r)return!1;const n=e?.name;return!(e instanceof Error&&e.name===m||n===m||e instanceof FetchError&&e.data?.name===m||e instanceof FetchError&&void 0!==e.status&&e.status>=400&&500>e.status)}function createHeaders(e={}){return new Headers(e)}class S{static validateBalancerConfig(e){if(!e.endpoints?.length||!Array.isArray(e.endpoints))throw new FetchError("Balancer endpoints are required and must be a non-empty array",void 0,void 0,"");if(!e.strategy||!["fastest","parallel"].includes(e.strategy))throw new FetchError('Balancer strategy must be either "fastest" or "parallel"',void 0,void 0,"");for(const t of e.endpoints)if(!F(t))throw new FetchError("Invalid balancer endpoint URL: "+t,void 0,void 0,t)}static async executeWithBalancer(e,t,r){return this.validateBalancerConfig(t),"fastest"===t.strategy?this.executeFastestStrategy(e,t,r):this.executeParallelStrategy(e,t,r)}static async executeFastestStrategy(e,t,r){const n=[];for(const s of t.endpoints){const t=this.buildFullUrl(s,e),a=await r(t);if(a.success)return a.data;n.push({endpoint:s,error:a.error})}const s=n[n.length-1];if(s?.error instanceof FetchError)throw s.error;throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,n,t.endpoints[0])}static async executeParallelStrategy(e,t,r){const n=t.endpoints.map(async t=>{const n=this.buildFullUrl(t,e),s=await r(n);return s.success?{success:!0,data:s.data,endpoint:t}:{success:!1,error:s.error,endpoint:t}}),s=await Promise.allSettled(n).then(e=>e.map(e=>"fulfilled"===e.status?e.value:{success:!1,error:e.reason,endpoint:"unknown"})),a=s.filter(e=>e.success);if(0===a.length){const e=s.filter(e=>!e.success).map(e=>({endpoint:e.endpoint,error:e.error}));throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,e,t.endpoints[0])}return a.map(e=>e.data)}static buildFullUrl(e,t){const r=e.endsWith("/")?e.slice(0,-1):e;return t&&""!==t.trim()?t.startsWith("http://")||t.startsWith("https://")?t:`${r}${t.startsWith("/")?t:"/"+t}`:r}}async function L(e,t,r,n,s,a){void 0!==t.maxRate&&t.maxRate>0&&await r.throttle(e.length,t.maxRate),n.push(e);const o=s+e.length,i=Math.round(o/a*100);return t.onProgress?.(i),o}async function O(e,t){const r=await e.blob();await D(r,t)}async function D(e,t){if("undefined"!=typeof window&&"undefined"!=typeof document){const r=URL.createObjectURL(e),n=document.createElement("a");n.href=r,n.download=t;try{document.body.appendChild(n),n.click()}finally{document.body.contains(n)&&document.body.removeChild(n),URL.revokeObjectURL(r)}}else{const{writeFileSync:r}=await import("node:fs"),n=await e.arrayBuffer();r(t,Buffer.from(n))}}class U{static normalizeExecuteError(e,t){const r=e?.name;return r===m?{success:!1,error:new FetchError("Request timeout - operation exceeded the specified timeout duration",void 0,e,t)}:{success:!1,error:e}}static async getErrorData(e){return async function(e){try{const t=e.headers.get(r);return null===t?null:v(t)?await e.json():await e.text()}catch{return null}}(e)}static createHttpError(e,t,r){const n=e.headers.get("Retry-After"),s=new FetchError(`HTTP ${e.status}: ${e.statusText}`,e.status,r,t);return null!==n&&(s.retryAfter=n),s}}class W{static validateForwarderConfig(e){if(!e.forwarders?.length||!Array.isArray(e.forwarders))throw new FetchError("Forwarder endpoints are required and must be a non-empty array",void 0,void 0,"");for(const t of e.forwarders)if(!F(t.url))throw new FetchError("Invalid forwarder endpoint URL: "+t.url,void 0,void 0,t.url)}static forwardResponse(e,t,r){this.validateForwarderConfig(t);const n=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(n).then(e=>{this.logForwarderResults(e)}).catch(e=>{}),Promise.resolve()}static forwardResponseWithCallback(e,t,r,n){this.validateForwarderConfig(t);const s=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(s).then(e=>{const t=e.map(e=>"fulfilled"===e.status?e.value:{endpoint:"unknown",success:!1,error:e.reason});n?n(t):this.logForwarderResults(e)}),Promise.resolve()}static logForwarderResults(e){const t=e.filter(e=>"fulfilled"===e.status&&e.value.success).length;e.length-t>0&&e.forEach(e=>{("fulfilled"!==e.status||e.value.success)&&e.status})}static async executeForwarderWithRetries(e,t,r){const{retries:n=g.RETRIES,timeout:s=g.TIMEOUT_MS,url:a,method:o,headers:i,body:c}=e;let u;u="function"==typeof c?c(t):void 0!==c?c:t;try{return{success:!0,data:await q.executeWithRetries(a,{retries:n,timeout:s,download:!1},()=>r(a,o,u,i),k)}}catch(e){return{success:!1,error:e}}}static createConfig(e,t=g.METHOD,r,n,s){return{forwarders:e.map(e=>({method:t,url:e,...r?{headers:r}:{},...void 0!==n?{timeout:n}:{},...void 0!==s?{retries:s}:{}}))}}static createConfigFromForwarders(e){return{forwarders:e}}}class q{static async executeWithRetries(e,t,r,n){let s=null;for(let e=0;e<=t.retries;e++){const a=await r();if(a.success)return a.data;if(s=a.error,!C(a.error,e,t.retries))break;await n(a.error)||await B(e)}if(s instanceof FetchError)throw s;throw new FetchError(s instanceof Error?s.message:p,void 0,s,e)}}class N{static createStreamIterator(e,t){const n=e.body;if(!n)throw new FetchError(y,void 0,null,t);return{async*[Symbol.asyncIterator](){const s=n.getReader();try{const n=e.headers.get(r),a=v(n),o=R(n);if(a)return void(yield*N.iterateNdjson(s,t));if(o)return void(yield*N.iterateText(s));yield*N.iterateBinary(s)}catch(e){throw new FetchError(`${w}${e instanceof Error?e.message:p}`,void 0,e,t)}finally{s.releaseLock()}}}}static async readDecodedChunk(e,t){const{done:r,value:n}=await e.read();return r?null:void 0===n?"":t.decode(n,{stream:!0})}static async*iterateText(e){const t=new TextDecoder;for(;;){const{done:r,value:n}=await e.read();if(r)break;if(void 0!==n){const e=t.decode(n,{stream:!0});e.length>0&&(yield e)}}}static async*iterateNdjson(e,t){let r="";const n=new TextDecoder;for(;;){const s=await N.readDecodedChunk(e,n);if(null===s)break;r+=s,yield*N.yieldCompleteJsonLines(r,e=>{r=e},t)}const s=r.trim();s.length>0&&(yield*N.safeParseJsonLine(s,t))}static*yieldCompleteJsonLines(e,t,r){let n;for(;-1!==(n=e.indexOf("\n"));){const s=e.slice(0,n).trim();t(e=e.slice(n+1)),0!==s.length&&(yield*N.safeParseJsonLine(s,r))}}static*safeParseJsonLine(e,t){try{yield JSON.parse(e)}catch(e){throw new FetchError(`${w}${e instanceof Error?e.message:p}`,void 0,e,t)}}static async*iterateBinary(e){for(;;){const{done:t,value:r}=await e.read();if(t)break;void 0!==r&&(yield r)}}}e.BalancerHandler=S,e.ForwarderHandler=W,e.createHeaders=createHeaders,e.default=class{static defaultConfig={timeout:3e4,retries:1,headers:{"Content-Type":n},baseURL:"",stream:!1,download:!1,responseType:"auto"};static async get(e,t={}){return this.request("GET",e,t)}static async post(e,t,r={}){return this.request(c,e,this.createRequestOptions(r,t))}static async put(e,t,r={}){return this.request("PUT",e,this.createRequestOptions(r,t))}static async patch(e,t,r={}){return this.request(u,e,this.createRequestOptions(r,t))}static async delete(e,t={}){return this.request("DELETE",e,t)}static async head(e,t={}){return this.request(l,e,t)}static async options(e,t={}){return this.request(d,e,t)}static createRequestOptions(e,t){return{...e,...void 0!==t?{body:t}:{}}}static async request(e,t,r={}){if("string"!=typeof t||""===t.trim())throw new FetchError("URL must be a non-empty string",void 0,void 0,t);const n={...this.defaultConfig,...r};if(0>n.retries)throw new FetchError("Retries must be a non-negative number",void 0,void 0,t);if(0>n.timeout)throw new FetchError("Timeout must be a non-negative number",void 0,void 0,t);if(n.download&&(void 0===n.filename||""===n.filename.trim()))throw new FetchError(f,void 0,void 0,t);if(n.balancer){const r=await this.executeWithBalancer(e,t,n);return n.forwarder&&n.forwarder.length>0&&(W.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(r,n)),r}const s=function(e,t){return e.startsWith("http://")||e.startsWith("https://")?e:t?`${t.endsWith("/")?t.slice(0,-1):t}${e.startsWith("/")?e:"/"+e}`:e}(t,n.baseURL),a=await q.executeWithRetries(s,{retries:n.retries,timeout:n.timeout,download:n.download,...void 0!==n.filename?{filename:n.filename}:{}},()=>this.executeRequest(e,s,n),k);return n.forwarder&&n.forwarder.length>0&&(W.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(a,n)),a}static async executeRequest(e,t,r){let s;try{r.signal||(s=function(e){const t=new AbortController;if(e>0){const r=setTimeout(()=>{t.abort()},e);t.timeoutId=r}return t}(r.timeout));const o=function(e,t,r){const s={...t.headers};void 0!==t.body&&(t.body instanceof FormData?delete s["Content-Type"]:t.body instanceof URLSearchParams&&(s["Content-Type"]=a));const o={method:e,headers:createHeaders(s)},l=[c,"PUT",u];if(void 0!==t.body&&l.includes(e)){const e=function(e,t){if(function(e){return e instanceof FormData||e instanceof URLSearchParams||e instanceof Blob||e instanceof ArrayBuffer||e instanceof Uint8Array||"string"==typeof e}(e))return{body:e,needsDuplex:!1};if("object"==typeof e&&null!=e){t["Content-Type"]??=n;const r=function(e,t){let r;const s=t["Content-Type"];if(void 0===s||s===n)r=JSON.stringify(e);else if(s.includes(a)){const t=function(e){const t=new URLSearchParams;for(const[r,n]of Object.entries(e))if(null!=n){let e;e="object"==typeof n?JSON.stringify(n):"string"==typeof n?n:"number"==typeof n||"boolean"==typeof n?n+"":JSON.stringify(n),t.append(r,e)}return t}(e);r=t.toString()}else s.includes(i),r=JSON.stringify(e);return r}(e,t);return{body:r,needsDuplex:!1}}const r=e+"";return t["Content-Type"]??=i,{body:r,needsDuplex:!1}}(t.body,t.headers,t.maxRate);o.body=e.body,e.needsDuplex&&(o.duplex="half")}return t.signal?o.signal=t.signal:r&&(o.signal=r.signal),o}(e,r,s),f=await globalThis.fetch(t,o);if(s&&A(s),!f.ok){const e=await U.getErrorData(f),r=f.headers.get("Retry-After"),n=new FetchError(`HTTP ${f.status}: ${f.statusText}`,f.status,e,t);throw null!==r&&(n.retryAfter=r),n}if(r.stream)return{success:!0,data:N.createStreamIterator(f,t)};if(r.download)return await this.handleDownloadResponse(f,r);if(e===l||e===d)return{success:!0,data:void 0};const h={responseType:r.responseType};return void 0!==r.onProgress&&(h.onProgress=r.onProgress),{success:!0,data:await x(f,h,t,e)}}catch(e){return s&&A(s),U.normalizeExecuteError(e,t)}}static async handleDownloadResponse(e,n){const a={};void 0!==n.filename&&(a.filename=n.filename),void 0!==n.maxRate&&(a.maxRate=n.maxRate),void 0!==n.onProgress&&(a.onProgress=n.onProgress),await async function(e,n){if(void 0===n.filename||""===n.filename.trim())throw Error(f);void 0!==n.onProgress&&null!==e.body?await async function(e,n){const a=e.headers.get(t),o=null!==a?parseInt(a,10):0;if(o>0)await async function(e,t,n){if(null===e.body)throw Error(y);if(void 0===t.onProgress)throw Error("Progress callback is undefined");if(void 0===t.filename)throw Error(h);let a=0;const o=[],i=new E,c=e.body.getReader();try{for(;;){const{done:e,value:r}=await c.read();if(e)break;if(void 0!==r)for(let e=0;e<r.length;e+=128){const s=r.slice(e,e+128);a=await L(s,t,i,o,a,n)}}const u=function(e,t,n){let a=0;const o=new ArrayBuffer(t),i=new Uint8Array(o);for(const t of e)i.set(t,a),a+=t.length;return new Blob([o],{type:n.headers.get(r)??s})}(o,a,e);await D(u,t.filename)}finally{c.releaseLock()}}(e,n,o);else{if(void 0===n.filename)throw Error(h);await O(e,n.filename)}}(e,n):await O(e,n.filename)}(e,a);const o=e.headers.get(t),i=e.headers.get(r);return{success:!0,data:{filename:n.filename??"download",size:null!==o?parseInt(o,10):0,type:i??"application/octet-stream",status:e.status,ok:e.ok}}}static async executeWithBalancer(e,t,r){const{balancer:n,...s}=r;return await S.executeWithBalancer(t,n,async t=>{try{return{success:!0,data:await q.executeWithRetries(t,{retries:s.retries,timeout:s.timeout,download:s.download,...void 0!==s.filename?{filename:s.filename}:{}},()=>this.executeRequest(e,t,s),k)}}catch(e){return{success:!1,error:e}}})}static async forwardResponse(e,t){const{forwarder:r,...n}=t,s=W.createConfigFromForwarders(r);await W.forwardResponse(e,s,(e,t,r,s)=>this.executeRequest(t,e,{...n,body:r,headers:s??{}}))}},Object.defineProperty(e,"__esModule",{value:!0})}); | ||
| !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Fetch={})}(this,function(e){"use strict";class FetchError extends Error{status;data;url;constructor(e,t,r,n){super(e),this.name="FetchError",this.status=t,this.data=r,this.url=n}}const t="content-length",r="content-type",n="application/json",s="application/octet-stream",a="application/x-www-form-urlencoded",o="text/",i="text/plain",c="POST",u="PATCH",l="HEAD",d="OPTIONS",f="Filename is required when download is enabled",h="Filename is undefined",y="Response body is null",w="Failed to parse streaming response: ",p="Unknown error",m="AbortError",g={RETRIES:3,TIMEOUT_MS:1e4,METHOD:"POST"};async function b(e){try{return await e.json()}catch(t){try{return await e.text()}catch(e){return}}}function v(e){return!0===e?.includes(n)}function R(e){return!0===e?.includes(o)}async function T(e,t,s){const a=e.headers.get(r);if(s!==l){if("auto"!==t.responseType)switch(t.responseType){case"json":return b(e);case"text":return await e.text();case"buffer":{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}case"blob":return await e.blob();default:return async function(e){const t=e.headers.get(r);if(null===t)try{return await e.text()}catch{const t=await e.arrayBuffer();return Object.assign(t,{length:t.byteLength})}if(t.includes(n))return await e.json();if(t.includes(o))return await e.text();const s=await e.arrayBuffer();return Object.assign(s,{length:s.byteLength})}(e)}return async function(e,t,r){if(r===l)return;if(v(t))return b(e);if(R(t))return await e.text();const n=await e.arrayBuffer();return Object.assign(n,{length:n.byteLength})}(e,a,s)}}async function x(e,n,a,o){return n.onProgress?async function(e,n,a,o){const i=e.headers.get(t),c=null!==i?(()=>{const e=parseInt(i,10);return Number.isNaN(e)?0:e})():0;if(null===e.body)return T(e,{responseType:"auto"},o);const u=e.body.getReader(),l=[];let d=0;try{for(;;){const{done:e,value:t}=await u.read();if(e)break;if(void 0!==t&&(l.push(t),d+=t.length),d>10485760)throw new FetchError("Response too large",void 0,void 0,a);if(c>0&&void 0!==n.onProgress){const e=Math.round(d/c*100);n.onProgress(e)}}if(0===l.length)return await T(e,{responseType:"auto"},o);const t=new ArrayBuffer(d),i=new Uint8Array(t);let f=0;for(const e of l)i.set(e,f),f+=e.length;const h=new Blob([t],{type:e.headers.get(r)??s}),y=new Response(h,{status:e.status,statusText:e.statusText,headers:e.headers});return await T(y,{responseType:"auto"},o)}finally{u.releaseLock()}}(e,{onProgress:n.onProgress},a,o):T(e,{responseType:n.responseType},o)}class P{static parseRetryAfterMs(e){const t=e.trim();if(""===t)return null;const r=+t;if(!Number.isNaN(r)&&r>=0)return Math.floor(1e3*r);try{const e=new Date(t);if(Number.isNaN(e.getTime()))return null;const r=new Date,n=e.getTime()-r.getTime();return n>0?n:0}catch{return null}}static async honorRetryAfterIfPresent(e){const t=e instanceof Error&&"retryAfter"in e?e.retryAfter:void 0;if(void 0===t)return!1;const r=this.parseRetryAfterMs(t);return null!==r&&(await new Promise(e=>setTimeout(e,r)),!0)}}async function k(e){return P.honorRetryAfterIfPresent(e)}class E{startTime=0;transferredBytes=0;tokens=0;maxTokens=0;refillRate=0;constructor(){this.reset()}calculateDelay(e,t){if(0>=t)return 0;const r=Date.now()-this.startTime;0===this.maxTokens&&(this.maxTokens=Math.min(t/4,256),this.tokens=this.maxTokens,this.refillRate=t/1e3);const n=r*this.refillRate;if(this.tokens=Math.min(this.maxTokens,this.tokens+n),this.tokens>=e)return this.tokens-=e,this.transferredBytes+=e,0;const s=(e-this.tokens)/this.refillRate;return this.tokens=0,this.transferredBytes+=e,Math.min(s,2e3)}reset(){this.startTime=Date.now(),this.transferredBytes=0,this.tokens=0,this.maxTokens=0,this.refillRate=0}async throttle(e,t){const r=this.calculateDelay(e,t);r>0&&await new Promise(e=>setTimeout(e,r))}}function F(e){if(!e||"string"!=typeof e)return!1;try{const t=new URL(e);if(!["http:","https:"].includes(t.protocol))return!1;if(!t.hostname||0===t.hostname.length)return!1;if(e.endsWith(":"))return!1;if(t.port){const e=parseInt(t.port,10);if(isNaN(e)||1>e||e>65535)return!1}return!0}catch{return!1}}function A(e){e.timeoutId&&clearTimeout(e.timeoutId)}function C(e,t=1e3){const r=Math.min(t*Math.pow(2,e),1e4),n=.75+.5*function(){const e=globalThis.crypto;if(void 0!==e&&"object"==typeof e&&"function"==typeof e.getRandomValues){const t=e,r=new Uint32Array(1);t.getRandomValues(r);const n=(r.at(0)??0)/4294967295;return Number.isFinite(n)?n:.5}const t=Date.now();return(4294967295&(t^t>>>3))%1e6/1e6}(),s=Math.round(r*n);return new Promise(e=>setTimeout(e,s))}function B(e,t,r){if(t>=r)return!1;const n=e?.name;return!(e instanceof Error&&e.name===m||n===m||e instanceof FetchError&&e.data?.name===m||e instanceof FetchError&&void 0!==e.status&&e.status>=400&&500>e.status)}function createHeaders(e={}){return new Headers(e)}class S{static validateBalancerConfig(e){if(!e.endpoints?.length||!Array.isArray(e.endpoints))throw new FetchError("Balancer endpoints are required and must be a non-empty array",void 0,void 0,"");if(!e.strategy||!["fastest","parallel"].includes(e.strategy))throw new FetchError('Balancer strategy must be either "fastest" or "parallel"',void 0,void 0,"");for(const t of e.endpoints)if(!F(t))throw new FetchError("Invalid balancer endpoint URL: "+t,void 0,void 0,t)}static async executeWithBalancer(e,t,r){return this.validateBalancerConfig(t),"fastest"===t.strategy?this.executeFastestStrategy(e,t,r):this.executeParallelStrategy(e,t,r)}static async executeFastestStrategy(e,t,r){const n=[];for(const s of t.endpoints){const t=this.buildFullUrl(s,e),a=await r(t);if(a.success)return a.data;n.push({endpoint:s,error:a.error})}const s=n[n.length-1];if(s?.error instanceof FetchError)throw s.error;throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,n,t.endpoints[0])}static async executeParallelStrategy(e,t,r){const n=t.endpoints.map(async t=>{const n=this.buildFullUrl(t,e),s=await r(n);return s.success?{success:!0,data:s.data,endpoint:t}:{success:!1,error:s.error,endpoint:t}}),s=await Promise.allSettled(n).then(e=>e.map(e=>"fulfilled"===e.status?e.value:{success:!1,error:e.reason,endpoint:"unknown"})),a=s.filter(e=>e.success);if(0===a.length){const e=s.filter(e=>!e.success).map(e=>({endpoint:e.endpoint,error:e.error}));throw new FetchError(`All ${t.endpoints.length} balancer endpoints failed`,void 0,e,t.endpoints[0])}return a.map(e=>e.data)}static buildFullUrl(e,t){const r=e.endsWith("/")?e.slice(0,-1):e;return t&&""!==t.trim()?t.startsWith("http://")||t.startsWith("https://")?t:`${r}${t.startsWith("/")?t:"/"+t}`:r}}async function L(e,t,r,n,s,a){void 0!==t.maxRate&&t.maxRate>0&&await r.throttle(e.length,t.maxRate),n.push(e);const o=s+e.length,i=Math.round(o/a*100);return t.onProgress?.(i),o}async function O(e,t){const r=await e.blob();await D(r,t)}async function D(e,t){if("undefined"!=typeof window&&"undefined"!=typeof document){const r=URL.createObjectURL(e),n=document.createElement("a");n.href=r,n.download=t;try{document.body.appendChild(n),n.click()}finally{document.body.contains(n)&&document.body.removeChild(n),URL.revokeObjectURL(r)}}else{const{writeFileSync:r}=await import("node:fs"),n=await e.arrayBuffer();r(t,Buffer.from(n))}}class U{static normalizeExecuteError(e,t){const r=e?.name;return r===m?{success:!1,error:new FetchError("Request timeout - operation exceeded the specified timeout duration",void 0,e,t)}:{success:!1,error:e}}static async getErrorData(e){return async function(e){try{const t=e.headers.get(r);return null===t?null:v(t)?await e.json():await e.text()}catch{return null}}(e)}static createHttpError(e,t,r){const n=e.headers.get("Retry-After"),s=new FetchError(`HTTP ${e.status}: ${e.statusText}`,e.status,r,t);return null!==n&&(s.retryAfter=n),s}}class q{static validateForwarderConfig(e){if(!e.forwarders?.length||!Array.isArray(e.forwarders))throw new FetchError("Forwarder endpoints are required and must be a non-empty array",void 0,void 0,"");for(const t of e.forwarders)if(!F(t.url))throw new FetchError("Invalid forwarder endpoint URL: "+t.url,void 0,void 0,t.url)}static forwardResponse(e,t,r){this.validateForwarderConfig(t);const n=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(n).then(e=>{this.logForwarderResults(e)}).catch(e=>{}),Promise.resolve()}static forwardResponseWithCallback(e,t,r,n){this.validateForwarderConfig(t);const s=t.forwarders.map(async t=>{try{const n=await this.executeForwarderWithRetries(t,e,r);return{endpoint:t.url,success:n.success,error:n.success?void 0:n.error}}catch(e){return{endpoint:t.url,success:!1,error:e}}});return Promise.allSettled(s).then(e=>{const t=e.map(e=>"fulfilled"===e.status?e.value:{endpoint:"unknown",success:!1,error:e.reason});n?n(t):this.logForwarderResults(e)}),Promise.resolve()}static logForwarderResults(e){const t=e.filter(e=>"fulfilled"===e.status&&e.value.success).length;e.length-t>0&&e.forEach(e=>{("fulfilled"!==e.status||e.value.success)&&e.status})}static async executeForwarderWithRetries(e,t,r){const{retries:n=g.RETRIES,timeout:s=g.TIMEOUT_MS,url:a,method:o,headers:i,body:c}=e;let u;u="function"==typeof c?c(t):void 0!==c?c:t;try{return{success:!0,data:await W.executeWithRetries(a,{retries:n,timeout:s,download:!1},()=>r(a,o,u,i),k)}}catch(e){return{success:!1,error:e}}}static createConfig(e,t=g.METHOD,r,n,s){return{forwarders:e.map(e=>({method:t,url:e,...r?{headers:r}:{},...void 0!==n?{timeout:n}:{},...void 0!==s?{retries:s}:{}}))}}static createConfigFromForwarders(e){return{forwarders:e}}}class W{static async executeWithRetries(e,t,r,n){let s=null;for(let e=0;e<=t.retries;e++){const a=await r();if(a.success)return a.data;if(s=a.error,!B(a.error,e,t.retries))break;await n(a.error)||await C(e)}if(s instanceof FetchError)throw s;throw new FetchError(s instanceof Error?s.message:p,void 0,s,e)}}class N{static createStreamIterator(e,t){const n=e.body;if(!n)throw new FetchError(y,void 0,null,t);return{async*[Symbol.asyncIterator](){const s=n.getReader();try{const n=e.headers.get(r),a=v(n),o=R(n);if(a)return void(yield*N.iterateNdjson(s,t));if(o)return void(yield*N.iterateText(s));yield*N.iterateBinary(s)}catch(e){throw new FetchError(`${w}${e instanceof Error?e.message:p}`,void 0,e,t)}finally{s.releaseLock()}}}}static async readDecodedChunk(e,t){const{done:r,value:n}=await e.read();return r?null:void 0===n?"":t.decode(n,{stream:!0})}static async*iterateText(e){const t=new TextDecoder;for(;;){const{done:r,value:n}=await e.read();if(r)break;if(void 0!==n){const e=t.decode(n,{stream:!0});e.length>0&&(yield e)}}}static async*iterateNdjson(e,t){let r="";const n=new TextDecoder;for(;;){const s=await N.readDecodedChunk(e,n);if(null===s)break;r+=s,yield*N.yieldCompleteJsonLines(r,e=>{r=e},t)}const s=r.trim();s.length>0&&(yield*N.safeParseJsonLine(s,t))}static*yieldCompleteJsonLines(e,t,r){let n;for(;-1!==(n=e.indexOf("\n"));){const s=e.slice(0,n).trim();t(e=e.slice(n+1)),0!==s.length&&(yield*N.safeParseJsonLine(s,r))}}static*safeParseJsonLine(e,t){try{yield JSON.parse(e)}catch(e){throw new FetchError(`${w}${e instanceof Error?e.message:p}`,void 0,e,t)}}static async*iterateBinary(e){for(;;){const{done:t,value:r}=await e.read();if(t)break;void 0!==r&&(yield r)}}}e.BalancerHandler=S,e.ForwarderHandler=q,e.createHeaders=createHeaders,e.default=class{static defaultConfig={timeout:3e4,retries:1,headers:{"Content-Type":n},baseURL:"",stream:!1,download:!1,responseType:"auto"};static async get(e,t={}){return this.request("GET",e,t)}static async post(e,t,r={}){return this.request(c,e,this.createRequestOptions(r,t))}static async put(e,t,r={}){return this.request("PUT",e,this.createRequestOptions(r,t))}static async patch(e,t,r={}){return this.request(u,e,this.createRequestOptions(r,t))}static async delete(e,t={}){return this.request("DELETE",e,t)}static async head(e,t={}){return this.request(l,e,t)}static async options(e,t={}){return this.request(d,e,t)}static async trace(e,t={}){return this.request("TRACE",e,t)}static createRequestOptions(e,t){return{...e,...void 0!==t?{body:t}:{}}}static async request(e,t,r={}){if("string"!=typeof t||""===t.trim())throw new FetchError("URL must be a non-empty string",void 0,void 0,t);const n={...this.defaultConfig,...r};if(0>n.retries)throw new FetchError("Retries must be a non-negative number",void 0,void 0,t);if(0>n.timeout)throw new FetchError("Timeout must be a non-negative number",void 0,void 0,t);if(n.download&&(void 0===n.filename||""===n.filename.trim()))throw new FetchError(f,void 0,void 0,t);if(n.balancer){const r=await this.executeWithBalancer(e,t,n);return n.forwarder&&n.forwarder.length>0&&(q.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(r,n)),r}const s=function(e,t){return e.startsWith("http://")||e.startsWith("https://")?e:t?`${t.endsWith("/")?t.slice(0,-1):t}${e.startsWith("/")?e:"/"+e}`:e}(t,n.baseURL),a=await W.executeWithRetries(s,{retries:n.retries,timeout:n.timeout,download:n.download,...void 0!==n.filename?{filename:n.filename}:{}},()=>this.executeRequest(e,s,n),k);return n.forwarder&&n.forwarder.length>0&&(q.validateForwarderConfig({forwarders:n.forwarder}),await this.forwardResponse(a,n)),a}static async executeRequest(e,t,r){let s;try{r.signal||(s=function(e){const t=new AbortController;if(e>0){const r=setTimeout(()=>{t.abort()},e);t.timeoutId=r}return t}(r.timeout));const o=function(e,t,r){const s={...t.headers};void 0!==t.body&&(t.body instanceof FormData?delete s["Content-Type"]:t.body instanceof URLSearchParams&&(s["Content-Type"]=a));const o={method:e,headers:createHeaders(s)},l=[c,"PUT",u];if(void 0!==t.body&&l.includes(e)){const e=function(e,t){if(function(e){return e instanceof FormData||e instanceof URLSearchParams||e instanceof Blob||e instanceof ArrayBuffer||e instanceof Uint8Array||"string"==typeof e}(e))return{body:e,needsDuplex:!1};if("object"==typeof e&&null!=e){t["Content-Type"]??=n;const r=function(e,t){let r;const s=t["Content-Type"];if(void 0===s||s===n)r=JSON.stringify(e);else if(s.includes(a)){const t=function(e){const t=new URLSearchParams;for(const[r,n]of Object.entries(e))if(null!=n){let e;e="object"==typeof n?JSON.stringify(n):"string"==typeof n?n:"number"==typeof n||"boolean"==typeof n?n+"":JSON.stringify(n),t.append(r,e)}return t}(e);r=t.toString()}else s.includes(i),r=JSON.stringify(e);return r}(e,t);return{body:r,needsDuplex:!1}}const r=e+"";return t["Content-Type"]??=i,{body:r,needsDuplex:!1}}(t.body,t.headers,t.maxRate);o.body=e.body,e.needsDuplex&&(o.duplex="half")}return t.signal?o.signal=t.signal:r&&(o.signal=r.signal),o}(e,r,s),f=await globalThis.fetch(t,o);if(s&&A(s),!f.ok){const e=await U.getErrorData(f),r=f.headers.get("Retry-After"),n=new FetchError(`HTTP ${f.status}: ${f.statusText}`,f.status,e,t);throw null!==r&&(n.retryAfter=r),n}if(r.stream)return{success:!0,data:N.createStreamIterator(f,t)};if(r.download)return await this.handleDownloadResponse(f,r);if(e===l||e===d)return{success:!0,data:void 0};const h={responseType:r.responseType};return void 0!==r.onProgress&&(h.onProgress=r.onProgress),{success:!0,data:await x(f,h,t,e)}}catch(e){return s&&A(s),U.normalizeExecuteError(e,t)}}static async handleDownloadResponse(e,n){const a={};void 0!==n.filename&&(a.filename=n.filename),void 0!==n.maxRate&&(a.maxRate=n.maxRate),void 0!==n.onProgress&&(a.onProgress=n.onProgress),await async function(e,n){if(void 0===n.filename||""===n.filename.trim())throw Error(f);void 0!==n.onProgress&&null!==e.body?await async function(e,n){const a=e.headers.get(t),o=null!==a?parseInt(a,10):0;if(o>0)await async function(e,t,n){if(null===e.body)throw Error(y);if(void 0===t.onProgress)throw Error("Progress callback is undefined");if(void 0===t.filename)throw Error(h);let a=0;const o=[],i=new E,c=e.body.getReader();try{for(;;){const{done:e,value:r}=await c.read();if(e)break;if(void 0!==r)for(let e=0;e<r.length;e+=128){const s=r.slice(e,e+128);a=await L(s,t,i,o,a,n)}}const u=function(e,t,n){let a=0;const o=new ArrayBuffer(t),i=new Uint8Array(o);for(const t of e)i.set(t,a),a+=t.length;return new Blob([o],{type:n.headers.get(r)??s})}(o,a,e);await D(u,t.filename)}finally{c.releaseLock()}}(e,n,o);else{if(void 0===n.filename)throw Error(h);await O(e,n.filename)}}(e,n):await O(e,n.filename)}(e,a);const o=e.headers.get(t),i=e.headers.get(r);return{success:!0,data:{filename:n.filename??"download",size:null!==o?parseInt(o,10):0,type:i??"application/octet-stream",status:e.status,ok:e.ok}}}static async executeWithBalancer(e,t,r){const{balancer:n,...s}=r;return await S.executeWithBalancer(t,n,async t=>{try{return{success:!0,data:await W.executeWithRetries(t,{retries:s.retries,timeout:s.timeout,download:s.download,...void 0!==s.filename?{filename:s.filename}:{}},()=>this.executeRequest(e,t,s),k)}}catch(e){return{success:!1,error:e}}})}static async forwardResponse(e,t){const{forwarder:r,...n}=t,s=q.createConfigFromForwarders(r);await q.forwardResponse(e,s,(e,t,r,s)=>this.executeRequest(t,e,{...n,body:r,headers:s??{}}))}},Object.defineProperty(e,"__esModule",{value:!0})}); |
+4
-2
| { | ||
| "name": "@neabyte/fetch", | ||
| "version": "1.3.0", | ||
| "version": "1.3.1", | ||
| "description": "HTTP client with timeout, retries, streaming, downloads, and error handling for browser and Node.js.", | ||
@@ -43,3 +43,4 @@ "type": "module", | ||
| "type-check": "tsc --noEmit", | ||
| "check-all": "npm run lint && npm run type-check" | ||
| "check-all": "npm run lint && npm run type-check", | ||
| "test:browser": "playwright test" | ||
| }, | ||
@@ -81,2 +82,3 @@ "keywords": [ | ||
| "@eslint/js": "^9.36.0", | ||
| "@playwright/test": "^1.55.1", | ||
| "@rollup/plugin-commonjs": "^28.0.6", | ||
@@ -83,0 +85,0 @@ "@rollup/plugin-node-resolve": "^16.0.1", |
+56
-11
@@ -1,10 +0,51 @@ | ||
| # @neabyte/fetch | ||
| <div align="center"> | ||
| <h1>@neabyte/fetch</h1> | ||
| <p> | ||
| HTTP client with timeout, retries, streaming, downloads, and error handling for browser and Node.js. | ||
| </p> | ||
| <p> | ||
| <a href="https://www.typescriptlang.org/"> | ||
| <img src="https://img.shields.io/badge/TypeScript-007ACC?logo=typescript&logoColor=white" alt="TypeScript"> | ||
| </a> | ||
| <a href="https://www.npmjs.com/package/@neabyte/fetch"> | ||
| <img alt="npm version" src="https://img.shields.io/npm/v/@neabyte/fetch.svg?color=red"> | ||
| </a> | ||
| <a href="https://github.com/NeaByteLab/Fetch/blob/main/LICENSE"> | ||
| <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"> | ||
| </a> | ||
| <a href="https://bundlephobia.com/result?p=@neabyte/fetch"> | ||
| <img src="https://badgen.net/bundlephobia/minzip/@neabyte/fetch" alt="Bundle Size"> | ||
| </a> | ||
| <a href="https://nodejs.org/"> | ||
| <img src="https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen" alt="Node.js"> | ||
| </a> | ||
| </p> | ||
| <table style="margin: 0 auto;"> | ||
| <tr> | ||
| <td> | ||
| <img src="https://raw.githubusercontent.com/alrra/browser-logos/main/src/chrome/chrome_48x48.png" alt="Chrome" width="48" height="48"> | ||
| </td> | ||
| <td> | ||
| <img src="https://raw.githubusercontent.com/alrra/browser-logos/main/src/firefox/firefox_48x48.png" alt="Firefox" width="48" height="48"> | ||
| </td> | ||
| <td> | ||
| <img src="https://raw.githubusercontent.com/alrra/browser-logos/main/src/safari/safari_48x48.png" alt="Safari" width="48" height="48"> | ||
| </td> | ||
| <td> | ||
| <img src="https://raw.githubusercontent.com/alrra/browser-logos/main/src/opera/opera_48x48.png" alt="Opera" width="48" height="48"> | ||
| </td> | ||
| <td> | ||
| <img src="https://raw.githubusercontent.com/alrra/browser-logos/main/src/edge/edge_48x48.png" alt="Edge" width="48" height="48"> | ||
| </td> | ||
| </tr> | ||
| <tr> | ||
| <td>Latest ✔</td> | ||
| <td>Latest ✔</td> | ||
| <td>Latest ✔</td> | ||
| <td>Latest ✔</td> | ||
| <td>Latest ✔</td> | ||
| </tr> | ||
| </table> | ||
| </div> | ||
| [](https://www.typescriptlang.org/) | ||
| [](https://www.npmjs.com/package/@neabyte/fetch) | ||
| [](https://github.com/NeaByteLab/Fetch/blob/main/LICENSE) | ||
| [](https://bundlephobia.com/result?p=@neabyte/fetch) | ||
| HTTP client with timeout, retries, streaming, downloads, and error handling for browser and Node.js. | ||
| ## ✨ Features | ||
@@ -104,5 +145,9 @@ | ||
| ### 🔮 **Planned Features** | ||
| - [ ] **HTTP Proxy Support** - Connect through HTTP proxies | ||
| - [ ] **SOCKS Proxy Support** - Connect through SOCKS4/SOCKS5 proxies | ||
| - [ ] **Proxy Authentication** - Username/password authentication for proxies | ||
| - [ ] **Authentication Helpers** - Basic, Bearer, and API key auth | ||
| - [ ] **Cookie Management** - Cookie handling | ||
| - [ ] **HTTP Proxy Support** - HTTP proxy connections | ||
| - [ ] **Proxy Authentication** - Username/password for proxies | ||
| - [ ] **Request/Response Interceptors** - Request and response modification | ||
| - [ ] **Request/Response Transformers** - Data transformation | ||
| - [ ] **SOCKS Proxy Support** - SOCKS4/SOCKS5 proxy connections | ||
@@ -109,0 +154,0 @@ ### 📋 **Future Considerations** |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
150030
1.58%627
0.32%163
38.14%20
5.26%