expo-beacon 0.8.3 → 0.8.5

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.
@@ -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
- if (lastConnected == connected) return
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
- private companion object {
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"]}
@@ -33,11 +33,17 @@ 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()`.
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
39
44
  /// secondary signal only — it will not emit events, to avoid duplicate
40
- /// connect/disconnect notifications.
45
+ /// connect/disconnect notifications. Reset to `false` on entitled disconnect
46
+ /// so the audio-session fallback can take over for any subsequent events.
41
47
  private var isEntitledMode: Bool = false
42
48
 
43
49
  private init() {}
@@ -46,10 +52,17 @@ final class CarPlayMonitor {
46
52
  /// previous emit callback but does not register a duplicate observer.
47
53
  /// Emits an immediate `onCarPlayConnected` event if a CarPlay route is
48
54
  /// already active at the time of the call.
49
- func start(emit: @escaping Emit) {
55
+ ///
56
+ /// - Parameter defaults: Optional `UserDefaults` suite used to persist the
57
+ /// last-known connection state for cross-process reconciliation. When the
58
+ /// module is recreated in a new process (e.g. after a background-wake)
59
+ /// the persisted value is used by `reconcileOnProcessStart()` to
60
+ /// synthesize a missed disconnect. Pass `nil` to disable persistence.
61
+ func start(emit: @escaping Emit, defaults: UserDefaults? = nil) {
50
62
  queue.async { [weak self] in
51
63
  guard let self = self else { return }
52
64
  self.emit = emit
65
+ if defaults != nil { self.defaults = defaults }
53
66
  if self.observer == nil {
54
67
  self.observer = NotificationCenter.default.addObserver(
55
68
  forName: AVAudioSession.routeChangeNotification,
@@ -69,7 +82,10 @@ final class CarPlayMonitor {
69
82
  }
70
83
  }
71
84
 
72
- /// Stop observing route changes and clear the emit callback.
85
+ /// Stop observing route changes and clear the emit callback. Also clears
86
+ /// the persisted last-known state so a subsequent `start(...)` in a future
87
+ /// process doesn't trigger a spurious reconciliation. Call this only when
88
+ /// the user has explicitly opted out of CarPlay monitoring.
73
89
  func stop() {
74
90
  queue.async { [weak self] in
75
91
  guard let self = self else { return }
@@ -80,6 +96,8 @@ final class CarPlayMonitor {
80
96
  }
81
97
  self.emit = nil
82
98
  self.isConnected = false
99
+ self.isEntitledMode = false
100
+ self.persistConnectionState(false)
83
101
  }
84
102
  }
85
103
 
@@ -118,13 +136,17 @@ final class CarPlayMonitor {
118
136
  }
119
137
 
120
138
  /// Called by `BeaconCarPlaySceneDelegate.templateApplicationScene(_:didDisconnect:)`.
121
- /// Emits `onCarPlayDisconnected` and keeps the entitled-mode flag set so
122
- /// subsequent audio-session events remain suppressed (the scene delegate is
123
- /// the source of truth for the lifetime of the process).
139
+ /// Emits `onCarPlayDisconnected` and clears the entitled-mode flag so the
140
+ /// audio-session fallback path becomes authoritative again until the next
141
+ /// entitled connect. Without this reset, a single missed scene-delegate
142
+ /// disconnect (force-quit, OS reclaim, abrupt cable yank between connect
143
+ /// and disconnect callbacks) would silently suppress the audio-session
144
+ /// fallback for the rest of the process lifetime.
124
145
  func notifyEntitledDisconnect() {
125
146
  queue.async { [weak self] in
126
147
  guard let self = self else { return }
127
148
  os_log("CarPlay scene disconnected (entitled source)", log: self.log, type: .info)
149
+ self.isEntitledMode = false
128
150
  if self.isConnected {
129
151
  self.isConnected = false
130
152
  self.emitDisconnected()
@@ -163,13 +185,20 @@ final class CarPlayMonitor {
163
185
  return
164
186
  }
165
187
  }
188
+ let (connected, transport) = Self.currentCarPlayState()
166
189
  // When an entitled CarPlay scene source is active it is authoritative.
167
190
  // The audio-session signal is kept as a redundant secondary check but
168
191
  // must NOT emit events — the scene delegate already did, or will.
192
+ // We DO still update the cached `isConnected` snapshot and persist it
193
+ // so that cross-process reconciliation (`reconcileOnProcessStart`) and
194
+ // the post-entitled-disconnect fallback path see an accurate state.
169
195
  if isEntitledMode {
196
+ if connected != isConnected {
197
+ isConnected = connected
198
+ persistConnectionState(connected)
199
+ }
170
200
  return
171
201
  }
172
- let (connected, transport) = Self.currentCarPlayState()
173
202
  if connected == isConnected { return }
174
203
  isConnected = connected
175
204
  if connected {
@@ -179,6 +208,37 @@ final class CarPlayMonitor {
179
208
  }
180
209
  }
181
210
 
211
+ // MARK: - Cross-process reconciliation
212
+
213
+ /// Compare persisted last-known state against the current audio route and
214
+ /// emit a synthetic `onCarPlayDisconnected` if persisted=connected but the
215
+ /// route is no longer CarPlay. Use case: the previous process was killed or
216
+ /// suspended-then-OS-reclaimed while CarPlay was connected, and the disconnect
217
+ /// fired off-process. JS listeners attached to the freshly recreated module
218
+ /// would otherwise never learn the session ended.
219
+ ///
220
+ /// Must be called AFTER `start(emit:defaults:)` so the emit callback is
221
+ /// installed and persistence target is known. Idempotent.
222
+ func reconcileOnProcessStart() {
223
+ queue.async { [weak self] in
224
+ guard let self = self else { return }
225
+ guard let defaults = self.defaults else { return }
226
+ let persistedConnected = defaults.bool(forKey: CARPLAY_LAST_CONNECTED_KEY)
227
+ let (currentConnected, _) = Self.currentCarPlayState()
228
+ if persistedConnected && !currentConnected {
229
+ os_log("CarPlay reconcile: persisted=connected, current=disconnected — emitting synthetic disconnect", log: self.log, type: .info)
230
+ self.isConnected = false
231
+ self.emitDisconnected(reason: "reconciled")
232
+ }
233
+ }
234
+ }
235
+
236
+ /// Write the current connection state to the injected `UserDefaults` suite
237
+ /// (when available). No-op when persistence wasn't configured.
238
+ private func persistConnectionState(_ connected: Bool) {
239
+ defaults?.set(connected, forKey: CARPLAY_LAST_CONNECTED_KEY)
240
+ }
241
+
182
242
  private func emitConnected(transport: String) {
183
243
  let now = Date()
184
244
  let payload: [String: Any] = [
@@ -186,15 +246,24 @@ final class CarPlayMonitor {
186
246
  "timestamp": now.timeIntervalSince1970 * 1000.0,
187
247
  "timestampIso": Self.isoFormatter.string(from: now),
188
248
  ]
249
+ persistConnectionState(true)
189
250
  emit?("onCarPlayConnected", payload)
190
251
  }
191
252
 
192
- private func emitDisconnected() {
253
+ /// Emit a disconnect event. When `reason` is non-nil it is included in the
254
+ /// payload so consumers can distinguish real-time disconnects from
255
+ /// post-hoc reconciled ones (currently `"reconciled"` from
256
+ /// `reconcileOnProcessStart`). Additive, non-breaking.
257
+ private func emitDisconnected(reason: String? = nil) {
193
258
  let now = Date()
194
- let payload: [String: Any] = [
259
+ var payload: [String: Any] = [
195
260
  "timestamp": now.timeIntervalSince1970 * 1000.0,
196
261
  "timestampIso": Self.isoFormatter.string(from: now),
197
262
  ]
263
+ if let reason = reason {
264
+ payload["reason"] = reason
265
+ }
266
+ persistConnectionState(false)
198
267
  emit?("onCarPlayDisconnected", payload)
199
268
  }
200
269
 
@@ -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
- // Skipped when the entitled CarPlay scene path is providing real-time
27
- // events that source keeps the app awake for the entire CarPlay
28
- // session and renders SLC/Visit redundant.
29
- if !CarPlayMonitor.shared.isUsingEntitledSource {
30
- startCarPlayBackgroundWakes()
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
- CarPlayMonitor.shared.stop()
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-beacon",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
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",
@@ -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). */