# Embed the Collector

The collector is a JavaScript file you serve from your own site. You start it when the page loads, and you wait for it when the user acts. This guide assumes your edge runs at `https://octet.example.com`. See [Deploy the Edge](/docs/browser/integration/deploy-edge/).

## 1. Install the collector

Every Octet Browser file is a release asset in the public repository [octetproof/octet-browser](https://github.com/octetproof/octet-browser/releases). No registry account or access token is needed. Pin a version in everything you install. This guide uses v1.3.0.

### As a script tag

Download the script and its Subresource Integrity hash from the release:

```bash
curl -fLO https://github.com/octetproof/octet-browser/releases/download/v1.3.0/octet-collector.js
curl -fLO https://github.com/octetproof/octet-browser/releases/download/v1.3.0/octet-collector.js.sri
```

Serve `octet-collector.js` from your own origin, and put the contents of `octet-collector.js.sri` in the `integrity` attribute:

```html
<script src="/static/octet-collector.js"
        integrity="sha384-REPLACE_WITH_THE_CONTENTS_OF_octet-collector.js.sri"
        crossorigin="anonymous"></script>
```

Serving the file yourself keeps the collector first-party and covered by your own `script-src`. Don't point the `src` at the GitHub release URL. GitHub serves release assets as downloads, without the headers a browser needs to run a cross-origin script with an `integrity` check, so the script won't load.

The script defines a global `octet` object with the functions `octet.start()`, `octet.ready()` and `octet.verify()`.

### With npm or a bundler

Install the package straight from the release:

```bash
npm install https://github.com/octetproof/octet-browser/releases/download/v1.3.0/octetproof-collector-1.3.0.tgz
```

npm records the URL in your `package.json`, so every install gets the same version. Then import the functions:

```js
import { start, ready } from '@octetproof/collector';
```

The package includes TypeScript types.

## 2. Create a `sessionRef` on your backend

Each page view needs its own `sessionRef`. Your backend fetches the verdict with it, so it must be unguessable and must never be reused. Generate at least 128 random bits and encode them URL-safe:

```js
// Node.js
import { randomBytes } from 'node:crypto';
const sessionRef = randomBytes(32).toString('base64url');
```

Store the `sessionRef` in the user's server-side session, and render it into the page. The rules for session references, and why they matter, are in [The session reference](/docs/browser/concepts/how-it-works/#the-session-reference).

## 3. Start at page load, wait at the moment of action

Call `start()` as soon as the page loads, so the result is ready by the time the user acts. Call `ready()` when the user acts:

```html
<script>
  octet.start({
    apiUrl: 'https://octet.example.com',
    sessionRef: 'SESSION_REF_FROM_YOUR_BACKEND',
  });

  document.querySelector('#withdraw-form').addEventListener('submit', async (event) => {
    event.preventDefault();
    try {
      await octet.ready();
    } catch (err) {
      // Collection failed. Submit anyway, and your backend will get a 404 when it fetches the verdict.
      console.warn('octet', err);
    }
    event.target.submit();
  });
</script>
```

`ready()` resolves once your edge has passed the session to Octet, which means your backend can now fetch the verdict. Fetch it within 30 seconds of `ready()` resolving. When `ready()` resolves, the verdict can already be up to 90 seconds old, and verdicts expire at 2 minutes. See [Fetch the Verdict](/docs/browser/integration/fetch-verdict/).

If `ready()` is called more than 90 seconds after the last collection finished, it collects again before it resolves. If a collection fails, the next `ready()` call collects again. To start over with a new `sessionRef`, call `start()` again.

`ready()` resolves with `{ response, issuedAtMs, expiresAtMs, elapsedMs }`. `response` is `{ "ok": true }`. It never contains the verdict. See [Collector API](/docs/browser/reference/collector-api/).

## 4. Choose a mode

The `mode` option sets how much network measurement the collector does. The default is `full`.

| Mode | What it does | Network requests beyond your edge | Typical time to `ready()` |
|---|---|---|---|
| `full` | Everything, for the strongest results | HTTPS and UDP 3478 to three Octet network hosts | Well under a second, up to about 4.5 s on a connection tunnelled to an exit far from the user |
| `lite` | Skips the HTTPS timing requests | UDP 3478 to three Octet network hosts | Well under a second |
| `passive` | No network measurement and no WebRTC | None | Fastest |

In every mode the collector sends one POST to your edge. In `full` and `lite` it also opens a WebSocket to your edge. Every host the collector contacts is run by you or by Octet.

`lite` and `passive` give weaker results. They give Octet less evidence that a connection is masked, so fewer masked sessions reach `medium` or `high`.

```js
octet.start({ apiUrl: 'https://octet.example.com', sessionRef, mode: 'lite' });
```

Each mode's hosts, ports and CSP entries are in [Network and CSP](/docs/browser/reference/network/).

## 5. Rules the collector enforces

- **`apiUrl` must be `https://`.** On any other scheme, `start()` throws and `verify()` rejects at once. The only exception is `http://localhost`, `http://127.0.0.1` or `http://[::1]`, for local development.
- **The WebSocket URL follows `apiUrl`.** It is `wss://` on the same host, at `/v1/ws`.
- **Nothing blocks the page.** A measurement that fails or is blocked is left out, and the collector sends what it has. Of the collector's requests, only a failed POST to your edge makes `ready()` or `verify()` reject.

## Cancel a collection

Pass an `AbortSignal` to stop a collection, for example when the user leaves the page:

```js
const controller = new AbortController();
octet.start({ apiUrl: 'https://octet.example.com', sessionRef, signal: controller.signal });

// later
controller.abort();
```

Aborting stops the collection and cancels the POST to your edge if it has started. `ready()` and `verify()` then reject with an `AbortError`. If the abort comes before the POST reaches your edge, no verdict is produced for that run.

## One-shot collection

`verify()` runs one collection and resolves with your edge's reply. Use it where there is no separate moment of action, for example on a sign-in page:

```js
await octet.verify({ apiUrl: 'https://octet.example.com', sessionRef });
```

`verify()` takes the same options as `start()`.
