fix(ios): emit every plugin event on the main thread
Merged
OR
24d ago+67+67+01234567890123456789-27-27-01234567890123456789
What
- Every event the iOS plugin emits now goes through
notifyListenersOnMain, so it's delivered on the main thread. That coversappReady,updateAvailable,set,noNeedUpdate,updateFailed,appReloaded,updateCheckResult,breakingAvailable/majorAvailableand the download-failure events. notifyListenersOnMaingains aretainUntilConsumedparameter (defaultfalse), so retained events keep their behavior.CapgoUpdater.notifyListenersreuses the same helper instead of its own copy of the main-thread hop.
Why
CAPPluginstores listeners in an unsynchronizedNSMutableDictionary(ionic-team/capacitor#8157).addListener/removeListenerrun on the plugin's queue, so notifying from another thread can crash in-[__NSDictionaryM objectForKey:]inside-[CAPPlugin notifyListeners:data:retainUntilConsumed:].- Most events already hop to main through
notifyListenersOnMain, butappReadyis still emitted fromDispatchQueue.global()insendReadyToJs, andupdateAvailablefrom background download work. We shipped a patch for exactly these calls in our app after seeing this crash in production. - I've also opened a fix in Capacitor core (ionic-team/capacitor#8635). This change keeps apps on current and older Capacitor versions safe regardless.
How
- Mechanical: replaced the remaining direct
self.notifyListeners(...)calls withself.notifyListenersOnMain(...). Calls already on the main thread stay synchronous, so ordering and existing behavior on main are unchanged.
Testing
- New
testSendReadyToJsNotifiesAppReadyOnMainThread: callssendReadyToJsand assertsappReadyarrives on the main thread withretainUntilConsumed: true.- Without the fix:
XCTAssertEqual failed: ("Optional(false)") is not equal to ("Optional(true)") - With the fix: passes.
- Without the fix:
- Full iOS suite,
xcodebuild test -scheme CapgoCapacitorUpdater -destination 'platform=iOS Simulator,name=iPhone 17'(iOS 26.5, Xcode 26.6): 202 tests, 0 failures (main: 201, 0 failures). - SwiftLint 0.65.1 with
@ionic/swiftlint-config: same warning count before and after on both changed files, none new.
Not Tested
- Android is unchanged and not affected.
- Didn't run
bun run verify/bun run fmt(no Bun on this machine); ranxcodebuildtests and SwiftLint directly instead. - Not exercised in the example app or Maestro flows.
This PR was prepared with an AI agent (Claude Code); I reviewed the change and the test results.
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.
Summary by CodeRabbit
- Bug Fixes
- Improved delivery of app-ready notifications, including when events are emitted outside the main thread.
- App-ready notifications are now retained until consumed, helping ensure the application receives readiness updates.