expo-beacon 0.6.10 → 0.6.12
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.
|
@@ -459,8 +459,10 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
459
459
|
|
|
460
460
|
private fun scheduleTimeoutIfConfigured(region: Region) {
|
|
461
461
|
val seconds = beaconTimeouts[region.uniqueId] ?: return
|
|
462
|
-
//
|
|
463
|
-
|
|
462
|
+
// If a timer is already running, don't reset it. Miss-based exits clear
|
|
463
|
+
// enteredRegions without cancelling the timer, so a subsequent re-entry
|
|
464
|
+
// must not restart the clock — doing so would defer the timeout indefinitely.
|
|
465
|
+
if (timeoutRunnables.containsKey(region.uniqueId)) return
|
|
464
466
|
val runnable = Runnable {
|
|
465
467
|
timeoutRunnables.remove(region.uniqueId)
|
|
466
468
|
// Fire unconditionally. A miss-based exit may have cleared enteredRegions before
|
|
@@ -850,6 +850,7 @@ public class ExpoBeaconModule: Module {
|
|
|
850
850
|
postBeaconNotification(identifier: identifier, eventType: "enter")
|
|
851
851
|
scheduleEddystoneTimeout(identifier: identifier, namespace: ns, instance: inst)
|
|
852
852
|
case .exit:
|
|
853
|
+
smoothedDistances.removeValue(forKey: identifier)
|
|
853
854
|
cancelEddystoneTimeout(identifier: identifier)
|
|
854
855
|
sendLoggedEvent("onEddystoneExit", [
|
|
855
856
|
"identifier": identifier,
|
|
@@ -1078,7 +1079,8 @@ public class ExpoBeaconModule: Module {
|
|
|
1078
1079
|
eddystoneEnterCounters[identifier] = 0
|
|
1079
1080
|
eddystoneExitCounters[identifier] = 0
|
|
1080
1081
|
eddystoneLatestSeen.removeValue(forKey: identifier)
|
|
1081
|
-
|
|
1082
|
+
smoothedDistances.removeValue(forKey: identifier)
|
|
1083
|
+
// Do NOT cancel the timeout here — same reason as iBeacon miss-based exit.
|
|
1082
1084
|
|
|
1083
1085
|
let ns = paired["namespace"] as? String ?? ""
|
|
1084
1086
|
let inst = paired["instance"] as? String ?? ""
|
|
@@ -1098,9 +1100,10 @@ public class ExpoBeaconModule: Module {
|
|
|
1098
1100
|
// MARK: - Timeout timer helpers
|
|
1099
1101
|
|
|
1100
1102
|
private func scheduleBeaconTimeout(identifier: String, beacon: CLBeacon) {
|
|
1101
|
-
//
|
|
1102
|
-
|
|
1103
|
-
|
|
1103
|
+
// If a timer is already running, don't reset it. Miss-based exits clear
|
|
1104
|
+
// enteredRegions without cancelling the timer, so a subsequent re-entry
|
|
1105
|
+
// must not restart the clock — doing so would defer the timeout indefinitely.
|
|
1106
|
+
guard beaconTimeoutTimers[identifier] == nil else { return }
|
|
1104
1107
|
|
|
1105
1108
|
let paired = loadPairedBeaconsRaw().first { ($0["identifier"] as? String) == identifier }
|
|
1106
1109
|
guard let seconds = paired?["timeoutSeconds"] as? Int, seconds > 0 else { return }
|
|
@@ -1108,8 +1111,10 @@ public class ExpoBeaconModule: Module {
|
|
|
1108
1111
|
let work = DispatchWorkItem { [weak self] in
|
|
1109
1112
|
guard let self = self else { return }
|
|
1110
1113
|
self.beaconTimeoutTimers.removeValue(forKey: identifier)
|
|
1111
|
-
//
|
|
1112
|
-
|
|
1114
|
+
// Fire unconditionally. A miss-based exit may have cleared enteredRegions before
|
|
1115
|
+
// the timer elapsed (ranging gaps can cause false exits), but the beacon may still
|
|
1116
|
+
// be physically present. Distance-based exits call cancelBeaconTimeout() so this
|
|
1117
|
+
// work item is cancelled before it runs on genuine out-of-range departures.
|
|
1113
1118
|
self.sendLoggedEvent("onBeaconTimeout", self.makeBeaconEventParams(identifier: identifier, beacon: beacon))
|
|
1114
1119
|
}
|
|
1115
1120
|
beaconTimeoutTimers[identifier] = work
|
|
@@ -1121,8 +1126,8 @@ public class ExpoBeaconModule: Module {
|
|
|
1121
1126
|
}
|
|
1122
1127
|
|
|
1123
1128
|
private func scheduleEddystoneTimeout(identifier: String, namespace: String, instance: String) {
|
|
1124
|
-
|
|
1125
|
-
eddystoneTimeoutTimers
|
|
1129
|
+
// If a timer is already running, don't reset it — same reason as iBeacon timeout.
|
|
1130
|
+
guard eddystoneTimeoutTimers[identifier] == nil else { return }
|
|
1126
1131
|
|
|
1127
1132
|
let paired = loadPairedEddystonesRaw().first { ($0["identifier"] as? String) == identifier }
|
|
1128
1133
|
guard let seconds = paired?["timeoutSeconds"] as? Int, seconds > 0 else { return }
|
|
@@ -1130,7 +1135,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1130
1135
|
let work = DispatchWorkItem { [weak self] in
|
|
1131
1136
|
guard let self = self else { return }
|
|
1132
1137
|
self.eddystoneTimeoutTimers.removeValue(forKey: identifier)
|
|
1133
|
-
|
|
1138
|
+
// Fire unconditionally — same reason as iBeacon timeout.
|
|
1134
1139
|
self.sendLoggedEvent("onEddystoneTimeout", [
|
|
1135
1140
|
"identifier": identifier,
|
|
1136
1141
|
"namespace": namespace,
|
|
@@ -1195,16 +1200,21 @@ public class ExpoBeaconModule: Module {
|
|
|
1195
1200
|
// MARK: - Distance smoothing + enter/exit hysteresis
|
|
1196
1201
|
|
|
1197
1202
|
/// Apply exponential moving average (EMA) smoothing to a raw distance reading.
|
|
1198
|
-
///
|
|
1203
|
+
/// If the reading is a large jump (> DISTANCE_JUMP_FACTOR), resets the EMA to the new
|
|
1204
|
+
/// value instead of rejecting it — this ensures distance events keep flowing when the
|
|
1205
|
+
/// user moves away from a beacon, rather than freezing because the EMA is stuck at the
|
|
1206
|
+
/// old close-range value and every new far-range reading is rejected.
|
|
1199
1207
|
private func smoothDistance(identifier: String, rawDistance: Double) -> Double? {
|
|
1200
1208
|
guard let prev = smoothedDistances[identifier] else {
|
|
1201
1209
|
smoothedDistances[identifier] = rawDistance
|
|
1202
1210
|
return rawDistance
|
|
1203
1211
|
}
|
|
1204
|
-
// Jump guard: if the raw value is wildly different,
|
|
1212
|
+
// Jump guard: if the raw value is wildly different, reset EMA to the new reading
|
|
1213
|
+
// so the hysteresis pipeline keeps receiving data and can fire the exit event.
|
|
1205
1214
|
let ratio = prev > 0.001 ? rawDistance / prev : rawDistance
|
|
1206
1215
|
if ratio > Self.DISTANCE_JUMP_FACTOR || (ratio > 0 && ratio < 1.0 / Self.DISTANCE_JUMP_FACTOR) {
|
|
1207
|
-
|
|
1216
|
+
smoothedDistances[identifier] = rawDistance
|
|
1217
|
+
return rawDistance
|
|
1208
1218
|
}
|
|
1209
1219
|
let smoothed = Self.DISTANCE_EMA_ALPHA * rawDistance + (1 - Self.DISTANCE_EMA_ALPHA) * prev
|
|
1210
1220
|
smoothedDistances[identifier] = smoothed
|
|
@@ -1354,6 +1364,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1354
1364
|
postBeaconNotification(identifier: identifier, eventType: "enter")
|
|
1355
1365
|
scheduleBeaconTimeout(identifier: identifier, beacon: beacon)
|
|
1356
1366
|
case .exit:
|
|
1367
|
+
smoothedDistances.removeValue(forKey: identifier)
|
|
1357
1368
|
cancelBeaconTimeout(identifier: identifier)
|
|
1358
1369
|
sendLoggedEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, beacon: beacon, event: "exit"))
|
|
1359
1370
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
@@ -1377,7 +1388,12 @@ public class ExpoBeaconModule: Module {
|
|
|
1377
1388
|
missCounters[identifier] = 0
|
|
1378
1389
|
enterCounters[identifier] = 0
|
|
1379
1390
|
exitCounters[identifier] = 0
|
|
1380
|
-
|
|
1391
|
+
smoothedDistances.removeValue(forKey: identifier)
|
|
1392
|
+
// Do NOT cancel the timeout here. A miss-based exit is triggered by
|
|
1393
|
+
// ranging gaps (e.g. accuracy == -1), not a confirmed physical departure.
|
|
1394
|
+
// Cancelling here prevents the timeout from firing when it is longer than
|
|
1395
|
+
// the miss window. Distance-based exits call cancelBeaconTimeout() so the
|
|
1396
|
+
// timer is still cancelled on genuine out-of-range events.
|
|
1381
1397
|
|
|
1382
1398
|
// Look up region info for the exit event payload
|
|
1383
1399
|
let region = monitoredRegions.first { $0.identifier == identifier }
|
|
@@ -1423,6 +1439,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1423
1439
|
enterCounters.removeValue(forKey: identifier)
|
|
1424
1440
|
exitCounters.removeValue(forKey: identifier)
|
|
1425
1441
|
missCounters.removeValue(forKey: identifier)
|
|
1442
|
+
smoothedDistances.removeValue(forKey: identifier)
|
|
1426
1443
|
if wasEntered {
|
|
1427
1444
|
cancelBeaconTimeout(identifier: identifier)
|
|
1428
1445
|
sendLoggedEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, region: beaconRegion, event: "exit"))
|