# `Session.activateAR`

> Contract 2.0.0 · package sha256 `a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e` · generated from `configurator-runtime.d.ts` by `services/onsite-scripter/scripts/build-contract-docs.mjs`; edit the source, not this file.

Method of [`Session`](Session.md) · inherited by [`testing.FakeSession`](testing.FakeSession.md) · stable · since 2.0.0 · called as `session.activateAR`

```ts
activateAR(options?: ActivateAROptions): Promise<ActivateARReport>;
```

Launches AR for the product. Status changes fire 'ar:status'. Call it from the shopper's tap
or click: WebXR needs that gesture's user activation (about 5 s in Chromium). The runtime loads
its AR code at idle once the product is on screen, so a tap after that waits for no download;
a tap before it opens the native viewer, never the in-page one.
Read `info().ar.modes` before showing an AR control; a tap on a device with no mode resolves
`{ launched: false, reason: 'unsupported' }`.
Routing (spec section 5.11): on Android the in-page WebXR viewer opens only for an account that
opts in (`ar.place.android: 'holostep'` in its published layer), with the platform's AR switch
on, no `glbUrl`, a device and embed that pass the probe, and the tap's activation (the other
conditions: spec section 5.11). Every other Android call opens Scene Viewer with `glbUrl` or,
without one, with the product's public plain twin when the product is one encrypted model with
an http(s) `plainGlbUrl`; else it resolves `reason: 'unsupported'`. WebXR never loads
`glbUrl`. On iOS and iPadOS Holostep's in-page AR opens for an account whose published layer
sets `ar.place.ios: 'holostep'` (browsers) or `ar.place.inpage: 'fallback'` (app shells), with
the platform's `xr.place` and `xr.inpage` switches on; Quick Look is its fallback, and in an
app shell the fallback is an escape sheet that resolves `reason: 'unsupported'`. Quick Look
gets `usdzUrl` (or else the runtime's own USDZ export: [`ActivateAROptions.usdzUrl`](ActivateAROptions.usdzUrl.md)), never a
GLB, in Safari and in Chrome, Firefox, Edge and DuckDuckGo on iOS alike.
[`ActivateARReport.mode`](ActivateARReport.mode.md) 'webxr' names Holostep's in-page viewer on either route (WebXR on
Android, the camera tracker on iOS). Where the escape sheet is the only route, `modes` lists
'webxr', so the AR control shows and its tap opens the sheet, as 1.x does.
A WebXR session draws its own copy of the committed product, built at launch (what it draws
over the camera feed: spec section 5.11). Staged writes and holds never reach it, so a
transaction during AR never blanks it; a committed material change of a part it holds does,
and a capture meanwhile renders the live scene.
Quick Look and Scene Viewer get the newest committed state too (the runtime's own USDZ or the
files you pass): a write still loading when the shopper taps (a fabric pick whose map is
still downloading) is not in that copy, though the page shows it a moment later. Don't await
the pick inside the tap (its user activation would lapse): keep the AR control off while a
write the shopper made is pending, as the example's `pick` does.
A rejected WebXR `requestSession` never falls through to another viewer inside the call: it
resolves, and the next viewer needs a new tap. A call while another is pending or active
returns that call's Promise. Scene Viewer and Quick Look report no failure at the launch.
[`ActivateARReport.mode`](ActivateARReport.mode.md) names the one that opened.
When it resolves: WebXR once its session starts; Scene Viewer `launched: true` when the page
turns hidden, or `{ launched: false, reason: 'error' }` when Chrome returns to the runtime's
fallback hash (no Google app, or a launch outside the gesture), whichever comes first within
2 s of the launch; after 2 s with neither it resolves `launched: true`, and a later return to the hash
still fires 'ar:status' with 'error'. Quick
Look resolves `launched: true` once its `rel="ar"` link is clicked: iOS reports no failure.
Every failure, before or after the call resolves, fires 'ar:status' with 'error'.
Errors: a failed launch resolves `{ launched: false, reason: 'error' }`, and a declined AR
prompt or a close before AR started `reason: 'cancelled'`; only invalid input rejects
(HoloInputError).

## Options

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `glbUrl` | `string` | undefined (an opted-in account's in-page viewer shows a copy of the committed product; else Scene Viewer gets the product's plain twin, or the call resolves 'unsupported': spec section 5.11) | The GLB Scene Viewer opens: an http(s) URL, never a `.holo` (Scene Viewer runs outside the page, so it can't read a `blob:` URL either). |
| `occlusion` | `boolean` | true (Scene Viewer's own) | Scene Viewer's depth occlusion. |
| `overlayRoot` | `HTMLElement` | undefined (Holostep's screens alone, in a layer over the whole page, removed when AR closes) | Your own UI over the camera in Holostep's in-page AR (a swatch strip, a part toggle, add to cart): an element you created before the call. |
| `scale` | `'auto' \| 'fixed'` | 'auto' | 'fixed' keeps the product at true size, so the shopper can't pinch-scale it (Scene Viewer `resizable=false`, Quick Look `allowsContentScaling=0`, and no… |
| `title` | `string` | undefined (the account's AR title, else the viewer's own) | The name the AR viewer shows: Scene Viewer's `title`, and the Quick Look banner's title where the account turns the banner on. |
| `ui` | `'default' \| 'headless'` | 'default' | 'default' shows Holostep's AR screens; 'headless' lets the page draw its own: Holostep's in-page AR then shows only the camera and the product (no coaching… |
| `usdzUrl` | `string` | undefined (the runtime exports one) | The USDZ iOS Quick Look opens: a hosted http(s) URL, or a `blob:` URL of a USDZ, such as one made from [`session.export({ format: 'usdz' })`](Session.export.md) (an app that… |

## Example

Imports: values (`has`, `validate`, the `Holo*Error` classes, the pure helpers) from `@holostep-configurators/runtime-client`, types from `@holostep-configurators/runtime-client/contract`, `/math` and `/testing` names from those entries; `boot` from the runtime module the root's `loadRuntimeAtFloor` resolves to. A type your page defines is your app's: declare it there, never import it from the package.

Compiled under the contract's strict settings (`examples/declaration/05-Session.activateAR.ts`):

```ts
import type { Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
declare const files: { glb: string; usdz: string };
declare function showArButton(visible: boolean): void;
declare function toast(text: string): void;
// This page passes its own files, so a viewer `fileModes` lists opens too (an encrypted
// product without a public plain twin leaves Scene Viewer out of `modes` only).
const canAr = () => {
  const { modes, fileModes } = session.info().ar;
  return modes.length > 0 || fileModes.length > 0;
};
showArButton(canAr());
// 'webxr' can join after boot: follow the modes, in the same task as the read above.
session.on('ar:modes', () => showArButton(canAr()), { signal });
// 'error' also comes after the call resolved, when Scene Viewer bounces back late.
session.on('ar:status', ({ status }) => {
  if (status === 'error') toast('AR did not start on this phone.');
}, { signal });
// In the button's click handler (the shopper's gesture):
const report = await session.activateAR({ glbUrl: files.glb, usdzUrl: files.usdz, title: 'Pergola' });
// An escape sheet (an app shell) resolves 'unsupported' but keeps 'webxr' listed: keep the
// control there; any other 'unsupported' (e.g. no hosted file for the native viewer) hides it.
if (!report.launched && report.reason === 'unsupported') {
  showArButton(session.info().ar.modes.includes('webxr'));
}
// A pick still loading is not in Quick Look's or Scene Viewer's copy: keep the control off
// until the shopper's writes have committed.
let loading = 0;
export async function pick(group: string, material: string): Promise<void> {
  loading++;
  showArButton(false);
  try {
    await session.select(group, material);
  } finally {
    if (--loading === 0) showArButton(canAr());
  }
}
```

## Generic-API test (spec 3.1)

| Rule | Result |
| --- | --- |
| 1. Neutral name | pass: `activateAR` names no product |
| 2. A precedent | pass: [model-viewer (ar, camera-orbit, auto-rotate)](https://modelviewer.dev/docs/) (row `G18`); [Unity Animator.Rebind](https://docs.unity3d.com/ScriptReference/Animator.Rebind.html) (row `G18`); [Google Scene Viewer intent (title, resizable)](https://developers.google.com/ar/develop/scene-viewer) (row `G59`) |
| 3. Two apps, or labs | pass: 24 apps (ledger: app-04c2774145, app-4762caacdc, app-4f16d2f6da, app-ceb4eccc72, app-f9a32142f1, app-fb16dd1985, app-fc07850558, app-fd09090420; fleet cross-check: app-04c2774145, app-37356bf09f, app-4762caacdc, app-4f16d2f6da, app-4fd23bc869, app-510d750508, app-59c5b5751e, app-822bb9d6aa and 16 more) |
| 4. The cheaper fix first | decided per need in Appendix B: rows `G18`, `G59` at tier stable (a member only where no rule, data, asset or app code serves the need) |
