react-native-notify-kit 10.4.4 → 10.4.6

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.
package/README.md CHANGED
@@ -499,7 +499,7 @@ This fork fixes the following bugs that were never resolved in the original Noti
499
499
 
500
500
  <!-- markdownlint-disable-line MD028 -->
501
501
 
502
- > **Note for apps requiring guaranteed exact alarms (alarm clocks, timers, calendars):**
502
+ > **Note for policy-eligible apps requiring exact alarm APIs (alarm clocks, timers, calendars):**
503
503
  > Add `<uses-permission android:name="android.permission.USE_EXACT_ALARM" />` to your app's
504
504
  > `AndroidManifest.xml`. This permission is auto-granted and not revocable, but Google Play
505
505
  > restricts its use to apps whose core function requires exact timing.
@@ -659,6 +659,13 @@ The original Notifee used WorkManager by default, which is battery-friendly but
659
659
  for time-sensitive notifications — Android may defer or drop WorkManager tasks based on
660
660
  battery optimization, Doze mode, and OEM power management.
661
661
 
662
+ This path also handles normal reboot recovery. Android clears `AlarmManager` registrations
663
+ during reboot, but NotifyKit persists local trigger notifications and re-arms them after boot.
664
+ Future one-shot `TriggerType.TIMESTAMP` triggers are supported by this recovery path; a trigger
665
+ does not need to be recurring or daily only to survive reboot, and push notifications are not
666
+ required for local scheduled trigger recovery. If exact-alarm access is missing, NotifyKit falls
667
+ back to an inexact alarm so the trigger is retained, but exact fire timing is not guaranteed.
668
+
662
669
  If you need battery-friendly scheduling where exact timing is not critical (e.g., daily digest
663
670
  notifications), you can opt out:
664
671
 
@@ -719,12 +726,14 @@ await notifee.createTriggerNotification(
719
726
  );
720
727
  ```
721
728
 
722
- **Required permissions on Android 12+.** `SET_EXACT`, `SET_EXACT_AND_ALLOW_WHILE_IDLE`, and
723
- `SET_ALARM_CLOCK` all require the `SCHEDULE_EXACT_ALARM` or `USE_EXACT_ALARM` permission.
724
- If the permission is not granted, Notifee falls back to `setAndAllowWhileIdle` (inexact)
725
- instead of crashing see the `SecurityException` handling in `NotifeeAlarmManager`.
726
- For use cases that must be exact on first install, declare `USE_EXACT_ALARM` in your manifest
727
- and consider prompting the user with `openAlarmPermissionSettings()`.
729
+ **Exact-alarm permission on Android 12+.** `SET_EXACT`, `SET_EXACT_AND_ALLOW_WHILE_IDLE`,
730
+ and `SET_ALARM_CLOCK` all require exact-alarm access through `SCHEDULE_EXACT_ALARM` or
731
+ `USE_EXACT_ALARM`. This affects exact fire timing, not trigger persistence. If permission is
732
+ not granted, Notifee falls back to `setAndAllowWhileIdle` (inexact) instead of crashing or
733
+ dropping the trigger. Apps that require exact timing should check
734
+ `getNotificationSettings().android.alarm`, explain the permission, and guide users with
735
+ `openAlarmPermissionSettings()`. `SET_ALARM_CLOCK` can be considered for alarm-clock or
736
+ timer-like use cases within Android and Google Play policy constraints.
728
737
 
729
738
  ### Timers: foreground service or `SET_ALARM_CLOCK`?
730
739
 
@@ -810,6 +819,33 @@ Default is `true` (backward compatible — Notifee handles everything, same as o
810
819
 
811
820
  ### Troubleshooting
812
821
 
822
+ #### Scheduled Android trigger notifications do not fire after reboot
823
+
824
+ Android removes `AlarmManager` registrations during reboot. NotifyKit persists local trigger
825
+ notifications and re-arms them through `app.notifee.core.RebootBroadcastReceiver` after a
826
+ normal reboot, with a cold-start `BOOT_COUNT` recovery path for devices that delay or suppress
827
+ `BOOT_COMPLETED`. If triggers do not return after reboot, check the generated app rather than
828
+ only the source manifest:
829
+
830
+ - Verify the final merged manifest contains `android.permission.RECEIVE_BOOT_COMPLETED`,
831
+ `android.permission.SCHEDULE_EXACT_ALARM`, `app.notifee.core.RebootBroadcastReceiver` with
832
+ `android.intent.action.BOOT_COMPLETED`, `app.notifee.core.NotificationAlarmReceiver`, and
833
+ `io.invertase.notifee.NotifeeInitProvider`.
834
+ - For Expo CNG/prebuild apps, these entries normally arrive from the NotifyKit library manifest
835
+ through manifest merge. A custom boot-receiver config plugin is usually unnecessary and can
836
+ break recovery if it replaces the receiver declaration or intent filters.
837
+ - Use package-specific logcat output. Do not rely only on `adb logcat -s NOTIFEE`; reboot
838
+ recovery logs use native tags such as `RebootReceiver`, `NotifeeAlarmManager`, `InitProvider`,
839
+ and `AlarmPermissionReceiver`, while Android broadcast logs include your real application ID.
840
+ - Compare `adb shell dumpsys alarm` before and after reboot, filtered by your real package name,
841
+ to confirm the pending alarm disappears during reboot and is re-armed afterward.
842
+ - Check exact-alarm permission if timing is late. Without permission, NotifyKit falls back to an
843
+ inexact alarm: the trigger is retained, but exact fire timing is not guaranteed.
844
+ - Check OEM autostart and background restrictions, especially on Xiaomi/Redmi, Oppo/Realme,
845
+ Huawei/Honor, Vivo/iQOO, and Samsung devices.
846
+ - Validate with 1-5 future one-shot `TriggerType.TIMESTAMP` triggers before scaling up to large
847
+ batches such as 50 active triggers.
848
+
813
849
  #### Small icon not showing in Android release builds (falls back to launcher icon)
814
850
 
815
851
  From 10.1.0, when the resource ID for `android.smallIcon` cannot be resolved at runtime, the library logs a warning and falls back to your app's launcher icon instead of failing the notification display. If you see your launcher icon in a notification where you expected a custom small icon, filter logcat for the `NOTIFEE` tag to find the resolution failure.
@@ -451,23 +451,27 @@ class NotificationManager {
451
451
  PendingIntent pendingIntent = null;
452
452
  int targetSdkVersion =
453
453
  ContextHolder.getApplicationContext().getApplicationInfo().targetSdkVersion;
454
+ NotificationAndroidPressActionModel pressAction = actionBundle.getPressAction();
455
+ boolean shouldCreateLaunchActivityIntent =
456
+ NotificationPendingIntent.shouldCreateLaunchActivityIntent(pressAction);
454
457
  if (targetSdkVersion >= Build.VERSION_CODES.S
455
- && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
458
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
459
+ && shouldCreateLaunchActivityIntent) {
456
460
  pendingIntent =
457
461
  NotificationPendingIntent.createIntent(
458
462
  notificationModel.getHashCode(),
459
- actionBundle.getPressAction().toBundle(),
463
+ pressAction.toBundle(),
460
464
  TYPE_ACTION_PRESS,
461
465
  new String[] {"notification", "pressAction"},
462
466
  notificationModel.toBundle(),
463
- actionBundle.getPressAction().toBundle());
467
+ pressAction.toBundle());
464
468
  } else {
465
469
  pendingIntent =
466
470
  ReceiverService.createIntent(
467
471
  ACTION_PRESS_INTENT,
468
472
  new String[] {"notification", "pressAction"},
469
473
  notificationModel.toBundle(),
470
- actionBundle.getPressAction().toBundle());
474
+ pressAction.toBundle());
471
475
  }
472
476
 
473
477
  String icon = actionBundle.getIcon();
@@ -98,9 +98,21 @@ class HeadlessTask {
98
98
 
99
99
  if (!mIsReactContextInitialized.get()) {
100
100
  createReactContextAndScheduleTask(context)
101
- } else {
102
- invokeStartTask(getReactContext(context)!!, taskConfig)
101
+ return
102
+ }
103
+
104
+ val reactContext = getReactContext(context)
105
+ if (reactContext == null) {
106
+ Log.w(HEADLESS_TASK_NAME, "ReactContext is stale, reinitializing ReactHost")
107
+ mIsReactContextInitialized.set(false)
108
+ mWillDrainTaskQueue.set(false)
109
+ mIsInitializingReactContext.set(false)
110
+ mIsHeadlessJsTaskListenerRegistered.set(false)
111
+ createReactContextAndScheduleTask(context, reactContext)
112
+ return
103
113
  }
114
+
115
+ invokeStartTask(reactContext, taskConfig)
104
116
  }
105
117
 
106
118
  @Synchronized
@@ -139,11 +151,13 @@ class HeadlessTask {
139
151
  }
140
152
  }
141
153
 
142
- private fun createReactContextAndScheduleTask(context: Context) {
143
- val reactContext = getReactContext(context)
154
+ private fun createReactContextAndScheduleTask(
155
+ context: Context,
156
+ reactContext: ReactContext? = getReactContext(context),
157
+ ) {
144
158
  if (reactContext != null && !mIsInitializingReactContext.get()) {
145
159
  mIsReactContextInitialized.set(true)
146
- drainTaskQueue(reactContext)
160
+ drainTaskQueue(context, reactContext)
147
161
  return
148
162
  }
149
163
  if (mIsInitializingReactContext.compareAndSet(false, true)) {
@@ -152,7 +166,7 @@ class HeadlessTask {
152
166
  val callback = object : ReactInstanceEventListener {
153
167
  override fun onReactContextInitialized(reactCtx: ReactContext) {
154
168
  mIsReactContextInitialized.set(true)
155
- drainTaskQueue(reactCtx)
169
+ drainTaskQueue(context, reactCtx)
156
170
  try {
157
171
  val removeMethod = reactHost!!.javaClass.getMethod(
158
172
  "removeReactInstanceEventListener",
@@ -178,10 +192,12 @@ class HeadlessTask {
178
192
  }
179
193
  }
180
194
 
181
- private fun drainTaskQueue(reactContext: ReactContext) {
195
+ private fun drainTaskQueue(context: Context, reactContext: ReactContext) {
182
196
  if (mWillDrainTaskQueue.compareAndSet(false, true)) {
183
197
  Handler(Looper.getMainLooper()).postDelayed(
184
198
  {
199
+ if (getReactContext(context) !== reactContext) return@postDelayed
200
+
185
201
  synchronized(mTaskQueue) {
186
202
  for (taskConfig in mTaskQueue) {
187
203
  invokeStartTask(reactContext, taskConfig)
@@ -0,0 +1,305 @@
1
+ package app.notifee.core;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+
19
+ import static app.notifee.core.NotificationPendingIntent.EVENT_TYPE_INTENT_KEY;
20
+ import static app.notifee.core.event.NotificationEvent.TYPE_ACTION_PRESS;
21
+ import static org.junit.Assert.assertEquals;
22
+ import static org.junit.Assert.assertNotNull;
23
+ import static org.junit.Assert.assertTrue;
24
+
25
+ import android.Manifest;
26
+ import android.app.Activity;
27
+ import android.app.Notification;
28
+ import android.app.NotificationChannel;
29
+ import android.app.PendingIntent;
30
+ import android.content.ComponentName;
31
+ import android.content.Context;
32
+ import android.content.Intent;
33
+ import android.content.pm.ActivityInfo;
34
+ import android.content.pm.ResolveInfo;
35
+ import android.os.Build;
36
+ import android.os.Bundle;
37
+ import app.notifee.core.event.MainComponentEvent;
38
+ import app.notifee.core.model.NotificationModel;
39
+ import com.google.common.util.concurrent.ListenableFuture;
40
+ import java.util.ArrayList;
41
+ import java.util.concurrent.TimeUnit;
42
+ import org.junit.After;
43
+ import org.junit.Before;
44
+ import org.junit.Test;
45
+ import org.junit.runner.RunWith;
46
+ import org.robolectric.RobolectricTestRunner;
47
+ import org.robolectric.RuntimeEnvironment;
48
+ import org.robolectric.Shadows;
49
+ import org.robolectric.annotation.Config;
50
+ import org.robolectric.shadows.ShadowNotificationManager;
51
+ import org.robolectric.shadows.ShadowPackageManager;
52
+ import org.robolectric.shadows.ShadowPendingIntent;
53
+
54
+ @RunWith(RobolectricTestRunner.class)
55
+ @Config(sdk = 34)
56
+ public class NotificationManagerActionRoutingTest {
57
+ private static final String CHANNEL_ID = "action-routing-test-channel";
58
+
59
+ private Context context;
60
+
61
+ @Before
62
+ public void setUp() {
63
+ context = RuntimeEnvironment.getApplication();
64
+ ContextHolder.setApplicationContext(context);
65
+ setTargetSdk(Build.VERSION_CODES.S);
66
+ ShadowPendingIntent.reset();
67
+ EventBus.removeStickEvent(MainComponentEvent.class);
68
+
69
+ Shadows.shadowOf(RuntimeEnvironment.getApplication())
70
+ .grantPermissions(Manifest.permission.POST_NOTIFICATIONS);
71
+ registerDefaultLaunchActivity();
72
+ createChannel();
73
+
74
+ android.app.NotificationManager notificationManager =
75
+ (android.app.NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
76
+ notificationManager.cancelAll();
77
+ }
78
+
79
+ @After
80
+ public void tearDown() {
81
+ EventBus.removeStickEvent(MainComponentEvent.class);
82
+ ShadowPendingIntent.reset();
83
+ }
84
+
85
+ @Test
86
+ public void displayNotification_android12PlusNoLaunchAction_routesToReceiverService()
87
+ throws Exception {
88
+ ShadowPendingIntent shadowPendingIntent =
89
+ actionPendingIntent(
90
+ displayNotification(
91
+ buildNotificationModel(
92
+ "android12-no-launch", buildPressAction("decline", null, null))));
93
+
94
+ assertReceiverServiceActionIntent(shadowPendingIntent, "decline", null, null);
95
+ }
96
+
97
+ @Test
98
+ public void displayNotification_android12PlusLaunchActivityAction_routesToLaunchActivity()
99
+ throws Exception {
100
+ ShadowPendingIntent shadowPendingIntent =
101
+ actionPendingIntent(
102
+ displayNotification(
103
+ buildNotificationModel(
104
+ "android12-launch-activity",
105
+ buildPressAction("answer", ExplicitLaunchActivity.class.getName(), null))));
106
+
107
+ assertLaunchActivityActionIntent(
108
+ shadowPendingIntent,
109
+ ExplicitLaunchActivity.class,
110
+ "answer",
111
+ ExplicitLaunchActivity.class.getName(),
112
+ null);
113
+ }
114
+
115
+ @Test
116
+ public void displayNotification_android12PlusMainComponentAction_routesToLaunchActivity()
117
+ throws Exception {
118
+ ShadowPendingIntent shadowPendingIntent =
119
+ actionPendingIntent(
120
+ displayNotification(
121
+ buildNotificationModel(
122
+ "android12-main-component",
123
+ buildPressAction("open-main", null, "CustomMainComponent"))));
124
+
125
+ assertLaunchActivityActionIntent(
126
+ shadowPendingIntent, DefaultLaunchActivity.class, "open-main", null, "CustomMainComponent");
127
+ }
128
+
129
+ @Test
130
+ public void displayNotification_android12PlusDefaultActionId_routesToLaunchActivity()
131
+ throws Exception {
132
+ ShadowPendingIntent shadowPendingIntent =
133
+ actionPendingIntent(
134
+ displayNotification(
135
+ buildNotificationModel(
136
+ "android12-default-action", buildPressAction("default", null, null))));
137
+
138
+ assertLaunchActivityActionIntent(
139
+ shadowPendingIntent, DefaultLaunchActivity.class, "default", null, null);
140
+ }
141
+
142
+ @Test
143
+ @Config(sdk = 30)
144
+ public void displayNotification_preAndroid12LaunchActivityAction_keepsReceiverServiceRoute()
145
+ throws Exception {
146
+ setTargetSdk(Build.VERSION_CODES.S);
147
+
148
+ ShadowPendingIntent shadowPendingIntent =
149
+ actionPendingIntent(
150
+ displayNotification(
151
+ buildNotificationModel(
152
+ "pre-android12-launch-activity",
153
+ buildPressAction("answer", ExplicitLaunchActivity.class.getName(), null))));
154
+
155
+ assertReceiverServiceActionIntent(
156
+ shadowPendingIntent, "answer", ExplicitLaunchActivity.class.getName(), null);
157
+ }
158
+
159
+ @Test
160
+ public void displayNotification_targetBelow31LaunchActivityAction_keepsReceiverServiceRoute()
161
+ throws Exception {
162
+ setTargetSdk(Build.VERSION_CODES.R);
163
+
164
+ ShadowPendingIntent shadowPendingIntent =
165
+ actionPendingIntent(
166
+ displayNotification(
167
+ buildNotificationModel(
168
+ "target-below31-launch-activity",
169
+ buildPressAction("answer", ExplicitLaunchActivity.class.getName(), null))));
170
+
171
+ assertReceiverServiceActionIntent(
172
+ shadowPendingIntent, "answer", ExplicitLaunchActivity.class.getName(), null);
173
+ }
174
+
175
+ private Notification displayNotification(NotificationModel notificationModel) throws Exception {
176
+ ListenableFuture<Void> future =
177
+ NotificationManager.displayNotification(notificationModel, null);
178
+ future.get(5, TimeUnit.SECONDS);
179
+
180
+ android.app.NotificationManager notificationManager =
181
+ (android.app.NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
182
+ ShadowNotificationManager shadowNotificationManager = Shadows.shadowOf(notificationManager);
183
+ Notification notification =
184
+ shadowNotificationManager.getNotification(notificationModel.getHashCode());
185
+ assertNotNull("notification must be posted", notification);
186
+ return notification;
187
+ }
188
+
189
+ private static ShadowPendingIntent actionPendingIntent(Notification notification) {
190
+ assertNotNull("action array must be present", notification.actions);
191
+ assertEquals("one action must be built", 1, notification.actions.length);
192
+
193
+ PendingIntent pendingIntent = notification.actions[0].actionIntent;
194
+ assertNotNull("action PendingIntent must be present", pendingIntent);
195
+ return Shadows.shadowOf(pendingIntent);
196
+ }
197
+
198
+ private static void assertReceiverServiceActionIntent(
199
+ ShadowPendingIntent shadowPendingIntent,
200
+ String id,
201
+ String launchActivity,
202
+ String mainComponent) {
203
+ assertTrue("action PendingIntent must target ReceiverService", shadowPendingIntent.isService());
204
+
205
+ Intent intent = shadowPendingIntent.getSavedIntent();
206
+ assertNotNull("service intent must be saved", intent);
207
+ assertEquals(ReceiverService.ACTION_PRESS_INTENT, intent.getAction());
208
+ assertEquals(ReceiverService.class.getName(), intent.getComponent().getClassName());
209
+ assertPressAction(intent.getBundleExtra("pressAction"), id, launchActivity, mainComponent);
210
+ }
211
+
212
+ private static void assertLaunchActivityActionIntent(
213
+ ShadowPendingIntent shadowPendingIntent,
214
+ Class<? extends Activity> launchActivityClass,
215
+ String id,
216
+ String launchActivity,
217
+ String mainComponent) {
218
+ assertTrue(
219
+ "action PendingIntent must use the launch-activity path", shadowPendingIntent.isActivity());
220
+
221
+ Intent[] intents = shadowPendingIntent.getSavedIntents();
222
+ assertNotNull("activity intent stack must be saved", intents);
223
+ assertEquals("launch path must include app launch and receiver activity", 2, intents.length);
224
+ assertEquals(launchActivityClass.getName(), intents[0].getComponent().getClassName());
225
+ assertEquals(
226
+ NotificationReceiverActivity.class.getName(), intents[1].getComponent().getClassName());
227
+ assertEquals(TYPE_ACTION_PRESS, intents[1].getIntExtra(EVENT_TYPE_INTENT_KEY, -1));
228
+ assertPressAction(intents[1].getBundleExtra("pressAction"), id, launchActivity, mainComponent);
229
+ }
230
+
231
+ private static void assertPressAction(
232
+ Bundle pressAction, String id, String launchActivity, String mainComponent) {
233
+ assertNotNull("pressAction extras must be present", pressAction);
234
+ assertEquals(id, pressAction.getString("id"));
235
+ assertEquals(launchActivity, pressAction.getString("launchActivity"));
236
+ assertEquals(mainComponent, pressAction.getString("mainComponent"));
237
+ }
238
+
239
+ private void registerDefaultLaunchActivity() {
240
+ Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
241
+ launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
242
+ launcherIntent.setPackage(context.getPackageName());
243
+
244
+ ResolveInfo resolveInfo = new ResolveInfo();
245
+ resolveInfo.activityInfo = new ActivityInfo();
246
+ resolveInfo.activityInfo.packageName = context.getPackageName();
247
+ resolveInfo.activityInfo.name = DefaultLaunchActivity.class.getName();
248
+
249
+ ShadowPackageManager shadowPackageManager = Shadows.shadowOf(context.getPackageManager());
250
+ shadowPackageManager.addActivityIfNotPresent(
251
+ new ComponentName(context.getPackageName(), DefaultLaunchActivity.class.getName()));
252
+ shadowPackageManager.addResolveInfoForIntent(launcherIntent, resolveInfo);
253
+ }
254
+
255
+ private void createChannel() {
256
+ android.app.NotificationManager notificationManager =
257
+ (android.app.NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
258
+ if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
259
+ NotificationChannel channel =
260
+ new NotificationChannel(
261
+ CHANNEL_ID, "Action routing test", android.app.NotificationManager.IMPORTANCE_LOW);
262
+ notificationManager.createNotificationChannel(channel);
263
+ }
264
+ }
265
+
266
+ private static void setTargetSdk(int targetSdk) {
267
+ RuntimeEnvironment.getApplication().getApplicationInfo().targetSdkVersion = targetSdk;
268
+ }
269
+
270
+ private static NotificationModel buildNotificationModel(String id, Bundle pressAction) {
271
+ Bundle notificationBundle = new Bundle();
272
+ notificationBundle.putString("id", id);
273
+ notificationBundle.putString("title", "Action routing " + id);
274
+ notificationBundle.putString("body", "Body " + id);
275
+
276
+ Bundle androidBundle = new Bundle();
277
+ androidBundle.putString("channelId", CHANNEL_ID);
278
+
279
+ Bundle action = new Bundle();
280
+ action.putString("title", "Action " + pressAction.getString("id"));
281
+ action.putBundle("pressAction", pressAction);
282
+ ArrayList<Bundle> actions = new ArrayList<>();
283
+ actions.add(action);
284
+ androidBundle.putParcelableArrayList("actions", actions);
285
+
286
+ notificationBundle.putBundle("android", androidBundle);
287
+ return NotificationModel.fromBundle(notificationBundle);
288
+ }
289
+
290
+ private static Bundle buildPressAction(String id, String launchActivity, String mainComponent) {
291
+ Bundle pressAction = new Bundle();
292
+ pressAction.putString("id", id);
293
+ if (launchActivity != null) {
294
+ pressAction.putString("launchActivity", launchActivity);
295
+ }
296
+ if (mainComponent != null) {
297
+ pressAction.putString("mainComponent", mainComponent);
298
+ }
299
+ return pressAction;
300
+ }
301
+
302
+ public static class DefaultLaunchActivity extends Activity {}
303
+
304
+ public static class ExplicitLaunchActivity extends Activity {}
305
+ }
@@ -8,6 +8,7 @@ import android.app.Activity;
8
8
  import android.content.Context;
9
9
  import android.content.Intent;
10
10
  import android.os.Bundle;
11
+ import androidx.core.app.RemoteInput;
11
12
  import app.notifee.core.event.InitialNotificationEvent;
12
13
  import app.notifee.core.event.MainComponentEvent;
13
14
  import app.notifee.core.event.NotificationEvent;
@@ -73,6 +74,38 @@ public class ReceiverServiceInitialNotificationTest {
73
74
  EventBus.getStickyEvent(InitialNotificationEvent.class));
74
75
  }
75
76
 
77
+ @Test
78
+ public void onActionPressIntent_withoutLaunch_preservesRemoteInputInNotificationEvent() {
79
+ Intent intent =
80
+ buildServiceIntent(
81
+ buildNotificationBundle("service-action-remote-input"),
82
+ buildPressActionBundle("reply", null, null));
83
+ Bundle remoteInputResults = new Bundle();
84
+ remoteInputResults.putCharSequence(ReceiverService.REMOTE_INPUT_RECEIVER_KEY, "Accepted");
85
+ RemoteInput.addResultsToIntent(
86
+ new RemoteInput[] {
87
+ new RemoteInput.Builder(ReceiverService.REMOTE_INPUT_RECEIVER_KEY).build()
88
+ },
89
+ intent,
90
+ remoteInputResults);
91
+
92
+ service.onStartCommand(intent, 0, 1);
93
+
94
+ assertEquals("one NotificationEvent should be posted", 1, capture.events.size());
95
+ NotificationEvent event = capture.events.get(0);
96
+ assertEquals(NotificationEvent.TYPE_ACTION_PRESS, event.getType());
97
+ assertEquals("service-action-remote-input", event.getNotification().getId());
98
+ assertNotNull("ACTION_PRESS extras should be preserved", event.getExtras());
99
+ assertPressAction(event.getExtras(), "reply", null, null);
100
+ assertEquals(
101
+ "remote input result should be preserved",
102
+ "Accepted",
103
+ event.getExtras().getString("input"));
104
+ assertNull(
105
+ "no-launch remote input ACTION_PRESS should not post InitialNotificationEvent",
106
+ EventBus.getStickyEvent(InitialNotificationEvent.class));
107
+ }
108
+
76
109
  @Test
77
110
  public void onActionPressIntent_withLaunch_postsInitialNotificationAfterPendingIntentSend() {
78
111
  Intent intent =
@@ -7,6 +7,7 @@ import static org.mockito.ArgumentMatchers.eq;
7
7
  import static org.mockito.ArgumentMatchers.same;
8
8
  import static org.mockito.Mockito.mock;
9
9
  import static org.mockito.Mockito.never;
10
+ import static org.mockito.Mockito.times;
10
11
  import static org.mockito.Mockito.verify;
11
12
  import static org.mockito.Mockito.when;
12
13
 
@@ -54,11 +55,8 @@ public class HeadlessTaskReactHostTest {
54
55
  HeadlessTask headlessTask = new HeadlessTask();
55
56
  HeadlessTask.TaskConfig taskConfig =
56
57
  new HeadlessTask.TaskConfig("test-headless-task", 60000L, params, null);
57
- ReactContext reactContext = mock(ReactContext.class);
58
58
  AppRegistry appRegistry = mock(AppRegistry.class);
59
- when(reactContext.getLifecycleState()).thenReturn(LifecycleState.BEFORE_RESUME);
60
- when(reactContext.hasActiveReactInstance()).thenReturn(true);
61
- when(reactContext.getJSModule(AppRegistry.class)).thenReturn(appRegistry);
59
+ ReactContext reactContext = createReactContext(appRegistry);
62
60
 
63
61
  WritableMap copiedParams = taskConfig.getTaskConfig().getData();
64
62
 
@@ -73,6 +71,7 @@ public class HeadlessTaskReactHostTest {
73
71
  verify(appRegistry, never())
74
72
  .startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
75
73
 
74
+ application.reactHost.currentReactContext = reactContext;
76
75
  ReactInstanceEventListener listener = application.reactHost.listeners.get(0);
77
76
  listener.onReactContextInitialized(reactContext);
78
77
 
@@ -101,6 +100,136 @@ public class HeadlessTaskReactHostTest {
101
100
  copiedParams.getInt("taskId"));
102
101
  }
103
102
 
103
+ @Test
104
+ public void
105
+ startTask_whenInitializedContextDisappears_reinitializesReactHostAndDrainsQueuedTasks() {
106
+ HeadlessTask headlessTask = new HeadlessTask();
107
+ AppRegistry firstAppRegistry = mock(AppRegistry.class);
108
+ AppRegistry secondAppRegistry = mock(AppRegistry.class);
109
+ ReactContext firstReactContext = createReactContext(firstAppRegistry);
110
+ ReactContext secondReactContext = createReactContext(secondAppRegistry);
111
+ HeadlessTask.TaskConfig firstTaskConfig =
112
+ new HeadlessTask.TaskConfig("first-headless-task", 60000L, params("first"), null);
113
+ HeadlessTask.TaskConfig secondTaskConfig =
114
+ new HeadlessTask.TaskConfig("second-headless-task", 60000L, params("second"), null);
115
+ HeadlessTask.TaskConfig thirdTaskConfig =
116
+ new HeadlessTask.TaskConfig("third-headless-task", 60000L, params("third"), null);
117
+ WritableMap firstCopiedParams = firstTaskConfig.getTaskConfig().getData();
118
+ WritableMap secondCopiedParams = secondTaskConfig.getTaskConfig().getData();
119
+ WritableMap thirdCopiedParams = thirdTaskConfig.getTaskConfig().getData();
120
+
121
+ headlessTask.startTask(application, firstTaskConfig);
122
+
123
+ assertEquals(1, application.reactHost.startCalls);
124
+ assertEquals(1, application.reactHost.listeners.size());
125
+
126
+ application.reactHost.currentReactContext = firstReactContext;
127
+ application.reactHost.listeners.get(0).onReactContextInitialized(firstReactContext);
128
+ Shadows.shadowOf(Looper.getMainLooper())
129
+ .idleFor(500, java.util.concurrent.TimeUnit.MILLISECONDS);
130
+
131
+ verify(firstAppRegistry)
132
+ .startHeadlessTask(anyInt(), eq("first-headless-task"), same(firstCopiedParams));
133
+
134
+ application.reactHost.currentReactContext = null;
135
+
136
+ headlessTask.startTask(application, secondTaskConfig);
137
+
138
+ assertEquals(
139
+ "ReactHost should be started again for stale ReactContext",
140
+ 2,
141
+ application.reactHost.startCalls);
142
+ assertEquals(1, application.reactHost.listeners.size());
143
+ verify(secondAppRegistry, never())
144
+ .startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
145
+
146
+ headlessTask.startTask(application, thirdTaskConfig);
147
+
148
+ assertEquals(
149
+ "ReactHost should not be started again while reinitialization is pending",
150
+ 2,
151
+ application.reactHost.startCalls);
152
+ verify(secondAppRegistry, never())
153
+ .startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
154
+
155
+ application.reactHost.currentReactContext = secondReactContext;
156
+ application.reactHost.listeners.get(0).onReactContextInitialized(secondReactContext);
157
+ Shadows.shadowOf(Looper.getMainLooper())
158
+ .idleFor(499, java.util.concurrent.TimeUnit.MILLISECONDS);
159
+ verify(secondAppRegistry, never())
160
+ .startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
161
+
162
+ Shadows.shadowOf(Looper.getMainLooper()).idleFor(1, java.util.concurrent.TimeUnit.MILLISECONDS);
163
+
164
+ verify(firstAppRegistry, times(1))
165
+ .startHeadlessTask(anyInt(), eq("first-headless-task"), same(firstCopiedParams));
166
+ verify(secondAppRegistry)
167
+ .startHeadlessTask(anyInt(), eq("second-headless-task"), same(secondCopiedParams));
168
+ verify(secondAppRegistry)
169
+ .startHeadlessTask(anyInt(), eq("third-headless-task"), same(thirdCopiedParams));
170
+ }
171
+
172
+ @Test
173
+ public void startTask_whenContextDisappearsBeforeInitialDrain_waitsForRecoveredReactContext() {
174
+ HeadlessTask headlessTask = new HeadlessTask();
175
+ AppRegistry staleAppRegistry = mock(AppRegistry.class);
176
+ AppRegistry recoveredAppRegistry = mock(AppRegistry.class);
177
+ ReactContext staleReactContext = createReactContext(staleAppRegistry);
178
+ ReactContext recoveredReactContext = createReactContext(recoveredAppRegistry);
179
+ HeadlessTask.TaskConfig firstTaskConfig =
180
+ new HeadlessTask.TaskConfig("pending-first-headless-task", 60000L, params("first"), null);
181
+ HeadlessTask.TaskConfig secondTaskConfig =
182
+ new HeadlessTask.TaskConfig("pending-second-headless-task", 60000L, params("second"), null);
183
+ WritableMap firstCopiedParams = firstTaskConfig.getTaskConfig().getData();
184
+ WritableMap secondCopiedParams = secondTaskConfig.getTaskConfig().getData();
185
+
186
+ headlessTask.startTask(application, firstTaskConfig);
187
+ application.reactHost.currentReactContext = staleReactContext;
188
+ application.reactHost.listeners.get(0).onReactContextInitialized(staleReactContext);
189
+
190
+ application.reactHost.currentReactContext = null;
191
+ headlessTask.startTask(application, secondTaskConfig);
192
+
193
+ assertEquals(
194
+ "ReactHost should be restarted when the pending context disappears",
195
+ 2,
196
+ application.reactHost.startCalls);
197
+ assertEquals(1, application.reactHost.listeners.size());
198
+
199
+ Shadows.shadowOf(Looper.getMainLooper())
200
+ .idleFor(500, java.util.concurrent.TimeUnit.MILLISECONDS);
201
+ verify(staleAppRegistry, never())
202
+ .startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
203
+ verify(recoveredAppRegistry, never())
204
+ .startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
205
+
206
+ application.reactHost.currentReactContext = recoveredReactContext;
207
+ application.reactHost.listeners.get(0).onReactContextInitialized(recoveredReactContext);
208
+ Shadows.shadowOf(Looper.getMainLooper())
209
+ .idleFor(500, java.util.concurrent.TimeUnit.MILLISECONDS);
210
+
211
+ verify(staleAppRegistry, never())
212
+ .startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
213
+ verify(recoveredAppRegistry)
214
+ .startHeadlessTask(anyInt(), eq("pending-first-headless-task"), same(firstCopiedParams));
215
+ verify(recoveredAppRegistry)
216
+ .startHeadlessTask(anyInt(), eq("pending-second-headless-task"), same(secondCopiedParams));
217
+ }
218
+
219
+ private static JavaOnlyMap params(String source) {
220
+ JavaOnlyMap params = new JavaOnlyMap();
221
+ params.putString("source", source);
222
+ return params;
223
+ }
224
+
225
+ private static ReactContext createReactContext(AppRegistry appRegistry) {
226
+ ReactContext reactContext = mock(ReactContext.class);
227
+ when(reactContext.getLifecycleState()).thenReturn(LifecycleState.BEFORE_RESUME);
228
+ when(reactContext.hasActiveReactInstance()).thenReturn(true);
229
+ when(reactContext.getJSModule(AppRegistry.class)).thenReturn(appRegistry);
230
+ return reactContext;
231
+ }
232
+
104
233
  public static class TestApplication extends Application {
105
234
  final TestReactHost reactHost = new TestReactHost();
106
235
 
@@ -111,10 +240,11 @@ public class HeadlessTaskReactHostTest {
111
240
 
112
241
  public static class TestReactHost {
113
242
  int startCalls;
243
+ ReactContext currentReactContext;
114
244
  final List<ReactInstanceEventListener> listeners = new ArrayList<>();
115
245
 
116
246
  public ReactContext getCurrentReactContext() {
117
- return null;
247
+ return currentReactContext;
118
248
  }
119
249
 
120
250
  public void addReactInstanceEventListener(ReactInstanceEventListener listener) {
@@ -131,6 +261,7 @@ public class HeadlessTaskReactHostTest {
131
261
 
132
262
  void reset() {
133
263
  startCalls = 0;
264
+ currentReactContext = null;
134
265
  listeners.clear();
135
266
  }
136
267
  }
@@ -5,8 +5,9 @@ export interface NativeModuleConfig {
5
5
  nativeEvents: string[];
6
6
  }
7
7
  export default class NotifeeNativeModule {
8
- private _nativeModule;
9
- private _nativeEmitter;
8
+ private readonly _config;
9
+ private _nativeModule?;
10
+ private _nativeEmitter?;
10
11
  constructor(config: NativeModuleConfig);
11
12
  get emitter(): import("react-native/Libraries/vendor/emitter/EventEmitter").default;
12
13
  get native(): Spec;
@@ -4,23 +4,46 @@
4
4
  import NotifeeJSEventEmitter from './NotifeeJSEventEmitter';
5
5
  import { NativeEventEmitter, TurboModuleRegistry } from 'react-native';
6
6
  export default class NotifeeNativeModule {
7
+ _config;
7
8
  _nativeModule;
8
9
  _nativeEmitter;
9
10
  constructor(config) {
10
- this._nativeModule = TurboModuleRegistry.getEnforcing(config.nativeModuleName);
11
- // @ts-ignore - change here needs resolution https://github.com/DefinitelyTyped/DefinitelyTyped/pull/49560/files
12
- this._nativeEmitter = new NativeEventEmitter(this.native);
13
- for (let i = 0; i < config.nativeEvents.length; i++) {
14
- const eventName = config.nativeEvents[i];
15
- this._nativeEmitter.addListener(eventName, (payload) => {
16
- this.emitter.emit(eventName, payload);
17
- });
18
- }
11
+ // Defer all native access out of the constructor. The default export of this package is a
12
+ // singleton constructed at module-evaluation time, so resolving the TurboModule here meant
13
+ // it ran the instant `react-native-notify-kit` was imported. On the New Architecture in
14
+ // bridgeless mode, importing the package early (e.g. registering `onBackgroundEvent` at the
15
+ // top of index.js, before the app root mounts) executes before TurboModules are installed,
16
+ // so `getEnforcing` throws "NotifeeApiModule could not be found" / "runtime not ready" and
17
+ // the module is unusable for the whole session. Resolving lazily on first `.native` access
18
+ // defers it until a notifee method actually runs, when the TurboModule is available.
19
+ this._config = config;
19
20
  }
20
21
  get emitter() {
21
22
  return NotifeeJSEventEmitter;
22
23
  }
23
24
  get native() {
25
+ if (!this._nativeModule || !this._nativeEmitter) {
26
+ const nativeModule = TurboModuleRegistry.getEnforcing(this._config.nativeModuleName);
27
+ // @ts-ignore - change here needs resolution https://github.com/DefinitelyTyped/DefinitelyTyped/pull/49560/files
28
+ const nativeEmitter = new NativeEventEmitter(nativeModule);
29
+ const subscriptions = [];
30
+ try {
31
+ for (let i = 0; i < this._config.nativeEvents.length; i++) {
32
+ const eventName = this._config.nativeEvents[i];
33
+ subscriptions.push(nativeEmitter.addListener(eventName, (payload) => {
34
+ this.emitter.emit(eventName, payload);
35
+ }));
36
+ }
37
+ }
38
+ catch (error) {
39
+ for (let i = 0; i < subscriptions.length; i++) {
40
+ subscriptions[i].remove();
41
+ }
42
+ throw error;
43
+ }
44
+ this._nativeModule = nativeModule;
45
+ this._nativeEmitter = nativeEmitter;
46
+ }
24
47
  return this._nativeModule;
25
48
  }
26
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NotifeeNativeModule.js","sourceRoot":"","sources":["../src/NotifeeNativeModule.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAqB,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAS1F,MAAM,CAAC,OAAO,OAAO,mBAAmB;IAC9B,aAAa,CAAO;IACpB,cAAc,CAAqB;IAE3C,YAAmB,MAA0B;QAC3C,IAAI,CAAC,aAAa,GAAG,mBAAmB,CAAC,YAAY,CAAO,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAErF,gHAAgH;QAChH,IAAI,CAAC,cAAc,GAAG,IAAI,kBAAkB,CAAC,IAAI,CAAC,MAAyC,CAAC,CAAC;QAC7F,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACpD,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACzC,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,OAAY,EAAE,EAAE;gBAC1D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACxC,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAW,OAAO;QAChB,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IAED,IAAW,MAAM;QACf,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;CACF"}
1
+ {"version":3,"file":"NotifeeNativeModule.js","sourceRoot":"","sources":["../src/NotifeeNativeModule.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAqB,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAS1F,MAAM,CAAC,OAAO,OAAO,mBAAmB;IACrB,OAAO,CAAqB;IACrC,aAAa,CAAQ;IACrB,cAAc,CAAsB;IAE5C,YAAmB,MAA0B;QAC3C,0FAA0F;QAC1F,2FAA2F;QAC3F,wFAAwF;QACxF,4FAA4F;QAC5F,2FAA2F;QAC3F,2FAA2F;QAC3F,2FAA2F;QAC3F,qFAAqF;QACrF,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED,IAAW,OAAO;QAChB,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IAED,IAAW,MAAM;QACf,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YAChD,MAAM,YAAY,GAAG,mBAAmB,CAAC,YAAY,CAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;YAE3F,gHAAgH;YAChH,MAAM,aAAa,GAAG,IAAI,kBAAkB,CAAC,YAA+C,CAAC,CAAC;YAC9F,MAAM,aAAa,GAAwB,EAAE,CAAC;YAE9C,IAAI,CAAC;gBACH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;oBAC/C,aAAa,CAAC,IAAI,CAChB,aAAa,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,OAAY,EAAE,EAAE;wBACpD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;oBACxC,CAAC,CAAC,CACH,CAAC;gBACJ,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC9C,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;gBAC5B,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC;YAED,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;QACtC,CAAC;QACD,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;CACF"}
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "10.4.4";
1
+ export declare const version = "10.4.6";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by genversion.
2
- export const version = '10.4.4';
2
+ export const version = '10.4.6';
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-notify-kit",
3
- "version": "10.4.4",
3
+ "version": "10.4.6",
4
4
  "author": "Marco Crupi",
5
5
  "description": "Maintained Notifee-compatible React Native notifications library for Android, iOS, FCM Mode, rich notifications, foreground services, and Expo CNG development builds.",
6
6
  "main": "dist/index.js",
@@ -13,20 +13,20 @@ export interface NativeModuleConfig {
13
13
  }
14
14
 
15
15
  export default class NotifeeNativeModule {
16
- private _nativeModule: Spec;
17
- private _nativeEmitter: NativeEventEmitter;
16
+ private readonly _config: NativeModuleConfig;
17
+ private _nativeModule?: Spec;
18
+ private _nativeEmitter?: NativeEventEmitter;
18
19
 
19
20
  public constructor(config: NativeModuleConfig) {
20
- this._nativeModule = TurboModuleRegistry.getEnforcing<Spec>(config.nativeModuleName);
21
-
22
- // @ts-ignore - change here needs resolution https://github.com/DefinitelyTyped/DefinitelyTyped/pull/49560/files
23
- this._nativeEmitter = new NativeEventEmitter(this.native as EventSubscription['subscriber']);
24
- for (let i = 0; i < config.nativeEvents.length; i++) {
25
- const eventName = config.nativeEvents[i];
26
- this._nativeEmitter.addListener(eventName, (payload: any) => {
27
- this.emitter.emit(eventName, payload);
28
- });
29
- }
21
+ // Defer all native access out of the constructor. The default export of this package is a
22
+ // singleton constructed at module-evaluation time, so resolving the TurboModule here meant
23
+ // it ran the instant `react-native-notify-kit` was imported. On the New Architecture in
24
+ // bridgeless mode, importing the package early (e.g. registering `onBackgroundEvent` at the
25
+ // top of index.js, before the app root mounts) executes before TurboModules are installed,
26
+ // so `getEnforcing` throws "NotifeeApiModule could not be found" / "runtime not ready" and
27
+ // the module is unusable for the whole session. Resolving lazily on first `.native` access
28
+ // defers it until a notifee method actually runs, when the TurboModule is available.
29
+ this._config = config;
30
30
  }
31
31
 
32
32
  public get emitter() {
@@ -34,6 +34,32 @@ export default class NotifeeNativeModule {
34
34
  }
35
35
 
36
36
  public get native(): Spec {
37
+ if (!this._nativeModule || !this._nativeEmitter) {
38
+ const nativeModule = TurboModuleRegistry.getEnforcing<Spec>(this._config.nativeModuleName);
39
+
40
+ // @ts-ignore - change here needs resolution https://github.com/DefinitelyTyped/DefinitelyTyped/pull/49560/files
41
+ const nativeEmitter = new NativeEventEmitter(nativeModule as EventSubscription['subscriber']);
42
+ const subscriptions: EventSubscription[] = [];
43
+
44
+ try {
45
+ for (let i = 0; i < this._config.nativeEvents.length; i++) {
46
+ const eventName = this._config.nativeEvents[i];
47
+ subscriptions.push(
48
+ nativeEmitter.addListener(eventName, (payload: any) => {
49
+ this.emitter.emit(eventName, payload);
50
+ }),
51
+ );
52
+ }
53
+ } catch (error) {
54
+ for (let i = 0; i < subscriptions.length; i++) {
55
+ subscriptions[i].remove();
56
+ }
57
+ throw error;
58
+ }
59
+
60
+ this._nativeModule = nativeModule;
61
+ this._nativeEmitter = nativeEmitter;
62
+ }
37
63
  return this._nativeModule;
38
64
  }
39
65
  }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '10.4.4';
2
+ export const version = '10.4.6';