@zykeco/expo-background-task 1.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 (49) hide show
  1. package/.eslintrc.js +2 -0
  2. package/CHANGELOG.md +185 -0
  3. package/README.md +115 -0
  4. package/android/build.gradle +21 -0
  5. package/android/proguard-rules.pro +1 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskConsumer.kt +60 -0
  8. package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskExceptions.kt +13 -0
  9. package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskModule.kt +56 -0
  10. package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskScheduler.kt +294 -0
  11. package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskWork.kt +38 -0
  12. package/app.plugin.js +1 -0
  13. package/build/BackgroundTask.d.ts +63 -0
  14. package/build/BackgroundTask.d.ts.map +1 -0
  15. package/build/BackgroundTask.js +141 -0
  16. package/build/BackgroundTask.js.map +1 -0
  17. package/build/BackgroundTask.types.d.ts +71 -0
  18. package/build/BackgroundTask.types.d.ts.map +1 -0
  19. package/build/BackgroundTask.types.js +31 -0
  20. package/build/BackgroundTask.types.js.map +1 -0
  21. package/build/ExpoBackgroundTaskModule.d.ts +14 -0
  22. package/build/ExpoBackgroundTaskModule.d.ts.map +1 -0
  23. package/build/ExpoBackgroundTaskModule.js +3 -0
  24. package/build/ExpoBackgroundTaskModule.js.map +1 -0
  25. package/build/ExpoBackgroundTaskModule.web.d.ts +6 -0
  26. package/build/ExpoBackgroundTaskModule.web.d.ts.map +1 -0
  27. package/build/ExpoBackgroundTaskModule.web.js +7 -0
  28. package/build/ExpoBackgroundTaskModule.web.js.map +1 -0
  29. package/expo-module.config.json +10 -0
  30. package/ios/BackgorundTaskExceptions.swift +48 -0
  31. package/ios/BackgroundTaskAppDelegate.swift +74 -0
  32. package/ios/BackgroundTaskConstants.swift +13 -0
  33. package/ios/BackgroundTaskConsumer.swift +62 -0
  34. package/ios/BackgroundTaskDebugHelper.swift +21 -0
  35. package/ios/BackgroundTaskModule.swift +80 -0
  36. package/ios/BackgroundTaskRecords.swift +12 -0
  37. package/ios/BackgroundTaskScheduler.swift +206 -0
  38. package/ios/ExpoBackgroundTask.podspec +33 -0
  39. package/package.json +42 -0
  40. package/plugin/build/withBackgroundTask.d.ts +3 -0
  41. package/plugin/build/withBackgroundTask.js +32 -0
  42. package/plugin/src/withBackgroundTask.ts +39 -0
  43. package/plugin/tsconfig.json +9 -0
  44. package/plugin/tsconfig.tsbuildinfo +1 -0
  45. package/src/BackgroundTask.ts +162 -0
  46. package/src/BackgroundTask.types.ts +75 -0
  47. package/src/ExpoBackgroundTaskModule.ts +16 -0
  48. package/src/ExpoBackgroundTaskModule.web.ts +7 -0
  49. package/tsconfig.json +9 -0
@@ -0,0 +1,294 @@
1
+ package expo.modules.backgroundtask
2
+
3
+ import android.content.Context
4
+ import android.os.Build
5
+ import android.util.Log
6
+ import androidx.work.Constraints
7
+ import androidx.work.Data
8
+ import androidx.work.ExistingPeriodicWorkPolicy
9
+ import androidx.work.ExistingWorkPolicy
10
+ import androidx.work.NetworkType
11
+ import androidx.work.OneTimeWorkRequestBuilder
12
+ import androidx.work.Operation
13
+ import androidx.work.PeriodicWorkRequestBuilder
14
+ import androidx.work.WorkInfo
15
+ import androidx.work.WorkManager
16
+ import com.google.common.util.concurrent.ListenableFuture
17
+ import expo.modules.interfaces.taskManager.TaskServiceProviderHelper
18
+ import kotlinx.coroutines.CancellationException
19
+ import kotlinx.coroutines.CompletableDeferred
20
+ import kotlinx.coroutines.Dispatchers
21
+ import kotlinx.coroutines.awaitAll
22
+ import kotlinx.coroutines.CoroutineScope
23
+ import kotlinx.coroutines.launch
24
+
25
+ import kotlinx.coroutines.withContext
26
+ import java.time.Duration
27
+ import java.util.concurrent.TimeUnit
28
+
29
+ object BackgroundTaskScheduler {
30
+ // Default interval
31
+ const val DEFAULT_INTERVAL_MINUTES = 60L * 24L // Once every day
32
+
33
+ // Unique identifier (generated by us) to identify the worker
34
+ private const val WORKER_IDENTIFIER = "EXPO_BACKGROUND_WORKER"
35
+
36
+ // Log tag
37
+ private val TAG = BackgroundTaskScheduler::class.java.simpleName
38
+
39
+ // Number of active task consumers
40
+ private var numberOfRegisteredTasksOfThisType = 0
41
+
42
+ // Interval
43
+ private var intervalMinutes: Long = DEFAULT_INTERVAL_MINUTES
44
+
45
+ // Whether network connectivity is required
46
+ private var requiresNetwork: Boolean = true
47
+
48
+ // Tracks whether in foreground
49
+ var inForeground: Boolean = false
50
+
51
+ /**
52
+ * Call when a task is registered
53
+ */
54
+ fun registerTask(context: Context, intervalMinutes: Long, requiresNetwork: Boolean? = null) {
55
+ numberOfRegisteredTasksOfThisType += 1
56
+ this.intervalMinutes = intervalMinutes
57
+ if (requiresNetwork != null) {
58
+ this.requiresNetwork = requiresNetwork
59
+ }
60
+
61
+ if (numberOfRegisteredTasksOfThisType == 1) {
62
+ CoroutineScope(Dispatchers.IO).launch {
63
+ // Only schedule if no worker is already running/enqueued
64
+ if (!isWorkerRunning(context)) {
65
+ scheduleWorker(context, context.packageName)
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Call when a task is unregistered
73
+ */
74
+ fun unregisterTask(context: Context) {
75
+ numberOfRegisteredTasksOfThisType -= 1
76
+
77
+ if (numberOfRegisteredTasksOfThisType == 0) {
78
+ CoroutineScope(Dispatchers.IO).launch {
79
+ stopWorker(context)
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Schedules the worker task to run. The worker should run periodically.
86
+ */
87
+ private suspend fun scheduleWorker(context: Context, appScopeKey: String, cancelExisting: Boolean = true, overriddenIntervalMinutes: Long = intervalMinutes): Boolean {
88
+ if (numberOfRegisteredTasksOfThisType == 0) {
89
+ Log.d(TAG, "Will not enqueue worker. No registered tasks to run.")
90
+ return false
91
+ }
92
+
93
+ // Stop the current worker (if any)
94
+ if (cancelExisting) {
95
+ stopWorker(context)
96
+ }
97
+
98
+ Log.d(TAG, "Enqueuing worker with identifier $WORKER_IDENTIFIER and '$overriddenIntervalMinutes' minutes delay.")
99
+
100
+ // Build the work request
101
+ val data = Data.Builder()
102
+ .putString("appScopeKey", appScopeKey)
103
+ .build()
104
+ val constraints = Constraints.Builder()
105
+ .setRequiredNetworkType(
106
+ if (requiresNetwork) NetworkType.CONNECTED else NetworkType.NOT_REQUIRED
107
+ )
108
+ .build()
109
+
110
+ // Get Work manager
111
+ val workManager = WorkManager.getInstance(context)
112
+
113
+ // We have two different paths here, since on Android 14-15 we need to use a periodic request
114
+ // builder since the OneTimeWorkRequest doesn't support setting initial delay (which is how we
115
+ // control executing a task periodically - we spawn a One-time work request when backgrounding,
116
+ // and when this is done we spawn a new one. This makes it a lot easier to debug since we can
117
+ // use adb to spawn jobs whenever we want)
118
+ // The following is the path we have to follow on Android.O and later:
119
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
120
+ // Create the work request
121
+ val builder = OneTimeWorkRequestBuilder<BackgroundTaskWork>()
122
+ .setInputData(data)
123
+ .setConstraints(constraints)
124
+
125
+ // Add minimum interval here as well so that the work doesn't start immediately
126
+ builder.setInitialDelay(Duration.ofMinutes(overriddenIntervalMinutes))
127
+
128
+ // Create work request
129
+ val workRequest = builder.build()
130
+
131
+ // Enqueue the work
132
+ return try {
133
+ workManager.enqueueUniqueWork(
134
+ WORKER_IDENTIFIER,
135
+ // This is where we decide if we should cancel or replace the task - cancelling is done
136
+ // when spawning the first task, while appending is when we spawn from a running task
137
+ // to set up the next periodic run of the task
138
+ if (cancelExisting) ExistingWorkPolicy.REPLACE else ExistingWorkPolicy.APPEND,
139
+ workRequest
140
+ ).await()
141
+
142
+ true
143
+ } catch (e: Exception) {
144
+ Log.e(TAG, "Worker failed to start with error " + e.message)
145
+ false
146
+ }
147
+ } else {
148
+ val builder = PeriodicWorkRequestBuilder<BackgroundTaskWork>(
149
+ repeatIntervalTimeUnit = TimeUnit.MINUTES,
150
+ repeatInterval = intervalMinutes
151
+ )
152
+ .setInputData(data)
153
+ .setConstraints(constraints)
154
+
155
+ // Create work request
156
+ val workRequest = builder.build()
157
+
158
+ // Enqueue the work
159
+ return try {
160
+ workManager.enqueueUniquePeriodicWork(
161
+ WORKER_IDENTIFIER,
162
+ ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
163
+ workRequest
164
+ ).await()
165
+
166
+ true
167
+ } catch (e: Exception) {
168
+ Log.e(TAG, "Worker failed to start with error " + e.message)
169
+ false
170
+ }
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Cancels the worker task
176
+ */
177
+ private suspend fun stopWorker(context: Context): Boolean {
178
+ if (!isWorkerRunning(context)) {
179
+ return false
180
+ }
181
+
182
+ Log.d(TAG, "Cancelling worker with identifier $WORKER_IDENTIFIER")
183
+
184
+ // Stop our main worker
185
+ val workManager = WorkManager.getInstance(context)
186
+ return try {
187
+ workManager.cancelUniqueWork(WORKER_IDENTIFIER).await()
188
+ } catch (e: Exception) {
189
+ Log.d(TAG, "Stopping worker failed with error " + e.message)
190
+ false
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Returns true if the worker task is pending
196
+ */
197
+ private suspend fun isWorkerRunning(context: Context): Boolean {
198
+ val workInfo = getWorkerInfo(context)
199
+ return workInfo?.state == WorkInfo.State.RUNNING ||
200
+ workInfo?.state == WorkInfo.State.ENQUEUED
201
+ }
202
+
203
+ /**
204
+ * Runs tasks with the given appScopeKey
205
+ */
206
+ suspend fun runTasks(context: Context, appScopeKey: String) {
207
+ // Get task service
208
+ val taskService = TaskServiceProviderHelper.getTaskServiceImpl(context)
209
+ ?: throw MissingTaskServiceException()
210
+
211
+ Log.d(TAG, "runTasks: $appScopeKey")
212
+
213
+ // Get all task consumers
214
+ val consumers = taskService.getTaskConsumers(appScopeKey)
215
+ Log.d(TAG, "runTasks: number of consumers ${consumers.size}")
216
+
217
+ if (consumers.isEmpty()) {
218
+ return
219
+ }
220
+
221
+ // Make sure we're in the background before running a task
222
+ if (inForeground) {
223
+ // Schedule task in an hour (or at least minimumInterval) - the app is foregrounded and
224
+ // we don't want to run anything to avoid performance issues.
225
+ Log.d(TAG, "runTasks: App is in the foreground")
226
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
227
+ scheduleWorker(context, appScopeKey, false, 60L.coerceAtMost(intervalMinutes))
228
+ Log.d(TAG, "runTasks: Scheduled new worker in $intervalMinutes minutes")
229
+ }
230
+ return
231
+ }
232
+
233
+ val tasks = consumers.filterIsInstance<BackgroundTaskConsumer>()
234
+ .map { bgTaskConsumer ->
235
+ Log.d(TAG, "runTasks: executing tasks for consumer of type ${bgTaskConsumer.taskType()}")
236
+ val taskCompletion = CompletableDeferred<Unit>()
237
+ try {
238
+ bgTaskConsumer.executeTask {
239
+ Log.d(TAG, "Task successfully finished")
240
+ taskCompletion.complete(Unit)
241
+ }
242
+ } catch (e: Exception) {
243
+ Log.e(TAG, "Task failed: ${e.message}")
244
+ }
245
+ taskCompletion
246
+ }
247
+ // Await all tasks to complete
248
+ tasks.awaitAll()
249
+
250
+ // Schedule next task
251
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
252
+ scheduleWorker(context, appScopeKey, false)
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Returns the worker info object from the WorkManager if the worker has been
258
+ * registered, otherwise returns null
259
+ */
260
+ private suspend fun getWorkerInfo(context: Context): WorkInfo? {
261
+ // Get work manager
262
+ val workManager = WorkManager.getInstance(context)
263
+
264
+ return try {
265
+ val workInfos = workManager.getWorkInfosForUniqueWork(WORKER_IDENTIFIER).await()
266
+ return workInfos.firstOrNull()
267
+ } catch (e: Exception) {
268
+ Log.d(TAG, "Calling getWorkInfosForUniqueWork failed with error " + e.message)
269
+ null
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Helper function for calling functions returning an Operation
275
+ */
276
+ private suspend fun Operation.await(): Boolean = withContext(Dispatchers.IO) {
277
+ result.get()
278
+ true
279
+ }
280
+
281
+ /**
282
+ * Helper function for calling functions returning a ListenableFuture
283
+ */
284
+ private suspend fun <T> ListenableFuture<T>.await(): T = withContext(Dispatchers.IO) {
285
+ try {
286
+ get()
287
+ } catch (e: CancellationException) {
288
+ cancel(true)
289
+ throw e
290
+ } catch (e: Exception) {
291
+ throw e
292
+ }
293
+ }
294
+ }
@@ -0,0 +1,38 @@
1
+ package expo.modules.backgroundtask
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import androidx.work.CoroutineWorker
6
+ import androidx.work.Data
7
+ import androidx.work.WorkerParameters
8
+
9
+ class BackgroundTaskWork(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
10
+ companion object {
11
+ private val TAG = BackgroundTaskWork::class.java.simpleName
12
+ }
13
+
14
+ /**
15
+ * The doWork function is called by the Android WorkManager to execute scheduled work.
16
+ */
17
+ override suspend fun doWork(): Result {
18
+ Log.i(TAG, "doWork: Running worker")
19
+
20
+ // Get the app scope key
21
+ val appScopeKey = inputData.getString("appScopeKey") ?: throw MissingAppScopeKey()
22
+
23
+ try {
24
+ // Run tasks async using the task service. This call will return when the task has finished
25
+ // ie. When JS task executor has notified the task manager that it is done.
26
+ BackgroundTaskScheduler.runTasks(applicationContext, appScopeKey)
27
+ } catch (e: Exception) {
28
+ // Wrap exception in Data:
29
+ val outputData = Data.Builder()
30
+ .putString("error", e.message)
31
+ .putString("stackTrace", e.stackTraceToString())
32
+ .build()
33
+
34
+ return Result.failure(outputData)
35
+ }
36
+ return Result.success()
37
+ }
38
+ }
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./plugin/build/withBackgroundTask');
@@ -0,0 +1,63 @@
1
+ import { BackgroundTaskOptions, BackgroundTaskStatus } from './BackgroundTask.types';
2
+ /**
3
+ * Returns the status for the Background Task API. On web, it always returns `BackgroundTaskStatus.Restricted`,
4
+ * while on native platforms it returns `BackgroundTaskStatus.Available`.
5
+ *
6
+ * @returns A BackgroundTaskStatus enum value or `null` if not available.
7
+ */
8
+ export declare const getStatusAsync: () => Promise<BackgroundTaskStatus>;
9
+ /**
10
+ * Registers a background task with the given name. Registered tasks are saved in persistent storage and restored once the app is initialized.
11
+ * @param taskName Name of the task to register. The task needs to be defined first - see [`TaskManager.defineTask`](task-manager/#taskmanagerdefinetasktaskname-taskexecutor)
12
+ * for more details.
13
+ * @param options An object containing the background task options.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import * as TaskManager from 'expo-task-manager';
18
+ *
19
+ * // Register the task outside of the component
20
+ * TaskManager.defineTask(BACKGROUND_TASK_IDENTIFIER, () => {
21
+ * try {
22
+ * await AsyncStorage.setItem(LAST_TASK_DATE_KEY, Date.now().toString());
23
+ * } catch (error) {
24
+ * console.error('Failed to save the last fetch date', error);
25
+ * return BackgroundTaskResult.Failed;
26
+ * }
27
+ * return BackgroundTaskResult.Success;
28
+ * });
29
+ * ```
30
+ *
31
+ * You can now use the `registerTaskAsync` function to register the task:
32
+ *
33
+ * ```ts
34
+ * BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDENTIFIER, {});
35
+ * ```
36
+ */
37
+ export declare function registerTaskAsync(taskName: string, options?: BackgroundTaskOptions): Promise<void>;
38
+ /**
39
+ * Unregisters a background task, so the application will no longer be executing this task.
40
+ * @param taskName Name of the task to unregister.
41
+ * @return A promise which fulfils when the task is fully unregistered.
42
+ */
43
+ export declare function unregisterTaskAsync(taskName: string): Promise<void>;
44
+ /**
45
+ * When in debug mode this function will trigger running the background tasks.
46
+ * This function will only work for apps built in debug mode.
47
+ * This method is only available in development mode. It will not work in production builds.
48
+ * @returns A promise which fulfils when the task is triggered.
49
+ */
50
+ export declare function triggerTaskWorkerForTestingAsync(): Promise<boolean>;
51
+ /**
52
+ * Adds a listener that is called when the background executor expires. On iOS, tasks can run
53
+ * for minutes, but the system can interrupt the process at any time. This listener is called
54
+ * when the system decides to stop the background tasks and should be used to clean up resources
55
+ * or save state. When the expiry handler is called, the main task runner is rescheduled automatically.
56
+ * @platform ios
57
+ * @return An object with a `remove` method to unsubscribe the listener.
58
+ */
59
+ export declare function addExpirationListener(listener: () => void): {
60
+ remove: () => void;
61
+ };
62
+ export { BackgroundTaskStatus, BackgroundTaskResult, BackgroundTaskOptions, } from './BackgroundTask.types';
63
+ //# sourceMappingURL=BackgroundTask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackgroundTask.d.ts","sourceRoot":"","sources":["../src/BackgroundTask.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAwBrF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAa,OAAO,CAAC,oBAAoB,CAQnE,CAAC;AAGF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAGD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASzE;AAGD;;;;;GAKG;AACH,wBAAsB,gCAAgC,IAAI,OAAO,CAAC,OAAO,CAAC,CAUzE;AAGD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG;IAAE,MAAM,EAAE,MAAM,IAAI,CAAA;CAAE,CAKlF;AAGD,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,141 @@
1
+ import { isRunningInExpoGo } from 'expo';
2
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
3
+ import * as TaskManager from 'expo-task-manager';
4
+ import { BackgroundTaskStatus } from './BackgroundTask.types';
5
+ import ExpoBackgroundTaskModule from './ExpoBackgroundTaskModule';
6
+ // Flag to warn about running on Apple simulator
7
+ let warnAboutRunningOniOSSimulator = false;
8
+ let warnedAboutExpoGo = false;
9
+ function _validate(taskName) {
10
+ if (isRunningInExpoGo()) {
11
+ if (!warnedAboutExpoGo) {
12
+ const message = '`Background Task` functionality is not available in Expo Go:\n' +
13
+ 'You can use this API and any others in a development build. Learn more: https://expo.fyi/dev-client.';
14
+ console.warn(message);
15
+ warnedAboutExpoGo = true;
16
+ }
17
+ }
18
+ if (!taskName || typeof taskName !== 'string') {
19
+ throw new TypeError('`taskName` must be a non-empty string.');
20
+ }
21
+ }
22
+ // @needsAudit
23
+ /**
24
+ * Returns the status for the Background Task API. On web, it always returns `BackgroundTaskStatus.Restricted`,
25
+ * while on native platforms it returns `BackgroundTaskStatus.Available`.
26
+ *
27
+ * @returns A BackgroundTaskStatus enum value or `null` if not available.
28
+ */
29
+ export const getStatusAsync = async () => {
30
+ if (!ExpoBackgroundTaskModule.getStatusAsync) {
31
+ throw new UnavailabilityError('BackgroundTask', 'getStatusAsync');
32
+ }
33
+ return isRunningInExpoGo()
34
+ ? BackgroundTaskStatus.Restricted
35
+ : ExpoBackgroundTaskModule.getStatusAsync();
36
+ };
37
+ // @needsAudit
38
+ /**
39
+ * Registers a background task with the given name. Registered tasks are saved in persistent storage and restored once the app is initialized.
40
+ * @param taskName Name of the task to register. The task needs to be defined first - see [`TaskManager.defineTask`](task-manager/#taskmanagerdefinetasktaskname-taskexecutor)
41
+ * for more details.
42
+ * @param options An object containing the background task options.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * import * as TaskManager from 'expo-task-manager';
47
+ *
48
+ * // Register the task outside of the component
49
+ * TaskManager.defineTask(BACKGROUND_TASK_IDENTIFIER, () => {
50
+ * try {
51
+ * await AsyncStorage.setItem(LAST_TASK_DATE_KEY, Date.now().toString());
52
+ * } catch (error) {
53
+ * console.error('Failed to save the last fetch date', error);
54
+ * return BackgroundTaskResult.Failed;
55
+ * }
56
+ * return BackgroundTaskResult.Success;
57
+ * });
58
+ * ```
59
+ *
60
+ * You can now use the `registerTaskAsync` function to register the task:
61
+ *
62
+ * ```ts
63
+ * BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDENTIFIER, {});
64
+ * ```
65
+ */
66
+ export async function registerTaskAsync(taskName, options = {}) {
67
+ if (!ExpoBackgroundTaskModule.registerTaskAsync) {
68
+ throw new UnavailabilityError('BackgroundTask', 'registerTaskAsync');
69
+ }
70
+ if (!TaskManager.isTaskDefined(taskName)) {
71
+ throw new Error(`Task '${taskName}' is not defined. You must define a task using TaskManager.defineTask before registering.`);
72
+ }
73
+ if ((await ExpoBackgroundTaskModule.getStatusAsync()) === BackgroundTaskStatus.Restricted) {
74
+ if (!warnAboutRunningOniOSSimulator) {
75
+ const message = Platform.OS === 'ios'
76
+ ? `Background tasks are not supported on iOS simulators. Skipped registering task: ${taskName}.`
77
+ : `Background tasks are not available in the current environment. Skipped registering task: ${taskName}.`;
78
+ console.warn(message);
79
+ warnAboutRunningOniOSSimulator = true;
80
+ }
81
+ return;
82
+ }
83
+ _validate(taskName);
84
+ if (await TaskManager.isTaskRegisteredAsync(taskName)) {
85
+ return;
86
+ }
87
+ await ExpoBackgroundTaskModule.registerTaskAsync(taskName, options);
88
+ }
89
+ // @needsAudit
90
+ /**
91
+ * Unregisters a background task, so the application will no longer be executing this task.
92
+ * @param taskName Name of the task to unregister.
93
+ * @return A promise which fulfils when the task is fully unregistered.
94
+ */
95
+ export async function unregisterTaskAsync(taskName) {
96
+ if (!ExpoBackgroundTaskModule.unregisterTaskAsync) {
97
+ throw new UnavailabilityError('BackgroundTask', 'unregisterTaskAsync');
98
+ }
99
+ _validate(taskName);
100
+ if (!(await TaskManager.isTaskRegisteredAsync(taskName))) {
101
+ return;
102
+ }
103
+ await ExpoBackgroundTaskModule.unregisterTaskAsync(taskName);
104
+ }
105
+ // @needsAudit
106
+ /**
107
+ * When in debug mode this function will trigger running the background tasks.
108
+ * This function will only work for apps built in debug mode.
109
+ * This method is only available in development mode. It will not work in production builds.
110
+ * @returns A promise which fulfils when the task is triggered.
111
+ */
112
+ export async function triggerTaskWorkerForTestingAsync() {
113
+ if (__DEV__) {
114
+ if (!ExpoBackgroundTaskModule.triggerTaskWorkerForTestingAsync) {
115
+ throw new UnavailabilityError('BackgroundTask', 'triggerTaskWorkerForTestingAsync');
116
+ }
117
+ console.log('Calling triggerTaskWorkerForTestingAsync');
118
+ return await ExpoBackgroundTaskModule.triggerTaskWorkerForTestingAsync();
119
+ }
120
+ else {
121
+ return Promise.resolve(false);
122
+ }
123
+ }
124
+ // @needsAudit
125
+ /**
126
+ * Adds a listener that is called when the background executor expires. On iOS, tasks can run
127
+ * for minutes, but the system can interrupt the process at any time. This listener is called
128
+ * when the system decides to stop the background tasks and should be used to clean up resources
129
+ * or save state. When the expiry handler is called, the main task runner is rescheduled automatically.
130
+ * @platform ios
131
+ * @return An object with a `remove` method to unsubscribe the listener.
132
+ */
133
+ export function addExpirationListener(listener) {
134
+ if (!ExpoBackgroundTaskModule.addListener) {
135
+ throw new UnavailabilityError('BackgroundTask', 'addListener');
136
+ }
137
+ return ExpoBackgroundTaskModule.addListener('onTasksExpired', listener);
138
+ }
139
+ // Export types
140
+ export { BackgroundTaskStatus, BackgroundTaskResult, } from './BackgroundTask.types';
141
+ //# sourceMappingURL=BackgroundTask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackgroundTask.js","sourceRoot":"","sources":["../src/BackgroundTask.ts"],"names":[],"mappings":"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","sourcesContent":["import { isRunningInExpoGo } from 'expo';\nimport { Platform, UnavailabilityError } from 'expo-modules-core';\nimport * as TaskManager from 'expo-task-manager';\n\nimport { BackgroundTaskOptions, BackgroundTaskStatus } from './BackgroundTask.types';\nimport ExpoBackgroundTaskModule from './ExpoBackgroundTaskModule';\n\n// Flag to warn about running on Apple simulator\nlet warnAboutRunningOniOSSimulator = false;\n\nlet warnedAboutExpoGo = false;\n\nfunction _validate(taskName: unknown) {\n if (isRunningInExpoGo()) {\n if (!warnedAboutExpoGo) {\n const message =\n '`Background Task` functionality is not available in Expo Go:\\n' +\n 'You can use this API and any others in a development build. Learn more: https://expo.fyi/dev-client.';\n console.warn(message);\n warnedAboutExpoGo = true;\n }\n }\n if (!taskName || typeof taskName !== 'string') {\n throw new TypeError('`taskName` must be a non-empty string.');\n }\n}\n\n// @needsAudit\n/**\n * Returns the status for the Background Task API. On web, it always returns `BackgroundTaskStatus.Restricted`,\n * while on native platforms it returns `BackgroundTaskStatus.Available`.\n *\n * @returns A BackgroundTaskStatus enum value or `null` if not available.\n */\nexport const getStatusAsync = async (): Promise<BackgroundTaskStatus> => {\n if (!ExpoBackgroundTaskModule.getStatusAsync) {\n throw new UnavailabilityError('BackgroundTask', 'getStatusAsync');\n }\n\n return isRunningInExpoGo()\n ? BackgroundTaskStatus.Restricted\n : ExpoBackgroundTaskModule.getStatusAsync();\n};\n\n// @needsAudit\n/**\n * Registers a background task with the given name. Registered tasks are saved in persistent storage and restored once the app is initialized.\n * @param taskName Name of the task to register. The task needs to be defined first - see [`TaskManager.defineTask`](task-manager/#taskmanagerdefinetasktaskname-taskexecutor)\n * for more details.\n * @param options An object containing the background task options.\n *\n * @example\n * ```ts\n * import * as TaskManager from 'expo-task-manager';\n *\n * // Register the task outside of the component\n * TaskManager.defineTask(BACKGROUND_TASK_IDENTIFIER, () => {\n * try {\n * await AsyncStorage.setItem(LAST_TASK_DATE_KEY, Date.now().toString());\n * } catch (error) {\n * console.error('Failed to save the last fetch date', error);\n * return BackgroundTaskResult.Failed;\n * }\n * return BackgroundTaskResult.Success;\n * });\n * ```\n *\n * You can now use the `registerTaskAsync` function to register the task:\n *\n * ```ts\n * BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDENTIFIER, {});\n * ```\n */\nexport async function registerTaskAsync(\n taskName: string,\n options: BackgroundTaskOptions = {}\n): Promise<void> {\n if (!ExpoBackgroundTaskModule.registerTaskAsync) {\n throw new UnavailabilityError('BackgroundTask', 'registerTaskAsync');\n }\n if (!TaskManager.isTaskDefined(taskName)) {\n throw new Error(\n `Task '${taskName}' is not defined. You must define a task using TaskManager.defineTask before registering.`\n );\n }\n\n if ((await ExpoBackgroundTaskModule.getStatusAsync()) === BackgroundTaskStatus.Restricted) {\n if (!warnAboutRunningOniOSSimulator) {\n const message =\n Platform.OS === 'ios'\n ? `Background tasks are not supported on iOS simulators. Skipped registering task: ${taskName}.`\n : `Background tasks are not available in the current environment. Skipped registering task: ${taskName}.`;\n console.warn(message);\n warnAboutRunningOniOSSimulator = true;\n }\n return;\n }\n _validate(taskName);\n if (await TaskManager.isTaskRegisteredAsync(taskName)) {\n return;\n }\n await ExpoBackgroundTaskModule.registerTaskAsync(taskName, options);\n}\n\n// @needsAudit\n/**\n * Unregisters a background task, so the application will no longer be executing this task.\n * @param taskName Name of the task to unregister.\n * @return A promise which fulfils when the task is fully unregistered.\n */\nexport async function unregisterTaskAsync(taskName: string): Promise<void> {\n if (!ExpoBackgroundTaskModule.unregisterTaskAsync) {\n throw new UnavailabilityError('BackgroundTask', 'unregisterTaskAsync');\n }\n _validate(taskName);\n if (!(await TaskManager.isTaskRegisteredAsync(taskName))) {\n return;\n }\n await ExpoBackgroundTaskModule.unregisterTaskAsync(taskName);\n}\n\n// @needsAudit\n/**\n * When in debug mode this function will trigger running the background tasks.\n * This function will only work for apps built in debug mode.\n * This method is only available in development mode. It will not work in production builds.\n * @returns A promise which fulfils when the task is triggered.\n */\nexport async function triggerTaskWorkerForTestingAsync(): Promise<boolean> {\n if (__DEV__) {\n if (!ExpoBackgroundTaskModule.triggerTaskWorkerForTestingAsync) {\n throw new UnavailabilityError('BackgroundTask', 'triggerTaskWorkerForTestingAsync');\n }\n console.log('Calling triggerTaskWorkerForTestingAsync');\n return await ExpoBackgroundTaskModule.triggerTaskWorkerForTestingAsync();\n } else {\n return Promise.resolve(false);\n }\n}\n\n// @needsAudit\n/**\n * Adds a listener that is called when the background executor expires. On iOS, tasks can run\n * for minutes, but the system can interrupt the process at any time. This listener is called\n * when the system decides to stop the background tasks and should be used to clean up resources\n * or save state. When the expiry handler is called, the main task runner is rescheduled automatically.\n * @platform ios\n * @return An object with a `remove` method to unsubscribe the listener.\n */\nexport function addExpirationListener(listener: () => void): { remove: () => void } {\n if (!ExpoBackgroundTaskModule.addListener) {\n throw new UnavailabilityError('BackgroundTask', 'addListener');\n }\n return ExpoBackgroundTaskModule.addListener('onTasksExpired', listener);\n}\n\n// Export types\nexport {\n BackgroundTaskStatus,\n BackgroundTaskResult,\n BackgroundTaskOptions,\n} from './BackgroundTask.types';\n"]}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Availability status for background tasks
3
+ */
4
+ export declare enum BackgroundTaskStatus {
5
+ /**
6
+ * Background tasks are unavailable.
7
+ */
8
+ Restricted = 1,
9
+ /**
10
+ * Background tasks are available for the app.
11
+ */
12
+ Available = 2
13
+ }
14
+ /**
15
+ * Return value for background tasks.
16
+ */
17
+ export declare enum BackgroundTaskResult {
18
+ /**
19
+ * The task finished successfully.
20
+ */
21
+ Success = 1,
22
+ /**
23
+ * The task failed.
24
+ */
25
+ Failed = 2
26
+ }
27
+ /**
28
+ * Options for registering a background task
29
+ */
30
+ export type BackgroundTaskOptions = {
31
+ /**
32
+ * Inexact interval in minutes between subsequent repeats of the background tasks. The final
33
+ * interval may differ from the specified one to minimize wakeups and battery usage.
34
+ * - Defaults to once every 12 hours (The minimum interval is 15 minutes)
35
+ * - The system controls the background task execution interval and treats the
36
+ * specified value as a minimum delay. Tasks won't run exactly on schedule. On iOS, short
37
+ * intervals are often ignored—the system typically runs background tasks during
38
+ * specific windows, such as overnight.
39
+ *
40
+ */
41
+ minimumInterval?: number;
42
+ /**
43
+ * Whether the task requires network connectivity.
44
+ *
45
+ * @default true
46
+ * @platform ios, android
47
+ */
48
+ requiresNetworkConnectivity?: boolean;
49
+ /**
50
+ * iOS-only: Which BGTask type to use.
51
+ * - `'refresh'` (default): BGAppRefreshTaskRequest — aggressive scheduling (~15-30 min),
52
+ * ~30 s execution limit. Suitable for periodic syncs and content updates.
53
+ * - `'processing'`: BGProcessingTaskRequest — iOS defers at its own discretion
54
+ * (hours/days), but allows several minutes of runtime. Suitable for heavy work
55
+ * (ML training, DB migration). Supports `requiresNetworkConnectivity` and
56
+ * `requiresExternalPower`.
57
+ *
58
+ * @default 'refresh'
59
+ * @platform ios
60
+ */
61
+ iosTaskType?: 'refresh' | 'processing';
62
+ /**
63
+ * iOS-only: Whether the task requires external power.
64
+ * Only relevant when `iosTaskType: 'processing'`.
65
+ *
66
+ * @default false
67
+ * @platform ios
68
+ */
69
+ requiresExternalPower?: boolean;
70
+ };
71
+ //# sourceMappingURL=BackgroundTask.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackgroundTask.types.d.ts","sourceRoot":"","sources":["../src/BackgroundTask.types.ts"],"names":[],"mappings":"AACA;;GAEG;AACH,oBAAY,oBAAoB;IAC9B;;OAEG;IACH,UAAU,IAAI;IACd;;OAEG;IACH,SAAS,IAAI;CACd;AAGD;;GAEG;AACH,oBAAY,oBAAoB;IAC9B;;OAEG;IACH,OAAO,IAAI;IACX;;OAEG;IACH,MAAM,IAAI;CACX;AAGD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;;;;;;OASG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IACvC;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC"}
@@ -0,0 +1,31 @@
1
+ // @needsAudit
2
+ /**
3
+ * Availability status for background tasks
4
+ */
5
+ export var BackgroundTaskStatus;
6
+ (function (BackgroundTaskStatus) {
7
+ /**
8
+ * Background tasks are unavailable.
9
+ */
10
+ BackgroundTaskStatus[BackgroundTaskStatus["Restricted"] = 1] = "Restricted";
11
+ /**
12
+ * Background tasks are available for the app.
13
+ */
14
+ BackgroundTaskStatus[BackgroundTaskStatus["Available"] = 2] = "Available";
15
+ })(BackgroundTaskStatus || (BackgroundTaskStatus = {}));
16
+ // @needsAudit
17
+ /**
18
+ * Return value for background tasks.
19
+ */
20
+ export var BackgroundTaskResult;
21
+ (function (BackgroundTaskResult) {
22
+ /**
23
+ * The task finished successfully.
24
+ */
25
+ BackgroundTaskResult[BackgroundTaskResult["Success"] = 1] = "Success";
26
+ /**
27
+ * The task failed.
28
+ */
29
+ BackgroundTaskResult[BackgroundTaskResult["Failed"] = 2] = "Failed";
30
+ })(BackgroundTaskResult || (BackgroundTaskResult = {}));
31
+ //# sourceMappingURL=BackgroundTask.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackgroundTask.types.js","sourceRoot":"","sources":["../src/BackgroundTask.types.ts"],"names":[],"mappings":"AAAA,cAAc;AACd;;GAEG;AACH,MAAM,CAAN,IAAY,oBASX;AATD,WAAY,oBAAoB;IAC9B;;OAEG;IACH,2EAAc,CAAA;IACd;;OAEG;IACH,yEAAa,CAAA;AACf,CAAC,EATW,oBAAoB,KAApB,oBAAoB,QAS/B;AAED,cAAc;AACd;;GAEG;AACH,MAAM,CAAN,IAAY,oBASX;AATD,WAAY,oBAAoB;IAC9B;;OAEG;IACH,qEAAW,CAAA;IACX;;OAEG;IACH,mEAAU,CAAA;AACZ,CAAC,EATW,oBAAoB,KAApB,oBAAoB,QAS/B","sourcesContent":["// @needsAudit\n/**\n * Availability status for background tasks\n */\nexport enum BackgroundTaskStatus {\n /**\n * Background tasks are unavailable.\n */\n Restricted = 1,\n /**\n * Background tasks are available for the app.\n */\n Available = 2,\n}\n\n// @needsAudit\n/**\n * Return value for background tasks.\n */\nexport enum BackgroundTaskResult {\n /**\n * The task finished successfully.\n */\n Success = 1,\n /**\n * The task failed.\n */\n Failed = 2,\n}\n\n// @needsAudit\n/**\n * Options for registering a background task\n */\nexport type BackgroundTaskOptions = {\n /**\n * Inexact interval in minutes between subsequent repeats of the background tasks. The final\n * interval may differ from the specified one to minimize wakeups and battery usage.\n * - Defaults to once every 12 hours (The minimum interval is 15 minutes)\n * - The system controls the background task execution interval and treats the\n * specified value as a minimum delay. Tasks won't run exactly on schedule. On iOS, short\n * intervals are often ignored—the system typically runs background tasks during\n * specific windows, such as overnight.\n *\n */\n minimumInterval?: number;\n /**\n * Whether the task requires network connectivity.\n *\n * @default true\n * @platform ios, android\n */\n requiresNetworkConnectivity?: boolean;\n /**\n * iOS-only: Which BGTask type to use.\n * - `'refresh'` (default): BGAppRefreshTaskRequest — aggressive scheduling (~15-30 min),\n * ~30 s execution limit. Suitable for periodic syncs and content updates.\n * - `'processing'`: BGProcessingTaskRequest — iOS defers at its own discretion\n * (hours/days), but allows several minutes of runtime. Suitable for heavy work\n * (ML training, DB migration). Supports `requiresNetworkConnectivity` and\n * `requiresExternalPower`.\n *\n * @default 'refresh'\n * @platform ios\n */\n iosTaskType?: 'refresh' | 'processing';\n /**\n * iOS-only: Whether the task requires external power.\n * Only relevant when `iosTaskType: 'processing'`.\n *\n * @default false\n * @platform ios\n */\n requiresExternalPower?: boolean;\n};\n"]}