@wayq/beekon-rn 0.0.5 → 0.0.7
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/BeekonRn.podspec +4 -2
- package/CHANGELOG.md +127 -0
- package/README.md +303 -81
- package/android/build.gradle +3 -2
- package/android/src/main/java/in/wayq/beekonrn/BeekonRnModule.kt +164 -7
- package/ios/BeekonRn.mm +23 -0
- package/ios/BeekonRn.swift +199 -10
- package/ios/Frameworks/BeekonKit.xcframework/Info.plist +5 -5
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/BeekonKit +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Info.plist +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.abi.json +7784 -2697
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios.swiftinterface +111 -3
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/BeekonKit +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Info.plist +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.abi.json +7784 -2697
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +111 -3
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +7784 -2697
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/Modules/BeekonKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +111 -3
- package/ios/Frameworks/BeekonKit.xcframework/ios-arm64_x86_64-simulator/BeekonKit.framework/_CodeSignature/CodeResources +1 -1
- package/lib/module/NativeBeekonRn.js +20 -0
- package/lib/module/NativeBeekonRn.js.map +1 -1
- package/lib/module/beekon.js +90 -7
- package/lib/module/beekon.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal/mappers.js +98 -4
- package/lib/module/internal/mappers.js.map +1 -1
- package/lib/module/types/auth.js +4 -0
- package/lib/module/types/auth.js.map +1 -0
- package/lib/module/types/error.js +13 -3
- package/lib/module/types/error.js.map +1 -1
- package/lib/module/types/license.js +2 -0
- package/lib/module/types/license.js.map +1 -0
- package/lib/typescript/src/NativeBeekonRn.d.ts +84 -0
- package/lib/typescript/src/NativeBeekonRn.d.ts.map +1 -1
- package/lib/typescript/src/beekon.d.ts +45 -1
- package/lib/typescript/src/beekon.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/internal/mappers.d.ts +12 -1
- package/lib/typescript/src/internal/mappers.d.ts.map +1 -1
- package/lib/typescript/src/types/auth.d.ts +99 -0
- package/lib/typescript/src/types/auth.d.ts.map +1 -0
- package/lib/typescript/src/types/config.d.ts +29 -0
- package/lib/typescript/src/types/config.d.ts.map +1 -1
- package/lib/typescript/src/types/enums.d.ts +14 -0
- package/lib/typescript/src/types/enums.d.ts.map +1 -1
- package/lib/typescript/src/types/error.d.ts +14 -4
- package/lib/typescript/src/types/error.d.ts.map +1 -1
- package/lib/typescript/src/types/license.d.ts +50 -0
- package/lib/typescript/src/types/license.d.ts.map +1 -0
- package/package.json +10 -2
- package/scripts/fetch-beekonkit.sh +4 -4
- package/src/NativeBeekonRn.ts +93 -0
- package/src/beekon.ts +104 -6
- package/src/index.tsx +4 -0
- package/src/internal/mappers.ts +109 -1
- package/src/types/auth.ts +101 -0
- package/src/types/config.ts +29 -0
- package/src/types/enums.ts +16 -0
- package/src/types/error.ts +19 -4
- package/src/types/license.ts +47 -0
|
@@ -9,15 +9,22 @@ 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
|
|
15
20
|
import `in`.wayq.beekon.BeekonGeofence
|
|
16
21
|
import `in`.wayq.beekon.BeekonState
|
|
17
22
|
import `in`.wayq.beekon.GeofenceEvent
|
|
23
|
+
import `in`.wayq.beekon.LicenseStatus
|
|
18
24
|
import `in`.wayq.beekon.Location
|
|
19
25
|
import `in`.wayq.beekon.LocationQuality
|
|
20
26
|
import `in`.wayq.beekon.LocationTrigger
|
|
27
|
+
import `in`.wayq.beekon.LocationUnavailableReason
|
|
21
28
|
import `in`.wayq.beekon.MotionState
|
|
22
29
|
import `in`.wayq.beekon.NotificationConfig
|
|
23
30
|
import `in`.wayq.beekon.StationaryMode
|
|
@@ -43,6 +50,11 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
|
|
|
43
50
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
|
44
51
|
|
|
45
52
|
init {
|
|
53
|
+
// Identify this wrapper to the native verifier (license-format-v1 §11) before
|
|
54
|
+
// any configure() can run — a direct native call, deliberately not bridged to
|
|
55
|
+
// JS. The module is constructed before its first method dispatch, so this is
|
|
56
|
+
// always set first.
|
|
57
|
+
Beekon.setWrapperInfo("rn", WRAPPER_VERSION)
|
|
46
58
|
// No Beekon.initialize() — the SDK auto-initializes via AndroidX Startup.
|
|
47
59
|
scope.launch { Beekon.state.collect { emitOnState(stateToWire(it)) } }
|
|
48
60
|
scope.launch { Beekon.locations.collect { emitOnLocation(locationToWire(it)) } }
|
|
@@ -50,6 +62,8 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
|
|
|
50
62
|
Beekon.geofenceEvents.collect { emitOnGeofenceEvent(geofenceEventToWire(it)) }
|
|
51
63
|
}
|
|
52
64
|
scope.launch { Beekon.syncStatus.collect { emitOnSyncStatus(syncStatusToWire(it)) } }
|
|
65
|
+
scope.launch { Beekon.authChanges.collect { emitOnAuthTokens(tokenRefreshToWire(it)) } }
|
|
66
|
+
scope.launch { Beekon.licenseStatus.collect { emitOnLicenseStatus(licenseStatusToWire(it)) } }
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
// ---------------------------------------------------------------------------
|
|
@@ -87,12 +101,13 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
|
|
|
87
101
|
}
|
|
88
102
|
}
|
|
89
103
|
|
|
90
|
-
//
|
|
91
|
-
//
|
|
104
|
+
// Calls the guarded native resume (Beekon.resumeIfNeeded), which only
|
|
105
|
+
// re-adopts a previously-active, non-user-stopped session — a no-op when
|
|
106
|
+
// already Tracking or when the user explicitly stopped (mirrors iOS).
|
|
92
107
|
override fun resumeIfNeeded(promise: Promise) {
|
|
93
108
|
scope.launch {
|
|
94
109
|
try {
|
|
95
|
-
Beekon.
|
|
110
|
+
Beekon.resumeIfNeeded()
|
|
96
111
|
promise.resolve(null)
|
|
97
112
|
} catch (t: Throwable) {
|
|
98
113
|
promise.reject(errorCode(t), t.message ?: "resumeIfNeeded failed", t)
|
|
@@ -100,6 +115,21 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
|
|
|
100
115
|
}
|
|
101
116
|
}
|
|
102
117
|
|
|
118
|
+
override fun getCurrentLocation(timeoutMs: Double, accuracy: String, promise: Promise) {
|
|
119
|
+
scope.launch {
|
|
120
|
+
try {
|
|
121
|
+
// Empty accuracy is the wire encoding of "use the configured mode".
|
|
122
|
+
val mode = if (accuracy.isEmpty()) null else toAccuracyMode(accuracy)
|
|
123
|
+
val loc = Beekon.getCurrentLocation(timeoutMs.toLong(), mode)
|
|
124
|
+
val arr: WritableArray = Arguments.createArray()
|
|
125
|
+
if (loc != null) arr.pushMap(locationToWire(loc))
|
|
126
|
+
promise.resolve(arr)
|
|
127
|
+
} catch (t: Throwable) {
|
|
128
|
+
promise.reject(errorCode(t), t.message ?: "getCurrentLocation failed", t)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
103
133
|
// ---------------------------------------------------------------------------
|
|
104
134
|
// History
|
|
105
135
|
// ---------------------------------------------------------------------------
|
|
@@ -193,6 +223,15 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
|
|
|
193
223
|
}
|
|
194
224
|
}
|
|
195
225
|
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// License (observational only — never blocks; license-format-v1 §8)
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
override fun licenseStatus(promise: Promise) {
|
|
231
|
+
// StateFlow.value is the current, synchronously-readable status.
|
|
232
|
+
promise.resolve(licenseStatusToWire(Beekon.licenseStatus.value))
|
|
233
|
+
}
|
|
234
|
+
|
|
196
235
|
override fun invalidate() {
|
|
197
236
|
super.invalidate()
|
|
198
237
|
scope.cancel()
|
|
@@ -226,27 +265,86 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
|
|
|
226
265
|
detectActivity = map.getBoolean("detectActivity"),
|
|
227
266
|
sync = sync,
|
|
228
267
|
notification = notification ?: NotificationConfig(),
|
|
268
|
+
// Passed through verbatim; absent/null means unset — the SDK falls through
|
|
269
|
+
// to the manifest meta-data, then evaluation (license-format-v1 §9).
|
|
270
|
+
licenseKey = optString(map, "licenseKey"),
|
|
229
271
|
)
|
|
230
272
|
}
|
|
231
273
|
|
|
232
|
-
private fun wireToSyncConfig(map: ReadableMap): SyncConfig
|
|
233
|
-
|
|
274
|
+
private fun wireToSyncConfig(map: ReadableMap): SyncConfig {
|
|
275
|
+
val auth =
|
|
276
|
+
if (map.hasKey("auth") && !map.isNull("auth")) {
|
|
277
|
+
map.getMap("auth")?.let { wireToAuthConfig(it) }
|
|
278
|
+
} else {
|
|
279
|
+
null
|
|
280
|
+
}
|
|
281
|
+
return SyncConfig(
|
|
234
282
|
url = map.getString("url") ?: "",
|
|
235
283
|
headers = entriesToMap(map.getArray("headers")),
|
|
236
284
|
intervalSeconds = map.getDouble("intervalSeconds").toLong(),
|
|
237
285
|
batchSize = map.getDouble("batchSize").toInt(),
|
|
286
|
+
auth = auth,
|
|
238
287
|
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private fun wireToAuthConfig(map: ReadableMap): AuthConfig {
|
|
291
|
+
val expiresAt =
|
|
292
|
+
if (map.hasKey("expiresAtMs") && !map.isNull("expiresAtMs")) {
|
|
293
|
+
// Wire carries epoch millis; the native recipe wants epoch seconds.
|
|
294
|
+
(map.getDouble("expiresAtMs") / 1000.0).toLong()
|
|
295
|
+
} else {
|
|
296
|
+
null
|
|
297
|
+
}
|
|
298
|
+
val seedEpoch =
|
|
299
|
+
if (map.hasKey("seedEpoch") && !map.isNull("seedEpoch")) {
|
|
300
|
+
map.getDouble("seedEpoch").toLong()
|
|
301
|
+
} else {
|
|
302
|
+
null
|
|
303
|
+
}
|
|
304
|
+
val responseMapping =
|
|
305
|
+
if (map.hasKey("responseMapping") && !map.isNull("responseMapping")) {
|
|
306
|
+
map.getMap("responseMapping")?.let { wireToResponseMapping(it) } ?: AuthResponseMapping()
|
|
307
|
+
} else {
|
|
308
|
+
AuthResponseMapping()
|
|
309
|
+
}
|
|
310
|
+
return AuthConfig(
|
|
311
|
+
accessToken = optString(map, "accessToken"),
|
|
312
|
+
refreshToken = optString(map, "refreshToken"),
|
|
313
|
+
expiresAt = expiresAt,
|
|
314
|
+
strategy = toAuthStrategy(map.getString("strategy")),
|
|
315
|
+
refreshUrl = optString(map, "refreshUrl"),
|
|
316
|
+
refreshPayload = entriesToMap(map.getArray("refreshPayload")),
|
|
317
|
+
refreshHeaders = entriesToMap(map.getArray("refreshHeaders")),
|
|
318
|
+
refreshBodyFormat = toRefreshBodyFormat(map.getString("refreshBodyFormat")),
|
|
319
|
+
responseMapping = responseMapping,
|
|
320
|
+
skewMarginSeconds = map.getDouble("skewMarginSeconds").toLong(),
|
|
321
|
+
seedEpoch = seedEpoch,
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private fun wireToResponseMapping(map: ReadableMap): AuthResponseMapping =
|
|
326
|
+
AuthResponseMapping(
|
|
327
|
+
accessToken = optString(map, "accessToken"),
|
|
328
|
+
refreshToken = optString(map, "refreshToken"),
|
|
329
|
+
expiresIn = optString(map, "expiresIn"),
|
|
330
|
+
expiresAt = optString(map, "expiresAt"),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
private fun optString(map: ReadableMap, key: String): String? =
|
|
334
|
+
if (map.hasKey(key) && !map.isNull(key)) map.getString(key) else null
|
|
239
335
|
|
|
240
336
|
private fun wireToNotification(map: ReadableMap): NotificationConfig {
|
|
241
337
|
val title =
|
|
242
338
|
if (map.hasKey("title") && !map.isNull("title")) map.getString("title") else null
|
|
243
339
|
val text =
|
|
244
340
|
if (map.hasKey("text") && !map.isNull("text")) map.getString("text") else null
|
|
341
|
+
val smallIcon =
|
|
342
|
+
if (map.hasKey("smallIcon") && !map.isNull("smallIcon")) map.getString("smallIcon") else null
|
|
245
343
|
// Use the data-class default for `title` when the wire value is absent.
|
|
246
344
|
return if (title != null) {
|
|
247
|
-
NotificationConfig(title = title, text = text)
|
|
345
|
+
NotificationConfig(title = title, text = text, smallIcon = smallIcon)
|
|
248
346
|
} else {
|
|
249
|
-
NotificationConfig(text = text)
|
|
347
|
+
NotificationConfig(text = text, smallIcon = smallIcon)
|
|
250
348
|
}
|
|
251
349
|
}
|
|
252
350
|
|
|
@@ -347,6 +445,45 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
|
|
|
347
445
|
return m
|
|
348
446
|
}
|
|
349
447
|
|
|
448
|
+
private fun tokenRefreshToWire(t: AuthTokens): WritableMap {
|
|
449
|
+
val m = Arguments.createMap()
|
|
450
|
+
m.putString("accessToken", t.accessToken)
|
|
451
|
+
val refreshToken = t.refreshToken
|
|
452
|
+
if (refreshToken != null) m.putString("refreshToken", refreshToken) else m.putNull("refreshToken")
|
|
453
|
+
val expiresAt = t.expiresAt
|
|
454
|
+
// Native epoch seconds → wire epoch millis.
|
|
455
|
+
if (expiresAt != null) m.putDouble("expiresAtMs", (expiresAt * 1000L).toDouble()) else m.putNull("expiresAtMs")
|
|
456
|
+
m.putDouble("epoch", t.epoch.toDouble())
|
|
457
|
+
return m
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Reads the SDK's wire-stable names (no validation here). `tier`/`entitlements`
|
|
461
|
+
// populate only for Licensed; `reason` only for Invalid.
|
|
462
|
+
private fun licenseStatusToWire(s: LicenseStatus): WritableMap {
|
|
463
|
+
val m = Arguments.createMap()
|
|
464
|
+
m.putString("status", s.wireName)
|
|
465
|
+
when (s) {
|
|
466
|
+
is LicenseStatus.Licensed -> {
|
|
467
|
+
m.putString("tier", s.tier)
|
|
468
|
+
val arr: WritableArray = Arguments.createArray()
|
|
469
|
+
for (e in s.entitlements) arr.pushString(e)
|
|
470
|
+
m.putArray("entitlements", arr)
|
|
471
|
+
m.putNull("reason")
|
|
472
|
+
}
|
|
473
|
+
is LicenseStatus.Invalid -> {
|
|
474
|
+
m.putNull("tier")
|
|
475
|
+
m.putArray("entitlements", Arguments.createArray())
|
|
476
|
+
m.putString("reason", s.reason.wireName)
|
|
477
|
+
}
|
|
478
|
+
else -> {
|
|
479
|
+
m.putNull("tier")
|
|
480
|
+
m.putArray("entitlements", Arguments.createArray())
|
|
481
|
+
m.putNull("reason")
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return m
|
|
485
|
+
}
|
|
486
|
+
|
|
350
487
|
private fun putNullableDouble(map: WritableMap, key: String, value: Double?) {
|
|
351
488
|
if (value == null) map.putNull(key) else map.putDouble(key, value)
|
|
352
489
|
}
|
|
@@ -367,6 +504,16 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
|
|
|
367
504
|
else -> StationaryMode.Pause
|
|
368
505
|
}
|
|
369
506
|
|
|
507
|
+
private fun toAuthStrategy(s: String?): AuthStrategy = when (s) {
|
|
508
|
+
"raw" -> AuthStrategy.Raw
|
|
509
|
+
else -> AuthStrategy.Bearer
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private fun toRefreshBodyFormat(s: String?): AuthBodyFormat = when (s) {
|
|
513
|
+
"json" -> AuthBodyFormat.Json
|
|
514
|
+
else -> AuthBodyFormat.Form
|
|
515
|
+
}
|
|
516
|
+
|
|
370
517
|
private fun stopReasonToWire(r: StopReason): String = when (r) {
|
|
371
518
|
StopReason.User -> "user"
|
|
372
519
|
StopReason.PermissionDenied -> "permissionDenied"
|
|
@@ -419,10 +566,20 @@ class BeekonRnModule(reactContext: ReactApplicationContext) :
|
|
|
419
566
|
private fun errorCode(t: Throwable): String = when (t) {
|
|
420
567
|
is BeekonException.StorageException -> "STORAGE_FAILURE"
|
|
421
568
|
is BeekonException.InvalidGeofence -> "INVALID_GEOFENCE"
|
|
569
|
+
is BeekonException.LocationUnavailable -> when (t.reason) {
|
|
570
|
+
LocationUnavailableReason.PermissionDenied -> "PERMISSION_DENIED"
|
|
571
|
+
LocationUnavailableReason.LocationServicesDisabled -> "LOCATION_SERVICES_DISABLED"
|
|
572
|
+
LocationUnavailableReason.Unavailable -> "LOCATION_UNAVAILABLE"
|
|
573
|
+
}
|
|
422
574
|
else -> "INTERNAL_ERROR"
|
|
423
575
|
}
|
|
424
576
|
|
|
425
577
|
companion object {
|
|
426
578
|
const val NAME = NativeBeekonRnSpec.NAME
|
|
579
|
+
|
|
580
|
+
// Reported to the native verifier via setWrapperInfo (diagnostics only — the
|
|
581
|
+
// verifier consumes only the product). Keep in sync with package.json
|
|
582
|
+
// "version" on release.
|
|
583
|
+
private const val WRAPPER_VERSION = "0.0.7"
|
|
427
584
|
}
|
|
428
585
|
}
|
package/ios/BeekonRn.mm
CHANGED
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
}
|
|
30
30
|
onSyncStatus:^(NSDictionary *_Nonnull st) {
|
|
31
31
|
[weakSelf emitOnSyncStatus:st];
|
|
32
|
+
}
|
|
33
|
+
onAuthTokens:^(NSDictionary *_Nonnull t) {
|
|
34
|
+
[weakSelf emitOnAuthTokens:t];
|
|
35
|
+
}
|
|
36
|
+
onLicenseStatus:^(NSDictionary *_Nonnull ls) {
|
|
37
|
+
[weakSelf emitOnLicenseStatus:ls];
|
|
32
38
|
}];
|
|
33
39
|
}
|
|
34
40
|
return self;
|
|
@@ -72,6 +78,16 @@
|
|
|
72
78
|
[_impl resumeIfNeededWithResolver:resolve rejecter:reject];
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
- (void)getCurrentLocation:(double)timeoutMs
|
|
82
|
+
accuracy:(NSString *)accuracy
|
|
83
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
84
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
85
|
+
[_impl getCurrentLocationTimeoutMs:timeoutMs
|
|
86
|
+
accuracy:accuracy
|
|
87
|
+
resolver:resolve
|
|
88
|
+
rejecter:reject];
|
|
89
|
+
}
|
|
90
|
+
|
|
75
91
|
// MARK: - History
|
|
76
92
|
|
|
77
93
|
- (void)getLocations:(double)fromMs
|
|
@@ -124,6 +140,13 @@
|
|
|
124
140
|
[_impl listGeofencesWithResolver:resolve rejecter:reject];
|
|
125
141
|
}
|
|
126
142
|
|
|
143
|
+
// MARK: - License
|
|
144
|
+
|
|
145
|
+
- (void)licenseStatus:(RCTPromiseResolveBlock)resolve
|
|
146
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
147
|
+
[_impl licenseStatusWithResolver:resolve rejecter:reject];
|
|
148
|
+
}
|
|
149
|
+
|
|
127
150
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
128
151
|
(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
129
152
|
return std::make_shared<facebook::react::NativeBeekonRnSpecJSI>(params);
|
package/ios/BeekonRn.swift
CHANGED
|
@@ -22,22 +22,39 @@ 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
|
|
26
|
+
private let onLicenseStatusCb: (NSDictionary) -> Void
|
|
25
27
|
|
|
26
28
|
private var stateTask: Task<Void, Never>?
|
|
27
29
|
private var locationsTask: Task<Void, Never>?
|
|
28
30
|
private var geofenceEventsTask: Task<Void, Never>?
|
|
29
31
|
private var syncStatusTask: Task<Void, Never>?
|
|
32
|
+
private var authTokensTask: Task<Void, Never>?
|
|
33
|
+
private var licenseStatusTask: Task<Void, Never>?
|
|
34
|
+
|
|
35
|
+
// Reported to the native verifier via setWrapperInfo (diagnostics only — the
|
|
36
|
+
// verifier consumes only the product). Keep in sync with package.json
|
|
37
|
+
// "version" on release.
|
|
38
|
+
private static let wrapperVersion = "0.0.7"
|
|
30
39
|
|
|
31
40
|
@objc public init(
|
|
32
41
|
onState: @escaping (NSDictionary) -> Void,
|
|
33
42
|
onLocation: @escaping (NSDictionary) -> Void,
|
|
34
43
|
onGeofenceEvent: @escaping (NSDictionary) -> Void,
|
|
35
|
-
onSyncStatus: @escaping (NSDictionary) -> Void
|
|
44
|
+
onSyncStatus: @escaping (NSDictionary) -> Void,
|
|
45
|
+
onAuthTokens: @escaping (NSDictionary) -> Void,
|
|
46
|
+
onLicenseStatus: @escaping (NSDictionary) -> Void
|
|
36
47
|
) {
|
|
48
|
+
// Identify this wrapper to the native verifier (license-format-v1 §11) before
|
|
49
|
+
// any configure() can run — a direct native call (nonisolated static),
|
|
50
|
+
// deliberately not bridged to JS.
|
|
51
|
+
Beekon.setWrapperInfo(product: "rn", version: BeekonRnImpl.wrapperVersion)
|
|
37
52
|
self.onStateCb = onState
|
|
38
53
|
self.onLocationCb = onLocation
|
|
39
54
|
self.onGeofenceEventCb = onGeofenceEvent
|
|
40
55
|
self.onSyncStatusCb = onSyncStatus
|
|
56
|
+
self.onAuthTokensCb = onAuthTokens
|
|
57
|
+
self.onLicenseStatusCb = onLicenseStatus
|
|
41
58
|
super.init()
|
|
42
59
|
self.stateTask = Task { [weak self] in
|
|
43
60
|
guard let self = self else { return }
|
|
@@ -63,6 +80,18 @@ import BeekonKit
|
|
|
63
80
|
self.onSyncStatusCb(self.syncStatusToWire(status))
|
|
64
81
|
}
|
|
65
82
|
}
|
|
83
|
+
self.authTokensTask = Task { [weak self] in
|
|
84
|
+
guard let self = self else { return }
|
|
85
|
+
for await tokens in await Beekon.shared.authChanges {
|
|
86
|
+
self.onAuthTokensCb(self.authTokensToWire(tokens))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
self.licenseStatusTask = Task { [weak self] in
|
|
90
|
+
guard let self = self else { return }
|
|
91
|
+
for await status in await Beekon.shared.licenseStatusUpdates {
|
|
92
|
+
self.onLicenseStatusCb(self.licenseStatusToWire(status))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
66
95
|
}
|
|
67
96
|
|
|
68
97
|
@objc public func invalidate() {
|
|
@@ -70,10 +99,14 @@ import BeekonKit
|
|
|
70
99
|
locationsTask?.cancel()
|
|
71
100
|
geofenceEventsTask?.cancel()
|
|
72
101
|
syncStatusTask?.cancel()
|
|
102
|
+
authTokensTask?.cancel()
|
|
103
|
+
licenseStatusTask?.cancel()
|
|
73
104
|
stateTask = nil
|
|
74
105
|
locationsTask = nil
|
|
75
106
|
geofenceEventsTask = nil
|
|
76
107
|
syncStatusTask = nil
|
|
108
|
+
authTokensTask = nil
|
|
109
|
+
licenseStatusTask = nil
|
|
77
110
|
}
|
|
78
111
|
|
|
79
112
|
/// Register Beekon's background-refresh task and install cold-launch hooks.
|
|
@@ -132,6 +165,29 @@ import BeekonKit
|
|
|
132
165
|
}
|
|
133
166
|
}
|
|
134
167
|
|
|
168
|
+
@objc public func getCurrentLocationTimeoutMs(
|
|
169
|
+
_ timeoutMs: Double,
|
|
170
|
+
accuracy: String,
|
|
171
|
+
resolver resolve: @escaping @Sendable (Any?) -> Void,
|
|
172
|
+
rejecter reject: @escaping @Sendable (String?, String?, Error?) -> Void
|
|
173
|
+
) {
|
|
174
|
+
// Empty accuracy is the wire encoding of "use the configured mode".
|
|
175
|
+
let mode: AccuracyMode? = accuracy.isEmpty ? nil : accuracyModeFromWire(accuracy)
|
|
176
|
+
Task { [weak self] in
|
|
177
|
+
guard let self = self else { return }
|
|
178
|
+
do {
|
|
179
|
+
let location = try await Beekon.shared.getCurrentLocation(
|
|
180
|
+
timeout: timeoutMs / 1000.0,
|
|
181
|
+
accuracy: mode
|
|
182
|
+
)
|
|
183
|
+
// 0- or 1-element array — empty means timeout / no fix.
|
|
184
|
+
resolve(location.map { [self.locationToWire($0)] } ?? [])
|
|
185
|
+
} catch {
|
|
186
|
+
reject(self.errorCode(error), error.localizedDescription, error)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
135
191
|
// MARK: - History
|
|
136
192
|
|
|
137
193
|
@objc public func getLocationsFromMs(
|
|
@@ -253,6 +309,19 @@ import BeekonKit
|
|
|
253
309
|
}
|
|
254
310
|
}
|
|
255
311
|
|
|
312
|
+
// MARK: - License (observational only — never blocks; license-format-v1 §8)
|
|
313
|
+
|
|
314
|
+
@objc public func licenseStatusWithResolver(
|
|
315
|
+
_ resolve: @escaping @Sendable (Any?) -> Void,
|
|
316
|
+
rejecter _: @escaping @Sendable (String?, String?, Error?) -> Void
|
|
317
|
+
) {
|
|
318
|
+
Task { [weak self] in
|
|
319
|
+
guard let self = self else { return }
|
|
320
|
+
// `licenseStatus` is an actor-isolated property — read it with `await`.
|
|
321
|
+
resolve(self.licenseStatusToWire(await Beekon.shared.licenseStatus))
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
256
325
|
// MARK: - Mappers: wire (NSDictionary/NSArray) → Beekon
|
|
257
326
|
|
|
258
327
|
private func wireToConfig(_ d: NSDictionary) -> BeekonConfig {
|
|
@@ -266,14 +335,21 @@ import BeekonKit
|
|
|
266
335
|
|
|
267
336
|
var sync: SyncConfig?
|
|
268
337
|
if let s = d["sync"] as? NSDictionary,
|
|
269
|
-
let urlStr = s["url"] as? String
|
|
270
|
-
let url = URL(string: urlStr) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
338
|
+
let urlStr = s["url"] as? String {
|
|
339
|
+
if let url = URL(string: urlStr) {
|
|
340
|
+
let auth = (s["auth"] as? NSDictionary).map { wireToAuthConfig($0) }
|
|
341
|
+
sync = SyncConfig(
|
|
342
|
+
url: url,
|
|
343
|
+
headers: entriesToDict((s["headers"] as? NSArray) ?? []),
|
|
344
|
+
intervalSeconds: (s["intervalSeconds"] as? NSNumber)?.doubleValue ?? 300,
|
|
345
|
+
batchSize: (s["batchSize"] as? NSNumber)?.intValue ?? 100,
|
|
346
|
+
auth: auth
|
|
347
|
+
)
|
|
348
|
+
} else {
|
|
349
|
+
// Don't silently drop the whole sync config on an unparseable URL —
|
|
350
|
+
// surface it so a malformed endpoint is diagnosable instead of a no-op.
|
|
351
|
+
NSLog("[BeekonRn] sync config ignored: url is not a valid URL: %@", urlStr)
|
|
352
|
+
}
|
|
277
353
|
}
|
|
278
354
|
|
|
279
355
|
// `notification` is Android-only — iOS ignores it.
|
|
@@ -284,7 +360,10 @@ import BeekonKit
|
|
|
284
360
|
whenStationary: stationaryModeFromWire(d["whenStationary"] as? String),
|
|
285
361
|
stationaryRadiusMeters: stationaryRadius,
|
|
286
362
|
detectActivity: detectActivity,
|
|
287
|
-
sync: sync
|
|
363
|
+
sync: sync,
|
|
364
|
+
// Passed through verbatim; nil/blank means unset — the SDK falls through to
|
|
365
|
+
// the Info.plist value, then evaluation (license-format-v1 §9).
|
|
366
|
+
licenseKey: d["licenseKey"] as? String
|
|
288
367
|
)
|
|
289
368
|
}
|
|
290
369
|
|
|
@@ -321,6 +400,35 @@ import BeekonKit
|
|
|
321
400
|
return out
|
|
322
401
|
}
|
|
323
402
|
|
|
403
|
+
private func wireToAuthConfig(_ d: NSDictionary) -> AuthConfig {
|
|
404
|
+
// Wire carries epoch millis; the native recipe wants epoch seconds.
|
|
405
|
+
let expiresAt = (d["expiresAtMs"] as? NSNumber).map { $0.doubleValue / 1000.0 }
|
|
406
|
+
let responseMapping = (d["responseMapping"] as? NSDictionary)
|
|
407
|
+
.map { wireToResponseMapping($0) } ?? AuthResponseMapping()
|
|
408
|
+
return AuthConfig(
|
|
409
|
+
accessToken: d["accessToken"] as? String,
|
|
410
|
+
refreshToken: d["refreshToken"] as? String,
|
|
411
|
+
expiresAt: expiresAt,
|
|
412
|
+
strategy: authStrategyFromWire(d["strategy"] as? String),
|
|
413
|
+
refreshUrl: (d["refreshUrl"] as? String).flatMap { URL(string: $0) },
|
|
414
|
+
refreshPayload: entriesToDict((d["refreshPayload"] as? NSArray) ?? []),
|
|
415
|
+
refreshHeaders: entriesToDict((d["refreshHeaders"] as? NSArray) ?? []),
|
|
416
|
+
refreshBodyFormat: authBodyFormatFromWire(d["refreshBodyFormat"] as? String),
|
|
417
|
+
responseMapping: responseMapping,
|
|
418
|
+
skewMarginSeconds: (d["skewMarginSeconds"] as? NSNumber)?.doubleValue ?? 60,
|
|
419
|
+
seedEpoch: (d["seedEpoch"] as? NSNumber)?.intValue
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private func wireToResponseMapping(_ d: NSDictionary) -> AuthResponseMapping {
|
|
424
|
+
return AuthResponseMapping(
|
|
425
|
+
accessToken: d["accessToken"] as? String,
|
|
426
|
+
refreshToken: d["refreshToken"] as? String,
|
|
427
|
+
expiresIn: d["expiresIn"] as? String,
|
|
428
|
+
expiresAt: d["expiresAt"] as? String
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
324
432
|
// MARK: - Mappers: Beekon → wire (NSDictionary)
|
|
325
433
|
|
|
326
434
|
private func locationToWire(_ loc: Location) -> NSDictionary {
|
|
@@ -390,6 +498,66 @@ import BeekonKit
|
|
|
390
498
|
}
|
|
391
499
|
}
|
|
392
500
|
|
|
501
|
+
private func authTokensToWire(_ t: AuthTokens) -> NSDictionary {
|
|
502
|
+
// NSDictionary literals can't carry `nil`; optionals collapse to `NSNull`,
|
|
503
|
+
// which the Codegen layer translates back to `null` on the JS side.
|
|
504
|
+
let d = NSMutableDictionary()
|
|
505
|
+
d["accessToken"] = t.accessToken
|
|
506
|
+
d["refreshToken"] = t.refreshToken.map { $0 as Any } ?? NSNull()
|
|
507
|
+
// Native epoch seconds → wire epoch millis.
|
|
508
|
+
d["expiresAtMs"] = t.expiresAt.map { ($0 * 1000.0) as Any } ?? NSNull()
|
|
509
|
+
d["epoch"] = t.epoch
|
|
510
|
+
return d
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private func licenseStatusToWire(_ s: LicenseStatus) -> NSDictionary {
|
|
514
|
+
// NSDictionary literals can't carry `nil`; optionals collapse to `NSNull`,
|
|
515
|
+
// which the Codegen layer translates back to `null` on the JS side. iOS has
|
|
516
|
+
// no `.wireName`, so the wire-stable strings are written literally here (as
|
|
517
|
+
// with stateToWire / syncStatusToWire). No validation — the SDK already
|
|
518
|
+
// decided the status.
|
|
519
|
+
let d = NSMutableDictionary()
|
|
520
|
+
switch s {
|
|
521
|
+
case let .licensed(tier, entitlements):
|
|
522
|
+
d["status"] = "licensed"
|
|
523
|
+
d["tier"] = tier
|
|
524
|
+
d["entitlements"] = entitlements
|
|
525
|
+
d["reason"] = NSNull()
|
|
526
|
+
case let .invalid(reason):
|
|
527
|
+
d["status"] = "invalid"
|
|
528
|
+
d["tier"] = NSNull()
|
|
529
|
+
d["entitlements"] = [String]()
|
|
530
|
+
d["reason"] = reason.rawValue
|
|
531
|
+
case .evaluation:
|
|
532
|
+
d["status"] = "evaluation"
|
|
533
|
+
d["tier"] = NSNull()
|
|
534
|
+
d["entitlements"] = [String]()
|
|
535
|
+
d["reason"] = NSNull()
|
|
536
|
+
case .expired:
|
|
537
|
+
d["status"] = "expired"
|
|
538
|
+
d["tier"] = NSNull()
|
|
539
|
+
d["entitlements"] = [String]()
|
|
540
|
+
d["reason"] = NSNull()
|
|
541
|
+
case .updateEntitlementLapsed:
|
|
542
|
+
d["status"] = "updateEntitlementLapsed"
|
|
543
|
+
d["tier"] = NSNull()
|
|
544
|
+
d["entitlements"] = [String]()
|
|
545
|
+
d["reason"] = NSNull()
|
|
546
|
+
case .notDetermined:
|
|
547
|
+
d["status"] = "notDetermined"
|
|
548
|
+
d["tier"] = NSNull()
|
|
549
|
+
d["entitlements"] = [String]()
|
|
550
|
+
d["reason"] = NSNull()
|
|
551
|
+
// BeekonKit enums are non-frozen (library-evolution binary); handle unknowns.
|
|
552
|
+
@unknown default:
|
|
553
|
+
d["status"] = "notDetermined"
|
|
554
|
+
d["tier"] = NSNull()
|
|
555
|
+
d["entitlements"] = [String]()
|
|
556
|
+
d["reason"] = NSNull()
|
|
557
|
+
}
|
|
558
|
+
return d
|
|
559
|
+
}
|
|
560
|
+
|
|
393
561
|
// MARK: - Enum mappers
|
|
394
562
|
|
|
395
563
|
private func accuracyModeFromWire(_ s: String?) -> AccuracyMode {
|
|
@@ -408,6 +576,20 @@ import BeekonKit
|
|
|
408
576
|
}
|
|
409
577
|
}
|
|
410
578
|
|
|
579
|
+
private func authStrategyFromWire(_ s: String?) -> AuthStrategy {
|
|
580
|
+
switch s {
|
|
581
|
+
case "raw": return .raw
|
|
582
|
+
default: return .bearer
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private func authBodyFormatFromWire(_ s: String?) -> AuthBodyFormat {
|
|
587
|
+
switch s {
|
|
588
|
+
case "json": return .json
|
|
589
|
+
default: return .form
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
411
593
|
private func stopReasonToWire(_ r: StopReason) -> String {
|
|
412
594
|
switch r {
|
|
413
595
|
case .user: return "user"
|
|
@@ -483,6 +665,13 @@ import BeekonKit
|
|
|
483
665
|
switch be {
|
|
484
666
|
case .storage: return "STORAGE_FAILURE"
|
|
485
667
|
case .invalidGeofence: return "INVALID_GEOFENCE"
|
|
668
|
+
case .locationUnavailable(let reason):
|
|
669
|
+
switch reason {
|
|
670
|
+
case .permissionDenied: return "PERMISSION_DENIED"
|
|
671
|
+
case .locationServicesDisabled: return "LOCATION_SERVICES_DISABLED"
|
|
672
|
+
case .unavailable: return "LOCATION_UNAVAILABLE"
|
|
673
|
+
@unknown default: return "LOCATION_UNAVAILABLE"
|
|
674
|
+
}
|
|
486
675
|
@unknown default: return "INTERNAL_ERROR"
|
|
487
676
|
}
|
|
488
677
|
}
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>BeekonKit.framework/BeekonKit</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
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-
|
|
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>
|
|
Binary file
|