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.
- package/.eslintrc.js +2 -0
- package/CHANGELOG.md +17 -0
- package/README.md +43 -0
- package/android/build.gradle +24 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskConsumer.kt +54 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskExceptions.kt +13 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskModule.kt +66 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskScheduler.kt +212 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskWork.kt +36 -0
- package/app.plugin.js +1 -0
- package/build/BackgroundTask.d.ts +51 -0
- package/build/BackgroundTask.d.ts.map +1 -0
- package/build/BackgroundTask.js +89 -0
- package/build/BackgroundTask.js.map +1 -0
- package/build/BackgroundTask.types.d.ts +41 -0
- package/build/BackgroundTask.types.d.ts.map +1 -0
- package/build/BackgroundTask.types.js +31 -0
- package/build/BackgroundTask.types.js.map +1 -0
- package/build/ExpoBackgroundTaskModule.d.ts +11 -0
- package/build/ExpoBackgroundTaskModule.d.ts.map +1 -0
- package/build/ExpoBackgroundTaskModule.js +3 -0
- package/build/ExpoBackgroundTaskModule.js.map +1 -0
- package/build/ExpoBackgroundTaskModule.web.d.ts +6 -0
- package/build/ExpoBackgroundTaskModule.web.d.ts.map +1 -0
- package/build/ExpoBackgroundTaskModule.web.js +7 -0
- package/build/ExpoBackgroundTaskModule.web.js.map +1 -0
- package/expo-module.config.json +11 -0
- package/ios/BackgorundTaskExceptions.swift +48 -0
- package/ios/BackgroundTaskAppDelegate.swift +44 -0
- package/ios/BackgroundTaskConstants.swift +13 -0
- package/ios/BackgroundTaskConsumer.swift +57 -0
- package/ios/BackgroundTaskDebugHelper.swift +21 -0
- package/ios/BackgroundTaskModule.swift +78 -0
- package/ios/BackgroundTaskRecords.swift +12 -0
- package/ios/BackgroundTaskScheduler.swift +101 -0
- package/ios/ExpoBackgroundTask.podspec +32 -0
- package/package.json +43 -0
- package/plugin/build/withBackgroundTask.d.ts +3 -0
- package/plugin/build/withBackgroundTask.js +29 -0
- package/plugin/src/withBackgroundTask.ts +36 -0
- package/plugin/tsconfig.json +9 -0
- package/src/BackgroundTask.ts +105 -0
- package/src/BackgroundTask.types.ts +45 -0
- package/src/ExpoBackgroundTaskModule.ts +12 -0
- package/src/ExpoBackgroundTaskModule.web.ts +7 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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,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);
|