Skip to content

Commit

Permalink
Implement the ability to attach and detach a room
Browse files Browse the repository at this point in the history
Based on the simplified requirements described in #19. This doesn’t
include the emission of a room status change; will do that in a separate
PR.
  • Loading branch information
lawrence-forooghian committed Sep 3, 2024
1 parent fa0e7be commit 09e54f6
Show file tree
Hide file tree
Showing 10 changed files with 639 additions and 12 deletions.
167 changes: 165 additions & 2 deletions Example/AblyChatExample/Mocks/MockRealtime.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Ably
import AblyChat

/// A mock implementation of `ARTRealtimeProtocol`. It only exists so that we can construct an instance of `DefaultChatClient` without needing to create a proper `ARTRealtime` instance (which we can’t yet do because we don’t have a method for inserting an API key into the example app). TODO remove this once we start building the example app
final class MockRealtime: NSObject, ARTRealtimeProtocol, Sendable {
/// A mock implementation of `RealtimeClientProtocol`. It only exists so that we can construct an instance of `DefaultChatClient` without needing to create a proper `ARTRealtime` instance (which we can’t yet do because we don’t have a method for inserting an API key into the example app). TODO remove this once we start building the example app
final class MockRealtime: NSObject, RealtimeClientProtocol, Sendable {
var device: ARTLocalDevice {
fatalError("Not implemented")
}
Expand All @@ -10,6 +11,168 @@ final class MockRealtime: NSObject, ARTRealtimeProtocol, Sendable {
fatalError("Not implemented")
}

let channels = Channels()

final class Channels: RealtimeChannelsProtocol {
func get(_: String) -> Channel {
fatalError("Not implemented")
}

func exists(_: String) -> Bool {
fatalError("Not implemented")
}

func release(_: String, callback _: ARTCallback? = nil) {
fatalError("Not implemented")
}

func release(_: String) {
fatalError("Not implemented")
}
}

final class Channel: RealtimeChannelProtocol {
var state: ARTRealtimeChannelState {
fatalError("Not implemented")
}

var errorReason: ARTErrorInfo? {
fatalError("Not implemented")
}

var options: ARTRealtimeChannelOptions? {
fatalError("Not implemented")
}

func attach() {
fatalError("Not implemented")
}

func attach(_: ARTCallback? = nil) {
fatalError("Not implemented")
}

func detach() {
fatalError("Not implemented")
}

func detach(_: ARTCallback? = nil) {
fatalError("Not implemented")
}

func subscribe(_: @escaping ARTMessageCallback) -> ARTEventListener? {
fatalError("Not implemented")
}

func subscribe(attachCallback _: ARTCallback?, callback _: @escaping ARTMessageCallback) -> ARTEventListener? {
fatalError("Not implemented")
}

func subscribe(_: String, callback _: @escaping ARTMessageCallback) -> ARTEventListener? {
fatalError("Not implemented")
}

func subscribe(_: String, onAttach _: ARTCallback?, callback _: @escaping ARTMessageCallback) -> ARTEventListener? {
fatalError("Not implemented")
}

func unsubscribe() {
fatalError("Not implemented")
}

func unsubscribe(_: ARTEventListener?) {
fatalError("Not implemented")
}

func unsubscribe(_: String, listener _: ARTEventListener?) {
fatalError("Not implemented")
}

func history(_: ARTRealtimeHistoryQuery?, callback _: @escaping ARTPaginatedMessagesCallback) throws {
fatalError("Not implemented")
}

func setOptions(_: ARTRealtimeChannelOptions?, callback _: ARTCallback? = nil) {
fatalError("Not implemented")
}

func on(_: ARTChannelEvent, callback _: @escaping (ARTChannelStateChange) -> Void) -> ARTEventListener {
fatalError("Not implemented")
}

func on(_: @escaping (ARTChannelStateChange) -> Void) -> ARTEventListener {
fatalError("Not implemented")
}

func once(_: ARTChannelEvent, callback _: @escaping (ARTChannelStateChange) -> Void) -> ARTEventListener {
fatalError("Not implemented")
}

func once(_: @escaping (ARTChannelStateChange) -> Void) -> ARTEventListener {
fatalError("Not implemented")
}

func off(_: ARTChannelEvent, listener _: ARTEventListener) {
fatalError("Not implemented")
}

func off(_: ARTEventListener) {
fatalError("Not implemented")
}

func off() {
fatalError("Not implemented")
}

var name: String {
fatalError("Not implemented")
}

func publish(_: String?, data _: Any?) {
fatalError("Not implemented")
}

func publish(_: String?, data _: Any?, callback _: ARTCallback? = nil) {
fatalError("Not implemented")
}

func publish(_: String?, data _: Any?, clientId _: String) {
fatalError("Not implemented")
}

func publish(_: String?, data _: Any?, clientId _: String, callback _: ARTCallback? = nil) {
fatalError("Not implemented")
}

func publish(_: String?, data _: Any?, extras _: (any ARTJsonCompatible)?) {
fatalError("Not implemented")
}

func publish(_: String?, data _: Any?, extras _: (any ARTJsonCompatible)?, callback _: ARTCallback? = nil) {
fatalError("Not implemented")
}

func publish(_: String?, data _: Any?, clientId _: String, extras _: (any ARTJsonCompatible)?) {
fatalError("Not implemented")
}

func publish(_: String?, data _: Any?, clientId _: String, extras _: (any ARTJsonCompatible)?, callback _: ARTCallback? = nil) {
fatalError("Not implemented")
}

func publish(_: [ARTMessage]) {
fatalError("Not implemented")
}

func publish(_: [ARTMessage], callback _: ARTCallback? = nil) {
fatalError("Not implemented")
}

func history(_: @escaping ARTPaginatedMessagesCallback) {
fatalError("Not implemented")
}
}

required init(options _: ARTClientOptions) {}

required init(key _: String) {}
Expand Down
30 changes: 30 additions & 0 deletions Sources/AblyChat/AblyCocoaExtensions/Ably+Concurrency.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Ably

// This file contains extensions to ably-cocoa’s types, to make them easier to use in Swift concurrency.
// TODO: remove once we improve this experience in ably-cocoa (https://github.com/ably/ably-cocoa/issues/1967)

internal extension ARTRealtimeChannelProtocol {
func attachAsync() async throws {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, _>) in
attach { error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume()
}
}
}
}

func detachAsync() async throws {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, _>) in
detach { error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume()
}
}
}
}
}
18 changes: 18 additions & 0 deletions Sources/AblyChat/AblyCocoaExtensions/Ably+Dependencies.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Ably

// TODO: remove "@unchecked Sendable" once https://github.com/ably/ably-cocoa/issues/1962 done

#if swift(>=6)
// This @retroactive is needed to silence the Swift 6 compiler error "extension declares a conformance of imported type 'ARTRealtimeChannels' to imported protocol 'Sendable'; this will not behave correctly if the owners of 'Ably' introduce this conformance in the future (…) add '@retroactive' to silence this warning". I don’t fully understand the implications of this but don’t really mind since both libraries are in our control.
extension ARTRealtime: RealtimeClientProtocol, @retroactive @unchecked Sendable {}

extension ARTRealtimeChannels: RealtimeChannelsProtocol, @retroactive @unchecked Sendable {}

extension ARTRealtimeChannel: RealtimeChannelProtocol, @retroactive @unchecked Sendable {}
#else
extension ARTRealtime: RealtimeClientProtocol, @unchecked Sendable {}

extension ARTRealtimeChannels: RealtimeChannelsProtocol, @unchecked Sendable {}

extension ARTRealtimeChannel: RealtimeChannelProtocol, @unchecked Sendable {}
#endif
2 changes: 1 addition & 1 deletion Sources/AblyChat/ChatClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public protocol ChatClient: AnyObject, Sendable {
var clientOptions: ClientOptions { get }
}

public typealias RealtimeClient = any(ARTRealtimeProtocol & Sendable)
public typealias RealtimeClient = any RealtimeClientProtocol

public actor DefaultChatClient: ChatClient {
public let realtime: RealtimeClient
Expand Down
22 changes: 22 additions & 0 deletions Sources/AblyChat/Dependencies.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Ably

/// Expresses the requirements of the Ably realtime client that is supplied to the Chat SDK.
///
/// The `ARTRealtime` class from the ably-cocoa SDK implements this protocol.
public protocol RealtimeClientProtocol: ARTRealtimeProtocol, Sendable {
associatedtype Channels: RealtimeChannelsProtocol

// It’s not clear to me why ARTRealtimeProtocol doesn’t include this property. I briefly tried adding it but ran into compilation failures that it wasn’t immediately obvious how to fix.
var channels: Channels { get }
}

/// Expresses the requirements of the object returned by ``RealtimeClientProtocol.channels``.
public protocol RealtimeChannelsProtocol: ARTRealtimeChannelsProtocol, Sendable {
associatedtype Channel: RealtimeChannelProtocol

// It’s not clear to me why ARTRealtimeChannelsProtocol doesn’t include this property (https://github.com/ably/ably-cocoa/issues/1968).
func get(_ name: String) -> Channel
}

/// Expresses the requirements of the object returned by ``RealtimeChannelsProtocol.get(_:)``.
public protocol RealtimeChannelProtocol: ARTRealtimeChannelProtocol, Sendable {}
19 changes: 17 additions & 2 deletions Sources/AblyChat/Room.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,26 @@ internal actor DefaultRoom: Room {
fatalError("Not yet implemented")
}

/// Fetches the channels that contribute to this room.
private func channels() -> [any RealtimeChannelProtocol] {
[
"chatMessages",
"typingIndicators",
"reactions",
].map { suffix in
realtime.channels.get("\(roomID)::$chat::$\(suffix)")
}
}

public func attach() async throws {
fatalError("Not yet implemented")
for channel in channels() {
try await channel.attachAsync()
}
}

public func detach() async throws {
fatalError("Not yet implemented")
for channel in channels() {
try await channel.detachAsync()
}
}
}
Loading

0 comments on commit 09e54f6

Please sign in to comment.