Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion .changeset/stable-device-info-and-ota.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
"@microblink/blinkid": patch
"@microblink/blinkcard": patch
"@microblink/blinkid-verify": patch
---

Fixed slow handling of device info and OTA URLs.
5 changes: 0 additions & 5 deletions .changeset/verify-tooltip-delay.md

This file was deleted.

18 changes: 17 additions & 1 deletion apps/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,23 @@ Replace `your-license-key` with the actual license key you obtained.

**Note:** The `camera-manager` and `camera-selection` examples do not require a license key.

### 4. Run an Example Application
### 4. Set up BlinkID Verify

The [simple](./blinkid-verify-simple/) and [advanced](./blinkid-verify-advanced-setup/) BlinkID Verify examples leave `verifyApiBaseUrl` unset, so the SDK posts each capture to this page's origin at `/api/v3/verify`. The SDK sends no API key. While `pnpm dev` is running, the dev server forwards that route to your Verify host and adds `Authorization`.

In the same `.env.local` file, set:

```env
VITE_LICENCE_KEY=
VERIFY_API_URL=https://your-verify-api.example.com
VERIFY_API_KEY=
```

`VERIFY_API_URL` is the https origin. The forwarded request always uses `/api/v3/verify` on that host. `VERIFY_API_KEY` is optional. When it is set, that string is the `Authorization` header value. Keep the `VERIFY_API_` prefix. Vite inlines only names that start with `VITE_`, so these values stay on the dev server.

The [custom UI](./blinkid-verify-custom-ui/) example stops after capture, so it needs only the license key.

### 5. Run an Example Application

Navigate to the directory of the example you want to run and start the development server.

Expand Down
7 changes: 0 additions & 7 deletions apps/examples/blinkid-advanced-setup/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,6 @@ export const App: Component = () => {

console.log("creating new session");

/* Optionally use one of our presets for the scanning session settings
const scanningSettings = await blinkIdCore.buildDocumentVideoSettings({
scenario: "general",
captureEnvironment: "hand-held",
});
*/

/*
* Initialize the session with the default settings.
* For additional configuration look at the BlinkIdSessionSettings type.
Expand Down
7 changes: 7 additions & 0 deletions apps/examples/blinkid-verify-advanced-setup/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@


VITE_LICENCE_KEY=PLACE_YOUR_LICENCE_KEY_HERE

# Server-side only. Vite reads these and does not inline them into the page.
# The example posts to /api/v3/verify on this dev server.
# VERIFY_API_URL is the https origin. The forwarded path is always /api/v3/verify on that host.
# VERIFY_API_KEY is optional. When set, that string is the Authorization header value.
VERIFY_API_URL=https://your-verify-api.example.com
VERIFY_API_KEY=
10 changes: 6 additions & 4 deletions apps/examples/blinkid-verify-advanced-setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ The application performs the following actions:

5. **Renders the UI**: The application creates and mounts the camera and feedback UI components to the DOM, rendered as a portal outside the root element. It provides an option to show or hide an onboarding guide for the user.

6. **Handles Results**: Upon successful scanning, the application displays the captured front, back, and barcode frame images. It also includes an optional debug overlay that shows live feedback stabilizer scores as a bar chart, the current UX state key, and the processing status.
6. **Handles Results**: Upon successful scanning, the camera UI closes immediately. The capture resolver loads the session result and submits it with `verifyCaptureResult()`. That request goes to this page's origin at `/api/v3/verify`, and the dev server forwards it. The application then displays the captured front, back, and barcode frame images and the Verify API response (the verdict and full JSON, or the error message with status and body when the request fails). It also includes an optional debug overlay that shows live feedback stabilizer scores as a bar chart, the current UX state key, and the processing status.

7. **Cleanup**: When the UI is dismounted, the application terminates the BlinkID Verify Core instance to free up resources.
7. **Cleanup**: When the UI is dismounted before a capture result, the application terminates the BlinkID Verify Core instance. After a successful scan, the core stays alive until the Verify API request settles, then terminates.

## Key Features Demonstrated

- **Modular Integration**: Shows how to import and use different modules of the BlinkID Verify SDK (`blinkid-verify-core`, `blinkid-verify-ux-manager`, `camera-manager`) independently.
- **Manual Control**: Demonstrates how to manually control the initialization, scanning process, and UI components.
- **Custom UI**: Provides an example of how to integrate the SDK's UI components into a custom application layout.
- **Event Handling**: Shows how to subscribe to events from the `CameraManager` and `BlinkIdVerifyUxManager` to create a responsive user experience.
- **Result Handling**: Illustrates how to receive and display scanning results, including captured document frame images.
- **Result Handling**: Illustrates how to receive and display scanning results, including captured document frame images and the Verify API response from `verifyCaptureResult()`.

## How to Run

For detailed instructions on how to install dependencies and run this example, please refer to the [main README file](./../README.md).
Copy `.env.example` to `.env.local`. Set `VITE_LICENCE_KEY`, and set `VERIFY_API_URL` to the https origin of your Verify API. `VERIFY_API_KEY` is optional. When set, it is sent as the `Authorization` value. The example leaves `verifyApiBaseUrl` unset, so the SDK posts to `/api/v3/verify` on this dev server.

For install and start commands, see the [examples README](./../README.md).
153 changes: 113 additions & 40 deletions apps/examples/blinkid-verify-advanced-setup/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@

import {
BlinkIdVerifyProcessResult,
BlinkIdVerifyScanningResult,
loadBlinkIdVerifyCore,
type BlinkIdVerifySessionResult,
type VerifyApiError,
type VerifyApiResult,
} from "@microblink/blinkid-verify-core";
import {
BlinkIdVerifyUxManager,
createBlinkIdVerifyFeedbackUi,
createBlinkIdVerifyUxManager,
} from "@microblink/blinkid-verify-ux-manager";
import { CameraManager, createCameraManagerUi } from "@microblink/camera-manager";
import { Component, createEffect, createMemo, createSignal, onMount, Show } from "solid-js";

/** Debug info will be displayed in the UI. */
const SHOW_DEBUG = true;
import { Component, createEffect, createMemo, createSignal, Match, onMount, Show, Switch } from "solid-js";

/**
* If you are using a portal, you can set this to true. Portal is a way to render the UI outside of the root element.
Expand All @@ -25,30 +24,30 @@ const SHOW_DEBUG = true;
const USE_PORTAL = true;

/** If the onboarding guide should be shown. */
const SHOW_ONBOARDING = false;
const SHOW_ONBOARDING = true;

/** This is the target node for the UI. */
const targetNode = !USE_PORTAL ? document.getElementById("root")! : undefined;

/** Result of the Verify API request started after capture, or pending while that request is in flight. */
type VerifyOutcome =
| { status: "pending" }
| { status: "success"; result: VerifyApiResult }
| { status: "error"; error: VerifyApiError };

/** This is the main component of the application. */
export const App: Component = () => {
const [result, setResult] = createSignal<BlinkIdVerifyScanningResult>();
const [result, setResult] = createSignal<BlinkIdVerifySessionResult>();
const [verifyOutcome, setVerifyOutcome] = createSignal<VerifyOutcome>();
const [blinkIdVerifyUxManager, setBlinkIdVerifyUxManager] = createSignal<BlinkIdVerifyUxManager>();
const [loadState, setLoadState] = createSignal<"not-loaded" | "loading" | "ready">("not-loaded");

/**
* This function removes the images from the result object. This is done only so we don't display raw images data in
* the UI.
*/
const resultWithoutImages = () => {
const resultCopy = structuredClone(result());

return resultCopy;
};
let captureGeneration = 0;

async function init() {
const generation = ++captureGeneration;
setLoadState("loading");
setResult(undefined);
setVerifyOutcome(undefined);

/*
* We first initialize the direct API. This loads the WASM module and initializes the engine.
Expand All @@ -59,8 +58,6 @@ export const App: Component = () => {
licenseKey: import.meta.env.VITE_LICENCE_KEY,
});

console.log("creating new session");

/*
* Initialize the session with the default settings.
* For additional configuration look at the BlinkIdVerifySessionSettings type.
Expand All @@ -74,13 +71,17 @@ export const App: Component = () => {
const cameraManager = new CameraManager();

/*
* Create the UX manager.
* Create the consent gate. RequireConsent does not return a manager until the user accepts.
*/
const uxManager = await createBlinkIdVerifyUxManager(cameraManager, session);
// set the timeout duration to null to disable the timeout.
uxManager.setTimeoutDuration(null);

setBlinkIdVerifyUxManager(uxManager);
const consentGate = await createBlinkIdVerifyUxManager(cameraManager, session, {
consentUxConfig: {
consentMode: "RequireConsent",
consent: {
userId: "example-user",
durationDays: 365,
},
},
});

/*
* This creates the UI and attaches it to the DOM.
Expand All @@ -92,30 +93,70 @@ export const App: Component = () => {
});

/*
* This callback is called when the UI is dismounted.
* This is useful if you want to perform some actions when the UI is dismounted.
* A finished capture closes the camera immediately, but the core has to stay alive until the Verify API request
* settles. Terminating it here would cancel that request.
*/
let preserveCoreForResult = false;
cameraUi.addOnDismountCallback(() => {
void blinkIdVerifyCore.terminate();
if (!preserveCoreForResult) {
void blinkIdVerifyCore.terminate();
}
setBlinkIdVerifyUxManager(undefined);
setLoadState("not-loaded");
});

const uxManager = await consentGate.consentUiResponse(cameraUi);
if (!uxManager) {
await blinkIdVerifyCore.terminate();
setLoadState("not-loaded");
return;
}

// set the timeout duration to null to disable the timeout.
uxManager.setTimeoutDuration(null);

setBlinkIdVerifyUxManager(uxManager);

/*
* This callback is called when the result is ready.
* This is useful if you want to perform some actions when the result is ready.
* Capture is already complete when this runs, after the success animation. Close the camera right away and let the
* Verify API request finish in the background.
*/
uxManager.addOnResultCallback((result) => {
setResult(result);
uxManager.addOnCaptureCompletedCallback(async (resolver) => {
preserveCoreForResult = true;
setVerifyOutcome({ status: "pending" });
cameraUi.dismount();

const captureResultPromise = resolver.getCaptureResult().then(
(captureResult) => {
if (generation === captureGeneration) {
setResult(captureResult);
}
},
(error: unknown) => {
console.error("Failed to load capture result", error);
},
);

try {
const outcome = await resolver.verifyCaptureResult();
if (generation !== captureGeneration) {
return;
}
setVerifyOutcome(
outcome.ok ? { status: "success", result: outcome.result } : { status: "error", error: outcome.error },
);
} finally {
await captureResultPromise;
void blinkIdVerifyCore.terminate();
}
});

/*
* This callback is called when the frame is processed.
* This is useful if you want to perform some actions on certain results.
*/
uxManager.addOnFrameProcessCallback((frameProcessResult: BlinkIdVerifyProcessResult) => {
console.log("frame processed", frameProcessResult);
//console.log("frame processed", frameProcessResult);
});

/*
Expand Down Expand Up @@ -186,11 +227,12 @@ export const App: Component = () => {

{/* Results */}
<Show when={result()}>{(trimmedResult) => <DisplayBlinkIdVerifyResult result={trimmedResult()} />}</Show>
<Show when={verifyOutcome()}>{(outcome) => <DisplayVerifyApiResult outcome={outcome()} />}</Show>
</div>
);
};

function DisplayBlinkIdVerifyResult(props: { result: BlinkIdVerifyScanningResult }) {
function DisplayBlinkIdVerifyResult(props: { result: BlinkIdVerifySessionResult }) {
createEffect(() => {
console.log(props.result);
});
Expand All @@ -214,15 +256,46 @@ function DisplayBlinkIdVerifyResult(props: { result: BlinkIdVerifyScanningResult

return (
<div>
<Show when={props.result.frontFrame}>
<CreateImageSection title="Front Frame" bytes={props.result.frontFrame!.jpegBytes} />
<Show when={props.result.serializedPayload.imageFirstSide}>
{(image) => <CreateImageSection title="First Side" bytes={image().jpegBytes} />}
</Show>
<Show when={props.result.backFrame}>
<CreateImageSection title="Back Frame" bytes={props.result.backFrame!.jpegBytes} />
<Show when={props.result.serializedPayload.imageSecondSide}>
{(image) => <CreateImageSection title="Second Side" bytes={image().jpegBytes} />}
</Show>
<Show when={props.result.barcodeFrame}>
<CreateImageSection title="Barcode Frame" bytes={props.result.barcodeFrame!.jpegBytes} />
<Show when={props.result.serializedPayload.imageBarcode}>
{(image) => <CreateImageSection title="Barcode" bytes={image().jpegBytes} />}
</Show>
</div>
);
}

function DisplayVerifyApiResult(props: { outcome: VerifyOutcome }) {
return (
<Switch>
<Match when={props.outcome.status === "pending"}>
<p>Submitting to the Verify API…</p>
</Match>
<Match when={props.outcome.status === "success" ? props.outcome.result : undefined}>
{(result) => (
<div>
<h2>{result().verification.verdict}</h2>
<pre>{JSON.stringify(result(), null, 2)}</pre>
</div>
)}
</Match>
<Match when={props.outcome.status === "error" ? props.outcome.error : undefined}>
{(error) => (
<div>
<p>{error().message}</p>
<Show when={error().status !== undefined}>
<p>Status: {error().status}</p>
</Show>
<Show when={error().body !== undefined}>
<pre>{JSON.stringify(error().body, null, 2)}</pre>
</Show>
</div>
)}
</Match>
</Switch>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ body {
:root {
background-color: #282c34;
color: white;
min-height: 300vh;
min-height: 100vh;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
Expand Down
11 changes: 11 additions & 0 deletions apps/examples/blinkid-verify-advanced-setup/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** Copyright (c) 2026 Microblink Ltd. All rights reserved. */

/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_LICENCE_KEY: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
2 changes: 2 additions & 0 deletions apps/examples/blinkid-verify-advanced-setup/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dns from "dns";

import { moveResources } from "@microblink/repo-utils";
import { verifyApiDevProxy } from "@microblink/repo-utils/vite/verifyApiDevProxy.mts";
import { ServerOptions, defineConfig } from "vite";
import mkcert from "vite-plugin-mkcert";
import { qrcode } from "vite-plugin-qrcode";
Expand All @@ -29,6 +30,7 @@ export default defineConfig((config) => {
target: "es2022",
},
plugins: [
verifyApiDevProxy(),
// symlink wasm resources to public/resources
{
name: "move-resources",
Expand Down
2 changes: 2 additions & 0 deletions apps/examples/blinkid-verify-custom-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This plain TypeScript application supplies its own capture UI. It imports the UX Manager and Camera Manager from their `/core` entrypoints, so it does not need the optional UI peer dependencies.

The example captures a document and stops there. It does not call the Verify API, so `VERIFY_API_URL` is not used.

The example starts loading Core and its SDK resources as soon as the page opens. After loading, **Start scanning** creates a new capture session on the existing Core instance. Completing or stopping a capture deletes only that session, allowing another session to start without downloading or initializing the SDK again. **Destroy SDK** demonstrates full teardown; **Load SDK** creates a fresh Core instance afterward.

`BlinkIdVerifyCustomUiExample` keeps one Core instance and Camera Manager for the SDK lifetime, while each capture owns a new scanning session, UX Manager, and subscriptions. Session cleanup uses one shared teardown operation to remove callbacks, mark camera interruption as user initiated, destroy the UX Manager, stop the camera stream, and delete the session. A run identifier prevents late asynchronous work from an obsolete capture from changing the current interface. Captured result images remain visible after successful cleanup and are revoked before the next run.
Expand Down
Loading
Loading