@dynamicore/jumio-sdk 1.0.0 → 1.0.2

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.
@@ -1,24 +1,17 @@
1
1
  import { useMemo, useState, useRef, useEffect, useCallback } from 'react';
2
2
  import axios from 'axios';
3
3
 
4
- // src/react/useJumioVerification.ts
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
5
13
 
6
14
  // src/core/envelope.ts
7
- var FINAL_JUMIO_STATUSES = [
8
- "PROCESSED",
9
- "APPROVED_VERIFIED",
10
- "REJECTED",
11
- "FAILED",
12
- "ERROR",
13
- "DENIED",
14
- "EXPIRED",
15
- "ABANDONED",
16
- "DONE",
17
- "COMPLETED",
18
- "NOT_READABLE",
19
- "FRAUD",
20
- "UNSUPPORTED_ID_TYPE"
21
- ];
22
15
  function toLowerText(value) {
23
16
  return String(value ?? "").trim().toLowerCase();
24
17
  }
@@ -26,10 +19,13 @@ function toUpperText(value) {
26
19
  return String(value ?? "").trim().toUpperCase();
27
20
  }
28
21
  function extractEnvelopeData(responseData) {
29
- if (responseData && typeof responseData === "object" && "data" in responseData) {
30
- const candidate = responseData.data;
31
- if (candidate !== void 0) {
32
- return candidate;
22
+ if (responseData && typeof responseData === "object") {
23
+ const obj = responseData;
24
+ if ("data" in obj && obj.data !== void 0) {
25
+ return obj.data;
26
+ }
27
+ if ("values" in obj && obj.values !== void 0) {
28
+ return obj.values;
33
29
  }
34
30
  }
35
31
  return responseData;
@@ -100,62 +96,442 @@ function resolveVerificationValidity(data, fallbackStatus) {
100
96
  }
101
97
  return isApprovedJumioStatus(fallbackStatus);
102
98
  }
99
+ function encodeUrlToBase64UrlSafe(url) {
100
+ try {
101
+ let base64;
102
+ if (typeof Buffer !== "undefined") {
103
+ base64 = Buffer.from(url, "utf-8").toString("base64");
104
+ } else if (typeof TextEncoder !== "undefined") {
105
+ const encoder = new TextEncoder();
106
+ const data = encoder.encode(url);
107
+ base64 = btoa(String.fromCharCode(...new Uint8Array(data)));
108
+ } else {
109
+ base64 = btoa(url);
110
+ }
111
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+ var FINAL_JUMIO_STATUSES;
117
+ var init_envelope = __esm({
118
+ "src/core/envelope.ts"() {
119
+ FINAL_JUMIO_STATUSES = [
120
+ "PROCESSED",
121
+ "APPROVED_VERIFIED",
122
+ "REJECTED",
123
+ "FAILED",
124
+ "ERROR",
125
+ "DENIED",
126
+ "EXPIRED",
127
+ "ABANDONED",
128
+ "DONE",
129
+ "COMPLETED",
130
+ "NOT_READABLE",
131
+ "FRAUD",
132
+ "UNSUPPORTED_ID_TYPE"
133
+ ];
134
+ }
135
+ });
103
136
 
104
137
  // src/core/errors.ts
105
- var JumioError = class extends Error {
106
- isJumioError = true;
107
- statusCode;
108
- rawData;
109
- code;
110
- constructor(message, options) {
111
- super(message);
112
- this.name = "JumioError";
113
- this.statusCode = options?.statusCode;
114
- this.rawData = options?.rawData;
115
- this.code = options?.code;
116
- if (options?.cause) {
117
- this.cause = options.cause;
138
+ var JumioError, JumioTimeoutError, JumioNetworkError, JumioValidationError, JumioImageProcessingError, JumioPollingTimeoutError, JumioAbortError;
139
+ var init_errors = __esm({
140
+ "src/core/errors.ts"() {
141
+ JumioError = class extends Error {
142
+ isJumioError = true;
143
+ statusCode;
144
+ rawData;
145
+ code;
146
+ constructor(message, options) {
147
+ super(message);
148
+ this.name = "JumioError";
149
+ this.statusCode = options?.statusCode;
150
+ this.rawData = options?.rawData;
151
+ this.code = options?.code;
152
+ if (options?.cause) {
153
+ this.cause = options.cause;
154
+ }
155
+ }
156
+ };
157
+ JumioTimeoutError = class extends JumioError {
158
+ constructor(message = "El servicio de verificaci\xF3n de Jumio excedi\xF3 el tiempo l\xEDmite de espera.", rawData) {
159
+ super(message, { code: "TIMEOUT", rawData });
160
+ this.name = "JumioTimeoutError";
161
+ }
162
+ };
163
+ JumioNetworkError = class extends JumioError {
164
+ constructor(message = "No fue posible comunicarse con los servidores de validaci\xF3n de identidad.", cause) {
165
+ super(message, { code: "NETWORK_ERROR", cause });
166
+ this.name = "JumioNetworkError";
167
+ }
168
+ };
169
+ JumioValidationError = class extends JumioError {
170
+ constructor(message = "El documento no pudo ser validado o fue rechazado por Jumio.", rawData) {
171
+ super(message, { code: "VALIDATION_FAILED", rawData });
172
+ this.name = "JumioValidationError";
173
+ }
174
+ };
175
+ JumioImageProcessingError = class extends JumioError {
176
+ constructor(message, cause) {
177
+ super(message, { code: "IMAGE_PROCESSING_ERROR", cause });
178
+ this.name = "JumioImageProcessingError";
179
+ }
180
+ };
181
+ JumioPollingTimeoutError = class extends JumioError {
182
+ attempts;
183
+ constructor(attempts, message = "La validaci\xF3n sigue en proceso. Por favor verifica nuevamente m\xE1s tarde.", rawData) {
184
+ super(message, { code: "POLLING_TIMEOUT", rawData });
185
+ this.name = "JumioPollingTimeoutError";
186
+ this.attempts = attempts;
187
+ }
188
+ };
189
+ JumioAbortError = class extends JumioError {
190
+ constructor(message = "La operaci\xF3n de validaci\xF3n de Jumio fue cancelada.") {
191
+ super(message, { code: "ABORTED" });
192
+ this.name = "JumioAbortError";
193
+ }
194
+ };
195
+ }
196
+ });
197
+
198
+ // src/core/retry.ts
199
+ function delay(ms, signal) {
200
+ return new Promise((resolve, reject) => {
201
+ if (signal?.aborted) {
202
+ return reject(new JumioAbortError());
118
203
  }
204
+ const timer = setTimeout(() => {
205
+ signal?.removeEventListener("abort", abortHandler);
206
+ resolve();
207
+ }, ms);
208
+ const abortHandler = () => {
209
+ clearTimeout(timer);
210
+ reject(new JumioAbortError());
211
+ };
212
+ signal?.addEventListener("abort", abortHandler, { once: true });
213
+ });
214
+ }
215
+ function shouldRetryJumio(errorOrMessage) {
216
+ if (!errorOrMessage) return false;
217
+ const text = toLowerText(
218
+ typeof errorOrMessage === "string" ? errorOrMessage : extractJumioMessage(errorOrMessage)
219
+ );
220
+ const errorObj = errorOrMessage;
221
+ const status = errorObj?.status || errorObj?.response?.status;
222
+ const code = String(errorObj?.code || "").toUpperCase();
223
+ if (status === 408 || status === 502 || status === 503 || status === 504) {
224
+ return true;
119
225
  }
120
- };
121
- var JumioTimeoutError = class extends JumioError {
122
- constructor(message = "El servicio de verificaci\xF3n de Jumio excedi\xF3 el tiempo l\xEDmite de espera.", rawData) {
123
- super(message, { code: "TIMEOUT", rawData });
124
- this.name = "JumioTimeoutError";
226
+ if (code === "ECONNABORTED" || code === "ETIMEDOUT" || code === "ECONNRESET" || code === "EAI_AGAIN") {
227
+ return true;
125
228
  }
126
- };
127
- var JumioNetworkError = class extends JumioError {
128
- constructor(message = "No fue posible comunicarse con los servidores de validaci\xF3n de identidad.", cause) {
129
- super(message, { code: "NETWORK_ERROR", cause });
130
- this.name = "JumioNetworkError";
229
+ 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");
230
+ }
231
+ async function withRetry(operation, options = {}) {
232
+ const {
233
+ maxRetries = 3,
234
+ initialDelayMs = 800,
235
+ maxDelayMs = 1e4,
236
+ factor = 1.5,
237
+ shouldRetry = shouldRetryJumio,
238
+ signal,
239
+ onRetry
240
+ } = options;
241
+ let lastError;
242
+ for (let attempt = 1; attempt <= maxRetries; attempt += 1) {
243
+ if (signal?.aborted) {
244
+ throw new JumioAbortError();
245
+ }
246
+ try {
247
+ return await operation(attempt);
248
+ } catch (error) {
249
+ lastError = error;
250
+ if (signal?.aborted) {
251
+ throw new JumioAbortError();
252
+ }
253
+ const isLastAttempt = attempt >= maxRetries;
254
+ const isRetryable = shouldRetry(error);
255
+ if (isLastAttempt || !isRetryable) {
256
+ throw error;
257
+ }
258
+ const currentDelay = Math.min(
259
+ Math.round(initialDelayMs * Math.pow(factor, attempt - 1)),
260
+ maxDelayMs
261
+ );
262
+ onRetry?.(attempt, error, currentDelay);
263
+ await delay(currentDelay, signal);
264
+ }
131
265
  }
132
- };
133
- var JumioValidationError = class extends JumioError {
134
- constructor(message = "El documento no pudo ser validado o fue rechazado por Jumio.", rawData) {
135
- super(message, { code: "VALIDATION_FAILED", rawData });
136
- this.name = "JumioValidationError";
266
+ throw lastError;
267
+ }
268
+ var init_retry = __esm({
269
+ "src/core/retry.ts"() {
270
+ init_envelope();
271
+ init_errors();
137
272
  }
138
- };
139
- var JumioImageProcessingError = class extends JumioError {
140
- constructor(message, cause) {
141
- super(message, { code: "IMAGE_PROCESSING_ERROR", cause });
142
- this.name = "JumioImageProcessingError";
273
+ });
274
+
275
+ // src/core/webflow.ts
276
+ var webflow_exports = {};
277
+ __export(webflow_exports, {
278
+ buildRedirectUrl: () => buildRedirectUrl,
279
+ parseWebflowReturnParams: () => parseWebflowReturnParams,
280
+ pollWebflowStatus: () => pollWebflowStatus,
281
+ resolveWebflowResult: () => resolveWebflowResult,
282
+ startWebflowSession: () => startWebflowSession
283
+ });
284
+ function buildRedirectUrl(returnUrl, proxyUrl) {
285
+ if (!proxyUrl) {
286
+ return returnUrl;
143
287
  }
144
- };
145
- var JumioPollingTimeoutError = class extends JumioError {
146
- attempts;
147
- constructor(attempts, message = "La validaci\xF3n sigue en proceso. Por favor verifica nuevamente m\xE1s tarde.", rawData) {
148
- super(message, { code: "POLLING_TIMEOUT", rawData });
149
- this.name = "JumioPollingTimeoutError";
150
- this.attempts = attempts;
288
+ const encoded = encodeUrlToBase64UrlSafe(returnUrl);
289
+ if (!encoded) {
290
+ throw new JumioValidationError(
291
+ `No fue posible codificar la URL de retorno: "${returnUrl}"`
292
+ );
151
293
  }
152
- };
153
- var JumioAbortError = class extends JumioError {
154
- constructor(message = "La operaci\xF3n de validaci\xF3n de Jumio fue cancelada.") {
155
- super(message, { code: "ABORTED" });
156
- this.name = "JumioAbortError";
294
+ const normalizedProxy = proxyUrl.endsWith("/") ? proxyUrl : `${proxyUrl}/`;
295
+ return `${normalizedProxy}${encoded}`;
296
+ }
297
+ function parseWebflowReturnParams(source) {
298
+ let searchString;
299
+ if (!source) {
300
+ if (typeof window !== "undefined" && window.location) {
301
+ searchString = window.location.search;
302
+ } else {
303
+ return { allParams: {} };
304
+ }
305
+ } else if (typeof source === "string") {
306
+ const questionIdx = source.indexOf("?");
307
+ searchString = questionIdx >= 0 ? source.slice(questionIdx) : "";
308
+ } else if (source instanceof URLSearchParams) {
309
+ searchString = `?${source.toString()}`;
310
+ } else {
311
+ const hrefSearch = source.href ? (() => {
312
+ try {
313
+ return new URL(source.href).search;
314
+ } catch {
315
+ return "";
316
+ }
317
+ })() : "";
318
+ searchString = source.search ?? hrefSearch;
157
319
  }
158
- };
320
+ const params = new URLSearchParams(searchString);
321
+ const allParams = {};
322
+ params.forEach((value, key) => {
323
+ allParams[key] = value;
324
+ });
325
+ const accountId = params.get("accountId") ?? void 0;
326
+ let workflowId = params.get("workflowExecutionId") ?? params.get("workflowId") ?? void 0;
327
+ const rawStatus = params.get("status") ?? void 0;
328
+ if (rawStatus && (!accountId || !workflowId)) {
329
+ const statusParts = rawStatus.split("?");
330
+ const nestedSearch = statusParts.length > 1 ? `?${statusParts.slice(1).join("?")}` : "";
331
+ if (nestedSearch && nestedSearch !== "?") {
332
+ const nestedParams = new URLSearchParams(nestedSearch);
333
+ nestedParams.forEach((value, key) => {
334
+ if (!allParams[key]) allParams[key] = value;
335
+ });
336
+ if (!workflowId) {
337
+ workflowId = nestedParams.get("workflowExecutionId") ?? nestedParams.get("workflowId") ?? workflowId;
338
+ }
339
+ }
340
+ }
341
+ return {
342
+ accountId: accountId || void 0,
343
+ workflowId: workflowId || void 0,
344
+ rawStatus,
345
+ allParams
346
+ };
347
+ }
348
+ function resolveWebflowResult(rawData, accountId, workflowId) {
349
+ if (!rawData || typeof rawData !== "object") return null;
350
+ const data = rawData;
351
+ if (data.valid === true) {
352
+ return {
353
+ valid: true,
354
+ status: String(data.workflowStatus ?? data.status ?? "APPROVED_VERIFIED"),
355
+ decision: data.decision,
356
+ extraction: data.extraction,
357
+ accountId,
358
+ workflowId,
359
+ data: rawData
360
+ };
361
+ }
362
+ if (data.valid === false) {
363
+ return {
364
+ valid: false,
365
+ status: String(data.workflowStatus ?? data.status ?? "REJECTED"),
366
+ decision: data.decision,
367
+ accountId,
368
+ workflowId,
369
+ data: rawData,
370
+ errorMessage: extractJumioMessage(rawData) || "El documento fue rechazado durante la verificacion."
371
+ };
372
+ }
373
+ const capData = data;
374
+ const decisionType = capData?.decision?.type;
375
+ const extractionArr = capData?.capabilities?.extraction ?? [];
376
+ const extraction = extractionArr[0];
377
+ const isExtractionEmpty = extraction !== void 0 && Object.keys(extraction).length === 0;
378
+ if (decisionType === "REJECTED") {
379
+ return {
380
+ valid: false,
381
+ status: "REJECTED",
382
+ decision: decisionType,
383
+ accountId,
384
+ workflowId,
385
+ data: rawData,
386
+ errorMessage: extractJumioMessage(rawData) || "El documento fue rechazado por Jumio."
387
+ };
388
+ }
389
+ if (extraction && !isExtractionEmpty) {
390
+ const isApproved = decisionType === "PASSED" || decisionType === "WARNING";
391
+ return {
392
+ valid: isApproved,
393
+ status: isApproved ? decisionType ?? "PASSED" : decisionType ?? "REJECTED",
394
+ decision: decisionType,
395
+ extraction,
396
+ accountId,
397
+ workflowId,
398
+ data: rawData,
399
+ ...!isApproved ? {
400
+ errorMessage: extractJumioMessage(rawData) || "La verificacion no fue aprobada."
401
+ } : {}
402
+ };
403
+ }
404
+ return null;
405
+ }
406
+ async function pollWebflowStatus(http, params, options = {}) {
407
+ const config = http.getConfig();
408
+ const maxAttempts = options.maxAttempts ?? config.maxPollingAttempts ?? 20;
409
+ const intervalMs = options.pollingIntervalMs ?? config.pollingIntervalMs ?? 1e4;
410
+ const signal = options.signal;
411
+ let lastRawData = {};
412
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
413
+ if (signal?.aborted) {
414
+ throw new JumioAbortError();
415
+ }
416
+ const responseData = await withRetry(
417
+ () => http.getVerificationStatus(params.accountId, params.workflowId, {
418
+ signal,
419
+ timeout: config.statusTimeout,
420
+ clientId: params.clientId
421
+ }),
422
+ {
423
+ maxRetries: config.maxRetries ?? 3,
424
+ initialDelayMs: config.retryDelayMs ?? 800,
425
+ signal
426
+ }
427
+ );
428
+ const firstLevel = extractEnvelopeData(responseData);
429
+ const statusData = extractNestedData(firstLevel) ?? {};
430
+ lastRawData = statusData;
431
+ options.onAttempt?.(attempt, maxAttempts, statusData);
432
+ const result = resolveWebflowResult(
433
+ statusData,
434
+ params.accountId,
435
+ params.workflowId
436
+ );
437
+ if (result !== null) {
438
+ return result;
439
+ }
440
+ if (attempt < maxAttempts) {
441
+ await delay(intervalMs, signal);
442
+ }
443
+ }
444
+ const lastMessage = extractJumioMessage(lastRawData) || "La verificacion Jumio sigue en proceso. Intenta nuevamente en unos momentos.";
445
+ throw new JumioPollingTimeoutError(maxAttempts, lastMessage, lastRawData);
446
+ }
447
+ async function startWebflowSession(http, config, input) {
448
+ const {
449
+ clientId,
450
+ successUrl,
451
+ errorUrl,
452
+ useRedirectProxy = false,
453
+ autoRedirect = false,
454
+ openInNewTab = false,
455
+ signal
456
+ } = input;
457
+ if (!clientId?.trim()) {
458
+ throw new JumioValidationError(
459
+ "Se requiere un identificador de cliente valido (clientId)."
460
+ );
461
+ }
462
+ if (!successUrl?.trim()) {
463
+ throw new JumioValidationError("Se requiere la URL de exito (successUrl).");
464
+ }
465
+ if (!errorUrl?.trim()) {
466
+ throw new JumioValidationError("Se requiere la URL de error (errorUrl).");
467
+ }
468
+ if (signal?.aborted) {
469
+ throw new JumioAbortError();
470
+ }
471
+ const proxyUrl = useRedirectProxy ? config.redirectProxyUrl ?? void 0 : void 0;
472
+ const finalSuccessUrl = buildRedirectUrl(successUrl, proxyUrl);
473
+ const finalErrorUrl = buildRedirectUrl(errorUrl, proxyUrl);
474
+ try {
475
+ const responseData = await withRetry(
476
+ () => http.postVerification(
477
+ {
478
+ client: clientId.trim(),
479
+ successUrl: finalSuccessUrl,
480
+ errorUrl: finalErrorUrl
481
+ },
482
+ { signal, timeout: config.requestTimeout }
483
+ ),
484
+ {
485
+ maxRetries: config.maxRetries ?? 3,
486
+ initialDelayMs: config.retryDelayMs ?? 800,
487
+ signal
488
+ }
489
+ );
490
+ const firstLevel = extractEnvelopeData(responseData);
491
+ const payload = extractNestedData(firstLevel) ?? {};
492
+ const href = payload?.href;
493
+ if (!href) {
494
+ throw new JumioError(
495
+ extractJumioMessage(payload) || "El backend no devolvio la URL de verificacion de Jumio (href).",
496
+ { rawData: payload }
497
+ );
498
+ }
499
+ const result = {
500
+ href,
501
+ accountId: payload?.accountId,
502
+ workflowId: payload?.workflowId,
503
+ data: payload
504
+ };
505
+ if (typeof window !== "undefined") {
506
+ if (openInNewTab) {
507
+ const win = window.open(href, "_blank");
508
+ if (!win || win.closed || typeof win.closed === "undefined") {
509
+ window.location.href = href;
510
+ }
511
+ } else if (autoRedirect) {
512
+ window.location.href = href;
513
+ }
514
+ }
515
+ return result;
516
+ } catch (err) {
517
+ throw err;
518
+ }
519
+ }
520
+ var init_webflow = __esm({
521
+ "src/core/webflow.ts"() {
522
+ init_envelope();
523
+ init_errors();
524
+ init_retry();
525
+ }
526
+ });
527
+
528
+ // src/core/client.ts
529
+ init_envelope();
530
+ init_errors();
531
+
532
+ // src/core/http.ts
533
+ init_errors();
534
+ init_envelope();
159
535
  var JumioHttpClient = class {
160
536
  client;
161
537
  config;
@@ -271,6 +647,7 @@ var JumioHttpClient = class {
271
647
  }
272
648
  /**
273
649
  * Ejecuta una petición GET para consultar el estado actual de una verificación de INE.
650
+ * Soporta un `clientId` opcional requerido por algunos endpoints de DynamiCore (p. ej. Hosted Webflow).
274
651
  */
275
652
  async getVerificationStatus(accountId, workflowId, options) {
276
653
  if (options?.signal?.aborted) {
@@ -280,12 +657,16 @@ var JumioHttpClient = class {
280
657
  const dynamicHeaders = await this.resolveHeaders();
281
658
  const endpoint = this.config.endpoint || "/marketplace/apps/jumio";
282
659
  const timeout = options?.timeout ?? this.config.statusTimeout ?? 12e4;
660
+ const queryParams = {
661
+ type: "ine",
662
+ accountId,
663
+ workflowId
664
+ };
665
+ if (options?.clientId) {
666
+ queryParams.clientId = options.clientId;
667
+ }
283
668
  const response = await this.client.get(endpoint, {
284
- params: {
285
- type: "ine",
286
- accountId,
287
- workflowId
288
- },
669
+ params: queryParams,
289
670
  headers: dynamicHeaders,
290
671
  timeout,
291
672
  signal: options?.signal
@@ -301,6 +682,7 @@ var JumioHttpClient = class {
301
682
  };
302
683
 
303
684
  // src/core/image.ts
685
+ init_errors();
304
686
  function stripDataUrlPrefix(dataUrl) {
305
687
  if (!dataUrl) return "";
306
688
  const commaIndex = dataUrl.indexOf(",");
@@ -457,78 +839,10 @@ async function resolveImageSourceToBase64(source, s3Signer, signal) {
457
839
  throw new JumioImageProcessingError("Tipo de dato de imagen no soportado.");
458
840
  }
459
841
 
460
- // src/core/retry.ts
461
- function delay(ms, signal) {
462
- return new Promise((resolve, reject) => {
463
- if (signal?.aborted) {
464
- return reject(new JumioAbortError());
465
- }
466
- const timer = setTimeout(() => {
467
- signal?.removeEventListener("abort", abortHandler);
468
- resolve();
469
- }, ms);
470
- const abortHandler = () => {
471
- clearTimeout(timer);
472
- reject(new JumioAbortError());
473
- };
474
- signal?.addEventListener("abort", abortHandler, { once: true });
475
- });
476
- }
477
- function shouldRetryJumio(errorOrMessage) {
478
- if (!errorOrMessage) return false;
479
- const text = toLowerText(
480
- typeof errorOrMessage === "string" ? errorOrMessage : extractJumioMessage(errorOrMessage)
481
- );
482
- const errorObj = errorOrMessage;
483
- const status = errorObj?.status || errorObj?.response?.status;
484
- const code = String(errorObj?.code || "").toUpperCase();
485
- if (status === 408 || status === 502 || status === 503 || status === 504) {
486
- return true;
487
- }
488
- if (code === "ECONNABORTED" || code === "ETIMEDOUT" || code === "ECONNRESET" || code === "EAI_AGAIN") {
489
- return true;
490
- }
491
- 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");
492
- }
493
- async function withRetry(operation, options = {}) {
494
- const {
495
- maxRetries = 3,
496
- initialDelayMs = 800,
497
- maxDelayMs = 1e4,
498
- factor = 1.5,
499
- shouldRetry = shouldRetryJumio,
500
- signal,
501
- onRetry
502
- } = options;
503
- let lastError;
504
- for (let attempt = 1; attempt <= maxRetries; attempt += 1) {
505
- if (signal?.aborted) {
506
- throw new JumioAbortError();
507
- }
508
- try {
509
- return await operation(attempt);
510
- } catch (error) {
511
- lastError = error;
512
- if (signal?.aborted) {
513
- throw new JumioAbortError();
514
- }
515
- const isLastAttempt = attempt >= maxRetries;
516
- const isRetryable = shouldRetry(error);
517
- if (isLastAttempt || !isRetryable) {
518
- throw error;
519
- }
520
- const currentDelay = Math.min(
521
- Math.round(initialDelayMs * Math.pow(factor, attempt - 1)),
522
- maxDelayMs
523
- );
524
- onRetry?.(attempt, error, currentDelay);
525
- await delay(currentDelay, signal);
526
- }
527
- }
528
- throw lastError;
529
- }
530
-
531
842
  // src/core/polling.ts
843
+ init_envelope();
844
+ init_errors();
845
+ init_retry();
532
846
  async function pollJumioStatus(http, accountId, workflowId, options = {}) {
533
847
  const config = http.getConfig();
534
848
  const maxAttempts = options.maxAttempts ?? config.maxPollingAttempts ?? 20;
@@ -585,6 +899,8 @@ async function pollJumioStatus(http, accountId, workflowId, options = {}) {
585
899
  }
586
900
 
587
901
  // src/core/client.ts
902
+ init_retry();
903
+ init_webflow();
588
904
  var JumioClient = class {
589
905
  http;
590
906
  config;
@@ -724,9 +1040,109 @@ var JumioClient = class {
724
1040
  onStatusResolved?.(finalResult);
725
1041
  return finalResult;
726
1042
  }
1043
+ // ─────────────────────────────────────────────────────────────────────────
1044
+ // Hosted Webflow (Redirección)
1045
+ // ─────────────────────────────────────────────────────────────────────────
1046
+ /**
1047
+ * Construye la URL de redirección de retorno para el flujo Hosted Webflow.
1048
+ *
1049
+ * Si el cliente está configurado con un `redirectProxyUrl` y `useProxy` es `true`,
1050
+ * la URL de retorno se codifica en Base64 URL-safe y se adjunta al proxy.
1051
+ *
1052
+ * @param returnUrl URL de la aplicación a la que Jumio redirigirá tras la verificación.
1053
+ * @param useProxy Si es `true` y hay `redirectProxyUrl` configurado, usa el proxy.
1054
+ * @returns URL de redirección construida.
1055
+ */
1056
+ buildRedirectUrl(returnUrl, useProxy = false) {
1057
+ const proxyUrl = useProxy ? this.config.redirectProxyUrl ?? void 0 : void 0;
1058
+ return buildRedirectUrl(returnUrl, proxyUrl);
1059
+ }
1060
+ /**
1061
+ * Inicia el flujo Hosted Webflow solicitando al backend de DynamiCore la URL
1062
+ * de verificación de Jumio (`href`).
1063
+ *
1064
+ * El usuario debe ser redirigido a esa URL para completar la verificación en la
1065
+ * página oficial de Jumio. Al terminar, Jumio redirige de vuelta a la aplicación
1066
+ * con `accountId` y `workflowExecutionId` como query params.
1067
+ *
1068
+ * @param input Parámetros del flujo: `clientId`, `successUrl`, `errorUrl`, opciones de proxy y redirección.
1069
+ * @returns `StartWebflowResult` con la URL de Jumio (`href`) y datos adicionales.
1070
+ *
1071
+ * @example
1072
+ * ```ts
1073
+ * const { href } = await client.startWebflow({
1074
+ * clientId: "usr_123",
1075
+ * successUrl: "https://app.example.com/verify/success",
1076
+ * errorUrl: "https://app.example.com/verify/error",
1077
+ * });
1078
+ * window.location.href = href;
1079
+ * ```
1080
+ */
1081
+ async startWebflow(input) {
1082
+ return startWebflowSession(this.http, this.config, input);
1083
+ }
1084
+ /**
1085
+ * Extrae los parámetros de seguimiento de Jumio de la URL de retorno.
1086
+ *
1087
+ * Jumio añade `accountId` y `workflowExecutionId` (o `workflowId`) como query
1088
+ * params al redirigir de vuelta a la aplicación tras la verificación.
1089
+ *
1090
+ * @param source URL completa, `URLSearchParams`, objeto con `search`/`href`, o nada para leer `window.location`.
1091
+ * @returns `WebflowReturnParams` con `accountId`, `workflowId`, `rawStatus` y todos los params.
1092
+ *
1093
+ * @example En React, en el componente de retorno:
1094
+ * ```ts
1095
+ * const { accountId, workflowId } = client.parseWebflowReturnParams(window.location.href);
1096
+ * ```
1097
+ */
1098
+ parseWebflowReturnParams(source) {
1099
+ return parseWebflowReturnParams(source);
1100
+ }
1101
+ /**
1102
+ * Consulta una única vez el estado actual de la verificación del flujo Hosted Webflow.
1103
+ *
1104
+ * @param input Parámetros de consulta: `accountId`, `workflowId`, y `clientId` opcional.
1105
+ * @returns `WebflowResult | null` — `null` si la verificación aún está pendiente.
1106
+ */
1107
+ async getWebflowStatus(input) {
1108
+ const { accountId, workflowId, clientId, signal } = input;
1109
+ const responseData = await this.http.getVerificationStatus(accountId, workflowId, {
1110
+ signal,
1111
+ timeout: this.config.statusTimeout,
1112
+ clientId
1113
+ });
1114
+ const firstLevel = extractEnvelopeData(responseData);
1115
+ const statusData = extractNestedData(firstLevel) ?? {};
1116
+ const { resolveWebflowResult: resolveWebflowResult2 } = await Promise.resolve().then(() => (init_webflow(), webflow_exports));
1117
+ return resolveWebflowResult2(statusData, accountId, workflowId);
1118
+ }
1119
+ /**
1120
+ * Sondea periódicamente el estado de verificación de un flujo Hosted Webflow de Jumio.
1121
+ *
1122
+ * Continúa consultando hasta obtener un resultado definitivo o agotar los intentos.
1123
+ *
1124
+ * @param params `accountId`, `workflowId` y `clientId` opcional.
1125
+ * @param options Opciones de sondeo (`maxAttempts`, `pollingIntervalMs`, `signal`, `onAttempt`).
1126
+ * @returns `WebflowResult` con el resultado definitivo.
1127
+ *
1128
+ * @example
1129
+ * ```ts
1130
+ * // En el componente de retorno, tras leer los params de la URL:
1131
+ * const result = await client.pollWebflowStatus({ accountId, workflowId, clientId });
1132
+ * if (result.valid) {
1133
+ * console.log("Verificación aprobada:", result.extraction);
1134
+ * } else {
1135
+ * console.warn("Rechazado:", result.errorMessage);
1136
+ * }
1137
+ * ```
1138
+ */
1139
+ async pollWebflowStatus(params, options) {
1140
+ return pollWebflowStatus(this.http, params, options);
1141
+ }
727
1142
  };
728
1143
 
729
1144
  // src/react/useJumioVerification.ts
1145
+ init_errors();
730
1146
  var INITIAL_STATE = {
731
1147
  isSubmitting: false,
732
1148
  isPolling: false,
@@ -878,6 +1294,13 @@ function useJumioVerification(options = {}) {
878
1294
  };
879
1295
  const handleError = (err) => {
880
1296
  if (err instanceof JumioAbortError) {
1297
+ setState((prev) => ({
1298
+ ...prev,
1299
+ isSubmitting: false,
1300
+ isPolling: false,
1301
+ isLoading: false,
1302
+ stage: "CANCELLED"
1303
+ }));
881
1304
  return;
882
1305
  }
883
1306
  setState((prev) => ({
@@ -941,7 +1364,207 @@ function useJumioVerification(options = {}) {
941
1364
  client
942
1365
  };
943
1366
  }
1367
+ init_errors();
1368
+ var INITIAL_STATE2 = {
1369
+ isStarting: false,
1370
+ isChecking: false,
1371
+ isLoading: false,
1372
+ result: null,
1373
+ error: null,
1374
+ isValid: false,
1375
+ isRejected: false
1376
+ };
1377
+ function useJumioWebflow(options = {}) {
1378
+ const {
1379
+ client: customClient,
1380
+ baseUrl,
1381
+ endpoint,
1382
+ context,
1383
+ authToken,
1384
+ authTokenPrefix,
1385
+ requestTimeout,
1386
+ statusTimeout,
1387
+ maxRetries,
1388
+ retryDelayMs,
1389
+ pollingIntervalMs,
1390
+ maxPollingAttempts,
1391
+ redirectProxyUrl,
1392
+ s3Signer,
1393
+ customHeaders,
1394
+ axiosInstance,
1395
+ onResult: globalOnResult,
1396
+ onError: globalOnError
1397
+ } = options;
1398
+ const client = useMemo(() => {
1399
+ if (customClient) return customClient;
1400
+ return new JumioClient({
1401
+ baseUrl,
1402
+ endpoint,
1403
+ context,
1404
+ authToken,
1405
+ authTokenPrefix,
1406
+ requestTimeout,
1407
+ statusTimeout,
1408
+ maxRetries,
1409
+ retryDelayMs,
1410
+ pollingIntervalMs,
1411
+ maxPollingAttempts,
1412
+ redirectProxyUrl,
1413
+ s3Signer,
1414
+ customHeaders,
1415
+ axiosInstance
1416
+ });
1417
+ }, [
1418
+ customClient,
1419
+ baseUrl,
1420
+ endpoint,
1421
+ context,
1422
+ authToken,
1423
+ authTokenPrefix,
1424
+ requestTimeout,
1425
+ statusTimeout,
1426
+ maxRetries,
1427
+ retryDelayMs,
1428
+ pollingIntervalMs,
1429
+ maxPollingAttempts,
1430
+ redirectProxyUrl,
1431
+ s3Signer,
1432
+ customHeaders,
1433
+ axiosInstance
1434
+ ]);
1435
+ const [state, setState] = useState(INITIAL_STATE2);
1436
+ const abortControllerRef = useRef(null);
1437
+ const callbacksRef = useRef({ onResult: globalOnResult, onError: globalOnError });
1438
+ useEffect(() => {
1439
+ callbacksRef.current = { onResult: globalOnResult, onError: globalOnError };
1440
+ }, [globalOnResult, globalOnError]);
1441
+ useEffect(() => {
1442
+ return () => {
1443
+ abortControllerRef.current?.abort();
1444
+ };
1445
+ }, []);
1446
+ const cancel = useCallback(() => {
1447
+ abortControllerRef.current?.abort();
1448
+ abortControllerRef.current = null;
1449
+ setState((prev) => ({
1450
+ ...prev,
1451
+ isStarting: false,
1452
+ isChecking: false,
1453
+ isLoading: false
1454
+ }));
1455
+ }, []);
1456
+ const reset = useCallback(() => {
1457
+ cancel();
1458
+ setState(INITIAL_STATE2);
1459
+ }, [cancel]);
1460
+ const handleError = useCallback((err) => {
1461
+ if (err instanceof JumioAbortError) {
1462
+ setState((prev) => ({
1463
+ ...prev,
1464
+ isStarting: false,
1465
+ isChecking: false,
1466
+ isLoading: false
1467
+ }));
1468
+ return;
1469
+ }
1470
+ setState((prev) => ({
1471
+ ...prev,
1472
+ isStarting: false,
1473
+ isChecking: false,
1474
+ isLoading: false,
1475
+ error: err,
1476
+ isRejected: true
1477
+ }));
1478
+ callbacksRef.current.onError?.(err);
1479
+ }, []);
1480
+ const handleResult = useCallback((result) => {
1481
+ setState((prev) => ({
1482
+ ...prev,
1483
+ isChecking: false,
1484
+ isLoading: false,
1485
+ result,
1486
+ isValid: result.valid,
1487
+ isRejected: !result.valid,
1488
+ error: result.valid ? null : new Error(result.errorMessage ?? "Verificacion de identidad rechazada")
1489
+ }));
1490
+ callbacksRef.current.onResult?.(result);
1491
+ }, []);
1492
+ const startWebflow = useCallback(
1493
+ async (input) => {
1494
+ if (abortControllerRef.current) {
1495
+ abortControllerRef.current.abort();
1496
+ }
1497
+ const controller = new AbortController();
1498
+ abortControllerRef.current = controller;
1499
+ setState({
1500
+ ...INITIAL_STATE2,
1501
+ isStarting: true,
1502
+ isLoading: true
1503
+ });
1504
+ try {
1505
+ const result = await client.startWebflow({
1506
+ ...input,
1507
+ signal: input.signal ?? controller.signal
1508
+ });
1509
+ setState((prev) => ({
1510
+ ...prev,
1511
+ isStarting: false,
1512
+ isLoading: false
1513
+ }));
1514
+ return result;
1515
+ } catch (err) {
1516
+ const normalizedError = err instanceof Error ? err : new Error(String(err));
1517
+ handleError(normalizedError);
1518
+ throw normalizedError;
1519
+ }
1520
+ },
1521
+ [client, handleError]
1522
+ );
1523
+ const checkResult = useCallback(
1524
+ async (params, pollOptions) => {
1525
+ if (abortControllerRef.current) {
1526
+ abortControllerRef.current.abort();
1527
+ }
1528
+ const controller = new AbortController();
1529
+ abortControllerRef.current = controller;
1530
+ setState((prev) => ({
1531
+ ...prev,
1532
+ isChecking: true,
1533
+ isLoading: true,
1534
+ error: null
1535
+ }));
1536
+ try {
1537
+ const result = await client.pollWebflowStatus(params, {
1538
+ ...pollOptions,
1539
+ signal: pollOptions?.signal ?? controller.signal
1540
+ });
1541
+ handleResult(result);
1542
+ return result;
1543
+ } catch (err) {
1544
+ const normalizedError = err instanceof Error ? err : new Error(String(err));
1545
+ handleError(normalizedError);
1546
+ throw normalizedError;
1547
+ }
1548
+ },
1549
+ [client, handleResult, handleError]
1550
+ );
1551
+ const parseReturnParams = useCallback(
1552
+ (source) => {
1553
+ return client.parseWebflowReturnParams(source);
1554
+ },
1555
+ [client]
1556
+ );
1557
+ return {
1558
+ ...state,
1559
+ startWebflow,
1560
+ checkResult,
1561
+ parseReturnParams,
1562
+ cancel,
1563
+ reset,
1564
+ client
1565
+ };
1566
+ }
944
1567
 
945
- export { useJumioVerification };
1568
+ export { useJumioVerification, useJumioWebflow };
946
1569
  //# sourceMappingURL=index.mjs.map
947
1570
  //# sourceMappingURL=index.mjs.map