UNPKG

konfig-axios-fetch-adapter

Version:
243 lines 9.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const axios_1 = require("axios"); const settle_1 = __importDefault(require("./settle")); const buildURL_1 = __importDefault(require("./helpers/buildURL")); const buildFullPath_1 = __importDefault(require("./core/buildFullPath")); const SafeReadableStream = typeof ReadableStream !== "undefined" ? ReadableStream : require("web-streams-polyfill").ReadableStream; const SafeHeaders = typeof Headers !== "undefined" ? Headers : require("node-fetch").Headers; const SafeRequest = typeof Request !== "undefined" ? Request : require("node-fetch").Request; const safeFetch = typeof fetch !== "undefined" ? fetch : require("node-fetch"); const utils_1 = require("./utils"); /** * - Create a request object * - Get response body * - Check if timeout */ const fetchAdapter = (config) => __awaiter(void 0, void 0, void 0, function* () { if (config.headers) { // In axios version >= 1.0.0, a falsy value ("undefined") for Content-Type // is automatically set for some reason This can cause issues if the server // expects Content-Type header to be something meaningful. To avoid this, // we unset Content-Type if it's falsy (e.g. "undefined"). if (!config.headers.getContentType()) { config.headers.clear("Content-Type"); } } const request = createRequest(config); const promiseChain = [getResponse(request, config)]; if (config.timeout && config.timeout > 0) { promiseChain.push(new Promise((res) => { setTimeout(() => { const message = config.timeoutErrorMessage ? config.timeoutErrorMessage : "timeout of " + config.timeout + "ms exceeded"; res(createError(message, config, "ECONNABORTED", request)); }, config.timeout); })); } const data = yield Promise.race(promiseChain); return new Promise((resolve, reject) => { if (data instanceof Error) { reject(data); } else { (0, settle_1.default)(resolve, reject, data); } }); }); /** * Fetch API stage two is to get response body. This funtion tries to retrieve * response body based on response's type */ function getResponse(request, config) { return __awaiter(this, void 0, void 0, function* () { let stageOne; try { stageOne = yield safeFetch(request); } catch (e) { if (e instanceof Error) return createError(e.message, config, "ERR_NETWORK", request); return createError("Network Error", config, "ERR_NETWORK", request); } let data; if (stageOne.status >= 200 && stageOne.status !== 204) { switch (config.responseType) { case "arraybuffer": data = yield stageOne.arrayBuffer(); break; case "blob": data = yield stageOne.blob(); break; case "json": data = yield stageOne.json(); break; case "stream": // Check if the stream is a NodeJS stream or a browser stream. // @ts-ignore - TS doesn't know about `pipe` on streams. const isNodeJsStream = typeof stageOne.body.pipe === "function"; data = isNodeJsStream ? nodeToWebReadableStream(stageOne.body) : stageOne.body; break; default: data = yield stageOne.text(); break; } } function nodeToWebReadableStream(nodeReadable) { return new SafeReadableStream({ start(controller) { nodeReadable.on("data", (chunk) => { controller.enqueue(chunk); }); nodeReadable.on("end", () => { controller.close(); }); nodeReadable.on("error", (err) => { controller.error(err); }); }, }); } const response = { data, status: stageOne.status, statusText: stageOne.statusText, headers: Object.fromEntries(Object.entries(stageOne.headers)), // Make a copy of headers config: Object.assign(Object.assign({}, config), { headers: new axios_1.AxiosHeaders(config.headers) }), request, }; return response; }); } /** * This function will create a Request object based on configuration's axios */ function createRequest(config) { var _a; const headers = config.headers ? new SafeHeaders(Object.keys(config.headers).reduce((obj, key) => { if (config.headers === undefined) throw Error(); obj[key] = String(config.headers[key]); return obj; }, {})) : new SafeHeaders({}); // HTTP basic authentication if (config.auth) { const username = config.auth.username || ""; const password = config.auth.password ? decodeURI(encodeURIComponent(config.auth.password)) : ""; headers.set("Authorization", `Basic ${Buffer.from(username + ":" + password).toString("base64")}`); } const method = (_a = config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase(); const options = { headers: headers, method, }; if (method !== "GET" && method !== "HEAD") { options.body = config.data; // In these cases the browser will automatically set the correct Content-Type, // but only if that header hasn't been set yet. So that's why we're deleting it. if ((0, utils_1.isFormData)(options.body) && (0, utils_1.isStandardBrowserEnv)()) { headers.delete("Content-Type"); } } // This config is similar to XHR’s withCredentials flag, but with three available values instead of two. // So if withCredentials is not set, default value 'same-origin' will be used if (!(0, utils_1.isUndefined)(config.withCredentials)) { options.credentials = config.withCredentials ? "include" : "omit"; } const fullPath = (0, buildFullPath_1.default)(config.baseURL, config.url); const url = (0, buildURL_1.default)(fullPath, config.params, config.paramsSerializer); // Expected browser to throw error if there is any wrong configuration value return new SafeRequest(url, options); } /** * Note: * * From version >= 0.27.0, createError function is replaced by AxiosError class. * So I copy the old createError function here for backward compatible. * * * * Create an Error with the specified message, config, error code, request and response. * * @param {string} message The error message. * @param {Object} config The config. * @param {string} [code] The error code (for example, 'ECONNABORTED'). * @param {Object} [request] The request. * @param {Object} [response] The response. * @returns {Error} The created error. */ function createError(message, config, code, request, response) { if (axios_1.AxiosError && typeof axios_1.AxiosError === "function") { return new axios_1.AxiosError(message, axios_1.AxiosError[code], config, request); } var error = new Error(message); return enhanceError(error, config, code, request, response); } /** * * Note: * * This function is for backward compatible. * * * Update an Error with the specified config, error code, and response. * * @param {Error} error The error to update. * @param {Object} config The config. * @param {string} [code] The error code (for example, 'ECONNABORTED'). * @param {Object} [request] The request. * @param {Object} [response] The response. * @returns {Error} The error. */ function enhanceError(error, config, code, request, response) { error.config = config; if (code) { error.code = code; } error.request = request; error.response = response; error.isAxiosError = true; error.toJSON = function toJSON() { return { // Standard message: this.message, name: this.name, // Microsoft description: this.description, number: this.number, // Mozilla fileName: this.fileName, lineNumber: this.lineNumber, columnNumber: this.columnNumber, stack: this.stack, // Axios config: this.config, code: this.code, status: this.response && this.response.status ? this.response.status : null, }; }; return error; } exports.default = fetchAdapter; //# sourceMappingURL=index.js.map