### Setup Ably Chat Swift SDK Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md Commands to set up the project, including updating git submodules, bootstrapping dependencies with mint, and installing npm packages. ```bash git submodule update --init mint bootstrap # Takes ~5 minutes first time npm install ``` -------------------------------- ### Connect, Subscribe, and Send Messages with Ably Chat Swift SDK Source: https://github.com/ably/ably-chat-swift/blob/main/README.md Example demonstrating how to initialize the Ably client, create a chat client, get a chat room, attach to it, subscribe to messages, and send a message. Ensure you replace placeholder values like '' and 'your-client-id'. ```swift import Ably import AblyChat // Initialize Ably Realtime client let realtimeOptions = ARTClientOptions() realtimeOptions.key = "" realtimeOptions.clientId = "your-client-id" let realtime = ARTRealtime(options: realtimeOptions) // Create a chat client let chatClient = ChatClient(realtime: realtime, clientOptions: ChatClientOptions()) // Get a chat room let room = try await chatClient.rooms.get(named: "my-room", options: RoomOptions()) // Monitor room status room.onStatusChange { statusChange in switch statusChange.current { case .attached: print("Room is attached") case .detached: print("Room is detached") case .failed(let error): print("Room failed: \(error)") default: print("Room status: \(statusChange.current)") } } // Attach to the room try await room.attach() // Subscribe to messages let subscription = room.messages.subscribe { event in print("Received message: \(event.message.text)") } // Send a message try await room.messages.send(withParams: SendMessageParams(text: "Hello, World!")) ``` -------------------------------- ### Build Ably Chat Example App Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md Command to build the example application for a specified platform (iOS, macOS, or tvOS). ```bash swift run BuildTool build-example-app --platform ``` -------------------------------- ### Install Node.js Dependencies Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md Installs project dependencies using npm. Ensure Node.js is installed and available in your PATH. ```bash npm install ``` -------------------------------- ### Bootstrap Dependencies with Mint Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md Installs project dependencies using the Mint package manager. This command may take a significant amount of time on the first run. ```bash mint bootstrap ``` -------------------------------- ### Install Ably Chat Swift SDK Source: https://context7.com/ably/ably-chat-swift/llms.txt Add the Ably Chat Swift SDK to your project's Package.swift file to include it as a dependency. ```swift .package(url: "https://github.com/ably/ably-chat-swift", from: "1.2.0"), ``` -------------------------------- ### Get a Room with Custom Options Source: https://context7.com/ably/ably-chat-swift/llms.txt Retrieve a chat room with specific configurations for messages, presence, typing indicators, and occupancy. ```swift // Get a room with all features configured let room = try await chatClient.rooms.get( named: "support-chat-007", options: RoomOptions( messages: MessagesOptions( rawMessageReactions: false, // enable to subscribe to individual reaction events defaultMessageReactionType: .distinct ), presence: PresenceOptions(enableEvents: true), // receive presence events typing: TypingOptions(heartbeatThrottle: 5), // seconds between typing heartbeats occupancy: OccupancyOptions(enableEvents: true) // receive occupancy updates ) ) ``` -------------------------------- ### Get a Room with Default Options Source: https://context7.com/ably/ably-chat-swift/llms.txt Obtain a chat room using default options, which includes enabling message subscriptions. ```swift // Get a room with default options (messages always enabled) let room = try await chatClient.rooms.get(named: "livestream-room-42") ``` -------------------------------- ### Validate Website Doc Snippets Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md This prompt guides the validation of Swift code snippets in website documentation against the SDK source code. It details steps for finding, analyzing, and cross-checking snippets for accuracy. ```text Verify all `swift` annotated code snippets in `.mdx` files located at `{DOCS_PATH}/src/pages/docs/chat` against the `ably-chat-swift` source code repository at `{SDK_PATH}`. ### Verification Steps: 1. **Find all code snippets**: Search for all code blocks with the `swift` annotation in `.mdx` files. 2. **Understand SDK structure**: Analyze the SDK source code to understand: - Public classes and their constructors - Public methods and their signatures (parameters, return types) - Public properties and their types - Enums and their values - Namespaces and import requirements 3. **Cross-check each snippet** for the following issues: - **Syntax errors**: Incorrect initializer syntax (e.g., missing argument labels, incorrect use of `try`/`await`), misuse of optional unwrapping (`!` vs `if let`/`guard let`), incorrect protocol conformance signatures, misplaced braces or parentheses - **Naming conventions**: Verify casing matches Swift conventions (e.g., `UpperCamelCase` for types, protocols, and enums; `lowerCamelCase` for methods, properties, and enum cases) - **API accuracy**: Verify method names, property names, and enum values exist in the SDK (e.g., correct `init()` signatures, protocol method names, `Optional` usage) - **Type correctness**: Verify correct types are used (e.g., `ConnectionEvent` vs `ConnectionState`) - **Namespace/import requirements**: Note any required imports that are missing from examples - **Wrong language**: Detect if code from another language was accidentally used 4. **Generate a verification report** with: - Total snippets found - List of issues found with: - File path and line number - Current (incorrect) code - Expected (correct) code - Source reference in SDK - List of verified APIs that are correct - Success rate percentage - Recommendations for fixes ### Output Format: Create/update a markdown report file `chat_swift_api_verification_report.md` with all findings. ``` -------------------------------- ### Attribute Tests to Spec Points with Tags Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md Examples of how to attribute test cases to specific points in the Chat SDK features spec using comment tags like @spec, @specOneOf, and @specPartial. This helps in tracking spec coverage. ```swift // @spec CHA-EX3f func test1 { … } ``` ```swift // @specOneOf(1/2) CHA-EX2h — Tests the case where the room is FAILED func test2 { … } ``` ```swift // @specOneOf(2/2) CHA-EX2h — Tests the case where the room is SUSPENDED func test3 { … } ``` ```swift // @specPartial CHA-EX1h4 - Tests that we retry, but not the retry attempt limit because we've not implemented it yet func test4 { … } ``` -------------------------------- ### Attach and Detach Room in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Attach a room to start receiving real-time events. Attaching may throw an error on non-retriable failures. Detach the room when it's no longer needed. ```swift do { try await room.attach() } catch { print("Attach failed: \(error.message)") } try await room.detach() ``` -------------------------------- ### ChatClient Initialization Source: https://context7.com/ably/ably-chat-swift/llms.txt Initialize the ChatClient, which is the main entry point for all chat functionalities. It requires an ARTRealtime instance and can be configured with ChatClientOptions for logging. ```APIDOC ## ChatClient — Initialize the chat client `ChatClient` is the entry point for all chat functionality. It wraps an `ARTRealtime` instance and exposes `rooms` and `connection`. Pass `ChatClientOptions` to configure logging. ```swift import Ably import AblyChat // Configure the underlying Ably Realtime client let realtimeOptions = ARTClientOptions() realtimeOptions.key = "YOUR_ABLY_API_KEY" realtimeOptions.clientId = "user-42" let realtime = ARTRealtime(options: realtimeOptions) // Custom log handler (optional) struct MyLogger: LogHandler.Simple { func log(message: String, level: LogLevel) { print("[\(level)] \(message)") } } let chatOptions = ChatClientOptions( logHandler: .simple(MyLogger()), logLevel: .debug // .trace | .debug | .info | .warn | .error | nil (disable) ) // Create the chat client let chatClient = ChatClient(realtime: realtime, clientOptions: chatOptions) // Access the clientID (available immediately when using an API key) print(chatClient.clientID ?? "unknown") // Access the underlying realtime client let _ = chatClient.realtime ``` ``` -------------------------------- ### Build Ably Chat Documentation Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md Command to build the documentation for the Ably Chat SDK. ```bash swift run BuildTool build-documentation ``` -------------------------------- ### Initialize ChatClient Source: https://context7.com/ably/ably-chat-swift/llms.txt Initialize the ChatClient, which is the main entry point for chat functionality. Configure the underlying Ably Realtime client and optionally provide custom logging options. ```swift import Ably import AblyChat // Configure the underlying Ably Realtime client let realtimeOptions = ARTClientOptions() realtimeOptions.key = "YOUR_ABLY_API_KEY" realtimeOptions.clientId = "user-42" let realtime = ARTRealtime(options: realtimeOptions) // Custom log handler (optional) struct MyLogger: LogHandler.Simple { func log(message: String, level: LogLevel) { print("[\(level)] \(message)") } } let chatOptions = ChatClientOptions( logHandler: .simple(MyLogger()), logLevel: .debug // .trace | .debug | .info | .warn | .error | nil (disable) ) // Create the chat client let chatClient = ChatClient(realtime: realtime, clientOptions: chatOptions) // Access the clientID (available immediately when using an API key) print(chatClient.clientID ?? "unknown") // Access the underlying realtime client let _ = chatClient.realtime ``` -------------------------------- ### Build Ably Chat Swift Library Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md Command to build the Swift library using the swift build system. ```bash swift build ``` -------------------------------- ### Run Ably Chat Swift Tests Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md Command to execute all tests for the Ably Chat Swift SDK. ```bash swift test # All tests ``` -------------------------------- ### Run Swift Tests Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md Executes all tests for the Swift project. Alternatively, tests can be run via Xcode by opening the `AblyChat.xcworkspace` file and selecting the `AblyChat` scheme. ```bash swift test ``` -------------------------------- ### Lint Code Quality with Swift BuildTool Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md Checks code formatting and quality using the Swift BuildTool. Use the `--fix` flag to automatically resolve fixable issues. ```bash swift run BuildTool lint ``` ```bash swift run BuildTool lint --fix ``` -------------------------------- ### Occupancy - Monitor room connections and presence member counts Source: https://context7.com/ably/ably-chat-swift/llms.txt Provides connection and presence member counts in real time. Requires `OccupancyOptions(enableEvents: true)` to receive push updates. ```APIDOC ## Occupancy — Monitor room connections and presence member counts `room.occupancy` provides connection and presence member counts in real time. Requires `OccupancyOptions(enableEvents: true)` to receive push updates. ```swift // Fetch current occupancy via REST (works regardless of enableEvents) let data: OccupancyData = try await room.occupancy.get() print("Connections: \(data.connections), Presence members: \(data.presenceMembers)") // Read the last received realtime occupancy value (nil until first event received) if let current = room.occupancy.current { print("Live occupancy: \(current.connections) connections") } // Subscribe to occupancy updates (requires enableEvents: true in OccupancyOptions) room.occupancy.subscribe { event in // event.type == .updated print("Occupancy updated — connections: \(event.occupancy.connections), presence: \(event.occupancy.presenceMembers)") } // AsyncSequence variant Task { for await event in room.occupancy.subscribe() { print("Connections: \(event.occupancy.connections)") } } ``` ``` -------------------------------- ### Fetch and Subscribe to Room Occupancy Updates Source: https://context7.com/ably/ably-chat-swift/llms.txt Fetch current room occupancy via REST or subscribe to real-time push updates. Requires `OccupancyOptions(enableEvents: true)` for live updates. The `current` property provides the last received value. ```swift // Fetch current occupancy via REST (works regardless of enableEvents) let data: OccupancyData = try await room.occupancy.get() print("Connections: \(data.connections), Presence members: \(data.presenceMembers)") ``` ```swift // Read the last received realtime occupancy value (nil until first event received) if let current = room.occupancy.current { print("Live occupancy: \(current.connections) connections") } ``` ```swift // Subscribe to occupancy updates (requires enableEvents: true in OccupancyOptions) room.occupancy.subscribe { event in // event.type == .updated print("Occupancy updated — connections: \(event.occupancy.connections), presence: \(event.occupancy.presenceMembers)") } ``` ```swift // AsyncSequence variant Task { for await event in room.occupancy.subscribe() { print("Connections: \(event.occupancy.connections)") } } ``` -------------------------------- ### Presence Management Source: https://context7.com/ably/ably-chat-swift/llms.txt Enter, leave, update, and observe members in a room. Requires `PresenceOptions(enableEvents: true)` (the default) to receive events. ```APIDOC ## Presence Management ### Description Manage who is currently in the room. Requires `PresenceOptions(enableEvents: true)` (the default) to receive events. ### Methods - `enter(withData: [String: JSONValue]?)`: Enter the room, publishing an enter event. Can include presence data. - `enter()`: Enter the room without data. - `update(withData: [String: JSONValue])`: Update presence data without re-entering. - `leave()`: Leave the room, publishing a leave event. - `get()`: Get the full presence set, waiting for sync by default. Returns `[PresenceMember]`. - `get(withParams: PresenceParams)`: Get a filtered presence set. `PresenceParams` can include `clientID` and `waitForSync`. - `isUserPresent(withClientID: String)`: Check if a specific user is present. Returns `Bool`. - `subscribe(_:)`: Subscribe to presence events. The closure receives `PresenceEvent`. - `subscribe()`: Returns an `AsyncSequence` for presence events. ### PresenceMember Properties - `clientID`: Client ID of the member - `connectionID`: Connection ID of the member - `data`: Presence data associated with the member ``` -------------------------------- ### Presence Management in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Manage user presence in a room, including entering, updating, leaving, and subscribing to presence events. Requires `PresenceOptions(enableEvents: true)` for event subscription. ```swift // Enter the room (publishes an enter event to all subscribers) try await room.presence.enter(withData: ["status": "available", "avatar": "https://..."]) // Or without data: try await room.presence.enter() ``` ```swift // Update presence data without re-entering try await room.presence.update(withData: ["status": "busy"]) ``` ```swift // Leave (publishes a leave event) try await room.presence.leave() ``` ```swift // Get the full presence set (waits for sync by default) let members: [PresenceMember] = try await room.presence.get() for member in members { print("\(member.clientID) [\(member.connectionID)]: \(String(describing: member.data))") } ``` ```swift // Get filtered presence set let filteredMembers = try await room.presence.get( withParams: PresenceParams(clientID: "user-42", waitForSync: true) ) ``` ```swift // Check if a specific user is present let isOnline = try await room.presence.isUserPresent(withClientID: "user-42") ``` ```swift // Subscribe to presence events room.presence.subscribe { event in switch event.type { case .enter: print("\(event.member.clientID) entered") case .leave: print("\(event.member.clientID) left") case .update: print("\(event.member.clientID) updated: \(String(describing: event.member.data))") case .present: print("\(event.member.clientID) already present (initial sync)") } } ``` ```swift // AsyncSequence variant Task { for await event in room.presence.subscribe() { print("[\(event.type)] \(event.member.clientID)") } } ``` -------------------------------- ### Send and Subscribe to Room-Level Reactions Source: https://context7.com/ably/ably-chat-swift/llms.txt Broadcast ephemeral reactions to a room, not tied to specific messages. Reactions can include metadata and headers. Subscribe to receive these reactions in real-time. ```swift // Send a room-level reaction try await room.reactions.send( withParams: SendReactionParams( name: "🎉", metadata: ["animation": .string("confetti")], headers: ["region": .string("US")] ) ) ``` ```swift // Subscribe to room reactions room.reactions.subscribe { event in let reaction = event.reaction print("Reaction '\(reaction.name)' from \(reaction.clientID) at \(reaction.createdAt)") print("Is self: \(reaction.isSelf)") print("Metadata: \(reaction.metadata), Headers: \(reaction.headers)") } ``` ```swift // AsyncSequence variant Task { for await event in room.reactions.subscribe() { print("Room reaction: \(event.reaction.name)") } } ``` -------------------------------- ### Monitor Connection Status in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Check the current connection status and subscribe to status changes using callbacks or AsyncSequence. Error messages are available when disconnected or failed. ```swift // Check current connection status print(chatClient.connection.status) // .initialized | .connecting | .connected | .disconnected | .suspended | .failed | .closing | .closed ``` ```swift // Check error if in a failed/disconnected state print(chatClient.connection.error?.message ?? "no error") ``` ```swift // Subscribe to connection status changes via callback chatClient.connection.onStatusChange { change in switch change.current { case .connected: print("Connected!") case .disconnected: let retryIn = change.retryIn.map { "\($0)s" } ?? "unknown" print("Disconnected, retrying in \(retryIn): \(change.error?.message ?? "")") case .failed: print("Connection failed (no retry): \(change.error?.message ?? "")") default: print("\(change.previous) → \(change.current)") } } ``` ```swift // AsyncSequence variant Task { for await change in chatClient.connection.onStatusChange() { print("Connection: \(change.current)") } } ``` -------------------------------- ### Mark Spec Point as Untested Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md Use this comment to indicate that an SDK implements a spec point but lacks automated tests. Provide an explanation for why it cannot be tested. ```swift // @specUntested CHA-EX2b - I was unable to find a way to test this spec point in an environment in which concurrency is being used; there is no obvious moment at which to stop observing the emitted state changes in order to be sure that FAILED has not been emitted twice. ``` -------------------------------- ### Generate Ably Chat Spec Coverage Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md Command to generate a spec coverage report for the Ably Chat SDK. ```bash swift run BuildTool spec-coverage ``` -------------------------------- ### Fetch History Before Subscription in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Retrieve historical messages sent before a message subscription was established. This helps fill any gaps in message history after attaching to a room. ```swift let history = try await subscription.historyBeforeSubscribe( withParams: HistoryBeforeSubscribeParams(limit: 50) ) for message in history.items { print(message.text) } if history.hasNext { let nextPage = try await history.next() } ``` -------------------------------- ### Expose Internal State for Testing with testOnly_ Prefix Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md This code shows how to expose internal state for testing purposes using the `testOnly_` prefix for `internal` access level APIs. This convention helps in identifying and preventing the use of test-only APIs within the SDK. ```swift private let realtime: any RealtimeClientProtocol #if DEBUG internal var testsOnly_realtime: any RealtimeClientProtocol { realtime } #endif ``` -------------------------------- ### Access Room Status and Metadata in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Check the current status and error of a room without subscribing to changes. Access room name and options. ```swift print(room.status) // RoomStatus print(room.error) // ErrorInfo? print(room.name) // "support-chat-007" print(room.options) // RoomOptions copy ``` -------------------------------- ### Fetch Single Message by Serial in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Retrieve a specific message from the room using its unique serial identifier. ```swift let fetched = try await room.messages.get(withSerial: sent.serial) ``` -------------------------------- ### Add Ably Chat Swift Package to Xcode Project Source: https://github.com/ably/ably-chat-swift/blob/main/README.md Instructions for adding the Ably Chat Swift SDK as a dependency using Xcode's Package Dependencies feature. ```swift .package(url: "https://github.com/ably/ably-chat-swift", from: "1.2.0"), ``` -------------------------------- ### Observe Room Status Changes in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Observe room status transitions using callbacks or AsyncSequence. Handles attached, suspended, and failed states. Ensure to detach when no longer receiving events. ```swift let statusSub = room.onStatusChange { change in switch change.current { case .attached: print("Room attached — ready to send and receive") case .suspended: print("Temporarily suspended, will retry: \(change.error?.message ?? "")") case .failed: print("Failed, user intervention required: \(change.error?.message ?? "")") default: print("\(change.previous) → \(change.current)") } } Task { for await change in room.onStatusChange() { print("Status: \(change.current)") } } ``` -------------------------------- ### Room Status Management Source: https://context7.com/ably/ably-chat-swift/llms.txt Manage room connections and observe status changes. This includes attaching to a room, detaching, and subscribing to status updates via callbacks or AsyncSequence. It also covers observing discontinuities and accessing room metadata. ```APIDOC ## Room — Attach, detach, and observe status Before receiving real-time events, a room must be attached. Room status transitions through `.initialized → .attaching → .attached`, and may enter `.suspended` (auto-retry) or `.failed` (manual intervention required). ```swift // Observe status changes via callback let statusSub = room.onStatusChange { change in switch change.current { case .attached: print("Room attached — ready to send and receive") case .suspended: print("Temporarily suspended, will retry: \(change.error?.message ?? \"")") case .failed: print("Failed, user intervention required: \(change.error?.message ?? \"")") default: print("\(change.previous) → \(change.current)") } } // Observe status changes via AsyncSequence Task { for await change in room.onStatusChange() { print("Status: \(change.current)") } } // Observe discontinuities (gaps in message history after reconnect) room.onDiscontinuity { error in print("Discontinuity detected: \(error.message). Reload history.") } // Attach (throws ErrorInfo on non-retriable failure) do { try await room.attach() } catch { print("Attach failed: \(error.message)") } // Check current status and error without subscribing print(room.status) // RoomStatus print(room.error) // ErrorInfo? // Detach when no longer receiving events try await room.detach() // Access room metadata print(room.name) // "support-chat-007" print(room.options) // RoomOptions copy // Remove a specific status subscription statusSub.off() ``` ``` -------------------------------- ### Subscribe to Room Messages in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Subscribe to incoming message events (created, updated, deleted) using callbacks or AsyncSequence. This is essential for real-time chat functionality. ```swift let subscription = room.messages.subscribe { event in switch event.type { case .created: print("New message from \(event.message.clientID): \(event.message.text)") case .updated: print("Message \(event.message.serial) updated to: \(event.message.text)") case .deleted: print("Message \(event.message.serial) was deleted") } } Task { for await event in room.messages.subscribe() { print("[\(event.type)] \(event.message.text)") } } ``` -------------------------------- ### Control AsyncSequence Buffering in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Configure `BufferingPolicy` for `AsyncSequence` subscriptions to manage event handling when events arrive faster than they are consumed. Options include unbounded, newest, and oldest buffering. ```swift // Unbounded buffer — never drops events (default) let sub = room.messages.subscribe(bufferingPolicy: .unbounded) ``` ```swift // Keep only the newest 100 events when the consumer falls behind let sub = room.messages.subscribe(bufferingPolicy: .bufferingNewest(100)) ``` ```swift // Keep only the oldest 50 events (drop newest when buffer is full) let sub = room.messages.subscribe(bufferingPolicy: .bufferingOldest(50)) ``` ```swift // Use with any feature that returns SubscriptionAsyncSequence let statusSub = room.onStatusChange(bufferingPolicy: .bufferingNewest(10)) let presenceSub = room.presence.subscribe(bufferingPolicy: .unbounded) let typingSub = room.typing.subscribe(bufferingPolicy: .bufferingNewest(5)) ``` -------------------------------- ### Generate Ably Chat Code Coverage Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md Command to generate a code coverage report for the Ably Chat SDK. ```bash swift run BuildTool generate-code-coverage ``` -------------------------------- ### Typing Indicators in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Manage and observe typing indicators in a room. This includes signaling keystrokes, stopping typing, and subscribing to changes in the typing set. Uses a heartbeat model and is throttled. ```swift // Get the current set of typing client IDs (synchronous, no async needed) let currentlyTyping: Set = room.typing.current print("Currently typing: \(currentlyTyping)") ``` ```swift // Signal a keystroke (throttled by heartbeatThrottle; auto-stops after timeout) try await room.typing.keystroke() ``` ```swift // Explicitly stop typing (clears the timer immediately) try await room.typing.stop() ``` ```swift // Subscribe to typing set changes room.typing.subscribe { event in // event.type == .setChanged print("Typing set changed: \(event.currentlyTyping)") print("Changed client: \(event.change.clientID), type: \(event.change.type)") // .started | .stopped } ``` ```swift // AsyncSequence variant Task { for await event in room.typing.subscribe() { if event.currentlyTyping.isEmpty { print("No one is typing") } else { print("\(event.currentlyTyping.joined(separator: ", ")) are typing...") } } } ``` -------------------------------- ### Room Management Source: https://context7.com/ably/ably-chat-swift/llms.txt Manage Room objects using ChatClient.rooms. Obtain rooms by name, optionally configuring features like messages, presence, typing, and occupancy. Always release rooms when they are no longer needed. ```APIDOC ## Rooms — Get and release rooms `ChatClient.rooms` manages the lifecycle of `Room` objects, ensuring that only one reference per room name exists at a time. Always release a room when it is no longer needed. ```swift // Get a room with default options (messages always enabled) let room = try await chatClient.rooms.get(named: "livestream-room-42") // Get a room with all features configured let room = try await chatClient.rooms.get( named: "support-chat-007", options: RoomOptions( messages: MessagesOptions( rawMessageReactions: false, // enable to subscribe to individual reaction events defaultMessageReactionType: .distinct ), presence: PresenceOptions(enableEvents: true), // receive presence events typing: TypingOptions(heartbeatThrottle: 5), // seconds between typing heartbeats occupancy: OccupancyOptions(enableEvents: true) // receive occupancy updates ) ) // Always release when done await chatClient.rooms.release(named: "support-chat-007") ``` ``` -------------------------------- ### Message Lifecycle Operations Source: https://context7.com/ably/ably-chat-swift/llms.txt Handle the complete message lifecycle within a room, including subscribing to message events (created, updated, deleted), fetching message history, sending new messages, updating existing ones, and deleting messages. ```APIDOC ## Messages — Send, update, delete, and query messages `room.messages` handles the full message lifecycle. Messages are permanently `Sendable` structs with a stable `serial` identifier. ```swift // Subscribe to incoming message events (created / updated / deleted) let subscription = room.messages.subscribe { event in switch event.type { case .created: print("New message from \(event.message.clientID): \(event.message.text)") case .updated: print("Message \(event.message.serial) updated to: \(event.message.text)") case .deleted: print("Message \(event.message.serial) was deleted") } } // AsyncSequence variant Task { for await event in room.messages.subscribe() { print("[\(event.type)] \(event.message.text)") } } // Fetch messages sent before subscription (fills gap after attach) let history = try await subscription.historyBeforeSubscribe( withParams: HistoryBeforeSubscribeParams(limit: 50) ) for message in history.items { print(message.text) } if history.hasNext { let nextPage = try await history.next() } // Send a message let sent = try await room.messages.send( withParams: SendMessageParams( text: "Hello, world!", metadata: ["highlight": .string("yellow")], headers: ["priority": .string("high")] ) ) print("Sent with serial: \(sent.serial)") // Update a message (PUT semantics — omitted headers/metadata are cleared) let updated = try await room.messages.update( withSerial: sent.serial, params: UpdateMessageParams(text: "Hello, updated world!"), details: OperationDetails(description: "Fixed typo", metadata: ["reason": "typo"]) ) // Soft-delete a message (restorable via update) let deleted = try await room.messages.delete( withSerial: sent.serial, details: OperationDetails(description: "Spam") ) // Fetch a single message by serial let fetched = try await room.messages.get(withSerial: sent.serial) // Query historical messages directly let page = try await room.messages.history( withParams: HistoryParams( start: Date().addingTimeInterval(-3600), // last hour end: Date(), limit: 100, orderBy: .newestFirst // or .oldestFirst ) ) // Unsubscribe a callback-based subscription subscription.unsubscribe() ``` ``` -------------------------------- ### Send Message in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Send a new message to the room. Supports custom metadata and headers. The sent message will have a stable serial identifier. ```swift let sent = try await room.messages.send( withParams: SendMessageParams( text: "Hello, world!", metadata: ["highlight": .string("yellow")], headers: ["priority": .string("high")] ) ) print("Sent with serial: \(sent.serial)") ``` -------------------------------- ### Paginate Through Message History in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Fetch and iterate through paginated message history using `PaginatedResult`. Use `next()`, `first()`, and `current()` to navigate pages. Ensure to handle potential errors during fetching. ```swift var page: any PaginatedResult = try await room.messages.history( withParams: HistoryParams(limit: 25, orderBy: .newestFirst) ) repeat { for message in page.items { print("[\(message.timestamp)] \(message.clientID): \(message.text)") } print("hasNext: \(page.hasNext), isLast: \(page.isLast)") if page.hasNext { guard let nextPage = try await page.next() else { break } page = nextPage } } while page.hasNext // Jump back to first page let firstPage = try await page.first() ``` -------------------------------- ### Message Operations Source: https://context7.com/ably/ably-chat-swift/llms.txt Work with message state and events. The `Message` struct is `Sendable` and `Equatable`. Use `with(_:)` to apply incoming events to a locally cached message. ```APIDOC ## Message Operations ### Description Work with message state and events. The `Message` struct is `Sendable` and `Equatable`. Use `with(_:)` to apply incoming events to a locally cached message. ### Properties - `serial`: Unique message identifier - `clientID`: Sender's client ID - `text`: Message body - `timestamp`: Date created - `action`: Message action type (.messageCreate, .messageUpdate, .messageDelete) - `metadata`: [String: JSONValue] extra data - `headers`: [String: HeadersValue] routing/filter data - `version`: MessageVersion (serial, timestamp, clientID, description) - `reactions`: MessageReactionSummary ### Methods - `with(_: MessageEvent)`: Apply an update/delete event to a cached message. Returns a new instance or the original if older. - `with(_: ReactionSummaryEvent)`: Apply a reaction summary event to a cached message. - `copy(text: String?, metadata: [String: JSONValue]?)`: Create a copy of the message with specific fields overridden. ``` -------------------------------- ### RoomReactions - Send and receive room-level reactions Source: https://context7.com/ably/ably-chat-swift/llms.txt Broadcasts ephemeral reactions to the whole room (emoji bursts, applause, likes). Unlike message reactions, these are not tied to a specific message. ```APIDOC ## RoomReactions — Send and receive room-level reactions `room.reactions` broadcasts ephemeral reactions to the whole room (emoji bursts, applause, likes). Unlike message reactions, these are not tied to a specific message. ```swift // Send a room-level reaction try await room.reactions.send( withParams: SendReactionParams( name: "🎉", metadata: ["animation": .string("confetti")], headers: ["region": .string("US")] ) ) // Subscribe to room reactions room.reactions.subscribe { event in let reaction = event.reaction print("Reaction '\(reaction.name)' from \(reaction.clientID) at \(reaction.createdAt)") print("Is self: \(reaction.isSelf)") print("Metadata: \(reaction.metadata), Headers: \(reaction.headers)") } // AsyncSequence variant Task { for await event in room.reactions.subscribe() { print("Room reaction: \(event.reaction.name)") } } ``` ``` -------------------------------- ### Mark Spec Point as Not Applicable Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md Use this comment to indicate that a spec item is not relevant for the current SDK version. Provide an explanation for its inapplicability. ```swift // @specNotApplicable CHA-EX3a - Our API does not have a concept of "partial options" unlike the JS API which this spec item considers. ``` -------------------------------- ### Query Historical Messages in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Fetch a paginated list of historical messages within a specified time range. Supports ordering by newest or oldest first. ```swift let page = try await room.messages.history( withParams: HistoryParams( start: Date().addingTimeInterval(-3600), // last hour end: Date(), limit: 100, orderBy: .newestFirst // or .oldestFirst ) ) // To fetch the next page: if page.hasNext { let nextPage = try await page.next() } ``` -------------------------------- ### Typing Indicators Source: https://context7.com/ably/ably-chat-swift/llms.txt Broadcast typing state using a heartbeat model. Subscribers receive a `TypingSetEvent` with the full set of currently typing clients. ```APIDOC ## Typing Indicators ### Description Broadcast typing state using a heartbeat model. Subscribers receive a `TypingSetEvent` with the full set of currently typing clients. ### Properties - `current`: Set of currently typing client IDs (synchronous). ### Methods - `keystroke()`: Signal a keystroke. This is throttled and auto-stops after a timeout. - `stop()`: Explicitly stop typing, clearing the timer immediately. - `subscribe(_:)`: Subscribe to typing set changes. The closure receives `TypingSetEvent`. - `subscribe()`: Returns an `AsyncSequence` for typing events. ### TypingSetEvent Properties - `currentlyTyping`: Set of client IDs currently typing. - `change`: Details about the typing change (`TypingChange` with `clientID` and `type` [.started, .stopped]). ``` -------------------------------- ### Release a Room Source: https://context7.com/ably/ably-chat-swift/llms.txt Release a chat room when it is no longer needed to manage resources effectively. This should be called after obtaining a room. ```swift // Always release when done await chatClient.rooms.release(named: "support-chat-007") ``` -------------------------------- ### MapValues with Typed Throws Extension Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md Extension to handle Dictionary.mapValues when typed throws are involved, as the standard method does not support them. ```swift extension Dictionary { func ablyChat_mapValuesWithTypedThrow(_ transform: (Value) throws(E) -> T) rethrows -> [Key: T] { var result = [Key: T]() for (key, value) in self { result[key] = try transform(value) } return result } } ``` -------------------------------- ### Message Properties and Updates in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Access properties of a Message object and apply incoming events to a cached message. Use `with(_:)` to ensure the cached message is updated only if the incoming event is newer. ```swift // Message properties let message: Message = ... print(message.serial) // Unique message identifier print(message.clientID) // Sender's client ID print(message.text) // Message body print(message.timestamp) // Date created print(message.action) // .messageCreate | .messageUpdate | .messageDelete print(message.metadata) // [String: JSONValue] extra data print(message.headers) // [String: HeadersValue] routing/filter data print(message.version) // MessageVersion (serial, timestamp, clientID, description) print(message.reactions) // MessageReactionSummary ``` ```swift // Apply an update/delete event to a cached message (returns new instance or original if older) var cachedMessage = message for await event in room.messages.subscribe() { if event.type != .created { cachedMessage = try cachedMessage.with(event) } } ``` ```swift // Apply a reaction summary event to a cached message room.messages.reactions.subscribe { summaryEvent in cachedMessage = try? cachedMessage.with(summaryEvent) } ``` ```swift // Copy with specific fields overridden let edited = message.copy(text: "Edited text", metadata: ["version": .number(2)]) ``` -------------------------------- ### Workaround for Typed Throws in Swift Testing Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md For Swift Testing's #expect(throws:), move typed-throw code into a separate non-typed-throw function. This is a workaround for a compiler crash until Xcode 16.3+. ```swift // For Swift Testing #expect(throws:) with typed errors, move typed-throw code into separate non-typed-throw function (workaround for compiler crash until Xcode 16.3+) ``` -------------------------------- ### Observe Room Discontinuities in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Detect discontinuities in message history after a reconnect. This indicates a potential gap in message delivery that may require reloading history. ```swift room.onDiscontinuity { error in print("Discontinuity detected: \(error.message). Reload history.") } ``` -------------------------------- ### Lint Ably Chat Swift Code Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md Commands for linting the Swift code, including checking for issues and automatically fixing them. ```bash swift run BuildTool lint # Check only swift run BuildTool lint --fix # Auto-fix where possible ``` -------------------------------- ### Manage Per-Message Reactions Source: https://context7.com/ably/ably-chat-swift/llms.txt Add, remove, and aggregate emoji reactions on individual messages. Supports `.unique`, `.distinct`, and `.multiple` reaction types. Subscribe to reaction summary or raw event streams. ```swift // Send a reaction to a specific message try await room.messages.reactions.send( forMessageWithSerial: message.serial, params: SendMessageReactionParams( name: "👍", type: .distinct, // optional; defaults to MessagesOptions.defaultMessageReactionType count: 1 // only meaningful for .multiple type ) ) ``` ```swift // Delete a reaction try await room.messages.reactions.delete( fromMessageWithSerial: message.serial, params: DeleteMessageReactionParams(name: "👍", type: .distinct) ) ``` ```swift // Subscribe to reaction summary events (aggregated — efficient for UI counters) room.messages.reactions.subscribe { event in print("Reactions updated for message: \(event.messageSerial)") let summary = event.reactions // .unique: [String: ClientIDList] → at most one per client // .distinct: [String: ClientIDList] → one of each emoji per client // .multiple: [String: ClientIDCounts] → counted for (emoji, info) in summary.distinct { print("\(emoji): \(info.total) reactions by \(info.clientIDs)") if info.clipped { print(" (list clipped — more clients not shown)") } } } ``` ```swift // Subscribe to individual raw reaction events (requires rawMessageReactions: true in MessagesOptions) room.messages.reactions.subscribeRaw { event in print("Raw reaction [\(event.type)]: \(event.reaction.name) on msg \(event.reaction.messageSerial)") print(" client: \(event.reaction.clientID), count: \(event.reaction.count ?? 1)") } ``` ```swift // Get per-client reaction summary (for handling clipped summaries) let clientReactions = try await room.messages.reactions.clientReactions( forMessageWithSerial: message.serial, clientID: "user-42" ) if clientReactions.distinct["👍"] != nil { print("user-42 has reacted with 👍") } ``` ```swift // AsyncSequence variants Task { for await event in room.messages.reactions.subscribe() { print("Summary update for: \(event.messageSerial)") } } ``` ```swift Task { for await event in room.messages.reactions.subscribeRaw() { print("Raw: \(event.reaction.name) [\(event.type)]") } } ``` -------------------------------- ### Handling Typed Throws with Result Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md When working with Swift concurrency features like `Task`, `CheckedContinuation`, or `AsyncThrowingStream`, which do not directly support typed throws, use a `Result` type and its `.get()` method to handle potential errors. ```swift let result: Result = await performOperation() let value = try result.get() ``` -------------------------------- ### Update Message in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Update an existing message using its serial identifier. Note that PUT semantics apply, meaning omitted headers and metadata will be cleared. ```swift let updated = try await room.messages.update( withSerial: sent.serial, params: UpdateMessageParams(text: "Hello, updated world!"), details: OperationDetails(description: "Fixed typo", metadata: ["reason": "typo"]) ) ``` -------------------------------- ### Custom Dictionary Mapping with Typed Throws Source: https://github.com/ably/ably-chat-swift/blob/main/CONTRIBUTING.md Use the `ablyChat_mapValuesWithTypedThrow` extension for dictionaries when performing value transformations that may throw typed errors, as the standard library's `mapValues` does not support typed throws. ```swift let mappedValues = try dictionary.ablyChat_mapValuesWithTypedThrow { value in try processValue(value) } ``` -------------------------------- ### Handle Typed Throws with AsyncSequence Operators Source: https://github.com/ably/ably-chat-swift/blob/main/CLAUDE.md When using AsyncSequence operators in @MainActor contexts, mark operator closures as @Sendable to prevent data race warnings. ```swift // When using AsyncSequence operators in @MainActor contexts, mark operator closures as @Sendable to avoid data race warnings. ``` -------------------------------- ### MessageReactions - Per-message emoji reactions Source: https://context7.com/ably/ably-chat-swift/llms.txt Adds, removes, and aggregates emoji reactions on individual messages. Supports `.unique`, `.distinct`, and `.multiple` reaction types. ```APIDOC ## MessageReactions — Per-message emoji reactions `room.messages.reactions` adds, removes, and aggregates emoji reactions on individual messages. Three reaction types are supported: `.unique` (one per client), `.distinct` (one of each per client), `.multiple` (counted repeats). ```swift // Send a reaction to a specific message try await room.messages.reactions.send( forMessageWithSerial: message.serial, params: SendMessageReactionParams( name: "👍", type: .distinct, // optional; defaults to MessagesOptions.defaultMessageReactionType count: 1 // only meaningful for .multiple type ) ) // Delete a reaction try await room.messages.reactions.delete( fromMessageWithSerial: message.serial, params: DeleteMessageReactionParams(name: "👍", type: .distinct) ) // Subscribe to reaction summary events (aggregated — efficient for UI counters) room.messages.reactions.subscribe { event in print("Reactions updated for message: \(event.messageSerial)") let summary = event.reactions // .unique: [String: ClientIDList] → at most one per client // .distinct: [String: ClientIDList] → one of each emoji per client // .multiple: [String: ClientIDCounts] → counted for (emoji, info) in summary.distinct { print("\(emoji): \(info.total) reactions by \(info.clientIDs)") if info.clipped { print(" (list clipped — more clients not shown)") } } } // Subscribe to individual raw reaction events (requires rawMessageReactions: true in MessagesOptions) room.messages.reactions.subscribeRaw { event in print("Raw reaction [\(event.type)]: \(event.reaction.name) on msg \(event.reaction.messageSerial)") print(" client: \(event.reaction.clientID), count: \(event.reaction.count ?? 1)") } // Get per-client reaction summary (for handling clipped summaries) let clientReactions = try await room.messages.reactions.clientReactions( forMessageWithSerial: message.serial, clientID: "user-42" ) if clientReactions.distinct["👍"] != nil { print("user-42 has reacted with 👍") } // AsyncSequence variants Task { for await event in room.messages.reactions.subscribe() { print("Summary update for: \(event.messageSerial)") } } Task { for await event in room.messages.reactions.subscribeRaw() { print("Raw: \(event.reaction.name) [\(event.type)]") } } ``` ``` -------------------------------- ### Remove Room Status Subscription in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Remove a specific status change subscription using its subscription object. ```swift statusSub.off() ``` -------------------------------- ### Unsubscribe from Room Messages in Swift Source: https://context7.com/ably/ably-chat-swift/llms.txt Remove a callback-based message subscription using its subscription object. ```swift subscription.unsubscribe() ```