expo-beacon 0.6.1 → 0.6.3

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.
@@ -10,6 +10,8 @@ internal const val EDDYSTONE_PREFS_NAME = "expo.beacon.paired_eddystones"
10
10
  internal const val EDDYSTONE_PREFS_KEY = "paired_eddystones"
11
11
  internal const val NOTIFICATION_CONFIG_PREFS = "expo.beacon.notification_config"
12
12
  internal const val MONITORING_OPTIONS_PREFS = "expo.beacon.monitoring_options"
13
+ internal const val EVENT_LOGGING_PREFS = "expo.beacon.event_logging"
14
+ internal const val EVENT_LOGGING_ENABLED_KEY = "enabled"
13
15
 
14
16
  /** Number of consecutive ranging misses before emitting a distance-based exit event. */
15
17
  internal const val EXIT_MISS_THRESHOLD = 3
@@ -11,12 +11,30 @@ import org.json.JSONObject
11
11
  * Thread-safe — all writes go through a single SQLiteOpenHelper.
12
12
  */
13
13
  internal class BeaconEventLogger(context: Context) :
14
- SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
14
+ SQLiteOpenHelper(context.applicationContext, DB_NAME, null, DB_VERSION) {
15
15
 
16
16
  companion object {
17
17
  private const val DB_NAME = "expo_beacon_events.db"
18
18
  private const val DB_VERSION = 1
19
19
  private const val TABLE = "events"
20
+
21
+ fun isLoggingEnabled(context: Context): Boolean {
22
+ return context.applicationContext
23
+ .getSharedPreferences(EVENT_LOGGING_PREFS, Context.MODE_PRIVATE)
24
+ .getBoolean(EVENT_LOGGING_ENABLED_KEY, false)
25
+ }
26
+
27
+ fun setLoggingEnabled(context: Context, enabled: Boolean) {
28
+ context.applicationContext
29
+ .getSharedPreferences(EVENT_LOGGING_PREFS, Context.MODE_PRIVATE)
30
+ .edit()
31
+ .putBoolean(EVENT_LOGGING_ENABLED_KEY, enabled)
32
+ .apply()
33
+ }
34
+
35
+ fun deleteLogDatabase(context: Context) {
36
+ context.applicationContext.deleteDatabase(DB_NAME)
37
+ }
20
38
  }
21
39
 
22
40
  override fun onCreate(db: SQLiteDatabase) {
@@ -90,9 +108,4 @@ internal class BeaconEventLogger(context: Context) :
90
108
  fun clearEvents() {
91
109
  writableDatabase.delete(TABLE, null, null)
92
110
  }
93
-
94
- fun destroyDatabase(context: Context) {
95
- close()
96
- context.deleteDatabase(DB_NAME)
97
- }
98
111
  }
@@ -55,6 +55,7 @@ class BeaconForegroundService : Service(), BeaconConsumer {
55
55
  private val timeoutRunnables = java.util.concurrent.ConcurrentHashMap<String, Runnable>()
56
56
  // Per-beacon timeout seconds lookup (identifier → seconds), loaded from paired data
57
57
  private val beaconTimeouts = java.util.concurrent.ConcurrentHashMap<String, Int>()
58
+ private var eventLogger: BeaconEventLogger? = null
58
59
 
59
60
  companion object {
60
61
  private const val PREF_IS_MONITORING = "expo.beacon.is_monitoring"
@@ -404,6 +405,26 @@ class BeaconForegroundService : Service(), BeaconConsumer {
404
405
  val id1Str = region.id1?.toString() ?: ""
405
406
  val isEddystone = id1Str.startsWith("0x")
406
407
 
408
+ val params = if (isEddystone) {
409
+ buildMap<String, Any?> {
410
+ put("identifier", region.uniqueId)
411
+ put("namespace", id1Str.removePrefix("0x"))
412
+ put("instance", region.id2?.toString()?.removePrefix("0x") ?: "")
413
+ put("event", eventType)
414
+ put("distance", distance)
415
+ }
416
+ } else {
417
+ buildMap<String, Any?> {
418
+ put("identifier", region.uniqueId)
419
+ put("uuid", id1Str)
420
+ put("major", region.id2?.toInt() ?: 0)
421
+ put("minor", region.id3?.toInt() ?: 0)
422
+ put("event", eventType)
423
+ put("distance", distance)
424
+ }
425
+ }
426
+ monitoringEventName(isEddystone, eventType)?.let { logBeaconEvent(it, params) }
427
+
407
428
  val intent = Intent(ACTION_BEACON_EVENT).apply {
408
429
  putExtra("identifier", region.uniqueId)
409
430
  putExtra("event", eventType)
@@ -424,6 +445,36 @@ class BeaconForegroundService : Service(), BeaconConsumer {
424
445
  sendBroadcast(intent)
425
446
  }
426
447
 
448
+ private fun monitoringEventName(isEddystone: Boolean, eventType: String): String? {
449
+ return when (eventType) {
450
+ "enter" -> if (isEddystone) "onEddystoneEnter" else "onBeaconEnter"
451
+ "exit" -> if (isEddystone) "onEddystoneExit" else "onBeaconExit"
452
+ "distance" -> if (isEddystone) "onEddystoneDistance" else "onBeaconDistance"
453
+ "timeout" -> if (isEddystone) "onEddystoneTimeout" else "onBeaconTimeout"
454
+ else -> null
455
+ }
456
+ }
457
+
458
+ @Synchronized
459
+ private fun getOrCreateEventLogger(): BeaconEventLogger {
460
+ return eventLogger ?: BeaconEventLogger(applicationContext).also { eventLogger = it }
461
+ }
462
+
463
+ @Synchronized
464
+ private fun releaseEventLogger() {
465
+ eventLogger?.close()
466
+ eventLogger = null
467
+ }
468
+
469
+ private fun logBeaconEvent(eventType: String, params: Map<String, Any?>) {
470
+ if (!BeaconEventLogger.isLoggingEnabled(this)) {
471
+ releaseEventLogger()
472
+ return
473
+ }
474
+ val identifier = params["identifier"] as? String
475
+ getOrCreateEventLogger().logEvent(eventType, identifier, params)
476
+ }
477
+
427
478
  private fun showEnterExitNotification(region: Region, eventType: String) {
428
479
  val config = readNotificationConfig()
429
480
  val eventsConfig = config.optJSONObject("beaconEvents")
@@ -527,6 +578,7 @@ class BeaconForegroundService : Service(), BeaconConsumer {
527
578
  timeoutHandler.removeCallbacksAndMessages(null)
528
579
  timeoutRunnables.clear()
529
580
  beaconTimeouts.clear()
581
+ releaseEventLogger()
530
582
  beaconManager.removeMonitorNotifier(monitorNotifier)
531
583
  beaconManager.removeRangeNotifier(rangeNotifier)
532
584
  beaconManager.removeRangeNotifier(distanceLoggingRangeNotifier)
@@ -385,15 +385,17 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
385
385
  if (eventLogger == null) {
386
386
  eventLogger = BeaconEventLogger(ctx)
387
387
  }
388
+ BeaconEventLogger.setLoggingEnabled(ctx, true)
388
389
  loggingEnabled = true
389
390
  }
390
391
 
391
392
  Function("disableEventLogging") {
393
+ appContext.reactContext?.let { BeaconEventLogger.setLoggingEnabled(it, false) }
392
394
  loggingEnabled = false
393
395
  }
394
396
 
395
397
  Function("getEventLogs") { options: Map<String, Any?>? ->
396
- val logger = eventLogger ?: return@Function emptyList<Map<String, Any?>>()
398
+ val logger = getOrCreateEventLogger() ?: return@Function emptyList<Map<String, Any?>>()
397
399
  val limit = (options?.get("limit") as? Number)?.toInt() ?: 1000
398
400
  val eventType = options?.get("eventType") as? String
399
401
  val sinceTimestamp = (options?.get("sinceTimestamp") as? Number)?.toLong()
@@ -401,14 +403,16 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
401
403
  }
402
404
 
403
405
  Function("clearEventLogs") {
404
- eventLogger?.clearEvents()
406
+ getOrCreateEventLogger()?.clearEvents()
405
407
  }
406
408
 
407
409
  Function("destroyEventLogs") {
408
410
  loggingEnabled = false
409
- val ctx = appContext.reactContext ?: return@Function
410
- eventLogger?.destroyDatabase(ctx)
411
+ val ctx = appContext.reactContext ?: return@Function null
412
+ BeaconEventLogger.setLoggingEnabled(ctx, false)
413
+ eventLogger?.close()
411
414
  eventLogger = null
415
+ BeaconEventLogger.deleteLogDatabase(ctx)
412
416
  }
413
417
 
414
418
  OnDestroy {
@@ -699,7 +703,6 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
699
703
  private fun registerEventReceiver() {
700
704
  if (eventReceiver != null) return
701
705
  val receiver = BeaconEventReceiver { eventName, params ->
702
- logBeaconEvent(eventName, params)
703
706
  sendEvent(eventName, params)
704
707
  }
705
708
  eventReceiver = receiver
@@ -740,10 +743,26 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
740
743
  }
741
744
  }
742
745
 
746
+ private fun getOrCreateEventLogger(): BeaconEventLogger? {
747
+ val context = appContext.reactContext ?: return null
748
+ return eventLogger ?: BeaconEventLogger(context).also { eventLogger = it }
749
+ }
750
+
751
+ private fun isEventLoggingEnabled(): Boolean {
752
+ if (loggingEnabled) return true
753
+ val context = appContext.reactContext ?: return false
754
+ if (!BeaconEventLogger.isLoggingEnabled(context)) return false
755
+ loggingEnabled = true
756
+ if (eventLogger == null) {
757
+ eventLogger = BeaconEventLogger(context)
758
+ }
759
+ return true
760
+ }
761
+
743
762
  /** Log an event to SQLite if logging is enabled. */
744
763
  private fun logBeaconEvent(eventType: String, params: Map<String, Any?>) {
745
- if (!loggingEnabled) return
764
+ if (!isEventLoggingEnabled()) return
746
765
  val identifier = params["identifier"] as? String
747
- eventLogger?.logEvent(eventType, identifier, params)
766
+ getOrCreateEventLogger()?.logEvent(eventType, identifier, params)
748
767
  }
749
768
  }
@@ -4,17 +4,28 @@ import SQLite3
4
4
  /// SQLite-backed event logger for beacon events (iOS).
5
5
  /// All access is expected from the main thread (same as ExpoBeaconModule).
6
6
  final class BeaconEventLogger {
7
+ private static let databaseFileName = "expo_beacon_events.db"
8
+
9
+ private static var databaseURL: URL {
10
+ let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
11
+ return dir.appendingPathComponent(databaseFileName)
12
+ }
13
+
7
14
  private var db: OpaquePointer?
8
15
  private let dbPath: String
9
16
 
10
17
  init() {
11
- let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
18
+ let dir = Self.databaseURL.deletingLastPathComponent()
12
19
  try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
13
- dbPath = dir.appendingPathComponent("expo_beacon_events.db").path
20
+ dbPath = Self.databaseURL.path
14
21
  openDatabase()
15
22
  createTableIfNeeded()
16
23
  }
17
24
 
25
+ static func destroyPersistentStore() {
26
+ try? FileManager.default.removeItem(at: databaseURL)
27
+ }
28
+
18
29
  private func openDatabase() {
19
30
  guard sqlite3_open(dbPath, &db) == SQLITE_OK else {
20
31
  print("[ExpoBeacon] Failed to open event log database")
@@ -126,7 +137,7 @@ final class BeaconEventLogger {
126
137
  sqlite3_close(db)
127
138
  db = nil
128
139
  }
129
- try? FileManager.default.removeItem(atPath: dbPath)
140
+ Self.destroyPersistentStore()
130
141
  }
131
142
 
132
143
  deinit {
@@ -10,6 +10,7 @@ private let IS_MONITORING_KEY = "expo.beacon.is_monitoring"
10
10
  private let MAX_DISTANCE_KEY = "expo.beacon.max_distance"
11
11
  private let EXIT_DISTANCE_KEY = "expo.beacon.exit_distance"
12
12
  private let NOTIFICATION_CONFIG_KEY = "expo.beacon.notification_config"
13
+ private let EVENT_LOGGING_ENABLED_KEY = "expo.beacon.event_logging_enabled"
13
14
 
14
15
  /// Number of consecutive ranging misses before emitting a distance-based exit event.
15
16
  /// IMPORTANT: Keep in sync with BeaconConstants.kt (Android).
@@ -402,15 +403,17 @@ public class ExpoBeaconModule: Module {
402
403
  if self.eventLogger == nil {
403
404
  self.eventLogger = BeaconEventLogger()
404
405
  }
406
+ self.defaults.set(true, forKey: EVENT_LOGGING_ENABLED_KEY)
405
407
  self.loggingEnabled = true
406
408
  }
407
409
 
408
410
  Function("disableEventLogging") { () -> Void in
411
+ self.defaults.set(false, forKey: EVENT_LOGGING_ENABLED_KEY)
409
412
  self.loggingEnabled = false
410
413
  }
411
414
 
412
415
  Function("getEventLogs") { (options: [String: Any]?) -> [[String: Any]] in
413
- guard let logger = self.eventLogger else { return [] }
416
+ let logger = self.getOrCreateEventLogger()
414
417
  let limit = (options?["limit"] as? Int) ?? 1000
415
418
  let eventType = options?["eventType"] as? String
416
419
  let sinceTimestamp: Int64? = (options?["sinceTimestamp"] as? NSNumber)?.int64Value
@@ -418,12 +421,17 @@ public class ExpoBeaconModule: Module {
418
421
  }
419
422
 
420
423
  Function("clearEventLogs") { () -> Void in
421
- self.eventLogger?.clearEvents()
424
+ self.getOrCreateEventLogger().clearEvents()
422
425
  }
423
426
 
424
427
  Function("destroyEventLogs") { () -> Void in
428
+ self.defaults.set(false, forKey: EVENT_LOGGING_ENABLED_KEY)
425
429
  self.loggingEnabled = false
426
- self.eventLogger?.destroy()
430
+ if let logger = self.eventLogger {
431
+ logger.destroy()
432
+ } else {
433
+ BeaconEventLogger.destroyPersistentStore()
434
+ }
427
435
  self.eventLogger = nil
428
436
  }
429
437
 
@@ -855,13 +863,36 @@ public class ExpoBeaconModule: Module {
855
863
 
856
864
  /// Sends an event to JS and logs it to SQLite if logging is enabled.
857
865
  private func sendLoggedEvent(_ eventName: String, _ params: [String: Any]) {
858
- if loggingEnabled {
866
+ if isEventLoggingEnabled() {
859
867
  let identifier = params["identifier"] as? String
860
- eventLogger?.logEvent(eventType: eventName, identifier: identifier, data: params)
868
+ getOrCreateEventLogger().logEvent(eventType: eventName, identifier: identifier, data: params)
861
869
  }
862
870
  sendEvent(eventName, params)
863
871
  }
864
872
 
873
+ private func getOrCreateEventLogger() -> BeaconEventLogger {
874
+ if let logger = eventLogger {
875
+ return logger
876
+ }
877
+ let logger = BeaconEventLogger()
878
+ eventLogger = logger
879
+ return logger
880
+ }
881
+
882
+ private func isEventLoggingEnabled() -> Bool {
883
+ if loggingEnabled {
884
+ return true
885
+ }
886
+ guard defaults.bool(forKey: EVENT_LOGGING_ENABLED_KEY) else {
887
+ return false
888
+ }
889
+ loggingEnabled = true
890
+ if eventLogger == nil {
891
+ eventLogger = BeaconEventLogger()
892
+ }
893
+ return true
894
+ }
895
+
865
896
  private func loadPairedBeaconsRaw() -> [[String: Any]] {
866
897
  if let cached = cachedPairedBeacons { return cached }
867
898
  let value = self.defaults.array(forKey: PAIRED_BEACONS_KEY) as? [[String: Any]] ?? []
@@ -881,7 +912,8 @@ public class ExpoBeaconModule: Module {
881
912
  guard !defaults.bool(forKey: migrationKey) else { return }
882
913
  let keysToMigrate = [
883
914
  PAIRED_BEACONS_KEY, PAIRED_EDDYSTONES_KEY,
884
- IS_MONITORING_KEY, MAX_DISTANCE_KEY, NOTIFICATION_CONFIG_KEY
915
+ IS_MONITORING_KEY, MAX_DISTANCE_KEY, NOTIFICATION_CONFIG_KEY,
916
+ EVENT_LOGGING_ENABLED_KEY
885
917
  ]
886
918
  for key in keysToMigrate {
887
919
  if let value = UserDefaults.standard.object(forKey: key) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-beacon",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Expo module for scanning, pairing, and monitoring iBeacons on Android and iOS",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",