Loading the module
The CDN URLs, the two build flavours, and the complete Handler API.
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.
| Build | URL | How you get Handler |
|---|---|---|
| Classic script | https://cdn.saphere.ai/saphere-scan/v1/js/main.min.js | window.Handler, or the handler-ready event |
| ES module | https://cdn.saphere.ai/saphere-scan/v1/mjs/main.min.js | import { 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 value must be a UUID: anything else makes the CDN refuse the request, and the script does not load. An identifier that matches no v1 customisation is not an error — the module loads without one.
Two older addresses still serve the same module: /saphere-scan/js/main.min.js and /modules/M1/<version>/js/main.min.js, with their mjs counterparts. They are deprecated; move to the /saphere-scan/v1/ paths above.
The URLs above are Production. Only the host changes between environments; the paths are identical:
| Environment | CDN base |
|---|---|
| Production | https://cdn.saphere.ai |
| Test | https://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.
Read the global, or wait for the event — not one or the other
window.Handler is only defined once the script has finished executing, and handler-ready is dispatched once, at that moment. A plain <script> tag runs before the code below it, so the global is already there and a listener added afterwards never fires. A script loaded with async, injected at runtime or loaded by a framework may run later, and then the global is not there yet. Handle both cases:
const mount = Handler => Handler.load("#container", options)
if (window.Handler)
mount(window.Handler)
else
window.addEventListener("handler-ready", ({ detail: { Handler } }) => mount(Handler), { once: true })
The event carries Handler in its detail, so the second branch never needs the global.
The Handler API
Everything is static. There is one widget per page.
Handler.load(target, options)
Mounts the widget. Returns nothing.
| Parameter | Accepts |
|---|---|
target | A CSS selector string, or an HTMLDivElement |
options | The options object — see Options |
The container’s contents are replaced. Give it a height, because the widget fills it.
Throws:
| Message | Cause |
|---|---|
SaphereScan is already loaded. | A widget is already mounted. Call destroy() first, or use reload(). |
Module must be loaded in an HTMLDivElement | The 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: createMeasure missing or incomplete, an unknown top-level key, or a customisation conflicting with your options. |
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
| Getter | Type | Value |
|---|---|---|
Handler.isLoaded | boolean | Whether a widget is currently mounted |
Handler.langs | string[] | ["ar","de","en","es","fr","it","pl","pt","pt_BR","tr"] |
Handler.defaultModuleOptions | object | The defaults, before your options are merged |
Handler.isMobile | boolean | Device detection — the widget’s own guess, which an isMobile option overrides inside the widget |
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.