Inquiry embed
Embed the Folio inquiry in any web page with one script tag and window.FolioInquiry.mount.
Preview
This page describes a pre-release version of the SDK. Names, versions and APIs on this page can change before the release.
The inquiry embed runs the Folio inquiry on any web page without npm, a bundler or React. Your page
loads one module script from Folio and calls window.FolioInquiry.mount. The inquiry then runs in
an iframe served from Folio's origin: the SDK, its WebAssembly module, the illustrations, the
animations and the API requests all stay on that origin, and your page talks to the iframe only
through postMessage to one pinned origin.
To run the inquiry inside your own React app instead, use the npm package: see Web setup.
How it works
- Your backend creates an inquiry and receives a one-time launch code. The embed never creates an inquiry.
- Your page loads the loader
embed.v<major>.js. It registerswindow.FolioInquiry. FolioInquiry.mount(options)loads the embed runtime next to the loader, adds an iframe that shows<baseUrl>/inquiry.htmland sends it your app metadata.- The iframe starts the SDK and reports
READYwith its SDK version. The loader checks that version against the major version in its own file name. - Only then does the loader send the entry with your launch code to the iframe. The inquiry runs,
and your
onLifecyclecallback receives every lifecycle change.
Load the loader
<script type="module" src="<embed-loader-url>"></script><embed-loader-url> is the URL of the loader that Folio gives you; Folio does not publish one
URL for everyone. The iframe accepts messages only from host origins Folio has configured for it,
so ask Folio for the loader URL and give it every origin your pages run on, including development
and staging origins. A page on an origin Folio has not configured fails with READY_TIMEOUT; see
Launch code handling and security.
- Load it as a module script:
type="module"is required. - The loader finds the rest of the embed from its own URL. It fetches
runtime-manifest.jsonfrom the directory it was loaded from and imports the runtime chunk that the manifest names. Load it from the URL Folio gives you, so that these files are next to it. - The
v<major>in the file name is the major version the loader is pinned to. See Versions and caching.
Mount the inquiry
Call mount from a module script that comes after the loader, so that window.FolioInquiry
exists when it runs:
<div id="folio-inquiry"></div>
<script type="module">
const handle = window.FolioInquiry.mount({
container: '#folio-inquiry',
app: { version: '2.3.4', build: '567' },
mode: 'built-in',
backdrop: { color: 'rgba(0, 0, 0, 0.5)' },
panel: { background: '#ffffff', shadow: '0 12px 48px rgba(0, 0, 0, 0.24)' },
entry: { type: 'START', value: { launchCode: backendMintedLaunchCode } },
onLifecycle(lifecycle) {
if (lifecycle.type === 'FAILED') console.error(lifecycle.value.error);
},
});
</script>backendMintedLaunchCode stands for the launch code your backend passed to the page.
Options
| Option | Type | Default | Meaning |
|---|---|---|---|
container | HTMLElement | string | Required | The element to render into, or a CSS selector for it. |
app | { version: string; build: string } | Required | Your app's version and build. Both must be non-empty after trimming. With your page's origin as the identifier they are the application identity the SDK in the iframe reports; see Application identity. |
entry | InquiryEntry | Required | The inquiry to open. See Entry. |
mode | 'modal' | 'built-in' | 'modal' | 'modal' shows the iframe in an overlay above the page. 'built-in' puts the iframe into container. |
backdrop | { color: string } | Required | The overlay behind the modal. color is any CSS color. Required in every mode; 'built-in' does not use it. |
panel | { background: string; shadow: string } | Required | The modal panel that holds the iframe. background is any CSS background and shadow any CSS box-shadow. Required in every mode; 'built-in' does not use it. |
baseUrl | string | The directory the loader was loaded from | The Folio location that serves the iframe document, the WebAssembly module, the illustrations, the animations and the API. |
allowedOrigin | string | The origin of baseUrl | The one origin the loader exchanges messages with. '*' is rejected. |
onLifecycle | (lifecycle: InquiryLifecycle) => void | None | Receives every lifecycle the inquiry reports, and the embed's own failures. |
backdrop.color, panel.background and panel.shadow must be non-empty after trimming. The embed
has no defaults for them, so your page decides how the modal looks.
Leave baseUrl and allowedOrigin out unless Folio tells you otherwise: the defaults point at the
location you loaded the loader from.
Entry
entry is the generated InquiryEntry in its JSON shape:
| Entry | Meaning |
|---|---|
{ type: 'START', value: { launchCode } } | Start the inquiry that your backend created. |
{ type: 'RESUME', value: { source } } | Continue an inquiry. source is a ResumeSource. |
A source of { type: 'ANOTHER_DEVICE', value: { code } } continues an inquiry that was handed off
from another device with its handoff code. Inquiry store describes the
resume sources.
mount checks only that entry.type is START or RESUME. A blank launch code or handoff code,
or a blank inquiryId in a SAME_DEVICE source, passes that check and then fails in the inquiry
with INVALID_ENTRY.
Modes
| Mode | Where the iframe goes | Size |
|---|---|---|
modal | An overlay element appended to container. The overlay is fixed to the viewport, covers it, has the background backdrop.color, sits at z-index 2147483000 and centers a panel with rounded corners that holds the iframe. The panel has the background panel.background and the shadow panel.shadow. | The panel is at most 460 by 720 pixels and keeps 24 pixels to each edge of the viewport. |
built-in | The iframe is appended to container directly. | Full width of container. The height follows the content of the inquiry: the iframe reports it and the loader sets it. |
A modal removes itself when the lifecycle reaches CLOSED. The loader adds no close button of its
own: the user leaves through the inquiry's own close control. A built-in iframe stays in the page
after CLOSED; call destroy() to remove it.
The mount handle
mount returns a handle right away, before the embed runtime has loaded:
| Method | Effect |
|---|---|
open() | Sends the entry of the mount options to the inquiry again. mount already sends it once. |
close() | Closes the inquiry inside the iframe (InquiryAction.close). The inquiry reports CLOSED. |
destroy() | Removes the iframe, or the modal overlay with it, and the message listener. Pending messages are dropped. A second call does nothing. |
- Messages to the iframe wait until the iframe reports
READY.open()andclose()called in that time are delivered afterREADY. open()andclose()called before the embed runtime has loaded do nothing.destroy()called before the embed runtime has loaded prevents the mount.- After
destroy(),open()andclose()do nothing. Callmountagain for a new inquiry.
Lifecycle
onLifecycle receives the generated InquiryLifecycle, in the order the inquiry reports it:
lifecycle.type | lifecycle.value | Meaning |
|---|---|---|
READY | { sdkVersion: string | undefined } | The SDK in the iframe has started. The loader checks sdkVersion against its pinned major; a pinned loader rejects a runtime without one. |
STARTED | { correlationId: string } | The inquiry is active, also while its terminal screen shows. |
CLOSED | { outcome: TerminalOutcome | undefined } | The inquiry was closed: by its own close control, by the terminal screen after its redirect, or by close(). outcome is the terminal result, or undefined when there is none. |
FAILED | { error: InquiryError } | The inquiry cannot continue. For an error of the inquiry the iframe shows the failure notice, and its close button leads to CLOSED. The embed's own failures (EMBED, BOOT_FAILED) end there. |
outcome is one of COMPLETED, FAILED, ERROR, CANCELED, EXPIRED and NEEDS_REVIEW. The
iframe reports a lifecycle only when it differs from the last one it reported.
The inquiry UI runs inside the iframe, so its links act from there. When the inquiry reaches its
terminal screen and has finished loading, it closes the inquiry; when that screen has a redirect,
the inquiry first navigates the iframe to the redirect URL with window.location.assign. Your page
is not navigated.
An EXTERNAL link of the inquiry opens in a new browser tab.
function onLifecycle(lifecycle) {
switch (lifecycle.type) {
case 'READY':
console.log('Folio SDK', lifecycle.value.sdkVersion);
break;
case 'STARTED':
console.log('inquiry', lifecycle.value.correlationId);
break;
case 'CLOSED':
console.log('closed', lifecycle.value.outcome);
break;
case 'FAILED':
console.error(lifecycle.value.error.type, lifecycle.value.error.value);
break;
}
}Failures
The embed reports its own failures as a FAILED lifecycle with an InquiryError, like the
failures of the inquiry itself:
const lifecycle: InquiryLifecycle = {
type: 'FAILED',
value: {
error: {
type: 'EMBED',
value: {
reason: { type: 'READY_TIMEOUT' },
message: 'Inquiry runtime did not report READY in time',
},
},
},
};error.type | error.value | Reported when |
|---|---|---|
EMBED | { reason, message } | The embed failed; reason.type says how. See the next table. |
BOOT_FAILED | { message } | The embed runtime could not load: runtime-manifest.json failed, named an unexpected file, or the runtime chunk failed to load or register. Or the iframe failed to start the SDK, for example when the WebAssembly module failed to load. |
INVALID_ENTRY | None | The entry carried a blank launch code or handoff code, or a blank inquiryId in a SAME_DEVICE source. |
RECORD_MAPPING | { message } | Saving a verified identity failed: the identity record mapper threw, or an image of the identity could not be read. Nothing was saved. |
FILE_TOO_LARGE | { limitBytes } | An image of a verified identity is larger than limitBytes. Nothing was saved. |
EMBED reasons:
reason.type | Reported when |
|---|---|
INVALID_OPTIONS | mount rejected its options. message names the problem: app without a non-empty version and build, an entry that is not an InquiryEntry, an empty or missing backdrop.color, panel.background or panel.shadow, a container not found, no baseUrl to use, or an allowedOrigin that is '*' or cannot be derived. mount then adds nothing to the page. |
READY_TIMEOUT | The iframe did not report READY within 15 seconds after it was added. |
ORIGIN_REJECTED | A Folio inquiry message came from another origin or another window than the pinned iframe, or the iframe received one from another origin or window than your page. The message is ignored. |
VERSION_MISMATCH | The major version of the SDK in the iframe differs from the major in the loader's file name, or the iframe reported no SDK version or one without a readable major. The entry is never sent. Load the loader of the matching major. |
After VERSION_MISMATCH the loader still waits for READY, so a READY_TIMEOUT follows it.
The other InquiryError values come from the inquiry itself; see Errors.
Launch code handling and security
- Your backend creates the inquiry. It passes the one-time launch code to the page. Mint a fresh launch code for each inquiry. Keep the credentials your backend uses with Folio out of the page.
- The launch code travels in one message only. The loader sends it in the body of the
OPENmessage to the pinned origin, after the iframe has reportedREADYand passed the version check. It never puts the launch code in a URL, a query string,srcdocor a message addressed to'*'. The first message to the iframe carries onlybaseUrl, the origins and yourappmetadata. - One pinned origin in each direction. The loader posts only to
allowedOriginand accepts messages only from that origin and from its own iframe's window. The iframe accepts its first message only from the host origin Folio configured for it and from its parent window, and later messages only from that same origin and window. - Your page's origin must be configured. The iframe ignores the first message from any other
origin, so a page on an origin Folio has not configured gets no
READYand fails withREADY_TIMEOUT. - The runtime chunk name is checked. The loader imports only a file whose name matches
runtime.<hex>.jsfrom the manifest next to it. - The inquiry's data stays on Folio's origin. The SDK runs inside the iframe, so its storage and its API requests belong to Folio's origin, not to your page.
- Camera and microphone. The iframe is created with
allow="camera; microphone"so that document capture and selfie steps can use the camera. If your page sends aPermissions-Policyheader, it must allowcameraandmicrophonefor the Folio origin, for examplePermissions-Policy: camera=*, microphone=*.
Content Security Policy
If your page sends a Content Security Policy, allow what the embed loads from the Folio origin:
| Directive | Allow | Because |
|---|---|---|
script-src | <embed-origin> | The loader module script and the runtime chunk it imports. |
connect-src | <embed-origin> | The loader fetches runtime-manifest.json. |
frame-src | <embed-origin> | The iframe with inquiry.html. |
Replace <embed-origin> with the origin of the loader URL Folio gives you, and with the
origin of baseUrl if you set it. The iframe document, its WebAssembly module, the illustrations,
the animations and the API requests are loaded by the iframe on Folio's origin and are not governed
by your page's policy.
Versions and caching
The embed carries the version of the Folio web SDK. The loader file name holds only its major
version: SDK 0.x.y is served as embed.v0.js, 1.x.y as embed.v1.js.
- Patch and minor releases replace the files under the same loader URL. Your page gets them without a change.
- A major release gets a new loader file name. The loader of the previous major stays
published for pages that reference it. To upgrade, change the
srcof your script tag after reading the release notes. Do not add a query string to the loader URL to upgrade. - The version check. The loader reads its pinned major from the first
v<digits>in its URL and rejects an iframe SDK of another major, or one that reports no version, withVERSION_MISMATCH. A loader URL without such a part skips the check.
Caching of the published files:
| File | Cache-Control |
|---|---|
embed.v<major>.js | public, max-age=300 |
runtime-manifest.json | public, max-age=300 |
runtime.<hash>.js, runtime.<hash>.css | public, max-age=31536000, immutable |
| Other files | public, max-age=300 |
The loader and the manifest are cached for 5 minutes, so a fix reaches your page within minutes. The content-hashed runtime files are cached for one year. Keep the loader URL stable; the loader resolves the content-hashed runtime chunk itself.