Web setup

Install the @folio/sdk npm package, load its WebAssembly module, create the runtime and render the inquiry with FolioInquiry.

Preview

This page describes a pre-release version of the SDK. Names, versions and APIs on this page can change before the release.

On the web, the Folio SDK is the npm package @folio/sdk. It holds the SDK core as a WebAssembly module, the TypeScript types and classes generated from it (FolioSdk, the stores, their actions and state), the browser platform layer, and the inquiry UI as React components. The steps that are the same on every platform are in Get started.

Two ways to run an inquiry on a web page exist:

IntegrationWhat your page doesPage
npm package @folio/sdkBundles the SDK, creates a FolioSdk runtime in the page and renders the FolioInquiry React component.This page
Script tag embed.v<major>.jsLoads one module script from Folio and calls window.FolioInquiry.mount. No npm install, no bundler, no React.Inquiry embed

Requirements

  • React 18 or 19: react and react-dom in a version that matches ^18.0.0 || ^19.0.0.
  • A browser with WebAssembly support.
  • A bundler that serves ES modules. The package ships ES modules only.
  • With TypeScript, version 5.2 or later with ESNext.Disposable (or ESNext) in lib of your tsconfig.json: the type declarations use Disposable and AsyncDisposable.
  • A way to forward the /api path and /.well-known/jwks.json of your page's origin to Folio (see Route API requests).

Install

Folio gives you access to the package. Install it from the URL that Folio provides:

npm install <web-package-url>

Peer dependencies:

PackageVersionRequired
react^18.0.0 || ^19.0.0Yes
react-dom^18.0.0 || ^19.0.0Yes

The package brings its other dependencies itself: the @plexui/ui component library that the inquiry UI is built on, lottie-react for animations, qrcode.react for the QR code of the device handoff, pdfjs-dist 6.x, which the platform layer loads when the SDK renders a PDF page, and the web component of the capture engine of the selfie step.

The selfie step runs that engine component in its LIVENESS and PASSIVE states, in active or passive liveness mode as the inquiry asks. The engine component is a dependency of the package at an exact version, so there is nothing to install for it. The inquiry UI loads it lazily, when that step opens, so it stays out of your initial bundle. See Selfie capture for the selfie step.

The selfie step loads the engine's worker at run time from the engine's own content delivery origin, https://wasm.regulaforensics.com; this release reads it from https://wasm.regulaforensics.com/face/release/8.2/6b01d803-872e3641/. It downloads Liveness.worker.js from there and starts it as a worker from a blob: URL, and the worker downloads Liveness.wasm and Liveness.data from the same place. A page with a strict Content-Security-Policy must allow https://wasm.regulaforensics.com in connect-src, blob: in worker-src and 'wasm-unsafe-eval' in script-src. The engine can load these files from a host of your own through its workerPath setting; the SDK does not set it in this release.

A release published again

Folio can publish a release again under the same version, at a new commit. Your npm lockfile records the commit the package URL resolved to, and npm keeps installing that commit. To move to the new one, remove the package and install it again from the same URL:

npm uninstall @folio/sdk
npm install <web-package-url>

npm install also rewrites the dependency in package.json to npm's shorthand form, for example github:<owner>/<repository>#v<version> for a GitHub URL. The shorthand is equivalent to the URL you installed from. Commit the updated package.json and lockfile.

Entry points

The package has these entry points. Import only from them; other files inside the package are not part of its interface.

Import pathHolds
@folio/sdkThe generated SDK API: FolioSdk, FolioSdkConfig, DevelopmentConfig, ServiceEnvironment, SdkService, LogLevel, SdkBuildInfo, the stores of FolioSdk (AuthStore, SessionStore, VaultStore, InquiryStore, MrtdStore, LogsStore, FolioDocumentListStore, FolioDocumentStore), their actions, state and error types, FolioDocument and its parts, the logging classes InteractionRecorder and SdkLogger, InquiryEntry, InquiryLifecycle, IdentityRecordMapper, RecordContent, VerifiedIdentity, TextKey. The WebAssembly loading API: initializeFolioWasm, FolioWasmSource, WebApplication, loadFolioSDKWasm, FolioSDKWasmLoadOptions, FolioSDKLoadedWasm, FolioSDKWasmManifest, FolioSDKWasmArtifact. The build info constant folioSDKBuildInfo. The error class FolioSDKError and the code table FolioSDKErrorCode. registerPageProxiedApiOrigin, see Route API requests. Everything in @folio/sdk/runtime.
@folio/sdk/runtimeThe runtime support the generated classes run on. @folio/sdk re-exports it, so an app does not need this path.
@folio/sdk/providerFolioSDKProvider and useFolioSDKRuntime, the React provider and hook for a FolioSdk runtime, and one provider and hook per host interface of FolioSdk: AuthHostProvider and useAuthHost, DiagnosticsHostProvider and useDiagnosticsHost, InquiryHostProvider and useInquiryHost, MrtdHostProvider and useMrtdHost, SessionHostProvider and useSessionHost, VaultHostProvider and useVaultHost, FolioDocumentListHostProvider and useFolioDocumentListHost, FolioDocumentHostProvider and useFolioDocumentHost. FolioSDKProvider provides all of them.
@folio/sdk/storesThe React hooks for the stores of FolioSdk: useAuthStore, useSessionStore, useVaultStore, useInquiryStore, useMrtdStore, useLogsStore(init), useFolioDocumentListStore and useFolioDocumentStore(init). See React provider and hooks.
@folio/sdk/platformThe browser platform layer the WebAssembly module calls: storage, secure storage, file system, crypto, HTTP and the other platform services. initializeFolioWasm wires it for you. @folio/sdk/platform/* reaches single modules of it. Its type declarations are generated from the JavaScript of each module.
@folio/sdk/inquiryThe inquiry React component FolioInquiry, its props type FolioInquiryProps, openInquiry, browserInquiryPlatform, adoptInquiryTheme and the type InquiryPlatform.
@folio/sdk/inquiry/uiThe building blocks FolioInquiry renders with: InquiryView, InquiryLayout, ProcessingSpinner, adoptInquiryTheme, browserInquiryPlatform, AssetBaseUrlProvider, useAssetBaseUrl, useAssetResolver and the types InquiryActions, InquiryPlatform, InquiryViewProps, InquiryLayoutProps, ProcessingSpinnerProps, StageChrome, ScreenStageProps, VerificationStageProps, HandoffStageProps.
@folio/sdk/inquiry/ui/captureThe document camera: CaptureCamera, CameraHintBar and the types CaptureCameraProps and LumaFrame. The inquiry UI loads it lazily when a document capture opens.
@folio/sdk/inquiry/ui/selfieThe selfie liveness view: SelfieLiveness, its props type SelfieLivenessProps and cropPortraitToSquare. The inquiry UI loads it lazily when a selfie liveness step opens.
@folio/sdk/inquiry/styles.cssThe stylesheet of the inquiry UI. It includes theme.css. Every rule is scoped to the inquiry's root element; see Import the styles.
@folio/sdk/inquiry/theme.cssOnly the theme layer of the inquiry UI: the PlexUI variables mapped onto the inquiry theme variables, for the light and the dark theme.
@folio/sdk/package.jsonThe package manifest, for reading the package version.

Most apps need @folio/sdk, @folio/sdk/inquiry and @folio/sdk/inquiry/styles.css. The capture and selfie entry points exist so that the inquiry UI can load them on demand; an app that renders FolioInquiry does not import them.

The notification service of the platform layer, @folio/sdk/platform/notifications.js, dispatches a folio-notification-target CustomEvent on window when the user clicks a notification it posted with a target, and passes the target as detail; the module exports the event name as NOTIFICATION_TARGET_EVENT. Products built on the SDK, such as Folio's own apps, use this service. The SDK API on this page posts no notification, so the event is not part of what your app receives.

Serve the WebAssembly module

The SDK core is one WebAssembly module in the package's dist directory, next to a manifest that names it:

FileHolds
id_folio_<version>.manifest.jsonThe bundle id, the version, the metadata digest, the file name of the module and its SHA-256.
id_folio_<version>.<content-hash-8>.<metadata-digest-8>.wasmThe WebAssembly module. Its name changes with its content.

<version> is the version of the SDK core, folioSDKBuildInfo.lib.version, not the version of the npm package. Copy both files from node_modules/@folio/sdk/dist/ to the same directory of your site, for example the site root. FolioSDKWasmArtifact.manifestFilename is the manifest's file name, so your code does not hard-code the version.

Load the module once per page, before you create a runtime:

folio.ts
import { FolioSDKWasmArtifact, initializeFolioWasm, loadFolioSDKWasm } from '@folio/sdk';

const manifestUrl = `/${FolioSDKWasmArtifact.manifestFilename}`;

await initializeFolioWasm({
    source: manifestUrl,
    application: { identifier: window.location.origin, version: '1.0.0', build: '1' },
    load: () => loadFolioSDKWasm({ manifestUrl }),
});

initializeFolioWasm({ source, application, load }) loads the module through load, instantiates it with the platform layer and your application's identity, and initializes the SDK classes with it:

  • application is a WebApplication: identifier, version and build, all non-empty strings. A web application uses its origin as the identifier. The SDK reports the version and build to Folio; see Application identity. A missing or blank value rejects the promise with the error xplatform: application.<key> must be a non-empty string.
  • A page loads the module once. A second call with the same source returns the same promise.
  • A call with another source rejects with an error that names the source already loaded.
  • If loading fails, the promise rejects and the next call tries again.
  • A module that was not built together with the TypeScript classes of the package rejects the promise with an error that names the mismatch.

load returns a promise of { bytes }, the bytes of the module. loadFolioSDKWasm(options) is the loader the package provides. It takes a FolioSDKWasmLoadOptions:

OptionTypeMeaning
manifestUrlstring | URLThe URL of the manifest. The module is fetched from the manifest's directory.
wasmUrlstring | URLThe URL of the module itself. When set, manifestUrl is not used and nothing is checked.
fetchtypeof globalThis.fetchThe fetch function to use. Defaults to the global fetch.

One of manifestUrl and wasmUrl is required. Through the manifest, loadFolioSDKWasm checks that the manifest names the bundle and the version the package was built for, that the file name of the module matches its SHA-256 and metadata digest, and that the SHA-256 of the fetched bytes matches the manifest. Any mismatch, an HTTP error or a network error rejects the promise with an Error that names the URL. The promise resolves with a FolioSDKLoadedWasm: bytes, the url the module came from and, when loaded through the manifest, the manifest.

Use no SDK class before the promise of initializeFolioWasm resolves. The WebAssembly module aborts on an internal panic instead of throwing a recoverable error: the page then gets a WebAssembly.RuntimeError.

Route API requests

The SDK sends its service requests to <baseUrl>/api/<service> and fetches the inquiry signing keys from <baseUrl>/.well-known/jwks.json when an inquiry starts, where baseUrl is the field of the runtime config:

PathServes
/api/umSession and sign-in
/api/docDocuments
/api/notificationNotifications
/api/vaultThe vault
/api/inquiryThe inquiry
/api/otpOne-time code verification
/api/v-gov-idGovernment id verification
/api/v-gov-id-nfcGovernment id chip verification
/api/selfieSelfie verification
/.well-known/jwks.jsonThe inquiry signing keys

A ServiceEnvironment in development.serviceEnvironments gives its service the suffix -<environment>, for example /api/inquiry-<environment>.

Set baseUrl to Folio's backend origin, https://app.folio.mobi, and route the requests through your page's own origin:

  1. Call registerPageProxiedApiOrigin(baseUrl) from @folio/sdk before you create the runtime. The SDK then sends every request for that origin to the same path on your page's origin, for example /api/inquiry/... instead of https://app.folio.mobi/api/inquiry/....
  2. Forward the /api path and /.well-known/jwks.json of your page's origin to https://app.folio.mobi.

baseUrl stays the backend origin because the SDK signs each request for the address it builds from baseUrl, and Folio accepts that signature only for its own address. A baseUrl that is your page's origin produces requests Folio rejects.

folio.ts
import { registerPageProxiedApiOrigin } from '@folio/sdk';

registerPageProxiedApiOrigin('https://app.folio.mobi');

registerPageProxiedApiOrigin takes an absolute http or https URL and registers its origin. Any other value, such as '', /api or app.folio.mobi, throws a TypeError with the message registerPageProxiedApiOrigin: baseUrl must be an absolute http(s) URL, got "<value>".

In the Vite dev server, forward both paths with server.proxy:

vite.config.ts
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

export default defineConfig({
    plugins: [react()],
    server: {
        proxy: {
            '/api': { target: 'https://app.folio.mobi', changeOrigin: true, secure: true },
            '/.well-known/jwks.json': {
                target: 'https://app.folio.mobi',
                changeOrigin: true,
                secure: true,
            },
        },
    },
});

server.proxy applies only to the dev server. Wherever you host the built app, forward both paths the same way, for example with a rewrite of /api/:path* to https://app.folio.mobi/api/:path* and of /.well-known/jwks.json to https://app.folio.mobi/.well-known/jwks.json.

The selfie liveness component sends its requests to the liveness service URL the inquiry gives it. When the path of that URL starts with /api/, the inquiry UI sends them to the same path on your page's origin, so the same /api forwarding serves them.

Create the runtime

Your app owns the runtime. Create it with FolioSdk.create(config, mapper) after the WebAssembly module has loaded. The promise resolves with a runtime that is ready: the SDK has restored the stored locale and session before it resolves. Call await runtime.close() when your app no longer needs it.

folio.ts
import {
    FolioSdk,
    registerPageProxiedApiOrigin,
    type IdentityRecordMapper,
    type RecordContent,
    type VerifiedIdentity,
} from '@folio/sdk';

interface StoredIdentity extends Omit<VerifiedIdentity, 'issuedAt' | 'expiresAt'> {
    readonly issuedAt: number;
    readonly expiresAt: number;
}

interface SavedIdentity {
    readonly identities: readonly StoredIdentity[];
}

const savedIdentityMapper: IdentityRecordMapper = {
    map(identity: VerifiedIdentity, existing: RecordContent | undefined): RecordContent {
        if (existing !== undefined && existing.schema !== 'identity') {
            throw new Error(`a ${existing.schema} record is no saved identity`);
        }
        const earlier =
            existing === undefined
                ? []
                : (JSON.parse(existing.payload) as SavedIdentity).identities;
        const stored: StoredIdentity = {
            ...identity,
            issuedAt: Number(identity.issuedAt),
            expiresAt: Number(identity.expiresAt),
        };
        const document: SavedIdentity = {
            identities: [...earlier, stored],
        };
        return { schema: 'identity', version: 1, payload: JSON.stringify(document) };
    },
};

registerPageProxiedApiOrigin('https://app.folio.mobi');

export const runtime = await FolioSdk.create(
    {
        baseUrl: 'https://app.folio.mobi',
        organization: '<organization>',
        development: undefined,
        logging: FolioSdk.loggingConfig(undefined),
    },
    savedIdentityMapper,
);

The config is the generated FolioSdkConfig. Every field is set by your code; the SDK adds no defaults:

FieldTypeMeaning
baseUrlstringThe Folio backend origin, https://app.folio.mobi. Register it with registerPageProxiedApiOrigin so the requests go through your page's origin.
organizationstringThe organization your vault records belong to: non-empty, lowercase letters, digits and -. Another value fails create.
developmentDevelopmentConfig | undefinedundefined in production. { realm, serviceEnvironments } for development; see DevelopmentConfig.
loggingLoggingConfigFile and console writers for this runtime; see Logging.

Configuration describes each field on every platform.

FolioSdk.create takes an optional third argument { signal } with an AbortSignal. Aborting it rejects the promise with an AbortError. When the SDK cannot start, the promise rejects with a FolioSDKError, whose message describes the failure. A FolioSDKError also carries the numeric status and lastError with their names statusName and lastErrorName, the keys of FolioSDKErrorCode. Every FolioSDKError the SDK throws is also printed with console.error, prefixed [FolioSDK].

The runtime has two ways to end:

MemberEffect
runtime.close()Returns a promise. Unmounts every store mounted on the runtime, shuts the runtime down and releases it, even when the shutdown fails. A second call returns the same promise. After it, the runtime and its stores can no longer be used.
runtime.shutdown({ signal })An async method. Unmounts the stores inside the SDK and ends the session scope, but keeps the JavaScript object. close() calls it for you. On a closed runtime the promise rejects.

Use close(). FolioSdk is AsyncDisposable: [Symbol.asyncDispose]() returns close(), so an await using declaration closes it at the end of its scope. The stores are Disposable: [Symbol.dispose]() calls close(), so a using declaration closes them.

The second argument is your IdentityRecordMapper. The runtime keeps it for its lifetime. When a government id verification succeeds, the inquiry calls map(identity, existing) and saves the RecordContent it returns to the vault:

  • existing is the record saved before for the same physical document (same document number and date of birth), and the SDK updates it. Otherwise existing is undefined and the SDK creates a record.
  • payload is your own JSON document. Build it with JSON.stringify and read it with JSON.parse; the SDK has no helper layer over it.
  • identity.files holds the attachment ids of the stored artifacts.
  • Selfie and one-time code results never reach the mapper.
  • An error thrown by map writes nothing and ends the inquiry with lifecycle FAILED and error RECORD_MAPPING, whose message is the error's message.

See Identity records.

The runtime orchestrates and returns no data. Data comes from stores: mount a store on the runtime, dispatch actions to it and read its state. With the inquiry store:

inquiry.ts
import { InquiryAction, InquiryEntry, InquiryStore } from '@folio/sdk';
import { runtime } from './folio';

export function openInquiry(launchCode: string): void {
    const store = InquiryStore.mount(runtime);
    const unsubscribe = store.subscribe(() => {
        if (store.state.lifecycle.type === 'CLOSED') {
            unsubscribe();
            store.close();
        }
    });
    store.dispatch(InquiryAction.open(InquiryEntry.start(launchCode)));
}
MemberMeaning
<Store>.mount(runtime)Mounts the store on the runtime and returns it.
store.stateThe current state. When the stream that keeps it current fails, the store unmounts and state throws that error.
store.subscribe(listener)Calls listener when the state changes. Returns a function that unsubscribes.
store.subscribe()Returns a subscription, such as InquirySubscription. await subscription.next({ signal }) resolves with the current state first, then with the latest state after each change, and with undefined once the store is unmounted. The subscription is an async iterable for for await, and [Symbol.dispose]() ends it.
store.dispatch(action, context)Sends an action to the store. context is an optional LogContext that ties the action's journal entries to an interaction; see Logging.
store.close()Unmounts the store.

Runtime lists the stores, and each store has its own page.

React provider and hooks

In React, put the runtime into FolioSDKProvider from @folio/sdk/provider and read stores with the hooks from @folio/sdk/stores:

App.tsx
import { InquiryAction, InquiryEntry } from '@folio/sdk';
import { FolioSDKProvider } from '@folio/sdk/provider';
import { useInquiryStore } from '@folio/sdk/stores';
import { runtime } from './folio';

function InquiryStatus({ launchCode }: { launchCode: string }) {
    const { store, state } = useInquiryStore();
    if (store === undefined || state === undefined) return null;
    return (
        <button onClick={() => store.dispatch(InquiryAction.open(InquiryEntry.start(launchCode)))}>
            {state.lifecycle.type}
        </button>
    );
}

export function App({ launchCode }: { launchCode: string }) {
    return (
        <FolioSDKProvider runtime={runtime}>
            <InquiryStatus launchCode={launchCode} />
        </FolioSDKProvider>
    );
}

FolioSDKProvider takes runtime, a FolioSdk, and children. It provides the runtime to useFolioSDKRuntime() and to the host hooks (useAuthHost, useDiagnosticsHost, useSessionHost, useVaultHost, useInquiryHost, useMrtdHost, useFolioDocumentListHost, useFolioDocumentHost). Each of these hooks throws when no provider is above it.

HookReturns
useAuthStore(){ store: AuthStore | undefined; state: AuthUiModel | undefined }
useSessionStore(){ store: SessionStore | undefined; state: SessionUiModel | undefined }
useVaultStore(){ store: VaultStore | undefined; state: VaultUiModel | undefined }
useInquiryStore(){ store: InquiryStore | undefined; state: InquiryUiModel | undefined }
useMrtdStore(){ store: MrtdStore | undefined; state: MrtdUiModel | undefined }
useLogsStore(init){ store: LogsStore | undefined; state: LogsUiModel | undefined }; init is the LogFilter the store mounts with. See Logging.
useFolioDocumentListStore(){ store: FolioDocumentListStore | undefined; state: FolioDocumentListUiModel | undefined }. See Folio documents.
useFolioDocumentStore(init){ store: FolioDocumentStore | undefined; state: FolioDocumentUiModel | undefined }; init is the FolioDocumentInit with the id of the document. See Folio documents.

Each store hook mounts its own store on the runtime in an effect after the first render and closes it when the component unmounts or the runtime changes. Under React StrictMode the hook mounts, closes and mounts again during development; the store it returns is the second one, so a component never sees the closed store. Until the store is mounted, store and state are undefined. state follows every state change of the store; dispatch actions with store.dispatch. The runtime itself is yours: the provider and the hooks never close it.

Each <Host>Provider takes runtime and children and provides only that host interface. Use one when a part of your tree needs a single store and you do not use FolioSDKProvider.

Import the styles

Import the inquiry stylesheet once, in your app's entry module:

main.tsx
import '@folio/sdk/inquiry/styles.css';

styles.css contains the theme layer. Every rule in it is scoped to the .folio-inquiry element that FolioInquiry renders, and it ships no preflight and no theme tokens of its own, so it never restyles the rest of your page. Your Tailwind build does not need to scan the package for it. Import @folio/sdk/inquiry/theme.css alone only if you build your own styles on the theme.

The inquiry UI is built on @plexui/ui, and your app supplies the PlexUI stylesheet, for example @plexui/ui/css; the inquiry's styles read the PlexUI variables and fall back to their own values where one is missing.

The theme values come from FolioSdk.inquiryTheme(). FolioInquiry installs them as --inq-* CSS custom properties for a light theme on :root and on [data-theme='light'], and for a dark theme on [data-theme='dark']. Set data-theme="dark" on an ancestor of the inquiry to show it dark.

Render the inquiry

FolioInquiry from @folio/sdk/inquiry renders the whole inquiry: every screen, the document camera, the chip reading instructions, the selfie capture and the device handoff. Your backend creates the inquiry and passes its launch code to your page.

Inquiry.tsx
import { InquiryAction, InquiryEntry, type InquiryStore } from '@folio/sdk';
import { browserInquiryPlatform, FolioInquiry } from '@folio/sdk/inquiry';
import { useInquiryStore } from '@folio/sdk/stores';
import { useEffect, useRef } from 'react';

const platform = browserInquiryPlatform('/folio');

export function Inquiry({ launchCode, onDone }: { launchCode: string; onDone: () => void }) {
    const { store, state } = useInquiryStore();
    const opened = useRef<InquiryStore | undefined>(undefined);
    useEffect(() => {
        if (store === undefined || opened.current === store) return;
        opened.current = store;
        store.dispatch(InquiryAction.open(InquiryEntry.start(launchCode)));
    }, [store, launchCode]);
    useEffect(() => {
        if (state?.lifecycle.type === 'CLOSED') onDone();
    }, [state, onDone]);
    return store === undefined ? null : <FolioInquiry store={store} platform={platform} />;
}

Render Inquiry inside FolioSDKProvider (or InquiryHostProvider), with key={launchCode} so that a new launch code mounts a new store. useInquiryStore mounts the store and closes it when the component unmounts, and the ref sends Open once per store, so the component also works under React StrictMode, which runs effects twice in development.

openInquiry(runtime, entry) from @folio/sdk/inquiry mounts an InquiryStore on the runtime and dispatches InquiryAction.open(entry) in one call: InquiryEntry.start(launchCode) for a new inquiry, InquiryEntry.resume(source) to continue one. The runtime is any object that implements InquiryHost; a FolioSdk is one. Call it from an event handler or outside React, never from an effect or during rendering: under StrictMode an effect would open a second inquiry with the same single-use launch code. The store it returns is yours to close; the runtime stays open.

Props (FolioInquiryProps):

PropTypeMeaning
storeInquiryStoreThe store the component renders. Every interaction dispatches InquiryAction.act(intent) or InquiryAction.close on it.
platformInquiryPlatformThe browser bridges. browserInquiryPlatform(assetBaseUrl) opens links in the browser and loads the illustrations and animations from assetBaseUrl, where '' means the site root.

Read the progress from store.state.lifecycle:

lifecycle.typelifecycle.valueMeaning
READY{ sdkVersion }The runtime is ready. sdkVersion is undefined in an unstamped build.
STARTED{ correlationId }The inquiry is active, including while its terminal screen shows.
CLOSED{ outcome }The component sent InquiryAction.close. outcome is the TerminalOutcome, or undefined when there is none. Close the store now.
FAILED{ error }The inquiry cannot continue. error is an InquiryError. Informational: the component shows the failure notice, and its close button leads to CLOSED.

The rest of InquiryUiModel (view, loading, busy, chrome, error, failure, identity) and every InquiryError value are on Inquiry store and Errors.

What the component does on its own:

  • It renders a full-viewport layout: a full-height sheet on small screens and a centered card of at most 400 by 746 pixels on large screens.
  • When failure is set, it shows the failure notice instead of any screen, for example the invalid link notice for an empty launch code or the launch code notice for one that was already used or has expired, with a retry button when failure.retry is set and a close button that closes the inquiry.
  • When the inquiry reaches its terminal screen and the store has finished loading, it closes the inquiry. When the terminal screen has a redirect, the SDK delivers it as a UNIVERSAL link target, and the component first navigates the page to it through the openLink of its platform; browserInquiryPlatform does that in the same tab with window.location.assign. The SDK normalizes the redirect URL and rejects one that is not a valid absolute URL with the error INVALID_LINK. When openLink returns false for the redirect, which an InquiryPlatform of your own can do and browserInquiryPlatform never does, the component sends InquiryIntent.linkRefused(url) instead of closing and shows the projected error with a close button.
  • The close control of the inquiry asks for confirmation in a dialog. Confirming it, and the close button of the failure notice, close the inquiry. Your store subscriber then sees CLOSED. After it sent InquiryAction.close, the component sends nothing more to the store.
  • It opens an EXTERNAL link of the inquiry in a new browser tab through window.open, without giving the new tab a reference to your page, and navigates the page itself to a UNIVERSAL or WEBVIEW link with window.location.assign. When the browser refuses to open the new tab, it sends InquiryIntent.linkRefused(url) and shows the banner error the store projects.
  • When the selfie liveness check fails (the liveness component runs out of retries, its result is not a success or carries no portrait, or the portrait cannot be processed), it shows a generic error dialog whose retry button retries the selfie step. Closing the liveness component goes back one step.
  • It loads the liveness component and the chip reading animations when they are first needed. When one of them fails to load (for example, the network request for the lazy chunk of the liveness component fails, or an animation file is not served), the component throws that error while rendering, so it reaches the nearest React error boundary of your app.

Serve the illustrations and animations

The inquiry UI loads its images and animations at run time from the assetBaseUrl of browserInquiryPlatform. The package ships them in two directories:

DirectoryHoldsLoaded from
node_modules/@folio/sdk/dist/illustrationsThe SVG illustrations of the inquiry screens, <id>.svg and <id>-dark.svg for the dark theme, and the images of the selfie liveness screens.<assetBaseUrl>/illustrations/
node_modules/@folio/sdk/dist/animationsThe chip reading animations nfc-passport-scan.json and nfc-idcard-scan.json. Their image layers load from illustrations.<assetBaseUrl>/animations/

Copy both directories side by side to your site and pass the URL of their parent directory as assetBaseUrl. With the directories at /folio/illustrations and /folio/animations, pass browserInquiryPlatform('/folio'). With '' the files load from /illustrations and /animations at the site root. A chip reading animation that fails to load is an error of the component; see Render the inquiry.

Browser storage

The SDK keeps its data in the storage of your page's origin:

StorageHolds
localStorageKeys with the prefix folio_storage:
IndexedDBThe database folio_secret_storage for secure storage and the database folio_key_store for the signing keys
Origin private file systemFiles the SDK stores

These names are the same in every app that uses the SDK. Serve your app from an origin that no other app using the Folio SDK shares, or the two apps read each other's data. Storage describes what the SDK stores.

Next steps

  • Inquiry UI: the inquiry screens on every platform.
  • Inquiry store: run an inquiry without the ready-made UI.
  • Inquiry embed: embed the inquiry with one script tag instead of the npm package.

On this page