falconhub-apilibrary 1.1.0
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/LICENSE +15 -0
- package/README.md +22 -0
- package/dist/index.d.mts +357 -0
- package/dist/index.d.ts +357 -0
- package/dist/index.js +867 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +825 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
API: () => API,
|
|
34
|
+
AuthInterceptor: () => AuthInterceptor,
|
|
35
|
+
AuthService: () => AuthService,
|
|
36
|
+
CryptoService: () => CryptoService,
|
|
37
|
+
FalconHUBSDK: () => FalconHUBSDK,
|
|
38
|
+
TokenManager: () => TokenManager
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/types/ErrorResponse.ts
|
|
43
|
+
var ErrorResponse = class extends Error {
|
|
44
|
+
constructor(message, statusCode, responseData) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.statusCode = statusCode;
|
|
47
|
+
this.responseData = responseData;
|
|
48
|
+
this.name = "FalconHUB API Error";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/core/API.ts
|
|
53
|
+
var API = class {
|
|
54
|
+
constructor(properties, cryptoService, authInterceptor, getAccessToken, getRefreshToken) {
|
|
55
|
+
this.properties = properties;
|
|
56
|
+
this.cryptoService = cryptoService;
|
|
57
|
+
this.getAccessToken = getAccessToken;
|
|
58
|
+
this.getRefreshToken = getRefreshToken;
|
|
59
|
+
this.authInterceptor = authInterceptor;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Ejecuta una request HTTP al API
|
|
63
|
+
*
|
|
64
|
+
* @param options - Opciones de la request
|
|
65
|
+
* @returns Respuesta parseada del tipo esperado
|
|
66
|
+
*/
|
|
67
|
+
async apiExecute(options) {
|
|
68
|
+
const {
|
|
69
|
+
endpoint,
|
|
70
|
+
method,
|
|
71
|
+
body,
|
|
72
|
+
requiresSignature,
|
|
73
|
+
requiresAuth,
|
|
74
|
+
timeStamp
|
|
75
|
+
} = options;
|
|
76
|
+
const context = {
|
|
77
|
+
requestId: this.cryptoService.generateUniqueId(),
|
|
78
|
+
startTime: Date.now(),
|
|
79
|
+
retryCount: 0,
|
|
80
|
+
metadata: {}
|
|
81
|
+
};
|
|
82
|
+
try {
|
|
83
|
+
const url = `${this.properties.url}/${endpoint}`;
|
|
84
|
+
const headers = {};
|
|
85
|
+
if (body && method !== "GET") {
|
|
86
|
+
headers["Content-Type"] = "application/json";
|
|
87
|
+
}
|
|
88
|
+
;
|
|
89
|
+
const accessToken = this.getAccessToken();
|
|
90
|
+
if (accessToken) {
|
|
91
|
+
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
92
|
+
}
|
|
93
|
+
;
|
|
94
|
+
const newTimeStamp = timeStamp || (/* @__PURE__ */ new Date()).toString();
|
|
95
|
+
headers["X-Timestamp"] = newTimeStamp;
|
|
96
|
+
let bodyString;
|
|
97
|
+
if (body && method !== "GET") {
|
|
98
|
+
bodyString = JSON.stringify(body);
|
|
99
|
+
}
|
|
100
|
+
;
|
|
101
|
+
if (requiresSignature && bodyString) {
|
|
102
|
+
try {
|
|
103
|
+
const refreshToken = this.getRefreshToken();
|
|
104
|
+
if (refreshToken) {
|
|
105
|
+
const signature = this.cryptoService.genSignature(
|
|
106
|
+
bodyString,
|
|
107
|
+
newTimeStamp,
|
|
108
|
+
refreshToken
|
|
109
|
+
);
|
|
110
|
+
if (signature) {
|
|
111
|
+
headers["X-Signature"] = signature;
|
|
112
|
+
}
|
|
113
|
+
;
|
|
114
|
+
}
|
|
115
|
+
;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
throw new ErrorResponse(
|
|
118
|
+
"Failed to generate request signature.",
|
|
119
|
+
401
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
;
|
|
123
|
+
}
|
|
124
|
+
;
|
|
125
|
+
const requestConfig = {
|
|
126
|
+
url,
|
|
127
|
+
method,
|
|
128
|
+
headers,
|
|
129
|
+
body: bodyString,
|
|
130
|
+
requiresAuth,
|
|
131
|
+
requiresSignature
|
|
132
|
+
};
|
|
133
|
+
const modifiedConfig = this.authInterceptor ? await this.authInterceptor.onRequest(requestConfig, context) : requestConfig;
|
|
134
|
+
const requestInit = {
|
|
135
|
+
method: modifiedConfig.method,
|
|
136
|
+
headers: modifiedConfig.headers,
|
|
137
|
+
mode: "cors",
|
|
138
|
+
credentials: "omit"
|
|
139
|
+
};
|
|
140
|
+
if (bodyString) {
|
|
141
|
+
requestInit.body = bodyString;
|
|
142
|
+
}
|
|
143
|
+
;
|
|
144
|
+
const response = await fetch(
|
|
145
|
+
modifiedConfig.url,
|
|
146
|
+
requestInit
|
|
147
|
+
);
|
|
148
|
+
if (response.ok) {
|
|
149
|
+
const newAccessToken = response.headers.get("X-New-Access-Token");
|
|
150
|
+
const newRefreshToken = response.headers.get("X-New-Refresh-Token");
|
|
151
|
+
if (newAccessToken || newRefreshToken) {
|
|
152
|
+
this.properties.onTokensRefreshed?.({
|
|
153
|
+
accessToken: newAccessToken,
|
|
154
|
+
refreshToken: newRefreshToken
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
;
|
|
158
|
+
const contentType = response.headers.get("Content-Type");
|
|
159
|
+
if (contentType && contentType.includes("application/json")) {
|
|
160
|
+
const responseText = await response.text();
|
|
161
|
+
return JSON.parse(responseText);
|
|
162
|
+
}
|
|
163
|
+
;
|
|
164
|
+
return void 0;
|
|
165
|
+
} else {
|
|
166
|
+
await this.handleErrorResponse(response);
|
|
167
|
+
}
|
|
168
|
+
;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (error instanceof ErrorResponse) {
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
;
|
|
174
|
+
throw new ErrorResponse(
|
|
175
|
+
"Unexpected error occurred during API request.",
|
|
176
|
+
500
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Maneja errores HTTP y lanza excepciones apropiadas
|
|
183
|
+
*/
|
|
184
|
+
async handleErrorResponse(response) {
|
|
185
|
+
const statusCode = response.status;
|
|
186
|
+
let errorMessage = `Error ${statusCode}`;
|
|
187
|
+
let responseData = null;
|
|
188
|
+
try {
|
|
189
|
+
const contentType = response.headers.get("Content-Type");
|
|
190
|
+
if (contentType && contentType.includes("application/json")) {
|
|
191
|
+
responseData = await response.json();
|
|
192
|
+
errorMessage = responseData.message || responseData.error || errorMessage;
|
|
193
|
+
} else {
|
|
194
|
+
errorMessage = await response.text();
|
|
195
|
+
}
|
|
196
|
+
;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
}
|
|
199
|
+
;
|
|
200
|
+
switch (statusCode) {
|
|
201
|
+
case 401:
|
|
202
|
+
const authError = new ErrorResponse(
|
|
203
|
+
"Unauthorized access.",
|
|
204
|
+
401,
|
|
205
|
+
responseData
|
|
206
|
+
);
|
|
207
|
+
this.properties.onAuthError?.(authError);
|
|
208
|
+
throw authError;
|
|
209
|
+
case 403:
|
|
210
|
+
throw new ErrorResponse(
|
|
211
|
+
"No tienes permisos para realizar esta acci\xF3n.",
|
|
212
|
+
403,
|
|
213
|
+
responseData
|
|
214
|
+
);
|
|
215
|
+
case 404:
|
|
216
|
+
throw new ErrorResponse(
|
|
217
|
+
"Recurso no encontrado.",
|
|
218
|
+
404,
|
|
219
|
+
responseData
|
|
220
|
+
);
|
|
221
|
+
case 422:
|
|
222
|
+
throw new ErrorResponse(
|
|
223
|
+
errorMessage || "Datos de entrada inv\xE1lidos.",
|
|
224
|
+
422,
|
|
225
|
+
responseData
|
|
226
|
+
);
|
|
227
|
+
case 500:
|
|
228
|
+
throw new ErrorResponse(
|
|
229
|
+
"Error interno del servidor.",
|
|
230
|
+
500,
|
|
231
|
+
responseData
|
|
232
|
+
);
|
|
233
|
+
default:
|
|
234
|
+
throw new ErrorResponse(
|
|
235
|
+
errorMessage,
|
|
236
|
+
statusCode,
|
|
237
|
+
responseData
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
;
|
|
241
|
+
}
|
|
242
|
+
//#region REQUEST METHODS
|
|
243
|
+
async executeGET(endpoint, requiresAuth = false) {
|
|
244
|
+
return this.apiExecute({
|
|
245
|
+
endpoint,
|
|
246
|
+
method: "GET",
|
|
247
|
+
requiresAuth,
|
|
248
|
+
requiresSignature: false
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
async executePOST(endpoint, body, requiresAuth = true, requiresSignature = true) {
|
|
252
|
+
return this.apiExecute({
|
|
253
|
+
endpoint,
|
|
254
|
+
method: "POST",
|
|
255
|
+
body,
|
|
256
|
+
requiresAuth,
|
|
257
|
+
requiresSignature
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
async executePATCH(endpoint, body, requiresAuth = true, requiresSignature = true) {
|
|
261
|
+
return this.apiExecute({
|
|
262
|
+
endpoint,
|
|
263
|
+
method: "PATCH",
|
|
264
|
+
body,
|
|
265
|
+
requiresAuth,
|
|
266
|
+
requiresSignature
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
async executeDELETE(endpoint, body, requiresAuth = true, requiresSignature = false) {
|
|
270
|
+
return this.apiExecute({
|
|
271
|
+
endpoint,
|
|
272
|
+
method: "DELETE",
|
|
273
|
+
body,
|
|
274
|
+
requiresAuth,
|
|
275
|
+
requiresSignature
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
async executePUT(endpoint, body, requiresAuth = true, requiresSignature = true) {
|
|
279
|
+
return this.apiExecute({
|
|
280
|
+
endpoint,
|
|
281
|
+
method: "PUT",
|
|
282
|
+
body,
|
|
283
|
+
requiresAuth,
|
|
284
|
+
requiresSignature
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
async executePublicRequest(endpoint, method, body, timestamp) {
|
|
288
|
+
return this.apiExecute({
|
|
289
|
+
endpoint,
|
|
290
|
+
method,
|
|
291
|
+
body,
|
|
292
|
+
requiresAuth: false,
|
|
293
|
+
requiresSignature: false,
|
|
294
|
+
timeStamp: timestamp
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
//#endregion
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// src/core/CryptoService.ts
|
|
301
|
+
var CryptoJS = __toESM(require("crypto-js"));
|
|
302
|
+
var CryptoService = class {
|
|
303
|
+
/**
|
|
304
|
+
* Encripta el texto plano utilizando AES con una clave derivada del secretWord y el timestamp
|
|
305
|
+
*
|
|
306
|
+
* @param plaintText -- Texto plano a encriptar
|
|
307
|
+
* @param secretWord - Palabra secreta utilizada para derivar la clave
|
|
308
|
+
* @param timestamp - Timestamp actual
|
|
309
|
+
* @returns Texto encriptado en formato Base64
|
|
310
|
+
*/
|
|
311
|
+
encrypt(plaintText, secretWord, timestamp) {
|
|
312
|
+
const fullKey = `${secretWord}:${timestamp}`;
|
|
313
|
+
const key = CryptoJS.SHA256(fullKey);
|
|
314
|
+
const iv = CryptoJS.lib.WordArray.random(16);
|
|
315
|
+
const encrypted = CryptoJS.AES.encrypt(plaintText, key, {
|
|
316
|
+
iv,
|
|
317
|
+
mode: CryptoJS.mode.CBC,
|
|
318
|
+
padding: CryptoJS.pad.Pkcs7
|
|
319
|
+
});
|
|
320
|
+
const ivAndCipher = iv.concat(encrypted.ciphertext);
|
|
321
|
+
return CryptoJS.enc.Base64.stringify(ivAndCipher);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Genera la firma Signature para las operaciones de tipo POST
|
|
325
|
+
*
|
|
326
|
+
* @param body - Cuerpo de la solicitud
|
|
327
|
+
* @param timestamp - Timestamp actual
|
|
328
|
+
* @param refreshToken - Refresh token del usuario
|
|
329
|
+
* @returns Firma signature en formato Base64
|
|
330
|
+
*/
|
|
331
|
+
genSignature(body, timestamp, refreshToken) {
|
|
332
|
+
const message = `${body}:${timestamp}`;
|
|
333
|
+
const hmac = CryptoJS.HmacSHA256(message, refreshToken);
|
|
334
|
+
return CryptoJS.enc.Base64.stringify(hmac);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Desencripta texto usando AES-256-CBC
|
|
338
|
+
*
|
|
339
|
+
* @param encryptedBase64 - Texto encriptado en base64
|
|
340
|
+
* @param secret - Clave secreta
|
|
341
|
+
* @param timestamp - Timestamp usado en la encriptación
|
|
342
|
+
* @returns Texto desencriptado
|
|
343
|
+
*/
|
|
344
|
+
decrypt(encryptedText, secretWord, timestamp) {
|
|
345
|
+
try {
|
|
346
|
+
const fullKey = `${secretWord}:${timestamp}`;
|
|
347
|
+
const key = CryptoJS.SHA256(fullKey);
|
|
348
|
+
const ivAndCipher = CryptoJS.enc.Base64.parse(encryptedText);
|
|
349
|
+
const iv = CryptoJS.lib.WordArray.create(ivAndCipher.words.slice(0, 4));
|
|
350
|
+
const ciphertext = CryptoJS.lib.WordArray.create(
|
|
351
|
+
ivAndCipher.words.slice(4),
|
|
352
|
+
ivAndCipher.sigBytes - 16
|
|
353
|
+
);
|
|
354
|
+
const decrypted = CryptoJS.AES.decrypt(
|
|
355
|
+
{ ciphertext },
|
|
356
|
+
key,
|
|
357
|
+
{
|
|
358
|
+
iv,
|
|
359
|
+
mode: CryptoJS.mode.CBC,
|
|
360
|
+
padding: CryptoJS.pad.Pkcs7
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
return CryptoJS.enc.Utf8.stringify(decrypted);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
throw new Error("Decryption failed, ex:" + error);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Verifica una firma HMAC-SHA256 (útil para testing o validación)
|
|
370
|
+
*
|
|
371
|
+
* @param body - Cuerpo original
|
|
372
|
+
* @param timestamp - Timestamp original
|
|
373
|
+
* @param refreshToken - Token usado como clave
|
|
374
|
+
* @param signature - Firma a verificar
|
|
375
|
+
* @returns true si la firma es válida
|
|
376
|
+
*/
|
|
377
|
+
verifySignature(body, timestamp, refreshToken, signature) {
|
|
378
|
+
try {
|
|
379
|
+
const expectedSignature = this.genSignature(body, timestamp, refreshToken);
|
|
380
|
+
return expectedSignature === signature;
|
|
381
|
+
} catch {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Genera un hash SHA256 de un texto (útil para fingerprints)
|
|
387
|
+
*
|
|
388
|
+
* @param text - Texto a hashear
|
|
389
|
+
* @returns Hash en formato hexadecimal
|
|
390
|
+
*/
|
|
391
|
+
sha256(text) {
|
|
392
|
+
return CryptoJS.SHA256(text).toString(CryptoJS.enc.Hex);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Genera un identificador único basado en timestamp y random
|
|
396
|
+
* (útil para request IDs)
|
|
397
|
+
*
|
|
398
|
+
* @returns ID único
|
|
399
|
+
*/
|
|
400
|
+
generateUniqueId() {
|
|
401
|
+
const timestamp = Date.now().toString(36);
|
|
402
|
+
const randomPart = Math.random().toString(36).substring(2, 15);
|
|
403
|
+
return `${timestamp}-${randomPart}`;
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/core/TokenManager.ts
|
|
408
|
+
var TokenManager = class {
|
|
409
|
+
constructor(onTokensChanged, onSessionExpiring, onSessionExpired) {
|
|
410
|
+
this.tokens = null;
|
|
411
|
+
this.sessionCheckInterval = null;
|
|
412
|
+
this.hasNotifiedExpiring = false;
|
|
413
|
+
this.onTokensChanged = onTokensChanged;
|
|
414
|
+
this.onSessionExpiring = onSessionExpiring;
|
|
415
|
+
this.onSessionExpired = onSessionExpired;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Guarda los tokens de autenticación y dispara el callback onTokensChanged.
|
|
419
|
+
*/
|
|
420
|
+
setTokens(tokens) {
|
|
421
|
+
this.tokens = tokens;
|
|
422
|
+
this.hasNotifiedExpiring = false;
|
|
423
|
+
this.onTokensChanged?.(this.tokens);
|
|
424
|
+
if (!tokens.rememberMe) {
|
|
425
|
+
this.startExpirationCheck();
|
|
426
|
+
} else {
|
|
427
|
+
this.stopExpirationCheck();
|
|
428
|
+
}
|
|
429
|
+
;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Obtiene los tokens de autenticación almacenados.
|
|
433
|
+
*/
|
|
434
|
+
getTokens() {
|
|
435
|
+
return this.tokens;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Limpia los tokens de autenticación almacenados y dispara el callback onTokensChanged.
|
|
439
|
+
*/
|
|
440
|
+
clearTokens() {
|
|
441
|
+
this.stopExpirationCheck();
|
|
442
|
+
this.tokens = null;
|
|
443
|
+
this.onTokensChanged?.(this.tokens);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Valida si existen tokens y si el access token no ha expirado.
|
|
447
|
+
*/
|
|
448
|
+
hasValidTokens() {
|
|
449
|
+
if (!this.tokens) return false;
|
|
450
|
+
const now = Date.now();
|
|
451
|
+
const expiresAt = this.tokens.expiresAt.getTime();
|
|
452
|
+
return now < expiresAt;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Valida si el access token expirará dentro del umbral especificado (en minutos).
|
|
456
|
+
*/
|
|
457
|
+
isTokenExpiringSoon(thresholdMinutes) {
|
|
458
|
+
if (!this.tokens) return false;
|
|
459
|
+
const now = Date.now();
|
|
460
|
+
const expiresAt = this.tokens.expiresAt.getTime();
|
|
461
|
+
const thresholdMs = thresholdMinutes * 60 * 1e3;
|
|
462
|
+
const timeLeft = expiresAt - now;
|
|
463
|
+
return timeLeft > 0 && timeLeft <= thresholdMs;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Obtiene el tiempo restante hasta la expiración del access token en milisegundos.
|
|
467
|
+
*/
|
|
468
|
+
getTimeUntilExpiry() {
|
|
469
|
+
if (!this.tokens) return null;
|
|
470
|
+
const now = Date.now();
|
|
471
|
+
const expiresAt = this.tokens.expiresAt.getTime();
|
|
472
|
+
const timeLeft = Math.max(0, expiresAt - now);
|
|
473
|
+
return timeLeft;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Obtiene el access token almacenado.
|
|
477
|
+
*/
|
|
478
|
+
getAccessToken() {
|
|
479
|
+
return this.tokens ? this.tokens.accessToken : null;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Obtiene el refresh token almacenado.
|
|
483
|
+
*/
|
|
484
|
+
getRefreshToken() {
|
|
485
|
+
return this.tokens ? this.tokens.refreshToken : null;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Verifica si el auto-refresh debe ejecutarse
|
|
489
|
+
* Solo si el usuario activó "rememberMe" durante el login
|
|
490
|
+
*/
|
|
491
|
+
shouldAutoRefresh() {
|
|
492
|
+
if (!this.tokens) return false;
|
|
493
|
+
return this.tokens.rememberMe === true;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Inicia el monitoreo de expiración para sesiones sin rememberMe
|
|
497
|
+
* - Notifica cuando quedan 5 minutos (onSessionExpiring)
|
|
498
|
+
* - Ejecuta logout cuando queda 1 minuto (onSessionExpired)
|
|
499
|
+
*/
|
|
500
|
+
startExpirationCheck() {
|
|
501
|
+
this.stopExpirationCheck();
|
|
502
|
+
this.sessionCheckInterval = setInterval(async () => {
|
|
503
|
+
if (!this.tokens) {
|
|
504
|
+
this.stopExpirationCheck();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
;
|
|
508
|
+
const timeLeft = this.getTimeUntilExpiry();
|
|
509
|
+
if (timeLeft === null) return;
|
|
510
|
+
const minutesLeft = Math.floor(timeLeft / (60 * 1e3));
|
|
511
|
+
if (minutesLeft <= 5 && minutesLeft > 1 && !this.hasNotifiedExpiring) {
|
|
512
|
+
this.hasNotifiedExpiring = true;
|
|
513
|
+
this.onSessionExpiring?.(minutesLeft);
|
|
514
|
+
}
|
|
515
|
+
;
|
|
516
|
+
if (minutesLeft <= 1) {
|
|
517
|
+
this.stopExpirationCheck();
|
|
518
|
+
if (this.onSessionExpired) {
|
|
519
|
+
await this.onSessionExpired();
|
|
520
|
+
}
|
|
521
|
+
;
|
|
522
|
+
}
|
|
523
|
+
;
|
|
524
|
+
}, 6e4);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Detiene el monitoreo de expiración
|
|
528
|
+
*/
|
|
529
|
+
stopExpirationCheck() {
|
|
530
|
+
if (this.sessionCheckInterval) {
|
|
531
|
+
clearInterval(this.sessionCheckInterval);
|
|
532
|
+
this.sessionCheckInterval = null;
|
|
533
|
+
}
|
|
534
|
+
;
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// src/core/interceptors/AuthInterceptor.ts
|
|
539
|
+
var AuthInterceptor = class {
|
|
540
|
+
constructor(config) {
|
|
541
|
+
this.name = "AuthInterceptor";
|
|
542
|
+
this.refreshPromise = null;
|
|
543
|
+
this.isRefreshing = false;
|
|
544
|
+
this.config = {
|
|
545
|
+
enabled: config.enabled ?? true,
|
|
546
|
+
thresholdMinutes: config.thresholdMinutes ?? 5,
|
|
547
|
+
isTokenExpiringSoon: config.isTokenExpiringSoon,
|
|
548
|
+
hasValidTokens: config.hasValidTokens,
|
|
549
|
+
shouldAutoRefresh: config.shouldAutoRefresh,
|
|
550
|
+
refreshSession: config.refreshSession
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
async onRequest(config, context) {
|
|
554
|
+
if (!config.requiresAuth || !this.config.enabled) {
|
|
555
|
+
return config;
|
|
556
|
+
}
|
|
557
|
+
;
|
|
558
|
+
if (!this.config.hasValidTokens()) {
|
|
559
|
+
return config;
|
|
560
|
+
}
|
|
561
|
+
;
|
|
562
|
+
if (!this.config.shouldAutoRefresh()) {
|
|
563
|
+
return config;
|
|
564
|
+
}
|
|
565
|
+
;
|
|
566
|
+
if (this.config.isTokenExpiringSoon()) {
|
|
567
|
+
try {
|
|
568
|
+
if (this.isRefreshing && this.refreshPromise) {
|
|
569
|
+
await this.refreshPromise;
|
|
570
|
+
} else {
|
|
571
|
+
this.isRefreshing = true;
|
|
572
|
+
this.refreshPromise = this.config.refreshSession();
|
|
573
|
+
await this.refreshPromise;
|
|
574
|
+
this.isRefreshing = false;
|
|
575
|
+
this.refreshPromise = null;
|
|
576
|
+
}
|
|
577
|
+
;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
this.isRefreshing = false;
|
|
580
|
+
this.refreshPromise = null;
|
|
581
|
+
}
|
|
582
|
+
;
|
|
583
|
+
}
|
|
584
|
+
;
|
|
585
|
+
return config;
|
|
586
|
+
}
|
|
587
|
+
updateConfig(config) {
|
|
588
|
+
this.config = {
|
|
589
|
+
...this.config,
|
|
590
|
+
...config
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
reset() {
|
|
594
|
+
this.isRefreshing = false;
|
|
595
|
+
this.refreshPromise = null;
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/services/AuthService.ts
|
|
600
|
+
var AuthService = class {
|
|
601
|
+
constructor(serviceProperties, tokenManager, cryptoService, api) {
|
|
602
|
+
this.api = api;
|
|
603
|
+
this.crytpoService = cryptoService;
|
|
604
|
+
this.tokenManager = tokenManager;
|
|
605
|
+
this.serviceProperties = serviceProperties;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Login con las credenciales proporcionadas
|
|
609
|
+
* @param credentials - Objeto con email, password y rememberMe
|
|
610
|
+
* @returns Retorna el cuerpo de respuesta del login.
|
|
611
|
+
*/
|
|
612
|
+
async login(credentials) {
|
|
613
|
+
const timestamp = Date.now().toString();
|
|
614
|
+
const encryptedEmail = this.crytpoService.encrypt(
|
|
615
|
+
credentials.email,
|
|
616
|
+
`${this.serviceProperties.originRequest}_${this.serviceProperties.domain}`,
|
|
617
|
+
timestamp
|
|
618
|
+
);
|
|
619
|
+
const encryptedPassword = this.crytpoService.encrypt(
|
|
620
|
+
credentials.password,
|
|
621
|
+
`${this.serviceProperties.originRequest}_${this.serviceProperties.domain}`,
|
|
622
|
+
timestamp
|
|
623
|
+
);
|
|
624
|
+
const loginData = {
|
|
625
|
+
email: encryptedEmail,
|
|
626
|
+
password: encryptedPassword,
|
|
627
|
+
rememberMe: credentials.rememberMe
|
|
628
|
+
};
|
|
629
|
+
const response = await this.api.executePublicRequest(
|
|
630
|
+
"oauth/login",
|
|
631
|
+
"POST",
|
|
632
|
+
loginData,
|
|
633
|
+
timestamp
|
|
634
|
+
);
|
|
635
|
+
if (!response) {
|
|
636
|
+
throw new ErrorResponse("No response from server");
|
|
637
|
+
}
|
|
638
|
+
;
|
|
639
|
+
if (response.success) {
|
|
640
|
+
const data = response.data;
|
|
641
|
+
const expiresIn = parseInt(data.expiresIn.replace("s", ""));
|
|
642
|
+
const expiresAt = new Date(Date.now() + expiresIn * 1e3);
|
|
643
|
+
const tokens = {
|
|
644
|
+
accessToken: data.accessToken,
|
|
645
|
+
refreshToken: data.refreshToken,
|
|
646
|
+
expiresAt,
|
|
647
|
+
rememberMe: credentials.rememberMe
|
|
648
|
+
};
|
|
649
|
+
await this.tokenManager.setTokens(tokens);
|
|
650
|
+
this.serviceProperties.onTokensUpdated?.(tokens);
|
|
651
|
+
const responseData = {
|
|
652
|
+
success: data.accessToken ? true : false,
|
|
653
|
+
data,
|
|
654
|
+
message: response.message,
|
|
655
|
+
responseTime: "3s"
|
|
656
|
+
};
|
|
657
|
+
return responseData;
|
|
658
|
+
}
|
|
659
|
+
return response;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Valida la sesión actual y retorna información de expiración
|
|
663
|
+
*
|
|
664
|
+
* El servidor puede responder de 2 formas:
|
|
665
|
+
* 1. Sesión renovada (sliding window): retorna nuevos tokens
|
|
666
|
+
* 2. Sesión válida: retorna info de expiración sin renovar
|
|
667
|
+
*/
|
|
668
|
+
async validateSession() {
|
|
669
|
+
const response = await this.api.executePOST(
|
|
670
|
+
"oauth/validateSession"
|
|
671
|
+
);
|
|
672
|
+
if (!response) {
|
|
673
|
+
throw new ErrorResponse("No response from server");
|
|
674
|
+
}
|
|
675
|
+
;
|
|
676
|
+
if (response.success && response.data) {
|
|
677
|
+
const respData = response.data;
|
|
678
|
+
if ("expiresIn" in respData && respData.expiresIn) {
|
|
679
|
+
const sessionData = respData;
|
|
680
|
+
const expiresIn = parseInt(sessionData.expiresIn.replace("s", ""));
|
|
681
|
+
const expiresAt = new Date(Date.now() + expiresIn * 1e3);
|
|
682
|
+
const currentTokens = this.tokenManager.getTokens();
|
|
683
|
+
const rememberMe = currentTokens?.rememberMe ?? sessionData.isRemembered;
|
|
684
|
+
const tokens = {
|
|
685
|
+
accessToken: sessionData.accessToken,
|
|
686
|
+
refreshToken: sessionData.refreshToken,
|
|
687
|
+
expiresAt,
|
|
688
|
+
rememberMe
|
|
689
|
+
};
|
|
690
|
+
this.tokenManager.setTokens(tokens);
|
|
691
|
+
this.serviceProperties.onTokensUpdated?.(tokens);
|
|
692
|
+
} else if ("expiresAt" in respData && respData.expiresAt) {
|
|
693
|
+
const sessionData = respData;
|
|
694
|
+
if (sessionData.remainingMinutes <= 5 && sessionData.remainingMinutes > 1) {
|
|
695
|
+
this.serviceProperties.onSessionExpiring?.(sessionData.remainingMinutes);
|
|
696
|
+
} else if (sessionData.remainingMinutes <= 1) {
|
|
697
|
+
if (this.serviceProperties.onSessionExpired) {
|
|
698
|
+
await this.serviceProperties.onSessionExpired();
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
;
|
|
704
|
+
return response;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Refresca los tokens usando el refresh token
|
|
708
|
+
*/
|
|
709
|
+
async refreshTokens(rememberMe) {
|
|
710
|
+
const refreshToken = this.tokenManager.getRefreshToken();
|
|
711
|
+
if (!refreshToken) {
|
|
712
|
+
throw new ErrorResponse("No refresh token available");
|
|
713
|
+
}
|
|
714
|
+
;
|
|
715
|
+
const response = await this.api.executePublicRequest(
|
|
716
|
+
"oauth/refresh",
|
|
717
|
+
"POST",
|
|
718
|
+
{ refreshToken }
|
|
719
|
+
);
|
|
720
|
+
if (!response) {
|
|
721
|
+
throw new ErrorResponse("No response from server");
|
|
722
|
+
}
|
|
723
|
+
;
|
|
724
|
+
if (response.success) {
|
|
725
|
+
const sessionData = response.data;
|
|
726
|
+
const currentTokens = this.tokenManager.getTokens();
|
|
727
|
+
const shouldRemember = rememberMe ?? currentTokens?.rememberMe ?? false;
|
|
728
|
+
const expiresIn = parseInt(sessionData.expiresIn.replace("s", ""));
|
|
729
|
+
const expiresAt = new Date(Date.now() + expiresIn * 1e3);
|
|
730
|
+
const tokens = {
|
|
731
|
+
accessToken: sessionData.accessToken,
|
|
732
|
+
refreshToken: sessionData.refreshToken,
|
|
733
|
+
expiresAt,
|
|
734
|
+
rememberMe: shouldRemember
|
|
735
|
+
};
|
|
736
|
+
this.tokenManager.setTokens(tokens);
|
|
737
|
+
this.serviceProperties.onTokensUpdated?.(tokens);
|
|
738
|
+
}
|
|
739
|
+
;
|
|
740
|
+
return response;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Cierra sesión: notifica al servidor y limpia tokens
|
|
744
|
+
*/
|
|
745
|
+
async logout() {
|
|
746
|
+
try {
|
|
747
|
+
await this.api.executePOST("oauth/logout");
|
|
748
|
+
} catch (error) {
|
|
749
|
+
}
|
|
750
|
+
;
|
|
751
|
+
this.tokenManager.clearTokens();
|
|
752
|
+
this.serviceProperties.onTokensUpdated?.(null);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Verifica si el usuario está autenticado
|
|
756
|
+
*/
|
|
757
|
+
isAuthenticated() {
|
|
758
|
+
return this.tokenManager.hasValidTokens();
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Obtiene los tokens actuales
|
|
762
|
+
*/
|
|
763
|
+
getTokens() {
|
|
764
|
+
return this.tokenManager.getTokens();
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
// src/FalconHUBSDK.ts
|
|
769
|
+
var FalconHUBSDK = class {
|
|
770
|
+
constructor(serviceProperties) {
|
|
771
|
+
this.serviceProperties = serviceProperties;
|
|
772
|
+
this.cryptoService = new CryptoService();
|
|
773
|
+
this.tokenManager = new TokenManager(
|
|
774
|
+
this.serviceProperties.onTokensUpdated,
|
|
775
|
+
this.serviceProperties.onSessionExpiring,
|
|
776
|
+
async () => {
|
|
777
|
+
await this.auth.logout();
|
|
778
|
+
}
|
|
779
|
+
);
|
|
780
|
+
const autoRefreshConfig = this.normalizeAutoRefreshConfig(this.serviceProperties.autoRefresh);
|
|
781
|
+
if (autoRefreshConfig.enabled) {
|
|
782
|
+
this.authInterceptor = new AuthInterceptor({
|
|
783
|
+
enabled: true,
|
|
784
|
+
thresholdMinutes: autoRefreshConfig.thresholdMinutes,
|
|
785
|
+
isTokenExpiringSoon: () => this.tokenManager.isTokenExpiringSoon(autoRefreshConfig.thresholdMinutes),
|
|
786
|
+
hasValidTokens: () => this.tokenManager.hasValidTokens(),
|
|
787
|
+
shouldAutoRefresh: () => this.tokenManager.shouldAutoRefresh(),
|
|
788
|
+
refreshSession: async () => {
|
|
789
|
+
await this.auth.validateSession();
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
} else {
|
|
793
|
+
this.authInterceptor = null;
|
|
794
|
+
}
|
|
795
|
+
this.api = new API(
|
|
796
|
+
this.serviceProperties,
|
|
797
|
+
this.cryptoService,
|
|
798
|
+
this.authInterceptor,
|
|
799
|
+
() => this.tokenManager.getAccessToken(),
|
|
800
|
+
() => this.tokenManager.getRefreshToken()
|
|
801
|
+
);
|
|
802
|
+
this.auth = new AuthService(
|
|
803
|
+
this.serviceProperties,
|
|
804
|
+
this.tokenManager,
|
|
805
|
+
this.cryptoService,
|
|
806
|
+
this.api
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
normalizeAutoRefreshConfig(config) {
|
|
810
|
+
if (typeof config === "boolean") {
|
|
811
|
+
return { enabled: config, thresholdMinutes: 5 };
|
|
812
|
+
}
|
|
813
|
+
if (typeof config === "object" && config !== null) {
|
|
814
|
+
return {
|
|
815
|
+
enabled: config.enabled,
|
|
816
|
+
thresholdMinutes: config.thresholdMinutes ?? 5
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
return { enabled: true, thresholdMinutes: 5 };
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Inyecta tokens desde storage externo (cookies/localStorage/etc)
|
|
823
|
+
*/
|
|
824
|
+
setTokensFromExternal(accessToken, refreshToken, expiresAt, rememberMe = false) {
|
|
825
|
+
const tokens = {
|
|
826
|
+
accessToken,
|
|
827
|
+
refreshToken,
|
|
828
|
+
expiresAt,
|
|
829
|
+
rememberMe
|
|
830
|
+
};
|
|
831
|
+
this.tokenManager.setTokens(tokens);
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Obtiene los tokens actuales
|
|
835
|
+
*/
|
|
836
|
+
getTokens() {
|
|
837
|
+
return this.tokenManager.getTokens();
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Verifica si el usuario está autenticado
|
|
841
|
+
*/
|
|
842
|
+
isAuthenticated() {
|
|
843
|
+
return this.tokenManager.hasValidTokens();
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Verifica si la sesión está por expirar
|
|
847
|
+
*/
|
|
848
|
+
isSessionExpiringSoon(thresholdMinutes = 5) {
|
|
849
|
+
return this.tokenManager.isTokenExpiringSoon(thresholdMinutes);
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Obtiene el tiempo restante hasta la expiración en milisegundos
|
|
853
|
+
*/
|
|
854
|
+
getTimeUntilExpiry() {
|
|
855
|
+
return this.tokenManager.getTimeUntilExpiry();
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
859
|
+
0 && (module.exports = {
|
|
860
|
+
API,
|
|
861
|
+
AuthInterceptor,
|
|
862
|
+
AuthService,
|
|
863
|
+
CryptoService,
|
|
864
|
+
FalconHUBSDK,
|
|
865
|
+
TokenManager
|
|
866
|
+
});
|
|
867
|
+
//# sourceMappingURL=index.js.map
|