Skip to content

feat(ffi): expose Client.sync() - #6359

Merged
bnjbvr merged 3 commits into
matrix-org:mainfrom
tumata:feat/6348-sync
Mar 31, 2026
Merged

bnjbvr merged 3 commits into
matrix-org:mainfrom
tumata:feat/6348-sync

Conversation

@tumata

@tumata tumata commented Mar 27, 2026 •

Copy link
Copy Markdown
Contributor

Expose sync v2 API through FFI via Client.sync() and Client.sync_once() #6348

Mobile clients can now sync using the traditional /sync v2 endpoint through the FFI bindings, without requiring Sliding Sync (MSC4186) support on the homeserver.

This PR introduces a new API (non-breaking change).

Implementation notes

I chose to expose only the list of room IDs (invited, joined, left, knocked) in the SyncResponse, rather than forwarding the full per-room event payloads. This keeps the FFI surface simple. Clients that need room details can query them through the existing Room APIs after being notified of changes via the listener.

Two entry points are provided:

  • Client.sync(settings, listener) starts a continuous sync loop, returning a TaskHandle for cancellation. The SyncListener callback fires after each successful sync response.
  • Client.sync_once(settings) performs a single sync call, useful for initial sync or one-off use cases.

SyncSettings exposes timeout_ms and full_state

Build

I verified building the XCFramework

cargo xtask swift build-framework --ios-deployment-target 15.0

I also verified integrating this build into our test iOS application and used the new API, with success.

Use of AI

I used AI as a reference while making this change. I reviewed and validated the implementation myself, and take full accountability for it.

Closes #6348

  • I've documented the public API Changes in the appropriate CHANGELOG.md files.
  • This PR was made with the help of AI.

@tumata
tumata requested a review from a team as a code owner March 27, 2026 00:42
@tumata
tumata requested review from bnjbvr and removed request for a team March 27, 2026 00:42
@codecov

codecov Bot commented Mar 27, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.86%. Comparing base (02c41e2) to head (6356ef2).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6359      +/-   ##
==========================================
- Coverage   89.87%   89.86%   -0.01%     
==========================================
  Files         376      376              
  Lines      103093   103093              
  Branches   103093   103093              
==========================================
- Hits        92657    92649       -8     
- Misses       6863     6872       +9     
+ Partials     3573     3572       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@codspeed

codspeed Bot commented Mar 27, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 49.34%

⚡ 1 improved benchmark
✅ 49 untouched benchmarks

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ Simulation Restore session [memory store] 167.3 ms 112 ms +49.34%

Comparing tumata:feat/6348-sync (6356ef2) with main (02c41e2)

Open in CodSpeed

@bnjbvr bnjbvr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code looks fine to me. I'm a bit curious about how it would be used, as returning the room IDs in the sync responses seem very minimal.

As an opportunity to not flood the client.rs file which is already a bit big, can you move this code to a new sync_v2.rs sub-module?

Also, I think it'd be nice to make it clear that this is the previous sync mechanism, so can you include a v2 suffix to all the methods and types, please? (e.g. SyncSettingsV2, sync_v2(), sync_once_v2(), etc.)

Thanks!

Comment thread bindings/matrix-sdk-ffi/CHANGELOG.md Outdated
requiring Sliding Sync support on the homeserver. `Client.sync()`
accepts a `SyncListener` callback that receives a `SyncResponse`
after each successful sync.
([#6348](https://github.com/matrix-org/matrix-rust-sdk/issues/6348))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR number is wrong here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed: 028080c

Comment thread bindings/matrix-sdk-ffi/src/client.rs Outdated
pub struct SyncSettings {
/// Timeout in milliseconds for the server long-poll.
/// If not set, defaults to 30 seconds.
#[uniffi(default = None)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Curious, but are these annotations any useful? I'd imagine that uniffi uses the Default::default implementation for each type by default, which will set to None/false for these two types?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wanted to provided sensible defaults on the Swift side. This is meant to avoid having to provide a default value when constructing the Swift structure. Otherwise the implementor has to provide the arguments, such as nil in this particular line.

// Without the annotations:
SyncSettings(timeoutMs: nil, fullState: false)  // must provide all args

// With #[uniffi(default = ...)]:
SyncSettings() // can provide no args
SyncSettings(timeoutMs: 0) // ... some args only
SyncSettings(fullState: false)
SyncSettings(timeoutMs: 0, fullState: false) // or all args

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sweet, thanks for explaining!

Comment thread bindings/matrix-sdk-ffi/src/client.rs Outdated
Comment on lines +306 to +307
/// Room IDs of joined rooms that had updates.
pub joined: Vec<String>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Curious about your use case; is it sufficient to only get room IDs here? How do you get new events/timelines for these rooms, out of curiosity?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Here's the flow I tested and that suits our needs:

  1. Call sync and register a SyncListener
  2. When SyncListener.on_update is called:
    • For all invited rooms: join via client.joinRoomById, then subscribe to the room timeline
    • For all joined rooms: subscribe to the room timeline

From there, the TimelineListener callback gives me the events, state events, and such.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@bnjbvr
Note that I am relying on what does not seems like a production-ready API to get the data I need from the event (EventTimelineItem)

event.lazyProvider.debugInfo().originalJson

Unfortunately I could not find another API to get the raw event data. Any suggestions? I was going to create another issue to tackle this specific request.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Interesting. Out of curiosity, why do you need the raw event data? That makes sense to me for custom events, even though we could probably provide some new variant of TimelineItemContent for those. But for other events, you should have everything you need as part of the EventTimelineItem::content, likely?

@tumata tumata Mar 31, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, we need it exactly to handle custom events.

I created a PR and provided explanation there: #6387

@tumata

tumata commented Mar 30, 2026

Copy link
Copy Markdown
Contributor Author

@bnjbvr I actually started with "v2" in the names, but then reverted back to sync because:

  1. sync is also what's used at the rust layer
  2. I was thinking that "v2" might make it appear like it was newer/preferred over the Sliding Sync option, which it isn't.

Sounds like you're fine with it though, so I will proceed with your suggestion 👍

tumata added 2 commits March 30, 2026 14:10
Signed-off-by: Philippe Bertin <pbertin@teladochealth.com>
Link PRs instead of Issues
Rename to sync_v2 and create sub-module
@tumata

tumata commented Mar 30, 2026

Copy link
Copy Markdown
Contributor Author

The PR is ready for another review. I addressed the requested changes in 6356ef2. Thanks

@tumata
tumata requested a review from bnjbvr March 30, 2026 22:14

@bnjbvr bnjbvr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you! Feel free to ask extra questions in our dev room :-)

@bnjbvr
bnjbvr merged commit 2214ade into matrix-org:main Mar 31, 2026
52 of 53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FFI bindings: expose regular sync (v2) API for Swift/iOS

2 participants