UNPKG

@vulog/aima-client

Version:

```bash npm i @vulog/aima-client ```

300 lines (297 loc) 10.5 kB
// src/getClient.ts import axios, { AxiosError } from "axios"; import { isEqual, trimEnd } from "es-toolkit"; import { LRUCache } from "lru-cache"; // src/CurlHelper.ts var CurlHelper = class { request; constructor(config) { this.request = config; } getHeaders() { let { headers } = this.request; let curlHeaders = ""; if (headers.hasOwnProperty("common")) { headers = this.request.headers[this.request.method]; } for (const property in this.request.headers) { if (!["common", "delete", "get", "head", "patch", "post", "put"].includes(property)) { headers[property] = this.request.headers[property]; } } for (const property in headers) { if ({}.hasOwnProperty.call(headers, property)) { const header = `${property}:${headers[property]}`; curlHeaders = `${curlHeaders} -H '${header}'`; } } return curlHeaders.trim(); } getMethod() { return `-X ${this.request.method.toUpperCase()}`; } getBody() { if (typeof this.request.data !== "undefined" && this.request.data !== "" && this.request.data !== null && this.request.method.toUpperCase() !== "GET") { const data = typeof this.request.data === "object" || Object.prototype.toString.call(this.request.data) === "[object Array]" ? JSON.stringify(this.request.data) : this.request.data; return `--data '${data}'`.trim(); } return ""; } getUrl() { if (this.request.baseURL) { const baseUrl = this.request.baseURL; const { url } = this.request; const finalUrl = url.startsWith("http") ? url : `${baseUrl}/${url}`; return finalUrl.replace(/\/{2,}/g, "/").replace("http:/", "http://").replace("https:/", "https://"); } return this.request.url; } getQueryString() { if (this.request.paramsSerializer) { const params2 = this.request.paramsSerializer(this.request.params); if (!params2 || params2.length === 0) return ""; if (params2.startsWith("?")) return params2; return `?${params2}`; } let params = ""; let i = 0; for (const param in this.request.params) { if ({}.hasOwnProperty.call(this.request.params, param)) { params += i !== 0 ? `&${param}=${this.request.params[param]}` : `?${param}=${this.request.params[param]}`; i += 1; } } return params; } getBuiltURL() { let url = this.getUrl(); if (this.getQueryString() !== "") { url += this.getQueryString(); } return url.trim(); } generateCommand() { return `curl ${this.getMethod()} "${this.getBuiltURL()}" ${this.getHeaders()} ${this.getBody()}`.trim().replace(/\s{2,}/g, " "); } }; // src/getClient.ts var clientCache = new LRUCache({ max: 100 }); var tokenCache = new LRUCache({ max: 100 }); var getMemoryStore = (options) => ({ getToken: async () => { const log = options.onLog ?? console.log; log("getMemoryStore.getToken", options.name ?? options.fleetId); if (tokenCache.has(options.name ?? options.fleetId)) { log("getMemoryStore.getToken", tokenCache.get(options.name ?? options.fleetId)); return tokenCache.get(options.name ?? options.fleetId); } return void 0; }, setToken: async (token) => { const log = options.onLog ?? console.log; log("getMemoryStore.setToken", options.name ?? options.fleetId, token); tokenCache.set(options.name ?? options.fleetId, token); } }); var formatError = (error) => { if (error instanceof AxiosError) { return { originalError: error.toJSON(), formattedError: { status: error.response?.status ?? error.status, data: error.response?.data, message: error.message } }; } return { formattedError: {}, originalError: JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error))) }; }; var getClient = (options) => { if (clientCache.has(options.name ?? options.fleetId)) { const { options: cachedOptions, client: client2 } = clientCache.get(options.fleetId); if (isEqual(cachedOptions, options)) { return client2; } } const client = axios.create({ baseURL: trimEnd(options.baseUrl, "/"), timeout: 3e4, headers: { "Cache-Control": "no-cache", "Content-Type": "application/json", "X-Api-Key": options.apiKey }, withCredentials: false }); client.clientOptions = options; const clientCredentialsAuthentification = async () => { const params = new URLSearchParams(); params.append("client_id", options.clientId); params.append("client_secret", options.clientSecret); params.append("securityOptions", "SSL_OP_NO_SSLv3"); params.append("grant_type", "client_credentials"); const { data: token } = await axios.post( `${trimEnd(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`, params, { timeout: 3e4, headers: { "Content-Type": "application/x-www-form-urlencoded" }, withCredentials: false } ); const store = options.store ?? getMemoryStore(options); await store.setToken({ accessToken: token.access_token, refreshToken: token.refresh_token }); return token.access_token; }; const refreshTokenAuthentification = async () => { const store = options.store ?? getMemoryStore(options); const oldToken = await store.getToken(); if (!oldToken?.refreshToken) { throw new Error("No refresh token available"); } const params = new URLSearchParams(); params.append("client_id", options.clientId); params.append("client_secret", options.clientSecret); params.append("securityOptions", "SSL_OP_NO_SSLv3"); params.append("grant_type", "refresh_token"); params.append("refresh_token", oldToken.refreshToken); const { data: token } = await axios.post( `${trimEnd(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`, params, { timeout: 3e4, headers: { "Content-Type": "application/x-www-form-urlencoded" }, withCredentials: false } ); await store.setToken({ accessToken: token.access_token, refreshToken: token.refresh_token }); return token.access_token; }; client.signInWithPassword = async (username, password) => { if (!options.secure) { throw new Error("Not secure"); } const params = new URLSearchParams(); params.append("client_id", options.clientId); params.append("client_secret", options.clientSecret); params.append("securityOptions", "SSL_OP_NO_SSLv3"); params.append("grant_type", "password"); params.append("username", username); params.append("password", password); const { data: token } = await axios.post( `${trimEnd(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`, params, { timeout: 3e4, headers: { "Content-Type": "application/x-www-form-urlencoded" }, withCredentials: false } ); const newToken = { accessToken: token.access_token, refreshToken: token.refresh_token }; const store = options.store ?? getMemoryStore(options); await store.setToken(newToken); return newToken; }; client.interceptors.request.use(async (request) => { const newRequest = request; const store = options.store ?? getMemoryStore(options); const token = await store.getToken(); if (token?.accessToken) { newRequest.headers.Authorization = `Bearer ${token.accessToken}`; } if (options.logCurl) { const curl = new CurlHelper(newRequest).generateCommand(); if (options.onLog) options.onLog({ curl, message: "getClient > Curl command" }); else console.log({ curl, message: "getClient > Curl command" }); } return newRequest; }); let isRefreshing = false; let refreshSubscribers = []; const executorRefresh = (config) => { return new Promise((resolve, reject) => { refreshSubscribers.push((token, error) => { if (error) { reject(formatError(error)); return; } resolve(client.request(config)); }); }); }; client.interceptors.response.use( (response) => { if (options.logResponse) { const finalUrl = new CurlHelper(response.config).getBuiltURL(); const { data, headers } = response; const dataLog = response.config.responseType !== "arraybuffer" && response.config.responseType !== "blob" ? data : "ArrayBuffer or blob"; if (options.onLog) options.onLog({ finalUrl, data: dataLog, headers, message: "getClient > Response" }); else console.log({ finalUrl, data: dataLog, headers, message: "getClient > Response" }); } return response; }, (error) => { const { config, response: { status } = { status: 500 } } = error; const originalRequest = config; if (originalRequest.attemptCount === void 0) { originalRequest.attemptCount = 0; } if (originalRequest.attemptCount === 5) { return Promise.reject(formatError(error)); } if (status === 401) { originalRequest.attemptCount += 1; if (!isRefreshing) { isRefreshing = true; let authentification; if (options.secure) { authentification = refreshTokenAuthentification; } else { authentification = async () => { const store = options.store ?? getMemoryStore(options); const token = await store.getToken(); if (!token?.refreshToken) { return clientCredentialsAuthentification(); } return refreshTokenAuthentification(); }; } authentification().then((accessToken) => { refreshSubscribers.forEach((cb) => cb(accessToken)); }).catch((errorAuth) => { refreshSubscribers.forEach((cb) => cb(void 0, errorAuth)); }).finally(() => { isRefreshing = false; refreshSubscribers = []; }); } return executorRefresh(originalRequest); } return Promise.reject(formatError(error)); } ); clientCache.set(options.name ?? options.fleetId, { options, client }); return client; }; var getClient_default = getClient; export { getClient_default as getClient };