Loading the module

The CDN URLs, the two build flavours, and the complete Handler API.

Legacy module — no longer recommended

This page documents the v1 widget, kept for integrations already in production. New integrations should use Saphere Scan v2.

New integrations should use Saphere Scan v2. What follows documents the v1 module for products already running it.

The two builds

The same widget is published twice, and which one you load decides how you reach it.

BuildURLHow you get Handler
Classic scripthttps://cdn.saphere.ai/saphere-scan/v1/js/main.min.jswindow.Handler, or the handler-ready event
ES modulehttps://cdn.saphere.ai/saphere-scan/v1/mjs/main.min.jsimport { Handler } from …

Both accept an optional ?customizationId=<uuid> query parameter, which applies a customisation configured in the console before your own options are merged on top.

The URLs above are Production. Only the host changes between environments; the paths are identical:

EnvironmentCDN base
Productionhttps://cdn.saphere.ai
Testhttps://cdn.test.saphere.ai

As with v2, the CDN you load from is what decides which environment the module works against. Environments covers the rest.

The ES module build

Preferred where you control the page and can use modules.

<script type="module">
    import { Handler } from "https://cdn.saphere.ai/saphere-scan/v1/mjs/main.min.js"

    Handler.load("#container", options)
</script>

The classic build

For pages that cannot use modules — an Ionic shell, for instance, where the script sits in the app template.

<script src="https://cdn.saphere.ai/saphere-scan/v1/js/main.min.js"></script>

The bundle sets window.Handler and then dispatches a handler-ready event on window.

Wait for the event, do not poll

window.Handler is only defined once the script has finished executing. A script tag lower in the page that reads it immediately may run first.

window.addEventListener("handler-ready", ({ detail: { Handler } }) => {
    Handler.load("#container", options)
})

The event carries Handler in its detail, so you never need to touch the global at all.

The Handler API

Everything is static. There is one widget per page.

Handler.load(target, options)

Mounts the widget. Returns nothing.

ParameterAccepts
targetA CSS selector string, or an HTMLDivElement
optionsThe options object — see Options

The container’s contents are replaced. Give it a height, because the widget fills it.

Throws:

MessageCause
SaphereScan is already loaded.A widget is already mounted. Call destroy() first, or use reload().
Module must be loaded in an HTMLDivElementThe selector matched nothing, or matched something that is not a div.
Bad integrator module options!Your options failed validation. The details are logged to the console.
Bad module options!The merged options failed validation — usually a customisation conflicting with yours.

Read the console when options are refused

Both option errors log the full validation output before throwing. The thrown message alone does not say which field was wrong; the console entry does.

Handler.destroy()

Asynchronous. Unmounts the widget, empties the container and releases the camera.

await Handler.destroy()

Safe to call when nothing is mounted — it returns immediately.

Handler.reload(target, options)

Asynchronous. Equivalent to await destroy() then load(...). This is how you restart a measurement, or apply new options.

Getters

GetterTypeValue
Handler.isLoadedbooleanWhether a widget is currently mounted
Handler.langsstring[]["ar","de","en","es","fr","it","pl","pt","pt_BR","tr"]
Handler.defaultModuleOptionsobjectThe defaults, before your options are merged
Handler.isMobilebooleanDevice detection, as the widget itself sees it

Handler.isMobile is useful for layout: on a desktop browser you usually want to constrain the widget to a phone-shaped column rather than let it fill a wide viewport.

A complete page

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=0">
    <title>Saphere Scan v1</title>
    <style>
        html, body { padding: 0; margin: 0; overflow: hidden; }
        #container { height: 100vh; }
        /* On a desktop browser, keep the widget in a phone-shaped column. */
        #container:not(.mobile) { aspect-ratio: 9 / 16; margin: auto; }
    </style>
</head>
<body>
    <div id="container"></div>

    <script type="module">
        import { Handler } from "https://cdn.saphere.ai/saphere-scan/v1/mjs/main.min.js"

        const container = document.getElementById("container")

        const options = {
            createMeasure: {
                // Your server creates the measure and returns { id }.
                // Never put an API key in a page — see the warning below.
                strategy: "delegate",
                url: "/api/saphere-measure"
            },
            onEvent: event => {
                console.log(event.type, event)
                if (event.type === "result")
                    console.log("variables:", event.variables)
            },
            allowLeave: true,
            lang: "en"
        }

        if (Handler.isMobile)
            container.classList.add("mobile")

        Handler.load(container, options)
    </script>
</body>
</html>

The API key does not belong in the page

Published v1 examples show headers: { Authorization: "Bearer <apiKey>" } written directly into the page. That places a credential which opens your entire account, and which cannot be revoked without deactivating the account, into anything that can read your JavaScript.

Point createMeasure.url at an endpoint on your own server, authenticate your user there, and have that endpoint call the Saphere API with the key. See Options for both strategies.

Serve the page over HTTPS or from localhost. Browsers refuse camera access on an insecure origin.