jvdvleuten/phoenixnectar
Actor-owned runtime • typed push & event helpers • reconnect/rejoin • async streams
Requirements
| Platform | Minimum | | --------- | ------- | | Swift | 6.1+ | | iOS | 16+ | | macOS | 15+ | | tvOS | 18+ | | watchOS | 11+ |
Install
.package(url: "https://github.com/jvdvleuten/PhoenixNectar.git", from: "0.1.0")Quickstart
import PhoenixNectar
struct ChatMessage: Codable, Sendable {
let body: String
}
let client = try Socket(endpoint: "wss://api.example.com/socket")
try await client.connect()
let channel = try await client.joinChannel("room:lobby")
// Subscribe to inbound events
let posted = Event<ChatMessage>("message:posted")
Task {
for try await message in await channel.subscribe(to: posted) {
print(message.body)
}
}
// Push a typed event and await the reply
let send = Push<ChatMessage, ChatMessage>("message:send")
let reply = try await channel.push(send, payload: ChatMessage(body: "hello"))
print(reply.body)For most apps, this is enough:
connect()joinChannel("room:lobby")subscribe(to: Event<T>)push(Push<Req, Resp>, payload: ...)
Use joinChannel(..., params: ...) only when your server expects a phx_join payload. Use authTokenProvider and connectParamsProvider only when your socket connect flow needs them.
Contents
Why This Library
async/awaitinstead of callback-heavy channel APIs- Typed
PushandEventhelpers for compile-time safety - Actor-owned runtime state with Swift 6 strict concurrency
- Reconnect, heartbeat, and automatic rejoin built in
- Compact public API with lower-level APIs still available when needed
Core Behavior
| Behavior | Detail | | --- | --- | | Provider closures | Re-evaluated on connect and reconnect | | Transport loss | Triggers reconnect with configured backoff | | Rejoin | Joined topics are automatically rejoined after reconnect | | Heartbeat timeout | Follows the same reconnect path | | Clean close (1000) | Reconnects by default; disable with reconnectOnCleanClose: false | | Explicit disconnect() | Treated as intentional — no auto-reconnect | | State observation | connectionStateStream() emits lifecycle transitions |
Authentication and Join Params
| Parameter | Purpose | | --- | --- | | authTokenProvider | Phoenix auth tokens — re-evaluated on each connect/reconnect | | connectParamsProvider | Socket-level metadata (device ID, locale, etc.) | | joinChannel(..., params:) | Channel-specific phx_join payloads only |
Connection State
for await state in await client.connectionStateStream() {
print(state) // .connecting, .connected, .disconnected(reason)
}Raw Replies and Binary Pushes
Use typed Push/Event first. Drop to raw replies only when needed:
let reply = try await channel.push("message:send", payload: ["body": "hello"])
guard reply.status == .ok else {
throw PhoenixError.serverError(reply)
}Binary channel payloads:
let reply = try await channel.pushBinary("binary:upload", data: bytes)Demo
Start the local Phoenix backend:
./scripts/run-demo-backend.shThen:
- Open
http://127.0.0.1:4000/chat_demo.htmlfor the browser demo - Open
demos/ChatShowcaseApp/ChatShowcaseApp.xcodeprojfor the iOS demo
Default demo endpoint: ws://127.0.0.1:4000/socket
Docs
| Document | Description | | --- | --- | | API Reference | Full public API with signatures and examples | | Examples | Short reference snippets for common tasks | | E2E Harness | Phoenix backend for integration testing | | Contributing | Development setup and PR guidelines | | Changelog | Version history |
Package Metadata
Repository: jvdvleuten/phoenixnectar
Default branch: main
README: README.md