@react-native-firebase/messaging 25.1.0 → 26.0.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 (71) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/RNFBMessaging.podspec +20 -6
  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 +82 -12
  32. package/ios/RNFBMessaging/RNFBMessaging+NSNotificationCenter.m +14 -4
  33. package/ios/RNFBMessaging/RNFBMessaging+UNUserNotificationCenter.m +31 -28
  34. package/ios/RNFBMessaging/RNFBMessagingModule.h +3 -2
  35. package/ios/RNFBMessaging/RNFBMessagingModule.mm +500 -0
  36. package/ios/RNFBMessaging.xcodeproj/project.pbxproj +8 -8
  37. package/ios/generated/RCTAppDependencyProvider.h +25 -0
  38. package/ios/generated/RCTAppDependencyProvider.mm +55 -0
  39. package/ios/generated/RCTModulesConformingToProtocolsProvider.h +18 -0
  40. package/ios/generated/RCTModulesConformingToProtocolsProvider.mm +33 -0
  41. package/ios/generated/RCTThirdPartyComponentsProvider.h +16 -0
  42. package/ios/generated/RCTThirdPartyComponentsProvider.mm +23 -0
  43. package/ios/generated/RNFBMessagingTurboModules/RNFBMessagingTurboModules-generated.mm +184 -0
  44. package/ios/generated/RNFBMessagingTurboModules/RNFBMessagingTurboModules.h +222 -0
  45. package/ios/generated/RNFBMessagingTurboModulesJSI-generated.cpp +161 -0
  46. package/ios/generated/RNFBMessagingTurboModulesJSI.h +356 -0
  47. package/ios/generated/ReactAppDependencyProvider.podspec +34 -0
  48. package/lib/index.ts +821 -15
  49. package/lib/statics.ts +1 -1
  50. package/lib/types/internal.ts +59 -0
  51. package/lib/types/messaging.ts +7 -72
  52. package/lib/version.ts +1 -1
  53. package/package.json +30 -5
  54. package/plugin/build/app.plugin.d.ts +2 -0
  55. package/plugin/build/app.plugin.js +6 -0
  56. package/plugin/src/app.plugin.ts +3 -0
  57. package/plugin/tsconfig.tsbuildinfo +1 -1
  58. package/react-native.config.js +10 -0
  59. package/specs/NativeRNFBTurboMessaging.ts +46 -0
  60. package/typedoc.json +2 -19
  61. package/dist/module/modular.js +0 -396
  62. package/dist/module/modular.js.map +0 -1
  63. package/dist/module/namespaced.js +0 -415
  64. package/dist/module/namespaced.js.map +0 -1
  65. package/dist/typescript/lib/modular.d.ts +0 -255
  66. package/dist/typescript/lib/modular.d.ts.map +0 -1
  67. package/dist/typescript/lib/namespaced.d.ts +0 -12
  68. package/dist/typescript/lib/namespaced.d.ts.map +0 -1
  69. package/ios/RNFBMessaging/RNFBMessagingModule.m +0 -439
  70. package/lib/modular.ts +0 -455
  71. package/lib/namespaced.ts +0 -593
@@ -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,500 @@
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
+ #import <Firebase/Firebase.h>
19
+ #import <RNFBApp/RNFBSharedUtils.h>
20
+ #import <React/RCTConvert.h>
21
+ #import <React/RCTUtils.h>
22
+
23
+ #import "RNFBMessaging+AppDelegate.h"
24
+ #import "RNFBMessaging+NSNotificationCenter.h"
25
+ #import "RNFBMessaging+UNUserNotificationCenter.h"
26
+ #import "RNFBMessagingModule.h"
27
+ #import "RNFBMessagingSerializer.h"
28
+ #import "RNFBMessagingTurboModules.h"
29
+
30
+ @interface RNFBMessagingModule () <NativeRNFBTurboMessagingSpec>
31
+ @end
32
+
33
+ @implementation RNFBMessagingModule
34
+ #pragma mark -
35
+ #pragma mark Module Setup
36
+
37
+ RCT_EXPORT_MODULE(NativeRNFBTurboMessaging)
38
+
39
+ + (BOOL)requiresMainQueueSetup {
40
+ return NO;
41
+ }
42
+
43
+ - (id)init {
44
+ self = [super init];
45
+ return self;
46
+ }
47
+
48
+ + (NSDictionary *)addCustomPropsToUserProps:(NSDictionary *_Nullable)userProps
49
+ withLaunchOptions:(NSDictionary *_Nullable)launchOptions {
50
+ NSMutableDictionary *appProperties =
51
+ userProps != nil ? [userProps mutableCopy] : [NSMutableDictionary dictionary];
52
+ appProperties[@"isHeadless"] = @([RCTConvert BOOL:@(NO)]);
53
+
54
+ if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
55
+ if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
56
+ appProperties[@"isHeadless"] = @([RCTConvert BOOL:@(YES)]);
57
+ }
58
+ }
59
+
60
+ return [NSDictionary dictionaryWithDictionary:appProperties];
61
+ }
62
+
63
+ - (NSDictionary *)messagingConstantsDictionary {
64
+ NSMutableDictionary *constants = [NSMutableDictionary new];
65
+ constants[@"isAutoInitEnabled"] =
66
+ @([RCTConvert BOOL:@([FIRMessaging messaging].autoInitEnabled)]);
67
+ #if TARGET_IPHONE_SIMULATOR
68
+ constants[@"isRegisteredForRemoteNotifications"] = @NO;
69
+ #else
70
+ constants[@"isRegisteredForRemoteNotifications"] = @(
71
+ [RCTConvert BOOL:@([[UIApplication sharedApplication] isRegisteredForRemoteNotifications])]);
72
+ #endif
73
+ constants[@"isDeliveryMetricsExportToBigQueryEnabled"] =
74
+ @([RCTConvert BOOL:@(_isDeliveryMetricsExportToBigQueryEnabled)]);
75
+ return constants;
76
+ }
77
+
78
+ - (facebook::react::ModuleConstants<JS::NativeRNFBTurboMessaging::Constants::Builder>)
79
+ constantsToExport {
80
+ return [_RCTTypedModuleConstants newWithUnsafeDictionary:[self messagingConstantsDictionary]];
81
+ }
82
+
83
+ - (facebook::react::ModuleConstants<JS::NativeRNFBTurboMessaging::Constants::Builder>)getConstants {
84
+ return [self constantsToExport];
85
+ }
86
+
87
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
88
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
89
+ return std::make_shared<facebook::react::NativeRNFBTurboMessagingSpecJSI>(params);
90
+ }
91
+
92
+ #pragma mark -
93
+ #pragma mark Firebase Messaging Methods
94
+
95
+ - (void)getInitialNotification:(RCTPromiseResolveBlock)resolve
96
+ reject:(RCTPromiseRejectBlock)reject {
97
+ resolve([[RNFBMessagingUNUserNotificationCenter sharedInstance] getInitialNotification]);
98
+ }
99
+
100
+ - (void)getDidOpenSettingsForNotification:(RCTPromiseResolveBlock)resolve
101
+ reject:(RCTPromiseRejectBlock)reject {
102
+ resolve(
103
+ [[RNFBMessagingUNUserNotificationCenter sharedInstance] getDidOpenSettingsForNotification]);
104
+ }
105
+
106
+ - (void)setAutoInitEnabled:(BOOL)enabled
107
+ resolve:(RCTPromiseResolveBlock)resolve
108
+ reject:(RCTPromiseRejectBlock)reject {
109
+ @try {
110
+ [FIRMessaging messaging].autoInitEnabled = enabled;
111
+ } @catch (NSException *exception) {
112
+ return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
113
+ }
114
+
115
+ return resolve([NSNull null]);
116
+ }
117
+
118
+ - (void)signalBackgroundMessageHandlerSet {
119
+ DLog(@"signalBackgroundMessageHandlerSet called");
120
+ @try {
121
+ [[RNFBMessagingAppDelegate sharedInstance] signalBackgroundMessageHandlerSet];
122
+ } @catch (NSException *exception) {
123
+ ELog(@"signalBackgroundMessageHandlerSet failed");
124
+ }
125
+ }
126
+
127
+ - (void)getToken:(NSString *)appName
128
+ senderId:(NSString *)senderId
129
+ resolve:(RCTPromiseResolveBlock)resolve
130
+ reject:(RCTPromiseRejectBlock)reject {
131
+ if ([UIApplication sharedApplication].isRegisteredForRemoteNotifications == NO) {
132
+ [RNFBSharedUtils
133
+ rejectPromiseWithUserInfo:reject
134
+ userInfo:(NSMutableDictionary *)@{
135
+ @"code" : @"unregistered",
136
+ @"message" : @"You must be registered for remote "
137
+ @"messages before calling "
138
+ @"getToken, see "
139
+ @"registerDeviceForRemoteMessages(getMessaging()).",
140
+ }];
141
+ return;
142
+ }
143
+
144
+ NSData *apnsToken = [FIRMessaging messaging].APNSToken;
145
+ if (apnsToken == nil) {
146
+ DLog(@"RNFBMessaging getToken - no APNS token is available. Firebase "
147
+ @"requires an APNS token to "
148
+ @"vend an FCM token in firebase-ios-sdk 10.4.0 and higher. See "
149
+ @"documentation on "
150
+ @"setAPNSToken and getAPNSToken.")
151
+ }
152
+
153
+ [[FIRMessaging messaging]
154
+ retrieveFCMTokenForSenderID:senderId
155
+ completion:^(NSString *_Nullable token, NSError *_Nullable error) {
156
+ if (error) {
157
+ [RNFBSharedUtils rejectPromiseWithNSError:reject error:error];
158
+ } else {
159
+ resolve(token);
160
+ }
161
+ }];
162
+ }
163
+
164
+ - (void)deleteToken:(NSString *)appName
165
+ senderId:(NSString *)senderId
166
+ resolve:(RCTPromiseResolveBlock)resolve
167
+ reject:(RCTPromiseRejectBlock)reject {
168
+ [[FIRMessaging messaging] deleteFCMTokenForSenderID:senderId
169
+ completion:^(NSError *_Nullable error) {
170
+ if (error) {
171
+ [RNFBSharedUtils rejectPromiseWithNSError:reject
172
+ error:error];
173
+ } else {
174
+ resolve([NSNull null]);
175
+ }
176
+ }];
177
+ }
178
+
179
+ - (void)getAPNSToken:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
180
+ NSData *apnsToken = [FIRMessaging messaging].APNSToken;
181
+ if (apnsToken) {
182
+ resolve([RNFBMessagingSerializer APNSTokenFromNSData:apnsToken]);
183
+ } else {
184
+ #if TARGET_IPHONE_SIMULATOR
185
+ #if !TARGET_CPU_ARM64
186
+ DLog(@"RNFBMessaging getAPNSToken - Simulator without APNS support "
187
+ @"detected, with no token "
188
+ @"set. Use setAPNSToken with an arbitrary string if needed for "
189
+ @"testing.") resolve([NSNull null]);
190
+ return;
191
+ #endif
192
+ DLog(@"RNFBMessaging getAPNSToken - ARM64 Simulator detected, but no APNS "
193
+ @"token available. "
194
+ @"APNS token may be possible. macOS13+ / iOS16+ / M1 mac required for "
195
+ @"assumption to be "
196
+ @"valid. "
197
+ @"Use setAPNSToken in testing if needed.");
198
+ #endif
199
+ if ([UIApplication sharedApplication].isRegisteredForRemoteNotifications == NO) {
200
+ [RNFBSharedUtils
201
+ rejectPromiseWithUserInfo:reject
202
+ userInfo:(NSMutableDictionary *)@{
203
+ @"code" : @"unregistered",
204
+ @"message" : @"You must be registered for remote "
205
+ @"messages before "
206
+ @"calling getAPNSToken, see "
207
+ @"registerDeviceForRemoteMessages(getMessaging()).",
208
+ }];
209
+ return;
210
+ }
211
+ resolve([NSNull null]);
212
+ }
213
+ }
214
+
215
+ - (void)setAPNSToken:(NSString *)token
216
+ type:(NSString *)type
217
+ resolve:(RCTPromiseResolveBlock)resolve
218
+ reject:(RCTPromiseRejectBlock)reject {
219
+ FIRMessagingAPNSTokenType tokenType = FIRMessagingAPNSTokenTypeUnknown;
220
+ if (type != nil && [@"prod" isEqualToString:type]) {
221
+ tokenType = FIRMessagingAPNSTokenTypeProd;
222
+ } else if (type != nil && [@"sandbox" isEqualToString:type]) {
223
+ tokenType = FIRMessagingAPNSTokenTypeSandbox;
224
+ }
225
+
226
+ [[FIRMessaging messaging] setAPNSToken:[RNFBMessagingSerializer APNSTokenDataFromNSString:token]
227
+ type:tokenType];
228
+ resolve([NSNull null]);
229
+ }
230
+
231
+ - (void)getIsHeadless:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
232
+ RNFBMessagingNSNotificationCenter *notifCenter =
233
+ [RNFBMessagingNSNotificationCenter sharedInstance];
234
+
235
+ return resolve(@([RCTConvert BOOL:@(notifCenter.isHeadless)]));
236
+ }
237
+
238
+ - (void)completeNotificationProcessing {
239
+ dispatch_async(dispatch_get_main_queue(), ^{
240
+ RNFBMessagingAppDelegate *appDelegate = [RNFBMessagingAppDelegate sharedInstance];
241
+ if (appDelegate.completionHandler) {
242
+ appDelegate.completionHandler(UIBackgroundFetchResultNewData);
243
+ appDelegate.completionHandler = nil;
244
+ }
245
+ if (appDelegate.backgroundTaskId != UIBackgroundTaskInvalid) {
246
+ [[UIApplication sharedApplication] endBackgroundTask:appDelegate.backgroundTaskId];
247
+ appDelegate.backgroundTaskId = UIBackgroundTaskInvalid;
248
+ }
249
+ });
250
+ }
251
+
252
+ - (void)requestPermission:(JS::NativeRNFBTurboMessaging::IOSPermissions &)permissions
253
+ resolve:(RCTPromiseResolveBlock)resolve
254
+ reject:(RCTPromiseRejectBlock)reject {
255
+ if (RCTRunningInAppExtension()) {
256
+ [RNFBSharedUtils rejectPromiseWithUserInfo:reject
257
+ userInfo:[@{
258
+ @"code" : @"unavailable-in-extension",
259
+ @"message" : @"requestPermission can not be called "
260
+ @"in App Extensions"
261
+ } mutableCopy]];
262
+ return;
263
+ }
264
+
265
+ UNAuthorizationOptions options = UNAuthorizationOptionNone;
266
+
267
+ if (permissions.alert().has_value() && permissions.alert().value()) {
268
+ options |= UNAuthorizationOptionAlert;
269
+ }
270
+
271
+ if (permissions.announcement().has_value() && permissions.announcement().value()) {
272
+ options |= UNAuthorizationOptionAnnouncement;
273
+ }
274
+
275
+ if (permissions.badge().has_value() && permissions.badge().value()) {
276
+ options |= UNAuthorizationOptionBadge;
277
+ }
278
+
279
+ if (permissions.sound().has_value() && permissions.sound().value()) {
280
+ options |= UNAuthorizationOptionSound;
281
+ }
282
+
283
+ if (permissions.criticalAlert().has_value() && permissions.criticalAlert().value()) {
284
+ options |= UNAuthorizationOptionCriticalAlert;
285
+ }
286
+
287
+ if (permissions.provisional().has_value() && permissions.provisional().value()) {
288
+ options |= UNAuthorizationOptionProvisional;
289
+ }
290
+
291
+ if (permissions.carPlay().has_value() && permissions.carPlay().value()) {
292
+ options |= UNAuthorizationOptionCarPlay;
293
+ }
294
+
295
+ if (permissions.providesAppNotificationSettings().has_value() &&
296
+ permissions.providesAppNotificationSettings().value()) {
297
+ options |= UNAuthorizationOptionProvidesAppNotificationSettings;
298
+ }
299
+
300
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
301
+ [center requestAuthorizationWithOptions:options
302
+ completionHandler:^(BOOL granted, NSError *_Nullable error) {
303
+ if (error) {
304
+ [RNFBSharedUtils rejectPromiseWithNSError:reject error:error];
305
+ } else {
306
+ #if TARGET_IPHONE_SIMULATOR && TARGET_CPU_ARM64
307
+ // Intentional: do not call UIKit registerForRemoteNotifications on
308
+ // ARM64 Simulator — it can wedge main indefinitely. See
309
+ // registerForRemoteNotifications:reject: for the same policy.
310
+ DLog(@"RNFBMessaging requestPermission - ARM64 Simulator detected, "
311
+ @"skipping UIKit registerForRemoteNotifications.");
312
+ #else
313
+ dispatch_async(dispatch_get_main_queue(), ^{
314
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
315
+ });
316
+ #endif
317
+ [self hasPermission:resolve reject:reject];
318
+ }
319
+ }];
320
+ }
321
+
322
+ - (void)registerForRemoteNotifications:(RCTPromiseResolveBlock)resolve
323
+ reject:(RCTPromiseRejectBlock)reject {
324
+ // Schedule the timeout BEFORE any UIKit work. UIKit getters / registerForRemoteNotifications
325
+ // must run on main; if main is already stalled (ARM64 Simulator APNs), scheduling the timer
326
+ // first guarantees the JS promise cannot hang forever. Reject from a global queue so the
327
+ // timer itself does not depend on main. Overlapping calls supersede the prior promise
328
+ // (registration-superseded) and bump a generation so stale timeouts cannot settle a newer call.
329
+ NSUInteger generation =
330
+ [[RNFBMessagingAppDelegate sharedInstance] beginRegisterPromiseResolve:resolve
331
+ andPromiseReject:reject];
332
+
333
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)),
334
+ dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
335
+ RCTPromiseResolveBlock pendingResolve = nil;
336
+ RCTPromiseRejectBlock pendingReject = nil;
337
+ if (![[RNFBMessagingAppDelegate sharedInstance]
338
+ claimPendingRegisterPromiseGeneration:generation
339
+ resolve:&pendingResolve
340
+ reject:&pendingReject]) {
341
+ DLog(@"RNFBMessaging registerForRemoteNotifications timeout: already settled "
342
+ @"(generation %lu).",
343
+ (unsigned long)generation);
344
+ return;
345
+ }
346
+ DLog(@"RNFBMessaging registerForRemoteNotifications timed out; rejecting.");
347
+ if (pendingReject != nil) {
348
+ [RNFBSharedUtils
349
+ rejectPromiseWithUserInfo:pendingReject
350
+ userInfo:[@{
351
+ @"code" : @"registration-timeout",
352
+ @"message" : @"registerDeviceForRemoteMessages "
353
+ @"timed out waiting for APNs "
354
+ @"device registration. The system "
355
+ @"did not respond — possibly "
356
+ @"missing notification permission, "
357
+ @"or the main thread is blocked "
358
+ @"(common on ARM64 Simulator)."
359
+ } mutableCopy]];
360
+ }
361
+ });
362
+
363
+ dispatch_async(dispatch_get_main_queue(), ^{
364
+ #if TARGET_IPHONE_SIMULATOR
365
+ #if !TARGET_CPU_ARM64
366
+ // Intel simulator: APNs unsupported — settle immediately after poking UIKit.
367
+ RCTPromiseResolveBlock pendingResolve = nil;
368
+ RCTPromiseRejectBlock pendingReject = nil;
369
+ if ([[RNFBMessagingAppDelegate sharedInstance]
370
+ claimPendingRegisterPromiseGeneration:generation
371
+ resolve:&pendingResolve
372
+ reject:&pendingReject] &&
373
+ pendingResolve != nil) {
374
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
375
+ pendingResolve(@([RCTConvert BOOL:@(YES)]));
376
+ }
377
+ return;
378
+ #else
379
+ // ARM64 Simulator (intentional product policy): do NOT call UIKit
380
+ // registerForRemoteNotifications. Calling it can wedge the main thread so
381
+ // the JS bridge cannot observe the global-queue registration-timeout reject
382
+ // even though the timer fires. Leave the promise pending; the 10s
383
+ // global-queue timeout rejects with registration-timeout. Use a physical
384
+ // device for real APNs tokens. Documented in docs/messaging and OKF.
385
+ DLog(@"RNFBMessaging registerForRemoteNotifications ARM64 Simulator "
386
+ @"detected — skipping UIKit registerForRemoteNotifications; "
387
+ @"global-queue timeout will reject with registration-timeout.");
388
+ return;
389
+ #endif
390
+ #endif
391
+ if ([UIApplication sharedApplication].isRegisteredForRemoteNotifications == YES) {
392
+ DLog(@"RNFBMessaging registerForRemoteNotifications - already registered.");
393
+ RCTPromiseResolveBlock pendingResolve = nil;
394
+ RCTPromiseRejectBlock pendingReject = nil;
395
+ if ([[RNFBMessagingAppDelegate sharedInstance]
396
+ claimPendingRegisterPromiseGeneration:generation
397
+ resolve:&pendingResolve
398
+ reject:&pendingReject] &&
399
+ pendingResolve != nil) {
400
+ pendingResolve(@([RCTConvert BOOL:@(YES)]));
401
+ }
402
+ return;
403
+ }
404
+
405
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
406
+ });
407
+ }
408
+
409
+ - (void)unregisterForRemoteNotifications:(RCTPromiseResolveBlock)resolve
410
+ reject:(RCTPromiseRejectBlock)reject {
411
+ [[UIApplication sharedApplication] unregisterForRemoteNotifications];
412
+ resolve(nil);
413
+ }
414
+
415
+ - (void)hasPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
416
+ [[UNUserNotificationCenter currentNotificationCenter]
417
+ getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *_Nonnull settings) {
418
+ NSNumber *authorizedStatus = @-1;
419
+ if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) {
420
+ authorizedStatus = @-1;
421
+ } else if (settings.authorizationStatus == UNAuthorizationStatusDenied) {
422
+ authorizedStatus = @0;
423
+ } else if (settings.authorizationStatus == UNAuthorizationStatusAuthorized) {
424
+ authorizedStatus = @1;
425
+ } else if (settings.authorizationStatus == UNAuthorizationStatusProvisional) {
426
+ authorizedStatus = @2;
427
+ }
428
+
429
+ if (@available(iOS 14.0, macCatalyst 14.0, *)) {
430
+ if (settings.authorizationStatus == UNAuthorizationStatusEphemeral) {
431
+ authorizedStatus = @3;
432
+ }
433
+ }
434
+
435
+ resolve(authorizedStatus);
436
+ }];
437
+ }
438
+
439
+ - (void)sendMessage:(NSDictionary *)remoteMessageMap
440
+ resolve:(RCTPromiseResolveBlock)resolve
441
+ reject:(RCTPromiseRejectBlock)reject {
442
+ [RNFBSharedUtils rejectPromiseWithUserInfo:reject
443
+ userInfo:[@{
444
+ @"code" : @"unimplemented",
445
+ @"message" : @"sendMessage is only supported on "
446
+ @"Android devices."
447
+ } mutableCopy]];
448
+ }
449
+
450
+ - (void)subscribeToTopic:(NSString *)topic
451
+ resolve:(RCTPromiseResolveBlock)resolve
452
+ reject:(RCTPromiseRejectBlock)reject {
453
+ [[FIRMessaging messaging] subscribeToTopic:topic
454
+ completion:^(NSError *error) {
455
+ if (error) {
456
+ [RNFBSharedUtils rejectPromiseWithNSError:reject error:error];
457
+ } else {
458
+ resolve(nil);
459
+ }
460
+ }];
461
+ }
462
+
463
+ - (void)unsubscribeFromTopic:(NSString *)topic
464
+ resolve:(RCTPromiseResolveBlock)resolve
465
+ reject:(RCTPromiseRejectBlock)reject {
466
+ [[FIRMessaging messaging] unsubscribeFromTopic:topic
467
+ completion:^(NSError *error) {
468
+ if (error) {
469
+ [RNFBSharedUtils rejectPromiseWithNSError:reject
470
+ error:error];
471
+ } else {
472
+ resolve(nil);
473
+ }
474
+ }];
475
+ }
476
+
477
+ - (void)setDeliveryMetricsExportToBigQuery:(BOOL)enabled
478
+ resolve:(RCTPromiseResolveBlock)resolve
479
+ reject:(RCTPromiseRejectBlock)reject {
480
+ @try {
481
+ _isDeliveryMetricsExportToBigQueryEnabled = enabled;
482
+ } @catch (NSException *exception) {
483
+ return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
484
+ }
485
+
486
+ return resolve([NSNull null]);
487
+ }
488
+
489
+ - (void)setNotificationDelegationEnabled:(BOOL)enabled
490
+ resolve:(RCTPromiseResolveBlock)resolve
491
+ reject:(RCTPromiseRejectBlock)reject {
492
+ resolve([NSNull null]);
493
+ }
494
+
495
+ - (void)isNotificationDelegationEnabled:(RCTPromiseResolveBlock)resolve
496
+ reject:(RCTPromiseRejectBlock)reject {
497
+ resolve(@NO);
498
+ }
499
+
500
+ @end