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

# Integrating into your Swift app

> How the SDK behaves from first launch to logout, and how to wire it into an app that already has users.

ClarioDesk is built to drop into an app you have already shipped. This page walks
the whole integration, from the very first launch to logout, explains what the
SDK does at each step, and covers the case most teams get subtly wrong: an app
that already has signed-in users from before ClarioDesk existed.

If you just want the fastest path to a working ticket, start with the
[quickstart](/quickstart). This page is the "why and when" behind those calls.

## The lifecycle at a glance

```mermaid theme={null}
flowchart TD
    A[App launches] --> B["ClarioDesk.initialize(.init(apiKey:))"]
    B --> C{First launch ever?}
    C -->|Yes| D["Generate Secure Enclave key,<br/>register device"]
    C -->|No| E["Reuse stored key<br/>(no network)"]
    D --> F{Is a user signed in?}
    E --> F
    F -->|Yes| G["identify(externalId:email:)"]
    F -->|No| H["Anonymous device"]
    G --> I["Live use:<br/>AsyncStreams + optimistic sends"]
    H --> I
    I --> J{Logout or switch user?}
    J -->|Yes| K["reset()"]
    K --> A
    J -->|No| I
```

Three calls drive everything above: `initialize` once at launch, `identify` when
you know who the user is, and `reset` when they leave. The SDK owns the rest:
the device key, the local cache, and the realtime connection (Centrifugo by
default, falling back to SSE).

## The mental model

The SDK is a thin client that owns four things: a device identity, a local
cache, a realtime connection, and (optionally) a set of prebuilt screens. Your
app never holds SDK state. You make a few calls in, and reads come back to you
as replay-last `AsyncStream`s (or closure subscriptions for UIKit). See
[Architecture](/concepts/architecture) for the full picture.

## 1. The first launch

`ClarioDesk.initialize()` runs once at app start — synchronous and
fire-and-forget, so it drops into `App.init` or your AppDelegate with no
`await`:

```swift theme={null}
import ClarioDesk

@main
struct MyApp: App {
  init() {
    ClarioDesk.initialize(.init(apiKey: "pk_live_…"))
  }

  var body: some Scene {
    WindowGroup { ContentView() }
  }
}
```

On the very first launch, the SDK generates a non-extractable hardware keypair
in the Secure Enclave (software P-256 fallback where the SEP is unavailable,
e.g. the simulator) and registers the device with the backend. That keypair is
the device's identity from then on, and it signs every request. On every later
launch, the stored key is reused with no network round-trip. Every subsequent
SDK call awaits the shared bootstrap internally, so nothing races the
initialization. See [Authentication](/concepts/authentication) for how the
keypair stands in for tokens.

## 2. Identify your user

Once your own auth knows who the user is, hand that down to ClarioDesk:

```swift theme={null}
try await ClarioDesk.identify(
  externalId: user.id,
  email: user.email,
  traits: ["plan": user.plan]
)
```

`identify` is pure metadata. It does not grant access (the device key already
did that), so it is safe to call often, and it is idempotent: the same values
twice is a no-op.

### When to call it

The right moment depends on the user's state when ClarioDesk first runs. Each row
below is a real case worth handling:

| Scenario                                                            | What to do                                                                                                                    |
| ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| New user signs in or signs up                                       | Call `identify` right after your auth succeeds.                                                                               |
| User was already signed in before you added (or updated to) the SDK | Call `identify` on launch from your persisted session. They never pass through login again, so a login-only hook misses them. |
| No user yet (anonymous)                                             | Skip `identify`. Tickets still work; the device shows as unverified.                                                          |
| Anonymous user later signs in                                       | Call `identify` at that point. The anonymous device and its existing tickets carry forward to the identified user.            |
| Returning signed-in user (relaunch)                                 | Call `identify` again on launch. It is idempotent, so this is a cheap no-op.                                                  |
| The user's email or traits changed                                  | Call `identify` again with the new values to update the device row.                                                           |

<Tip>
  One habit covers the whole table: identify on every launch whenever you have a
  session, and again right after a fresh login.
</Tip>

### The existing-user trap

This is the case most integrations get wrong, so it is worth slowing down on.

If you only call `identify` from your login handler, you label the users who sign
in *after* you ship and nobody else. Everyone who was already logged in when you
released the update never visits your login screen again, so they stay unlabeled
devices, and your agents see "Unverified device" instead of an email.

The fix is to identify from your persisted session on every launch, not from the
login event:

<CodeGroup>
  ```swift Identify on launch (correct) theme={null}
  ClarioDesk.initialize(.init(apiKey: "pk_live_…"))

  // Read the session your app already persisted, then identify on every launch.
  if let user = await yourAuth.currentUser() {
    try await ClarioDesk.identify(externalId: user.id, email: user.email)
  }
  ```

  ```swift Identify only on login (misses existing users) theme={null}
  // Runs only for users who sign in AFTER this build ships.
  onLoginSuccess { user in
    Task { try await ClarioDesk.identify(externalId: user.id, email: user.email) }
  }
  ```
</CodeGroup>

<Warning>
  Wire `identify` into your launch path, not just your login path. An
  "identify on login" hook silently skips every user who was already signed in
  before you added the SDK.
</Warning>

<Note>
  Skipping `identify` entirely is fine for anonymous feedback. Tickets still work;
  they just arrive without an email. When the user signs in later, call `identify`
  then and the existing device, and its ticket history, carry forward.
</Note>

## 3. Live by default

After `initialize` (and `identify`, if you have a user), reads are live. You do
not poll. Iterate a stream and the SDK primes it from cache, then pushes updates
as the agent replies.

```swift theme={null}
.task {
  do {
    for try await tickets in ClarioDesk.tickets { self.tickets = tickets }
  } catch { /* render a retry state */ }
}
```

Writes are optimistic: a message appears immediately as pending and settles when
the backend confirms (`failed == true` means render tap-to-retry via
`retryMessage`). A send made while offline waits in a durable Keychain outbox
and flushes on reconnect. Watch the realtime link with `ClarioDesk.connection`
to show an offline banner. See [Realtime](/concepts/realtime) for the
connection states.

## 4. Logout and user switching

When the user signs out, call `reset` before clearing your own session:

```swift theme={null}
await ClarioDesk.reset()
await yourAuth.signOut()
```

`reset` revokes the device server-side (best effort), wipes the hardware key,
and clears local caches. The next `initialize` registers a brand-new device. For
a user switch on the same install (A to B without a restart), do the same and
then identify B:

```swift theme={null}
await ClarioDesk.reset()
ClarioDesk.initialize(.init(apiKey: "pk_live_…"))
try await ClarioDesk.identify(externalId: userB.id, email: userB.email)
```

<Warning>
  Don't rebind one device to a second user. `reset` plus a fresh device is the
  correct boundary, because each device row carries its own ticket history.
</Warning>

## 5. Relaunches and reinstalls

| After this                   | Device identity                                                           | Ticket history                 |
| ---------------------------- | ------------------------------------------------------------------------- | ------------------------------ |
| A normal relaunch            | Same device; key reused with no network                                   | Visible to the user            |
| `reset()` (logout or switch) | Wiped; the next `initialize()` makes a fresh device                       | Detached from this device      |
| An uninstall and reinstall   | The stored key may be gone, so `initialize()` can register a fresh device | Starts clean on the new device |

The takeaway: identity is anchored to the hardware key on a single install. It is
not a cloud account that follows the user between devices, so always re-`identify`
on a fresh device to reattach their email and traits.

## Your integration checklist

For an app already in production:

* [ ] `initialize` runs at app start, before any support screen.
* [ ] `identify` runs on launch from your persisted session, not only on login.
* [ ] `reset` runs on logout and on user switch.
* [ ] Tickets render from `ClarioDesk.tickets` / `ClarioDesk.messages(ticketId:)` (or the prebuilt UI).
* [ ] Optional: `ClarioDesk.connection` drives an offline indicator.

## Where to go next

<CardGroup cols={2}>
  <Card title="Lifecycle event reference" icon="user-check" href="/swift/lifecycle">
    The same four events as a tight code reference.
  </Card>

  <Card title="API reference" icon="code" href="/swift/api-reference">
    Every method, stream, and type.
  </Card>

  <Card title="Authentication" icon="shield-check" href="/concepts/authentication">
    Why the device key replaces tokens.
  </Card>

  <Card title="Push (no Firebase)" icon="bell" href="/swift/push">
    APNs-direct push: one line in your AppDelegate.
  </Card>
</CardGroup>
