@woosmap/react-native-plugin-geofencing 1.0.0-beta.2 → 1.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/CHANGELOG.md +1 -1
- package/README.md +0 -9
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/{reactnativeplugingeofencing → woosmap/reactnativeplugingeofencing}/AbstractPushHelper.java +1 -1
- package/android/src/main/java/com/{reactnativeplugingeofencing → woosmap/reactnativeplugingeofencing}/AirshipPushHelper.java +1 -1
- package/android/src/main/java/com/{reactnativeplugingeofencing → woosmap/reactnativeplugingeofencing}/WoosLocationReadyListener.java +1 -1
- package/android/src/main/java/com/{reactnativeplugingeofencing → woosmap/reactnativeplugingeofencing}/WoosRegionReadyListener.java +1 -1
- package/android/src/main/java/com/woosmap/reactnativeplugingeofencing/WoosmapGeofencingTurboModule.java +1025 -0
- package/android/src/main/java/com/{reactnativeplugingeofencing → woosmap/reactnativeplugingeofencing}/WoosmapGeofencingTurboPackage.java +3 -7
- package/android/src/main/java/com/{reactnativeplugingeofencing → woosmap/reactnativeplugingeofencing}/WoosmapMessageAndKey.java +2 -3
- package/android/src/main/java/com/{reactnativeplugingeofencing → woosmap/reactnativeplugingeofencing}/WoosmapTask.java +34 -128
- package/android/src/main/java/com/{reactnativeplugingeofencing → woosmap/reactnativeplugingeofencing}/WoosmapUtil.java +1 -1
- package/ios/WoosmapGeofenceMessage.swift +1 -0
- package/ios/WoosmapGeofencingTurbo.mm +110 -10
- package/ios/WoosmapGeofencingTurbo.swift +873 -11
- package/lib/commonjs/NativeWoosmapGeofencingTurbo.js +6 -3
- package/lib/commonjs/NativeWoosmapGeofencingTurbo.js.map +1 -1
- package/lib/commonjs/index.js +37 -131
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeWoosmapGeofencingTurbo.js +6 -3
- package/lib/module/NativeWoosmapGeofencingTurbo.js.map +1 -1
- package/lib/module/index.js +37 -131
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeWoosmapGeofencingTurbo.d.ts +37 -3
- package/lib/typescript/src/NativeWoosmapGeofencingTurbo.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeWoosmapGeofencingTurbo.ts +56 -3
- package/android/src/main/java/com/reactnativeplugingeofencing/PluginGeofencingModule.java +0 -1204
- package/android/src/main/java/com/reactnativeplugingeofencing/PluginGeofencingPackage.java +0 -28
- package/android/src/main/java/com/reactnativeplugingeofencing/WoosmapGeofencingTurboModule.java +0 -185
- package/ios/PluginGeofencing.mm +0 -123
- package/ios/PluginGeofencing.swift +0 -1243
- package/lib/commonjs/internal/nativeInterface.js +0 -13
- package/lib/commonjs/internal/nativeInterface.js.map +0 -1
- package/lib/module/internal/nativeInterface.js +0 -9
- package/lib/module/internal/nativeInterface.js.map +0 -1
- package/lib/typescript/src/internal/nativeInterface.d.ts +0 -3
- package/lib/typescript/src/internal/nativeInterface.d.ts.map +0 -1
- /package/ios/{PluginGeofencing-Bridging-Header.h → WoosmapGeofencing-Bridging-Header.h} +0 -0
|
@@ -1,22 +1,71 @@
|
|
|
1
1
|
import CoreLocation
|
|
2
2
|
import WoosmapGeofencing
|
|
3
3
|
|
|
4
|
-
/// TurboModule implementation
|
|
5
|
-
///
|
|
6
|
-
/// `WoosmapGeofenceService` — the same service the legacy `PluginGeofencing`
|
|
7
|
-
/// module talks to — so the two modules stay independent without duplicating
|
|
8
|
-
/// business logic.
|
|
4
|
+
/// TurboModule implementation — Approach B: extends RCTEventEmitter so it can
|
|
5
|
+
/// both expose native methods and fire JS events (geolocationDidChange, etc.).
|
|
9
6
|
///
|
|
10
7
|
/// The Objective-C++ bridge in `WoosmapGeofencingTurbo.mm` exposes these
|
|
11
8
|
/// methods to the generated `RCTNativeWoosmapGeofencingTurboSpec` protocol and
|
|
12
9
|
/// vends the JSI TurboModule.
|
|
13
10
|
@objc(WoosmapGeofencingTurbo)
|
|
14
|
-
class WoosmapGeofencingTurbo:
|
|
11
|
+
class WoosmapGeofencingTurbo: RCTEventEmitter {
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
/// Location manager used solely to request permissions.
|
|
14
|
+
private var templocationChecker: CLLocationManager!
|
|
15
|
+
|
|
16
|
+
/// Active location watch IDs keyed by watch-id string.
|
|
17
|
+
private var locationWatchStack: [String: String] = [:]
|
|
18
|
+
|
|
19
|
+
/// Active region watch IDs keyed by watch-id string.
|
|
20
|
+
private var regionWatchStack: [String: String] = [:]
|
|
21
|
+
|
|
22
|
+
/// Resolver for an in-flight `requestPermissions` call that is waiting for the
|
|
23
|
+
/// user to answer the system location dialog. Settled in
|
|
24
|
+
/// `locationManagerDidChangeAuthorization(_:)` once the OS reports the choice.
|
|
25
|
+
private var permissionResolve: RCTPromiseResolveBlock?
|
|
26
|
+
|
|
27
|
+
/// Forces the module to be initialised on the main queue.
|
|
28
|
+
/// - Returns: `true` so UIKit / CoreLocation calls are safe at setup.
|
|
29
|
+
@objc override static func requiresMainQueueSetup() -> Bool {
|
|
17
30
|
return true
|
|
18
31
|
}
|
|
19
32
|
|
|
33
|
+
// MARK: - RCTEventEmitter
|
|
34
|
+
|
|
35
|
+
/// Creates the module and configures the permission-checking location manager.
|
|
36
|
+
override init() {
|
|
37
|
+
super.init()
|
|
38
|
+
templocationChecker = CLLocationManager()
|
|
39
|
+
templocationChecker.delegate = self
|
|
40
|
+
templocationChecker.desiredAccuracy = kCLLocationAccuracyBest
|
|
41
|
+
locationWatchStack = [:]
|
|
42
|
+
regionWatchStack = [:]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// The set of event names this emitter can dispatch to JS.
|
|
46
|
+
/// - Returns: The supported `geolocation` / `woosmapgeofenceRegion` event names.
|
|
47
|
+
override func supportedEvents() -> [String]! {
|
|
48
|
+
return ["geolocationDidChange", "geolocationError",
|
|
49
|
+
"woosmapgeofenceRegionDidChange", "woosmapgeofenceRegionError"]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Registers a JS listener for the given event. Required stub so the
|
|
53
|
+
/// TurboModule codegen emits the correct selector.
|
|
54
|
+
/// - Parameter eventName: The event the JS side is subscribing to.
|
|
55
|
+
@objc(addListener:)
|
|
56
|
+
override func addListener(_ eventName: String) { super.addListener(eventName) }
|
|
57
|
+
|
|
58
|
+
/// Removes JS listeners. Required stub so the TurboModule codegen emits the
|
|
59
|
+
/// correct selector.
|
|
60
|
+
/// - Parameter count: The number of listeners to remove.
|
|
61
|
+
@objc(removeListeners:)
|
|
62
|
+
override func removeListeners(_ count: Double) { super.removeListeners(count) }
|
|
63
|
+
|
|
64
|
+
// MARK: - Private helpers
|
|
65
|
+
|
|
66
|
+
/// Builds an `NSError` in the plugin's error domain.
|
|
67
|
+
/// - Parameter msg: The localized description for the error.
|
|
68
|
+
/// - Returns: An `NSError` carrying `msg` under `NSLocalizedDescriptionKey`.
|
|
20
69
|
private func woosmapError(_ msg: String) -> NSError {
|
|
21
70
|
return NSError(
|
|
22
71
|
domain: WoosmapGeofenceMessage.plugin_errorDomain,
|
|
@@ -25,6 +74,11 @@ class WoosmapGeofencingTurbo: NSObject {
|
|
|
25
74
|
)
|
|
26
75
|
}
|
|
27
76
|
|
|
77
|
+
// MARK: - Formatters
|
|
78
|
+
|
|
79
|
+
/// Maps a native `Region` into the lower-cased dictionary shape the JS facade expects.
|
|
80
|
+
/// - Parameter woosdata: The region to serialize.
|
|
81
|
+
/// - Returns: A dictionary mirroring the `RegionRecord` spec type.
|
|
28
82
|
private func formatRegionData(woosdata: Region) -> [AnyHashable: Any] {
|
|
29
83
|
var result: [AnyHashable: Any] = [:]
|
|
30
84
|
result["date"] = woosdata.date.timeIntervalSince1970 * 1000
|
|
@@ -39,6 +93,377 @@ class WoosmapGeofencingTurbo: NSObject {
|
|
|
39
93
|
return result
|
|
40
94
|
}
|
|
41
95
|
|
|
96
|
+
/// Maps a native `Location` into the dictionary shape the JS facade expects.
|
|
97
|
+
/// - Parameter woosdata: The location to serialize.
|
|
98
|
+
/// - Returns: A dictionary with the location's date/coordinates/identifier.
|
|
99
|
+
private func formatLocationData(woosdata: Location) -> [AnyHashable: Any] {
|
|
100
|
+
var result: [AnyHashable: Any] = [:]
|
|
101
|
+
if let date = woosdata.date {
|
|
102
|
+
result["date"] = date.timeIntervalSince1970 * 1000
|
|
103
|
+
} else {
|
|
104
|
+
result["date"] = 0
|
|
105
|
+
}
|
|
106
|
+
result["latitude"] = woosdata.latitude
|
|
107
|
+
result["locationdescription"] = woosdata.locationDescription
|
|
108
|
+
result["locationid"] = woosdata.locationId
|
|
109
|
+
result["longitude"] = woosdata.longitude
|
|
110
|
+
return result
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Maps a native `POI` into the dictionary shape the JS facade expects.
|
|
114
|
+
/// - Parameter woosdata: The POI to serialize.
|
|
115
|
+
/// - Returns: A dictionary with the POI's metadata, coordinates and user properties.
|
|
116
|
+
private func formatPOIData(woosdata: POI) -> [AnyHashable: Any] {
|
|
117
|
+
var result: [AnyHashable: Any] = [:]
|
|
118
|
+
if let data = woosdata.jsonData {
|
|
119
|
+
do {
|
|
120
|
+
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
|
|
121
|
+
result["jsondata"] = json
|
|
122
|
+
} catch { }
|
|
123
|
+
}
|
|
124
|
+
result["city"] = woosdata.city
|
|
125
|
+
result["idstore"] = woosdata.idstore
|
|
126
|
+
result["name"] = woosdata.name
|
|
127
|
+
if let date = woosdata.date {
|
|
128
|
+
result["date"] = date.timeIntervalSince1970 * 1000
|
|
129
|
+
} else {
|
|
130
|
+
result["date"] = 0
|
|
131
|
+
}
|
|
132
|
+
result["distance"] = woosdata.distance
|
|
133
|
+
result["duration"] = woosdata.duration ?? "-"
|
|
134
|
+
result["latitude"] = woosdata.latitude
|
|
135
|
+
result["locationid"] = woosdata.locationId
|
|
136
|
+
result["longitude"] = woosdata.longitude
|
|
137
|
+
result["zipcode"] = woosdata.zipCode
|
|
138
|
+
result["radius"] = woosdata.radius
|
|
139
|
+
result["address"] = woosdata.address
|
|
140
|
+
result["countrycode"] = woosdata.countryCode
|
|
141
|
+
result["tags"] = woosdata.tags
|
|
142
|
+
result["types"] = woosdata.types
|
|
143
|
+
result["contact"] = woosdata.contact
|
|
144
|
+
result["userProperties"] = woosdata.user_properties
|
|
145
|
+
result["openNow"] = woosdata.openNow
|
|
146
|
+
return result
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// Maps a native `IndoorBeacon` into the dictionary shape the JS facade expects.
|
|
150
|
+
/// - Parameter woosdata: The indoor beacon to serialize.
|
|
151
|
+
/// - Returns: A dictionary with the beacon's identifiers, coordinates and properties.
|
|
152
|
+
private func formatIndoorBeaconData(woosdata: IndoorBeacon) -> [AnyHashable: Any] {
|
|
153
|
+
var result: [AnyHashable: Any] = [:]
|
|
154
|
+
result["identifier"] = woosdata.identifier
|
|
155
|
+
result["properties"] = woosdata.properties
|
|
156
|
+
result["latitude"] = woosdata.latitude
|
|
157
|
+
result["longitude"] = woosdata.longitude
|
|
158
|
+
result["venueId"] = woosdata.venue_id
|
|
159
|
+
result["major"] = woosdata.Major
|
|
160
|
+
result["minor"] = woosdata.Minor
|
|
161
|
+
result["beaconId"] = woosdata.BeaconID
|
|
162
|
+
result["date"] = woosdata.date.timeIntervalSince1970 * 1000
|
|
163
|
+
return result
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// MARK: - Initialize
|
|
167
|
+
|
|
168
|
+
/// Initializes the Woosmap geofencing service with the supplied options
|
|
169
|
+
/// (Woosmap key, tracking profile, protected-region slot, Airship flag).
|
|
170
|
+
/// - Parameters:
|
|
171
|
+
/// - command: A dictionary of initialization options.
|
|
172
|
+
/// - resolve: Promise resolver invoked on success.
|
|
173
|
+
/// - reject: Promise rejecter invoked on invalid options or failure.
|
|
174
|
+
@objc(initialize:resolve:reject:)
|
|
175
|
+
func initialize(command: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
176
|
+
guard let initParameters = command as? [String: Any] else {
|
|
177
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
178
|
+
WoosmapGeofenceMessage.plugin_parsingFailed,
|
|
179
|
+
woosmapError(WoosmapGeofenceMessage.plugin_parsingFailed))
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
var isCallUnsuccessful = false
|
|
183
|
+
var privateKeyWoosmapAPI = ""
|
|
184
|
+
var isAirshipCallbackEnable: Bool = false
|
|
185
|
+
var protectedRegionSlot: Int = -1
|
|
186
|
+
|
|
187
|
+
if let keyProtectedRegionSlot = initParameters["protectedRegionSlot"] as? Int {
|
|
188
|
+
if keyProtectedRegionSlot > 3 {
|
|
189
|
+
isCallUnsuccessful = true
|
|
190
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
191
|
+
WoosmapGeofenceMessage.invalidProtectedRegionSlot,
|
|
192
|
+
woosmapError(WoosmapGeofenceMessage.invalidProtectedRegionSlot))
|
|
193
|
+
} else {
|
|
194
|
+
protectedRegionSlot = keyProtectedRegionSlot
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if let keyWoosmapAPI = initParameters["privateKeyWoosmapAPI"] as? String {
|
|
199
|
+
privateKeyWoosmapAPI = keyWoosmapAPI
|
|
200
|
+
}
|
|
201
|
+
#if canImport(AirshipKit)
|
|
202
|
+
if let keyAirshipPush = initParameters["enableAirshipConnector"] as? Bool {
|
|
203
|
+
isAirshipCallbackEnable = keyAirshipPush
|
|
204
|
+
}
|
|
205
|
+
#endif
|
|
206
|
+
|
|
207
|
+
if let trackingProfile = initParameters["trackingProfile"] as? String {
|
|
208
|
+
if ConfigurationProfile(rawValue: trackingProfile) != nil {
|
|
209
|
+
WoosmapGeofenceService.setup(woosmapKey: privateKeyWoosmapAPI,
|
|
210
|
+
configurationProfile: trackingProfile,
|
|
211
|
+
airshipTrackingEnable: isAirshipCallbackEnable,
|
|
212
|
+
protectedRegionSlot: protectedRegionSlot)
|
|
213
|
+
} else {
|
|
214
|
+
isCallUnsuccessful = true
|
|
215
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
216
|
+
WoosmapGeofenceMessage.invalidProfile,
|
|
217
|
+
woosmapError(WoosmapGeofenceMessage.invalidProfile))
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
WoosmapGeofenceService.setup(woosmapKey: privateKeyWoosmapAPI,
|
|
221
|
+
configurationProfile: "",
|
|
222
|
+
airshipTrackingEnable: isAirshipCallbackEnable,
|
|
223
|
+
protectedRegionSlot: protectedRegionSlot)
|
|
224
|
+
}
|
|
225
|
+
if isCallUnsuccessful == false {
|
|
226
|
+
resolve(WoosmapGeofenceMessage.initialize)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// MARK: - Permission requests
|
|
231
|
+
|
|
232
|
+
/// Requests location permission from the user, presenting a settings prompt
|
|
233
|
+
/// when the permission has already been decided.
|
|
234
|
+
/// - Parameters:
|
|
235
|
+
/// - background: Pass `true` for always-on access, `false` for when-in-use access.
|
|
236
|
+
/// - resolve: Promise resolver invoked with the permission-flow status.
|
|
237
|
+
/// - reject: Promise rejecter invoked on failure.
|
|
238
|
+
@objc(requestPermissions:resolve:reject:)
|
|
239
|
+
func requestPermissions(background: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
240
|
+
// CLLocationManager must be created/messaged on the main thread; bridge
|
|
241
|
+
// methods otherwise run on a background queue. Hop to main before touching it.
|
|
242
|
+
DispatchQueue.main.async {
|
|
243
|
+
self.performRequestPermissions(background: background, resolve: resolve, reject: reject)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Main-thread body of `requestPermissions`. See that method for parameter docs.
|
|
248
|
+
private func performRequestPermissions(background: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
249
|
+
// A request is already awaiting the OS dialog: reject this new call rather
|
|
250
|
+
// than overwriting (and leaking) the in-flight promise.
|
|
251
|
+
if permissionResolve != nil {
|
|
252
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
253
|
+
WoosmapGeofenceMessage.permissionRequestInProgress,
|
|
254
|
+
woosmapError(WoosmapGeofenceMessage.permissionRequestInProgress))
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
let status = CLLocationManager().authorizationStatus
|
|
258
|
+
|
|
259
|
+
if background {
|
|
260
|
+
if status == .notDetermined {
|
|
261
|
+
// Fresh install: defer the promise until the OS reports the user's
|
|
262
|
+
// choice via locationManagerDidChangeAuthorization(_:). Not settling
|
|
263
|
+
// here would hang the JS caller forever.
|
|
264
|
+
permissionResolve = resolve
|
|
265
|
+
self.templocationChecker.requestAlwaysAuthorization()
|
|
266
|
+
} else if status == .authorizedAlways {
|
|
267
|
+
resolve(WoosmapGeofenceMessage.samePermission)
|
|
268
|
+
} else {
|
|
269
|
+
resolve(WoosmapGeofenceMessage.showingPermissionBox)
|
|
270
|
+
Task { @MainActor in
|
|
271
|
+
let appname: String = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String ?? ""
|
|
272
|
+
var alertInfo: String = ""
|
|
273
|
+
if status == .denied {
|
|
274
|
+
alertInfo = String(format: WoosmapGeofenceMessage.deniedPermission, appname)
|
|
275
|
+
} else {
|
|
276
|
+
alertInfo = String(format: WoosmapGeofenceMessage.replacePermission, appname)
|
|
277
|
+
}
|
|
278
|
+
let alert = UIAlertController(title: "", message: alertInfo, preferredStyle: UIAlertController.Style.alert)
|
|
279
|
+
alert.addAction(UIAlertAction(title: WoosmapGeofenceMessage.cancel, style: UIAlertAction.Style.default, handler: nil))
|
|
280
|
+
alert.addAction(UIAlertAction(title: WoosmapGeofenceMessage.setting, style: UIAlertAction.Style.default, handler: { _ in
|
|
281
|
+
if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
|
|
282
|
+
UIApplication.shared.open(settingsUrl, options: [:], completionHandler: nil)
|
|
283
|
+
}
|
|
284
|
+
}))
|
|
285
|
+
var rootViewController = UIApplication.shared.topViewController
|
|
286
|
+
if let navigationController = rootViewController as? UINavigationController {
|
|
287
|
+
rootViewController = navigationController.viewControllers.first
|
|
288
|
+
}
|
|
289
|
+
if let tabBarController = rootViewController as? UITabBarController {
|
|
290
|
+
rootViewController = tabBarController.selectedViewController
|
|
291
|
+
}
|
|
292
|
+
rootViewController?.present(alert, animated: true, completion: nil)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
if status == .notDetermined {
|
|
297
|
+
// Fresh install: defer the promise until the OS reports the user's
|
|
298
|
+
// choice via locationManagerDidChangeAuthorization(_:). Not settling
|
|
299
|
+
// here would hang the JS caller forever.
|
|
300
|
+
permissionResolve = resolve
|
|
301
|
+
self.templocationChecker.requestWhenInUseAuthorization()
|
|
302
|
+
} else if status == .authorizedWhenInUse {
|
|
303
|
+
resolve(WoosmapGeofenceMessage.samePermission)
|
|
304
|
+
} else {
|
|
305
|
+
resolve(WoosmapGeofenceMessage.showingPermissionBox)
|
|
306
|
+
Task { @MainActor in
|
|
307
|
+
let appname: String = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String ?? ""
|
|
308
|
+
var alertInfo: String = ""
|
|
309
|
+
if status == .denied {
|
|
310
|
+
alertInfo = String(format: WoosmapGeofenceMessage.deniedPermission, appname)
|
|
311
|
+
} else {
|
|
312
|
+
alertInfo = String(format: WoosmapGeofenceMessage.replacePermission, appname)
|
|
313
|
+
}
|
|
314
|
+
let alert = UIAlertController(title: "", message: alertInfo, preferredStyle: UIAlertController.Style.alert)
|
|
315
|
+
alert.addAction(UIAlertAction(title: WoosmapGeofenceMessage.cancel, style: UIAlertAction.Style.default, handler: nil))
|
|
316
|
+
alert.addAction(UIAlertAction(title: WoosmapGeofenceMessage.setting, style: UIAlertAction.Style.default, handler: { _ in
|
|
317
|
+
if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
|
|
318
|
+
UIApplication.shared.open(settingsUrl, options: [:], completionHandler: nil)
|
|
319
|
+
}
|
|
320
|
+
}))
|
|
321
|
+
var rootViewController = UIApplication.shared.topViewController
|
|
322
|
+
if let navigationController = rootViewController as? UINavigationController {
|
|
323
|
+
rootViewController = navigationController.viewControllers.first
|
|
324
|
+
}
|
|
325
|
+
if let tabBarController = rootViewController as? UITabBarController {
|
|
326
|
+
rootViewController = tabBarController.selectedViewController
|
|
327
|
+
}
|
|
328
|
+
rootViewController?.present(alert, animated: true, completion: nil)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// Requests Bluetooth permission. iOS does not require an explicit BLE
|
|
335
|
+
/// permission for geofencing, so this always resolves `GRANTED`.
|
|
336
|
+
/// - Parameters:
|
|
337
|
+
/// - resolve: Promise resolver invoked with `"GRANTED"`.
|
|
338
|
+
/// - reject: Promise rejecter (unused on iOS).
|
|
339
|
+
@objc(requestBLEPermissions:reject:)
|
|
340
|
+
func requestBLEPermissions(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
341
|
+
resolve("GRANTED")
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/// Requests notification permission. iOS does not require this for
|
|
345
|
+
/// geofencing, so this always resolves `GRANTED`.
|
|
346
|
+
/// - Parameters:
|
|
347
|
+
/// - resolve: Promise resolver invoked with `"GRANTED"`.
|
|
348
|
+
/// - reject: Promise rejecter (unused on iOS).
|
|
349
|
+
@objc(requestNotificationPermissions:reject:)
|
|
350
|
+
func requestNotificationPermissions(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
351
|
+
resolve("GRANTED")
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// MARK: - Watch / clear location
|
|
355
|
+
|
|
356
|
+
/// Registers a location watch. The first watcher subscribes to `.newLocationSaved`.
|
|
357
|
+
/// - Parameters:
|
|
358
|
+
/// - watchid: The caller-provided identifier for this watch.
|
|
359
|
+
/// - resolve: Promise resolver invoked with `watchid` on success.
|
|
360
|
+
/// - reject: Promise rejecter invoked when the service is not initialized.
|
|
361
|
+
@objc(watchLocation:resolve:reject:)
|
|
362
|
+
func watchLocation(watchid: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
363
|
+
if WoosmapGeofenceService.shared != nil {
|
|
364
|
+
locationWatchStack[watchid] = watchid
|
|
365
|
+
if locationWatchStack.count == 1 {
|
|
366
|
+
NotificationCenter.default.addObserver(
|
|
367
|
+
self,
|
|
368
|
+
selector: #selector(newLocationAdded(_:)),
|
|
369
|
+
name: .newLocationSaved,
|
|
370
|
+
object: nil)
|
|
371
|
+
}
|
|
372
|
+
resolve(watchid)
|
|
373
|
+
} else {
|
|
374
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
375
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
376
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/// Removes the location watch with the given ID, unsubscribing once empty.
|
|
381
|
+
/// - Parameters:
|
|
382
|
+
/// - watchid: The identifier of the watch to remove.
|
|
383
|
+
/// - resolve: Promise resolver invoked with `watchid`.
|
|
384
|
+
/// - reject: Promise rejecter (unused).
|
|
385
|
+
@objc(clearLocationWatch:resolve:reject:)
|
|
386
|
+
func clearLocationWatch(watchid: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
387
|
+
if let _ = locationWatchStack[watchid] {
|
|
388
|
+
locationWatchStack.removeValue(forKey: watchid)
|
|
389
|
+
}
|
|
390
|
+
if locationWatchStack.count == 0 {
|
|
391
|
+
NotificationCenter.default.removeObserver(self, name: .newLocationSaved, object: nil)
|
|
392
|
+
}
|
|
393
|
+
resolve(watchid)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/// Removes all active location watches and unsubscribes from updates.
|
|
397
|
+
/// - Parameters:
|
|
398
|
+
/// - resolve: Promise resolver invoked with a sentinel watch ID.
|
|
399
|
+
/// - reject: Promise rejecter (unused).
|
|
400
|
+
@objc(clearAllLocationWatch:reject:)
|
|
401
|
+
func clearAllLocationWatch(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
402
|
+
locationWatchStack.removeAll()
|
|
403
|
+
NotificationCenter.default.removeObserver(self, name: .newLocationSaved, object: nil)
|
|
404
|
+
resolve("00000-00000-0000")
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// MARK: - Watch / clear regions
|
|
408
|
+
|
|
409
|
+
/// Registers a region watch. The first watcher subscribes to `.didEventPOIRegion`.
|
|
410
|
+
/// - Parameters:
|
|
411
|
+
/// - watchid: The caller-provided identifier for this watch.
|
|
412
|
+
/// - resolve: Promise resolver invoked with `watchid` on success.
|
|
413
|
+
/// - reject: Promise rejecter invoked when the service is not initialized.
|
|
414
|
+
@objc(watchRegions:resolve:reject:)
|
|
415
|
+
func watchRegions(watchid: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
416
|
+
if WoosmapGeofenceService.shared != nil {
|
|
417
|
+
regionWatchStack[watchid] = watchid
|
|
418
|
+
if regionWatchStack.count == 1 {
|
|
419
|
+
NotificationCenter.default.addObserver(
|
|
420
|
+
self,
|
|
421
|
+
selector: #selector(didEventPOIRegion(_:)),
|
|
422
|
+
name: .didEventPOIRegion,
|
|
423
|
+
object: nil)
|
|
424
|
+
}
|
|
425
|
+
resolve(watchid)
|
|
426
|
+
} else {
|
|
427
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
428
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
429
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/// Removes the region watch with the given ID, unsubscribing once empty.
|
|
434
|
+
/// - Parameters:
|
|
435
|
+
/// - watchid: The identifier of the watch to remove.
|
|
436
|
+
/// - resolve: Promise resolver invoked with `watchid`.
|
|
437
|
+
/// - reject: Promise rejecter (unused).
|
|
438
|
+
@objc(clearRegionsWatch:resolve:reject:)
|
|
439
|
+
func clearRegionsWatch(watchid: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
440
|
+
if let _ = regionWatchStack[watchid] {
|
|
441
|
+
regionWatchStack.removeValue(forKey: watchid)
|
|
442
|
+
}
|
|
443
|
+
if regionWatchStack.count == 0 {
|
|
444
|
+
NotificationCenter.default.removeObserver(self, name: .didEventPOIRegion, object: nil)
|
|
445
|
+
}
|
|
446
|
+
resolve(watchid)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/// Removes all active region watches and unsubscribes from updates.
|
|
450
|
+
/// - Parameters:
|
|
451
|
+
/// - resolve: Promise resolver invoked with a sentinel watch ID.
|
|
452
|
+
/// - reject: Promise rejecter (unused).
|
|
453
|
+
@objc(clearAllRegionsWatch:reject:)
|
|
454
|
+
func clearAllRegionsWatch(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
455
|
+
regionWatchStack.removeAll()
|
|
456
|
+
NotificationCenter.default.removeObserver(self, name: .didEventPOIRegion, object: nil)
|
|
457
|
+
resolve("00000-00000-0000")
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// MARK: - Region CRUD
|
|
461
|
+
|
|
462
|
+
/// Sets the Woosmap private API key on the running service.
|
|
463
|
+
/// - Parameters:
|
|
464
|
+
/// - apiKey: The new Woosmap private API key.
|
|
465
|
+
/// - resolve: Promise resolver invoked on success.
|
|
466
|
+
/// - reject: Promise rejecter invoked on an empty key or failure.
|
|
42
467
|
@objc(setWoosmapApiKey:resolve:reject:)
|
|
43
468
|
func setWoosmapApiKey(apiKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
44
469
|
guard let woosService = WoosmapGeofenceService.shared else {
|
|
@@ -63,6 +488,11 @@ class WoosmapGeofencingTurbo: NSObject {
|
|
|
63
488
|
}
|
|
64
489
|
}
|
|
65
490
|
|
|
491
|
+
/// Sets the POI geofence radius, accepting a number, double or property name.
|
|
492
|
+
/// - Parameters:
|
|
493
|
+
/// - radius: The radius value (e.g. `"100"`) or a user-property name.
|
|
494
|
+
/// - resolve: Promise resolver invoked on success.
|
|
495
|
+
/// - reject: Promise rejecter invoked on an empty/invalid value.
|
|
66
496
|
@objc(setPoiRadius:resolve:reject:)
|
|
67
497
|
func setPoiRadius(radius: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
68
498
|
guard let woosService = WoosmapGeofenceService.shared else {
|
|
@@ -89,6 +519,11 @@ class WoosmapGeofencingTurbo: NSObject {
|
|
|
89
519
|
resolve(WoosmapGeofenceMessage.initialize)
|
|
90
520
|
}
|
|
91
521
|
|
|
522
|
+
/// Adds a custom region (circle or isochrone) to monitor.
|
|
523
|
+
/// - Parameters:
|
|
524
|
+
/// - region: A dictionary with `regionId`, `lat`, `lng`, `radius`, `type`.
|
|
525
|
+
/// - resolve: Promise resolver invoked with the created region identifier.
|
|
526
|
+
/// - reject: Promise rejecter invoked on validation failure or a duplicate ID.
|
|
92
527
|
@objc(addRegion:resolve:reject:)
|
|
93
528
|
func addRegion(region: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
94
529
|
guard let woosService = WoosmapGeofenceService.shared else {
|
|
@@ -165,6 +600,11 @@ class WoosmapGeofencingTurbo: NSObject {
|
|
|
165
600
|
}
|
|
166
601
|
}
|
|
167
602
|
|
|
603
|
+
/// Retrieves saved regions. With an ID, returns the single match; otherwise all.
|
|
604
|
+
/// - Parameters:
|
|
605
|
+
/// - regionId: An optional region identifier to filter by.
|
|
606
|
+
/// - resolve: Promise resolver invoked with an array of region dictionaries.
|
|
607
|
+
/// - reject: Promise rejecter invoked when the ID is not found.
|
|
168
608
|
@objc(getRegions:resolve:reject:)
|
|
169
609
|
func getRegions(regionId: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
170
610
|
guard let woosService = WoosmapGeofenceService.shared else {
|
|
@@ -173,10 +613,6 @@ class WoosmapGeofencingTurbo: NSObject {
|
|
|
173
613
|
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
174
614
|
return
|
|
175
615
|
}
|
|
176
|
-
|
|
177
|
-
// The spec collapses the legacy getAllRegions()/getRegions(id) pair into a
|
|
178
|
-
// single optional-arg method that always resolves an array. A nil/empty id
|
|
179
|
-
// returns the whole collection.
|
|
180
616
|
if let regionId = regionId, !regionId.isEmpty {
|
|
181
617
|
if let captured = woosService.getRegions(id: regionId) {
|
|
182
618
|
resolve([formatRegionData(woosdata: captured)])
|
|
@@ -191,6 +627,10 @@ class WoosmapGeofencingTurbo: NSObject {
|
|
|
191
627
|
}
|
|
192
628
|
}
|
|
193
629
|
|
|
630
|
+
/// Removes all saved regions.
|
|
631
|
+
/// - Parameters:
|
|
632
|
+
/// - resolve: Promise resolver invoked once regions are deleted.
|
|
633
|
+
/// - reject: Promise rejecter invoked when the service is not initialized.
|
|
194
634
|
@objc(removeAllRegions:reject:)
|
|
195
635
|
func removeAllRegions(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
196
636
|
guard let woosService = WoosmapGeofenceService.shared else {
|
|
@@ -203,6 +643,11 @@ class WoosmapGeofencingTurbo: NSObject {
|
|
|
203
643
|
resolve(WoosmapGeofenceMessage.regionDeleted)
|
|
204
644
|
}
|
|
205
645
|
|
|
646
|
+
/// Removes the saved region with the given identifier.
|
|
647
|
+
/// - Parameters:
|
|
648
|
+
/// - regionId: The identifier of the region to remove.
|
|
649
|
+
/// - resolve: Promise resolver invoked once the region is deleted.
|
|
650
|
+
/// - reject: Promise rejecter invoked on a missing ID or failure.
|
|
206
651
|
@objc(removeRegion:resolve:reject:)
|
|
207
652
|
func removeRegion(regionId: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
208
653
|
guard let woosService = WoosmapGeofenceService.shared else {
|
|
@@ -226,4 +671,421 @@ class WoosmapGeofencingTurbo: NSObject {
|
|
|
226
671
|
woosmapError(error.localizedDescription))
|
|
227
672
|
}
|
|
228
673
|
}
|
|
674
|
+
|
|
675
|
+
// MARK: - Tracking
|
|
676
|
+
|
|
677
|
+
/// Starts tracking using the given configuration profile.
|
|
678
|
+
/// - Parameters:
|
|
679
|
+
/// - profile: The tracking profile name (e.g. `liveTracking`).
|
|
680
|
+
/// - resolve: Promise resolver invoked on success.
|
|
681
|
+
/// - reject: Promise rejecter invoked on an invalid profile or failure.
|
|
682
|
+
@objc(startTracking:resolve:reject:)
|
|
683
|
+
func startTracking(profile: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
684
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
685
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
686
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
687
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
688
|
+
return
|
|
689
|
+
}
|
|
690
|
+
do {
|
|
691
|
+
try woosService.startTracking(profile: profile)
|
|
692
|
+
resolve(WoosmapGeofenceMessage.initialize)
|
|
693
|
+
} catch {
|
|
694
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
695
|
+
error.localizedDescription,
|
|
696
|
+
woosmapError(error.localizedDescription))
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/// Stops location tracking.
|
|
701
|
+
/// - Parameters:
|
|
702
|
+
/// - resolve: Promise resolver invoked on success.
|
|
703
|
+
/// - reject: Promise rejecter invoked when the service is not initialized.
|
|
704
|
+
@objc(stopTracking:reject:)
|
|
705
|
+
func stopTracking(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
706
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
707
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
708
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
709
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
710
|
+
return
|
|
711
|
+
}
|
|
712
|
+
woosService.stopTracking()
|
|
713
|
+
resolve(WoosmapGeofenceMessage.initialize)
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/// Starts tracking from a custom profile hosted locally or externally.
|
|
717
|
+
/// - Parameters:
|
|
718
|
+
/// - mode: The profile source (`local` or `external`).
|
|
719
|
+
/// - source: The file name or URL of the profile.
|
|
720
|
+
/// - resolve: Promise resolver invoked once the profile is applied.
|
|
721
|
+
/// - reject: Promise rejecter invoked on failure.
|
|
722
|
+
@objc(startCustomTracking:source:resolve:reject:)
|
|
723
|
+
func startCustomTracking(mode: String,
|
|
724
|
+
source: String,
|
|
725
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
726
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
727
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
728
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
729
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
730
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
731
|
+
return
|
|
732
|
+
}
|
|
733
|
+
woosService.startCustomTracking(mode: mode, source: source) { value, error in
|
|
734
|
+
if let functionError = error {
|
|
735
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
736
|
+
functionError.localizedDescription,
|
|
737
|
+
self.woosmapError(functionError.localizedDescription))
|
|
738
|
+
} else if value {
|
|
739
|
+
resolve(WoosmapGeofenceMessage.initialize)
|
|
740
|
+
} else {
|
|
741
|
+
// value == false with no error: the profile could not be applied.
|
|
742
|
+
// Reject so the JS promise always settles instead of hanging.
|
|
743
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
744
|
+
WoosmapGeofenceMessage.invalid_profilefile,
|
|
745
|
+
self.woosmapError(WoosmapGeofenceMessage.invalid_profilefile))
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// MARK: - Permissions status
|
|
751
|
+
|
|
752
|
+
/// Reports the current location-permission status.
|
|
753
|
+
/// - Parameters:
|
|
754
|
+
/// - resolve: Promise resolver invoked with `GRANTED_BACKGROUND`,
|
|
755
|
+
/// `GRANTED_FOREGROUND`, `DENIED` or `UNKNOWN`.
|
|
756
|
+
/// - reject: Promise rejecter (unused).
|
|
757
|
+
@objc(getPermissionsStatus:reject:)
|
|
758
|
+
func getPermissionsStatus(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
759
|
+
// CLLocationManager must be created/queried on the main thread; bridge
|
|
760
|
+
// methods otherwise run on a background queue.
|
|
761
|
+
DispatchQueue.main.async {
|
|
762
|
+
let authorizationStatus: CLAuthorizationStatus
|
|
763
|
+
if #available(iOS 14, *) {
|
|
764
|
+
authorizationStatus = CLLocationManager().authorizationStatus
|
|
765
|
+
} else {
|
|
766
|
+
authorizationStatus = CLLocationManager.authorizationStatus()
|
|
767
|
+
}
|
|
768
|
+
let str: String
|
|
769
|
+
switch authorizationStatus {
|
|
770
|
+
case .denied, .restricted:
|
|
771
|
+
str = "DENIED"
|
|
772
|
+
case .authorizedAlways:
|
|
773
|
+
str = "GRANTED_BACKGROUND"
|
|
774
|
+
case .authorizedWhenInUse:
|
|
775
|
+
str = "GRANTED_FOREGROUND"
|
|
776
|
+
default:
|
|
777
|
+
str = "UNKNOWN"
|
|
778
|
+
}
|
|
779
|
+
resolve(str)
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/// Reports the Bluetooth-permission status. Not required on iOS for
|
|
784
|
+
/// geofencing, so this always resolves `GRANTED`.
|
|
785
|
+
/// - Parameters:
|
|
786
|
+
/// - resolve: Promise resolver invoked with `"GRANTED"`.
|
|
787
|
+
/// - reject: Promise rejecter (unused).
|
|
788
|
+
@objc(getBLEPermissionsStatus:reject:)
|
|
789
|
+
func getBLEPermissionsStatus(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
790
|
+
resolve("GRANTED")
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/// Reports the notification-permission status. Not required on iOS for
|
|
794
|
+
/// geofencing tracking, so this always resolves `GRANTED`.
|
|
795
|
+
/// - Parameters:
|
|
796
|
+
/// - resolve: Promise resolver invoked with `"GRANTED"`.
|
|
797
|
+
/// - reject: Promise rejecter (unused).
|
|
798
|
+
@objc(getNotificationPermissionsStatus:reject:)
|
|
799
|
+
func getNotificationPermissionsStatus(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
800
|
+
resolve("GRANTED")
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// MARK: - Configuration
|
|
804
|
+
|
|
805
|
+
/// Sets the Salesforce Marketing Cloud (SFMC) credentials.
|
|
806
|
+
/// - Parameters:
|
|
807
|
+
/// - credentials: A dictionary of SFMC credential key/value pairs.
|
|
808
|
+
/// - resolve: Promise resolver invoked on success.
|
|
809
|
+
/// - reject: Promise rejecter invoked on invalid credentials or failure.
|
|
810
|
+
@objc(setSFMCCredentials:resolve:reject:)
|
|
811
|
+
func setSFMCCredentials(credentials: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
812
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
813
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
814
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
815
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
816
|
+
return
|
|
817
|
+
}
|
|
818
|
+
guard let creds = credentials as? [String: String] else {
|
|
819
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
820
|
+
WoosmapGeofenceMessage.invalidSFMCCredentials,
|
|
821
|
+
woosmapError(WoosmapGeofenceMessage.invalidSFMCCredentials))
|
|
822
|
+
return
|
|
823
|
+
}
|
|
824
|
+
do {
|
|
825
|
+
try woosService.setSFMCCredentials(credentials: creds)
|
|
826
|
+
resolve(WoosmapGeofenceMessage.initialize)
|
|
827
|
+
} catch {
|
|
828
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
829
|
+
error.localizedDescription,
|
|
830
|
+
woosmapError(error.localizedDescription))
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/// Reserves protected region slots for third-party SDKs (max 3).
|
|
835
|
+
/// - Parameters:
|
|
836
|
+
/// - slots: The number of slots to reserve.
|
|
837
|
+
/// - resolve: Promise resolver invoked on success.
|
|
838
|
+
/// - reject: Promise rejecter invoked on an invalid slot count.
|
|
839
|
+
@objc(setProtectedRegionSlot:resolve:reject:)
|
|
840
|
+
func setProtectedRegionSlot(slots: NSNumber, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
841
|
+
guard WoosmapGeofenceService.shared != nil else {
|
|
842
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
843
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
844
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
845
|
+
return
|
|
846
|
+
}
|
|
847
|
+
do {
|
|
848
|
+
try WoosmapGeofenceService.shared?.setProtectedRegionSlot(slots: slots.intValue)
|
|
849
|
+
resolve(WoosmapGeofenceMessage.initialize)
|
|
850
|
+
} catch {
|
|
851
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
852
|
+
WoosmapGeofenceMessage.invalidProtectedRegionSlot,
|
|
853
|
+
woosmapError(WoosmapGeofenceMessage.invalidProtectedRegionSlot))
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// MARK: - Data queries
|
|
858
|
+
|
|
859
|
+
/// Retrieves saved locations. With an ID, returns the single match; otherwise all.
|
|
860
|
+
/// - Parameters:
|
|
861
|
+
/// - locationId: An optional location identifier to filter by.
|
|
862
|
+
/// - resolve: Promise resolver invoked with an array of location dictionaries.
|
|
863
|
+
/// - reject: Promise rejecter invoked when the ID is not found.
|
|
864
|
+
@objc(getLocations:resolve:reject:)
|
|
865
|
+
func getLocations(locationId: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
866
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
867
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
868
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
869
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
870
|
+
return
|
|
871
|
+
}
|
|
872
|
+
if let locationId = locationId, !locationId.isEmpty {
|
|
873
|
+
if let captured = woosService.getLocations(id: locationId) {
|
|
874
|
+
resolve([formatLocationData(woosdata: captured)])
|
|
875
|
+
} else {
|
|
876
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
877
|
+
WoosmapGeofenceMessage.notfound_locationid,
|
|
878
|
+
woosmapError(WoosmapGeofenceMessage.notfound_locationid))
|
|
879
|
+
}
|
|
880
|
+
} else {
|
|
881
|
+
resolve(woosService.getLocations().map { formatLocationData(woosdata: $0) })
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/// Retrieves saved POIs. With an ID, returns the single match; otherwise all.
|
|
886
|
+
/// - Parameters:
|
|
887
|
+
/// - poiId: An optional POI identifier (location or store ID) to filter by.
|
|
888
|
+
/// - resolve: Promise resolver invoked with an array of POI dictionaries.
|
|
889
|
+
/// - reject: Promise rejecter invoked when the ID is not found.
|
|
890
|
+
@objc(getPois:resolve:reject:)
|
|
891
|
+
func getPois(poiId: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
892
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
893
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
894
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
895
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
896
|
+
return
|
|
897
|
+
}
|
|
898
|
+
if let poiId = poiId, !poiId.isEmpty {
|
|
899
|
+
if let captured = woosService.getPOIs(id: poiId) {
|
|
900
|
+
resolve([formatPOIData(woosdata: captured)])
|
|
901
|
+
} else {
|
|
902
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
903
|
+
WoosmapGeofenceMessage.notfound_poiid,
|
|
904
|
+
woosmapError(WoosmapGeofenceMessage.notfound_poiid))
|
|
905
|
+
}
|
|
906
|
+
} else {
|
|
907
|
+
resolve(woosService.getPOIs().map { formatPOIData(woosdata: $0) })
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/// Retrieves saved indoor beacons, optionally filtered by venue.
|
|
912
|
+
/// - Parameters:
|
|
913
|
+
/// - venueId: An optional venue identifier to filter by.
|
|
914
|
+
/// - resolve: Promise resolver invoked with an array of beacon dictionaries.
|
|
915
|
+
/// - reject: Promise rejecter invoked when the service is not initialized.
|
|
916
|
+
@objc(getIndoorBeacons:resolve:reject:)
|
|
917
|
+
func getIndoorBeacons(venueId: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
918
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
919
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
920
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
921
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
922
|
+
return
|
|
923
|
+
}
|
|
924
|
+
let beacons = woosService.getIndoorBeacons(id: venueId) ?? []
|
|
925
|
+
resolve(beacons.map { formatIndoorBeaconData(woosdata: $0) })
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// MARK: - Data removal
|
|
929
|
+
|
|
930
|
+
/// Removes all saved locations.
|
|
931
|
+
/// - Parameters:
|
|
932
|
+
/// - resolve: Promise resolver invoked once locations are deleted.
|
|
933
|
+
/// - reject: Promise rejecter invoked when the service is not initialized.
|
|
934
|
+
@objc(removeLocations:reject:)
|
|
935
|
+
func removeLocations(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
936
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
937
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
938
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
939
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
940
|
+
return
|
|
941
|
+
}
|
|
942
|
+
woosService.deleteAllLocations()
|
|
943
|
+
resolve(WoosmapGeofenceMessage.locationDeleted)
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/// Removes all saved POIs.
|
|
947
|
+
/// - Parameters:
|
|
948
|
+
/// - resolve: Promise resolver invoked once POIs are deleted.
|
|
949
|
+
/// - reject: Promise rejecter invoked when the service is not initialized.
|
|
950
|
+
@objc(removePois:reject:)
|
|
951
|
+
func removePois(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
952
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
953
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
954
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
955
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
956
|
+
return
|
|
957
|
+
}
|
|
958
|
+
woosService.deletePOI()
|
|
959
|
+
resolve(WoosmapGeofenceMessage.poiDeleted)
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/// Removes all saved indoor beacons.
|
|
963
|
+
/// - Parameters:
|
|
964
|
+
/// - resolve: Promise resolver invoked once beacons are deleted.
|
|
965
|
+
/// - reject: Promise rejecter invoked when the service is not initialized.
|
|
966
|
+
@objc(removeIndoorBeacons:reject:)
|
|
967
|
+
func removeIndoorBeacons(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
968
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
969
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
970
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
971
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
972
|
+
return
|
|
973
|
+
}
|
|
974
|
+
woosService.deleteIndoorBeacons()
|
|
975
|
+
resolve(WoosmapGeofenceMessage.locationDeleted)
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/// Refreshes POI data from the Woosmap server.
|
|
979
|
+
/// - Parameters:
|
|
980
|
+
/// - resolve: Promise resolver invoked with the refresh status.
|
|
981
|
+
/// - reject: Promise rejecter invoked when the service is not initialized.
|
|
982
|
+
@objc(refreshPois:reject:)
|
|
983
|
+
func refreshPois(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
984
|
+
guard let woosService = WoosmapGeofenceService.shared else {
|
|
985
|
+
reject(WoosmapGeofenceMessage.plugin_errorDomain,
|
|
986
|
+
WoosmapGeofenceMessage.woosmapNotInitialized,
|
|
987
|
+
woosmapError(WoosmapGeofenceMessage.woosmapNotInitialized))
|
|
988
|
+
return
|
|
989
|
+
}
|
|
990
|
+
woosService.refreshPOI()
|
|
991
|
+
resolve(WoosmapGeofenceMessage.refreshPOIStatus)
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// MARK: - Notification handlers
|
|
995
|
+
|
|
996
|
+
/// Handles `.newLocationSaved` notifications and forwards them to JS as a
|
|
997
|
+
/// `geolocationDidChange` event when at least one watcher is active.
|
|
998
|
+
/// - Parameter notification: The notification carrying the new `Location`.
|
|
999
|
+
@objc func newLocationAdded(_ notification: Notification) {
|
|
1000
|
+
if let location = notification.userInfo?["Location"] as? Location {
|
|
1001
|
+
if locationWatchStack.count > 0 {
|
|
1002
|
+
sendEvent(withName: "geolocationDidChange", body: formatLocationData(woosdata: location))
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/// Handles `.didEventPOIRegion` notifications and forwards them to JS as a
|
|
1008
|
+
/// `woosmapgeofenceRegionDidChange` event.
|
|
1009
|
+
/// - Parameter notification: The notification carrying the `Region`.
|
|
1010
|
+
@objc func didEventPOIRegion(_ notification: Notification) {
|
|
1011
|
+
if let region = notification.userInfo?["Region"] as? Region {
|
|
1012
|
+
sendEvent(withName: "woosmapgeofenceRegionDidChange", body: formatRegionData(woosdata: region))
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// MARK: - CLLocationManagerDelegate
|
|
1018
|
+
|
|
1019
|
+
extension WoosmapGeofencingTurbo: CLLocationManagerDelegate {
|
|
1020
|
+
/// Receives location updates from the permission-checking manager.
|
|
1021
|
+
/// - Parameters:
|
|
1022
|
+
/// - manager: The location manager reporting the update.
|
|
1023
|
+
/// - locations: The array of new locations; only the last is used.
|
|
1024
|
+
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
1025
|
+
guard let location = locations.last else { return }
|
|
1026
|
+
print(location)
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/// Settles an in-flight `requestPermissions` promise once the user answers the
|
|
1030
|
+
/// system location dialog. Mirrors the Android `onRequestPermissionsResult`
|
|
1031
|
+
/// path so a fresh install no longer hangs. No-ops when there is no pending
|
|
1032
|
+
/// request (e.g. the initial callback fired on delegate assignment) or while
|
|
1033
|
+
/// the dialog is still showing (`.notDetermined`).
|
|
1034
|
+
/// - Parameter manager: The location manager reporting the change.
|
|
1035
|
+
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
1036
|
+
guard let resolve = permissionResolve else { return }
|
|
1037
|
+
let status = manager.authorizationStatus
|
|
1038
|
+
if status == .notDetermined { return }
|
|
1039
|
+
permissionResolve = nil
|
|
1040
|
+
switch status {
|
|
1041
|
+
case .authorizedAlways:
|
|
1042
|
+
resolve("GRANTED_BACKGROUND")
|
|
1043
|
+
case .authorizedWhenInUse:
|
|
1044
|
+
resolve("GRANTED_FOREGROUND")
|
|
1045
|
+
case .denied, .restricted:
|
|
1046
|
+
resolve("DENIED")
|
|
1047
|
+
default:
|
|
1048
|
+
resolve("UNKNOWN")
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/// Receives location-manager failures.
|
|
1053
|
+
/// - Parameters:
|
|
1054
|
+
/// - manager: The location manager reporting the failure.
|
|
1055
|
+
/// - error: The error that occurred.
|
|
1056
|
+
private func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
|
|
1057
|
+
print(error)
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// MARK: - UIApplication top view controller
|
|
1062
|
+
|
|
1063
|
+
extension UIApplication {
|
|
1064
|
+
/// The top-most view controller in the app, used to present permission alerts.
|
|
1065
|
+
/// - Returns: The visible view controller, or `nil` if no key window is found.
|
|
1066
|
+
var topViewController: UIViewController? {
|
|
1067
|
+
guard let windowScene = connectedScenes
|
|
1068
|
+
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
|
|
1069
|
+
let rootVC = windowScene.windows
|
|
1070
|
+
.first(where: { $0.isKeyWindow })?.rootViewController
|
|
1071
|
+
else {
|
|
1072
|
+
return nil
|
|
1073
|
+
}
|
|
1074
|
+
return topViewController(from: rootVC)
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/// Recursively walks presented / navigation / tab controllers to find the top one.
|
|
1078
|
+
/// - Parameter root: The view controller to start the walk from.
|
|
1079
|
+
/// - Returns: The top-most view controller reachable from `root`.
|
|
1080
|
+
private func topViewController(from root: UIViewController) -> UIViewController {
|
|
1081
|
+
if let presented = root.presentedViewController {
|
|
1082
|
+
return topViewController(from: presented)
|
|
1083
|
+
} else if let nav = root as? UINavigationController, let visible = nav.visibleViewController {
|
|
1084
|
+
return topViewController(from: visible)
|
|
1085
|
+
} else if let tab = root as? UITabBarController, let selected = tab.selectedViewController {
|
|
1086
|
+
return topViewController(from: selected)
|
|
1087
|
+
} else {
|
|
1088
|
+
return root
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
229
1091
|
}
|