@capacitor/background-runner 2.2.0 → 3.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/CapacitorBackgroundRunner.podspec +2 -2
- package/Package.swift +45 -0
- package/README.md +12 -2
- package/android/build.gradle +12 -12
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunnerPlugin.kt +1 -1
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Device.kt +1 -1
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt +2 -10
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/TimedNotificationPublisher.kt +1 -0
- package/android/src/main/libs/android-js-engine-release.aar +0 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +2 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +2 -4
- package/dist/plugin.js.map +1 -1
- package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/BackgroundRunner.swift +26 -11
- package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Geolocation.swift +21 -9
- package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Notifications.swift +2 -1
- package/ios/Sources/CapacitorBackgroundRunner/CapacitorBackgroundRunner.swift +308 -0
- package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/Context+CapacitorAPI.swift +8 -2
- package/ios/{Plugin → Sources}/RunnerEngine/Context.swift +1 -1
- package/ios/{Plugin → Sources}/RunnerEngine/JSCrypto.swift +3 -3
- package/ios/{Plugin → Sources}/RunnerEngine/JSError.swift +1 -1
- package/ios/{Plugin → Sources}/RunnerEngine/Runner.swift +1 -1
- package/ios/Tests/CapacitorBackgroundRunnerTests/CapacitorBackgroundRunnerTests.swift +21 -0
- package/ios/Tests/RunnerEngineTests/ContextTests.swift +393 -0
- package/ios/Tests/RunnerEngineTests/RunnerTests.swift +32 -0
- package/package.json +24 -22
- package/scripts/install_libs.js +4 -12
- package/ios/Plugin/BackgroundRunnerPlugin.h +0 -10
- package/ios/Plugin/BackgroundRunnerPlugin.m +0 -12
- package/ios/Plugin/BackgroundRunnerPlugin.swift +0 -228
- package/ios/Plugin/Info.plist +0 -24
- /package/ios/{Plugin/BackgroundRunnerErrors.swift → Sources/CapacitorBackgroundRunner/BackgroundRunnerPluginError.swift} +0 -0
- /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/App.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Device.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/KV.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Reachability.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Watch.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/RunnerConfig.swift +0 -0
- /package/ios/{Plugin/WatchConnectivtySessionDelegate.swift → Sources/CapacitorBackgroundRunner/WatchConnectivitySessionDelegate.swift} +0 -0
- /package/ios/{Plugin → Sources}/RunnerEngine/Errors.swift +0 -0
- /package/ios/{Plugin → Sources}/RunnerEngine/JSConsole.swift +0 -0
- /package/ios/{Plugin → Sources}/RunnerEngine/JSFetch.swift +0 -0
- /package/ios/{Plugin → Sources}/RunnerEngine/JSResponse.swift +0 -0
- /package/ios/{Plugin → Sources}/RunnerEngine/JSTextDecoder.swift +0 -0
- /package/ios/{Plugin → Sources}/RunnerEngine/JSTextEncoder.swift +0 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import Capacitor
|
|
2
|
+
import Foundation
|
|
3
|
+
import UserNotifications
|
|
4
|
+
import WatchConnectivity
|
|
5
|
+
|
|
6
|
+
@objc(BackgroundRunnerPlugin)
|
|
7
|
+
public class BackgroundRunnerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
8
|
+
public let identifier = "CapacitorBackgroundRunnerPlugin"
|
|
9
|
+
public let jsName = "CapacitorBackgroundRunner"
|
|
10
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
11
|
+
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
12
|
+
CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
|
|
13
|
+
CAPPluginMethod(name: "dispatchEvent", returnType: CAPPluginReturnPromise),
|
|
14
|
+
CAPPluginMethod(name: "registerBackgroundTask", returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: "removeNotificationListeners", returnType: CAPPluginReturnPromise),
|
|
16
|
+
]
|
|
17
|
+
private let impl = BackgroundRunner()
|
|
18
|
+
private weak var originalDelegate: UNUserNotificationCenterDelegate?
|
|
19
|
+
|
|
20
|
+
override public func load() {
|
|
21
|
+
NotificationCenter.default.addObserver(
|
|
22
|
+
self,
|
|
23
|
+
selector: #selector(didEnterBackground),
|
|
24
|
+
name: UIApplication.didEnterBackgroundNotification,
|
|
25
|
+
object: nil
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
// Register as notification delegate
|
|
29
|
+
registerNotificationCategories()
|
|
30
|
+
|
|
31
|
+
initWatchConnectivity()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
35
|
+
// check geolocation permissions
|
|
36
|
+
let geolocationState = CapacitorGeolocation.checkPermission()
|
|
37
|
+
|
|
38
|
+
// check notification permissions
|
|
39
|
+
let notificationState = CapacitorNotifications.checkPermission()
|
|
40
|
+
|
|
41
|
+
call.resolve([
|
|
42
|
+
"geolocation": geolocationState,
|
|
43
|
+
"notifications": notificationState,
|
|
44
|
+
])
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@objc override public func requestPermissions(_ call: CAPPluginCall) {
|
|
48
|
+
Task {
|
|
49
|
+
let permissions = call.getArray("apis") as? [String] ?? []
|
|
50
|
+
|
|
51
|
+
do {
|
|
52
|
+
if permissions.contains("notifications") {
|
|
53
|
+
try CapacitorNotifications.requestPermission()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if permissions.contains("geolocation") {
|
|
57
|
+
let geolocation = CapacitorGeolocation()
|
|
58
|
+
print("geolocation requested...")
|
|
59
|
+
try await geolocation.requestPermission()
|
|
60
|
+
print("geolocation requested...done")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
self.checkPermissions(call)
|
|
64
|
+
} catch {
|
|
65
|
+
call.reject("\(error)")
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@objc func dispatchEvent(_ call: CAPPluginCall) {
|
|
71
|
+
do {
|
|
72
|
+
guard let runnerEvent = call.getString("event"), !runnerEvent.isEmpty else {
|
|
73
|
+
throw BackgroundRunnerPluginError.invalidArgument(reason: "event is missing or invalid")
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let details = call.getObject("details", JSObject())
|
|
77
|
+
|
|
78
|
+
guard var config = impl.config else {
|
|
79
|
+
throw BackgroundRunnerPluginError.noRunnerConfig
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
config.event = runnerEvent
|
|
83
|
+
|
|
84
|
+
// swiftlint:disable:next unowned_variable_capture
|
|
85
|
+
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
|
86
|
+
do {
|
|
87
|
+
let result = try self.impl.execute(
|
|
88
|
+
config: config,
|
|
89
|
+
inputArgs: details as [String: Any],
|
|
90
|
+
callbackId: call.callbackId
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if let result = result {
|
|
94
|
+
call.resolve(result)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
call.resolve()
|
|
99
|
+
} catch {
|
|
100
|
+
call.reject("\(error)")
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
call.reject("\(error)")
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@objc func registerBackgroundTask(_ call: CAPPluginCall) {
|
|
109
|
+
call.resolve()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@objc func removeNotificationListeners(_ call: CAPPluginCall) {
|
|
113
|
+
notifyListeners("backgroundRunnerNotificationReceived", data: nil)
|
|
114
|
+
call.resolve()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@objc private func didEnterBackground() {
|
|
118
|
+
do {
|
|
119
|
+
try impl.scheduleBackgroundTasks()
|
|
120
|
+
} catch {
|
|
121
|
+
print("could not schedule background task: \(error)")
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public static func registerBackgroundTask() {
|
|
126
|
+
do {
|
|
127
|
+
try BackgroundRunner.shared.registerBackgroundTask()
|
|
128
|
+
} catch {
|
|
129
|
+
print("could not register background task: \(error)")
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public static func dispatchEvent(
|
|
134
|
+
event: String, eventArgs: [AnyHashable: Any], completionHandler: ((Result<Bool, Error>) -> Void)
|
|
135
|
+
) {
|
|
136
|
+
if event.isEmpty {
|
|
137
|
+
completionHandler(
|
|
138
|
+
.failure(BackgroundRunnerPluginError.invalidArgument(reason: "event is missing or invalid"))
|
|
139
|
+
)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
var args: [String: Any] = [:]
|
|
144
|
+
|
|
145
|
+
eventArgs.forEach { (key: AnyHashable, value: Any) in
|
|
146
|
+
if let strKey = key as? String {
|
|
147
|
+
args[strKey] = value
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
do {
|
|
152
|
+
try BackgroundRunner.shared.dispatchEvent(event: event, inputArgs: args)
|
|
153
|
+
completionHandler(.success(true))
|
|
154
|
+
} catch {
|
|
155
|
+
completionHandler(.failure(error))
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public static func handleApplicationDidFinishLaunching(
|
|
160
|
+
launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
161
|
+
) {
|
|
162
|
+
guard let launchOptions = launchOptions else {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
var events: [String: [String: Any]?] = [:]
|
|
167
|
+
|
|
168
|
+
if launchOptions[.location] != nil {
|
|
169
|
+
// application was launched for a location event
|
|
170
|
+
var details: [String: Any] = [:]
|
|
171
|
+
details["locations"] = nil
|
|
172
|
+
|
|
173
|
+
if let location = CapacitorGeolocation.getCurrentKnownLocation() {
|
|
174
|
+
var details: [String: Any] = [:]
|
|
175
|
+
details["locations"] = [location]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
events["currentLocation"] = details
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
print("emit events: \(events)")
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private func initWatchConnectivity() {
|
|
186
|
+
if !WCSession.isSupported() {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
guard let config = impl.config else {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if !config.enableWatchConnectivity {
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
WCSession.default.delegate = self
|
|
199
|
+
WCSession.default.activate()
|
|
200
|
+
|
|
201
|
+
print("Watch Connectivity Enabled")
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@objc func registerNotificationCategories() {
|
|
205
|
+
let notificationCenter = UNUserNotificationCenter.current()
|
|
206
|
+
|
|
207
|
+
// Capture the original delegate if it's not self
|
|
208
|
+
if notificationCenter.delegate !== self {
|
|
209
|
+
originalDelegate = notificationCenter.delegate
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Set the plugin as the notification delegate
|
|
213
|
+
notificationCenter.delegate = self
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// MARK: - UNUserNotificationCenterDelegate
|
|
218
|
+
extension BackgroundRunnerPlugin: UNUserNotificationCenterDelegate {
|
|
219
|
+
// This method will be called when a notification is tapped
|
|
220
|
+
public func userNotificationCenter(
|
|
221
|
+
_ center: UNUserNotificationCenter,
|
|
222
|
+
didReceive response: UNNotificationResponse,
|
|
223
|
+
withCompletionHandler completionHandler: @escaping () -> Void
|
|
224
|
+
) {
|
|
225
|
+
let userInfo = response.notification.request.content.userInfo
|
|
226
|
+
|
|
227
|
+
if isBackgroundRunnerNotification(userInfo) {
|
|
228
|
+
handleBackgroundRunnerNotification(response, completionHandler: completionHandler)
|
|
229
|
+
} else {
|
|
230
|
+
forwardToOriginalDelegate(
|
|
231
|
+
center, didReceive: response, withCompletionHandler: completionHandler)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// This is needed to present notifications when the app is in the foreground
|
|
236
|
+
public func userNotificationCenter(
|
|
237
|
+
_ center: UNUserNotificationCenter,
|
|
238
|
+
willPresent notification: UNNotification,
|
|
239
|
+
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
240
|
+
) {
|
|
241
|
+
let userInfo = notification.request.content.userInfo
|
|
242
|
+
|
|
243
|
+
if isBackgroundRunnerNotification(userInfo) {
|
|
244
|
+
handleBackgroundRunnerPresentation(completionHandler: completionHandler)
|
|
245
|
+
} else {
|
|
246
|
+
forwardToOriginalDelegate(
|
|
247
|
+
center, willPresent: notification, withCompletionHandler: completionHandler)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// MARK: - Helper Methods
|
|
252
|
+
|
|
253
|
+
private func isBackgroundRunnerNotification(_ userInfo: [AnyHashable: Any]) -> Bool {
|
|
254
|
+
return (userInfo["source"] as? String) == "capacitor-background-runner"
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private func handleBackgroundRunnerNotification(
|
|
258
|
+
_ response: UNNotificationResponse, completionHandler: @escaping () -> Void
|
|
259
|
+
) {
|
|
260
|
+
let actionTypeId = response.notification.request.content.categoryIdentifier
|
|
261
|
+
let notificationId = Int(response.notification.request.identifier) ?? -1
|
|
262
|
+
|
|
263
|
+
let eventData: [String: Any] = [
|
|
264
|
+
"actionTypeId": actionTypeId,
|
|
265
|
+
"notificationId": notificationId,
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
notifyListeners(
|
|
269
|
+
"backgroundRunnerNotificationReceived", data: eventData, retainUntilConsumed: true)
|
|
270
|
+
completionHandler()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private func forwardToOriginalDelegate(
|
|
274
|
+
_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse,
|
|
275
|
+
withCompletionHandler completionHandler: @escaping () -> Void
|
|
276
|
+
) {
|
|
277
|
+
if let originalDelegate = originalDelegate,
|
|
278
|
+
originalDelegate.responds(
|
|
279
|
+
to: #selector(userNotificationCenter(_:didReceive:withCompletionHandler:)))
|
|
280
|
+
{
|
|
281
|
+
originalDelegate.userNotificationCenter?(
|
|
282
|
+
center, didReceive: response, withCompletionHandler: completionHandler)
|
|
283
|
+
} else {
|
|
284
|
+
completionHandler()
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private func handleBackgroundRunnerPresentation(
|
|
289
|
+
completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
290
|
+
) {
|
|
291
|
+
completionHandler([.banner, .sound, .badge])
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private func forwardToOriginalDelegate(
|
|
295
|
+
_ center: UNUserNotificationCenter, willPresent notification: UNNotification,
|
|
296
|
+
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
297
|
+
) {
|
|
298
|
+
if let originalDelegate = originalDelegate,
|
|
299
|
+
originalDelegate.responds(
|
|
300
|
+
to: #selector(userNotificationCenter(_:willPresent:withCompletionHandler:)))
|
|
301
|
+
{
|
|
302
|
+
originalDelegate.userNotificationCenter?(
|
|
303
|
+
center, willPresent: notification, withCompletionHandler: completionHandler)
|
|
304
|
+
} else {
|
|
305
|
+
handleBackgroundRunnerPresentation(completionHandler: completionHandler)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
|
+
#if canImport(RunnerEngine)
|
|
4
|
+
import RunnerEngine
|
|
5
|
+
#endif
|
|
6
|
+
|
|
3
7
|
extension Context {
|
|
4
8
|
public func setupCapacitorAPI() {
|
|
5
9
|
jsContext.setObject(CapacitorKVStore.self, forKeyedSubscript: "CapacitorKV" as NSString)
|
|
6
|
-
jsContext.setObject(
|
|
10
|
+
jsContext.setObject(
|
|
11
|
+
CapacitorGeolocation(context: self), forKeyedSubscript: "CapacitorGeolocation" as NSString)
|
|
7
12
|
jsContext.setObject(CapacitorWatch(), forKeyedSubscript: "CapacitorWatch" as NSString)
|
|
8
|
-
jsContext.setObject(
|
|
13
|
+
jsContext.setObject(
|
|
14
|
+
CapacitorNotifications.self, forKeyedSubscript: "CapacitorNotifications" as NSString)
|
|
9
15
|
jsContext.setObject(CapacitorDevice(), forKeyedSubscript: "CapacitorDevice" as NSString)
|
|
10
16
|
jsContext.setObject(CapacitorApp.self, forKeyedSubscript: "CapacitorApp" as NSString)
|
|
11
17
|
}
|
|
@@ -13,17 +13,17 @@ class JSCrypto: NSObject, JSCryptoExports {
|
|
|
13
13
|
var rnd = [UInt8](repeating: 0, count: size)
|
|
14
14
|
_ = SecRandomCopyBytes(kSecRandomDefault, size, &rnd)
|
|
15
15
|
|
|
16
|
-
if arr.toArray()
|
|
16
|
+
if arr.toArray() is [UInt8] {
|
|
17
17
|
for i in 0...(size - 1) {
|
|
18
18
|
arr.setObject(rnd[i], atIndexedSubscript: i)
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
if arr.toArray()
|
|
21
|
+
if arr.toArray() is [UInt16] {
|
|
22
22
|
for i in 0...(size - 1) {
|
|
23
23
|
arr.setObject(UInt16(rnd[i]), atIndexedSubscript: i)
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
if arr.toArray()
|
|
26
|
+
if arr.toArray() is [UInt32] {
|
|
27
27
|
for i in 0...(size - 1) {
|
|
28
28
|
arr.setObject(UInt32(rnd[i]), forKeyedSubscript: i)
|
|
29
29
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Testing
|
|
2
|
+
@testable import CapacitorBackgroundRunner
|
|
3
|
+
@testable import RunnerEngine
|
|
4
|
+
|
|
5
|
+
@Test func testKeyValueAPI() async throws {
|
|
6
|
+
let runner = Runner()
|
|
7
|
+
let context = try runner.createContext(name: "io.backgroundrunner.testkv")
|
|
8
|
+
context.setupCapacitorAPI()
|
|
9
|
+
|
|
10
|
+
_ = try context.execute(code: "CapacitorKV.set('test', 'hello world');")
|
|
11
|
+
|
|
12
|
+
let value = try context.execute(code: "CapacitorKV.get('test');")
|
|
13
|
+
|
|
14
|
+
#expect(value?.toDictionary()?["value"] as? String == "hello world")
|
|
15
|
+
|
|
16
|
+
_ = try context.execute(code: "CapacitorKV.remove('test');")
|
|
17
|
+
|
|
18
|
+
let nullValue = try context.execute(code: "CapacitorKV.get('test');")
|
|
19
|
+
|
|
20
|
+
#expect(nullValue?.isNull ?? false)
|
|
21
|
+
}
|