@wayq/beekon-rn 0.0.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/BeekonRn.podspec +3 -3
  2. package/CHANGELOG.md +58 -6
  3. package/LICENSE.txt +3 -3
  4. package/README.md +85 -264
  5. package/android/build.gradle +2 -2
  6. package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +133 -8
  7. package/ios/BeekonRn.mm +40 -0
  8. package/ios/BeekonRn.swift +176 -8
  9. package/ios/Frameworks/BeekonKit.xcframework/Info.plist +5 -5
  10. package/ios/Frameworks/BeekonKit.xcframework/LICENSE.txt +3 -3
  11. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/BeekonKit +0 -0
  12. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.abi.json +5839 -3482
  13. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  14. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftinterface +93 -16
  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/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +5839 -3482
  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 +93 -16
  19. package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +5839 -3482
  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 +93 -16
  22. package/lib/module/NativeBeekonRn.js +6 -0
  23. package/lib/module/NativeBeekonRn.js.map +1 -1
  24. package/lib/module/beekon.js +109 -1
  25. package/lib/module/beekon.js.map +1 -1
  26. package/lib/module/index.js +4 -0
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/internal/licenseNudge.js +64 -0
  29. package/lib/module/internal/licenseNudge.js.map +1 -0
  30. package/lib/module/internal/mappers.js +68 -8
  31. package/lib/module/internal/mappers.js.map +1 -1
  32. package/lib/module/types/config.js +73 -1
  33. package/lib/module/types/config.js.map +1 -1
  34. package/lib/module/types/log.js +4 -0
  35. package/lib/module/types/log.js.map +1 -0
  36. package/lib/module/types/permission.js +2 -0
  37. package/lib/module/types/permission.js.map +1 -0
  38. package/lib/typescript/src/NativeBeekonRn.d.ts +66 -2
  39. package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
  40. package/lib/typescript/src/beekon.d.ts +56 -1
  41. package/lib/typescript/src/beekon.d.ts.map +1 -1
  42. package/lib/typescript/src/index.d.ts +5 -2
  43. package/lib/typescript/src/index.d.ts.map +1 -1
  44. package/lib/typescript/src/internal/licenseNudge.d.ts +38 -0
  45. package/lib/typescript/src/internal/licenseNudge.d.ts.map +1 -0
  46. package/lib/typescript/src/internal/mappers.d.ts +5 -1
  47. package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
  48. package/lib/typescript/src/types/config.d.ts +90 -7
  49. package/lib/typescript/src/types/config.d.ts.map +1 -1
  50. package/lib/typescript/src/types/enums.d.ts +8 -0
  51. package/lib/typescript/src/types/enums.d.ts.map +1 -1
  52. package/lib/typescript/src/types/log.d.ts +22 -0
  53. package/lib/typescript/src/types/log.d.ts.map +1 -0
  54. package/lib/typescript/src/types/permission.d.ts +42 -0
  55. package/lib/typescript/src/types/permission.d.ts.map +1 -0
  56. package/lib/typescript/src/types/state.d.ts +4 -1
  57. package/lib/typescript/src/types/state.d.ts.map +1 -1
  58. package/package.json +5 -5
  59. package/scripts/fetch-beekonkit.sh +6 -6
  60. package/src/NativeBeekonRn.ts +70 -2
  61. package/src/beekon.ts +114 -1
  62. package/src/index.tsx +12 -1
  63. package/src/internal/licenseNudge.ts +80 -0
  64. package/src/internal/mappers.ts +93 -7
  65. package/src/types/config.ts +99 -7
  66. package/src/types/enums.ts +9 -0
  67. package/src/types/log.ts +22 -0
  68. package/src/types/permission.ts +48 -0
  69. package/src/types/state.ts +4 -0
@@ -25,8 +25,11 @@ import `in`.wayq.beekon.Location
25
25
  import `in`.wayq.beekon.LocationQuality
26
26
  import `in`.wayq.beekon.LocationTrigger
27
27
  import `in`.wayq.beekon.LocationUnavailableReason
28
+ import `in`.wayq.beekon.LogEntry
29
+ import `in`.wayq.beekon.LogLevel
28
30
  import `in`.wayq.beekon.MotionState
29
31
  import `in`.wayq.beekon.NotificationConfig
32
+ import `in`.wayq.beekon.PermissionStatus
30
33
  import `in`.wayq.beekon.StationaryMode
31
34
  import `in`.wayq.beekon.StopReason
32
35
  import `in`.wayq.beekon.SyncConfig
@@ -64,6 +67,7 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
64
67
  scope.launch { Beekon.syncStatus.collect { emitOnSyncStatus(syncStatusToWire(it)) } }
65
68
  scope.launch { Beekon.authChanges.collect { emitOnAuthTokens(tokenRefreshToWire(it)) } }
66
69
  scope.launch { Beekon.licenseStatus.collect { emitOnLicenseStatus(licenseStatusToWire(it)) } }
70
+ scope.launch { Beekon.logs.collect { emitOnLog(logEntryToWire(it)) } }
67
71
  }
68
72
 
69
73
  // ---------------------------------------------------------------------------
@@ -232,6 +236,60 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
232
236
  promise.resolve(licenseStatusToWire(Beekon.licenseStatus.value))
233
237
  }
234
238
 
239
+ override fun getPermissionStatus(promise: Promise) {
240
+ // Synchronous, non-throwing read on the native SDK.
241
+ promise.resolve(permissionStatusToWire(Beekon.getPermissionStatus()))
242
+ }
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // Diagnostic logs (spec diagnostics/log-format-v1)
246
+ // ---------------------------------------------------------------------------
247
+
248
+ override fun getLog(fromMs: Double, toMs: Double, promise: Promise) {
249
+ scope.launch {
250
+ try {
251
+ val from = Instant.ofEpochMilli(fromMs.toLong())
252
+ val to = Instant.ofEpochMilli(toMs.toLong())
253
+ val arr: WritableArray = Arguments.createArray()
254
+ for (e in Beekon.getLog(from, to)) arr.pushMap(logEntryToWire(e))
255
+ promise.resolve(arr)
256
+ } catch (t: Throwable) {
257
+ promise.reject(errorCode(t), t.message ?: "getLog failed", t)
258
+ }
259
+ }
260
+ }
261
+
262
+ override fun exportLog(promise: Promise) {
263
+ scope.launch {
264
+ try {
265
+ promise.resolve(Beekon.exportLog())
266
+ } catch (t: Throwable) {
267
+ promise.reject(errorCode(t), t.message ?: "exportLog failed", t)
268
+ }
269
+ }
270
+ }
271
+
272
+ override fun clearLog(promise: Promise) {
273
+ scope.launch {
274
+ try {
275
+ Beekon.clearLog()
276
+ promise.resolve(null)
277
+ } catch (t: Throwable) {
278
+ promise.reject(errorCode(t), t.message ?: "clearLog failed", t)
279
+ }
280
+ }
281
+ }
282
+
283
+ override fun setLogLevel(level: String, promise: Promise) {
284
+ Beekon.setLogLevel(toLogLevel(level))
285
+ promise.resolve(null)
286
+ }
287
+
288
+ override fun log(level: String, message: String, promise: Promise) {
289
+ Beekon.log(toLogLevel(level), message)
290
+ promise.resolve(null)
291
+ }
292
+
235
293
  override fun invalidate() {
236
294
  super.invalidate()
237
295
  scope.cancel()
@@ -241,20 +299,47 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
241
299
  // Mappers: wire (ReadableMap/Array) → Kotlin
242
300
  // ---------------------------------------------------------------------------
243
301
 
302
+ // GATED on beekon native 0.0.9 (sealed BeekonConfig) — does not compile against
303
+ // the pinned 0.0.8 dep (the flat BeekonConfig(...) constructor). Switches on the
304
+ // wire `mode` (cloud-mode-v1 §2) to build the matching sealed arm. A cloud
305
+ // configuration with an empty/invalid projectKey throws
306
+ // BeekonException.InvalidConfiguration, which the configure() catch turns into a
307
+ // promise rejection via errorCode().
244
308
  private fun wireToConfig(map: ReadableMap): BeekonConfig {
245
- val sync =
246
- if (map.hasKey("sync") && !map.isNull("sync")) {
247
- map.getMap("sync")?.let { wireToSyncConfig(it) }
248
- } else {
249
- null
250
- }
251
309
  val notification =
252
310
  if (map.hasKey("notification") && !map.isNull("notification")) {
253
311
  map.getMap("notification")?.let { wireToNotification(it) }
254
312
  } else {
255
313
  null
256
314
  }
257
- return BeekonConfig(
315
+
316
+ if (optString(map, "mode") == "cloud") {
317
+ val projectKey = optString(map, "projectKey") ?: ""
318
+ val endpoint = optString(map, "endpoint")
319
+ // Omitted endpoint → fall back to the constructor's baked-in default.
320
+ return if (endpoint != null) {
321
+ BeekonConfig.Cloud(
322
+ projectKey = projectKey,
323
+ endpoint = endpoint,
324
+ notification = notification ?: NotificationConfig(),
325
+ logLevel = toLogLevel(optString(map, "logLevel")),
326
+ )
327
+ } else {
328
+ BeekonConfig.Cloud(
329
+ projectKey = projectKey,
330
+ notification = notification ?: NotificationConfig(),
331
+ logLevel = toLogLevel(optString(map, "logLevel")),
332
+ )
333
+ }
334
+ }
335
+
336
+ val sync =
337
+ if (map.hasKey("sync") && !map.isNull("sync")) {
338
+ map.getMap("sync")?.let { wireToSyncConfig(it) }
339
+ } else {
340
+ null
341
+ }
342
+ return BeekonConfig.SelfManaged(
258
343
  minTimeBetweenLocationsSeconds =
259
344
  map.getDouble("minTimeBetweenLocationsSeconds").toLong(),
260
345
  minDistanceBetweenLocationsMeters =
@@ -268,6 +353,7 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
268
353
  // Passed through verbatim; absent/null means unset — the SDK falls through
269
354
  // to the manifest meta-data, then evaluation (license-format-v1 §9).
270
355
  licenseKey = optString(map, "licenseKey"),
356
+ logLevel = toLogLevel(optString(map, "logLevel")),
271
357
  )
272
358
  }
273
359
 
@@ -283,6 +369,7 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
283
369
  headers = entriesToMap(map.getArray("headers")),
284
370
  intervalSeconds = map.getDouble("intervalSeconds").toLong(),
285
371
  batchSize = map.getDouble("batchSize").toInt(),
372
+ syncThreshold = if (map.hasKey("syncThreshold")) map.getDouble("syncThreshold").toInt() else 0,
286
373
  auth = auth,
287
374
  )
288
375
  }
@@ -432,6 +519,27 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
432
519
  return m
433
520
  }
434
521
 
522
+ private fun permissionStatusToWire(p: PermissionStatus): WritableMap {
523
+ val m = Arguments.createMap()
524
+ m.putString("level", permissionLevelToWire(p.level))
525
+ // '' is the wire encoding of null (not granted).
526
+ m.putString("accuracy", p.accuracy?.let { permissionAccuracyToWire(it) } ?: "")
527
+ return m
528
+ }
529
+
530
+ private fun permissionLevelToWire(l: PermissionStatus.Level): String = when (l) {
531
+ PermissionStatus.Level.NotDetermined -> "notDetermined"
532
+ PermissionStatus.Level.Denied -> "denied"
533
+ PermissionStatus.Level.Restricted -> "restricted"
534
+ PermissionStatus.Level.Foreground -> "foreground"
535
+ PermissionStatus.Level.Background -> "background"
536
+ }
537
+
538
+ private fun permissionAccuracyToWire(a: PermissionStatus.Accuracy): String = when (a) {
539
+ PermissionStatus.Accuracy.Full -> "full"
540
+ PermissionStatus.Accuracy.Reduced -> "reduced"
541
+ }
542
+
435
543
  private fun syncStatusToWire(s: SyncStatus): WritableMap {
436
544
  val m = Arguments.createMap()
437
545
  when (s) {
@@ -484,6 +592,16 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
484
592
  return m
485
593
  }
486
594
 
595
+ private fun logEntryToWire(e: LogEntry): WritableMap {
596
+ val m = Arguments.createMap()
597
+ m.putString("id", e.id)
598
+ m.putDouble("timestampMs", e.timestamp.toEpochMilli().toDouble())
599
+ m.putString("level", e.level.wire)
600
+ m.putString("category", e.category)
601
+ m.putString("message", e.message)
602
+ return m
603
+ }
604
+
487
605
  private fun putNullableDouble(map: WritableMap, key: String, value: Double?) {
488
606
  if (value == null) map.putNull(key) else map.putDouble(key, value)
489
607
  }
@@ -514,11 +632,18 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
514
632
  else -> AuthBodyFormat.Form
515
633
  }
516
634
 
635
+ // Wire token (lowercase) -> native LogLevel; unknown/absent defaults to Info.
636
+ private fun toLogLevel(s: String?): LogLevel =
637
+ LogLevel.fromWire(s ?: "") ?: LogLevel.Info
638
+
639
+ // GATED on beekon native 0.0.9 (StopReason.CloudModeUnavailable) — the new arm
640
+ // does not exist on the pinned 0.0.8 dep, so this `when` is non-exhaustive there.
517
641
  private fun stopReasonToWire(r: StopReason): String = when (r) {
518
642
  StopReason.User -> "user"
519
643
  StopReason.PermissionDenied -> "permissionDenied"
520
644
  StopReason.LocationServicesDisabled -> "locationServicesDisabled"
521
645
  StopReason.LocationUnavailable -> "locationUnavailable"
646
+ StopReason.CloudModeUnavailable -> "cloudModeUnavailable"
522
647
  StopReason.System -> "system"
523
648
  }
524
649
 
@@ -580,6 +705,6 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
580
705
  // Reported to the native verifier via setWrapperInfo (diagnostics only — the
581
706
  // verifier consumes only the product). Keep in sync with package.json
582
707
  // "version" on release.
583
- private const val WRAPPER_VERSION = "0.0.7"
708
+ private const val WRAPPER_VERSION = "0.1.0"
584
709
  }
585
710
  }
package/ios/BeekonRn.mm CHANGED
@@ -35,6 +35,9 @@
35
35
  }
36
36
  onLicenseStatus:^(NSDictionary *_Nonnull ls) {
37
37
  [weakSelf emitOnLicenseStatus:ls];
38
+ }
39
+ onLog:^(NSDictionary *_Nonnull e) {
40
+ [weakSelf emitOnLog:e];
38
41
  }];
39
42
  }
40
43
  return self;
@@ -147,6 +150,43 @@
147
150
  [_impl licenseStatusWithResolver:resolve rejecter:reject];
148
151
  }
149
152
 
153
+ - (void)getPermissionStatus:(RCTPromiseResolveBlock)resolve
154
+ reject:(RCTPromiseRejectBlock)reject {
155
+ [_impl getPermissionStatusWithResolver:resolve rejecter:reject];
156
+ }
157
+
158
+ // MARK: - Diagnostic logs
159
+
160
+ - (void)getLog:(double)fromMs
161
+ toMs:(double)toMs
162
+ resolve:(RCTPromiseResolveBlock)resolve
163
+ reject:(RCTPromiseRejectBlock)reject {
164
+ [_impl getLogFromMs:fromMs toMs:toMs resolver:resolve rejecter:reject];
165
+ }
166
+
167
+ - (void)exportLog:(RCTPromiseResolveBlock)resolve
168
+ reject:(RCTPromiseRejectBlock)reject {
169
+ [_impl exportLogWithResolver:resolve rejecter:reject];
170
+ }
171
+
172
+ - (void)clearLog:(RCTPromiseResolveBlock)resolve
173
+ reject:(RCTPromiseRejectBlock)reject {
174
+ [_impl clearLogWithResolver:resolve rejecter:reject];
175
+ }
176
+
177
+ - (void)setLogLevel:(NSString *)level
178
+ resolve:(RCTPromiseResolveBlock)resolve
179
+ reject:(RCTPromiseRejectBlock)reject {
180
+ [_impl setLogLevel:level resolver:resolve rejecter:reject];
181
+ }
182
+
183
+ - (void)log:(NSString *)level
184
+ message:(NSString *)message
185
+ resolve:(RCTPromiseResolveBlock)resolve
186
+ reject:(RCTPromiseRejectBlock)reject {
187
+ [_impl logWithLevel:level message:message resolver:resolve rejecter:reject];
188
+ }
189
+
150
190
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
151
191
  (const facebook::react::ObjCTurboModule::InitParams &)params {
152
192
  return std::make_shared<facebook::react::NativeBeekonRnSpecJSI>(params);
@@ -24,6 +24,7 @@ import BeekonKit
24
24
  private let onSyncStatusCb: (NSDictionary) -> Void
25
25
  private let onAuthTokensCb: (NSDictionary) -> Void
26
26
  private let onLicenseStatusCb: (NSDictionary) -> Void
27
+ private let onLogCb: (NSDictionary) -> Void
27
28
 
28
29
  private var stateTask: Task<Void, Never>?
29
30
  private var locationsTask: Task<Void, Never>?
@@ -31,11 +32,12 @@ import BeekonKit
31
32
  private var syncStatusTask: Task<Void, Never>?
32
33
  private var authTokensTask: Task<Void, Never>?
33
34
  private var licenseStatusTask: Task<Void, Never>?
35
+ private var logsTask: Task<Void, Never>?
34
36
 
35
37
  // Reported to the native verifier via setWrapperInfo (diagnostics only — the
36
38
  // verifier consumes only the product). Keep in sync with package.json
37
39
  // "version" on release.
38
- private static let wrapperVersion = "0.0.7"
40
+ private static let wrapperVersion = "0.1.0"
39
41
 
40
42
  @objc public init(
41
43
  onState: @escaping (NSDictionary) -> Void,
@@ -43,7 +45,8 @@ import BeekonKit
43
45
  onGeofenceEvent: @escaping (NSDictionary) -> Void,
44
46
  onSyncStatus: @escaping (NSDictionary) -> Void,
45
47
  onAuthTokens: @escaping (NSDictionary) -> Void,
46
- onLicenseStatus: @escaping (NSDictionary) -> Void
48
+ onLicenseStatus: @escaping (NSDictionary) -> Void,
49
+ onLog: @escaping (NSDictionary) -> Void
47
50
  ) {
48
51
  // Identify this wrapper to the native verifier (license-format-v1 §11) before
49
52
  // any configure() can run — a direct native call (nonisolated static),
@@ -55,6 +58,7 @@ import BeekonKit
55
58
  self.onSyncStatusCb = onSyncStatus
56
59
  self.onAuthTokensCb = onAuthTokens
57
60
  self.onLicenseStatusCb = onLicenseStatus
61
+ self.onLogCb = onLog
58
62
  super.init()
59
63
  self.stateTask = Task { [weak self] in
60
64
  guard let self = self else { return }
@@ -92,6 +96,12 @@ import BeekonKit
92
96
  self.onLicenseStatusCb(self.licenseStatusToWire(status))
93
97
  }
94
98
  }
99
+ self.logsTask = Task { [weak self] in
100
+ guard let self = self else { return }
101
+ for await entry in await Beekon.shared.logs {
102
+ self.onLogCb(self.logEntryToWire(entry))
103
+ }
104
+ }
95
105
  }
96
106
 
97
107
  @objc public func invalidate() {
@@ -101,12 +111,14 @@ import BeekonKit
101
111
  syncStatusTask?.cancel()
102
112
  authTokensTask?.cancel()
103
113
  licenseStatusTask?.cancel()
114
+ logsTask?.cancel()
104
115
  stateTask = nil
105
116
  locationsTask = nil
106
117
  geofenceEventsTask = nil
107
118
  syncStatusTask = nil
108
119
  authTokensTask = nil
109
120
  licenseStatusTask = nil
121
+ logsTask = nil
110
122
  }
111
123
 
112
124
  /// Register Beekon's background-refresh task and install cold-launch hooks.
@@ -123,11 +135,22 @@ import BeekonKit
123
135
  @objc public func configure(
124
136
  _ config: NSDictionary,
125
137
  resolver resolve: @escaping @Sendable (Any?) -> Void,
126
- rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
138
+ rejecter reject: @escaping @Sendable (String?, String?, Error?) -> Void
127
139
  ) {
140
+ // GATED on beekon native 0.0.9 (sealed BeekonConfig) — does not compile
141
+ // against the pinned 0.0.8 dep. `wireToConfig` now throws: the cloud arm
142
+ // (`try BeekonConfig.cloud(...)`) raises BeekonError.invalidConfiguration on
143
+ // an empty/invalid projectKey, which we surface via the existing reject path.
144
+ //
128
145
  // Convert the non-Sendable NSDictionary to a Sendable BeekonConfig before
129
146
  // crossing into the Task closure.
130
- let cfg = wireToConfig(config)
147
+ let cfg: BeekonConfig
148
+ do {
149
+ cfg = try wireToConfig(config)
150
+ } catch {
151
+ reject(errorCode(error), error.localizedDescription, error)
152
+ return
153
+ }
131
154
  Task {
132
155
  await Beekon.shared.configure(cfg)
133
156
  resolve(nil)
@@ -322,9 +345,104 @@ import BeekonKit
322
345
  }
323
346
  }
324
347
 
348
+ @objc public func getPermissionStatusWithResolver(
349
+ _ resolve: @escaping @Sendable (Any?) -> Void,
350
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
351
+ ) {
352
+ // `getPermissionStatus()` is `nonisolated` + synchronous — no Task/await.
353
+ resolve(permissionStatusToWire(Beekon.shared.getPermissionStatus()))
354
+ }
355
+
356
+ // MARK: - Diagnostic logs (spec diagnostics/log-format-v1)
357
+
358
+ @objc public func getLogFromMs(
359
+ _ fromMs: Double,
360
+ toMs: Double,
361
+ resolver resolve: @escaping @Sendable (Any?) -> Void,
362
+ rejecter reject: @escaping @Sendable (String?, String?, Error?) -> Void
363
+ ) {
364
+ Task { [weak self] in
365
+ guard let self = self else { return }
366
+ do {
367
+ let from = Date(timeIntervalSince1970: fromMs / 1000.0)
368
+ let to = Date(timeIntervalSince1970: toMs / 1000.0)
369
+ let entries = try await Beekon.shared.getLog(from: from, to: to)
370
+ resolve(entries.map { self.logEntryToWire($0) })
371
+ } catch {
372
+ reject(self.errorCode(error), error.localizedDescription, error)
373
+ }
374
+ }
375
+ }
376
+
377
+ @objc public func exportLogWithResolver(
378
+ _ resolve: @escaping @Sendable (Any?) -> Void,
379
+ rejecter reject: @escaping @Sendable (String?, String?, Error?) -> Void
380
+ ) {
381
+ Task { [weak self] in
382
+ guard let self = self else { return }
383
+ do {
384
+ resolve(try await Beekon.shared.exportLog())
385
+ } catch {
386
+ reject(self.errorCode(error), error.localizedDescription, error)
387
+ }
388
+ }
389
+ }
390
+
391
+ @objc public func clearLogWithResolver(
392
+ _ resolve: @escaping @Sendable (Any?) -> Void,
393
+ rejecter reject: @escaping @Sendable (String?, String?, Error?) -> Void
394
+ ) {
395
+ Task { [weak self] in
396
+ guard let self = self else { return }
397
+ do {
398
+ try await Beekon.shared.clearLog()
399
+ resolve(nil)
400
+ } catch {
401
+ reject(self.errorCode(error), error.localizedDescription, error)
402
+ }
403
+ }
404
+ }
405
+
406
+ @objc public func setLogLevel(
407
+ _ level: String,
408
+ resolver resolve: @escaping @Sendable (Any?) -> Void,
409
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
410
+ ) {
411
+ // setLogLevel is nonisolated on the actor — callable synchronously.
412
+ Beekon.shared.setLogLevel(logLevelFromWire(level))
413
+ resolve(nil)
414
+ }
415
+
416
+ @objc public func logWithLevel(
417
+ _ level: String,
418
+ message: String,
419
+ resolver resolve: @escaping @Sendable (Any?) -> Void,
420
+ rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
421
+ ) {
422
+ Beekon.shared.log(logLevelFromWire(level), message)
423
+ resolve(nil)
424
+ }
425
+
325
426
  // MARK: - Mappers: wire (NSDictionary/NSArray) → Beekon
326
427
 
327
- private func wireToConfig(_ d: NSDictionary) -> BeekonConfig {
428
+ // GATED on beekon native 0.0.9 (sealed BeekonConfig) — does not compile against
429
+ // the pinned 0.0.8 dep (the flat BeekonConfig(...) init). Switches on the wire
430
+ // `mode` (cloud-mode-v1 §2) to build the matching sealed arm. The cloud factory
431
+ // is throwing (`try BeekonConfig.cloud(...)`): an empty/invalid projectKey
432
+ // raises BeekonError.invalidConfiguration, propagated to configure()'s reject.
433
+ // iOS has NO `notification`, so the cloud arm omits it.
434
+ private func wireToConfig(_ d: NSDictionary) throws -> BeekonConfig {
435
+ if d["mode"] as? String == "cloud" {
436
+ let projectKey = (d["projectKey"] as? String) ?? ""
437
+ // Omitted endpoint → the baked-in Beekon Cloud default.
438
+ let endpoint = (d["endpoint"] as? String) ?? "https://api.getbeekon.com"
439
+ return try BeekonConfig.cloud(
440
+ projectKey: projectKey,
441
+ endpoint: endpoint,
442
+ logLevel: logLevelFromWire(d["logLevel"] as? String)
443
+ )
444
+ }
445
+
328
446
  let minTime =
329
447
  (d["minTimeBetweenLocationsSeconds"] as? NSNumber)?.doubleValue ?? 30
330
448
  let minDist =
@@ -343,6 +461,7 @@ import BeekonKit
343
461
  headers: entriesToDict((s["headers"] as? NSArray) ?? []),
344
462
  intervalSeconds: (s["intervalSeconds"] as? NSNumber)?.doubleValue ?? 300,
345
463
  batchSize: (s["batchSize"] as? NSNumber)?.intValue ?? 100,
464
+ syncThreshold: (s["syncThreshold"] as? NSNumber)?.intValue ?? 0,
346
465
  auth: auth
347
466
  )
348
467
  } else {
@@ -352,8 +471,9 @@ import BeekonKit
352
471
  }
353
472
  }
354
473
 
355
- // `notification` is Android-only — iOS ignores it.
356
- return BeekonConfig(
474
+ // `notification` is Android-only — iOS ignores it (the selfManaged factory
475
+ // takes no notification parameter on iOS).
476
+ return BeekonConfig.selfManaged(
357
477
  minTimeBetweenLocationsSeconds: minTime,
358
478
  minDistanceBetweenLocationsMeters: minDist,
359
479
  accuracyMode: accuracyModeFromWire(d["accuracyMode"] as? String),
@@ -363,7 +483,8 @@ import BeekonKit
363
483
  sync: sync,
364
484
  // Passed through verbatim; nil/blank means unset — the SDK falls through to
365
485
  // the Info.plist value, then evaluation (license-format-v1 §9).
366
- licenseKey: d["licenseKey"] as? String
486
+ licenseKey: d["licenseKey"] as? String,
487
+ logLevel: logLevelFromWire(d["logLevel"] as? String)
367
488
  )
368
489
  }
369
490
 
@@ -471,6 +592,44 @@ import BeekonKit
471
592
  ]
472
593
  }
473
594
 
595
+ private func logEntryToWire(_ e: LogEntry) -> NSDictionary {
596
+ return [
597
+ "id": e.id,
598
+ "timestampMs": e.timestamp.timeIntervalSince1970 * 1000.0,
599
+ "level": e.level.rawValue,
600
+ "category": e.category,
601
+ "message": e.message,
602
+ ]
603
+ }
604
+
605
+ private func permissionStatusToWire(_ p: PermissionStatus) -> NSDictionary {
606
+ return [
607
+ "level": permissionLevelToWire(p.level),
608
+ // "" is the wire encoding of null (not granted).
609
+ "accuracy": p.accuracy.map { permissionAccuracyToWire($0) } ?? "",
610
+ ]
611
+ }
612
+
613
+ private func permissionLevelToWire(_ l: PermissionStatus.Level) -> String {
614
+ switch l {
615
+ case .notDetermined: return "notDetermined"
616
+ case .denied: return "denied"
617
+ case .restricted: return "restricted"
618
+ case .foreground: return "foreground"
619
+ case .background: return "background"
620
+ // BeekonKit enums are non-frozen (library-evolution binary); handle unknowns.
621
+ @unknown default: return "notDetermined"
622
+ }
623
+ }
624
+
625
+ private func permissionAccuracyToWire(_ a: PermissionStatus.Accuracy) -> String {
626
+ switch a {
627
+ case .full: return "full"
628
+ case .reduced: return "reduced"
629
+ @unknown default: return "full"
630
+ }
631
+ }
632
+
474
633
  private func stateToWire(_ s: BeekonState) -> NSDictionary {
475
634
  switch s {
476
635
  case .idle:
@@ -590,12 +749,21 @@ import BeekonKit
590
749
  }
591
750
  }
592
751
 
752
+ // Wire token (LogLevel.rawValue, lowercase) -> LogLevel; unknown defaults to .info.
753
+ private func logLevelFromWire(_ s: String?) -> LogLevel {
754
+ LogLevel(rawValue: s ?? "") ?? .info
755
+ }
756
+
757
+ // GATED on beekon native 0.0.9 (StopReason.cloudModeUnavailable) — the new case
758
+ // does not exist on the pinned 0.0.8 dep. (Non-frozen enum, so the existing
759
+ // `@unknown default` already keeps this compiling either way.)
593
760
  private func stopReasonToWire(_ r: StopReason) -> String {
594
761
  switch r {
595
762
  case .user: return "user"
596
763
  case .permissionDenied: return "permissionDenied"
597
764
  case .locationServicesDisabled: return "locationServicesDisabled"
598
765
  case .locationUnavailable: return "locationUnavailable"
766
+ case .cloudModeUnavailable: return "cloudModeUnavailable"
599
767
  case .system: return "system"
600
768
  @unknown default: return "system"
601
769
  }
@@ -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>
@@ -1,9 +1,9 @@
1
1
  Beekon SDK
2
- Copyright (c) 2026 wayqteam. All rights reserved.
2
+ Copyright (c) 2026 beekonlabs. All rights reserved.
3
3
 
4
4
  This software is licensed for evaluation use only. No production deployment,
5
5
  redistribution, sublicensing, or commercial use is permitted without a separate
6
- written agreement with wayqteam.
6
+ written agreement with beekonlabs.
7
7
 
8
8
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9
9
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
@@ -11,4 +11,4 @@ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
11
11
  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING
12
12
  FROM USE OF THE SOFTWARE.
13
13
 
14
- For licensing inquiries: contact wayqteam.
14
+ For licensing inquiries: contact beekonlabs.