konfig-axios-fetch-adapter
Version:
Fetch adapter for axios written in TypeScript
243 lines • 9.8 kB
JavaScript
;
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