@dynamicore/jumio-sdk 1.0.1 → 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.
- package/README.md +235 -160
- package/dist/client-BwEiGMPy.d.mts +515 -0
- package/dist/client-BwEiGMPy.d.ts +515 -0
- package/dist/core/index.d.mts +94 -4
- package/dist/core/index.d.ts +94 -4
- package/dist/core/index.js +545 -125
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +506 -83
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +747 -126
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +706 -83
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +146 -2
- package/dist/react/index.d.ts +146 -2
- package/dist/react/index.js +766 -142
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +766 -143
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/client-BR1xIZ0X.d.mts +0 -252
- package/dist/client-BR1xIZ0X.d.ts +0 -252
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @dynamicore/jumio-sdk
|
|
2
2
|
|
|
3
|
-
> SDK modular, tipado y agnóstico para la validación de identidad
|
|
3
|
+
> SDK modular, tipado y agnóstico para la validación de identidad mediante **Jumio** en el ecosistema DynamiCore. Soporta tanto el flujo de **Hosted Webflow (Redirección Web)** como el de **Carga Directa de Documentos (INE API)**.
|
|
4
4
|
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
6
|
[](https://react.dev/)
|
|
@@ -12,17 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
## 🌟
|
|
15
|
+
## 🌟 Modalidades de Verificación Soportadas
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- **Tipado Estricto y Errores Específicos**: Jerarquía clara de errores (`JumioTimeoutError`, `JumioValidationError`, `JumioImageProcessingError`, etc.).
|
|
25
|
-
- **Bundle Dual Optimizado**: Salida ESM (`.js`) y CommonJS (`.cjs`) con source maps y definiciones TypeScript (`.d.ts`).
|
|
17
|
+
| Característica | 🌐 1. Hosted Webflow (Redirección) | 📄 2. Carga Directa (INE API) |
|
|
18
|
+
| :--- | :--- | :--- |
|
|
19
|
+
| **Experiencia de Usuario** | El usuario es redirigido a la interfaz alojada oficial de Jumio para captura biométrica y de documentos. | El usuario permanece en tu app; tu interfaz captura/sube las imágenes de la INE. |
|
|
20
|
+
| **Casos de Uso** | Panel web, onboarding sin cámara nativa, verificación biométrica completa + Liveness. | Formularios web/móviles integrados con controles de archivo personalizados. |
|
|
21
|
+
| **Entrada Requerida** | `clientId`, `successUrl`, `errorUrl` | `clientId`, `frontImage`, `backImage` |
|
|
22
|
+
| **Hooks de React** | `useJumioWebflow` | `useJumioVerification` |
|
|
23
|
+
| **Métodos de Core** | `startWebflow`, `getWebflowStatus`, `pollWebflowStatus`, `parseWebflowReturnParams`, `buildRedirectUrl` | `startIneVerification`, `getIneStatus`, `verifyIne`, `pollIneStatus` |
|
|
26
24
|
|
|
27
25
|
---
|
|
28
26
|
|
|
@@ -31,110 +29,229 @@
|
|
|
31
29
|
### Desde npm / registro privado:
|
|
32
30
|
```bash
|
|
33
31
|
npm install @dynamicore/jumio-sdk axios
|
|
34
|
-
# o con pnpm:
|
|
32
|
+
# o con pnpm / yarn:
|
|
35
33
|
pnpm add @dynamicore/jumio-sdk axios
|
|
36
|
-
# o con yarn:
|
|
37
|
-
yarn add @dynamicore/jumio-sdk axios
|
|
38
34
|
```
|
|
39
35
|
|
|
40
|
-
###
|
|
36
|
+
### En proyectos locales (Monorepo o enlace local):
|
|
41
37
|
```bash
|
|
42
38
|
npm install file:../jumio-sdk
|
|
43
39
|
```
|
|
44
40
|
|
|
45
41
|
---
|
|
46
42
|
|
|
47
|
-
## 🚀
|
|
43
|
+
## 🚀 Guías y Ejemplos de Uso
|
|
48
44
|
|
|
49
|
-
|
|
45
|
+
---
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
### 🌐 Servicio 1: Hosted Webflow (Redirección Web)
|
|
48
|
+
|
|
49
|
+
En este flujo, la app solicita una sesión webflow, redirige al cliente a Jumio (`href`) y consulta el veredicto al regresar.
|
|
50
|
+
|
|
51
|
+
> [!NOTE]
|
|
52
|
+
> Llegar a `successUrl` solo significa que el usuario **completó los pasos en Jumio**, no que fue aprobado. Llegar a `errorUrl` significa que **abandonó o falló el flujo**. El veredicto real (`PASSED` / `WARNING` / `REJECTED` + `extraction`) solo se obtiene consultando el estado con `accountId` + `workflowId` (`getWebflowStatus` una vez, o `pollWebflowStatus` con sondeo). Por eso ambos suelen apuntar a la misma ruta `/verify/return`.
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
#### A. Ejemplo con React / Next.js (`useJumioWebflow`)
|
|
55
|
+
|
|
56
|
+
**Paso 1: Iniciar la verificación (Pantalla de Inicio)**
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
progress,
|
|
66
|
-
result,
|
|
67
|
-
error,
|
|
68
|
-
reset,
|
|
69
|
-
} = useJumioVerification({
|
|
58
|
+
```tsx
|
|
59
|
+
import React from "react";
|
|
60
|
+
import { useJumioWebflow } from "@dynamicore/jumio-sdk/react";
|
|
61
|
+
|
|
62
|
+
export function StartIdentityVerification({ clientId }: { clientId: string }) {
|
|
63
|
+
const { startWebflow, isStarting, error } = useJumioWebflow({
|
|
70
64
|
baseUrl: process.env.NEXT_PUBLIC_API_URL || "https://front.dynamicore.io",
|
|
71
|
-
context: process.env.
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
if (res.valid) {
|
|
75
|
-
alert("¡Tu INE ha sido validada exitosamente!");
|
|
76
|
-
} else {
|
|
77
|
-
alert(`No se pudo validar el INE: ${res.errorMessage || "Documento no legible"}`);
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
onStatusError: (err) => {
|
|
81
|
-
console.error("Error en validación en segundo plano:", err);
|
|
82
|
-
},
|
|
65
|
+
context: process.env.NEXT_PUBLIC_DYNAMICORE_CONTEXT,
|
|
66
|
+
// Opcional: URL base para proxy de redirección
|
|
67
|
+
redirectProxyUrl: "https://inllhuznm2.execute-api.us-west-2.amazonaws.com/prod/jumio/redirect/",
|
|
83
68
|
});
|
|
84
69
|
|
|
85
|
-
const
|
|
86
|
-
e.preventDefault();
|
|
87
|
-
if (!frontFile || !backFile) return;
|
|
88
|
-
|
|
70
|
+
const handleStart = async () => {
|
|
89
71
|
try {
|
|
90
|
-
|
|
91
|
-
|
|
72
|
+
const baseUrl = window.location.origin;
|
|
73
|
+
await startWebflow({
|
|
92
74
|
clientId,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
75
|
+
successUrl: `${baseUrl}/verify/return`,
|
|
76
|
+
errorUrl: `${baseUrl}/verify/return`,
|
|
77
|
+
useRedirectProxy: true, // Codifica las URLs en Base64 URL-safe con proxy
|
|
78
|
+
openInNewTab: true, // Abre Jumio en una nueva pestaña (window.open) sin salir de la app
|
|
79
|
+
// autoRedirect: true, // O redirige en la misma pestaña (window.location.href)
|
|
96
80
|
});
|
|
97
81
|
|
|
98
|
-
if (res.valid) {
|
|
99
|
-
onNext(); // Avanzar al siguiente paso del onboarding
|
|
100
|
-
}
|
|
101
82
|
} catch (err) {
|
|
102
83
|
console.error("Error al iniciar verificación:", err);
|
|
103
84
|
}
|
|
104
85
|
};
|
|
105
86
|
|
|
106
87
|
return (
|
|
107
|
-
<
|
|
88
|
+
<div>
|
|
89
|
+
<h3>Verificación de Identidad</h3>
|
|
90
|
+
{error && <p style={{ color: "red" }}>{error.message}</p>}
|
|
91
|
+
<button onClick={handleStart} disabled={isStarting}>
|
|
92
|
+
{isStarting ? "Cargando..." : "Iniciar Verificación con Jumio"}
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Paso 2: Procesar el retorno (Pantalla `/verify/return`)**
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import React, { useEffect } from "react";
|
|
103
|
+
import { useJumioWebflow } from "@dynamicore/jumio-sdk/react";
|
|
104
|
+
|
|
105
|
+
export function IdentityReturnPage({ clientId }: { clientId: string }) {
|
|
106
|
+
const { parseReturnParams, checkResult, isChecking, result, isValid, isRejected, error } =
|
|
107
|
+
useJumioWebflow({
|
|
108
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL,
|
|
109
|
+
context: process.env.NEXT_PUBLIC_DYNAMICORE_CONTEXT,
|
|
110
|
+
onResult: (res) => {
|
|
111
|
+
if (res.valid) {
|
|
112
|
+
console.log("¡Verificación aprobada!", res.extraction);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
// 1. Extraer accountId y workflowId de los query params de la URL
|
|
119
|
+
const { accountId, workflowId } = parseReturnParams();
|
|
120
|
+
|
|
121
|
+
if (accountId && workflowId) {
|
|
122
|
+
// 2. Sondear el resultado final (acepta { maxAttempts, pollingIntervalMs, signal })
|
|
123
|
+
// Para una sola consulta sin sondeo: await client.getWebflowStatus({ accountId, workflowId, clientId })
|
|
124
|
+
// Retorna null si aún está pendiente.
|
|
125
|
+
checkResult({ accountId, workflowId, clientId });
|
|
126
|
+
}
|
|
127
|
+
return () => cancel(); // Limpia el sondeo si el componente se desmonta (abort resetea isChecking)
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
if (isChecking) {
|
|
131
|
+
return <p>Verificando identidad, por favor espera un momento...</p>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (isValid) {
|
|
135
|
+
return (
|
|
108
136
|
<div>
|
|
109
|
-
<
|
|
110
|
-
<
|
|
111
|
-
type="file"
|
|
112
|
-
accept="image/*"
|
|
113
|
-
onChange={(e) => setFrontFile(e.target.files?.[0] || null)}
|
|
114
|
-
/>
|
|
137
|
+
<h2>✅ Verificación Completada</h2>
|
|
138
|
+
<p>CURP Extraída: {String(result?.extraction?.curp || "N/A")}</p>
|
|
115
139
|
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
116
142
|
|
|
143
|
+
if (isRejected || error) {
|
|
144
|
+
return (
|
|
117
145
|
<div>
|
|
118
|
-
<
|
|
119
|
-
<
|
|
120
|
-
type="file"
|
|
121
|
-
accept="image/*"
|
|
122
|
-
onChange={(e) => setBackFile(e.target.files?.[0] || null)}
|
|
123
|
-
/>
|
|
146
|
+
<h2>❌ Verificación No Aprobada</h2>
|
|
147
|
+
<p>{error?.message || "El documento fue rechazado."}</p>
|
|
124
148
|
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
125
151
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
152
|
+
return <p>Cargando información de la sesión...</p>;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
#### B. Ejemplo con TypeScript / Node.js (`JumioClient`)
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { JumioClient } from "@dynamicore/jumio-sdk";
|
|
162
|
+
|
|
163
|
+
const jumio = new JumioClient({
|
|
164
|
+
baseUrl: "https://front.dynamicore.io",
|
|
165
|
+
context: "MI_CONTEXTO_NEGOCIO",
|
|
166
|
+
authToken: () => getAuthTokenFromSession(),
|
|
167
|
+
redirectProxyUrl: "https://inllhuznm2.execute-api.us-west-2.amazonaws.com/prod/jumio/redirect/",
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// 1. Solicitar la URL de verificación
|
|
171
|
+
async function initVerificationSession(clientId: string) {
|
|
172
|
+
const { href, accountId, workflowId } = await jumio.startWebflow({
|
|
173
|
+
clientId,
|
|
174
|
+
successUrl: "https://myapp.com/identity/callback",
|
|
175
|
+
errorUrl: "https://myapp.com/identity/callback",
|
|
176
|
+
useRedirectProxy: true,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
console.log("Redirigir cliente a:", href);
|
|
180
|
+
return { href, accountId, workflowId };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 2. Consultar o sonder el resultado al regresar
|
|
184
|
+
async function verifyReturnStatus(returnUrl: string, clientId: string) {
|
|
185
|
+
const { accountId, workflowId } = jumio.parseWebflowReturnParams(returnUrl);
|
|
186
|
+
|
|
187
|
+
if (!accountId || !workflowId) {
|
|
188
|
+
throw new Error("No se encontraron parámetros de seguimiento en la URL.");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = await jumio.pollWebflowStatus(
|
|
192
|
+
{ accountId, workflowId, clientId },
|
|
193
|
+
{ maxAttempts: 15, pollingIntervalMs: 5000 }
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (result.valid) {
|
|
197
|
+
console.log("Veredicto APROBADO:", result.extraction);
|
|
198
|
+
} else {
|
|
199
|
+
console.warn("Veredicto RECHAZADO:", result.errorMessage);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
133
207
|
|
|
134
|
-
|
|
208
|
+
### 📄 Servicio 2: Carga Directa de INE (INE API)
|
|
209
|
+
|
|
210
|
+
En este flujo, la app captura las imágenes de la INE (frente y reverso) en su propia interfaz y las envía directamente al backend.
|
|
211
|
+
|
|
212
|
+
#### A. Ejemplo con React / Next.js (`useJumioVerification`)
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import React, { useState } from "react";
|
|
216
|
+
import { useJumioVerification } from "@dynamicore/jumio-sdk/react";
|
|
217
|
+
|
|
218
|
+
export function DirectIneVerificationStep({ clientId, onNext }: { clientId: string; onNext: () => void }) {
|
|
219
|
+
const [frontFile, setFrontFile] = useState<File | null>(null);
|
|
220
|
+
const [backFile, setBackFile] = useState<File | null>(null);
|
|
221
|
+
|
|
222
|
+
const { verify, isSubmitting, isPolling, isLoading, stage, progress, error } =
|
|
223
|
+
useJumioVerification({
|
|
224
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL,
|
|
225
|
+
context: process.env.NEXT_PUBLIC_DYNAMICORE_CONTEXT,
|
|
226
|
+
onStatusResolved: (res) => {
|
|
227
|
+
if (res.valid) {
|
|
228
|
+
onNext();
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
234
|
+
e.preventDefault();
|
|
235
|
+
if (!frontFile || !backFile) return;
|
|
236
|
+
|
|
237
|
+
await verify({
|
|
238
|
+
clientId,
|
|
239
|
+
frontImage: frontFile,
|
|
240
|
+
backImage: backFile,
|
|
241
|
+
awaitFinalStatus: false, // Sondeo en 2do plano sin bloquear la UI
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<form onSubmit={handleSubmit}>
|
|
247
|
+
<input type="file" accept="image/*" onChange={(e) => setFrontFile(e.target.files?.[0] || null)} />
|
|
248
|
+
<input type="file" accept="image/*" onChange={(e) => setBackFile(e.target.files?.[0] || null)} />
|
|
249
|
+
|
|
250
|
+
{isLoading && <p>Estado: {stage} ({progress}%)</p>}
|
|
251
|
+
{error && <p style={{ color: "red" }}>{error.message}</p>}
|
|
135
252
|
|
|
136
253
|
<button type="submit" disabled={isLoading || !frontFile || !backFile}>
|
|
137
|
-
|
|
254
|
+
Continuar
|
|
138
255
|
</button>
|
|
139
256
|
</form>
|
|
140
257
|
);
|
|
@@ -143,41 +260,34 @@ export function IneVerificationStep({ clientId, onNext }: { clientId: string; on
|
|
|
143
260
|
|
|
144
261
|
---
|
|
145
262
|
|
|
146
|
-
|
|
263
|
+
#### B. Ejemplo con TypeScript / Node.js (`JumioClient.verifyIne`)
|
|
147
264
|
|
|
148
265
|
```typescript
|
|
149
266
|
import { JumioClient, isJumioError } from "@dynamicore/jumio-sdk";
|
|
150
267
|
|
|
151
268
|
const jumio = new JumioClient({
|
|
152
269
|
baseUrl: "https://front.dynamicore.io",
|
|
153
|
-
context: "MI_CONTEXTO_NEGOCIO",
|
|
154
|
-
authToken: () => getAuthTokenFromSession(),
|
|
155
|
-
// Opcional: Función para firmar URLs privadas de S3
|
|
156
|
-
s3Signer: async (path, expires) => {
|
|
157
|
-
return await myS3SignerService(path, expires);
|
|
158
|
-
},
|
|
270
|
+
context: "MI_CONTEXTO_NEGOCIO",
|
|
271
|
+
authToken: () => getAuthTokenFromSession(),
|
|
159
272
|
});
|
|
160
273
|
|
|
161
|
-
async function
|
|
274
|
+
async function runDirectVerification() {
|
|
162
275
|
try {
|
|
163
276
|
const result = await jumio.verifyIne({
|
|
164
277
|
clientId: "usr_987654",
|
|
165
278
|
frontImage: "https://my-bucket.s3.amazonaws.com/uploads/ine_front.jpg",
|
|
166
279
|
backImage: "https://my-bucket.s3.amazonaws.com/uploads/ine_back.jpg",
|
|
167
|
-
awaitFinalStatus: true,
|
|
280
|
+
awaitFinalStatus: true,
|
|
168
281
|
});
|
|
169
282
|
|
|
170
283
|
if (result.valid) {
|
|
171
|
-
console.log("
|
|
284
|
+
console.log("INE validada con éxito:", result.data);
|
|
172
285
|
} else {
|
|
173
|
-
console.warn("
|
|
286
|
+
console.warn("INE rechazada:", result.errorMessage);
|
|
174
287
|
}
|
|
175
288
|
} catch (error) {
|
|
176
289
|
if (isJumioError(error)) {
|
|
177
290
|
console.error(`Error Jumio [${error.code}]:`, error.message);
|
|
178
|
-
console.error("Detalle crudo:", error.rawData);
|
|
179
|
-
} else {
|
|
180
|
-
console.error("Error inesperado:", error);
|
|
181
291
|
}
|
|
182
292
|
}
|
|
183
293
|
}
|
|
@@ -191,85 +301,50 @@ async function runVerification() {
|
|
|
191
301
|
| :--- | :--- | :--- | :--- |
|
|
192
302
|
| `baseUrl` | `string` | `"https://front.dynamicore.io"` | URL base de la API de DynamiCore. |
|
|
193
303
|
| `endpoint` | `string` | `"/marketplace/apps/jumio"` | Ruta del endpoint de verificación. |
|
|
194
|
-
| `context` | `string` | `undefined` |
|
|
195
|
-
| `authToken` | `string \|
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## 🚦 Estados y Veredicto (`valid` / `decision`)
|
|
221
|
-
|
|
222
|
-
> **Regla de oro:** el veredicto real de la verificación está en los campos `valid` y `decision`, **no** en `workflowStatus`. `workflowStatus: "PROCESSED"` solo significa que el workflow terminó de procesar; un `PROCESSED` + `decision: "NOT_EXECUTED"` es un **rechazo**, no una aprobación.
|
|
223
|
-
|
|
224
|
-
### Estados del workflow (`JumioWorkflowStatus`)
|
|
225
|
-
|
|
226
|
-
- `PENDING` / `INITIATED`: En proceso de análisis por los motores de Jumio.
|
|
227
|
-
- `PROCESSED`: El workflow terminó de procesar. **No implica aprobación**: el veredicto se determina con `valid` / `decision`.
|
|
228
|
-
- `APPROVED_VERIFIED` / `DONE` / `COMPLETED`: Estados inequívocamente aprobados (fallback conservador).
|
|
229
|
-
- `REJECTED` / `FAILED` / `DENIED` / `EXPIRED` / `ABANDONED` / `NOT_READABLE` / `FRAUD` / `UNSUPPORTED_ID_TYPE`: Estados finales no válidos.
|
|
230
|
-
|
|
231
|
-
### Precedencia del veredicto
|
|
232
|
-
|
|
233
|
-
El SDK resuelve la validez del resultado priorizando los campos explícitos:
|
|
234
|
-
|
|
235
|
-
1. **`valid` booleano** (fuente de verdad de la gateway): `true` = aprobado, `false` = rechazado.
|
|
236
|
-
2. **`decision`** presente: `PASSED` = aprobado; cualquier otro valor (`NOT_EXECUTED`, `REJECTED`, `FAILED`, etc.) = rechazado.
|
|
237
|
-
3. **Fallback conservador** por `workflowStatus` (fail-closed): solo `APPROVED_VERIFIED` / `DONE` / `COMPLETED` cuentan como aprobados; si no hay `valid` ni `decision`, un `PROCESSED` se considera rechazado.
|
|
238
|
-
|
|
239
|
-
> Siempre lee `result.valid` en lugar de interpretar el payload crudo de Jumio.
|
|
240
|
-
|
|
241
|
-
---
|
|
242
|
-
|
|
243
|
-
## 🔄 Flujo No Bloqueante y Ciclo de Vida del Polling
|
|
244
|
-
|
|
245
|
-
Con `awaitFinalStatus: false` el SDK retorna de inmediato para no detener al usuario y ejecuta el sondeo (`polling`) en segundo plano consultando el estado del workflow hasta obtener un veredicto final:
|
|
246
|
-
|
|
247
|
-
- El polling en segundo plano está **desacoplado del `signal`** del llamador: continúa aunque el componente que lo inició se desmonte o se aborte la señal original (navegación, cierre de pantalla). El `signal` / `AbortController` solo aplica a la fase inicial síncrona (upload y consulta de arranque).
|
|
248
|
-
- El veredicto final llega a `onStatusResolved` (o a `isSuccess` / `isError` del hook); en caso de agotarse los `maxPollingAttempts` se invoca `onStatusError` con un `JumioPollingTimeoutError`.
|
|
249
|
-
- Si la respuesta inicial ya trae un veredicto definitivo (p. ej. `valid: false` + `workflowStatus: "PROCESSED"`), el SDK lo resuelve de inmediato sin requerir los identificadores de seguimiento.
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
304
|
+
| `context` | `string` | `undefined` | Header de contexto enviado en las peticiones. |
|
|
305
|
+
| `authToken` | `string \| Provider` | `undefined` | Token Bearer o función proveedora dinámica. |
|
|
306
|
+
| `authTokenPrefix` | `string` | `""` | Prefijo del header `Authorization` (ej. `"Bearer"`). |
|
|
307
|
+
| `redirectProxyUrl` | `string` | `undefined` | URL base del proxy de redirección para Hosted Webflow. |
|
|
308
|
+
| `requestTimeout` | `number` | `180000` (3 min) | Timeout para peticiones POST. |
|
|
309
|
+
| `statusTimeout` | `number` | `120000` (2 min) | Timeout para peticiones GET de estado. |
|
|
310
|
+
| `maxRetries` | `number` | `3` | Número de reintentos ante caídas de red. |
|
|
311
|
+
| `retryDelayMs` | `number` | `800` | Delay base del backoff exponencial entre reintentos. |
|
|
312
|
+
| `pollingIntervalMs` | `number` | `10000` (10s) | Intervalo entre consultas de sondeo. |
|
|
313
|
+
| `maxPollingAttempts` | `number` | `20` | Máximo de consultas de sondeo antes de timeout. |
|
|
314
|
+
| `s3Signer` | `S3SignerFunction` | `undefined` | Función para firmar rutas privadas de S3. |
|
|
315
|
+
| `customHeaders` | `Record<string,string>` | `undefined` | Headers adicionales en cada petición. |
|
|
316
|
+
| `axiosInstance` | `AxiosInstance` | `undefined` | Instancia propia de Axios (opcional). |
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 🧰 Helpers y notas del flujo Webflow
|
|
321
|
+
|
|
322
|
+
- `client.buildRedirectUrl(returnUrl, useProxy)` / `buildRedirectUrl(url, proxy)` — construye la URL de retorno, con Base64 URL-safe si usas `redirectProxyUrl`.
|
|
323
|
+
- `client.getWebflowStatus({ accountId, workflowId, clientId })` — una sola consulta `GET ?type=ine&accountId&workflowId&clientId`; retorna `WebflowResult | null` (`null` = pendiente).
|
|
324
|
+
- `client.pollWebflowStatus(params, { maxAttempts, pollingIntervalMs, signal, onAttempt })` — sondeo hasta veredicto o `JumioPollingTimeoutError`.
|
|
325
|
+
- Hooks exponen `cancel()` / `reset()`; un abort (desmontaje, `cancel`, StrictMode) baja `isChecking/isStarting/isLoading` sin marcar error.
|
|
326
|
+
- El backend puede responder con envelope `{ data }` o `{ values }` y datos anidados; el SDK los desempaqueta. `parseWebflowReturnParams` acepta `workflowExecutionId` / `workflowId` y params anidados en `?status=`.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
253
330
|
## 🧪 Pruebas y Construcción
|
|
254
331
|
|
|
255
|
-
Para compilar y ejecutar la suite de pruebas del paquete:
|
|
256
|
-
|
|
257
332
|
```bash
|
|
258
333
|
# Instalar dependencias
|
|
259
334
|
npm install
|
|
260
335
|
|
|
261
|
-
# Ejecutar pruebas unitarias
|
|
336
|
+
# Ejecutar suite completa de pruebas unitarias (59 tests)
|
|
262
337
|
npm test
|
|
263
338
|
|
|
264
|
-
# Compilar para producción (ESM, CJS y .d.ts)
|
|
265
|
-
npm run build
|
|
266
|
-
|
|
267
339
|
# Verificación de tipos TypeScript
|
|
268
340
|
npm run typecheck
|
|
341
|
+
|
|
342
|
+
# Compilar para producción (ESM, CJS y .d.ts)
|
|
343
|
+
npm run build
|
|
269
344
|
```
|
|
270
345
|
|
|
271
346
|
---
|
|
272
347
|
|
|
273
348
|
## 📄 Licencia
|
|
274
349
|
|
|
275
|
-
MIT © DynamiCore
|
|
350
|
+
MIT © DynamiCore
|