# Single-page apps and mobile

> Use AgentGate from React, Vue and other component frameworks, and inside mobile WebViews.

The SDK is one script with no dependencies and one global, `AgentGate`. It
works the same in server-rendered pages and single-page apps; what changes
is when you create and clean up widgets.

## Load the script once

Load `https://agentgate.example/sdk/v1/agentgate.js` once per page load, for
example in your HTML shell, and call `AgentGate.init()` as early as you
can. Loading it a second time does nothing: the bundle returns early when
`window.AgentGate` is already present.

```js
AgentGate.init({ siteKey: "site_…", endpoint: "https://agentgate.example" });
```

`init` starts the document-wide interaction counters that the check scores.
They run for the life of the page, across route changes, and `init` may be
called more than once (later calls only update `siteKey` and `endpoint`).

## Forms your code submits: use execute()

When your component sends the request itself (`fetch`, a GraphQL mutation, a
form library), call `AgentGate.execute()` in the submit handler and send the
token with the request. There is nothing to mount or unmount.

```jsx title="React"
function ContactForm() {
  async function onSubmit(e) {
    e.preventDefault();
    try {
      const { token } = await AgentGate.execute({ action: "submit" });
      await fetch("/api/contact", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-AgentGate-Token": token },
        body: JSON.stringify(Object.fromEntries(new FormData(e.currentTarget))),
      });
    } catch (err) {
      // err.code: rate_limited, challenge_failed, canceled, timeout, network, …
      showError(err.code);
    }
  }
  return <form onSubmit={onSubmit}>{/* fields */}<button>Send</button></form>;
}
```

```js title="Vue"
async function onSubmit(event) {
  const { token } = await AgentGate.execute({ action: "submit" });
  await fetch("/api/contact", {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-AgentGate-Token": token },
    body: JSON.stringify(Object.fromEntries(new FormData(event.target))),
  });
}
// <form @submit.prevent="onSubmit">…</form>
```

Call `execute()` at submit time, not when the component mounts: a token is
single use and expires after two minutes by default.

## Visible widgets: render() and remove()

To show the managed chip or the interactive Verify button inside a
component, render the widget when the component mounts and remove it when it
unmounts:

```jsx title="React"
function Widget({ onToken }) {
  const ref = useRef(null);
  useEffect(() => {
    const id = AgentGate.render(ref.current, {
      siteKey: "site_…", action: "submit", mode: "managed",
      callback: (token) => onToken(token),
      expiredCallback: () => onToken(""),
    });
    return () => AgentGate.remove(id);
  }, []);
  return <div ref={ref} />;
}
```

```js title="Vue"
// <div ref="host"></div>
onMounted(() => {
  id = AgentGate.render(host.value, { siteKey: "site_…", action: "submit", mode: "managed" });
});
onBeforeUnmount(() => AgentGate.remove(id));
```

What the lifecycle guarantees:

- `remove(id)` cancels a verification in progress and takes back everything
  the widget added: its submit handler, the `agentgate_token` field it
  created (a field your page provides is kept), its elements, its timers and
  the attributes it set on the host element.
- React StrictMode's mount, unmount, mount sequence is safe.
- Rendering into an element that already holds a widget replaces it, so a
  host never carries two widgets.
- `reset(id)` and `remove(id)` with an id that no longer exists do nothing.

## Route changes

Tokens are not tied to a URL within your origin, so a route change does not
invalidate anything. The origin matters: the page's origin must be in the
site's allowed origins.

## Mobile WebViews

Inside a WebView (Android `WebView`, iOS `WKWebView`, Capacitor, React
Native WebView) the page runs the JavaScript SDK like any browser, with two
conditions:

- the page must be served from an origin in the site's allowed origins.
  Pages loaded from `file://` or a custom scheme send no usable `Origin`
  and get `origin_denied`;
- JavaScript, `fetch` and Web Workers must be enabled (without workers the
  proof of work runs on the main thread, which is slower).

> [!WARNING]
> AgentGate has not yet been measured on mobile hardware or in WebViews, and
> there is no native iOS or Android SDK. Start such a site in monitor mode
> and check the would-have decisions for your app's traffic before you
> enforce.

Native clients that do not render a page (a mobile app calling your API
directly) have no browser evidence to give. Protect those routes with the
[gateway](/docs/gateway) as machine routes (rate policy, rules and agent
identity, no browser token).
