@dynamicore/jumio-sdk 1.0.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/dist/index.js ADDED
@@ -0,0 +1,993 @@
1
+ 'use strict';
2
+
3
+ var axios = require('axios');
4
+ var react = require('react');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var axios__default = /*#__PURE__*/_interopDefault(axios);
9
+
10
+ // src/core/errors.ts
11
+ var JumioError = class extends Error {
12
+ isJumioError = true;
13
+ statusCode;
14
+ rawData;
15
+ code;
16
+ constructor(message, options) {
17
+ super(message);
18
+ this.name = "JumioError";
19
+ this.statusCode = options?.statusCode;
20
+ this.rawData = options?.rawData;
21
+ this.code = options?.code;
22
+ if (options?.cause) {
23
+ this.cause = options.cause;
24
+ }
25
+ }
26
+ };
27
+ var JumioTimeoutError = class extends JumioError {
28
+ constructor(message = "El servicio de verificaci\xF3n de Jumio excedi\xF3 el tiempo l\xEDmite de espera.", rawData) {
29
+ super(message, { code: "TIMEOUT", rawData });
30
+ this.name = "JumioTimeoutError";
31
+ }
32
+ };
33
+ var JumioNetworkError = class extends JumioError {
34
+ constructor(message = "No fue posible comunicarse con los servidores de validaci\xF3n de identidad.", cause) {
35
+ super(message, { code: "NETWORK_ERROR", cause });
36
+ this.name = "JumioNetworkError";
37
+ }
38
+ };
39
+ var JumioValidationError = class extends JumioError {
40
+ constructor(message = "El documento no pudo ser validado o fue rechazado por Jumio.", rawData) {
41
+ super(message, { code: "VALIDATION_FAILED", rawData });
42
+ this.name = "JumioValidationError";
43
+ }
44
+ };
45
+ var JumioImageProcessingError = class extends JumioError {
46
+ constructor(message, cause) {
47
+ super(message, { code: "IMAGE_PROCESSING_ERROR", cause });
48
+ this.name = "JumioImageProcessingError";
49
+ }
50
+ };
51
+ var JumioPollingTimeoutError = class extends JumioError {
52
+ attempts;
53
+ constructor(attempts, message = "La validaci\xF3n sigue en proceso. Por favor verifica nuevamente m\xE1s tarde.", rawData) {
54
+ super(message, { code: "POLLING_TIMEOUT", rawData });
55
+ this.name = "JumioPollingTimeoutError";
56
+ this.attempts = attempts;
57
+ }
58
+ };
59
+ var JumioAbortError = class extends JumioError {
60
+ constructor(message = "La operaci\xF3n de validaci\xF3n de Jumio fue cancelada.") {
61
+ super(message, { code: "ABORTED" });
62
+ this.name = "JumioAbortError";
63
+ }
64
+ };
65
+ function isJumioError(error) {
66
+ return error instanceof JumioError || typeof error === "object" && error !== null && "isJumioError" in error && error.isJumioError === true;
67
+ }
68
+
69
+ // src/core/envelope.ts
70
+ var FINAL_JUMIO_STATUSES = [
71
+ "PROCESSED",
72
+ "APPROVED_VERIFIED",
73
+ "REJECTED",
74
+ "FAILED",
75
+ "ERROR",
76
+ "DENIED",
77
+ "EXPIRED",
78
+ "ABANDONED",
79
+ "DONE",
80
+ "COMPLETED",
81
+ "NOT_READABLE",
82
+ "FRAUD",
83
+ "UNSUPPORTED_ID_TYPE"
84
+ ];
85
+ var APPROVED_JUMIO_STATUSES = [
86
+ "APPROVED_VERIFIED",
87
+ "DONE",
88
+ "COMPLETED"
89
+ ];
90
+ function toLowerText(value) {
91
+ return String(value ?? "").trim().toLowerCase();
92
+ }
93
+ function toUpperText(value) {
94
+ return String(value ?? "").trim().toUpperCase();
95
+ }
96
+ function extractEnvelopeData(responseData) {
97
+ if (responseData && typeof responseData === "object" && "data" in responseData) {
98
+ const candidate = responseData.data;
99
+ if (candidate !== void 0) {
100
+ return candidate;
101
+ }
102
+ }
103
+ return responseData;
104
+ }
105
+ function extractNestedData(source, maxDepth = 4) {
106
+ let current = source;
107
+ let depth = 0;
108
+ while (depth < maxDepth && current && typeof current === "object" && "data" in current) {
109
+ const next = current.data;
110
+ if (next == null) break;
111
+ current = next;
112
+ depth += 1;
113
+ }
114
+ return current;
115
+ }
116
+ function extractJumioMessage(source) {
117
+ if (!source) return "";
118
+ if (typeof source === "string") return source.trim();
119
+ if (source instanceof Error) {
120
+ return source.message;
121
+ }
122
+ if (typeof source === "object") {
123
+ const record = source;
124
+ if (record.response && typeof record.response === "object") {
125
+ const responseObj = record.response;
126
+ const subMessage = extractJumioMessage(responseObj.data);
127
+ if (subMessage) return subMessage;
128
+ }
129
+ const directCandidates = [
130
+ record.message,
131
+ record.msg,
132
+ record.error,
133
+ record.detail,
134
+ record.errorMessage,
135
+ record.description,
136
+ record.reason
137
+ ];
138
+ for (const cand of directCandidates) {
139
+ if (typeof cand === "string" && cand.trim()) {
140
+ return cand.trim();
141
+ }
142
+ }
143
+ if (record.data) {
144
+ return extractJumioMessage(record.data);
145
+ }
146
+ }
147
+ return "";
148
+ }
149
+ function isFinalJumioStatus(status) {
150
+ const upperStatus = toUpperText(status);
151
+ if (!upperStatus) return false;
152
+ return FINAL_JUMIO_STATUSES.some((s) => s === upperStatus);
153
+ }
154
+ function isApprovedJumioStatus(status) {
155
+ const upperStatus = toUpperText(status);
156
+ if (!upperStatus) return false;
157
+ return upperStatus === "APPROVED_VERIFIED" || upperStatus === "DONE" || upperStatus === "COMPLETED";
158
+ }
159
+ function isPassedJumioDecision(decision) {
160
+ return toUpperText(decision) === "PASSED";
161
+ }
162
+ function resolveVerificationValidity(data, fallbackStatus) {
163
+ if (typeof data.valid === "boolean") {
164
+ return data.valid;
165
+ }
166
+ if (data.decision) {
167
+ return isPassedJumioDecision(data.decision);
168
+ }
169
+ return isApprovedJumioStatus(fallbackStatus);
170
+ }
171
+
172
+ // src/core/retry.ts
173
+ function delay(ms, signal) {
174
+ return new Promise((resolve, reject) => {
175
+ if (signal?.aborted) {
176
+ return reject(new JumioAbortError());
177
+ }
178
+ const timer = setTimeout(() => {
179
+ signal?.removeEventListener("abort", abortHandler);
180
+ resolve();
181
+ }, ms);
182
+ const abortHandler = () => {
183
+ clearTimeout(timer);
184
+ reject(new JumioAbortError());
185
+ };
186
+ signal?.addEventListener("abort", abortHandler, { once: true });
187
+ });
188
+ }
189
+ function shouldRetryJumio(errorOrMessage) {
190
+ if (!errorOrMessage) return false;
191
+ const text = toLowerText(
192
+ typeof errorOrMessage === "string" ? errorOrMessage : extractJumioMessage(errorOrMessage)
193
+ );
194
+ const errorObj = errorOrMessage;
195
+ const status = errorObj?.status || errorObj?.response?.status;
196
+ const code = String(errorObj?.code || "").toUpperCase();
197
+ if (status === 408 || status === 502 || status === 503 || status === 504) {
198
+ return true;
199
+ }
200
+ if (code === "ECONNABORTED" || code === "ETIMEDOUT" || code === "ECONNRESET" || code === "EAI_AGAIN") {
201
+ return true;
202
+ }
203
+ return text.includes("timed out") || text.includes("timeout") || text.includes("temporarily unavailable") || text.includes("network error") || text.includes("socket hang up") || text.includes("gateway timeout") || text.includes("service unavailable") || text.includes("excedi\xF3 el tiempo de espera");
204
+ }
205
+ async function withRetry(operation, options = {}) {
206
+ const {
207
+ maxRetries = 3,
208
+ initialDelayMs = 800,
209
+ maxDelayMs = 1e4,
210
+ factor = 1.5,
211
+ shouldRetry = shouldRetryJumio,
212
+ signal,
213
+ onRetry
214
+ } = options;
215
+ let lastError;
216
+ for (let attempt = 1; attempt <= maxRetries; attempt += 1) {
217
+ if (signal?.aborted) {
218
+ throw new JumioAbortError();
219
+ }
220
+ try {
221
+ return await operation(attempt);
222
+ } catch (error) {
223
+ lastError = error;
224
+ if (signal?.aborted) {
225
+ throw new JumioAbortError();
226
+ }
227
+ const isLastAttempt = attempt >= maxRetries;
228
+ const isRetryable = shouldRetry(error);
229
+ if (isLastAttempt || !isRetryable) {
230
+ throw error;
231
+ }
232
+ const currentDelay = Math.min(
233
+ Math.round(initialDelayMs * Math.pow(factor, attempt - 1)),
234
+ maxDelayMs
235
+ );
236
+ onRetry?.(attempt, error, currentDelay);
237
+ await delay(currentDelay, signal);
238
+ }
239
+ }
240
+ throw lastError;
241
+ }
242
+
243
+ // src/core/image.ts
244
+ function stripDataUrlPrefix(dataUrl) {
245
+ if (!dataUrl) return "";
246
+ const commaIndex = dataUrl.indexOf(",");
247
+ return commaIndex >= 0 ? dataUrl.slice(commaIndex + 1).trim() : dataUrl.trim();
248
+ }
249
+ function isBase64String(input) {
250
+ const trimmed = input.trim();
251
+ if (trimmed.startsWith("data:")) return true;
252
+ if (!trimmed.startsWith("http://") && !trimmed.startsWith("https://") && !trimmed.startsWith("blob:") && !trimmed.startsWith("/")) {
253
+ return /^[A-Za-z0-9+/=_\-\r\n]+$/.test(trimmed);
254
+ }
255
+ return false;
256
+ }
257
+ function normalizeSignedUrl(input, fallback = "") {
258
+ if (typeof input === "string" && input.trim()) {
259
+ return input.trim();
260
+ }
261
+ if (input && typeof input === "object") {
262
+ const record = input;
263
+ if ("url" in record && typeof record.url === "string" && record.url.trim()) {
264
+ return record.url.trim();
265
+ }
266
+ if ("Location" in record && typeof record.Location === "string" && record.Location.trim()) {
267
+ return record.Location.trim();
268
+ }
269
+ if ("signedUrl" in record && typeof record.signedUrl === "string" && record.signedUrl.trim()) {
270
+ return record.signedUrl.trim();
271
+ }
272
+ }
273
+ return fallback;
274
+ }
275
+ function blobToBase64(blob) {
276
+ return new Promise((resolve, reject) => {
277
+ if (typeof FileReader === "undefined") {
278
+ blob.arrayBuffer().then((buffer) => {
279
+ resolve(arrayBufferToBase64(buffer));
280
+ }).catch(
281
+ (err) => reject(new JumioImageProcessingError("No se pudo leer el archivo binario.", err))
282
+ );
283
+ return;
284
+ }
285
+ const reader = new FileReader();
286
+ reader.onload = () => {
287
+ const dataUrl = String(reader.result ?? "");
288
+ resolve(stripDataUrlPrefix(dataUrl));
289
+ };
290
+ reader.onerror = () => reject(new JumioImageProcessingError("No se pudo convertir la imagen a base64.", reader.error));
291
+ reader.readAsDataURL(blob);
292
+ });
293
+ }
294
+ function arrayBufferToBase64(buffer) {
295
+ if (typeof Buffer !== "undefined") {
296
+ const nodeBuf = buffer instanceof Uint8Array ? Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength) : Buffer.from(buffer);
297
+ return nodeBuf.toString("base64");
298
+ }
299
+ let binary = "";
300
+ const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
301
+ const len = bytes.byteLength;
302
+ for (let i = 0; i < len; i++) {
303
+ binary += String.fromCharCode(bytes[i]);
304
+ }
305
+ return btoa(binary);
306
+ }
307
+ async function fetchUrlAsBase64(url, signal) {
308
+ if (signal?.aborted) {
309
+ throw new JumioAbortError();
310
+ }
311
+ const trimmed = url.trim();
312
+ if (trimmed.startsWith("data:")) {
313
+ return stripDataUrlPrefix(trimmed);
314
+ }
315
+ try {
316
+ const response = await fetch(trimmed, { signal });
317
+ if (!response.ok) {
318
+ throw new JumioImageProcessingError(
319
+ `No se pudo descargar la imagen desde la URL remota (HTTP ${response.status}).`
320
+ );
321
+ }
322
+ const blob = await response.blob();
323
+ return await blobToBase64(blob);
324
+ } catch (error) {
325
+ if (signal?.aborted || error instanceof Error && error.name === "AbortError") {
326
+ throw new JumioAbortError();
327
+ }
328
+ if (error instanceof JumioImageProcessingError) {
329
+ throw error;
330
+ }
331
+ throw new JumioImageProcessingError(
332
+ `Error al descargar o procesar la imagen desde la URL: ${url}`,
333
+ error
334
+ );
335
+ }
336
+ }
337
+ async function resolveImageSourceToBase64(source, s3Signer, signal) {
338
+ if (signal?.aborted) {
339
+ throw new JumioAbortError();
340
+ }
341
+ if (!source) {
342
+ throw new JumioImageProcessingError("No se proporcion\xF3 una imagen v\xE1lida para la verificaci\xF3n.");
343
+ }
344
+ if (typeof Blob !== "undefined" && source instanceof Blob) {
345
+ return blobToBase64(source);
346
+ }
347
+ if (source instanceof ArrayBuffer || source instanceof Uint8Array) {
348
+ return arrayBufferToBase64(source);
349
+ }
350
+ if (typeof source === "object" && source !== null) {
351
+ const extractedUrl = normalizeSignedUrl(source, "");
352
+ if (extractedUrl) {
353
+ return resolveImageSourceToBase64(extractedUrl, s3Signer, signal);
354
+ }
355
+ }
356
+ if (typeof source === "string") {
357
+ const text = source.trim();
358
+ if (!text) {
359
+ throw new JumioImageProcessingError("La ruta o contenido de la imagen est\xE1 vac\xEDo.");
360
+ }
361
+ if (text.startsWith("data:")) {
362
+ return stripDataUrlPrefix(text);
363
+ }
364
+ if (text.startsWith("http://") || text.startsWith("https://") || text.startsWith("blob:")) {
365
+ if (s3Signer && (text.includes(".amazonaws.com") || text.includes("/s3/"))) {
366
+ try {
367
+ const signed = await s3Signer(text, 300);
368
+ const signedUrl = normalizeSignedUrl(signed, text);
369
+ return await fetchUrlAsBase64(signedUrl, signal);
370
+ } catch {
371
+ return await fetchUrlAsBase64(text, signal);
372
+ }
373
+ }
374
+ return await fetchUrlAsBase64(text, signal);
375
+ }
376
+ if (s3Signer && (text.startsWith("company/") || text.startsWith("/") || text.includes("/"))) {
377
+ try {
378
+ const signed = await s3Signer(text, 300);
379
+ const signedUrl = normalizeSignedUrl(signed, "");
380
+ if (signedUrl) {
381
+ return await fetchUrlAsBase64(signedUrl, signal);
382
+ }
383
+ } catch (err) {
384
+ throw new JumioImageProcessingError(
385
+ `No se pudo firmar la ruta de almacenamiento S3: ${text}`,
386
+ err
387
+ );
388
+ }
389
+ }
390
+ if (isBase64String(text)) {
391
+ return stripDataUrlPrefix(text);
392
+ }
393
+ throw new JumioImageProcessingError(
394
+ `Formato de imagen no reconocido o ruta inv\xE1lida: ${text.slice(0, 50)}...`
395
+ );
396
+ }
397
+ throw new JumioImageProcessingError("Tipo de dato de imagen no soportado.");
398
+ }
399
+ var JumioHttpClient = class {
400
+ client;
401
+ config;
402
+ constructor(config = {}) {
403
+ this.config = {
404
+ baseUrl: "https://front.dynamicore.io",
405
+ endpoint: "/marketplace/apps/jumio",
406
+ requestTimeout: 18e4,
407
+ statusTimeout: 12e4,
408
+ maxRetries: 3,
409
+ retryDelayMs: 800,
410
+ pollingIntervalMs: 1e4,
411
+ maxPollingAttempts: 20,
412
+ authTokenPrefix: "",
413
+ ...config
414
+ };
415
+ if (config.axiosInstance) {
416
+ this.client = config.axiosInstance;
417
+ } else {
418
+ this.client = axios__default.default.create({
419
+ baseURL: this.config.baseUrl,
420
+ headers: {
421
+ "Content-Type": "application/json",
422
+ ...this.config.context ? { context: this.config.context } : {},
423
+ ...this.config.customHeaders || {}
424
+ }
425
+ });
426
+ }
427
+ }
428
+ /**
429
+ * Resuelve los headers dinámicos (token de auth y context).
430
+ */
431
+ async resolveHeaders() {
432
+ const headers = {
433
+ ...this.config.customHeaders || {}
434
+ };
435
+ if (this.config.context) {
436
+ headers["context"] = this.config.context;
437
+ }
438
+ if (this.config.authToken) {
439
+ let token;
440
+ if (typeof this.config.authToken === "function") {
441
+ token = await this.config.authToken();
442
+ } else {
443
+ token = this.config.authToken;
444
+ }
445
+ if (token) {
446
+ const prefix = this.config.authTokenPrefix ? `${this.config.authTokenPrefix.trim()} ` : "";
447
+ headers["Authorization"] = `${prefix}${token}`;
448
+ }
449
+ }
450
+ return headers;
451
+ }
452
+ /**
453
+ * Normaliza cualquier error de Axios a una excepción tipada JumioError.
454
+ */
455
+ normalizeError(error) {
456
+ if (error instanceof JumioError) {
457
+ return error;
458
+ }
459
+ if (axios__default.default.isAxiosError(error)) {
460
+ const axiosErr = error;
461
+ const status = axiosErr.response?.status;
462
+ const responseData = axiosErr.response?.data;
463
+ const serverMessage = extractJumioMessage(responseData) || extractJumioMessage(axiosErr);
464
+ if (axiosErr.code === "ECONNABORTED" || axiosErr.code === "ETIMEDOUT") {
465
+ return new JumioTimeoutError(
466
+ serverMessage || "La solicitud al servicio de Jumio excedi\xF3 el tiempo l\xEDmite.",
467
+ responseData
468
+ );
469
+ }
470
+ if (!axiosErr.response) {
471
+ return new JumioNetworkError(
472
+ serverMessage || "No fue posible comunicarse con el servicio de Jumio.",
473
+ axiosErr
474
+ );
475
+ }
476
+ return new JumioError(serverMessage || `Error en la solicitud HTTP (${status}).`, {
477
+ statusCode: status,
478
+ rawData: responseData,
479
+ code: axiosErr.code,
480
+ cause: axiosErr
481
+ });
482
+ }
483
+ if (error instanceof Error) {
484
+ if (error.name === "AbortError") {
485
+ return new JumioAbortError();
486
+ }
487
+ return new JumioError(error.message, { cause: error });
488
+ }
489
+ return new JumioError("Ocurri\xF3 un error inesperado al contactar a Jumio.");
490
+ }
491
+ /**
492
+ * Ejecuta una petición POST al endpoint de Jumio para iniciar o procesar la verificación.
493
+ */
494
+ async postVerification(payload, options) {
495
+ if (options?.signal?.aborted) {
496
+ throw new JumioAbortError();
497
+ }
498
+ try {
499
+ const dynamicHeaders = await this.resolveHeaders();
500
+ const endpoint = this.config.endpoint || "/marketplace/apps/jumio";
501
+ const timeout = options?.timeout ?? this.config.requestTimeout ?? 18e4;
502
+ const response = await this.client.post(endpoint, payload, {
503
+ headers: dynamicHeaders,
504
+ timeout,
505
+ signal: options?.signal
506
+ });
507
+ return response.data;
508
+ } catch (err) {
509
+ throw this.normalizeError(err);
510
+ }
511
+ }
512
+ /**
513
+ * Ejecuta una petición GET para consultar el estado actual de una verificación de INE.
514
+ */
515
+ async getVerificationStatus(accountId, workflowId, options) {
516
+ if (options?.signal?.aborted) {
517
+ throw new JumioAbortError();
518
+ }
519
+ try {
520
+ const dynamicHeaders = await this.resolveHeaders();
521
+ const endpoint = this.config.endpoint || "/marketplace/apps/jumio";
522
+ const timeout = options?.timeout ?? this.config.statusTimeout ?? 12e4;
523
+ const response = await this.client.get(endpoint, {
524
+ params: {
525
+ type: "ine",
526
+ accountId,
527
+ workflowId
528
+ },
529
+ headers: dynamicHeaders,
530
+ timeout,
531
+ signal: options?.signal
532
+ });
533
+ return response.data;
534
+ } catch (err) {
535
+ throw this.normalizeError(err);
536
+ }
537
+ }
538
+ getConfig() {
539
+ return this.config;
540
+ }
541
+ };
542
+
543
+ // src/core/polling.ts
544
+ async function pollJumioStatus(http, accountId, workflowId, options = {}) {
545
+ const config = http.getConfig();
546
+ const maxAttempts = options.maxAttempts ?? config.maxPollingAttempts ?? 20;
547
+ const intervalMs = options.pollingIntervalMs ?? config.pollingIntervalMs ?? 1e4;
548
+ const signal = options.signal;
549
+ let lastData = {};
550
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
551
+ if (signal?.aborted) {
552
+ throw new JumioAbortError();
553
+ }
554
+ const responseData = await withRetry(
555
+ () => http.getVerificationStatus(accountId, workflowId, {
556
+ signal,
557
+ timeout: config.statusTimeout
558
+ }),
559
+ {
560
+ maxRetries: config.maxRetries ?? 3,
561
+ initialDelayMs: config.retryDelayMs ?? 800,
562
+ signal
563
+ }
564
+ );
565
+ const firstLevel = extractEnvelopeData(responseData);
566
+ const statusData = extractNestedData(firstLevel) ?? {};
567
+ lastData = statusData;
568
+ options.onAttempt?.(attempt, maxAttempts, statusData);
569
+ if (statusData.valid === true) {
570
+ return {
571
+ valid: true,
572
+ status: statusData.workflowStatus || statusData.status || "APPROVED_VERIFIED",
573
+ accountId,
574
+ workflowId,
575
+ data: statusData
576
+ };
577
+ }
578
+ const workflowStatus = statusData.workflowStatus ?? statusData.status ?? statusData.decision;
579
+ if (isFinalJumioStatus(workflowStatus) || statusData.decision && isPassedJumioDecision(statusData.decision)) {
580
+ const isValid = resolveVerificationValidity(statusData, workflowStatus);
581
+ const errorMessage = isValid ? void 0 : extractJumioMessage(statusData) || "El documento fue rechazado durante la validaci\xF3n.";
582
+ return {
583
+ valid: isValid,
584
+ status: String(workflowStatus),
585
+ accountId,
586
+ workflowId,
587
+ data: statusData,
588
+ errorMessage
589
+ };
590
+ }
591
+ if (attempt < maxAttempts) {
592
+ await delay(intervalMs, signal);
593
+ }
594
+ }
595
+ const lastMessage = extractJumioMessage(lastData) || "La validaci\xF3n de INE sigue en proceso. Intenta nuevamente en unos momentos.";
596
+ throw new JumioPollingTimeoutError(maxAttempts, lastMessage, lastData);
597
+ }
598
+
599
+ // src/core/client.ts
600
+ var JumioClient = class {
601
+ http;
602
+ config;
603
+ constructor(config = {}) {
604
+ this.config = config;
605
+ this.http = new JumioHttpClient(config);
606
+ }
607
+ /**
608
+ * Inicia el proceso de verificación convirtiendo las imágenes y enviando el payload inicial a Jumio.
609
+ */
610
+ async startIneVerification(params) {
611
+ const { clientId, frontImage, backImage, signal, onProgress } = params;
612
+ if (!clientId || !clientId.trim()) {
613
+ throw new JumioValidationError("Se requiere un identificador de cliente v\xE1lido (clientId).");
614
+ }
615
+ if (signal?.aborted) {
616
+ throw new JumioAbortError();
617
+ }
618
+ onProgress?.("RESOLVING_IMAGES", 10);
619
+ const [frontBase64, backBase64] = await Promise.all([
620
+ resolveImageSourceToBase64(frontImage, this.config.s3Signer, signal),
621
+ resolveImageSourceToBase64(backImage, this.config.s3Signer, signal)
622
+ ]);
623
+ if (signal?.aborted) {
624
+ throw new JumioAbortError();
625
+ }
626
+ onProgress?.("SENDING_PAYLOAD", 40);
627
+ const payload = {
628
+ client: clientId.trim(),
629
+ front_image_b64: frontBase64,
630
+ back_image_b64: backBase64
631
+ };
632
+ const response = await withRetry(
633
+ () => this.http.postVerification(payload, {
634
+ signal,
635
+ timeout: this.config.requestTimeout
636
+ }),
637
+ {
638
+ maxRetries: this.config.maxRetries ?? 3,
639
+ initialDelayMs: this.config.retryDelayMs ?? 800,
640
+ signal
641
+ }
642
+ );
643
+ onProgress?.("PAYLOAD_SENT", 60);
644
+ const firstLevel = extractEnvelopeData(response);
645
+ const startData = extractNestedData(firstLevel) ?? {};
646
+ return startData;
647
+ }
648
+ /**
649
+ * Consulta una única vez el estado actual de la validación.
650
+ */
651
+ async getIneStatus(accountId, workflowId, signal) {
652
+ const response = await this.http.getVerificationStatus(
653
+ accountId,
654
+ workflowId,
655
+ { signal, timeout: this.config.statusTimeout }
656
+ );
657
+ const firstLevel = extractEnvelopeData(response);
658
+ return extractNestedData(firstLevel) ?? {};
659
+ }
660
+ /**
661
+ * Sondea periódicamente el estado hasta obtener un resultado definitivo.
662
+ */
663
+ async pollIneStatus(accountId, workflowId, options) {
664
+ return pollJumioStatus(this.http, accountId, workflowId, options);
665
+ }
666
+ /**
667
+ * Ejecuta el flujo completo de validación de INE.
668
+ *
669
+ * @param input Parámetros de validación y callbacks.
670
+ * @returns `VerifyIneResult` con el resultado inicial o definitivo según `awaitFinalStatus`.
671
+ */
672
+ async verifyIne(input) {
673
+ const {
674
+ clientId,
675
+ frontImage,
676
+ backImage,
677
+ awaitFinalStatus = false,
678
+ onStatusResolved,
679
+ onStatusError,
680
+ onProgress,
681
+ signal
682
+ } = input;
683
+ const startData = await this.startIneVerification({
684
+ clientId,
685
+ frontImage,
686
+ backImage,
687
+ signal,
688
+ onProgress
689
+ });
690
+ const startStatus = startData?.workflowStatus ?? startData?.status ?? startData?.decision;
691
+ const isStartFinal = isFinalJumioStatus(startStatus);
692
+ if (startData?.valid === true || startData?.valid === false && isStartFinal) {
693
+ const isValid = startData?.valid === true;
694
+ const immediateResult = {
695
+ valid: isValid,
696
+ status: startStatus || (isValid ? "APPROVED_VERIFIED" : "REJECTED"),
697
+ accountId: startData?.accountId,
698
+ workflowId: startData?.workflowId,
699
+ data: startData,
700
+ ...isValid ? {} : {
701
+ errorMessage: extractJumioMessage(startData) || "El documento fue rechazado durante la validaci\xF3n."
702
+ }
703
+ };
704
+ onProgress?.("COMPLETED", 100);
705
+ onStatusResolved?.(immediateResult);
706
+ return immediateResult;
707
+ }
708
+ const accountId = String(startData?.accountId ?? "").trim();
709
+ const workflowId = String(startData?.workflowId ?? "").trim();
710
+ if (!accountId || !workflowId) {
711
+ const errorMessage = extractJumioMessage(startData) || "No fue posible obtener los identificadores de seguimiento de la validaci\xF3n.";
712
+ const error = new JumioError(errorMessage, { rawData: startData });
713
+ onStatusError?.(error);
714
+ throw error;
715
+ }
716
+ if (!awaitFinalStatus) {
717
+ onProgress?.("POLLING_BACKGROUND", 70);
718
+ this.pollIneStatus(accountId, workflowId).then((finalResult2) => {
719
+ onProgress?.("COMPLETED", 100);
720
+ onStatusResolved?.(finalResult2);
721
+ }).catch((err) => {
722
+ const normalizedError = err instanceof Error ? err : new Error("No se pudo consultar el estatus de validaci\xF3n.");
723
+ onStatusError?.(normalizedError);
724
+ });
725
+ return {
726
+ valid: true,
727
+ status: startData.status || startData.workflowStatus || "INITIATED",
728
+ accountId,
729
+ workflowId,
730
+ data: startData
731
+ };
732
+ }
733
+ onProgress?.("POLLING_FOREGROUND", 70);
734
+ const finalResult = await this.pollIneStatus(accountId, workflowId, { signal });
735
+ onProgress?.("COMPLETED", 100);
736
+ onStatusResolved?.(finalResult);
737
+ return finalResult;
738
+ }
739
+ };
740
+ function createJumioClient(config) {
741
+ return new JumioClient(config);
742
+ }
743
+ var INITIAL_STATE = {
744
+ isSubmitting: false,
745
+ isPolling: false,
746
+ isLoading: false,
747
+ stage: "IDLE",
748
+ progress: 0,
749
+ result: null,
750
+ error: null,
751
+ isSuccess: false,
752
+ isError: false
753
+ };
754
+ function useJumioVerification(options = {}) {
755
+ const {
756
+ client: customClient,
757
+ baseUrl,
758
+ endpoint,
759
+ context,
760
+ authToken,
761
+ authTokenPrefix,
762
+ requestTimeout,
763
+ statusTimeout,
764
+ maxRetries,
765
+ retryDelayMs,
766
+ pollingIntervalMs,
767
+ maxPollingAttempts,
768
+ s3Signer,
769
+ customHeaders,
770
+ axiosInstance,
771
+ onStatusResolved: globalOnStatusResolved,
772
+ onStatusError: globalOnStatusError,
773
+ onProgress: globalOnProgress
774
+ } = options;
775
+ const client = react.useMemo(() => {
776
+ if (customClient) return customClient;
777
+ return new JumioClient({
778
+ baseUrl,
779
+ endpoint,
780
+ context,
781
+ authToken,
782
+ authTokenPrefix,
783
+ requestTimeout,
784
+ statusTimeout,
785
+ maxRetries,
786
+ retryDelayMs,
787
+ pollingIntervalMs,
788
+ maxPollingAttempts,
789
+ s3Signer,
790
+ customHeaders,
791
+ axiosInstance
792
+ });
793
+ }, [
794
+ customClient,
795
+ baseUrl,
796
+ endpoint,
797
+ context,
798
+ authToken,
799
+ authTokenPrefix,
800
+ requestTimeout,
801
+ statusTimeout,
802
+ maxRetries,
803
+ retryDelayMs,
804
+ pollingIntervalMs,
805
+ maxPollingAttempts,
806
+ s3Signer,
807
+ customHeaders,
808
+ axiosInstance
809
+ ]);
810
+ const [state, setState] = react.useState(INITIAL_STATE);
811
+ const abortControllerRef = react.useRef(null);
812
+ const callbacksRef = react.useRef({
813
+ onStatusResolved: globalOnStatusResolved,
814
+ onStatusError: globalOnStatusError,
815
+ onProgress: globalOnProgress
816
+ });
817
+ react.useEffect(() => {
818
+ callbacksRef.current = {
819
+ onStatusResolved: globalOnStatusResolved,
820
+ onStatusError: globalOnStatusError,
821
+ onProgress: globalOnProgress
822
+ };
823
+ }, [globalOnStatusResolved, globalOnStatusError, globalOnProgress]);
824
+ react.useEffect(() => {
825
+ return () => {
826
+ if (abortControllerRef.current) {
827
+ abortControllerRef.current.abort();
828
+ }
829
+ };
830
+ }, []);
831
+ const cancel = react.useCallback(() => {
832
+ if (abortControllerRef.current) {
833
+ abortControllerRef.current.abort();
834
+ abortControllerRef.current = null;
835
+ }
836
+ setState((prev) => ({
837
+ ...prev,
838
+ isSubmitting: false,
839
+ isPolling: false,
840
+ isLoading: false,
841
+ stage: "CANCELLED"
842
+ }));
843
+ }, []);
844
+ const reset = react.useCallback(() => {
845
+ cancel();
846
+ setState(INITIAL_STATE);
847
+ }, [cancel]);
848
+ const verify = react.useCallback(
849
+ async (input) => {
850
+ if (abortControllerRef.current) {
851
+ abortControllerRef.current.abort();
852
+ }
853
+ const controller = new AbortController();
854
+ abortControllerRef.current = controller;
855
+ setState({
856
+ isSubmitting: true,
857
+ isPolling: false,
858
+ isLoading: true,
859
+ stage: "STARTING",
860
+ progress: 5,
861
+ result: null,
862
+ error: null,
863
+ isSuccess: false,
864
+ isError: false
865
+ });
866
+ const handleProgress = (stage, percent) => {
867
+ setState((prev) => ({
868
+ ...prev,
869
+ stage,
870
+ progress: percent ?? prev.progress,
871
+ isPolling: stage.includes("POLLING")
872
+ }));
873
+ input.onProgress?.(stage, percent);
874
+ callbacksRef.current.onProgress?.(stage, percent);
875
+ };
876
+ const handleResolved = (res) => {
877
+ setState((prev) => ({
878
+ ...prev,
879
+ isSubmitting: false,
880
+ isPolling: false,
881
+ isLoading: false,
882
+ stage: "COMPLETED",
883
+ progress: 100,
884
+ result: res,
885
+ isSuccess: res.valid,
886
+ isError: !res.valid,
887
+ error: res.valid ? null : new Error(res.errorMessage || "Validaci\xF3n de documento fallida")
888
+ }));
889
+ input.onStatusResolved?.(res);
890
+ callbacksRef.current.onStatusResolved?.(res);
891
+ };
892
+ const handleError = (err) => {
893
+ if (err instanceof JumioAbortError) {
894
+ return;
895
+ }
896
+ setState((prev) => ({
897
+ ...prev,
898
+ isSubmitting: false,
899
+ isPolling: false,
900
+ isLoading: false,
901
+ stage: "ERROR",
902
+ error: err,
903
+ isError: true,
904
+ isSuccess: false
905
+ }));
906
+ input.onStatusError?.(err);
907
+ callbacksRef.current.onStatusError?.(err);
908
+ };
909
+ try {
910
+ const awaitFinal = input.awaitFinalStatus ?? false;
911
+ const initialResult = await client.verifyIne({
912
+ ...input,
913
+ awaitFinalStatus: awaitFinal,
914
+ signal: controller.signal,
915
+ onProgress: handleProgress,
916
+ onStatusResolved: (res) => {
917
+ handleResolved(res);
918
+ },
919
+ onStatusError: (err) => {
920
+ handleError(err);
921
+ }
922
+ });
923
+ if (!awaitFinal && initialResult.valid && !initialResult.data) {
924
+ setState((prev) => ({
925
+ ...prev,
926
+ isSubmitting: false,
927
+ isPolling: true,
928
+ stage: "POLLING_BACKGROUND",
929
+ result: initialResult
930
+ }));
931
+ } else if (!awaitFinal && initialResult.valid) {
932
+ setState((prev) => ({
933
+ ...prev,
934
+ isSubmitting: false,
935
+ isPolling: true,
936
+ stage: "POLLING_BACKGROUND",
937
+ result: initialResult
938
+ }));
939
+ }
940
+ return initialResult;
941
+ } catch (error) {
942
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
943
+ handleError(normalizedError);
944
+ throw normalizedError;
945
+ }
946
+ },
947
+ [client]
948
+ );
949
+ return {
950
+ ...state,
951
+ verify,
952
+ cancel,
953
+ reset,
954
+ client
955
+ };
956
+ }
957
+
958
+ exports.APPROVED_JUMIO_STATUSES = APPROVED_JUMIO_STATUSES;
959
+ exports.FINAL_JUMIO_STATUSES = FINAL_JUMIO_STATUSES;
960
+ exports.JumioAbortError = JumioAbortError;
961
+ exports.JumioClient = JumioClient;
962
+ exports.JumioError = JumioError;
963
+ exports.JumioHttpClient = JumioHttpClient;
964
+ exports.JumioImageProcessingError = JumioImageProcessingError;
965
+ exports.JumioNetworkError = JumioNetworkError;
966
+ exports.JumioPollingTimeoutError = JumioPollingTimeoutError;
967
+ exports.JumioTimeoutError = JumioTimeoutError;
968
+ exports.JumioValidationError = JumioValidationError;
969
+ exports.arrayBufferToBase64 = arrayBufferToBase64;
970
+ exports.blobToBase64 = blobToBase64;
971
+ exports.createJumioClient = createJumioClient;
972
+ exports.delay = delay;
973
+ exports.extractEnvelopeData = extractEnvelopeData;
974
+ exports.extractJumioMessage = extractJumioMessage;
975
+ exports.extractNestedData = extractNestedData;
976
+ exports.fetchUrlAsBase64 = fetchUrlAsBase64;
977
+ exports.isApprovedJumioStatus = isApprovedJumioStatus;
978
+ exports.isBase64String = isBase64String;
979
+ exports.isFinalJumioStatus = isFinalJumioStatus;
980
+ exports.isJumioError = isJumioError;
981
+ exports.isPassedJumioDecision = isPassedJumioDecision;
982
+ exports.normalizeSignedUrl = normalizeSignedUrl;
983
+ exports.pollJumioStatus = pollJumioStatus;
984
+ exports.resolveImageSourceToBase64 = resolveImageSourceToBase64;
985
+ exports.resolveVerificationValidity = resolveVerificationValidity;
986
+ exports.shouldRetryJumio = shouldRetryJumio;
987
+ exports.stripDataUrlPrefix = stripDataUrlPrefix;
988
+ exports.toLowerText = toLowerText;
989
+ exports.toUpperText = toUpperText;
990
+ exports.useJumioVerification = useJumioVerification;
991
+ exports.withRetry = withRetry;
992
+ //# sourceMappingURL=index.js.map
993
+ //# sourceMappingURL=index.js.map