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

# @ave-id/embed

> UI integration helpers for Ave auth, Connector, and Signing flows, including iframe, sheet, and popup patterns.

```bash theme={null}
bun add @ave-id/embed
```

The embed library provides inline, sheet, and popup presentations for Ave flows. It accepts callbacks only from its configured Ave origin and the iframe or popup belonging to that flow.

For sign-in, `startAveAuth`, `mountAveEmbed`, `openAveSheet`, and `openAvePopup` return promises. Await the result before using its controls. Connector and signing helpers return their controls synchronously.

Provide `onTokens` to handle PKCE, state, nonce, and the token exchange automatically. Granted encryption keys, previous keys, and the `app_key_reset` recovery flag are included from the callback fragment. Profile and email fields depend on the granted scopes.

```ts theme={null}
import { startAveAuth } from "@ave-id/embed";

const auth = await startAveAuth({
  redirectUri: "https://yourapp.com/callback",
  onTokens: (tokens) => createSession(tokens),
  onError: ({ message }) => showError(message),
});
```

Omit `clientId` in `startAveAuth` to use Quick Ave for your callback origin. Supply `onSuccess` instead of `onTokens` for your own callback and code exchange. Public clients using `onSuccess` must persist their PKCE verifier, state, and nonce themselves.

## UI patterns

<CardGroup cols={3}>
  <Card title="Inline embed" icon="window-maximize">
    Mount an iframe directly in a container element. Stays in page, no overlay. Good for dedicated auth pages.
  </Card>

  <Card title="Sheet">
    Slides up from the bottom as a fixed modal overlay. Good for triggering login from any page.
  </Card>

  <Card title="Popup">
    Opens a new browser window. Works well on desktop. Returns `null` if the browser blocks the popup.
  </Card>
</CardGroup>

***

## Auth flows

### `mountAveEmbed(options)`

Mounts an Ave auth iframe inside a container element. The iframe stays visible while the user completes login.

```ts theme={null}
function mountAveEmbed(options: MountAveEmbedOptions): Promise<{
  iframe: HTMLIFrameElement;
  destroy: () => void;
  postMessage: (payload: unknown) => void;
}>
```

<ParamField path="options.container" type="HTMLElement" required>
  The DOM element that will contain the iframe.
</ParamField>

<ParamField path="options.clientId" type="string" required>
  Your app's client ID.
</ParamField>

<ParamField path="options.redirectUri" type="string" required>
  Registered redirect URI.
</ParamField>

<ParamField path="options.scope" type="string" default="&#x22;openid profile email&#x22;">
  Space-separated scopes.
</ParamField>

<ParamField path="options.onSuccess" type="(payload) => void">
  Called when the user completes login. `payload.redirectUrl` is the callback URL with the authorization code.

  ```ts theme={null}
  onSuccess: (payload) => {
    // Extract code from payload.redirectUrl and exchange it
    const url = new URL(payload.redirectUrl);
    const code = url.searchParams.get("code");
  }
  ```
</ParamField>

<ParamField path="options.onError" type="(payload) => void">
  Called on error. `payload.error` is the error string, `payload.message` is a human-readable description.
</ParamField>

<ParamField path="options.onClose" type="() => void">
  Called when the user closes the embed without completing auth.
</ParamField>

```ts theme={null}
import { mountAveEmbed } from "@ave-id/embed";

const { destroy } = await mountAveEmbed({
  container: document.getElementById("auth-container")!,
  clientId: "YOUR_CLIENT_ID",
  redirectUri: "https://yourapp.com/callback",
  scope: "openid profile email",
  onSuccess: async ({ redirectUrl }) => {
    const code = new URL(redirectUrl).searchParams.get("code")!;
    const tokens = await exchangeCode({ clientId, redirectUri }, { code, codeVerifier });
    // Create your session
  },
  onError: ({ error, message }) => {
    console.error("Auth error:", error, message);
  },
  onClose: () => {
    console.log("User closed auth");
  },
});

// When done
destroy();
```

***

### `openAveSheet(options)`

Opens a full-width sheet overlay from the bottom. Non-blocking — the user can dismiss it.

```ts theme={null}
function openAveSheet(options: OpenAveSheetOptions): Promise<{
  close: () => void;
  iframe: HTMLIFrameElement;
}>
```

The sheet mounts to the document body and supports the same auth callbacks and PKCE options as the inline embed.

When Ave requires a full browser context, the sheet opens a popup. If blocked, an `onTokens` flow reports `popup_blocked` so the app can invite the user to allow popups and retry. An application-managed `onSuccess` flow redirects the current page to Ave; persist callback state before opening it.

```ts theme={null}
import { openAveSheet } from "@ave-id/embed";
import { generateCodeVerifier, generateCodeChallenge, exchangeCode } from "@ave-id/sdk";

async function loginWithSheet() {
  const verifier = generateCodeVerifier();
  const challenge = await generateCodeChallenge(verifier);

  const { close } = await openAveSheet({
    clientId: "YOUR_CLIENT_ID",
    redirectUri: "https://yourapp.com/callback",
    scope: "openid profile email",
    codeChallenge: challenge,
    codeChallengeMethod: "S256",
    onSuccess: async ({ redirectUrl }) => {
      close();
      const code = new URL(redirectUrl).searchParams.get("code")!;
      const tokens = await exchangeCode({ clientId, redirectUri }, { code, codeVerifier: verifier });
      // Create session
    },
    onError: ({ error }) => {
      close();
      showError(error);
    },
  });
}
```

***

### `openAvePopup(options)`

Opens a popup window for Ave auth. Returns `null` if the browser blocked the popup — always handle that case.

```ts theme={null}
function openAvePopup(options: OpenAvePopupOptions): Promise<{
  popup: Window;
  close: () => void;
} | null>
```

Additional options beyond sheet:

<ParamField path="options.width" type="number" default="450">
  Popup window width in pixels.
</ParamField>

<ParamField path="options.height" type="number" default="650">
  Popup window height in pixels.
</ParamField>

```ts theme={null}
import { openAvePopup } from "@ave-id/embed";

const result = await openAvePopup({
  clientId: "YOUR_CLIENT_ID",
  redirectUri: "https://yourapp.com/callback",
  onSuccess: async ({ redirectUrl }) => {
    // Handle success
  },
});

if (!result) {
  // Popup was blocked — fall back to sheet or redirect
  openAveSheet(/* ... */);
}
```

***

## Connector flows

### `openAveConnectorSheet(options)` / `openAveConnectorPopup(options)`

Opens `/connect` for Connector consent in a sheet or popup. Both helpers return controls synchronously.

```ts theme={null}
function openAveConnectorSheet(options: OpenAveConnectorOptions): {
  close: () => void;
  iframe: HTMLIFrameElement;
}
```

<ParamField path="options.resource" type="string" required>
  The target resource key.
</ParamField>

<ParamField path="options.scope" type="string">
  The resource scope(s) to request.
</ParamField>

<ParamField path="options.mode" type="string" default="&#x22;user_present&#x22;">
  Communication mode: `"user_present"` or `"background"`.
</ParamField>

```ts theme={null}
import { openAveConnectorSheet } from "@ave-id/embed";

const { close } = openAveConnectorSheet({
  clientId: "YOUR_CLIENT_ID",
  redirectUri: "https://yourapp.com/callback",
  resource: "target-resource-key",
  scope: "resource.read",
  mode: "user_present",
  onSuccess: async ({ redirectUrl }) => {
    close();
    // Exchange code for source token, then do token-exchange for delegated token
  },
  onError: ({ error }) => {
    close();
  },
});
```

***

### `openAveConnectorRuntime(options)`

Mounts a Connector runtime iframe for active connector sessions where the source app communicates with the target resource UI.

```ts theme={null}
function openAveConnectorRuntime(options: OpenAveConnectorRuntimeOptions): {
  iframe: HTMLIFrameElement;
  send: (payload: unknown) => void;
  destroy: () => void;
}
```

<ParamField path="options.delegatedToken" type="string" required>
  The delegated access token from the token-exchange grant.
</ParamField>

<ParamField path="options.container" type="HTMLElement">
  DOM element to mount the runtime iframe in.
</ParamField>

<ParamField path="options.onReady" type="() => void">
  Called when the runtime iframe is ready to receive messages.
</ParamField>

<ParamField path="options.onEvent" type="(payload) => void">
  Called for events sent from the runtime iframe.
</ParamField>

***

## Signing flows

### `openAveSigningSheet(options)`

Opens the signing UI as a sheet overlay and includes the embedding origin for result delivery.

```ts theme={null}
function openAveSigningSheet(options: OpenAveSigningSheetOptions): {
  close: () => void;
  iframe: HTMLIFrameElement;
}
```

<ParamField path="options.requestId" type="string" required>
  The signing request ID from `createSignatureRequest`.
</ParamField>

<ParamField path="options.onSigned" type="(payload) => void">
  Called when the user approves and signs.
</ParamField>

<ParamField path="options.onDenied" type="(payload) => void">
  Called when the user explicitly denies the request.
</ParamField>

<ParamField path="options.onClose" type="() => void">
  Called when the user closes the sheet without acting.
</ParamField>

```ts theme={null}
import { openAveSigningSheet } from "@ave-id/embed";
import { createSignatureRequest, verifySignature } from "@ave-id/sdk";

// 1. Create request on your server first
const { requestId } = await fetch("/api/signing/create", {
  method: "POST",
  body: JSON.stringify({ action: "approve_transfer" }),
}).then(r => r.json());

// 2. Present signing UI
const { close } = openAveSigningSheet({
  requestId,
  onSigned: async (payload) => {
    close();
    // Verify and execute the approved action server-side
    await fetch("/api/signing/confirm", {
      method: "POST",
      body: JSON.stringify({ requestId }),
    });
  },
  onDenied: () => {
    close();
    showMessage("Request was denied");
  },
  onClose: () => {
    close();
    showMessage("Request was cancelled");
  },
});
```

### `openAveSigningPopup(options)`

Same as sheet but in a popup window.

```ts theme={null}
function openAveSigningPopup(options: OpenAveSigningPopupOptions): {
  popup: Window;
  close: () => void;
} | null
```

Returns `null` if the popup was blocked. Fall back to sheet.

***

## Security rules

<Warning>
  Always validate `postMessage` event origins. Only trust events from your configured Ave issuer origin (`https://aveid.net` by default). The embed library handles this internally. If you listen for `message` events manually, validate both `event.origin` and `event.source` against the iframe or popup you opened.
</Warning>

* Always call `destroy()` or `close()` when the component unmounts to remove event listeners and clean up iframes
* Handle success, error, and dismissal. `onClose` fires once for dismissal; successful or failed completion does not also call it
* Do not use popups as the primary UX on mobile — fall back to sheet
* The embed does not work inside sandboxed iframes that restrict credentials or passkey access
