@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.
Files changed (49) hide show
  1. package/CapacitorBackgroundRunner.podspec +2 -2
  2. package/Package.swift +45 -0
  3. package/README.md +12 -2
  4. package/android/build.gradle +12 -12
  5. package/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunnerPlugin.kt +1 -1
  6. package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Device.kt +1 -1
  7. package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt +2 -10
  8. package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/TimedNotificationPublisher.kt +1 -0
  9. package/android/src/main/libs/android-js-engine-release.aar +0 -0
  10. package/dist/esm/definitions.js.map +1 -1
  11. package/dist/esm/index.js +2 -2
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/web.js.map +1 -1
  14. package/dist/plugin.cjs.js +2 -4
  15. package/dist/plugin.cjs.js.map +1 -1
  16. package/dist/plugin.js +2 -4
  17. package/dist/plugin.js.map +1 -1
  18. package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/BackgroundRunner.swift +26 -11
  19. package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Geolocation.swift +21 -9
  20. package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Notifications.swift +2 -1
  21. package/ios/Sources/CapacitorBackgroundRunner/CapacitorBackgroundRunner.swift +308 -0
  22. package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/Context+CapacitorAPI.swift +8 -2
  23. package/ios/{Plugin → Sources}/RunnerEngine/Context.swift +1 -1
  24. package/ios/{Plugin → Sources}/RunnerEngine/JSCrypto.swift +3 -3
  25. package/ios/{Plugin → Sources}/RunnerEngine/JSError.swift +1 -1
  26. package/ios/{Plugin → Sources}/RunnerEngine/Runner.swift +1 -1
  27. package/ios/Tests/CapacitorBackgroundRunnerTests/CapacitorBackgroundRunnerTests.swift +21 -0
  28. package/ios/Tests/RunnerEngineTests/ContextTests.swift +393 -0
  29. package/ios/Tests/RunnerEngineTests/RunnerTests.swift +32 -0
  30. package/package.json +24 -22
  31. package/scripts/install_libs.js +4 -12
  32. package/ios/Plugin/BackgroundRunnerPlugin.h +0 -10
  33. package/ios/Plugin/BackgroundRunnerPlugin.m +0 -12
  34. package/ios/Plugin/BackgroundRunnerPlugin.swift +0 -228
  35. package/ios/Plugin/Info.plist +0 -24
  36. /package/ios/{Plugin/BackgroundRunnerErrors.swift → Sources/CapacitorBackgroundRunner/BackgroundRunnerPluginError.swift} +0 -0
  37. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/App.swift +0 -0
  38. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Device.swift +0 -0
  39. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/KV.swift +0 -0
  40. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Reachability.swift +0 -0
  41. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Watch.swift +0 -0
  42. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/RunnerConfig.swift +0 -0
  43. /package/ios/{Plugin/WatchConnectivtySessionDelegate.swift → Sources/CapacitorBackgroundRunner/WatchConnectivitySessionDelegate.swift} +0 -0
  44. /package/ios/{Plugin → Sources}/RunnerEngine/Errors.swift +0 -0
  45. /package/ios/{Plugin → Sources}/RunnerEngine/JSConsole.swift +0 -0
  46. /package/ios/{Plugin → Sources}/RunnerEngine/JSFetch.swift +0 -0
  47. /package/ios/{Plugin → Sources}/RunnerEngine/JSResponse.swift +0 -0
  48. /package/ios/{Plugin → Sources}/RunnerEngine/JSTextDecoder.swift +0 -0
  49. /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(CapacitorGeolocation(context: self), forKeyedSubscript: "CapacitorGeolocation" as NSString)
10
+ jsContext.setObject(
11
+ CapacitorGeolocation(context: self), forKeyedSubscript: "CapacitorGeolocation" as NSString)
7
12
  jsContext.setObject(CapacitorWatch(), forKeyedSubscript: "CapacitorWatch" as NSString)
8
- jsContext.setObject(CapacitorNotifications.self, forKeyedSubscript: "CapacitorNotifications" as NSString)
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
  }
@@ -3,7 +3,7 @@ import JavaScriptCore
3
3
 
4
4
  public class Context {
5
5
  let name: String
6
- let jsContext: JSContext
6
+ public let jsContext: JSContext
7
7
 
8
8
  private var eventListeners = [String: JSValue]()
9
9
  private var timers = [Int: Timer]()
@@ -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() as? [UInt8] != nil {
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() as? [UInt16] != nil {
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() as? [UInt32] != nil {
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
  }
@@ -27,7 +27,7 @@ import JavaScriptCore
27
27
  self.cause = nil
28
28
  }
29
29
 
30
- func toString() -> String {
30
+ public func toString() -> String {
31
31
  return message
32
32
  }
33
33
  }
@@ -12,7 +12,7 @@ public class Runner {
12
12
  runLoop = RunLoop.current
13
13
  }
14
14
 
15
- func createContext(name: String) throws -> Context {
15
+ public func createContext(name: String) throws -> Context {
16
16
  return try Context(vm: machine, contextName: name, runLoop: runLoop)
17
17
  }
18
18
  }
@@ -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
+ }