@vulog/aima-client
Version:
```bash npm i @vulog/aima-client ```
300 lines (297 loc) • 10.5 kB
JavaScript
// 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
};