expo-beacon 0.6.11 → 0.6.13
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.
|
@@ -288,9 +288,10 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
288
288
|
missCounters.remove(region.uniqueId)
|
|
289
289
|
}
|
|
290
290
|
if (wasEntered) {
|
|
291
|
-
cancelTimeout(region.uniqueId)
|
|
292
291
|
sendBeaconBroadcast(region, "exit", -1.0)
|
|
293
292
|
showEnterExitNotification(region, "exit")
|
|
293
|
+
// OS-level exit safety net — start the timeout clock.
|
|
294
|
+
scheduleTimeoutIfConfigured(region)
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
|
|
@@ -329,13 +330,15 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
329
330
|
enteredRegions.add(region.uniqueId)
|
|
330
331
|
sendBeaconBroadcast(region, "enter", beacon.distance, beacon.rssi)
|
|
331
332
|
showEnterExitNotification(region, "enter")
|
|
332
|
-
|
|
333
|
+
// Beacon returned — cancel any running timeout timer.
|
|
334
|
+
cancelTimeout(region.uniqueId)
|
|
333
335
|
}
|
|
334
336
|
HysteresisAction.EXIT -> {
|
|
335
|
-
cancelTimeout(region.uniqueId)
|
|
336
337
|
enteredRegions.remove(region.uniqueId)
|
|
337
338
|
sendBeaconBroadcast(region, "exit", beacon.distance, beacon.rssi)
|
|
338
339
|
showEnterExitNotification(region, "exit")
|
|
340
|
+
// Beacon left — start the timeout clock.
|
|
341
|
+
scheduleTimeoutIfConfigured(region)
|
|
339
342
|
}
|
|
340
343
|
HysteresisAction.NONE -> {}
|
|
341
344
|
}
|
|
@@ -348,18 +351,14 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
348
351
|
missCounters[region.uniqueId] = count
|
|
349
352
|
|
|
350
353
|
if (enteredRegions.contains(region.uniqueId) && count >= EXIT_MISS_THRESHOLD) {
|
|
351
|
-
// Do NOT cancel the timeout here. A miss-based exit is triggered by BLE
|
|
352
|
-
// scan gaps (unreliable signal disappearance), not a confirmed physical
|
|
353
|
-
// departure. Cancelling the timeout here would prevent it from ever firing
|
|
354
|
-
// when the configured timeout (e.g. 25 s) exceeds the miss window (~21 s).
|
|
355
|
-
// The timeout runnable fires unconditionally; distance-based exits still
|
|
356
|
-
// call cancelTimeout() reliably when the beacon moves out of range.
|
|
357
354
|
enteredRegions.remove(region.uniqueId)
|
|
358
355
|
missCounters[region.uniqueId] = 0
|
|
359
356
|
enterCounters[region.uniqueId] = 0
|
|
360
357
|
exitCounters[region.uniqueId] = 0
|
|
361
358
|
sendBeaconBroadcast(region, "exit", -1.0)
|
|
362
359
|
showEnterExitNotification(region, "exit")
|
|
360
|
+
// Beacon disappeared — start the timeout clock.
|
|
361
|
+
scheduleTimeoutIfConfigured(region)
|
|
363
362
|
}
|
|
364
363
|
}
|
|
365
364
|
}
|
|
@@ -459,14 +458,10 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
459
458
|
|
|
460
459
|
private fun scheduleTimeoutIfConfigured(region: Region) {
|
|
461
460
|
val seconds = beaconTimeouts[region.uniqueId] ?: return
|
|
462
|
-
// Cancel any existing timer
|
|
461
|
+
// Cancel any existing timer so each exit resets the clock.
|
|
463
462
|
cancelTimeout(region.uniqueId)
|
|
464
463
|
val runnable = Runnable {
|
|
465
464
|
timeoutRunnables.remove(region.uniqueId)
|
|
466
|
-
// Fire unconditionally. A miss-based exit may have cleared enteredRegions before
|
|
467
|
-
// the timer elapsed (BLE gaps can cause false exits at ~21 s), but the beacon
|
|
468
|
-
// may still be physically present. Distance-based exits call cancelTimeout() so
|
|
469
|
-
// this runnable is never queued when the beacon has genuinely moved away.
|
|
470
465
|
sendBeaconBroadcast(region, "timeout", -1.0)
|
|
471
466
|
}
|
|
472
467
|
timeoutRunnables[region.uniqueId] = runnable
|
|
@@ -848,9 +848,10 @@ public class ExpoBeaconModule: Module {
|
|
|
848
848
|
"rssi": beaconRssi
|
|
849
849
|
])
|
|
850
850
|
postBeaconNotification(identifier: identifier, eventType: "enter")
|
|
851
|
-
|
|
852
|
-
case .exit:
|
|
851
|
+
// Beacon returned — cancel any running timeout timer.
|
|
853
852
|
cancelEddystoneTimeout(identifier: identifier)
|
|
853
|
+
case .exit:
|
|
854
|
+
smoothedDistances.removeValue(forKey: identifier)
|
|
854
855
|
sendLoggedEvent("onEddystoneExit", [
|
|
855
856
|
"identifier": identifier,
|
|
856
857
|
"namespace": ns,
|
|
@@ -860,6 +861,8 @@ public class ExpoBeaconModule: Module {
|
|
|
860
861
|
"rssi": beaconRssi
|
|
861
862
|
])
|
|
862
863
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
864
|
+
// Beacon left — start the timeout clock.
|
|
865
|
+
scheduleEddystoneTimeout(identifier: identifier, namespace: ns, instance: inst)
|
|
863
866
|
case .none:
|
|
864
867
|
break
|
|
865
868
|
}
|
|
@@ -1078,7 +1081,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1078
1081
|
eddystoneEnterCounters[identifier] = 0
|
|
1079
1082
|
eddystoneExitCounters[identifier] = 0
|
|
1080
1083
|
eddystoneLatestSeen.removeValue(forKey: identifier)
|
|
1081
|
-
|
|
1084
|
+
smoothedDistances.removeValue(forKey: identifier)
|
|
1082
1085
|
|
|
1083
1086
|
let ns = paired["namespace"] as? String ?? ""
|
|
1084
1087
|
let inst = paired["instance"] as? String ?? ""
|
|
@@ -1091,16 +1094,17 @@ public class ExpoBeaconModule: Module {
|
|
|
1091
1094
|
]
|
|
1092
1095
|
sendLoggedEvent("onEddystoneExit", params)
|
|
1093
1096
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1097
|
+
// Beacon disappeared — start the timeout clock.
|
|
1098
|
+
scheduleEddystoneTimeout(identifier: identifier, namespace: ns, instance: inst)
|
|
1094
1099
|
}
|
|
1095
1100
|
}
|
|
1096
1101
|
}
|
|
1097
1102
|
|
|
1098
1103
|
// MARK: - Timeout timer helpers
|
|
1099
1104
|
|
|
1100
|
-
private func scheduleBeaconTimeout(identifier: String, beacon: CLBeacon) {
|
|
1101
|
-
// Cancel any existing timer
|
|
1102
|
-
|
|
1103
|
-
beaconTimeoutTimers.removeValue(forKey: identifier)
|
|
1105
|
+
private func scheduleBeaconTimeout(identifier: String, beacon: CLBeacon? = nil, region: CLBeaconRegion? = nil) {
|
|
1106
|
+
// Cancel any existing timer so each exit resets the clock.
|
|
1107
|
+
cancelBeaconTimeout(identifier: identifier)
|
|
1104
1108
|
|
|
1105
1109
|
let paired = loadPairedBeaconsRaw().first { ($0["identifier"] as? String) == identifier }
|
|
1106
1110
|
guard let seconds = paired?["timeoutSeconds"] as? Int, seconds > 0 else { return }
|
|
@@ -1108,11 +1112,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1108
1112
|
let work = DispatchWorkItem { [weak self] in
|
|
1109
1113
|
guard let self = self else { return }
|
|
1110
1114
|
self.beaconTimeoutTimers.removeValue(forKey: identifier)
|
|
1111
|
-
|
|
1112
|
-
// the timer elapsed (ranging gaps can cause false exits), but the beacon may still
|
|
1113
|
-
// be physically present. Distance-based exits call cancelBeaconTimeout() so this
|
|
1114
|
-
// work item is cancelled before it runs on genuine out-of-range departures.
|
|
1115
|
-
self.sendLoggedEvent("onBeaconTimeout", self.makeBeaconEventParams(identifier: identifier, beacon: beacon))
|
|
1115
|
+
self.sendLoggedEvent("onBeaconTimeout", self.makeBeaconEventParams(identifier: identifier, beacon: beacon, region: region))
|
|
1116
1116
|
}
|
|
1117
1117
|
beaconTimeoutTimers[identifier] = work
|
|
1118
1118
|
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(seconds), execute: work)
|
|
@@ -1123,8 +1123,8 @@ public class ExpoBeaconModule: Module {
|
|
|
1123
1123
|
}
|
|
1124
1124
|
|
|
1125
1125
|
private func scheduleEddystoneTimeout(identifier: String, namespace: String, instance: String) {
|
|
1126
|
-
|
|
1127
|
-
|
|
1126
|
+
// Cancel any existing timer so each exit resets the clock.
|
|
1127
|
+
cancelEddystoneTimeout(identifier: identifier)
|
|
1128
1128
|
|
|
1129
1129
|
let paired = loadPairedEddystonesRaw().first { ($0["identifier"] as? String) == identifier }
|
|
1130
1130
|
guard let seconds = paired?["timeoutSeconds"] as? Int, seconds > 0 else { return }
|
|
@@ -1132,7 +1132,6 @@ public class ExpoBeaconModule: Module {
|
|
|
1132
1132
|
let work = DispatchWorkItem { [weak self] in
|
|
1133
1133
|
guard let self = self else { return }
|
|
1134
1134
|
self.eddystoneTimeoutTimers.removeValue(forKey: identifier)
|
|
1135
|
-
// Fire unconditionally — same reason as iBeacon timeout.
|
|
1136
1135
|
self.sendLoggedEvent("onEddystoneTimeout", [
|
|
1137
1136
|
"identifier": identifier,
|
|
1138
1137
|
"namespace": namespace,
|
|
@@ -1197,16 +1196,21 @@ public class ExpoBeaconModule: Module {
|
|
|
1197
1196
|
// MARK: - Distance smoothing + enter/exit hysteresis
|
|
1198
1197
|
|
|
1199
1198
|
/// Apply exponential moving average (EMA) smoothing to a raw distance reading.
|
|
1200
|
-
///
|
|
1199
|
+
/// If the reading is a large jump (> DISTANCE_JUMP_FACTOR), resets the EMA to the new
|
|
1200
|
+
/// value instead of rejecting it — this ensures distance events keep flowing when the
|
|
1201
|
+
/// user moves away from a beacon, rather than freezing because the EMA is stuck at the
|
|
1202
|
+
/// old close-range value and every new far-range reading is rejected.
|
|
1201
1203
|
private func smoothDistance(identifier: String, rawDistance: Double) -> Double? {
|
|
1202
1204
|
guard let prev = smoothedDistances[identifier] else {
|
|
1203
1205
|
smoothedDistances[identifier] = rawDistance
|
|
1204
1206
|
return rawDistance
|
|
1205
1207
|
}
|
|
1206
|
-
// Jump guard: if the raw value is wildly different,
|
|
1208
|
+
// Jump guard: if the raw value is wildly different, reset EMA to the new reading
|
|
1209
|
+
// so the hysteresis pipeline keeps receiving data and can fire the exit event.
|
|
1207
1210
|
let ratio = prev > 0.001 ? rawDistance / prev : rawDistance
|
|
1208
1211
|
if ratio > Self.DISTANCE_JUMP_FACTOR || (ratio > 0 && ratio < 1.0 / Self.DISTANCE_JUMP_FACTOR) {
|
|
1209
|
-
|
|
1212
|
+
smoothedDistances[identifier] = rawDistance
|
|
1213
|
+
return rawDistance
|
|
1210
1214
|
}
|
|
1211
1215
|
let smoothed = Self.DISTANCE_EMA_ALPHA * rawDistance + (1 - Self.DISTANCE_EMA_ALPHA) * prev
|
|
1212
1216
|
smoothedDistances[identifier] = smoothed
|
|
@@ -1354,11 +1358,14 @@ public class ExpoBeaconModule: Module {
|
|
|
1354
1358
|
case .enter:
|
|
1355
1359
|
sendLoggedEvent("onBeaconEnter", makeBeaconEventParams(identifier: identifier, beacon: beacon, event: "enter"))
|
|
1356
1360
|
postBeaconNotification(identifier: identifier, eventType: "enter")
|
|
1357
|
-
|
|
1358
|
-
case .exit:
|
|
1361
|
+
// Beacon returned — cancel any running timeout timer.
|
|
1359
1362
|
cancelBeaconTimeout(identifier: identifier)
|
|
1363
|
+
case .exit:
|
|
1364
|
+
smoothedDistances.removeValue(forKey: identifier)
|
|
1360
1365
|
sendLoggedEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, beacon: beacon, event: "exit"))
|
|
1361
1366
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1367
|
+
// Beacon left — start the timeout clock.
|
|
1368
|
+
scheduleBeaconTimeout(identifier: identifier, beacon: beacon)
|
|
1362
1369
|
case .none:
|
|
1363
1370
|
break
|
|
1364
1371
|
}
|
|
@@ -1379,16 +1386,14 @@ public class ExpoBeaconModule: Module {
|
|
|
1379
1386
|
missCounters[identifier] = 0
|
|
1380
1387
|
enterCounters[identifier] = 0
|
|
1381
1388
|
exitCounters[identifier] = 0
|
|
1382
|
-
|
|
1383
|
-
// ranging gaps (e.g. accuracy == -1), not a confirmed physical departure.
|
|
1384
|
-
// Cancelling here prevents the timeout from firing when it is longer than
|
|
1385
|
-
// the miss window. Distance-based exits call cancelBeaconTimeout() so the
|
|
1386
|
-
// timer is still cancelled on genuine out-of-range events.
|
|
1389
|
+
smoothedDistances.removeValue(forKey: identifier)
|
|
1387
1390
|
|
|
1388
1391
|
// Look up region info for the exit event payload
|
|
1389
1392
|
let region = monitoredRegions.first { $0.identifier == identifier }
|
|
1390
1393
|
sendLoggedEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, region: region, event: "exit"))
|
|
1391
1394
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1395
|
+
// Beacon disappeared — start the timeout clock.
|
|
1396
|
+
scheduleBeaconTimeout(identifier: identifier, region: region)
|
|
1392
1397
|
}
|
|
1393
1398
|
}
|
|
1394
1399
|
return
|
|
@@ -1429,10 +1434,12 @@ public class ExpoBeaconModule: Module {
|
|
|
1429
1434
|
enterCounters.removeValue(forKey: identifier)
|
|
1430
1435
|
exitCounters.removeValue(forKey: identifier)
|
|
1431
1436
|
missCounters.removeValue(forKey: identifier)
|
|
1437
|
+
smoothedDistances.removeValue(forKey: identifier)
|
|
1432
1438
|
if wasEntered {
|
|
1433
|
-
cancelBeaconTimeout(identifier: identifier)
|
|
1434
1439
|
sendLoggedEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, region: beaconRegion, event: "exit"))
|
|
1435
1440
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1441
|
+
// OS-level exit safety net — start the timeout clock.
|
|
1442
|
+
scheduleBeaconTimeout(identifier: identifier, region: beaconRegion)
|
|
1436
1443
|
}
|
|
1437
1444
|
}
|
|
1438
1445
|
|