expo-notifications 0.29.5 → 0.29.7

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/CHANGELOG.md CHANGED
@@ -10,6 +10,17 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.29.7 — 2024-11-13
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 0.29.6 — 2024-11-10
18
+
19
+ ### 🎉 New features
20
+
21
+ - [android] run notification tasks from killed state ([#32531](https://github.com/expo/expo/pull/32531) by [@vonovak](https://github.com/vonovak))
22
+ - add `enableBackgroundRemoteNotifications` option to config plugin ([#32716](https://github.com/expo/expo/pull/32716) by [@vonovak](https://github.com/vonovak))
23
+
13
24
  ## 0.29.5 — 2024-11-07
14
25
 
15
26
  ### 🛠 Breaking changes
@@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
2
2
  apply plugin: 'kotlin-parcelize'
3
3
 
4
4
  group = 'host.exp.exponent'
5
- version = '0.29.5'
5
+ version = '0.29.7'
6
6
 
7
7
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
8
8
  apply from: expoModulesCorePlugin
@@ -15,7 +15,7 @@ android {
15
15
  namespace "expo.modules.notifications"
16
16
  defaultConfig {
17
17
  versionCode 21
18
- versionName '0.29.5'
18
+ versionName '0.29.7'
19
19
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20
20
  }
21
21
 
@@ -6,6 +6,7 @@ import com.google.firebase.messaging.RemoteMessage;
6
6
 
7
7
  import java.util.Map;
8
8
 
9
+ import androidx.annotation.NonNull;
9
10
  import androidx.annotation.Nullable;
10
11
 
11
12
  /**
@@ -19,7 +20,7 @@ public class RemoteMessageSerializer {
19
20
  * @param message {@link RemoteMessage} to serialize
20
21
  * @return Serialized message
21
22
  */
22
- public static Bundle toBundle(RemoteMessage message) {
23
+ public static @NonNull Bundle toBundle(RemoteMessage message) {
23
24
  Bundle serializedMessage = new Bundle();
24
25
  serializedMessage.putString("collapseKey", message.getCollapseKey());
25
26
  serializedMessage.putBundle("data", toBundle(message.getData()));
@@ -6,6 +6,7 @@ import android.content.Context;
6
6
  import android.os.Bundle;
7
7
  import android.os.PersistableBundle;
8
8
  import android.util.Log;
9
+ import androidx.annotation.NonNull;
9
10
 
10
11
  import org.json.JSONException;
11
12
  import org.json.JSONObject;
@@ -120,5 +121,9 @@ public class BackgroundRemoteNotificationTaskConsumer extends TaskConsumer imple
120
121
  return bundle;
121
122
  }
122
123
 
124
+ public void executeTask(@NonNull Bundle bundle) {
125
+ mTask.execute(bundle, null);
126
+ }
127
+
123
128
  //endregion
124
129
  }
@@ -45,7 +45,7 @@ class ExpoHandlingDelegate(protected val context: Context) : HandlingDelegate {
45
45
  }
46
46
 
47
47
  sListenersReferences[listener] = WeakReference(listener)
48
- if (!sPendingNotificationResponses.isEmpty()) {
48
+ if (sPendingNotificationResponses.isNotEmpty()) {
49
49
  val responseIterator = sPendingNotificationResponses.iterator()
50
50
  while (responseIterator.hasNext()) {
51
51
  listener.onNotificationResponseReceived(responseIterator.next())
@@ -139,11 +139,11 @@ class ExpoHandlingDelegate(protected val context: Context) : HandlingDelegate {
139
139
  if (notificationResponse.action.opensAppToForeground()) {
140
140
  openAppToForeground(context, notificationResponse)
141
141
  }
142
-
143
- if (getListeners().isEmpty()) {
142
+ val listeners = getListeners()
143
+ if (listeners.isEmpty()) {
144
144
  sPendingNotificationResponses.add(notificationResponse)
145
145
  } else {
146
- getListeners().forEach {
146
+ listeners.forEach {
147
147
  it.onNotificationResponseReceived(notificationResponse)
148
148
  }
149
149
  }
@@ -2,6 +2,7 @@ package expo.modules.notifications.service.delegates
2
2
 
3
3
  import android.content.Context
4
4
  import com.google.firebase.messaging.RemoteMessage
5
+ import expo.modules.interfaces.taskManager.TaskServiceProviderHelper
5
6
  import expo.modules.notifications.notifications.RemoteMessageSerializer
6
7
  import expo.modules.notifications.notifications.background.BackgroundRemoteNotificationTaskConsumer
7
8
  import expo.modules.notifications.notifications.debug.DebugLogging
@@ -94,9 +95,16 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM
94
95
  val notification = createNotification(remoteMessage)
95
96
  DebugLogging.logNotification("FirebaseMessagingDelegate.onMessageReceived: notification", notification)
96
97
  NotificationsService.receive(context, notification)
97
- // background tasks are separate from the main notification handling flow; they are executed through task-manager
98
+ runTaskManagerTasks(remoteMessage)
99
+ }
100
+
101
+ private fun runTaskManagerTasks(remoteMessage: RemoteMessage) {
102
+ // getTaskServiceImpl() has a side effect:
103
+ // the TaskService constructor calls restoreTasks which then constructs a BackgroundRemoteNotificationTaskConsumer,
104
+ // and the getBackgroundTasks() call below doesn't return an empty collection.
105
+ TaskServiceProviderHelper.getTaskServiceImpl(context.applicationContext)
98
106
  getBackgroundTasks().forEach {
99
- it.scheduleJob(RemoteMessageSerializer.toBundle(remoteMessage))
107
+ it.executeTask(RemoteMessageSerializer.toBundle(remoteMessage))
100
108
  }
101
109
  }
102
110
 
@@ -1,9 +1,14 @@
1
1
  /**
2
- * When a notification is received while the app is backgrounded, using this function you can set a callback that will be run in response to that notification.
3
- * Under the hood, this function is run using `expo-task-manager`. You **must** define the task first, with [`TaskManager.defineTask`](./task-manager#taskmanagerdefinetasktaskname-taskexecutor).
4
- * Make sure you define it in the global scope. It is recommended to define only one task because running a task can have a high cost.
2
+ * Call `registerTaskAsync` to set a callback (task) that will run in response to when a notification is received while the app is in foreground, background, or terminated.
3
+ * When app is terminated, only a [data message](https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages) (Android) / [background notification](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#Create-a-background-notification) (iOS) triggers the task execution.
4
+ * However, the OS may decide not to deliver the notification to your app in some cases (e.g. when the device is in Doze mode on Android, or when you send too many notifications - Apple recommends to not ["send more than two or three per hour"](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#overview)).
5
5
  *
6
- * The callback function you define with `TaskManager.defineTask` will receive an object with the following fields:
6
+ * Under the hood, this function is run using `expo-task-manager`. You **must** define the task first, with [`TaskManager.defineTask`](./task-manager#taskmanagerdefinetasktaskname-taskexecutor) and register it with `registerTaskAsync`.
7
+ *
8
+ * Make sure you define and register the task in the module scope of a JS module which is required early by your app (e.g. in the `index.js` file).
9
+ * `expo-task-manager` loads your app's JS bundle in the background and executes the task, as well as any side effects which may happen as a consequence of requiring any JS modules.
10
+ *
11
+ * The callback function you define with `TaskManager.defineTask` receives an object with the following fields:
7
12
  * - `data`: The remote payload delivered by either FCM (Android) or APNs (iOS). See [`PushNotificationTrigger`](#pushnotificationtrigger) for details.
8
13
  * - `error`: The error (if any) that occurred during execution of the task.
9
14
  * - `executionInfo`: JSON object of additional info related to the task, including the `taskName`.
@@ -1 +1 @@
1
- {"version":3,"file":"registerTaskAsync.d.ts","sourceRoot":"","sources":["../src/registerTaskAsync.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAA8B,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/E"}
1
+ {"version":3,"file":"registerTaskAsync.d.ts","sourceRoot":"","sources":["../src/registerTaskAsync.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAA8B,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/E"}
@@ -1,11 +1,16 @@
1
1
  import { UnavailabilityError } from 'expo-modules-core';
2
2
  import BackgroundNotificationTasksModule from './BackgroundNotificationTasksModule';
3
3
  /**
4
- * When a notification is received while the app is backgrounded, using this function you can set a callback that will be run in response to that notification.
5
- * Under the hood, this function is run using `expo-task-manager`. You **must** define the task first, with [`TaskManager.defineTask`](./task-manager#taskmanagerdefinetasktaskname-taskexecutor).
6
- * Make sure you define it in the global scope. It is recommended to define only one task because running a task can have a high cost.
4
+ * Call `registerTaskAsync` to set a callback (task) that will run in response to when a notification is received while the app is in foreground, background, or terminated.
5
+ * When app is terminated, only a [data message](https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages) (Android) / [background notification](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#Create-a-background-notification) (iOS) triggers the task execution.
6
+ * However, the OS may decide not to deliver the notification to your app in some cases (e.g. when the device is in Doze mode on Android, or when you send too many notifications - Apple recommends to not ["send more than two or three per hour"](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#overview)).
7
7
  *
8
- * The callback function you define with `TaskManager.defineTask` will receive an object with the following fields:
8
+ * Under the hood, this function is run using `expo-task-manager`. You **must** define the task first, with [`TaskManager.defineTask`](./task-manager#taskmanagerdefinetasktaskname-taskexecutor) and register it with `registerTaskAsync`.
9
+ *
10
+ * Make sure you define and register the task in the module scope of a JS module which is required early by your app (e.g. in the `index.js` file).
11
+ * `expo-task-manager` loads your app's JS bundle in the background and executes the task, as well as any side effects which may happen as a consequence of requiring any JS modules.
12
+ *
13
+ * The callback function you define with `TaskManager.defineTask` receives an object with the following fields:
9
14
  * - `data`: The remote payload delivered by either FCM (Android) or APNs (iOS). See [`PushNotificationTrigger`](#pushnotificationtrigger) for details.
10
15
  * - `error`: The error (if any) that occurred during execution of the task.
11
16
  * - `executionInfo`: JSON object of additional info related to the task, including the `taskName`.
@@ -1 +1 @@
1
- {"version":3,"file":"registerTaskAsync.js","sourceRoot":"","sources":["../src/registerTaskAsync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,iCAAiC,MAAM,qCAAqC,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IAC9D,IAAI,CAAC,iCAAiC,CAAC,iBAAiB,EAAE;QACxD,MAAM,IAAI,mBAAmB,CAAC,eAAe,EAAE,mBAAmB,CAAC,CAAC;KACrE;IAED,OAAO,MAAM,iCAAiC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["import { UnavailabilityError } from 'expo-modules-core';\n\nimport BackgroundNotificationTasksModule from './BackgroundNotificationTasksModule';\n\n/**\n * When a notification is received while the app is backgrounded, using this function you can set a callback that will be run in response to that notification.\n * Under the hood, this function is run using `expo-task-manager`. You **must** define the task first, with [`TaskManager.defineTask`](./task-manager#taskmanagerdefinetasktaskname-taskexecutor).\n * Make sure you define it in the global scope. It is recommended to define only one task because running a task can have a high cost.\n *\n * The callback function you define with `TaskManager.defineTask` will receive an object with the following fields:\n * - `data`: The remote payload delivered by either FCM (Android) or APNs (iOS). See [`PushNotificationTrigger`](#pushnotificationtrigger) for details.\n * - `error`: The error (if any) that occurred during execution of the task.\n * - `executionInfo`: JSON object of additional info related to the task, including the `taskName`.\n * @param taskName The string you passed to `TaskManager.defineTask` as the `taskName` parameter.\n *\n * @example\n * ```ts\n * import * as TaskManager from 'expo-task-manager';\n * import * as Notifications from 'expo-notifications';\n *\n * const BACKGROUND_NOTIFICATION_TASK = 'BACKGROUND-NOTIFICATION-TASK';\n *\n * TaskManager.defineTask(BACKGROUND_NOTIFICATION_TASK, ({ data, error, executionInfo }) => {\n * console.log('Received a notification in the background!');\n * // Do something with the notification data\n * });\n *\n * Notifications.registerTaskAsync(BACKGROUND_NOTIFICATION_TASK);\n * ```\n * @header inBackground\n */\nexport default async function registerTaskAsync(taskName: string): Promise<null> {\n if (!BackgroundNotificationTasksModule.registerTaskAsync) {\n throw new UnavailabilityError('Notifications', 'registerTaskAsync');\n }\n\n return await BackgroundNotificationTasksModule.registerTaskAsync(taskName);\n}\n"]}
1
+ {"version":3,"file":"registerTaskAsync.js","sourceRoot":"","sources":["../src/registerTaskAsync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,iCAAiC,MAAM,qCAAqC,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IAC9D,IAAI,CAAC,iCAAiC,CAAC,iBAAiB,EAAE;QACxD,MAAM,IAAI,mBAAmB,CAAC,eAAe,EAAE,mBAAmB,CAAC,CAAC;KACrE;IAED,OAAO,MAAM,iCAAiC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["import { UnavailabilityError } from 'expo-modules-core';\n\nimport BackgroundNotificationTasksModule from './BackgroundNotificationTasksModule';\n\n/**\n * Call `registerTaskAsync` to set a callback (task) that will run in response to when a notification is received while the app is in foreground, background, or terminated.\n * When app is terminated, only a [data message](https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages) (Android) / [background notification](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#Create-a-background-notification) (iOS) triggers the task execution.\n * However, the OS may decide not to deliver the notification to your app in some cases (e.g. when the device is in Doze mode on Android, or when you send too many notifications - Apple recommends to not [\"send more than two or three per hour\"](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#overview)).\n *\n * Under the hood, this function is run using `expo-task-manager`. You **must** define the task first, with [`TaskManager.defineTask`](./task-manager#taskmanagerdefinetasktaskname-taskexecutor) and register it with `registerTaskAsync`.\n *\n * Make sure you define and register the task in the module scope of a JS module which is required early by your app (e.g. in the `index.js` file).\n * `expo-task-manager` loads your app's JS bundle in the background and executes the task, as well as any side effects which may happen as a consequence of requiring any JS modules.\n *\n * The callback function you define with `TaskManager.defineTask` receives an object with the following fields:\n * - `data`: The remote payload delivered by either FCM (Android) or APNs (iOS). See [`PushNotificationTrigger`](#pushnotificationtrigger) for details.\n * - `error`: The error (if any) that occurred during execution of the task.\n * - `executionInfo`: JSON object of additional info related to the task, including the `taskName`.\n * @param taskName The string you passed to `TaskManager.defineTask` as the `taskName` parameter.\n *\n * @example\n * ```ts\n * import * as TaskManager from 'expo-task-manager';\n * import * as Notifications from 'expo-notifications';\n *\n * const BACKGROUND_NOTIFICATION_TASK = 'BACKGROUND-NOTIFICATION-TASK';\n *\n * TaskManager.defineTask(BACKGROUND_NOTIFICATION_TASK, ({ data, error, executionInfo }) => {\n * console.log('Received a notification in the background!');\n * // Do something with the notification data\n * });\n *\n * Notifications.registerTaskAsync(BACKGROUND_NOTIFICATION_TASK);\n * ```\n * @header inBackground\n */\nexport default async function registerTaskAsync(taskName: string): Promise<null> {\n if (!BackgroundNotificationTasksModule.registerTaskAsync) {\n throw new UnavailabilityError('Notifications', 'registerTaskAsync');\n }\n\n return await BackgroundNotificationTasksModule.registerTaskAsync(taskName);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-notifications",
3
- "version": "0.29.5",
3
+ "version": "0.29.7",
4
4
  "description": "Provides an API to fetch push notification tokens and to present, schedule, receive, and respond to notifications.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -55,5 +55,5 @@
55
55
  "react": "*",
56
56
  "react-native": "*"
57
57
  },
58
- "gitHead": "c0899e56c5be08b0142f06f05aa7bac8d6bc18b8"
58
+ "gitHead": "2c50bd7f0abaefb6fabcfd6d3935066724990b33"
59
59
  }
@@ -28,6 +28,14 @@ export type NotificationsPluginProps = {
28
28
  * @platform ios
29
29
  */
30
30
  mode?: 'development' | 'production';
31
+ /**
32
+ * Whether to enable background remote notifications, as described in [Apple documentation](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app).
33
+ *
34
+ * This sets the `UIBackgroundModes` key in the `Info.plist` to include `remote-notification`.
35
+ * @default false
36
+ * @platform ios
37
+ */
38
+ enableBackgroundRemoteNotifications?: boolean;
31
39
  };
32
40
  declare const _default: ConfigPlugin<void | NotificationsPluginProps>;
33
41
  export default _default;
@@ -1,9 +1,6 @@
1
1
  import { ConfigPlugin, XcodeProject } from 'expo/config-plugins';
2
2
  import { NotificationsPluginProps } from './withNotifications';
3
3
  export declare const withNotificationsIOS: ConfigPlugin<NotificationsPluginProps>;
4
- export declare const withNotificationSounds: ConfigPlugin<{
5
- sounds: string[];
6
- }>;
7
4
  /**
8
5
  * Save sound files to the Xcode project root and add them to the Xcode project.
9
6
  */
@@ -1,21 +1,42 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setNotificationSounds = exports.withNotificationSounds = exports.withNotificationsIOS = void 0;
3
+ exports.setNotificationSounds = exports.withNotificationsIOS = void 0;
4
4
  const config_plugins_1 = require("expo/config-plugins");
5
5
  const fs_1 = require("fs");
6
6
  const path_1 = require("path");
7
7
  const ERROR_MSG_PREFIX = 'An error occurred while configuring iOS notifications. ';
8
- const withNotificationsIOS = (config, { mode = 'development', sounds = [] }) => {
8
+ const withNotificationsIOS = (config, { mode = 'development', sounds = [], enableBackgroundRemoteNotifications }) => {
9
9
  config = (0, config_plugins_1.withEntitlementsPlist)(config, (config) => {
10
10
  if (!config.modResults['aps-environment']) {
11
11
  config.modResults['aps-environment'] = mode;
12
12
  }
13
13
  return config;
14
14
  });
15
- config = (0, exports.withNotificationSounds)(config, { sounds });
15
+ config = withNotificationSounds(config, { sounds });
16
+ config = withBackgroundRemoteNotifications(config, enableBackgroundRemoteNotifications);
16
17
  return config;
17
18
  };
18
19
  exports.withNotificationsIOS = withNotificationsIOS;
20
+ const withBackgroundRemoteNotifications = (config, enableBackgroundRemoteNotifications) => {
21
+ if (!(enableBackgroundRemoteNotifications === undefined ||
22
+ typeof enableBackgroundRemoteNotifications === 'boolean')) {
23
+ throw new Error(ERROR_MSG_PREFIX +
24
+ `"enableBackgroundRemoteNotifications" has an invalid value: ${enableBackgroundRemoteNotifications}. Expected a boolean.`);
25
+ }
26
+ if (!enableBackgroundRemoteNotifications) {
27
+ return config;
28
+ }
29
+ return (0, config_plugins_1.withInfoPlist)(config, (config) => {
30
+ if (!Array.isArray(config.modResults.UIBackgroundModes)) {
31
+ config.modResults.UIBackgroundModes = [];
32
+ }
33
+ const notificationBackgroundMode = 'remote-notification';
34
+ if (!config.modResults.UIBackgroundModes.includes(notificationBackgroundMode)) {
35
+ config.modResults.UIBackgroundModes.push(notificationBackgroundMode);
36
+ }
37
+ return config;
38
+ });
39
+ };
19
40
  const withNotificationSounds = (config, { sounds }) => {
20
41
  return (0, config_plugins_1.withXcodeProject)(config, (config) => {
21
42
  setNotificationSounds(config.modRequest.projectRoot, {
@@ -26,7 +47,6 @@ const withNotificationSounds = (config, { sounds }) => {
26
47
  return config;
27
48
  });
28
49
  };
29
- exports.withNotificationSounds = withNotificationSounds;
30
50
  /**
31
51
  * Save sound files to the Xcode project root and add them to the Xcode project.
32
52
  */
@@ -34,6 +34,15 @@ export type NotificationsPluginProps = {
34
34
  * @platform ios
35
35
  */
36
36
  mode?: 'development' | 'production';
37
+
38
+ /**
39
+ * Whether to enable background remote notifications, as described in [Apple documentation](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app).
40
+ *
41
+ * This sets the `UIBackgroundModes` key in the `Info.plist` to include `remote-notification`.
42
+ * @default false
43
+ * @platform ios
44
+ */
45
+ enableBackgroundRemoteNotifications?: boolean;
37
46
  };
38
47
 
39
48
  const withNotifications: ConfigPlugin<NotificationsPluginProps | void> = (config, props) => {
@@ -4,6 +4,7 @@ import {
4
4
  IOSConfig,
5
5
  withXcodeProject,
6
6
  XcodeProject,
7
+ withInfoPlist,
7
8
  } from 'expo/config-plugins';
8
9
  import { copyFileSync } from 'fs';
9
10
  import { basename, resolve } from 'path';
@@ -14,7 +15,7 @@ const ERROR_MSG_PREFIX = 'An error occurred while configuring iOS notifications.
14
15
 
15
16
  export const withNotificationsIOS: ConfigPlugin<NotificationsPluginProps> = (
16
17
  config,
17
- { mode = 'development', sounds = [] }
18
+ { mode = 'development', sounds = [], enableBackgroundRemoteNotifications }
18
19
  ) => {
19
20
  config = withEntitlementsPlist(config, (config) => {
20
21
  if (!config.modResults['aps-environment']) {
@@ -23,10 +24,42 @@ export const withNotificationsIOS: ConfigPlugin<NotificationsPluginProps> = (
23
24
  return config;
24
25
  });
25
26
  config = withNotificationSounds(config, { sounds });
27
+ config = withBackgroundRemoteNotifications(config, enableBackgroundRemoteNotifications);
28
+
26
29
  return config;
27
30
  };
28
31
 
29
- export const withNotificationSounds: ConfigPlugin<{ sounds: string[] }> = (config, { sounds }) => {
32
+ const withBackgroundRemoteNotifications: ConfigPlugin<boolean | undefined> = (
33
+ config,
34
+ enableBackgroundRemoteNotifications
35
+ ) => {
36
+ if (
37
+ !(
38
+ enableBackgroundRemoteNotifications === undefined ||
39
+ typeof enableBackgroundRemoteNotifications === 'boolean'
40
+ )
41
+ ) {
42
+ throw new Error(
43
+ ERROR_MSG_PREFIX +
44
+ `"enableBackgroundRemoteNotifications" has an invalid value: ${enableBackgroundRemoteNotifications}. Expected a boolean.`
45
+ );
46
+ }
47
+ if (!enableBackgroundRemoteNotifications) {
48
+ return config;
49
+ }
50
+ return withInfoPlist(config, (config) => {
51
+ if (!Array.isArray(config.modResults.UIBackgroundModes)) {
52
+ config.modResults.UIBackgroundModes = [];
53
+ }
54
+ const notificationBackgroundMode = 'remote-notification';
55
+ if (!config.modResults.UIBackgroundModes.includes(notificationBackgroundMode)) {
56
+ config.modResults.UIBackgroundModes.push(notificationBackgroundMode);
57
+ }
58
+ return config;
59
+ });
60
+ };
61
+
62
+ const withNotificationSounds: ConfigPlugin<{ sounds: string[] }> = (config, { sounds }) => {
30
63
  return withXcodeProject(config, (config) => {
31
64
  setNotificationSounds(config.modRequest.projectRoot, {
32
65
  sounds,
@@ -3,11 +3,16 @@ import { UnavailabilityError } from 'expo-modules-core';
3
3
  import BackgroundNotificationTasksModule from './BackgroundNotificationTasksModule';
4
4
 
5
5
  /**
6
- * When a notification is received while the app is backgrounded, using this function you can set a callback that will be run in response to that notification.
7
- * Under the hood, this function is run using `expo-task-manager`. You **must** define the task first, with [`TaskManager.defineTask`](./task-manager#taskmanagerdefinetasktaskname-taskexecutor).
8
- * Make sure you define it in the global scope. It is recommended to define only one task because running a task can have a high cost.
6
+ * Call `registerTaskAsync` to set a callback (task) that will run in response to when a notification is received while the app is in foreground, background, or terminated.
7
+ * When app is terminated, only a [data message](https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages) (Android) / [background notification](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#Create-a-background-notification) (iOS) triggers the task execution.
8
+ * However, the OS may decide not to deliver the notification to your app in some cases (e.g. when the device is in Doze mode on Android, or when you send too many notifications - Apple recommends to not ["send more than two or three per hour"](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#overview)).
9
9
  *
10
- * The callback function you define with `TaskManager.defineTask` will receive an object with the following fields:
10
+ * Under the hood, this function is run using `expo-task-manager`. You **must** define the task first, with [`TaskManager.defineTask`](./task-manager#taskmanagerdefinetasktaskname-taskexecutor) and register it with `registerTaskAsync`.
11
+ *
12
+ * Make sure you define and register the task in the module scope of a JS module which is required early by your app (e.g. in the `index.js` file).
13
+ * `expo-task-manager` loads your app's JS bundle in the background and executes the task, as well as any side effects which may happen as a consequence of requiring any JS modules.
14
+ *
15
+ * The callback function you define with `TaskManager.defineTask` receives an object with the following fields:
11
16
  * - `data`: The remote payload delivered by either FCM (Android) or APNs (iOS). See [`PushNotificationTrigger`](#pushnotificationtrigger) for details.
12
17
  * - `error`: The error (if any) that occurred during execution of the task.
13
18
  * - `executionInfo`: JSON object of additional info related to the task, including the `taskName`.