UNPKG

mt-mappersmith

Version:

It is a lightweight rest client for node.js and the browser

1,166 lines (1,153 loc) 38.2 kB
// src/method-descriptor.ts var MethodDescriptor = class { allowResourceHostOverride; parameterEncoder; authAttr; binary; bodyAttr; headers; headersAttr; host; hostAttr; method; middleware; params; path; pathAttr; queryParamAlias; timeoutAttr; constructor(params) { this.allowResourceHostOverride = params.allowResourceHostOverride || false; this.parameterEncoder = params.parameterEncoder || encodeURIComponent; this.binary = params.binary || false; this.headers = params.headers; this.host = params.host; this.method = params.method || "get"; this.params = params.params; this.path = params.path; this.queryParamAlias = params.queryParamAlias || {}; this.authAttr = params.authAttr || "auth"; this.bodyAttr = params.bodyAttr || "body"; this.headersAttr = params.headersAttr || "headers"; this.hostAttr = params.hostAttr || "host"; this.pathAttr = params.pathAttr || "path"; this.timeoutAttr = params.timeoutAttr || "timeout"; const resourceMiddleware = params.middleware || params.middlewares || []; this.middleware = resourceMiddleware; } }; // src/utils/index.ts var _process; var getNanoSeconds; var loadTime; try { _process = eval( 'typeof __TEST_WEB__ === "undefined" && typeof process === "object" ? process : undefined' ); } catch (e) { } var hasProcessHrtime = () => { return typeof _process !== "undefined" && _process !== null && _process.hrtime; }; if (hasProcessHrtime()) { getNanoSeconds = () => { const hr = _process.hrtime(); return hr[0] * 1e9 + hr[1]; }; loadTime = getNanoSeconds(); } var R20 = /%20/g; var isNeitherNullNorUndefined = (x) => x !== null && x !== void 0; var validKeys = (entry) => Object.keys(entry).filter((key) => isNeitherNullNorUndefined(entry[key])); var buildRecursive = (key, value, suffix = "", encoderFn = encodeURIComponent) => { if (Array.isArray(value)) { return value.map((v) => buildRecursive(key, v, suffix + "[]", encoderFn)).join("&"); } if (typeof value !== "object") { return `${encoderFn(key + suffix)}=${encoderFn(value)}`; } return Object.keys(value).map((nestedKey) => { const nestedValue = value[nestedKey]; if (isNeitherNullNorUndefined(nestedValue)) { return buildRecursive(key, nestedValue, suffix + "[" + nestedKey + "]", encoderFn); } return null; }).filter(isNeitherNullNorUndefined).join("&"); }; var toQueryString = (entry, encoderFn = encodeURIComponent) => { if (!isPlainObject(entry)) { return entry; } return Object.keys(entry).map((key) => { const value = entry[key]; if (isNeitherNullNorUndefined(value)) { return buildRecursive(key, value, "", encoderFn); } return null; }).filter(isNeitherNullNorUndefined).join("&").replace(R20, "+"); }; var performanceNow = () => { if (hasProcessHrtime() && getNanoSeconds !== void 0) { const now = getNanoSeconds(); if (now !== void 0 && loadTime !== void 0) { return (now - loadTime) / 1e6; } } return Date.now(); }; var parseResponseHeaders = (headerStr) => { const headers = {}; if (!headerStr) { return headers; } const headerPairs = headerStr.split("\r\n"); for (let i = 0; i < headerPairs.length; i++) { const headerPair = headerPairs[i]; const index = headerPair.indexOf(": "); if (index > 0) { const key = headerPair.substring(0, index).toLowerCase().trim(); const val = headerPair.substring(index + 2).trim(); headers[key] = val; } } return headers; }; var lowerCaseObjectKeys = (obj) => { return Object.keys(obj).reduce((target, key) => { target[key.toLowerCase()] = obj[key]; return target; }, {}); }; var hasOwnProperty = Object.prototype.hasOwnProperty; var assign = Object.assign || function(target) { for (let i = 1; i < arguments.length; i++) { const source = arguments[i]; for (const key in source) { if (hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var toString = Object.prototype.toString; var isPlainObject = (value) => { return toString.call(value) === "[object Object]" && Object.getPrototypeOf(value) === Object.getPrototypeOf({}); }; var isObject = (value) => { return typeof value === "object" && value !== null && !Array.isArray(value); }; var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var btoa = (input) => { let output = ""; let map = CHARS; const str = String(input); for ( let block = 0, charCode, idx = 0; // if the next str index does not exist: // change the mapping table to "=" // check if d has no fractional digits str.charAt(idx | 0) || (map = "=", idx % 1); // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 output += map.charAt(63 & block >> 8 - idx % 1 * 8) ) { charCode = str.charCodeAt(idx += 3 / 4); if (charCode > 255) { throw new Error( "[Mappersmith] 'btoa' failed: The string to be encoded contains characters outside of the Latin1 range." ); } block = block << 8 | charCode; } return output; }; // src/manifest.ts var Manifest = class { host; allowResourceHostOverride; parameterEncoder; bodyAttr; headersAttr; authAttr; timeoutAttr; hostAttr; clientId; gatewayConfigs; resources; context; middleware; constructor(options, { gatewayConfigs, middleware = [], context = {} }) { this.host = options.host; this.allowResourceHostOverride = options.allowResourceHostOverride || false; this.parameterEncoder = options.parameterEncoder || encodeURIComponent; this.bodyAttr = options.bodyAttr; this.headersAttr = options.headersAttr; this.authAttr = options.authAttr; this.timeoutAttr = options.timeoutAttr; this.hostAttr = options.hostAttr; this.clientId = options.clientId || null; this.gatewayConfigs = assign({}, gatewayConfigs, options.gatewayConfigs); this.resources = options.resources || {}; this.context = context; const clientMiddleware = options.middleware || options.middlewares || []; if (options.ignoreGlobalMiddleware) { this.middleware = clientMiddleware; } else { this.middleware = [...clientMiddleware, ...middleware]; } } eachResource(callback) { Object.keys(this.resources).forEach((resourceName) => { const methods = this.eachMethod(resourceName, (methodName) => ({ name: methodName, descriptor: this.createMethodDescriptor(resourceName, methodName) })); callback(resourceName, methods); }); } eachMethod(resourceName, callback) { return Object.keys(this.resources[resourceName]).map(callback); } createMethodDescriptor(resourceName, methodName) { const definition = this.resources[resourceName][methodName]; if (!definition || !["string", "function"].includes(typeof definition.path)) { throw new Error( `[Mappersmith] path is undefined for resource "${resourceName}" method "${methodName}"` ); } return new MethodDescriptor( assign( { host: this.host, allowResourceHostOverride: this.allowResourceHostOverride, parameterEncoder: this.parameterEncoder, bodyAttr: this.bodyAttr, headersAttr: this.headersAttr, authAttr: this.authAttr, timeoutAttr: this.timeoutAttr, hostAttr: this.hostAttr }, definition ) ); } /** * @param {Object} args * @param {String|Null} args.clientId * @param {String} args.resourceName * @param {String} args.resourceMethod * @param {Object} args.context * @param {Boolean} args.mockRequest * * @return {Array<Object>} */ createMiddleware(args) { const createInstance = (middlewareFactory) => { const defaultDescriptor = { __name: middlewareFactory.name || middlewareFactory.toString(), response(next) { return next(); }, /** * @since 2.27.0 * Replaced the request method */ prepareRequest(next) { return this.request ? next().then((req) => { var _a; return (_a = this.request) == null ? void 0 : _a.call(this, req); }) : next(); } }; const middlewareParams = assign(args, { clientId: this.clientId, context: assign({}, this.context) }); return assign(defaultDescriptor, middlewareFactory(middlewareParams)); }; const { resourceName: name, resourceMethod: method } = args; const resourceMiddleware = this.createMethodDescriptor(name, method).middleware; const middlewares = [...resourceMiddleware, ...this.middleware]; return middlewares.map(createInstance); } }; // src/request.ts var REGEXP_DYNAMIC_SEGMENT = /{([^}?]+)\??}/; var REGEXP_OPTIONAL_DYNAMIC_SEGMENT = /\/?{([^}?]+)\?}/g; var REGEXP_TRAILING_SLASH = /\/$/; var Request = class _Request { methodDescriptor; requestParams; requestContext; constructor(methodDescriptor, requestParams = {}, requestContext = {}) { this.methodDescriptor = methodDescriptor; this.requestParams = requestParams; this.requestContext = requestContext; } isParam(key) { return key !== this.methodDescriptor.headersAttr && key !== this.methodDescriptor.bodyAttr && key !== this.methodDescriptor.authAttr && key !== this.methodDescriptor.timeoutAttr && key !== this.methodDescriptor.hostAttr && key !== this.methodDescriptor.pathAttr; } params() { const params = assign({}, this.methodDescriptor.params, this.requestParams); return Object.keys(params).reduce((obj, key) => { if (this.isParam(key)) { obj[key] = params[key]; } return obj; }, {}); } /** * Returns the request context; a key value object. * Useful to pass information from upstream middleware to a downstream one. */ context() { return this.requestContext; } /** * Returns the HTTP method in lowercase */ method() { return this.methodDescriptor.method.toLowerCase(); } /** * Returns host name without trailing slash * Example: 'http://example.org' */ host() { const { allowResourceHostOverride, hostAttr, host } = this.methodDescriptor; const originalHost = allowResourceHostOverride ? this.requestParams[hostAttr] || host || "" : host || ""; if (typeof originalHost === "string") { return originalHost.replace(REGEXP_TRAILING_SLASH, ""); } return ""; } /** * Returns path with parameters and leading slash. * Example: '/some/path?param1=true' * * @throws {Error} if any dynamic segment is missing. * Example: * Imagine the path '/some/{name}', the error will be similar to: * '[Mappersmith] required parameter missing (name), "/some/{name}" cannot be resolved' */ path() { const { pathAttr: mdPathAttr, path: mdPath } = this.methodDescriptor; const originalPath = this.requestParams[mdPathAttr] || mdPath || ""; const params = this.params(); let path; if (typeof originalPath === "function") { path = originalPath(params); if (typeof path !== "string") { throw new Error( `[Mappersmith] method descriptor function did not return a string, params=${JSON.stringify( params )}` ); } } else { path = originalPath; } const regexp = new RegExp(REGEXP_DYNAMIC_SEGMENT, "g"); const dynamicSegmentKeys = []; let match; while ((match = regexp.exec(path)) !== null) { dynamicSegmentKeys.push(match[1]); } for (const key of dynamicSegmentKeys) { const pattern = new RegExp(`{${key}\\??}`, "g"); const value = params[key]; if (value != null && typeof value !== "object") { path = path.replace(pattern, this.methodDescriptor.parameterEncoder(value)); delete params[key]; } } path = path.replace(REGEXP_OPTIONAL_DYNAMIC_SEGMENT, ""); const missingDynamicSegmentMatch = path.match(REGEXP_DYNAMIC_SEGMENT); if (missingDynamicSegmentMatch) { throw new Error( `[Mappersmith] required parameter missing (${missingDynamicSegmentMatch[1]}), "${path}" cannot be resolved` ); } const aliasedParams = Object.keys(params).reduce( (aliased, key) => { const aliasedKey = this.methodDescriptor.queryParamAlias[key] || key; const value = params[key]; if (value != null) { aliased[aliasedKey] = value; } return aliased; }, {} ); const queryString = toQueryString(aliasedParams, this.methodDescriptor.parameterEncoder); if (typeof queryString === "string" && queryString.length !== 0) { const hasQuery = path.includes("?"); path += `${hasQuery ? "&" : "?"}${queryString}`; } if (path[0] !== "/" && path.length > 0) { path = `/${path}`; } return path; } /** * Returns the template path, without params, before interpolation. * If path is a function, returns the result of request.path() * Example: '/some/{param}/path' */ pathTemplate() { const path = this.methodDescriptor.path; const prependSlash = (str) => str[0] !== "/" ? `/${str}` : str; if (typeof path === "function") { return prependSlash(path(this.params())); } return prependSlash(path); } /** * Returns the full URL * Example: http://example.org/some/path?param1=true * */ url() { return `${this.host()}${this.path()}`; } /** * Returns an object with the headers. Header names are converted to * lowercase */ headers() { const headerAttr = this.methodDescriptor.headersAttr; const headers = this.requestParams[headerAttr] || {}; if (typeof headers === "function") { return headers; } const mergedHeaders = { ...this.methodDescriptor.headers, ...headers }; return lowerCaseObjectKeys(mergedHeaders); } /** * Utility method to get a header value by name */ header(name) { const key = name.toLowerCase(); if (key in this.headers()) { return this.headers()[key]; } return void 0; } body() { return this.requestParams[this.methodDescriptor.bodyAttr]; } auth() { return this.requestParams[this.methodDescriptor.authAttr]; } timeout() { return this.requestParams[this.methodDescriptor.timeoutAttr]; } /** * Enhances current request returning a new Request * @param {RequestParams} extras * @param {Object} extras.auth - it will replace the current auth * @param {String|Object} extras.body - it will replace the current body * @param {Headers} extras.headers - it will be merged with current headers * @param {String} extras.host - it will replace the current timeout * @param {RequestParams} extras.params - it will be merged with current params * @param {Number} extras.timeout - it will replace the current timeout * @param {Object} requestContext - Use to pass information between different middleware. */ enhance(extras, requestContext) { const authKey = this.methodDescriptor.authAttr; const bodyKey = this.methodDescriptor.bodyAttr; const headerKey = this.methodDescriptor.headersAttr; const hostKey = this.methodDescriptor.hostAttr; const timeoutKey = this.methodDescriptor.timeoutAttr; const pathKey = this.methodDescriptor.pathAttr; const requestParams = assign({}, this.requestParams, extras.params); const headers = this.requestParams[headerKey]; const mergedHeaders = assign({}, headers, extras.headers); requestParams[headerKey] = mergedHeaders; extras.auth && (requestParams[authKey] = extras.auth); extras.body && (requestParams[bodyKey] = extras.body); extras.host && (requestParams[hostKey] = extras.host); extras.timeout && (requestParams[timeoutKey] = extras.timeout); extras.path && (requestParams[pathKey] = extras.path); const nextContext = { ...this.requestContext, ...requestContext }; return new _Request(this.methodDescriptor, requestParams, nextContext); } /** * Is the request expecting a binary response? */ isBinary() { return this.methodDescriptor.binary; } }; // src/client-builder.ts var isFactoryConfigured = (factory) => { if (!factory || !factory()) { return false; } return true; }; var ClientBuilder = class { Promise; manifest; GatewayClassFactory; maxMiddlewareStackExecutionAllowed; constructor(manifestDefinition, GatewayClassFactory, configs2) { if (!manifestDefinition) { throw new Error(`[Mappersmith] invalid manifest (${manifestDefinition})`); } if (!isFactoryConfigured(GatewayClassFactory)) { throw new Error("[Mappersmith] gateway class not configured (configs.gateway)"); } if (!configs2.Promise) { throw new Error("[Mappersmith] Promise not configured (configs.Promise)"); } this.Promise = configs2.Promise; this.manifest = new Manifest(manifestDefinition, configs2); this.GatewayClassFactory = GatewayClassFactory; this.maxMiddlewareStackExecutionAllowed = configs2.maxMiddlewareStackExecutionAllowed; } build() { const client = { _manifest: this.manifest }; this.manifest.eachResource((resourceName, methods) => { client[resourceName] = this.buildResource(resourceName, methods); }); return client; } buildResource(resourceName, methods) { const initialResourceValue = {}; const resource = methods.reduce((resource2, method) => { const resourceMethod = (requestParams, context) => { const request = new Request(method.descriptor, requestParams, context); return this.invokeMiddlewares(String(resourceName), method.name, request); }; return { ...resource2, [method.name]: resourceMethod }; }, initialResourceValue); return resource; } invokeMiddlewares(resourceName, resourceMethod, initialRequest) { const middleware = this.manifest.createMiddleware({ resourceName, resourceMethod }); const GatewayClass = this.GatewayClassFactory(); const gatewayConfigs = this.manifest.gatewayConfigs; const requestPhaseFailureContext = { middleware: null, returnedInvalidRequest: false, abortExecution: false }; const getInitialRequest = () => this.Promise.resolve(initialRequest); const chainRequestPhase = (next, middleware2) => () => { const abort = (error) => { requestPhaseFailureContext.abortExecution = true; throw error; }; return this.Promise.resolve().then(() => middleware2.prepareRequest(next, abort)).then((request) => { if (request instanceof Request) { return request; } requestPhaseFailureContext.returnedInvalidRequest = true; const typeValue = typeof request; const prettyType = typeValue === "object" || typeValue === "function" ? ( // eslint-disable-next-line @typescript-eslint/no-explicit-any request.name || typeValue ) : typeValue; throw new Error( `[Mappersmith] middleware "${middleware2.__name}" should return "Request" but returned "${prettyType}"` ); }).catch((e) => { requestPhaseFailureContext.middleware = middleware2.__name || null; throw e; }); }; const prepareRequest = middleware.reduce(chainRequestPhase, getInitialRequest); let executions = 0; const executeMiddlewareStack = () => prepareRequest().catch((e) => { const { returnedInvalidRequest, abortExecution, middleware: middleware2 } = requestPhaseFailureContext; if (returnedInvalidRequest || abortExecution) { throw e; } const error = new Error( `[Mappersmith] middleware "${middleware2}" failed in the request phase: ${e.message}` ); error.stack = e.stack; throw error; }).then((finalRequest) => { executions++; if (executions > this.maxMiddlewareStackExecutionAllowed) { throw new Error( `[Mappersmith] infinite loop detected (middleware stack invoked ${executions} times). Check the use of "renew" in one of the middleware.` ); } const renew = executeMiddlewareStack; const chainResponsePhase = (previousValue, currentValue) => () => { const nextValue = currentValue.response(previousValue, renew, finalRequest); return nextValue; }; const callGateway = () => new GatewayClass(finalRequest, gatewayConfigs).call(); const execute = middleware.reduce(chainResponsePhase, callGateway); return execute(); }); return new this.Promise((resolve, reject) => { executeMiddlewareStack().then((response) => resolve(response)).catch(reject); }); } }; // src/response.ts var REGEXP_CONTENT_TYPE_JSON = /^application\/(json|.*\+json)/; var Response = class _Response { originalRequest; responseStatus; responseData; responseHeaders; // eslint-disable-next-line no-use-before-define errors; timeElapsed; constructor(originalRequest, responseStatus, responseData, responseHeaders, errors) { const auth = originalRequest.requestParams && originalRequest.requestParams.auth; if (auth) { const maskedAuth = { ...auth, password: "***" }; this.originalRequest = originalRequest.enhance({ auth: maskedAuth }); } else { this.originalRequest = originalRequest; } this.responseStatus = responseStatus; this.responseData = responseData ?? null; this.responseHeaders = responseHeaders || {}; this.errors = errors || []; this.timeElapsed = null; } request() { return this.originalRequest; } status() { if (this.responseStatus === 1223) { return 204; } return this.responseStatus; } /** * Returns true if status is greater or equal 200 or lower than 400 */ success() { const status = this.status(); return status >= 200 && status < 400; } /** * Returns an object with the headers. Header names are converted to * lowercase */ headers() { return lowerCaseObjectKeys(this.responseHeaders); } /** * Utility method to get a header value by name */ header(name) { const key = name.toLowerCase(); if (key in this.headers()) { return this.headers()[key]; } return void 0; } /** * Returns the original response data */ rawData() { return this.responseData; } /** * Returns the response data, if "Content-Type" is "application/json" * it parses the response and returns an object. * Friendly reminder: * - JSON.parse() can return null, an Array or an object. */ data() { if (this.isContentTypeJSON() && this.responseData !== null) { try { return JSON.parse(this.responseData); } catch (e) { } } return this.responseData; } isContentTypeJSON() { const contentType = this.header("content-type"); if (contentType === void 0) { return false; } return REGEXP_CONTENT_TYPE_JSON.test(contentType); } /** * Returns the last error instance that caused the request to fail */ error() { const lastError = this.errors[this.errors.length - 1] || null; if (typeof lastError === "string") { return new Error(lastError); } return lastError; } /** * Enhances current Response returning a new Response * * @param {Object} extras * @param {Integer} extras.status - it will replace the current status * @param {String} extras.rawData - it will replace the current rawData * @param {Object} extras.headers - it will be merged with current headers * @param {Error} extras.error - it will be added to the list of errors */ enhance(extras) { const mergedHeaders = { ...this.headers(), ...extras.headers || {} }; const enhancedResponse = new _Response( this.request(), extras.status || this.status(), extras.rawData || this.rawData(), mergedHeaders, extras.error ? [...this.errors, extras.error] : [...this.errors] ); enhancedResponse.timeElapsed = this.timeElapsed; return enhancedResponse; } }; var response_default = Response; // src/version.ts var version = "0.0.3"; // src/mappersmith.ts var configs = { context: {}, middleware: [], Promise: typeof Promise === "function" ? Promise : null, fetch: typeof fetch === "function" ? fetch : null, /** * The maximum amount of executions allowed before it is considered an infinite loop. * In the response phase of middleware, it's possible to execute a function called "renew", * which can be used to rerun the middleware stack. This feature is useful in some scenarios, * for example, re-fetching an invalid access token. * This configuration is used to detect infinite loops, don't increase this value too much * @default 2 */ maxMiddlewareStackExecutionAllowed: 2, /** * Gateway implementation, it defaults to "lib/gateway/xhr" for browsers and * "lib/gateway/http" for node */ gateway: null, gatewayConfigs: { /** * Setting this option will fake PUT, PATCH and DELETE requests with a HTTP POST. It will * add "_method" and "X-HTTP-Method-Override" with the original requested method * @default false */ emulateHTTP: false, /** * Setting this option will return HTTP status 408 (Request Timeout) when a request times * out. When "false", HTTP status 400 (Bad Request) will be used instead. * @default false */ enableHTTP408OnTimeouts: false, XHR: { /** * Indicates whether or not cross-site Access-Control requests should be made using credentials * such as cookies, authorization headers or TLS client certificates. * Setting withCredentials has no effect on same-site requests * * https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials * * @default false */ withCredentials: false, /** * For additional configurations to the XMLHttpRequest object. * @param {XMLHttpRequest} xhr * @default null */ configure: null }, HTTP: { /** * Enable this option to evaluate timeout on entire request durations, * including DNS resolution and socket connection. * * See original nodejs issue: https://github.com/nodejs/node/pull/8101 * * @default false */ useSocketConnectionTimeout: false, /** * For additional configurations to the http/https module * For http: https://nodejs.org/api/http.html#http_http_request_options_callback * For https: https://nodejs.org/api/https.html#https_https_request_options_callback * * @param {object} options * @default null */ configure: null, onRequestWillStart: null, onRequestSocketAssigned: null, onSocketLookup: null, onSocketConnect: null, onSocketSecureConnect: null, onResponseReadable: null, onResponseEnd: null }, Fetch: { /** * Indicates whether the user agent should send cookies from the other domain in the case of cross-origin * requests. This is similar to XHR’s withCredentials flag, but with three available values (instead of two): * * "omit": Never send cookies. * "same-origin": Only send cookies if the URL is on the same origin as the calling script. * "include": Always send cookies, even for cross-origin calls. * * https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials * * @default "omit" */ credentials: "omit" } } }; var setContext = (context) => { console.warn( "The use of setContext is deprecated - you need to find another way to pass data between your middlewares." ); configs.context = assign(configs.context, context); }; function forge(manifest) { const GatewayClassFactory = () => configs.gateway; return new ClientBuilder(manifest, GatewayClassFactory, configs).build(); } // src/gateway/timeout-error.ts var isTimeoutError = (e) => { return e && e.name === "TimeoutError"; }; var createTimeoutError = (message) => { const error = new Error(message); error.name = "TimeoutError"; return error; }; // src/gateway/gateway.ts var REGEXP_EMULATE_HTTP = /^(delete|put|patch)/i; var Gateway = class { request; configs; successCallback; failCallback; constructor(request, configs2) { this.request = request; this.configs = configs2; this.successCallback = function() { return void 0; }; this.failCallback = function() { return void 0; }; } get() { throw new Error("Not implemented"); } head() { throw new Error("Not implemented"); } post() { throw new Error("Not implemented"); } put() { throw new Error("Not implemented"); } patch() { throw new Error("Not implemented"); } delete() { throw new Error("Not implemented"); } options() { return this.configs; } shouldEmulateHTTP() { return this.options().emulateHTTP && REGEXP_EMULATE_HTTP.test(this.request.method()); } // eslint-disable-next-line @typescript-eslint/no-explicit-any call() { const timeStart = performanceNow(); if (!configs.Promise) { throw new Error("[Mappersmith] Promise not configured (configs.Promise)"); } return new configs.Promise((resolve, reject) => { this.successCallback = (response) => { response.timeElapsed = performanceNow() - timeStart; resolve(response); }; this.failCallback = (response) => { response.timeElapsed = performanceNow() - timeStart; reject(response); }; try { this[this.request.method()].apply(this, arguments); } catch (e) { const err = e; this.dispatchClientError(err.message, err); } }); } dispatchResponse(response) { response.success() ? this.successCallback(response) : this.failCallback(response); } dispatchClientError(message, error) { if (isTimeoutError(error) && this.options().enableHTTP408OnTimeouts) { this.failCallback(new Response(this.request, 408, message, {}, [error])); } else { this.failCallback(new Response(this.request, 400, message, {}, [error])); } } prepareBody(method, headers) { let body = this.request.body(); if (this.shouldEmulateHTTP()) { body = body || {}; isPlainObject(body) && (body["_method"] = method); headers["x-http-method-override"] = method; } const bodyString = toQueryString(body); if (bodyString) { if (isPlainObject(body)) { headers["content-type"] = "application/x-www-form-urlencoded;charset=utf-8"; } } return bodyString; } }; // src/gateway/xhr.ts var toBase64; try { toBase64 = window.btoa; } catch { toBase64 = btoa; } var XHR = class extends Gateway { canceled = false; timer; get() { const xmlHttpRequest = this.createXHR(); xmlHttpRequest.open("GET", this.request.url(), true); this.setHeaders(xmlHttpRequest, {}); this.configureTimeout(xmlHttpRequest); this.configureBinary(xmlHttpRequest); xmlHttpRequest.send(); } head() { const xmlHttpRequest = this.createXHR(); xmlHttpRequest.open("HEAD", this.request.url(), true); this.setHeaders(xmlHttpRequest, {}); this.configureTimeout(xmlHttpRequest); this.configureBinary(xmlHttpRequest); xmlHttpRequest.send(); } post() { this.performRequest("post"); } put() { this.performRequest("put"); } patch() { this.performRequest("patch"); } delete() { this.performRequest("delete"); } configureBinary(xmlHttpRequest) { if (this.request.isBinary()) { xmlHttpRequest.responseType = "blob"; } } configureTimeout(xmlHttpRequest) { this.canceled = false; this.timer = void 0; const timeout = this.request.timeout(); if (timeout) { xmlHttpRequest.timeout = timeout; xmlHttpRequest.addEventListener("timeout", () => { this.canceled = true; this.timer && clearTimeout(this.timer); const error = createTimeoutError(`Timeout (${timeout}ms)`); this.dispatchClientError(error.message, error); }); this.timer = setTimeout(() => { this.canceled = true; const error = createTimeoutError(`Timeout (${timeout}ms)`); this.dispatchClientError(error.message, error); }, timeout + 1); } } configureCallbacks(xmlHttpRequest) { xmlHttpRequest.addEventListener("load", () => { if (this.canceled) { return; } this.timer && clearTimeout(this.timer); this.dispatchResponse(this.createResponse(xmlHttpRequest)); }); xmlHttpRequest.addEventListener("error", (e) => { if (this.canceled) { return; } this.timer && clearTimeout(this.timer); const guessedErrorCause = e ? ( // eslint-disable-next-line @typescript-eslint/no-explicit-any e.message || e.name ) : xmlHttpRequest.responseText; const errorMessage = "Network error"; const enhancedMessage = guessedErrorCause ? `: ${guessedErrorCause}` : ""; const error = new Error(`${errorMessage}${enhancedMessage}`); this.dispatchClientError(errorMessage, error); }); const xhrOptions = this.options().XHR; if (xhrOptions.withCredentials) { xmlHttpRequest.withCredentials = true; } if (xhrOptions.configure) { xhrOptions.configure(xmlHttpRequest); } } performRequest(method) { const requestMethod = this.shouldEmulateHTTP() ? "post" : method; const xmlHttpRequest = this.createXHR(); xmlHttpRequest.open(requestMethod.toUpperCase(), this.request.url(), true); const customHeaders = {}; const body = this.prepareBody(method, customHeaders); this.setHeaders(xmlHttpRequest, customHeaders); this.configureTimeout(xmlHttpRequest); this.configureBinary(xmlHttpRequest); xmlHttpRequest.send(body); } createResponse(xmlHttpRequest) { const status = xmlHttpRequest.status; const data = this.request.isBinary() ? xmlHttpRequest.response : xmlHttpRequest.responseText; const responseHeaders = parseResponseHeaders(xmlHttpRequest.getAllResponseHeaders()); return new response_default(this.request, status, data, responseHeaders); } setHeaders(xmlHttpRequest, customHeaders) { const auth = this.request.auth(); const headers = assign(customHeaders, { ...this.request.headers(), ...auth ? { authorization: `Basic ${toBase64(`${auth.username}:${auth.password}`)}` } : {} }); Object.keys(headers).forEach((headerName) => { xmlHttpRequest.setRequestHeader(headerName, `${headers[headerName]}`); }); } createXHR() { const xmlHttpRequest = new XMLHttpRequest(); this.configureCallbacks(xmlHttpRequest); return xmlHttpRequest; } }; // src/gateway/fetch.ts var Fetch = class extends Gateway { get() { this.performRequest("get"); } head() { this.performRequest("head"); } post() { this.performRequest("post"); } put() { this.performRequest("put"); } patch() { this.performRequest("patch"); } delete() { this.performRequest("delete"); } performRequest(method) { const fetch2 = configs.fetch; if (!fetch2) { throw new Error( `[Mappersmith] global fetch does not exist, please assign "configs.fetch" to a valid implementation` ); } const customHeaders = {}; const body = this.prepareBody(method, customHeaders); const auth = this.request.auth(); if (auth) { const username = auth.username || ""; const password = auth.password || ""; customHeaders["authorization"] = `Basic ${btoa(`${username}:${password}`)}`; } const headers = assign(customHeaders, this.request.headers()); const requestMethod = this.shouldEmulateHTTP() ? "post" : method; const init = assign({ method: requestMethod, headers, body }, this.options().Fetch); const timeout = this.request.timeout(); let timer = null; let canceled = false; if (timeout) { timer = setTimeout(() => { canceled = true; const error = createTimeoutError(`Timeout (${timeout}ms)`); this.dispatchClientError(error.message, error); }, timeout); } fetch2(this.request.url(), init).then((fetchResponse) => { if (canceled) { return; } timer && clearTimeout(timer); let responseData; if (this.request.isBinary()) { if (typeof fetchResponse.buffer === "function") { responseData = fetchResponse.buffer(); } else { responseData = fetchResponse.arrayBuffer(); } } else { responseData = fetchResponse.text(); } responseData.then((data) => { this.dispatchResponse(this.createResponse(fetchResponse, data)); }); }).catch((error) => { if (canceled) { return; } timer && clearTimeout(timer); this.dispatchClientError(error.message, error); }); } createResponse(fetchResponse, data) { const status = fetchResponse.status; const responseHeaders = {}; fetchResponse.headers.forEach((value, key) => { responseHeaders[key] = value; }); return new response_default(this.request, status, data, responseHeaders); } }; // src/index.ts var _process2 = null; var defaultGateway = null; try { _process2 = eval( 'typeof __TEST_SERVICE_WORKER__ === "undefined" && typeof process === "object" ? process : undefined' ); } catch (e) { } if (typeof XMLHttpRequest !== "undefined") { defaultGateway = XHR; } else if (typeof _process2 !== "undefined") { defaultGateway = void 0; } else if (typeof self !== "undefined") { defaultGateway = Fetch; } configs.gateway = defaultGateway; export { Response, configs, forge as default, forge, setContext, version }; //# sourceMappingURL=mappersmith.production.min.mjs.map