Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(messaging, ios): Background Completion Handler from JS #8128

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN
@property _Nullable RCTPromiseResolveBlock registerPromiseResolver;
@property(nonatomic, strong) NSCondition *conditionBackgroundMessageHandlerSet;
@property(nonatomic) BOOL backgroundMessageHandlerSet;
@property(nonatomic, copy) void (^completionHandler)(UIBackgroundFetchResult);
@property(nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskId;

+ (_Nonnull instancetype)sharedInstance;

Expand Down
32 changes: 18 additions & 14 deletions packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -156,30 +156,34 @@
DLog(@"didReceiveRemoteNotification gcm.message_id was present %@", userInfo);

if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
// Store the completion handler to call later when the JS code finishes
RNFBMessagingAppDelegate *sharedInstance = [RNFBMessagingAppDelegate sharedInstance];
sharedInstance.completionHandler = completionHandler;

// If app is in background state, register background task to guarantee async queues aren't
// frozen.
UIBackgroundTaskIdentifier __block backgroundTaskId =
[application beginBackgroundTaskWithExpirationHandler:^{
if (backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
// TODO add support in a later version for calling completion handler directly from JS when
// user JS code complete
sharedInstance.backgroundTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
if (backgroundTaskId != UIBackgroundTaskInvalid) {

Check failure on line 166 in packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m

View workflow job for this annotation

GitHub Actions / iOS

use of undeclared identifier 'backgroundTaskId'; did you mean '_backgroundTaskId'?
Beat-YT marked this conversation as resolved.
Show resolved Hide resolved
[application endBackgroundTask:backgroundTaskId];

Check failure on line 167 in packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m

View workflow job for this annotation

GitHub Actions / iOS

use of undeclared identifier 'backgroundTaskId'; did you mean '_backgroundTaskId'?
backgroundTaskId = UIBackgroundTaskInvalid;

Check failure on line 168 in packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m

View workflow job for this annotation

GitHub Actions / iOS

use of undeclared identifier 'backgroundTaskId'; did you mean '_backgroundTaskId'?
}
}];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(25 * NSEC_PER_SEC)),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
completionHandler(UIBackgroundFetchResultNewData);
if (sharedInstance.completionHandler) {
sharedInstance.completionHandler(UIBackgroundFetchResultNewData);
sharedInstance.completionHandler = nil;
}

// Stop background task after the longest timeout, async queue is okay to
// freeze again after handling period
if (backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
if (sharedInstance.backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:sharedInstance.backgroundTaskId];
sharedInstance.backgroundTaskId = UIBackgroundTaskInvalid;
}
});

RNFBMessagingAppDelegate *sharedInstance = [RNFBMessagingAppDelegate sharedInstance];
[sharedInstance.conditionBackgroundMessageHandlerSet lock];
@try {
DLog(@"didReceiveRemoteNotification sharedInstance.backgroundMessageHandlerSet = %@",
Expand Down
13 changes: 13 additions & 0 deletions packages/messaging/ios/RNFBMessaging/RNFBMessagingModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,19 @@ - (NSDictionary *)constantsToExport {
return resolve(@([RCTConvert BOOL:@(notifCenter.isHeadless)]));
}

RCT_EXPORT_METHOD(completeNotificationProcessing) {
RNFBMessagingAppDelegate *appDelegate = [RNFBMessagingAppDelegate sharedInstance];
if (appDelegate.completionHandler) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this should be on the same queue as the initial set of completionHandler / backgroundTaskId otherwise there's a tiny chance via race condition that multiple threads are working on their state at the same time

It's this above, would that work here?

                dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{`

appDelegate.completionHandler(UIBackgroundFetchResultNewData);
appDelegate.completionHandler = nil;
}

if (appDelegate.backgroundTaskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:appDelegate.backgroundTaskId];
appDelegate.backgroundTaskId = UIBackgroundTaskInvalid;
}
}

RCT_EXPORT_METHOD(requestPermission
: (NSDictionary *)permissions
: (RCTPromiseResolveBlock)resolve
Expand Down
8 changes: 7 additions & 1 deletion packages/messaging/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,13 @@
return Promise.resolve();
}

return backgroundMessageHandler(remoteMessage);
// Ensure the handler is a promise
const handlerPromise = Promise.resolve(backgroundMessageHandler(remoteMessage));
handlerPromise.finally(() => {
this.native.completeNotificationProcessing();

Check warning on line 104 in packages/messaging/lib/index.js

View check run for this annotation

Codecov / codecov/patch

packages/messaging/lib/index.js#L102-L104

Added lines #L102 - L104 were not covered by tests
});

return handlerPromise;

Check warning on line 107 in packages/messaging/lib/index.js

View check run for this annotation

Codecov / codecov/patch

packages/messaging/lib/index.js#L107

Added line #L107 was not covered by tests
});

this.emitter.addListener('messaging_settings_for_notification_opened', remoteMessage => {
Expand Down
Loading