@wayq/beekon-rn 0.0.5 → 0.0.6

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 (59) hide show
  1. package/BeekonRn.podspec +4 -2
  2. package/CHANGELOG.md +103 -0
  3. package/README.md +303 -81
  4. package/android/build.gradle +1 -1
  5. package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +113 -7
  6. package/ios/BeekonRn.mm +13 -0
  7. package/ios/BeekonRn.swift +113 -9
  8. package/ios/Frameworks/BeekonKit.xcframework/Info.plist +5 -5
  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 +5399 -1463
  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 +77 -2
  14. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/BeekonKit +0 -0
  15. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Info.plist +0 -0
  16. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +5399 -1463
  17. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  18. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +77 -2
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +5399 -1463
  20. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  21. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +77 -2
  22. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +1 -1
  23. package/lib/module/NativeBeekonRn.js +13 -0
  24. package/lib/module/NativeBeekonRn.js.map +1 -1
  25. package/lib/module/beekon.js +56 -7
  26. package/lib/module/beekon.js.map +1 -1
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/internal/mappers.js +50 -3
  29. package/lib/module/internal/mappers.js.map +1 -1
  30. package/lib/module/types/auth.js +4 -0
  31. package/lib/module/types/auth.js.map +1 -0
  32. package/lib/module/types/error.js +13 -3
  33. package/lib/module/types/error.js.map +1 -1
  34. package/lib/typescript/src/NativeBeekonRn.d.ts +48 -0
  35. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
  36. package/lib/typescript/src/beekon.d.ts +30 -1
  37. package/lib/typescript/src/beekon.d.ts.map +1 -1
  38. package/lib/typescript/src/index.d.ts +2 -1
  39. package/lib/typescript/src/index.d.ts.map +1 -1
  40. package/lib/typescript/src/internal/mappers.d.ts +5 -1
  41. package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
  42. package/lib/typescript/src/types/auth.d.ts +99 -0
  43. package/lib/typescript/src/types/auth.d.ts.map +1 -0
  44. package/lib/typescript/src/types/config.d.ts +16 -0
  45. package/lib/typescript/src/types/config.d.ts.map +1 -1
  46. package/lib/typescript/src/types/enums.d.ts +14 -0
  47. package/lib/typescript/src/types/enums.d.ts.map +1 -1
  48. package/lib/typescript/src/types/error.d.ts +14 -4
  49. package/lib/typescript/src/types/error.d.ts.map +1 -1
  50. package/package.json +5 -1
  51. package/scripts/fetch-beekonkit.sh +4 -4
  52. package/src/NativeBeekonRn.ts +55 -0
  53. package/src/beekon.ts +66 -6
  54. package/src/index.tsx +3 -0
  55. package/src/internal/mappers.ts +59 -1
  56. package/src/types/auth.ts +101 -0
  57. package/src/types/config.ts +16 -0
  58. package/src/types/enums.ts +16 -0
  59. package/src/types/error.ts +19 -4
@@ -9,6 +9,11 @@ import com.facebook.react.bridge.WritableArray
9
9
  import com.facebook.react.bridge.WritableMap
10
10
  import `in`.wayq.beekon.AccuracyMode
11
11
  import `in`.wayq.beekon.ActivityType
12
+ import `in`.wayq.beekon.AuthBodyFormat
13
+ import `in`.wayq.beekon.AuthConfig
14
+ import `in`.wayq.beekon.AuthResponseMapping
15
+ import `in`.wayq.beekon.AuthStrategy
16
+ import `in`.wayq.beekon.AuthTokens
12
17
  import `in`.wayq.beekon.Beekon
13
18
  import `in`.wayq.beekon.BeekonConfig
14
19
  import `in`.wayq.beekon.BeekonException
@@ -18,6 +23,7 @@ import `in`.wayq.beekon.GeofenceEvent
18
23
  import `in`.wayq.beekon.Location
19
24
  import `in`.wayq.beekon.LocationQuality
20
25
  import `in`.wayq.beekon.LocationTrigger
26
+ import `in`.wayq.beekon.LocationUnavailableReason
21
27
  import `in`.wayq.beekon.MotionState
22
28
  import `in`.wayq.beekon.NotificationConfig
23
29
  import `in`.wayq.beekon.StationaryMode
@@ -50,6 +56,7 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
50
56
  Beekon.geofenceEvents.collect { emitOnGeofenceEvent(geofenceEventToWire(it)) }
51
57
  }
52
58
  scope.launch { Beekon.syncStatus.collect { emitOnSyncStatus(syncStatusToWire(it)) } }
59
+ scope.launch { Beekon.authChanges.collect { emitOnAuthTokens(tokenRefreshToWire(it)) } }
53
60
  }
54
61
 
55
62
  // ---------------------------------------------------------------------------
@@ -87,12 +94,13 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
87
94
  }
88
95
  }
89
96
 
90
- // Android has no native resumeIfNeeded Beekon.start() rehydrates the
91
- // persisted intent (mirrors the Flutter plugin).
97
+ // Calls the guarded native resume (Beekon.resumeIfNeeded), which only
98
+ // re-adopts a previously-active, non-user-stopped session — a no-op when
99
+ // already Tracking or when the user explicitly stopped (mirrors iOS).
92
100
  override fun resumeIfNeeded(promise: Promise) {
93
101
  scope.launch {
94
102
  try {
95
- Beekon.start()
103
+ Beekon.resumeIfNeeded()
96
104
  promise.resolve(null)
97
105
  } catch (t: Throwable) {
98
106
  promise.reject(errorCode(t), t.message ?: "resumeIfNeeded failed", t)
@@ -100,6 +108,21 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
100
108
  }
101
109
  }
102
110
 
111
+ override fun getCurrentLocation(timeoutMs: Double, accuracy: String, promise: Promise) {
112
+ scope.launch {
113
+ try {
114
+ // Empty accuracy is the wire encoding of "use the configured mode".
115
+ val mode = if (accuracy.isEmpty()) null else toAccuracyMode(accuracy)
116
+ val loc = Beekon.getCurrentLocation(timeoutMs.toLong(), mode)
117
+ val arr: WritableArray = Arguments.createArray()
118
+ if (loc != null) arr.pushMap(locationToWire(loc))
119
+ promise.resolve(arr)
120
+ } catch (t: Throwable) {
121
+ promise.reject(errorCode(t), t.message ?: "getCurrentLocation failed", t)
122
+ }
123
+ }
124
+ }
125
+
103
126
  // ---------------------------------------------------------------------------
104
127
  // History
105
128
  // ---------------------------------------------------------------------------
@@ -229,24 +252,80 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
229
252
  )
230
253
  }
231
254
 
232
- private fun wireToSyncConfig(map: ReadableMap): SyncConfig =
233
- SyncConfig(
255
+ private fun wireToSyncConfig(map: ReadableMap): SyncConfig {
256
+ val auth =
257
+ if (map.hasKey("auth") && !map.isNull("auth")) {
258
+ map.getMap("auth")?.let { wireToAuthConfig(it) }
259
+ } else {
260
+ null
261
+ }
262
+ return SyncConfig(
234
263
  url = map.getString("url") ?: "",
235
264
  headers = entriesToMap(map.getArray("headers")),
236
265
  intervalSeconds = map.getDouble("intervalSeconds").toLong(),
237
266
  batchSize = map.getDouble("batchSize").toInt(),
267
+ auth = auth,
238
268
  )
269
+ }
270
+
271
+ private fun wireToAuthConfig(map: ReadableMap): AuthConfig {
272
+ val expiresAt =
273
+ if (map.hasKey("expiresAtMs") && !map.isNull("expiresAtMs")) {
274
+ // Wire carries epoch millis; the native recipe wants epoch seconds.
275
+ (map.getDouble("expiresAtMs") / 1000.0).toLong()
276
+ } else {
277
+ null
278
+ }
279
+ val seedEpoch =
280
+ if (map.hasKey("seedEpoch") && !map.isNull("seedEpoch")) {
281
+ map.getDouble("seedEpoch").toLong()
282
+ } else {
283
+ null
284
+ }
285
+ val responseMapping =
286
+ if (map.hasKey("responseMapping") && !map.isNull("responseMapping")) {
287
+ map.getMap("responseMapping")?.let { wireToResponseMapping(it) } ?: AuthResponseMapping()
288
+ } else {
289
+ AuthResponseMapping()
290
+ }
291
+ return AuthConfig(
292
+ accessToken = optString(map, "accessToken"),
293
+ refreshToken = optString(map, "refreshToken"),
294
+ expiresAt = expiresAt,
295
+ strategy = toAuthStrategy(map.getString("strategy")),
296
+ refreshUrl = optString(map, "refreshUrl"),
297
+ refreshPayload = entriesToMap(map.getArray("refreshPayload")),
298
+ refreshHeaders = entriesToMap(map.getArray("refreshHeaders")),
299
+ refreshBodyFormat = toRefreshBodyFormat(map.getString("refreshBodyFormat")),
300
+ responseMapping = responseMapping,
301
+ skewMarginSeconds = map.getDouble("skewMarginSeconds").toLong(),
302
+ seedEpoch = seedEpoch,
303
+ )
304
+ }
305
+
306
+ private fun wireToResponseMapping(map: ReadableMap): AuthResponseMapping =
307
+ AuthResponseMapping(
308
+ accessToken = optString(map, "accessToken"),
309
+ refreshToken = optString(map, "refreshToken"),
310
+ expiresIn = optString(map, "expiresIn"),
311
+ expiresAt = optString(map, "expiresAt"),
312
+ )
313
+
314
+ private fun optString(map: ReadableMap, key: String): String? =
315
+ if (map.hasKey(key) && !map.isNull(key)) map.getString(key) else null
239
316
 
240
317
  private fun wireToNotification(map: ReadableMap): NotificationConfig {
241
318
  val title =
242
319
  if (map.hasKey("title") && !map.isNull("title")) map.getString("title") else null
243
320
  val text =
244
321
  if (map.hasKey("text") && !map.isNull("text")) map.getString("text") else null
322
+ val smallIcon =
323
+ if (map.hasKey("smallIcon") && !map.isNull("smallIcon")) map.getString("smallIcon") else null
245
324
  // Use the data-class default for `title` when the wire value is absent.
246
325
  return if (title != null) {
247
- NotificationConfig(title = title, text = text)
326
+ NotificationConfig(title = title, text = text, smallIcon = smallIcon)
248
327
  } else {
249
- NotificationConfig(text = text)
328
+ NotificationConfig(text = text, smallIcon = smallIcon)
250
329
  }
251
330
  }
252
331
 
@@ -347,6 +426,18 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
347
426
  return m
348
427
  }
349
428
 
429
+ private fun tokenRefreshToWire(t: AuthTokens): WritableMap {
430
+ val m = Arguments.createMap()
431
+ m.putString("accessToken", t.accessToken)
432
+ val refreshToken = t.refreshToken
433
+ if (refreshToken != null) m.putString("refreshToken", refreshToken) else m.putNull("refreshToken")
434
+ val expiresAt = t.expiresAt
435
+ // Native epoch seconds → wire epoch millis.
436
+ if (expiresAt != null) m.putDouble("expiresAtMs", (expiresAt * 1000L).toDouble()) else m.putNull("expiresAtMs")
437
+ m.putDouble("epoch", t.epoch.toDouble())
438
+ return m
439
+ }
440
+
350
441
  private fun putNullableDouble(map: WritableMap, key: String, value: Double?) {
351
442
  if (value == null) map.putNull(key) else map.putDouble(key, value)
352
443
  }
@@ -367,6 +458,16 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
367
458
  else -> StationaryMode.Pause
368
459
  }
369
460
 
461
+ private fun toAuthStrategy(s: String?): AuthStrategy = when (s) {
462
+ "raw" -> AuthStrategy.Raw
463
+ else -> AuthStrategy.Bearer
464
+ }
465
+
466
+ private fun toRefreshBodyFormat(s: String?): AuthBodyFormat = when (s) {
467
+ "json" -> AuthBodyFormat.Json
468
+ else -> AuthBodyFormat.Form
469
+ }
470
+
370
471
  private fun stopReasonToWire(r: StopReason): String = when (r) {
371
472
  StopReason.User -> "user"
372
473
  StopReason.PermissionDenied -> "permissionDenied"
@@ -419,6 +520,11 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
419
520
  private fun errorCode(t: Throwable): String = when (t) {
420
521
  is BeekonException.StorageException -> "STORAGE_FAILURE"
421
522
  is BeekonException.InvalidGeofence -> "INVALID_GEOFENCE"
523
+ is BeekonException.LocationUnavailable -> when (t.reason) {
524
+ LocationUnavailableReason.PermissionDenied -> "PERMISSION_DENIED"
525
+ LocationUnavailableReason.LocationServicesDisabled -> "LOCATION_SERVICES_DISABLED"
526
+ LocationUnavailableReason.Unavailable -> "LOCATION_UNAVAILABLE"
527
+ }
422
528
  else -> "INTERNAL_ERROR"
423
529
  }
424
530
 
package/ios/BeekonRn.mm CHANGED
@@ -29,6 +29,9 @@
29
29
  }
30
30
  onSyncStatus:^(NSDictionary *_Nonnull st) {
31
31
  [weakSelf emitOnSyncStatus:st];
32
+ }
33
+ onAuthTokens:^(NSDictionary *_Nonnull t) {
34
+ [weakSelf emitOnAuthTokens:t];
32
35
  }];
33
36
  }
34
37
  return self;
@@ -72,6 +75,16 @@
72
75
  [_impl resumeIfNeededWithResolver:resolve rejecter:reject];
73
76
  }
74
77
 
78
+ - (void)getCurrentLocation:(double)timeoutMs
79
+ accuracy:(NSString *)accuracy
80
+ resolve:(RCTPromiseResolveBlock)resolve
81
+ reject:(RCTPromiseRejectBlock)reject {
82
+ [_impl getCurrentLocationTimeoutMs:timeoutMs
83
+ accuracy:accuracy
84
+ resolver:resolve
85
+ rejecter:reject];
86
+ }
87
+
75
88
  // MARK: - History
76
89
 
77
90
  - (void)getLocations:(double)fromMs
@@ -22,22 +22,26 @@ import BeekonKit
22
22
  private let onLocationCb: (NSDictionary) -> Void
23
23
  private let onGeofenceEventCb: (NSDictionary) -> Void
24
24
  private let onSyncStatusCb: (NSDictionary) -> Void
25
+ private let onAuthTokensCb: (NSDictionary) -> Void
25
26
 
26
27
  private var stateTask: Task<Void, Never>?
27
28
  private var locationsTask: Task<Void, Never>?
28
29
  private var geofenceEventsTask: Task<Void, Never>?
29
30
  private var syncStatusTask: Task<Void, Never>?
31
+ private var authTokensTask: Task<Void, Never>?
30
32
 
31
33
  @objc public init(
32
34
  onState: @escaping (NSDictionary) -> Void,
33
35
  onLocation: @escaping (NSDictionary) -> Void,
34
36
  onGeofenceEvent: @escaping (NSDictionary) -> Void,
35
- onSyncStatus: @escaping (NSDictionary) -> Void
37
+ onSyncStatus: @escaping (NSDictionary) -> Void,
38
+ onAuthTokens: @escaping (NSDictionary) -> Void
36
39
  ) {
37
40
  self.onStateCb = onState
38
41
  self.onLocationCb = onLocation
39
42
  self.onGeofenceEventCb = onGeofenceEvent
40
43
  self.onSyncStatusCb = onSyncStatus
44
+ self.onAuthTokensCb = onAuthTokens
41
45
  super.init()
42
46
  self.stateTask = Task { [weak self] in
43
47
  guard let self = self else { return }
@@ -63,6 +67,12 @@ import BeekonKit
63
67
  self.onSyncStatusCb(self.syncStatusToWire(status))
64
68
  }
65
69
  }
70
+ self.authTokensTask = Task { [weak self] in
71
+ guard let self = self else { return }
72
+ for await tokens in await Beekon.shared.authChanges {
73
+ self.onAuthTokensCb(self.authTokensToWire(tokens))
74
+ }
75
+ }
66
76
  }
67
77
 
68
78
  @objc public func invalidate() {
@@ -70,10 +80,12 @@ import BeekonKit
70
80
  locationsTask?.cancel()
71
81
  geofenceEventsTask?.cancel()
72
82
  syncStatusTask?.cancel()
83
+ authTokensTask?.cancel()
73
84
  stateTask = nil
74
85
  locationsTask = nil
75
86
  geofenceEventsTask = nil
76
87
  syncStatusTask = nil
88
+ authTokensTask = nil
77
89
  }
78
90
 
79
91
  /// Register Beekon's background-refresh task and install cold-launch hooks.
@@ -132,6 +144,29 @@ import BeekonKit
132
144
  }
133
145
  }
134
146
 
147
+ @objc public func getCurrentLocationTimeoutMs(
148
+ _ timeoutMs: Double,
149
+ accuracy: String,
150
+ resolver resolve: @escaping @Sendable (Any?) -> Void,
151
+ rejecter reject: @escaping @Sendable (String?, String?, Error?) -> Void
152
+ ) {
153
+ // Empty accuracy is the wire encoding of "use the configured mode".
154
+ let mode: AccuracyMode? = accuracy.isEmpty ? nil : accuracyModeFromWire(accuracy)
155
+ Task { [weak self] in
156
+ guard let self = self else { return }
157
+ do {
158
+ let location = try await Beekon.shared.getCurrentLocation(
159
+ timeout: timeoutMs / 1000.0,
160
+ accuracy: mode
161
+ )
162
+ // 0- or 1-element array — empty means timeout / no fix.
163
+ resolve(location.map { [self.locationToWire($0)] } ?? [])
164
+ } catch {
165
+ reject(self.errorCode(error), error.localizedDescription, error)
166
+ }
167
+ }
168
+ }
169
+
135
170
  // MARK: - History
136
171
 
137
172
  @objc public func getLocationsFromMs(
@@ -266,14 +301,21 @@ import BeekonKit
266
301
 
267
302
  var sync: SyncConfig?
268
303
  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
- )
304
+ let urlStr = s["url"] as? String {
305
+ if let url = URL(string: urlStr) {
306
+ let auth = (s["auth"] as? NSDictionary).map { wireToAuthConfig($0) }
307
+ sync = SyncConfig(
308
+ url: url,
309
+ headers: entriesToDict((s["headers"] as? NSArray) ?? []),
310
+ intervalSeconds: (s["intervalSeconds"] as? NSNumber)?.doubleValue ?? 300,
311
+ batchSize: (s["batchSize"] as? NSNumber)?.intValue ?? 100,
312
+ auth: auth
313
+ )
314
+ } else {
315
+ // Don't silently drop the whole sync config on an unparseable URL —
316
+ // surface it so a malformed endpoint is diagnosable instead of a no-op.
317
+ NSLog("[BeekonRn] sync config ignored: url is not a valid URL: %@", urlStr)
318
+ }
277
319
  }
278
320
 
279
321
  // `notification` is Android-only — iOS ignores it.
@@ -321,6 +363,35 @@ import BeekonKit
321
363
  return out
322
364
  }
323
365
 
366
+ private func wireToAuthConfig(_ d: NSDictionary) -> AuthConfig {
367
+ // Wire carries epoch millis; the native recipe wants epoch seconds.
368
+ let expiresAt = (d["expiresAtMs"] as? NSNumber).map { $0.doubleValue / 1000.0 }
369
+ let responseMapping = (d["responseMapping"] as? NSDictionary)
370
+ .map { wireToResponseMapping($0) } ?? AuthResponseMapping()
371
+ return AuthConfig(
372
+ accessToken: d["accessToken"] as? String,
373
+ refreshToken: d["refreshToken"] as? String,
374
+ expiresAt: expiresAt,
375
+ strategy: authStrategyFromWire(d["strategy"] as? String),
376
+ refreshUrl: (d["refreshUrl"] as? String).flatMap { URL(string: $0) },
377
+ refreshPayload: entriesToDict((d["refreshPayload"] as? NSArray) ?? []),
378
+ refreshHeaders: entriesToDict((d["refreshHeaders"] as? NSArray) ?? []),
379
+ refreshBodyFormat: authBodyFormatFromWire(d["refreshBodyFormat"] as? String),
380
+ responseMapping: responseMapping,
381
+ skewMarginSeconds: (d["skewMarginSeconds"] as? NSNumber)?.doubleValue ?? 60,
382
+ seedEpoch: (d["seedEpoch"] as? NSNumber)?.intValue
383
+ )
384
+ }
385
+
386
+ private func wireToResponseMapping(_ d: NSDictionary) -> AuthResponseMapping {
387
+ return AuthResponseMapping(
388
+ accessToken: d["accessToken"] as? String,
389
+ refreshToken: d["refreshToken"] as? String,
390
+ expiresIn: d["expiresIn"] as? String,
391
+ expiresAt: d["expiresAt"] as? String
392
+ )
393
+ }
394
+
324
395
  // MARK: - Mappers: Beekon → wire (NSDictionary)
325
396
 
326
397
  private func locationToWire(_ loc: Location) -> NSDictionary {
@@ -390,6 +461,18 @@ import BeekonKit
390
461
  }
391
462
  }
392
463
 
464
+ private func authTokensToWire(_ t: AuthTokens) -> NSDictionary {
465
+ // NSDictionary literals can't carry `nil`; optionals collapse to `NSNull`,
466
+ // which the Codegen layer translates back to `null` on the JS side.
467
+ let d = NSMutableDictionary()
468
+ d["accessToken"] = t.accessToken
469
+ d["refreshToken"] = t.refreshToken.map { $0 as Any } ?? NSNull()
470
+ // Native epoch seconds → wire epoch millis.
471
+ d["expiresAtMs"] = t.expiresAt.map { ($0 * 1000.0) as Any } ?? NSNull()
472
+ d["epoch"] = t.epoch
473
+ return d
474
+ }
475
+
393
476
  // MARK: - Enum mappers
394
477
 
395
478
  private func accuracyModeFromWire(_ s: String?) -> AccuracyMode {
@@ -408,6 +491,20 @@ import BeekonKit
408
491
  }
409
492
  }
410
493
 
494
+ private func authStrategyFromWire(_ s: String?) -> AuthStrategy {
495
+ switch s {
496
+ case "raw": return .raw
497
+ default: return .bearer
498
+ }
499
+ }
500
+
501
+ private func authBodyFormatFromWire(_ s: String?) -> AuthBodyFormat {
502
+ switch s {
503
+ case "json": return .json
504
+ default: return .form
505
+ }
506
+ }
507
+
411
508
  private func stopReasonToWire(_ r: StopReason) -> String {
412
509
  switch r {
413
510
  case .user: return "user"
@@ -483,6 +580,13 @@ import BeekonKit
483
580
  switch be {
484
581
  case .storage: return "STORAGE_FAILURE"
485
582
  case .invalidGeofence: return "INVALID_GEOFENCE"
583
+ case .locationUnavailable(let reason):
584
+ switch reason {
585
+ case .permissionDenied: return "PERMISSION_DENIED"
586
+ case .locationServicesDisabled: return "LOCATION_SERVICES_DISABLED"
587
+ case .unavailable: return "LOCATION_UNAVAILABLE"
588
+ @unknown default: return "LOCATION_UNAVAILABLE"
589
+ }
486
590
  @unknown default: return "INTERNAL_ERROR"
487
591
  }
488
592
  }
@@ -8,32 +8,32 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>BeekonKit.framework/BeekonKit</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64_x86_64-simulator</string>
11
+ <string>ios-arm64</string>
12
12
  <key>LibraryPath</key>
13
13
  <string>BeekonKit.framework</string>
14
14
  <key>SupportedArchitectures</key>
15
15
  <array>
16
16
  <string>arm64</string>
17
- <string>x86_64</string>
18
17
  </array>
19
18
  <key>SupportedPlatform</key>
20
19
  <string>ios</string>
21
- <key>SupportedPlatformVariant</key>
22
- <string>simulator</string>
23
20
  </dict>
24
21
  <dict>
25
22
  <key>BinaryPath</key>
26
23
  <string>BeekonKit.framework/BeekonKit</string>
27
24
  <key>LibraryIdentifier</key>
28
- <string>ios-arm64</string>
25
+ <string>ios-arm64_x86_64-simulator</string>
29
26
  <key>LibraryPath</key>
30
27
  <string>BeekonKit.framework</string>
31
28
  <key>SupportedArchitectures</key>
32
29
  <array>
33
30
  <string>arm64</string>
31
+ <string>x86_64</string>
34
32
  </array>
35
33
  <key>SupportedPlatform</key>
36
34
  <string>ios</string>
35
+ <key>SupportedPlatformVariant</key>
36
+ <string>simulator</string>
37
37
  </dict>
38
38
  </array>
39
39
  <key>CFBundlePackageType</key>