Skip to content

Commit

Permalink
Merge pull request #166 from ably/readme-update
Browse files Browse the repository at this point in the history
[ECO-5102] Updated README
maratal authored Dec 11, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents c1915d4 + 189d5d7 commit cdd61d1
Showing 2 changed files with 501 additions and 5 deletions.
506 changes: 501 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -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).
<p style="text-align: left">
<img src="https://img.shields.io/badge/development_status-Private_Beta-ab7df8" alt="Development status" />
<img src="https://badgen.net/github/license/3scale/saas-operator" alt="License" />
<img src="https://img.shields.io/badge/version-0.1.0--SNAPSHOT-2ea44f" alt="version: 0.1.0-SNAPSHOT" />
</p>

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,449 @@ 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 (search for `github.com/ably/ably-chat-swift` package) 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
import Ably
import AblyChat

let realtimeOptions = ARTClientOptions()
realtimeOptions.key = "<API_KEY>"
realtimeOptions.clientId = "<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 complete before completing
itself.
- If a `get` call is currently in progress for the room and `release` is called, the `get` call will fail.

### 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 (and error), you can use the `status` property like this:

```swift
switch await room.status {
case let .failed(error: error):
// use 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: ["status": "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: ["status": "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: ["status": "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 +484,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 | `<roomId>::$chat::$chatMessages` |
| Presence | `<roomId>::$chat::$chatMessages` |
| Occupancy | `<roomId>::$chat::$chatMessages` |
| Reactions | `<roomId>::$chat::$reactions` |
| Typing | `<roomId>::$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.
Binary file added images/ably-chat-github-header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit cdd61d1

Please sign in to comment.