Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ECO-4936] Implement the ability to attach and detach a room #4

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}
}
}
2 changes: 1 addition & 1 deletion Sources/BuildTool/ProcessRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ enum ProcessRunner {
// There’s probably a better way to implement these, which doesn’t involve having to use a separate dispatch queue. There’s a proposal for a Subprocess API coming up in Foundation which will marry Process with Swift concurrency.

static func run(executableName: String, arguments: [String]) async throws {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Swift.Error>) in
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, _>) in
queue.async {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
Expand Down
Loading