@capacitor/background-runner 2.1.0 → 2.3.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 (50) hide show
  1. package/CapacitorBackgroundRunner.podspec +1 -1
  2. package/Package.swift +45 -0
  3. package/README.md +65 -3
  4. package/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunnerPlugin.kt +45 -1
  5. package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notification.kt +4 -2
  6. package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt +21 -0
  7. package/android/src/main/libs/android-js-engine-release.aar +0 -0
  8. package/dist/docs.json +83 -0
  9. package/dist/esm/definitions.d.ts +17 -1
  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.d.ts +4 -1
  14. package/dist/esm/web.js +6 -0
  15. package/dist/esm/web.js.map +1 -1
  16. package/dist/plugin.cjs.js +8 -4
  17. package/dist/plugin.cjs.js.map +1 -1
  18. package/dist/plugin.js +8 -4
  19. package/dist/plugin.js.map +1 -1
  20. package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/BackgroundRunner.swift +26 -11
  21. package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Geolocation.swift +21 -9
  22. package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Notifications.swift +4 -3
  23. package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Reachability.swift +1 -1
  24. package/ios/{Plugin/BackgroundRunnerPlugin.swift → Sources/CapacitorBackgroundRunner/CapacitorBackgroundRunner.swift} +104 -1
  25. package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/Context+CapacitorAPI.swift +8 -2
  26. package/ios/{Plugin → Sources}/RunnerEngine/Context.swift +1 -1
  27. package/ios/{Plugin → Sources}/RunnerEngine/JSCrypto.swift +3 -3
  28. package/ios/{Plugin → Sources}/RunnerEngine/JSError.swift +1 -1
  29. package/ios/{Plugin → Sources}/RunnerEngine/Runner.swift +1 -1
  30. package/ios/Tests/CapacitorBackgroundRunnerTests/CapacitorBackgroundRunnerTests.swift +21 -0
  31. package/ios/Tests/RunnerEngineTests/ContextTests.swift +393 -0
  32. package/ios/Tests/RunnerEngineTests/RunnerTests.swift +32 -0
  33. package/package.json +24 -22
  34. package/scripts/install_libs.js +4 -12
  35. package/ios/Plugin/BackgroundRunnerPlugin.h +0 -10
  36. package/ios/Plugin/BackgroundRunnerPlugin.m +0 -11
  37. package/ios/Plugin/Info.plist +0 -24
  38. /package/ios/{Plugin/BackgroundRunnerErrors.swift → Sources/CapacitorBackgroundRunner/BackgroundRunnerPluginError.swift} +0 -0
  39. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/App.swift +0 -0
  40. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Device.swift +0 -0
  41. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/KV.swift +0 -0
  42. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/CapacitorAPI/Watch.swift +0 -0
  43. /package/ios/{Plugin → Sources/CapacitorBackgroundRunner}/RunnerConfig.swift +0 -0
  44. /package/ios/{Plugin/WatchConnectivtySessionDelegate.swift → Sources/CapacitorBackgroundRunner/WatchConnectivitySessionDelegate.swift} +0 -0
  45. /package/ios/{Plugin → Sources}/RunnerEngine/Errors.swift +0 -0
  46. /package/ios/{Plugin → Sources}/RunnerEngine/JSConsole.swift +0 -0
  47. /package/ios/{Plugin → Sources}/RunnerEngine/JSFetch.swift +0 -0
  48. /package/ios/{Plugin → Sources}/RunnerEngine/JSResponse.swift +0 -0
  49. /package/ios/{Plugin → Sources}/RunnerEngine/JSTextDecoder.swift +0 -0
  50. /package/ios/{Plugin → Sources}/RunnerEngine/JSTextEncoder.swift +0 -0
@@ -1,8 +1,12 @@
1
- import Foundation
2
- import Capacitor
3
1
  import BackgroundTasks
2
+ import Capacitor
3
+ import Foundation
4
4
  import JavaScriptCore
5
5
 
6
+ #if canImport(RunnerEngine)
7
+ import RunnerEngine
8
+ #endif
9
+
6
10
  public class BackgroundRunner {
7
11
  public static let shared = BackgroundRunner()
8
12
  public var config: RunnerConfig?
@@ -27,7 +31,8 @@ public class BackgroundRunner {
27
31
  }
28
32
 
29
33
  guard let interval = config.interval else {
30
- throw BackgroundRunnerPluginError.invalidRunnerConfig(reason: "cannot register background task without a configured interval")
34
+ throw BackgroundRunnerPluginError.invalidRunnerConfig(
35
+ reason: "cannot register background task without a configured interval")
31
36
  }
32
37
 
33
38
  let request = BGAppRefreshTaskRequest(identifier: config.label)
@@ -64,7 +69,9 @@ public class BackgroundRunner {
64
69
  }
65
70
 
66
71
  // swiftlint:disable:next cyclomatic_complexity function_body_length
67
- public func execute(config: RunnerConfig, inputArgs: [String: Any]? = nil, callbackId: String? = nil) throws -> [String: Any]? {
72
+ public func execute(
73
+ config: RunnerConfig, inputArgs: [String: Any]? = nil, callbackId: String? = nil
74
+ ) throws -> [String: Any]? {
68
75
  do {
69
76
  let context = try initContext(config: config, callbackId: callbackId)
70
77
 
@@ -73,7 +80,7 @@ public class BackgroundRunner {
73
80
  var result: [String: Any]?
74
81
  var rejectionErr: JSError?
75
82
 
76
- let resolveFunc: @convention(block)(JavaScriptCore.JSValue) -> Void = { returnObj in
83
+ let resolveFunc: @convention(block) (JavaScriptCore.JSValue) -> Void = { returnObj in
77
84
  if !returnObj.isUndefined && !returnObj.isNull {
78
85
  var dict: [String: Any] = [:]
79
86
 
@@ -88,7 +95,7 @@ public class BackgroundRunner {
88
95
  return
89
96
  }
90
97
 
91
- let rejectFunc: @convention(block)(JavaScriptCore.JSValue) -> Void = { rejectObj in
98
+ let rejectFunc: @convention(block) (JavaScriptCore.JSValue) -> Void = { rejectObj in
92
99
  var rejectionTitle = "Error"
93
100
  var rejectionMessage = ""
94
101
 
@@ -136,7 +143,7 @@ public class BackgroundRunner {
136
143
  waitGroup.wait()
137
144
 
138
145
  if let rejection = rejectionErr {
139
- throw EngineError.jsException(details: rejection.message)
146
+ throw EngineError.jsException(details: rejection.toString())
140
147
  }
141
148
 
142
149
  return result
@@ -146,7 +153,8 @@ public class BackgroundRunner {
146
153
  }
147
154
  }
148
155
 
149
- public func dispatchEvent(event: String, inputArgs: [String: Any]?, callbackId: String? = nil) throws {
156
+ public func dispatchEvent(event: String, inputArgs: [String: Any]?, callbackId: String? = nil)
157
+ throws {
150
158
  if let config = config {
151
159
  let waitGroup = DispatchGroup()
152
160
  waitGroup.enter()
@@ -174,13 +182,17 @@ public class BackgroundRunner {
174
182
  }
175
183
 
176
184
  private func loadRunnerConfig() throws -> RunnerConfig? {
177
- guard let capacitorConfigFileURL = Bundle.main.url(forResource: "capacitor.config.json", withExtension: nil) else {
185
+ guard
186
+ let capacitorConfigFileURL = Bundle.main.url(
187
+ forResource: "capacitor.config.json", withExtension: nil)
188
+ else {
178
189
  throw BackgroundRunnerPluginError.runnerError(reason: "capacitor.config.json file not found")
179
190
  }
180
191
 
181
192
  let capacitorConfigFileData = try Data(contentsOf: capacitorConfigFileURL)
182
193
 
183
- if let capConfig = JSTypes.coerceDictionaryToJSObject(try JSONSerialization.jsonObject(with: capacitorConfigFileData) as? [String: Any]) {
194
+ if let capConfig = JSTypes.coerceDictionaryToJSObject(
195
+ try JSONSerialization.jsonObject(with: capacitorConfigFileData) as? [String: Any]) {
184
196
  if let jsonConfig = capConfig[keyPath: "plugins.BackgroundRunner"] as? JSObject {
185
197
  return try RunnerConfig(from: jsonConfig)
186
198
  }
@@ -190,7 +202,10 @@ public class BackgroundRunner {
190
202
  }
191
203
 
192
204
  private func initContext(config: RunnerConfig, callbackId: String?) throws -> Context {
193
- guard let srcFileURL = Bundle.main.url(forResource: config.src, withExtension: nil, subdirectory: "public") else {
205
+ guard
206
+ let srcFileURL = Bundle.main.url(
207
+ forResource: config.src, withExtension: nil, subdirectory: "public")
208
+ else {
194
209
  throw BackgroundRunnerPluginError.runnerError(reason: "source file not found")
195
210
  }
196
211
 
@@ -1,6 +1,10 @@
1
+ import CoreLocation
1
2
  import Foundation
2
3
  import JavaScriptCore
3
- import CoreLocation
4
+
5
+ #if canImport(RunnerEngine)
6
+ import RunnerEngine
7
+ #endif
4
8
 
5
9
  @objc protocol CapacitorGeolocationExports: JSExport {
6
10
  var isWatchingPosition: Bool { get }
@@ -32,9 +36,12 @@ class CapacitorGeolocation: NSObject, CapacitorGeolocationExports, CLLocationMan
32
36
  self.context = context
33
37
  self.locationManager.delegate = self
34
38
 
35
- self.locationManager.allowsBackgroundLocationUpdates = true
36
- self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
37
- self.locationManager.pausesLocationUpdatesAutomatically = false
39
+ if CLLocationManager.authorizationStatus() == .authorizedAlways {
40
+ self.locationManager.allowsBackgroundLocationUpdates = true
41
+ self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
42
+ self.locationManager.pausesLocationUpdatesAutomatically = false
43
+ }
44
+
38
45
  }
39
46
 
40
47
  static func getCurrentKnownLocation() -> CLLocation? {
@@ -71,7 +78,8 @@ class CapacitorGeolocation: NSObject, CapacitorGeolocationExports, CLLocationMan
71
78
  return
72
79
  }
73
80
 
74
- return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation<Void, Error>) in
81
+ return try await withCheckedThrowingContinuation({
82
+ (continuation: CheckedContinuation<Void, Error>) in
75
83
  permissionContinuation = continuation
76
84
 
77
85
  DispatchQueue.main.async {
@@ -96,7 +104,8 @@ class CapacitorGeolocation: NSObject, CapacitorGeolocationExports, CLLocationMan
96
104
 
97
105
  self.isWatchingLocation = true
98
106
  } catch {
99
- JSContext.current().exception = JSValue(newErrorFromMessage: "\(error)", in: JSContext.current())
107
+ JSContext.current().exception = JSValue(
108
+ newErrorFromMessage: "\(error)", in: JSContext.current())
100
109
  }
101
110
  }
102
111
 
@@ -121,7 +130,8 @@ class CapacitorGeolocation: NSObject, CapacitorGeolocationExports, CLLocationMan
121
130
 
122
131
  return buildLocationDict(location: lastLocation)
123
132
  } catch {
124
- JSContext.current().exception = JSValue(newErrorFromMessage: "\(error)", in: JSContext.current())
133
+ JSContext.current().exception = JSValue(
134
+ newErrorFromMessage: "\(error)", in: JSContext.current())
125
135
 
126
136
  return nil
127
137
  }
@@ -139,7 +149,8 @@ class CapacitorGeolocation: NSObject, CapacitorGeolocationExports, CLLocationMan
139
149
  try? context.dispatchEvent(event: "currentLocation", details: details)
140
150
  }
141
151
 
142
- pendingCurrentLocationCalls.forEach { (callId: Int, callback: (Result<CLLocation?, Error>) -> Void) in
152
+ pendingCurrentLocationCalls.forEach {
153
+ (callId: Int, callback: (Result<CLLocation?, Error>) -> Void) in
143
154
  pendingCurrentLocationCalls.removeValue(forKey: callId)
144
155
  callback(.success(locations.last))
145
156
  }
@@ -162,7 +173,8 @@ class CapacitorGeolocation: NSObject, CapacitorGeolocationExports, CLLocationMan
162
173
  try? context.dispatchEvent(event: "currentLocation", details: details)
163
174
  }
164
175
 
165
- pendingCurrentLocationCalls.forEach { (callId: Int, callback: (Result<CLLocation?, Error>) -> Void) in
176
+ pendingCurrentLocationCalls.forEach {
177
+ (callId: Int, callback: (Result<CLLocation?, Error>) -> Void) in
166
178
  pendingCurrentLocationCalls.removeValue(forKey: callId)
167
179
  callback(.failure(error))
168
180
  }
@@ -1,4 +1,5 @@
1
1
  import Foundation
2
+ import UIKit
2
3
  import JavaScriptCore
3
4
  import UserNotifications
4
5
 
@@ -136,7 +137,7 @@ class CapacitorNotifications: NSObject, CapacitorNotificationsExports {
136
137
  let content = UNMutableNotificationContent()
137
138
  content.title = NSString.localizedUserNotificationString(forKey: notificationOption.title, arguments: nil)
138
139
  content.body = NSString.localizedUserNotificationString(forKey: notificationOption.body, arguments: nil)
139
- content.userInfo = [:]
140
+ content.userInfo = ["source": "capacitor-background-runner"]
140
141
 
141
142
  if let extra = notificationOption.extra {
142
143
  content.userInfo["cap_extra"] = extra
@@ -195,7 +196,7 @@ class CapacitorNotifications: NSObject, CapacitorNotificationsExports {
195
196
 
196
197
  let badgeOptions = SetBadgeOption(from: jsonDict)
197
198
 
198
- DispatchQueue.main.sync {
199
+ DispatchQueue.main.async {
199
200
  if #available(iOS 16.0, *) {
200
201
  UNUserNotificationCenter.current().setBadgeCount(badgeOptions.count)
201
202
  } else {
@@ -213,7 +214,7 @@ class CapacitorNotifications: NSObject, CapacitorNotificationsExports {
213
214
  throw CapacitorNotificationsErrors.permissionDenied
214
215
  }
215
216
 
216
- DispatchQueue.main.sync {
217
+ DispatchQueue.main.async {
217
218
  if #available(iOS 16.0, *) {
218
219
  UNUserNotificationCenter.current().setBadgeCount(0)
219
220
  } else {
@@ -191,7 +191,7 @@ public extension Reachability {
191
191
  _ = unmanagedWeakifiedReachability.retain()
192
192
  return UnsafeRawPointer(unmanagedWeakifiedReachability.toOpaque())
193
193
  },
194
- release: { (info: UnsafeRawPointer) -> Void in
194
+ release: { (info: UnsafeRawPointer) in
195
195
  let unmanagedWeakifiedReachability = Unmanaged<ReachabilityWeakifier>.fromOpaque(info)
196
196
  unmanagedWeakifiedReachability.release()
197
197
  },
@@ -1,10 +1,21 @@
1
1
  import Foundation
2
2
  import Capacitor
3
3
  import WatchConnectivity
4
+ import UserNotifications
4
5
 
5
6
  @objc(BackgroundRunnerPlugin)
6
- public class BackgroundRunnerPlugin: CAPPlugin {
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
+ ]
7
17
  private let impl = BackgroundRunner()
18
+ private weak var originalDelegate: UNUserNotificationCenterDelegate?
8
19
 
9
20
  override public func load() {
10
21
  NotificationCenter.default.addObserver(
@@ -14,6 +25,9 @@ public class BackgroundRunnerPlugin: CAPPlugin {
14
25
  object: nil
15
26
  )
16
27
 
28
+ // Register as notification delegate
29
+ registerNotificationCategories()
30
+
17
31
  initWatchConnectivity()
18
32
  }
19
33
 
@@ -95,6 +109,11 @@ public class BackgroundRunnerPlugin: CAPPlugin {
95
109
  call.resolve()
96
110
  }
97
111
 
112
+ @objc func removeNotificationListeners(_ call: CAPPluginCall) {
113
+ notifyListeners("backgroundRunnerNotificationReceived", data: nil)
114
+ call.resolve()
115
+ }
116
+
98
117
  @objc private func didEnterBackground() {
99
118
  do {
100
119
  try impl.scheduleBackgroundTasks()
@@ -175,4 +194,88 @@ public class BackgroundRunnerPlugin: CAPPlugin {
175
194
 
176
195
  print("Watch Connectivity Enabled")
177
196
  }
197
+
198
+ @objc func registerNotificationCategories() {
199
+ let notificationCenter = UNUserNotificationCenter.current()
200
+
201
+ // Capture the original delegate if it's not self
202
+ if notificationCenter.delegate !== self {
203
+ originalDelegate = notificationCenter.delegate
204
+ }
205
+
206
+ // Set the plugin as the notification delegate
207
+ notificationCenter.delegate = self
208
+ }
209
+ }
210
+
211
+ // MARK: - UNUserNotificationCenterDelegate
212
+ extension BackgroundRunnerPlugin: UNUserNotificationCenterDelegate {
213
+ // This method will be called when a notification is tapped
214
+ public func userNotificationCenter(_ center: UNUserNotificationCenter,
215
+ didReceive response: UNNotificationResponse,
216
+ withCompletionHandler completionHandler: @escaping () -> Void) {
217
+ let userInfo = response.notification.request.content.userInfo
218
+
219
+ if isBackgroundRunnerNotification(userInfo) {
220
+ handleBackgroundRunnerNotification(response, completionHandler: completionHandler)
221
+ } else {
222
+ forwardToOriginalDelegate(center, didReceive: response, withCompletionHandler: completionHandler)
223
+ }
224
+ }
225
+
226
+ // This is needed to present notifications when the app is in the foreground
227
+ public func userNotificationCenter(_ center: UNUserNotificationCenter,
228
+ willPresent notification: UNNotification,
229
+ withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
230
+ let userInfo = notification.request.content.userInfo
231
+
232
+ if isBackgroundRunnerNotification(userInfo) {
233
+ handleBackgroundRunnerPresentation(completionHandler: completionHandler)
234
+ } else {
235
+ forwardToOriginalDelegate(center, willPresent: notification, withCompletionHandler: completionHandler)
236
+ }
237
+ }
238
+
239
+ // MARK: - Helper Methods
240
+
241
+ private func isBackgroundRunnerNotification(_ userInfo: [AnyHashable: Any]) -> Bool {
242
+ return (userInfo["source"] as? String) == "capacitor-background-runner"
243
+ }
244
+
245
+ private func handleBackgroundRunnerNotification(_ response: UNNotificationResponse, completionHandler: @escaping () -> Void) {
246
+ let actionTypeId = response.notification.request.content.categoryIdentifier
247
+ let notificationId = Int(response.notification.request.identifier) ?? -1
248
+
249
+ let eventData: [String: Any] = [
250
+ "actionTypeId": actionTypeId,
251
+ "notificationId": notificationId
252
+ ]
253
+
254
+ notifyListeners("backgroundRunnerNotificationReceived", data: eventData, retainUntilConsumed: true)
255
+ completionHandler()
256
+ }
257
+
258
+ private func forwardToOriginalDelegate(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
259
+ if let originalDelegate = originalDelegate, originalDelegate.responds(to: #selector(userNotificationCenter(_:didReceive:withCompletionHandler:))) {
260
+ originalDelegate.userNotificationCenter?(center, didReceive: response, withCompletionHandler: completionHandler)
261
+ } else {
262
+ completionHandler()
263
+ }
264
+ }
265
+
266
+ private func handleBackgroundRunnerPresentation(completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
267
+ if #available(iOS 14.0, *) {
268
+ completionHandler([.banner, .sound, .badge])
269
+ } else {
270
+ completionHandler([.alert, .sound, .badge])
271
+ }
272
+ }
273
+
274
+ private func forwardToOriginalDelegate(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
275
+ if let originalDelegate = originalDelegate, originalDelegate.responds(to: #selector(userNotificationCenter(_:willPresent:withCompletionHandler:))) {
276
+ originalDelegate.userNotificationCenter?(center, willPresent: notification, withCompletionHandler: completionHandler)
277
+ } else {
278
+ handleBackgroundRunnerPresentation(completionHandler: completionHandler)
279
+ }
280
+ }
178
281
  }
@@ -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
+ }