# Capture NUPTIAL plugin for Flutter
© Everial

Ceci est un projet exemple minimaliste utilisant le plugin Capture Everial : NUPTIAL

Le plug-in apporte des fonctionnalités de reconnaissance de documents officiels et d'extraction de données
Il prend en charge l'intégralité des fonctions de capture.

> ### Note sur le fonctionnement 
>  Le plugin est conçu pour effectuer les traitements sur le mobile. Cependant, il doit
> accèder à certains webservices périodiquement: 
>  - première utilisation de l'application
>  - relevé d'utilisation
>  - mise à jour des modèles de reconnaissance
>  - mise à jour de la configuration
>  - test de preuve de vie  
>  - accès à des webservices complémentaires pour les mobiles peu puissans
>
> La détection de l'état de connection est automatique et transparente


# Préalable à l’installation
### Demande d’identifiant 

Le SDK s’initialise à partir d’un login et d’un mot de passe fourni par Everial. Ce login est associé au nom du bundle de l’application embarquant le SDK (iOs, Android). Exemple : *com.yourcompany.yourapp*

> L'application de test est pré-configuré avec vos identifiants. Voir le fichier "home.html"

### Versions 

**IOS**
Le plugin doit être compilé en swift version 5 pour la version iOS.
La version minimale iOS est la 13.

**Android**
Le plugin supporte Android SDK depuis la version 24.
    
# Installation de l'application de test

 - Dézipper le paquet puis à la racine exécutez:
   ```shell
    flutter pub get
    ```
  Cela permettra d'importer les dépendances.

  **iOS**
 - Dans le répertoire du projet ./example, buildez : flutter build ios --no-codesign (https://docs.flutter.dev/packages-and-plugins/developing-packages#step-2c-add-ios-platform-code-swifthm)
 - Dans ./ios/App, ouvrir "App.xcworkspace".
 - Indiquez vos  signatures de développement et de certificats.
 - Dans l'onglet info des paramètres du projet, ajoutez l'entrée: "Privacy - Camera Usage Description" et un texte à destination de l'utilisateur pour l'usage de la caméra.
 - Run

**Android**
 - Ouvrir le répertoire ./android dans Android Studio.
Dans le "build.gradle" de l'app, ajoutez le repository maven everial et https://jitpack.io:
```gradle
repositories {
(...)
   maven {
        url 'https://pkgs.dev.azure.com/Everial/Doclab/_packaging/plugins/maven/v1'
        name 'Main_Feed'
    }
    maven {
        url  'https://jitpack.io'
    }   
}
````

Dans variables.gradle, montez le minSDK à la version 24 pour une compatibilité CameraX:
```gradle
ext {
    minSdkVersion = 24
    compileSdkVersion = 29
    (...)
}
```

# Utilisation
Le plugin est disponible dans Flutter/Dart sous le nom 'capture':
```dart
import 'package:capture/capture.dart';

```

Il doit être ajouté dans le fichier 'pubspec.yaml' :
```yaml
dependencies:
  flutter:
    sdk: flutter
    git:
        url: [adresse git azure]
```

**Code d'exemple des fonctions principales : example/lib/initialization.dart et example/lib/home/home_page.dart**

## initialize({ login, password } success, error)

Le SDK doit être initialisé en appelant la fonction **initialize** (login, mot de passe)

Lors de la première utilisation, le SDK va se connecter au serveur d’authentification et obtenir les noms des paquets applicatifs autorisés. Tant que cette première vérification n’aura pas été effectuée, le SDK sera inutilisable.

#### Objet retourné
Dans les deux cas, une trace JSON est renvoyée. Le JSON est un objet RTRLogs :

	
    {
	    RTRLogs :[RTRLog],
	    Severity : int    
    }
    
    RTRLog
    {  
	    type : case info=0,warning=1,error=2,fatal=3
	    caller:String,
	    shortDescription: String,
	    longDescription: String
    }

La log est de type cumulative, il faut tester le champ **severity** afin de connaitre l’erreur la plus grave. 

#### Classement des typologies d'erreurs:

- 0: information: permet de suivre le traitement 
 - 1: warning: une    anomalie a été rencontrée mais n'empêche le déroulement de l'analyse.    Il s'agit majoritairement du passage en mode offline de    l'application.
- 2: error: une erreur est survenue pendant le traitement    d'une analyse (capture, ocr, validation). Cela ne compromet les    analyses suivantes, ni la poursuite de la capture. 
 - 3: fatal: erreur  bloquante. Le SDK ne peut fonctionner. Majoritairement, une mauvaise    authentification ou initialisation impossible de l'analyse.

Toute erreur de type 3 (fatale) rend l’usage de l’analyse inopérante. Le tableau de RTRLog est informatif.


**Exemples :**
``` dart
	try {
      final res = await Capture().onInitialize(login, password);
      if (res != null) {
        (...)
      }
    } on PlatformException catch (err) {
      (...)
    }
```

_Quand l’initialisation est OK :_


```json
       {  
		    "RTRLogs":[  
			    {  
			    "caller":"<EverialRT.ConfigurationManager: 0x102f129c0>",  
			    "longDescription":"remote config loaded for: bundle",  
			    "type":0,  
			    "shortDescription":"remote config loaded"  
			    },  
			    {  
			    "caller":"<EverialRT.ConfigurationManager: 0x102f129c0>",  
			    "longDescription":"remote config loaded for: schema",  
			    "type":0,  
			    "shortDescription":"remote config loaded"  
			    }  
		    ],  
		    "severityFlag":0  
    }
```
_Exemple de retour avec connection internet coupée :_
```json
    {  
    "RTRLogs":[  
	    {  
	    "caller":"<EverialRT.ConfigurationManager: 0x1056052c0>",  
	    "longDescription":"Error while authenticating: Optional(Error Domain=NSURLErrorDomain Code=-1009 \"The Internet connection appears to be offline.\" UserInfo={NSUnderlyingError=0x282b57870 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 \"(null)\" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https:\/\/auth.devlab.everial.com\/auth\/realms\/quota\/protocol\/openid-connect\/token, NSErrorFailingURLKey=https:\/\/auth.devlab.everial.com\/auth\/realms\/quota\/protocol\/openid-connect\/token, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=50, NSLocalizedDescription=The Internet connection appears to be offline.})",  
	    "type":2,  
	    "shortDescription":"Error while authenticating"  
	    }  
    ],  
    "severityFlag":2  
    }
```
*Autre exemple, lors du premier usage, l’initialisation peut renvoyer ce genre de réponse, si la connection internet n’est pas activée*
```json

    {  
    "caller":"<EverialRT.RTR: 0x133e03af0>:processHiRes",  
    "longDescription":"User or SDK not granted for this application",  
    "type":3,  
    "shortDescription":"User or SDK not granted"  
    }
```

  ## process({}, success, error)
  
 La fonction instancie une vue de capture dans la vue parente et démarre le scan. **Il est possible d'interrompre l'analyse en touchant la vue.**

L'analyse est relançable sans repasser par la méthode d'initialisation.
Lorsque la capture est achevée (match ou interruption) , la vue est supprimée de la vue parente.

**Exemple:**
```dart
dynamic result = await Capture().onProcess();
```
> result contient des données : SUCCESS: JSON: données extraites de la pièces"
> result contient une erreur / try-catch : ERROR: message d'erreur

### Réponse
La réponse a pour content-type : application/json; charset=utf-8
La structure du champ validation est la suivante.

La structure de la réponse est hiérarchiquement:
- *subscriber* -> cela correspond au niveau inter-document, c'est la notion de dossier
- un tableau de *documents* -> un document: CNI, JUDO
- pour chaque document, un tableau de *pages* -> les pages de ce document, un recto/verso par exemple

Chaque niveau est  structuré: 
result -> "errors", "descriptions", "value" + "fields" au niveau *page*



**En cas d'échec, la log d'erreur  restitue l'image dans l'entrée de tableau suivante:**
```json
{  
    "caller":"analyse",  
    "longDescription":"base64 image url encodée",  
    "type":0,  
    "shortDescription":"Document"  
    }
```

### Utilisation pour la manipulation de la réponse
la réponse est de la forme 
```json
{value:"JSON_DOC_STRING"}
```
Il faut donc procéder de la sorte
```dart
try {
  dynamic result = await Capture().onProcess();
  // Cette fonction est équivalente à : json.decode(docToFormat)['value'];
  dynamic resValue = _getDocValue(result);

  if (resValue != '') {
  (...)
  } else {
   throw Exception("Nothing to add.");
  }
} on PlatformException catch (err) {
  (...)
}
```

 ## consolidate({array of json documents}, success, error)
Cette méthode permet d'effectuer les contrôles de cohérences entre plusieurs documents.

```dart
await Capture().onConsolidate({ documents: docs })
```

**IOS**
Si le tableau fournit est vide, l'application compare les documents qu'elle a déjà analysés c'est à dire  à partir de la liste constituée par les analyses précédentes. La constitution de cette liste est de type LIFO: chaque document de même type  vient écraser le précédent.
Cependant dans le cas d'une gestion des documents côté applicatif client, il est possible de stocker les documents analysés dans l'application cliente, puis de demander une validation en refournissant ceux qui sont appropriés. 
Dans ce cas, les json de documents doivent être fournis dans un tableau.
> **Attention**: dans ce cas, la liste du moteur d'analyse est remplacée par
> celle refournie.
> 

**ANDROID**
Il est de la responsabilité de l'applicatif de gérer les documents analysés. Le plugin ne conserve aucune trace des analyses précédentes.
Les documents analysés doivent être stockés  dans l'application cliente qui peut demander une consolidation sur ceux qu'elle estime appropriés. Les documents sont à refournir dans la chaine JSON telle que reçue de l'analyse et stockés dans un tableau

### Réponse
La réponse a pour content-type : application/json; charset=utf-8
La structure du champ validation est la même que pour analyze des contrôles supplémentaire sont présent dans la clé "**subscriber**"

## analyze({ fileuri: file }, success, error)

Cette méthode permet de réaliser l'analyse d'un document sans appeler la fonction de capture. Dans ce cas, on suppose que l'applicatif prend en charge la collecte du fichier. Il faut fournir le chemin du fichier sur le device. La réponse est identique à "**process**"
Les types acceptés sont image/* et application/pdf.
Dans le cas d'un PDF seule la première page est analysée

```dart
await Capture().onAnalyze({ fileuri: file })
```

**Android**
La chaine attendue est de type Uri (android.net.Uri: Uri.toString())

**iOS**:
La chaine attendu est de type URL (url.absolutestring())

## selfie({ baseimage: extractedPhoto }, success, error)

```dart
    await Capture().onSelfie({ baseimage: extractedPhoto })
```

Cette méthode effectue 2 contrôles:
* comparaison de l'image fournie en paramètre "imgBase" (image base 64) avec le visage de l'utilisateur
* test de preuve de vie
 
Lors de l'appel de cette fonction la caméra est déclenchée et l'utilisateur est invité à placer son visage dans un cadre quelques secondes.
Sur Ios, l'expérience est légèrement différente avec l'affichage de landmarks.
L'analyse ne prend que quelques secondes, sans aucun scénario pour l'utilisateur.

La réponse est de type json avec une seule clé:
```json
     {"valid": "true" | "false"}
```
> Note: L'application enregistre toutes les photos liées aux documents précédemment enregistrés.

### SSL Pinning

Un contrôle de type "Man in the middle" peut être effectué par le plugin.
Pour cela, Everial active le paramètre au niveau de votre user.
```json
 {"RTRContext" : "2.3.0:capsule-dev", "caller" : "com.everial.rtr.Configuration", "longDescription" : "SSL pinning enabled", "shortDescription" : "SSL pinning enabled", "type" : 0}
 ````

Si le contrôle est activé, le plugin au moment de son initialisation s'assurera que la clé publique du certificat présenté est bien celle d'Everial.

L'initialisation renvoie alors une erreur.

Cette erreur n'est pas bloquante et il appartient à l'applicatif de décider de stopper ou non la poursuite de l'application.

```json
{"RTRContext" : "2.3.0:capsule-dev", "caller" : "com.everial.rtr.Configuration", "longDescription" : "user=capsule-dev,pub=CBSA08mY+VfrZrjVgkDDpFbz6JBBavdW6TPgbCYde58=, received certificate: Certificate pinning failure!\n  Peer certificate chain:\n    sha256/tJqnS+/9e9HeWUtNxnTbdpfA0oCOC9ka30l8fGiE4p0=: CN=k8s.doclab.fr\n    sha256/Ns1wdAOg3MUrrOICXWYlMXelaJL9M6n/Y9nuYfZawsM=: C=NZ,ST=Auckland,L=Auckland,O=XK72 Ltd,OU=https://charlesproxy.com/ssl,CN=Charles Proxy CA (4 Nov 2021\\, MacBook-Pro-de-Sebastien.local)\n  Pinned certificates for log.k8s.doclab.fr:\n    sha256/CBSA08mY+VfrZrjVgkDDpFbz6JBBavdW6TPgbCYde58=", "shortDescription" : "Error while pinning certificate", "type" : 2}
```

>*NOTE*

> Pour des raisons de réactivité, le plugin n'embarque aucune valeur de clé. La clé est téléchargée depuis la configuration. Elle n'est pas falsifiable dans le transport (HMAC256).
>
>Ainsi Everial peut mettre à jour la clé "à chaud" sans relivrer le plugin en cas par exemple de mise à jour des certificats.

>En cas d'échec de vérification, une alerte est générée sur les serveurs Everial. Everial peut dans ce cas intervenir et soit mettre à jour la clé en cas de renouvellement automatique du certificat ou avertir le client s'il s'agit d'une tentative de fraude.