# Swift SDK

`BirdRealtime` is the official Swift client for [Bird Realtime](/docs/guides/realtime/overview): it holds a WebSocket to the Bird edge, subscribes to channels, and delivers events as they are published. It is hand-written rather than generated, because Realtime is a WebSocket data plane rather than a REST surface, and it speaks the same wire dialect as the [browser client](/docs/guides/realtime/overview).

It has no dependencies (`URLSessionWebSocketTask` for transport, `JSONSerialization` for frames) and runs on iOS 15+, macOS 12+, tvOS 15+, watchOS 8+, and Linux.

This client is the **receiving** side. Publishing events, authorizing channels, and terminating connections are server-side operations on the Bird API; see [publishing events](/docs/guides/realtime/publishing-events).

## Install

In Xcode, use File › Add Package Dependencies with the repository URL. In a `Package.swift`:

```swift
dependencies: [
    .package(url: "https://github.com/messagebird/bird-sdk-swift.git", from: "0.1.0")
]
```

Then add the product to the target that uses it:

```swift
.target(
    name: "YourApp",
    dependencies: [.product(name: "BirdRealtime", package: "bird-sdk-swift")]
)
```

## Construct a client

```swift
import BirdRealtime

let bird = BirdRealtime(options: .init(
    appKey: "your-app-key",
    region: "us1"
))
```

Only `appKey` is required, plus either `region` (`us1` or `eu1`, which resolves the edge host) or an explicit `wsHost`. The app key is public and ships in client code: it identifies the app, it does not authorize anything. Handlers run on the main queue unless you pass a `deliveryQueue`.

## Subscribe to a channel

```swift
let orders = bird.subscribe("orders")
orders.bind("order-updated") { data in
    print("order changed:", data ?? "")
}
```

A channel whose name has no `private-` or `presence-` prefix is [public](/docs/guides/realtime/public-channels): any client with the app key can subscribe, so treat its contents as readable by anyone who can open your app.

## Private and presence channels

[Private](/docs/guides/realtime/private-channels) and [presence](/docs/guides/realtime/presence-channels) channels are authorized by your backend, which holds the app secret. Point the client at your endpoint: it POSTs `{"connection_id", "channel_name"}` and expects `{"auth", "member_data"?}` back.

```swift
let bird = BirdRealtime(options: .init(
    appKey: "your-app-key",
    region: "us1",
    authEndpoint: URL(string: "https://your-backend.example.com/bird/auth")!,
    authHeaders: ["authorization": "Bearer <session token>"]
))

guard let room = bird.subscribe("presence-room-42") as? PresenceChannel else { return }
room.bind(BirdProtocol.Event.subscriptionSucceeded) { _ in
    print("me:", room.myId ?? "?", "members:", Array(room.members.keys))
}
room.bind(BirdProtocol.Event.memberAdded) { member in
    print("joined:", member ?? "")
}
```

Pass an `authorizer` closure instead to sign through your own networking stack.

A rejection from the server (a bad signature, an app over capacity) arrives on the connection rather than the channel, because the wire carries no channel attribution. Observe those with `onError`. A failure inside your authorizer does emit `bird:subscription_error` on the channel.

## Sign in a member

`signin()` tells the edge which member this connection belongs to. That identity is what lets the API [address a member](/docs/guides/realtime/sending-events-to-a-member) or [terminate their connections](/docs/guides/realtime/terminating-member-connections), and it is what satisfies an app configured to require [authorized connections](/docs/guides/realtime/authorized-connections).

```swift
let bird = BirdRealtime(options: .init(
    appKey: "your-app-key",
    region: "us1",
    memberAuthEndpoint: URL(string: "https://your-backend.example.com/bird/auth/member")!
))

let me = try await bird.signin()
print("signed in as", me.memberId)

bird.member.bind("order.shipped") { data in
    print("addressed to me:", data ?? "")
}
```

Call `signin()` once. The identity lives on the connection, so the client signs in again automatically after a reconnect; a failed re-signin has no call to throw from and surfaces on `onSigninError` instead, which keeps a failing member endpoint from disturbing channel subscriptions.

## Connection lifecycle

```swift
bird.onConnectionStateChange { previous, current in
    print("connection: \(previous) → \(current)")
}
bird.onError { error in
    if error.code == 4009 { print("session ended elsewhere") }
}
```

Reconnection is automatic, with full-jitter exponential backoff from 1s to a 30s cap. Close codes in 4000–4099 are refusals and terminal: the client stops and reports the code, because a refusal is a decision rather than a transient failure. Codes in 4200–4299 reconnect immediately; anything else backs off. Channels re-subscribe with fresh authorization on every reconnect. See [connection lifecycle](/docs/guides/realtime/connection-lifecycle).

Transport is TLS for every non-loopback host. `allowInsecure` permits `ws://` only for `localhost`, `127.0.0.1` and `[::1]`, so a config copied from a development target cannot silently downgrade production traffic.

## Client events

With the app's client-events setting enabled, a client subscribed to a private or presence channel can publish to its peers directly:

```swift
try room.trigger("client-typing", data: ["on": true])
```

See [client events](/docs/guides/realtime/client-events).

## Source

[github.com/messagebird/bird-sdk-swift](https://github.com/messagebird/bird-sdk-swift), MIT licensed. Report issues there.