@capacitor/background-runner 1.0.0-rc.1
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 +17 -0
- package/README.md +310 -0
- package/android/build.gradle +67 -0
- package/android/src/main/AndroidManifest.xml +12 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunner.kt +166 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunnerPlugin.kt +102 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/CoroutineHelper.kt +21 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/RunnerConfig.kt +33 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/RunnerWorker.kt +46 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Device.kt +99 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Geolocation.kt +55 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/KV.kt +29 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/NetworkStatus.kt +10 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notification.kt +59 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt +158 -0
- package/android/src/main/java/io/ionic/backgroundrunner/plugin/api/TimedNotificationPublisher.kt +40 -0
- package/android/src/main/libs/android-js-engine-release.aar +0 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +208 -0
- package/dist/esm/apis.d.ts +52 -0
- package/dist/esm/apis.js +2 -0
- package/dist/esm/apis.js.map +1 -0
- package/dist/esm/definitions.d.ts +82 -0
- package/dist/esm/definitions.js +3 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +8 -0
- package/dist/esm/web.js +16 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +32 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +35 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/BackgroundRunner.swift +223 -0
- package/ios/Plugin/BackgroundRunnerErrors.swift +8 -0
- package/ios/Plugin/BackgroundRunnerPlugin.h +10 -0
- package/ios/Plugin/BackgroundRunnerPlugin.m +11 -0
- package/ios/Plugin/BackgroundRunnerPlugin.swift +161 -0
- package/ios/Plugin/CapacitorAPI/Device.swift +116 -0
- package/ios/Plugin/CapacitorAPI/Geolocation.swift +190 -0
- package/ios/Plugin/CapacitorAPI/KV.swift +22 -0
- package/ios/Plugin/CapacitorAPI/Notifications.swift +165 -0
- package/ios/Plugin/CapacitorAPI/Reachability.swift +395 -0
- package/ios/Plugin/CapacitorAPI/Wearable.swift +48 -0
- package/ios/Plugin/Context+CapacitorAPI.swift +11 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/RunnerConfig.swift +36 -0
- package/ios/Plugin/RunnerEngine/Context.swift +151 -0
- package/ios/Plugin/RunnerEngine/Errors.swift +8 -0
- package/ios/Plugin/RunnerEngine/JSConsole.swift +38 -0
- package/ios/Plugin/RunnerEngine/JSCrypto.swift +38 -0
- package/ios/Plugin/RunnerEngine/JSError.swift +33 -0
- package/ios/Plugin/RunnerEngine/JSFetch.swift +50 -0
- package/ios/Plugin/RunnerEngine/JSResponse.swift +69 -0
- package/ios/Plugin/RunnerEngine/JSTextDecoder.swift +60 -0
- package/ios/Plugin/RunnerEngine/JSTextEncoder.swift +12 -0
- package/ios/Plugin/RunnerEngine/Runner.swift +23 -0
- package/ios/Plugin/WatchConnectivtySessionDelegate.swift +34 -0
- package/package.json +81 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import JavaScriptCore
|
|
3
|
+
import CoreLocation
|
|
4
|
+
|
|
5
|
+
@objc protocol CapacitorGeolocationExports: JSExport {
|
|
6
|
+
var isWatchingPosition: Bool { get }
|
|
7
|
+
func startWatchingPosition(_ highAccuracy: Bool)
|
|
8
|
+
func stopWatchingPosition()
|
|
9
|
+
func getCurrentPosition() -> [String: Any]?
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
enum CapacitorGeolocationErrors: Error, Equatable {
|
|
13
|
+
case unauthorized
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class CapacitorGeolocation: NSObject, CapacitorGeolocationExports, CLLocationManagerDelegate {
|
|
17
|
+
private weak var context: Context?
|
|
18
|
+
private var pendingCurrentLocationCalls: [Int: (Result<CLLocation?, Error>) -> Void] = [:]
|
|
19
|
+
private var isWatchingLocation: Bool = false
|
|
20
|
+
private let locationManager: CLLocationManager
|
|
21
|
+
|
|
22
|
+
private var permissionContinuation: CheckedContinuation<Void, Error>?
|
|
23
|
+
|
|
24
|
+
var isWatchingPosition: Bool {
|
|
25
|
+
return isWatchingLocation
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
init(context: Context? = nil) {
|
|
29
|
+
self.locationManager = CLLocationManager()
|
|
30
|
+
super.init()
|
|
31
|
+
|
|
32
|
+
self.context = context
|
|
33
|
+
self.locationManager.delegate = self
|
|
34
|
+
|
|
35
|
+
self.locationManager.allowsBackgroundLocationUpdates = true
|
|
36
|
+
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
|
|
37
|
+
self.locationManager.pausesLocationUpdatesAutomatically = false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static func getCurrentKnownLocation() -> CLLocation? {
|
|
41
|
+
return CLLocationManager().location
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static func checkPermission() -> String {
|
|
45
|
+
if !CLLocationManager.locationServicesEnabled() {
|
|
46
|
+
return "denied"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
var permission: String = "prompt"
|
|
50
|
+
|
|
51
|
+
switch CLLocationManager.authorizationStatus() {
|
|
52
|
+
case .authorized, .authorizedWhenInUse, .authorizedAlways:
|
|
53
|
+
permission = "granted"
|
|
54
|
+
case .notDetermined:
|
|
55
|
+
permission = "prompt"
|
|
56
|
+
case .denied, .restricted:
|
|
57
|
+
permission = "denied"
|
|
58
|
+
default:
|
|
59
|
+
permission = "prompt"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return permission
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func requestPermission() async throws {
|
|
66
|
+
if !CLLocationManager.locationServicesEnabled() {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if CLLocationManager.authorizationStatus() != .notDetermined {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation<Void, Error>) in
|
|
75
|
+
permissionContinuation = continuation
|
|
76
|
+
|
|
77
|
+
DispatchQueue.main.async {
|
|
78
|
+
self.locationManager.requestAlwaysAuthorization()
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func startWatchingPosition(_ highAccuracy: Bool) {
|
|
84
|
+
do {
|
|
85
|
+
if CapacitorGeolocation.checkPermission() != "granted" {
|
|
86
|
+
throw CapacitorGeolocationErrors.unauthorized
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
DispatchQueue.main.sync {
|
|
90
|
+
if highAccuracy {
|
|
91
|
+
self.locationManager.startUpdatingLocation()
|
|
92
|
+
} else {
|
|
93
|
+
self.locationManager.startMonitoringSignificantLocationChanges()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
self.isWatchingLocation = true
|
|
98
|
+
} catch {
|
|
99
|
+
JSContext.current().exception = JSValue(newErrorFromMessage: "\(error)", in: JSContext.current())
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
func stopWatchingPosition() {
|
|
104
|
+
DispatchQueue.main.sync {
|
|
105
|
+
self.locationManager.stopUpdatingLocation()
|
|
106
|
+
self.locationManager.stopMonitoringSignificantLocationChanges()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
self.isWatchingLocation = false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func getCurrentPosition() -> [String: Any]? {
|
|
113
|
+
do {
|
|
114
|
+
if CapacitorGeolocation.checkPermission() != "granted" {
|
|
115
|
+
throw CapacitorGeolocationErrors.unauthorized
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
guard let lastLocation = self.locationManager.location else {
|
|
119
|
+
return nil
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return buildLocationDict(location: lastLocation)
|
|
123
|
+
} catch {
|
|
124
|
+
JSContext.current().exception = JSValue(newErrorFromMessage: "\(error)", in: JSContext.current())
|
|
125
|
+
|
|
126
|
+
return nil
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
131
|
+
if let context = self.context, self.isWatchingLocation {
|
|
132
|
+
let locations: [[String: Any]] = locations.map { location in
|
|
133
|
+
return buildLocationDict(location: location)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var details: [String: Any] = [:]
|
|
137
|
+
details["locations"] = locations
|
|
138
|
+
|
|
139
|
+
try? context.dispatchEvent(event: "currentLocation", details: details)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
pendingCurrentLocationCalls.forEach { (callId: Int, callback: (Result<CLLocation?, Error>) -> Void) in
|
|
143
|
+
pendingCurrentLocationCalls.removeValue(forKey: callId)
|
|
144
|
+
callback(.success(locations.last))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if let location = locations.last {
|
|
148
|
+
print("\(location.coordinate.latitude),\(location.coordinate.longitude)")
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
153
|
+
if let continuation = permissionContinuation {
|
|
154
|
+
continuation.resume(throwing: error)
|
|
155
|
+
permissionContinuation = nil
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if let context = context, self.isWatchingLocation {
|
|
159
|
+
var details: [String: Any] = [:]
|
|
160
|
+
details["error"] = error
|
|
161
|
+
|
|
162
|
+
try? context.dispatchEvent(event: "currentLocation", details: details)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pendingCurrentLocationCalls.forEach { (callId: Int, callback: (Result<CLLocation?, Error>) -> Void) in
|
|
166
|
+
pendingCurrentLocationCalls.removeValue(forKey: callId)
|
|
167
|
+
callback(.failure(error))
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
172
|
+
if let continuation = permissionContinuation {
|
|
173
|
+
continuation.resume()
|
|
174
|
+
permissionContinuation = nil
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private func buildLocationDict(location: CLLocation) -> [String: Any] {
|
|
179
|
+
var coords: [String: Any] = [:]
|
|
180
|
+
coords["latitude"] = location.coordinate.latitude
|
|
181
|
+
coords["longitude"] = location.coordinate.longitude
|
|
182
|
+
coords["accuracy"] = location.horizontalAccuracy
|
|
183
|
+
coords["altitude"] = location.altitude
|
|
184
|
+
coords["altitudeAccuracy"] = location.verticalAccuracy
|
|
185
|
+
coords["speed"] = location.speed
|
|
186
|
+
coords["heading"] = location.course
|
|
187
|
+
|
|
188
|
+
return coords
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import JavaScriptCore
|
|
3
|
+
|
|
4
|
+
@objc protocol CapacitorKVStoreExports: JSExport {
|
|
5
|
+
static func set(_ key: String, _ value: String)
|
|
6
|
+
static func get(_ key: String) -> String?
|
|
7
|
+
static func remove(_ key: String)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class CapacitorKVStore: NSObject, CapacitorKVStoreExports {
|
|
11
|
+
static func set(_ key: String, _ value: String) {
|
|
12
|
+
UserDefaults.standard.set(value, forKey: key)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static func get(_ key: String) -> String? {
|
|
16
|
+
return UserDefaults.standard.string(forKey: key)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static func remove(_ key: String) {
|
|
20
|
+
return UserDefaults.standard.removeObject(forKey: key)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import JavaScriptCore
|
|
3
|
+
import UserNotifications
|
|
4
|
+
|
|
5
|
+
enum CapacitorNotificationsErrors: Error, Equatable {
|
|
6
|
+
case invalidOptions(reason: String)
|
|
7
|
+
case unknownError(reason: String)
|
|
8
|
+
case permissionDenied
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
struct NotificationOption {
|
|
12
|
+
let id: Int
|
|
13
|
+
let title: String
|
|
14
|
+
let body: String
|
|
15
|
+
let scheduleAt: Date
|
|
16
|
+
let sound: String?
|
|
17
|
+
let actionTypeId: String?
|
|
18
|
+
let extra: [String: Any]?
|
|
19
|
+
let threadIdentifier: String?
|
|
20
|
+
let summaryArgument: String?
|
|
21
|
+
let groupSummary: String?
|
|
22
|
+
|
|
23
|
+
init(from dict: [String: Any?]) throws {
|
|
24
|
+
guard let id = dict["id"] as? Int else {
|
|
25
|
+
throw CapacitorNotificationsErrors.invalidOptions(reason: "notification id is required")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
guard let title = dict["title"] as? String else {
|
|
29
|
+
throw CapacitorNotificationsErrors.invalidOptions(reason: "notification title is required")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
guard let body = dict["body"] as? String else {
|
|
33
|
+
throw CapacitorNotificationsErrors.invalidOptions(reason: "notification body is required")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
guard let scheduleAt = dict["scheduleAt"] as? Date else {
|
|
37
|
+
throw CapacitorNotificationsErrors.invalidOptions(reason: "notification schedule date is required")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
self.id = id
|
|
41
|
+
self.title = title
|
|
42
|
+
self.body = body
|
|
43
|
+
self.scheduleAt = scheduleAt
|
|
44
|
+
self.threadIdentifier = dict["threadIdentifier"] as? String
|
|
45
|
+
self.sound = dict["sound"] as? String
|
|
46
|
+
self.actionTypeId = dict["actionTypeId"] as? String
|
|
47
|
+
self.extra = dict["extra"] as? [String: Any]
|
|
48
|
+
self.summaryArgument = dict["summaryArgument"] as? String
|
|
49
|
+
self.groupSummary = dict["groupSummary"] as? String
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@objc protocol CapacitorNotificationsExports: JSExport {
|
|
54
|
+
static func schedule(_ options: JSValue)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class CapacitorNotifications: NSObject, CapacitorNotificationsExports {
|
|
58
|
+
static func checkPermission() -> String {
|
|
59
|
+
let group = DispatchGroup()
|
|
60
|
+
var permission: String = "prompt"
|
|
61
|
+
|
|
62
|
+
group.enter()
|
|
63
|
+
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
|
64
|
+
switch settings.authorizationStatus {
|
|
65
|
+
case .authorized, .ephemeral, .provisional:
|
|
66
|
+
permission = "granted"
|
|
67
|
+
case .denied:
|
|
68
|
+
permission = "denied"
|
|
69
|
+
case .notDetermined:
|
|
70
|
+
permission = "prompt"
|
|
71
|
+
@unknown default:
|
|
72
|
+
permission = "prompt"
|
|
73
|
+
}
|
|
74
|
+
group.leave()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
group.wait()
|
|
78
|
+
|
|
79
|
+
return permission
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static func requestPermission() throws {
|
|
83
|
+
var permissionsError: CapacitorNotificationsErrors?
|
|
84
|
+
|
|
85
|
+
let group = DispatchGroup()
|
|
86
|
+
|
|
87
|
+
group.enter()
|
|
88
|
+
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, error in
|
|
89
|
+
if let error = error {
|
|
90
|
+
permissionsError = CapacitorNotificationsErrors.unknownError(reason: "\(error)")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
group.leave()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
group.wait()
|
|
97
|
+
|
|
98
|
+
if let err = permissionsError {
|
|
99
|
+
throw err
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static func schedule(_ options: JSValue) {
|
|
104
|
+
do {
|
|
105
|
+
if CapacitorNotifications.checkPermission() != "granted" {
|
|
106
|
+
throw CapacitorNotificationsErrors.permissionDenied
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if options.isUndefined || options.isNull {
|
|
110
|
+
throw CapacitorNotificationsErrors.invalidOptions(reason: "options are null")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if !options.isArray {
|
|
114
|
+
throw CapacitorNotificationsErrors.invalidOptions(reason: "options must be an array")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if let notificationOptions = options.toArray() as? [[String: Any?]] {
|
|
118
|
+
for option in notificationOptions {
|
|
119
|
+
let notificationOption = try NotificationOption(from: option)
|
|
120
|
+
|
|
121
|
+
let content = UNMutableNotificationContent()
|
|
122
|
+
content.title = NSString.localizedUserNotificationString(forKey: notificationOption.title, arguments: nil)
|
|
123
|
+
content.body = NSString.localizedUserNotificationString(forKey: notificationOption.body, arguments: nil)
|
|
124
|
+
content.userInfo = [:]
|
|
125
|
+
|
|
126
|
+
if let extra = notificationOption.extra {
|
|
127
|
+
content.userInfo["cap_extra"] = extra
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if let actionTypeId = notificationOption.actionTypeId {
|
|
131
|
+
content.categoryIdentifier = actionTypeId
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if let threadIdentifier = notificationOption.threadIdentifier {
|
|
135
|
+
content.threadIdentifier = threadIdentifier
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if let summaryArgument = notificationOption.summaryArgument {
|
|
139
|
+
content.summaryArgument = summaryArgument
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if let sound = notificationOption.sound {
|
|
143
|
+
content.sound = UNNotificationSound(named: UNNotificationSoundName(sound))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
var notificationDate = notificationOption.scheduleAt
|
|
147
|
+
|
|
148
|
+
if notificationDate < Date() {
|
|
149
|
+
notificationDate = Date()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let dateInfo = Calendar.current.dateComponents(in: TimeZone.current, from: notificationDate)
|
|
153
|
+
let dateInterval = DateInterval(start: Date(), end: dateInfo.date!)
|
|
154
|
+
|
|
155
|
+
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: dateInterval.duration, repeats: false)
|
|
156
|
+
let request = UNNotificationRequest(identifier: "\(notificationOption.id)", content: content, trigger: trigger)
|
|
157
|
+
|
|
158
|
+
UNUserNotificationCenter.current().add(request)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
JSContext.current().exception = JSValue(newErrorFromMessage: "\(error)", in: JSContext.current())
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|