From 1e3c071d5a44ea58101d2510e5df4d53fb4bc82b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 5 Dec 2024 09:24:29 -0300 Subject: [PATCH 1/4] Remove unnecessary argument `.unbounded` is the default value. --- Example/AblyChatExample/Mocks/MockSubscription.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/AblyChatExample/Mocks/MockSubscription.swift b/Example/AblyChatExample/Mocks/MockSubscription.swift index 0a39833b..eb702e54 100644 --- a/Example/AblyChatExample/Mocks/MockSubscription.swift +++ b/Example/AblyChatExample/Mocks/MockSubscription.swift @@ -20,7 +20,7 @@ struct MockSubscription: Sendable, AsyncSequence { } init(randomElement: @escaping @Sendable () -> Element, interval: Double) { - let (stream, continuation) = AsyncStream.makeStream(of: Element.self, bufferingPolicy: .unbounded) + let (stream, continuation) = AsyncStream.makeStream(of: Element.self) self.continuation = continuation let timer: AsyncTimerSequence = .init(interval: .seconds(interval), clock: .init()) mergedSequence = merge(stream, timer.map { _ in From 1ede4fbeacba62f13e48eaca96c7a98244f3672f Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 5 Dec 2024 09:22:28 -0300 Subject: [PATCH 2/4] Pass correct bufferingPolicy in DefaultTyping.subscribe Mistake in d165504. --- Sources/AblyChat/DefaultTyping.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AblyChat/DefaultTyping.swift b/Sources/AblyChat/DefaultTyping.swift index 0c41e917..74be048a 100644 --- a/Sources/AblyChat/DefaultTyping.swift +++ b/Sources/AblyChat/DefaultTyping.swift @@ -22,7 +22,7 @@ internal final class DefaultTyping: Typing { // (CHA-T6) Users may subscribe to typing events – updates to a set of clientIDs that are typing. This operation, like all subscription operations, has no side-effects in relation to room lifecycle. internal func subscribe(bufferingPolicy: BufferingPolicy) async -> Subscription { - let subscription = Subscription(bufferingPolicy: .unbounded) + let subscription = Subscription(bufferingPolicy: bufferingPolicy) let eventTracker = EventTracker() channel.presence.subscribe { [weak self] message in From 2703782357527929e5ae5d13ed234dc38ed407f6 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 5 Dec 2024 09:48:53 -0300 Subject: [PATCH 3/4] Make sure all public subscription API has a `bufferingPolicy` parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not sure why I didn’t give these APIs such a parameter when I introduced the public API in 20e7f5f; if there was a good reason then it’s not obvious to me now. --- Example/AblyChatExample/ContentView.swift | 2 +- Example/AblyChatExample/Mocks/MockClients.swift | 14 +++++++------- Sources/AblyChat/DefaultMessages.swift | 4 ++-- Sources/AblyChat/DefaultOccupancy.swift | 4 ++-- Sources/AblyChat/DefaultPresence.swift | 12 ++++++------ .../AblyChat/DefaultRoomLifecycleContributor.swift | 4 ++-- Sources/AblyChat/DefaultRoomReactions.swift | 4 ++-- Sources/AblyChat/DefaultTyping.swift | 4 ++-- Sources/AblyChat/EmitsDiscontinuities.swift | 2 +- Sources/AblyChat/Presence.swift | 4 ++-- Sources/AblyChat/RoomFeature.swift | 4 ++-- Tests/AblyChatTests/DefaultMessagesTests.swift | 2 +- .../AblyChatTests/DefaultRoomReactionsTests.swift | 2 +- Tests/AblyChatTests/IntegrationTests.swift | 2 +- Tests/AblyChatTests/Mocks/MockFeatureChannel.swift | 4 ++-- 15 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Example/AblyChatExample/ContentView.swift b/Example/AblyChatExample/ContentView.swift index 4f334144..8d17b7c8 100644 --- a/Example/AblyChatExample/ContentView.swift +++ b/Example/AblyChatExample/ContentView.swift @@ -206,7 +206,7 @@ struct ContentView: View { // Continue listening for new presence events on a background task so this function can return Task { - for await event in try await room().presence.subscribe(events: [.enter, .leave, .update]) { + for await event in try await room().presence.subscribe(events: [.enter, .leave, .update], bufferingPolicy: .unbounded) { withAnimation { let status = event.data?.userCustomData?["status"]?.value as? String let clientPresenceChangeMessage = "\(event.clientID) \(event.action.displayedText)" diff --git a/Example/AblyChatExample/Mocks/MockClients.swift b/Example/AblyChatExample/Mocks/MockClients.swift index 1a950d65..14fdcfc2 100644 --- a/Example/AblyChatExample/Mocks/MockClients.swift +++ b/Example/AblyChatExample/Mocks/MockClients.swift @@ -144,7 +144,7 @@ actor MockMessages: Messages { return message } - func subscribeToDiscontinuities() -> Subscription { + func subscribeToDiscontinuities(bufferingPolicy _: BufferingPolicy) -> Subscription { fatalError("Not yet implemented") } } @@ -195,7 +195,7 @@ actor MockRoomReactions: RoomReactions { .init(mockAsyncSequence: createSubscription()) } - func subscribeToDiscontinuities() -> Subscription { + func subscribeToDiscontinuities(bufferingPolicy _: BufferingPolicy) -> Subscription { fatalError("Not yet implemented") } } @@ -244,7 +244,7 @@ actor MockTyping: Typing { } } - func subscribeToDiscontinuities() -> Subscription { + func subscribeToDiscontinuities(bufferingPolicy _: BufferingPolicy) -> Subscription { fatalError("Not yet implemented") } } @@ -340,15 +340,15 @@ actor MockPresence: Presence { } } - func subscribe(event _: PresenceEventType) -> Subscription { + func subscribe(event _: PresenceEventType, bufferingPolicy _: BufferingPolicy) -> Subscription { .init(mockAsyncSequence: createSubscription()) } - func subscribe(events _: [PresenceEventType]) -> Subscription { + func subscribe(events _: [PresenceEventType], bufferingPolicy _: BufferingPolicy) -> Subscription { .init(mockAsyncSequence: createSubscription()) } - func subscribeToDiscontinuities() -> Subscription { + func subscribeToDiscontinuities(bufferingPolicy _: BufferingPolicy) -> Subscription { fatalError("Not yet implemented") } } @@ -383,7 +383,7 @@ actor MockOccupancy: Occupancy { OccupancyEvent(connections: 10, presenceMembers: 5) } - func subscribeToDiscontinuities() -> Subscription { + func subscribeToDiscontinuities(bufferingPolicy _: BufferingPolicy) -> Subscription { fatalError("Not yet implemented") } } diff --git a/Sources/AblyChat/DefaultMessages.swift b/Sources/AblyChat/DefaultMessages.swift index 84836681..5d0383a7 100644 --- a/Sources/AblyChat/DefaultMessages.swift +++ b/Sources/AblyChat/DefaultMessages.swift @@ -109,8 +109,8 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities { } // (CHA-M7) Users may subscribe to discontinuity events to know when there’s been a break in messages that they need to resolve. Their listener will be called when a discontinuity event is triggered from the room lifecycle. - internal func subscribeToDiscontinuities() async -> Subscription { - await featureChannel.subscribeToDiscontinuities() + internal func subscribeToDiscontinuities(bufferingPolicy: BufferingPolicy) async -> Subscription { + await featureChannel.subscribeToDiscontinuities(bufferingPolicy: bufferingPolicy) } private func getBeforeSubscriptionStart(_ uuid: UUID, params: QueryOptions) async throws -> any PaginatedResult { diff --git a/Sources/AblyChat/DefaultOccupancy.swift b/Sources/AblyChat/DefaultOccupancy.swift index 31c95dd3..9ae67bb2 100644 --- a/Sources/AblyChat/DefaultOccupancy.swift +++ b/Sources/AblyChat/DefaultOccupancy.swift @@ -50,7 +50,7 @@ internal final class DefaultOccupancy: Occupancy, EmitsDiscontinuities { } // (CHA-O5) Users may subscribe to discontinuity events to know when there’s been a break in occupancy. Their listener will be called when a discontinuity event is triggered from the room lifecycle. For occupancy, there shouldn’t need to be user action as most channels will send occupancy updates regularly as clients churn. - internal func subscribeToDiscontinuities() async -> Subscription { - await featureChannel.subscribeToDiscontinuities() + internal func subscribeToDiscontinuities(bufferingPolicy: BufferingPolicy) async -> Subscription { + await featureChannel.subscribeToDiscontinuities(bufferingPolicy: bufferingPolicy) } } diff --git a/Sources/AblyChat/DefaultPresence.swift b/Sources/AblyChat/DefaultPresence.swift index e3e9d96c..e68d57a5 100644 --- a/Sources/AblyChat/DefaultPresence.swift +++ b/Sources/AblyChat/DefaultPresence.swift @@ -164,9 +164,9 @@ internal final class DefaultPresence: Presence, EmitsDiscontinuities { // (CHA-PR7a) Users may provide a listener to subscribe to all presence events in a room. // (CHA-PR7b) Users may provide a listener and a list of selected presence events, to subscribe to just those events in a room. - internal func subscribe(event: PresenceEventType) async -> Subscription { + internal func subscribe(event: PresenceEventType, bufferingPolicy: BufferingPolicy) async -> Subscription { logger.log(message: "Subscribing to presence events", level: .debug) - let subscription = Subscription(bufferingPolicy: .unbounded) + let subscription = Subscription(bufferingPolicy: bufferingPolicy) channel.presence.subscribe(event.toARTPresenceAction()) { [processPresenceSubscribe, logger] message in logger.log(message: "Received presence message: \(message)", level: .debug) Task { @@ -178,9 +178,9 @@ internal final class DefaultPresence: Presence, EmitsDiscontinuities { return subscription } - internal func subscribe(events: [PresenceEventType]) async -> Subscription { + internal func subscribe(events: [PresenceEventType], bufferingPolicy: BufferingPolicy) async -> Subscription { logger.log(message: "Subscribing to presence events", level: .debug) - let subscription = Subscription(bufferingPolicy: .unbounded) + let subscription = Subscription(bufferingPolicy: bufferingPolicy) for event in events { channel.presence.subscribe(event.toARTPresenceAction()) { [processPresenceSubscribe, logger] message in logger.log(message: "Received presence message: \(message)", level: .debug) @@ -194,8 +194,8 @@ internal final class DefaultPresence: Presence, EmitsDiscontinuities { } // (CHA-PR8) Users may subscribe to discontinuity events to know when there’s been a break in presence. Their listener will be called when a discontinuity event is triggered from the room lifecycle. For presence, there shouldn’t need to be user action as the underlying core SDK will heal the presence set. - internal func subscribeToDiscontinuities() async -> Subscription { - await featureChannel.subscribeToDiscontinuities() + internal func subscribeToDiscontinuities(bufferingPolicy: BufferingPolicy) async -> Subscription { + await featureChannel.subscribeToDiscontinuities(bufferingPolicy: bufferingPolicy) } private func decodePresenceData(from data: Any?) -> PresenceData? { diff --git a/Sources/AblyChat/DefaultRoomLifecycleContributor.swift b/Sources/AblyChat/DefaultRoomLifecycleContributor.swift index d8fee69f..f30a87fc 100644 --- a/Sources/AblyChat/DefaultRoomLifecycleContributor.swift +++ b/Sources/AblyChat/DefaultRoomLifecycleContributor.swift @@ -18,8 +18,8 @@ internal actor DefaultRoomLifecycleContributor: RoomLifecycleContributor, EmitsD } } - internal func subscribeToDiscontinuities() -> Subscription { - let subscription = Subscription(bufferingPolicy: .unbounded) + internal func subscribeToDiscontinuities(bufferingPolicy: BufferingPolicy) -> Subscription { + let subscription = Subscription(bufferingPolicy: bufferingPolicy) // TODO: clean up old subscriptions (https://github.com/ably-labs/ably-chat-swift/issues/36) discontinuitySubscriptions.append(subscription) return subscription diff --git a/Sources/AblyChat/DefaultRoomReactions.swift b/Sources/AblyChat/DefaultRoomReactions.swift index 1a670010..171bf8e7 100644 --- a/Sources/AblyChat/DefaultRoomReactions.swift +++ b/Sources/AblyChat/DefaultRoomReactions.swift @@ -80,8 +80,8 @@ internal final class DefaultRoomReactions: RoomReactions, EmitsDiscontinuities { } // (CHA-ER5) Users may subscribe to discontinuity events to know when there’s been a break in reactions that they need to resolve. Their listener will be called when a discontinuity event is triggered from the room lifecycle. - internal func subscribeToDiscontinuities() async -> Subscription { - await featureChannel.subscribeToDiscontinuities() + internal func subscribeToDiscontinuities(bufferingPolicy: BufferingPolicy) async -> Subscription { + await featureChannel.subscribeToDiscontinuities(bufferingPolicy: bufferingPolicy) } private enum RoomReactionsError: Error { diff --git a/Sources/AblyChat/DefaultTyping.swift b/Sources/AblyChat/DefaultTyping.swift index 74be048a..5964ef11 100644 --- a/Sources/AblyChat/DefaultTyping.swift +++ b/Sources/AblyChat/DefaultTyping.swift @@ -160,8 +160,8 @@ internal final class DefaultTyping: Typing { } // (CHA-T7) Users may subscribe to discontinuity events to know when there’s been a break in typing indicators. Their listener will be called when a discontinuity event is triggered from the room lifecycle. For typing, there shouldn’t need to be user action as the underlying core SDK will heal the presence set. - internal func subscribeToDiscontinuities() async -> Subscription { - await featureChannel.subscribeToDiscontinuities() + internal func subscribeToDiscontinuities(bufferingPolicy: BufferingPolicy) async -> Subscription { + await featureChannel.subscribeToDiscontinuities(bufferingPolicy: bufferingPolicy) } private func processPresenceGet(members: [ARTPresenceMessage]?, error: ARTErrorInfo?) throws -> Set { diff --git a/Sources/AblyChat/EmitsDiscontinuities.swift b/Sources/AblyChat/EmitsDiscontinuities.swift index 21b9e5ad..1763668e 100644 --- a/Sources/AblyChat/EmitsDiscontinuities.swift +++ b/Sources/AblyChat/EmitsDiscontinuities.swift @@ -10,5 +10,5 @@ public struct DiscontinuityEvent: Sendable, Equatable { } public protocol EmitsDiscontinuities { - func subscribeToDiscontinuities() async -> Subscription + func subscribeToDiscontinuities(bufferingPolicy: BufferingPolicy) async -> Subscription } diff --git a/Sources/AblyChat/Presence.swift b/Sources/AblyChat/Presence.swift index 7c58ba88..7916f29c 100644 --- a/Sources/AblyChat/Presence.swift +++ b/Sources/AblyChat/Presence.swift @@ -82,8 +82,8 @@ public protocol Presence: AnyObject, Sendable, EmitsDiscontinuities { func enter(data: PresenceData?) async throws func update(data: PresenceData?) async throws func leave(data: PresenceData?) async throws - func subscribe(event: PresenceEventType) async -> Subscription - func subscribe(events: [PresenceEventType]) async -> Subscription + func subscribe(event: PresenceEventType, bufferingPolicy: BufferingPolicy) async -> Subscription + func subscribe(events: [PresenceEventType], bufferingPolicy: BufferingPolicy) async -> Subscription } public struct PresenceMember: Sendable { diff --git a/Sources/AblyChat/RoomFeature.swift b/Sources/AblyChat/RoomFeature.swift index 5383b8ff..3e571f7d 100644 --- a/Sources/AblyChat/RoomFeature.swift +++ b/Sources/AblyChat/RoomFeature.swift @@ -57,8 +57,8 @@ internal struct DefaultFeatureChannel: FeatureChannel { internal var contributor: DefaultRoomLifecycleContributor internal var roomLifecycleManager: RoomLifecycleManager - internal func subscribeToDiscontinuities() async -> Subscription { - await contributor.subscribeToDiscontinuities() + internal func subscribeToDiscontinuities(bufferingPolicy: BufferingPolicy) async -> Subscription { + await contributor.subscribeToDiscontinuities(bufferingPolicy: bufferingPolicy) } internal func waitToBeAbleToPerformPresenceOperations(requestedByFeature requester: RoomFeature) async throws(ARTErrorInfo) { diff --git a/Tests/AblyChatTests/DefaultMessagesTests.swift b/Tests/AblyChatTests/DefaultMessagesTests.swift index a771fddf..8fb07ac1 100644 --- a/Tests/AblyChatTests/DefaultMessagesTests.swift +++ b/Tests/AblyChatTests/DefaultMessagesTests.swift @@ -81,7 +81,7 @@ struct DefaultMessagesTests { // When: The feature channel emits a discontinuity through `subscribeToDiscontinuities` let featureChannelDiscontinuity = DiscontinuityEvent(error: ARTErrorInfo.createUnknownError() /* arbitrary */ ) - let messagesDiscontinuitySubscription = await messages.subscribeToDiscontinuities() + let messagesDiscontinuitySubscription = await messages.subscribeToDiscontinuities(bufferingPolicy: .unbounded) await featureChannel.emitDiscontinuity(featureChannelDiscontinuity) // Then: The DefaultMessages instance emits this discontinuity through `subscribeToDiscontinuities` diff --git a/Tests/AblyChatTests/DefaultRoomReactionsTests.swift b/Tests/AblyChatTests/DefaultRoomReactionsTests.swift index e9233750..f608f876 100644 --- a/Tests/AblyChatTests/DefaultRoomReactionsTests.swift +++ b/Tests/AblyChatTests/DefaultRoomReactionsTests.swift @@ -72,7 +72,7 @@ struct DefaultRoomReactionsTests { // When: The feature channel emits a discontinuity through `subscribeToDiscontinuities` let featureChannelDiscontinuity = DiscontinuityEvent(error: ARTErrorInfo.createUnknownError() /* arbitrary */ ) - let messagesDiscontinuitySubscription = await roomReactions.subscribeToDiscontinuities() + let messagesDiscontinuitySubscription = await roomReactions.subscribeToDiscontinuities(bufferingPolicy: .unbounded) await featureChannel.emitDiscontinuity(featureChannelDiscontinuity) // Then: The DefaultRoomReactions instance emits this discontinuity through `subscribeToDiscontinuities` diff --git a/Tests/AblyChatTests/IntegrationTests.swift b/Tests/AblyChatTests/IntegrationTests.swift index a0104399..26abfb7f 100644 --- a/Tests/AblyChatTests/IntegrationTests.swift +++ b/Tests/AblyChatTests/IntegrationTests.swift @@ -202,7 +202,7 @@ struct IntegrationTests { // MARK: - Presence // (1) Subscribe to presence - let rxPresenceSubscription = await rxRoom.presence.subscribe(events: [.enter, .leave, .update]) + let rxPresenceSubscription = await rxRoom.presence.subscribe(events: [.enter, .leave, .update], bufferingPolicy: .unbounded) // (2) Send `.enter` presence event with custom data on the other client and check that we receive it on the subscription try await txRoom.presence.enter(data: .init(userCustomData: ["randomData": .string("randomValue")])) diff --git a/Tests/AblyChatTests/Mocks/MockFeatureChannel.swift b/Tests/AblyChatTests/Mocks/MockFeatureChannel.swift index 63df47a4..d18e6e92 100644 --- a/Tests/AblyChatTests/Mocks/MockFeatureChannel.swift +++ b/Tests/AblyChatTests/Mocks/MockFeatureChannel.swift @@ -15,8 +15,8 @@ final actor MockFeatureChannel: FeatureChannel { resultOfWaitToBeAbleToPerformPresenceOperations = resultOfWaitToBeAblePerformPresenceOperations } - func subscribeToDiscontinuities() async -> Subscription { - let subscription = Subscription(bufferingPolicy: .unbounded) + func subscribeToDiscontinuities(bufferingPolicy: BufferingPolicy) async -> Subscription { + let subscription = Subscription(bufferingPolicy: bufferingPolicy) discontinuitySubscriptions.append(subscription) return subscription } From 403292c2a82e8bc13232be3169ee4043ff1d986a Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 5 Dec 2024 09:07:10 -0300 Subject: [PATCH 4/4] Make bufferingPolicy parameter optional in public API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When I introduced the public API in 20e7f5f, I didn’t want users to be in a situation where there was an buffer of unbounded size growing without them even knowing about it, and I hence forced them to be explicit about their choice of buffering policy. But, a couple of things: - Marat pointed out that this makes our code examples and README look quite cluttered, with `bufferingPolicy` arguments all over the place. This distracts from demonstrating the functionality of the library. - Swift’s own `AsyncStream` defaults to a buffering policy of `.unbounded`. If doing so is good enough for them, then it’s probably good enough for us. So, default to `.unbounded`. Resolves #172. --- Example/AblyChatExample/ContentView.swift | 14 +++++++------- Sources/AblyChat/Connection.swift | 10 ++++++++++ Sources/AblyChat/EmitsDiscontinuities.swift | 10 ++++++++++ Sources/AblyChat/Messages.swift | 10 ++++++++++ Sources/AblyChat/Occupancy.swift | 10 ++++++++++ Sources/AblyChat/Presence.swift | 18 ++++++++++++++++++ Sources/AblyChat/Room.swift | 10 ++++++++++ Sources/AblyChat/RoomReactions.swift | 10 ++++++++++ Sources/AblyChat/Typing.swift | 10 ++++++++++ Tests/AblyChatTests/DefaultMessagesTests.swift | 6 +++--- .../DefaultRoomReactionsTests.swift | 4 ++-- Tests/AblyChatTests/DefaultRoomTests.swift | 2 +- Tests/AblyChatTests/IntegrationTests.swift | 14 +++++++------- 13 files changed, 108 insertions(+), 20 deletions(-) diff --git a/Example/AblyChatExample/ContentView.swift b/Example/AblyChatExample/ContentView.swift index 8d17b7c8..efa5c647 100644 --- a/Example/AblyChatExample/ContentView.swift +++ b/Example/AblyChatExample/ContentView.swift @@ -169,7 +169,7 @@ struct ContentView: View { } func showMessages() async throws { - let messagesSubscription = try await room().messages.subscribe(bufferingPolicy: .unbounded) + let messagesSubscription = try await room().messages.subscribe() let previousMessages = try await messagesSubscription.getPreviousMessages(params: .init()) for message in previousMessages.items { @@ -189,7 +189,7 @@ struct ContentView: View { } func showReactions() async throws { - let reactionSubscription = try await room().reactions.subscribe(bufferingPolicy: .unbounded) + let reactionSubscription = try await room().reactions.subscribe() // Continue listening for reactions on a background task so this function can return Task { @@ -206,7 +206,7 @@ struct ContentView: View { // Continue listening for new presence events on a background task so this function can return Task { - for await event in try await room().presence.subscribe(events: [.enter, .leave, .update], bufferingPolicy: .unbounded) { + for await event in try await room().presence.subscribe(events: [.enter, .leave, .update]) { withAnimation { let status = event.data?.userCustomData?["status"]?.value as? String let clientPresenceChangeMessage = "\(event.clientID) \(event.action.displayedText)" @@ -219,7 +219,7 @@ struct ContentView: View { } func showTypings() async throws { - let typingSubscription = try await room().typing.subscribe(bufferingPolicy: .unbounded) + let typingSubscription = try await room().typing.subscribe() // Continue listening for typing events on a background task so this function can return Task { for await typing in typingSubscription { @@ -241,7 +241,7 @@ struct ContentView: View { } Task { - for await event in try await room().occupancy.subscribe(bufferingPolicy: .unbounded) { + for await event in try await room().occupancy.subscribe() { withAnimation { occupancyInfo = "Connections: \(event.presenceMembers) (\(event.connections))" } @@ -250,7 +250,7 @@ struct ContentView: View { } func printConnectionStatusChange() async { - let connectionSubsciption = chatClient.connection.onStatusChange(bufferingPolicy: .unbounded) + let connectionSubsciption = chatClient.connection.onStatusChange() // Continue listening for connection status change on a background task so this function can return Task { @@ -263,7 +263,7 @@ struct ContentView: View { func showRoomStatus() async throws { // Continue listening for status change events on a background task so this function can return Task { - for await status in try await room().onStatusChange(bufferingPolicy: .unbounded) { + for await status in try await room().onStatusChange() { withAnimation { if status.current.isAttaching { statusInfo = "\(status.current)...".capitalized diff --git a/Sources/AblyChat/Connection.swift b/Sources/AblyChat/Connection.swift index 41d0f89f..565ff047 100644 --- a/Sources/AblyChat/Connection.swift +++ b/Sources/AblyChat/Connection.swift @@ -5,6 +5,16 @@ public protocol Connection: AnyObject, Sendable { // TODO: (https://github.com/ably-labs/ably-chat-swift/issues/12): consider how to avoid the need for an unwrap var error: ARTErrorInfo? { get async } func onStatusChange(bufferingPolicy: BufferingPolicy) -> Subscription + /// Same as calling ``onStatusChange(bufferingPolicy:)`` with ``BufferingPolicy.unbounded``. + /// + /// The `Connection` protocol provides a default implementation of this method. + func onStatusChange() -> Subscription +} + +public extension Connection { + func onStatusChange() -> Subscription { + onStatusChange(bufferingPolicy: .unbounded) + } } public enum ConnectionStatus: Sendable { diff --git a/Sources/AblyChat/EmitsDiscontinuities.swift b/Sources/AblyChat/EmitsDiscontinuities.swift index 1763668e..de2bb821 100644 --- a/Sources/AblyChat/EmitsDiscontinuities.swift +++ b/Sources/AblyChat/EmitsDiscontinuities.swift @@ -11,4 +11,14 @@ public struct DiscontinuityEvent: Sendable, Equatable { public protocol EmitsDiscontinuities { func subscribeToDiscontinuities(bufferingPolicy: BufferingPolicy) async -> Subscription + /// Same as calling ``subscribeToDiscontinuities(bufferingPolicy:)`` with ``BufferingPolicy.unbounded``. + /// + /// The `EmitsDiscontinuities` protocol provides a default implementation of this method. + func subscribeToDiscontinuities() async -> Subscription +} + +public extension EmitsDiscontinuities { + func subscribeToDiscontinuities() async -> Subscription { + await subscribeToDiscontinuities(bufferingPolicy: .unbounded) + } } diff --git a/Sources/AblyChat/Messages.swift b/Sources/AblyChat/Messages.swift index b880b83c..2205e975 100644 --- a/Sources/AblyChat/Messages.swift +++ b/Sources/AblyChat/Messages.swift @@ -2,11 +2,21 @@ import Ably public protocol Messages: AnyObject, Sendable, EmitsDiscontinuities { func subscribe(bufferingPolicy: BufferingPolicy) async throws -> MessageSubscription + /// Same as calling ``subscribe(bufferingPolicy:)`` with ``BufferingPolicy.unbounded``. + /// + /// The `Messages` protocol provides a default implementation of this method. + func subscribe() async throws -> MessageSubscription func get(options: QueryOptions) async throws -> any PaginatedResult func send(params: SendMessageParams) async throws -> Message var channel: RealtimeChannelProtocol { get } } +public extension Messages { + func subscribe() async throws -> MessageSubscription { + try await subscribe(bufferingPolicy: .unbounded) + } +} + public struct SendMessageParams: Sendable { public var text: String public var metadata: MessageMetadata? diff --git a/Sources/AblyChat/Occupancy.swift b/Sources/AblyChat/Occupancy.swift index 7ed17f13..d3004f75 100644 --- a/Sources/AblyChat/Occupancy.swift +++ b/Sources/AblyChat/Occupancy.swift @@ -2,10 +2,20 @@ import Ably public protocol Occupancy: AnyObject, Sendable, EmitsDiscontinuities { func subscribe(bufferingPolicy: BufferingPolicy) async -> Subscription + /// Same as calling ``subscribe(bufferingPolicy:)`` with ``BufferingPolicy.unbounded``. + /// + /// The `Occupancy` protocol provides a default implementation of this method. + func subscribe() async -> Subscription func get() async throws -> OccupancyEvent var channel: RealtimeChannelProtocol { get } } +public extension Occupancy { + func subscribe() async -> Subscription { + await subscribe(bufferingPolicy: .unbounded) + } +} + // (CHA-O2) The occupancy event format is shown here (https://sdk.ably.com/builds/ably/specification/main/chat-features/#chat-structs-occupancy-event) public struct OccupancyEvent: Sendable, Encodable, Decodable { public var connections: Int diff --git a/Sources/AblyChat/Presence.swift b/Sources/AblyChat/Presence.swift index 7916f29c..711ba5bf 100644 --- a/Sources/AblyChat/Presence.swift +++ b/Sources/AblyChat/Presence.swift @@ -83,7 +83,25 @@ public protocol Presence: AnyObject, Sendable, EmitsDiscontinuities { func update(data: PresenceData?) async throws func leave(data: PresenceData?) async throws func subscribe(event: PresenceEventType, bufferingPolicy: BufferingPolicy) async -> Subscription + /// Same as calling ``subscribe(event:bufferingPolicy:)`` with ``BufferingPolicy.unbounded``. + /// + /// The `Presence` protocol provides a default implementation of this method. + func subscribe(event: PresenceEventType) async -> Subscription func subscribe(events: [PresenceEventType], bufferingPolicy: BufferingPolicy) async -> Subscription + /// Same as calling ``subscribe(events:bufferingPolicy:)`` with ``BufferingPolicy.unbounded``. + /// + /// The `Presence` protocol provides a default implementation of this method. + func subscribe(events: [PresenceEventType]) async -> Subscription +} + +public extension Presence { + func subscribe(event: PresenceEventType) async -> Subscription { + await subscribe(event: event, bufferingPolicy: .unbounded) + } + + func subscribe(events: [PresenceEventType]) async -> Subscription { + await subscribe(events: events, bufferingPolicy: .unbounded) + } } public struct PresenceMember: Sendable { diff --git a/Sources/AblyChat/Room.swift b/Sources/AblyChat/Room.swift index 2d7e5186..3e913700 100644 --- a/Sources/AblyChat/Room.swift +++ b/Sources/AblyChat/Room.swift @@ -14,11 +14,21 @@ public protocol Room: AnyObject, Sendable { // TODO: change to `status` var status: RoomStatus { get async } func onStatusChange(bufferingPolicy: BufferingPolicy) async -> Subscription + /// Same as calling ``onStatusChange(bufferingPolicy:)`` with ``BufferingPolicy.unbounded``. + /// + /// The `Room` protocol provides a default implementation of this method. + func onStatusChange() async -> Subscription func attach() async throws func detach() async throws var options: RoomOptions { get } } +public extension Room { + func onStatusChange() async -> Subscription { + await onStatusChange(bufferingPolicy: .unbounded) + } +} + /// A ``Room`` that exposes additional functionality for use within the SDK. internal protocol InternalRoom: Room { func release() async diff --git a/Sources/AblyChat/RoomReactions.swift b/Sources/AblyChat/RoomReactions.swift index b50b9212..58bdb6f9 100644 --- a/Sources/AblyChat/RoomReactions.swift +++ b/Sources/AblyChat/RoomReactions.swift @@ -4,6 +4,16 @@ public protocol RoomReactions: AnyObject, Sendable, EmitsDiscontinuities { func send(params: SendReactionParams) async throws var channel: RealtimeChannelProtocol { get } func subscribe(bufferingPolicy: BufferingPolicy) async -> Subscription + /// Same as calling ``subscribe(bufferingPolicy:)`` with ``BufferingPolicy.unbounded``. + /// + /// The `RoomReactions` protocol provides a default implementation of this method. + func subscribe() async -> Subscription +} + +public extension RoomReactions { + func subscribe() async -> Subscription { + await subscribe(bufferingPolicy: .unbounded) + } } public struct SendReactionParams: Sendable { diff --git a/Sources/AblyChat/Typing.swift b/Sources/AblyChat/Typing.swift index 9613feac..7c3ea589 100644 --- a/Sources/AblyChat/Typing.swift +++ b/Sources/AblyChat/Typing.swift @@ -2,12 +2,22 @@ import Ably public protocol Typing: AnyObject, Sendable, EmitsDiscontinuities { func subscribe(bufferingPolicy: BufferingPolicy) async -> Subscription + /// Same as calling ``subscribe(bufferingPolicy:)`` with ``BufferingPolicy.unbounded``. + /// + /// The `Typing` protocol provides a default implementation of this method. + func subscribe() async -> Subscription func get() async throws -> Set func start() async throws func stop() async throws var channel: RealtimeChannelProtocol { get } } +public extension Typing { + func subscribe() async -> Subscription { + await subscribe(bufferingPolicy: .unbounded) + } +} + public struct TypingEvent: Sendable { public var currentlyTyping: Set diff --git a/Tests/AblyChatTests/DefaultMessagesTests.swift b/Tests/AblyChatTests/DefaultMessagesTests.swift index 8fb07ac1..8c4e13eb 100644 --- a/Tests/AblyChatTests/DefaultMessagesTests.swift +++ b/Tests/AblyChatTests/DefaultMessagesTests.swift @@ -17,7 +17,7 @@ struct DefaultMessagesTests { // Then await #expect(throws: ARTErrorInfo.create(withCode: 40000, status: 400, message: "channel is attached, but channelSerial is not defined"), performing: { // When - try await defaultMessages.subscribe(bufferingPolicy: .unbounded) + try await defaultMessages.subscribe() }) } @@ -56,7 +56,7 @@ struct DefaultMessagesTests { ) let featureChannel = MockFeatureChannel(channel: channel) let defaultMessages = await DefaultMessages(featureChannel: featureChannel, chatAPI: chatAPI, roomID: "basketball", clientID: "clientId", logger: TestLogger()) - let subscription = try await defaultMessages.subscribe(bufferingPolicy: .unbounded) + let subscription = try await defaultMessages.subscribe() let expectedPaginatedResult = PaginatedResultWrapper( paginatedResponse: MockHTTPPaginatedResponse.successGetMessagesWithNoItems, items: [] @@ -81,7 +81,7 @@ struct DefaultMessagesTests { // When: The feature channel emits a discontinuity through `subscribeToDiscontinuities` let featureChannelDiscontinuity = DiscontinuityEvent(error: ARTErrorInfo.createUnknownError() /* arbitrary */ ) - let messagesDiscontinuitySubscription = await messages.subscribeToDiscontinuities(bufferingPolicy: .unbounded) + let messagesDiscontinuitySubscription = await messages.subscribeToDiscontinuities() await featureChannel.emitDiscontinuity(featureChannelDiscontinuity) // Then: The DefaultMessages instance emits this discontinuity through `subscribeToDiscontinuities` diff --git a/Tests/AblyChatTests/DefaultRoomReactionsTests.swift b/Tests/AblyChatTests/DefaultRoomReactionsTests.swift index f608f876..bc6f33b4 100644 --- a/Tests/AblyChatTests/DefaultRoomReactionsTests.swift +++ b/Tests/AblyChatTests/DefaultRoomReactionsTests.swift @@ -55,7 +55,7 @@ struct DefaultRoomReactionsTests { let defaultRoomReactions = await DefaultRoomReactions(featureChannel: featureChannel, clientID: "mockClientId", roomID: "basketball", logger: TestLogger()) // When - let subscription: Subscription? = await defaultRoomReactions.subscribe(bufferingPolicy: .unbounded) + let subscription: Subscription? = await defaultRoomReactions.subscribe() // Then #expect(subscription != nil) @@ -72,7 +72,7 @@ struct DefaultRoomReactionsTests { // When: The feature channel emits a discontinuity through `subscribeToDiscontinuities` let featureChannelDiscontinuity = DiscontinuityEvent(error: ARTErrorInfo.createUnknownError() /* arbitrary */ ) - let messagesDiscontinuitySubscription = await roomReactions.subscribeToDiscontinuities(bufferingPolicy: .unbounded) + let messagesDiscontinuitySubscription = await roomReactions.subscribeToDiscontinuities() await featureChannel.emitDiscontinuity(featureChannelDiscontinuity) // Then: The DefaultRoomReactions instance emits this discontinuity through `subscribeToDiscontinuities` diff --git a/Tests/AblyChatTests/DefaultRoomTests.swift b/Tests/AblyChatTests/DefaultRoomTests.swift index 4560596a..5a6d9b41 100644 --- a/Tests/AblyChatTests/DefaultRoomTests.swift +++ b/Tests/AblyChatTests/DefaultRoomTests.swift @@ -284,7 +284,7 @@ struct DefaultRoomTests { // When: The room lifecycle manager emits a status change through `subscribeToState` let managerStatusChange = RoomStatusChange(current: .detached, previous: .detaching) // arbitrary - let roomStatusSubscription = await room.onStatusChange(bufferingPolicy: .unbounded) + let roomStatusSubscription = await room.onStatusChange() await lifecycleManager.emitStatusChange(managerStatusChange) // Then: The room emits this status change through `onStatusChange` diff --git a/Tests/AblyChatTests/IntegrationTests.swift b/Tests/AblyChatTests/IntegrationTests.swift index 26abfb7f..68a94f88 100644 --- a/Tests/AblyChatTests/IntegrationTests.swift +++ b/Tests/AblyChatTests/IntegrationTests.swift @@ -84,7 +84,7 @@ struct IntegrationTests { ) // (3) Subscribe to room status - let rxRoomStatusSubscription = await rxRoom.onStatusChange(bufferingPolicy: .unbounded) + let rxRoomStatusSubscription = await rxRoom.onStatusChange() // (4) Attach the room so we can receive messages on it try await rxRoom.attach() @@ -98,7 +98,7 @@ struct IntegrationTests { // (1) Send a message before subscribing to messages, so that later on we can check history works. // (2) Create a throwaway subscription and wait for it to receive a message. This is to make sure that rxRoom has seen the message that we send here, so that the first message we receive on the subscription created in (5) is that which we’ll send in (6), and not that which we send here. - let throwawayRxMessageSubscription = try await rxRoom.messages.subscribe(bufferingPolicy: .unbounded) + let throwawayRxMessageSubscription = try await rxRoom.messages.subscribe() // (3) Send the message let txMessageBeforeRxSubscribe = try await txRoom.messages.send(params: .init(text: "Hello from txRoom, before rxRoom subscribe")) @@ -108,7 +108,7 @@ struct IntegrationTests { #expect(throwawayRxMessage == txMessageBeforeRxSubscribe) // (5) Subscribe to messages - let rxMessageSubscription = try await rxRoom.messages.subscribe(bufferingPolicy: .unbounded) + let rxMessageSubscription = try await rxRoom.messages.subscribe() // (6) Now that we’re subscribed to messages, send a message on the other client and check that we receive it on the subscription let txMessageAfterRxSubscribe = try await txRoom.messages.send(params: .init(text: "Hello from txRoom, after rxRoom subscribe")) @@ -152,7 +152,7 @@ struct IntegrationTests { // MARK: - Reactions // (1) Subscribe to reactions - let rxReactionSubscription = await rxRoom.reactions.subscribe(bufferingPolicy: .unbounded) + let rxReactionSubscription = await rxRoom.reactions.subscribe() // (2) Now that we’re subscribed to reactions, send a reaction on the other client and check that we receive it on the subscription try await txRoom.reactions.send(params: .init(type: "heart")) @@ -170,7 +170,7 @@ struct IntegrationTests { #expect(currentOccupancy.presenceMembers == 0) // not yet entered presence // (2) Subscribe to occupancy - let rxOccupancySubscription = await rxRoom.occupancy.subscribe(bufferingPolicy: .unbounded) + let rxOccupancySubscription = await rxRoom.occupancy.subscribe() // (3) Attach the room so we can perform presence operations try await txRoom.attach() @@ -202,7 +202,7 @@ struct IntegrationTests { // MARK: - Presence // (1) Subscribe to presence - let rxPresenceSubscription = await rxRoom.presence.subscribe(events: [.enter, .leave, .update], bufferingPolicy: .unbounded) + let rxPresenceSubscription = await rxRoom.presence.subscribe(events: [.enter, .leave, .update]) // (2) Send `.enter` presence event with custom data on the other client and check that we receive it on the subscription try await txRoom.presence.enter(data: .init(userCustomData: ["randomData": .string("randomValue")])) @@ -243,7 +243,7 @@ struct IntegrationTests { // MARK: - Typing Indicators // (1) Subscribe to typing indicators - let rxTypingSubscription = await rxRoom.typing.subscribe(bufferingPolicy: .unbounded) + let rxTypingSubscription = await rxRoom.typing.subscribe() // (2) Start typing on txRoom and check that we receive the typing event on the subscription try await txRoom.typing.start()