@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/CapgoCapacitorBackgroundTask.podspec +17 -0
- package/LICENSE +373 -0
- package/Package.swift +28 -0
- package/README.md +421 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/app/capgo/backgroundtask/BackgroundTask.java +226 -0
- package/android/src/main/java/app/capgo/backgroundtask/BackgroundTaskPlugin.java +132 -0
- package/android/src/main/java/app/capgo/backgroundtask/BackgroundTaskWorker.java +25 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +407 -0
- package/dist/esm/definitions.d.ts +192 -0
- package/dist/esm/definitions.js +29 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +141 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +13 -0
- package/dist/esm/web.js +52 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +228 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +231 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/BackgroundTaskPlugin/BackgroundTask.swift +269 -0
- package/ios/Sources/BackgroundTaskPlugin/BackgroundTaskPlugin.swift +101 -0
- package/ios/Tests/BackgroundTaskPluginTests/BackgroundTaskTests.swift +18 -0
- package/package.json +89 -0
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
|
+
}
|