@zykeco/expo-background-task 1.1.0 → 1.2.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.
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskModule.kt +25 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskScheduler.kt +44 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskWork.kt +6 -0
- package/build/BackgroundTask.d.ts +14 -5
- package/build/BackgroundTask.d.ts.map +1 -1
- package/build/BackgroundTask.js +14 -5
- package/build/BackgroundTask.js.map +1 -1
- package/ios/BackgroundTaskModule.swift +2 -3
- package/package.json +1 -1
- package/src/BackgroundTask.ts +14 -5
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package expo.modules.backgroundtask
|
|
2
2
|
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
3
5
|
import android.util.Log
|
|
4
6
|
import com.facebook.react.common.build.ReactBuildConfig
|
|
5
7
|
import expo.modules.interfaces.taskManager.TaskManagerInterface
|
|
@@ -19,6 +21,29 @@ class BackgroundTaskModule : Module() {
|
|
|
19
21
|
override fun definition() = ModuleDefinition {
|
|
20
22
|
Name("ExpoBackgroundTask")
|
|
21
23
|
|
|
24
|
+
Events("onTasksExpired")
|
|
25
|
+
|
|
26
|
+
OnStartObserving("onTasksExpired") {
|
|
27
|
+
BackgroundTaskScheduler.setOnTasksExpiredListener {
|
|
28
|
+
// onStopped() runs on a WorkManager thread — sendEvent requires main thread
|
|
29
|
+
Handler(Looper.getMainLooper()).post {
|
|
30
|
+
sendEvent("onTasksExpired", emptyMap<String, Any>())
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Deferred delivery: if a task was stopped before JS attached its listener,
|
|
34
|
+
// the flag is still pending — deliver now that JS is ready to receive it.
|
|
35
|
+
appContext.reactContext?.let { context ->
|
|
36
|
+
if (BackgroundTaskScheduler.consumeStoppedFlag(context)) {
|
|
37
|
+
Log.d(TAG, "Deferred task-stopped event — emitting onTasksExpired")
|
|
38
|
+
sendEvent("onTasksExpired", emptyMap<String, Any>())
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
OnStopObserving("onTasksExpired") {
|
|
44
|
+
BackgroundTaskScheduler.setOnTasksExpiredListener(null)
|
|
45
|
+
}
|
|
46
|
+
|
|
22
47
|
AsyncFunction("getStatusAsync") {
|
|
23
48
|
return@AsyncFunction 2 // WorkManager is always available on Android.
|
|
24
49
|
}
|
|
@@ -33,6 +33,10 @@ object BackgroundTaskScheduler {
|
|
|
33
33
|
// Unique identifier (generated by us) to identify the worker
|
|
34
34
|
private const val WORKER_IDENTIFIER = "EXPO_BACKGROUND_WORKER"
|
|
35
35
|
|
|
36
|
+
// SharedPreferences keys for deferred task-stopped delivery
|
|
37
|
+
private const val PREFS_NAME = "expo_background_task"
|
|
38
|
+
private const val KEY_TASK_WAS_STOPPED = "task_was_stopped"
|
|
39
|
+
|
|
36
40
|
// Log tag
|
|
37
41
|
private val TAG = BackgroundTaskScheduler::class.java.simpleName
|
|
38
42
|
|
|
@@ -54,6 +58,46 @@ object BackgroundTaskScheduler {
|
|
|
54
58
|
@Volatile
|
|
55
59
|
var inForeground: Boolean = false
|
|
56
60
|
|
|
61
|
+
// Listener for task stopped/expired events (set by BackgroundTaskModule)
|
|
62
|
+
// @Volatile: written on main thread (OnStartObserving), read on WorkManager thread (onStopped)
|
|
63
|
+
@Volatile
|
|
64
|
+
private var onTasksExpiredListener: (() -> Unit)? = null
|
|
65
|
+
|
|
66
|
+
fun setOnTasksExpiredListener(listener: (() -> Unit)?) {
|
|
67
|
+
onTasksExpiredListener = listener
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Called by BackgroundTaskWork.onStopped() when the system stops the worker.
|
|
72
|
+
* Notifies the listener in real-time (if JS is still running) and persists a flag
|
|
73
|
+
* as a fallback for deferred delivery on next foreground.
|
|
74
|
+
*/
|
|
75
|
+
fun onWorkerStopped(context: Context) {
|
|
76
|
+
Log.d(TAG, "Worker was stopped by the system — notifying listener")
|
|
77
|
+
// Try real-time notification (JS may still be running)
|
|
78
|
+
onTasksExpiredListener?.invoke()
|
|
79
|
+
// Persist flag as fallback for deferred delivery when JS attaches its listener.
|
|
80
|
+
// commit() is intentional: onStopped() runs as the process is being killed,
|
|
81
|
+
// so apply()'s async write may never flush to disk.
|
|
82
|
+
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
83
|
+
.edit()
|
|
84
|
+
.putBoolean(KEY_TASK_WAS_STOPPED, true)
|
|
85
|
+
.commit()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check and clear the deferred "task was stopped" flag.
|
|
90
|
+
* Returns true if a previous task was stopped by the system.
|
|
91
|
+
*/
|
|
92
|
+
fun consumeStoppedFlag(context: Context): Boolean {
|
|
93
|
+
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
94
|
+
val wasStopped = prefs.getBoolean(KEY_TASK_WAS_STOPPED, false)
|
|
95
|
+
if (wasStopped) {
|
|
96
|
+
prefs.edit().putBoolean(KEY_TASK_WAS_STOPPED, false).apply()
|
|
97
|
+
}
|
|
98
|
+
return wasStopped
|
|
99
|
+
}
|
|
100
|
+
|
|
57
101
|
/**
|
|
58
102
|
* Call when a task is registered
|
|
59
103
|
*/
|
|
@@ -35,4 +35,10 @@ class BackgroundTaskWork(context: Context, params: WorkerParameters) : Coroutine
|
|
|
35
35
|
}
|
|
36
36
|
return Result.success()
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
override fun onStopped() {
|
|
40
|
+
super.onStopped()
|
|
41
|
+
Log.d(TAG, "onStopped: Worker was stopped by the system")
|
|
42
|
+
BackgroundTaskScheduler.onWorkerStopped(applicationContext)
|
|
43
|
+
}
|
|
38
44
|
}
|
|
@@ -56,11 +56,20 @@ export declare function triggerTaskWorkerForTestingAsync(): Promise<boolean>;
|
|
|
56
56
|
*/
|
|
57
57
|
export declare function getSchedulerDiagnosticsAsync(): Promise<SchedulerDiagnostics>;
|
|
58
58
|
/**
|
|
59
|
-
* Adds a listener that is called when the
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
59
|
+
* Adds a listener that is called when the OS stops a running background task.
|
|
60
|
+
*
|
|
61
|
+
* On iOS, this fires via the BGTask expiration handler when iOS reclaims resources.
|
|
62
|
+
* On Android, this fires when WorkManager stops the worker (e.g., system constraints,
|
|
63
|
+
* memory pressure). If the event cannot be delivered in real-time, it is delivered
|
|
64
|
+
* when the app next enters the foreground.
|
|
65
|
+
*
|
|
66
|
+
* Use this to clean up resources or save state before the task is terminated.
|
|
67
|
+
* The task runner is rescheduled automatically after expiration.
|
|
68
|
+
*
|
|
69
|
+
* **Android note:** This event uses at-least-once delivery — the listener may fire
|
|
70
|
+
* again on next app start if real-time delivery was also successful. Listeners
|
|
71
|
+
* should be idempotent.
|
|
72
|
+
*
|
|
64
73
|
* @return An object with a `remove` method to unsubscribe the listener.
|
|
65
74
|
*/
|
|
66
75
|
export declare function addExpirationListener(listener: () => void): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BackgroundTask.d.ts","sourceRoot":"","sources":["../src/BackgroundTask.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAwB3G;;;;;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;;;;;GAKG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAKlF;AAGD
|
|
1
|
+
{"version":3,"file":"BackgroundTask.d.ts","sourceRoot":"","sources":["../src/BackgroundTask.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAwB3G;;;;;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;;;;;GAKG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAKlF;AAGD;;;;;;;;;;;;;;;;GAgBG;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,EACrB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC"}
|
package/build/BackgroundTask.js
CHANGED
|
@@ -136,11 +136,20 @@ export async function getSchedulerDiagnosticsAsync() {
|
|
|
136
136
|
}
|
|
137
137
|
// @needsAudit
|
|
138
138
|
/**
|
|
139
|
-
* Adds a listener that is called when the
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
139
|
+
* Adds a listener that is called when the OS stops a running background task.
|
|
140
|
+
*
|
|
141
|
+
* On iOS, this fires via the BGTask expiration handler when iOS reclaims resources.
|
|
142
|
+
* On Android, this fires when WorkManager stops the worker (e.g., system constraints,
|
|
143
|
+
* memory pressure). If the event cannot be delivered in real-time, it is delivered
|
|
144
|
+
* when the app next enters the foreground.
|
|
145
|
+
*
|
|
146
|
+
* Use this to clean up resources or save state before the task is terminated.
|
|
147
|
+
* The task runner is rescheduled automatically after expiration.
|
|
148
|
+
*
|
|
149
|
+
* **Android note:** This event uses at-least-once delivery — the listener may fire
|
|
150
|
+
* again on next app start if real-time delivery was also successful. Listeners
|
|
151
|
+
* should be idempotent.
|
|
152
|
+
*
|
|
144
153
|
* @return An object with a `remove` method to unsubscribe the listener.
|
|
145
154
|
*/
|
|
146
155
|
export function addExpirationListener(listener) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BackgroundTask.js","sourceRoot":"","sources":["../src/BackgroundTask.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAyB,oBAAoB,EAAwB,MAAM,wBAAwB,CAAC;AAC3G,OAAO,wBAAwB,MAAM,4BAA4B,CAAC;AAElE,gDAAgD;AAChD,IAAI,8BAA8B,GAAG,KAAK,CAAC;AAE3C,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAE9B,SAAS,SAAS,CAAC,QAAiB;IAClC,IAAI,iBAAiB,EAAE,EAAE,CAAC;QACxB,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,MAAM,OAAO,GACX,gEAAgE;gBAChE,sGAAsG,CAAC;YACzG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,iBAAiB,GAAG,IAAI,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC9C,MAAM,IAAI,SAAS,CAAC,wCAAwC,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,cAAc;AACd;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,IAAmC,EAAE;IACtE,IAAI,CAAC,wBAAwB,CAAC,cAAc,EAAE,CAAC;QAC7C,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,iBAAiB,EAAE;QACxB,CAAC,CAAC,oBAAoB,CAAC,UAAU;QACjC,CAAC,CAAC,wBAAwB,CAAC,cAAc,EAAE,CAAC;AAChD,CAAC,CAAC;AAEF,cAAc;AACd;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,UAAiC,EAAE;IAEnC,IAAI,CAAC,wBAAwB,CAAC,iBAAiB,EAAE,CAAC;QAChD,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,SAAS,QAAQ,2FAA2F,CAC7G,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,MAAM,wBAAwB,CAAC,cAAc,EAAE,CAAC,KAAK,oBAAoB,CAAC,UAAU,EAAE,CAAC;QAC1F,IAAI,CAAC,8BAA8B,EAAE,CAAC;YACpC,MAAM,OAAO,GACX,QAAQ,CAAC,EAAE,KAAK,KAAK;gBACnB,CAAC,CAAC,mFAAmF,QAAQ,GAAG;gBAChG,CAAC,CAAC,4FAA4F,QAAQ,GAAG,CAAC;YAC9G,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,8BAA8B,GAAG,IAAI,CAAC;QACxC,CAAC;QACD,OAAO;IACT,CAAC;IACD,SAAS,CAAC,QAAQ,CAAC,CAAC;IACpB,IAAI,MAAM,WAAW,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,OAAO;IACT,CAAC;IACD,MAAM,wBAAwB,CAAC,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AACtE,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAgB;IACxD,IAAI,CAAC,wBAAwB,CAAC,mBAAmB,EAAE,CAAC;QAClD,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,CAAC;IACzE,CAAC;IACD,SAAS,CAAC,QAAQ,CAAC,CAAC;IACpB,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QACzD,OAAO;IACT,CAAC;IACD,MAAM,wBAAwB,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AAC/D,CAAC;AAED,cAAc;AACd;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gCAAgC;IACpD,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC,wBAAwB,CAAC,gCAAgC,EAAE,CAAC;YAC/D,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,kCAAkC,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,OAAO,MAAM,wBAAwB,CAAC,gCAAgC,EAAE,CAAC;IAC3E,CAAC;SAAM,CAAC;QACN,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;AACH,CAAC;AAED,cAAc;AACd;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B;IAChD,IAAI,CAAC,wBAAwB,CAAC,4BAA4B,EAAE,CAAC;QAC3D,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,8BAA8B,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,MAAM,wBAAwB,CAAC,4BAA4B,EAAE,CAAC;AACvE,CAAC;AAED,cAAc;AACd;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAoB;IACxD,IAAI,CAAC,wBAAwB,CAAC,WAAW,EAAE,CAAC;QAC1C,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,wBAAwB,CAAC,WAAW,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;AAC1E,CAAC;AAED,eAAe;AACf,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GAGrB,MAAM,wBAAwB,CAAC","sourcesContent":["import { isRunningInExpoGo } from 'expo';\nimport { Platform, UnavailabilityError } from 'expo-modules-core';\nimport * as TaskManager from 'expo-task-manager';\n\nimport { BackgroundTaskOptions, BackgroundTaskStatus, SchedulerDiagnostics } 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 * Returns diagnostic information about the background task scheduler.\n * Useful for debugging whether tasks are registered and scheduled correctly.\n *\n * @returns A promise that resolves with scheduler diagnostics. Fields vary by platform.\n */\nexport async function getSchedulerDiagnosticsAsync(): Promise<SchedulerDiagnostics> {\n if (!ExpoBackgroundTaskModule.getSchedulerDiagnosticsAsync) {\n throw new UnavailabilityError('BackgroundTask', 'getSchedulerDiagnosticsAsync');\n }\n return await ExpoBackgroundTaskModule.getSchedulerDiagnosticsAsync();\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 SchedulerDiagnostics,\n} from './BackgroundTask.types';\n"]}
|
|
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, SchedulerDiagnostics } 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 * Returns diagnostic information about the background task scheduler.\n * Useful for debugging whether tasks are registered and scheduled correctly.\n *\n * @returns A promise that resolves with scheduler diagnostics. Fields vary by platform.\n */\nexport async function getSchedulerDiagnosticsAsync(): Promise<SchedulerDiagnostics> {\n if (!ExpoBackgroundTaskModule.getSchedulerDiagnosticsAsync) {\n throw new UnavailabilityError('BackgroundTask', 'getSchedulerDiagnosticsAsync');\n }\n return await ExpoBackgroundTaskModule.getSchedulerDiagnosticsAsync();\n}\n\n// @needsAudit\n/**\n * Adds a listener that is called when the OS stops a running background task.\n *\n * On iOS, this fires via the BGTask expiration handler when iOS reclaims resources.\n * On Android, this fires when WorkManager stops the worker (e.g., system constraints,\n * memory pressure). If the event cannot be delivered in real-time, it is delivered\n * when the app next enters the foreground.\n *\n * Use this to clean up resources or save state before the task is terminated.\n * The task runner is rescheduled automatically after expiration.\n *\n * **Android note:** This event uses at-least-once delivery — the listener may fire\n * again on next app start if real-time delivery was also successful. Listeners\n * should be idempotent.\n *\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 SchedulerDiagnostics,\n} from './BackgroundTask.types';\n"]}
|
|
@@ -75,10 +75,9 @@ public class BackgroundTaskModule: Module {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// No payload validation needed — onTasksExpiredNotification is a module-private
|
|
79
|
+
// Notification.Name posted only by BackgroundTaskAppDelegateSubscriber.
|
|
78
80
|
@objc func handleTasksExpiredNotification(_ notification: Notification) {
|
|
79
|
-
guard let url = notification.userInfo?["url"] as? URL else {
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
81
|
self.sendEvent(onTasksExpired, [:])
|
|
83
82
|
}
|
|
84
83
|
}
|
package/package.json
CHANGED
package/src/BackgroundTask.ts
CHANGED
|
@@ -154,11 +154,20 @@ export async function getSchedulerDiagnosticsAsync(): Promise<SchedulerDiagnosti
|
|
|
154
154
|
|
|
155
155
|
// @needsAudit
|
|
156
156
|
/**
|
|
157
|
-
* Adds a listener that is called when the
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
157
|
+
* Adds a listener that is called when the OS stops a running background task.
|
|
158
|
+
*
|
|
159
|
+
* On iOS, this fires via the BGTask expiration handler when iOS reclaims resources.
|
|
160
|
+
* On Android, this fires when WorkManager stops the worker (e.g., system constraints,
|
|
161
|
+
* memory pressure). If the event cannot be delivered in real-time, it is delivered
|
|
162
|
+
* when the app next enters the foreground.
|
|
163
|
+
*
|
|
164
|
+
* Use this to clean up resources or save state before the task is terminated.
|
|
165
|
+
* The task runner is rescheduled automatically after expiration.
|
|
166
|
+
*
|
|
167
|
+
* **Android note:** This event uses at-least-once delivery — the listener may fire
|
|
168
|
+
* again on next app start if real-time delivery was also successful. Listeners
|
|
169
|
+
* should be idempotent.
|
|
170
|
+
*
|
|
162
171
|
* @return An object with a `remove` method to unsubscribe the listener.
|
|
163
172
|
*/
|
|
164
173
|
export function addExpirationListener(listener: () => void): { remove: () => void } {
|