expo-beacon 0.8.3 → 0.8.4
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/AndroidManifest.xml +15 -0
- package/android/src/main/java/expo/modules/beacon/BeaconForegroundService.kt +28 -0
- package/android/src/main/java/expo/modules/beacon/CarPlayMonitor.kt +124 -4
- 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/CarPlayMonitor.swift +88 -20
- package/ios/ExpoBeaconConstants.swift +4 -0
- package/ios/ExpoBeaconModule+CarPlay.swift +36 -8
- package/ios/ExpoBeaconModule.swift +18 -2
- package/package.json +1 -1
- package/src/ExpoBeacon.types.ts +9 -0
|
@@ -34,6 +34,21 @@
|
|
|
34
34
|
|
|
35
35
|
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
|
|
36
36
|
|
|
37
|
+
<!--
|
|
38
|
+
Package-visibility declaration required by Android 11+ so that
|
|
39
|
+
androidx.car.app's CarConnection can resolve the Android Auto /
|
|
40
|
+
Automotive OS content provider. Without this, CarConnection.type
|
|
41
|
+
silently reports CONNECTION_TYPE_NOT_CONNECTED on freshly-started
|
|
42
|
+
processes (e.g. after the foreground service is revived via
|
|
43
|
+
START_STICKY following a swipe-away), causing spurious disconnect
|
|
44
|
+
events.
|
|
45
|
+
-->
|
|
46
|
+
<queries>
|
|
47
|
+
<intent>
|
|
48
|
+
<action android:name="androidx.car.app.connection.action.CAR_PROVIDER" />
|
|
49
|
+
</intent>
|
|
50
|
+
</queries>
|
|
51
|
+
|
|
37
52
|
<application>
|
|
38
53
|
<!-- AltBeacon background scanning service -->
|
|
39
54
|
<service
|
|
@@ -1082,6 +1082,10 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
1082
1082
|
*/
|
|
1083
1083
|
fun disableCarPlay(context: Context) {
|
|
1084
1084
|
setCarPlayEnabled(context, false)
|
|
1085
|
+
// Wipe the monitor's persisted last-known connection so a future
|
|
1086
|
+
// re-enable starts from a clean slate (no stale "was connected"
|
|
1087
|
+
// assumption that would arm the bootstrap-grace re-check).
|
|
1088
|
+
CarPlayMonitor.clearPersistedState(context)
|
|
1085
1089
|
val intent = Intent(context, BeaconForegroundService::class.java)
|
|
1086
1090
|
.setAction(ACTION_DISABLE_CARPLAY)
|
|
1087
1091
|
// Best-effort: if the service isn't running, sending the intent will
|
|
@@ -1260,6 +1264,30 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
1260
1264
|
|
|
1261
1265
|
override fun onBind(intent: Intent?): IBinder? = null
|
|
1262
1266
|
|
|
1267
|
+
/**
|
|
1268
|
+
* Defensive override: when the user swipes the host app away from
|
|
1269
|
+
* Recents, Android delivers `onTaskRemoved` to bound services. Our
|
|
1270
|
+
* service is started via `startForegroundService` with `START_STICKY`
|
|
1271
|
+
* and is intended to keep running independently of the app's task —
|
|
1272
|
+
* specifically so that CarPlay / Android Auto observation and beacon
|
|
1273
|
+
* monitoring continue across swipe-away. We intentionally do NOT call
|
|
1274
|
+
* `stopSelf()` here; the system will redeliver `onStartCommand` on
|
|
1275
|
+
* its own if the process is later reclaimed.
|
|
1276
|
+
*
|
|
1277
|
+
* Logging is the only side-effect so that stuck-state issues are
|
|
1278
|
+
* traceable in logcat.
|
|
1279
|
+
*/
|
|
1280
|
+
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
1281
|
+
val keepAlive = isMonitoringActive(this) || isCarPlayEnabled(this)
|
|
1282
|
+
Log.d(
|
|
1283
|
+
TAG,
|
|
1284
|
+
"onTaskRemoved received (monitoring=${isMonitoringActive(this)}, " +
|
|
1285
|
+
"carPlay=${isCarPlayEnabled(this)}, keepAlive=$keepAlive). " +
|
|
1286
|
+
"Service will remain in foreground."
|
|
1287
|
+
)
|
|
1288
|
+
super.onTaskRemoved(rootIntent)
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1263
1291
|
override fun getApplicationContext(): Context = super.getApplicationContext()
|
|
1264
1292
|
}
|
|
1265
1293
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package expo.modules.beacon
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.content.SharedPreferences
|
|
4
5
|
import android.os.Handler
|
|
5
6
|
import android.os.Looper
|
|
6
7
|
import android.util.Log
|
|
@@ -36,6 +37,17 @@ internal class CarPlayMonitor(private val context: Context) {
|
|
|
36
37
|
private var observer: Observer<Int>? = null
|
|
37
38
|
private var emit: Emit? = null
|
|
38
39
|
@Volatile private var lastConnected: Boolean? = null
|
|
40
|
+
/**
|
|
41
|
+
* Pending bootstrap-grace runnable that will re-check the connection state
|
|
42
|
+
* after [BOOTSTRAP_GRACE_MS] before emitting a `disconnected` event.
|
|
43
|
+
* Used only when the persisted state was `connected` but the first
|
|
44
|
+
* observed value after [start] is `NOT_CONNECTED` — this absorbs the
|
|
45
|
+
* LiveData/Content-Provider initial-value race that occurs after a
|
|
46
|
+
* cold service restart.
|
|
47
|
+
*/
|
|
48
|
+
private var pendingDisconnectCheck: Runnable? = null
|
|
49
|
+
/** Most recent raw type seen from the LiveData; used by the grace re-check. */
|
|
50
|
+
@Volatile private var lastObservedType: Int = CarConnection.CONNECTION_TYPE_NOT_CONNECTED
|
|
39
51
|
|
|
40
52
|
/**
|
|
41
53
|
* Begin observing connection state. Idempotent — calling twice replaces the
|
|
@@ -45,12 +57,18 @@ internal class CarPlayMonitor(private val context: Context) {
|
|
|
45
57
|
fun start(emit: Emit) {
|
|
46
58
|
runOnMain {
|
|
47
59
|
this.emit = emit
|
|
60
|
+
// Seed in-memory state from persisted last-known connection so a
|
|
61
|
+
// cold restart can distinguish "we lost a previously-active
|
|
62
|
+
// connection" from "we have no prior knowledge yet".
|
|
63
|
+
if (lastConnected == null) {
|
|
64
|
+
lastConnected = readPersistedConnected()
|
|
65
|
+
}
|
|
48
66
|
if (observer == null) {
|
|
49
67
|
val obs = Observer<Int> { type -> handleType(type) }
|
|
50
68
|
observer = obs
|
|
51
69
|
try {
|
|
52
70
|
liveData.observeForever(obs)
|
|
53
|
-
Log.d(TAG, "CarPlay monitoring started")
|
|
71
|
+
Log.d(TAG, "CarPlay monitoring started (seeded lastConnected=$lastConnected)")
|
|
54
72
|
} catch (e: Exception) {
|
|
55
73
|
Log.w(TAG, "Failed to start CarPlay monitoring: ${e.message}")
|
|
56
74
|
}
|
|
@@ -61,6 +79,7 @@ internal class CarPlayMonitor(private val context: Context) {
|
|
|
61
79
|
/** Stop observing connection state and release the emit callback. */
|
|
62
80
|
fun stop() {
|
|
63
81
|
runOnMain {
|
|
82
|
+
cancelPendingDisconnectCheck()
|
|
64
83
|
observer?.let {
|
|
65
84
|
try { liveData.removeObserver(it) } catch (_: Exception) {}
|
|
66
85
|
}
|
|
@@ -72,9 +91,67 @@ internal class CarPlayMonitor(private val context: Context) {
|
|
|
72
91
|
}
|
|
73
92
|
|
|
74
93
|
private fun handleType(type: Int) {
|
|
94
|
+
lastObservedType = type
|
|
75
95
|
val connected = type != CarConnection.CONNECTION_TYPE_NOT_CONNECTED
|
|
76
|
-
|
|
96
|
+
val previous = lastConnected
|
|
97
|
+
|
|
98
|
+
// Same state as last emitted/seeded value → cancel any pending grace
|
|
99
|
+
// re-check (the connection came back) and bail.
|
|
100
|
+
if (previous == connected) {
|
|
101
|
+
if (connected) cancelPendingDisconnectCheck()
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// First-ever observation in this process AND no persisted prior state:
|
|
106
|
+
// the LiveData's initial value during a cold start is frequently
|
|
107
|
+
// NOT_CONNECTED before the underlying Content-Provider query resolves.
|
|
108
|
+
// Treat this purely as a state seed; do not emit a spurious disconnect.
|
|
109
|
+
if (previous == null && !connected) {
|
|
110
|
+
lastConnected = false
|
|
111
|
+
Log.d(TAG, "Suppressed initial NOT_CONNECTED — seeded lastConnected=false")
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Persisted/known state was `connected` but we just observed
|
|
116
|
+
// `NOT_CONNECTED`. This is the most likely false-positive path on
|
|
117
|
+
// process restart — defer emission for a short grace period and
|
|
118
|
+
// re-check; only emit if it's still NOT_CONNECTED.
|
|
119
|
+
if (previous == true && !connected) {
|
|
120
|
+
scheduleDisconnectCheck()
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Genuine transition — emit immediately.
|
|
125
|
+
emitTransition(connected, type)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private fun scheduleDisconnectCheck() {
|
|
129
|
+
cancelPendingDisconnectCheck()
|
|
130
|
+
val r = Runnable {
|
|
131
|
+
pendingDisconnectCheck = null
|
|
132
|
+
val nowType = lastObservedType
|
|
133
|
+
val nowConnected = nowType != CarConnection.CONNECTION_TYPE_NOT_CONNECTED
|
|
134
|
+
if (nowConnected) {
|
|
135
|
+
Log.d(TAG, "Disconnect grace re-check: connection recovered (type=$nowType) — suppressed")
|
|
136
|
+
// lastConnected stays true; no event.
|
|
137
|
+
return@Runnable
|
|
138
|
+
}
|
|
139
|
+
// Still disconnected after grace — emit the real transition.
|
|
140
|
+
emitTransition(false, nowType)
|
|
141
|
+
}
|
|
142
|
+
pendingDisconnectCheck = r
|
|
143
|
+
mainHandler.postDelayed(r, BOOTSTRAP_GRACE_MS)
|
|
144
|
+
Log.d(TAG, "Deferred CarPlay disconnect by ${BOOTSTRAP_GRACE_MS}ms (grace re-check pending)")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private fun cancelPendingDisconnectCheck() {
|
|
148
|
+
pendingDisconnectCheck?.let { mainHandler.removeCallbacks(it) }
|
|
149
|
+
pendingDisconnectCheck = null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private fun emitTransition(connected: Boolean, type: Int) {
|
|
77
153
|
lastConnected = connected
|
|
154
|
+
writePersistedConnected(connected)
|
|
78
155
|
val callback = emit ?: return
|
|
79
156
|
val now = System.currentTimeMillis()
|
|
80
157
|
val nowIso = formatIso(now)
|
|
@@ -97,6 +174,23 @@ internal class CarPlayMonitor(private val context: Context) {
|
|
|
97
174
|
}
|
|
98
175
|
}
|
|
99
176
|
|
|
177
|
+
private fun readPersistedConnected(): Boolean? {
|
|
178
|
+
val prefs = prefs() ?: return null
|
|
179
|
+
if (!prefs.contains(KEY_LAST_CONNECTED)) return null
|
|
180
|
+
return prefs.getBoolean(KEY_LAST_CONNECTED, false)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private fun writePersistedConnected(connected: Boolean) {
|
|
184
|
+
prefs()?.edit()?.putBoolean(KEY_LAST_CONNECTED, connected)?.apply()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private fun prefs(): SharedPreferences? = try {
|
|
188
|
+
context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
189
|
+
} catch (e: Throwable) {
|
|
190
|
+
Log.w(TAG, "Failed to open CarPlayMonitor prefs", e)
|
|
191
|
+
null
|
|
192
|
+
}
|
|
193
|
+
|
|
100
194
|
private fun formatIso(millis: Long): String {
|
|
101
195
|
// SimpleDateFormat is not thread-safe — synchronize on the shared instance.
|
|
102
196
|
synchronized(ISO_FORMAT) {
|
|
@@ -112,11 +206,37 @@ internal class CarPlayMonitor(private val context: Context) {
|
|
|
112
206
|
}
|
|
113
207
|
}
|
|
114
208
|
|
|
115
|
-
|
|
116
|
-
const val TAG = "CarPlayMonitor"
|
|
209
|
+
companion object {
|
|
210
|
+
private const val TAG = "CarPlayMonitor"
|
|
211
|
+
private const val PREFS_NAME = "expo_beacon_carplay_monitor"
|
|
212
|
+
private const val KEY_LAST_CONNECTED = "last_connected"
|
|
213
|
+
/**
|
|
214
|
+
* Grace window before emitting a `disconnected` event when the
|
|
215
|
+
* persisted state was `connected` but the freshly-attached observer
|
|
216
|
+
* reports `NOT_CONNECTED`. Absorbs the LiveData / Content-Provider
|
|
217
|
+
* initial-value race that occurs during a cold service restart
|
|
218
|
+
* (e.g. after the app is swiped away and START_STICKY revives us).
|
|
219
|
+
*/
|
|
220
|
+
private const val BOOTSTRAP_GRACE_MS = 3_000L
|
|
117
221
|
// ISO 8601 UTC with millisecond precision. Safe for all supported APIs (minSdk 23).
|
|
118
222
|
private val ISO_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply {
|
|
119
223
|
timeZone = TimeZone.getTimeZone("UTC")
|
|
120
224
|
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Clear the persisted last-known connection state. Call when CarPlay
|
|
228
|
+
* monitoring is being explicitly disabled by the user, so that a
|
|
229
|
+
* subsequent re-enable does not assume a stale prior connection.
|
|
230
|
+
*/
|
|
231
|
+
@JvmStatic
|
|
232
|
+
fun clearPersistedState(context: Context) {
|
|
233
|
+
try {
|
|
234
|
+
context.applicationContext
|
|
235
|
+
.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
236
|
+
.edit()
|
|
237
|
+
.remove(KEY_LAST_CONNECTED)
|
|
238
|
+
.apply()
|
|
239
|
+
} catch (_: Throwable) { /* best-effort */ }
|
|
240
|
+
}
|
|
121
241
|
}
|
|
122
242
|
}
|
|
@@ -306,6 +306,15 @@ export type CarPlayDisconnectedEvent = {
|
|
|
306
306
|
timestamp: number;
|
|
307
307
|
/** ISO 8601 UTC representation of {@link timestamp} (e.g. "2026-05-12T14:23:45.678Z"). */
|
|
308
308
|
timestampIso?: string;
|
|
309
|
+
/**
|
|
310
|
+
* Reason this disconnect was emitted. Absent for normal real-time disconnects.
|
|
311
|
+
* `"reconciled"` (iOS only) indicates the disconnect was synthesized after the
|
|
312
|
+
* module was recreated in a new process and detected that the previously
|
|
313
|
+
* persisted CarPlay state no longer matches the current audio route — i.e.
|
|
314
|
+
* the disconnect happened off-process (force-quit, OS reclaim, abrupt cable
|
|
315
|
+
* yank) and is being delivered post-hoc.
|
|
316
|
+
*/
|
|
317
|
+
reason?: "reconciled";
|
|
309
318
|
};
|
|
310
319
|
/** Payload for native beacon error events (monitoring/ranging failures). */
|
|
311
320
|
export type BeaconErrorEvent = {
|
|
@@ -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 * The timeout countdown also starts if no BLE readings are received\r\n * for 60 seconds (e.g. due to Doze mode or background throttling).\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 /** Notification title on beacon timeout. Default: \"Beacon Timeout\". */\r\n timeoutTitle?: 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/** Configuration for CarPlay / Android Auto connect/disconnect notifications. */\r\nexport type CarPlayNotificationConfig = {\r\n /** Whether to show CarPlay connect/disconnect notifications. Default: true. */\r\n enabled?: boolean;\r\n /** Notification title on CarPlay/Android Auto connect. Default: \"CarPlay Connected\". */\r\n connectedTitle?: string;\r\n /** Notification title on CarPlay/Android Auto disconnect. Default: \"CarPlay Disconnected\". */\r\n disconnectedTitle?: string;\r\n /**\r\n * Notification body template. Supports `{event}` (\"connected\"/\"disconnected\") and\r\n * `{transport}` (e.g. \"wired\", \"wireless\", \"projection\", \"native\", \"unknown\") placeholders.\r\n * Note: `{transport}` is only meaningful for connect events; on disconnect it is replaced with an empty string.\r\n * Default: \"CarPlay session {event}\".\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 notification channel used for CarPlay events. */\r\nexport type CarPlayChannelConfig = {\r\n /** Channel display name shown in system settings. Default: \"CarPlay / Android Auto\". */\r\n name?: string;\r\n /** Channel description shown in system settings. Default: \"CarPlay and Android Auto connect/disconnect notifications\". */\r\n description?: string;\r\n /**\r\n * Channel importance level. Default: 'default' (so connect/disconnect events make a sound).\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 CarPlay / Android Auto connect/disconnect notifications. */\r\n carPlayEvents?: CarPlayNotificationConfig;\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 /** Settings for the Android notification channel used for CarPlay events (Android only). */\r\n carPlayChannel?: CarPlayChannelConfig;\r\n};\r\n\r\n/** Snapshot of the current monitoring configuration and active state. */\r\nexport type MonitoringConfig = {\r\n /** Whether background monitoring is currently active. */\r\n isMonitoring: boolean;\r\n maxDistance?: number;\r\n exitDistance?: number;\r\n minRssi?: number;\r\n level?: 'all' | 'events';\r\n /** Seconds after last beacon sighting before an exit event fires. Default: 300. */\r\n exitTimeoutSeconds?: number;\r\n notifications?: NotificationConfig;\r\n};\r\n\r\n/** Current state snapshot for a paired monitored device. */\r\nexport type MonitoredDeviceState =\r\n | {\r\n kind: \"ibeacon\";\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n state: \"entered\" | \"exited\";\r\n /** Current distance in metres, or null when exited or no live reading is available. */\r\n distance: number | null;\r\n }\r\n | {\r\n kind: \"eddystone\";\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n state: \"entered\" | \"exited\";\r\n /** Current distance in metres, or null when exited or no live reading is available. */\r\n distance: number | null;\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 /**\r\n * Seconds after last beacon sighting before an exit event fires when the beacon\r\n * disappears without moving outside the exit distance threshold.\r\n *\r\n * Default: 300 (5 minutes). Minimum: 1.\r\n */\r\n exitTimeoutSeconds?: 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 * The timeout countdown also starts if no BLE readings are received\r\n * for 60 seconds (e.g. due to Doze mode or background throttling).\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/** Transport reported with CarPlay / Android Auto connection events. */\r\nexport type CarPlayTransport =\r\n | \"wired\" // iOS CarPlay over USB / Lightning\r\n | \"wireless\" // iOS CarPlay over Bluetooth + Wi-Fi\r\n | \"projection\" // Android Auto projection (phone projecting to head unit)\r\n | \"native\" // Android Automotive OS (running on the head unit)\r\n | \"unknown\";\r\n\r\n/** Payload fired when the device connects to a CarPlay or Android Auto session. */\r\nexport type CarPlayConnectedEvent = {\r\n transport: CarPlayTransport;\r\n /** Timestamp in milliseconds since epoch. */\r\n timestamp: number;\r\n /** ISO 8601 UTC representation of {@link timestamp} (e.g. \"2026-05-12T14:23:45.678Z\"). */\r\n timestampIso?: string;\r\n};\r\n\r\n/** Payload fired when the device disconnects from a CarPlay or Android Auto session. */\r\nexport type CarPlayDisconnectedEvent = {\r\n /** Timestamp in milliseconds since epoch. */\r\n timestamp: number;\r\n /** ISO 8601 UTC representation of {@link timestamp} (e.g. \"2026-05-12T14:23:45.678Z\"). */\r\n timestampIso?: string;\r\n};\r\n\r\n/** Payload for native beacon error events (monitoring/ranging failures). */\r\nexport type BeaconErrorEvent = {\r\n /** Region or constraint identifier, empty string if unavailable. */\r\n identifier: string;\r\n /** Machine-readable error code (e.g. \"MONITORING_FAILED\", \"RANGING_FAILED\", \"SECURITY_EXCEPTION\"). */\r\n code: string;\r\n /** Human-readable error message from the native layer. */\r\n message: string;\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 /** Fired when a native monitoring or ranging failure occurs (logged to DB and forwarded to JS). */\r\n onBeaconError: (params: BeaconErrorEvent) => void;\r\n /** Fired when the device connects to a CarPlay (iOS) or Android Auto (Android) session. */\r\n onCarPlayConnected: (params: CarPlayConnectedEvent) => void;\r\n /** Fired when the device disconnects from a CarPlay (iOS) or Android Auto (Android) session. */\r\n onCarPlayDisconnected: (params: CarPlayDisconnectedEvent) => 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 * The timeout countdown also starts if no BLE readings are received\r\n * for 60 seconds (e.g. due to Doze mode or background throttling).\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 /** Notification title on beacon timeout. Default: \"Beacon Timeout\". */\r\n timeoutTitle?: 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/** Configuration for CarPlay / Android Auto connect/disconnect notifications. */\r\nexport type CarPlayNotificationConfig = {\r\n /** Whether to show CarPlay connect/disconnect notifications. Default: true. */\r\n enabled?: boolean;\r\n /** Notification title on CarPlay/Android Auto connect. Default: \"CarPlay Connected\". */\r\n connectedTitle?: string;\r\n /** Notification title on CarPlay/Android Auto disconnect. Default: \"CarPlay Disconnected\". */\r\n disconnectedTitle?: string;\r\n /**\r\n * Notification body template. Supports `{event}` (\"connected\"/\"disconnected\") and\r\n * `{transport}` (e.g. \"wired\", \"wireless\", \"projection\", \"native\", \"unknown\") placeholders.\r\n * Note: `{transport}` is only meaningful for connect events; on disconnect it is replaced with an empty string.\r\n * Default: \"CarPlay session {event}\".\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 notification channel used for CarPlay events. */\r\nexport type CarPlayChannelConfig = {\r\n /** Channel display name shown in system settings. Default: \"CarPlay / Android Auto\". */\r\n name?: string;\r\n /** Channel description shown in system settings. Default: \"CarPlay and Android Auto connect/disconnect notifications\". */\r\n description?: string;\r\n /**\r\n * Channel importance level. Default: 'default' (so connect/disconnect events make a sound).\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 CarPlay / Android Auto connect/disconnect notifications. */\r\n carPlayEvents?: CarPlayNotificationConfig;\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 /** Settings for the Android notification channel used for CarPlay events (Android only). */\r\n carPlayChannel?: CarPlayChannelConfig;\r\n};\r\n\r\n/** Snapshot of the current monitoring configuration and active state. */\r\nexport type MonitoringConfig = {\r\n /** Whether background monitoring is currently active. */\r\n isMonitoring: boolean;\r\n maxDistance?: number;\r\n exitDistance?: number;\r\n minRssi?: number;\r\n level?: 'all' | 'events';\r\n /** Seconds after last beacon sighting before an exit event fires. Default: 300. */\r\n exitTimeoutSeconds?: number;\r\n notifications?: NotificationConfig;\r\n};\r\n\r\n/** Current state snapshot for a paired monitored device. */\r\nexport type MonitoredDeviceState =\r\n | {\r\n kind: \"ibeacon\";\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n state: \"entered\" | \"exited\";\r\n /** Current distance in metres, or null when exited or no live reading is available. */\r\n distance: number | null;\r\n }\r\n | {\r\n kind: \"eddystone\";\r\n identifier: string;\r\n namespace: string;\r\n instance: string;\r\n state: \"entered\" | \"exited\";\r\n /** Current distance in metres, or null when exited or no live reading is available. */\r\n distance: number | null;\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 /**\r\n * Seconds after last beacon sighting before an exit event fires when the beacon\r\n * disappears without moving outside the exit distance threshold.\r\n *\r\n * Default: 300 (5 minutes). Minimum: 1.\r\n */\r\n exitTimeoutSeconds?: 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 * The timeout countdown also starts if no BLE readings are received\r\n * for 60 seconds (e.g. due to Doze mode or background throttling).\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/** Transport reported with CarPlay / Android Auto connection events. */\r\nexport type CarPlayTransport =\r\n | \"wired\" // iOS CarPlay over USB / Lightning\r\n | \"wireless\" // iOS CarPlay over Bluetooth + Wi-Fi\r\n | \"projection\" // Android Auto projection (phone projecting to head unit)\r\n | \"native\" // Android Automotive OS (running on the head unit)\r\n | \"unknown\";\r\n\r\n/** Payload fired when the device connects to a CarPlay or Android Auto session. */\r\nexport type CarPlayConnectedEvent = {\r\n transport: CarPlayTransport;\r\n /** Timestamp in milliseconds since epoch. */\r\n timestamp: number;\r\n /** ISO 8601 UTC representation of {@link timestamp} (e.g. \"2026-05-12T14:23:45.678Z\"). */\r\n timestampIso?: string;\r\n};\r\n\r\n/** Payload fired when the device disconnects from a CarPlay or Android Auto session. */\r\nexport type CarPlayDisconnectedEvent = {\r\n /** Timestamp in milliseconds since epoch. */\r\n timestamp: number;\r\n /** ISO 8601 UTC representation of {@link timestamp} (e.g. \"2026-05-12T14:23:45.678Z\"). */\r\n timestampIso?: string;\r\n /**\r\n * Reason this disconnect was emitted. Absent for normal real-time disconnects.\r\n * `\"reconciled\"` (iOS only) indicates the disconnect was synthesized after the\r\n * module was recreated in a new process and detected that the previously\r\n * persisted CarPlay state no longer matches the current audio route — i.e.\r\n * the disconnect happened off-process (force-quit, OS reclaim, abrupt cable\r\n * yank) and is being delivered post-hoc.\r\n */\r\n reason?: \"reconciled\";\r\n};\r\n\r\n/** Payload for native beacon error events (monitoring/ranging failures). */\r\nexport type BeaconErrorEvent = {\r\n /** Region or constraint identifier, empty string if unavailable. */\r\n identifier: string;\r\n /** Machine-readable error code (e.g. \"MONITORING_FAILED\", \"RANGING_FAILED\", \"SECURITY_EXCEPTION\"). */\r\n code: string;\r\n /** Human-readable error message from the native layer. */\r\n message: string;\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 /** Fired when a native monitoring or ranging failure occurs (logged to DB and forwarded to JS). */\r\n onBeaconError: (params: BeaconErrorEvent) => void;\r\n /** Fired when the device connects to a CarPlay (iOS) or Android Auto (Android) session. */\r\n onCarPlayConnected: (params: CarPlayConnectedEvent) => void;\r\n /** Fired when the device disconnects from a CarPlay (iOS) or Android Auto (Android) session. */\r\n onCarPlayDisconnected: (params: CarPlayDisconnectedEvent) => 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"]}
|
package/ios/CarPlayMonitor.swift
CHANGED
|
@@ -33,6 +33,11 @@ final class CarPlayMonitor {
|
|
|
33
33
|
private var observer: NSObjectProtocol?
|
|
34
34
|
private var emit: Emit?
|
|
35
35
|
private var isConnected: Bool = false
|
|
36
|
+
/// Optional persistence target for last-known connection state. Injected via
|
|
37
|
+
/// `start(emit:defaults:)`. When set, every connect/disconnect emission is
|
|
38
|
+
/// mirrored to `CARPLAY_LAST_CONNECTED_KEY` so a freshly relaunched process
|
|
39
|
+
/// can detect a missed disconnect via `reconcileOnProcessStart(emit:)`.
|
|
40
|
+
private var defaults: UserDefaults?
|
|
36
41
|
/// When `true`, an authoritative source (CarPlay scene delegate, granted via the
|
|
37
42
|
/// `com.apple.developer.carplay-driving-task` entitlement) is providing
|
|
38
43
|
/// connect/disconnect events. The audio-session observer becomes a passive
|
|
@@ -46,10 +51,17 @@ final class CarPlayMonitor {
|
|
|
46
51
|
/// previous emit callback but does not register a duplicate observer.
|
|
47
52
|
/// Emits an immediate `onCarPlayConnected` event if a CarPlay route is
|
|
48
53
|
/// already active at the time of the call.
|
|
49
|
-
|
|
54
|
+
///
|
|
55
|
+
/// - Parameter defaults: Optional `UserDefaults` suite used to persist the
|
|
56
|
+
/// last-known connection state for cross-process reconciliation. When the
|
|
57
|
+
/// module is recreated in a new process (e.g. after a background-wake)
|
|
58
|
+
/// the persisted value is used by `reconcileOnProcessStart(emit:)` to
|
|
59
|
+
/// synthesize a missed disconnect. Pass `nil` to disable persistence.
|
|
60
|
+
func start(emit: @escaping Emit, defaults: UserDefaults? = nil) {
|
|
50
61
|
queue.async { [weak self] in
|
|
51
62
|
guard let self = self else { return }
|
|
52
63
|
self.emit = emit
|
|
64
|
+
if defaults != nil { self.defaults = defaults }
|
|
53
65
|
if self.observer == nil {
|
|
54
66
|
self.observer = NotificationCenter.default.addObserver(
|
|
55
67
|
forName: AVAudioSession.routeChangeNotification,
|
|
@@ -69,7 +81,10 @@ final class CarPlayMonitor {
|
|
|
69
81
|
}
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
/// Stop observing route changes and clear the emit callback.
|
|
84
|
+
/// Stop observing route changes and clear the emit callback. Also clears
|
|
85
|
+
/// the persisted last-known state so a subsequent `start(...)` in a future
|
|
86
|
+
/// process doesn't trigger a spurious reconciliation. Call this only when
|
|
87
|
+
/// the user has explicitly opted out of CarPlay monitoring.
|
|
73
88
|
func stop() {
|
|
74
89
|
queue.async { [weak self] in
|
|
75
90
|
guard let self = self else { return }
|
|
@@ -80,6 +95,8 @@ final class CarPlayMonitor {
|
|
|
80
95
|
}
|
|
81
96
|
self.emit = nil
|
|
82
97
|
self.isConnected = false
|
|
98
|
+
self.isEntitledMode = false
|
|
99
|
+
self.persistConnectionState(false)
|
|
83
100
|
}
|
|
84
101
|
}
|
|
85
102
|
|
|
@@ -111,13 +128,17 @@ final class CarPlayMonitor {
|
|
|
111
128
|
self.isEntitledMode = true
|
|
112
129
|
os_log("CarPlay scene connected (entitled source)", log: self.log, type: .info)
|
|
113
130
|
if !self.isConnected {
|
|
114
|
-
self.isConnected =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
131
|
+
self.isConnected = trueclears the entitled-mode flag so the
|
|
132
|
+
/// audio-session fallback path becomes authoritative again until the next
|
|
133
|
+
/// entitled connect. Without this reset, a single missed scene-delegate
|
|
134
|
+
/// disconnect (force-quit, OS reclaim, abrupt cable yank between connect
|
|
135
|
+
/// and disconnect callbacks) would silently suppress the audio-session
|
|
136
|
+
/// fallback for the rest of the process lifetime.
|
|
137
|
+
func notifyEntitledDisconnect() {
|
|
138
|
+
queue.async { [weak self] in
|
|
139
|
+
guard let self = self else { return }
|
|
140
|
+
os_log("CarPlay scene disconnected (entitled source)", log: self.log, type: .info)
|
|
141
|
+
self.isEntitledMode = false
|
|
121
142
|
/// Emits `onCarPlayDisconnected` and keeps the entitled-mode flag set so
|
|
122
143
|
/// subsequent audio-session events remain suppressed (the scene delegate is
|
|
123
144
|
/// the source of truth for the lifetime of the process).
|
|
@@ -152,20 +173,67 @@ final class CarPlayMonitor {
|
|
|
152
173
|
/// is always trusted.
|
|
153
174
|
private func handleRouteChange(notification: Notification?) {
|
|
154
175
|
if let userInfo = notification?.userInfo,
|
|
155
|
-
|
|
156
|
-
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonRaw) {
|
|
157
|
-
switch reason {
|
|
158
|
-
case .newDeviceAvailable, .oldDeviceUnavailable:
|
|
159
|
-
break // real device change — proceed
|
|
160
|
-
default:
|
|
161
|
-
// Category/override/configuration changes etc. don't represent
|
|
162
|
-
// a CarPlay connect/disconnect. Skip to avoid spurious events.
|
|
163
|
-
return
|
|
164
|
-
}
|
|
165
|
-
}
|
|
176
|
+
let (connected, transport) = Self.currentCarPlayState()
|
|
166
177
|
// When an entitled CarPlay scene source is active it is authoritative.
|
|
167
178
|
// The audio-session signal is kept as a redundant secondary check but
|
|
168
179
|
// must NOT emit events — the scene delegate already did, or will.
|
|
180
|
+
// We DO still update the cached `isConnected` snapshot and persist it
|
|
181
|
+
// so that cross-process reconciliation (`reconcileOnProcessStart`) and
|
|
182
|
+
// the post-entitled-disconnect fallback path see an accurate state.
|
|
183
|
+
if isEntitledMode {
|
|
184
|
+
if connected != isConnected {
|
|
185
|
+
isConnected = connected
|
|
186
|
+
persistConnectionState(connected)
|
|
187
|
+
}
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
if connected == isConnected { return }
|
|
191
|
+
isConnected = connected
|
|
192
|
+
if connected {
|
|
193
|
+
emitConnected(transport: transport)
|
|
194
|
+
} else {
|
|
195
|
+
emitDisconnected()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// MARK: - Cross-process reconciliation
|
|
200
|
+
|
|
201
|
+
/// Compare persisted last-known state against the current audio route and
|
|
202
|
+
/// emit a synthetic `onCarPlayDisconnected` if persisted=connected but the
|
|
203
|
+
/// route is no longer CarPlay. Use case: the previous process was killed or
|
|
204
|
+
/// suspended-then-OS-reclaimed while CarPlay was connected, and the disconnect
|
|
205
|
+
/// fired off-process. JS listeners attached to the freshly recreated module
|
|
206
|
+
/// persistConnectionState(true)
|
|
207
|
+
emit?("onCarPlayConnected", payload)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// Emit a disconnect event. When `reason` is non-nil it is included in the
|
|
211
|
+
/// payload so consumers can distinguish real-time disconnects from
|
|
212
|
+
/// post-hoc reconciled ones (currently `"reconciled"` from
|
|
213
|
+
/// `reconcileOnProcessStart`). Additive, non-breaking.
|
|
214
|
+
private func emitDisconnected(reason: String? = nil) {
|
|
215
|
+
let now = Date()
|
|
216
|
+
var payload: [String: Any] = [
|
|
217
|
+
"timestamp": now.timeIntervalSince1970 * 1000.0,
|
|
218
|
+
"timestampIso": Self.isoFormatter.string(from: now),
|
|
219
|
+
]
|
|
220
|
+
if let reason = reason {
|
|
221
|
+
payload["reason"] = reason
|
|
222
|
+
}
|
|
223
|
+
persistConnectionState(false) let persistedConnected = defaults.bool(forKey: CARPLAY_LAST_CONNECTED_KEY)
|
|
224
|
+
let (currentConnected, _) = Self.currentCarPlayState()
|
|
225
|
+
if persistedConnected && !currentConnected {
|
|
226
|
+
os_log("CarPlay reconcile: persisted=connected, current=disconnected — emitting synthetic disconnect", log: self.log, type: .info)
|
|
227
|
+
self.isConnected = false
|
|
228
|
+
self.emitDisconnected(reason: "reconciled")
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/// Write the current connection state to the injected `UserDefaults` suite
|
|
234
|
+
/// (when available). No-op when persistence wasn't configured.
|
|
235
|
+
private func persistConnectionState(_ connected: Bool) {
|
|
236
|
+
defaults?.set(connected, forKey: CARPLAY_LAST_CONNECTED_KEY)/ must NOT emit events — the scene delegate already did, or will.
|
|
169
237
|
if isEntitledMode {
|
|
170
238
|
return
|
|
171
239
|
}
|
|
@@ -13,6 +13,10 @@ internal let MIN_RSSI_KEY = "expo.beacon.min_rssi"
|
|
|
13
13
|
internal let EVENT_LEVEL_KEY = "expo.beacon.event_level"
|
|
14
14
|
internal let EXIT_TIMEOUT_SECONDS_KEY = "expo.beacon.exit_timeout_seconds"
|
|
15
15
|
internal let CARPLAY_MONITORING_ENABLED_KEY = "expo.beacon.carplay_monitoring_enabled"
|
|
16
|
+
/// Persisted last-known CarPlay connection state. Used by `CarPlayMonitor` to
|
|
17
|
+
/// reconcile across process recreations (e.g. background-launch wake) and emit
|
|
18
|
+
/// a synthetic `onCarPlayDisconnected` if the route is no longer CarPlay.
|
|
19
|
+
internal let CARPLAY_LAST_CONNECTED_KEY = "expo.beacon.carplay_last_connected"
|
|
16
20
|
|
|
17
21
|
// MARK: - Tuning thresholds
|
|
18
22
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import CoreLocation
|
|
2
|
+
import UIKit
|
|
2
3
|
|
|
3
4
|
extension ExpoBeaconModule {
|
|
4
5
|
/// Starts the shared `CarPlayMonitor` and routes its events through the
|
|
@@ -6,7 +7,7 @@ extension ExpoBeaconModule {
|
|
|
6
7
|
/// + lifecycle plugin registry). Idempotent — safe to call multiple times
|
|
7
8
|
/// (see `CarPlayMonitor.start(emit:)` semantics).
|
|
8
9
|
func startCarPlayMonitoringInternal() {
|
|
9
|
-
CarPlayMonitor.shared.start { [weak self] eventName, payload in
|
|
10
|
+
CarPlayMonitor.shared.start(emit: { [weak self] eventName, payload in
|
|
10
11
|
guard let self = self else { return }
|
|
11
12
|
self.sendLoggedEvent(eventName, payload)
|
|
12
13
|
switch eventName {
|
|
@@ -20,15 +21,20 @@ extension ExpoBeaconModule {
|
|
|
20
21
|
default:
|
|
21
22
|
break
|
|
22
23
|
}
|
|
23
|
-
}
|
|
24
|
+
}, defaults: defaults)
|
|
24
25
|
// Tier 2 fallback: subscribe to background-wake signals so suspended
|
|
25
26
|
// apps still notice CarPlay route changes that happened off-process.
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
// Run this UNCONDITIONALLY (even when the entitled scene-delegate
|
|
28
|
+
// source is active) — the scene delegate can fail to deliver a
|
|
29
|
+
// disconnect callback in edge cases (force-quit, OS reclaim, abrupt
|
|
30
|
+
// cable yank) and SLC + Visit are the only reconciliation safety net.
|
|
31
|
+
// Cost is negligible: no continuous GPS, just opportunistic wakes.
|
|
32
|
+
startCarPlayBackgroundWakes()
|
|
33
|
+
// Foreground reconciliation: when the user returns to the app, snapshot
|
|
34
|
+
// the current audio route and emit a connect/disconnect transition if
|
|
35
|
+
// it diverges from the cached state. Catches missed route-change
|
|
36
|
+
// notifications during long suspensions.
|
|
37
|
+
observeAppForegroundForCarPlay()
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
/// Start Significant Location Change + Visit monitoring as background-wake
|
|
@@ -58,4 +64,26 @@ extension ExpoBeaconModule {
|
|
|
58
64
|
CarPlayMonitor.shared.resyncIfNeeded()
|
|
59
65
|
}
|
|
60
66
|
}
|
|
67
|
+
|
|
68
|
+
/// Register a `UIApplication.didBecomeActiveNotification` observer that
|
|
69
|
+
/// resyncs CarPlay state on foreground. Idempotent — the observer token is
|
|
70
|
+
/// cached on the module instance and re-registration is a no-op.
|
|
71
|
+
func observeAppForegroundForCarPlay() {
|
|
72
|
+
if carPlayForegroundObserver != nil { return }
|
|
73
|
+
carPlayForegroundObserver = NotificationCenter.default.addObserver(
|
|
74
|
+
forName: UIApplication.didBecomeActiveNotification,
|
|
75
|
+
object: nil,
|
|
76
|
+
queue: .main
|
|
77
|
+
) { _ in
|
|
78
|
+
CarPlayMonitor.shared.resyncIfNeeded()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Tear down the foreground observer. Safe to call when none is registered.
|
|
83
|
+
func removeAppForegroundObserverForCarPlay() {
|
|
84
|
+
if let token = carPlayForegroundObserver {
|
|
85
|
+
NotificationCenter.default.removeObserver(token)
|
|
86
|
+
carPlayForegroundObserver = nil
|
|
87
|
+
}
|
|
88
|
+
}
|
|
61
89
|
}
|
|
@@ -93,6 +93,13 @@ public class ExpoBeaconModule: Module {
|
|
|
93
93
|
|
|
94
94
|
internal var permissionCompletion: ((Bool) -> Void)?
|
|
95
95
|
|
|
96
|
+
// MARK: - CarPlay
|
|
97
|
+
|
|
98
|
+
/// Observer token for `UIApplication.didBecomeActiveNotification` used to
|
|
99
|
+
/// resync CarPlay state on foreground. Owned by the CarPlay extension —
|
|
100
|
+
/// declared here so the module instance can hold it across calls.
|
|
101
|
+
internal var carPlayForegroundObserver: NSObjectProtocol?
|
|
102
|
+
|
|
96
103
|
// MARK: - Cached paired data (invalidated on pair/unpair)
|
|
97
104
|
|
|
98
105
|
internal var cachedPairedBeacons: [[String: Any]]?
|
|
@@ -137,6 +144,11 @@ public class ExpoBeaconModule: Module {
|
|
|
137
144
|
// would be missed until JS calls startCarPlayMonitoring() again.
|
|
138
145
|
if self.defaults.bool(forKey: CARPLAY_MONITORING_ENABLED_KEY) {
|
|
139
146
|
self.startCarPlayMonitoringInternal()
|
|
147
|
+
// Cross-process reconciliation: if the previous process recorded
|
|
148
|
+
// CarPlay as connected but the current audio route is no longer
|
|
149
|
+
// CarPlay, emit a synthetic disconnect so JS listeners attached
|
|
150
|
+
// to this freshly-created module learn the session ended.
|
|
151
|
+
CarPlayMonitor.shared.reconcileOnProcessStart()
|
|
140
152
|
}
|
|
141
153
|
}
|
|
142
154
|
|
|
@@ -455,7 +467,8 @@ public class ExpoBeaconModule: Module {
|
|
|
455
467
|
self.startCarPlayMonitoringInternal()
|
|
456
468
|
promise.resolve(nil)
|
|
457
469
|
}
|
|
458
|
-
|
|
470
|
+
self.removeAppForegroundObserverForCarPlay()
|
|
471
|
+
|
|
459
472
|
AsyncFunction("stopCarPlayMonitoring") { (promise: Promise) in
|
|
460
473
|
self.defaults.set(false, forKey: CARPLAY_MONITORING_ENABLED_KEY)
|
|
461
474
|
CarPlayMonitor.shared.stop()
|
|
@@ -620,7 +633,10 @@ public class ExpoBeaconModule: Module {
|
|
|
620
633
|
// that route changes continue to be observed across module recreations
|
|
621
634
|
// (e.g. background-launch wake → module re-init → OnDestroy on suspend).
|
|
622
635
|
if !self.defaults.bool(forKey: CARPLAY_MONITORING_ENABLED_KEY) {
|
|
623
|
-
|
|
636
|
+
|
|
637
|
+
// Foreground observer is bound to this module instance — always
|
|
638
|
+
// remove it on destroy to avoid leaking observers across recreations.
|
|
639
|
+
self.removeAppForegroundObserverForCarPlay() CarPlayMonitor.shared.stop()
|
|
624
640
|
}
|
|
625
641
|
self.centralManager?.stopScan()
|
|
626
642
|
self.centralManager = nil
|
package/package.json
CHANGED
package/src/ExpoBeacon.types.ts
CHANGED
|
@@ -335,6 +335,15 @@ export type CarPlayDisconnectedEvent = {
|
|
|
335
335
|
timestamp: number;
|
|
336
336
|
/** ISO 8601 UTC representation of {@link timestamp} (e.g. "2026-05-12T14:23:45.678Z"). */
|
|
337
337
|
timestampIso?: string;
|
|
338
|
+
/**
|
|
339
|
+
* Reason this disconnect was emitted. Absent for normal real-time disconnects.
|
|
340
|
+
* `"reconciled"` (iOS only) indicates the disconnect was synthesized after the
|
|
341
|
+
* module was recreated in a new process and detected that the previously
|
|
342
|
+
* persisted CarPlay state no longer matches the current audio route — i.e.
|
|
343
|
+
* the disconnect happened off-process (force-quit, OS reclaim, abrupt cable
|
|
344
|
+
* yank) and is being delivered post-hoc.
|
|
345
|
+
*/
|
|
346
|
+
reason?: "reconciled";
|
|
338
347
|
};
|
|
339
348
|
|
|
340
349
|
/** Payload for native beacon error events (monitoring/ranging failures). */
|