# Connect flows

`connect()` covers the common case: your agent runs outside the user's browser (a CLI, an IDE plugin, a server-side worker), shows the user a short code, and waits while they authorize. This page covers everything around that: driving the prompt yourself, keeping a stable install identity, and the non-blocking primitives for agents that can't wait.

## Drive the prompt yourself

By default the SDK prints the code and URL to stdout and opens the OS browser. If you have your own UI, pass `onUserPrompt`:

<Tabs syncKey="lang">
  <TabItem label="TypeScript">
    ```typescript
    const connection = await client.connect({
      onUserPrompt: ({ code, url }) => {
        myUi.show(`Code: ${code}`);
        myBrowser.open(url);
      },
    });
    ```
  </TabItem>
</Tabs>

The code is what binds the consent flow to the device the user is actually on. Always show it; the user types it on the connect screen.

## Reuse the install identity

`deviceKey` identifies the install across reconnects. Persist it if you want the same user and machine to appear as the same install instead of a fresh one every time:

<Tabs syncKey="lang">
  <TabItem label="TypeScript">
    ```typescript
    // First run
    const conn = await client.connect();
    saveSomewhere(conn.deviceKey);

    // Later
    const client2 = new WireClient({
      agentId: 'my-agent',
      deviceKey: loadSomewhere(),
    });
    const conn2 = await client2.connect();
    ```
  </TabItem>
</Tabs>

Local file, OS keychain, secrets manager, your call.

## Non-blocking connects

`connect()` holds a promise open while the user acts. Chat surfaces and turn-based harnesses often can't do that: the agent wants to show the code, end its turn, and pick the flow back up later. Use the primitives underneath instead:

<Tabs syncKey="lang">
  <TabItem label="TypeScript">
    ```typescript
    // Turn 1: start the handshake, show the code and URL, persist the handle
    const pending = await client.beginConnect({ label: 'my-laptop' });
    myUi.show(`Code: ${pending.userCode}. Open: ${pending.url}`);
    save(pending); // plain JSON, safe to stash anywhere

    // Any later turn: a single poll, no waiting
    const connection = await client.checkConnection(load());
    if (connection) {
      save(connection);
    }
    ```
  </TabItem>
</Tabs>

`beginConnect()` returns a `PendingConnection` handle: the user code, the connect-screen URL, and everything the SDK needs to resume, including the device key. It survives a JSON round-trip, so persist it however you persist anything else.

`checkConnection(pending)` polls exactly once. It returns the `Connection` when the user has picked a container, `null` while they haven't yet, and throws `NONCE_EXPIRED` once the handshake lapses (`pending.expiresAt` tells you when, about 10 minutes).

`connect()` is just these two composed with a prompt and a 2-second poll loop, so the two styles behave identically once connected.

## From a browser app

When your agent runs in the user's browser (a webapp, an in-browser assistant, an extension), there is no second device to bind and no code to type. `connectInBrowser()` sends the user through Wire's authorization screen, where they sign in if needed and pick a container, then returns them to your page with the connection ready:

<Tabs syncKey="lang">
  <TabItem label="TypeScript">
    ```typescript
    // Starting the flow (any page of your app):
    await client.connectInBrowser({ redirectUri: 'https://my-app.com/callback' });

    // Or keep your page open with popup mode, which resolves in place:
    const connection = await client.connectInBrowser({
      redirectUri: 'https://my-app.com/callback',
      popup: true,
    });

    // On your callback page (safe to call on every page load):
    const connection = await client.completeConnectInBrowser();
    if (connection) {
      // same Connection shape as connect()
    }
    ```
  </TabItem>
</Tabs>

Register your redirect URIs on the agent in the Wire dashboard first; the flow only redirects to URIs on that allowlist. In redirect mode your callback page finishes the connect with `completeConnectInBrowser()`; in popup mode the same call on the popup's page relays the result back to your open tab and closes the popup.

Browser connections return the same `Connection` as the device flow and behave identically afterward (`getStatus`, `claim`, `disconnect`, revocable from the container's connected-agents panel). The one difference: there is no `deviceKey`, since the authorization itself is the identity. See `examples/browser-connect/` in the SDK repo for a runnable page.