mt-mappersmith
Version:
It is a lightweight rest client for node.js and the browser
1,166 lines (1,153 loc) • 38.2 kB
JavaScript
// 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