UNPKG

@adyen/api-library

Version:

The Adyen API Library for NodeJS enables you to work with Adyen APIs.

284 lines 14.7 kB
"use strict"; /* * ###### * ###### * ############ ####( ###### #####. ###### ############ ############ * ############# #####( ###### #####. ###### ############# ############# * ###### #####( ###### #####. ###### ##### ###### ##### ###### * ###### ###### #####( ###### #####. ###### ##### ##### ##### ###### * ###### ###### #####( ###### #####. ###### ##### ##### ###### * ############# ############# ############# ############# ##### ###### * ############ ############ ############# ############ ##### ###### * ###### * ############# * ############ * Adyen NodeJS API Library * Copyright (c) 2020 Adyen B.V. * This file is open source and available under the MIT license. * See the LICENSE file for more info. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const http_1 = require("http"); const https_1 = require("https"); const https_proxy_agent_1 = require("https-proxy-agent"); const fs = __importStar(require("fs")); const url_1 = require("url"); const libraryConstants_1 = __importDefault(require("../constants/libraryConstants")); const httpClientException_1 = __importDefault(require("./httpClientException")); const apiException_1 = __importDefault(require("../services/exception/apiException")); const apiConstants_1 = require("../constants/apiConstants"); const checkServerIdentity_1 = __importDefault(require("../helpers/checkServerIdentity")); class HttpURLConnectionClient { /** * Sends an HTTP request to the specified endpoint with the provided JSON payload and configuration. * * This method sets up request headers, including authentication (API key or basic auth), content type, * and timeout. If a certificate path is provided in the config, it installs a certificate verifier. * Throws an ApiException when an error occurs (invalid API key, API error response, etc.). * * @param endpoint - The URL to which the request will be sent. * @param json - The JSON string to be sent as the request body. * @param config - The configuration object containing authentication, timeout, and certificate details. * @param isApiRequired - Indicates whether an API key is required for this request. * @param requestOptions - Additional options for the HTTP request, such as headers and timeout. * @returns A promise that resolves with the response body as a string. * @throws {ApiException} when an error occurs */ request(endpoint, json, config, isApiRequired, requestOptions) { var _a, _b; (_a = requestOptions.headers) !== null && _a !== void 0 ? _a : (requestOptions.headers = {}); requestOptions.timeout = config.connectionTimeoutMillis; if (config.certificatePath) { this.installCertificateVerifier(config.certificatePath); } const apiKey = config.apiKey; if (isApiRequired && !apiKey) { return Promise.reject(new apiException_1.default("Invalid X-API-Key was used", 401)); } if (apiKey) { requestOptions.headers[apiConstants_1.ApiConstants.API_KEY] = apiKey; } else { const authString = `${config.username}:${config.password}`; const authStringEnc = Buffer.from(authString, "utf8").toString("base64"); requestOptions.headers.Authorization = `Basic ${authStringEnc}`; } requestOptions.headers[apiConstants_1.ApiConstants.CONTENT_TYPE] = apiConstants_1.ApiConstants.APPLICATION_JSON_TYPE; const httpConnection = this.createRequest(endpoint, requestOptions, config.applicationName); return this.doRequest(httpConnection, json, (_b = config.enable308Redirect) !== null && _b !== void 0 ? _b : true); } // create Request object createRequest(endpoint, requestOptions, applicationName) { if (!requestOptions.headers) { requestOptions.headers = {}; } const url = new url_1.URL(endpoint); requestOptions.hostname = url.hostname; requestOptions.protocol = url.protocol; requestOptions.port = url.port; requestOptions.path = url.pathname; if (requestOptions.params) { requestOptions.path += "?" + new url_1.URLSearchParams(requestOptions.params).toString(); } if (requestOptions && requestOptions.idempotencyKey) { requestOptions.headers[apiConstants_1.ApiConstants.IDEMPOTENCY_KEY] = requestOptions.idempotencyKey; delete requestOptions.idempotencyKey; } if (this.proxy && this.proxy.host) { const { host, port, ...options } = this.proxy; requestOptions.agent = new https_proxy_agent_1.HttpsProxyAgent({ host, port: port || 443, ...options }); } else { requestOptions.agent = new https_1.Agent(this.agentOptions); } requestOptions.headers["Cache-Control"] = "no-cache"; if (!requestOptions.method) { requestOptions.method = apiConstants_1.ApiConstants.METHOD_POST; } requestOptions.headers[apiConstants_1.ApiConstants.ACCEPT_CHARSET] = HttpURLConnectionClient.CHARSET; // user-agent header const libInfo = `${libraryConstants_1.default.LIB_NAME}/${libraryConstants_1.default.LIB_VERSION}`; requestOptions.headers[apiConstants_1.ApiConstants.USER_AGENT] = applicationName ? `${applicationName} ${libInfo}` : libInfo; // custom headers requestOptions.headers[apiConstants_1.ApiConstants.ADYEN_LIBRARY_NAME] = libraryConstants_1.default.LIB_NAME; requestOptions.headers[apiConstants_1.ApiConstants.ADYEN_LIBRARY_VERSION] = libraryConstants_1.default.LIB_VERSION; // create a new ClientRequest object const req = (0, https_1.request)(requestOptions); // set the timeout on the ClientRequest instance if (requestOptions.timeout) { req.setTimeout(requestOptions.timeout); } return req; } /** * Invoke the request * @param connectionRequest The request * @param json The payload * @param allowRedirect Whether to allow redirect upon 308 response status code * @returns Promise with the API response */ doRequest(connectionRequest, json, allowRedirect) { return new Promise((resolve, reject) => { connectionRequest.flushHeaders(); connectionRequest.on("response", (res) => { const response = { statusCode: res.statusCode, headers: res.headers, body: "" }; // define default exception (in case of error during the handling of the response) const getException = (responseBody) => new httpClientException_1.default({ message: `HTTP Exception: ${response.statusCode}. ${res.statusMessage}`, statusCode: response.statusCode, errorCode: undefined, responseHeaders: response.headers, responseBody, }); let exception = getException(response.body); res.on("data", (chunk) => { response.body += chunk; }); res.on("end", () => { if (!res.complete) { reject(new Error("The connection was terminated while the message was still being sent")); } // Handle 308 redirect (when enabled) if (allowRedirect && res.statusCode && res.statusCode === 308) { const location = res.headers["location"]; if (location) { // follow the redirect try { const url = new url_1.URL(location); if (!this.verifyLocation(location)) { return reject(new Error(`Redirect to host ${url.hostname} is not allowed.`)); } const newRequestOptions = { hostname: url.hostname, port: url.port || (url.protocol === "https:" ? 443 : 80), path: url.pathname + url.search, method: connectionRequest.method, headers: connectionRequest.getHeaders(), protocol: url.protocol, }; const clientRequestFn = url.protocol === "https:" ? https_1.request : http_1.request; const redirectedRequest = clientRequestFn(newRequestOptions); // To prevent potential redirect loops, disable further redirects for this new request. const redirectResponse = this.doRequest(redirectedRequest, json, false); return resolve(redirectResponse); } catch (err) { return reject(err); } } else { return reject(new Error(`Redirect status ${res.statusCode} - Could not find location in response headers`)); } } if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) { // API error handling try { const formattedData = JSON.parse(response.body); const isApiError = "status" in formattedData; const isRequestError = "errors" in formattedData; if (isApiError) { // Adyen API has returned an error exception = new httpClientException_1.default({ message: `HTTP Exception: ${formattedData.status}. ${res.statusMessage}: ${formattedData.message}`, statusCode: formattedData.status, errorCode: formattedData.errorCode, responseHeaders: res.headers, responseBody: response.body, apiError: formattedData, }); } else if (isRequestError) { exception = new Error(response.body); } else { exception = getException(response.body); } } catch (e) { // parsing error exception = new httpClientException_1.default({ message: `HTTP Exception: ${response.statusCode}. Error ${e.message} while parsing response: ${response.body}`, statusCode: response.statusCode, responseHeaders: response.headers, responseBody: response.body, }); } return reject(exception); } resolve(response.body); }); res.on("error", reject); }); connectionRequest.on("timeout", () => { connectionRequest.abort(); }); connectionRequest.on("error", (e) => reject(new apiException_1.default(e.message))); connectionRequest.write(Buffer.from(json)); connectionRequest.end(); }); } installCertificateVerifier(terminalCertificatePath) { try { if (terminalCertificatePath == "unencrypted") { this.agentOptions = { rejectUnauthorized: false }; } else { const certificateInput = fs.readFileSync(terminalCertificatePath); this.agentOptions = { ca: certificateInput, checkServerIdentity: checkServerIdentity_1.default, }; } } catch (e) { const message = e instanceof Error ? e.message : "undefined"; return Promise.reject(new httpClientException_1.default({ message: `Error loading certificate from path: ${message}` })); } } verifyLocation(location) { try { const url = new url_1.URL(location); // allow-list of trusted domains (*.adyen.com, *.adyenpayments.com) const allowedHostnameRegex = /(\.adyen\.com|\.adyenpayments\.com)$/i; return allowedHostnameRegex.test(url.hostname); } catch (e) { return false; } } } HttpURLConnectionClient.CHARSET = "utf-8"; exports.default = HttpURLConnectionClient; //# sourceMappingURL=httpURLConnectionClient.js.map