UNPKG

ripe-commons

Version:
1,489 lines (1,294 loc) 87.8 kB
/** * RIPE Commons (for Javascript) 0.13.0. * * Copyright (c) 2014-2023 Platforme International. * * This source code is licensed under the Apache 2.0 license found in the * LICENSE file in the root directory of this source tree. */ import * as fs from 'fs'; import { join, normalize as normalize$1, resolve, dirname } from 'path'; import { env } from 'process'; import fetch from 'node-fetch'; /** * Splits the provided array (or iterable that uses `slice`) into * array chunks of the provided size. * * @param {Array} array The array that is going to be split into * chunks of the provided size. * @param {Number} size The size of each chunk as an integer. * @returns {Array} An array containing arrays of the provided size * with the contents of each chunk. */ const splitArray = (array, size = 1) => { const result = []; for (let index = 0; index < array.length; index += size) { const chunk = array.slice(index, index + size); result.push(chunk); } return result; }; /** * Yonius 0.11.7. * * Copyright (c) 2008-2022 Hive Solutions Lda. * * This source code is licensed under the Apache 2.0 license found in the * LICENSE file in the root directory of this source tree. */ const verify$1 = function( condition, message = null, code = null, exception = null, kwargs = {}, safeKeys = ["message"] ) { if (condition) return; message = message || "Verification failed"; const Exception = exception || Error; kwargs = Object.assign({}, kwargs); if (message !== null && message !== undefined) kwargs.message = message; if (code !== null && message !== undefined) kwargs.code = code; const throwable = new Exception(kwargs.message || undefined); throwable.kwargs = kwargs; for (const [key, value] of Object.entries(kwargs)) { if (safeKeys.includes(key) && throwable[key] !== undefined) { continue; } throwable[key] = value; } throw throwable; }; const globals$1 = typeof global === "undefined" ? typeof window === "undefined" ? typeof self === "undefined" ? {} : self : window : global; globals$1.CONFIGS = globals$1.CONFIGS === undefined ? {} : globals$1.CONFIGS; globals$1.CONFIG_F = globals$1.CONFIG_F === undefined ? [] : globals$1.CONFIG_F; globals$1.HOMES = globals$1.HOMES === undefined ? [] : globals$1.HOMES; globals$1.LOADED = globals$1.LOADED === undefined ? false : globals$1.LOADED; class YoniusError extends Error { constructor(message, code = 500) { super(message); this.name = this.constructor.name; this.code = code; } get isClient() { return Math.floor(this.code / 100) === 4; } get isServer() { return Math.floor(this.code / 100) === 5; } } class OperationalError extends YoniusError { constructor(message = "Operational error", code = 500) { super(message, code); } } const buildGetAgent$1 = (AgentHttp, AgentHttps, set = true, options = {}) => { const httpAgent = new AgentHttp({ keepAlive: options.keepAlive === undefined ? true : options.keepAlive, keepAliveMsecs: options.keepAliveMsecs || 120000, timeout: options.timeout || 60000, scheduling: options.scheduling || "fifo" }); const httpsAgent = new AgentHttps({ keepAlive: options.keepAlive === undefined ? true : options.keepAlive, keepAliveMsecs: options.keepAliveMsecs || 120000, timeout: options.timeout || 60000, scheduling: options.scheduling || "fifo" }); const getAgent = parsedURL => (parsedURL.protocol === "http:" ? httpAgent : httpsAgent); if (set) globals$1.getAgent = getAgent; return getAgent; }; /** * Tries to patch the global environment with a proper `getAgent` * function that can handle HTTP and HTTP connection polling. * * This can only be performed in a node.js environment (uses `require`). * * @returns {Function} The `getAgent` function that has just been * built and set in the globals. */ const patchAgent$1 = () => { if (typeof require !== "function") return; if (globals$1.getAgent) return; let http, https; try { http = require("http"); https = require("https"); } catch (err) { return; } if (!http || !https) return; if (!http.Agent || !https.Agent) return; return buildGetAgent$1(http.Agent, https.Agent, true); }; // patches the global agent if possible, using the // global dynamic require statements patchAgent$1(); /** * RIPE ID API (for Javascript) 0.7.2. * * Copyright (c) 2014-2022 Platforme International. * * This source code is licensed under the Apache 2.0 license found in the * LICENSE file in the root directory of this source tree. */ const AccountAPI = superclass => class extends superclass { async selfAccount() { const url = this.baseUrl + "accounts/me"; const contents = await this.get(url); return contents; } async updateSelfAccount(account) { const url = this.baseUrl + "accounts/me"; account = Object.assign({}, account); if (account.meta_extra) { account.meta_extra = JSON.stringify(account.meta_extra); } const contents = await this.put(url, { dataM: account }); return contents; } async aclAccount() { const url = this.baseUrl + "accounts/acl"; const contents = await this.get(url); return contents; } async listAccounts(options = {}) { const url = this.baseUrl + "accounts"; const contents = await this.get(url, options); return contents; } async createAccount(account, sendEmail = false) { const url = this.baseUrl + "accounts"; const contents = await this.post(url, { params: { send_email: sendEmail }, dataJ: account }); return contents; } async createAccountMultipart(account, sendEmail = false) { const url = this.baseUrl + "accounts"; const contents = await this.post(url, { params: { send_email: sendEmail }, dataM: account }); return contents; } async getAccount(username) { const url = this.baseUrl + `accounts/${username}`; const contents = await this.get(url); return contents; } async notifyAccount(username, engine, payload) { payload = typeof payload === "string" ? { subject: payload, title: payload, contents: payload } : payload; const url = this.baseUrl + `accounts/${username}/notify`; const contents = await this.put(url, { dataJ: Object.apply({}, payload, { engine: engine }) }); return contents; } async recoverAccount(username) { const url = this.baseUrl + `accounts/${username}/recover`; const contents = await this.post(url, { kwargs: { auth: false } }); return contents; } showUrlAccount(username) { return this.loginUrl + `accounts/${username}`; } avatarUrlAccount( username, { size = undefined, width = undefined, height = undefined, cache = undefined } = {} ) { const baseUrl = this.loginUrl + `accounts/${username}/avatar`; const params = []; if (size !== undefined) params.push(["size", size]); if (width !== undefined) params.push(["width", width]); if (height !== undefined) params.push(["height", height]); if (cache !== undefined) params.push(["cache", cache ? "1" : "0"]); const extraUrl = this._generateExtraUrl(params); return `${baseUrl}${extraUrl}`; } }; /** * Yonius 0.8.3. * * Copyright (c) 2008-2021 Hive Solutions Lda. * * This source code is licensed under the Apache 2.0 license found in the * LICENSE file in the root directory of this source tree. */ class Observable { constructor() { this.callbacks = {}; } bind(event, callback) { const callbacks = this.callbacks[event] || []; callbacks.push(callback); this.callbacks[event] = callbacks; return callback; } unbind(event, callback) { const callbacks = this.callbacks[event] || []; if (!callback) { delete this.callbacks[event]; return; } const index = callbacks.indexOf(callback); if (index === -1) { return; } callbacks.splice(index, 1); this.callbacks[event] = callbacks; } trigger(event) { const callbacks = this.callbacks[event] || []; const results = []; for (const callback of callbacks) { const result = callback.apply(this, Array.prototype.slice.call(arguments, 1)); result !== undefined && result !== null && results.push(result); } return Promise.all(results); } } const verify = function( condition, message = null, code = null, exception = null, kwargs = {} ) { if (condition) return; message = message || "Verification failed"; const Exception = exception || Error; kwargs = Object.assign({}, kwargs); if (message !== null && message !== undefined) kwargs.message = message; if (code !== null && message !== undefined) kwargs.code = code; const throwable = new Exception(kwargs.message || undefined); throwable.kwargs = kwargs; for (const [key, value] of Object.entries(kwargs)) { if (throwable[key] !== undefined) continue; throwable[key] = value; } throw throwable; }; const verifyMany = function( sequence, message = null, code = null, exception = null, kwargs = {} ) { sequence.forEach(element => { verify(element, message, code, exception, kwargs); }); }; let HOME_DIR = null; const pathExists = async function(path) { try { await fs.promises.access(path); } catch (error) { return false; } return true; }; const expandUser = function(path) { if (!path) return path; if (path === "~") return _homeDir(); if (path.slice(0, 2) !== "~/") return path; return join(HOME_DIR, path.slice(2)); }; const getEnv = function(name) { // eslint-disable-next-line no-undef if (typeof Deno !== "undefined") return Deno.env.get(name); return env[name]; }; const getEnvObject = function() { // eslint-disable-next-line no-undef if (typeof Deno !== "undefined") return Deno.env.toObject(); return env; }; const _homeDir = function() { if (HOME_DIR !== null) return HOME_DIR; const isWindows = Boolean(typeof process !== "undefined" && process.platform === "win32"); HOME_DIR = getEnv(isWindows ? "USERPROFILE" : "HOME") || "/"; return HOME_DIR; }; const FILE_NAME = "yonius.json"; const HOME_FILE = "~/.home"; const IMPORT_NAMES = ["$import", "$include", "$IMPORT", "$INCLUDE"]; const CASTS = { int: v => (typeof v === "number" ? v : parseInt(v)), float: v => (typeof v === "number" ? v : parseFloat(v)), bool: v => (typeof v === "boolean" ? v : ["1", "true", "True"].includes(v)), list: v => (Array.isArray(v) ? v : v.split(";")), tuple: v => (Array.isArray(v) ? v : v.split(";")) }; const globals = typeof global === "undefined" ? typeof window === "undefined" ? typeof self === "undefined" ? {} : self : window : global; globals.CONFIGS = globals.CONFIGS === undefined ? {} : globals.CONFIGS; globals.CONFIG_F = globals.CONFIG_F === undefined ? [] : globals.CONFIG_F; globals.HOMES = globals.HOMES === undefined ? [] : globals.HOMES; globals.LOADED = globals.LOADED === undefined ? false : globals.LOADED; const conf = function(name, fallback = undefined, cast = null, ctx = null) { const configs = ctx ? ctx.configs : globals.CONFIGS; cast = _castR(cast); let value = configs[name] === undefined ? fallback : configs[name]; if (cast && value !== undefined && value !== null) value = cast(value); return value; }; const load$1 = async function( names = [FILE_NAME], path = null, encoding = "utf-8", force = false, ctx = null ) { if (globals.LOADED && !force) return; let paths = []; const homes = await getHomes(); for (const home of homes) { paths = paths.concat([join(home), join(home, ".config")]); } paths.push(path); for (const path of paths) { for (const name of names) { await loadFile(name, path, encoding, ctx); } } await loadEnv(ctx); globals.LOADED = true; }; const loadFile = async function( name = FILE_NAME, path = null, encoding = "utf-8", ctx = null ) { const configs = ctx ? ctx.configs : globals.CONFIGS; const configF = ctx ? ctx.configF : globals.CONFIG_F; let key; let value; let exists; let filePath; if (path) path = normalize$1(path); if (path) filePath = join(path, name); else filePath = name; filePath = resolve(filePath); filePath = normalize$1(filePath); const basePath = dirname(filePath); exists = await pathExists(filePath); if (!exists) return; exists = configF.includes(filePath); if (exists) configF.splice(configF.indexOf(filePath), 1); configF.push(filePath); const data = await fs.promises.readFile(filePath, { encoding: encoding }); const dataJ = JSON.parse(data); await _loadIncludes(basePath, dataJ, encoding); for ([key, value] of Object.entries(dataJ)) { if (!_isValid(key)) continue; configs[key] = value; } }; const loadEnv = async function(ctx = null) { const env = getEnvObject(); const configs = ctx ? ctx.configs : globals.CONFIGS; if (env === undefined || env === null) return; Object.entries(env).forEach(function([key, value]) { configs[key] = value; }); }; const getHomes = async function( filePath = HOME_FILE, fallback = "~", encoding = "utf-8", forceDefault = false ) { if (globals.HOMES.length > 0) return globals.HOMES; const env = getEnvObject(); globals.HOMES = env.HOMES === undefined ? null : env.HOMES; globals.HOMES = globals.HOMES ? globals.HOMES.split(";") : globals.HOMES; if (globals.HOMES !== null) return globals.HOMES; fallback = expandUser(fallback); fallback = normalize$1(fallback); globals.HOMES = [fallback]; filePath = expandUser(filePath); filePath = normalize$1(filePath); const exists = await pathExists(filePath); if (!exists) return globals.HOMES; if (!forceDefault) globals.HOMES.splice(0, globals.HOMES.length); let data = await fs.promises.readFile(filePath, { encoding: encoding }); data = data.trim(); let paths = data.split(/\r?\n/); paths = paths.map(v => v.trim()); for (let path of paths) { path = path.trim(); if (!path) continue; path = expandUser(path); path = normalize$1(path); globals.HOMES.push(path); } return globals.HOMES; }; const _castR = function(cast) { return CASTS[cast] === undefined ? cast : CASTS[cast]; }; const _loadIncludes = async function(basePath, config, encoding = "utf-8") { let includes = []; for (const alias of IMPORT_NAMES) { includes = config[alias] === undefined ? includes : config[alias]; } if (typeof includes === "string") { includes = includes.split(";"); } for (const include of includes) { await loadFile(include, basePath, encoding); } }; const _isValid = function(key) { if (IMPORT_NAMES.includes(key)) return false; return true; }; class MixinBuilder { constructor(superclass) { this.superclass = superclass; } with(...mixins) { return mixins.reduce((c, mixin) => mixin(c), this.superclass); } } const mix = superclass => new MixinBuilder(superclass); /** * Encodes the multiple values as and encoded URI component, the * values can be wither defined as an array (order is preserved) * or as an object (where sequence order is not preserved). * * The value of each item can be either a primitive type or a sequence * in case it's of sequence the values are going to be encoded as * multiple parameters separated by the '&' character. * * @param {(Array|Object[])} values The values to be encoded as an * URI component (like GET params). * @returns {String} A string with the query encoded values. */ const urlEncode = function(values) { // constructs the parts array that is going to // store the multiple and values const parts = []; // in case the provided value is not an array // then assumes it's an object and retrieve entries if (!Array.isArray(values)) { values = Object.entries(values); } // iterates over the complete set of pairs available // from the key value pairs to be able to encode them // properly, notice that the values themselves can be // sequences allowing multiple repetition of key values.forEach(([key, value]) => { if (!Array.isArray(value)) { value = [value]; } const keyEncoded = encodeURIComponent(key); value.forEach(_value => { if (_value === undefined || _value === null) { return; } const valueEncoded = encodeURIComponent(_value); parts.push(`${keyEncoded}=${valueEncoded}`); }); }); // joins the complete set of parts with the and // separator and then returns the final string value return parts.join("&"); }; const AUTH_ERRORS = [401, 403, 440, 499]; class API$1 extends Observable { constructor(kwargs = {}) { super(); this.kwargs = kwargs; } async build(method, url, options = {}) {} async authCallback(params, headers) {} async get(url, options = {}) { const result = await this.methodBasic("GET", url, options); return result; } async post(url, options = {}) { const result = await this.methodPayload("POST", url, options); return result; } async put(url, options = {}) { const result = await this.methodPayload("PUT", url, options); return result; } async delete(url, options = {}) { const result = await this.methodBasic("DELETE", url, options); return result; } async patch(url, options = {}) { const result = await this.methodPayload("PATCH", url, options); return result; } async options(url, options = {}) { const result = await this.methodBasic("OPTIONS", url, options); return result; } async methodBasic(method, url, options = {}) { options.params = options.params !== undefined ? options.params : {}; options.headers = options.headers !== undefined ? options.headers : {}; try { return await this._methodBasic(method, url, options); } catch (err) { if (AUTH_ERRORS.includes(err.code)) { await this.authCallback(options.params, options.headers); return await this._methodBasic(method, url, options); } else { throw err; } } } async methodPayload(method, url, options = {}) { options.params = options.params !== undefined ? options.params : {}; options.headers = options.headers !== undefined ? options.headers : {}; try { return await this._methodPayload(method, url, options); } catch (err) { if (AUTH_ERRORS.includes(err.code)) { await this.authCallback(options.params, options.headers); return await this._methodPayload(method, url, options); } else { throw err; } } } async _methodBasic(method, url, options = {}) { const params = options.params !== undefined ? options.params : {}; const headers = options.headers !== undefined ? options.headers : {}; const kwargs = options.kwargs !== undefined ? options.kwargs : {}; const handle = options.handle !== undefined ? options.handle : true; const getAgent = options.getAgent !== undefined ? options.getAgent : undefined; await this.build(method, url, { params: params, headers: headers, kwargs: kwargs }); const query = urlEncode(params || {}); if (query) url += url.includes("?") ? "&" + query : "?" + query; const response = await fetch(url, { method: method, headers: headers || {}, agent: getAgent || globals.getAgent || undefined }); const result = handle ? await this._handleResponse(response) : response; return result; } async _methodPayload(method, url, options = {}) { const params = options.params !== undefined ? options.params : {}; let headers = options.headers !== undefined ? options.headers : {}; let data = options.data !== undefined ? options.data : null; const dataJ = options.dataJ !== undefined ? options.dataJ : null; const dataM = options.dataM !== undefined ? options.dataM : null; let mime = options.mime !== undefined ? options.mime : null; const kwargs = options.kwargs !== undefined ? options.kwargs : {}; const handle = options.handle !== undefined ? options.handle : true; const getAgent = options.getAgent !== undefined ? options.getAgent : undefined; await this.build(method, url, { params: params, headers: headers, data: data, dataJ: dataJ, dataM: dataM, mime: mime, kwargs: kwargs }); const query = urlEncode(params || {}); if (data !== null) { if (query) url += url.includes("?") ? "&" + query : "?" + query; } else if (dataJ !== null) { data = JSON.stringify(dataJ); if (query) url += url.includes("?") ? "&" + query : "?" + query; mime = mime || "application/json"; } else if (dataM !== null) { if (query) url += url.includes("?") ? "&" + query : "?" + query; [mime, data] = this._encodeMultipart(dataM, mime, true); } else if (query) { data = query; mime = mime || "application/x-www-form-urlencoded"; } headers = Object.assign({}, headers); if (mime) headers["Content-Type"] = mime; const response = await fetch(url, { method: method, headers: headers || {}, body: data, agent: getAgent || global.getAgent || undefined }); const result = handle ? await this._handleResponse(response) : response; return result; } async _handleResponse(response, errorMessage = "Problem in request") { let result = null; if ( response.headers.get("content-type") && response.headers.get("content-type").toLowerCase().startsWith("application/json") ) { result = await response.json(); } else if ( response.headers.get("content-type") && response.headers.get("content-type").toLowerCase().startsWith("text/") ) { result = await response.text(); } else { result = await response.blob(); } verify(response.ok, result.error || errorMessage, response.status || 500); return result; } _encodeMultipart(fields, mime = null, doseq = false) { mime = mime || "multipart/form-data"; const boundary = this._createBoundary(fields, undefined, doseq); const encoder = new TextEncoder("utf-8"); const buffer = []; for (let [key, values] of Object.entries(fields)) { const isList = doseq && Array.isArray(values); values = isList ? values : [values]; for (let value of values) { if (value === null) continue; let header; if ( typeof value === "object" && !(value instanceof Array) && value.constructor !== Uint8Array ) { const headerL = []; let data = null; for (const [key, item] of Object.entries(value)) { if (key === "data") data = item; else headerL.push(`${key}: ${item}`); } value = data; header = headerL.join("\r\n"); } else if (value instanceof Array) { let name = null; let contents = null; let contentTypeD = null; if (value.length === 2) [name, contents] = value; else [name, contentTypeD, contents] = value; header = `Content-Disposition: form-data; name="${key}"; filename="${name}"`; if (contentTypeD) header += `\r\nContent-Type: ${contentTypeD}`; value = contents; } else { header = `Content-Disposition: form-data; name="${key}"`; value = value.constructor === Uint8Array ? value : encoder.encode(value); } buffer.push(encoder.encode("--" + boundary + "\r\n")); buffer.push(encoder.encode(header + "\r\n")); buffer.push(encoder.encode("\r\n")); buffer.push(value); buffer.push(encoder.encode("\r\n")); } } buffer.push(encoder.encode("--" + boundary + "--\r\n")); buffer.push(encoder.encode("\r\n")); const body = this._joinBuffer(buffer); const contentType = `${mime}; boundary=${boundary}`; return [contentType, body]; } _createBoundary(fields, size = 32, doseq = false) { return "Vq2xNWWHbmWYF644q9bC5T2ALtj5CynryArNQRXGYsfm37vwFKMNsqPBrpPeprFs"; } _joinBuffer(bufferArray) { const bufferSize = bufferArray.map(item => item.byteLength).reduce((a, v) => a + v, 0); const buffer = new Uint8Array(bufferSize); let offset = 0; for (const item of bufferArray) { buffer.set(item, offset); offset += item.byteLength; } return buffer; } } const buildGetAgent = (AgentHttp, AgentHttps, set = true, options = {}) => { const httpAgent = new AgentHttp({ keepAlive: options.keepAlive === undefined ? true : options.keepAlive, keepAliveMsecs: options.keepAliveMsecs || 120000, timeout: options.timeout || 60000, scheduling: options.scheduling || "fifo" }); const httpsAgent = new AgentHttps({ keepAlive: options.keepAlive === undefined ? true : options.keepAlive, keepAliveMsecs: options.keepAliveMsecs || 120000, timeout: options.timeout || 60000, scheduling: options.scheduling || "fifo" }); const getAgent = parsedURL => (parsedURL.protocol === "http:" ? httpAgent : httpsAgent); if (set) globals.getAgent = getAgent; return getAgent; }; /** * Tries to patch the global environment with a proper `getAgent` * function that can handle HTTP and HTTP connection polling. * * This can only be performed in a node.js environment (uses `require`). * * @returns {Function} The `getAgent` function that has just been * built and set in the globals. */ const patchAgent = () => { if (typeof require !== "function") return; if (globals.getAgent) return; let http, https; try { http = require("http"); https = require("https"); } catch (err) { return; } if (!http || !https) return; if (!http.Agent || !https.Agent) return; return buildGetAgent(http.Agent, https.Agent, true); }; // patches the global agent if possible, using the // global dynamic require statements patchAgent(); class OAuthAPI extends API$1 {} class OAuth2API extends OAuthAPI { constructor(kwargs = {}) { super(kwargs); this.accessToken = null; } async build(method, url, options = {}) { await super.build(method, url, options); const params = options.params !== undefined ? options.params : {}; const headers = options.headers !== undefined ? options.headers : {}; const kwargs = options.kwargs !== undefined ? options.kwargs : {}; const token = kwargs.token === undefined ? this.tokenDefault : kwargs.token; delete kwargs.token; if (token && this.oauthTypes.includes("param")) { params[this.oauthParam] = this.getAccessToken(); } if (token && this.oauthTypes.includes("header")) { headers.Authorization = `Bearer ${this.getAccessToken()}`; } } getAccessToken() { if (this.accessToken) return this.accessToken; throw new Error("No access token found must re-authorize"); } get oauthTypes() { return ["param", "header"]; } get oauthParam() { return "access_token"; } get tokenDefault() { return true; } } const load = async function() { await load$1(); }; const RoleAPI = superclass => class extends superclass { async listRoles(options = {}) { const url = this.baseUrl + "roles"; const contents = await this.get(url, options); return contents; } async getRole(name) { const url = this.baseUrl + `roles/${name}`; const contents = await this.get(url); return contents; } }; const SecretAPI = superclass => class extends superclass { async createSecret(secret) { const url = this.baseUrl + "secrets"; const contents = await this.post(url, { dataJ: secret }); return contents; } async getSecret(name) { const url = this.baseUrl + `secrets/${name}`; const contents = await this.get(url); return contents; } }; const TokenAPI = superclass => class extends superclass { async issueToken() { const url = this.baseUrl + "tokens/issue"; const contents = await this.post(url); return contents; } async redeemToken(token) { const url = this.baseUrl + "tokens/redeem"; const contents = await this.post(url, { params: { token: token }, kwargs: { token: false, auth: false } }); return contents; } }; const RIPEID_BASE_URL = "https://id.platforme.com/api/"; const RIPEID_LOGIN_URL = "https://id.platforme.com/"; const RIPEID_SCOPE = ["account.me", "account.acl"]; class API extends mix(OAuth2API).with(AccountAPI, RoleAPI, SecretAPI, TokenAPI) { constructor(kwargs = {}) { super(kwargs); this.baseUrl = conf("RIPEID_BASE_URL", RIPEID_BASE_URL); this.loginUrl = conf("RIPEID_LOGIN_URL", RIPEID_LOGIN_URL); this.secretKey = conf("RIPEID_SECRET_KEY", null); this.clientId = conf("RIPEID_ID", null); this.clientSecret = conf("RIPEID_SECRET", null); this.redirectUrl = conf("RIPEID_REDIRECT_URL", null); this.baseUrl = kwargs.baseUrl === undefined ? this.baseUrl : kwargs.baseUrl; this.loginUrl = kwargs.loginUrl === undefined ? this.loginUrl : kwargs.loginUrl; this.secretKey = kwargs.secretKey === undefined ? this.secretKey : kwargs.secretKey; this.clientId = kwargs.clientId === undefined ? this.clientId : kwargs.clientId; this.clientSecret = kwargs.clientSecret === undefined ? this.clientSecret : kwargs.clientSecret; this.redirectUrl = kwargs.redirectUrl === undefined ? this.redirectUrl : kwargs.redirectUrl; this.scope = kwargs.scope === undefined ? RIPEID_SCOPE : kwargs.scope; this.accessToken = kwargs.accessToken === undefined ? null : kwargs.accessToken; this.refreshToken = kwargs.refreshToken === undefined ? null : kwargs.refreshToken; this.sessionId = kwargs.sessionId === undefined ? null : kwargs.sessionId; } static async load() { await load(); } async build(method, url, options = {}) { await super.build(method, url, options); options.params = options.params !== undefined ? options.params : {}; options.kwargs = options.kwargs !== undefined ? options.kwargs : {}; options.headers = options.headers !== undefined ? options.headers : {}; const auth = options.kwargs.auth === undefined ? true : options.kwargs.auth; delete options.kwargs.auth; if (auth && !this.secretKey) options.params.sid = await this.getSessionId(); if (this.secretKey) options.headers["X-Secret-Key"] = this.secretKey; } async authCallback(params, headers) { if (this.refreshToken && params.access_token) { await this.oauthRefresh(); params.access_token = this.getAccessToken(); } if (this.sessionId && params.sid) { this.sessionId = null; const sessionId = await this.getSessionId(); params.sid = sessionId; } } async getSessionId() { if (this.sessionId) return this.sessionId; await this.oauthLogin(); return this.sessionId; } async oauthAuthorize(state = null) { let url = this.loginUrl + "oauth2/auth"; verifyMany([this.clientId, this.redirectUrl, this.scope]); const values = { client_id: this.clientId, redirect_uri: this.redirectUrl, response_type: "code", scope: this.scope.join(" ") }; if (state) values.state = state; const data = urlEncode(values); url = url + "?" + data; return url; } async oauthAccess(code) { const url = this.loginUrl + "oauth2/token"; const contents = await this.post(url, { params: { client_id: this.clientId, client_secret: this.clientSecret, grant_type: "authorization_code", redirect_uri: this.redirectUrl, code: code }, kwargs: { token: false, auth: false } }); this.accessToken = contents.access_token; this.refreshToken = contents.refresh_token || null; this.trigger("access_token", this.accessToken); this.trigger("refresh_token", this.refreshToken); return this.accessToken; } async oauthRefresh() { const url = this.loginUrl + "oauth2/token"; const contents = await this.post(url, { callback: false, params: { client_id: this.clientId, client_secret: this.clientSecret, grant_type: "refresh_token", redirect_uri: this.redirectUrl, refresh_token: this.refreshToken }, kwargs: { token: false, auth: false } }); this.accessToken = contents.access_token; this.trigger("access_token", this.accessToken); return this.accessToken; } async oauthLogin() { const url = this.loginUrl + "oauth2/login"; const contents = await this.post(url, { callback: false, kwargs: { token: true, auth: false } }); this.sessionId = contents.session_id || null; this.tokens = contents.tokens || null; this.trigger("auth", contents); return this.sessionId; } async login(username, password) { const url = this.baseUrl + "login"; const contents = await this.post(url, { callback: false, params: { username: username, password: password }, kwargs: { auth: false } }); return contents; } _generateExtraUrl(params) { return params.length > 0 ? "?" + params.map(([k, v]) => `${k}=${String(v)}`).join("&") : ""; } get oauthTypes() { return ["param"]; } get tokenDefault() { return false; } } /** * Tries to obtain a RIPE authorization object (username and ACL) * for the current request context, using the (redeem) token in context * as the basis for the retrieval of the authorization. * * In case the process fails an exception is raised indicating the source * of such problem. * * @param {Request} req The request object that is going to be used * as the context provided and from which the (redeem) token is going * to be retrieved and the authorization stored. * @returns The resulting RIPE authorization object for the current * context, exception is raised in case it's not possible to redeem one. */ const getRipeAuth = async req => { // checks the current request's cache to check if there's // an authorization information already present in it if (req.ripeAuth) return req.ripeAuth; // allocates space for the (redeem) token value that is going // to be "gathered" from all possible request sources let token = null; // tries to "find" the token in the route parameters if (req.params.token) token = req.params.token; // tries to "find" the token in the GET parameters if (req.query.token) token = req.query.token; // tries to verify if the authorization header is present and // if that's the case unpacks the token from it if (req.headers.authorization) { [, token] = req.headers.authorization.split(" ", 2); } // in case no redeem token has been found on request must throw an error // indicating such issue (as expected) if (!token) { throw new OperationalError("No (redeem) token available", 401); } // creates a new instance of the RIPE ID API client to be used for // the "redeem" operation of the redeem token const api = new API(); // tries to redeem the token and then reacts to the most usual problems // in the expected manner, the redeem operation represents the guarantee // that the 3rd party who provided this token has effectively access to the // ACL and username that is provided as part of the resulting authorization const auth = await api.redeemToken(token); if (auth.code === 404) { throw new OperationalError("Token not found", 401); } // stores the (redeem) token associated with the request in the authorization, // so that it can be easily retrieved latter if needed (no need to find the // token among the multiple possible request locations: GET or POST params) auth.token = token; // updates the current request with the authorization information // retrieved from the provided token (avoids new calls, it's a cache) req.ripeAuth = auth; return req.ripeAuth; }; /** * The characters that are going to be used as the shift * ones in the encoding operation. */ const SHIFT_CHARS = ["Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê"]; /** * Encodes the given value following the Code 128 Set B * guidelines, so that the value can be shown using the * barcode 128 font. * * This function contains limitations when more than 3 * consecutive numbers are present in the value. * * @param {String} value The string value to be encoded. * @returns {String} The Code 128 Set B value to be used with the * barcode 128 font family. * @see https://www.precisionid.com/code-128-faq/ */ const encodeBarcode128B = value => { // builds the checksum for the encoded barcode value // 128 Set B by summing the START code (104) and the // product of each data character with its position // within the data const length = value.length; let checksumValue = 104; for (let i = 0; i < length; i++) { const encodedChar = value.charCodeAt(i) - 32; checksumValue += (i + 1) * encodedChar; } // the checksum value is finally calculated by // doing the remainder of the total by 103 // (Modulus 103 Checksum) and replacing shift // characters code with the respective characters const checksumInt = parseInt(checksumValue % 103, 10); const checksum = checksumInt > 94 ? SHIFT_CHARS[checksumInt - 95] : String.fromCharCode(checksumInt + 32); // builds the encoded barcode value by adding the // START and STOP code, the value and the checksum const startCodeB = "Ì"; const stopCodeB = "Î"; return `${startCodeB}${value}${checksum}${stopCodeB}`; }; const features = { "*": true }; const listeners = []; const hasFeature = (name, fallback = false, source = null) => { source = source || features; fallback = source["*"] === undefined ? fallback : source["*"]; return Boolean(source[name] === undefined ? fallback : source[name]); }; const setFeature = (name, value) => { features[name] = value; listeners.forEach(callable => callable(name, value)); }; const setFeatures = _features => { Object.keys(features).forEach(k => delete features[k]); Object.entries(_features).forEach(([k, v]) => { features[k] = v; }); }; const bindFeature = callable => { listeners.push(callable); return callable; }; const unbindFeature = callable => { listeners.splice(listeners.indexOf(callable), 1); return callable; }; const serializeContext = context => { const result = {}; for (const [key, value] of Object.entries(context)) { switch (typeof value) { case "boolean": result[key] = value ? "1" : "0"; break; case "string": result[key] = value; break; default: result[key] = String(value); break; } } return result; }; /** * Creates the contents of the CSV file with the data given * as an array of rows and the headers. * * @param {Array} data Array of rows with cell data. * @param {Array} headers List of headers for the CSV file. * @param {Object} options The options that are going to be * used to build the CSV text stricture. * @returns {String} The generated CSV string from the provided * structure of arrays. */ const buildCsv = ( data, headers = [], { delimiter = ",", eol = "\n", useHeaders = true } = {} ) => { data = useHeaders ? [headers, ...data] : data; return data.map(row => row.map(r => _toString(r, delimiter)).join(delimiter)).join(eol); }; const dumpsCsv = buildCsv; const arraysToCsv = (data, headers = [], options = {}) => { return buildCsv(data, headers, options); }; const arrayToCsv = (data, headers = [], options = {}) => { return arraysToCsv([data], headers, options); }; const objectsToCsv = (data, headers = [], options = {}) => { headers = Object.keys(headers).length === 0 && data.length > 0 ? Object.keys(data[0]).sort() : headers; const dataArray = data.map(d => headers.map(h => d[h])); return buildCsv(dataArray, headers, options); }; const objectToCsv = (data, headers = [], options = {}) => { return objectsToCsv([data], headers, options); }; /** * Parses a CSV file and returns a sequence of arrays * containing the multiple lines of data (table). * * @param {Blob|File} file The file object that is going * to be read and have its contents parsed as CSV. * @param {Object} options The options that are going to be * used in the parsing operation. * @returns {Array} An array containing the multiple parsed * CSV lines. */ const parseCsvFile = (file, options = undefined) => { const parser = options.parser || _parseCsvComplex; return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = e => resolve(parser(e.target.result, options)); reader.onerror = e => reject(e); reader.readAsText(file); }); }; const parseCsv = (dataS, options = {}) => { const parser = options.parser || _parseCsvComplex; return parser(dataS, options); }; const loadsCsv = parseCsv; const _parseCsvSimple = ( dataS, { object = false, sanitize = true, delimiter = ",", eol = "\n" } = {} ) => { // in case the sanitize operation has been requested runs a pre-operation // to make sure no extra information exist at the end of the lines and // that only the valid lines are taken into account if (sanitize) { dataS = dataS .split(eol) .filter(row => Boolean(row)) .map(row => row.trim()) .join(eol); } const data = dataS.split(eol).map(row => row.split(delimiter)); if (!object) return data; return _toObject(data); }; const _parseCsvComplex = ( dataS, { object = false, sanitize = true, delimiter = ",", eol = "\n" } = {} ) => { // in case the sanitize operation has been required runs a pre-operation // to make sure no extra information exist at the end of the lines and // that only the valid lines are taken into account if (sanitize) { dataS = dataS .split(eol) .filter(row => Boolean(row)) .map(row => row.trim()) .join(eol); } // builds the custom pattern that is going to try to // match the delimiter and the escaped and non escaped // values to properly pars the CSV values const pattern = new RegExp( "(\\" + delimiter + "|\\r?\\n|\\r|^)" + '(?:"([^"]*(?:""[^"]*)*)"|' + '([^"\\' + delimiter + "\\r\\n]*))", "gi" ); // creates the initial data structure already populated // with an initial value as we need to "look-back" const data = [[]]; // creates an array to hold our individual pattern // matching groups let matches = null; // keeps looping over the regular expression matches // until we can no longer find a match while ((matches = pattern.exec(dataS))) { // retrieves the delimiter that was found, this will // affect the newline operation const matchedDelimiter = matches[1]; // checks to see if the given delimiter has a length // (if it0s not the start of string) and if it matches // field delimiter, if id does not, then we know // that this delimiter is a row delimiter if (matchedDelimiter.length && matchedDelimiter !== delimiter) { // since we have reached a new row of data, // adds an empty row to our data array data.push([]); } // allocates space for the value that has been matched let value; // now that we have our delimiter out of the way, // checks to see which kind of value we captured // (can be quoted or unquoted) if (matches[2]) { // as a quoted value has been found we need to // unescape the double quotes value = matches[2].replace(/""/g, '"'); } else { // a simple (non quoted) value is found and as // such it should be considered the matched value // without any kind of manipulation value = matches[3]; } // adds the newly (matched) value to the last item in // the data array sequence data[data.length - 1].push(value); } if (!object) return data; return _toObject(data); }; /** * Converts a sequence of parsed data items into a s