Refactor SYNC-4917 Migrate to use app-services FxA state machine#32716
Refactor SYNC-4917 Migrate to use app-services FxA state machine#32716skhamis wants to merge 2 commits intomozilla-mobile:mainfrom
Conversation
mhammond
left a comment
There was a problem hiding this comment.
this looks like a nice improvement. I wonder if claude can help identify if this patch leaves behind dead code that can be removed?
bendk
left a comment
There was a problem hiding this comment.
This looks really nice to me.
The one thing I think needs to be figured out is the threading model for sending events and processing the side effects. I think it might be just mean making fxaFsmQueue serialized and documenting the correct usage.
An actor could be a great way of handling this, since it's mostly about making sure the state isn't being mutated by 2 threads at once. However, that would probably be too big of a change.
I don't know enough about the firefox-ios side of things to review that side of the integration code. I just focused on checking if the Rust parts seem correct. For that I guess we need an ios engineer and/or some good testing.
MozillaRustComponents/Sources/MozillaRustComponentsWrapper/FxAClient/FxAccountManager.swift
Outdated
Show resolved
Hide resolved
MozillaRustComponents/Sources/MozillaRustComponentsWrapper/FxAClient/FxAccountManager.swift
Show resolved
Hide resolved
MozillaRustComponents/Sources/MozillaRustComponentsWrapper/FxAClient/FxAccountManager.swift
Outdated
Show resolved
Hide resolved
mhammond
left a comment
There was a problem hiding this comment.
I've only a couple of nit about your comments - sorry about that 😅 . LGTM, nice work! I'm happy when Ben and iOS are happy :)
bendk
left a comment
There was a problem hiding this comment.
I agree with Mark's comment suggestions, other than that this is looking right to me. Thanks for the serial comment on the dispatch queue, that makes me feel better that this is all going to run correctly.
0de9847 to
4e7bd82
Compare
💪 Quality guardian2 tests files modified. You're a champion of test coverage! 🚀 🏔️ Summit ClimberThis PR is a big climb with 828 lines changed! 💬 Description craftsmanGreat PR description! Reviewers salute you 🫡 ✅ New file code coverageNo new file detected so code coverage gate wasn't ran. Client.app: Coverage: 39.41
CredentialProvider.appex: Coverage: 31.36
NotificationService.appex: Coverage: 36.46
ShareTo.appex: Coverage: 28.48
Generated by 🚫 Danger Swift against e263ace |
MozillaRustComponents/Sources/MozillaRustComponentsWrapper/FxAClient/FxAccountManager.swift
Show resolved
Hide resolved
MozillaRustComponents/Sources/MozillaRustComponentsWrapper/FxAClient/FxAccountManager.swift
Outdated
Show resolved
Hide resolved
MozillaRustComponents/Sources/MozillaRustComponentsWrapper/FxAClient/FxAccountManager.swift
Outdated
Show resolved
Hide resolved
| return try acct.gatherTelemetry() | ||
| } | ||
|
|
||
| // Serial by default (no .concurrent attribute). All FSM state mutations and |
There was a problem hiding this comment.
What does FSM means + we should move this variable declaration at the top of the file. This is where we normally search and expect variables
| DispatchQueue.main.async { | ||
| NotificationCenter.default.post(name: .accountLoggedOut, object: nil) | ||
| } |
There was a problem hiding this comment.
Posting the notification on the main queue doesn't guarantee the actual notification to be received in the observer on that queue since this responsibility falls onto the observer itself, not sure if this was the intent here?
I checked, and we're actually calling ensureMainThread where we are listening to that notification, which does guarantee that code will be run on the main thread.
So I would personally remove this DispatchQueue, unless the intent was something else?
| FxALog.info("Ensuring device capabilities...") | ||
| requireConstellation().ensureCapabilities(capabilities: deviceConfig.capabilities) | ||
| case .authIssues: | ||
| DispatchQueue.main.async { |
There was a problem hiding this comment.
Same comment about queue/notification, but not sure where this notification is observed.
| private func refreshProfileAsync(ignoreCache: Bool) { | ||
| DispatchQueue.global().async { | ||
| do { | ||
| let p = try self.requireAccount().getProfile(ignoreCache: ignoreCache) |
There was a problem hiding this comment.
| let p = try self.requireAccount().getProfile(ignoreCache: ignoreCache) | |
| let profile = try self.requireAccount().getProfile(ignoreCache: ignoreCache) |
| guard self.state == .connected || self.state == .authIssues else { return } | ||
| self.profile = p | ||
| DispatchQueue.main.async { | ||
| NotificationCenter.default.post( |
There was a problem hiding this comment.
Same comment about queue/notification
| // starts in .uninitialized. Without this call, | ||
| // subsequent events like .beginOAuthFlow would be rejected by the FSM. | ||
| account = createAccount() | ||
| let dc = DeviceConfig( |
There was a problem hiding this comment.
| let dc = DeviceConfig( | |
| let deviceConfig = DeviceConfig( |
📜 Tickets
Jira ticket
Github issue
💡 Description
This migrates the FxA auth lifecycle on iOS over to the Rust-native state machine, which Android already uses. Previously iOS was manually tracking auth states, calling initDevice/ensureCapabilities on login, and reading the local device ID from DeviceConstellation — which required waiting on an async server round-trip. Now we just call processEvent(_:) into Rust and let it handle the state transitions and device registration internally.
What changed
State machine: Auth lifecycle is now driven through processEvent(_:) / FxaEvent. No longer manual calls initDevice or ensureCapabilities -> a-s handles those as part of completeOAuthFlow.
Device ID fix: Swapped out deviceConstellation().state().localDevice for getCurrentDeviceId(), which is available immediately after login. This fixes a silent sync failure (DeviceIdError) that happened when DeviceConstellation.refreshState() hadn't finished yet.
Post-login tabs fix: AccountSyncHandler now listens for .accountAuthenticated to call setLocalTabs() on login. Before this, syncEverything() would fire on login but the Rust TabsStore had no local tabs yet, so tabs wouldn't show up on other clients until the user actually add/removed/switched tabs.
📝 Checklist