expo-beacon 0.5.0 → 0.5.1
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/README.md +4 -1
- package/android/src/main/java/expo/modules/beacon/BeaconForegroundService.kt +20 -4
- package/android/src/main/java/expo/modules/beacon/ExpoBeaconModule.kt +4 -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 +27 -1
- package/package.json +1 -1
- package/src/ExpoBeacon.types.ts +9 -0
package/README.md
CHANGED
|
@@ -832,6 +832,7 @@ Accepts a `MonitoringOptions` object, a plain `number` (shorthand for `maxDistan
|
|
|
832
832
|
| Property | Type | Default | Description |
|
|
833
833
|
|---|---|---|---|
|
|
834
834
|
| `maxDistance` | `number` | `undefined` | Distance threshold in metres. `onBeaconEnter` / `onEddystoneEnter` only fires when measured distance ≤ this value. `onBeaconExit` / `onEddystoneExit` always fires. Omit to disable filtering. |
|
|
835
|
+
| `exitDistance` | `number` | `maxDistance + min(maxDistance × 0.5, 2.5)` | Distance in metres at which exit events fire. Must be ≥ `maxDistance`. Creates a hysteresis band between enter and exit thresholds to prevent rapid toggling near the boundary. Only used when `maxDistance` is set. |
|
|
835
836
|
| `notifications` | `NotificationConfig` | `undefined` | Notification overrides for this session (persisted). |
|
|
836
837
|
|
|
837
838
|
**What happens on each platform**:
|
|
@@ -847,9 +848,10 @@ Accepts a `MonitoringOptions` object, a plain `number` (shorthand for `maxDistan
|
|
|
847
848
|
// Shorthand — just a distance threshold
|
|
848
849
|
await ExpoBeacon.startMonitoring(5);
|
|
849
850
|
|
|
850
|
-
// Full options
|
|
851
|
+
// Full options with custom exit threshold
|
|
851
852
|
await ExpoBeacon.startMonitoring({
|
|
852
853
|
maxDistance: 10,
|
|
854
|
+
exitDistance: 15, // Exit fires when distance exceeds 15m
|
|
853
855
|
notifications: {
|
|
854
856
|
beaconEvents: {
|
|
855
857
|
enterTitle: "Welcome!",
|
|
@@ -1181,6 +1183,7 @@ Passed to `startMonitoring()`.
|
|
|
1181
1183
|
```ts
|
|
1182
1184
|
type MonitoringOptions = {
|
|
1183
1185
|
maxDistance?: number;
|
|
1186
|
+
exitDistance?: number;
|
|
1184
1187
|
notifications?: NotificationConfig;
|
|
1185
1188
|
};
|
|
1186
1189
|
```
|
|
@@ -31,6 +31,7 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
31
31
|
|
|
32
32
|
// Distance filtering
|
|
33
33
|
@Volatile private var maxDistance: Double? = null
|
|
34
|
+
@Volatile private var exitDistance: Double? = null
|
|
34
35
|
private val rangingRegions = java.util.concurrent.CopyOnWriteArraySet<Region>()
|
|
35
36
|
private val enteredRegions = java.util.concurrent.CopyOnWriteArraySet<String>()
|
|
36
37
|
|
|
@@ -101,9 +102,10 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
override fun onBeaconServiceConnect() {
|
|
104
|
-
// Read max distance from options prefs
|
|
105
|
+
// Read max distance and exit distance from options prefs
|
|
105
106
|
val optPrefs = getSharedPreferences(MONITORING_OPTIONS_PREFS, Context.MODE_PRIVATE)
|
|
106
107
|
maxDistance = optPrefs.getString("max_distance", null)?.toDoubleOrNull()
|
|
108
|
+
exitDistance = optPrefs.getString("exit_distance", null)?.toDoubleOrNull()
|
|
107
109
|
|
|
108
110
|
beaconManager.addMonitorNotifier(monitorNotifier)
|
|
109
111
|
beaconManager.addRangeNotifier(rangeNotifier)
|
|
@@ -296,6 +298,15 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
296
298
|
|
|
297
299
|
private enum class HysteresisAction { NONE, ENTER, EXIT }
|
|
298
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Computes the effective exit distance from maxDistance and an optional explicit exitDistance.
|
|
303
|
+
* Default: maxDistance + min(maxDistance × 0.5, 2.5).
|
|
304
|
+
*/
|
|
305
|
+
private fun effectiveExitDistance(maxDist: Double): Double {
|
|
306
|
+
exitDistance?.let { return it }
|
|
307
|
+
return maxDist + minOf(maxDist * 0.5, 2.5)
|
|
308
|
+
}
|
|
309
|
+
|
|
299
310
|
/**
|
|
300
311
|
* Evaluate distance-based enter/exit with hysteresis counters.
|
|
301
312
|
* Must be called within synchronized(distanceLock).
|
|
@@ -306,8 +317,9 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
306
317
|
distance: Double,
|
|
307
318
|
maxDist: Double
|
|
308
319
|
): HysteresisAction {
|
|
320
|
+
val exitDist = effectiveExitDistance(maxDist)
|
|
309
321
|
if (distance <= maxDist) {
|
|
310
|
-
// Inside threshold
|
|
322
|
+
// Inside enter threshold
|
|
311
323
|
exitCounters[regionId] = 0
|
|
312
324
|
val count = (enterCounters[regionId] ?: 0) + 1
|
|
313
325
|
enterCounters[regionId] = count
|
|
@@ -315,8 +327,8 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
315
327
|
enterCounters[regionId] = 0
|
|
316
328
|
return HysteresisAction.ENTER
|
|
317
329
|
}
|
|
318
|
-
} else {
|
|
319
|
-
// Outside threshold
|
|
330
|
+
} else if (distance > exitDist) {
|
|
331
|
+
// Outside exit threshold
|
|
320
332
|
enterCounters[regionId] = 0
|
|
321
333
|
val count = (exitCounters[regionId] ?: 0) + 1
|
|
322
334
|
exitCounters[regionId] = count
|
|
@@ -324,6 +336,10 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
324
336
|
exitCounters[regionId] = 0
|
|
325
337
|
return HysteresisAction.EXIT
|
|
326
338
|
}
|
|
339
|
+
} else {
|
|
340
|
+
// In the hysteresis band (maxDist < distance <= exitDist) — do nothing
|
|
341
|
+
enterCounters[regionId] = 0
|
|
342
|
+
exitCounters[regionId] = 0
|
|
327
343
|
}
|
|
328
344
|
return HysteresisAction.NONE
|
|
329
345
|
}
|
|
@@ -264,12 +264,14 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
264
264
|
return@AsyncFunction
|
|
265
265
|
}
|
|
266
266
|
var maxDistance: Double? = null
|
|
267
|
+
var exitDistance: Double? = null
|
|
267
268
|
when (options) {
|
|
268
269
|
is Double -> maxDistance = options
|
|
269
270
|
is Map<*, *> -> {
|
|
270
271
|
@Suppress("UNCHECKED_CAST")
|
|
271
272
|
val map = options as Map<String, Any?>
|
|
272
273
|
maxDistance = (map["maxDistance"] as? Number)?.toDouble()
|
|
274
|
+
exitDistance = (map["exitDistance"] as? Number)?.toDouble()
|
|
273
275
|
val notifications = map["notifications"]
|
|
274
276
|
if (notifications is Map<*, *>) {
|
|
275
277
|
@Suppress("UNCHECKED_CAST")
|
|
@@ -282,6 +284,8 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
282
284
|
.edit().apply {
|
|
283
285
|
if (maxDistance != null) putString("max_distance", maxDistance.toString())
|
|
284
286
|
else remove("max_distance")
|
|
287
|
+
if (exitDistance != null) putString("exit_distance", exitDistance.toString())
|
|
288
|
+
else remove("exit_distance")
|
|
285
289
|
}.apply()
|
|
286
290
|
// Verify we have the permissions needed for background monitoring
|
|
287
291
|
val hasLocation = ContextCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
|
@@ -92,6 +92,15 @@ export type MonitoringOptions = {
|
|
|
92
92
|
* Exit events are always emitted when the region is lost.
|
|
93
93
|
*/
|
|
94
94
|
maxDistance?: number;
|
|
95
|
+
/**
|
|
96
|
+
* Distance in metres at which exit events fire (must be ≥ maxDistance).
|
|
97
|
+
* Creates a hysteresis band between enter and exit thresholds to prevent
|
|
98
|
+
* rapid toggling near the boundary.
|
|
99
|
+
*
|
|
100
|
+
* Default when omitted: `maxDistance + min(maxDistance × 0.5, 2.5)`.
|
|
101
|
+
* Only used when `maxDistance` is set.
|
|
102
|
+
*/
|
|
103
|
+
exitDistance?: number;
|
|
95
104
|
/** Notification configuration overrides to apply for this monitoring session. */
|
|
96
105
|
notifications?: NotificationConfig;
|
|
97
106
|
};
|
|
@@ -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":"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"}
|
|
@@ -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};\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};\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};\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};\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 /**\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/** 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 /** 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};\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};\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};\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};\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 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};\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};\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};\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};\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};\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 /**\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/** 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 /** 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};\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};\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};\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};\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 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};\r\n"]}
|
|
@@ -8,6 +8,7 @@ private let PAIRED_BEACONS_KEY = "expo.beacon.paired"
|
|
|
8
8
|
private let PAIRED_EDDYSTONES_KEY = "expo.beacon.paired_eddystones"
|
|
9
9
|
private let IS_MONITORING_KEY = "expo.beacon.is_monitoring"
|
|
10
10
|
private let MAX_DISTANCE_KEY = "expo.beacon.max_distance"
|
|
11
|
+
private let EXIT_DISTANCE_KEY = "expo.beacon.exit_distance"
|
|
11
12
|
private let NOTIFICATION_CONFIG_KEY = "expo.beacon.notification_config"
|
|
12
13
|
|
|
13
14
|
/// Number of consecutive ranging misses before emitting a distance-based exit event.
|
|
@@ -282,10 +283,12 @@ public class ExpoBeaconModule: Module {
|
|
|
282
283
|
|
|
283
284
|
AsyncFunction("startMonitoring") { (options: Either<Double, [String: Any]>?, promise: Promise) in
|
|
284
285
|
var maxDistance: Double? = nil
|
|
286
|
+
var exitDistance: Double? = nil
|
|
285
287
|
if let dist: Double = options?.get() {
|
|
286
288
|
maxDistance = dist
|
|
287
289
|
} else if let map: [String: Any] = options?.get() {
|
|
288
290
|
maxDistance = map["maxDistance"] as? Double
|
|
291
|
+
exitDistance = map["exitDistance"] as? Double
|
|
289
292
|
if let notifications = map["notifications"] as? [String: Any],
|
|
290
293
|
let data = try? JSONSerialization.data(withJSONObject: notifications),
|
|
291
294
|
let json = String(data: data, encoding: .utf8) {
|
|
@@ -297,6 +300,11 @@ public class ExpoBeaconModule: Module {
|
|
|
297
300
|
} else {
|
|
298
301
|
self.defaults.removeObject(forKey: MAX_DISTANCE_KEY)
|
|
299
302
|
}
|
|
303
|
+
if let exitDist = exitDistance {
|
|
304
|
+
self.defaults.set(exitDist, forKey: EXIT_DISTANCE_KEY)
|
|
305
|
+
} else {
|
|
306
|
+
self.defaults.removeObject(forKey: EXIT_DISTANCE_KEY)
|
|
307
|
+
}
|
|
300
308
|
self.defaults.set(true, forKey: IS_MONITORING_KEY)
|
|
301
309
|
self.requestLocationPermission(requireAlways: true) { granted in
|
|
302
310
|
guard granted else {
|
|
@@ -312,6 +320,7 @@ public class ExpoBeaconModule: Module {
|
|
|
312
320
|
AsyncFunction("stopMonitoring") { (promise: Promise) in
|
|
313
321
|
self.defaults.set(false, forKey: IS_MONITORING_KEY)
|
|
314
322
|
self.defaults.removeObject(forKey: MAX_DISTANCE_KEY)
|
|
323
|
+
self.defaults.removeObject(forKey: EXIT_DISTANCE_KEY)
|
|
315
324
|
self.stopRegionMonitoring()
|
|
316
325
|
promise.resolve(nil)
|
|
317
326
|
}
|
|
@@ -674,10 +683,12 @@ public class ExpoBeaconModule: Module {
|
|
|
674
683
|
|
|
675
684
|
// Distance-driven enter/exit with hysteresis
|
|
676
685
|
let maxDist = self.defaults.object(forKey: MAX_DISTANCE_KEY) as? Double
|
|
686
|
+
let exitDist = self.defaults.object(forKey: EXIT_DISTANCE_KEY) as? Double
|
|
677
687
|
let action = evaluateDistanceHysteresis(
|
|
678
688
|
identifier: identifier,
|
|
679
689
|
distance: distance,
|
|
680
690
|
maxDistance: maxDist,
|
|
691
|
+
exitDistance: exitDist,
|
|
681
692
|
entered: &eddystoneEnteredRegions,
|
|
682
693
|
enterCtrs: &eddystoneEnterCounters,
|
|
683
694
|
exitCtrs: &eddystoneExitCounters
|
|
@@ -921,17 +932,26 @@ public class ExpoBeaconModule: Module {
|
|
|
921
932
|
case none, enter, exit
|
|
922
933
|
}
|
|
923
934
|
|
|
935
|
+
/// Computes the effective exit distance from maxDistance and an optional explicit exitDistance.
|
|
936
|
+
/// Default: maxDistance + min(maxDistance × 0.5, 2.5).
|
|
937
|
+
private static func effectiveExitDistance(maxDistance: Double, exitDistance: Double?) -> Double {
|
|
938
|
+
if let explicit = exitDistance { return explicit }
|
|
939
|
+
return maxDistance + min(maxDistance * 0.5, 2.5)
|
|
940
|
+
}
|
|
941
|
+
|
|
924
942
|
/// Shared distance-based enter/exit evaluation with hysteresis.
|
|
925
943
|
/// Used by both iBeacon (handleDidRange) and Eddystone (handleEddystoneDiscovery) paths.
|
|
926
944
|
private func evaluateDistanceHysteresis(
|
|
927
945
|
identifier: String,
|
|
928
946
|
distance: Double,
|
|
929
947
|
maxDistance: Double?,
|
|
948
|
+
exitDistance: Double?,
|
|
930
949
|
entered: inout Set<String>,
|
|
931
950
|
enterCtrs: inout [String: Int],
|
|
932
951
|
exitCtrs: inout [String: Int]
|
|
933
952
|
) -> HysteresisAction {
|
|
934
953
|
if let maxDist = maxDistance {
|
|
954
|
+
let exitDist = Self.effectiveExitDistance(maxDistance: maxDist, exitDistance: exitDistance)
|
|
935
955
|
if distance <= maxDist {
|
|
936
956
|
exitCtrs[identifier] = 0
|
|
937
957
|
enterCtrs[identifier] = (enterCtrs[identifier] ?? 0) + 1
|
|
@@ -940,7 +960,7 @@ public class ExpoBeaconModule: Module {
|
|
|
940
960
|
enterCtrs[identifier] = 0
|
|
941
961
|
return .enter
|
|
942
962
|
}
|
|
943
|
-
} else {
|
|
963
|
+
} else if distance > exitDist {
|
|
944
964
|
enterCtrs[identifier] = 0
|
|
945
965
|
exitCtrs[identifier] = (exitCtrs[identifier] ?? 0) + 1
|
|
946
966
|
if entered.contains(identifier) && (exitCtrs[identifier] ?? 0) >= HYSTERESIS_COUNT {
|
|
@@ -948,6 +968,10 @@ public class ExpoBeaconModule: Module {
|
|
|
948
968
|
exitCtrs[identifier] = 0
|
|
949
969
|
return .exit
|
|
950
970
|
}
|
|
971
|
+
} else {
|
|
972
|
+
// In the hysteresis band (maxDist < distance <= exitDist) — do nothing
|
|
973
|
+
enterCtrs[identifier] = 0
|
|
974
|
+
exitCtrs[identifier] = 0
|
|
951
975
|
}
|
|
952
976
|
} else {
|
|
953
977
|
enterCtrs[identifier] = (enterCtrs[identifier] ?? 0) + 1
|
|
@@ -1020,10 +1044,12 @@ public class ExpoBeaconModule: Module {
|
|
|
1020
1044
|
|
|
1021
1045
|
// Distance-driven enter/exit synthesis with hysteresis
|
|
1022
1046
|
if let maxDist = self.defaults.object(forKey: MAX_DISTANCE_KEY) as? Double {
|
|
1047
|
+
let exitDist = self.defaults.object(forKey: EXIT_DISTANCE_KEY) as? Double
|
|
1023
1048
|
let action = evaluateDistanceHysteresis(
|
|
1024
1049
|
identifier: identifier,
|
|
1025
1050
|
distance: beacon.accuracy,
|
|
1026
1051
|
maxDistance: maxDist,
|
|
1052
|
+
exitDistance: exitDist,
|
|
1027
1053
|
entered: &enteredRegions,
|
|
1028
1054
|
enterCtrs: &enterCounters,
|
|
1029
1055
|
exitCtrs: &exitCounters
|
package/package.json
CHANGED
package/src/ExpoBeacon.types.ts
CHANGED
|
@@ -100,6 +100,15 @@ export type MonitoringOptions = {
|
|
|
100
100
|
* Exit events are always emitted when the region is lost.
|
|
101
101
|
*/
|
|
102
102
|
maxDistance?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Distance in metres at which exit events fire (must be ≥ maxDistance).
|
|
105
|
+
* Creates a hysteresis band between enter and exit thresholds to prevent
|
|
106
|
+
* rapid toggling near the boundary.
|
|
107
|
+
*
|
|
108
|
+
* Default when omitted: `maxDistance + min(maxDistance × 0.5, 2.5)`.
|
|
109
|
+
* Only used when `maxDistance` is set.
|
|
110
|
+
*/
|
|
111
|
+
exitDistance?: number;
|
|
103
112
|
/** Notification configuration overrides to apply for this monitoring session. */
|
|
104
113
|
notifications?: NotificationConfig;
|
|
105
114
|
};
|