Skip to content

Ably Chat SDK for Swift to build chat experiences at scale

License

Notifications You must be signed in to change notification settings

ably/ably-chat-swift

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ably Chat SDK for Swift

Development status License version: 0.1.0-SNAPSHOT

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 core service, it abstracts complex details to enable efficient chat architectures.

Important

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 and are welcome to provide us with feedback. Coming soon: chat moderation, editing and deleting messages.

Get started using the 📚 documentation.

Ably Chat Header

Supported Platforms

  • macOS 11 and above
  • iOS 14 and above
  • tvOS 14 and above

Requirements

Xcode 16 or later.

Installation

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:

.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.

Usage

You will need the following prerequisites:

  • An Ably account

    • You can sign up 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.
    • Make sure your API key has the following capabilities: publish, subscribe, presence, history and channel-metadata.

To instantiate the Chat SDK, create an Ably client and pass it into the Chat constructor:

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 i.e. the API Key directly for testing purposes, however it is strongly recommended that you use token authentication in production environments.

To use Chat you must also set a clientId 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:

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:

let subscription = chatClient.connection.onStatusChange()
for await statusChange in subscription {
    print("Connection status changed to: \(statusChange.current)")
}

To stop listening to changes, call the unsubscribe method on the returned subscription instance:

subscription.unsubscribe()

Chat rooms

Creating or retrieving a chat room

You can create or retrieve a chat room with name "basketball-stream" this way:

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:

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:

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:

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:

_ = 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:

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:

let statusSubscription = try await room.onStatusChange()
for await status in statusSubscription {
    print("Room status: \(status)")
}

To stop listening to room status changes, call the unsubscribe method on the returned subscription instance:

statusSubscription.unsubscribe()

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 onDiscontinuity 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:

let subscription = room.messages.onDiscontinuity()
for await error in subscription {
    print("Recovering from the error: \(error)")
}

To stop listening to discontinuities, call unsubscribe 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:

let messagesSubscription = try await room.messages.subscribe()
for await message in messagesSubscription {
    print("Message received: \(message)")
}

To stop listening for the new messages, call the unsubscribe 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:

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:

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:

let messagesSubscription = try await room.messages.subscribe()
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:

// 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 (can be a nested dictionary):

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 user's status:

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:

try await room.presence.leave(data: ["status": "Bye!"])

Subscribing to presence updates

You can provide a single listener for all presence event types:

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 unsubscribe 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 clientIds, by calling the typing.get method.

// 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.

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.

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:

let typingSubscription = try await room.typing.subscribe()
for await typing in typingSubscription {
    typingInfo = typing.currentlyTyping.isEmpty ? "" : "Typing: \(typing.currentlyTyping.joined(separator: ", "))..."
}

To stop listening for the typing events, call the unsubscribe 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:

let occupancySubscription = try await room.occupancy.subscribe()
for await event in occupancySubscription {
    occupancyInfo = "Connections: \(event.presenceMembers) (\(event.connections))"
}

To stop listening for the typing events, call the unsubscribe 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:

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 to the room.

Sending a reaction

To send a reaction such as like:

try await room.reactions.send(params: .init(type: "like"))

You can also add any metadata and headers to reactions:

try await room.reactions.send(params: .init(type: "🎉", metadata: ["effect": "fireworks"]))

Subscribing to room reactions

Subscribe to receive room-level reactions:

let reactionSubscription = try await room.reactions.subscribe()
for await reaction in reactionSubscription {
    print("Received a reaction of type \(reaction.type), and metadata \(reaction.metadata)")
}

To stop receiving reactions, call the unsubscribe 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.

In order to allow the app to use modern SwiftUI features, it supports the following OS versions:

  • macOS 14 and above
  • iOS 17 and above
  • tvOS 17 and above

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 for those platforms.

The channel for each feature can be obtained via the channel property on that feature.

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 guidance on how to contribute to this project, see the contributing guidelines.

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 or raise one yourself.

To see what has changed in recent versions, see the changelog.

Further reading

About

Ably Chat SDK for Swift to build chat experiences at scale

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages