expo-background-task 0.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 (47) hide show
  1. package/.eslintrc.js +2 -0
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +43 -0
  4. package/android/build.gradle +24 -0
  5. package/android/src/main/AndroidManifest.xml +3 -0
  6. package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskConsumer.kt +54 -0
  7. package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskExceptions.kt +13 -0
  8. package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskModule.kt +66 -0
  9. package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskScheduler.kt +212 -0
  10. package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskWork.kt +36 -0
  11. package/app.plugin.js +1 -0
  12. package/build/BackgroundTask.d.ts +51 -0
  13. package/build/BackgroundTask.d.ts.map +1 -0
  14. package/build/BackgroundTask.js +89 -0
  15. package/build/BackgroundTask.js.map +1 -0
  16. package/build/BackgroundTask.types.d.ts +41 -0
  17. package/build/BackgroundTask.types.d.ts.map +1 -0
  18. package/build/BackgroundTask.types.js +31 -0
  19. package/build/BackgroundTask.types.js.map +1 -0
  20. package/build/ExpoBackgroundTaskModule.d.ts +11 -0
  21. package/build/ExpoBackgroundTaskModule.d.ts.map +1 -0
  22. package/build/ExpoBackgroundTaskModule.js +3 -0
  23. package/build/ExpoBackgroundTaskModule.js.map +1 -0
  24. package/build/ExpoBackgroundTaskModule.web.d.ts +6 -0
  25. package/build/ExpoBackgroundTaskModule.web.d.ts.map +1 -0
  26. package/build/ExpoBackgroundTaskModule.web.js +7 -0
  27. package/build/ExpoBackgroundTaskModule.web.js.map +1 -0
  28. package/expo-module.config.json +11 -0
  29. package/ios/BackgorundTaskExceptions.swift +48 -0
  30. package/ios/BackgroundTaskAppDelegate.swift +44 -0
  31. package/ios/BackgroundTaskConstants.swift +13 -0
  32. package/ios/BackgroundTaskConsumer.swift +57 -0
  33. package/ios/BackgroundTaskDebugHelper.swift +21 -0
  34. package/ios/BackgroundTaskModule.swift +78 -0
  35. package/ios/BackgroundTaskRecords.swift +12 -0
  36. package/ios/BackgroundTaskScheduler.swift +101 -0
  37. package/ios/ExpoBackgroundTask.podspec +32 -0
  38. package/package.json +43 -0
  39. package/plugin/build/withBackgroundTask.d.ts +3 -0
  40. package/plugin/build/withBackgroundTask.js +29 -0
  41. package/plugin/src/withBackgroundTask.ts +36 -0
  42. package/plugin/tsconfig.json +9 -0
  43. package/src/BackgroundTask.ts +105 -0
  44. package/src/BackgroundTask.types.ts +45 -0
  45. package/src/ExpoBackgroundTaskModule.ts +12 -0
  46. package/src/ExpoBackgroundTaskModule.web.ts +7 -0
  47. package/tsconfig.json +9 -0
@@ -0,0 +1,41 @@
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
+ * - On iOS, the system determines the interval for background task execution,
36
+ * but will wait until the specified minimum interval has elapsed before starting a task.
37
+ * @platform android
38
+ */
39
+ minimumInterval?: number;
40
+ };
41
+ //# 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;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,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 * - On iOS, the system determines the interval for background task execution,\n * but will wait until the specified minimum interval has elapsed before starting a task.\n * @platform android\n */\n minimumInterval?: number;\n};\n"]}
@@ -0,0 +1,11 @@
1
+ import { type NativeModule } from 'expo';
2
+ import { BackgroundTaskOptions, BackgroundTaskStatus } from './BackgroundTask.types';
3
+ declare class ExpoBackgroundTaskModule extends NativeModule {
4
+ getStatusAsync(): Promise<BackgroundTaskStatus>;
5
+ registerTaskAsync(name: string, options: BackgroundTaskOptions): Promise<void>;
6
+ unregisterTaskAsync(name: string): Promise<void>;
7
+ triggerTaskWorkerForTestingAsync(): Promise<boolean>;
8
+ }
9
+ declare const _default: ExpoBackgroundTaskModule;
10
+ export default _default;
11
+ //# sourceMappingURL=ExpoBackgroundTaskModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoBackgroundTaskModule.d.ts","sourceRoot":"","sources":["../src/ExpoBackgroundTaskModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,MAAM,CAAC;AAE9D,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAErF,OAAO,OAAO,wBAAyB,SAAQ,YAAY;IACzD,cAAc,IAAI,OAAO,CAAC,oBAAoB,CAAC;IAC/C,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9E,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAChD,gCAAgC,IAAI,OAAO,CAAC,OAAO,CAAC;CACrD;;AAED,wBAAmF"}
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo';
2
+ export default requireNativeModule('ExpoBackgroundTask');
3
+ //# sourceMappingURL=ExpoBackgroundTaskModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoBackgroundTaskModule.js","sourceRoot":"","sources":["../src/ExpoBackgroundTaskModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAqB,MAAM,MAAM,CAAC;AAW9D,eAAe,mBAAmB,CAA2B,oBAAoB,CAAC,CAAC","sourcesContent":["import { requireNativeModule, type NativeModule } from 'expo';\n\nimport { BackgroundTaskOptions, BackgroundTaskStatus } from './BackgroundTask.types';\n\ndeclare class ExpoBackgroundTaskModule extends NativeModule {\n getStatusAsync(): Promise<BackgroundTaskStatus>;\n registerTaskAsync(name: string, options: BackgroundTaskOptions): Promise<void>;\n unregisterTaskAsync(name: string): Promise<void>;\n triggerTaskWorkerForTestingAsync(): Promise<boolean>;\n}\n\nexport default requireNativeModule<ExpoBackgroundTaskModule>('ExpoBackgroundTask');\n"]}
@@ -0,0 +1,6 @@
1
+ import { BackgroundTaskStatus } from './BackgroundTask.types';
2
+ declare const _default: {
3
+ getStatusAsync(): Promise<BackgroundTaskStatus>;
4
+ };
5
+ export default _default;
6
+ //# sourceMappingURL=ExpoBackgroundTaskModule.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoBackgroundTaskModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoBackgroundTaskModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;;sBAGpC,QAAQ,oBAAoB,CAAC;;AADvD,wBAIE"}
@@ -0,0 +1,7 @@
1
+ import { BackgroundTaskStatus } from './BackgroundTask.types';
2
+ export default {
3
+ async getStatusAsync() {
4
+ return BackgroundTaskStatus.Restricted;
5
+ },
6
+ };
7
+ //# sourceMappingURL=ExpoBackgroundTaskModule.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoBackgroundTaskModule.web.js","sourceRoot":"","sources":["../src/ExpoBackgroundTaskModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,eAAe;IACb,KAAK,CAAC,cAAc;QAClB,OAAO,oBAAoB,CAAC,UAAU,CAAC;IACzC,CAAC;CACF,CAAC","sourcesContent":["import { BackgroundTaskStatus } from './BackgroundTask.types';\n\nexport default {\n async getStatusAsync(): Promise<BackgroundTaskStatus> {\n return BackgroundTaskStatus.Restricted;\n },\n};\n"]}
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "expo-background-task",
3
+ "platforms": [ "ios", "android" ],
4
+ "ios": {
5
+ "appDelegateSubscribers": [ "BackgroundTaskAppDelegateSubscriber" ],
6
+ "modules": [ "BackgroundTaskModule" ]
7
+ },
8
+ "android": {
9
+ "modules": [ "expo.modules.backgroundtask.BackgroundTaskModule" ]
10
+ }
11
+ }
@@ -0,0 +1,48 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+ import ExpoModulesCore
3
+
4
+ internal final class BackgroundTasksNotConfigured: Exception {
5
+ override var reason: String {
6
+ "Background Task has not been configured. To enable it, add `process` to `UIBackgroundModes` in the application's Info.plist file"
7
+ }
8
+ }
9
+
10
+ internal final class BackgroundTasksRestricted: Exception {
11
+ override var reason: String {
12
+ #if targetEnvironment(simulator)
13
+ "Background Task is not available on simulators. Use a device to test it."
14
+ #else
15
+ "Background Task is not available in the current context."
16
+ #endif
17
+ }
18
+ }
19
+
20
+ internal final class TaskManagerNotFound: Exception {
21
+ override var reason: String {
22
+ "TaskManager not found. Are you sure that Expo modules are properly linked?"
23
+ }
24
+ }
25
+
26
+ internal final class CouldNotRegisterWorkerTask: GenericException<String> {
27
+ override var reason: String {
28
+ "Expo BackgroundTasks: The task could not be registered: \(param)"
29
+ }
30
+ }
31
+
32
+ internal final class CouldNotRegisterWorker: Exception {
33
+ override var reason: String {
34
+ "Expo BackgroundTasks: Could not register native worker task"
35
+ }
36
+ }
37
+
38
+ internal final class ErrorInvokingTaskHandler: Exception {
39
+ override var reason: String {
40
+ "Expo BackgroundTasks: An error occured when running the task handler"
41
+ }
42
+ }
43
+
44
+ internal final class InvalidFinishTaskRun: Exception {
45
+ override var reason: String {
46
+ "Expo BackgroundTasks: Tried to mark task run as finished when there are no task runs active"
47
+ }
48
+ }
@@ -0,0 +1,44 @@
1
+ // Copyright 2018-present 650 Industries. All rights reserved.
2
+
3
+ import Foundation
4
+ import BackgroundTasks
5
+ import ExpoModulesCore
6
+
7
+ public class BackgroundTaskAppDelegateSubscriber: ExpoAppDelegateSubscriber {
8
+ public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
9
+ if BackgroundTaskScheduler.supportsBackgroundTasks() {
10
+ BGTaskScheduler.shared.register(forTaskWithIdentifier: BackgroundTaskConstants.BackgroundWorkerIdentifier, using: nil) { task in
11
+ log.debug("Expo Background Tasks - starting background work")
12
+
13
+ // Set up expiration handler
14
+ task.expirationHandler = { ()
15
+ log.warn("Expo Background Tasks - task expired")
16
+ task.setTaskCompleted(success: false)
17
+ }
18
+
19
+ // Let's find the task service implementation and call the runTasks(withReason)
20
+ if let taskService = ModuleRegistryProvider.singletonModules().first(where: { $0 is EXTaskServiceInterface }) as? EXTaskServiceInterface {
21
+ taskService.runTasks(with: EXTaskLaunchReasonBackgroundTask, userInfo: nil, completionHandler: { _ in
22
+ // Mark iOS task as finished - this is important so that we can continue calling it
23
+ task.setTaskCompleted(success: true)
24
+
25
+ // Reschedule
26
+ Task {
27
+ do {
28
+ log.debug("Background task successfully finished. Rescheduling")
29
+ try await BackgroundTaskScheduler.tryScheduleWorker()
30
+ } catch {
31
+ log.error("Could not reschedule the worker after task finished: \(error.localizedDescription)")
32
+ }
33
+ }
34
+ })
35
+ } else {
36
+ task.setTaskCompleted(success: false)
37
+ log.error("Expo Background Tasks: Could not find TaskService module")
38
+ }
39
+ }
40
+ }
41
+
42
+ return true
43
+ }
44
+ }
@@ -0,0 +1,13 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+ import Foundation
3
+
4
+ public struct BackgroundTaskConstants {
5
+ public static let BackgroundWorkerIdentifier = "com.expo.modules.backgroundtask.processing"
6
+ public static let EVENT_PERFORM_WORK = "onPerformWork"
7
+ public static let EVENT_WORK_DONE = "onWorkDone"
8
+
9
+ /**
10
+ Startup argument that will cause us to simulate starting from the background
11
+ */
12
+ public static let EXPO_RUN_BACKGROUND_TASK = "EXPO_RUN_BACKGROUND_TASK"
13
+ }
@@ -0,0 +1,57 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+ import ExpoModulesCore
3
+
4
+ class BackgroundTaskConsumer: NSObject, EXTaskConsumerInterface {
5
+ var task: EXTaskInterface?
6
+
7
+ static func supportsLaunchReason(_ launchReason: EXTaskLaunchReason) -> Bool {
8
+ return launchReason == EXTaskLaunchReasonBackgroundTask
9
+ }
10
+
11
+ func taskType() -> String {
12
+ return "backgroundTask"
13
+ }
14
+
15
+ func normalizeTaskResult(_ result: Any?) -> UInt {
16
+ guard let result = result as? Int else {
17
+ return UIBackgroundFetchResult.noData.rawValue
18
+ }
19
+
20
+ switch result {
21
+ case BackgroundTaskResult.success.rawValue:
22
+ return UIBackgroundFetchResult.newData.rawValue
23
+ case BackgroundTaskResult.failed.rawValue:
24
+ return UIBackgroundFetchResult.failed.rawValue
25
+ default:
26
+ return UIBackgroundFetchResult.newData.rawValue
27
+ }
28
+ }
29
+
30
+ func didBecomeReadyToExecute(withData data: [AnyHashable: Any]?) {
31
+ // Run on main thread. The task execution needs to be called on the main thread
32
+ // since it accesses the UIApplication's state
33
+ EXUtilities.performSynchronously {
34
+ self.task?.execute(withData: data, withError: nil)
35
+ }
36
+ }
37
+
38
+ func didRegisterTask(_ task: EXTaskInterface) {
39
+ self.task = task
40
+
41
+ if !BackgroundTaskScheduler.supportsBackgroundTasks() {
42
+ return
43
+ }
44
+
45
+ BackgroundTaskScheduler.didRegisterTask(minutes: self.task?.options?["minimumInterval"] as? Int)
46
+ }
47
+
48
+ func didUnregister() {
49
+ self.task = nil
50
+
51
+ if !BackgroundTaskScheduler.supportsBackgroundTasks() {
52
+ return
53
+ }
54
+
55
+ BackgroundTaskScheduler.didUnregisterTask()
56
+ }
57
+ }
@@ -0,0 +1,21 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+ import BackgroundTasks
3
+
4
+ final class BackgroundTaskDebugHelper {
5
+ static func triggerBackgroundTaskTest() {
6
+ #if DEBUG
7
+ let selector = NSSelectorFromString("_simulate".appending("LaunchForTaskWithIdentifier:"))
8
+
9
+ if let method = class_getInstanceMethod(BGTaskScheduler.self, selector) {
10
+ typealias MethodImplementation = @convention(c) (AnyObject, Selector, String) -> Void
11
+ let implementation = unsafeBitCast(method_getImplementation(method), to: MethodImplementation.self)
12
+
13
+ implementation(BGTaskScheduler.shared, selector, BackgroundTaskConstants.BackgroundWorkerIdentifier)
14
+ } else {
15
+ print("BackgroundTaskScheduler: _simulateLaunchForTaskWithIdentifier method not found on BGTaskScheduler.")
16
+ }
17
+ #else
18
+ fatalError("Triggering background tasks are not allowed in release builds.")
19
+ #endif
20
+ }
21
+ }
@@ -0,0 +1,78 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+ import ExpoModulesCore
3
+
4
+ public class BackgroundTaskModule: Module {
5
+ private var taskManager: EXTaskManagerInterface?
6
+
7
+ public func definition() -> ModuleDefinition {
8
+ Name("ExpoBackgroundTask")
9
+
10
+ OnCreate {
11
+ taskManager = appContext?.legacyModule(implementing: EXTaskManagerInterface.self)
12
+ }
13
+
14
+ AsyncFunction("triggerTaskWorkerForTestingAsync") {
15
+ if await BackgroundTaskScheduler.isWorkerRunning() {
16
+ BackgroundTaskDebugHelper.triggerBackgroundTaskTest()
17
+ return true
18
+ }
19
+ return false
20
+ }
21
+
22
+ AsyncFunction("registerTaskAsync") { (name: String, options: [String: Any]) in
23
+ guard let taskManager else {
24
+ throw TaskManagerNotFound()
25
+ }
26
+
27
+ if !BackgroundTaskScheduler.supportsBackgroundTasks() {
28
+ throw BackgroundTasksRestricted()
29
+ }
30
+
31
+ if !taskManager.hasBackgroundModeEnabled("processing") {
32
+ throw BackgroundTasksNotConfigured()
33
+ }
34
+
35
+ // Register task
36
+ taskManager.registerTask(withName: name, consumer: BackgroundTaskConsumer.self, options: options)
37
+ }
38
+
39
+ AsyncFunction("unregisterTaskAsync") { (name: String) in
40
+ guard let taskManager else {
41
+ throw TaskManagerNotFound()
42
+ }
43
+
44
+ if !BackgroundTaskScheduler.supportsBackgroundTasks() {
45
+ throw BackgroundTasksRestricted()
46
+ }
47
+
48
+ if !taskManager.hasBackgroundModeEnabled("processing") {
49
+ throw BackgroundTasksNotConfigured()
50
+ }
51
+
52
+ taskManager.unregisterTask(withName: name, consumerClass: BackgroundTaskConsumer.self)
53
+ }
54
+
55
+ AsyncFunction("getStatusAsync") {
56
+ return BackgroundTaskScheduler.supportsBackgroundTasks() ?
57
+ BackgroundTaskStatus.available : .restricted
58
+ }
59
+
60
+ OnAppEntersBackground {
61
+ Task {
62
+ // Try start worker when app enters background
63
+ do {
64
+ try await BackgroundTaskScheduler.tryScheduleWorker()
65
+ } catch {
66
+ log.error("Could not schedule the worker: \(error.localizedDescription)")
67
+ }
68
+ }
69
+ }
70
+
71
+ OnAppEntersForeground {
72
+ Task {
73
+ // When entering foreground we'll stop the worker
74
+ await BackgroundTaskScheduler.stopWorker()
75
+ }
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,12 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+ import ExpoModulesCore
3
+
4
+ enum BackgroundTaskStatus: Int, Enumerable {
5
+ case restricted = 1
6
+ case available = 2
7
+ }
8
+
9
+ enum BackgroundTaskResult: Int, Enumerable {
10
+ case success = 1
11
+ case failed = 2
12
+ }
@@ -0,0 +1,101 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+ import BackgroundTasks
3
+
4
+ public class BackgroundTaskScheduler {
5
+ /**
6
+ * Keep track of number of registered task consumers
7
+ */
8
+ static var numberOfRegisteredTasksOfThisType: Int = 0
9
+
10
+ /**
11
+ * Interval for task scheduler. The iOS BGTaskScheduler does not guarantee that the number of minutes will be
12
+ * exact, but it indicates when we'd like the task to start. This will be set to at least 12 hours
13
+ */
14
+ private static var intervalSeconds: TimeInterval = 12 * 60 * 60
15
+
16
+ /**
17
+ * Call when a task is registered to keep track of how many background task consumers we have
18
+ */
19
+ public static func didRegisterTask(minutes: Int?) {
20
+ if let minutes = minutes {
21
+ intervalSeconds = Double(minutes) * 60
22
+ }
23
+ numberOfRegisteredTasksOfThisType += 1
24
+ }
25
+
26
+ /**
27
+ * Call when a task is unregistered to keep track of how many background task consumers we have
28
+ */
29
+ public static func didUnregisterTask() {
30
+ numberOfRegisteredTasksOfThisType -= 1
31
+ }
32
+
33
+ /**
34
+ * Tries to schedule the worker task to run
35
+ */
36
+ public static func tryScheduleWorker() async throws {
37
+ if numberOfRegisteredTasksOfThisType == 0 {
38
+ print("Background Task: skipping scheduling. No registered tasks")
39
+ return
40
+ }
41
+
42
+ // Stop existing tasks
43
+ await stopWorker()
44
+
45
+ // Create request
46
+ let request = BGProcessingTaskRequest(identifier: BackgroundTaskConstants.BackgroundWorkerIdentifier)
47
+
48
+ // We'll require network but accept running on battery power.
49
+ request.requiresNetworkConnectivity = true
50
+ request.requiresExternalPower = false
51
+
52
+ // Set up mimimum start date
53
+ request.earliestBeginDate = Date().addingTimeInterval(intervalSeconds)
54
+
55
+ do {
56
+ try BGTaskScheduler.shared.submit(request)
57
+ } catch let error as BGTaskScheduler.Error {
58
+ switch error.code {
59
+ case .unavailable:
60
+ throw CouldNotRegisterWorkerTask("Background task scheduling is unavailable.")
61
+ case .tooManyPendingTaskRequests:
62
+ throw CouldNotRegisterWorkerTask("Too many pending task requests.")
63
+ case .notPermitted:
64
+ throw CouldNotRegisterWorkerTask("Task request not permitted.")
65
+ @unknown default:
66
+ print("An unknown BGTaskScheduler error occurred.")
67
+ // Handle any future cases added by Apple
68
+ }
69
+ } catch {
70
+ // All other errors
71
+ throw CouldNotRegisterWorkerTask("Unknown error occurred.")
72
+ }
73
+ }
74
+
75
+ /**
76
+ Cancels the worker task
77
+ */
78
+ public static func stopWorker() async {
79
+ BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundTaskConstants.BackgroundWorkerIdentifier)
80
+ }
81
+
82
+ /**
83
+ Returns true if the worker task is pending
84
+ */
85
+ public static func isWorkerRunning() async -> Bool {
86
+ let requests = await BGTaskScheduler.shared.pendingTaskRequests()
87
+ return requests.contains(where: { $0.identifier == BackgroundTaskConstants.BackgroundWorkerIdentifier })
88
+ }
89
+
90
+ /**
91
+ Returns true if we're on a device that supports background tasks
92
+ */
93
+ public static func supportsBackgroundTasks() -> Bool {
94
+ #if targetEnvironment(simulator)
95
+ // If we're on emulator we should definetly return restricted
96
+ return false
97
+ #else
98
+ return true
99
+ #endif
100
+ }
101
+ }
@@ -0,0 +1,32 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'ExpoBackgroundTask'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platforms = {
14
+ :ios => '15.1'
15
+ }
16
+ s.source = { git: 'https://github.com/expo/expo.git' }
17
+ s.static_framework = true
18
+
19
+ s.dependency 'ExpoModulesCore'
20
+ # Swift/Objective-C compatibility
21
+ s.pod_target_xcconfig = {
22
+ 'DEFINES_MODULE' => 'YES',
23
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule'
24
+ }
25
+
26
+ if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
27
+ s.source_files = "**/*.h"
28
+ s.vendored_frameworks = "#{s.name}.xcframework"
29
+ else
30
+ s.source_files = "**/*.{h,m,swift}"
31
+ end
32
+ end
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "expo-background-task",
3
+ "version": "0.0.0",
4
+ "description": "Expo universal module for BackgroundTask API",
5
+ "main": "build/BackgroundTask.js",
6
+ "types": "build/BackgroundTask.d.ts",
7
+ "sideEffects": false,
8
+ "scripts": {
9
+ "build": "expo-module build",
10
+ "clean": "expo-module clean",
11
+ "lint": "expo-module lint",
12
+ "test": "expo-module test",
13
+ "prepare": "expo-module prepare",
14
+ "prepublishOnly": "expo-module prepublishOnly",
15
+ "expo-module": "expo-module"
16
+ },
17
+ "keywords": [
18
+ "expo",
19
+ "react-native",
20
+ "background",
21
+ "background-task"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/expo/expo.git",
26
+ "directory": "packages/expo-background-task"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/expo/expo/issues"
30
+ },
31
+ "author": "650 Industries, Inc.",
32
+ "license": "MIT",
33
+ "homepage": "https://docs.expo.dev/versions/latest/sdk/background-task/",
34
+ "dependencies": {
35
+ "expo-task-manager": "~12.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "expo-module-scripts": "^4.0.0"
39
+ },
40
+ "peerDependencies": {
41
+ "expo": "*"
42
+ }
43
+ }
@@ -0,0 +1,3 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ declare const _default: ConfigPlugin<void>;
3
+ export default _default;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("expo/config-plugins");
4
+ const pkg = require('expo-background-task/package.json');
5
+ const withBackgroundTask = (config) => {
6
+ return (0, config_plugins_1.withInfoPlist)(config, (config) => {
7
+ // Enable background mode processing
8
+ if (!Array.isArray(config.modResults.UIBackgroundModes)) {
9
+ config.modResults.UIBackgroundModes = [];
10
+ }
11
+ if (!config.modResults.UIBackgroundModes.includes('processing')) {
12
+ config.modResults.UIBackgroundModes.push('processing');
13
+ }
14
+ // With the new background task module we need to install the identifier in the Info.plist:
15
+ // BGTaskSchedulerPermittedIdentifiers should be an array of strings - we need to
16
+ // define our own identifier: com.expo.modules.backgroundtask.taskidentifer
17
+ if (!Array.isArray(config.modResults.BGTaskSchedulerPermittedIdentifiers)) {
18
+ config.modResults.BGTaskSchedulerPermittedIdentifiers = [];
19
+ }
20
+ if (!config.modResults.BGTaskSchedulerPermittedIdentifiers.includes('com.expo.modules.backgroundtask.processing')) {
21
+ config.modResults.BGTaskSchedulerPermittedIdentifiers = [
22
+ ...(config.modResults.BGTaskSchedulerPermittedIdentifiers || []),
23
+ 'com.expo.modules.backgroundtask.processing',
24
+ ];
25
+ }
26
+ return config;
27
+ });
28
+ };
29
+ exports.default = (0, config_plugins_1.createRunOncePlugin)(withBackgroundTask, pkg.name, pkg.version);