# Migration to OzCapsula

This guide describes the migration to the latest OzCapsula architecture: the approach where you use an encrypted container to securely transmit data between frontend and backend.

## Component Version Requirements <a href="#id-2.-minimal-supported-versions" id="id-2.-minimal-supported-versions"></a>

Before starting the migration, ensure that all components are updated to the minimum required versions:

* **API:** 6.4.2.
* **Web SDK:** 1.9.2.
* **Native SDKs (iOS / Android):** 8.22.

{% hint style="danger" %}
Older versions are **not compatible** with the new capture and analysis flow.
{% endhint %}

{% hint style="info" %}
For best results, migrate all components simultaneously and avoid partial upgrades.
{% endhint %}

## Authentication Changes <a href="#id-3.-authentication-changes" id="id-3.-authentication-changes"></a>

All capture sessions now require a `session_token` issued by the backend.

* The token must be obtained **before** starting video capture.
* It is tied to the current session and the specific container.
* The token has a limited lifetime.

### **Migration actions**

* Implement a backend call to obtain `session_token`.
* Pass the token explicitly when creating the capture screen.

## Flow Changes <a href="#id-4.-high-level-flow-changes" id="id-4.-high-level-flow-changes"></a>

### Before Container (Legacy Flow) <a href="#before-migration-legacy-flow" id="before-migration-legacy-flow"></a>

1. You launch media capture.
2. Media is captured.
3. Media along with required data is sent to Oz API (using your backend as intermediate if needed).

### With Container (New Flow) <a href="#after-migration-new-flow" id="after-migration-new-flow"></a>

1. You request `session_token` from backend.
2. You put additional data like metadata (if needed) into container and launch video capture with `session_token`.
3. SDK captures media, packages it into container, and returns an encrypted file.
4. The encrypted file is sent to Oz API (using your backend as intermediate if needed).

### Migration actions

#### API

* Upgrade to v**6.4.2** or higher.
* Switch `Content-Type` of data you send to `application/octet-stream`.
* For Instant API, obtain private and public keys as described [here](https://doc.ozforensics.com/oz-knowledge/guides/developer-guide/api/oz-api/ozcapsula-data-container).

#### Web SDK

* Upgrade to v**1.9.2** or higher.
* Ensure backend supports `session_token`.
* In the configuration file, set `use_wasm_container` to true and `api_use_session_token` to `api` or `client` (please refer to [this article](https://doc.ozforensics.com/oz-knowledge/guides/sdk/oz-liveness-websdk/using-ozcapsula-data-container-in-web-sdk#api_use_session_token-client) for details).
* Update initialization to pass token explicitly.
* If you use the `capture` architecture type, ensure you receive and send to your backend and then to us a **blob** object (`application/octet-stream`).

#### Mobile SDKs

* Upgrade to v**8.22** or higher.
* Ensure backend supports `session_token`.
* Implement new interfaces as described below.

{% hint style="info" %}
Please note: do not use "\_" (underscore) in file names for the container flow.
{% endhint %}

**Android**

{% stepper %}
{% step %}
Launching Capture Screen

{% tabs %}
{% tab title="Container flow" %}

```kotlin
val sessionToken: String = getSessionToken()
val captureRequest = CaptureRequest(
    listOf(
        AnalysisProfile(
            Analysis.Type.QUALITY,
            listOf(MediaRequest.ActionMedia(OzAction.Blank)),
        )
    )
)
val intent = OzLivenessSDK.createMediaCaptureScreen(captureRequest, sessionToken)
startActivityForResult(intent, REQUEST_LIVENESS_CONTAINER)
```

{% endtab %}

{% tab title="Old flow" %}

```kotlin
val intent = OzLivenessSDK.createStartIntent(listOf(OzAction.Blank))
startActivityForResult(intent, REQUEST_LIVENESS_MEDIA)
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}
Delegate

{% tabs %}
{% tab title="Container flow" %}

```kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_LIVENESS_CONTAINER) {
        when (resultCode) {
            OzLivenessResultCode.SUCCESS -> runAnalysis(OzLivenessSDK.getContainerFromIntent(data))
            OzLivenessResultCode.USER_CLOSED_LIVENESS -> { /* user closed the screen */ }
            else -> {
                val errorMessage = OzLivenessSDK.getErrorFromIntent(data)
                /* show error */
            }
        }
    }
}
```

{% endtab %}

{% tab title="Old flow" %}

```kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_LIVENESS_MEDIA) {
        when (resultCode) {
            OzLivenessResultCode.SUCCESS -> runAnalysis(OzLivenessSDK.getResultFromIntent(data))
            OzLivenessResultCode.USER_CLOSED_LIVENESS -> { /* user closed the screen */ }
            else -> {
                val errorMessage = OzLivenessSDK.getErrorFromIntent(data)
                /* show error */
            }
        }
    }
}
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}
Launching Analysis

{% tabs %}
{% tab title="Container flow" %}

```kotlin
private fun runAnalysis(container: DataContainer?) {
    if (container == null) return

    AnalysisRequest.Builder()
        .addContainer(container)
        .build()
        .run(
            { result ->
                val isSuccess = result.analysisResults.all { it.resolution == Resolution.SUCCESS }
            },
            { /* show error */ },
            { /* update status */ },
        )
}
```

{% endtab %}

{% tab title="Old flow" %}

```kotlin
private fun runAnalysis(media: List<OzAbstractMedia>?) {
    if (media.isNullOrEmpty()) return
    AnalysisRequest.Builder()
        .addAnalysis(Analysis(Analysis.Type.QUALITY, Analysis.Mode.SERVER_BASED, media))
        .build()
        .run(
            { result ->
                val isSuccess = result.analysisResults.all { it.resolution == Resolution.SUCCESS }
            },
            { /* show error */ },
            { /* update status */ },
        )
}
```

{% endtab %}
{% endtabs %}

{% endstep %}
{% endstepper %}

**iOS**

{% stepper %}
{% step %}
Launching Capture Screen

{% tabs %}
{% tab title="Container flow" %}

```swift
getSessionToken() { sessionToken in
            DispatchQueue.main.async {
                do {
                    let action:OZVerificationMovement = .selfie
                    let mediaRequest = MediaRequest.action(action)
                    let profile = AnalysisProfile(mediaList: [mediaRequest],
                                                  type: .quality,
                                                  params: [:] )
                    let request = CaptureRequest(analysisProfileList: [profile], cameraPosition: .front)
                    let ozLivenessVC = try OZSDK.createMediaCaptureScreen(self, request, sessionToken: sessionToken)
                    self.present(ozLivenessVC, animated: true)
                } catch let error {
                    print(error.localizedDescription)
                }
            }
        }
```

{% endtab %}

{% tab title="Old flow" %}

```swift
do {
    let ozLivenessVC : UIViewController = try OZSDK.createVerificationVCWithDelegate(self, actions: .selfie)   
    self.present(ozLivenessVC, animated: true)
} catch let error {
    print(error.localizedDescription)
}
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}
Delegate

{% tabs %}
{% tab title="Container flow" %}

```swift
extension ViewController: LivenessDelegate {
    func onResult(container: DataContainer) {
    }
    
    func onError(status: OZVerificationStatus?) {
    }
}
```

{% endtab %}

{% tab title="Old flow" %}

```swift
extension ViewController: OZLivenessDelegate {
    func onOZLivenessResult(results: [OZMedia]) {
    }
  
    func onError(status: OZVerificationStatus?) {
    }
}
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}
Launching Analysis

{% tabs %}
{% tab title="Container flow" %}

```swift
func onResult(container: DataContainer) {
  let analysisRequest = AnalysisRequestBuilder()
  analysisRequest.addContainer(container)
  analysisRequest.run(
            statusHandler: { status in
            },
            errorHandler: { error in
            }
        ) { result in
            
        }
}
```

{% endtab %}

{% tab title="Old flow" %}

```swift
func onResult(results: [OZMedia]) {
        let analysisRequest = AnalysisRequestBuilder()
        let analysis = Analysis(media: results,
                                type: .quality,
                                mode: .serverBased,
                                params: nil)
        analysisRequest.addAnalysis(analysis)
        analysisRequest.run { status in
        } errorHandler: { error in
        } completionHandler: { results in
            
        }
    }
```

{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

## Migration Checklist

* [x] API is updated to 6.4.2 or newer, with `session_token` supported.
  * [x] `Content-Type`  is changed to `application/octet-stream`.
  * [x] For Instant API, private and public keys are obtained.
* [x] Mobile SDKs are updated to 8.22 or newer.
  * [x] Added the `session_token` backend call.
  * [x] New public interface is implemented: `createMediaCaptureScreen` with `CaptureRequest`.
* [x] Web SDK is updated to 1.9.2 or newer.
  * [x] `use_wasm_container` is set to `true`.
  * [x] `api_use_session_token` is set to `api` or `client`.
  * [x] If `api_use_session_token` = `client`, `session_token` is requested before session and passed in `open.options()`.
