expo-beacon 0.7.24 → 0.7.25
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/android/src/main/java/expo/modules/beacon/BeaconConstants.kt +11 -14
- package/android/src/main/java/expo/modules/beacon/BeaconForegroundService.kt +10 -5
- package/android/src/main/java/expo/modules/beacon/ExpoBeaconModule.kt +10 -0
- package/build/ExpoBeacon.types.d.ts +9 -0
- package/build/ExpoBeacon.types.d.ts.map +1 -1
- package/build/ExpoBeacon.types.js.map +1 -1
- package/ios/ExpoBeaconModule.swift +65 -19
- package/package.json +1 -1
- package/src/ExpoBeacon.types.ts +9 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
package expo.modules.beacon
|
|
2
2
|
|
|
3
3
|
// Shared constants used across ExpoBeaconModule and BeaconForegroundService.
|
|
4
|
-
// NOTE:
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// NOTE: Scan timing deviates from iOS intentionally — Android requires a non-zero
|
|
5
|
+
// between-scan gap to prevent OS-level BLE throttling.
|
|
6
|
+
// ENTER_HYSTERESIS_COUNT and EXIT_HYSTERESIS_COUNT should stay in sync with
|
|
7
|
+
// ExpoBeaconModule.swift (iOS).
|
|
8
8
|
|
|
9
9
|
internal const val PREFS_NAME = "expo.beacon.paired"
|
|
10
10
|
internal const val PREFS_KEY = "paired_beacons"
|
|
@@ -42,22 +42,19 @@ internal const val REGION_EXIT_PERIOD_MS = 60000L
|
|
|
42
42
|
*/
|
|
43
43
|
internal const val RECENT_RANGING_SIGHTING_GRACE_MS = REGION_EXIT_PERIOD_MS
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
* Number of consecutive ranging misses before emitting a distance-based exit event.
|
|
47
|
-
* With a ~2.1 s scan cycle (1100 ms scan + 1000 ms gap), 10 misses ≈ 21 s of
|
|
48
|
-
* silence before declaring exit — tolerant of brief BLE gaps while still
|
|
49
|
-
* responsive to actual departures.
|
|
50
|
-
*/
|
|
51
|
-
internal const val EXIT_MISS_THRESHOLD = 10
|
|
52
|
-
|
|
53
45
|
/**
|
|
54
46
|
* Milliseconds of no valid BLE readings before starting the timeout countdown.
|
|
55
47
|
* Acts as a safety net when ranging cycles stop entirely (e.g. Doze mode).
|
|
56
48
|
*/
|
|
57
49
|
internal const val DISTANCE_INACTIVITY_MS = 60_000L
|
|
58
50
|
|
|
59
|
-
/** Number of consecutive readings required to confirm a distance-based enter
|
|
60
|
-
internal const val
|
|
51
|
+
/** Number of consecutive in-range readings required to confirm a distance-based enter transition. */
|
|
52
|
+
internal const val ENTER_HYSTERESIS_COUNT = 1
|
|
53
|
+
/** Number of consecutive out-of-range readings required to confirm a distance-based exit transition. */
|
|
54
|
+
internal const val EXIT_HYSTERESIS_COUNT = 3
|
|
55
|
+
|
|
56
|
+
/** Default seconds of silence after last sighting before a disappearance-based exit fires. */
|
|
57
|
+
internal const val DEFAULT_EXIT_TIMEOUT_SECONDS = 300.0
|
|
61
58
|
|
|
62
59
|
/** Default minimum RSSI (dBm) below which beacon readings are discarded as unreliable. */
|
|
63
60
|
internal const val DEFAULT_MIN_RSSI = -85
|
|
@@ -81,6 +81,8 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
81
81
|
private var apiForwarder: BeaconApiForwarder? = null
|
|
82
82
|
// Event level: "all" emits distance + enter/exit/timeout; "events" suppresses distance.
|
|
83
83
|
@Volatile private var eventLevel: String = "all"
|
|
84
|
+
// Seconds of silence after last valid sighting before a disappearance-based exit fires.
|
|
85
|
+
@Volatile private var exitTimeoutMs: Long = (DEFAULT_EXIT_TIMEOUT_SECONDS * 1000.0).toLong()
|
|
84
86
|
|
|
85
87
|
override fun onCreate() {
|
|
86
88
|
super.onCreate()
|
|
@@ -187,6 +189,7 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
187
189
|
exitDistance = optPrefs.getString("exit_distance", null)?.toDoubleOrNull()
|
|
188
190
|
minRssiThreshold = optPrefs.getInt("min_rssi", DEFAULT_MIN_RSSI)
|
|
189
191
|
eventLevel = optPrefs.getString("level", "all") ?: "all"
|
|
192
|
+
exitTimeoutMs = ((optPrefs.getString("exit_timeout_seconds", null)?.toDoubleOrNull() ?: DEFAULT_EXIT_TIMEOUT_SECONDS) * 1000.0).toLong()
|
|
190
193
|
|
|
191
194
|
beaconManager.addMonitorNotifier(monitorNotifier)
|
|
192
195
|
beaconManager.addRangeNotifier(rangeNotifier)
|
|
@@ -442,14 +445,16 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
442
445
|
// On Android 17+ (API 37) the BLE scan callbacks are more intermittent: valid
|
|
443
446
|
// readings are interspersed with occasional null cycles even when the beacon is
|
|
444
447
|
// nearby. Resetting direction counters on every null would prevent the hysteresis
|
|
445
|
-
// from ever accumulating to
|
|
448
|
+
// from ever accumulating to ENTER_HYSTERESIS_COUNT, breaking enter/exit entirely while
|
|
446
449
|
// still allowing distance events (which fire on each individual valid reading).
|
|
447
450
|
// Direction counters are reset by evaluateDistanceHysteresis when a valid reading
|
|
448
451
|
// contradicts the current direction (e.g., in-range reading resets exitCounters).
|
|
449
452
|
val count = (missCounters[region.uniqueId] ?: 0) + 1
|
|
450
453
|
missCounters[region.uniqueId] = count
|
|
451
454
|
|
|
452
|
-
|
|
455
|
+
val lastSeen = lastSeenAtMs[region.uniqueId]
|
|
456
|
+
val silentMs = if (lastSeen != null) SystemClock.elapsedRealtime() - lastSeen else Long.MAX_VALUE
|
|
457
|
+
if (enteredRegions.contains(region.uniqueId) && silentMs >= exitTimeoutMs) {
|
|
453
458
|
enteredRegions.remove(region.uniqueId)
|
|
454
459
|
missCounters[region.uniqueId] = 0
|
|
455
460
|
enterCounters[region.uniqueId] = 0
|
|
@@ -520,7 +525,7 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
520
525
|
}
|
|
521
526
|
val count = (enterCounters[regionId] ?: 0) + 1
|
|
522
527
|
enterCounters[regionId] = count
|
|
523
|
-
if (count >=
|
|
528
|
+
if (count >= ENTER_HYSTERESIS_COUNT) {
|
|
524
529
|
enterCounters[regionId] = 0
|
|
525
530
|
return HysteresisAction.ENTER
|
|
526
531
|
}
|
|
@@ -533,7 +538,7 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
533
538
|
exitCounters[regionId] = 0
|
|
534
539
|
val count = (enterCounters[regionId] ?: 0) + 1
|
|
535
540
|
enterCounters[regionId] = count
|
|
536
|
-
if (!enteredRegions.contains(regionId) && count >=
|
|
541
|
+
if (!enteredRegions.contains(regionId) && count >= ENTER_HYSTERESIS_COUNT) {
|
|
537
542
|
enterCounters[regionId] = 0
|
|
538
543
|
return HysteresisAction.ENTER
|
|
539
544
|
}
|
|
@@ -542,7 +547,7 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
542
547
|
enterCounters[regionId] = 0
|
|
543
548
|
val count = (exitCounters[regionId] ?: 0) + 1
|
|
544
549
|
exitCounters[regionId] = count
|
|
545
|
-
if (enteredRegions.contains(regionId) && count >=
|
|
550
|
+
if (enteredRegions.contains(regionId) && count >= EXIT_HYSTERESIS_COUNT) {
|
|
546
551
|
exitCounters[regionId] = 0
|
|
547
552
|
return HysteresisAction.EXIT
|
|
548
553
|
}
|
|
@@ -290,6 +290,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
290
290
|
var exitDistance: Double? = null
|
|
291
291
|
var minRssi: Int? = null
|
|
292
292
|
var level: String = "all"
|
|
293
|
+
var exitTimeoutSeconds: Double? = null
|
|
293
294
|
when (options) {
|
|
294
295
|
is Double -> maxDistance = options
|
|
295
296
|
is Map<*, *> -> {
|
|
@@ -299,6 +300,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
299
300
|
exitDistance = (map["exitDistance"] as? Number)?.toDouble()
|
|
300
301
|
minRssi = (map["minRssi"] as? Number)?.toInt()
|
|
301
302
|
level = (map["level"] as? String) ?: "all"
|
|
303
|
+
exitTimeoutSeconds = (map["exitTimeoutSeconds"] as? Number)?.toDouble()
|
|
302
304
|
val notifications = map["notifications"]
|
|
303
305
|
if (notifications is Map<*, *>) {
|
|
304
306
|
@Suppress("UNCHECKED_CAST")
|
|
@@ -327,6 +329,11 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
327
329
|
sendEvent("onBeaconError", mapOf("identifier" to "", "code" to "INVALID_EXIT_DISTANCE", "message" to "exitDistance must be greater than or equal to maxDistance"))
|
|
328
330
|
return@AsyncFunction
|
|
329
331
|
}
|
|
332
|
+
if (exitTimeoutSeconds != null && (!exitTimeoutSeconds.isFinite() || exitTimeoutSeconds <= 0.0)) {
|
|
333
|
+
promise.reject("INVALID_EXIT_TIMEOUT", "exitTimeoutSeconds must be a finite number greater than 0", null)
|
|
334
|
+
sendEvent("onBeaconError", mapOf("identifier" to "", "code" to "INVALID_EXIT_TIMEOUT", "message" to "exitTimeoutSeconds must be a finite number greater than 0"))
|
|
335
|
+
return@AsyncFunction
|
|
336
|
+
}
|
|
330
337
|
ctx.getSharedPreferences(MONITORING_OPTIONS_PREFS, Context.MODE_PRIVATE)
|
|
331
338
|
.edit().apply {
|
|
332
339
|
if (maxDistance != null) putString("max_distance", maxDistance.toString())
|
|
@@ -336,6 +343,8 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
336
343
|
if (minRssi != null) putInt("min_rssi", minRssi)
|
|
337
344
|
else remove("min_rssi")
|
|
338
345
|
putString("level", level)
|
|
346
|
+
if (exitTimeoutSeconds != null) putString("exit_timeout_seconds", exitTimeoutSeconds.toString())
|
|
347
|
+
else remove("exit_timeout_seconds")
|
|
339
348
|
}.apply()
|
|
340
349
|
// Verify we have the permissions needed for background monitoring
|
|
341
350
|
val hasLocation = ContextCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
|
@@ -495,6 +504,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
495
504
|
optPrefs.getString("exit_distance", null)?.toDoubleOrNull()?.let { put("exitDistance", it) }
|
|
496
505
|
if (optPrefs.contains("min_rssi")) put("minRssi", optPrefs.getInt("min_rssi", -85))
|
|
497
506
|
optPrefs.getString("level", null)?.let { put("level", it) }
|
|
507
|
+
optPrefs.getString("exit_timeout_seconds", null)?.toDoubleOrNull()?.let { put("exitTimeoutSeconds", it) }
|
|
498
508
|
val json = ctx.getSharedPreferences(NOTIFICATION_CONFIG_PREFS, Context.MODE_PRIVATE)
|
|
499
509
|
.getString("config", null)
|
|
500
510
|
if (json != null) {
|
|
@@ -121,6 +121,8 @@ export type MonitoringConfig = {
|
|
|
121
121
|
exitDistance?: number;
|
|
122
122
|
minRssi?: number;
|
|
123
123
|
level?: 'all' | 'events';
|
|
124
|
+
/** Seconds after last beacon sighting before an exit event fires. Default: 300. */
|
|
125
|
+
exitTimeoutSeconds?: number;
|
|
124
126
|
notifications?: NotificationConfig;
|
|
125
127
|
};
|
|
126
128
|
/** Current state snapshot for a paired monitored device. */
|
|
@@ -173,6 +175,13 @@ export type MonitoringOptions = {
|
|
|
173
175
|
* - `'events'`: enter + exit + timeout only (no distance events).
|
|
174
176
|
*/
|
|
175
177
|
level?: 'all' | 'events';
|
|
178
|
+
/**
|
|
179
|
+
* Seconds after last beacon sighting before an exit event fires when the beacon
|
|
180
|
+
* disappears without moving outside the exit distance threshold.
|
|
181
|
+
*
|
|
182
|
+
* Default: 300 (5 minutes). Minimum: 1.
|
|
183
|
+
*/
|
|
184
|
+
exitTimeoutSeconds?: number;
|
|
176
185
|
/** Notification configuration overrides to apply for this monitoring session. */
|
|
177
186
|
notifications?: NotificationConfig;
|
|
178
187
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoBeacon.types.d.ts","sourceRoot":"","sources":["../src/ExpoBeacon.types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExpoBeacon.types.d.ts","sourceRoot":"","sources":["../src/ExpoBeacon.types.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,0GAA0G;IAC1G,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qEAAqE;AACrE,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,+DAA+D;AAC/D,MAAM,MAAM,wBAAwB,GAAG;IACrC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yFAAyF;IACzF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,mGAAmG;AACnG,MAAM,MAAM,uBAAuB,GAAG;IACpC,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sGAAsG;IACtG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,0DAA0D;AAC1D,MAAM,MAAM,yBAAyB,GAAG;IACtC,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8GAA8G;IAC9G,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;CACzC,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,0DAA0D;IAC1D,YAAY,CAAC,EAAE,wBAAwB,CAAC;IACxC,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,uBAAuB,CAAC;IAC5C,oEAAoE;IACpE,OAAO,CAAC,EAAE,yBAAyB,CAAC;CACrC,CAAC;AAEF,yEAAyE;AACzE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yDAAyD;IACzD,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACzB,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC,CAAC;AAEF,4DAA4D;AAC5D,MAAM,MAAM,oBAAoB,GAC5B;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,uFAAuF;IACvF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,uFAAuF;IACvF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAEN,6CAA6C;AAC7C,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACzB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC,CAAC;AAEF,4BAA4B;AAC5B,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,KAAK,CAAC;AAE/C,qDAAqD;AACrD,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,kBAAkB,CAAC;IAC9B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,sDAAsD;AACtD,MAAM,MAAM,oBAAoB,GAAG;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,sBAAsB,GAAG;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,sFAAsF;AACtF,MAAM,MAAM,qBAAqB,GAAG;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAC;IACnB,sGAAsG;IACtG,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAwB;AACxB,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACnD,YAAY,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,gBAAgB,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,2GAA2G;IAC3G,eAAe,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACtD,yEAAyE;IACzE,aAAa,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,kFAAkF;IAClF,gBAAgB,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,gBAAgB,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACzD,eAAe,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACxD,mBAAmB,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAC9D,8GAA8G;IAC9G,kBAAkB,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAC5D,mGAAmG;IACnG,aAAa,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,wCAAwC;AACxC,MAAM,MAAM,oBAAoB,GAAG;IACjC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,0CAA0C;AAC1C,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoBeacon.types.js","sourceRoot":"","sources":["../src/ExpoBeacon.types.ts"],"names":[],"mappings":"","sourcesContent":["/** Raw beacon discovered during a scan. */\r\nexport type BeaconScanResult = {\r\n uuid: string; // iBeacon proximity UUID (uppercase, formatted)\r\n major: number; // iBeacon major value (0–65535)\r\n minor: number; // iBeacon minor value (0–65535)\r\n rssi: number; // Signal strength in dBm (negative number)\r\n distance: number; // Estimated distance in meters\r\n txPower: number; // Calibrated TX power\r\n /** BLE advertising device name. May be undefined on iOS (CoreLocation does not expose it for iBeacon). */\r\n name?: string;\r\n};\r\n\r\n/**\r\n * A beacon that has been paired/registered for monitoring.\r\n *\r\n * Note: Paired beacon data is stored unencrypted in UserDefaults (iOS) /\r\n * SharedPreferences (Android) and may be included in device backups.\r\n */\r\nexport type PairedBeacon = {\r\n identifier: string; // User-defined label (e.g. \"lobby-door\")\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n /** BLE advertising device name, if provided at pairing time. */\r\n name?: string;\r\n /**\r\n * Timeout in seconds. When set, the module fires `onBeaconTimeout` once\r\n * after the beacon has been continuously in range for this duration.\r\n * The timer resets if the beacon exits and re-enters range.\r\n *\r\n * The timeout countdown also starts if no BLE readings are received\r\n * for 60 seconds (e.g. due to Doze mode or background throttling).\r\n */\r\n timeoutSeconds?: number;\r\n};\r\n\r\n/** Payload for enter/exit region events. */\r\nexport type BeaconRegionEvent = {\r\n identifier: string; // Matches PairedBeacon.identifier\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n event: \"enter\" | \"exit\";\r\n /** Measured distance in metres at the time of the event (–1 if unavailable). */\r\n distance: number;\r\n /** Signal strength in dBm at the time of the event (0 if unavailable). */\r\n rssi?: number;\r\n};\r\n\r\n/** Payload for periodic distance update events during monitoring. */\r\nexport type BeaconDistanceEvent = {\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n distance: number;\r\n /** Signal strength in dBm (0 if unavailable). */\r\n rssi?: number;\r\n};\r\n\r\n/** Payload for beacon timeout events (beacon in range for configured duration). */\r\nexport type BeaconTimeoutEvent = {\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n /** Current distance in metres at the time the timeout fired. */\r\n distance: number;\r\n};\r\n\r\n/** Configuration for beacon enter/exit event notifications. */\r\nexport type BeaconNotificationConfig = {\r\n /** Whether to show enter/exit notifications. Default: true. */\r\n enabled?: boolean;\r\n /** Notification title on beacon enter. Default: \"Beacon Entered\". */\r\n enterTitle?: string;\r\n /** Notification title on beacon exit. Default: \"Beacon Exited\". */\r\n exitTitle?: string;\r\n /** Notification title on beacon timeout. Default: \"Beacon Timeout\". */\r\n timeoutTitle?: string;\r\n /**\r\n * Notification body template. Supports {identifier} and {event} placeholders.\r\n * Default: \"{identifier} region {event}ed\".\r\n */\r\n body?: string;\r\n /** Play a sound with the notification (iOS only). Default: true. */\r\n sound?: boolean;\r\n /** Android drawable resource name for the notification icon (e.g. \"ic_notification\"). */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android foreground service notification (persistent status bar entry). */\r\nexport type ForegroundServiceConfig = {\r\n /** Title of the persistent notification. Default: \"Beacon Monitoring Active\". */\r\n title?: string;\r\n /** Body text of the persistent notification. Default: \"Monitoring for iBeacons in the background\". */\r\n text?: string;\r\n /** Android drawable resource name for the notification icon. */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android notification channel. */\r\nexport type NotificationChannelConfig = {\r\n /** Channel display name shown in system settings. Default: \"Beacon Monitoring\". */\r\n name?: string;\r\n /** Channel description shown in system settings. Default: \"Used for background iBeacon region monitoring\". */\r\n description?: string;\r\n /**\r\n * Channel importance level. Default: 'low'.\r\n * Note: Android may ignore decreases in importance after first channel creation until the app is reinstalled.\r\n */\r\n importance?: \"low\" | \"default\" | \"high\";\r\n};\r\n\r\n/** Combined notification configuration for all notification types. */\r\nexport type NotificationConfig = {\r\n /** Settings for beacon enter/exit event notifications. */\r\n beaconEvents?: BeaconNotificationConfig;\r\n /** Settings for the persistent foreground service notification (Android only). */\r\n foregroundService?: ForegroundServiceConfig;\r\n /** Settings for the Android notification channel (Android only). */\r\n channel?: NotificationChannelConfig;\r\n};\r\n\r\n/** Snapshot of the current monitoring configuration and active state. */\r\nexport type MonitoringConfig = {\r\n /** Whether background monitoring is currently active. */\r\n isMonitoring: boolean;\r\n maxDistance?: number;\r\n exitDistance?: number;\r\n minRssi?: number;\r\n level?: 'all' | 'events';\r\n notifications?: NotificationConfig;\r\n};\r\n\r\n/** Current state snapshot for a paired monitored device. */\r\nexport type MonitoredDeviceState =\r\n | {\r\n kind: \"ibeacon\";\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n state: \"entered\" | \"exited\";\r\n /** Current distance in metres, or null when exited or no live reading is available. */\r\n distance: number | null;\r\n }\r\n | {\r\n kind: \"eddystone\";\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n state: \"entered\" | \"exited\";\r\n /** Current distance in metres, or null when exited or no live reading is available. */\r\n distance: number | null;\r\n };\r\n\r\n/** Options accepted by startMonitoring(). */\r\nexport type MonitoringOptions = {\r\n /**\r\n * Maximum distance in metres for distance-based enter events.\r\n * Exit events are always emitted when the region is lost.\r\n */\r\n maxDistance?: number;\r\n /**\r\n * Distance in metres at which exit events fire (must be ≥ maxDistance).\r\n * Creates a hysteresis band between enter and exit thresholds to prevent\r\n * rapid toggling near the boundary.\r\n *\r\n * Default when omitted: `maxDistance + min(maxDistance × 0.5, 2.5)`.\r\n * Only used when `maxDistance` is set.\r\n */\r\n exitDistance?: number;\r\n /**\r\n * Minimum RSSI (dBm) for a beacon reading to be considered valid.\r\n * Readings below this threshold are discarded as unreliable, preventing\r\n * false detections from reflected or distant signals.\r\n *\r\n * Default: -85. Typical range: -100 (very permissive) to -70 (strict).\r\n */\r\n minRssi?: number;\r\n /**\r\n * Controls which event types are emitted, logged, and forwarded to the API.\r\n *\r\n * - `'all'` (default): distance + enter + exit + timeout events.\r\n * - `'events'`: enter + exit + timeout only (no distance events).\r\n */\r\n level?: 'all' | 'events';\r\n /** Notification configuration overrides to apply for this monitoring session. */\r\n notifications?: NotificationConfig;\r\n};\r\n\r\n/** Eddystone frame type. */\r\nexport type EddystoneFrameType = \"uid\" | \"url\";\r\n\r\n/** Raw Eddystone beacon discovered during a scan. */\r\nexport type EddystoneScanResult = {\r\n frameType: EddystoneFrameType;\r\n /** 10-byte namespace ID as hex string (20 chars). Present for UID frames. */\r\n namespace?: string;\r\n /** 6-byte instance ID as hex string (12 chars). Present for UID frames. */\r\n instance?: string;\r\n /** Decoded URL. Present for URL frames. */\r\n url?: string;\r\n rssi: number;\r\n distance: number;\r\n txPower: number;\r\n /** BLE advertising device name. */\r\n name?: string;\r\n};\r\n\r\n/**\r\n * An Eddystone-UID beacon that has been paired/registered for monitoring.\r\n *\r\n * Note: Paired beacon data is stored unencrypted in UserDefaults (iOS) /\r\n * SharedPreferences (Android) and may be included in device backups.\r\n */\r\nexport type PairedEddystone = {\r\n identifier: string;\r\n /** 10-byte namespace ID as hex string (20 chars). */\r\n namespace: string;\r\n /** 6-byte instance ID as hex string (12 chars). */\r\n instance: string;\r\n /** BLE advertising device name, if provided at pairing time. */\r\n name?: string;\r\n /**\r\n * Timeout in seconds. When set, the module fires `onEddystoneTimeout` once\r\n * after the beacon has been continuously in range for this duration.\r\n * The timer resets if the beacon exits and re-enters range.\r\n *\r\n * The timeout countdown also starts if no BLE readings are received\r\n * for 60 seconds (e.g. due to Doze mode or background throttling).\r\n */\r\n timeoutSeconds?: number;\r\n};\r\n\r\n/** Payload for Eddystone enter/exit region events. */\r\nexport type EddystoneRegionEvent = {\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n event: \"enter\" | \"exit\";\r\n /** Measured distance in metres at the time of the event (–1 if unavailable). */\r\n distance: number;\r\n /** Signal strength in dBm at the time of the event (0 if unavailable). */\r\n rssi?: number;\r\n};\r\n\r\n/** Payload for periodic Eddystone distance update events during monitoring. */\r\nexport type EddystoneDistanceEvent = {\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n distance: number;\r\n /** Signal strength in dBm (0 if unavailable). */\r\n rssi?: number;\r\n};\r\n\r\n/** Payload for Eddystone timeout events (beacon in range for configured duration). */\r\nexport type EddystoneTimeoutEvent = {\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n /** Current distance in metres at the time the timeout fired. */\r\n distance: number;\r\n};\r\n\r\n/** Payload for native beacon error events (monitoring/ranging failures). */\r\nexport type BeaconErrorEvent = {\r\n /** Region or constraint identifier, empty string if unavailable. */\r\n identifier: string;\r\n /** Machine-readable error code (e.g. \"MONITORING_FAILED\", \"RANGING_FAILED\", \"SECURITY_EXCEPTION\"). */\r\n code: string;\r\n /** Human-readable error message from the native layer. */\r\n message: string;\r\n};\r\n\r\n/** Module event map. */\r\nexport type ExpoBeaconModuleEvents = {\r\n onBeaconEnter: (params: BeaconRegionEvent) => void;\r\n onBeaconExit: (params: BeaconRegionEvent) => void;\r\n onBeaconDistance: (params: BeaconDistanceEvent) => void;\r\n /** Fired once after a paired beacon has been continuously in range for its configured `timeoutSeconds`. */\r\n onBeaconTimeout: (params: BeaconTimeoutEvent) => void;\r\n /** Fired continuously during a live scan as each iBeacon is detected. */\r\n onBeaconFound: (params: BeaconScanResult) => void;\r\n /** Fired continuously during a live scan as each Eddystone beacon is detected. */\r\n onEddystoneFound: (params: EddystoneScanResult) => void;\r\n onEddystoneEnter: (params: EddystoneRegionEvent) => void;\r\n onEddystoneExit: (params: EddystoneRegionEvent) => void;\r\n onEddystoneDistance: (params: EddystoneDistanceEvent) => void;\r\n /** Fired once after a paired Eddystone has been continuously in range for its configured `timeoutSeconds`. */\r\n onEddystoneTimeout: (params: EddystoneTimeoutEvent) => void;\r\n /** Fired when a native monitoring or ranging failure occurs (logged to DB and forwarded to JS). */\r\n onBeaconError: (params: BeaconErrorEvent) => void;\r\n};\r\n\r\n/** Options for filtering event logs. */\r\nexport type EventLogQueryOptions = {\r\n /** Maximum number of log entries to return (default: 1000, max: 10000). */\r\n limit?: number;\r\n /** Filter by event type (e.g. \"onBeaconEnter\", \"onBeaconExit\"). */\r\n eventType?: string;\r\n /** Only return events with timestamp >= this value (ms since epoch). */\r\n sinceTimestamp?: number;\r\n};\r\n\r\n/** A single logged beacon event entry. */\r\nexport type EventLogEntry = {\r\n id: number;\r\n /** Timestamp in milliseconds since epoch. */\r\n timestamp: number;\r\n /** The event type that was logged (e.g. \"onBeaconEnter\"). */\r\n eventType: string;\r\n /** Beacon identifier, if available. */\r\n identifier?: string;\r\n /** The full event payload that was sent to JS. */\r\n data: Record<string, unknown>;\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"file":"ExpoBeacon.types.js","sourceRoot":"","sources":["../src/ExpoBeacon.types.ts"],"names":[],"mappings":"","sourcesContent":["/** Raw beacon discovered during a scan. */\r\nexport type BeaconScanResult = {\r\n uuid: string; // iBeacon proximity UUID (uppercase, formatted)\r\n major: number; // iBeacon major value (0–65535)\r\n minor: number; // iBeacon minor value (0–65535)\r\n rssi: number; // Signal strength in dBm (negative number)\r\n distance: number; // Estimated distance in meters\r\n txPower: number; // Calibrated TX power\r\n /** BLE advertising device name. May be undefined on iOS (CoreLocation does not expose it for iBeacon). */\r\n name?: string;\r\n};\r\n\r\n/**\r\n * A beacon that has been paired/registered for monitoring.\r\n *\r\n * Note: Paired beacon data is stored unencrypted in UserDefaults (iOS) /\r\n * SharedPreferences (Android) and may be included in device backups.\r\n */\r\nexport type PairedBeacon = {\r\n identifier: string; // User-defined label (e.g. \"lobby-door\")\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n /** BLE advertising device name, if provided at pairing time. */\r\n name?: string;\r\n /**\r\n * Timeout in seconds. When set, the module fires `onBeaconTimeout` once\r\n * after the beacon has been continuously in range for this duration.\r\n * The timer resets if the beacon exits and re-enters range.\r\n *\r\n * The timeout countdown also starts if no BLE readings are received\r\n * for 60 seconds (e.g. due to Doze mode or background throttling).\r\n */\r\n timeoutSeconds?: number;\r\n};\r\n\r\n/** Payload for enter/exit region events. */\r\nexport type BeaconRegionEvent = {\r\n identifier: string; // Matches PairedBeacon.identifier\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n event: \"enter\" | \"exit\";\r\n /** Measured distance in metres at the time of the event (–1 if unavailable). */\r\n distance: number;\r\n /** Signal strength in dBm at the time of the event (0 if unavailable). */\r\n rssi?: number;\r\n};\r\n\r\n/** Payload for periodic distance update events during monitoring. */\r\nexport type BeaconDistanceEvent = {\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n distance: number;\r\n /** Signal strength in dBm (0 if unavailable). */\r\n rssi?: number;\r\n};\r\n\r\n/** Payload for beacon timeout events (beacon in range for configured duration). */\r\nexport type BeaconTimeoutEvent = {\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n /** Current distance in metres at the time the timeout fired. */\r\n distance: number;\r\n};\r\n\r\n/** Configuration for beacon enter/exit event notifications. */\r\nexport type BeaconNotificationConfig = {\r\n /** Whether to show enter/exit notifications. Default: true. */\r\n enabled?: boolean;\r\n /** Notification title on beacon enter. Default: \"Beacon Entered\". */\r\n enterTitle?: string;\r\n /** Notification title on beacon exit. Default: \"Beacon Exited\". */\r\n exitTitle?: string;\r\n /** Notification title on beacon timeout. Default: \"Beacon Timeout\". */\r\n timeoutTitle?: string;\r\n /**\r\n * Notification body template. Supports {identifier} and {event} placeholders.\r\n * Default: \"{identifier} region {event}ed\".\r\n */\r\n body?: string;\r\n /** Play a sound with the notification (iOS only). Default: true. */\r\n sound?: boolean;\r\n /** Android drawable resource name for the notification icon (e.g. \"ic_notification\"). */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android foreground service notification (persistent status bar entry). */\r\nexport type ForegroundServiceConfig = {\r\n /** Title of the persistent notification. Default: \"Beacon Monitoring Active\". */\r\n title?: string;\r\n /** Body text of the persistent notification. Default: \"Monitoring for iBeacons in the background\". */\r\n text?: string;\r\n /** Android drawable resource name for the notification icon. */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android notification channel. */\r\nexport type NotificationChannelConfig = {\r\n /** Channel display name shown in system settings. Default: \"Beacon Monitoring\". */\r\n name?: string;\r\n /** Channel description shown in system settings. Default: \"Used for background iBeacon region monitoring\". */\r\n description?: string;\r\n /**\r\n * Channel importance level. Default: 'low'.\r\n * Note: Android may ignore decreases in importance after first channel creation until the app is reinstalled.\r\n */\r\n importance?: \"low\" | \"default\" | \"high\";\r\n};\r\n\r\n/** Combined notification configuration for all notification types. */\r\nexport type NotificationConfig = {\r\n /** Settings for beacon enter/exit event notifications. */\r\n beaconEvents?: BeaconNotificationConfig;\r\n /** Settings for the persistent foreground service notification (Android only). */\r\n foregroundService?: ForegroundServiceConfig;\r\n /** Settings for the Android notification channel (Android only). */\r\n channel?: NotificationChannelConfig;\r\n};\r\n\r\n/** Snapshot of the current monitoring configuration and active state. */\r\nexport type MonitoringConfig = {\r\n /** Whether background monitoring is currently active. */\r\n isMonitoring: boolean;\r\n maxDistance?: number;\r\n exitDistance?: number;\r\n minRssi?: number;\r\n level?: 'all' | 'events';\r\n /** Seconds after last beacon sighting before an exit event fires. Default: 300. */\r\n exitTimeoutSeconds?: number;\r\n notifications?: NotificationConfig;\r\n};\r\n\r\n/** Current state snapshot for a paired monitored device. */\r\nexport type MonitoredDeviceState =\r\n | {\r\n kind: \"ibeacon\";\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n state: \"entered\" | \"exited\";\r\n /** Current distance in metres, or null when exited or no live reading is available. */\r\n distance: number | null;\r\n }\r\n | {\r\n kind: \"eddystone\";\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n state: \"entered\" | \"exited\";\r\n /** Current distance in metres, or null when exited or no live reading is available. */\r\n distance: number | null;\r\n };\r\n\r\n/** Options accepted by startMonitoring(). */\r\nexport type MonitoringOptions = {\r\n /**\r\n * Maximum distance in metres for distance-based enter events.\r\n * Exit events are always emitted when the region is lost.\r\n */\r\n maxDistance?: number;\r\n /**\r\n * Distance in metres at which exit events fire (must be ≥ maxDistance).\r\n * Creates a hysteresis band between enter and exit thresholds to prevent\r\n * rapid toggling near the boundary.\r\n *\r\n * Default when omitted: `maxDistance + min(maxDistance × 0.5, 2.5)`.\r\n * Only used when `maxDistance` is set.\r\n */\r\n exitDistance?: number;\r\n /**\r\n * Minimum RSSI (dBm) for a beacon reading to be considered valid.\r\n * Readings below this threshold are discarded as unreliable, preventing\r\n * false detections from reflected or distant signals.\r\n *\r\n * Default: -85. Typical range: -100 (very permissive) to -70 (strict).\r\n */\r\n minRssi?: number;\r\n /**\r\n * Controls which event types are emitted, logged, and forwarded to the API.\r\n *\r\n * - `'all'` (default): distance + enter + exit + timeout events.\r\n * - `'events'`: enter + exit + timeout only (no distance events).\r\n */\r\n level?: 'all' | 'events';\r\n /**\r\n * Seconds after last beacon sighting before an exit event fires when the beacon\r\n * disappears without moving outside the exit distance threshold.\r\n *\r\n * Default: 300 (5 minutes). Minimum: 1.\r\n */\r\n exitTimeoutSeconds?: number;\r\n /** Notification configuration overrides to apply for this monitoring session. */\r\n notifications?: NotificationConfig;\r\n};\r\n\r\n/** Eddystone frame type. */\r\nexport type EddystoneFrameType = \"uid\" | \"url\";\r\n\r\n/** Raw Eddystone beacon discovered during a scan. */\r\nexport type EddystoneScanResult = {\r\n frameType: EddystoneFrameType;\r\n /** 10-byte namespace ID as hex string (20 chars). Present for UID frames. */\r\n namespace?: string;\r\n /** 6-byte instance ID as hex string (12 chars). Present for UID frames. */\r\n instance?: string;\r\n /** Decoded URL. Present for URL frames. */\r\n url?: string;\r\n rssi: number;\r\n distance: number;\r\n txPower: number;\r\n /** BLE advertising device name. */\r\n name?: string;\r\n};\r\n\r\n/**\r\n * An Eddystone-UID beacon that has been paired/registered for monitoring.\r\n *\r\n * Note: Paired beacon data is stored unencrypted in UserDefaults (iOS) /\r\n * SharedPreferences (Android) and may be included in device backups.\r\n */\r\nexport type PairedEddystone = {\r\n identifier: string;\r\n /** 10-byte namespace ID as hex string (20 chars). */\r\n namespace: string;\r\n /** 6-byte instance ID as hex string (12 chars). */\r\n instance: string;\r\n /** BLE advertising device name, if provided at pairing time. */\r\n name?: string;\r\n /**\r\n * Timeout in seconds. When set, the module fires `onEddystoneTimeout` once\r\n * after the beacon has been continuously in range for this duration.\r\n * The timer resets if the beacon exits and re-enters range.\r\n *\r\n * The timeout countdown also starts if no BLE readings are received\r\n * for 60 seconds (e.g. due to Doze mode or background throttling).\r\n */\r\n timeoutSeconds?: number;\r\n};\r\n\r\n/** Payload for Eddystone enter/exit region events. */\r\nexport type EddystoneRegionEvent = {\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n event: \"enter\" | \"exit\";\r\n /** Measured distance in metres at the time of the event (–1 if unavailable). */\r\n distance: number;\r\n /** Signal strength in dBm at the time of the event (0 if unavailable). */\r\n rssi?: number;\r\n};\r\n\r\n/** Payload for periodic Eddystone distance update events during monitoring. */\r\nexport type EddystoneDistanceEvent = {\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n distance: number;\r\n /** Signal strength in dBm (0 if unavailable). */\r\n rssi?: number;\r\n};\r\n\r\n/** Payload for Eddystone timeout events (beacon in range for configured duration). */\r\nexport type EddystoneTimeoutEvent = {\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n /** Current distance in metres at the time the timeout fired. */\r\n distance: number;\r\n};\r\n\r\n/** Payload for native beacon error events (monitoring/ranging failures). */\r\nexport type BeaconErrorEvent = {\r\n /** Region or constraint identifier, empty string if unavailable. */\r\n identifier: string;\r\n /** Machine-readable error code (e.g. \"MONITORING_FAILED\", \"RANGING_FAILED\", \"SECURITY_EXCEPTION\"). */\r\n code: string;\r\n /** Human-readable error message from the native layer. */\r\n message: string;\r\n};\r\n\r\n/** Module event map. */\r\nexport type ExpoBeaconModuleEvents = {\r\n onBeaconEnter: (params: BeaconRegionEvent) => void;\r\n onBeaconExit: (params: BeaconRegionEvent) => void;\r\n onBeaconDistance: (params: BeaconDistanceEvent) => void;\r\n /** Fired once after a paired beacon has been continuously in range for its configured `timeoutSeconds`. */\r\n onBeaconTimeout: (params: BeaconTimeoutEvent) => void;\r\n /** Fired continuously during a live scan as each iBeacon is detected. */\r\n onBeaconFound: (params: BeaconScanResult) => void;\r\n /** Fired continuously during a live scan as each Eddystone beacon is detected. */\r\n onEddystoneFound: (params: EddystoneScanResult) => void;\r\n onEddystoneEnter: (params: EddystoneRegionEvent) => void;\r\n onEddystoneExit: (params: EddystoneRegionEvent) => void;\r\n onEddystoneDistance: (params: EddystoneDistanceEvent) => void;\r\n /** Fired once after a paired Eddystone has been continuously in range for its configured `timeoutSeconds`. */\r\n onEddystoneTimeout: (params: EddystoneTimeoutEvent) => void;\r\n /** Fired when a native monitoring or ranging failure occurs (logged to DB and forwarded to JS). */\r\n onBeaconError: (params: BeaconErrorEvent) => void;\r\n};\r\n\r\n/** Options for filtering event logs. */\r\nexport type EventLogQueryOptions = {\r\n /** Maximum number of log entries to return (default: 1000, max: 10000). */\r\n limit?: number;\r\n /** Filter by event type (e.g. \"onBeaconEnter\", \"onBeaconExit\"). */\r\n eventType?: string;\r\n /** Only return events with timestamp >= this value (ms since epoch). */\r\n sinceTimestamp?: number;\r\n};\r\n\r\n/** A single logged beacon event entry. */\r\nexport type EventLogEntry = {\r\n id: number;\r\n /** Timestamp in milliseconds since epoch. */\r\n timestamp: number;\r\n /** The event type that was logged (e.g. \"onBeaconEnter\"). */\r\n eventType: string;\r\n /** Beacon identifier, if available. */\r\n identifier?: string;\r\n /** The full event payload that was sent to JS. */\r\n data: Record<string, unknown>;\r\n};\r\n"]}
|
|
@@ -13,19 +13,19 @@ private let NOTIFICATION_CONFIG_KEY = "expo.beacon.notification_config"
|
|
|
13
13
|
private let EVENT_LOGGING_ENABLED_KEY = "expo.beacon.event_logging_enabled"
|
|
14
14
|
private let MIN_RSSI_KEY = "expo.beacon.min_rssi"
|
|
15
15
|
private let EVENT_LEVEL_KEY = "expo.beacon.event_level"
|
|
16
|
+
private let EXIT_TIMEOUT_SECONDS_KEY = "expo.beacon.exit_timeout_seconds"
|
|
16
17
|
|
|
17
18
|
/// Default minimum RSSI (dBm) below which beacon readings are discarded as unreliable.
|
|
18
19
|
private let DEFAULT_MIN_RSSI: Int = -85
|
|
20
|
+
/// Default seconds of silence after last beacon sighting before a disappearance-based exit fires.
|
|
21
|
+
private let DEFAULT_EXIT_TIMEOUT_SECONDS: TimeInterval = 300.0
|
|
19
22
|
|
|
20
|
-
/// Number of consecutive
|
|
21
|
-
/// With ~1 s CoreLocation ranging cycles (iBeacon) or ~2 s Eddystone monitoring ticks,
|
|
22
|
-
/// 20 misses ≈ 20–40 s of silence before declaring exit — tolerant of iOS background
|
|
23
|
-
/// BLE throttling while still responsive to actual departures.
|
|
24
|
-
/// NOTE: Android uses 10 with a ~2.1 s scan cycle (≈21 s effective).
|
|
25
|
-
private let EXIT_MISS_THRESHOLD = 20
|
|
26
|
-
/// Number of consecutive readings required to confirm a distance-based enter or exit transition.
|
|
23
|
+
/// Number of consecutive in-range readings required before an enter event is emitted.
|
|
27
24
|
/// IMPORTANT: Keep in sync with BeaconConstants.kt (Android).
|
|
28
|
-
private let
|
|
25
|
+
private let ENTER_HYSTERESIS_COUNT = 1
|
|
26
|
+
/// Number of consecutive out-of-range readings required before an exit event is emitted.
|
|
27
|
+
/// IMPORTANT: Keep in sync with BeaconConstants.kt (Android).
|
|
28
|
+
private let EXIT_HYSTERESIS_COUNT = 3
|
|
29
29
|
|
|
30
30
|
/// Eddystone monitoring timer interval in seconds.
|
|
31
31
|
private let EDDYSTONE_MONITORING_TICK_INTERVAL: TimeInterval = 2.0
|
|
@@ -67,8 +67,10 @@ public class ExpoBeaconModule: Module {
|
|
|
67
67
|
private var distanceRangingConstraints: [String: CLBeaconIdentityConstraint] = [:]
|
|
68
68
|
// Identifiers currently in "entered" state (used for distance-driven enter/exit)
|
|
69
69
|
private var enteredRegions: Set<String> = []
|
|
70
|
-
// Consecutive miss counter per identifier (for
|
|
70
|
+
// Consecutive miss counter per identifier (for tracking silence; exit now time-based)
|
|
71
71
|
private var missCounters: [String: Int] = [:]
|
|
72
|
+
// Per-identifier timestamp of the last valid ranging reading (for time-based exit detection)
|
|
73
|
+
private var lastSeenTimes: [String: Date] = [:]
|
|
72
74
|
// Hysteresis counters: consecutive readings inside/outside threshold per identifier
|
|
73
75
|
private var enterCounters: [String: Int] = [:]
|
|
74
76
|
private var exitCounters: [String: Int] = [:]
|
|
@@ -105,6 +107,9 @@ public class ExpoBeaconModule: Module {
|
|
|
105
107
|
/// Event level: "all" emits distance + enter/exit/timeout; "events" suppresses distance.
|
|
106
108
|
private var eventLevel: String = "all"
|
|
107
109
|
|
|
110
|
+
/// Seconds of silence after last valid beacon sighting before a miss-based exit fires.
|
|
111
|
+
private var exitTimeoutSeconds: TimeInterval = DEFAULT_EXIT_TIMEOUT_SECONDS
|
|
112
|
+
|
|
108
113
|
/// Distance smoothing (EMA) state per identifier.
|
|
109
114
|
private var smoothedDistances: [String: Double] = [:]
|
|
110
115
|
/// EMA weight for new readings. 0.4 balances responsiveness vs noise rejection.
|
|
@@ -342,12 +347,14 @@ public class ExpoBeaconModule: Module {
|
|
|
342
347
|
var maxDistance: Double? = nil
|
|
343
348
|
var exitDistance: Double? = nil
|
|
344
349
|
var minRssi: Int? = nil
|
|
350
|
+
var exitTimeoutSecs: Double? = nil
|
|
345
351
|
if let dist: Double = options?.get() {
|
|
346
352
|
maxDistance = dist
|
|
347
353
|
} else if let map: [String: Any] = options?.get() {
|
|
348
354
|
maxDistance = map["maxDistance"] as? Double
|
|
349
355
|
exitDistance = map["exitDistance"] as? Double
|
|
350
356
|
minRssi = map["minRssi"] as? Int
|
|
357
|
+
exitTimeoutSecs = map["exitTimeoutSeconds"] as? Double
|
|
351
358
|
if let lvl = map["level"] as? String, lvl == "events" || lvl == "all" {
|
|
352
359
|
self.eventLevel = lvl
|
|
353
360
|
self.defaults.set(lvl, forKey: EVENT_LEVEL_KEY)
|
|
@@ -381,6 +388,11 @@ public class ExpoBeaconModule: Module {
|
|
|
381
388
|
self.sendLoggedEvent("onBeaconError", ["identifier": "", "code": "INVALID_EXIT_DISTANCE", "message": "exitDistance must be greater than or equal to maxDistance"])
|
|
382
389
|
return
|
|
383
390
|
}
|
|
391
|
+
if let t = exitTimeoutSecs, (!t.isFinite || t <= 0) {
|
|
392
|
+
promise.reject("INVALID_EXIT_TIMEOUT", "exitTimeoutSeconds must be a finite number greater than 0")
|
|
393
|
+
self.sendLoggedEvent("onBeaconError", ["identifier": "", "code": "INVALID_EXIT_TIMEOUT", "message": "exitTimeoutSeconds must be a finite number greater than 0"])
|
|
394
|
+
return
|
|
395
|
+
}
|
|
384
396
|
if let dist = maxDistance {
|
|
385
397
|
self.defaults.set(dist, forKey: MAX_DISTANCE_KEY)
|
|
386
398
|
} else {
|
|
@@ -398,6 +410,13 @@ public class ExpoBeaconModule: Module {
|
|
|
398
410
|
self.defaults.removeObject(forKey: MIN_RSSI_KEY)
|
|
399
411
|
self.minRssiThreshold = DEFAULT_MIN_RSSI
|
|
400
412
|
}
|
|
413
|
+
if let t = exitTimeoutSecs {
|
|
414
|
+
self.defaults.set(t, forKey: EXIT_TIMEOUT_SECONDS_KEY)
|
|
415
|
+
self.exitTimeoutSeconds = t
|
|
416
|
+
} else {
|
|
417
|
+
self.defaults.removeObject(forKey: EXIT_TIMEOUT_SECONDS_KEY)
|
|
418
|
+
self.exitTimeoutSeconds = DEFAULT_EXIT_TIMEOUT_SECONDS
|
|
419
|
+
}
|
|
401
420
|
self.defaults.set(true, forKey: IS_MONITORING_KEY)
|
|
402
421
|
self.requestLocationPermission { granted in
|
|
403
422
|
guard granted else {
|
|
@@ -422,7 +441,10 @@ public class ExpoBeaconModule: Module {
|
|
|
422
441
|
self.defaults.removeObject(forKey: MAX_DISTANCE_KEY)
|
|
423
442
|
self.defaults.removeObject(forKey: EXIT_DISTANCE_KEY)
|
|
424
443
|
self.defaults.removeObject(forKey: EVENT_LEVEL_KEY)
|
|
444
|
+
self.defaults.removeObject(forKey: EXIT_TIMEOUT_SECONDS_KEY)
|
|
425
445
|
self.eventLevel = "all"
|
|
446
|
+
self.exitTimeoutSeconds = DEFAULT_EXIT_TIMEOUT_SECONDS
|
|
447
|
+
self.lastSeenTimes.removeAll()
|
|
426
448
|
self.stopRegionMonitoring()
|
|
427
449
|
promise.resolve(nil)
|
|
428
450
|
}
|
|
@@ -541,6 +563,9 @@ public class ExpoBeaconModule: Module {
|
|
|
541
563
|
if let level = self.defaults.string(forKey: EVENT_LEVEL_KEY) {
|
|
542
564
|
result["level"] = level
|
|
543
565
|
}
|
|
566
|
+
if let t = self.defaults.object(forKey: EXIT_TIMEOUT_SECONDS_KEY) as? Double {
|
|
567
|
+
result["exitTimeoutSeconds"] = t
|
|
568
|
+
}
|
|
544
569
|
if let json = self.defaults.string(forKey: NOTIFICATION_CONFIG_KEY),
|
|
545
570
|
let data = json.data(using: .utf8),
|
|
546
571
|
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
@@ -647,6 +672,13 @@ public class ExpoBeaconModule: Module {
|
|
|
647
672
|
// Restore persisted event level (survives app restarts)
|
|
648
673
|
eventLevel = defaults.string(forKey: EVENT_LEVEL_KEY) ?? "all"
|
|
649
674
|
|
|
675
|
+
// Restore persisted exit timeout (survives app restarts)
|
|
676
|
+
if let t = defaults.object(forKey: EXIT_TIMEOUT_SECONDS_KEY) as? Double, t > 0 {
|
|
677
|
+
exitTimeoutSeconds = t
|
|
678
|
+
} else {
|
|
679
|
+
exitTimeoutSeconds = DEFAULT_EXIT_TIMEOUT_SECONDS
|
|
680
|
+
}
|
|
681
|
+
|
|
650
682
|
let beacons = loadPairedBeaconsRaw()
|
|
651
683
|
|
|
652
684
|
// CLLocationManager supports a maximum of 20 monitored regions.
|
|
@@ -708,6 +740,7 @@ public class ExpoBeaconModule: Module {
|
|
|
708
740
|
distanceRangingConstraints.removeAll()
|
|
709
741
|
enteredRegions.removeAll()
|
|
710
742
|
missCounters.removeAll()
|
|
743
|
+
lastSeenTimes.removeAll()
|
|
711
744
|
enterCounters.removeAll()
|
|
712
745
|
exitCounters.removeAll()
|
|
713
746
|
smoothedDistances.removeAll()
|
|
@@ -1261,14 +1294,23 @@ public class ExpoBeaconModule: Module {
|
|
|
1261
1294
|
|
|
1262
1295
|
// Not seen recently — reset exit counter (miss counter handles exit
|
|
1263
1296
|
// separately) but preserve enter counter so that background BLE
|
|
1264
|
-
// throttling gaps don't force re-accumulating
|
|
1297
|
+
// throttling gaps don't force re-accumulating ENTER_HYSTERESIS_COUNT reads.
|
|
1265
1298
|
eddystoneExitCounters[identifier] = 0
|
|
1266
1299
|
guard eddystoneEnteredRegions.contains(identifier) else { continue }
|
|
1267
1300
|
|
|
1268
1301
|
let count = (eddystoneMissCounters[identifier] ?? 0) + 1
|
|
1269
1302
|
eddystoneMissCounters[identifier] = count
|
|
1270
1303
|
|
|
1271
|
-
|
|
1304
|
+
// Fire exit only after silence exceeds the configured exitTimeoutSeconds.
|
|
1305
|
+
// eddystoneLatestSeen already tracks Date of last sighting for this identifier.
|
|
1306
|
+
let silentLongEnough: Bool
|
|
1307
|
+
if let lastSeen = eddystoneLatestSeen[identifier] {
|
|
1308
|
+
silentLongEnough = now.timeIntervalSince(lastSeen) >= exitTimeoutSeconds
|
|
1309
|
+
} else {
|
|
1310
|
+
silentLongEnough = false
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
if silentLongEnough {
|
|
1272
1314
|
eddystoneEnteredRegions.remove(identifier)
|
|
1273
1315
|
eddystoneMissCounters[identifier] = 0
|
|
1274
1316
|
eddystoneEnterCounters[identifier] = 0
|
|
@@ -1492,7 +1534,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1492
1534
|
if distance <= maxDist {
|
|
1493
1535
|
exitCtrs[identifier] = 0
|
|
1494
1536
|
enterCtrs[identifier] = (enterCtrs[identifier] ?? 0) + 1
|
|
1495
|
-
if !entered.contains(identifier) && (enterCtrs[identifier] ?? 0) >=
|
|
1537
|
+
if !entered.contains(identifier) && (enterCtrs[identifier] ?? 0) >= ENTER_HYSTERESIS_COUNT {
|
|
1496
1538
|
entered.insert(identifier)
|
|
1497
1539
|
enterCtrs[identifier] = 0
|
|
1498
1540
|
return .enter
|
|
@@ -1500,7 +1542,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1500
1542
|
} else if distance > exitDist {
|
|
1501
1543
|
enterCtrs[identifier] = 0
|
|
1502
1544
|
exitCtrs[identifier] = (exitCtrs[identifier] ?? 0) + 1
|
|
1503
|
-
if entered.contains(identifier) && (exitCtrs[identifier] ?? 0) >=
|
|
1545
|
+
if entered.contains(identifier) && (exitCtrs[identifier] ?? 0) >= EXIT_HYSTERESIS_COUNT {
|
|
1504
1546
|
entered.remove(identifier)
|
|
1505
1547
|
exitCtrs[identifier] = 0
|
|
1506
1548
|
return .exit
|
|
@@ -1512,7 +1554,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1512
1554
|
}
|
|
1513
1555
|
} else {
|
|
1514
1556
|
enterCtrs[identifier] = (enterCtrs[identifier] ?? 0) + 1
|
|
1515
|
-
if !entered.contains(identifier) && (enterCtrs[identifier] ?? 0) >=
|
|
1557
|
+
if !entered.contains(identifier) && (enterCtrs[identifier] ?? 0) >= ENTER_HYSTERESIS_COUNT {
|
|
1516
1558
|
entered.insert(identifier)
|
|
1517
1559
|
enterCtrs[identifier] = 0
|
|
1518
1560
|
return .enter
|
|
@@ -1574,8 +1616,9 @@ public class ExpoBeaconModule: Module {
|
|
|
1574
1616
|
let validBeacon = beacons.first(where: { $0.accuracy >= 0 && $0.rssi >= minRssiThreshold })
|
|
1575
1617
|
|
|
1576
1618
|
if let beacon = validBeacon {
|
|
1577
|
-
// Got a valid reading — reset miss counter
|
|
1619
|
+
// Got a valid reading — reset miss counter and record sighting time
|
|
1578
1620
|
missCounters[identifier] = 0
|
|
1621
|
+
lastSeenTimes[identifier] = Date()
|
|
1579
1622
|
// Valid BLE reading — reset inactivity timer.
|
|
1580
1623
|
rescheduleBeaconInactivity(identifier: identifier, beacon: beacon)
|
|
1581
1624
|
|
|
@@ -1587,7 +1630,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1587
1630
|
// Enter/exit synthesis with hysteresis — always applied.
|
|
1588
1631
|
// When maxDistance is set, distance thresholds control transitions.
|
|
1589
1632
|
// When maxDistance is nil, pure presence-based hysteresis is used
|
|
1590
|
-
// (
|
|
1633
|
+
// (ENTER_HYSTERESIS_COUNT consecutive readings to confirm enter).
|
|
1591
1634
|
let maxDist = self.defaults.object(forKey: MAX_DISTANCE_KEY) as? Double
|
|
1592
1635
|
let exitDist = self.defaults.object(forKey: EXIT_DISTANCE_KEY) as? Double
|
|
1593
1636
|
|
|
@@ -1626,16 +1669,19 @@ public class ExpoBeaconModule: Module {
|
|
|
1626
1669
|
} else {
|
|
1627
1670
|
// No valid beacon reading — beacon may have disappeared.
|
|
1628
1671
|
// Preserve enter counter so background accuracy=-1 gaps don't
|
|
1629
|
-
// force re-accumulating
|
|
1672
|
+
// force re-accumulating ENTER_HYSTERESIS_COUNT reads from scratch.
|
|
1630
1673
|
exitCounters[identifier] = 0
|
|
1631
1674
|
let count = (missCounters[identifier] ?? 0) + 1
|
|
1632
1675
|
missCounters[identifier] = count
|
|
1633
1676
|
|
|
1634
|
-
if enteredRegions.contains(identifier)
|
|
1677
|
+
if enteredRegions.contains(identifier),
|
|
1678
|
+
let lastSeen = lastSeenTimes[identifier],
|
|
1679
|
+
Date().timeIntervalSince(lastSeen) >= exitTimeoutSeconds {
|
|
1635
1680
|
enteredRegions.remove(identifier)
|
|
1636
1681
|
missCounters[identifier] = 0
|
|
1637
1682
|
enterCounters[identifier] = 0
|
|
1638
1683
|
exitCounters[identifier] = 0
|
|
1684
|
+
lastSeenTimes.removeValue(forKey: identifier)
|
|
1639
1685
|
smoothedDistances.removeValue(forKey: identifier)
|
|
1640
1686
|
|
|
1641
1687
|
// Look up region info for the exit event payload
|
|
@@ -1670,7 +1716,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1670
1716
|
fileprivate func handleDidEnterRegion(_ region: CLRegion) {
|
|
1671
1717
|
// Region callbacks are suppressed — all enter/exit logic goes through
|
|
1672
1718
|
// ranging-based hysteresis in handleDidRange for consistent behaviour
|
|
1673
|
-
// with
|
|
1719
|
+
// with ENTER_HYSTERESIS_COUNT / EXIT_HYSTERESIS_COUNT, regardless of whether maxDistance is set.
|
|
1674
1720
|
}
|
|
1675
1721
|
|
|
1676
1722
|
fileprivate func handleDidExitRegion(_ region: CLRegion) {
|
package/package.json
CHANGED
package/src/ExpoBeacon.types.ts
CHANGED
|
@@ -130,6 +130,8 @@ export type MonitoringConfig = {
|
|
|
130
130
|
exitDistance?: number;
|
|
131
131
|
minRssi?: number;
|
|
132
132
|
level?: 'all' | 'events';
|
|
133
|
+
/** Seconds after last beacon sighting before an exit event fires. Default: 300. */
|
|
134
|
+
exitTimeoutSeconds?: number;
|
|
133
135
|
notifications?: NotificationConfig;
|
|
134
136
|
};
|
|
135
137
|
|
|
@@ -186,6 +188,13 @@ export type MonitoringOptions = {
|
|
|
186
188
|
* - `'events'`: enter + exit + timeout only (no distance events).
|
|
187
189
|
*/
|
|
188
190
|
level?: 'all' | 'events';
|
|
191
|
+
/**
|
|
192
|
+
* Seconds after last beacon sighting before an exit event fires when the beacon
|
|
193
|
+
* disappears without moving outside the exit distance threshold.
|
|
194
|
+
*
|
|
195
|
+
* Default: 300 (5 minutes). Minimum: 1.
|
|
196
|
+
*/
|
|
197
|
+
exitTimeoutSeconds?: number;
|
|
189
198
|
/** Notification configuration overrides to apply for this monitoring session. */
|
|
190
199
|
notifications?: NotificationConfig;
|
|
191
200
|
};
|