expo-beacon 0.6.2 → 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.
- package/android/src/main/java/expo/modules/beacon/BeaconConstants.kt +2 -0
- package/android/src/main/java/expo/modules/beacon/BeaconEventLogger.kt +19 -6
- package/android/src/main/java/expo/modules/beacon/BeaconForegroundService.kt +52 -0
- package/android/src/main/java/expo/modules/beacon/ExpoBeaconModule.kt +25 -6
- package/ios/BeaconEventLogger.swift +14 -3
- package/ios/ExpoBeaconModule.swift +38 -6
- package/package.json +1 -1
|
@@ -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 =
|
|
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
|
-
|
|
406
|
+
getOrCreateEventLogger()?.clearEvents()
|
|
405
407
|
}
|
|
406
408
|
|
|
407
409
|
Function("destroyEventLogs") {
|
|
408
410
|
loggingEnabled = false
|
|
409
411
|
val ctx = appContext.reactContext ?: return@Function null
|
|
410
|
-
|
|
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 (!
|
|
764
|
+
if (!isEventLoggingEnabled()) return
|
|
746
765
|
val identifier = params["identifier"] as? String
|
|
747
|
-
|
|
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 =
|
|
18
|
+
let dir = Self.databaseURL.deletingLastPathComponent()
|
|
12
19
|
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
13
|
-
dbPath =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
866
|
+
if isEventLoggingEnabled() {
|
|
859
867
|
let identifier = params["identifier"] as? String
|
|
860
|
-
|
|
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) {
|