expo-beacon 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -11,7 +11,7 @@ An Expo module for scanning, pairing, and monitoring iBeacons in React Native ap
11
11
  | Platform | Native implementation |
12
12
  | -------- | --------------------------------------------------------------------------------------------------------- |
13
13
  | Android | [AltBeacon](https://altbeacon.github.io/android-beacon-library/) (`org.altbeacon:android-beacon-library`) |
14
- | iOS | CoreLocation / `CLLocationManager` (iBeacon native API) |
14
+ | iOS | CoreLocation (UUID-targeted scans & monitoring) + CoreBluetooth (wildcard scanning) |
15
15
  | Web | Not supported (throws on all calls) |
16
16
 
17
17
  ---
@@ -47,6 +47,8 @@ In Xcode under **Signing & Capabilities**, enable:
47
47
  - **Background Modes → Uses Bluetooth LE accessories**
48
48
 
49
49
  > iOS limits apps to **20 simultaneously monitored regions**.
50
+ >
51
+ > **Wildcard scanning**: When you call `scanForBeaconsAsync()` with an empty or omitted `uuids` array, iOS uses CoreBluetooth raw BLE scanning to discover all nearby iBeacons. This works **in the foreground only** — it is an Apple platform limitation. UUID-targeted scans and background monitoring continue to use CoreLocation.
50
52
 
51
53
  ### Android
52
54
 
@@ -111,7 +113,8 @@ export default function App() {
111
113
  }, []);
112
114
 
113
115
  async function scan() {
114
- const results = await ExpoBeacon.scanForBeaconsAsync(5000);
116
+ // Wildcard scan discovers all nearby iBeacons (no UUID filter)
117
+ const results = await ExpoBeacon.scanForBeaconsAsync([], 5000);
115
118
  setBeacons(results);
116
119
  }
117
120
 
@@ -158,23 +161,41 @@ if (!granted) {
158
161
 
159
162
  ---
160
163
 
161
- ### `scanForBeaconsAsync(scanDurationMs?)`
164
+ ### `scanForBeaconsAsync(uuids?, scanDurationMs?)`
162
165
 
163
166
  ```ts
164
- scanForBeaconsAsync(scanDurationMs?: number): Promise<BeaconScanResult[]>
167
+ scanForBeaconsAsync(uuids?: string[], scanDurationMs?: number): Promise<BeaconScanResult[]>
165
168
  ```
166
169
 
167
170
  Starts a **one-shot BLE scan**, waits for `scanDurationMs` milliseconds, then resolves with all beacons discovered during that window.
168
171
 
169
- | Parameter | Type | Default | Description |
170
- | --------------- | -------- | ------- | ---------------------------------------- |
171
- | `scanDurationMs`| `number` | `5000` | How long to scan in milliseconds (1–60 000 recommended) |
172
+ | Parameter | Type | Default | Description |
173
+ | ---------------- | ---------- | ------- | ---------------------------------------- |
174
+ | `uuids` | `string[]` | `[]` | Proximity UUIDs to filter by. Pass `[]` or omit for a **wildcard scan** that discovers all nearby iBeacons. |
175
+ | `scanDurationMs` | `number` | `5000` | How long to scan in milliseconds (1–60 000 recommended) |
172
176
 
173
177
  Returns an array of [`BeaconScanResult`](#beaconscanresult) objects. Rejects with `SCAN_IN_PROGRESS` if another scan is already running.
174
178
 
179
+ **Wildcard vs. targeted scans**
180
+
181
+ | | Wildcard (`[]` / omitted) | Targeted (`['UUID-1', …]`) |
182
+ |---|---|---|
183
+ | **Android** | Discovers all iBeacons via AltBeacon | Filters results to matching UUIDs |
184
+ | **iOS** | CoreBluetooth raw BLE scan (**foreground only**) | CoreLocation ranging (works in background) |
185
+
186
+ > **iOS limitation**: Wildcard scanning uses CoreBluetooth, which cannot scan in the background. If your app is backgrounded during a wildcard scan, no new beacons will be discovered. Use UUID-targeted scans or `startMonitoring()` for background beacon detection.
187
+
175
188
  ```ts
176
- const beacons = await ExpoBeacon.scanForBeaconsAsync(8000); // 8-second scan
177
- beacons.forEach((b) =>
189
+ // Wildcard scan discover all nearby iBeacons
190
+ const all = await ExpoBeacon.scanForBeaconsAsync([], 5000);
191
+
192
+ // Targeted scan — only beacons matching these UUIDs
193
+ const filtered = await ExpoBeacon.scanForBeaconsAsync(
194
+ ['E2C56DB5-DFFB-48D2-B060-D0F5A71096E0'],
195
+ 8000,
196
+ );
197
+
198
+ filtered.forEach((b) =>
178
199
  console.log(`${b.uuid} major=${b.major} minor=${b.minor} dist=${b.distance.toFixed(1)}m rssi=${b.rssi}dBm`)
179
200
  );
180
201
  ```
@@ -191,6 +212,8 @@ Begins a **continuous BLE scan** that fires an [`onBeaconFound`](#onbeaconfound)
191
212
 
192
213
  Unlike `scanForBeaconsAsync`, this never resolves — it streams results in real time.
193
214
 
215
+ > **iOS note**: Due to CoreLocation API constraints, `startContinuousScan()` on iOS only ranges beacons that have been previously paired with `pairBeacon()`. On Android, all nearby BLE beacons are reported regardless of pairing status.
216
+
194
217
  ```ts
195
218
  const sub = ExpoBeacon.addListener("onBeaconFound", (beacon) => {
196
219
  console.log("Live:", beacon.uuid, beacon.distance);
@@ -420,15 +443,7 @@ ExpoBeacon.addListener("onBeaconExit", (e) => {
420
443
 
421
444
  ### `onBeaconRanging`
422
445
 
423
- Fired periodically during active monitoring with the latest ranging measurement for a paired beacon.
424
-
425
- **Payload**: [`BeaconRangingEvent`](#beaconrangingevent)
426
-
427
- ```ts
428
- ExpoBeacon.addListener("onBeaconRanging", (e) => {
429
- console.log(`Ranging ${e.identifier}: ${e.distance.toFixed(2)} m rssi=${e.rssi}`);
430
- });
431
- ```
446
+ > **Not currently emitted.** This event is declared in the TypeScript types ([`BeaconRangingEvent`](#beaconrangingevent)) but is not fired by either the iOS or Android native implementation. Use [`onBeaconDistance`](#onbeacondistance) for periodic distance updates during monitoring.
432
447
 
433
448
  ---
434
449
 
@@ -507,7 +522,7 @@ type BeaconRegionEvent = {
507
522
 
508
523
  ### `BeaconRangingEvent`
509
524
 
510
- Payload for `onBeaconRanging`.
525
+ Payload type for the `onBeaconRanging` event. **Declared for future use — this event is not currently emitted by either platform.** Use [`BeaconDistanceEvent`](#beacondistanceevent) and [`onBeaconDistance`](#onbeacondistance) for real-time distance updates.
511
526
 
512
527
  ```ts
513
528
  type BeaconRangingEvent = {
@@ -648,7 +663,7 @@ Both notifications share the channel id `expo_beacon_channel`. The channel is re
648
663
 
649
664
  ## Contributing
650
665
 
651
- Contributions are welcome! Please refer to the [contributing guide](https://github.com/expo/expo#contributing).
666
+ Contributions are welcome! Open an issue or pull request on [GitHub](https://github.com/martinmikesCCS/expo-beacon).
652
667
 
653
668
  ## License
654
669
 
@@ -18,7 +18,12 @@ private const val PREFS_NAME = "expo.beacon.paired"
18
18
  private const val PREFS_KEY = "paired_beacons"
19
19
  private const val CHANNEL_ID = "expo_beacon_channel"
20
20
  private const val FOREGROUND_NOTIF_ID = 1001
21
- private const val ENTER_EXIT_NOTIF_ID = 1002
21
+ private const val ENTER_EXIT_NOTIF_BASE_ID = 2000
22
+
23
+ /// Number of consecutive ranging misses before emitting a distance-based exit event.
24
+ private const val EXIT_MISS_THRESHOLD = 3
25
+ /// Number of consecutive readings required to confirm a distance-based enter or exit transition.
26
+ private const val HYSTERESIS_COUNT = 3
22
27
 
23
28
  class BeaconForegroundService : Service(), BeaconConsumer {
24
29
 
@@ -30,6 +35,16 @@ class BeaconForegroundService : Service(), BeaconConsumer {
30
35
  private val rangingRegions = java.util.concurrent.CopyOnWriteArraySet<Region>()
31
36
  private val enteredRegions = java.util.concurrent.CopyOnWriteArraySet<String>()
32
37
 
38
+ // Hysteresis counters (synchronized on distanceLock)
39
+ private val distanceLock = Any()
40
+ private val enterCounters = java.util.concurrent.ConcurrentHashMap<String, Int>()
41
+ private val exitCounters = java.util.concurrent.ConcurrentHashMap<String, Int>()
42
+ private val missCounters = java.util.concurrent.ConcurrentHashMap<String, Int>()
43
+
44
+ // Notification ID counter for unique per-beacon notifications
45
+ private var notifIdCounter = 0
46
+ private val notifIdMap = java.util.concurrent.ConcurrentHashMap<String, Int>()
47
+
33
48
  // Distance logging
34
49
  private val distanceLogRegions = java.util.concurrent.CopyOnWriteArraySet<Region>()
35
50
 
@@ -153,26 +168,62 @@ class BeaconForegroundService : Service(), BeaconConsumer {
153
168
  Log.d("BeaconMonitor", "[${region.uniqueId}] distance: ${"%.2f".format(closest.distance)} m rssi=${closest.rssi} txPower=${closest.txPower}")
154
169
  sendBeaconBroadcast(region, "distance", closest.distance)
155
170
 
171
+ // Reset miss counter — we got a valid reading
172
+ missCounters[region.uniqueId] = 0
173
+
156
174
  val maxDist = maxDistance
157
175
  if (maxDist != null) {
158
- if (!enteredRegions.contains(region.uniqueId) && closest.distance <= maxDist) {
159
- // Distance-based entry
160
- Log.d("BeaconMonitor", "[${region.uniqueId}] distance ${closest.distance}m <= maxDistance ${maxDist}m — synthesizing enter")
161
- enteredRegions.add(region.uniqueId)
162
- rangingRegions.remove(region)
163
- sendBeaconBroadcast(region, "enter", closest.distance)
164
- showEnterExitNotification(region, "enter")
165
- } else if (enteredRegions.contains(region.uniqueId) && closest.distance > maxDist) {
166
- // Distance-based exit
167
- Log.d("BeaconMonitor", "[${region.uniqueId}] distance ${closest.distance}m > maxDistance ${maxDist}m — synthesizing exit")
168
- enteredRegions.remove(region.uniqueId)
169
- rangingRegions.add(region)
170
- sendBeaconBroadcast(region, "exit", closest.distance)
171
- showEnterExitNotification(region, "exit")
176
+ synchronized(distanceLock) {
177
+ if (closest.distance <= maxDist) {
178
+ // Reading is inside threshold
179
+ exitCounters[region.uniqueId] = 0
180
+ val count = (enterCounters[region.uniqueId] ?: 0) + 1
181
+ enterCounters[region.uniqueId] = count
182
+
183
+ if (!enteredRegions.contains(region.uniqueId) && count >= HYSTERESIS_COUNT) {
184
+ enteredRegions.add(region.uniqueId)
185
+ enterCounters[region.uniqueId] = 0
186
+ rangingRegions.remove(region)
187
+ sendBeaconBroadcast(region, "enter", closest.distance)
188
+ showEnterExitNotification(region, "enter")
189
+ }
190
+ } else {
191
+ // Reading is outside threshold
192
+ enterCounters[region.uniqueId] = 0
193
+ val count = (exitCounters[region.uniqueId] ?: 0) + 1
194
+ exitCounters[region.uniqueId] = count
195
+
196
+ if (enteredRegions.contains(region.uniqueId) && count >= HYSTERESIS_COUNT) {
197
+ enteredRegions.remove(region.uniqueId)
198
+ exitCounters[region.uniqueId] = 0
199
+ rangingRegions.add(region)
200
+ sendBeaconBroadcast(region, "exit", closest.distance)
201
+ showEnterExitNotification(region, "exit")
202
+ }
203
+ }
172
204
  }
173
205
  }
174
206
  } else {
175
207
  Log.d("BeaconMonitor", "[${region.uniqueId}] no beacons in range")
208
+
209
+ // Beacon may have disappeared — track consecutive misses
210
+ val maxDist = maxDistance
211
+ if (maxDist != null) {
212
+ val count = (missCounters[region.uniqueId] ?: 0) + 1
213
+ missCounters[region.uniqueId] = count
214
+
215
+ if (enteredRegions.contains(region.uniqueId) && count >= EXIT_MISS_THRESHOLD) {
216
+ synchronized(distanceLock) {
217
+ if (enteredRegions.remove(region.uniqueId)) {
218
+ missCounters[region.uniqueId] = 0
219
+ enterCounters[region.uniqueId] = 0
220
+ exitCounters[region.uniqueId] = 0
221
+ sendBeaconBroadcast(region, "exit", -1.0)
222
+ showEnterExitNotification(region, "exit")
223
+ }
224
+ }
225
+ }
226
+ }
176
227
  }
177
228
  }
178
229
 
@@ -190,8 +241,15 @@ class BeaconForegroundService : Service(), BeaconConsumer {
190
241
  }
191
242
 
192
243
  override fun didExitRegion(region: Region) {
193
- // Remove from confirmation tracking (ranging stays active for distance logging)
194
244
  rangingRegions.remove(region)
245
+
246
+ if (maxDistance != null) {
247
+ // When maxDistance is set, distance ranging handles exit events.
248
+ // Just clean up tracked state so distance-driven exit can fire.
249
+ enteredRegions.remove(region.uniqueId)
250
+ return
251
+ }
252
+
195
253
  enteredRegions.remove(region.uniqueId)
196
254
  sendBeaconBroadcast(region, "exit", -1.0)
197
255
  showEnterExitNotification(region, "exit")
@@ -209,12 +267,19 @@ class BeaconForegroundService : Service(), BeaconConsumer {
209
267
  .filter { it.distance >= 0 }
210
268
  .minByOrNull { it.distance } ?: return@RangeNotifier
211
269
 
212
- if (beacon.distance <= maxDist && !enteredRegions.contains(region.uniqueId)) {
213
- enteredRegions.add(region.uniqueId)
214
- // Remove from confirmation tracking (ranging stays active for distance logging)
215
- rangingRegions.remove(region)
216
- sendBeaconBroadcast(region, "enter", beacon.distance)
217
- showEnterExitNotification(region, "enter")
270
+ synchronized(distanceLock) {
271
+ if (beacon.distance <= maxDist && !enteredRegions.contains(region.uniqueId)) {
272
+ val count = (enterCounters[region.uniqueId] ?: 0) + 1
273
+ enterCounters[region.uniqueId] = count
274
+
275
+ if (count >= HYSTERESIS_COUNT) {
276
+ enteredRegions.add(region.uniqueId)
277
+ enterCounters[region.uniqueId] = 0
278
+ rangingRegions.remove(region)
279
+ sendBeaconBroadcast(region, "enter", beacon.distance)
280
+ showEnterExitNotification(region, "enter")
281
+ }
282
+ }
218
283
  }
219
284
  }
220
285
 
@@ -266,7 +331,10 @@ class BeaconForegroundService : Service(), BeaconConsumer {
266
331
  .build()
267
332
 
268
333
  try {
269
- NotificationManagerCompat.from(this).notify(ENTER_EXIT_NOTIF_ID, notification)
334
+ val notifId = notifIdMap.getOrPut(region.uniqueId) {
335
+ ENTER_EXIT_NOTIF_BASE_ID + notifIdCounter++
336
+ }
337
+ NotificationManagerCompat.from(this).notify(notifId, notification)
270
338
  } catch (_: SecurityException) {
271
339
  // POST_NOTIFICATIONS not granted — silently skip notification
272
340
  }
@@ -288,12 +356,13 @@ class BeaconForegroundService : Service(), BeaconConsumer {
288
356
  }
289
357
 
290
358
  val notifMgr = getSystemService(NotificationManager::class.java)
291
- // Delete and recreate so config changes take effect
292
- notifMgr?.deleteNotificationChannel(CHANNEL_ID)
293
- val channel = NotificationChannel(CHANNEL_ID, channelName, importance).apply {
294
- description = channelDesc
359
+ // Only create channel if it doesn't exist yet — preserves user notification preferences
360
+ if (notifMgr?.getNotificationChannel(CHANNEL_ID) == null) {
361
+ val channel = NotificationChannel(CHANNEL_ID, channelName, importance).apply {
362
+ description = channelDesc
363
+ }
364
+ notifMgr?.createNotificationChannel(channel)
295
365
  }
296
- notifMgr?.createNotificationChannel(channel)
297
366
  }
298
367
  }
299
368
 
@@ -339,6 +408,10 @@ class BeaconForegroundService : Service(), BeaconConsumer {
339
408
  }
340
409
  distanceLogRegions.clear()
341
410
  enteredRegions.clear()
411
+ enterCounters.clear()
412
+ exitCounters.clear()
413
+ missCounters.clear()
414
+ notifIdMap.clear()
342
415
  monitoredRegions.forEach {
343
416
  try { beaconManager.stopMonitoringBeaconsInRegion(it) } catch (_: RemoteException) {}
344
417
  }
@@ -52,6 +52,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
52
52
  private var scanPromise: Promise? = null
53
53
  private var scanJob: Job? = null
54
54
  private val scanResults = mutableListOf<Beacon>()
55
+ private var scanUuidFilter: Set<String> = emptySet()
55
56
  private var isBoundForScan = false
56
57
 
57
58
  // Continuous scan state
@@ -61,14 +62,15 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
61
62
  override fun definition() = ModuleDefinition {
62
63
  Name("ExpoBeacon")
63
64
 
64
- Events("onBeaconEnter", "onBeaconExit", "onBeaconRanging", "onBeaconDistance", "onBeaconFound")
65
+ Events("onBeaconEnter", "onBeaconExit", "onBeaconDistance", "onBeaconFound")
65
66
 
66
- AsyncFunction("scanForBeaconsAsync") { scanDurationMs: Int, promise: Promise ->
67
+ AsyncFunction("scanForBeaconsAsync") { uuids: List<String>?, scanDurationMs: Int, promise: Promise ->
67
68
  if (scanPromise != null) {
68
69
  promise.reject("SCAN_IN_PROGRESS", "A scan is already running", null)
69
70
  return@AsyncFunction
70
71
  }
71
72
  scanResults.clear()
73
+ scanUuidFilter = uuids?.map { it.lowercase() }?.toSet() ?: emptySet()
72
74
  scanPromise = promise
73
75
 
74
76
  beaconManager.addRangeNotifier(scanRangeNotifier)
@@ -113,6 +115,19 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
113
115
  }
114
116
 
115
117
  Function("pairBeacon") { identifier: String, uuid: String, major: Int, minor: Int ->
118
+ // Validate UUID format
119
+ try {
120
+ java.util.UUID.fromString(uuid)
121
+ } catch (_: IllegalArgumentException) {
122
+ throw expo.modules.kotlin.exception.CodedException("INVALID_UUID", "Invalid UUID format: $uuid", null)
123
+ }
124
+ if (major !in 0..65535) {
125
+ throw expo.modules.kotlin.exception.CodedException("INVALID_MAJOR", "Major must be 0\u201365535, got $major", null)
126
+ }
127
+ if (minor !in 0..65535) {
128
+ throw expo.modules.kotlin.exception.CodedException("INVALID_MINOR", "Minor must be 0\u201365535, got $minor", null)
129
+ }
130
+
116
131
  val beacons = loadPairedBeaconsJson()
117
132
  // Remove duplicate if exists
118
133
  val filtered = (0 until beacons.length())
@@ -176,6 +191,15 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
176
191
  if (maxDistance != null) putFloat("max_distance", maxDistance.toFloat())
177
192
  else remove("max_distance")
178
193
  }.apply()
194
+ // Verify we have the permissions needed for background monitoring
195
+ val hasLocation = ContextCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
196
+ val hasBgLocation = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ||
197
+ ContextCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED
198
+ if (!hasLocation || !hasBgLocation) {
199
+ promise.reject("PERMISSION_DENIED", "Location permissions required for background monitoring. Call requestPermissionsAsync() first.", null)
200
+ return@AsyncFunction
201
+ }
202
+
179
203
  registerEventReceiver()
180
204
  BeaconForegroundService.start(ctx)
181
205
  promise.resolve(null)
@@ -195,7 +219,8 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
195
219
  }
196
220
 
197
221
  AsyncFunction("requestPermissionsAsync") { promise: Promise ->
198
- val required = buildList {
222
+ // Step 1: request foreground permissions
223
+ val foreground = buildList {
199
224
  add(Manifest.permission.ACCESS_FINE_LOCATION)
200
225
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
201
226
  add(Manifest.permission.BLUETOOTH_SCAN)
@@ -212,7 +237,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
212
237
  promise.resolve(false)
213
238
  return@AsyncFunction
214
239
  }
215
- val allGranted = required.all {
240
+ val allGranted = foreground.all {
216
241
  ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
217
242
  }
218
243
  promise.resolve(allGranted)
@@ -220,11 +245,24 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
220
245
  }
221
246
 
222
247
  permissionsManager.askForPermissions({ results ->
223
- val allGranted = required.all { perm ->
248
+ val fgGranted = foreground.all { perm ->
224
249
  results[perm]?.status == PermissionsStatus.GRANTED
225
250
  }
226
- promise.resolve(allGranted)
227
- }, *required.toTypedArray())
251
+ if (!fgGranted) {
252
+ promise.resolve(false)
253
+ return@askForPermissions
254
+ }
255
+ // Step 2: request background location (Android 10+)
256
+ // Must be requested separately after foreground location is granted
257
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
258
+ permissionsManager.askForPermissions({ bgResults ->
259
+ val bgGranted = bgResults[Manifest.permission.ACCESS_BACKGROUND_LOCATION]?.status == PermissionsStatus.GRANTED
260
+ promise.resolve(bgGranted)
261
+ }, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
262
+ } else {
263
+ promise.resolve(true)
264
+ }
265
+ }, *foreground.toTypedArray())
228
266
  }
229
267
 
230
268
  OnDestroy {
@@ -266,7 +304,13 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
266
304
  }
267
305
 
268
306
  private val scanRangeNotifier = RangeNotifier { beacons, _ ->
269
- scanResults.addAll(beacons)
307
+ if (scanUuidFilter.isEmpty()) {
308
+ scanResults.addAll(beacons)
309
+ } else {
310
+ scanResults.addAll(beacons.filter { beacon ->
311
+ scanUuidFilter.contains(beacon.id1.toString().lowercase())
312
+ })
313
+ }
270
314
  }
271
315
 
272
316
  private val continuousScanRangeNotifier = RangeNotifier { beacons, _ ->
@@ -309,6 +353,7 @@ class ExpoBeaconModule : Module(), BeaconConsumer {
309
353
  }
310
354
  scanPromise?.resolve(results)
311
355
  scanPromise = null
356
+ scanUuidFilter = emptySet()
312
357
  }
313
358
 
314
359
  // --- Notification config helpers ---
@@ -24,15 +24,6 @@ export type BeaconRegionEvent = {
24
24
  /** Measured distance in metres at the time of the event (–1 if unavailable). */
25
25
  distance: number;
26
26
  };
27
- /** Payload for ranging events (beacon proximity update). */
28
- export type BeaconRangingEvent = {
29
- identifier: string;
30
- uuid: string;
31
- major: number;
32
- minor: number;
33
- rssi: number;
34
- distance: number;
35
- };
36
27
  /** Payload for periodic distance update events during monitoring. */
37
28
  export type BeaconDistanceEvent = {
38
29
  identifier: string;
@@ -103,7 +94,6 @@ export type MonitoringOptions = {
103
94
  export type ExpoBeaconModuleEvents = {
104
95
  onBeaconEnter: (params: BeaconRegionEvent) => void;
105
96
  onBeaconExit: (params: BeaconRegionEvent) => void;
106
- onBeaconRanging: (params: BeaconRangingEvent) => void;
107
97
  onBeaconDistance: (params: BeaconDistanceEvent) => void;
108
98
  /** Fired continuously during a live scan as each beacon is detected. */
109
99
  onBeaconFound: (params: BeaconScanResult) => void;
@@ -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};\r\n\r\n/** A beacon that has been paired/registered for monitoring. */\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};\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};\r\n\r\n/** Payload for ranging events (beacon proximity update). */\r\nexport type BeaconRangingEvent = {\r\n identifier: string;\r\n uuid: string;\r\n major: number;\r\n minor: number;\r\n rssi: number;\r\n distance: 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};\r\n\r\n/** Configuration for beacon enter/exit event notifications. */\r\nexport type BeaconNotificationConfig = {\r\n /** Whether to show enter/exit notifications. Default: true. */\r\n enabled?: boolean;\r\n /** Notification title on beacon enter. Default: \"Beacon Entered\". */\r\n enterTitle?: string;\r\n /** Notification title on beacon exit. Default: \"Beacon Exited\". */\r\n exitTitle?: string;\r\n /**\r\n * Notification body template. Supports {identifier} and {event} placeholders.\r\n * Default: \"{identifier} region {event}ed\".\r\n */\r\n body?: string;\r\n /** Play a sound with the notification (iOS only). Default: true. */\r\n sound?: boolean;\r\n /** Android drawable resource name for the notification icon (e.g. \"ic_notification\"). */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android foreground service notification (persistent status bar entry). */\r\nexport type ForegroundServiceConfig = {\r\n /** Title of the persistent notification. Default: \"Beacon Monitoring Active\". */\r\n title?: string;\r\n /** Body text of the persistent notification. Default: \"Monitoring for iBeacons in the background\". */\r\n text?: string;\r\n /** Android drawable resource name for the notification icon. */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android notification channel. */\r\nexport type NotificationChannelConfig = {\r\n /** Channel display name shown in system settings. Default: \"Beacon Monitoring\". */\r\n name?: string;\r\n /** Channel description shown in system settings. Default: \"Used for background iBeacon region monitoring\". */\r\n description?: string;\r\n /**\r\n * Channel importance level. Default: 'low'.\r\n * Note: Android may ignore decreases in importance after first channel creation until the app is reinstalled.\r\n */\r\n importance?: \"low\" | \"default\" | \"high\";\r\n};\r\n\r\n/** Combined notification configuration for all notification types. */\r\nexport type NotificationConfig = {\r\n /** Settings for beacon enter/exit event notifications. */\r\n beaconEvents?: BeaconNotificationConfig;\r\n /** Settings for the persistent foreground service notification (Android only). */\r\n foregroundService?: ForegroundServiceConfig;\r\n /** Settings for the Android notification channel (Android only). */\r\n channel?: NotificationChannelConfig;\r\n};\r\n\r\n/** Options accepted by startMonitoring(). */\r\nexport type MonitoringOptions = {\r\n /**\r\n * Maximum distance in metres for distance-based enter events.\r\n * Exit events are always emitted when the region is lost.\r\n */\r\n maxDistance?: number;\r\n /** Notification configuration overrides to apply for this monitoring session. */\r\n notifications?: NotificationConfig;\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 onBeaconRanging: (params: BeaconRangingEvent) => void;\r\n onBeaconDistance: (params: BeaconDistanceEvent) => void;\r\n /** Fired continuously during a live scan as each beacon is detected. */\r\n onBeaconFound: (params: BeaconScanResult) => void;\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};\r\n\r\n/** A beacon that has been paired/registered for monitoring. */\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};\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};\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};\r\n\r\n/** Configuration for beacon enter/exit event notifications. */\r\nexport type BeaconNotificationConfig = {\r\n /** Whether to show enter/exit notifications. Default: true. */\r\n enabled?: boolean;\r\n /** Notification title on beacon enter. Default: \"Beacon Entered\". */\r\n enterTitle?: string;\r\n /** Notification title on beacon exit. Default: \"Beacon Exited\". */\r\n exitTitle?: string;\r\n /**\r\n * Notification body template. Supports {identifier} and {event} placeholders.\r\n * Default: \"{identifier} region {event}ed\".\r\n */\r\n body?: string;\r\n /** Play a sound with the notification (iOS only). Default: true. */\r\n sound?: boolean;\r\n /** Android drawable resource name for the notification icon (e.g. \"ic_notification\"). */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android foreground service notification (persistent status bar entry). */\r\nexport type ForegroundServiceConfig = {\r\n /** Title of the persistent notification. Default: \"Beacon Monitoring Active\". */\r\n title?: string;\r\n /** Body text of the persistent notification. Default: \"Monitoring for iBeacons in the background\". */\r\n text?: string;\r\n /** Android drawable resource name for the notification icon. */\r\n icon?: string;\r\n};\r\n\r\n/** Configuration for the Android notification channel. */\r\nexport type NotificationChannelConfig = {\r\n /** Channel display name shown in system settings. Default: \"Beacon Monitoring\". */\r\n name?: string;\r\n /** Channel description shown in system settings. Default: \"Used for background iBeacon region monitoring\". */\r\n description?: string;\r\n /**\r\n * Channel importance level. Default: 'low'.\r\n * Note: Android may ignore decreases in importance after first channel creation until the app is reinstalled.\r\n */\r\n importance?: \"low\" | \"default\" | \"high\";\r\n};\r\n\r\n/** Combined notification configuration for all notification types. */\r\nexport type NotificationConfig = {\r\n /** Settings for beacon enter/exit event notifications. */\r\n beaconEvents?: BeaconNotificationConfig;\r\n /** Settings for the persistent foreground service notification (Android only). */\r\n foregroundService?: ForegroundServiceConfig;\r\n /** Settings for the Android notification channel (Android only). */\r\n channel?: NotificationChannelConfig;\r\n};\r\n\r\n/** Options accepted by startMonitoring(). */\r\nexport type MonitoringOptions = {\r\n /**\r\n * Maximum distance in metres for distance-based enter events.\r\n * Exit events are always emitted when the region is lost.\r\n */\r\n maxDistance?: number;\r\n /** Notification configuration overrides to apply for this monitoring session. */\r\n notifications?: NotificationConfig;\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 continuously during a live scan as each beacon is detected. */\r\n onBeaconFound: (params: BeaconScanResult) => void;\r\n};\r\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoBeaconModule.d.ts","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAiFA,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"ExpoBeaconModule.d.ts","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AA0FA,eAAe,MAAM,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoBeaconModule.js","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAuEzD,IAAI,CAAC;IACH,qDAAqD;IACrD,IAAI,MAAM,GAAG,mBAAmB,CAAmB,YAAY,CAAC,CAAC;AACnE,CAAC;AAAC,MAAM,CAAC;IACP,MAAM,IAAI,KAAK,CACb,oFAAoF;QAClF,8EAA8E,CACjF,CAAC;AACJ,CAAC;AAED,eAAe,MAAM,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\r\n\r\nimport {\r\n ExpoBeaconModuleEvents,\r\n BeaconScanResult,\r\n PairedBeacon,\r\n NotificationConfig,\r\n MonitoringOptions,\r\n} from \"./ExpoBeacon.types\";\r\n\r\ndeclare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {\r\n /**\r\n * Start a one-shot BLE scan. Resolves with discovered beacons after scanDuration ms.\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForBeaconsAsync(scanDuration?: number): Promise<BeaconScanResult[]>;\r\n\r\n /**\r\n * Register a beacon for persistent region monitoring.\r\n */\r\n pairBeacon(\r\n identifier: string,\r\n uuid: string,\r\n major: number,\r\n minor: number,\r\n ): void;\r\n\r\n /**\r\n * Remove a previously paired beacon.\r\n */\r\n unpairBeacon(identifier: string): void;\r\n\r\n /**\r\n * Return all currently paired beacons.\r\n */\r\n getPairedBeacons(): PairedBeacon[];\r\n\r\n /**\r\n * Set persistent notification configuration. Settings are saved and applied to all\r\n * subsequent monitoring sessions until explicitly changed.\r\n */\r\n setNotificationConfig(config: NotificationConfig): void;\r\n\r\n /**\r\n * Start background region monitoring for all paired beacons.\r\n * On Android starts a foreground service.\r\n * On iOS starts CLLocationManager region monitoring.\r\n *\r\n * Accepts a plain number (backward-compatible maxDistance shorthand) or a\r\n * MonitoringOptions object with maxDistance and/or notification overrides.\r\n */\r\n startMonitoring(options?: MonitoringOptions | number): Promise<void>;\r\n\r\n /**\r\n * Stop background region monitoring.\r\n */\r\n stopMonitoring(): Promise<void>;\r\n\r\n /**\r\n * Start a continuous BLE scan. Fires `onBeaconFound` events as beacons are detected.\r\n * Call stopContinuousScan() to end the scan.\r\n */\r\n startContinuousScan(): void;\r\n\r\n /** Stop the continuous scan started by startContinuousScan(). */\r\n stopContinuousScan(): void;\r\n\r\n /** Request Bluetooth + Location permissions. Returns true if granted. */\r\n requestPermissionsAsync(): Promise<boolean>;\r\n}\r\n\r\ntry {\r\n // eslint-disable-next-line import/no-mutable-exports\r\n var module = requireNativeModule<ExpoBeaconModule>(\"ExpoBeacon\");\r\n} catch {\r\n throw new Error(\r\n \"expo-beacon: native module not found. Make sure you are using a development build \" +\r\n \"(not Expo Go) and have run `npx expo prebuild` followed by a native rebuild.\",\r\n );\r\n}\r\n\r\nexport default module;\r\n"]}
1
+ {"version":3,"file":"ExpoBeaconModule.js","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAgFzD,IAAI,CAAC;IACH,qDAAqD;IACrD,IAAI,MAAM,GAAG,mBAAmB,CAAmB,YAAY,CAAC,CAAC;AACnE,CAAC;AAAC,MAAM,CAAC;IACP,MAAM,IAAI,KAAK,CACb,oFAAoF;QAClF,8EAA8E,CACjF,CAAC;AACJ,CAAC;AAED,eAAe,MAAM,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\r\n\r\nimport {\r\n ExpoBeaconModuleEvents,\r\n BeaconScanResult,\r\n PairedBeacon,\r\n NotificationConfig,\r\n MonitoringOptions,\r\n} from \"./ExpoBeacon.types\";\r\n\r\ndeclare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {\r\n /**\r\n * Start a one-shot iBeacon scan. Resolves with discovered beacons after scanDuration ms.\r\n *\r\n * Pass one or more UUIDs to scan for specific beacons (uses CoreLocation on iOS).\r\n * Pass an empty array or omit to perform a wildcard scan that discovers all nearby\r\n * iBeacons (uses CoreBluetooth on iOS — foreground only).\r\n *\r\n * @param uuids Proximity UUIDs to filter by. Empty/omitted = wildcard scan.\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForBeaconsAsync(\r\n uuids?: string[],\r\n scanDuration?: number,\r\n ): Promise<BeaconScanResult[]>;\r\n\r\n /**\r\n * Register a beacon for persistent region monitoring.\r\n */\r\n pairBeacon(\r\n identifier: string,\r\n uuid: string,\r\n major: number,\r\n minor: number,\r\n ): void;\r\n\r\n /**\r\n * Remove a previously paired beacon.\r\n */\r\n unpairBeacon(identifier: string): void;\r\n\r\n /**\r\n * Return all currently paired beacons.\r\n */\r\n getPairedBeacons(): PairedBeacon[];\r\n\r\n /**\r\n * Set persistent notification configuration. Settings are saved and applied to all\r\n * subsequent monitoring sessions until explicitly changed.\r\n */\r\n setNotificationConfig(config: NotificationConfig): void;\r\n\r\n /**\r\n * Start background region monitoring for all paired beacons.\r\n * On Android starts a foreground service.\r\n * On iOS starts CLLocationManager region monitoring.\r\n *\r\n * Accepts a plain number (backward-compatible maxDistance shorthand) or a\r\n * MonitoringOptions object with maxDistance and/or notification overrides.\r\n */\r\n startMonitoring(options?: MonitoringOptions | number): Promise<void>;\r\n\r\n /**\r\n * Stop background region monitoring.\r\n */\r\n stopMonitoring(): Promise<void>;\r\n\r\n /**\r\n * Start a continuous BLE scan. Fires `onBeaconFound` events as beacons are detected.\r\n * Call stopContinuousScan() to end the scan.\r\n */\r\n startContinuousScan(): void;\r\n\r\n /** Stop the continuous scan started by startContinuousScan(). */\r\n stopContinuousScan(): void;\r\n\r\n /** Request Bluetooth + Location permissions. Returns true if granted. */\r\n requestPermissionsAsync(): Promise<boolean>;\r\n}\r\n\r\ntry {\r\n // eslint-disable-next-line import/no-mutable-exports\r\n var module = requireNativeModule<ExpoBeaconModule>(\"ExpoBeacon\");\r\n} catch {\r\n throw new Error(\r\n \"expo-beacon: native module not found. Make sure you are using a development build \" +\r\n \"(not Expo Go) and have run `npx expo prebuild` followed by a native rebuild.\",\r\n );\r\n}\r\n\r\nexport default module;\r\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { ExpoBeaconModuleEvents, BeaconScanResult, PairedBeacon } from "./ExpoBeacon.types";
2
2
  declare const stub: {
3
- scanForBeaconsAsync: (_scanDuration?: number) => Promise<BeaconScanResult[]>;
3
+ scanForBeaconsAsync: (_uuids: string[], _scanDuration?: number) => Promise<BeaconScanResult[]>;
4
4
  pairBeacon: (_identifier: string, _uuid: string, _major: number, _minor: number) => void;
5
5
  unpairBeacon: (_identifier: string) => void;
6
6
  getPairedBeacons: () => PairedBeacon[];
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoBeaconModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoBeaconModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,YAAY,EACb,MAAM,oBAAoB,CAAC;AAM5B,QAAA,MAAM,IAAI;0CAC8B,MAAM,KAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;8BAG3D,MAAM,SACZ,MAAM,UACL,MAAM,UACN,MAAM,KACb,IAAI;gCACqB,MAAM,KAAG,IAAI;4BACnB,YAAY,EAAE;2BACf,OAAO,CAAC,IAAI,CAAC;0BACd,OAAO,CAAC,IAAI,CAAC;mCACJ,OAAO,CAAC,OAAO,CAAC;8BACnB,MAAM,sBAAsB,aAAa,GAAG;;;qCAGrC,MAAM,sBAAsB;CAC9D,CAAC;AAEF,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"ExpoBeaconModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoBeaconModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,YAAY,EACb,MAAM,oBAAoB,CAAC;AAM5B,QAAA,MAAM,IAAI;kCAEE,MAAM,EAAE,kBACA,MAAM,KACrB,OAAO,CAAC,gBAAgB,EAAE,CAAC;8BAEf,MAAM,SACZ,MAAM,UACL,MAAM,UACN,MAAM,KACb,IAAI;gCACqB,MAAM,KAAG,IAAI;4BACnB,YAAY,EAAE;2BACf,OAAO,CAAC,IAAI,CAAC;0BACd,OAAO,CAAC,IAAI,CAAC;mCACJ,OAAO,CAAC,OAAO,CAAC;8BACnB,MAAM,sBAAsB,aAAa,GAAG;;;qCAGrC,MAAM,sBAAsB;CAC9D,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -2,7 +2,7 @@ const notSupported = () => {
2
2
  throw new Error("expo-beacon is not supported on web.");
3
3
  };
4
4
  const stub = {
5
- scanForBeaconsAsync: (_scanDuration) => notSupported(),
5
+ scanForBeaconsAsync: (_uuids, _scanDuration) => notSupported(),
6
6
  pairBeacon: (_identifier, _uuid, _major, _minor) => notSupported(),
7
7
  unpairBeacon: (_identifier) => notSupported(),
8
8
  getPairedBeacons: () => notSupported(),
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoBeaconModule.web.js","sourceRoot":"","sources":["../src/ExpoBeaconModule.web.ts"],"names":[],"mappings":"AAMA,MAAM,YAAY,GAAG,GAAU,EAAE;IAC/B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;AAC1D,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG;IACX,mBAAmB,EAAE,CAAC,aAAsB,EAA+B,EAAE,CAC3E,YAAY,EAAE;IAChB,UAAU,EAAE,CACV,WAAmB,EACnB,KAAa,EACb,MAAc,EACd,MAAc,EACR,EAAE,CAAC,YAAY,EAAE;IACzB,YAAY,EAAE,CAAC,WAAmB,EAAQ,EAAE,CAAC,YAAY,EAAE;IAC3D,gBAAgB,EAAE,GAAmB,EAAE,CAAC,YAAY,EAAE;IACtD,eAAe,EAAE,GAAkB,EAAE,CAAC,YAAY,EAAE;IACpD,cAAc,EAAE,GAAkB,EAAE,CAAC,YAAY,EAAE;IACnD,uBAAuB,EAAE,GAAqB,EAAE,CAAC,YAAY,EAAE;IAC/D,WAAW,EAAE,CAAC,UAAwC,EAAE,SAAc,EAAE,EAAE,CAAC,CAAC;QAC1E,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC;KACjB,CAAC;IACF,kBAAkB,EAAE,CAAC,UAAwC,EAAE,EAAE,GAAE,CAAC;CACrE,CAAC;AAEF,eAAe,IAAI,CAAC","sourcesContent":["import type {\r\n ExpoBeaconModuleEvents,\r\n BeaconScanResult,\r\n PairedBeacon,\r\n} from \"./ExpoBeacon.types\";\r\n\r\nconst notSupported = (): never => {\r\n throw new Error(\"expo-beacon is not supported on web.\");\r\n};\r\n\r\nconst stub = {\r\n scanForBeaconsAsync: (_scanDuration?: number): Promise<BeaconScanResult[]> =>\r\n notSupported(),\r\n pairBeacon: (\r\n _identifier: string,\r\n _uuid: string,\r\n _major: number,\r\n _minor: number,\r\n ): void => notSupported(),\r\n unpairBeacon: (_identifier: string): void => notSupported(),\r\n getPairedBeacons: (): PairedBeacon[] => notSupported(),\r\n startMonitoring: (): Promise<void> => notSupported(),\r\n stopMonitoring: (): Promise<void> => notSupported(),\r\n requestPermissionsAsync: (): Promise<boolean> => notSupported(),\r\n addListener: (_eventName: keyof ExpoBeaconModuleEvents, _listener: any) => ({\r\n remove: () => {},\r\n }),\r\n removeAllListeners: (_eventName: keyof ExpoBeaconModuleEvents) => {},\r\n};\r\n\r\nexport default stub;\r\n"]}
1
+ {"version":3,"file":"ExpoBeaconModule.web.js","sourceRoot":"","sources":["../src/ExpoBeaconModule.web.ts"],"names":[],"mappings":"AAMA,MAAM,YAAY,GAAG,GAAU,EAAE;IAC/B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;AAC1D,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG;IACX,mBAAmB,EAAE,CACnB,MAAgB,EAChB,aAAsB,EACO,EAAE,CAAC,YAAY,EAAE;IAChD,UAAU,EAAE,CACV,WAAmB,EACnB,KAAa,EACb,MAAc,EACd,MAAc,EACR,EAAE,CAAC,YAAY,EAAE;IACzB,YAAY,EAAE,CAAC,WAAmB,EAAQ,EAAE,CAAC,YAAY,EAAE;IAC3D,gBAAgB,EAAE,GAAmB,EAAE,CAAC,YAAY,EAAE;IACtD,eAAe,EAAE,GAAkB,EAAE,CAAC,YAAY,EAAE;IACpD,cAAc,EAAE,GAAkB,EAAE,CAAC,YAAY,EAAE;IACnD,uBAAuB,EAAE,GAAqB,EAAE,CAAC,YAAY,EAAE;IAC/D,WAAW,EAAE,CAAC,UAAwC,EAAE,SAAc,EAAE,EAAE,CAAC,CAAC;QAC1E,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC;KACjB,CAAC;IACF,kBAAkB,EAAE,CAAC,UAAwC,EAAE,EAAE,GAAE,CAAC;CACrE,CAAC;AAEF,eAAe,IAAI,CAAC","sourcesContent":["import type {\r\n ExpoBeaconModuleEvents,\r\n BeaconScanResult,\r\n PairedBeacon,\r\n} from \"./ExpoBeacon.types\";\r\n\r\nconst notSupported = (): never => {\r\n throw new Error(\"expo-beacon is not supported on web.\");\r\n};\r\n\r\nconst stub = {\r\n scanForBeaconsAsync: (\r\n _uuids: string[],\r\n _scanDuration?: number,\r\n ): Promise<BeaconScanResult[]> => notSupported(),\r\n pairBeacon: (\r\n _identifier: string,\r\n _uuid: string,\r\n _major: number,\r\n _minor: number,\r\n ): void => notSupported(),\r\n unpairBeacon: (_identifier: string): void => notSupported(),\r\n getPairedBeacons: (): PairedBeacon[] => notSupported(),\r\n startMonitoring: (): Promise<void> => notSupported(),\r\n stopMonitoring: (): Promise<void> => notSupported(),\r\n requestPermissionsAsync: (): Promise<boolean> => notSupported(),\r\n addListener: (_eventName: keyof ExpoBeaconModuleEvents, _listener: any) => ({\r\n remove: () => {},\r\n }),\r\n removeAllListeners: (_eventName: keyof ExpoBeaconModuleEvents) => {},\r\n};\r\n\r\nexport default stub;\r\n"]}
package/build/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { default } from "./ExpoBeaconModule";
2
- export type { BeaconScanResult, PairedBeacon, BeaconRegionEvent, BeaconRangingEvent, BeaconDistanceEvent, ExpoBeaconModuleEvents, NotificationConfig, MonitoringOptions, BeaconNotificationConfig, ForegroundServiceConfig, NotificationChannelConfig, } from "./ExpoBeacon.types";
2
+ export type { BeaconScanResult, PairedBeacon, BeaconRegionEvent, BeaconDistanceEvent, ExpoBeaconModuleEvents, NotificationConfig, MonitoringOptions, BeaconNotificationConfig, ForegroundServiceConfig, NotificationChannelConfig, } from "./ExpoBeacon.types";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC","sourcesContent":["// Native module (default export)\r\nexport { default } from \"./ExpoBeaconModule\";\r\n\r\n// All public types\r\nexport type {\r\n BeaconScanResult,\r\n PairedBeacon,\r\n BeaconRegionEvent,\r\n BeaconRangingEvent,\r\n BeaconDistanceEvent,\r\n ExpoBeaconModuleEvents,\r\n NotificationConfig,\r\n MonitoringOptions,\r\n BeaconNotificationConfig,\r\n ForegroundServiceConfig,\r\n NotificationChannelConfig,\r\n} from \"./ExpoBeacon.types\";\r\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC","sourcesContent":["// Native module (default export)\r\nexport { default } from \"./ExpoBeaconModule\";\r\n\r\n// All public types\r\nexport type {\r\n BeaconScanResult,\r\n PairedBeacon,\r\n BeaconRegionEvent,\r\n BeaconDistanceEvent,\r\n ExpoBeaconModuleEvents,\r\n NotificationConfig,\r\n MonitoringOptions,\r\n BeaconNotificationConfig,\r\n ForegroundServiceConfig,\r\n NotificationChannelConfig,\r\n} from \"./ExpoBeacon.types\";\r\n"]}
@@ -19,7 +19,7 @@ Pod::Spec.new do |s|
19
19
 
20
20
  s.dependency 'ExpoModulesCore'
21
21
 
22
- # Required system frameworks for iBeacon monitoring
22
+ # Required system frameworks for iBeacon monitoring + wildcard BLE scanning
23
23
  s.frameworks = 'CoreLocation', 'CoreBluetooth', 'UserNotifications'
24
24
 
25
25
  # Swift/Objective-C compatibility