@vulog/aima-client 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +91 -25
- package/dist/index.mjs +91 -25
- package/package.json +11 -11
- package/src/getClient.ts +107 -27
- package/src/types.ts +11 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { AxiosInstance } from 'axios';
|
|
2
2
|
|
|
3
|
+
type Token = {
|
|
4
|
+
accessToken: string;
|
|
5
|
+
refreshToken: string;
|
|
6
|
+
};
|
|
7
|
+
type Store = {
|
|
8
|
+
getToken: () => Promise<Token | undefined>;
|
|
9
|
+
setToken: (token: Token) => Promise<void>;
|
|
10
|
+
};
|
|
3
11
|
type ClientOptions = {
|
|
4
12
|
fleetId: string;
|
|
5
13
|
name?: string;
|
|
@@ -11,7 +19,7 @@ type ClientOptions = {
|
|
|
11
19
|
secure?: boolean;
|
|
12
20
|
logCurl?: boolean;
|
|
13
21
|
logResponse?: boolean;
|
|
14
|
-
|
|
22
|
+
store?: Store;
|
|
15
23
|
onLog?: (...args: any[]) => void;
|
|
16
24
|
};
|
|
17
25
|
type ClientError = {
|
|
@@ -23,7 +31,6 @@ type ClientError = {
|
|
|
23
31
|
originalError: any;
|
|
24
32
|
};
|
|
25
33
|
type Client = AxiosInstance & {
|
|
26
|
-
refreshToken?: string;
|
|
27
34
|
signInWithPassword: (username: string, password: string) => Promise<void>;
|
|
28
35
|
clientOptions: ClientOptions;
|
|
29
36
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { AxiosInstance } from 'axios';
|
|
2
2
|
|
|
3
|
+
type Token = {
|
|
4
|
+
accessToken: string;
|
|
5
|
+
refreshToken: string;
|
|
6
|
+
};
|
|
7
|
+
type Store = {
|
|
8
|
+
getToken: () => Promise<Token | undefined>;
|
|
9
|
+
setToken: (token: Token) => Promise<void>;
|
|
10
|
+
};
|
|
3
11
|
type ClientOptions = {
|
|
4
12
|
fleetId: string;
|
|
5
13
|
name?: string;
|
|
@@ -11,7 +19,7 @@ type ClientOptions = {
|
|
|
11
19
|
secure?: boolean;
|
|
12
20
|
logCurl?: boolean;
|
|
13
21
|
logResponse?: boolean;
|
|
14
|
-
|
|
22
|
+
store?: Store;
|
|
15
23
|
onLog?: (...args: any[]) => void;
|
|
16
24
|
};
|
|
17
25
|
type ClientError = {
|
|
@@ -23,7 +31,6 @@ type ClientError = {
|
|
|
23
31
|
originalError: any;
|
|
24
32
|
};
|
|
25
33
|
type Client = AxiosInstance & {
|
|
26
|
-
refreshToken?: string;
|
|
27
34
|
signInWithPassword: (username: string, password: string) => Promise<void>;
|
|
28
35
|
clientOptions: ClientOptions;
|
|
29
36
|
};
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
36
36
|
|
|
37
37
|
// src/getClient.ts
|
|
38
38
|
var import_axios = __toESM(require("axios"));
|
|
39
|
-
var
|
|
39
|
+
var import_es_toolkit = require("es-toolkit");
|
|
40
40
|
var import_lru_cache = require("lru-cache");
|
|
41
41
|
|
|
42
42
|
// src/CurlHelper.ts
|
|
@@ -114,6 +114,23 @@ var CurlHelper = class {
|
|
|
114
114
|
|
|
115
115
|
// src/getClient.ts
|
|
116
116
|
var clientCache = new import_lru_cache.LRUCache({ max: 100 });
|
|
117
|
+
var tokenCache = new import_lru_cache.LRUCache({ max: 100 });
|
|
118
|
+
var getMemoryStore = (options) => ({
|
|
119
|
+
getToken: async () => {
|
|
120
|
+
const log = options.onLog ?? console.log;
|
|
121
|
+
log("getMemoryStore.getToken", options.name ?? options.fleetId);
|
|
122
|
+
if (tokenCache.has(options.name ?? options.fleetId)) {
|
|
123
|
+
log("getMemoryStore.getToken", tokenCache.get(options.name ?? options.fleetId));
|
|
124
|
+
return tokenCache.get(options.name ?? options.fleetId);
|
|
125
|
+
}
|
|
126
|
+
return void 0;
|
|
127
|
+
},
|
|
128
|
+
setToken: async (token) => {
|
|
129
|
+
const log = options.onLog ?? console.log;
|
|
130
|
+
log("getMemoryStore.setToken", options.name ?? options.fleetId, token);
|
|
131
|
+
tokenCache.set(options.name ?? options.fleetId, token);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
117
134
|
var formatError = (error) => {
|
|
118
135
|
if (error instanceof import_axios.AxiosError) {
|
|
119
136
|
return {
|
|
@@ -133,12 +150,12 @@ var formatError = (error) => {
|
|
|
133
150
|
var getClient = (options) => {
|
|
134
151
|
if (clientCache.has(options.name ?? options.fleetId)) {
|
|
135
152
|
const { options: cachedOptions, client: client2 } = clientCache.get(options.fleetId);
|
|
136
|
-
if (
|
|
153
|
+
if ((0, import_es_toolkit.isEqual)(cachedOptions, options)) {
|
|
137
154
|
return client2;
|
|
138
155
|
}
|
|
139
156
|
}
|
|
140
157
|
const client = import_axios.default.create({
|
|
141
|
-
baseURL:
|
|
158
|
+
baseURL: (0, import_es_toolkit.trimEnd)(options.baseUrl, "/"),
|
|
142
159
|
timeout: 3e4,
|
|
143
160
|
headers: {
|
|
144
161
|
"Cache-Control": "no-cache",
|
|
@@ -155,7 +172,7 @@ var getClient = (options) => {
|
|
|
155
172
|
params.append("securityOptions", "SSL_OP_NO_SSLv3");
|
|
156
173
|
params.append("grant_type", "client_credentials");
|
|
157
174
|
const { data: token } = await import_axios.default.post(
|
|
158
|
-
`${
|
|
175
|
+
`${(0, import_es_toolkit.trimEnd)(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
159
176
|
params,
|
|
160
177
|
{
|
|
161
178
|
timeout: 3e4,
|
|
@@ -165,11 +182,17 @@ var getClient = (options) => {
|
|
|
165
182
|
withCredentials: false
|
|
166
183
|
}
|
|
167
184
|
);
|
|
168
|
-
|
|
185
|
+
const store = options.store ?? getMemoryStore(options);
|
|
186
|
+
await store.setToken({
|
|
187
|
+
accessToken: token.access_token,
|
|
188
|
+
refreshToken: token.refresh_token
|
|
189
|
+
});
|
|
169
190
|
return token.access_token;
|
|
170
191
|
};
|
|
171
192
|
const refreshTokenAuthentification = async () => {
|
|
172
|
-
|
|
193
|
+
const store = options.store ?? getMemoryStore(options);
|
|
194
|
+
const oldToken = await store.getToken();
|
|
195
|
+
if (!oldToken?.refreshToken) {
|
|
173
196
|
throw new Error("No refresh token available");
|
|
174
197
|
}
|
|
175
198
|
const params = new URLSearchParams();
|
|
@@ -177,9 +200,9 @@ var getClient = (options) => {
|
|
|
177
200
|
params.append("client_secret", options.clientSecret);
|
|
178
201
|
params.append("securityOptions", "SSL_OP_NO_SSLv3");
|
|
179
202
|
params.append("grant_type", "refresh_token");
|
|
180
|
-
params.append("refresh_token",
|
|
203
|
+
params.append("refresh_token", oldToken.refreshToken);
|
|
181
204
|
const { data: token } = await import_axios.default.post(
|
|
182
|
-
`${
|
|
205
|
+
`${(0, import_es_toolkit.trimEnd)(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
183
206
|
params,
|
|
184
207
|
{
|
|
185
208
|
timeout: 3e4,
|
|
@@ -189,9 +212,10 @@ var getClient = (options) => {
|
|
|
189
212
|
withCredentials: false
|
|
190
213
|
}
|
|
191
214
|
);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
215
|
+
await store.setToken({
|
|
216
|
+
accessToken: token.access_token,
|
|
217
|
+
refreshToken: token.refresh_token
|
|
218
|
+
});
|
|
195
219
|
return token.access_token;
|
|
196
220
|
};
|
|
197
221
|
client.signInWithPassword = async (username, password) => {
|
|
@@ -206,7 +230,7 @@ var getClient = (options) => {
|
|
|
206
230
|
params.append("username", username);
|
|
207
231
|
params.append("password", password);
|
|
208
232
|
const { data: token } = await import_axios.default.post(
|
|
209
|
-
`${
|
|
233
|
+
`${(0, import_es_toolkit.trimEnd)(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
210
234
|
params,
|
|
211
235
|
{
|
|
212
236
|
timeout: 3e4,
|
|
@@ -216,17 +240,39 @@ var getClient = (options) => {
|
|
|
216
240
|
withCredentials: false
|
|
217
241
|
}
|
|
218
242
|
);
|
|
219
|
-
|
|
220
|
-
|
|
243
|
+
const store = options.store ?? getMemoryStore(options);
|
|
244
|
+
await store.setToken({
|
|
245
|
+
accessToken: token.access_token,
|
|
246
|
+
refreshToken: token.refresh_token
|
|
247
|
+
});
|
|
221
248
|
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
249
|
+
client.interceptors.request.use(async (request) => {
|
|
250
|
+
const newRequest = request;
|
|
251
|
+
const store = options.store ?? getMemoryStore(options);
|
|
252
|
+
const token = await store.getToken();
|
|
253
|
+
if (token?.accessToken) {
|
|
254
|
+
newRequest.headers.Authorization = `Bearer ${token.accessToken}`;
|
|
255
|
+
}
|
|
256
|
+
if (options.logCurl) {
|
|
257
|
+
const curl = new CurlHelper(newRequest).generateCommand();
|
|
225
258
|
if (options.onLog) options.onLog({ curl, message: "getClient > Curl command" });
|
|
226
259
|
else console.log({ curl, message: "getClient > Curl command" });
|
|
227
|
-
|
|
260
|
+
}
|
|
261
|
+
return newRequest;
|
|
262
|
+
});
|
|
263
|
+
let isRefreshing = false;
|
|
264
|
+
let refreshSubscribers = [];
|
|
265
|
+
const executorRefresh = (config) => {
|
|
266
|
+
return new Promise((resolve, reject) => {
|
|
267
|
+
refreshSubscribers.push((token, error) => {
|
|
268
|
+
if (error) {
|
|
269
|
+
reject(formatError(error));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
resolve(client.request(config));
|
|
273
|
+
});
|
|
228
274
|
});
|
|
229
|
-
}
|
|
275
|
+
};
|
|
230
276
|
client.interceptors.response.use(
|
|
231
277
|
(response) => {
|
|
232
278
|
if (options.logResponse) {
|
|
@@ -247,12 +293,32 @@ var getClient = (options) => {
|
|
|
247
293
|
return Promise.reject(formatError(error));
|
|
248
294
|
}
|
|
249
295
|
if (status === 401) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
296
|
+
originalRequest.attemptCount += 1;
|
|
297
|
+
if (!isRefreshing) {
|
|
298
|
+
isRefreshing = true;
|
|
299
|
+
let authentification;
|
|
300
|
+
if (options.secure) {
|
|
301
|
+
authentification = refreshTokenAuthentification;
|
|
302
|
+
} else {
|
|
303
|
+
authentification = async () => {
|
|
304
|
+
const store = options.store ?? getMemoryStore(options);
|
|
305
|
+
const token = await store.getToken();
|
|
306
|
+
if (!token?.refreshToken) {
|
|
307
|
+
return clientCredentialsAuthentification();
|
|
308
|
+
}
|
|
309
|
+
return refreshTokenAuthentification();
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
authentification().then((accessToken) => {
|
|
313
|
+
refreshSubscribers.forEach((cb) => cb(accessToken));
|
|
314
|
+
}).catch((errorAuth) => {
|
|
315
|
+
refreshSubscribers.forEach((cb) => cb(void 0, errorAuth));
|
|
316
|
+
}).finally(() => {
|
|
317
|
+
isRefreshing = false;
|
|
318
|
+
refreshSubscribers = [];
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return executorRefresh(originalRequest);
|
|
256
322
|
}
|
|
257
323
|
return Promise.reject(formatError(error));
|
|
258
324
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/getClient.ts
|
|
2
2
|
import axios, { AxiosError } from "axios";
|
|
3
|
-
import
|
|
3
|
+
import { isEqual, trimEnd } from "es-toolkit";
|
|
4
4
|
import { LRUCache } from "lru-cache";
|
|
5
5
|
|
|
6
6
|
// src/CurlHelper.ts
|
|
@@ -78,6 +78,23 @@ var CurlHelper = class {
|
|
|
78
78
|
|
|
79
79
|
// src/getClient.ts
|
|
80
80
|
var clientCache = new LRUCache({ max: 100 });
|
|
81
|
+
var tokenCache = new LRUCache({ max: 100 });
|
|
82
|
+
var getMemoryStore = (options) => ({
|
|
83
|
+
getToken: async () => {
|
|
84
|
+
const log = options.onLog ?? console.log;
|
|
85
|
+
log("getMemoryStore.getToken", options.name ?? options.fleetId);
|
|
86
|
+
if (tokenCache.has(options.name ?? options.fleetId)) {
|
|
87
|
+
log("getMemoryStore.getToken", tokenCache.get(options.name ?? options.fleetId));
|
|
88
|
+
return tokenCache.get(options.name ?? options.fleetId);
|
|
89
|
+
}
|
|
90
|
+
return void 0;
|
|
91
|
+
},
|
|
92
|
+
setToken: async (token) => {
|
|
93
|
+
const log = options.onLog ?? console.log;
|
|
94
|
+
log("getMemoryStore.setToken", options.name ?? options.fleetId, token);
|
|
95
|
+
tokenCache.set(options.name ?? options.fleetId, token);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
81
98
|
var formatError = (error) => {
|
|
82
99
|
if (error instanceof AxiosError) {
|
|
83
100
|
return {
|
|
@@ -97,12 +114,12 @@ var formatError = (error) => {
|
|
|
97
114
|
var getClient = (options) => {
|
|
98
115
|
if (clientCache.has(options.name ?? options.fleetId)) {
|
|
99
116
|
const { options: cachedOptions, client: client2 } = clientCache.get(options.fleetId);
|
|
100
|
-
if (
|
|
117
|
+
if (isEqual(cachedOptions, options)) {
|
|
101
118
|
return client2;
|
|
102
119
|
}
|
|
103
120
|
}
|
|
104
121
|
const client = axios.create({
|
|
105
|
-
baseURL:
|
|
122
|
+
baseURL: trimEnd(options.baseUrl, "/"),
|
|
106
123
|
timeout: 3e4,
|
|
107
124
|
headers: {
|
|
108
125
|
"Cache-Control": "no-cache",
|
|
@@ -119,7 +136,7 @@ var getClient = (options) => {
|
|
|
119
136
|
params.append("securityOptions", "SSL_OP_NO_SSLv3");
|
|
120
137
|
params.append("grant_type", "client_credentials");
|
|
121
138
|
const { data: token } = await axios.post(
|
|
122
|
-
`${
|
|
139
|
+
`${trimEnd(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
123
140
|
params,
|
|
124
141
|
{
|
|
125
142
|
timeout: 3e4,
|
|
@@ -129,11 +146,17 @@ var getClient = (options) => {
|
|
|
129
146
|
withCredentials: false
|
|
130
147
|
}
|
|
131
148
|
);
|
|
132
|
-
|
|
149
|
+
const store = options.store ?? getMemoryStore(options);
|
|
150
|
+
await store.setToken({
|
|
151
|
+
accessToken: token.access_token,
|
|
152
|
+
refreshToken: token.refresh_token
|
|
153
|
+
});
|
|
133
154
|
return token.access_token;
|
|
134
155
|
};
|
|
135
156
|
const refreshTokenAuthentification = async () => {
|
|
136
|
-
|
|
157
|
+
const store = options.store ?? getMemoryStore(options);
|
|
158
|
+
const oldToken = await store.getToken();
|
|
159
|
+
if (!oldToken?.refreshToken) {
|
|
137
160
|
throw new Error("No refresh token available");
|
|
138
161
|
}
|
|
139
162
|
const params = new URLSearchParams();
|
|
@@ -141,9 +164,9 @@ var getClient = (options) => {
|
|
|
141
164
|
params.append("client_secret", options.clientSecret);
|
|
142
165
|
params.append("securityOptions", "SSL_OP_NO_SSLv3");
|
|
143
166
|
params.append("grant_type", "refresh_token");
|
|
144
|
-
params.append("refresh_token",
|
|
167
|
+
params.append("refresh_token", oldToken.refreshToken);
|
|
145
168
|
const { data: token } = await axios.post(
|
|
146
|
-
`${
|
|
169
|
+
`${trimEnd(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
147
170
|
params,
|
|
148
171
|
{
|
|
149
172
|
timeout: 3e4,
|
|
@@ -153,9 +176,10 @@ var getClient = (options) => {
|
|
|
153
176
|
withCredentials: false
|
|
154
177
|
}
|
|
155
178
|
);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
179
|
+
await store.setToken({
|
|
180
|
+
accessToken: token.access_token,
|
|
181
|
+
refreshToken: token.refresh_token
|
|
182
|
+
});
|
|
159
183
|
return token.access_token;
|
|
160
184
|
};
|
|
161
185
|
client.signInWithPassword = async (username, password) => {
|
|
@@ -170,7 +194,7 @@ var getClient = (options) => {
|
|
|
170
194
|
params.append("username", username);
|
|
171
195
|
params.append("password", password);
|
|
172
196
|
const { data: token } = await axios.post(
|
|
173
|
-
`${
|
|
197
|
+
`${trimEnd(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
174
198
|
params,
|
|
175
199
|
{
|
|
176
200
|
timeout: 3e4,
|
|
@@ -180,17 +204,39 @@ var getClient = (options) => {
|
|
|
180
204
|
withCredentials: false
|
|
181
205
|
}
|
|
182
206
|
);
|
|
183
|
-
|
|
184
|
-
|
|
207
|
+
const store = options.store ?? getMemoryStore(options);
|
|
208
|
+
await store.setToken({
|
|
209
|
+
accessToken: token.access_token,
|
|
210
|
+
refreshToken: token.refresh_token
|
|
211
|
+
});
|
|
185
212
|
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
213
|
+
client.interceptors.request.use(async (request) => {
|
|
214
|
+
const newRequest = request;
|
|
215
|
+
const store = options.store ?? getMemoryStore(options);
|
|
216
|
+
const token = await store.getToken();
|
|
217
|
+
if (token?.accessToken) {
|
|
218
|
+
newRequest.headers.Authorization = `Bearer ${token.accessToken}`;
|
|
219
|
+
}
|
|
220
|
+
if (options.logCurl) {
|
|
221
|
+
const curl = new CurlHelper(newRequest).generateCommand();
|
|
189
222
|
if (options.onLog) options.onLog({ curl, message: "getClient > Curl command" });
|
|
190
223
|
else console.log({ curl, message: "getClient > Curl command" });
|
|
191
|
-
|
|
224
|
+
}
|
|
225
|
+
return newRequest;
|
|
226
|
+
});
|
|
227
|
+
let isRefreshing = false;
|
|
228
|
+
let refreshSubscribers = [];
|
|
229
|
+
const executorRefresh = (config) => {
|
|
230
|
+
return new Promise((resolve, reject) => {
|
|
231
|
+
refreshSubscribers.push((token, error) => {
|
|
232
|
+
if (error) {
|
|
233
|
+
reject(formatError(error));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
resolve(client.request(config));
|
|
237
|
+
});
|
|
192
238
|
});
|
|
193
|
-
}
|
|
239
|
+
};
|
|
194
240
|
client.interceptors.response.use(
|
|
195
241
|
(response) => {
|
|
196
242
|
if (options.logResponse) {
|
|
@@ -211,12 +257,32 @@ var getClient = (options) => {
|
|
|
211
257
|
return Promise.reject(formatError(error));
|
|
212
258
|
}
|
|
213
259
|
if (status === 401) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
260
|
+
originalRequest.attemptCount += 1;
|
|
261
|
+
if (!isRefreshing) {
|
|
262
|
+
isRefreshing = true;
|
|
263
|
+
let authentification;
|
|
264
|
+
if (options.secure) {
|
|
265
|
+
authentification = refreshTokenAuthentification;
|
|
266
|
+
} else {
|
|
267
|
+
authentification = async () => {
|
|
268
|
+
const store = options.store ?? getMemoryStore(options);
|
|
269
|
+
const token = await store.getToken();
|
|
270
|
+
if (!token?.refreshToken) {
|
|
271
|
+
return clientCredentialsAuthentification();
|
|
272
|
+
}
|
|
273
|
+
return refreshTokenAuthentification();
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
authentification().then((accessToken) => {
|
|
277
|
+
refreshSubscribers.forEach((cb) => cb(accessToken));
|
|
278
|
+
}).catch((errorAuth) => {
|
|
279
|
+
refreshSubscribers.forEach((cb) => cb(void 0, errorAuth));
|
|
280
|
+
}).finally(() => {
|
|
281
|
+
isRefreshing = false;
|
|
282
|
+
refreshSubscribers = [];
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return executorRefresh(originalRequest);
|
|
220
286
|
}
|
|
221
287
|
return Promise.reject(formatError(error));
|
|
222
288
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vulog/aima-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"build": "tsup",
|
|
10
10
|
"dev": "tsup --watch",
|
|
11
11
|
"test": "vitest run",
|
|
12
|
-
"test:watch": "vitest"
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"lint": "eslint src/**/* --ext .ts"
|
|
13
14
|
},
|
|
14
15
|
"keywords": [
|
|
15
16
|
"AIMA",
|
|
@@ -20,23 +21,22 @@
|
|
|
20
21
|
"author": "Vulog",
|
|
21
22
|
"license": "ISC",
|
|
22
23
|
"devDependencies": {
|
|
23
|
-
"@types/
|
|
24
|
-
"@types/node": "^22.7.9",
|
|
24
|
+
"@types/node": "^22.10.1",
|
|
25
25
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
26
26
|
"eslint-config-airbnb-typescript": "^18.0.0",
|
|
27
27
|
"eslint-config-prettier": "^9.1.0",
|
|
28
28
|
"eslint-plugin-import": "^2.31.0",
|
|
29
29
|
"eslint-plugin-prettier": "^5.2.1",
|
|
30
30
|
"eslint-plugin-unused-imports": "^4.1.4",
|
|
31
|
-
"prettier": "^3.
|
|
32
|
-
"tsup": "^8.3.
|
|
33
|
-
"typescript": "^5.
|
|
34
|
-
"vitest": "^2.1.
|
|
31
|
+
"prettier": "^3.4.1",
|
|
32
|
+
"tsup": "^8.3.5",
|
|
33
|
+
"typescript": "^5.7.2",
|
|
34
|
+
"vitest": "^2.1.8"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"axios": "^1.7.
|
|
38
|
-
"
|
|
39
|
-
"lru-cache": "^11.0.
|
|
37
|
+
"axios": "^1.7.8",
|
|
38
|
+
"es-toolkit":"^1.29.0",
|
|
39
|
+
"lru-cache": "^11.0.2"
|
|
40
40
|
},
|
|
41
41
|
"description": ""
|
|
42
42
|
}
|
package/src/getClient.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import axios, { AxiosError } from 'axios';
|
|
2
|
-
import
|
|
2
|
+
import { isEqual, trimEnd } from 'es-toolkit';
|
|
3
3
|
import { LRUCache } from 'lru-cache';
|
|
4
4
|
|
|
5
5
|
import { CurlHelper } from './CurlHelper';
|
|
6
|
-
import { Client, ClientError, ClientOptions } from './types';
|
|
6
|
+
import { Client, ClientError, ClientOptions, Store, Token } from './types';
|
|
7
|
+
|
|
8
|
+
type RefreshSubscriber = (token?: string, error?: any) => void;
|
|
7
9
|
|
|
8
10
|
const clientCache = new LRUCache<
|
|
9
11
|
string,
|
|
@@ -13,6 +15,28 @@ const clientCache = new LRUCache<
|
|
|
13
15
|
}
|
|
14
16
|
>({ max: 100 });
|
|
15
17
|
|
|
18
|
+
const tokenCache = new LRUCache<string, Token>({ max: 100 });
|
|
19
|
+
|
|
20
|
+
const getMemoryStore = (options: ClientOptions): Store => ({
|
|
21
|
+
getToken: async (): Promise<Token | undefined> => {
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
const log = options.onLog ?? console.log;
|
|
24
|
+
log('getMemoryStore.getToken', options.name ?? options.fleetId);
|
|
25
|
+
if (tokenCache.has(options.name ?? options.fleetId)) {
|
|
26
|
+
log('getMemoryStore.getToken', tokenCache.get(options.name ?? options.fleetId));
|
|
27
|
+
return tokenCache.get(options.name ?? options.fleetId)!;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return undefined;
|
|
31
|
+
},
|
|
32
|
+
setToken: async (token: Token): Promise<void> => {
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
const log = options.onLog ?? console.log;
|
|
35
|
+
log('getMemoryStore.setToken', options.name ?? options.fleetId, token);
|
|
36
|
+
tokenCache.set(options.name ?? options.fleetId, token);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
16
40
|
const formatError = (error: any): ClientError => {
|
|
17
41
|
if (error instanceof AxiosError) {
|
|
18
42
|
return {
|
|
@@ -34,13 +58,13 @@ const formatError = (error: any): ClientError => {
|
|
|
34
58
|
const getClient = (options: ClientOptions): Client => {
|
|
35
59
|
if (clientCache.has(options.name ?? options.fleetId)) {
|
|
36
60
|
const { options: cachedOptions, client } = clientCache.get(options.fleetId)!;
|
|
37
|
-
if (
|
|
61
|
+
if (isEqual(cachedOptions, options)) {
|
|
38
62
|
return client;
|
|
39
63
|
}
|
|
40
64
|
}
|
|
41
65
|
|
|
42
66
|
const client = axios.create({
|
|
43
|
-
baseURL:
|
|
67
|
+
baseURL: trimEnd(options.baseUrl, '/'),
|
|
44
68
|
timeout: 30000,
|
|
45
69
|
headers: {
|
|
46
70
|
'Cache-Control': 'no-cache',
|
|
@@ -59,7 +83,7 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
59
83
|
params.append('grant_type', 'client_credentials');
|
|
60
84
|
|
|
61
85
|
const { data: token } = await axios.post(
|
|
62
|
-
`${
|
|
86
|
+
`${trimEnd(options.baseUrl, '/')}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
63
87
|
params,
|
|
64
88
|
{
|
|
65
89
|
timeout: 30000,
|
|
@@ -69,12 +93,18 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
69
93
|
withCredentials: false,
|
|
70
94
|
}
|
|
71
95
|
);
|
|
72
|
-
|
|
96
|
+
const store = options.store ?? getMemoryStore(options);
|
|
97
|
+
await store.setToken({
|
|
98
|
+
accessToken: token.access_token,
|
|
99
|
+
refreshToken: token.refresh_token,
|
|
100
|
+
});
|
|
73
101
|
return token.access_token;
|
|
74
102
|
};
|
|
75
103
|
|
|
76
104
|
const refreshTokenAuthentification = async (): Promise<string> => {
|
|
77
|
-
|
|
105
|
+
const store = options.store ?? getMemoryStore(options);
|
|
106
|
+
const oldToken = await store.getToken();
|
|
107
|
+
if (!oldToken?.refreshToken) {
|
|
78
108
|
throw new Error('No refresh token available');
|
|
79
109
|
}
|
|
80
110
|
const params = new URLSearchParams();
|
|
@@ -82,10 +112,10 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
82
112
|
params.append('client_secret', options.clientSecret);
|
|
83
113
|
params.append('securityOptions', 'SSL_OP_NO_SSLv3');
|
|
84
114
|
params.append('grant_type', 'refresh_token');
|
|
85
|
-
params.append('refresh_token',
|
|
115
|
+
params.append('refresh_token', oldToken.refreshToken);
|
|
86
116
|
|
|
87
117
|
const { data: token } = await axios.post(
|
|
88
|
-
`${
|
|
118
|
+
`${trimEnd(options.baseUrl, '/')}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
89
119
|
params,
|
|
90
120
|
{
|
|
91
121
|
timeout: 30000,
|
|
@@ -95,9 +125,10 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
95
125
|
withCredentials: false,
|
|
96
126
|
}
|
|
97
127
|
);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
128
|
+
await store.setToken({
|
|
129
|
+
accessToken: token.access_token,
|
|
130
|
+
refreshToken: token.refresh_token,
|
|
131
|
+
});
|
|
101
132
|
return token.access_token;
|
|
102
133
|
};
|
|
103
134
|
|
|
@@ -114,7 +145,7 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
114
145
|
params.append('password', password);
|
|
115
146
|
|
|
116
147
|
const { data: token } = await axios.post(
|
|
117
|
-
`${
|
|
148
|
+
`${trimEnd(options.baseUrl, '/')}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
118
149
|
params,
|
|
119
150
|
{
|
|
120
151
|
timeout: 30000,
|
|
@@ -124,19 +155,44 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
124
155
|
withCredentials: false,
|
|
125
156
|
}
|
|
126
157
|
);
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
const store = options.store ?? getMemoryStore(options);
|
|
159
|
+
await store.setToken({
|
|
160
|
+
accessToken: token.access_token,
|
|
161
|
+
refreshToken: token.refresh_token,
|
|
162
|
+
});
|
|
129
163
|
};
|
|
130
164
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
165
|
+
client.interceptors.request.use(async (request) => {
|
|
166
|
+
const newRequest = request;
|
|
167
|
+
const store = options.store ?? getMemoryStore(options);
|
|
168
|
+
const token = await store.getToken();
|
|
169
|
+
if (token?.accessToken) {
|
|
170
|
+
newRequest.headers.Authorization = `Bearer ${token.accessToken}`;
|
|
171
|
+
}
|
|
172
|
+
if (options.logCurl) {
|
|
173
|
+
const curl = new CurlHelper(newRequest).generateCommand();
|
|
134
174
|
if (options.onLog) options.onLog({ curl, message: 'getClient > Curl command' });
|
|
135
175
|
// eslint-disable-next-line no-console
|
|
136
176
|
else console.log({ curl, message: 'getClient > Curl command' });
|
|
137
|
-
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return newRequest;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
let isRefreshing = false;
|
|
183
|
+
let refreshSubscribers: RefreshSubscriber[] = [];
|
|
184
|
+
|
|
185
|
+
const executorRefresh = (config: any) => {
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
refreshSubscribers.push((token, error) => {
|
|
188
|
+
if (error) {
|
|
189
|
+
reject(formatError(error));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
resolve(client.request(config));
|
|
193
|
+
});
|
|
138
194
|
});
|
|
139
|
-
}
|
|
195
|
+
};
|
|
140
196
|
|
|
141
197
|
client.interceptors.response.use(
|
|
142
198
|
(response) => {
|
|
@@ -162,14 +218,38 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
162
218
|
}
|
|
163
219
|
|
|
164
220
|
if (status === 401) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
221
|
+
originalRequest.attemptCount += 1;
|
|
222
|
+
if (!isRefreshing) {
|
|
223
|
+
isRefreshing = true;
|
|
224
|
+
|
|
225
|
+
let authentification: () => Promise<string>;
|
|
226
|
+
if (options.secure) {
|
|
227
|
+
authentification = refreshTokenAuthentification;
|
|
228
|
+
} else {
|
|
229
|
+
authentification = async () => {
|
|
230
|
+
const store = options.store ?? getMemoryStore(options);
|
|
231
|
+
const token = await store.getToken();
|
|
232
|
+
if (!token?.refreshToken) {
|
|
233
|
+
return clientCredentialsAuthentification();
|
|
234
|
+
}
|
|
235
|
+
return refreshTokenAuthentification();
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
authentification()
|
|
240
|
+
.then((accessToken) => {
|
|
241
|
+
refreshSubscribers.forEach((cb) => cb(accessToken));
|
|
170
242
|
})
|
|
171
|
-
.catch(() =>
|
|
172
|
-
|
|
243
|
+
.catch((errorAuth) => {
|
|
244
|
+
refreshSubscribers.forEach((cb) => cb(undefined, errorAuth));
|
|
245
|
+
})
|
|
246
|
+
.finally(() => {
|
|
247
|
+
isRefreshing = false;
|
|
248
|
+
refreshSubscribers = [];
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return executorRefresh(originalRequest);
|
|
173
253
|
}
|
|
174
254
|
|
|
175
255
|
return Promise.reject(formatError(error));
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { AxiosInstance } from 'axios';
|
|
2
2
|
|
|
3
|
+
export type Token = {
|
|
4
|
+
accessToken: string;
|
|
5
|
+
refreshToken: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type Store = {
|
|
9
|
+
getToken: () => Promise<Token | undefined>;
|
|
10
|
+
setToken: (token: Token) => Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
|
|
3
13
|
export type ClientOptions = {
|
|
4
14
|
fleetId: string;
|
|
5
15
|
name?: string;
|
|
@@ -11,7 +21,7 @@ export type ClientOptions = {
|
|
|
11
21
|
secure?: boolean;
|
|
12
22
|
logCurl?: boolean;
|
|
13
23
|
logResponse?: boolean;
|
|
14
|
-
|
|
24
|
+
store?: Store;
|
|
15
25
|
onLog?: (...args: any[]) => void;
|
|
16
26
|
};
|
|
17
27
|
|
|
@@ -25,7 +35,6 @@ export type ClientError = {
|
|
|
25
35
|
};
|
|
26
36
|
|
|
27
37
|
export type Client = AxiosInstance & {
|
|
28
|
-
refreshToken?: string;
|
|
29
38
|
signInWithPassword: (username: string, password: string) => Promise<void>;
|
|
30
39
|
clientOptions: ClientOptions;
|
|
31
40
|
};
|