@react-native-firebase/messaging 25.1.0 → 26.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/RNFBMessaging.podspec +27 -8
  3. package/android/build.gradle +24 -0
  4. package/android/src/main/java/io/invertase/firebase/messaging/{ReactNativeFirebaseMessagingModule.java → NativeRNFBTurboMessaging.java} +143 -81
  5. package/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingPackage.java +1 -1
  6. package/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingReceiver.java +16 -0
  7. package/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java +50 -11
  8. package/android/src/main/java/io/invertase/firebase/messaging/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboMessagingSpec.java +151 -0
  9. package/android/src/main/java/io/invertase/firebase/messaging/generated/jni/CMakeLists.txt +36 -0
  10. package/android/src/main/java/io/invertase/firebase/messaging/generated/jni/RNFBMessagingTurboModules-generated.cpp +152 -0
  11. package/android/src/main/java/io/invertase/firebase/messaging/generated/jni/RNFBMessagingTurboModules.h +31 -0
  12. package/android/src/main/java/io/invertase/firebase/messaging/generated/jni/react/renderer/components/RNFBMessagingTurboModules/RNFBMessagingTurboModulesJSI-generated.cpp +161 -0
  13. package/android/src/main/java/io/invertase/firebase/messaging/generated/jni/react/renderer/components/RNFBMessagingTurboModules/RNFBMessagingTurboModulesJSI.h +356 -0
  14. package/app.plugin.js +1 -1
  15. package/dist/module/index.js +622 -7
  16. package/dist/module/index.js.map +1 -1
  17. package/dist/module/statics.js +1 -1
  18. package/dist/module/types/internal.js +4 -0
  19. package/dist/module/types/internal.js.map +1 -0
  20. package/dist/module/types/messaging.js +0 -15
  21. package/dist/module/types/messaging.js.map +1 -1
  22. package/dist/module/version.js +1 -1
  23. package/dist/typescript/lib/index.d.ts +182 -5
  24. package/dist/typescript/lib/index.d.ts.map +1 -1
  25. package/dist/typescript/lib/types/internal.d.ts +41 -0
  26. package/dist/typescript/lib/types/internal.d.ts.map +1 -0
  27. package/dist/typescript/lib/types/messaging.d.ts +7 -55
  28. package/dist/typescript/lib/types/messaging.d.ts.map +1 -1
  29. package/dist/typescript/lib/version.d.ts +1 -1
  30. package/ios/RNFBMessaging/RNFBMessaging+AppDelegate.h +24 -2
  31. package/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m +90 -12
  32. package/ios/RNFBMessaging/RNFBMessaging+FIRMessagingDelegate.h +8 -0
  33. package/ios/RNFBMessaging/RNFBMessaging+NSNotificationCenter.m +22 -4
  34. package/ios/RNFBMessaging/RNFBMessaging+UNUserNotificationCenter.m +31 -28
  35. package/ios/RNFBMessaging/RNFBMessagingModule.h +3 -2
  36. package/ios/RNFBMessaging/RNFBMessagingModule.mm +508 -0
  37. package/ios/RNFBMessaging/RNFBMessagingSerializer.h +8 -0
  38. package/ios/RNFBMessaging.xcodeproj/project.pbxproj +8 -8
  39. package/ios/generated/RCTAppDependencyProvider.h +25 -0
  40. package/ios/generated/RCTAppDependencyProvider.mm +55 -0
  41. package/ios/generated/RCTModulesConformingToProtocolsProvider.h +18 -0
  42. package/ios/generated/RCTModulesConformingToProtocolsProvider.mm +33 -0
  43. package/ios/generated/RCTThirdPartyComponentsProvider.h +16 -0
  44. package/ios/generated/RCTThirdPartyComponentsProvider.mm +23 -0
  45. package/ios/generated/RNFBMessagingTurboModules/RNFBMessagingTurboModules-generated.mm +184 -0
  46. package/ios/generated/RNFBMessagingTurboModules/RNFBMessagingTurboModules.h +222 -0
  47. package/ios/generated/RNFBMessagingTurboModulesJSI-generated.cpp +161 -0
  48. package/ios/generated/RNFBMessagingTurboModulesJSI.h +356 -0
  49. package/ios/generated/ReactAppDependencyProvider.podspec +34 -0
  50. package/lib/index.ts +821 -15
  51. package/lib/statics.ts +1 -1
  52. package/lib/types/internal.ts +59 -0
  53. package/lib/types/messaging.ts +7 -72
  54. package/lib/version.ts +1 -1
  55. package/package.json +30 -5
  56. package/plugin/build/android/setupFirebaseNotifationIcon.d.ts +3 -2
  57. package/plugin/build/android/setupFirebaseNotifationIcon.js +14 -7
  58. package/plugin/build/app.plugin.d.ts +2 -0
  59. package/plugin/build/app.plugin.js +6 -0
  60. package/plugin/build/index.d.ts +2 -1
  61. package/plugin/build/index.js +4 -4
  62. package/plugin/build/pluginConfig.d.ts +7 -0
  63. package/plugin/build/pluginConfig.js +2 -0
  64. package/plugin/src/android/setupFirebaseNotifationIcon.ts +19 -6
  65. package/plugin/src/app.plugin.ts +3 -0
  66. package/plugin/src/index.ts +5 -4
  67. package/plugin/src/pluginConfig.ts +8 -0
  68. package/plugin/tsconfig.tsbuildinfo +1 -1
  69. package/react-native.config.js +10 -0
  70. package/specs/NativeRNFBTurboMessaging.ts +46 -0
  71. package/typedoc.json +2 -19
  72. package/dist/module/modular.js +0 -396
  73. package/dist/module/modular.js.map +0 -1
  74. package/dist/module/namespaced.js +0 -415
  75. package/dist/module/namespaced.js.map +0 -1
  76. package/dist/typescript/lib/modular.d.ts +0 -255
  77. package/dist/typescript/lib/modular.d.ts.map +0 -1
  78. package/dist/typescript/lib/namespaced.d.ts +0 -12
  79. package/dist/typescript/lib/namespaced.d.ts.map +0 -1
  80. package/ios/RNFBMessaging/RNFBMessagingModule.m +0 -439
  81. package/lib/modular.ts +0 -455
  82. package/lib/namespaced.ts +0 -593
@@ -14,9 +14,18 @@
14
14
  * limitations under the License.
15
15
  *
16
16
  */
17
+ #if __has_include(<Firebase/Firebase.h>)
17
18
  #import <Firebase/Firebase.h>
19
+ #elif __has_include(<FirebaseMessaging/FirebaseMessaging.h>)
20
+ #import <FirebaseCore/FirebaseCore.h>
21
+ #import <FirebaseMessaging/FirebaseMessaging.h>
22
+ #else
23
+ @import FirebaseCore;
24
+ @import FirebaseMessaging;
25
+ #endif
18
26
  #import <RNFBApp/RNFBJSON.h>
19
27
  #import <RNFBApp/RNFBRCTEventEmitter.h>
28
+ #import <RNFBApp/RNFBSharedUtils.h>
20
29
  #import <React/RCTConvert.h>
21
30
  #import <React/RCTRootView.h>
22
31
 
@@ -87,12 +96,18 @@
87
96
  (RCTRootView *)[UIApplication sharedApplication].delegate.window.rootViewController.view;
88
97
  }
89
98
 
90
- // #if !(TARGET_IPHONE_SIMULATOR)
91
99
  if ([[RNFBJSON shared] getBooleanValue:@"messaging_ios_auto_register_for_remote_messages"
92
100
  defaultValue:YES]) {
101
+ #if TARGET_IPHONE_SIMULATOR && TARGET_CPU_ARM64
102
+ // Intentional: do not call UIKit registerForRemoteNotifications on ARM64 Simulator —
103
+ // it can wedge the main thread indefinitely. Physical devices and Intel Simulator still
104
+ // poke UIKit. See okf-bundle/packages/messaging/ios-apns-simulator-registration.md.
105
+ DLog(@"RNFBMessaging auto-register - ARM64 Simulator detected, "
106
+ @"skipping UIKit registerForRemoteNotifications.");
107
+ #else
93
108
  [[UIApplication sharedApplication] registerForRemoteNotifications];
109
+ #endif
94
110
  }
95
- // #endif
96
111
 
97
112
  if (notification.userInfo[UIApplicationLaunchOptionsRemoteNotificationKey]) {
98
113
  if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
@@ -108,7 +123,6 @@
108
123
  }
109
124
  }
110
125
 
111
- // #if !(TARGET_IPHONE_SIMULATOR)
112
126
  // When an app launches in the background (BG mode) and is launched with the notification
113
127
  // launch option the app delegate method
114
128
  // application:didReceiveRemoteNotification:fetchCompletionHandler: will not get called unless
@@ -117,8 +131,12 @@
117
131
  // resulting in the app being terminated. called irregardless of
118
132
  // `messaging_ios_auto_register_for_remote_messages` as this is most likely an app launching
119
133
  // as a result of a remote notification - so has been registered previously
134
+ #if TARGET_IPHONE_SIMULATOR && TARGET_CPU_ARM64
135
+ DLog(@"RNFBMessaging background launch register - ARM64 Simulator detected, "
136
+ @"skipping UIKit registerForRemoteNotifications.");
137
+ #else
120
138
  [[UIApplication sharedApplication] registerForRemoteNotifications];
121
- // #endif
139
+ #endif
122
140
  } else {
123
141
  isHeadless = NO;
124
142
  if (rctRootView != nil) {
@@ -106,24 +106,13 @@ struct {
106
106
  // if list or banner is true, ignore `alert` property
107
107
  if (banner || list) {
108
108
  if (banner) {
109
- if (@available(iOS 14, *)) {
110
- presentationOptions |= UNNotificationPresentationOptionBanner;
111
- } else {
112
- // for iOS 13 we need to set `alert`
113
- presentationOptions |= UNNotificationPresentationOptionAlert;
114
- }
109
+ presentationOptions |= UNNotificationPresentationOptionBanner;
115
110
  }
116
111
 
117
112
  if (list) {
118
- if (@available(iOS 14, *)) {
119
- presentationOptions |= UNNotificationPresentationOptionList;
120
- } else {
121
- // for iOS 13 we need to set `alert`
122
- presentationOptions |= UNNotificationPresentationOptionAlert;
123
- }
113
+ presentationOptions |= UNNotificationPresentationOptionList;
124
114
  }
125
115
  } else if (alert) {
126
- // TODO: Remove `alert` once iOS 14 becomes the minimum deployment target
127
116
  presentationOptions |= UNNotificationPresentationOptionAlert;
128
117
  }
129
118
 
@@ -139,22 +128,32 @@ struct {
139
128
  }
140
129
  }
141
130
 
142
- if (_originalDelegate != nil && originalDelegateRespondsTo.willPresentNotification) {
143
- [_originalDelegate userNotificationCenter:center
144
- willPresentNotification:notification
145
- withCompletionHandler:completionHandler];
131
+ // completionHandler must be invoked exactly once. If the original delegate
132
+ // implements willPresentNotification, defer to it entirely - it owns the
133
+ // completionHandler contract (and may call it asynchronously). Only fall
134
+ // back to our own presentationOptions when there is no original delegate
135
+ // to hand off to.
136
+ //
137
+ // _originalDelegate is weak, so it is captured into a strong local first -
138
+ // otherwise it could be deallocated between the nil-check and the message
139
+ // send (turning the send into a no-op) and completionHandler would never
140
+ // be called at all.
141
+ id<UNUserNotificationCenterDelegate> strongOriginalDelegate = _originalDelegate;
142
+ if (strongOriginalDelegate != nil && originalDelegateRespondsTo.willPresentNotification) {
143
+ [strongOriginalDelegate userNotificationCenter:center
144
+ willPresentNotification:notification
145
+ withCompletionHandler:completionHandler];
146
+ } else {
147
+ completionHandler(presentationOptions);
146
148
  }
147
-
148
- // Don't consume completionHandler before the _originalDelegate has been
149
- // processed
150
- completionHandler(presentationOptions);
151
149
  }
152
150
 
153
151
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center
154
152
  didReceiveNotificationResponse:(UNNotificationResponse *)response
155
153
  withCompletionHandler:(void (^)(void))completionHandler {
156
154
  NSDictionary *remoteNotification = response.notification.request.content.userInfo;
157
- if (remoteNotification[@"gcm.message_id"]) {
155
+ if ([[response actionIdentifier] isEqualToString:UNNotificationDefaultActionIdentifier] &&
156
+ remoteNotification[@"gcm.message_id"]) {
158
157
  NSDictionary *notificationDict =
159
158
  [RNFBMessagingSerializer remoteMessageUserInfoToDict:remoteNotification];
160
159
  [[RNFBRCTEventEmitter shared] sendEventWithName:@"messaging_notification_opened"
@@ -162,10 +161,13 @@ struct {
162
161
  _initialNotification = notificationDict;
163
162
  }
164
163
 
165
- if (_originalDelegate != nil && originalDelegateRespondsTo.didReceiveNotificationResponse) {
166
- [_originalDelegate userNotificationCenter:center
167
- didReceiveNotificationResponse:response
168
- withCompletionHandler:completionHandler];
164
+ // _originalDelegate is weak — capture strong local before nil-check + message
165
+ // (same race as willPresent; otherwise completionHandler may never run).
166
+ id<UNUserNotificationCenterDelegate> strongOriginalDelegate = _originalDelegate;
167
+ if (strongOriginalDelegate != nil && originalDelegateRespondsTo.didReceiveNotificationResponse) {
168
+ [strongOriginalDelegate userNotificationCenter:center
169
+ didReceiveNotificationResponse:response
170
+ withCompletionHandler:completionHandler];
169
171
  } else {
170
172
  completionHandler();
171
173
  }
@@ -173,8 +175,9 @@ struct {
173
175
 
174
176
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center
175
177
  openSettingsForNotification:(nullable UNNotification *)notification {
176
- if (_originalDelegate != nil && originalDelegateRespondsTo.openSettingsForNotification) {
177
- [_originalDelegate userNotificationCenter:center openSettingsForNotification:notification];
178
+ id<UNUserNotificationCenterDelegate> strongOriginalDelegate = _originalDelegate;
179
+ if (strongOriginalDelegate != nil && originalDelegateRespondsTo.openSettingsForNotification) {
180
+ [strongOriginalDelegate userNotificationCenter:center openSettingsForNotification:notification];
178
181
  } else {
179
182
  NSDictionary *notificationDict = [RNFBMessagingSerializer notificationToDict:notification];
180
183
  [[RNFBRCTEventEmitter shared] sendEventWithName:@"messaging_settings_for_notification_opened"
@@ -16,9 +16,10 @@
16
16
  */
17
17
 
18
18
  #import <Foundation/Foundation.h>
19
- #import <React/RCTBridgeModule.h>
20
19
 
21
- @interface RNFBMessagingModule : NSObject <RCTBridgeModule>
20
+ // Public / AppDelegate-safe: no C++/codegen imports. TurboModule conformance lives in .mm.
21
+ // Kept public for documented isHeadless integration via addCustomPropsToUserProps.
22
+ @interface RNFBMessagingModule : NSObject
22
23
  + (NSDictionary *_Nonnull)addCustomPropsToUserProps:(NSDictionary *_Nullable)userProps
23
24
  withLaunchOptions:(NSDictionary *_Nullable)launchOptions;
24
25
  @property BOOL isDeliveryMetricsExportToBigQueryEnabled;
@@ -0,0 +1,508 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ #if __has_include(<Firebase/Firebase.h>)
19
+ #import <Firebase/Firebase.h>
20
+ #elif __has_include(<FirebaseMessaging/FirebaseMessaging.h>)
21
+ #import <FirebaseCore/FirebaseCore.h>
22
+ #import <FirebaseMessaging/FirebaseMessaging.h>
23
+ #else
24
+ @import FirebaseCore;
25
+ @import FirebaseMessaging;
26
+ #endif
27
+ #import <RNFBApp/RNFBSharedUtils.h>
28
+ #import <React/RCTConvert.h>
29
+ #import <React/RCTUtils.h>
30
+
31
+ #import "RNFBMessaging+AppDelegate.h"
32
+ #import "RNFBMessaging+NSNotificationCenter.h"
33
+ #import "RNFBMessaging+UNUserNotificationCenter.h"
34
+ #import "RNFBMessagingModule.h"
35
+ #import "RNFBMessagingSerializer.h"
36
+ #import "RNFBMessagingTurboModules.h"
37
+
38
+ @interface RNFBMessagingModule () <NativeRNFBTurboMessagingSpec>
39
+ @end
40
+
41
+ @implementation RNFBMessagingModule
42
+ #pragma mark -
43
+ #pragma mark Module Setup
44
+
45
+ RCT_EXPORT_MODULE(NativeRNFBTurboMessaging)
46
+
47
+ + (BOOL)requiresMainQueueSetup {
48
+ return NO;
49
+ }
50
+
51
+ - (id)init {
52
+ self = [super init];
53
+ return self;
54
+ }
55
+
56
+ + (NSDictionary *)addCustomPropsToUserProps:(NSDictionary *_Nullable)userProps
57
+ withLaunchOptions:(NSDictionary *_Nullable)launchOptions {
58
+ NSMutableDictionary *appProperties =
59
+ userProps != nil ? [userProps mutableCopy] : [NSMutableDictionary dictionary];
60
+ appProperties[@"isHeadless"] = @([RCTConvert BOOL:@(NO)]);
61
+
62
+ if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
63
+ if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
64
+ appProperties[@"isHeadless"] = @([RCTConvert BOOL:@(YES)]);
65
+ }
66
+ }
67
+
68
+ return [NSDictionary dictionaryWithDictionary:appProperties];
69
+ }
70
+
71
+ - (NSDictionary *)messagingConstantsDictionary {
72
+ NSMutableDictionary *constants = [NSMutableDictionary new];
73
+ constants[@"isAutoInitEnabled"] =
74
+ @([RCTConvert BOOL:@([FIRMessaging messaging].autoInitEnabled)]);
75
+ #if TARGET_IPHONE_SIMULATOR
76
+ constants[@"isRegisteredForRemoteNotifications"] = @NO;
77
+ #else
78
+ constants[@"isRegisteredForRemoteNotifications"] = @(
79
+ [RCTConvert BOOL:@([[UIApplication sharedApplication] isRegisteredForRemoteNotifications])]);
80
+ #endif
81
+ constants[@"isDeliveryMetricsExportToBigQueryEnabled"] =
82
+ @([RCTConvert BOOL:@(_isDeliveryMetricsExportToBigQueryEnabled)]);
83
+ return constants;
84
+ }
85
+
86
+ - (facebook::react::ModuleConstants<JS::NativeRNFBTurboMessaging::Constants::Builder>)
87
+ constantsToExport {
88
+ return [_RCTTypedModuleConstants newWithUnsafeDictionary:[self messagingConstantsDictionary]];
89
+ }
90
+
91
+ - (facebook::react::ModuleConstants<JS::NativeRNFBTurboMessaging::Constants::Builder>)getConstants {
92
+ return [self constantsToExport];
93
+ }
94
+
95
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
96
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
97
+ return std::make_shared<facebook::react::NativeRNFBTurboMessagingSpecJSI>(params);
98
+ }
99
+
100
+ #pragma mark -
101
+ #pragma mark Firebase Messaging Methods
102
+
103
+ - (void)getInitialNotification:(RCTPromiseResolveBlock)resolve
104
+ reject:(RCTPromiseRejectBlock)reject {
105
+ resolve([[RNFBMessagingUNUserNotificationCenter sharedInstance] getInitialNotification]);
106
+ }
107
+
108
+ - (void)getDidOpenSettingsForNotification:(RCTPromiseResolveBlock)resolve
109
+ reject:(RCTPromiseRejectBlock)reject {
110
+ resolve(
111
+ [[RNFBMessagingUNUserNotificationCenter sharedInstance] getDidOpenSettingsForNotification]);
112
+ }
113
+
114
+ - (void)setAutoInitEnabled:(BOOL)enabled
115
+ resolve:(RCTPromiseResolveBlock)resolve
116
+ reject:(RCTPromiseRejectBlock)reject {
117
+ @try {
118
+ [FIRMessaging messaging].autoInitEnabled = enabled;
119
+ } @catch (NSException *exception) {
120
+ return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
121
+ }
122
+
123
+ return resolve([NSNull null]);
124
+ }
125
+
126
+ - (void)signalBackgroundMessageHandlerSet {
127
+ DLog(@"signalBackgroundMessageHandlerSet called");
128
+ @try {
129
+ [[RNFBMessagingAppDelegate sharedInstance] signalBackgroundMessageHandlerSet];
130
+ } @catch (NSException *exception) {
131
+ ELog(@"signalBackgroundMessageHandlerSet failed");
132
+ }
133
+ }
134
+
135
+ - (void)getToken:(NSString *)appName
136
+ senderId:(NSString *)senderId
137
+ resolve:(RCTPromiseResolveBlock)resolve
138
+ reject:(RCTPromiseRejectBlock)reject {
139
+ if ([UIApplication sharedApplication].isRegisteredForRemoteNotifications == NO) {
140
+ [RNFBSharedUtils
141
+ rejectPromiseWithUserInfo:reject
142
+ userInfo:(NSMutableDictionary *)@{
143
+ @"code" : @"unregistered",
144
+ @"message" : @"You must be registered for remote "
145
+ @"messages before calling "
146
+ @"getToken, see "
147
+ @"registerDeviceForRemoteMessages(getMessaging()).",
148
+ }];
149
+ return;
150
+ }
151
+
152
+ NSData *apnsToken = [FIRMessaging messaging].APNSToken;
153
+ if (apnsToken == nil) {
154
+ DLog(@"RNFBMessaging getToken - no APNS token is available. Firebase "
155
+ @"requires an APNS token to "
156
+ @"vend an FCM token in firebase-ios-sdk 10.4.0 and higher. See "
157
+ @"documentation on "
158
+ @"setAPNSToken and getAPNSToken.")
159
+ }
160
+
161
+ [[FIRMessaging messaging]
162
+ retrieveFCMTokenForSenderID:senderId
163
+ completion:^(NSString *_Nullable token, NSError *_Nullable error) {
164
+ if (error) {
165
+ [RNFBSharedUtils rejectPromiseWithNSError:reject error:error];
166
+ } else {
167
+ resolve(token);
168
+ }
169
+ }];
170
+ }
171
+
172
+ - (void)deleteToken:(NSString *)appName
173
+ senderId:(NSString *)senderId
174
+ resolve:(RCTPromiseResolveBlock)resolve
175
+ reject:(RCTPromiseRejectBlock)reject {
176
+ [[FIRMessaging messaging] deleteFCMTokenForSenderID:senderId
177
+ completion:^(NSError *_Nullable error) {
178
+ if (error) {
179
+ [RNFBSharedUtils rejectPromiseWithNSError:reject
180
+ error:error];
181
+ } else {
182
+ resolve([NSNull null]);
183
+ }
184
+ }];
185
+ }
186
+
187
+ - (void)getAPNSToken:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
188
+ NSData *apnsToken = [FIRMessaging messaging].APNSToken;
189
+ if (apnsToken) {
190
+ resolve([RNFBMessagingSerializer APNSTokenFromNSData:apnsToken]);
191
+ } else {
192
+ #if TARGET_IPHONE_SIMULATOR
193
+ #if !TARGET_CPU_ARM64
194
+ DLog(@"RNFBMessaging getAPNSToken - Simulator without APNS support "
195
+ @"detected, with no token "
196
+ @"set. Use setAPNSToken with an arbitrary string if needed for "
197
+ @"testing.") resolve([NSNull null]);
198
+ return;
199
+ #endif
200
+ DLog(@"RNFBMessaging getAPNSToken - ARM64 Simulator detected, but no APNS "
201
+ @"token available. "
202
+ @"APNS token may be possible. macOS13+ / iOS16+ / M1 mac required for "
203
+ @"assumption to be "
204
+ @"valid. "
205
+ @"Use setAPNSToken in testing if needed.");
206
+ #endif
207
+ if ([UIApplication sharedApplication].isRegisteredForRemoteNotifications == NO) {
208
+ [RNFBSharedUtils
209
+ rejectPromiseWithUserInfo:reject
210
+ userInfo:(NSMutableDictionary *)@{
211
+ @"code" : @"unregistered",
212
+ @"message" : @"You must be registered for remote "
213
+ @"messages before "
214
+ @"calling getAPNSToken, see "
215
+ @"registerDeviceForRemoteMessages(getMessaging()).",
216
+ }];
217
+ return;
218
+ }
219
+ resolve([NSNull null]);
220
+ }
221
+ }
222
+
223
+ - (void)setAPNSToken:(NSString *)token
224
+ type:(NSString *)type
225
+ resolve:(RCTPromiseResolveBlock)resolve
226
+ reject:(RCTPromiseRejectBlock)reject {
227
+ FIRMessagingAPNSTokenType tokenType = FIRMessagingAPNSTokenTypeUnknown;
228
+ if (type != nil && [@"prod" isEqualToString:type]) {
229
+ tokenType = FIRMessagingAPNSTokenTypeProd;
230
+ } else if (type != nil && [@"sandbox" isEqualToString:type]) {
231
+ tokenType = FIRMessagingAPNSTokenTypeSandbox;
232
+ }
233
+
234
+ [[FIRMessaging messaging] setAPNSToken:[RNFBMessagingSerializer APNSTokenDataFromNSString:token]
235
+ type:tokenType];
236
+ resolve([NSNull null]);
237
+ }
238
+
239
+ - (void)getIsHeadless:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
240
+ RNFBMessagingNSNotificationCenter *notifCenter =
241
+ [RNFBMessagingNSNotificationCenter sharedInstance];
242
+
243
+ return resolve(@([RCTConvert BOOL:@(notifCenter.isHeadless)]));
244
+ }
245
+
246
+ - (void)completeNotificationProcessing {
247
+ dispatch_async(dispatch_get_main_queue(), ^{
248
+ RNFBMessagingAppDelegate *appDelegate = [RNFBMessagingAppDelegate sharedInstance];
249
+ if (appDelegate.completionHandler) {
250
+ appDelegate.completionHandler(UIBackgroundFetchResultNewData);
251
+ appDelegate.completionHandler = nil;
252
+ }
253
+ if (appDelegate.backgroundTaskId != UIBackgroundTaskInvalid) {
254
+ [[UIApplication sharedApplication] endBackgroundTask:appDelegate.backgroundTaskId];
255
+ appDelegate.backgroundTaskId = UIBackgroundTaskInvalid;
256
+ }
257
+ });
258
+ }
259
+
260
+ - (void)requestPermission:(JS::NativeRNFBTurboMessaging::IOSPermissions &)permissions
261
+ resolve:(RCTPromiseResolveBlock)resolve
262
+ reject:(RCTPromiseRejectBlock)reject {
263
+ if (RCTRunningInAppExtension()) {
264
+ [RNFBSharedUtils rejectPromiseWithUserInfo:reject
265
+ userInfo:[@{
266
+ @"code" : @"unavailable-in-extension",
267
+ @"message" : @"requestPermission can not be called "
268
+ @"in App Extensions"
269
+ } mutableCopy]];
270
+ return;
271
+ }
272
+
273
+ UNAuthorizationOptions options = UNAuthorizationOptionNone;
274
+
275
+ if (permissions.alert().has_value() && permissions.alert().value()) {
276
+ options |= UNAuthorizationOptionAlert;
277
+ }
278
+
279
+ if (permissions.announcement().has_value() && permissions.announcement().value()) {
280
+ options |= UNAuthorizationOptionAnnouncement;
281
+ }
282
+
283
+ if (permissions.badge().has_value() && permissions.badge().value()) {
284
+ options |= UNAuthorizationOptionBadge;
285
+ }
286
+
287
+ if (permissions.sound().has_value() && permissions.sound().value()) {
288
+ options |= UNAuthorizationOptionSound;
289
+ }
290
+
291
+ if (permissions.criticalAlert().has_value() && permissions.criticalAlert().value()) {
292
+ options |= UNAuthorizationOptionCriticalAlert;
293
+ }
294
+
295
+ if (permissions.provisional().has_value() && permissions.provisional().value()) {
296
+ options |= UNAuthorizationOptionProvisional;
297
+ }
298
+
299
+ if (permissions.carPlay().has_value() && permissions.carPlay().value()) {
300
+ options |= UNAuthorizationOptionCarPlay;
301
+ }
302
+
303
+ if (permissions.providesAppNotificationSettings().has_value() &&
304
+ permissions.providesAppNotificationSettings().value()) {
305
+ options |= UNAuthorizationOptionProvidesAppNotificationSettings;
306
+ }
307
+
308
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
309
+ [center requestAuthorizationWithOptions:options
310
+ completionHandler:^(BOOL granted, NSError *_Nullable error) {
311
+ if (error) {
312
+ [RNFBSharedUtils rejectPromiseWithNSError:reject error:error];
313
+ } else {
314
+ #if TARGET_IPHONE_SIMULATOR && TARGET_CPU_ARM64
315
+ // Intentional: do not call UIKit registerForRemoteNotifications on
316
+ // ARM64 Simulator — it can wedge main indefinitely. See
317
+ // registerForRemoteNotifications:reject: for the same policy.
318
+ DLog(@"RNFBMessaging requestPermission - ARM64 Simulator detected, "
319
+ @"skipping UIKit registerForRemoteNotifications.");
320
+ #else
321
+ dispatch_async(dispatch_get_main_queue(), ^{
322
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
323
+ });
324
+ #endif
325
+ [self hasPermission:resolve reject:reject];
326
+ }
327
+ }];
328
+ }
329
+
330
+ - (void)registerForRemoteNotifications:(RCTPromiseResolveBlock)resolve
331
+ reject:(RCTPromiseRejectBlock)reject {
332
+ // Schedule the timeout BEFORE any UIKit work. UIKit getters / registerForRemoteNotifications
333
+ // must run on main; if main is already stalled (ARM64 Simulator APNs), scheduling the timer
334
+ // first guarantees the JS promise cannot hang forever. Reject from a global queue so the
335
+ // timer itself does not depend on main. Overlapping calls supersede the prior promise
336
+ // (registration-superseded) and bump a generation so stale timeouts cannot settle a newer call.
337
+ NSUInteger generation =
338
+ [[RNFBMessagingAppDelegate sharedInstance] beginRegisterPromiseResolve:resolve
339
+ andPromiseReject:reject];
340
+
341
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)),
342
+ dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
343
+ RCTPromiseResolveBlock pendingResolve = nil;
344
+ RCTPromiseRejectBlock pendingReject = nil;
345
+ if (![[RNFBMessagingAppDelegate sharedInstance]
346
+ claimPendingRegisterPromiseGeneration:generation
347
+ resolve:&pendingResolve
348
+ reject:&pendingReject]) {
349
+ DLog(@"RNFBMessaging registerForRemoteNotifications timeout: already settled "
350
+ @"(generation %lu).",
351
+ (unsigned long)generation);
352
+ return;
353
+ }
354
+ DLog(@"RNFBMessaging registerForRemoteNotifications timed out; rejecting.");
355
+ if (pendingReject != nil) {
356
+ [RNFBSharedUtils
357
+ rejectPromiseWithUserInfo:pendingReject
358
+ userInfo:[@{
359
+ @"code" : @"registration-timeout",
360
+ @"message" : @"registerDeviceForRemoteMessages "
361
+ @"timed out waiting for APNs "
362
+ @"device registration. The system "
363
+ @"did not respond — possibly "
364
+ @"missing notification permission, "
365
+ @"or the main thread is blocked "
366
+ @"(common on ARM64 Simulator)."
367
+ } mutableCopy]];
368
+ }
369
+ });
370
+
371
+ dispatch_async(dispatch_get_main_queue(), ^{
372
+ #if TARGET_IPHONE_SIMULATOR
373
+ #if !TARGET_CPU_ARM64
374
+ // Intel simulator: APNs unsupported — settle immediately after poking UIKit.
375
+ RCTPromiseResolveBlock pendingResolve = nil;
376
+ RCTPromiseRejectBlock pendingReject = nil;
377
+ if ([[RNFBMessagingAppDelegate sharedInstance]
378
+ claimPendingRegisterPromiseGeneration:generation
379
+ resolve:&pendingResolve
380
+ reject:&pendingReject] &&
381
+ pendingResolve != nil) {
382
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
383
+ pendingResolve(@([RCTConvert BOOL:@(YES)]));
384
+ }
385
+ return;
386
+ #else
387
+ // ARM64 Simulator (intentional product policy): do NOT call UIKit
388
+ // registerForRemoteNotifications. Calling it can wedge the main thread so
389
+ // the JS bridge cannot observe the global-queue registration-timeout reject
390
+ // even though the timer fires. Leave the promise pending; the 10s
391
+ // global-queue timeout rejects with registration-timeout. Use a physical
392
+ // device for real APNs tokens. Documented in docs/messaging and OKF.
393
+ DLog(@"RNFBMessaging registerForRemoteNotifications ARM64 Simulator "
394
+ @"detected — skipping UIKit registerForRemoteNotifications; "
395
+ @"global-queue timeout will reject with registration-timeout.");
396
+ return;
397
+ #endif
398
+ #endif
399
+ if ([UIApplication sharedApplication].isRegisteredForRemoteNotifications == YES) {
400
+ DLog(@"RNFBMessaging registerForRemoteNotifications - already registered.");
401
+ RCTPromiseResolveBlock pendingResolve = nil;
402
+ RCTPromiseRejectBlock pendingReject = nil;
403
+ if ([[RNFBMessagingAppDelegate sharedInstance]
404
+ claimPendingRegisterPromiseGeneration:generation
405
+ resolve:&pendingResolve
406
+ reject:&pendingReject] &&
407
+ pendingResolve != nil) {
408
+ pendingResolve(@([RCTConvert BOOL:@(YES)]));
409
+ }
410
+ return;
411
+ }
412
+
413
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
414
+ });
415
+ }
416
+
417
+ - (void)unregisterForRemoteNotifications:(RCTPromiseResolveBlock)resolve
418
+ reject:(RCTPromiseRejectBlock)reject {
419
+ [[UIApplication sharedApplication] unregisterForRemoteNotifications];
420
+ resolve(nil);
421
+ }
422
+
423
+ - (void)hasPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
424
+ [[UNUserNotificationCenter currentNotificationCenter]
425
+ getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *_Nonnull settings) {
426
+ NSNumber *authorizedStatus = @-1;
427
+ if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) {
428
+ authorizedStatus = @-1;
429
+ } else if (settings.authorizationStatus == UNAuthorizationStatusDenied) {
430
+ authorizedStatus = @0;
431
+ } else if (settings.authorizationStatus == UNAuthorizationStatusAuthorized) {
432
+ authorizedStatus = @1;
433
+ } else if (settings.authorizationStatus == UNAuthorizationStatusProvisional) {
434
+ authorizedStatus = @2;
435
+ }
436
+
437
+ if (@available(iOS 14.0, macCatalyst 14.0, *)) {
438
+ if (settings.authorizationStatus == UNAuthorizationStatusEphemeral) {
439
+ authorizedStatus = @3;
440
+ }
441
+ }
442
+
443
+ resolve(authorizedStatus);
444
+ }];
445
+ }
446
+
447
+ - (void)sendMessage:(NSDictionary *)remoteMessageMap
448
+ resolve:(RCTPromiseResolveBlock)resolve
449
+ reject:(RCTPromiseRejectBlock)reject {
450
+ [RNFBSharedUtils rejectPromiseWithUserInfo:reject
451
+ userInfo:[@{
452
+ @"code" : @"unimplemented",
453
+ @"message" : @"sendMessage is only supported on "
454
+ @"Android devices."
455
+ } mutableCopy]];
456
+ }
457
+
458
+ - (void)subscribeToTopic:(NSString *)topic
459
+ resolve:(RCTPromiseResolveBlock)resolve
460
+ reject:(RCTPromiseRejectBlock)reject {
461
+ [[FIRMessaging messaging] subscribeToTopic:topic
462
+ completion:^(NSError *error) {
463
+ if (error) {
464
+ [RNFBSharedUtils rejectPromiseWithNSError:reject error:error];
465
+ } else {
466
+ resolve(nil);
467
+ }
468
+ }];
469
+ }
470
+
471
+ - (void)unsubscribeFromTopic:(NSString *)topic
472
+ resolve:(RCTPromiseResolveBlock)resolve
473
+ reject:(RCTPromiseRejectBlock)reject {
474
+ [[FIRMessaging messaging] unsubscribeFromTopic:topic
475
+ completion:^(NSError *error) {
476
+ if (error) {
477
+ [RNFBSharedUtils rejectPromiseWithNSError:reject
478
+ error:error];
479
+ } else {
480
+ resolve(nil);
481
+ }
482
+ }];
483
+ }
484
+
485
+ - (void)setDeliveryMetricsExportToBigQuery:(BOOL)enabled
486
+ resolve:(RCTPromiseResolveBlock)resolve
487
+ reject:(RCTPromiseRejectBlock)reject {
488
+ @try {
489
+ _isDeliveryMetricsExportToBigQueryEnabled = enabled;
490
+ } @catch (NSException *exception) {
491
+ return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
492
+ }
493
+
494
+ return resolve([NSNull null]);
495
+ }
496
+
497
+ - (void)setNotificationDelegationEnabled:(BOOL)enabled
498
+ resolve:(RCTPromiseResolveBlock)resolve
499
+ reject:(RCTPromiseRejectBlock)reject {
500
+ resolve([NSNull null]);
501
+ }
502
+
503
+ - (void)isNotificationDelegationEnabled:(RCTPromiseResolveBlock)resolve
504
+ reject:(RCTPromiseRejectBlock)reject {
505
+ resolve(@NO);
506
+ }
507
+
508
+ @end