Skip to content

Conversation

maratal
Copy link
Collaborator

@maratal maratal commented Aug 3, 2025

Closes #2077, #1466

Summary by CodeRabbit

  • New Features
    • Introduced support for message annotations in both REST and Realtime APIs, enabling users to publish, delete, and retrieve annotations (such as reactions) on individual messages.
    • Added new query options for fetching annotations with pagination and limits.
    • Annotations are now accessible via new properties and methods on channels.
  • Bug Fixes
    • Improved error handling for publishing annotations that exceed the maximum allowed message size.
  • Tests
    • Added comprehensive tests covering annotation publishing, deletion, retrieval, idempotency, and error conditions for both REST and Realtime clients.

Copy link

coderabbitai bot commented Aug 3, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Walkthrough

This change introduces comprehensive support for message annotations in both REST and Realtime APIs. It adds new public and private headers, implementation files, and test suites for annotation publishing, deletion, retrieval, and idempotency. The Xcode project and module maps are updated to include these files. Extensive tests validate annotation behaviors, including size limits and idempotent operations.

Changes

Cohort / File(s) Change Summary
Xcode Project & Module Maps
Ably.xcodeproj/project.pbxproj, Source/Ably.modulemap, Source/include/module.modulemap
Added new source, header, and test files for Realtime and REST annotations to the project and module maps, including private headers.
Annotation Core Implementation
Source/ARTAnnotation.m, Source/include/Ably/ARTAnnotation.h, Source/PrivateHeaders/Ably/ARTAnnotation+Private.h
Added/renamed encode/decode methods, added annotationSize and isIdEmpty methods. Updated header signatures accordingly.
Constants
Source/ARTConstants.m, Source/PrivateHeaders/Ably/ARTConstants.h
Introduced ARTIdempotentLibraryGeneratedIdLength constant in implementation and header.
Encoder Support
Source/ARTJsonLikeEncoder.m, Source/PrivateHeaders/Ably/ARTEncoder.h, Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h
Added methods to encode/decode single and array annotation objects and dictionary conversions. Updated protocol and class interfaces.
Realtime Annotations Implementation
Source/ARTRealtimeAnnotations.m, Source/include/Ably/ARTRealtimeAnnotations.h, Source/PrivateHeaders/Ably/ARTRealtimeAnnotations+Private.h
Added full annotation management API (publish, delete, get) for messages/serials, forwarding, and internal logic.
REST Annotations Implementation
Source/ARTRestAnnotations.m, Source/include/Ably/ARTRestAnnotations.h, Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h
Added REST annotation management classes, query support, and internal logic for annotation operations.
REST Channel Integration
Source/ARTRestChannel.m, Source/include/Ably/ARTRestChannel.h, Source/PrivateHeaders/Ably/ARTRestChannel+Private.h
Integrated annotations property and internal handling into REST channels. Updated idempotent ID logic to use new constant.
Wrapper Proxy
Source/ARTWrapperSDKProxyRealtimeAnnotations.m
Added forwarding methods for annotation operations to the wrapper proxy class.
Public API Imports
Source/include/Ably/AblyPublic.h
Imported ARTRestAnnotations.h into the public API header.
Types Header
Source/include/Ably/ARTTypes.h
Removed ARTAnnotationErrorCallback typedef.
Test Infrastructure
Test/Codables/TestAppSetup.swift
Added mutableMessages property to Namespace struct for test setup.
Test Suites
Test/Tests/RealtimeAnnotationsTests.swift, Test/Tests/RestAnnotationsTests.swift
Added comprehensive test suites for realtime and REST annotation operations, including publish, delete, get, size limit, and idempotency.
Submodule Reference
ably-common
Updated submodule commit reference.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RealtimeAnnotations
    participant RealtimeAnnotationsInternal
    participant Channel
    participant Server

    Client->>RealtimeAnnotations: publishForMessage(message, annotation, callback)
    RealtimeAnnotations->>RealtimeAnnotationsInternal: publishForMessage(message, annotation, callback)
    RealtimeAnnotationsInternal->>Channel: send annotation via protocol message
    Channel->>Server: Send annotation
    Server-->>Channel: Acknowledge annotation
    Channel-->>RealtimeAnnotationsInternal: Confirmation
    RealtimeAnnotationsInternal-->>Client: callback(success/error)
Loading
sequenceDiagram
    participant Client
    participant RestAnnotations
    participant RestAnnotationsInternal
    participant RestChannel
    participant Server

    Client->>RestAnnotations: publishForMessageSerial(serial, annotation, callback)
    RestAnnotations->>RestAnnotationsInternal: publishForMessageSerial(serial, annotation, callback)
    RestAnnotationsInternal->>RestChannel: HTTP POST /annotations
    RestChannel->>Server: POST annotation
    Server-->>RestChannel: Response
    RestChannel-->>RestAnnotationsInternal: Result
    RestAnnotationsInternal-->>Client: callback(success/error)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
Complete implementation of message annotations: publish, delete, get, REST and Realtime support, idempotency, size constraints, and tests (#2077, ECO-5474)
Tests for annotation lifecycle, idempotency, and error conditions (#2077, ECO-5474)
Xcode project and module map integration for annotation files (#2077, ECO-5474)

Assessment against linked issues: Out-of-scope changes

No out-of-scope functional code changes detected.

Possibly related PRs

Suggested reviewers

  • umair-ably
  • lawrence-forooghian

Poem

A rabbit hopped through fields of code,
With annotations in its load.
Reactions, notes, and RESTful flair,
Now messages can truly share!
Tests abound, the code is neat—
This bunny’s work is quite complete.
🐇✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/2077-annotations

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot temporarily deployed to staging/pull/2080/features August 3, 2025 21:27 Inactive
@maratal maratal changed the title [ECO-5474 ]Publish and get annotations [ECO-5474] Publish and get annotations Aug 3, 2025
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: 5

🧹 Nitpick comments (7)
Source/include/Ably/ARTRestChannel.h (1)

58-62: Consider adding concise API docs for the new annotations property

Every other public surface on ARTRestChannel carries at least a one-liner.
A short description such as “Provides annotation publish / fetch / delete operations for this channel” will make generated docs clearer and align with the existing style.

Source/include/Ably/ARTRestAnnotations.h (2)

12-22: Consider adding a public convenience initializer.

The class only exposes a private initializer marked with :nodoc:, but users will need a way to create instances. Consider adding a public convenience method or making the existing initializer public.

+/**
+ * Creates a new annotations query with the specified limit.
+ *
+ * @param limit An upper limit on the number of annotations returned.
+ */
++ (instancetype)queryWithLimit:(NSUInteger)limit;
+
+/**
+ * Creates a new annotations query with default limit.
+ */
++ (instancetype)query;
+
-/// :nodoc:
+/**
+ * Designated initializer.
+ *
+ * @param limit An upper limit on the number of annotations returned.
+ */
- (instancetype)initWithLimit:(NSUInteger)limit;

74-75: Fix documentation formatting inconsistency.

The comment block starting at line 74 has inconsistent spacing compared to other method documentation blocks.

- /**
+/**
  * Get all annotations for a given message (as a paginated result).
Ably.xcodeproj/project.pbxproj (1)

1449-1451: Sanity-check path & group for new REST annotation sources

The new file refs for:

  • include/Ably/ARTRestAnnotations.h
  • PrivateHeaders/Ably/ARTRestAnnotations+Private.h
  • ARTRestAnnotations.m

sit at the root of the project hierarchy. For consistency with existing files (e.g. ARTRestPresence.*) they usually live under Source/ and PrivateHeaders/.

No functional impact, but keeping the physical layout parallel improves discoverability.

Source/include/Ably/ARTRealtimeAnnotations.h (1)

56-56: Fix typo in documentation.

- * @param callback A callback for retriving an `ARTPaginatedResult` containing annotations.
+ * @param callback A callback for retrieving an `ARTPaginatedResult` containing annotations.
Test/Tests/RealtimeAnnotationsTests.swift (1)

101-101: Replace unused closure parameter with underscore.

-            realtimeChannel.once(.attached) { stateChange in
+            realtimeChannel.once(.attached) { _ in
Test/Tests/RestAnnotationsTests.swift (1)

106-106: Replace unused closure parameters with underscore.

-            realtimeChannel.once(.attached) { stateChange in
+            realtimeChannel.once(.attached) { _ in

Also applies to: 256-256

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d33f1c3 and 3fe9352.

📒 Files selected for processing (27)
  • Ably.xcodeproj/project.pbxproj (18 hunks)
  • Source/ARTAnnotation.m (3 hunks)
  • Source/ARTConstants.m (1 hunks)
  • Source/ARTJsonLikeEncoder.m (4 hunks)
  • Source/ARTRealtimeAnnotations.m (5 hunks)
  • Source/ARTRestAnnotations.m (1 hunks)
  • Source/ARTRestChannel.m (7 hunks)
  • Source/ARTWrapperSDKProxyRealtimeAnnotations.m (1 hunks)
  • Source/Ably.modulemap (1 hunks)
  • Source/PrivateHeaders/Ably/ARTAnnotation+Private.h (1 hunks)
  • Source/PrivateHeaders/Ably/ARTConstants.h (1 hunks)
  • Source/PrivateHeaders/Ably/ARTEncoder.h (2 hunks)
  • Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h (1 hunks)
  • Source/PrivateHeaders/Ably/ARTRealtimeAnnotations+Private.h (1 hunks)
  • Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h (1 hunks)
  • Source/PrivateHeaders/Ably/ARTRestChannel+Private.h (1 hunks)
  • Source/include/Ably/ARTAnnotation.h (1 hunks)
  • Source/include/Ably/ARTRealtimeAnnotations.h (2 hunks)
  • Source/include/Ably/ARTRestAnnotations.h (1 hunks)
  • Source/include/Ably/ARTRestChannel.h (2 hunks)
  • Source/include/Ably/ARTTypes.h (0 hunks)
  • Source/include/Ably/AblyPublic.h (1 hunks)
  • Source/include/module.modulemap (1 hunks)
  • Test/Codables/TestAppSetup.swift (1 hunks)
  • Test/Tests/RealtimeAnnotationsTests.swift (1 hunks)
  • Test/Tests/RestAnnotationsTests.swift (1 hunks)
  • ably-common (1 hunks)
💤 Files with no reviewable changes (1)
  • Source/include/Ably/ARTTypes.h
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTBaseQuery.h:4-8
Timestamp: 2024-09-12T20:12:29.428Z
Learning: As a general rule, `NS_SWIFT_SENDABLE` annotations are not needed for private Objective-C interfaces in the ably-cocoa project.
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTBaseQuery.h:4-8
Timestamp: 2024-10-08T15:37:26.766Z
Learning: As a general rule, `NS_SWIFT_SENDABLE` annotations are not needed for private Objective-C interfaces in the ably-cocoa project.
Learnt from: maratal
PR: ably/ably-cocoa#2066
File: Source/ARTJsonLikeEncoder.m:341-351
Timestamp: 2025-06-16T15:39:16.005Z
Learning: In the Ably Cocoa SDK, when decoding annotation actions from integers, unknown/invalid action values should default to ARTAnnotationCreate rather than failing or using an unknown state. This is an intentional design decision for the annotation system.
📚 Learning: in `artrealtimechannelinternal`, the `internalpresence` property is declared in the private header `...
Learnt from: maratal
PR: ably/ably-cocoa#1995
File: Source/ARTRealtimeChannel.m:227-227
Timestamp: 2024-11-04T23:10:23.027Z
Learning: In `ARTRealtimeChannelInternal`, the `internalPresence` property is declared in the private header `ARTRealtimeChannel+Private.h`, so it does not need to be redeclared in `ARTRealtimeChannel.m`.

Applied to files:

  • Source/PrivateHeaders/Ably/ARTConstants.h
  • Source/include/Ably/AblyPublic.h
  • Source/include/Ably/ARTAnnotation.h
  • Source/ARTConstants.m
  • Source/PrivateHeaders/Ably/ARTRealtimeAnnotations+Private.h
  • Source/PrivateHeaders/Ably/ARTRestChannel+Private.h
  • Source/include/Ably/ARTRestChannel.h
  • Source/Ably.modulemap
  • Source/include/module.modulemap
  • Source/PrivateHeaders/Ably/ARTAnnotation+Private.h
  • Source/PrivateHeaders/Ably/ARTEncoder.h
  • Source/ARTRestChannel.m
  • Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h
  • Source/include/Ably/ARTRestAnnotations.h
  • Source/include/Ably/ARTRealtimeAnnotations.h
  • Source/ARTWrapperSDKProxyRealtimeAnnotations.m
  • Source/ARTRestAnnotations.m
  • Source/ARTRealtimeAnnotations.m
  • Ably.xcodeproj/project.pbxproj
📚 Learning: `artchanneloptions` is already marked with `ns_swift_sendable`....
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTRealtimeChannelOptions.h:33-33
Timestamp: 2024-10-08T15:37:26.766Z
Learning: `ARTChannelOptions` is already marked with `NS_SWIFT_SENDABLE`.

Applied to files:

  • Source/PrivateHeaders/Ably/ARTConstants.h
  • Source/include/Ably/AblyPublic.h
  • Source/include/Ably/ARTAnnotation.h
  • Source/ARTConstants.m
  • Source/PrivateHeaders/Ably/ARTRealtimeAnnotations+Private.h
  • Source/PrivateHeaders/Ably/ARTRestChannel+Private.h
  • Source/include/Ably/ARTRestChannel.h
  • Source/Ably.modulemap
  • Source/include/module.modulemap
  • Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h
  • Source/PrivateHeaders/Ably/ARTAnnotation+Private.h
  • Source/PrivateHeaders/Ably/ARTEncoder.h
  • Source/ARTRestChannel.m
  • Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h
  • Source/include/Ably/ARTRestAnnotations.h
  • Source/ARTJsonLikeEncoder.m
  • Test/Tests/RealtimeAnnotationsTests.swift
  • Source/include/Ably/ARTRealtimeAnnotations.h
  • Test/Tests/RestAnnotationsTests.swift
  • Source/ARTRestAnnotations.m
  • Source/ARTRealtimeAnnotations.m
  • Ably.xcodeproj/project.pbxproj
📚 Learning: in 'source/artjsonlikeencoder.m', when handling messages from 'input', using `mutablecopy` on the ar...
Learnt from: maratal
PR: ably/ably-cocoa#1998
File: Source/ARTJsonLikeEncoder.m:766-774
Timestamp: 2024-11-26T17:50:25.730Z
Learning: In 'Source/ARTJsonLikeEncoder.m', when handling messages from 'input', using `mutableCopy` on the array ensures that modifications to `messages` do not affect the original `input`.

Applied to files:

  • Test/Codables/TestAppSetup.swift
  • Source/ARTAnnotation.m
  • Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h
  • Source/PrivateHeaders/Ably/ARTAnnotation+Private.h
  • Source/PrivateHeaders/Ably/ARTEncoder.h
  • Source/ARTRestChannel.m
  • Source/ARTJsonLikeEncoder.m
  • Source/ARTRestAnnotations.m
  • Source/ARTRealtimeAnnotations.m
📚 Learning: in the swift test files within the `test/tests/` directory of the ably cocoa project, it's acceptabl...
Learnt from: maratal
PR: ably/ably-cocoa#1995
File: Test/Tests/RealtimeClientChannelTests.swift:192-193
Timestamp: 2024-11-04T23:11:06.909Z
Learning: In the Swift test files within the `Test/Tests/` directory of the Ably Cocoa project, it's acceptable to access internal types and properties directly in tests.

Applied to files:

  • Test/Codables/TestAppSetup.swift
  • Source/include/Ably/AblyPublic.h
  • Source/PrivateHeaders/Ably/ARTRestChannel+Private.h
  • Source/Ably.modulemap
  • Source/include/module.modulemap
  • Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h
  • Test/Tests/RealtimeAnnotationsTests.swift
  • Source/include/Ably/ARTRealtimeAnnotations.h
  • Test/Tests/RestAnnotationsTests.swift
  • Ably.xcodeproj/project.pbxproj
📚 Learning: parsing the message's `id` is necessary for checking `msgserial` and `index` in the `member:isnewert...
Learnt from: maratal
PR: ably/ably-cocoa#1982
File: Source/ARTPresenceMessage.m:59-59
Timestamp: 2024-10-06T12:27:40.858Z
Learning: Parsing the message's `id` is necessary for checking `msgSerial` and `index` in the `member:isNewerThan:` method.

Applied to files:

  • Test/Codables/TestAppSetup.swift
  • Source/ARTRestChannel.m
📚 Learning: as a general rule, `ns_swift_sendable` annotations are not needed for private objective-c interfaces...
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTBaseQuery.h:4-8
Timestamp: 2024-09-12T20:12:29.428Z
Learning: As a general rule, `NS_SWIFT_SENDABLE` annotations are not needed for private Objective-C interfaces in the ably-cocoa project.

Applied to files:

  • Source/include/Ably/AblyPublic.h
  • Source/include/Ably/ARTAnnotation.h
  • Source/PrivateHeaders/Ably/ARTRestChannel+Private.h
  • Source/include/Ably/ARTRestChannel.h
  • Source/Ably.modulemap
  • Source/include/module.modulemap
  • Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h
  • Source/PrivateHeaders/Ably/ARTAnnotation+Private.h
  • Source/PrivateHeaders/Ably/ARTEncoder.h
  • Source/ARTRestChannel.m
  • Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h
  • Source/include/Ably/ARTRestAnnotations.h
  • Source/include/Ably/ARTRealtimeAnnotations.h
  • Test/Tests/RestAnnotationsTests.swift
  • Source/ARTRealtimeAnnotations.m
  • Ably.xcodeproj/project.pbxproj
📚 Learning: in the ably cocoa sdk, when decoding annotation actions from integers, unknown/invalid action values...
Learnt from: maratal
PR: ably/ably-cocoa#2066
File: Source/ARTJsonLikeEncoder.m:341-351
Timestamp: 2025-06-16T15:39:16.005Z
Learning: In the Ably Cocoa SDK, when decoding annotation actions from integers, unknown/invalid action values should default to ARTAnnotationCreate rather than failing or using an unknown state. This is an intentional design decision for the annotation system.

Applied to files:

  • Source/include/Ably/AblyPublic.h
  • Source/include/Ably/ARTAnnotation.h
  • Source/PrivateHeaders/Ably/ARTRestChannel+Private.h
  • Source/include/Ably/ARTRestChannel.h
  • Source/ARTAnnotation.m
  • Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h
  • Source/PrivateHeaders/Ably/ARTAnnotation+Private.h
  • Source/PrivateHeaders/Ably/ARTEncoder.h
  • Source/ARTRestChannel.m
  • Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h
  • Source/include/Ably/ARTRestAnnotations.h
  • Source/ARTJsonLikeEncoder.m
  • Test/Tests/RealtimeAnnotationsTests.swift
  • Source/include/Ably/ARTRealtimeAnnotations.h
  • Test/Tests/RestAnnotationsTests.swift
  • Source/ARTWrapperSDKProxyRealtimeAnnotations.m
  • Source/ARTRestAnnotations.m
  • Source/ARTRealtimeAnnotations.m
📚 Learning: avoid suggesting refactoring the conditional compilation in `restclientchannelstests.swift` for `art...
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Test/Tests/RestClientChannelsTests.swift:5-18
Timestamp: 2024-09-17T13:25:42.060Z
Learning: Avoid suggesting refactoring the conditional compilation in `RestClientChannelsTests.swift` for `ARTRestChannels` extensions, as the user prefers to keep the current structure.

Applied to files:

  • Source/include/Ably/AblyPublic.h
  • Source/PrivateHeaders/Ably/ARTRestChannel+Private.h
  • Source/include/Ably/ARTRestChannel.h
  • Source/Ably.modulemap
  • Source/include/module.modulemap
  • Source/ARTRestChannel.m
  • Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h
  • Source/include/Ably/ARTRestAnnotations.h
  • Test/Tests/RealtimeAnnotationsTests.swift
  • Test/Tests/RestAnnotationsTests.swift
  • Source/ARTRestAnnotations.m
  • Ably.xcodeproj/project.pbxproj
📚 Learning: in `artmessage`, the `action` and `serial` fields are set by the realtime service and should not be ...
Learnt from: maratal
PR: ably/ably-cocoa#1998
File: Source/ARTJsonLikeEncoder.m:278-279
Timestamp: 2024-11-22T10:11:05.305Z
Learning: In `ARTMessage`, the `action` and `serial` fields are set by the realtime service and should not be serialized in the `messageToDictionary:` method in `Source/ARTJsonLikeEncoder.m`.

Applied to files:

  • Source/include/Ably/ARTAnnotation.h
  • Source/ARTConstants.m
  • Source/ARTAnnotation.m
  • Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h
  • Source/PrivateHeaders/Ably/ARTAnnotation+Private.h
  • Source/PrivateHeaders/Ably/ARTEncoder.h
  • Source/ARTRestChannel.m
  • Source/ARTJsonLikeEncoder.m
  • Source/include/Ably/ARTRealtimeAnnotations.h
  • Source/ARTWrapperSDKProxyRealtimeAnnotations.m
  • Source/ARTRestAnnotations.m
  • Source/ARTRealtimeAnnotations.m
📚 Learning: the unpublish method in artrealtimeannotations works by publishing an annotation with a delete actio...
Learnt from: maratal
PR: ably/ably-cocoa#2066
File: Source/include/Ably/ARTRealtimeAnnotations.h:25-28
Timestamp: 2025-06-16T14:18:14.569Z
Learning: The unpublish method in ARTRealtimeAnnotations works by publishing an annotation with a DELETE action, rather than directly deleting. This is why the documentation correctly refers to "The annotation to publish" even though it's an unpublish method.

Applied to files:

  • Test/Tests/RealtimeAnnotationsTests.swift
  • Source/include/Ably/ARTRealtimeAnnotations.h
  • Test/Tests/RestAnnotationsTests.swift
  • Source/ARTWrapperSDKProxyRealtimeAnnotations.m
  • Source/ARTRestAnnotations.m
  • Source/ARTRealtimeAnnotations.m
📚 Learning: in xcode project files, multiple pbxbuildfile entries for the same source or header file are valid i...
Learnt from: maratal
PR: ably/ably-cocoa#2066
File: Ably.xcodeproj/project.pbxproj:416-424
Timestamp: 2025-06-16T14:12:36.705Z
Learning: In Xcode project files, multiple PBXBuildFile entries for the same source or header file are valid if they are associated with different build targets.

Applied to files:

  • Ably.xcodeproj/project.pbxproj
🪛 SwiftLint (0.57.0)
Test/Tests/RealtimeAnnotationsTests.swift

[Warning] 101-101: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

Test/Tests/RestAnnotationsTests.swift

[Warning] 106-106: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 256-256: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

⏰ 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). (6)
  • GitHub Check: check
  • GitHub Check: check
  • GitHub Check: build
  • GitHub Check: check (tvOS, test_tvOS18_4)
  • GitHub Check: check (macOS, test_macOS)
  • GitHub Check: check (iOS, test_iOS18_4)
🔇 Additional comments (41)
ably-common (1)

1-1: Submodule advance verified for annotation APIs

Commit 3fe9352 ("Add rest annotations. Add publish method for realtime annotations. Add test for annotations") contains the expected spec updates and accompanying tests, with no unintended breaking changes detected.

Test/Codables/TestAppSetup.swift (1)

46-46: LGTM! Property addition follows established patterns.

The new mutableMessages optional Boolean property is consistent with the existing optional Boolean properties in the Namespace struct and supports the test configuration needs for the annotations feature.

Source/ARTConstants.m (1)

5-5: LGTM! Good centralization of the constant.

The new ARTIdempotentLibraryGeneratedIdLength constant centralizes what was previously a local definition, improving maintainability. The descriptive name and comment clarifying the unit (bytes) make the purpose clear.

Source/PrivateHeaders/Ably/ARTConstants.h (1)

5-5: LGTM! Proper extern declaration for the new constant.

The extern declaration correctly exposes the ARTIdempotentLibraryGeneratedIdLength constant for internal SDK use, following the established pattern of the existing constant declarations.

Source/include/Ably/ARTAnnotation.h (1)

77-78: LGTM! Important utility method for size validation.

The new annotationSize method provides necessary size calculation functionality for annotation publishing workflows. The :nodoc: annotation correctly marks it as internal-only, and the method name clearly conveys its purpose.

Source/PrivateHeaders/Ably/ARTRealtimeAnnotations+Private.h (1)

9-9: LGTM! Proper weak reference with clear documentation.

The new realtime property provides necessary back-reference to the owning realtime instance. The weak reference correctly prevents retain cycles, and the comment clearly explains the ownership relationship. This enables internal coordination between the annotations and realtime components.

Source/include/module.modulemap (1)

49-50: Addition looks correct – keep both module maps in sync

ARTRestAnnotations+Private.h is now exposed to SPM consumers via Ably.Private.
The header is placed next to the other annotation headers and the alphabetical order is preserved, so no issues here.

Source/include/Ably/AblyPublic.h (1)

24-25: Public umbrella updated correctly

Adding #import <Ably/ARTRestAnnotations.h> surfaces the new REST-side annotations API to SDK users and keeps the umbrella header in step with the newly-exposed class. All good.

Source/Ably.modulemap (1)

49-50: Xcode module map kept in lock-step with the SPM map

ARTRestAnnotations+Private.h is now available when clients use import Ably.Private inside Xcode builds.
The update mirrors the change in the SPM module map, avoiding the mismatches we have hit in the past.

Source/include/Ably/ARTRestChannel.h (1)

7-7: Forward declaration added – OK

The new forward declaration for ARTRestAnnotations prevents an extra public import in this header. Looks good.

Source/PrivateHeaders/Ably/ARTRestChannel+Private.h (1)

15-22: Internal plumbing in place – matches the public API

ARTRestAnnotationsInternal is wired in exactly the same pattern as presence and push, so the internal/external surfaces stay consistent. No concerns.

Source/PrivateHeaders/Ably/ARTAnnotation+Private.h (1)

11-12: LGTM! Method renaming improves clarity.

The addition of "Data" to the method names (decodeDataWithEncoder:error: and encodeDataWithEncoder:error:) provides better clarity about what these methods encode/decode, distinguishing them from other encoding operations in the annotation system.

Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h (1)

36-37: LGTM! Consistent API design for annotation support.

The new annotation encoding/decoding methods follow the established patterns for messages and presence messages, maintaining consistency in the encoder interface. The method names are clear and the placement in the private interface is appropriate.

Also applies to: 45-46

Source/ARTRestChannel.m (4)

21-23: LGTM! Proper imports for annotation support.

The addition of imports for ARTRestAnnotations, ARTRestAnnotations+Private.h, and ARTConstants.h correctly supports the new annotation functionality being integrated.


42-44: LGTM! Consistent pattern for exposing internal functionality.

The annotations getter method follows the same pattern as the existing presence and push getters, maintaining consistency in the API design.


120-120: LGTM! Clean integration of annotations into the channel.

The addition of the _annotations property and its eager initialization in the constructor, along with the simple getter method, cleanly integrates annotation support into the REST channel. The eager initialization differs from the lazy initialization of presence, which is appropriate based on expected annotation usage patterns.

Also applies to: 133-133, 151-153


309-309: LGTM! Replaced magic number with named constant.

The replacement of the hardcoded value with ARTIdempotentLibraryGeneratedIdLength improves code maintainability and consistency across the codebase.

Also applies to: 333-333

Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h (1)

1-24: LGTM! Well-structured private header following project conventions.

The private header follows established patterns in the codebase:

  • Proper forward declarations for dependencies
  • Clean separation between internal implementation and wrapper classes
  • Consistent naming conventions
  • Appropriate use of NS_ASSUME_NONNULL_BEGIN/END blocks

The structure aligns with other private headers like ARTRestPresence+Private.h and maintains the project's architectural consistency.

Source/PrivateHeaders/Ably/ARTEncoder.h (1)

7-7: LGTM! Consistent protocol extension for annotation support.

The additions to the ARTEncoder protocol properly extend the encoding infrastructure:

  • Forward declaration for ARTAnnotation is correctly placed
  • New encoding/decoding methods follow the established pattern for messages and presence messages
  • Method signatures are consistent with existing conventions
  • Logical grouping maintains protocol organization

The protocol extension enables proper annotation serialization across the SDK.

Also applies to: 52-55, 60-63

Source/include/Ably/ARTRestAnnotations.h (1)

85-92: LGTM!

The class declaration is well-structured with appropriate Swift sendable annotation and clear documentation referencing the protocol for implementation details.

Source/ARTAnnotation.m (4)

76-86: LGTM!

The method rename from decodeWithEncoder to decodeDataWithEncoder improves clarity about the operation's purpose. The implementation logic and error handling remain correct.


88-98: LGTM!

The method rename to encodeDataWithEncoder is consistent with the decode method and improves API clarity. The implementation and error handling are correct.


133-135: LGTM!

The isIdEmpty method correctly handles both nil and empty string cases with a clear and simple implementation.


100-131: Improve null safety and error handling.

The size calculation logic is comprehensive, but there are several potential null pointer issues and silent failures:

  1. self.name and self.clientId could be nil, causing crashes when calling lengthOfBytesUsingEncoding:
  2. [self.extras toJSONString] could return nil
  3. JSON serialization errors are silently ignored, potentially underestimating size
- (NSInteger)annotationSize {
    // TO3l8*
    NSInteger finalResult = 0;
-   finalResult += [self.name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
-   finalResult += [[self.extras toJSONString] lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
-   finalResult += [self.clientId lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+   if (self.name) {
+       finalResult += [self.name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+   }
+   NSString *extrasJSON = [self.extras toJSONString];
+   if (extrasJSON) {
+       finalResult += [extrasJSON lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+   }
+   if (self.clientId) {
+       finalResult += [self.clientId lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+   }
    if (self.data) {
        if ([self.data isKindOfClass:[NSString class]]) {
            finalResult += [self.data lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
        }
        else if ([self.data isKindOfClass:[NSData class]]) {
            finalResult += [self.data length];
        }
        else {
            NSError *error = nil;
            NSJSONWritingOptions options;
            if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
                options = NSJSONWritingWithoutEscapingSlashes;
            }
            else {
                options = 0; //no specific format
            }
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self.data
                                                               options:options
                                                                 error:&error];
-           if (!error) {
+           if (!error && jsonData) {
                finalResult += [jsonData length];
+           } else {
+               // Log warning about failed JSON serialization
+               ARTLogWarn(@"Failed to serialize annotation data for size calculation: %@", error.localizedDescription);
            }
        }
    }
    return finalResult;
}
⛔ Skipped due to learnings
Learnt from: maratal
PR: ably/ably-cocoa#2066
File: Source/ARTJsonLikeEncoder.m:341-351
Timestamp: 2025-06-16T15:39:16.005Z
Learning: In the Ably Cocoa SDK, when decoding annotation actions from integers, unknown/invalid action values should default to ARTAnnotationCreate rather than failing or using an unknown state. This is an intentional design decision for the annotation system.
Ably.xcodeproj/project.pbxproj (3)

401-406: Verify test-file membership – 3 build-file entries per test source could be overkill

RealtimeAnnotationsTests.swift and RestAnnotationsTests.swift each appear three times in the PBXBuildFile section.
Duplicating a file across different test targets (iOS / tvOS / macOS) is fine, but duplicating within the same target will trigger duplicate symbol / “file listed twice” linker warnings.

Please double-check the target membership in the Build Phases ▸ Compile Sources pages for every test bundle to ensure each file is present once per target.
If any duplicates are in the same target, drop the extra reference(s).


437-446: Public/Private header flags look correct – confirm they land in the right header phase

ARTRestAnnotations.h is marked Public, while ARTRestAnnotations+Private.h is Private, which matches the existing header-visibility conventions.
Just make sure the Public header is added to the “Public Headers” phase of all framework targets (Ably iOS / macOS / tvOS) so that CocoaPods / SwiftPM consumers can see it.


3545-3545: Triple inclusion of ARTRestAnnotations.m – verify per-target duplication only

ARTRestAnnotations.m shows up in three separate Compile Sources lists. If those map to the iOS, macOS and tvOS framework targets respectively, all good. If they map to the same target, a duplicate-object build failure will occur.

Run an Xcode build for each platform to be safe.

Also applies to: 3808-3808, 3947-3947

Source/include/Ably/ARTRealtimeAnnotations.h (1)

15-68: Well-designed API for annotation operations.

The new methods provide a comprehensive interface for annotation operations with good separation between message-based and serial-based variants. The consistent pattern across publish, delete, and get operations makes the API intuitive to use.

Test/Tests/RealtimeAnnotationsTests.swift (2)

128-128: Temporary sleep could cause test flakiness.

The hardcoded sleep(1) could make tests unreliable. I see this is documented as a temporary measure pending CHA-887. Consider using a more robust synchronization mechanism or at least wrapping this in a retry logic to reduce flakiness.


10-152: Comprehensive test coverage for annotation lifecycle.

The test thoroughly validates the annotation publish, delete, and retrieval flow with proper verification of annotation properties and message summaries. Good use of split done pattern for managing multiple async operations.

Source/ARTWrapperSDKProxyRealtimeAnnotations.m (1)

35-58: Correct proxy implementation for annotation methods.

All six new methods correctly forward calls to the underlying ARTRealtimeAnnotations instance, maintaining consistency with the existing proxy pattern.

Source/ARTJsonLikeEncoder.m (1)

87-109: Comprehensive annotation encoding/decoding implementation.

The implementation correctly handles:

  • Single annotation and array encoding/decoding
  • Proper action value conversion with intentional default to ARTAnnotationCreate
  • All annotation properties in serialization
  • Integration with protocol message encoding/decoding

The code follows established patterns and properly handles edge cases.

Also applies to: 357-367, 422-457, 493-535, 564-573, 634-636, 893-893

Test/Tests/RestAnnotationsTests.swift (2)

173-303: Excellent test for idempotent annotation publishing.

This test thoroughly validates that annotations with implicit IDs are only published once when idempotent publishing is enabled. The verification that duplicate publish attempts succeed without creating duplicate annotations is particularly valuable.


305-343: Good edge case testing for message size limits.

The test properly validates that annotations exceeding maxMessageSize are rejected with the correct error code (40009). Using ARTDefault.maxMessageSize() ensures the test adapts to configuration changes.

Source/ARTRestAnnotations.m (3)

19-45: LGTM!

The ARTAnnotationsQuery implementation correctly handles limit validation and defaults.


47-84: LGTM!

The ARTRestAnnotations wrapper correctly delegates all operations to the internal implementation.


149-213: Potential crash due to weak reference in async block

The weak _channel reference could become nil during async execution, potentially causing crashes when accessing channel properties. Consider capturing a strong reference or checking for nil.

-dispatch_async(_queue, ^{
+    ARTRestChannelInternal *channel = _channel;
+    if (!channel) {
+        if (callback) {
+            callback([ARTErrorInfo createWithCode:ARTErrorConnectionFailed message:@"Channel has been deallocated"]);
+        }
+        return;
+    }
+    
+dispatch_async(_queue, ^{
+    // Re-check channel availability inside the block
+    if (!channel) {
+        if (callback) {
+            callback([ARTErrorInfo createWithCode:ARTErrorConnectionFailed message:@"Channel has been deallocated"]);
+        }
+        return;
+    }
+    
     // RSAN1c3: encode annotation data
     NSError *encodeError = nil;
-    ARTAnnotation *annotationToPublish = self->_dataEncoder ? [annotation encodeDataWithEncoder:self->_dataEncoder error:&encodeError] : annotation;
+    ARTAnnotation *annotationToPublish = self->_dataEncoder ? [annotation encodeDataWithEncoder:self->_dataEncoder error:&encodeError] : annotation;

Then use channel instead of self->_channel throughout the block.

⛔ Skipped due to learnings
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTRealtimeChannelOptions.h:33-33
Timestamp: 2024-10-08T15:37:26.766Z
Learning: `ARTChannelOptions` is already marked with `NS_SWIFT_SENDABLE`.
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Test/Tests/RestClientChannelsTests.swift:5-18
Timestamp: 2024-09-17T13:25:42.060Z
Learning: Avoid suggesting refactoring the conditional compilation in `RestClientChannelsTests.swift` for `ARTRestChannels` extensions, as the user prefers to keep the current structure.
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Test/Tests/RestClientChannelsTests.swift:5-18
Timestamp: 2024-10-08T15:37:26.765Z
Learning: Avoid suggesting refactoring the conditional compilation in `RestClientChannelsTests.swift` for `ARTRestChannels` extensions, as the user prefers to keep the current structure.
Learnt from: maratal
PR: ably/ably-cocoa#1995
File: Source/ARTRealtimeChannel.m:227-227
Timestamp: 2024-11-04T23:10:23.027Z
Learning: In `ARTRealtimeChannelInternal`, the `internalPresence` property is declared in the private header `ARTRealtimeChannel+Private.h`, so it does not need to be redeclared in `ARTRealtimeChannel.m`.
Learnt from: maratal
PR: ably/ably-cocoa#1998
File: Source/ARTJsonLikeEncoder.m:278-279
Timestamp: 2024-11-22T10:11:05.305Z
Learning: In `ARTMessage`, the `action` and `serial` fields are set by the realtime service and should not be serialized in the `messageToDictionary:` method in `Source/ARTJsonLikeEncoder.m`.
Learnt from: maratal
PR: ably/ably-cocoa#2066
File: Source/ARTJsonLikeEncoder.m:341-351
Timestamp: 2025-06-16T15:39:16.005Z
Learning: In the Ably Cocoa SDK, when decoding annotation actions from integers, unknown/invalid action values should default to ARTAnnotationCreate rather than failing or using an unknown state. This is an intentional design decision for the annotation system.
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTBaseQuery.h:4-8
Timestamp: 2024-09-12T20:12:29.428Z
Learning: As a general rule, `NS_SWIFT_SENDABLE` annotations are not needed for private Objective-C interfaces in the ably-cocoa project.
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTBaseQuery.h:4-8
Timestamp: 2024-10-08T15:37:26.766Z
Learning: As a general rule, `NS_SWIFT_SENDABLE` annotations are not needed for private Objective-C interfaces in the ably-cocoa project.
Learnt from: maratal
PR: ably/ably-cocoa#1998
File: Source/ARTJsonLikeEncoder.m:766-774
Timestamp: 2024-11-26T17:50:25.730Z
Learning: In 'Source/ARTJsonLikeEncoder.m', when handling messages from 'input', using `mutableCopy` on the array ensures that modifications to `messages` do not affect the original `input`.
Learnt from: maratal
PR: ably/ably-cocoa#2066
File: Source/include/Ably/ARTRealtimeAnnotations.h:25-28
Timestamp: 2025-06-16T14:18:14.569Z
Learning: The unpublish method in ARTRealtimeAnnotations works by publishing an annotation with a DELETE action, rather than directly deleting. This is why the documentation correctly refers to "The annotation to publish" even though it's an unpublish method.
Source/ARTRealtimeAnnotations.m (4)

32-54: LGTM!

The new forwarding methods in ARTRealtimeAnnotations correctly delegate to the internal implementation, maintaining consistency with the existing pattern.


107-191: Well-structured implementation with proper state handling

The publishAnnotation method correctly handles connection state validation, annotation encoding, size limits, and channel state transitions. The use of dispatch_sync is appropriate for state checking.


192-221: LGTM! Clean refactoring

The _subscribe method has been refactored for better readability while maintaining the same functionality. The state checking and attachOnSubscribe handling remain correct.


287-287: LGTM!

The method call update to decodeDataWithEncoder:error: is consistent with the renamed API.

@github-actions github-actions bot temporarily deployed to staging/pull/2080/jazzydoc August 3, 2025 21:33 Inactive
@maratal maratal force-pushed the feature/2077-annotations branch from 3fe9352 to 966de7f Compare August 3, 2025 23:21
@maratal
Copy link
Collaborator Author

maratal commented Aug 3, 2025

@coderabbitai pause

@github-actions github-actions bot temporarily deployed to staging/pull/2080/features August 3, 2025 23:22 Inactive
@maratal maratal requested a review from Copilot August 3, 2025 23:24
@github-actions github-actions bot temporarily deployed to staging/pull/2080/jazzydoc August 3, 2025 23:24 Inactive
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements support for message annotations in both REST and Realtime APIs, enabling users to publish, delete, and retrieve annotations (such as reactions) on individual messages. The implementation adds new query options for fetching annotations with pagination and ensures proper error handling for annotations exceeding maximum message size.

  • Adds ARTRestAnnotations and ARTRealtimeAnnotations classes with comprehensive methods for annotation management
  • Implements annotation size validation against maxMessageSize with appropriate error codes
  • Adds comprehensive test coverage for both REST and realtime annotation operations including idempotency and error conditions

Reviewed Changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Source/include/Ably/ARTRestAnnotations.h Public header defining REST annotations API with query parameters and protocol methods
Source/ARTRestAnnotations.m Implementation of REST annotations functionality with HTTP requests and validation
Source/ARTRealtimeAnnotations.m Implementation of realtime annotations with protocol message handling
Test/Tests/RestAnnotationsTests.swift Comprehensive tests for REST annotation operations
Test/Tests/RealtimeAnnotationsTests.swift Comprehensive tests for realtime annotation operations
Various header/project files Module map updates and public API exposure

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

♻️ Duplicate comments (3)
Source/ARTRestAnnotations.m (3)

224-226: Add nil check for message parameter

The method should validate that the message parameter is not nil before accessing its serial property.

 - (void)getForMessage:(ARTMessage *)message query:(ARTAnnotationsQuery *)query callback:(ARTPaginatedAnnotationsCallback)callback {
+    if (!message || !message.serial) {
+        if (callback) {
+            dispatch_async(_userQueue, ^{
+                callback(nil, [ARTErrorInfo createWithCode:ARTErrorInvalidParams message:@"Message or message serial cannot be nil"]);
+            });
+        }
+        return;
+    }
     [self getForMessageSerial:message.serial query:query callback:callback];
 }

286-288: Add nil check for message parameter in publishForMessage

The method should validate that the message parameter is not nil before accessing its serial property.

 - (void)publishForMessage:(ARTMessage *)message annotation:(ARTAnnotation *)annotation callback:(ARTCallback)callback {
+    if (!message || !message.serial) {
+        if (callback) {
+            dispatch_async(_userQueue, ^{
+                callback([ARTErrorInfo createWithCode:ARTErrorInvalidParams message:@"Message or message serial cannot be nil"]);
+            });
+        }
+        return;
+    }
     [self publishAnnotation:annotation messageSerial:message.serial action:ARTAnnotationCreate callback:callback];
 }

294-296: Add nil check for message parameter in deleteForMessage

The method should validate that the message parameter is not nil before accessing its serial property.

 - (void)deleteForMessage:(ARTMessage *)message annotation:(ARTAnnotation *)annotation callback:(ARTCallback)callback {
+    if (!message || !message.serial) {
+        if (callback) {
+            dispatch_async(_userQueue, ^{
+                callback([ARTErrorInfo createWithCode:ARTErrorInvalidParams message:@"Message or message serial cannot be nil"]);
+            });
+        }
+        return;
+    }
     [self publishAnnotation:annotation messageSerial:message.serial action:ARTAnnotationDelete callback:callback];
 }
🧹 Nitpick comments (3)
Test/Tests/RealtimeAnnotationsTests.swift (1)

101-101: Replace unused closure parameter with underscore

The stateChange parameter is not used in the closure.

-realtimeChannel.once(.attached) { stateChange in
+realtimeChannel.once(.attached) { _ in
Test/Tests/RestAnnotationsTests.swift (2)

106-106: Replace unused closure parameter with underscore

The stateChange parameter is not used in the closure.

-realtimeChannel.once(.attached) { stateChange in
+realtimeChannel.once(.attached) { _ in

256-256: Replace unused closure parameter with underscore

The stateChange parameter is not used in the closure.

-realtimeChannel.once(.attached) { stateChange in
+realtimeChannel.once(.attached) { _ in
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fe9352 and 966de7f.

📒 Files selected for processing (26)
  • Ably.xcodeproj/project.pbxproj (18 hunks)
  • Source/ARTAnnotation.m (3 hunks)
  • Source/ARTConstants.m (1 hunks)
  • Source/ARTJsonLikeEncoder.m (4 hunks)
  • Source/ARTRealtimeAnnotations.m (5 hunks)
  • Source/ARTRestAnnotations.m (1 hunks)
  • Source/ARTRestChannel.m (7 hunks)
  • Source/ARTWrapperSDKProxyRealtimeAnnotations.m (1 hunks)
  • Source/Ably.modulemap (1 hunks)
  • Source/PrivateHeaders/Ably/ARTAnnotation+Private.h (1 hunks)
  • Source/PrivateHeaders/Ably/ARTConstants.h (1 hunks)
  • Source/PrivateHeaders/Ably/ARTEncoder.h (2 hunks)
  • Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h (1 hunks)
  • Source/PrivateHeaders/Ably/ARTRealtimeAnnotations+Private.h (1 hunks)
  • Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h (1 hunks)
  • Source/PrivateHeaders/Ably/ARTRestChannel+Private.h (1 hunks)
  • Source/include/Ably/ARTAnnotation.h (1 hunks)
  • Source/include/Ably/ARTRealtimeAnnotations.h (2 hunks)
  • Source/include/Ably/ARTRestAnnotations.h (1 hunks)
  • Source/include/Ably/ARTRestChannel.h (2 hunks)
  • Source/include/Ably/ARTTypes.h (0 hunks)
  • Source/include/Ably/AblyPublic.h (1 hunks)
  • Source/include/module.modulemap (1 hunks)
  • Test/Codables/TestAppSetup.swift (1 hunks)
  • Test/Tests/RealtimeAnnotationsTests.swift (1 hunks)
  • Test/Tests/RestAnnotationsTests.swift (1 hunks)
💤 Files with no reviewable changes (1)
  • Source/include/Ably/ARTTypes.h
✅ Files skipped from review due to trivial changes (5)
  • Source/include/module.modulemap
  • Source/Ably.modulemap
  • Source/include/Ably/AblyPublic.h
  • Source/ARTWrapperSDKProxyRealtimeAnnotations.m
  • Ably.xcodeproj/project.pbxproj
🚧 Files skipped from review as they are similar to previous changes (17)
  • Test/Codables/TestAppSetup.swift
  • Source/PrivateHeaders/Ably/ARTConstants.h
  • Source/ARTConstants.m
  • Source/PrivateHeaders/Ably/ARTRealtimeAnnotations+Private.h
  • Source/include/Ably/ARTAnnotation.h
  • Source/PrivateHeaders/Ably/ARTRestChannel+Private.h
  • Source/include/Ably/ARTRestChannel.h
  • Source/ARTRestChannel.m
  • Source/PrivateHeaders/Ably/ARTRestAnnotations+Private.h
  • Source/PrivateHeaders/Ably/ARTAnnotation+Private.h
  • Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h
  • Source/PrivateHeaders/Ably/ARTEncoder.h
  • Source/include/Ably/ARTRestAnnotations.h
  • Source/ARTAnnotation.m
  • Source/include/Ably/ARTRealtimeAnnotations.h
  • Source/ARTJsonLikeEncoder.m
  • Source/ARTRealtimeAnnotations.m
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTBaseQuery.h:4-8
Timestamp: 2024-09-12T20:12:29.428Z
Learning: As a general rule, `NS_SWIFT_SENDABLE` annotations are not needed for private Objective-C interfaces in the ably-cocoa project.
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTBaseQuery.h:4-8
Timestamp: 2024-10-08T15:37:26.766Z
Learning: As a general rule, `NS_SWIFT_SENDABLE` annotations are not needed for private Objective-C interfaces in the ably-cocoa project.
Learnt from: maratal
PR: ably/ably-cocoa#2066
File: Source/ARTJsonLikeEncoder.m:341-351
Timestamp: 2025-06-16T15:39:16.005Z
Learning: In the Ably Cocoa SDK, when decoding annotation actions from integers, unknown/invalid action values should default to ARTAnnotationCreate rather than failing or using an unknown state. This is an intentional design decision for the annotation system.
📚 Learning: in the swift test files within the `test/tests/` directory of the ably cocoa project, it's acceptabl...
Learnt from: maratal
PR: ably/ably-cocoa#1995
File: Test/Tests/RealtimeClientChannelTests.swift:192-193
Timestamp: 2024-11-04T23:11:06.909Z
Learning: In the Swift test files within the `Test/Tests/` directory of the Ably Cocoa project, it's acceptable to access internal types and properties directly in tests.

Applied to files:

  • Test/Tests/RealtimeAnnotationsTests.swift
  • Test/Tests/RestAnnotationsTests.swift
📚 Learning: avoid suggesting refactoring the conditional compilation in `restclientchannelstests.swift` for `art...
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Test/Tests/RestClientChannelsTests.swift:5-18
Timestamp: 2024-09-17T13:25:42.060Z
Learning: Avoid suggesting refactoring the conditional compilation in `RestClientChannelsTests.swift` for `ARTRestChannels` extensions, as the user prefers to keep the current structure.

Applied to files:

  • Test/Tests/RealtimeAnnotationsTests.swift
  • Source/ARTRestAnnotations.m
  • Test/Tests/RestAnnotationsTests.swift
📚 Learning: the unpublish method in artrealtimeannotations works by publishing an annotation with a delete actio...
Learnt from: maratal
PR: ably/ably-cocoa#2066
File: Source/include/Ably/ARTRealtimeAnnotations.h:25-28
Timestamp: 2025-06-16T14:18:14.569Z
Learning: The unpublish method in ARTRealtimeAnnotations works by publishing an annotation with a DELETE action, rather than directly deleting. This is why the documentation correctly refers to "The annotation to publish" even though it's an unpublish method.

Applied to files:

  • Test/Tests/RealtimeAnnotationsTests.swift
  • Source/ARTRestAnnotations.m
  • Test/Tests/RestAnnotationsTests.swift
📚 Learning: `artchanneloptions` is already marked with `ns_swift_sendable`....
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTRealtimeChannelOptions.h:33-33
Timestamp: 2024-10-08T15:37:26.766Z
Learning: `ARTChannelOptions` is already marked with `NS_SWIFT_SENDABLE`.

Applied to files:

  • Test/Tests/RealtimeAnnotationsTests.swift
  • Source/ARTRestAnnotations.m
  • Test/Tests/RestAnnotationsTests.swift
📚 Learning: in the ably cocoa sdk, when decoding annotation actions from integers, unknown/invalid action values...
Learnt from: maratal
PR: ably/ably-cocoa#2066
File: Source/ARTJsonLikeEncoder.m:341-351
Timestamp: 2025-06-16T15:39:16.005Z
Learning: In the Ably Cocoa SDK, when decoding annotation actions from integers, unknown/invalid action values should default to ARTAnnotationCreate rather than failing or using an unknown state. This is an intentional design decision for the annotation system.

Applied to files:

  • Test/Tests/RealtimeAnnotationsTests.swift
  • Source/ARTRestAnnotations.m
  • Test/Tests/RestAnnotationsTests.swift
📚 Learning: in `artmessage`, the `action` and `serial` fields are set by the realtime service and should not be ...
Learnt from: maratal
PR: ably/ably-cocoa#1998
File: Source/ARTJsonLikeEncoder.m:278-279
Timestamp: 2024-11-22T10:11:05.305Z
Learning: In `ARTMessage`, the `action` and `serial` fields are set by the realtime service and should not be serialized in the `messageToDictionary:` method in `Source/ARTJsonLikeEncoder.m`.

Applied to files:

  • Source/ARTRestAnnotations.m
📚 Learning: in `artrealtimechannelinternal`, the `internalpresence` property is declared in the private header `...
Learnt from: maratal
PR: ably/ably-cocoa#1995
File: Source/ARTRealtimeChannel.m:227-227
Timestamp: 2024-11-04T23:10:23.027Z
Learning: In `ARTRealtimeChannelInternal`, the `internalPresence` property is declared in the private header `ARTRealtimeChannel+Private.h`, so it does not need to be redeclared in `ARTRealtimeChannel.m`.

Applied to files:

  • Source/ARTRestAnnotations.m
📚 Learning: in 'source/artjsonlikeencoder.m', when handling messages from 'input', using `mutablecopy` on the ar...
Learnt from: maratal
PR: ably/ably-cocoa#1998
File: Source/ARTJsonLikeEncoder.m:766-774
Timestamp: 2024-11-26T17:50:25.730Z
Learning: In 'Source/ARTJsonLikeEncoder.m', when handling messages from 'input', using `mutableCopy` on the array ensures that modifications to `messages` do not affect the original `input`.

Applied to files:

  • Source/ARTRestAnnotations.m
📚 Learning: parsing the message's `id` is necessary for checking `msgserial` and `index` in the `member:isnewert...
Learnt from: maratal
PR: ably/ably-cocoa#1982
File: Source/ARTPresenceMessage.m:59-59
Timestamp: 2024-10-06T12:27:40.858Z
Learning: Parsing the message's `id` is necessary for checking `msgSerial` and `index` in the `member:isNewerThan:` method.

Applied to files:

  • Source/ARTRestAnnotations.m
📚 Learning: as a general rule, `ns_swift_sendable` annotations are not needed for private objective-c interfaces...
Learnt from: maratal
PR: ably/ably-cocoa#1973
File: Source/include/Ably/ARTBaseQuery.h:4-8
Timestamp: 2024-09-12T20:12:29.428Z
Learning: As a general rule, `NS_SWIFT_SENDABLE` annotations are not needed for private Objective-C interfaces in the ably-cocoa project.

Applied to files:

  • Test/Tests/RestAnnotationsTests.swift
🪛 SwiftLint (0.57.0)
Test/Tests/RealtimeAnnotationsTests.swift

[Warning] 101-101: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

Test/Tests/RestAnnotationsTests.swift

[Warning] 106-106: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 256-256: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

⏰ 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). (4)
  • GitHub Check: check (tvOS, test_tvOS18_4)
  • GitHub Check: check (macOS, test_macOS)
  • GitHub Check: check
  • GitHub Check: build

Copy link

coderabbitai bot commented Aug 3, 2025

✅ Actions performed

Reviews paused.

@maratal maratal force-pushed the feature/2077-annotations branch from 966de7f to 62f61a8 Compare August 4, 2025 08:52
@github-actions github-actions bot temporarily deployed to staging/pull/2080/features August 4, 2025 08:53 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2080/jazzydoc August 4, 2025 08:56 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2080/features August 9, 2025 23:11 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2080/jazzydoc August 9, 2025 23:13 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2080/features August 9, 2025 23:37 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2080/jazzydoc August 9, 2025 23:42 Inactive
@maratal maratal force-pushed the feature/2077-annotations branch from a6b3107 to 3f0ce6a Compare August 9, 2025 23:44
@github-actions github-actions bot temporarily deployed to staging/pull/2080/features August 9, 2025 23:45 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2080/jazzydoc August 9, 2025 23:48 Inactive
@maratal maratal force-pushed the feature/2077-annotations branch from 3f0ce6a to 953996e Compare August 10, 2025 00:20
@github-actions github-actions bot temporarily deployed to staging/pull/2080/features August 10, 2025 00:21 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2080/jazzydoc August 10, 2025 00:24 Inactive
@maratal maratal force-pushed the feature/2077-annotations branch from 953996e to 1512bda Compare August 10, 2025 00:27
@github-actions github-actions bot temporarily deployed to staging/pull/2080/features August 10, 2025 00:28 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2080/jazzydoc August 10, 2025 00:32 Inactive
@maratal maratal force-pushed the feature/2077-annotations branch from 1512bda to feb0394 Compare August 10, 2025 13:50
@github-actions github-actions bot temporarily deployed to staging/pull/2080/features August 10, 2025 13:50 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2080/jazzydoc August 10, 2025 13:52 Inactive
@maratal maratal force-pushed the feature/2077-annotations branch from feb0394 to a54d445 Compare August 10, 2025 15:06
@github-actions github-actions bot temporarily deployed to staging/pull/2080/features August 10, 2025 15:07 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2080/jazzydoc August 10, 2025 15:10 Inactive
- Rest annotations
- Publishing realtime annotations
- Tests
Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian left a comment

Choose a reason for hiding this comment

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

Some initial comments — haven't looked at the tests yet

let protocolMessage = ARTProtocolMessage()
protocolMessage.action = ARTProtocolMessageAction.attached
protocolMessage.channel = "test"
protocolMessage.flags = Int64(ARTProtocolMessageFlag.hasBacklog.rawValue | ARTProtocolMessageFlag.resumed.rawValue | ARTChannelMode.publish.rawValue | ARTChannelMode.subscribe.rawValue) // adding flags which are not modes
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you make it clearer here which are and aren't modes?

// Then: The modes property should be set from the protocol message and contain only channel modes flags
let expectedModes = ARTChannelMode.publish.rawValue | ARTChannelMode.subscribe.rawValue
XCTAssertEqual(channel.modes, ARTChannelMode(rawValue: expectedModes))
XCTAssertNotEqual(channel.modes, ARTChannelMode(rawValue: UInt(protocolMessage.flags)))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why assert channel.modes is not equal to something when on the previous line you already asserted its exact value?


// Then: The modes property should be set from the protocol message and contain only channel modes flags
let expectedModes = ARTChannelMode.publish.rawValue | ARTChannelMode.subscribe.rawValue
XCTAssertEqual(channel.modes, ARTChannelMode(rawValue: expectedModes))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't this just be XCTAssertEqual(channel.modes, [.publish, .subscribe])?

@@ -184,6 +184,14 @@ - (ARTConnectionDetails *)getConnectionDetails {
return _connectionDetails;
}

+ (ARTChannelMode)allChannelModes {
return ARTChannelModePresence | ARTChannelModePublish | ARTChannelModeSubscribe | ARTChannelModePresenceSubscribe | ARTChannelModeAnnotationPublish | ARTChannelModeAnnotationSubscribe;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I worry about the maintainability of this. It seems very likely we'll forget to update it when we add new modes. We either need some good internal documentation or could we just chop off the bottom 4 bits per https://sdk.ably.com/builds/ably/specification/main/features/#TB2d?

* @param annotation The annotation to publish. (Must include at least the `type`. Assumed to be an annotation.create if no action is specified)
* @param callback A success or failure callback function.
*/
- (void)publishForMessage:(ARTMessage *)message annotation:(ARTAnnotation *)annotation callback:(nullable ARTCallback)callback;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did you decide that the publish-annotation methods should accept an ARTAnnotation? This forces users to use that huge initializer and to specify non-nullable ARTAnnotation properties that it doesn't make sense for them to specify, such as serial or action. See RSAN1a2

NSError *error = nil;
NSJSONWritingOptions options;
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
options = NSJSONWritingWithoutEscapingSlashes;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why?

type:annotation.type
extras:annotation.extras];
dispatch_sync(_queue, ^{
if (!self.realtime.connection.isActive_nosync) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please include the spec points that this check and the channel state check are implementing.

ARTLogVerbose(self.logger, @"R:%p C:%p (%@) annotation subscribe to '%@' action(s)", self->_realtime, self->_channel, self->_channel.name, type == nil ? @"all" : type);
});
[self->_channel.internalEventEmitter once:^(ARTChannelStateChange *stateChange) {
if (stateChange.current == ARTRealtimeChannelAttached && !self->_channel.isAnnotationSubscribeGranted) { // RTAN4e
Copy link
Collaborator

Choose a reason for hiding this comment

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

What if the channel was already attached and so we didn't need to do an implicit attach, but the modes don't contain ANNOTATION_SUBSCRIBE?

- (instancetype)init {
self = [super init];
if (self) {
_limit = 100;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Where did this come from?

- (instancetype)initWithLimit:(NSUInteger)limit {
self = [super init];
if (self) {
_limit = limit > 0 ? limit : 100;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why? Seems like a negative limit is a programmer error

@lawrence-forooghian
Copy link
Collaborator

lawrence-forooghian commented Sep 1, 2025

Also, what about the https://sdk.ably.com/builds/ably/specification/main/features/#TM7 convenience types? (can do it as a separate issue if you want)

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.

Complete annotations implementation
2 participants