expo-beacon 0.6.12 → 0.6.14
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/BeaconForegroundService.kt +26 -30
- package/android/src/main/java/expo/modules/beacon/ExpoBeaconModule.kt +3 -0
- package/build/ExpoBeacon.types.d.ts +7 -0
- package/build/ExpoBeacon.types.d.ts.map +1 -1
- package/build/ExpoBeacon.types.js.map +1 -1
- package/build/ExpoBeaconModule.d.ts +1 -1
- package/build/ExpoBeaconModule.js.map +1 -1
- package/ios/ExpoBeaconModule.swift +50 -41
- package/package.json +1 -1
- package/src/ExpoBeacon.types.ts +7 -0
- package/src/ExpoBeaconModule.ts +1 -1
|
@@ -66,6 +66,8 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
66
66
|
private val beaconTimeouts = java.util.concurrent.ConcurrentHashMap<String, Int>()
|
|
67
67
|
private var eventLogger: BeaconEventLogger? = null
|
|
68
68
|
private var apiForwarder: BeaconApiForwarder? = null
|
|
69
|
+
// Event level: "all" emits distance + enter/exit/timeout; "events" suppresses distance.
|
|
70
|
+
@Volatile private var eventLevel: String = "all"
|
|
69
71
|
|
|
70
72
|
override fun onCreate() {
|
|
71
73
|
super.onCreate()
|
|
@@ -125,6 +127,7 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
125
127
|
maxDistance = optPrefs.getString("max_distance", null)?.toDoubleOrNull()
|
|
126
128
|
exitDistance = optPrefs.getString("exit_distance", null)?.toDoubleOrNull()
|
|
127
129
|
minRssiThreshold = optPrefs.getInt("min_rssi", DEFAULT_MIN_RSSI)
|
|
130
|
+
eventLevel = optPrefs.getString("level", "all") ?: "all"
|
|
128
131
|
|
|
129
132
|
beaconManager.addMonitorNotifier(monitorNotifier)
|
|
130
133
|
beaconManager.addRangeNotifier(rangeNotifier)
|
|
@@ -256,6 +259,7 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
256
259
|
|
|
257
260
|
// Distance logging only — emits distance broadcasts. Enter/exit logic lives in rangeNotifier.
|
|
258
261
|
private val distanceLoggingRangeNotifier = RangeNotifier { beacons, region ->
|
|
262
|
+
if (eventLevel != "all") return@RangeNotifier
|
|
259
263
|
if (!monitoredRegionIds.contains(region.uniqueId)) return@RangeNotifier
|
|
260
264
|
val closest = beacons.filter { it.distance >= 0 && it.rssi >= minRssiThreshold }.minByOrNull { it.distance }
|
|
261
265
|
if (closest != null) {
|
|
@@ -288,9 +292,10 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
288
292
|
missCounters.remove(region.uniqueId)
|
|
289
293
|
}
|
|
290
294
|
if (wasEntered) {
|
|
291
|
-
cancelTimeout(region.uniqueId)
|
|
292
295
|
sendBeaconBroadcast(region, "exit", -1.0)
|
|
293
296
|
showEnterExitNotification(region, "exit")
|
|
297
|
+
// OS-level exit safety net — start the timeout clock.
|
|
298
|
+
scheduleTimeoutIfConfigured(region)
|
|
294
299
|
}
|
|
295
300
|
}
|
|
296
301
|
|
|
@@ -316,12 +321,8 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
316
321
|
lastSeenAtMs[region.uniqueId] = SystemClock.elapsedRealtime()
|
|
317
322
|
missCounters[region.uniqueId] = 0
|
|
318
323
|
|
|
319
|
-
// Apply EMA smoothing; jump
|
|
324
|
+
// Apply EMA smoothing; jump resets EMA to the new value
|
|
320
325
|
val smoothed = smoothDistance(region.uniqueId, beacon.distance)
|
|
321
|
-
if (smoothed == null) {
|
|
322
|
-
// Outlier — treat as miss without resetting enter counter
|
|
323
|
-
return@RangeNotifier
|
|
324
|
-
}
|
|
325
326
|
|
|
326
327
|
val action = evaluateDistanceHysteresis(region.uniqueId, smoothed, maxDist)
|
|
327
328
|
when (action) {
|
|
@@ -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
|
}
|
|
@@ -371,18 +370,23 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
371
370
|
|
|
372
371
|
/**
|
|
373
372
|
* Apply exponential moving average (EMA) smoothing to a raw distance reading.
|
|
374
|
-
*
|
|
373
|
+
* If the reading is a large jump (> DISTANCE_JUMP_FACTOR), resets the EMA to the new
|
|
374
|
+
* value instead of rejecting it — this ensures the hysteresis pipeline keeps receiving
|
|
375
|
+
* data and can fire exit events when the user moves away from a beacon, rather than
|
|
376
|
+
* freezing because the EMA is stuck at the old close-range value.
|
|
375
377
|
*/
|
|
376
|
-
private fun smoothDistance(regionId: String, rawDistance: Double): Double
|
|
378
|
+
private fun smoothDistance(regionId: String, rawDistance: Double): Double {
|
|
377
379
|
val prev = smoothedDistances[regionId]
|
|
378
380
|
if (prev == null) {
|
|
379
381
|
smoothedDistances[regionId] = rawDistance
|
|
380
382
|
return rawDistance
|
|
381
383
|
}
|
|
382
|
-
// Jump guard: if the raw value is wildly different,
|
|
384
|
+
// Jump guard: if the raw value is wildly different, reset EMA to the new reading
|
|
385
|
+
// so the hysteresis pipeline keeps receiving data and can fire the exit event.
|
|
383
386
|
val ratio = if (prev > 0.001) rawDistance / prev else rawDistance
|
|
384
387
|
if (ratio > DISTANCE_JUMP_FACTOR || (ratio > 0 && ratio < 1.0 / DISTANCE_JUMP_FACTOR)) {
|
|
385
|
-
|
|
388
|
+
smoothedDistances[regionId] = rawDistance
|
|
389
|
+
return rawDistance
|
|
386
390
|
}
|
|
387
391
|
val smoothed = DISTANCE_EMA_ALPHA * rawDistance + (1 - DISTANCE_EMA_ALPHA) * prev
|
|
388
392
|
smoothedDistances[regionId] = smoothed
|
|
@@ -459,16 +463,10 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
459
463
|
|
|
460
464
|
private fun scheduleTimeoutIfConfigured(region: Region) {
|
|
461
465
|
val seconds = beaconTimeouts[region.uniqueId] ?: return
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
// must not restart the clock — doing so would defer the timeout indefinitely.
|
|
465
|
-
if (timeoutRunnables.containsKey(region.uniqueId)) return
|
|
466
|
+
// Cancel any existing timer so each exit resets the clock.
|
|
467
|
+
cancelTimeout(region.uniqueId)
|
|
466
468
|
val runnable = Runnable {
|
|
467
469
|
timeoutRunnables.remove(region.uniqueId)
|
|
468
|
-
// Fire unconditionally. A miss-based exit may have cleared enteredRegions before
|
|
469
|
-
// the timer elapsed (BLE gaps can cause false exits at ~21 s), but the beacon
|
|
470
|
-
// may still be physically present. Distance-based exits call cancelTimeout() so
|
|
471
|
-
// this runnable is never queued when the beacon has genuinely moved away.
|
|
472
470
|
sendBeaconBroadcast(region, "timeout", -1.0)
|
|
473
471
|
}
|
|
474
472
|
timeoutRunnables[region.uniqueId] = runnable
|
|
@@ -507,10 +505,8 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
507
505
|
}
|
|
508
506
|
monitoringEventName(isEddystone, eventType)?.let { logBeaconEvent(it, params) }
|
|
509
507
|
|
|
510
|
-
// Forward
|
|
511
|
-
|
|
512
|
-
apiForwarder?.forwardEvent(params)
|
|
513
|
-
}
|
|
508
|
+
// Forward all produced events to remote API
|
|
509
|
+
apiForwarder?.forwardEvent(params)
|
|
514
510
|
|
|
515
511
|
val intent = Intent(ACTION_BEACON_EVENT).apply {
|
|
516
512
|
putExtra("identifier", region.uniqueId)
|
|
@@ -282,6 +282,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
282
282
|
var maxDistance: Double? = null
|
|
283
283
|
var exitDistance: Double? = null
|
|
284
284
|
var minRssi: Int? = null
|
|
285
|
+
var level: String = "all"
|
|
285
286
|
when (options) {
|
|
286
287
|
is Double -> maxDistance = options
|
|
287
288
|
is Map<*, *> -> {
|
|
@@ -290,6 +291,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
290
291
|
maxDistance = (map["maxDistance"] as? Number)?.toDouble()
|
|
291
292
|
exitDistance = (map["exitDistance"] as? Number)?.toDouble()
|
|
292
293
|
minRssi = (map["minRssi"] as? Number)?.toInt()
|
|
294
|
+
level = (map["level"] as? String) ?: "all"
|
|
293
295
|
val notifications = map["notifications"]
|
|
294
296
|
if (notifications is Map<*, *>) {
|
|
295
297
|
@Suppress("UNCHECKED_CAST")
|
|
@@ -322,6 +324,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
|
|
|
322
324
|
else remove("exit_distance")
|
|
323
325
|
if (minRssi != null) putInt("min_rssi", minRssi)
|
|
324
326
|
else remove("min_rssi")
|
|
327
|
+
putString("level", level)
|
|
325
328
|
}.apply()
|
|
326
329
|
// Verify we have the permissions needed for background monitoring
|
|
327
330
|
val hasLocation = ContextCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
|
@@ -132,6 +132,13 @@ export type MonitoringOptions = {
|
|
|
132
132
|
* Default: -85. Typical range: -100 (very permissive) to -70 (strict).
|
|
133
133
|
*/
|
|
134
134
|
minRssi?: number;
|
|
135
|
+
/**
|
|
136
|
+
* Controls which event types are emitted, logged, and forwarded to the API.
|
|
137
|
+
*
|
|
138
|
+
* - `'all'` (default): distance + enter + exit + timeout events.
|
|
139
|
+
* - `'events'`: enter + exit + timeout only (no distance events).
|
|
140
|
+
*/
|
|
141
|
+
level?: 'all' | 'events';
|
|
135
142
|
/** Notification configuration overrides to apply for this monitoring session. */
|
|
136
143
|
notifications?: NotificationConfig;
|
|
137
144
|
};
|
|
@@ -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 /** 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 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 /**\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 /**\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 /** 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 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/** 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};\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 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 /**\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 /**\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 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/** 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};\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"]}
|
|
@@ -104,7 +104,7 @@ declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
|
|
|
104
104
|
destroyEventLogs(): void;
|
|
105
105
|
/**
|
|
106
106
|
* Configure a remote API endpoint for native event forwarding.
|
|
107
|
-
* Once set,
|
|
107
|
+
* Once set, beacon events are POSTed directly from native code,
|
|
108
108
|
* ensuring delivery even when the JS bridge is not active (app backgrounded).
|
|
109
109
|
*
|
|
110
110
|
* @param url The API endpoint URL to POST events to.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoBeaconModule.js","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAuKzD,eAAe,mBAAmB,CAAmB,YAAY,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\r\n\r\nimport {\r\n ExpoBeaconModuleEvents,\r\n BeaconScanResult,\r\n EddystoneScanResult,\r\n PairedBeacon,\r\n PairedEddystone,\r\n NotificationConfig,\r\n MonitoringOptions,\r\n EventLogQueryOptions,\r\n EventLogEntry,\r\n} from \"./ExpoBeacon.types\";\r\n\r\ndeclare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {\r\n /**\r\n * Start a one-shot iBeacon scan. Resolves with discovered beacons after scanDuration ms.\r\n *\r\n * Pass one or more UUIDs to scan for specific beacons (uses CoreLocation on iOS).\r\n * On iOS, at least one UUID is required — Apple strips iBeacon data from BLE\r\n * advertisements, making wildcard discovery impossible. When you pass an empty\r\n * array, the module automatically uses UUIDs from paired beacons.\r\n * On Android, pass an empty array to discover all nearby iBeacons.\r\n *\r\n * @param uuids Proximity UUIDs to filter by. Empty/omitted = use paired UUIDs (iOS) or wildcard (Android).\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForBeaconsAsync(\r\n uuids?: string[],\r\n scanDuration?: number,\r\n ): Promise<BeaconScanResult[]>;\r\n\r\n /**\r\n * Start a one-shot Eddystone beacon scan using BLE.\r\n * Discovers Eddystone-UID and Eddystone-URL frames.\r\n *\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForEddystonesAsync(\r\n scanDuration?: number,\r\n ): Promise<EddystoneScanResult[]>;\r\n\r\n /**\r\n * Register a beacon for persistent region monitoring.\r\n */\r\n pairBeacon(\r\n identifier: string,\r\n uuid: string,\r\n major: number,\r\n minor: number,\r\n name?: string,\r\n timeoutSeconds?: number,\r\n ): void;\r\n\r\n /**\r\n * Remove a previously paired beacon.\r\n */\r\n unpairBeacon(identifier: string): void;\r\n\r\n /**\r\n * Return all currently paired beacons.\r\n */\r\n getPairedBeacons(): PairedBeacon[];\r\n\r\n /**\r\n * Register an Eddystone-UID beacon for persistent monitoring.\r\n */\r\n pairEddystone(\r\n identifier: string,\r\n namespace: string,\r\n instance: string,\r\n name?: string,\r\n timeoutSeconds?: number,\r\n ): void;\r\n\r\n /**\r\n * Remove a previously paired Eddystone beacon.\r\n */\r\n unpairEddystone(identifier: string): void;\r\n\r\n /**\r\n * Return all currently paired Eddystone beacons.\r\n */\r\n getPairedEddystones(): PairedEddystone[];\r\n\r\n /**\r\n * Set persistent notification configuration. Settings are saved and applied to all\r\n * subsequent monitoring sessions until explicitly changed.\r\n */\r\n setNotificationConfig(config: NotificationConfig): void;\r\n\r\n /**\r\n * Start background region monitoring for all paired beacons.\r\n * On Android starts a foreground service.\r\n * On iOS starts CLLocationManager region monitoring.\r\n *\r\n * Accepts a plain number (backward-compatible maxDistance shorthand) or a\r\n * MonitoringOptions object with maxDistance and/or notification overrides.\r\n */\r\n startMonitoring(options?: MonitoringOptions | number): Promise<void>;\r\n\r\n /**\r\n * Stop background region monitoring.\r\n */\r\n stopMonitoring(): Promise<void>;\r\n\r\n /**\r\n * Start a continuous BLE scan. Fires `onBeaconFound` events as beacons are detected.\r\n * Call stopContinuousScan() to end the scan.\r\n */\r\n startContinuousScan(): void;\r\n\r\n /** Stop the continuous scan started by startContinuousScan(). */\r\n stopContinuousScan(): void;\r\n\r\n /**\r\n * Cancel any in-progress one-shot scan (iBeacon or Eddystone).\r\n * The pending promise will be rejected with code \"SCAN_CANCELLED\".\r\n */\r\n cancelScan(): void;\r\n\r\n /** Request Bluetooth + Location permissions. Returns true if granted. */\r\n requestPermissionsAsync(): Promise<boolean>;\r\n\r\n /**\r\n * Check whether the app is exempt from Android battery optimizations.\r\n * Always returns true on iOS and web (no equivalent concept).\r\n */\r\n isBatteryOptimizationExempt(): boolean;\r\n\r\n /**\r\n * Request exemption from Android battery optimizations.\r\n * Opens the system dialog asking the user to whitelist this app.\r\n * Returns true if the dialog was shown (or already exempt), false on failure.\r\n * Always resolves true on iOS and web.\r\n */\r\n requestBatteryOptimizationExemption(): Promise<boolean>;\r\n\r\n /** Enable SQLite event logging. All beacon events will be persisted to a local database. */\r\n enableEventLogging(): void;\r\n\r\n /** Disable event logging. Previously logged events are retained. */\r\n disableEventLogging(): void;\r\n\r\n /**\r\n * Retrieve logged beacon events from the SQLite database.\r\n * @param options Optional filters (limit, eventType, sinceTimestamp).\r\n */\r\n getEventLogs(options?: EventLogQueryOptions): EventLogEntry[];\r\n\r\n /** Delete all logged events from the database. */\r\n clearEventLogs(): void;\r\n\r\n /** Delete the entire event log database. Also disables logging. */\r\n destroyEventLogs(): void;\r\n\r\n /**\r\n * Configure a remote API endpoint for native event forwarding.\r\n * Once set,
|
|
1
|
+
{"version":3,"file":"ExpoBeaconModule.js","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAuKzD,eAAe,mBAAmB,CAAmB,YAAY,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\r\n\r\nimport {\r\n ExpoBeaconModuleEvents,\r\n BeaconScanResult,\r\n EddystoneScanResult,\r\n PairedBeacon,\r\n PairedEddystone,\r\n NotificationConfig,\r\n MonitoringOptions,\r\n EventLogQueryOptions,\r\n EventLogEntry,\r\n} from \"./ExpoBeacon.types\";\r\n\r\ndeclare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {\r\n /**\r\n * Start a one-shot iBeacon scan. Resolves with discovered beacons after scanDuration ms.\r\n *\r\n * Pass one or more UUIDs to scan for specific beacons (uses CoreLocation on iOS).\r\n * On iOS, at least one UUID is required — Apple strips iBeacon data from BLE\r\n * advertisements, making wildcard discovery impossible. When you pass an empty\r\n * array, the module automatically uses UUIDs from paired beacons.\r\n * On Android, pass an empty array to discover all nearby iBeacons.\r\n *\r\n * @param uuids Proximity UUIDs to filter by. Empty/omitted = use paired UUIDs (iOS) or wildcard (Android).\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForBeaconsAsync(\r\n uuids?: string[],\r\n scanDuration?: number,\r\n ): Promise<BeaconScanResult[]>;\r\n\r\n /**\r\n * Start a one-shot Eddystone beacon scan using BLE.\r\n * Discovers Eddystone-UID and Eddystone-URL frames.\r\n *\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForEddystonesAsync(\r\n scanDuration?: number,\r\n ): Promise<EddystoneScanResult[]>;\r\n\r\n /**\r\n * Register a beacon for persistent region monitoring.\r\n */\r\n pairBeacon(\r\n identifier: string,\r\n uuid: string,\r\n major: number,\r\n minor: number,\r\n name?: string,\r\n timeoutSeconds?: number,\r\n ): void;\r\n\r\n /**\r\n * Remove a previously paired beacon.\r\n */\r\n unpairBeacon(identifier: string): void;\r\n\r\n /**\r\n * Return all currently paired beacons.\r\n */\r\n getPairedBeacons(): PairedBeacon[];\r\n\r\n /**\r\n * Register an Eddystone-UID beacon for persistent monitoring.\r\n */\r\n pairEddystone(\r\n identifier: string,\r\n namespace: string,\r\n instance: string,\r\n name?: string,\r\n timeoutSeconds?: number,\r\n ): void;\r\n\r\n /**\r\n * Remove a previously paired Eddystone beacon.\r\n */\r\n unpairEddystone(identifier: string): void;\r\n\r\n /**\r\n * Return all currently paired Eddystone beacons.\r\n */\r\n getPairedEddystones(): PairedEddystone[];\r\n\r\n /**\r\n * Set persistent notification configuration. Settings are saved and applied to all\r\n * subsequent monitoring sessions until explicitly changed.\r\n */\r\n setNotificationConfig(config: NotificationConfig): void;\r\n\r\n /**\r\n * Start background region monitoring for all paired beacons.\r\n * On Android starts a foreground service.\r\n * On iOS starts CLLocationManager region monitoring.\r\n *\r\n * Accepts a plain number (backward-compatible maxDistance shorthand) or a\r\n * MonitoringOptions object with maxDistance and/or notification overrides.\r\n */\r\n startMonitoring(options?: MonitoringOptions | number): Promise<void>;\r\n\r\n /**\r\n * Stop background region monitoring.\r\n */\r\n stopMonitoring(): Promise<void>;\r\n\r\n /**\r\n * Start a continuous BLE scan. Fires `onBeaconFound` events as beacons are detected.\r\n * Call stopContinuousScan() to end the scan.\r\n */\r\n startContinuousScan(): void;\r\n\r\n /** Stop the continuous scan started by startContinuousScan(). */\r\n stopContinuousScan(): void;\r\n\r\n /**\r\n * Cancel any in-progress one-shot scan (iBeacon or Eddystone).\r\n * The pending promise will be rejected with code \"SCAN_CANCELLED\".\r\n */\r\n cancelScan(): void;\r\n\r\n /** Request Bluetooth + Location permissions. Returns true if granted. */\r\n requestPermissionsAsync(): Promise<boolean>;\r\n\r\n /**\r\n * Check whether the app is exempt from Android battery optimizations.\r\n * Always returns true on iOS and web (no equivalent concept).\r\n */\r\n isBatteryOptimizationExempt(): boolean;\r\n\r\n /**\r\n * Request exemption from Android battery optimizations.\r\n * Opens the system dialog asking the user to whitelist this app.\r\n * Returns true if the dialog was shown (or already exempt), false on failure.\r\n * Always resolves true on iOS and web.\r\n */\r\n requestBatteryOptimizationExemption(): Promise<boolean>;\r\n\r\n /** Enable SQLite event logging. All beacon events will be persisted to a local database. */\r\n enableEventLogging(): void;\r\n\r\n /** Disable event logging. Previously logged events are retained. */\r\n disableEventLogging(): void;\r\n\r\n /**\r\n * Retrieve logged beacon events from the SQLite database.\r\n * @param options Optional filters (limit, eventType, sinceTimestamp).\r\n */\r\n getEventLogs(options?: EventLogQueryOptions): EventLogEntry[];\r\n\r\n /** Delete all logged events from the database. */\r\n clearEventLogs(): void;\r\n\r\n /** Delete the entire event log database. Also disables logging. */\r\n destroyEventLogs(): void;\r\n\r\n /**\r\n * Configure a remote API endpoint for native event forwarding.\r\n * Once set, beacon events are POSTed directly from native code,\r\n * ensuring delivery even when the JS bridge is not active (app backgrounded).\r\n *\r\n * @param url The API endpoint URL to POST events to.\r\n * @param apiKey Optional API key sent as X-API-Key header.\r\n */\r\n setApiEndpoint(url: string, apiKey?: string): void;\r\n}\r\n\r\nexport default requireNativeModule<ExpoBeaconModule>(\"ExpoBeacon\");\r\n"]}
|
|
@@ -12,6 +12,7 @@ private let EXIT_DISTANCE_KEY = "expo.beacon.exit_distance"
|
|
|
12
12
|
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
|
+
private let EVENT_LEVEL_KEY = "expo.beacon.event_level"
|
|
15
16
|
|
|
16
17
|
/// Default minimum RSSI (dBm) below which beacon readings are discarded as unreliable.
|
|
17
18
|
private let DEFAULT_MIN_RSSI: Int = -85
|
|
@@ -98,6 +99,9 @@ public class ExpoBeaconModule: Module {
|
|
|
98
99
|
/// Minimum RSSI threshold — readings below this are treated as unreliable.
|
|
99
100
|
private var minRssiThreshold: Int = DEFAULT_MIN_RSSI
|
|
100
101
|
|
|
102
|
+
/// Event level: "all" emits distance + enter/exit/timeout; "events" suppresses distance.
|
|
103
|
+
private var eventLevel: String = "all"
|
|
104
|
+
|
|
101
105
|
/// Distance smoothing (EMA) state per identifier.
|
|
102
106
|
private var smoothedDistances: [String: Double] = [:]
|
|
103
107
|
/// EMA weight for new readings. 0.4 balances responsiveness vs noise rejection.
|
|
@@ -331,6 +335,13 @@ public class ExpoBeaconModule: Module {
|
|
|
331
335
|
maxDistance = map["maxDistance"] as? Double
|
|
332
336
|
exitDistance = map["exitDistance"] as? Double
|
|
333
337
|
minRssi = map["minRssi"] as? Int
|
|
338
|
+
if let lvl = map["level"] as? String, lvl == "events" || lvl == "all" {
|
|
339
|
+
self.eventLevel = lvl
|
|
340
|
+
self.defaults.set(lvl, forKey: EVENT_LEVEL_KEY)
|
|
341
|
+
} else {
|
|
342
|
+
self.eventLevel = "all"
|
|
343
|
+
self.defaults.set("all", forKey: EVENT_LEVEL_KEY)
|
|
344
|
+
}
|
|
334
345
|
if let notifications = map["notifications"] as? [String: Any],
|
|
335
346
|
let data = try? JSONSerialization.data(withJSONObject: notifications),
|
|
336
347
|
let json = String(data: data, encoding: .utf8) {
|
|
@@ -392,6 +403,8 @@ public class ExpoBeaconModule: Module {
|
|
|
392
403
|
self.defaults.set(false, forKey: IS_MONITORING_KEY)
|
|
393
404
|
self.defaults.removeObject(forKey: MAX_DISTANCE_KEY)
|
|
394
405
|
self.defaults.removeObject(forKey: EXIT_DISTANCE_KEY)
|
|
406
|
+
self.defaults.removeObject(forKey: EVENT_LEVEL_KEY)
|
|
407
|
+
self.eventLevel = "all"
|
|
395
408
|
self.stopRegionMonitoring()
|
|
396
409
|
promise.resolve(nil)
|
|
397
410
|
}
|
|
@@ -575,6 +588,9 @@ public class ExpoBeaconModule: Module {
|
|
|
575
588
|
let storedRssi = defaults.object(forKey: MIN_RSSI_KEY) as? Int
|
|
576
589
|
minRssiThreshold = storedRssi ?? DEFAULT_MIN_RSSI
|
|
577
590
|
|
|
591
|
+
// Restore persisted event level (survives app restarts)
|
|
592
|
+
eventLevel = defaults.string(forKey: EVENT_LEVEL_KEY) ?? "all"
|
|
593
|
+
|
|
578
594
|
let beacons = loadPairedBeaconsRaw()
|
|
579
595
|
|
|
580
596
|
// CLLocationManager supports a maximum of 20 monitored regions.
|
|
@@ -818,13 +834,10 @@ public class ExpoBeaconModule: Module {
|
|
|
818
834
|
let exitDist = self.defaults.object(forKey: EXIT_DISTANCE_KEY) as? Double
|
|
819
835
|
let hasValidDistance = distance.isFinite && distance >= 0
|
|
820
836
|
if hasValidDistance || maxDist == nil {
|
|
821
|
-
// Apply EMA smoothing; jump
|
|
837
|
+
// Apply EMA smoothing; jump resets EMA to the new value
|
|
822
838
|
let effectiveDistance: Double
|
|
823
|
-
if hasValidDistance
|
|
824
|
-
effectiveDistance =
|
|
825
|
-
} else if hasValidDistance {
|
|
826
|
-
// Jump outlier — skip this cycle without resetting counters
|
|
827
|
-
break
|
|
839
|
+
if hasValidDistance {
|
|
840
|
+
effectiveDistance = smoothDistance(identifier: identifier, rawDistance: distance)!
|
|
828
841
|
} else {
|
|
829
842
|
effectiveDistance = distance
|
|
830
843
|
}
|
|
@@ -848,10 +861,11 @@ public class ExpoBeaconModule: Module {
|
|
|
848
861
|
"rssi": beaconRssi
|
|
849
862
|
])
|
|
850
863
|
postBeaconNotification(identifier: identifier, eventType: "enter")
|
|
851
|
-
|
|
864
|
+
// Beacon returned — cancel any running timeout timer.
|
|
865
|
+
cancelEddystoneTimeout(identifier: identifier)
|
|
852
866
|
case .exit:
|
|
853
867
|
smoothedDistances.removeValue(forKey: identifier)
|
|
854
|
-
|
|
868
|
+
print("[ExpoBeacon] DEBUG: Eddystone distance-based EXIT for \(identifier)")
|
|
855
869
|
sendLoggedEvent("onEddystoneExit", [
|
|
856
870
|
"identifier": identifier,
|
|
857
871
|
"namespace": ns,
|
|
@@ -861,12 +875,15 @@ public class ExpoBeaconModule: Module {
|
|
|
861
875
|
"rssi": beaconRssi
|
|
862
876
|
])
|
|
863
877
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
878
|
+
// Beacon left — start the timeout clock.
|
|
879
|
+
scheduleEddystoneTimeout(identifier: identifier, namespace: ns, instance: inst)
|
|
864
880
|
case .none:
|
|
865
881
|
break
|
|
866
882
|
}
|
|
867
883
|
}
|
|
868
884
|
|
|
869
885
|
guard hasValidDistance else { break }
|
|
886
|
+
guard self.eventLevel == "all" else { break }
|
|
870
887
|
|
|
871
888
|
// Throttle distance events — enter/exit above is evaluated on every
|
|
872
889
|
// callback, but distance events are rate-limited to avoid flooding JS.
|
|
@@ -958,10 +975,8 @@ public class ExpoBeaconModule: Module {
|
|
|
958
975
|
let identifier = params["identifier"] as? String
|
|
959
976
|
getOrCreateEventLogger().logEvent(eventType: eventName, identifier: identifier, data: params)
|
|
960
977
|
}
|
|
961
|
-
// Forward
|
|
962
|
-
|
|
963
|
-
apiForwarder.forwardEvent(params)
|
|
964
|
-
}
|
|
978
|
+
// Forward all produced events to remote API
|
|
979
|
+
apiForwarder.forwardEvent(params)
|
|
965
980
|
sendEvent(eventName, params)
|
|
966
981
|
}
|
|
967
982
|
|
|
@@ -1080,7 +1095,6 @@ public class ExpoBeaconModule: Module {
|
|
|
1080
1095
|
eddystoneExitCounters[identifier] = 0
|
|
1081
1096
|
eddystoneLatestSeen.removeValue(forKey: identifier)
|
|
1082
1097
|
smoothedDistances.removeValue(forKey: identifier)
|
|
1083
|
-
// Do NOT cancel the timeout here — same reason as iBeacon miss-based exit.
|
|
1084
1098
|
|
|
1085
1099
|
let ns = paired["namespace"] as? String ?? ""
|
|
1086
1100
|
let inst = paired["instance"] as? String ?? ""
|
|
@@ -1092,18 +1106,19 @@ public class ExpoBeaconModule: Module {
|
|
|
1092
1106
|
"distance": -1
|
|
1093
1107
|
]
|
|
1094
1108
|
sendLoggedEvent("onEddystoneExit", params)
|
|
1109
|
+
print("[ExpoBeacon] DEBUG: Eddystone miss-based EXIT for \(identifier)")
|
|
1095
1110
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1111
|
+
// Beacon disappeared — start the timeout clock.
|
|
1112
|
+
scheduleEddystoneTimeout(identifier: identifier, namespace: ns, instance: inst)
|
|
1096
1113
|
}
|
|
1097
1114
|
}
|
|
1098
1115
|
}
|
|
1099
1116
|
|
|
1100
1117
|
// MARK: - Timeout timer helpers
|
|
1101
1118
|
|
|
1102
|
-
private func scheduleBeaconTimeout(identifier: String, beacon: CLBeacon) {
|
|
1103
|
-
//
|
|
1104
|
-
|
|
1105
|
-
// must not restart the clock — doing so would defer the timeout indefinitely.
|
|
1106
|
-
guard beaconTimeoutTimers[identifier] == nil else { return }
|
|
1119
|
+
private func scheduleBeaconTimeout(identifier: String, beacon: CLBeacon? = nil, region: CLBeaconRegion? = nil) {
|
|
1120
|
+
// Cancel any existing timer so each exit resets the clock.
|
|
1121
|
+
cancelBeaconTimeout(identifier: identifier)
|
|
1107
1122
|
|
|
1108
1123
|
let paired = loadPairedBeaconsRaw().first { ($0["identifier"] as? String) == identifier }
|
|
1109
1124
|
guard let seconds = paired?["timeoutSeconds"] as? Int, seconds > 0 else { return }
|
|
@@ -1111,11 +1126,7 @@ public class ExpoBeaconModule: Module {
|
|
|
1111
1126
|
let work = DispatchWorkItem { [weak self] in
|
|
1112
1127
|
guard let self = self else { return }
|
|
1113
1128
|
self.beaconTimeoutTimers.removeValue(forKey: identifier)
|
|
1114
|
-
|
|
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.
|
|
1118
|
-
self.sendLoggedEvent("onBeaconTimeout", self.makeBeaconEventParams(identifier: identifier, beacon: beacon))
|
|
1129
|
+
self.sendLoggedEvent("onBeaconTimeout", self.makeBeaconEventParams(identifier: identifier, beacon: beacon, region: region))
|
|
1119
1130
|
}
|
|
1120
1131
|
beaconTimeoutTimers[identifier] = work
|
|
1121
1132
|
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(seconds), execute: work)
|
|
@@ -1126,8 +1137,8 @@ public class ExpoBeaconModule: Module {
|
|
|
1126
1137
|
}
|
|
1127
1138
|
|
|
1128
1139
|
private func scheduleEddystoneTimeout(identifier: String, namespace: String, instance: String) {
|
|
1129
|
-
//
|
|
1130
|
-
|
|
1140
|
+
// Cancel any existing timer so each exit resets the clock.
|
|
1141
|
+
cancelEddystoneTimeout(identifier: identifier)
|
|
1131
1142
|
|
|
1132
1143
|
let paired = loadPairedEddystonesRaw().first { ($0["identifier"] as? String) == identifier }
|
|
1133
1144
|
guard let seconds = paired?["timeoutSeconds"] as? Int, seconds > 0 else { return }
|
|
@@ -1135,7 +1146,6 @@ public class ExpoBeaconModule: Module {
|
|
|
1135
1146
|
let work = DispatchWorkItem { [weak self] in
|
|
1136
1147
|
guard let self = self else { return }
|
|
1137
1148
|
self.eddystoneTimeoutTimers.removeValue(forKey: identifier)
|
|
1138
|
-
// Fire unconditionally — same reason as iBeacon timeout.
|
|
1139
1149
|
self.sendLoggedEvent("onEddystoneTimeout", [
|
|
1140
1150
|
"identifier": identifier,
|
|
1141
1151
|
"namespace": namespace,
|
|
@@ -1333,8 +1343,10 @@ public class ExpoBeaconModule: Module {
|
|
|
1333
1343
|
// Got a valid reading — reset miss counter
|
|
1334
1344
|
missCounters[identifier] = 0
|
|
1335
1345
|
|
|
1336
|
-
// Emit distance event every ranging cycle (~1 s)
|
|
1337
|
-
|
|
1346
|
+
// Emit distance event every ranging cycle (~1 s) if level allows
|
|
1347
|
+
if self.eventLevel == "all" {
|
|
1348
|
+
sendLoggedEvent("onBeaconDistance", makeBeaconEventParams(identifier: identifier, beacon: beacon))
|
|
1349
|
+
}
|
|
1338
1350
|
|
|
1339
1351
|
// Enter/exit synthesis with hysteresis — always applied.
|
|
1340
1352
|
// When maxDistance is set, distance thresholds control transitions.
|
|
@@ -1343,11 +1355,8 @@ public class ExpoBeaconModule: Module {
|
|
|
1343
1355
|
let maxDist = self.defaults.object(forKey: MAX_DISTANCE_KEY) as? Double
|
|
1344
1356
|
let exitDist = self.defaults.object(forKey: EXIT_DISTANCE_KEY) as? Double
|
|
1345
1357
|
|
|
1346
|
-
// Apply EMA smoothing; jump
|
|
1347
|
-
|
|
1348
|
-
// Jump outlier — skip this cycle without resetting counters
|
|
1349
|
-
return
|
|
1350
|
-
}
|
|
1358
|
+
// Apply EMA smoothing; jump resets EMA to the new value
|
|
1359
|
+
let smoothed = smoothDistance(identifier: identifier, rawDistance: beacon.accuracy)!
|
|
1351
1360
|
|
|
1352
1361
|
let action = evaluateDistanceHysteresis(
|
|
1353
1362
|
identifier: identifier,
|
|
@@ -1362,12 +1371,14 @@ public class ExpoBeaconModule: Module {
|
|
|
1362
1371
|
case .enter:
|
|
1363
1372
|
sendLoggedEvent("onBeaconEnter", makeBeaconEventParams(identifier: identifier, beacon: beacon, event: "enter"))
|
|
1364
1373
|
postBeaconNotification(identifier: identifier, eventType: "enter")
|
|
1365
|
-
|
|
1374
|
+
// Beacon returned — cancel any running timeout timer.
|
|
1375
|
+
cancelBeaconTimeout(identifier: identifier)
|
|
1366
1376
|
case .exit:
|
|
1367
1377
|
smoothedDistances.removeValue(forKey: identifier)
|
|
1368
|
-
cancelBeaconTimeout(identifier: identifier)
|
|
1369
1378
|
sendLoggedEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, beacon: beacon, event: "exit"))
|
|
1370
1379
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1380
|
+
// Beacon left — start the timeout clock.
|
|
1381
|
+
scheduleBeaconTimeout(identifier: identifier, beacon: beacon)
|
|
1371
1382
|
case .none:
|
|
1372
1383
|
break
|
|
1373
1384
|
}
|
|
@@ -1389,16 +1400,13 @@ public class ExpoBeaconModule: Module {
|
|
|
1389
1400
|
enterCounters[identifier] = 0
|
|
1390
1401
|
exitCounters[identifier] = 0
|
|
1391
1402
|
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.
|
|
1397
1403
|
|
|
1398
1404
|
// Look up region info for the exit event payload
|
|
1399
1405
|
let region = monitoredRegions.first { $0.identifier == identifier }
|
|
1400
1406
|
sendLoggedEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, region: region, event: "exit"))
|
|
1401
1407
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1408
|
+
// Beacon disappeared — start the timeout clock.
|
|
1409
|
+
scheduleBeaconTimeout(identifier: identifier, region: region)
|
|
1402
1410
|
}
|
|
1403
1411
|
}
|
|
1404
1412
|
return
|
|
@@ -1441,9 +1449,10 @@ public class ExpoBeaconModule: Module {
|
|
|
1441
1449
|
missCounters.removeValue(forKey: identifier)
|
|
1442
1450
|
smoothedDistances.removeValue(forKey: identifier)
|
|
1443
1451
|
if wasEntered {
|
|
1444
|
-
cancelBeaconTimeout(identifier: identifier)
|
|
1445
1452
|
sendLoggedEvent("onBeaconExit", makeBeaconEventParams(identifier: identifier, region: beaconRegion, event: "exit"))
|
|
1446
1453
|
postBeaconNotification(identifier: identifier, eventType: "exit")
|
|
1454
|
+
// OS-level exit safety net — start the timeout clock.
|
|
1455
|
+
scheduleBeaconTimeout(identifier: identifier, region: beaconRegion)
|
|
1447
1456
|
}
|
|
1448
1457
|
}
|
|
1449
1458
|
|
package/package.json
CHANGED
package/src/ExpoBeacon.types.ts
CHANGED
|
@@ -141,6 +141,13 @@ export type MonitoringOptions = {
|
|
|
141
141
|
* Default: -85. Typical range: -100 (very permissive) to -70 (strict).
|
|
142
142
|
*/
|
|
143
143
|
minRssi?: number;
|
|
144
|
+
/**
|
|
145
|
+
* Controls which event types are emitted, logged, and forwarded to the API.
|
|
146
|
+
*
|
|
147
|
+
* - `'all'` (default): distance + enter + exit + timeout events.
|
|
148
|
+
* - `'events'`: enter + exit + timeout only (no distance events).
|
|
149
|
+
*/
|
|
150
|
+
level?: 'all' | 'events';
|
|
144
151
|
/** Notification configuration overrides to apply for this monitoring session. */
|
|
145
152
|
notifications?: NotificationConfig;
|
|
146
153
|
};
|
package/src/ExpoBeaconModule.ts
CHANGED
|
@@ -156,7 +156,7 @@ declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
|
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
158
|
* Configure a remote API endpoint for native event forwarding.
|
|
159
|
-
* Once set,
|
|
159
|
+
* Once set, beacon events are POSTed directly from native code,
|
|
160
160
|
* ensuring delivery even when the JS bridge is not active (app backgrounded).
|
|
161
161
|
*
|
|
162
162
|
* @param url The API endpoint URL to POST events to.
|