@capgo/capacitor-background-task 8.1.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/dist/plugin.js ADDED
@@ -0,0 +1,231 @@
1
+ var capacitorBackgroundTask = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ /**
5
+ * Return value for a background task callback.
6
+ */
7
+ exports.BackgroundTaskResult = void 0;
8
+ (function (BackgroundTaskResult) {
9
+ /**
10
+ * The task finished successfully.
11
+ */
12
+ BackgroundTaskResult[BackgroundTaskResult["Success"] = 1] = "Success";
13
+ /**
14
+ * The task failed.
15
+ */
16
+ BackgroundTaskResult[BackgroundTaskResult["Failed"] = 2] = "Failed";
17
+ })(exports.BackgroundTaskResult || (exports.BackgroundTaskResult = {}));
18
+ /**
19
+ * Availability status for background task scheduling.
20
+ */
21
+ exports.BackgroundTaskStatus = void 0;
22
+ (function (BackgroundTaskStatus) {
23
+ /**
24
+ * Background task scheduling is unavailable or restricted.
25
+ */
26
+ BackgroundTaskStatus[BackgroundTaskStatus["Restricted"] = 1] = "Restricted";
27
+ /**
28
+ * Background task scheduling is available.
29
+ */
30
+ BackgroundTaskStatus[BackgroundTaskStatus["Available"] = 2] = "Available";
31
+ })(exports.BackgroundTaskStatus || (exports.BackgroundTaskStatus = {}));
32
+
33
+ const DEFAULT_TASK_NAME = 'CapgoBackgroundTask';
34
+ const NativeBackgroundTask = core.registerPlugin('BackgroundTask', {
35
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.BackgroundTaskWeb()),
36
+ });
37
+ const callbacks = new Map();
38
+ const processedTaskIds = new Set();
39
+ let listenerReady;
40
+ let currentReactNativeTask;
41
+ const ensureListener = () => {
42
+ if (!listenerReady) {
43
+ listenerReady = NativeBackgroundTask.addListener('backgroundTask', (event) => {
44
+ void runTask(event);
45
+ }).then(() => undefined);
46
+ }
47
+ return listenerReady;
48
+ };
49
+ const drainPendingTaskRuns = async () => {
50
+ const { tasks } = await NativeBackgroundTask.getPendingTaskRuns();
51
+ await Promise.all(tasks.map((task) => runTask(task)));
52
+ };
53
+ const runTask = async (event) => {
54
+ if (processedTaskIds.has(event.taskId)) {
55
+ return;
56
+ }
57
+ const callback = callbacks.get(event.taskName);
58
+ if (!callback) {
59
+ return;
60
+ }
61
+ processedTaskIds.add(event.taskId);
62
+ try {
63
+ currentReactNativeTask = {
64
+ taskId: event.taskId,
65
+ taskName: event.taskName,
66
+ result: exports.BackgroundTaskResult.Success,
67
+ };
68
+ const result = await callback(event);
69
+ await NativeBackgroundTask.finish({
70
+ taskId: event.taskId,
71
+ taskName: event.taskName,
72
+ result: result !== null && result !== void 0 ? result : exports.BackgroundTaskResult.Success,
73
+ });
74
+ }
75
+ catch (_a) {
76
+ await NativeBackgroundTask.finish({
77
+ taskId: event.taskId,
78
+ taskName: event.taskName,
79
+ result: exports.BackgroundTaskResult.Failed,
80
+ });
81
+ }
82
+ finally {
83
+ currentReactNativeTask = undefined;
84
+ }
85
+ };
86
+ const assertTaskName = (taskName) => {
87
+ if (!taskName || typeof taskName !== 'string') {
88
+ throw new Error('taskName is required');
89
+ }
90
+ };
91
+ const normalizeReactNativeOptions = (options) => {
92
+ var _a;
93
+ const period = (_a = options === null || options === void 0 ? void 0 : options.period) !== null && _a !== void 0 ? _a : 900;
94
+ return {
95
+ minimumInterval: Math.max(15, Math.ceil(period / 60)),
96
+ };
97
+ };
98
+ const BackgroundTask = {
99
+ defineTask(taskName, callback) {
100
+ assertTaskName(taskName);
101
+ callbacks.set(taskName, callback);
102
+ void ensureListener().then(drainPendingTaskRuns);
103
+ },
104
+ async registerTaskAsync(taskName, options) {
105
+ assertTaskName(taskName);
106
+ if (!callbacks.has(taskName)) {
107
+ throw new Error(`Task "${taskName}" must be defined before it is registered.`);
108
+ }
109
+ await NativeBackgroundTask.registerTask({ taskName, options });
110
+ await ensureListener();
111
+ await drainPendingTaskRuns();
112
+ },
113
+ async unregisterTaskAsync(taskName) {
114
+ assertTaskName(taskName);
115
+ await NativeBackgroundTask.unregisterTask({ taskName });
116
+ },
117
+ async isTaskRegisteredAsync(taskName) {
118
+ assertTaskName(taskName);
119
+ const { value } = await NativeBackgroundTask.isTaskRegistered({ taskName });
120
+ return value;
121
+ },
122
+ async getRegisteredTasksAsync() {
123
+ const { tasks } = await NativeBackgroundTask.getRegisteredTasks();
124
+ return tasks;
125
+ },
126
+ async getPendingTaskRunsAsync() {
127
+ const { tasks } = await NativeBackgroundTask.getPendingTaskRuns();
128
+ return tasks;
129
+ },
130
+ async getStatusAsync() {
131
+ const { status } = await NativeBackgroundTask.getStatus();
132
+ return status;
133
+ },
134
+ async triggerTaskWorkerForTestingAsync() {
135
+ await ensureListener();
136
+ const { value } = await NativeBackgroundTask.triggerTaskWorkerForTesting();
137
+ await drainPendingTaskRuns();
138
+ return value;
139
+ },
140
+ addExpirationListener(listener) {
141
+ return NativeBackgroundTask.addListener('expiration', listener);
142
+ },
143
+ define(callback) {
144
+ this.defineTask(DEFAULT_TASK_NAME, callback);
145
+ },
146
+ schedule(options) {
147
+ return this.registerTaskAsync(DEFAULT_TASK_NAME, normalizeReactNativeOptions(options));
148
+ },
149
+ cancel() {
150
+ return this.unregisterTaskAsync(DEFAULT_TASK_NAME);
151
+ },
152
+ async statusAsync() {
153
+ const status = await this.getStatusAsync();
154
+ if (status === exports.BackgroundTaskStatus.Available) {
155
+ return { available: true };
156
+ }
157
+ return {
158
+ available: false,
159
+ unavailableReason: 'restricted',
160
+ };
161
+ },
162
+ async finish(result = exports.BackgroundTaskResult.Success) {
163
+ if (!currentReactNativeTask) {
164
+ return;
165
+ }
166
+ await NativeBackgroundTask.finish(Object.assign(Object.assign({}, currentReactNativeTask), { result }));
167
+ },
168
+ };
169
+
170
+ class BackgroundTaskWeb extends core.WebPlugin {
171
+ constructor() {
172
+ super(...arguments);
173
+ this.registeredTasks = new Map();
174
+ }
175
+ async registerTask(options) {
176
+ this.registeredTasks.set(options.taskName, options);
177
+ }
178
+ async unregisterTask(options) {
179
+ this.registeredTasks.delete(options.taskName);
180
+ }
181
+ async isTaskRegistered(options) {
182
+ return {
183
+ value: this.registeredTasks.has(options.taskName),
184
+ };
185
+ }
186
+ async getRegisteredTasks() {
187
+ return {
188
+ tasks: [...this.registeredTasks.keys()],
189
+ };
190
+ }
191
+ async getPendingTaskRuns() {
192
+ return {
193
+ tasks: [],
194
+ };
195
+ }
196
+ async getStatus() {
197
+ return {
198
+ status: exports.BackgroundTaskStatus.Restricted,
199
+ };
200
+ }
201
+ async triggerTaskWorkerForTesting() {
202
+ const timestamp = Date.now();
203
+ for (const taskName of this.registeredTasks.keys()) {
204
+ void this.notifyListeners('backgroundTask', {
205
+ taskName,
206
+ taskId: `${taskName}:${timestamp}`,
207
+ timestamp,
208
+ test: true,
209
+ });
210
+ }
211
+ return {
212
+ value: this.registeredTasks.size > 0,
213
+ };
214
+ }
215
+ async finish() {
216
+ return;
217
+ }
218
+ }
219
+
220
+ var web = /*#__PURE__*/Object.freeze({
221
+ __proto__: null,
222
+ BackgroundTaskWeb: BackgroundTaskWeb
223
+ });
224
+
225
+ exports.BackgroundTask = BackgroundTask;
226
+ exports.NativeBackgroundTask = NativeBackgroundTask;
227
+
228
+ return exports;
229
+
230
+ })({}, capacitorExports);
231
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["/**\n * Return value for a background task callback.\n */\nexport var BackgroundTaskResult;\n(function (BackgroundTaskResult) {\n /**\n * The task finished successfully.\n */\n BackgroundTaskResult[BackgroundTaskResult[\"Success\"] = 1] = \"Success\";\n /**\n * The task failed.\n */\n BackgroundTaskResult[BackgroundTaskResult[\"Failed\"] = 2] = \"Failed\";\n})(BackgroundTaskResult || (BackgroundTaskResult = {}));\n/**\n * Availability status for background task scheduling.\n */\nexport var BackgroundTaskStatus;\n(function (BackgroundTaskStatus) {\n /**\n * Background task scheduling is unavailable or restricted.\n */\n BackgroundTaskStatus[BackgroundTaskStatus[\"Restricted\"] = 1] = \"Restricted\";\n /**\n * Background task scheduling is available.\n */\n BackgroundTaskStatus[BackgroundTaskStatus[\"Available\"] = 2] = \"Available\";\n})(BackgroundTaskStatus || (BackgroundTaskStatus = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nimport { BackgroundTaskResult, BackgroundTaskStatus } from './definitions';\nconst DEFAULT_TASK_NAME = 'CapgoBackgroundTask';\nconst NativeBackgroundTask = registerPlugin('BackgroundTask', {\n web: () => import('./web').then((m) => new m.BackgroundTaskWeb()),\n});\nconst callbacks = new Map();\nconst processedTaskIds = new Set();\nlet listenerReady;\nlet currentReactNativeTask;\nconst ensureListener = () => {\n if (!listenerReady) {\n listenerReady = NativeBackgroundTask.addListener('backgroundTask', (event) => {\n void runTask(event);\n }).then(() => undefined);\n }\n return listenerReady;\n};\nconst drainPendingTaskRuns = async () => {\n const { tasks } = await NativeBackgroundTask.getPendingTaskRuns();\n await Promise.all(tasks.map((task) => runTask(task)));\n};\nconst runTask = async (event) => {\n if (processedTaskIds.has(event.taskId)) {\n return;\n }\n const callback = callbacks.get(event.taskName);\n if (!callback) {\n return;\n }\n processedTaskIds.add(event.taskId);\n try {\n currentReactNativeTask = {\n taskId: event.taskId,\n taskName: event.taskName,\n result: BackgroundTaskResult.Success,\n };\n const result = await callback(event);\n await NativeBackgroundTask.finish({\n taskId: event.taskId,\n taskName: event.taskName,\n result: result !== null && result !== void 0 ? result : BackgroundTaskResult.Success,\n });\n }\n catch (_a) {\n await NativeBackgroundTask.finish({\n taskId: event.taskId,\n taskName: event.taskName,\n result: BackgroundTaskResult.Failed,\n });\n }\n finally {\n currentReactNativeTask = undefined;\n }\n};\nconst assertTaskName = (taskName) => {\n if (!taskName || typeof taskName !== 'string') {\n throw new Error('taskName is required');\n }\n};\nconst normalizeReactNativeOptions = (options) => {\n var _a;\n const period = (_a = options === null || options === void 0 ? void 0 : options.period) !== null && _a !== void 0 ? _a : 900;\n return {\n minimumInterval: Math.max(15, Math.ceil(period / 60)),\n };\n};\nconst BackgroundTask = {\n defineTask(taskName, callback) {\n assertTaskName(taskName);\n callbacks.set(taskName, callback);\n void ensureListener().then(drainPendingTaskRuns);\n },\n async registerTaskAsync(taskName, options) {\n assertTaskName(taskName);\n if (!callbacks.has(taskName)) {\n throw new Error(`Task \"${taskName}\" must be defined before it is registered.`);\n }\n await NativeBackgroundTask.registerTask({ taskName, options });\n await ensureListener();\n await drainPendingTaskRuns();\n },\n async unregisterTaskAsync(taskName) {\n assertTaskName(taskName);\n await NativeBackgroundTask.unregisterTask({ taskName });\n },\n async isTaskRegisteredAsync(taskName) {\n assertTaskName(taskName);\n const { value } = await NativeBackgroundTask.isTaskRegistered({ taskName });\n return value;\n },\n async getRegisteredTasksAsync() {\n const { tasks } = await NativeBackgroundTask.getRegisteredTasks();\n return tasks;\n },\n async getPendingTaskRunsAsync() {\n const { tasks } = await NativeBackgroundTask.getPendingTaskRuns();\n return tasks;\n },\n async getStatusAsync() {\n const { status } = await NativeBackgroundTask.getStatus();\n return status;\n },\n async triggerTaskWorkerForTestingAsync() {\n await ensureListener();\n const { value } = await NativeBackgroundTask.triggerTaskWorkerForTesting();\n await drainPendingTaskRuns();\n return value;\n },\n addExpirationListener(listener) {\n return NativeBackgroundTask.addListener('expiration', listener);\n },\n define(callback) {\n this.defineTask(DEFAULT_TASK_NAME, callback);\n },\n schedule(options) {\n return this.registerTaskAsync(DEFAULT_TASK_NAME, normalizeReactNativeOptions(options));\n },\n cancel() {\n return this.unregisterTaskAsync(DEFAULT_TASK_NAME);\n },\n async statusAsync() {\n const status = await this.getStatusAsync();\n if (status === BackgroundTaskStatus.Available) {\n return { available: true };\n }\n return {\n available: false,\n unavailableReason: 'restricted',\n };\n },\n async finish(result = BackgroundTaskResult.Success) {\n if (!currentReactNativeTask) {\n return;\n }\n await NativeBackgroundTask.finish(Object.assign(Object.assign({}, currentReactNativeTask), { result }));\n },\n};\nexport * from './definitions';\nexport { BackgroundTask, NativeBackgroundTask };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nimport { BackgroundTaskStatus } from './definitions';\nexport class BackgroundTaskWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.registeredTasks = new Map();\n }\n async registerTask(options) {\n this.registeredTasks.set(options.taskName, options);\n }\n async unregisterTask(options) {\n this.registeredTasks.delete(options.taskName);\n }\n async isTaskRegistered(options) {\n return {\n value: this.registeredTasks.has(options.taskName),\n };\n }\n async getRegisteredTasks() {\n return {\n tasks: [...this.registeredTasks.keys()],\n };\n }\n async getPendingTaskRuns() {\n return {\n tasks: [],\n };\n }\n async getStatus() {\n return {\n status: BackgroundTaskStatus.Restricted,\n };\n }\n async triggerTaskWorkerForTesting() {\n const timestamp = Date.now();\n for (const taskName of this.registeredTasks.keys()) {\n void this.notifyListeners('backgroundTask', {\n taskName,\n taskId: `${taskName}:${timestamp}`,\n timestamp,\n test: true,\n });\n }\n return {\n value: this.registeredTasks.size > 0,\n };\n }\n async finish() {\n return;\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["BackgroundTaskResult","BackgroundTaskStatus","registerPlugin","WebPlugin"],"mappings":"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"}
@@ -0,0 +1,269 @@
1
+ import BackgroundTasks
2
+ import Foundation
3
+ import UIKit
4
+
5
+ @objc public class BackgroundTask: NSObject {
6
+ static let defaultIdentifier = "app.capgo.backgroundtask.processing"
7
+ static let statusRestricted = 1
8
+ static let statusAvailable = 2
9
+ static let resultSuccess = 1
10
+ static let resultFailed = 2
11
+ static let defaultMinimumIntervalMinutes = 12 * 60
12
+ static let minimumIntervalMinutes = 15
13
+
14
+ private let taskIdentifier: String
15
+ private let defaults = UserDefaults.standard
16
+ private weak var plugin: BackgroundTaskPlugin?
17
+ private var schedulerRegistered = false
18
+ private var runningTaskIds = Set<String>()
19
+ private var activeTask: BGTask?
20
+ private var activeTaskSucceeded = true
21
+
22
+ private let taskNamesKey = "CapgoBackgroundTask.taskNames"
23
+ private let taskPrefix = "CapgoBackgroundTask.task."
24
+ private let pendingRunsKey = "CapgoBackgroundTask.pendingRuns"
25
+ private let lastRunPrefix = "CapgoBackgroundTask.lastRun."
26
+ private let lastResultPrefix = "CapgoBackgroundTask.lastResult."
27
+
28
+ init(taskIdentifier: String = BackgroundTask.defaultIdentifier) {
29
+ self.taskIdentifier = taskIdentifier
30
+ super.init()
31
+ }
32
+
33
+ func configure(plugin: BackgroundTaskPlugin) {
34
+ self.plugin = plugin
35
+ registerScheduler()
36
+ dispatchPendingRuns()
37
+ }
38
+
39
+ func registerTask(taskName: String, minimumInterval: Int, requiresNetwork: Bool) throws {
40
+ let normalizedInterval = normalizeInterval(minimumInterval)
41
+ var taskNames = getTaskNames()
42
+ taskNames.insert(taskName)
43
+ defaults.set(Array(taskNames), forKey: taskNamesKey)
44
+ defaults.set(
45
+ [
46
+ "taskName": taskName,
47
+ "minimumInterval": normalizedInterval,
48
+ "requiresNetwork": requiresNetwork
49
+ ],
50
+ forKey: taskPrefix + taskName
51
+ )
52
+ try scheduleNextProcessingTask()
53
+ }
54
+
55
+ func unregisterTask(taskName: String) {
56
+ var taskNames = getTaskNames()
57
+ taskNames.remove(taskName)
58
+ defaults.set(Array(taskNames), forKey: taskNamesKey)
59
+ defaults.removeObject(forKey: taskPrefix + taskName)
60
+
61
+ if taskNames.isEmpty {
62
+ BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: taskIdentifier)
63
+ } else {
64
+ try? scheduleNextProcessingTask()
65
+ }
66
+ }
67
+
68
+ func isTaskRegistered(taskName: String) -> Bool {
69
+ getTaskNames().contains(taskName)
70
+ }
71
+
72
+ func getRegisteredTasks() -> [String] {
73
+ Array(getTaskNames()).sorted()
74
+ }
75
+
76
+ func getPendingRuns() -> [[String: Any]] {
77
+ defaults.array(forKey: pendingRunsKey) as? [[String: Any]] ?? []
78
+ }
79
+
80
+ func getStatus() -> Int {
81
+ UIApplication.shared.backgroundRefreshStatus == .available ? BackgroundTask.statusAvailable : BackgroundTask.statusRestricted
82
+ }
83
+
84
+ func triggerTaskWorkerForTesting() -> Bool {
85
+ let taskNames = getTaskNames()
86
+ for taskName in taskNames {
87
+ recordAndDispatchRun(taskName: taskName, isTest: true)
88
+ }
89
+ return !taskNames.isEmpty
90
+ }
91
+
92
+ func finish(taskId: String, taskName: String?, result: Int) {
93
+ removePendingRun(taskId: taskId)
94
+
95
+ if let taskName = taskName, !taskName.isEmpty {
96
+ defaults.set(result, forKey: lastResultPrefix + taskName)
97
+ }
98
+
99
+ if runningTaskIds.contains(taskId) {
100
+ activeTaskSucceeded = activeTaskSucceeded && result == BackgroundTask.resultSuccess
101
+ runningTaskIds.remove(taskId)
102
+ }
103
+
104
+ completeActiveTaskIfNeeded()
105
+ }
106
+
107
+ private func registerScheduler() {
108
+ guard !schedulerRegistered else {
109
+ return
110
+ }
111
+
112
+ schedulerRegistered = BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: nil) { [weak self] task in
113
+ self?.handleProcessingTask(task)
114
+ }
115
+
116
+ if schedulerRegistered {
117
+ try? scheduleNextProcessingTask()
118
+ }
119
+ }
120
+
121
+ private func handleProcessingTask(_ task: BGTask) {
122
+ activeTask = task
123
+ activeTaskSucceeded = true
124
+ runningTaskIds.removeAll()
125
+ try? scheduleNextProcessingTask()
126
+
127
+ task.expirationHandler = { [weak self] in
128
+ self?.handleExpiration()
129
+ }
130
+
131
+ let dueTaskNames = dueTasks()
132
+ guard !dueTaskNames.isEmpty else {
133
+ task.setTaskCompleted(success: true)
134
+ activeTask = nil
135
+ return
136
+ }
137
+
138
+ for taskName in dueTaskNames {
139
+ let taskId = recordAndDispatchRun(taskName: taskName, isTest: false)
140
+ runningTaskIds.insert(taskId)
141
+ defaults.set(Date().timeIntervalSince1970, forKey: lastRunPrefix + taskName)
142
+ }
143
+ }
144
+
145
+ private func handleExpiration() {
146
+ for run in getPendingRuns() where runningTaskIds.contains(run["taskId"] as? String ?? "") {
147
+ plugin?.notifyListeners("expiration", data: run, retainUntilConsumed: true)
148
+ }
149
+
150
+ runningTaskIds.removeAll()
151
+ activeTaskSucceeded = false
152
+ completeActiveTaskIfNeeded()
153
+ }
154
+
155
+ private func completeActiveTaskIfNeeded() {
156
+ guard runningTaskIds.isEmpty, let task = activeTask else {
157
+ return
158
+ }
159
+
160
+ task.setTaskCompleted(success: activeTaskSucceeded)
161
+ activeTask = nil
162
+ try? scheduleNextProcessingTask()
163
+ }
164
+
165
+ private func scheduleNextProcessingTask() throws {
166
+ guard schedulerRegistered, !getTaskNames().isEmpty else {
167
+ return
168
+ }
169
+
170
+ let request = BGProcessingTaskRequest(identifier: taskIdentifier)
171
+ request.requiresNetworkConnectivity = anyTaskRequiresNetwork()
172
+ request.requiresExternalPower = false
173
+ request.earliestBeginDate = nextEarliestBeginDate()
174
+
175
+ BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: taskIdentifier)
176
+ try BGTaskScheduler.shared.submit(request)
177
+ }
178
+
179
+ private func dueTasks() -> [String] {
180
+ let now = Date().timeIntervalSince1970
181
+ return getTaskNames().filter { taskName in
182
+ let interval = TimeInterval(taskConfig(taskName: taskName).minimumInterval * 60)
183
+ let lastRun = defaults.double(forKey: lastRunPrefix + taskName)
184
+ return lastRun == 0 || now - lastRun >= interval
185
+ }
186
+ }
187
+
188
+ private func nextEarliestBeginDate() -> Date {
189
+ let now = Date()
190
+ var earliestDate = now.addingTimeInterval(TimeInterval(BackgroundTask.defaultMinimumIntervalMinutes * 60))
191
+
192
+ for taskName in getTaskNames() {
193
+ let config = taskConfig(taskName: taskName)
194
+ let interval = TimeInterval(config.minimumInterval * 60)
195
+ let lastRun = defaults.double(forKey: lastRunPrefix + taskName)
196
+ let nextDate = lastRun == 0 ? now.addingTimeInterval(interval) : Date(timeIntervalSince1970: lastRun + interval)
197
+ if nextDate < earliestDate {
198
+ earliestDate = nextDate
199
+ }
200
+ }
201
+
202
+ return earliestDate
203
+ }
204
+
205
+ private func anyTaskRequiresNetwork() -> Bool {
206
+ getTaskNames().contains { taskConfig(taskName: $0).requiresNetwork }
207
+ }
208
+
209
+ @discardableResult
210
+ private func recordAndDispatchRun(taskName: String, isTest: Bool) -> String {
211
+ let taskId = UUID().uuidString
212
+ let run: [String: Any] = [
213
+ "taskName": taskName,
214
+ "taskId": taskId,
215
+ "timestamp": Int(Date().timeIntervalSince1970 * 1000),
216
+ "test": isTest
217
+ ]
218
+
219
+ addPendingRun(run)
220
+ plugin?.notifyListeners("backgroundTask", data: run, retainUntilConsumed: true)
221
+ return taskId
222
+ }
223
+
224
+ private func dispatchPendingRuns() {
225
+ for run in getPendingRuns() {
226
+ plugin?.notifyListeners("backgroundTask", data: run, retainUntilConsumed: true)
227
+ }
228
+ }
229
+
230
+ private func addPendingRun(_ run: [String: Any]) {
231
+ var runs = getPendingRuns()
232
+ runs.append(run)
233
+ defaults.set(runs, forKey: pendingRunsKey)
234
+ }
235
+
236
+ private func removePendingRun(taskId: String) {
237
+ let runs = getPendingRuns().filter { run in
238
+ run["taskId"] as? String != taskId
239
+ }
240
+ defaults.set(runs, forKey: pendingRunsKey)
241
+ }
242
+
243
+ private func getTaskNames() -> Set<String> {
244
+ Set(defaults.stringArray(forKey: taskNamesKey) ?? [])
245
+ }
246
+
247
+ private func taskConfig(taskName: String) -> TaskConfig {
248
+ guard let rawConfig = defaults.dictionary(forKey: taskPrefix + taskName) else {
249
+ return TaskConfig(minimumInterval: BackgroundTask.defaultMinimumIntervalMinutes, requiresNetwork: true)
250
+ }
251
+
252
+ return TaskConfig(
253
+ minimumInterval: normalizeInterval(rawConfig["minimumInterval"] as? Int ?? BackgroundTask.defaultMinimumIntervalMinutes),
254
+ requiresNetwork: rawConfig["requiresNetwork"] as? Bool ?? true
255
+ )
256
+ }
257
+
258
+ private func normalizeInterval(_ minimumInterval: Int) -> Int {
259
+ if minimumInterval <= 0 {
260
+ return BackgroundTask.defaultMinimumIntervalMinutes
261
+ }
262
+ return max(BackgroundTask.minimumIntervalMinutes, minimumInterval)
263
+ }
264
+ }
265
+
266
+ private struct TaskConfig {
267
+ let minimumInterval: Int
268
+ let requiresNetwork: Bool
269
+ }
@@ -0,0 +1,101 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc(BackgroundTaskPlugin)
5
+ public class BackgroundTaskPlugin: CAPPlugin, CAPBridgedPlugin {
6
+ public let identifier = "BackgroundTaskPlugin"
7
+ public let jsName = "BackgroundTask"
8
+ public let pluginMethods: [CAPPluginMethod] = [
9
+ CAPPluginMethod(name: "registerTask", returnType: CAPPluginReturnPromise),
10
+ CAPPluginMethod(name: "unregisterTask", returnType: CAPPluginReturnPromise),
11
+ CAPPluginMethod(name: "isTaskRegistered", returnType: CAPPluginReturnPromise),
12
+ CAPPluginMethod(name: "getRegisteredTasks", returnType: CAPPluginReturnPromise),
13
+ CAPPluginMethod(name: "getPendingTaskRuns", returnType: CAPPluginReturnPromise),
14
+ CAPPluginMethod(name: "getStatus", returnType: CAPPluginReturnPromise),
15
+ CAPPluginMethod(name: "triggerTaskWorkerForTesting", returnType: CAPPluginReturnPromise),
16
+ CAPPluginMethod(name: "finish", returnType: CAPPluginReturnPromise)
17
+ ]
18
+
19
+ private lazy var implementation = BackgroundTask()
20
+
21
+ override public func load() {
22
+ implementation.configure(plugin: self)
23
+ }
24
+
25
+ @objc func registerTask(_ call: CAPPluginCall) {
26
+ guard let taskName = call.getString("taskName"), !taskName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
27
+ call.reject("taskName is required")
28
+ return
29
+ }
30
+
31
+ let options = call.getObject("options") ?? [:]
32
+ let minimumInterval = options["minimumInterval"] as? Int ?? BackgroundTask.defaultMinimumIntervalMinutes
33
+ let requiresNetwork = options["requiresNetwork"] as? Bool ?? true
34
+
35
+ do {
36
+ try implementation.registerTask(taskName: taskName, minimumInterval: minimumInterval, requiresNetwork: requiresNetwork)
37
+ call.resolve()
38
+ } catch {
39
+ call.reject("Unable to register background task", nil, error)
40
+ }
41
+ }
42
+
43
+ @objc func unregisterTask(_ call: CAPPluginCall) {
44
+ guard let taskName = call.getString("taskName"), !taskName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
45
+ call.reject("taskName is required")
46
+ return
47
+ }
48
+
49
+ implementation.unregisterTask(taskName: taskName)
50
+ call.resolve()
51
+ }
52
+
53
+ @objc func isTaskRegistered(_ call: CAPPluginCall) {
54
+ guard let taskName = call.getString("taskName"), !taskName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
55
+ call.reject("taskName is required")
56
+ return
57
+ }
58
+
59
+ call.resolve([
60
+ "value": implementation.isTaskRegistered(taskName: taskName)
61
+ ])
62
+ }
63
+
64
+ @objc func getRegisteredTasks(_ call: CAPPluginCall) {
65
+ call.resolve([
66
+ "tasks": implementation.getRegisteredTasks()
67
+ ])
68
+ }
69
+
70
+ @objc func getPendingTaskRuns(_ call: CAPPluginCall) {
71
+ call.resolve([
72
+ "tasks": implementation.getPendingRuns()
73
+ ])
74
+ }
75
+
76
+ @objc func getStatus(_ call: CAPPluginCall) {
77
+ call.resolve([
78
+ "status": implementation.getStatus()
79
+ ])
80
+ }
81
+
82
+ @objc func triggerTaskWorkerForTesting(_ call: CAPPluginCall) {
83
+ call.resolve([
84
+ "value": implementation.triggerTaskWorkerForTesting()
85
+ ])
86
+ }
87
+
88
+ @objc func finish(_ call: CAPPluginCall) {
89
+ guard let taskId = call.getString("taskId"), !taskId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
90
+ call.reject("taskId is required")
91
+ return
92
+ }
93
+
94
+ implementation.finish(
95
+ taskId: taskId,
96
+ taskName: call.getString("taskName"),
97
+ result: call.getInt("result") ?? BackgroundTask.resultSuccess
98
+ )
99
+ call.resolve()
100
+ }
101
+ }
@@ -0,0 +1,18 @@
1
+ import XCTest
2
+ @testable import BackgroundTaskPlugin
3
+
4
+ class BackgroundTaskTests: XCTestCase {
5
+ func testRegisterAndUnregisterTask() throws {
6
+ let implementation = BackgroundTask()
7
+ let taskName = "unit-test-background-task"
8
+
9
+ implementation.unregisterTask(taskName: taskName)
10
+ XCTAssertFalse(implementation.isTaskRegistered(taskName: taskName))
11
+
12
+ try implementation.registerTask(taskName: taskName, minimumInterval: 1, requiresNetwork: false)
13
+ XCTAssertTrue(implementation.isTaskRegistered(taskName: taskName))
14
+
15
+ implementation.unregisterTask(taskName: taskName)
16
+ XCTAssertFalse(implementation.isTaskRegistered(taskName: taskName))
17
+ }
18
+ }