> ## Documentation Index
> Fetch the complete documentation index at: https://docs.salesive.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Top level handoff

> Open a first party page from inside the embedded app, wait for it to finish, and close it.

Some flows need a top level page: setting your app's own cookies, linking an
external account, or completing an install that depends on first party storage.
Inside the dashboard iframe those flows cannot set cookies reliably, so the app
has to open its own page and then come back.

`salesive-dev-tools/handoff` does the whole dance: open, wait, close, and
validate.

```js theme={null}
import { openHandoff } from "salesive-dev-tools/handoff";

button.addEventListener("click", async () => {
  try {
    const { ok, data } = await openHandoff({
      url: "https://app.example.com/link/start",
    });
    if (ok) toast.success(`Linked ${data.account}`);
  } catch (error) {
    if (error.code === "popup_blocked") {
      // Show a link to error.url instead of a popup.
    }
  }
});
```

Call it from a click. Browsers block popups that open without a user gesture.

## `openHandoff({ url, timeoutMs })`

* Opens `url` in a popup and waits for it to report completion.
* Accepts only a `salesive:handoff-complete` message whose `source` is that
  popup and whose `origin` matches the URL's origin. Anything else is ignored.
* Resolves `{ ok, data }` and closes the popup.
* Rejects with `HandoffError`:
  * `popup_blocked`, with `url` so you can offer a plain link instead.
  * `handoff_timeout`, after `timeoutMs` (five minutes by default).

## `completeHandoff({ ok, data })`

On the top level page you opened, call this when the work is done. It posts
`{ type: "salesive:handoff-complete", ok, data }` to the opener with the exact
opener origin and closes the window.

```js theme={null}
import { completeHandoff } from "salesive-dev-tools/handoff";

await linkAccount();
completeHandoff({ ok: true, data: { account: "acme" } });
```

Keep `data` small and free of secrets. It comes back into the embedded app, not
into a server.

## Why a popup and not a redirect

A redirect would navigate the merchant away from the dashboard and lose the app
state. A popup keeps the app where it is, and the merchant sees the dashboard
again the moment the page closes itself.

## When not to use it

If the flow does not need first party cookies, do it inside the iframe. This is
for the cases that genuinely need a top level page.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.