diff --git a/README.md b/README.md
index c2ca74b..349c4ec 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,24 @@
# Ably Chat SDK for Swift
-This is the repository for the Swift version of the Ably Chat SDK. We aim to build the same functionality that’s available in the [JavaScript SDK](https://github.com/ably/ably-chat-js).
+
+
+
+
+
+
+Ably Chat is a set of purpose-built APIs for a host of chat features enabling you to create 1:1, 1:Many, Many:1 and Many:Many chat rooms for
+any scale. It is designed to meet a wide range of chat use cases, such as livestreams, in-game communication, customer support, or social
+interactions in SaaS products. Built on [Ably's](https://ably.com/) core service, it abstracts complex details to enable efficient chat
+architectures.
> [!IMPORTANT]
-> This SDK is currently in the early stages of development and is not ready to be used.
+> This SDK is currently under development. If you are interested in being an early adopter and providing feedback then you
+> can [sign up to the private beta](https://forms.gle/vB2kXhCXrTQpzHLu5) and are welcome
+> to [provide us with feedback](https://forms.gle/mBw9M53NYuCBLFpMA). Coming soon: chat moderation, editing and deleting messages.
+
+Get started using the [📚 documentation](https://ably.com/docs/products/chat).
+
+![Ably Chat Header](/images/ably-chat-github-header.png)
## Supported Platforms
@@ -17,11 +32,438 @@ Xcode 16 or later.
## Installation
-The SDK is distributed as a Swift package and can hence be installed using Xcode or by adding it as a dependency in your package’s `Package.swift`. We’ll add detailed instructions when we release the first version of the SDK.
+The SDK is distributed as a Swift package and can hence be installed using Xcode or by adding it as a dependency in your package’s `Package.swift`:
+
+```swift
+.package(url: "https://github.com/ably/ably-chat-swift", from: "0.1.0")
+```
+
+## Supported chat features
+
+This project is under development so we will be incrementally adding new features. At this stage, you'll find APIs for the following chat
+features:
+
+- Chat rooms for 1:1, 1:many, many:1 and many:many participation.
+- Sending and receiving chat messages.
+- Online status aka presence of chat participants.
+- Chat room occupancy, i.e total number of connections and presence members.
+- Typing indicators
+- Room-level reactions (ephemeral at this stage)
+
+If there are other features you'd like us to prioritize, please [let us know](https://forms.gle/mBw9M53NYuCBLFpMA).
+
+## Usage
+
+You will need the following prerequisites:
+
+- An Ably account
+ - You can [sign up](https://ably.com/signup) to the generous free tier.
+
+- An Ably API key
+ - Use the default or create a new API key in an app within
+ your [Ably account dashboard](https://ably.com/dashboard).
+ - Make sure your API key has the
+ following [capabilities](https://ably.com/docs/auth/capabilities): `publish`, `subscribe`, `presence`, `history` and
+ `channel-metadata`.
+
+To instantiate the Chat SDK, create an [Ably client](https://ably.com/docs/getting-started/setup) and pass it into the Chat constructor:
+
+```swift
+let realtimeOptions = ARTClientOptions()
+realtimeOptions.key = ""
+realtimeOptions.clientId = ""
+let realtime = ARTRealtime(options: realtimeOptions)
+let chatClient = DefaultChatClient(realtime: realtime, clientOptions: nil)
+```
+
+You can use [basic authentication](https://ably.com/docs/auth/basic) i.e. the API Key directly for testing purposes,
+however it is strongly recommended that you use [token authentication](https://ably.com/docs/auth/token) in production
+environments.
+
+To use Chat you must also set a [`clientId`](https://ably.com/docs/auth/identified-clients) so that users are
+identifiable.
+
+## Connections
+
+The Chat SDK uses a single connection to Ably, which is exposed via the `ChatClient#connection` property. You can use this
+property to observe the connection state and take action accordingly.
+
+### Current connection status
+
+You can view the current connection status at any time:
+
+```swift
+let status = await chatClient.connection.status
+let error = await chatClient.connection.error
+```
+
+### Subscribing to connection status changes
+
+You can subscribe to connection status changes by registering a listener, like so:
+
+```swift
+let subscription = chatClient.connection.onStatusChange(bufferingPolicy: .unbounded)
+for await statusChange in subscription {
+ print("Connection status changed to: \(statusChange.current)")
+}
+```
+
+To stop listening to changes, call the `finish` method on the returned subscription instance:
+
+```swift
+subscription.finish()
+```
+
+## Chat rooms
+
+### Creating or retrieving a chat room
+
+You can create or retrieve a chat room with name `"basketball-stream"` this way:
+
+```swift
+let room = try await chatClient.rooms.get(roomID: "basketball-stream", options: RoomOptions())
+```
+
+The second argument to `rooms.get` is a `RoomOptions` argument, which tells the Chat SDK what features you would like your room to use and
+how they should be configured.
+
+You can also use `RoomOptions.allFeaturesEnabled` to enable all room features with the default configuration.
+
+For example, you can set the timeout between keystrokes for typing events as part of the room options. Sensible defaults for each of the
+features are provided for your convenience:
+
+- A typing timeout (time of inactivity before typing stops) of 5 seconds.
+- Entry and subscription to presence.
+
+Here’s an example demonstrating how to specify a custom typing timeout of 3 seconds:
+
+```swift
+let room = try await chatClient.rooms.get(roomID: "basketball-stream",
+ options: .init(typing: TypingOptions(timeout: 3.0)))
+```
+
+In order to use the same room but with different options, you must first `release` the room before requesting an instance with the changed
+options (see below for more information on releasing rooms).
+
+Note that:
+
+- If a `release` call is currently in progress for the room (see below), then a call to `get` will wait for that to resolve before resolving
+ itself.
+- If a `get` call is currently in progress for the room and `release` is called, the `get` call will reject.
+
+### Attaching to a room
+
+To start receiving events on a room, it must first be attached. This can be done using the `attach` method:
+
+```swift
+try await room.attach()
+```
+
+### Detaching from a room
+
+To stop receiving events on a room, it must be detached, which can be achieved by using the `detach` method:
+
+```swift
+room.detach()
+```
+
+Note: This does not remove any event listeners you have registered and they will begin to receive events again in the
+event that the room is re-attached.
+
+### Releasing a room
+
+Depending on your application, you may have multiple rooms that come and go over time (e.g. if you are running 1:1 support chat). When you
+are completely finished with a room, you may `release` it which allows the underlying resources to be collected:
+
+```swift
+_ = try await rooms.release(roomID: "basketball-stream")
+```
+
+Once `release` is called, the room will become unusable and you will need to get a new instance using `rooms.get`.
+
+> [!NOTE]
+> Releasing a room may be optional for many applications. If release is not called, the server will automatically tidy up
+connections and other resources associated with the room after a period of time.
+
+### Monitoring room status
+
+Monitoring the status of the room is key to a number of common chat features. For example, you might want to display a warning when the room
+has become detached.
+
+### Current status of a room
+
+To get the current status, you can use the `status` property:
+
+```swift
+let status = try await room.status
+let error = try await status.error
+```
+
+### Listening to room status updates
+
+You can also subscribe to changes in the room status and be notified whenever they happen by registering a listener:
+
+```swift
+let statusSubscription = try await room.onStatusChange(bufferingPolicy: .unbounded)
+for await status in statusSubscription {
+ print("Room status: \(status)")
+}
+```
+
+To stop listening to room status changes, call the `finish` method on the returned subscription instance:
+
+```swift
+statusSubscription.finish()
+```
+
+## Handling discontinuity
+
+There may be instances where the connection to Ably is lost for a period of time, for example, when the user enters a tunnel. In many
+circumstances, the connection will recover and operation will continue with no discontinuity of messages. However, during extended
+periods of disconnection, continuity cannot be guaranteed and you'll need to take steps to recover messages you might have missed.
+
+Each feature of the Chat SDK provides an `subscribeToDiscontinuities` method. Here you can register a listener that will be notified whenever a
+discontinuity in that feature has been observed.
+
+Taking messages as an example, you can listen for discontinuities like so:
+
+```swift
+let subscription = room.messages.subscribeToDiscontinuities()
+for await error in subscription {
+ print("Recovering from the error: \(error)")
+}
+```
+
+To stop listening to discontinuities, call `finish` method on returned subscription instance.
+
+## Chat messages
+
+### Subscribing to incoming messages
+
+To subscribe to incoming messages you create a subscription for the room `messages` object:
+
+```swift
+let messagesSubscription = try await room.messages.subscribe(bufferingPolicy: .unbounded)
+for await message in messagesSubscription {
+ print("Message received: \(message)")
+}
+```
+
+To stop listening for the new messages, call the `finish` method on the returned subscription instance.
+
+### Sending messages
+
+To send a message, simply call `send` on the room `messages` property, with the message you want to send:
+
+```swift
+let message = try await room.messages.send(params: .init(text: "hello"))
+```
+
+### Retrieving message history
+
+The messages object also exposes the `get` method which can be used to request historical messages in the chat room according
+to the given criteria. It returns a paginated response that can be used to request more messages:
+
+```swift
+let paginatedResult = try await room.messages.get(options: .init(orderBy: .newestFirst))
+print(paginatedResult.items)
+
+if paginatedResult.hasNext {
+ let next = try await paginatedResult.next!
+ print(next.items)
+} else {
+ print("End of messages")
+}
+```
+
+### Retrieving message history for a subscribed listener
+
+In addition to being able to unsubscribe from messages, the return value from `messages.subscribe` also includes the `getPreviousMessages`
+method. It can be used to request historical messages in the chat room that were sent up to the point that a particular listener was subscribed. It returns a
+paginated response that can be used to request for more messages:
+
+```swift
+let messagesSubscription = try await room.messages.subscribe(bufferingPolicy: .unbounded)
+let paginatedResult = try await messagesSubscription.getPreviousMessages(params: .init(limit: 50)) // `orderBy` here is ignored and always `newestFirst`
+print(paginatedResult.items)
+
+if paginatedResult.hasNext {
+ let next = try await paginatedResult.next!
+ print(next.items)
+} else {
+ print("End of messages")
+}
+```
+
+## Online status
+
+### Retrieving online members
+
+You can get the complete list of currently online or present members, their state and data, by calling the `presence#get` method which returns
+a list of the presence messages, where each message contains the most recent data for a member:
+
+```swift
+// Retrieve all users entered into presence as an array:
+let presentMembers = try await room.presence.get()
+
+// Retrieve the status of specific users by their clientId:
+let presentMember = try await room.presence.get(params: .init(clientID: "clemons123"))
+
+// To check whether the user is online or not:
+let isPresent = try await room.presence.isUserPresent(clientID: "clemons123")
+```
+
+### Entering the presence set
+
+To appear online for other users, you can enter the presence set of a chat room. While entering presence, you can provide optional data that
+will be associated with the presence message:
+
+```swift
+try await room.presence.enter(data: .init(userCustomData: ["status": .string("Online")]))
+```
+
+### Updating the presence data
+
+Updates allow you to make changes to the custom data associated with a present user. Common use-cases include updating the users'
+status:
+
+```swift
+try await room.presence.update(data: .init(userCustomData: ["status": .string("Busy")]))
+```
+
+### Leaving the presence set
+
+Ably automatically triggers a presence leave if a client goes offline. But you can also manually leave the presence set as a result of a UI
+action. While leaving presence, you can provide optional data that will be associated with the presence message:
+
+```swift
+try await room.presence.leave(data: .init(userCustomData: ["status": .string("Bye!")]))
+```
+
+### Subscribing to presence updates
+
+You can provide a single listener for all presence event types:
+
+```swift
+let presenceSubscription = try await room.presence.subscribe(events: [.enter, .leave, .update])
+for await event in presenceSubscription {
+ print("Presence event `\(event.action)` from `\(event.clientId)` with data `\(event.data)`")
+}
+```
+
+To stop listening for the presence updates, call the `finish` method on the returned subscription instance.
+
+## Typing indicators
+
+> [!NOTE]
+> You should be attached to the room to enable this functionality.
+
+Typing events allow you to inform others that a client is typing and also subscribe to others' typing status.
+
+### Retrieving the set of current typers
+
+You can get the complete set of the current typing `clientId`s, by calling the `typing.get` method.
+
+```swift
+// Retrieve the entire list of currently typing clients
+let currentlyTypingClientIds = try await room.typing.get()
+```
+
+### Start typing
+
+To inform other users that you are typing, you can call the start method. This will begin a timer that will automatically stop typing after
+a set amount of time.
+
+```swift
+try await room.typing.start()
+```
+
+Repeated calls to start will reset the timer, so the clients typing status will remain active.
+
+### Stop typing
+
+You can immediately stop typing without waiting for the timer to expire.
+
+```swift
+try await room.typing.start()
+// Some short delay - timer not yet expired
+
+try await room.typing.stop()
+// Timer cleared and stopped typing event emitted and listeners are notified
+```
+
+### Subscribing to typing updates
+
+To subscribe to typing events, create a subscription with the `subscribe` method:
+
+```swift
+let typingSubscription = try await room.typing.subscribe(bufferingPolicy: .unbounded)
+for await typing in typingSubscription {
+ typingInfo = typing.currentlyTyping.isEmpty ? "" : "Typing: \(typing.currentlyTyping.joined(separator: ", "))..."
+}
+```
+To stop listening for the typing events, call the `finish` method on the returned subscription instance.
+
+## Occupancy of a chat room
+
+Occupancy tells you how many users are connected to the chat room.
+
+### Subscribing to occupancy updates
+
+To subscribe to occupancy updates, subscribe a listener to the chat room `occupancy` member:
+
+```swift
+let occupancySubscription = try await room.occupancy.subscribe(bufferingPolicy: .unbounded)
+for await event in occupancySubscription {
+ occupancyInfo = "Connections: \(event.presenceMembers) (\(event.connections))"
+}
+```
+To stop listening for the typing events, call the `finish` method on the returned subscription instance.
+
+Occupancy updates are delivered in near-real-time, with updates in quick succession batched together for performance.
+
+### Retrieving the occupancy of a chat room
+
+You can request the current occupancy of a chat room using the `occupancy.get` method:
+
+```swift
+let occupancy = try await room.occupancy.get()
+```
+
+## Room-level reactions
+
+You can subscribe to and send ephemeral room-level reactions by using the room `reactions` object.
+To send room-level reactions, you must be [attached](#attaching-to-a-room) to the room.
+
+### Sending a reaction
+
+To send a reaction such as `"like"`:
+
+```swift
+try await room.reactions.send(params: .init(type: "like"))
+```
+
+You can also add any metadata and headers to reactions:
+
+```swift
+try await room.reactions.send(params: .init(type: "🎉", metadata: ["effect": .string("fireworks")]))
+```
+
+### Subscribing to room reactions
+
+Subscribe to receive room-level reactions:
+
+```swift
+let reactionSubscription = try await room.reactions.subscribe(bufferingPolicy: .unbounded)
+for await reaction in reactionSubscription {
+ print("Received a reaction of type \(reaction.type), and metadata \(reaction.metadata)")
+}
+```
+To stop receiving reactions, call the `finish` method on the returned subscription instance.
## Example app
-This repository contains an example app, written using SwiftUI, which demonstrates how to use the SDK. The code for this app is in the [`Example` directory](Example).
+This repository contains an example app, written using SwiftUI, which demonstrates how to use the SDK. The code for this app is in the [`Example`](Example) directory.
In order to allow the app to use modern SwiftUI features, it supports the following OS versions:
@@ -31,6 +473,49 @@ In order to allow the app to use modern SwiftUI features, it supports the follow
To run the app, open the `AblyChat.xcworkspace` workspace in Xcode and run the `AblyChatExample` target. If you wish to run it on an iOS or tvOS device, you’ll need to set up code signing.
+## In-depth
+
+### Channels Behind Chat Features
+
+It might be useful to know that each feature is backed by an underlying Pub/Sub channel. You can use this information to enable
+interoperability with other platforms by subscribing to the channels directly using
+the [Ably Pub/Sub SDKs](https://ably.com/docs/products/channels) for those platforms.
+
+The channel for each feature can be obtained via the `channel` property
+on that feature.
+
+```swift
+let messagesChannel = room.messages.channel
+```
+
+**Warning**: You should not attempt to change the state of a channel directly. Doing so may cause unintended side-effects in the Chat SDK.
+
+### Channels Used
+
+For a given chat room, the channels used for features are as follows:
+
+| Feature | Channel |
+|-----------|--------------------------------------|
+| Messages | `::$chat::$chatMessages` |
+| Presence | `::$chat::$chatMessages` |
+| Occupancy | `::$chat::$chatMessages` |
+| Reactions | `::$chat::$reactions` |
+| Typing | `::$chat::$typingIndicators` |
+
+---
+
## Contributing
-For information on how to contribute to this repository, please see the [contributing guidelines](CONTRIBUTING.md).
+For guidance on how to contribute to this project, see the [contributing guidelines](CONTRIBUTING.md).
+
+## Support, feedback and troubleshooting
+
+Please visit http://support.ably.com/ for access to our knowledge base and to ask for any assistance. You can also view
+the community reported [Github issues](https://github.com/ably/ably-chat-swift/issues) or raise one yourself.
+
+To see what has changed in recent versions, see the [changelog](CHANGELOG.md).
+
+## Further reading
+
+- [Sign up](https://forms.gle/gRZa51erqNp1mSxVA) to the private beta and get started.
+- [Share feedback or request](https://forms.gle/mBw9M53NYuCBLFpMA) a new feature.