Skip to content

Commit 220bf01

Browse files
committed
presence: remove event filter
In the rest of the SDK, we've not provided event filters on subscription events - this is a fairly trivial thing for users to write (if/switch). Also, when dealing with multiple events in the list, the if/switch is required anyway and in many languages must be exhaustive, so the real benefit is when you're listening to a single event (but still marginal). Furthermore, the overloading is somewhat cumbersome to maintain. This change removes the event filtering for presence.
1 parent e21af0d commit 220bf01

File tree

7 files changed

+79
-174
lines changed

7 files changed

+79
-174
lines changed

Example/AblyChatExample/ContentView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ struct ContentView: View {
314314
}
315315

316316
func subscribeToPresence(room: any Room) {
317-
room.presence.subscribe(events: [.enter, .leave, .update]) { event in
317+
room.presence.subscribe { event in
318318
withAnimation {
319319
listItems.insert(
320320
.presence(

Example/AblyChatExample/Mocks/MockClients.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -538,14 +538,6 @@ class MockPresence: Presence {
538538
func subscribe(_ callback: @escaping @MainActor (PresenceEvent) -> Void) -> MockSubscription {
539539
createSubscription(callback: callback)
540540
}
541-
542-
func subscribe(event _: PresenceEventType, _ callback: @escaping @MainActor (PresenceEvent) -> Void) -> MockSubscription {
543-
createSubscription(callback: callback)
544-
}
545-
546-
func subscribe(events _: [PresenceEventType], _ callback: @escaping @MainActor (PresenceEvent) -> Void) -> MockSubscription {
547-
createSubscription(callback: callback)
548-
}
549541
}
550542

551543
class MockOccupancy: Occupancy {

Sources/AblyChat/DefaultPresence.swift

Lines changed: 5 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -206,16 +206,8 @@ internal final class DefaultPresence: Presence {
206206
return
207207
}
208208
logger.log(message: "Received presence message: \(message)", level: .debug)
209-
210-
guard let event = PresenceEventType(ablyCocoaValue: message.action) else {
211-
return
212-
}
213-
214209
// processPresenceSubscribe is logging so we don't need to log here
215-
let presenceEvent = processPresenceSubscribe(
216-
PresenceMessage(ablyCocoaPresenceMessage: message),
217-
for: event,
218-
)
210+
let presenceEvent = processPresenceSubscribe(PresenceMessage(ablyCocoaPresenceMessage: message))
219211
callback(presenceEvent)
220212
}
221213

@@ -226,54 +218,6 @@ internal final class DefaultPresence: Presence {
226218
}
227219
}
228220

229-
// (CHA-PR7b) Users may provide a listener and a list of selected presence events, to subscribe to just those events in a room.
230-
internal func subscribe(event: PresenceEventType, _ callback: @escaping @MainActor (PresenceEvent) -> Void) -> DefaultSubscription {
231-
fatalErrorIfEnableEventsDisabled()
232-
233-
logger.log(message: "Subscribing to presence events", level: .debug)
234-
235-
let eventListener = channel.presence.subscribe(event.toARTPresenceAction()) { [weak self] message in
236-
guard let self else {
237-
return
238-
}
239-
logger.log(message: "Received presence message: \(message)", level: .debug)
240-
// processPresenceSubscribe is logging so we don't need to log here
241-
let presenceEvent = processPresenceSubscribe(PresenceMessage(ablyCocoaPresenceMessage: message), for: event)
242-
callback(presenceEvent)
243-
}
244-
245-
return DefaultSubscription { [weak self] in
246-
if let eventListener {
247-
self?.channel.presence.unsubscribe(eventListener)
248-
}
249-
}
250-
}
251-
252-
internal func subscribe(events: [PresenceEventType], _ callback: @escaping @MainActor (PresenceEvent) -> Void) -> DefaultSubscription {
253-
fatalErrorIfEnableEventsDisabled()
254-
255-
logger.log(message: "Subscribing to presence events", level: .debug)
256-
257-
let eventListeners = events.map { event in
258-
channel.presence.subscribe(event.toARTPresenceAction()) { [weak self] message in
259-
guard let self else {
260-
return
261-
}
262-
logger.log(message: "Received presence message: \(message)", level: .debug)
263-
let presenceEvent = processPresenceSubscribe(PresenceMessage(ablyCocoaPresenceMessage: message), for: event)
264-
callback(presenceEvent)
265-
}
266-
}
267-
268-
return DefaultSubscription { [weak self] in
269-
for eventListener in eventListeners {
270-
if let eventListener {
271-
self?.channel.presence.unsubscribe(eventListener)
272-
}
273-
}
274-
}
275-
}
276-
277221
private func processPresenceGet(members: [PresenceMessage]) throws(InternalError) -> [PresenceMember] {
278222
let presenceMembers = try members.map { member throws(InternalError) in
279223
let presenceMember = PresenceMember(
@@ -289,16 +233,18 @@ internal final class DefaultPresence: Presence {
289233
return presenceMembers
290234
}
291235

292-
private func processPresenceSubscribe(_ message: PresenceMessage, for event: PresenceEventType) -> PresenceEvent {
236+
private func processPresenceSubscribe(_ message: PresenceMessage) -> PresenceEvent {
293237
let member = PresenceMember(
294238
clientID: message.clientId ?? "", // CHA-M4k1
295239
data: message.data,
296240
extras: message.extras,
297241
updatedAt: message.timestamp ?? Date(timeIntervalSince1970: 0), // CHA-M4k5
298242
)
299243

244+
let eventType = PresenceEventType(ablyCocoaPresenceAction: message.action)
245+
300246
let presenceEvent = PresenceEvent(
301-
type: event,
247+
type: eventType,
302248
member: member,
303249
)
304250

Sources/AblyChat/Presence.swift

Lines changed: 21 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public protocol Presence: AnyObject, Sendable {
7474
func leave(withData data: PresenceData) async throws(ARTErrorInfo)
7575

7676
/**
77-
* Subscribes a given listener to all presence events.
77+
* Subscribes a given listener to all presence events in the chat room.
7878
*
7979
* Note that it is a programmer error to call this method if presence events are not enabled in the room options. Make sure to set `enableEvents: true` in your room's presence options to use this feature (this is the default value).
8080
*
@@ -86,34 +86,6 @@ public protocol Presence: AnyObject, Sendable {
8686
@discardableResult
8787
func subscribe(_ callback: @escaping @MainActor (PresenceEvent) -> Void) -> Subscription
8888

89-
/**
90-
* Subscribes a given listener to a particular presence event in the chat room.
91-
*
92-
* Note that it is a programmer error to call this method if presence events are not enabled in the room options. Make sure to set `enableEvents: true` in your room's presence options to use this feature (this is the default value).
93-
*
94-
* - Parameters:
95-
* - event: A single presence event type ``PresenceEventType`` to subscribe to.
96-
* - callback: The listener closure for capturing room ``PresenceEvent`` events.
97-
*
98-
* - Returns: A subscription that can be used to unsubscribe from ``PresenceEvent`` events.
99-
*/
100-
@discardableResult
101-
func subscribe(event: PresenceEventType, _ callback: @escaping @MainActor (PresenceEvent) -> Void) -> Subscription
102-
103-
/**
104-
* Subscribes a given listener to different presence events in the chat room.
105-
*
106-
* Note that it is a programmer error to call this method if presence events are not enabled in the room options. Make sure to set `enableEvents: true` in your room's presence options to use this feature (this is the default value).
107-
*
108-
* - Parameters:
109-
* - events: An array of presence event types ``PresenceEventType`` to subscribe to.
110-
* - callback: The listener closure for capturing room ``PresenceEvent`` events.
111-
*
112-
* - Returns: A subscription that can be used to unsubscribe from ``PresenceEvent`` events.
113-
*/
114-
@discardableResult
115-
func subscribe(events: [PresenceEventType], _ callback: @escaping @MainActor (PresenceEvent) -> Void) -> Subscription
116-
11789
/**
11890
* Method to join room presence, will emit an enter event to all subscribers. Repeat calls will trigger more enter events.
11991
* In oppose to ``enter(data:)`` it doesn't publish any custom presence data.
@@ -142,7 +114,7 @@ public protocol Presence: AnyObject, Sendable {
142114
// swiftlint:disable:next missing_docs
143115
public extension Presence {
144116
/**
145-
* Subscribes to all presence events.
117+
* Subscribes to all presence events in the chat room.
146118
*
147119
* Note that it is a programmer error to call this method if presence events are not enabled in the room options. Make sure to set `enableEvents: true` in your room's presence options to use this feature (this is the default value).
148120
*
@@ -167,74 +139,10 @@ public extension Presence {
167139
return subscriptionAsyncSequence
168140
}
169141

170-
/**
171-
* Subscribes a given listener to a particular presence event in the chat room.
172-
*
173-
* Note that it is a programmer error to call this method if presence events are not enabled in the room options. Make sure to set `enableEvents: true` in your room's presence options to use this feature (this is the default value).
174-
*
175-
* - Parameters:
176-
* - event: A single presence event type ``PresenceEventType`` to subscribe to.
177-
* - bufferingPolicy: The ``BufferingPolicy`` for the created subscription.
178-
*
179-
* - Returns: A subscription `AsyncSequence` that can be used to iterate through ``PresenceEvent`` events.
180-
*/
181-
func subscribe(event: PresenceEventType, bufferingPolicy: BufferingPolicy) -> SubscriptionAsyncSequence<PresenceEvent> {
182-
let subscriptionAsyncSequence = SubscriptionAsyncSequence<PresenceEvent>(bufferingPolicy: bufferingPolicy)
183-
184-
let subscription = subscribe(event: event) { presence in
185-
subscriptionAsyncSequence.emit(presence)
186-
}
187-
188-
subscriptionAsyncSequence.addTerminationHandler {
189-
Task { @MainActor in
190-
subscription.unsubscribe()
191-
}
192-
}
193-
194-
return subscriptionAsyncSequence
195-
}
196-
197-
/**
198-
* Subscribes a given listener to different presence events in the chat room.
199-
*
200-
* Note that it is a programmer error to call this method if presence events are not enabled in the room options. Make sure to set `enableEvents: true` in your room's presence options to use this feature (this is the default value).
201-
*
202-
* - Parameters:
203-
* - events: An array of presence event types ``PresenceEventType`` to subscribe to.
204-
* - bufferingPolicy: The ``BufferingPolicy`` for the created subscription.
205-
*
206-
* - Returns: A subscription `AsyncSequence` that can be used to iterate through ``PresenceEvent`` events.
207-
*/
208-
func subscribe(events: [PresenceEventType], bufferingPolicy: BufferingPolicy) -> SubscriptionAsyncSequence<PresenceEvent> {
209-
let subscriptionAsyncSequence = SubscriptionAsyncSequence<PresenceEvent>(bufferingPolicy: bufferingPolicy)
210-
211-
let subscription = subscribe(events: events) { presence in
212-
subscriptionAsyncSequence.emit(presence)
213-
}
214-
215-
subscriptionAsyncSequence.addTerminationHandler {
216-
Task { @MainActor in
217-
subscription.unsubscribe()
218-
}
219-
}
220-
221-
return subscriptionAsyncSequence
222-
}
223-
224142
/// Same as calling ``subscribe(bufferingPolicy:)`` with ``BufferingPolicy/unbounded``.
225143
func subscribe() -> SubscriptionAsyncSequence<PresenceEvent> {
226144
subscribe(bufferingPolicy: .unbounded)
227145
}
228-
229-
/// Same as calling ``subscribe(event:bufferingPolicy:)`` with ``BufferingPolicy/unbounded``.
230-
func subscribe(event: PresenceEventType) -> SubscriptionAsyncSequence<PresenceEvent> {
231-
subscribe(event: event, bufferingPolicy: .unbounded)
232-
}
233-
234-
/// Same as calling ``subscribe(events:bufferingPolicy:)`` with ``BufferingPolicy/unbounded``.
235-
func subscribe(events: [PresenceEventType]) -> SubscriptionAsyncSequence<PresenceEvent> {
236-
subscribe(events: events, bufferingPolicy: .unbounded)
237-
}
238146
}
239147

240148
/**
@@ -324,6 +232,25 @@ public enum PresenceEventType: Sendable {
324232
.update
325233
}
326234
}
235+
236+
internal init(ablyCocoaPresenceAction action: ARTPresenceAction) {
237+
switch action {
238+
case .present:
239+
self = .present
240+
case .enter:
241+
self = .enter
242+
case .leave:
243+
self = .leave
244+
case .update:
245+
self = .update
246+
case .absent:
247+
// Absent is not expected in subscriptions, default to leave
248+
self = .leave
249+
@unknown default:
250+
// Default to leave for any unknown actions
251+
self = .leave
252+
}
253+
}
327254
}
328255

329256
/**

Tests/AblyChatTests/DefaultPresenceTests.swift

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,5 +432,54 @@ struct DefaultPresenceTests {
432432

433433
// MARK: CHA-PR7
434434

435-
// TODO: Test (https://github.com/ably/ably-chat-swift/issues/396)
435+
// @spec CHA-PR7a
436+
@Test
437+
func usersMaySubscribeToAllPresenceEvents() async throws {
438+
// Given
439+
let channel = await MockRealtimeChannel(name: "basketball::$chat::$chatMessages")
440+
let logger = TestLogger()
441+
let roomLifecycleManager = await MockRoomLifecycleManager()
442+
let defaultPresence = await DefaultPresence(
443+
channel: channel,
444+
roomLifecycleManager: roomLifecycleManager,
445+
roomName: "basketball",
446+
logger: logger,
447+
options: .init(),
448+
)
449+
450+
// Given
451+
let subscription = await defaultPresence.subscribe() // CHA-PR7a
452+
453+
// When
454+
subscription.emit(PresenceEvent(type: .present, member: PresenceMember(clientID: "client1", data: nil, extras: nil, updatedAt: Date())))
455+
456+
// Then
457+
let presentEvent = try #require(await subscription.first { _ in true })
458+
#expect(presentEvent.type == .present)
459+
#expect(presentEvent.member.clientID == "client1")
460+
461+
// When
462+
subscription.emit(PresenceEvent(type: .enter, member: PresenceMember(clientID: "client1", data: nil, extras: nil, updatedAt: Date())))
463+
464+
// Then
465+
let enterEvent = try #require(await subscription.first { _ in true })
466+
#expect(enterEvent.type == .enter)
467+
#expect(enterEvent.member.clientID == "client1")
468+
469+
// When
470+
subscription.emit(PresenceEvent(type: .update, member: PresenceMember(clientID: "client1", data: nil, extras: nil, updatedAt: Date())))
471+
472+
// Then
473+
let updateEvent = try #require(await subscription.first { _ in true })
474+
#expect(updateEvent.type == .update)
475+
#expect(updateEvent.member.clientID == "client1")
476+
477+
// When
478+
subscription.emit(PresenceEvent(type: .leave, member: PresenceMember(clientID: "client1", data: nil, extras: nil, updatedAt: Date())))
479+
480+
// Then
481+
let leaveEvent = try #require(await subscription.first { _ in true })
482+
#expect(leaveEvent.type == .leave)
483+
#expect(leaveEvent.member.clientID == "client1")
484+
}
436485
}

Tests/AblyChatTests/Helpers/Helpers.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,6 @@ extension ARTPresenceMessage {
8585
}
8686
}
8787

88-
extension [PresenceEventType] {
89-
static let all = [
90-
PresenceEventType.present,
91-
PresenceEventType.enter,
92-
PresenceEventType.leave,
93-
PresenceEventType.update,
94-
]
95-
}
96-
9788
/// Compares Any to another Any which is unavailable by default in swift for type safety, but useful to have in tests.
9889
func compareAny(_ any1: Any?, with any2: Any?) -> Bool {
9990
guard let any1, let any2 else {

Tests/AblyChatTests/IntegrationTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,8 @@ struct IntegrationTests {
375375

376376
// MARK: - Presence
377377

378-
// (1) Subscribe to presence
379-
let rxPresenceSubscription = rxRoom.presence.subscribe(events: [.enter, .leave, .update])
378+
// (1) Subscribe to all presence events
379+
let rxPresenceSubscription = rxRoom.presence.subscribe()
380380

381381
// (2) Send `.enter` presence event with custom data on the other client and check that we receive it on the subscription
382382
try await txRoom.presence.enter(withData: ["randomData": "randomValue"])

0 commit comments

Comments
 (0)