@vulog/aima-client 1.0.10 → 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/README.md +4 -0
- package/dist/index.d.mts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +103 -31
- package/dist/index.mjs +104 -32
- package/package.json +11 -11
- package/src/getClient.ts +120 -34
- package/src/types.ts +14 -4
package/README.md
CHANGED
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,18 +19,18 @@ 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 = {
|
|
18
26
|
formattedError: {
|
|
19
|
-
status
|
|
20
|
-
data
|
|
27
|
+
status?: number;
|
|
28
|
+
data?: any;
|
|
29
|
+
message?: string;
|
|
21
30
|
};
|
|
22
31
|
originalError: any;
|
|
23
32
|
};
|
|
24
33
|
type Client = AxiosInstance & {
|
|
25
|
-
refreshToken?: string;
|
|
26
34
|
signInWithPassword: (username: string, password: string) => Promise<void>;
|
|
27
35
|
clientOptions: ClientOptions;
|
|
28
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,18 +19,18 @@ 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 = {
|
|
18
26
|
formattedError: {
|
|
19
|
-
status
|
|
20
|
-
data
|
|
27
|
+
status?: number;
|
|
28
|
+
data?: any;
|
|
29
|
+
message?: string;
|
|
21
30
|
};
|
|
22
31
|
originalError: any;
|
|
23
32
|
};
|
|
24
33
|
type Client = AxiosInstance & {
|
|
25
|
-
refreshToken?: string;
|
|
26
34
|
signInWithPassword: (username: string, password: string) => Promise<void>;
|
|
27
35
|
clientOptions: ClientOptions;
|
|
28
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,25 +114,48 @@ 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) {
|
|
136
|
+
return {
|
|
137
|
+
originalError: error.toJSON(),
|
|
138
|
+
formattedError: {
|
|
139
|
+
status: error.response?.status ?? error.status,
|
|
140
|
+
data: error.response?.data,
|
|
141
|
+
message: error.message
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
119
145
|
return {
|
|
120
|
-
formattedError: {
|
|
121
|
-
|
|
122
|
-
data
|
|
123
|
-
},
|
|
124
|
-
originalError: error.toJSON ? error.toJSON() : error
|
|
146
|
+
formattedError: {},
|
|
147
|
+
originalError: JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error)))
|
|
125
148
|
};
|
|
126
149
|
};
|
|
127
150
|
var getClient = (options) => {
|
|
128
151
|
if (clientCache.has(options.name ?? options.fleetId)) {
|
|
129
152
|
const { options: cachedOptions, client: client2 } = clientCache.get(options.fleetId);
|
|
130
|
-
if (
|
|
153
|
+
if ((0, import_es_toolkit.isEqual)(cachedOptions, options)) {
|
|
131
154
|
return client2;
|
|
132
155
|
}
|
|
133
156
|
}
|
|
134
157
|
const client = import_axios.default.create({
|
|
135
|
-
baseURL:
|
|
158
|
+
baseURL: (0, import_es_toolkit.trimEnd)(options.baseUrl, "/"),
|
|
136
159
|
timeout: 3e4,
|
|
137
160
|
headers: {
|
|
138
161
|
"Cache-Control": "no-cache",
|
|
@@ -149,7 +172,7 @@ var getClient = (options) => {
|
|
|
149
172
|
params.append("securityOptions", "SSL_OP_NO_SSLv3");
|
|
150
173
|
params.append("grant_type", "client_credentials");
|
|
151
174
|
const { data: token } = await import_axios.default.post(
|
|
152
|
-
`${
|
|
175
|
+
`${(0, import_es_toolkit.trimEnd)(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
153
176
|
params,
|
|
154
177
|
{
|
|
155
178
|
timeout: 3e4,
|
|
@@ -159,11 +182,17 @@ var getClient = (options) => {
|
|
|
159
182
|
withCredentials: false
|
|
160
183
|
}
|
|
161
184
|
);
|
|
162
|
-
|
|
185
|
+
const store = options.store ?? getMemoryStore(options);
|
|
186
|
+
await store.setToken({
|
|
187
|
+
accessToken: token.access_token,
|
|
188
|
+
refreshToken: token.refresh_token
|
|
189
|
+
});
|
|
163
190
|
return token.access_token;
|
|
164
191
|
};
|
|
165
192
|
const refreshTokenAuthentification = async () => {
|
|
166
|
-
|
|
193
|
+
const store = options.store ?? getMemoryStore(options);
|
|
194
|
+
const oldToken = await store.getToken();
|
|
195
|
+
if (!oldToken?.refreshToken) {
|
|
167
196
|
throw new Error("No refresh token available");
|
|
168
197
|
}
|
|
169
198
|
const params = new URLSearchParams();
|
|
@@ -171,9 +200,9 @@ var getClient = (options) => {
|
|
|
171
200
|
params.append("client_secret", options.clientSecret);
|
|
172
201
|
params.append("securityOptions", "SSL_OP_NO_SSLv3");
|
|
173
202
|
params.append("grant_type", "refresh_token");
|
|
174
|
-
params.append("refresh_token",
|
|
203
|
+
params.append("refresh_token", oldToken.refreshToken);
|
|
175
204
|
const { data: token } = await import_axios.default.post(
|
|
176
|
-
`${
|
|
205
|
+
`${(0, import_es_toolkit.trimEnd)(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
177
206
|
params,
|
|
178
207
|
{
|
|
179
208
|
timeout: 3e4,
|
|
@@ -183,9 +212,10 @@ var getClient = (options) => {
|
|
|
183
212
|
withCredentials: false
|
|
184
213
|
}
|
|
185
214
|
);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
215
|
+
await store.setToken({
|
|
216
|
+
accessToken: token.access_token,
|
|
217
|
+
refreshToken: token.refresh_token
|
|
218
|
+
});
|
|
189
219
|
return token.access_token;
|
|
190
220
|
};
|
|
191
221
|
client.signInWithPassword = async (username, password) => {
|
|
@@ -200,7 +230,7 @@ var getClient = (options) => {
|
|
|
200
230
|
params.append("username", username);
|
|
201
231
|
params.append("password", password);
|
|
202
232
|
const { data: token } = await import_axios.default.post(
|
|
203
|
-
`${
|
|
233
|
+
`${(0, import_es_toolkit.trimEnd)(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
204
234
|
params,
|
|
205
235
|
{
|
|
206
236
|
timeout: 3e4,
|
|
@@ -210,17 +240,39 @@ var getClient = (options) => {
|
|
|
210
240
|
withCredentials: false
|
|
211
241
|
}
|
|
212
242
|
);
|
|
213
|
-
|
|
214
|
-
|
|
243
|
+
const store = options.store ?? getMemoryStore(options);
|
|
244
|
+
await store.setToken({
|
|
245
|
+
accessToken: token.access_token,
|
|
246
|
+
refreshToken: token.refresh_token
|
|
247
|
+
});
|
|
215
248
|
};
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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();
|
|
219
258
|
if (options.onLog) options.onLog({ curl, message: "getClient > Curl command" });
|
|
220
259
|
else console.log({ curl, message: "getClient > Curl command" });
|
|
221
|
-
|
|
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
|
+
});
|
|
222
274
|
});
|
|
223
|
-
}
|
|
275
|
+
};
|
|
224
276
|
client.interceptors.response.use(
|
|
225
277
|
(response) => {
|
|
226
278
|
if (options.logResponse) {
|
|
@@ -241,12 +293,32 @@ var getClient = (options) => {
|
|
|
241
293
|
return Promise.reject(formatError(error));
|
|
242
294
|
}
|
|
243
295
|
if (status === 401) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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);
|
|
250
322
|
}
|
|
251
323
|
return Promise.reject(formatError(error));
|
|
252
324
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/getClient.ts
|
|
2
|
-
import axios from "axios";
|
|
3
|
-
import
|
|
2
|
+
import axios, { AxiosError } from "axios";
|
|
3
|
+
import { isEqual, trimEnd } from "es-toolkit";
|
|
4
4
|
import { LRUCache } from "lru-cache";
|
|
5
5
|
|
|
6
6
|
// src/CurlHelper.ts
|
|
@@ -78,25 +78,48 @@ 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) {
|
|
100
|
+
return {
|
|
101
|
+
originalError: error.toJSON(),
|
|
102
|
+
formattedError: {
|
|
103
|
+
status: error.response?.status ?? error.status,
|
|
104
|
+
data: error.response?.data,
|
|
105
|
+
message: error.message
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
83
109
|
return {
|
|
84
|
-
formattedError: {
|
|
85
|
-
|
|
86
|
-
data
|
|
87
|
-
},
|
|
88
|
-
originalError: error.toJSON ? error.toJSON() : error
|
|
110
|
+
formattedError: {},
|
|
111
|
+
originalError: JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error)))
|
|
89
112
|
};
|
|
90
113
|
};
|
|
91
114
|
var getClient = (options) => {
|
|
92
115
|
if (clientCache.has(options.name ?? options.fleetId)) {
|
|
93
116
|
const { options: cachedOptions, client: client2 } = clientCache.get(options.fleetId);
|
|
94
|
-
if (
|
|
117
|
+
if (isEqual(cachedOptions, options)) {
|
|
95
118
|
return client2;
|
|
96
119
|
}
|
|
97
120
|
}
|
|
98
121
|
const client = axios.create({
|
|
99
|
-
baseURL:
|
|
122
|
+
baseURL: trimEnd(options.baseUrl, "/"),
|
|
100
123
|
timeout: 3e4,
|
|
101
124
|
headers: {
|
|
102
125
|
"Cache-Control": "no-cache",
|
|
@@ -113,7 +136,7 @@ var getClient = (options) => {
|
|
|
113
136
|
params.append("securityOptions", "SSL_OP_NO_SSLv3");
|
|
114
137
|
params.append("grant_type", "client_credentials");
|
|
115
138
|
const { data: token } = await axios.post(
|
|
116
|
-
`${
|
|
139
|
+
`${trimEnd(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
117
140
|
params,
|
|
118
141
|
{
|
|
119
142
|
timeout: 3e4,
|
|
@@ -123,11 +146,17 @@ var getClient = (options) => {
|
|
|
123
146
|
withCredentials: false
|
|
124
147
|
}
|
|
125
148
|
);
|
|
126
|
-
|
|
149
|
+
const store = options.store ?? getMemoryStore(options);
|
|
150
|
+
await store.setToken({
|
|
151
|
+
accessToken: token.access_token,
|
|
152
|
+
refreshToken: token.refresh_token
|
|
153
|
+
});
|
|
127
154
|
return token.access_token;
|
|
128
155
|
};
|
|
129
156
|
const refreshTokenAuthentification = async () => {
|
|
130
|
-
|
|
157
|
+
const store = options.store ?? getMemoryStore(options);
|
|
158
|
+
const oldToken = await store.getToken();
|
|
159
|
+
if (!oldToken?.refreshToken) {
|
|
131
160
|
throw new Error("No refresh token available");
|
|
132
161
|
}
|
|
133
162
|
const params = new URLSearchParams();
|
|
@@ -135,9 +164,9 @@ var getClient = (options) => {
|
|
|
135
164
|
params.append("client_secret", options.clientSecret);
|
|
136
165
|
params.append("securityOptions", "SSL_OP_NO_SSLv3");
|
|
137
166
|
params.append("grant_type", "refresh_token");
|
|
138
|
-
params.append("refresh_token",
|
|
167
|
+
params.append("refresh_token", oldToken.refreshToken);
|
|
139
168
|
const { data: token } = await axios.post(
|
|
140
|
-
`${
|
|
169
|
+
`${trimEnd(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
141
170
|
params,
|
|
142
171
|
{
|
|
143
172
|
timeout: 3e4,
|
|
@@ -147,9 +176,10 @@ var getClient = (options) => {
|
|
|
147
176
|
withCredentials: false
|
|
148
177
|
}
|
|
149
178
|
);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
179
|
+
await store.setToken({
|
|
180
|
+
accessToken: token.access_token,
|
|
181
|
+
refreshToken: token.refresh_token
|
|
182
|
+
});
|
|
153
183
|
return token.access_token;
|
|
154
184
|
};
|
|
155
185
|
client.signInWithPassword = async (username, password) => {
|
|
@@ -164,7 +194,7 @@ var getClient = (options) => {
|
|
|
164
194
|
params.append("username", username);
|
|
165
195
|
params.append("password", password);
|
|
166
196
|
const { data: token } = await axios.post(
|
|
167
|
-
`${
|
|
197
|
+
`${trimEnd(options.baseUrl, "/")}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
168
198
|
params,
|
|
169
199
|
{
|
|
170
200
|
timeout: 3e4,
|
|
@@ -174,17 +204,39 @@ var getClient = (options) => {
|
|
|
174
204
|
withCredentials: false
|
|
175
205
|
}
|
|
176
206
|
);
|
|
177
|
-
|
|
178
|
-
|
|
207
|
+
const store = options.store ?? getMemoryStore(options);
|
|
208
|
+
await store.setToken({
|
|
209
|
+
accessToken: token.access_token,
|
|
210
|
+
refreshToken: token.refresh_token
|
|
211
|
+
});
|
|
179
212
|
};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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();
|
|
183
222
|
if (options.onLog) options.onLog({ curl, message: "getClient > Curl command" });
|
|
184
223
|
else console.log({ curl, message: "getClient > Curl command" });
|
|
185
|
-
|
|
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
|
+
});
|
|
186
238
|
});
|
|
187
|
-
}
|
|
239
|
+
};
|
|
188
240
|
client.interceptors.response.use(
|
|
189
241
|
(response) => {
|
|
190
242
|
if (options.logResponse) {
|
|
@@ -205,12 +257,32 @@ var getClient = (options) => {
|
|
|
205
257
|
return Promise.reject(formatError(error));
|
|
206
258
|
}
|
|
207
259
|
if (status === 401) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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);
|
|
214
286
|
}
|
|
215
287
|
return Promise.reject(formatError(error));
|
|
216
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
|
-
import axios from 'axios';
|
|
2
|
-
import
|
|
1
|
+
import axios, { AxiosError } from 'axios';
|
|
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,28 +15,56 @@ 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) {
|
|
42
|
+
return {
|
|
43
|
+
originalError: error.toJSON(),
|
|
44
|
+
formattedError: {
|
|
45
|
+
status: error.response?.status ?? error.status,
|
|
46
|
+
data: error.response?.data,
|
|
47
|
+
message: error.message,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
18
51
|
|
|
19
52
|
return {
|
|
20
|
-
formattedError: {
|
|
21
|
-
|
|
22
|
-
data,
|
|
23
|
-
},
|
|
24
|
-
originalError: error.toJSON ? error.toJSON() : error,
|
|
53
|
+
formattedError: {},
|
|
54
|
+
originalError: JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error))),
|
|
25
55
|
};
|
|
26
56
|
};
|
|
27
57
|
|
|
28
58
|
const getClient = (options: ClientOptions): Client => {
|
|
29
59
|
if (clientCache.has(options.name ?? options.fleetId)) {
|
|
30
60
|
const { options: cachedOptions, client } = clientCache.get(options.fleetId)!;
|
|
31
|
-
if (
|
|
61
|
+
if (isEqual(cachedOptions, options)) {
|
|
32
62
|
return client;
|
|
33
63
|
}
|
|
34
64
|
}
|
|
35
65
|
|
|
36
66
|
const client = axios.create({
|
|
37
|
-
baseURL:
|
|
67
|
+
baseURL: trimEnd(options.baseUrl, '/'),
|
|
38
68
|
timeout: 30000,
|
|
39
69
|
headers: {
|
|
40
70
|
'Cache-Control': 'no-cache',
|
|
@@ -53,7 +83,7 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
53
83
|
params.append('grant_type', 'client_credentials');
|
|
54
84
|
|
|
55
85
|
const { data: token } = await axios.post(
|
|
56
|
-
`${
|
|
86
|
+
`${trimEnd(options.baseUrl, '/')}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
57
87
|
params,
|
|
58
88
|
{
|
|
59
89
|
timeout: 30000,
|
|
@@ -63,12 +93,18 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
63
93
|
withCredentials: false,
|
|
64
94
|
}
|
|
65
95
|
);
|
|
66
|
-
|
|
96
|
+
const store = options.store ?? getMemoryStore(options);
|
|
97
|
+
await store.setToken({
|
|
98
|
+
accessToken: token.access_token,
|
|
99
|
+
refreshToken: token.refresh_token,
|
|
100
|
+
});
|
|
67
101
|
return token.access_token;
|
|
68
102
|
};
|
|
69
103
|
|
|
70
104
|
const refreshTokenAuthentification = async (): Promise<string> => {
|
|
71
|
-
|
|
105
|
+
const store = options.store ?? getMemoryStore(options);
|
|
106
|
+
const oldToken = await store.getToken();
|
|
107
|
+
if (!oldToken?.refreshToken) {
|
|
72
108
|
throw new Error('No refresh token available');
|
|
73
109
|
}
|
|
74
110
|
const params = new URLSearchParams();
|
|
@@ -76,10 +112,10 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
76
112
|
params.append('client_secret', options.clientSecret);
|
|
77
113
|
params.append('securityOptions', 'SSL_OP_NO_SSLv3');
|
|
78
114
|
params.append('grant_type', 'refresh_token');
|
|
79
|
-
params.append('refresh_token',
|
|
115
|
+
params.append('refresh_token', oldToken.refreshToken);
|
|
80
116
|
|
|
81
117
|
const { data: token } = await axios.post(
|
|
82
|
-
`${
|
|
118
|
+
`${trimEnd(options.baseUrl, '/')}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
83
119
|
params,
|
|
84
120
|
{
|
|
85
121
|
timeout: 30000,
|
|
@@ -89,9 +125,10 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
89
125
|
withCredentials: false,
|
|
90
126
|
}
|
|
91
127
|
);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
await store.setToken({
|
|
129
|
+
accessToken: token.access_token,
|
|
130
|
+
refreshToken: token.refresh_token,
|
|
131
|
+
});
|
|
95
132
|
return token.access_token;
|
|
96
133
|
};
|
|
97
134
|
|
|
@@ -108,7 +145,7 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
108
145
|
params.append('password', password);
|
|
109
146
|
|
|
110
147
|
const { data: token } = await axios.post(
|
|
111
|
-
`${
|
|
148
|
+
`${trimEnd(options.baseUrl, '/')}/auth/realms/${options.fleetMaster ?? options.fleetId}/protocol/openid-connect/token`,
|
|
112
149
|
params,
|
|
113
150
|
{
|
|
114
151
|
timeout: 30000,
|
|
@@ -118,19 +155,44 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
118
155
|
withCredentials: false,
|
|
119
156
|
}
|
|
120
157
|
);
|
|
121
|
-
|
|
122
|
-
|
|
158
|
+
const store = options.store ?? getMemoryStore(options);
|
|
159
|
+
await store.setToken({
|
|
160
|
+
accessToken: token.access_token,
|
|
161
|
+
refreshToken: token.refresh_token,
|
|
162
|
+
});
|
|
123
163
|
};
|
|
124
164
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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();
|
|
128
174
|
if (options.onLog) options.onLog({ curl, message: 'getClient > Curl command' });
|
|
129
175
|
// eslint-disable-next-line no-console
|
|
130
176
|
else console.log({ curl, message: 'getClient > Curl command' });
|
|
131
|
-
|
|
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
|
+
});
|
|
132
194
|
});
|
|
133
|
-
}
|
|
195
|
+
};
|
|
134
196
|
|
|
135
197
|
client.interceptors.response.use(
|
|
136
198
|
(response) => {
|
|
@@ -156,14 +218,38 @@ const getClient = (options: ClientOptions): Client => {
|
|
|
156
218
|
}
|
|
157
219
|
|
|
158
220
|
if (status === 401) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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));
|
|
164
242
|
})
|
|
165
|
-
.catch(() =>
|
|
166
|
-
|
|
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);
|
|
167
253
|
}
|
|
168
254
|
|
|
169
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,20 +21,20 @@ 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
|
|
|
18
28
|
export type ClientError = {
|
|
19
29
|
formattedError: {
|
|
20
|
-
status
|
|
21
|
-
data
|
|
30
|
+
status?: number;
|
|
31
|
+
data?: any;
|
|
32
|
+
message?: string;
|
|
22
33
|
};
|
|
23
34
|
originalError: any;
|
|
24
35
|
};
|
|
25
36
|
|
|
26
37
|
export type Client = AxiosInstance & {
|
|
27
|
-
refreshToken?: string;
|
|
28
38
|
signInWithPassword: (username: string, password: string) => Promise<void>;
|
|
29
39
|
clientOptions: ClientOptions;
|
|
30
40
|
};
|