@41devs/naya-id-js 0.1.0 → 0.3.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/README.md CHANGED
@@ -1,45 +1,45 @@
1
- # Naya ID — SDK JavaScript
1
+ # @41devs/naya-id-js
2
2
 
3
- SDK JavaScript officiel de **Naya ID**, la vérification d'identité (eKYC) clé en
4
- main pour l'Afrique de l'Ouest (zone CEDEAO). **Aucun framework, aucune étape de
5
- build côté client** : vous ajoutez une balise `<script>`, vous appelez **une
6
- seule méthode**, l'interface Naya s'ouvre en plein écran, prend en charge la
7
- capture du document, la détection de vivacité et la décision, puis vous rend le
8
- résultat et rend la main à votre page.
3
+ SDK JavaScript officiel de **Naya ID** vérification d'identité (eKYC) clé en
4
+ main pour l'Afrique de l'Ouest. Une balise `<script>`, un Web Component ou un
5
+ import npm ; aucune dépendance à l'exécution ; ~40 Ko compressés.
9
6
 
10
- C'est le pendant « vanilla » des SDK [Flutter](https://pub.dev/packages/naya_id)
11
- et [Angular](https://www.npmjs.com/package/@41devs/naya-id-angular) : même
12
- parcours, mêmes écrans, même contrat d'API.
13
-
14
- - **npm** : [`@41devs/naya-id-js`](https://www.npmjs.com/package/@41devs/naya-id-js)
15
- - **Site** : https://naya.41devs.com
7
+ Le parcours : consentement capture guidée du document (recto, verso pour la
8
+ CNI) selfie avec **défi de vivacité imposé par le serveur** → confirmation des
9
+ données lues → décision expliquée. Sur ordinateur, relais vers le téléphone par
10
+ QR code. Trois langues : français, anglais, portugais.
16
11
 
17
12
  ---
18
13
 
19
- ## Installation
14
+ ## Démarrage rapide
15
+
16
+ ### 1. Votre backend ouvre la session (recommandé)
17
+
18
+ ```bash
19
+ curl -X POST https://api.naya.41devs.com/v1/verifications/start \
20
+ -H "X-API-Key: naya_live_xxx" -H "Content-Type: application/json" \
21
+ -d '{"documentType":"cni","country":"BJ","callbackUrl":"https://votre-backend.com/naya/hook"}'
22
+ # → { "data": { "verificationId": "…", "sessionToken": "…", "challenge": ["blink","turn-left","smile"] } }
23
+ ```
20
24
 
21
- ### Par balise `<script>` (le plus simple)
25
+ La clé ne quitte jamais votre serveur. Rendez `verificationId` + `sessionToken`
26
+ au navigateur (voir `docs/backend-session.md` pour Node, PHP, Python).
22
27
 
23
- Aucune installation. Chargez le SDK depuis un CDN — il expose un objet global
24
- `NayaId` :
28
+ ### 2. Votre page lance le parcours
25
29
 
26
30
  ```html
27
- <script src="https://cdn.jsdelivr.net/npm/@41devs/naya-id-js@0/dist/naya-id.min.js"></script>
31
+ <script
32
+ src="https://sdk.naya.41devs.com/sdk/0.3.0/naya-id.min.js"
33
+ integrity="sha384-…"
34
+ crossorigin="anonymous"
35
+ ></script>
28
36
  <script>
29
- document.getElementById('verifier').addEventListener('click', async () => {
30
- const result = await NayaId.startVerification({
31
- apiKey: 'naya_live_xxx',
32
- config: { country: 'BJ' },
33
- });
34
- console.log(result.status, result.verificationId, result.ocrData);
35
- });
37
+ const result = await NayaId.startVerification({ verificationId, sessionToken });
38
+ // result.status : 'approved' | 'review' | 'rejected' | 'cancelled' | 'error'
36
39
  </script>
37
40
  ```
38
41
 
39
- > Épinglez une version majeure (`@0`) ou exacte (`@0.1.0`) en production. `unpkg`
40
- > fonctionne de la même façon : `https://unpkg.com/@41devs/naya-id-js`.
41
-
42
- ### Par npm (bundlers : Vite, webpack, Rollup…)
42
+ Ou avec npm :
43
43
 
44
44
  ```bash
45
45
  npm install @41devs/naya-id-js
@@ -47,226 +47,135 @@ npm install @41devs/naya-id-js
47
47
 
48
48
  ```ts
49
49
  import { startVerification } from '@41devs/naya-id-js';
50
-
51
- const result = await startVerification({
52
- apiKey: 'naya_live_xxx',
53
- config: { country: 'BJ' },
54
- });
50
+ const result = await startVerification({ verificationId, sessionToken, config: { locale: 'fr' } });
55
51
  ```
56
52
 
57
- Le SDK est livré en **ESM**, **CommonJS** et **IIFE**, avec les **types
58
- TypeScript**. Il n'a **aucune dépendance npm** : la détection de vivacité
59
- (MediaPipe) est chargée à la demande depuis un CDN au moment du selfie.
53
+ Ne décidez jamais sur le résultat rendu par le navigateur : seul le webhook (ou
54
+ `GET /verifications/:id` avec votre clé) fait foi.
60
55
 
61
56
  ---
62
57
 
63
58
  ## Prérequis
64
59
 
65
- | Élément | Détail |
66
- |---|---|
67
- | **Clé API Naya** | Une clé `naya_live_…` ou `naya_test_…` (créée dans le dashboard Naya). |
68
- | **Entreprise vérifiée (KYB)** | La société propriétaire de la clé doit être **KYB-vérifiée**, sinon l'API renvoie `403`. |
69
- | **HTTPS** | La caméra (`getUserMedia`) exige un **contexte sécurisé** : `https://` en production, ou `http://localhost` en développement. Sur `http://` distant, la caméra ne démarre pas. |
70
- | **CORS** | L'origine de votre page doit être autorisée côté API Naya (liste `ALLOWED_ORIGINS`). |
60
+ | Élément | Détail |
61
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
62
+ | **Compte Naya vérifié (KYB)** | Sinon l'API répond `403` (`kyb_pending`). |
63
+ | **HTTPS** | La caméra exige un contexte sécurisé (`http://localhost` en développement). |
64
+ | **Navigateur** | Chrome, Safari, Firefox, Edge récents. Les navigateurs intégrés (WhatsApp, Facebook, Instagram) sont détectés : le SDK propose d'ouvrir le lien dans le navigateur. |
65
+ | **Origine (mode clé)** | Une clé **live** transmise au navigateur doit déclarer ses origines dans le dashboard. Sans objet avec le jeton de session. |
71
66
 
72
67
  ---
73
68
 
74
- ## Démarrage rapide
75
-
76
- `startVerification` retourne une promesse qui se résout quand l'utilisateur
77
- ressort du parcours, avec le résultat.
78
-
79
- ```js
80
- const result = await NayaId.startVerification({
81
- apiKey: 'naya_live_xxx',
82
- config: {
83
- country: 'BJ', // pré-sélectionne le pays
84
- callbackUrl: 'https://votre-backend.com/naya/hook', // notification serveur (optionnel)
85
- },
86
- });
87
-
88
- switch (result.status) {
89
- case 'approved': // Identité vérifiée. result.ocrData contient les champs extraits.
90
- break;
91
- case 'review': // Revue manuelle : décision finale notifiée plus tard via callbackUrl.
92
- break;
93
- case 'rejected': // Refus (result.note explique pourquoi).
94
- break;
95
- case 'cancelled': // L'utilisateur a fermé le parcours avant la fin.
96
- break;
97
- case 'error': // Problème technique : result.errorMessage.
98
- console.error(result.errorMessage);
99
- break;
100
- }
101
- ```
69
+ ## API
102
70
 
103
- `startVerification` **ne rejette jamais** : une erreur réseau ou une annulation
104
- revient toujours sous forme de `NayaResult` (`status: 'error'` ou `'cancelled'`).
71
+ ### `startVerification(params) Promise<NayaResult>`
105
72
 
106
- ---
73
+ | Champ | Type | Rôle |
74
+ | --------------------------------- | ------------ | --------------------------------------------------------------------- |
75
+ | `verificationId` + `sessionToken` | `string` | Session ouverte par votre backend. **Production.** |
76
+ | `apiKey` | `string` | Clé d'application transmise au navigateur. Démos et fronts maîtrisés. |
77
+ | `config` | `NayaConfig` | Voir ci-dessous. |
107
78
 
108
- ## Le parcours vu par l'utilisateur
79
+ Ne rejette jamais : erreurs et annulation reviennent en `NayaResult`.
109
80
 
110
- Une fois `startVerification` appelée, l'interface Naya enchaîne seule :
81
+ ### `create(params) NayaVerification`
111
82
 
112
- 1. **Bienvenue** écran d'accueil.
113
- 2. **Sélection** pays et type de document (sautée si `country` + un seul `documentTypes`).
114
- 3. **Capture du document** — recto, puis verso pour la CNI (bande MRZ). Cadre vert quand l'image est nette ; l'utilisateur déclenche.
115
- 4. **Vivacité** — selfie avec défi actif : regarder droit → cligner → tourner la tête.
116
- 5. **Traitement** — OCR, biométrie, décision côté serveur.
117
- 6. **Résultat** — approuvé / en revue / rejeté, puis retour à votre page.
83
+ Instance avec `open()`, `close()`, `on(event, cb)`, `destroy()`, `isOpen` —
84
+ pour les enveloppes de framework et l'analytique (`docs/frameworks.md`).
118
85
 
119
- L'interface est montée dans un **Shadow DOM** : ni le CSS de votre page
120
- n'affecte le SDK, ni l'inverse.
121
-
122
- ---
86
+ ### `isSupported() NayaSupport`
123
87
 
124
- ## API
88
+ À appeler avant d'afficher votre bouton : `ok`, `code`, `inAppBrowserName`,
89
+ `deviceClass`…
125
90
 
126
- ### `NayaId.startVerification(params)`
91
+ ### `<naya-id-verification>`
127
92
 
128
- | Champ | Type | Rôle |
129
- |---|---|---|
130
- | `apiKey` | `string` | Clé d'application, transmise depuis le navigateur. |
131
- | `sessionToken` | `string` | Jeton de session éphémère obtenu par votre backend (à privilégier). |
132
- | `auth` | `{ apiKey?, sessionToken? }` | Alternative aux deux champs ci-dessus, en objet. |
133
- | `config` | `NayaConfig` | Configuration optionnelle (voir ci-dessous). |
93
+ Web Component enregistré automatiquement par le bundle `<script>` ;
94
+ `defineElement()` en ESM/CJS.
134
95
 
135
96
  ### Configuration (`config`)
136
97
 
137
- Tous les champs sont optionnels — le SDK a des défauts raisonnables (tous pays,
138
- tous documents, français).
139
-
140
- | Champ | Type | Défaut | Rôle |
141
- |---|---|---|---|
142
- | `country` | `'BJ'\|'CI'\|'SN'\|'ML'\|'BF'\|'TG'` | | Pré-sélectionne le pays ; l'utilisateur ne le choisit plus. |
143
- | `documentTypes` | `('cni'\|'passeport'\|'permis')[]` | les trois | Types proposés. Un seul + `country` défini écran de sélection sauté. |
144
- | `countries` | `NayaCountryCode[]` | les six | Pays proposés quand `country` n'est pas fixé. |
145
- | `locale` | `'fr' \| 'en'` | `'fr'` | Langue de l'interface. |
146
- | `callbackUrl` | `string` | | URL HTTPS notifiée par le serveur Naya à chaque décision. |
147
- | `maxAttempts` | `number` | `2` | Nombre d'essais autorisés après un rejet (bouton « Réessayer »). |
148
- | `theme` | `{ primaryColor?, accentColor? }` | navy / ambre | Surcharge des couleurs de marque. |
149
- | `clientLogoUrl` | `string` | | Logo affiché à côté de « Propulsé par Naya ID ». |
150
- | `livenessAssets` | `{ esmUrl?, wasmBaseUrl?, modelUrl? }` | CDN public | Auto-hébergement MediaPipe (CSP stricte — voir plus bas). |
151
- | `baseUrl` | `string` | API de prod | À ne changer que pour un environnement de test. |
98
+ | Champ | Défaut | Rôle |
99
+ | --------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
100
+ | `country`, `countries`, `documentTypes` | tous | Pré-sélection ; l'écran de choix est sauté si un seul document et un pays. |
101
+ | `locale` | langue du navigateur | `fr`, `en`, `pt`. |
102
+ | `consent` | `true` | Écran de consentement ; `{ policyUrl, version }` ou `false` si recueilli en amont. |
103
+ | `confirmOcr` | `false` | Écran des données lues : `'display'` (lecture seule, l'utilisateur dit si c'est exact) ou `'edit'` (corrections saisies). `ocrData` n'est jamais altéré : le déclaré part à part (`declaredData`), un écart plafonne à la revue. |
104
+ | `capture` | `{ autoCapture: true, maxDimension: 1600, fileUpload: 'auto' }` | Capture automatique, taille d'image, import de fichier (ordinateur). |
105
+ | `liveness` | `{ timeoutMs: 45000, allowPassive: true, preload: true }` | Délai avant conseils, parcours sans geste, préchargement. |
106
+ | `handoff` | `true` | Relais QR vers mobile sur ordinateur ; `{ baseUrl }` pour une page hébergée par vous. |
107
+ | `container` | `null` | Monte dans un élément au lieu d'un overlay plein écran. |
108
+ | `theme` | encre / flamme | `primaryColor`, `accentColor`, `fontFamily`, `radius`, `mode` (`light`, `dark`, `auto`). Le texte des boutons reste lisible quelle que soit la couleur. |
109
+ | `clientLogoUrl` | | Logo affiché à côté de « Propulsé par Naya ID ». |
110
+ | `strings` | — | Surcharge de libellés par clé. |
111
+ | `livenessAssets` | CDN Naya | Auto-hébergement de MediaPipe (`docs/csp-and-self-hosting.md`). |
112
+ | `callbackUrl` | | Mode clé uniquement ; en session serveur, l'URL est posée à l'ouverture. |
113
+ | `maxAttempts` | `2` | Nouveaux essais après rejet (mode clé). |
114
+ | `eagerSession` | `false` | Ouvre la session dès le choix du document (latence) — attention à la facturation. |
115
+ | `telemetry` | `true` | Télémétrie anonyme (`docs/telemetry.md`). |
116
+ | `debug` | `false` | Journal `[NayaId]` dans la console. |
117
+ | `haptics` | `true` | Retour haptique sur mobile. |
118
+ | `onEvent` | — | Hook d'événements (`ready`, `step`, `session`, `document_captured`, `liveness_step`, `submitted`, `result`, `error`…). |
152
119
 
153
120
  ### Résultat (`NayaResult`)
154
121
 
155
- | Champ | Type | Présent quand |
156
- |---|---|---|
157
- | `status` | `'approved'\|'review'\|'rejected'\|'cancelled'\|'error'` | toujours |
158
- | `verificationId` | `string` | dès qu'une session a été créée — à conserver pour rapprocher notifications et dashboard |
159
- | `riskScore` | `number` (0-100) | après traitement score de confiance agrégé |
160
- | `matchScore` | `number` (0-100) | concordance visage document ↔ selfie |
161
- | `authenticityScore` | `number` (0-100) | intégrité du document |
162
- | `livenessScore` | `number` (0-100) | vivacité |
163
- | `ocrConfidence` | `number` (0-100) | confiance de l'OCR |
164
- | `ocrData` | `NayaOcrData` | champs extraits (nom, prénoms, date de naissance, n° document, NPI…) |
165
- | `note` | `string` | note d'analyse du pipeline |
166
- | `errorMessage` | `string` | uniquement si `status === 'error'` |
167
-
168
- ---
169
-
170
- ## Authentification
171
-
172
- Un seul des deux champs est à renseigner :
173
-
174
- | Mode | Usage |
175
- |---|---|
176
- | `apiKey` | Simple, mais la clé est **lisible dans le navigateur** (outils de dev). À réserver aux démos et aux intégrations où le front est maîtrisé. |
177
- | `sessionToken` | Jeton éphémère obtenu par **votre backend**, limité à une vérification. **À privilégier en production** : la clé ne quitte jamais votre serveur. |
178
-
179
- ```js
180
- // Production : votre backend échange sa clé contre un jeton de session.
181
- await NayaId.startVerification({
182
- sessionToken: jetonObtenuDeVotreBackend,
183
- config: { country: 'BJ' },
184
- });
185
- ```
186
-
187
- Passer de l'un à l'autre ne change rien au reste de l'intégration.
188
-
189
- ---
190
-
191
- ## Notifications serveur (`callbackUrl`)
192
-
193
- En passant `callbackUrl`, votre backend est notifié à chaque décision, sans
194
- interroger l'API. Deux événements possibles par session :
195
- `verification.completed` (décision automatique) puis, le cas échéant,
196
- `verification.reviewed` (après revue d'un analyste). Les envois sont signés
197
- (`X-Naya-Signature`, HMAC-SHA256). Détails côté documentation de l'API.
198
-
199
- ---
200
-
201
- ## Détection de vivacité
202
-
203
- La vivacité s'exécute **sur l'appareil** via [MediaPipe Face Landmarker]. Le
204
- module et le modèle sont chargés depuis un CDN public **au moment du selfie**
205
- seulement — aucune configuration requise, et rien n'alourdit le chargement
206
- initial de votre page. L'analyse est **locale** : la photo n'est transmise qu'à
207
- la fin du défi.
208
-
209
- Pour une page à **CSP stricte** (hôtes externes bloqués), auto-hébergez ces
210
- ressources et indiquez leurs URL :
211
-
212
- ```js
213
- config: {
214
- country: 'BJ',
215
- livenessAssets: {
216
- esmUrl: '/assets/naya/vision_bundle.mjs', // module @mediapipe/tasks-vision
217
- wasmBaseUrl: '/assets/naya/wasm', // dossier wasm/ du paquet
218
- modelUrl: '/assets/naya/face_landmarker.task', // modèle MediaPipe
219
- },
220
- }
221
- ```
222
-
223
- Copiez `vision_bundle.mjs`, le dossier `wasm/` et le modèle
224
- `face_landmarker.task` de `@mediapipe/tasks-vision@1.0.1`.
225
-
226
- [MediaPipe Face Landmarker]: https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker
122
+ | Champ | Présent | Rôle |
123
+ | ------------------------------------------------------ | ------------------------- | ---------------------------------------------------------------------------------------------------- |
124
+ | `status` | toujours | `approved`, `review`, `rejected`, `cancelled`, `error`. |
125
+ | `verificationId` | dès qu'une session existe | À conserver pour rapprocher webhooks et dashboard. |
126
+ | `reasonCodes` | décision | Motifs stables : `DOC_EXPIRED`, `FACE_MISMATCH`, `LIVENESS_LOW`, `AML_SANCTION`… (`docs/errors.md`). |
127
+ | `livenessMode` | décision | `active` ou `passive`. |
128
+ | `errorCode`, `errorMessage`, `statusCode`, `requestId` | `status: 'error'` | Catalogue dans `docs/errors.md` ; `requestId` pour le support. |
129
+ | scores, `ocrData`, `note` | décision | Indicatifs ; la vérité est côté serveur. `ocrData` est l'extraction brute, jamais modifiée. |
130
+ | `declaredData`, `dataMismatches`, `dataDisputed` | décision | Ce que l'utilisateur a déclaré ou contesté, conservé à part ; à vous de l'afficher ou non. |
227
131
 
228
132
  ---
229
133
 
230
- ## Contraintes navigateur
134
+ ## Sécurité, en deux lignes
231
135
 
232
- - **Contexte sécurisé obligatoire** pour la caméra : `https://` ou `http://localhost`.
233
- - Testé sur Chrome et Safari (desktop et iOS). Les webviews in-app (Instagram,
234
- Facebook…) restreignent souvent `getUserMedia` — orientez l'utilisateur vers
235
- le navigateur système si la caméra ne démarre pas.
236
- - L'interface s'ouvre en superposition plein écran (Shadow DOM) et fige le
237
- défilement de la page tant que le parcours est ouvert.
136
+ Le jeton de session n'ouvre qu'une vérification, expire en 4 h et meurt à la
137
+ soumission. Le défi de vivacité est tiré par le serveur et vérifié par lui ;
138
+ le score calculé sur l'appareil ne décide jamais. Les ressources MediaPipe
139
+ viennent du CDN Naya, empreinte du modèle vérifiée, repli public possible.
238
140
 
239
141
  ---
240
142
 
241
- ## Dépannage
143
+ ## Documentation
242
144
 
243
- | Symptôme | Cause probable |
244
- |---|---|
245
- | `status: 'error'`, « clé API invalide » (401) | Clé absente/révoquée, ou coquille. |
246
- | `status: 'error'`, « KYB… » (403) | La société de la clé n'est pas KYB-vérifiée. |
247
- | `status: 'error'`, « CORS » | L'origine de votre page n'est pas dans `ALLOWED_ORIGINS` côté API. |
248
- | La caméra ne démarre pas | Page servie en `http://` distant (contexte non sécurisé), permission refusée, ou webview in-app. |
249
- | Score de vivacité anormalement bas | Selfie de mauvaise qualité (contre-jour, reflets d'écran) — l'anti-fraude serveur le pénalise. |
145
+ - `docs/backend-session.md` ouvrir la session côté serveur (Node, PHP, Python)
146
+ - `docs/webhooks.md` — notifications signées
147
+ - `docs/errors.md` catalogue des `errorCode`
148
+ - `docs/csp-and-self-hosting.md` CSP, pare-feu, auto-hébergement, SRI
149
+ - `docs/frameworks.md` Web Component, React, Vue, Angular, conteneur
150
+ - `docs/liveness-challenge-spec.md` spécification du défi (commune aux SDK)
151
+ - `docs/telemetry.md`, `docs/accessibility.md`, `docs/migration-0.2-to-0.3.md`
152
+ - `hosting/README.md` — CDN `sdk.naya.41devs.com`
250
153
 
251
154
  ---
252
155
 
253
- ## Développement (ce dépôt)
156
+ ## Développement
254
157
 
255
158
  ```bash
256
- npm install # Node 18+ (22 recommandé)
257
- npm run build # produit dist/ (iife .min.js, esm .mjs, cjs .cjs, types .d.ts)
258
- npm run typecheck # vérification TypeScript
159
+ npm ci
160
+ npm run typecheck && npm run lint && npm test # unitaires (vitest, jsdom)
161
+ npm run build && npm run size # dist/ + budget de taille
162
+ npm run assets:fetch # MediaPipe local pour le parcours complet e2e
163
+ npx playwright install chromium && npm run test:e2e # parcours réel, caméra factice, API simulée
164
+ npm run serve # example/index.html
259
165
  ```
260
166
 
261
- Démo locale : après `npm run build`, servez la racine et ouvrez
262
- `example/index.html` sur `http://localhost` (contexte sécurisé requis) :
263
-
264
- ```bash
265
- npm run serve # puis http://localhost:3000/example/
266
- ```
167
+ Publication : tag `vX.Y.Z` CI `npm publish --provenance`.
267
168
 
268
169
  ---
269
170
 
270
- ## Licence
171
+ ## Dépannage
172
+
173
+ | Symptôme | Cause probable |
174
+ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
175
+ | `errorCode: 'origin_not_allowed'` | Clé live utilisée depuis une origine non déclarée : dashboard → clé → Origines autorisées, ou passez au jeton de session. |
176
+ | `errorCode: 'network'` | Hors-ligne, pare-feu ou proxy qui bloque `api.naya.41devs.com` ; CSP trop stricte (`docs/csp-and-self-hosting.md`). |
177
+ | `errorCode: 'in_app_browser'` | Lien ouvert dans WhatsApp/Facebook : le SDK propose de copier le lien. |
178
+ | `errorCode: 'liveness_assets'` | Module MediaPipe injoignable : autoriser `sdk.naya.41devs.com` ou auto-héberger. |
179
+ | Décision `review` avec `LIVENESS_PASSIVE` | L'utilisateur a choisi le parcours sans geste : revue humaine attendue. |
271
180
 
272
- Propriété de 41Devs. Voir le fichier `LICENSE`.
181
+ Licence : voir `LICENSE`.