@wayq/beekon-rn 0.0.1 → 0.0.5

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 (92) hide show
  1. package/BeekonRn.podspec +4 -4
  2. package/README.md +94 -48
  3. package/android/build.gradle +11 -6
  4. package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +428 -0
  5. package/android/src/main/java/{com → in}/wayq/beekonrn/BeekonRnPackage.kt +1 -1
  6. package/ios/BeekonRn.h +5 -1
  7. package/ios/BeekonRn.mm +90 -34
  8. package/ios/BeekonRn.swift +396 -116
  9. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/BeekonKit +0 -0
  10. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Info.plist +0 -0
  11. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.abi.json +8636 -0
  12. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  13. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftinterface +236 -0
  14. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/PrivacyInfo.xcprivacy +1 -1
  15. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/BeekonKit +0 -0
  16. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Info.plist +0 -0
  17. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +8636 -0
  18. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +236 -0
  20. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +8636 -0
  21. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  22. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +236 -0
  23. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/PrivacyInfo.xcprivacy +1 -1
  24. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +2 -14
  25. package/lib/module/NativeBeekonRn.js +22 -7
  26. package/lib/module/NativeBeekonRn.js.map +1 -1
  27. package/lib/module/beekon.js +209 -60
  28. package/lib/module/beekon.js.map +1 -1
  29. package/lib/module/index.js +1 -0
  30. package/lib/module/index.js.map +1 -1
  31. package/lib/module/internal/mappers.js +145 -23
  32. package/lib/module/internal/mappers.js.map +1 -1
  33. package/lib/module/types/enums.js +2 -0
  34. package/lib/module/types/{preset.js.map → enums.js.map} +1 -1
  35. package/lib/module/types/error.js +25 -0
  36. package/lib/module/types/error.js.map +1 -0
  37. package/lib/module/types/geofence.js +2 -0
  38. package/lib/module/types/{position.js.map → geofence.js.map} +1 -1
  39. package/lib/module/types/location.js +4 -0
  40. package/lib/module/types/location.js.map +1 -0
  41. package/lib/module/types/sync.js +2 -0
  42. package/lib/module/types/sync.js.map +1 -0
  43. package/lib/typescript/src/NativeBeekonRn.d.ts +113 -35
  44. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
  45. package/lib/typescript/src/beekon.d.ts +84 -49
  46. package/lib/typescript/src/beekon.d.ts.map +1 -1
  47. package/lib/typescript/src/index.d.ts +7 -4
  48. package/lib/typescript/src/index.d.ts.map +1 -1
  49. package/lib/typescript/src/internal/mappers.d.ts +16 -3
  50. package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
  51. package/lib/typescript/src/types/config.d.ts +53 -31
  52. package/lib/typescript/src/types/config.d.ts.map +1 -1
  53. package/lib/typescript/src/types/enums.d.ts +48 -0
  54. package/lib/typescript/src/types/enums.d.ts.map +1 -0
  55. package/lib/typescript/src/types/error.d.ts +20 -0
  56. package/lib/typescript/src/types/error.d.ts.map +1 -0
  57. package/lib/typescript/src/types/geofence.d.ts +36 -0
  58. package/lib/typescript/src/types/geofence.d.ts.map +1 -0
  59. package/lib/typescript/src/types/location.d.ts +40 -0
  60. package/lib/typescript/src/types/location.d.ts.map +1 -0
  61. package/lib/typescript/src/types/state.d.ts +18 -9
  62. package/lib/typescript/src/types/state.d.ts.map +1 -1
  63. package/lib/typescript/src/types/sync.d.ts +27 -0
  64. package/lib/typescript/src/types/sync.d.ts.map +1 -0
  65. package/package.json +5 -6
  66. package/scripts/fetch-beekonkit.sh +5 -2
  67. package/src/NativeBeekonRn.ts +120 -34
  68. package/src/beekon.ts +235 -63
  69. package/src/index.tsx +23 -4
  70. package/src/internal/mappers.ts +213 -22
  71. package/src/types/config.ts +54 -31
  72. package/src/types/enums.ts +64 -0
  73. package/src/types/error.ts +25 -0
  74. package/src/types/geofence.ts +37 -0
  75. package/src/types/location.ts +45 -0
  76. package/src/types/state.ts +23 -7
  77. package/src/types/sync.ts +23 -0
  78. package/android/src/main/java/com/wayq/beekonrn/BeekonRnModule.kt +0 -233
  79. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeDirectory +0 -0
  80. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeRequirements +0 -0
  81. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeRequirements-1 +0 -0
  82. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeResources +0 -233
  83. package/ios/Frameworks/BeekonKit.xcframework/_CodeSignature/CodeSignature +0 -0
  84. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/_CodeSignature/CodeResources +0 -113
  85. package/lib/module/types/position.js +0 -2
  86. package/lib/module/types/preset.js +0 -2
  87. package/lib/typescript/src/types/position.d.ts +0 -24
  88. package/lib/typescript/src/types/position.d.ts.map +0 -1
  89. package/lib/typescript/src/types/preset.d.ts +0 -12
  90. package/lib/typescript/src/types/preset.d.ts.map +0 -1
  91. package/src/types/position.ts +0 -23
  92. package/src/types/preset.ts +0 -11
@@ -1,209 +1,489 @@
1
1
  import Foundation
2
2
  import BeekonKit
3
3
 
4
- /// Swift impl of the Beekon RN TurboModule. Bridges the actor-based native
5
- /// SDK (`BeekonKit.Beekon.shared`) to the ObjC TurboModule conformance in
4
+ /// Swift impl of the Beekon RN TurboModule. Bridges the actor-based native SDK
5
+ /// (`BeekonKit.Beekon.shared`) to the ObjC TurboModule conformance in
6
6
  /// `BeekonRn.mm`. All public-facing methods take ObjC-compatible callbacks
7
- /// (resolver/rejecter) and dictionaries so they can be invoked from `.mm`.
7
+ /// (resolver/rejecter) and dictionaries/arrays so they can be invoked from `.mm`.
8
8
  ///
9
- /// Event delivery: `BeekonRn.mm` constructs this with two closures that call
10
- /// the codegen-generated `emitOnState:` / `emitOnPosition:` ObjC methods on
11
- /// the spec; the closures are invoked from the per-stream `Task`s started
12
- /// during `initialize`.
13
- @objc public final class BeekonRnImpl: NSObject {
9
+ /// Event delivery: `BeekonRn.mm` constructs this with four closures that call
10
+ /// the codegen `emitOnState:` / `emitOnLocation:` / `emitOnGeofenceEvent:` /
11
+ /// `emitOnSyncStatus:` ObjC methods; the closures are invoked from per-stream
12
+ /// `Task`s started during init.
13
+ //
14
+ // `@unchecked Sendable` is correct here: the four emit closures are immutable
15
+ // `let`s, and the stream `Task`s are mutated only from init/invalidate which run
16
+ // sequentially. NSObject can't be auto-Sendable and `@objc` rules out `actor`,
17
+ // so this is the path. Non-Sendable wire inputs (NSDictionary/NSArray) are
18
+ // converted to Sendable Beekon types *before* crossing into any `Task`.
19
+ @objc public final class BeekonRnImpl: NSObject, @unchecked Sendable {
14
20
 
15
21
  private let onStateCb: (NSDictionary) -> Void
16
- private let onPositionCb: (NSDictionary) -> Void
22
+ private let onLocationCb: (NSDictionary) -> Void
23
+ private let onGeofenceEventCb: (NSDictionary) -> Void
24
+ private let onSyncStatusCb: (NSDictionary) -> Void
17
25
 
18
26
  private var stateTask: Task<Void, Never>?
19
- private var positionsTask: Task<Void, Never>?
27
+ private var locationsTask: Task<Void, Never>?
28
+ private var geofenceEventsTask: Task<Void, Never>?
29
+ private var syncStatusTask: Task<Void, Never>?
20
30
 
21
31
  @objc public init(
22
32
  onState: @escaping (NSDictionary) -> Void,
23
- onPosition: @escaping (NSDictionary) -> Void
33
+ onLocation: @escaping (NSDictionary) -> Void,
34
+ onGeofenceEvent: @escaping (NSDictionary) -> Void,
35
+ onSyncStatus: @escaping (NSDictionary) -> Void
24
36
  ) {
25
37
  self.onStateCb = onState
26
- self.onPositionCb = onPosition
38
+ self.onLocationCb = onLocation
39
+ self.onGeofenceEventCb = onGeofenceEvent
40
+ self.onSyncStatusCb = onSyncStatus
27
41
  super.init()
42
+ self.stateTask = Task { [weak self] in
43
+ guard let self = self else { return }
44
+ for await state in await Beekon.shared.state {
45
+ self.onStateCb(self.stateToWire(state))
46
+ }
47
+ }
48
+ self.locationsTask = Task { [weak self] in
49
+ guard let self = self else { return }
50
+ for await loc in await Beekon.shared.locations {
51
+ self.onLocationCb(self.locationToWire(loc))
52
+ }
53
+ }
54
+ self.geofenceEventsTask = Task { [weak self] in
55
+ guard let self = self else { return }
56
+ for await event in await Beekon.shared.geofenceEvents {
57
+ self.onGeofenceEventCb(self.geofenceEventToWire(event))
58
+ }
59
+ }
60
+ self.syncStatusTask = Task { [weak self] in
61
+ guard let self = self else { return }
62
+ for await status in await Beekon.shared.syncStatus {
63
+ self.onSyncStatusCb(self.syncStatusToWire(status))
64
+ }
65
+ }
66
+ }
67
+
68
+ @objc public func invalidate() {
69
+ stateTask?.cancel()
70
+ locationsTask?.cancel()
71
+ geofenceEventsTask?.cancel()
72
+ syncStatusTask?.cancel()
73
+ stateTask = nil
74
+ locationsTask = nil
75
+ geofenceEventsTask = nil
76
+ syncStatusTask = nil
77
+ }
78
+
79
+ /// Register Beekon's background-refresh task and install cold-launch hooks.
80
+ /// Must be called once, synchronously, during app launch (before
81
+ /// `didFinishLaunchingWithOptions` returns) — invoke from the host AppDelegate.
82
+ @objc public static func registerBackgroundTasks() {
83
+ Beekon.registerBackgroundTasks()
84
+ // Touch the shared actor so its cold-launch resume hooks install.
85
+ _ = Beekon.shared
28
86
  }
29
87
 
30
88
  // MARK: - Lifecycle
31
89
 
32
- @objc public func initializeWithResolver(
33
- _ resolve: @escaping (Any?) -> Void,
34
- rejecter reject: @escaping (String?, String?, Error?) -> Void
90
+ @objc public func configure(
91
+ _ config: NSDictionary,
92
+ resolver resolve: @escaping @Sendable (Any?) -> Void,
93
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
94
+ ) {
95
+ // Convert the non-Sendable NSDictionary to a Sendable BeekonConfig before
96
+ // crossing into the Task closure.
97
+ let cfg = wireToConfig(config)
98
+ Task {
99
+ await Beekon.shared.configure(cfg)
100
+ resolve(nil)
101
+ }
102
+ }
103
+
104
+ @objc public func startWithResolver(
105
+ _ resolve: @escaping @Sendable (Any?) -> Void,
106
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
107
+ ) {
108
+ // start() never throws — outcome surfaces on the `state` stream.
109
+ Task {
110
+ await Beekon.shared.start()
111
+ resolve(nil)
112
+ }
113
+ }
114
+
115
+ @objc public func stopWithResolver(
116
+ _ resolve: @escaping @Sendable (Any?) -> Void,
117
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
118
+ ) {
119
+ Task {
120
+ await Beekon.shared.stop()
121
+ resolve(nil)
122
+ }
123
+ }
124
+
125
+ @objc public func resumeIfNeededWithResolver(
126
+ _ resolve: @escaping @Sendable (Any?) -> Void,
127
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
128
+ ) {
129
+ Task {
130
+ await Beekon.shared.resumeIfNeeded()
131
+ resolve(nil)
132
+ }
133
+ }
134
+
135
+ // MARK: - History
136
+
137
+ @objc public func getLocationsFromMs(
138
+ _ fromMs: Double,
139
+ toMs: Double,
140
+ resolver resolve: @escaping @Sendable (Any?) -> Void,
141
+ rejecter reject: @escaping @Sendable (String?, String?, Error?) -> Void
35
142
  ) {
36
143
  Task { [weak self] in
37
144
  guard let self = self else { return }
38
145
  do {
39
- try await Beekon.shared.initialize()
40
- // Idempotent: cancel any prior collectors before starting new ones.
41
- self.stateTask?.cancel()
42
- self.positionsTask?.cancel()
43
- self.stateTask = Task { [weak self] in
44
- guard let self = self else { return }
45
- for await state in await Beekon.shared.state {
46
- self.onStateCb(self.stateToWire(state))
47
- }
48
- }
49
- self.positionsTask = Task { [weak self] in
50
- guard let self = self else { return }
51
- for await pos in await Beekon.shared.positions {
52
- self.onPositionCb(self.positionToWire(pos))
53
- }
54
- }
55
- resolve(nil)
146
+ let from = Date(timeIntervalSince1970: fromMs / 1000.0)
147
+ let to = Date(timeIntervalSince1970: toMs / 1000.0)
148
+ let locations = try await Beekon.shared.getLocations(from: from, to: to)
149
+ resolve(locations.map { self.locationToWire($0) })
56
150
  } catch {
57
151
  reject(self.errorCode(error), error.localizedDescription, error)
58
152
  }
59
153
  }
60
154
  }
61
155
 
62
- @objc public func configure(
63
- _ config: NSDictionary,
64
- resolver resolve: @escaping RCTPromiseResolveBlock,
65
- rejecter reject: @escaping RCTPromiseRejectBlock
156
+ @objc public func deleteLocationsBeforeMs(
157
+ _ beforeMs: Double,
158
+ resolver resolve: @escaping @Sendable (Any?) -> Void,
159
+ rejecter reject: @escaping @Sendable (String?, String?, Error?) -> Void
66
160
  ) {
67
161
  Task { [weak self] in
68
162
  guard let self = self else { return }
69
163
  do {
70
- let cfg = try self.wireToConfig(config)
71
- await Beekon.shared.configure(cfg)
72
- resolve(nil)
164
+ // Negative is the wire sentinel for "delete all" (no cutoff).
165
+ let before: Date? =
166
+ beforeMs < 0 ? nil : Date(timeIntervalSince1970: beforeMs / 1000.0)
167
+ let count = try await Beekon.shared.deleteLocations(before: before)
168
+ resolve(count)
73
169
  } catch {
74
170
  reject(self.errorCode(error), error.localizedDescription, error)
75
171
  }
76
172
  }
77
173
  }
78
174
 
79
- @objc public func startWithResolver(
80
- _ resolve: @escaping (Any?) -> Void,
81
- rejecter reject: @escaping (String?, String?, Error?) -> Void
175
+ @objc public func pendingUploadCountWithResolver(
176
+ _ resolve: @escaping @Sendable (Any?) -> Void,
177
+ rejecter reject: @escaping @Sendable (String?, String?, Error?) -> Void
82
178
  ) {
83
179
  Task { [weak self] in
84
180
  guard let self = self else { return }
85
181
  do {
86
- try await Beekon.shared.start()
87
- resolve(nil)
182
+ let count = try await Beekon.shared.pendingUploadCount()
183
+ resolve(count)
88
184
  } catch {
89
185
  reject(self.errorCode(error), error.localizedDescription, error)
90
186
  }
91
187
  }
92
188
  }
93
189
 
94
- @objc public func stopWithResolver(
95
- _ resolve: @escaping (Any?) -> Void,
96
- rejecter reject: @escaping (String?, String?, Error?) -> Void
190
+ // MARK: - Sync
191
+
192
+ @objc public func syncWithResolver(
193
+ _ resolve: @escaping @Sendable (Any?) -> Void,
194
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
97
195
  ) {
98
- Task { [weak self] in
99
- guard let self = self else { return }
100
- await Beekon.shared.stop()
196
+ Task {
197
+ await Beekon.shared.sync()
101
198
  resolve(nil)
102
199
  }
103
200
  }
104
201
 
105
- @objc public func shutdownWithResolver(
106
- _ resolve: @escaping (Any?) -> Void,
107
- rejecter reject: @escaping (String?, String?, Error?) -> Void
202
+ @objc public func setExtras(
203
+ _ entries: NSArray,
204
+ resolver resolve: @escaping @Sendable (Any?) -> Void,
205
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
108
206
  ) {
109
- Task { [weak self] in
110
- guard let self = self else { return }
111
- self.stateTask?.cancel()
112
- self.positionsTask?.cancel()
113
- await Beekon.shared.shutdown()
207
+ let extras = entriesToDict(entries)
208
+ Task {
209
+ await Beekon.shared.setExtras(extras)
114
210
  resolve(nil)
115
211
  }
116
212
  }
117
213
 
118
- @objc public func historyFromMs(
119
- _ fromMs: Double,
120
- toMs: Double,
121
- resolver resolve: @escaping RCTPromiseResolveBlock,
122
- rejecter reject: @escaping RCTPromiseRejectBlock
214
+ // MARK: - Geofences
215
+
216
+ @objc public func addGeofences(
217
+ _ geofences: NSArray,
218
+ resolver resolve: @escaping @Sendable (Any?) -> Void,
219
+ rejecter reject: @escaping @Sendable (String?, String?, Error?) -> Void
123
220
  ) {
221
+ let list = wireToGeofences(geofences)
124
222
  Task { [weak self] in
125
223
  guard let self = self else { return }
126
224
  do {
127
- let from = Date(timeIntervalSince1970: fromMs / 1000.0)
128
- let to = Date(timeIntervalSince1970: toMs / 1000.0)
129
- let positions = try await Beekon.shared.history(from: from, to: to)
130
- let arr = positions.map { self.positionToWire($0) }
131
- resolve(arr)
225
+ try await Beekon.shared.addGeofences(list)
226
+ resolve(nil)
132
227
  } catch {
133
228
  reject(self.errorCode(error), error.localizedDescription, error)
134
229
  }
135
230
  }
136
231
  }
137
232
 
138
- // MARK: - Mappers
233
+ @objc public func removeGeofences(
234
+ _ ids: NSArray,
235
+ resolver resolve: @escaping @Sendable (Any?) -> Void,
236
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
237
+ ) {
238
+ let list = ids.compactMap { $0 as? String }
239
+ Task {
240
+ await Beekon.shared.removeGeofences(ids: list)
241
+ resolve(nil)
242
+ }
243
+ }
139
244
 
140
- private func wireToConfig(_ d: NSDictionary) throws -> BeekonConfig {
141
- let presetStr = (d["preset"] as? String) ?? "balanced"
142
- let preset: Preset
143
- switch presetStr {
144
- case "saver": preset = .saver
145
- case "precision": preset = .precision
146
- default: preset = .balanced
245
+ @objc public func listGeofencesWithResolver(
246
+ _ resolve: @escaping @Sendable (Any?) -> Void,
247
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
248
+ ) {
249
+ Task { [weak self] in
250
+ guard let self = self else { return }
251
+ let geofences = await Beekon.shared.listGeofences()
252
+ resolve(geofences.map { self.geofenceToWire($0) })
147
253
  }
254
+ }
255
+
256
+ // MARK: - Mappers: wire (NSDictionary/NSArray) → Beekon
148
257
 
149
- let distanceFilterMeters = d["distanceFilterMeters"] as? Double
150
- // JS sends millis; iOS BeekonConfig wants seconds. Normalize here.
151
- let intervalSeconds: Double?
152
- if let ms = d["intervalMillis"] as? Double {
153
- intervalSeconds = ms / 1000.0
154
- } else {
155
- intervalSeconds = nil
258
+ private func wireToConfig(_ d: NSDictionary) -> BeekonConfig {
259
+ let minTime =
260
+ (d["minTimeBetweenLocationsSeconds"] as? NSNumber)?.doubleValue ?? 30
261
+ let minDist =
262
+ (d["minDistanceBetweenLocationsMeters"] as? NSNumber)?.doubleValue ?? 100
263
+ let stationaryRadius =
264
+ (d["stationaryRadiusMeters"] as? NSNumber)?.doubleValue ?? 5
265
+ let detectActivity = (d["detectActivity"] as? NSNumber)?.boolValue ?? false
266
+
267
+ var sync: SyncConfig?
268
+ if let s = d["sync"] as? NSDictionary,
269
+ let urlStr = s["url"] as? String,
270
+ let url = URL(string: urlStr) {
271
+ sync = SyncConfig(
272
+ url: url,
273
+ headers: entriesToDict((s["headers"] as? NSArray) ?? []),
274
+ intervalSeconds: (s["intervalSeconds"] as? NSNumber)?.doubleValue ?? 300,
275
+ batchSize: (s["batchSize"] as? NSNumber)?.intValue ?? 100
276
+ )
156
277
  }
157
- let licenseKey = d["licenseKey"] as? String
158
278
 
279
+ // `notification` is Android-only — iOS ignores it.
159
280
  return BeekonConfig(
160
- preset: preset,
161
- distanceFilterMeters: distanceFilterMeters,
162
- intervalSeconds: intervalSeconds,
163
- licenseKey: licenseKey
281
+ minTimeBetweenLocationsSeconds: minTime,
282
+ minDistanceBetweenLocationsMeters: minDist,
283
+ accuracyMode: accuracyModeFromWire(d["accuracyMode"] as? String),
284
+ whenStationary: stationaryModeFromWire(d["whenStationary"] as? String),
285
+ stationaryRadiusMeters: stationaryRadius,
286
+ detectActivity: detectActivity,
287
+ sync: sync
164
288
  )
165
289
  }
166
290
 
167
- private func positionToWire(_ p: Position) -> NSDictionary {
291
+ private func wireToGeofences(_ arr: NSArray) -> [BeekonGeofence] {
292
+ var out: [BeekonGeofence] = []
293
+ for case let m as NSDictionary in arr {
294
+ guard
295
+ let id = m["id"] as? String,
296
+ let lat = (m["lat"] as? NSNumber)?.doubleValue,
297
+ let lng = (m["lng"] as? NSNumber)?.doubleValue,
298
+ let radius = (m["radiusMeters"] as? NSNumber)?.doubleValue
299
+ else { continue }
300
+ out.append(
301
+ BeekonGeofence(
302
+ id: id,
303
+ latitude: lat,
304
+ longitude: lng,
305
+ radiusMeters: radius,
306
+ notifyOnEntry: (m["notifyOnEntry"] as? NSNumber)?.boolValue ?? true,
307
+ notifyOnExit: (m["notifyOnExit"] as? NSNumber)?.boolValue ?? true
308
+ )
309
+ )
310
+ }
311
+ return out
312
+ }
313
+
314
+ private func entriesToDict(_ arr: NSArray) -> [String: String] {
315
+ var out: [String: String] = [:]
316
+ for case let e as NSDictionary in arr {
317
+ if let k = e["key"] as? String, let v = e["value"] as? String {
318
+ out[k] = v
319
+ }
320
+ }
321
+ return out
322
+ }
323
+
324
+ // MARK: - Mappers: Beekon → wire (NSDictionary)
325
+
326
+ private func locationToWire(_ loc: Location) -> NSDictionary {
327
+ // NSDictionary literals can't carry `nil`, so optionals collapse to
328
+ // `NSNull` — the JS Codegen layer translates that back to `null`.
329
+ let d = NSMutableDictionary()
330
+ d["id"] = loc.id
331
+ d["lat"] = loc.latitude
332
+ d["lng"] = loc.longitude
333
+ d["timestampMs"] = loc.timestamp.timeIntervalSince1970 * 1000.0
334
+ d["accuracy"] = loc.accuracy.map { $0 as Any } ?? NSNull()
335
+ d["speed"] = loc.speed.map { $0 as Any } ?? NSNull()
336
+ d["bearing"] = loc.bearing.map { $0 as Any } ?? NSNull()
337
+ d["altitude"] = loc.altitude.map { $0 as Any } ?? NSNull()
338
+ d["quality"] = qualityToWire(loc.quality)
339
+ d["trigger"] = triggerToWire(loc.trigger)
340
+ d["motion"] = motionToWire(loc.motion)
341
+ d["activity"] = loc.activity.map { activityToWire($0) as Any } ?? NSNull()
342
+ d["isMock"] = loc.isMock
343
+ return d
344
+ }
345
+
346
+ private func geofenceToWire(_ g: BeekonGeofence) -> NSDictionary {
168
347
  return [
169
- "lat": p.lat,
170
- "lng": p.lng,
171
- "accuracy": p.accuracy,
172
- "speed": p.speed,
173
- "bearing": p.bearing,
174
- "altitude": p.altitude,
175
- "timestampMs": p.timestamp.timeIntervalSince1970 * 1000.0,
348
+ "id": g.id,
349
+ "lat": g.latitude,
350
+ "lng": g.longitude,
351
+ "radiusMeters": g.radiusMeters,
352
+ "notifyOnEntry": g.notifyOnEntry,
353
+ "notifyOnExit": g.notifyOnExit,
354
+ ]
355
+ }
356
+
357
+ private func geofenceEventToWire(_ e: GeofenceEvent) -> NSDictionary {
358
+ return [
359
+ "id": e.id,
360
+ "geofenceId": e.geofenceId,
361
+ "type": transitionToWire(e.type),
362
+ "timestampMs": e.timestamp.timeIntervalSince1970 * 1000.0,
176
363
  ]
177
364
  }
178
365
 
179
366
  private func stateToWire(_ s: BeekonState) -> NSDictionary {
180
367
  switch s {
181
- case .idle: return ["type": "idle"]
182
- case .starting: return ["type": "starting"]
183
- case .tracking: return ["type": "tracking"]
184
- case .stopped: return ["type": "stopped"]
185
- case .paused(let reason):
186
- let r: String
187
- switch reason {
188
- case .permissionRevoked: r = "permissionRevoked"
189
- case .locationDisabled: r = "locationDisabled"
190
- case .unknown: r = "unknown"
191
- }
192
- return ["type": "paused", "pauseReason": r]
368
+ case .idle:
369
+ return ["type": "idle"]
370
+ case .tracking:
371
+ return ["type": "tracking"]
372
+ case .stopped(let reason):
373
+ return ["type": "stopped", "stopReason": stopReasonToWire(reason)]
374
+ // BeekonKit enums are non-frozen (library-evolution binary); handle unknowns.
375
+ @unknown default:
376
+ return ["type": "idle"]
377
+ }
378
+ }
379
+
380
+ private func syncStatusToWire(_ s: SyncStatus) -> NSDictionary {
381
+ switch s {
382
+ case .idle:
383
+ return ["type": "idle"]
384
+ case .pending:
385
+ return ["type": "pending"]
386
+ case .failed(let reason):
387
+ return ["type": "failed", "failure": syncFailureToWire(reason)]
388
+ @unknown default:
389
+ return ["type": "idle"]
390
+ }
391
+ }
392
+
393
+ // MARK: - Enum mappers
394
+
395
+ private func accuracyModeFromWire(_ s: String?) -> AccuracyMode {
396
+ switch s {
397
+ case "high": return .high
398
+ case "low": return .low
399
+ default: return .balanced
400
+ }
401
+ }
402
+
403
+ private func stationaryModeFromWire(_ s: String?) -> StationaryMode {
404
+ switch s {
405
+ case "keepTracking": return .keepTracking
406
+ case "pauseWithCheckIns": return .pauseWithCheckIns
407
+ default: return .pause
408
+ }
409
+ }
410
+
411
+ private func stopReasonToWire(_ r: StopReason) -> String {
412
+ switch r {
413
+ case .user: return "user"
414
+ case .permissionDenied: return "permissionDenied"
415
+ case .locationServicesDisabled: return "locationServicesDisabled"
416
+ case .locationUnavailable: return "locationUnavailable"
417
+ case .system: return "system"
418
+ @unknown default: return "system"
419
+ }
420
+ }
421
+
422
+ private func syncFailureToWire(_ f: SyncFailure) -> String {
423
+ switch f {
424
+ case .auth: return "auth"
425
+ case .rejected: return "rejected"
426
+ @unknown default: return "rejected"
427
+ }
428
+ }
429
+
430
+ private func qualityToWire(_ q: LocationQuality) -> String {
431
+ switch q {
432
+ case .ok: return "ok"
433
+ case .lowAccuracy: return "lowAccuracy"
434
+ case .implausibleSpeed: return "implausibleSpeed"
435
+ @unknown default: return "ok"
436
+ }
437
+ }
438
+
439
+ private func triggerToWire(_ t: LocationTrigger) -> String {
440
+ switch t {
441
+ case .interval: return "interval"
442
+ case .motion: return "motion"
443
+ case .checkIn: return "checkIn"
444
+ case .geofence: return "geofence"
445
+ case .manual: return "manual"
446
+ @unknown default: return "interval"
447
+ }
448
+ }
449
+
450
+ private func motionToWire(_ m: MotionState) -> String {
451
+ switch m {
452
+ case .moving: return "moving"
453
+ case .stationary: return "stationary"
454
+ case .unknown: return "unknown"
455
+ @unknown default: return "unknown"
456
+ }
457
+ }
458
+
459
+ private func activityToWire(_ a: ActivityType) -> String {
460
+ switch a {
461
+ case .stationary: return "stationary"
462
+ case .walking: return "walking"
463
+ case .running: return "running"
464
+ case .cycling: return "cycling"
465
+ case .automotive: return "automotive"
466
+ case .unknown: return "unknown"
467
+ @unknown default: return "unknown"
468
+ }
469
+ }
470
+
471
+ private func transitionToWire(_ t: Transition) -> String {
472
+ switch t {
473
+ case .enter: return "enter"
474
+ case .exit: return "exit"
475
+ @unknown default: return "enter"
193
476
  }
194
477
  }
195
478
 
196
- /// Maps native `BeekonError` cases to stable string codes shared with
197
- /// Android. JS-side error handling can switch on these without per-platform
198
- /// branches.
479
+ /// Maps native `BeekonError` cases to stable string codes shared with Android,
480
+ /// so JS-side error handling can switch on them without per-platform branches.
199
481
  private func errorCode(_ e: Error) -> String {
200
482
  if let be = e as? BeekonError {
201
483
  switch be {
202
- case .permissionDenied: return "PERMISSION_DENIED"
203
- case .locationServicesDisabled: return "LOCATION_SERVICES_DISABLED"
204
- case .notConfigured: return "NOT_CONFIGURED"
205
- case .notInitialised: return "NOT_INITIALISED"
206
- case .internalError: return "INTERNAL_ERROR"
484
+ case .storage: return "STORAGE_FAILURE"
485
+ case .invalidGeofence: return "INVALID_GEOFENCE"
486
+ @unknown default: return "INTERNAL_ERROR"
207
487
  }
208
488
  }
209
489
  return "INTERNAL_ERROR"