From 5b88d81b2f0b8a8fe49b22f9e2fb77263bb6a081 Mon Sep 17 00:00:00 2001 From: floydkim Date: Thu, 25 Dec 2025 22:00:18 +0900 Subject: [PATCH] fix(iOS): ensure download progress events reach JS on New Architecture --- ios/CodePush/CodePush.m | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index b8f625456..4483aed9a 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -32,6 +32,7 @@ @implementation CodePush { long long _latestExpectedContentLength; long long _latestReceivedConentLength; BOOL _didUpdateProgress; + NSTimeInterval _lastProgressEventTime; BOOL _allowed; BOOL _restartInProgress; @@ -336,6 +337,18 @@ - (void)dispatchDownloadProgressEvent { }]; } +- (void)dispatchThrottledDownloadProgressEventWithForce:(BOOL)force +{ + static const NSTimeInterval interval = 0.05; // 50 ms throttle + NSTimeInterval now = CFAbsoluteTimeGetCurrent(); + if (!force && _lastProgressEventTime > 0 && (now - _lastProgressEventTime) < interval) { + return; + } + + _lastProgressEventTime = now; + [self dispatchDownloadProgressEvent]; +} + /* * This method ensures that the app was packaged with a JS bundle * file, and if not, it throws the appropriate exception. @@ -377,6 +390,7 @@ - (instancetype)init _allowed = YES; _restartInProgress = NO; _restartQueue = [NSMutableArray arrayWithCapacity:1]; + _lastProgressEventTime = 0; self = [super init]; if (self) { @@ -723,6 +737,7 @@ -(void)loadBundleOnTick:(NSTimer *)timer { // progress events every frame if the progress is updated. _didUpdateProgress = NO; self.paused = NO; + _lastProgressEventTime = 0; } NSString * publicKey = [[CodePushConfig current] publicKey]; @@ -738,13 +753,19 @@ -(void)loadBundleOnTick:(NSTimer *)timer { _latestExpectedContentLength = expectedContentLength; _latestReceivedConentLength = receivedContentLength; _didUpdateProgress = YES; + // Fabric/TurboModules don't hook RCTFrameUpdateObserver, so _pauseCallback + // stays nil there and we must emit progress events directly. + // On the legacy bridge _pauseCallback is set, and didUpdateFrame handles dispatch. + if (!_pauseCallback) { + [self dispatchThrottledDownloadProgressEventWithForce:NO]; + } // If the download is completed, stop observing frame // updates and synchronously send the last event. if (expectedContentLength == receivedContentLength) { _didUpdateProgress = NO; self.paused = YES; - [self dispatchDownloadProgressEvent]; + [self dispatchThrottledDownloadProgressEventWithForce:YES]; } } // The download completed @@ -1115,7 +1136,7 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update return; } - [self dispatchDownloadProgressEvent]; + [self dispatchThrottledDownloadProgressEventWithForce:YES]; _didUpdateProgress = NO; }