Skip to content

Conversation

@AndyTWF
Copy link
Contributor

@AndyTWF AndyTWF commented Oct 11, 2025

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.

JS reference PR: ably/ably-chat-js#675

Summary by CodeRabbit

  • Refactor
    • Presence subscriptions simplified to a single default subscription that delivers all presence events; event-filtered subscription options removed.
  • Documentation
    • Clarified wording to reference “presence events in the chat room.”
  • Tests
    • Updated tests and integration flows to reflect the unified subscription model and revised channel naming.
  • Chores
    • Example app updated to use the default presence subscription and insert new presence items with animation.

@coderabbitai
Copy link

coderabbitai bot commented Oct 11, 2025

Walkthrough

Presence subscription APIs were simplified by removing per-event and multi-event subscribe overloads, consolidating to a single subscribe() path that derives event types internally. Example app and mocks were updated to call the unified subscription; tests were updated to use the new API and adjusted channel names. Internal handling now derives PresenceEvent from PresenceMessage.action.

Changes

Cohort / File(s) Summary
Example app: switch to default presence subscription
Example/AblyChatExample/ContentView.swift
Replaced filtered presence subscription (events: [.enter, .leave, .update]) with an unfiltered room.presence.subscribe { ... } and preserved insertion/animation behavior.
Example mocks: remove per-event subscribe overloads
Example/AblyChatExample/Mocks/MockClients.swift
Deleted subscribe(event:) and subscribe(events:) overloads from MockPresence; retained the single subscribe(_:) entry point.
Core presence API simplification
Sources/AblyChat/Presence.swift, Sources/AblyChat/DefaultPresence.swift
Removed per-event and multi-event subscribe APIs and related AsyncSequence wrappers/overloads; consolidated to a single subscribe path that derives event type from presence messages.
Tests: align with API and channel naming
Tests/AblyChatTests/DefaultPresenceTests.swift, Tests/AblyChatTests/IntegrationTests.swift, Tests/AblyChatTests/Helpers/Helpers.swift
Updated tests to use subscribe() (no event filters), changed channel name occurrences to basketball::$chat, and removed the PresenceEventType.all test helper.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as App UI
  participant Presence as Presence API
  participant Sub as Subscription
  participant Channel as Realtime Channel

  UI->>Presence: subscribe(callback)
  activate Presence
  Presence->>Sub: create unified subscription
  deactivate Presence

  Channel-->>Sub: PresenceMessage(action, data)
  activate Sub
  Sub->>Presence: deliver PresenceMessage
  Presence->>Presence: derive PresenceEvent from action
  Presence->>UI: callback(PresenceEvent)
  deactivate Sub

  rect rgba(230,245,255,0.6)
  note right of Presence: Removed per-event filtering<br/>Event type resolved internally
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

Thump-thump, I hop through tidy trails,
One subscribe now — no branching veils.
Actions whisper, events unfurl,
Tests and mocks in a simpler whirl.
A rabbit cheers for fewer trails. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly states the primary change—removing the event filter from presence subscriptions—using concise, specific phrasing that aligns with the PR objectives and summary.
Docstring Coverage ✅ Passed Docstring coverage is 85.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch presence-filter-removal

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8d9a763 and 57b06d6.

📒 Files selected for processing (7)
  • Example/AblyChatExample/ContentView.swift (1 hunks)
  • Example/AblyChatExample/Mocks/MockClients.swift (0 hunks)
  • Sources/AblyChat/DefaultPresence.swift (0 hunks)
  • Sources/AblyChat/Presence.swift (2 hunks)
  • Tests/AblyChatTests/DefaultPresenceTests.swift (15 hunks)
  • Tests/AblyChatTests/Helpers/Helpers.swift (0 hunks)
  • Tests/AblyChatTests/IntegrationTests.swift (1 hunks)
💤 Files with no reviewable changes (3)
  • Tests/AblyChatTests/Helpers/Helpers.swift
  • Example/AblyChatExample/Mocks/MockClients.swift
  • Sources/AblyChat/DefaultPresence.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • Tests/AblyChatTests/IntegrationTests.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
🧰 Additional context used
📓 Path-based instructions (2)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: Expose SDK functionality via protocols; prefer associated types with opaque return types (some Protocol) over existentials (any Protocol)
Isolate all mutable state to the main actor; mark stateful objects with @mainactor
When using AsyncSequence operators in @mainactor contexts, mark operator closures as @sendable
Task, CheckedContinuation, and AsyncThrowingStream don’t support typed errors—use Result and call .get()
Dictionary.mapValues doesn’t support typed throws—use ablyChat_mapValuesWithTypedThrow extension
When the compiler struggles with typed throws, explicitly specify the error type with do throws(InternalError)
Specify the error type in closures, e.g., try items.map { jsonValue throws(InternalError) in … }

Files:

  • Example/AblyChatExample/ContentView.swift
  • Sources/AblyChat/Presence.swift
Sources/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Sources/**/*.swift: Public API must use typed throws with ARTErrorInfo; use InternalError internally and convert at public API boundaries
Reference Chat SDK features spec items in code comments when implementing behavior (e.g., // @SPEC CHA-RL3g)
For public structs exposed by the API, provide an explicit public memberwise initializer
Test-only APIs in the library must be prefixed with testsOnly_ and wrapped in #if DEBUG

Files:

  • Sources/AblyChat/Presence.swift
🧬 Code graph analysis (1)
Example/AblyChatExample/ContentView.swift (2)
Sources/AblyChat/Presence.swift (2)
  • subscribe (126-140)
  • subscribe (143-145)
Sources/AblyChat/DefaultPresence.swift (1)
  • subscribe (199-224)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 26.0)
  • GitHub Check: Xcode, tvOS (Xcode 26.0)
  • GitHub Check: Example app, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, iOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 26.0)
  • GitHub Check: Example app, macOS (Xcode 26.0)
  • GitHub Check: SPM (Xcode 26.0)
  • GitHub Check: Example app, iOS (Xcode 26.0)
  • GitHub Check: SPM, release configuration (Xcode 26.0)
🔇 Additional comments (3)
Example/AblyChatExample/ContentView.swift (1)

316-329: LGTM!

The subscription correctly uses the simplified API that subscribes to all presence events. The callback appropriately handles all event types by inserting them into the list. This aligns with the PR objectives to remove event filtering and simplify the API.

Sources/AblyChat/Presence.swift (2)

76-87: LGTM!

Documentation correctly updated to reflect that subscriptions now capture all presence events. The comment clearly states the behavior change and maintains consistency with the new simplified API.


116-146: Approve removal of event-filtered subscription APIs

All old event-filtered presence subscription methods have been removed; no references remain.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot temporarily deployed to staging/pull/403/AblyChat October 11, 2025 10:57 Inactive
@AndyTWF AndyTWF force-pushed the presence-filter-removal branch from d730484 to 9794add Compare October 11, 2025 11:02
@github-actions github-actions bot temporarily deployed to staging/pull/403/AblyChat October 11, 2025 11:03 Inactive
@maratal
Copy link
Collaborator

maratal commented Oct 12, 2025

Looks like dup of 6964536

@AndyTWF
Copy link
Contributor Author

AndyTWF commented Oct 13, 2025

Not quite as this one goes further - will catch up with Lawrence

@lawrence-forooghian
Copy link
Collaborator

Not sure if this one is ready for review @AndyTWF (it's a draft) but if so please could you rebase on main now that #387 is merged?

@AndyTWF AndyTWF force-pushed the presence-filter-removal branch from 9794add to efcd7ff Compare October 13, 2025 12:18
@github-actions github-actions bot temporarily deployed to staging/pull/403/AblyChat October 13, 2025 12:20 Inactive
@AndyTWF AndyTWF force-pushed the presence-filter-removal branch from efcd7ff to f781955 Compare October 13, 2025 12:27
@github-actions github-actions bot temporarily deployed to staging/pull/403/AblyChat October 13, 2025 12:28 Inactive
@AndyTWF AndyTWF force-pushed the presence-filter-removal branch from f781955 to 0f50931 Compare October 13, 2025 12:33
@github-actions github-actions bot temporarily deployed to staging/pull/403/AblyChat October 13, 2025 12:34 Inactive
@AndyTWF AndyTWF marked this pull request as ready for review October 13, 2025 12:40
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
Tests/AblyChatTests/DefaultPresenceTests.swift (1)

457-457: Consider marking first predicates as @sendable for consistency

Not required here, but mirrors usage elsewhere and avoids potential warnings.

As per coding guidelines

Also applies to: 465-465, 473-473, 481-481

Sources/AblyChat/DefaultPresence.swift (1)

236-253: Event-type mapping: avoid duplication and clarify unknowns

You derive the type via PresenceEventType(ablyCocoaPresenceAction:), which defaults unknown/absent to .leave. Consider:

  • Deduplicate mapping by forwarding to the existing failable initializer.
  • Optionally log/ignore unknown or .absent actions rather than mapping to .leave to prevent misclassification.

Suggested minimal dedupe:

-    internal init(ablyCocoaPresenceAction action: ARTPresenceAction) {
-        switch action {
-        case .present: self = .present
-        case .enter:   self = .enter
-        case .leave:   self = .leave
-        case .update:  self = .update
-        case .absent:  self = .leave
-        @unknown default: self = .leave
-        }
-    }
+    internal init(ablyCocoaPresenceAction action: ARTPresenceAction) {
+        self = Self(ablyCocoaValue: action) ?? .leave
+    }
Sources/AblyChat/Presence.swift (1)

236-253: Deduplicate PresenceEventType mapping

This initializer duplicates the logic of init?(ablyCocoaValue:). Prefer delegating to it to keep one mapping source:

-    internal init(ablyCocoaPresenceAction action: ARTPresenceAction) {
-        switch action {
-        case .present: self = .present
-        case .enter:   self = .enter
-        case .leave:   self = .leave
-        case .update:  self = .update
-        case .absent:  self = .leave
-        @unknown default: self = .leave
-        }
-    }
+    internal init(ablyCocoaPresenceAction action: ARTPresenceAction) {
+        self = Self(ablyCocoaValue: action) ?? .leave
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e21af0d and 0f50931.

📒 Files selected for processing (7)
  • Example/AblyChatExample/ContentView.swift (1 hunks)
  • Example/AblyChatExample/Mocks/MockClients.swift (0 hunks)
  • Sources/AblyChat/DefaultPresence.swift (2 hunks)
  • Sources/AblyChat/Presence.swift (3 hunks)
  • Tests/AblyChatTests/DefaultPresenceTests.swift (16 hunks)
  • Tests/AblyChatTests/Helpers/Helpers.swift (0 hunks)
  • Tests/AblyChatTests/IntegrationTests.swift (1 hunks)
💤 Files with no reviewable changes (2)
  • Tests/AblyChatTests/Helpers/Helpers.swift
  • Example/AblyChatExample/Mocks/MockClients.swift
🧰 Additional context used
📓 Path-based instructions (3)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: Expose SDK functionality via protocols; prefer associated types with opaque return types (some Protocol) over existentials (any Protocol)
Isolate all mutable state to the main actor; mark stateful objects with @mainactor
When using AsyncSequence operators in @mainactor contexts, mark operator closures as @sendable
Task, CheckedContinuation, and AsyncThrowingStream don’t support typed errors—use Result and call .get()
Dictionary.mapValues doesn’t support typed throws—use ablyChat_mapValuesWithTypedThrow extension
When the compiler struggles with typed throws, explicitly specify the error type with do throws(InternalError)
Specify the error type in closures, e.g., try items.map { jsonValue throws(InternalError) in … }

Files:

  • Sources/AblyChat/DefaultPresence.swift
  • Sources/AblyChat/Presence.swift
  • Tests/AblyChatTests/IntegrationTests.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
  • Example/AblyChatExample/ContentView.swift
Sources/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Sources/**/*.swift: Public API must use typed throws with ARTErrorInfo; use InternalError internally and convert at public API boundaries
Reference Chat SDK features spec items in code comments when implementing behavior (e.g., // @SPEC CHA-RL3g)
For public structs exposed by the API, provide an explicit public memberwise initializer
Test-only APIs in the library must be prefixed with testsOnly_ and wrapped in #if DEBUG

Files:

  • Sources/AblyChat/DefaultPresence.swift
  • Sources/AblyChat/Presence.swift
Tests/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Tests/**/*.swift: Use test attribution tags in tests: @SPEC, @specOneOf(m/n), @specPartial, @specUntested - , @specNotApplicable -
For Swift Testing #expect(throws:) with typed errors, move typed-throw code into a separate non-typed-throw function

Files:

  • Tests/AblyChatTests/IntegrationTests.swift
  • Tests/AblyChatTests/DefaultPresenceTests.swift
🧬 Code graph analysis (4)
Sources/AblyChat/Presence.swift (3)
Example/AblyChatExample/Mocks/MockClients.swift (10)
  • enter (469-471)
  • enter (473-475)
  • enter (477-490)
  • leave (515-517)
  • leave (519-521)
  • leave (523-536)
  • update (181-195)
  • update (492-494)
  • update (496-498)
  • update (500-513)
Tests/AblyChatTests/Mocks/MockRealtimePresence.swift (3)
  • enter (42-47)
  • leave (35-40)
  • update (49-54)
Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (3)
  • enter (275-289)
  • leave (259-273)
  • update (291-305)
Tests/AblyChatTests/IntegrationTests.swift (2)
Sources/AblyChat/DefaultPresence.swift (1)
  • subscribe (199-219)
Sources/AblyChat/Presence.swift (2)
  • subscribe (126-140)
  • subscribe (143-145)
Tests/AblyChatTests/DefaultPresenceTests.swift (6)
Sources/AblyChat/DefaultPresence.swift (10)
  • subscribe (199-219)
  • enter (95-97)
  • enter (99-101)
  • enter (104-125)
  • update (127-129)
  • update (131-133)
  • update (136-157)
  • leave (159-161)
  • leave (163-165)
  • leave (168-189)
Sources/AblyChat/Presence.swift (2)
  • subscribe (126-140)
  • subscribe (143-145)
Example/AblyChatExample/Mocks/MockClients.swift (16)
  • subscribe (127-156)
  • subscribe (284-308)
  • subscribe (339-356)
  • subscribe (370-386)
  • subscribe (538-540)
  • subscribe (554-565)
  • enter (469-471)
  • enter (473-475)
  • enter (477-490)
  • update (181-195)
  • update (492-494)
  • update (496-498)
  • update (500-513)
  • leave (515-517)
  • leave (519-521)
  • leave (523-536)
Tests/AblyChatTests/Mocks/MockRealtimePresence.swift (5)
  • subscribe (7-9)
  • subscribe (11-13)
  • enter (42-47)
  • update (49-54)
  • leave (35-40)
Sources/AblyChat/Messages.swift (1)
  • emit (407-409)
Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (3)
  • emit (55-59)
  • emit (113-117)
  • emit (176-180)
Example/AblyChatExample/ContentView.swift (3)
Sources/AblyChat/DefaultPresence.swift (1)
  • subscribe (199-219)
Sources/AblyChat/Presence.swift (2)
  • subscribe (126-140)
  • subscribe (143-145)
Example/AblyChatExample/Mocks/MockClients.swift (6)
  • subscribe (127-156)
  • subscribe (284-308)
  • subscribe (339-356)
  • subscribe (370-386)
  • subscribe (538-540)
  • subscribe (554-565)
🔇 Additional comments (7)
Example/AblyChatExample/ContentView.swift (1)

317-329: Presence subscribe simplification LGTM

Switching to unfiltered subscribe aligns with the new API and keeps UI updates on MainActor.

Tests/AblyChatTests/IntegrationTests.swift (1)

378-380: Adoption of subscribe() without filters looks good

Matches the unified presence subscription API.

Tests/AblyChatTests/DefaultPresenceTests.swift (2)

13-13: Channel name updates to "basketball::$chat" are consistent

All adjusted tests target the correct channel namespace.

Also applies to: 37-37, 61-61, 89-89, 123-123, 153-153, 177-177, 205-205, 239-239, 269-269, 295-295, 323-323, 349-349, 377-377, 404-404


435-484: New test covers all presence events end-to-end

Good coverage for present/enter/update/leave via unified subscription.

Sources/AblyChat/DefaultPresence.swift (1)

204-212: Unified subscribe path looks correct

Deriving PresenceEvent from the message and invoking the callback on MainActor is sound.

Sources/AblyChat/Presence.swift (2)

76-88: Public subscribe(callback) docs/readability improved

Clear that this subscribes to all presence events; matches API surface reduction.


114-146: AsyncSequence wrapper for subscribe is solid

Termination handler unsubscribes on MainActor; good pattern.

@AndyTWF AndyTWF force-pushed the presence-filter-removal branch 3 times, most recently from 344dec8 to 8d9a763 Compare October 13, 2025 13:29
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.
@AndyTWF AndyTWF enabled auto-merge October 13, 2025 13:50
@AndyTWF AndyTWF merged commit d1b0655 into main Oct 13, 2025
17 checks passed
@AndyTWF AndyTWF deleted the presence-filter-removal branch October 13, 2025 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants