UNPKG

@team-internet/apiconnector

Version:
519 lines (518 loc) 16.6 kB
import fetch from "cross-fetch"; import { Logger } from "./logger.js"; import { Response } from "./response.js"; import { ResponseTemplateManager } from "./responsetemplatemanager.js"; import { fixedURLEnc, SocketConfig } from "./socketconfig.js"; import { toAscii } from "idna-uts46-hx"; export const CNR_CONNECTION_URL_PROXY = "http://127.0.0.1/api/call.cgi"; export const CNR_CONNECTION_URL_LIVE = "https://api.rrpproxy.net/api/call.cgi"; export const CNR_CONNECTION_URL_OTE = "https://api-ote.rrpproxy.net/api/call.cgi"; const rtm = ResponseTemplateManager.getInstance(); /** * APIClient class */ export class APIClient { /** * API connection timeout setting */ static socketTimeout = 300000; /** * User Agent string */ ua; /** * API connection url */ socketURL; /** * Object covering API connection data */ socketConfig; /** * activity flag for debug mode */ debugMode; /** * additional connection settings */ curlopts; /** * logger function for debug mode */ logger; /** * set sub user account */ subUser; /** * set sub user account role seperater */ roleSeparator = ":"; constructor() { this.ua = ""; this.socketURL = ""; this.debugMode = false; this.setURL(CNR_CONNECTION_URL_LIVE); this.socketConfig = new SocketConfig(); this.useLIVESystem(); this.curlopts = {}; this.logger = null; this.subUser = ""; this.roleSeparator = ":"; this.setDefaultLogger(); } /** * set custom logger to use instead of default one * @param customLogger * @returns Current APIClient instance for method chaining */ setCustomLogger(customLogger) { this.logger = customLogger; return this; } /** * set default logger to use * @returns Current APIClient instance for method chaining */ setDefaultLogger() { this.logger = new Logger(); return this; } /** * Enable Debug Output to STDOUT * @returns Current APIClient instance for method chaining */ enableDebugMode() { this.debugMode = true; return this; } /** * Disable Debug Output * @returns Current APIClient instance for method chaining */ disableDebugMode() { this.debugMode = false; return this; } /** * Get the API connection url that is currently set * @returns API connection url currently in use */ getURL() { return this.socketURL; } /** * Possibility to customize default user agent to fit your needs * @param str user agent label * @param rv revision of user agent * @param modules further modules to add to user agent string, format: ["<mod1>/<rev>", "<mod2>/<rev>", ... ] * @returns Current APIClient instance for method chaining */ setUserAgent(str, rv, modules = []) { const mods = modules.length ? " " + modules.join(" ") : ""; this.ua = `${str} ` + `(${process.platform}; ${process.arch}; rv:${rv})` + mods + ` node-sdk/${this.getVersion()} ` + `node/${process.version}`; return this; } /** * Get the User Agent * @returns User Agent string */ getUserAgent() { if (!this.ua.length) { this.ua = `NODE-SDK (${process.platform}; ${process.arch}; rv:${this.getVersion()}) ` + `node/${process.version}`; } return this.ua; } /** * Set the proxy server to use for API communication * @param proxy proxy server to use for communicatio * @returns Current APIClient instance for method chaining */ setProxy(proxy) { this.curlopts.proxy = proxy; return this; } /** * Get the proxy server configuration * @returns proxy server configuration value or null if not set */ getProxy() { if (Object.prototype.hasOwnProperty.call(this.curlopts, "proxy")) { return this.curlopts.proxy; } return null; } /** * Set the referer to use for API communication * @param referer Referer * @returns Current APIClient instance for method chaining */ setReferer(referer) { this.curlopts.referer = referer; return this; } /** * Get the referer configuration * @returns referer configuration value or null if not set */ getReferer() { if (Object.prototype.hasOwnProperty.call(this.curlopts, "referer")) { return this.curlopts.referer; } return null; } /** * Get the current module version * @returns module version */ getVersion() { return "8.0.2"; } /** * Apply session data (session id and system entity) to given client request session * @param session ClientRequest session instance * @returns Current APIClient instance for method chaining */ saveSession(session) { session.socketcfg = { login: this.socketConfig.getLogin(), session: this.socketConfig.getSession(), }; return this; } /** * Use existing configuration out of ClientRequest session * to rebuild and reuse connection settings * @param session ClientRequest session instance * @returns Current APIClient instance for method chaining */ reuseSession(session) { if (!session || !session.socketcfg || !session.socketcfg.login || !session.socketcfg.session) { return this; } this.setCredentials(session.socketcfg.login); this.socketConfig.setSession(session.socketcfg.session); return this; } /** * Set another connection url to be used for API communication * @param value API connection url to set * @returns Current APIClient instance for method chaining */ setURL(value) { this.socketURL = value; return this; } /** * Set Persistent to request session id for API communication * @param value API session id * @returns Current APIClient instance for method chaining */ setPersistent() { this.socketConfig.setPersistent(); return this; } /** * Set Credentials to be used for API communication * @param uid account name * @param pw account password * @returns Current APIClient instance for method chaining */ setCredentials(uid, pw = "") { this.socketConfig.setLogin(uid); this.socketConfig.setPassword(pw); return this; } /** * Set Credentials to be used for API communication * @param uid account name * @param role role user id * @param pw role user password * @returns Current APIClient instance for method chaining */ setRoleCredentials(uid, role, pw = "") { return this.setCredentials(role ? `${uid}${this.roleSeparator}${role}` : uid, pw); } /** * Perform API login to start session-based communication * @param otp optional one time password * @returns Promise resolving with API Response */ async login() { this.setPersistent(); const rr = await this.request({}, false); this.socketConfig.setSession(""); if (rr.isSuccess()) { const col = rr.getColumn("SESSIONID"); this.socketConfig.setSession(col ? col.getData()[0] : ""); } return rr; } /** * Perform API logout to close API session in use * @returns Promise resolving with API Response */ async logout() { const rr = await this.request({ COMMAND: "StopSession", }, false); if (rr.isSuccess()) { this.socketConfig.setSession(""); } return rr; } /** * Perform API request using the given command * @param cmd API command to request * @returns Promise resolving with API Response */ async request(cmd, setUserView = true) { // set sub user id if available if (setUserView && this.subUser !== "") { cmd.SUBUSER = this.subUser; } // flatten nested api command bulk parameters let mycmd = this.flattenCommand(cmd); // auto convert umlaut names to punycode mycmd = await this.autoIDNConvert(mycmd); // request command to API const cfg = { CONNECTION_URL: this.socketURL, }; // TODO: 300s (to be sure to get an API response) const reqCfg = { // encoding: "utf8", //default for type string // gzip: true, body: this.getPOSTData(mycmd), headers: { "User-Agent": this.getUserAgent(), "Content-Type": "application/x-www-form-urlencoded" }, method: "POST", timeout: APIClient.socketTimeout, url: cfg.CONNECTION_URL, }; const proxy = this.getProxy(); if (proxy) { reqCfg.proxy = proxy; } const referer = this.getReferer(); if (referer) { reqCfg.headers.Referer = referer; } return fetch(cfg.CONNECTION_URL, reqCfg) .then(async (res) => { let error = null; let body; if (res.ok) { // res.status >= 200 && res.status < 300 body = await res.text(); } else { error = res.status + (res.statusText ? " " + res.statusText : ""); body = rtm.getTemplate("httperror").getPlain(); } const rr = new Response(body, mycmd, cfg); if (this.debugMode && this.logger) { this.logger.log(this.getPOSTData(mycmd, true), rr, error); } return rr; }) .catch((err) => { const body = rtm.getTemplate("httperror").getPlain(); const rr = new Response(body, mycmd, cfg); if (this.debugMode && this.logger) { this.logger.log(this.getPOSTData(mycmd, true), rr, err.message); } return rr; }); } /** * Request the next page of list entries for the current list query * Useful for tables * @param rr API Response of current page * @returns Promise resolving with API Response or null in case there are no further list entries */ async requestNextResponsePage(rr) { const mycmd = rr.getCommand(); if (Object.prototype.hasOwnProperty.call(mycmd, "LAST")) { throw new Error("Parameter LAST in use. Please remove it to avoid issues in requestNextPage."); } let first = 0; if (Object.prototype.hasOwnProperty.call(mycmd, "FIRST")) { first = mycmd.FIRST; } const total = rr.getRecordsTotalCount(); const limit = rr.getRecordsLimitation(); first += limit; if (first < total) { mycmd.FIRST = first; mycmd.LIMIT = limit; return this.request(mycmd); } return null; } /** * Request all pages/entries for the given query command * @param cmd API list command to use * @returns Promise resolving with array of API Responses */ async requestAllResponsePages(cmd) { const responses = []; let rr = await this.request({ ...cmd, FIRST: 0 }); while (rr !== null) { responses.push(rr); rr = await this.requestNextResponsePage(rr); } return responses; } /** * Set a data view to a given subuser * @param uid subuser account name * @returns Current APIClient instance for method chaining */ setUserView(uid) { this.subUser = uid; return this; } /** * Reset data view back from subuser to user * @returns Current APIClient instance for method chaining */ resetUserView() { this.subUser = ""; return this; } /** * Activate High Performance Connection Setup * @see https://github.com/centralnicgroup-opensource/rtldev-middleware-node-sdk/blob/master/README.md * @returns Current APIClient instance for method chaining */ useHighPerformanceConnectionSetup() { this.setURL(CNR_CONNECTION_URL_PROXY); return this; } /** * Activate Default Connection Setup (the default) * @returns Current APIClient instance for method chaining */ useDefaultConnectionSetup() { this.setURL(CNR_CONNECTION_URL_LIVE); return this; } /** * Set OT&E System for API communication * @returns Current APIClient instance for method chaining */ useOTESystem() { this.setURL(CNR_CONNECTION_URL_OTE); return this; } /** * Set LIVE System for API communication (this is the default setting) * @returns Current APIClient instance for method chaining */ useLIVESystem() { this.setURL(CNR_CONNECTION_URL_LIVE); return this; } /** * Serialize given command for POST request including connection configuration data * @param cmd API command to encode * @returns encoded POST data string */ getPOSTData(cmd, secured = false) { let data = this.socketConfig.getPOSTData(); if (secured) { data = data.replace(/s_pw=[^&]+/, "s_pw=***"); } let tmp = ""; if (!(typeof cmd === "string" || cmd instanceof String)) { Object.keys(cmd).forEach((key) => { if (cmd[key] !== null && cmd[key] !== undefined) { tmp += `${key}=${cmd[key].toString().replace(/\r|\n/g, "")}\n`; } }); } else { tmp = "" + cmd; } if (secured) { tmp = tmp.replace(/PASSWORD=[^\n]+/, "PASSWORD=***"); } tmp = tmp.replace(/\n$/, ""); if (Object.keys(cmd).length > 0) { data += `${fixedURLEnc("s_command")}=${fixedURLEnc(tmp)}`; } return data.endsWith("&") ? data.slice(0, -1) : data; } /** * Flatten nested arrays in command * @param cmd api command * @returns api command with flattended parameters */ flattenCommand(cmd) { const newcmd = {}; Object.keys(cmd).forEach((key) => { const val = cmd[key]; const newKey = key.toUpperCase(); if (val !== null && val !== undefined) { if (Array.isArray(val)) { let index = 0; for (const row of val) { newcmd[`${newKey}${index}`] = (row + "").replace(/\r|\n/g, ""); index++; } } else { if (typeof val === "string" || val instanceof String) { newcmd[newKey] = val.replace(/\r|\n/g, ""); } else { newcmd[newKey] = val; } } } }); return newcmd; } /** * Auto convert API command parameters to punycode, if necessary. * @param cmd api command * @returns Promise resolving with api command with IDN values replaced to punycode */ async autoIDNConvert(cmd) { const keyPattern = /^(NAMESERVER|NS|DNSZONE)([0-9]*)$/i; const objClassPattern = /^(DOMAIN(APPLICATION|BLOCKING)?|NAMESERVER|NS|DNSZONE)$/i; const asciiPattern = /^[A-Za-z0-9.\-]+$/; const toConvert = []; const idxs = []; Object.keys(cmd).forEach((key) => { const val = cmd[key]; if ((keyPattern.test(key) || (key.toUpperCase() === "OBJECTID" && cmd.OBJECTCLASS && objClassPattern.test(cmd.OBJECTCLASS))) && !asciiPattern.test(val)) { toConvert.push(val); idxs.push(key); } }); if (toConvert.length > 0) { const convertedValues = toConvert.map((value) => toAscii(value)); convertedValues.forEach((convertedValue, idx) => { cmd[idxs[idx]] = convertedValue; }); } return cmd; } }