-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Spec complete for Presence in line with [1].
[1] - ably/specification#200
- Loading branch information
1 parent
609ea6d
commit e037276
Showing
12 changed files
with
430 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import Ably | ||
|
||
@MainActor | ||
internal final class DefaultPresence: Presence, EmitsDiscontinuities { | ||
private let featureChannel: FeatureChannel | ||
private let roomID: String | ||
private let clientID: String | ||
private let logger: InternalLogger | ||
|
||
internal init(featureChannel: FeatureChannel, roomID: String, clientID: String, logger: InternalLogger) { | ||
self.roomID = roomID | ||
self.featureChannel = featureChannel | ||
self.clientID = clientID | ||
self.logger = logger | ||
} | ||
|
||
internal nonisolated var channel: any RealtimeChannelProtocol { | ||
featureChannel.channel | ||
} | ||
|
||
internal func get() async throws -> [PresenceMember] { | ||
try await withCheckedThrowingContinuation { continuation in | ||
channel.presence.get { [processPresenceGet] members, error in | ||
Task { | ||
try processPresenceGet(continuation, members, error) | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal func get(params: PresenceQuery) async throws -> [PresenceMember] { | ||
try await withCheckedThrowingContinuation { continuation in | ||
channel.presence.get(params.asARTRealtimePresenceQuery()) { [processPresenceGet] members, error in | ||
Task { | ||
try processPresenceGet(continuation, members, error) | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal func isUserPresent(clientID: String) async throws -> Bool { | ||
try await withCheckedThrowingContinuation { continuation in | ||
channel.presence.get(ARTRealtimePresenceQuery(clientId: clientID, connectionId: nil)) { members, error in | ||
Task { | ||
guard let members else { | ||
throw error ?? ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without data or text") | ||
} | ||
continuation.resume(returning: !members.isEmpty) | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal func enter(data: PresenceData? = nil) async throws { | ||
channel.presence.enterClient(clientID, data: data?.asQueryItems()) { error in | ||
Task { | ||
if let error { | ||
throw error | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal func update(data: PresenceData? = nil) async throws { | ||
channel.presence.update(data?.asQueryItems()) { error in | ||
Task { | ||
if let error { | ||
throw error | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal func leave(data: PresenceData? = nil) async throws { | ||
channel.presence.leave(data?.asQueryItems()) { error in | ||
Task { | ||
if let error { | ||
throw error | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal func subscribe(event: PresenceEventType) async -> Subscription<PresenceEvent> { | ||
let subscription = Subscription<PresenceEvent>(bufferingPolicy: .unbounded) | ||
channel.presence.subscribe(event.toARTPresenceAction()) { [processPresenceSubscribe] message in | ||
Task { | ||
let presenceEvent = try processPresenceSubscribe(message, event) | ||
subscription.emit(presenceEvent) | ||
} | ||
} | ||
return subscription | ||
} | ||
|
||
internal func subscribe(events: [PresenceEventType]) async -> Subscription<PresenceEvent> { | ||
let subscription = Subscription<PresenceEvent>(bufferingPolicy: .unbounded) | ||
for event in events { | ||
channel.presence.subscribe(event.toARTPresenceAction()) { [processPresenceSubscribe] message in | ||
Task { | ||
let presenceEvent = try processPresenceSubscribe(message, event) | ||
subscription.emit(presenceEvent) | ||
} | ||
} | ||
} | ||
return subscription | ||
} | ||
|
||
internal func subscribeToDiscontinuities() async -> Subscription<ARTErrorInfo> { | ||
await featureChannel.subscribeToDiscontinuities() | ||
} | ||
|
||
private func decodePresenceData(from data: Any?) -> PresenceData? { | ||
guard let userData = (data as? [String: Any]) else { | ||
return nil | ||
} | ||
|
||
do { | ||
let jsonData = try JSONSerialization.data(withJSONObject: userData, options: []) | ||
let presenceData = try JSONDecoder().decode(PresenceData.self, from: jsonData) | ||
return presenceData | ||
} catch { | ||
print("Failed to decode PresenceData: \(error)") | ||
return nil | ||
} | ||
} | ||
|
||
private func processPresenceGet(continuation: CheckedContinuation<[PresenceMember], any Error>, members: [ARTPresenceMessage]?, error: ARTErrorInfo?) throws { | ||
guard let members else { | ||
throw error ?? ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without data or text") | ||
} | ||
let presenceMembers = try members.map { member in | ||
guard let data = member.data as? [String: Any] else { | ||
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without data") | ||
} | ||
|
||
guard let clientID = member.clientId else { | ||
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without clientId") | ||
} | ||
|
||
guard let timestamp = member.timestamp else { | ||
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without timestamp") | ||
} | ||
|
||
let userCustomData = data["userCustomData"] as? PresenceData | ||
|
||
// Seems like we want to just forward on `extras` from the cocoa SDK but that is an `ARTJsonCompatible` type which is not `Sendable`... currently just converting this to a `Sendable` type (`String`) until we know what to do with this. | ||
let extras = member.extras?.toJSONString() | ||
|
||
return PresenceMember( | ||
clientID: clientID, | ||
data: userCustomData ?? .init(), | ||
action: PresenceMember.Action(from: member.action), | ||
extras: extras, | ||
updatedAt: timestamp | ||
) | ||
} | ||
continuation.resume(returning: presenceMembers) | ||
} | ||
|
||
private func processPresenceSubscribe(_ message: ARTPresenceMessage, for event: PresenceEventType) throws -> PresenceEvent { | ||
guard let clientID = message.clientId else { | ||
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without clientId") | ||
} | ||
|
||
guard let timestamp = message.timestamp else { | ||
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without timestamp") | ||
} | ||
|
||
let userCustomDataDecoded = decodePresenceData(from: message.data) | ||
|
||
return PresenceEvent( | ||
action: event, | ||
clientID: clientID, | ||
timestamp: timestamp, | ||
data: userCustomDataDecoded ?? .init() | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.