expo-beacon 0.4.0 → 0.4.1

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
@@ -48,9 +48,9 @@ In Xcode under **Signing & Capabilities**, enable:
48
48
 
49
49
  > iOS limits apps to **20 simultaneously monitored regions** (iBeacon only — Eddystone beacons are monitored via BLE and do not count toward this limit).
50
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.
51
+ > **iBeacon scanning on iOS**: Apple strips iBeacon manufacturer data from CoreBluetooth BLE advertisements, so wildcard iBeacon discovery is **not possible** on iOS. You must provide at least one proximity UUID, or pair beacons first (the module will use paired beacon UUIDs automatically). UUID-targeted scans use CoreLocation ranging and work in both foreground and background.
52
52
  >
53
- > **Eddystone scanning**: Eddystone beacons use standard BLE service data (UUID `0xFEAA`), which iOS does not filter. `scanForEddystonesAsync()` and continuous scanning work in the foreground on both platforms.
53
+ > **Eddystone scanning**: Eddystone beacons use standard BLE service data (UUID `0xFEAA`), which iOS does not filter. `scanForEddystonesAsync()` and continuous scanning discover Eddystones on both platforms without restrictions.
54
54
 
55
55
  ### Android
56
56
 
@@ -203,30 +203,30 @@ Starts a **one-shot BLE scan**, waits for `scanDurationMs` milliseconds, then re
203
203
 
204
204
  | Parameter | Type | Default | Description |
205
205
  | ---------------- | ---------- | ------- | ---------------------------------------- |
206
- | `uuids` | `string[]` | `[]` | Proximity UUIDs to filter by. Pass `[]` or omit for a **wildcard scan** that discovers all nearby iBeacons. |
206
+ | `uuids` | `string[]` | `[]` | Proximity UUIDs to filter by. **iOS**: at least one UUID is required (or have paired beacons). **Android**: pass `[]` for a wildcard scan. |
207
207
  | `scanDurationMs` | `number` | `5000` | How long to scan in milliseconds (1–60 000 recommended) |
208
208
 
209
209
  Returns an array of [`BeaconScanResult`](#beaconscanresult) objects. Rejects with `SCAN_IN_PROGRESS` if another scan is already running.
210
210
 
211
- **Wildcard vs. targeted scans**
211
+ **Platform differences**
212
212
 
213
- | | Wildcard (`[]` / omitted) | Targeted (`['UUID-1', …]`) |
213
+ | | Empty UUIDs (`[]`) | Targeted (`['UUID-1', …]`) |
214
214
  |---|---|---|
215
215
  | **Android** | Discovers all iBeacons via AltBeacon | Filters results to matching UUIDs |
216
- | **iOS** | CoreBluetooth raw BLE scan (**foreground only**) | CoreLocation ranging (works in background) |
216
+ | **iOS** | Uses paired beacon UUIDs automatically. Rejects if no UUIDs and no paired beacons. | CoreLocation ranging (works in foreground & background) |
217
217
 
218
- > **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.
218
+ > **iOS limitation**: Apple strips iBeacon manufacturer data from CoreBluetooth BLE advertisements. Wildcard iBeacon scanning (no UUID filter) is not possible on iOS. When you pass an empty `uuids` array, the module automatically uses UUIDs from your paired beacons. If no beacons are paired, the call rejects with `WILDCARD_NOT_SUPPORTED`. For Eddystone scanning, use `scanForEddystonesAsync()` instead it works without restrictions on both platforms.
219
219
 
220
220
  ```ts
221
- // Wildcard scandiscover all nearby iBeacons
222
- const all = await ExpoBeacon.scanForBeaconsAsync([], 5000);
223
-
224
- // Targeted scan — only beacons matching these UUIDs
221
+ // Scan by UUID works on both platforms
225
222
  const filtered = await ExpoBeacon.scanForBeaconsAsync(
226
223
  ['E2C56DB5-DFFB-48D2-B060-D0F5A71096E0'],
227
224
  8000,
228
225
  );
229
226
 
227
+ // Wildcard scan — Android only. On iOS, uses paired beacon UUIDs.
228
+ const all = await ExpoBeacon.scanForBeaconsAsync([], 5000);
229
+
230
230
  filtered.forEach((b) =>
231
231
  console.log(`${b.uuid} major=${b.major} minor=${b.minor} dist=${b.distance.toFixed(1)}m rssi=${b.rssi}dBm`)
232
232
  );
@@ -5,10 +5,12 @@ declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
5
5
  * Start a one-shot iBeacon scan. Resolves with discovered beacons after scanDuration ms.
6
6
  *
7
7
  * Pass one or more UUIDs to scan for specific beacons (uses CoreLocation on iOS).
8
- * Pass an empty array or omit to perform a wildcard scan that discovers all nearby
9
- * iBeacons (uses CoreBluetooth on iOS foreground only).
8
+ * On iOS, at least one UUID is required Apple strips iBeacon data from BLE
9
+ * advertisements, making wildcard discovery impossible. When you pass an empty
10
+ * array, the module automatically uses UUIDs from paired beacons.
11
+ * On Android, pass an empty array to discover all nearby iBeacons.
10
12
  *
11
- * @param uuids Proximity UUIDs to filter by. Empty/omitted = wildcard scan.
13
+ * @param uuids Proximity UUIDs to filter by. Empty/omitted = use paired UUIDs (iOS) or wildcard (Android).
12
14
  * @param scanDuration Duration in ms (default 5000)
13
15
  */
14
16
  scanForBeaconsAsync(uuids?: string[], scanDuration?: number): Promise<BeaconScanResult[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoBeaconModule.d.ts","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,OAAO,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;IACzE;;;;;;;;;OASG;IACH,mBAAmB,CACjB,KAAK,CAAC,EAAE,MAAM,EAAE,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAE9B;;;;;OAKG;IACH,sBAAsB,CACpB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAEjC;;OAEG;IACH,UAAU,CACR,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,IAAI;IAEP;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAEtC;;OAEG;IACH,gBAAgB,IAAI,YAAY,EAAE;IAElC;;OAEG;IACH,aAAa,CACX,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,IAAI;IAEP;;OAEG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAEzC;;OAEG;IACH,mBAAmB,IAAI,eAAe,EAAE;IAExC;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAEvD;;;;;;;OAOG;IACH,eAAe,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEpE;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAE/B;;;OAGG;IACH,mBAAmB,IAAI,IAAI;IAE3B,iEAAiE;IACjE,kBAAkB,IAAI,IAAI;IAE1B,yEAAyE;IACzE,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;CAC5C;;AAED,wBAAmE"}
1
+ {"version":3,"file":"ExpoBeaconModule.d.ts","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,OAAO,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;IACzE;;;;;;;;;;;OAWG;IACH,mBAAmB,CACjB,KAAK,CAAC,EAAE,MAAM,EAAE,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAE9B;;;;;OAKG;IACH,sBAAsB,CACpB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAEjC;;OAEG;IACH,UAAU,CACR,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,IAAI;IAEP;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAEtC;;OAEG;IACH,gBAAgB,IAAI,YAAY,EAAE;IAElC;;OAEG;IACH,aAAa,CACX,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,IAAI;IAEP;;OAEG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAEzC;;OAEG;IACH,mBAAmB,IAAI,eAAe,EAAE;IAExC;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAEvD;;;;;;;OAOG;IACH,eAAe,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEpE;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAE/B;;;OAGG;IACH,mBAAmB,IAAI,IAAI;IAE3B,iEAAiE;IACjE,kBAAkB,IAAI,IAAI;IAE1B,yEAAyE;IACzE,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;CAC5C;;AAED,wBAAmE"}
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoBeaconModule.js","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AA+GzD,eAAe,mBAAmB,CAAmB,YAAY,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\r\n\r\nimport {\r\n ExpoBeaconModuleEvents,\r\n BeaconScanResult,\r\n EddystoneScanResult,\r\n PairedBeacon,\r\n PairedEddystone,\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 * Start a one-shot Eddystone beacon scan using BLE.\r\n * Discovers Eddystone-UID and Eddystone-URL frames.\r\n *\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForEddystonesAsync(\r\n scanDuration?: number,\r\n ): Promise<EddystoneScanResult[]>;\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 * Register an Eddystone-UID beacon for persistent monitoring.\r\n */\r\n pairEddystone(\r\n identifier: string,\r\n namespace: string,\r\n instance: string,\r\n ): void;\r\n\r\n /**\r\n * Remove a previously paired Eddystone beacon.\r\n */\r\n unpairEddystone(identifier: string): void;\r\n\r\n /**\r\n * Return all currently paired Eddystone beacons.\r\n */\r\n getPairedEddystones(): PairedEddystone[];\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\nexport default requireNativeModule<ExpoBeaconModule>(\"ExpoBeacon\");\r\n"]}
1
+ {"version":3,"file":"ExpoBeaconModule.js","sourceRoot":"","sources":["../src/ExpoBeaconModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAiHzD,eAAe,mBAAmB,CAAmB,YAAY,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\r\n\r\nimport {\r\n ExpoBeaconModuleEvents,\r\n BeaconScanResult,\r\n EddystoneScanResult,\r\n PairedBeacon,\r\n PairedEddystone,\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 * On iOS, at least one UUID is required Apple strips iBeacon data from BLE\r\n * advertisements, making wildcard discovery impossible. When you pass an empty\r\n * array, the module automatically uses UUIDs from paired beacons.\r\n * On Android, pass an empty array to discover all nearby iBeacons.\r\n *\r\n * @param uuids Proximity UUIDs to filter by. Empty/omitted = use paired UUIDs (iOS) or wildcard (Android).\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 * Start a one-shot Eddystone beacon scan using BLE.\r\n * Discovers Eddystone-UID and Eddystone-URL frames.\r\n *\r\n * @param scanDuration Duration in ms (default 5000)\r\n */\r\n scanForEddystonesAsync(\r\n scanDuration?: number,\r\n ): Promise<EddystoneScanResult[]>;\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 * Register an Eddystone-UID beacon for persistent monitoring.\r\n */\r\n pairEddystone(\r\n identifier: string,\r\n namespace: string,\r\n instance: string,\r\n ): void;\r\n\r\n /**\r\n * Remove a previously paired Eddystone beacon.\r\n */\r\n unpairEddystone(identifier: string): void;\r\n\r\n /**\r\n * Return all currently paired Eddystone beacons.\r\n */\r\n getPairedEddystones(): PairedEddystone[];\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\nexport default requireNativeModule<ExpoBeaconModule>(\"ExpoBeacon\");\r\n"]}
@@ -52,11 +52,9 @@ public class ExpoBeaconModule: Module {
52
52
  // Constraints started exclusively for continuous scan (not shared with distance ranging)
53
53
  private var continuousScanOnlyConstraints: [CLBeaconIdentityConstraint] = []
54
54
 
55
- // Wildcard (CoreBluetooth) scan state
55
+ // CoreBluetooth scan state (Eddystone + monitoring)
56
56
  private lazy var bluetoothDelegate = BluetoothDelegate(module: self)
57
57
  private var centralManager: CBCentralManager?
58
- private var wildcardScannedBeacons: [[String: Any]] = []
59
- private var wildcardScanTimer: DispatchWorkItem?
60
58
 
61
59
  // Eddystone (CoreBluetooth) scan state
62
60
  fileprivate var eddystoneScanPromise: Promise?
@@ -450,89 +448,6 @@ public class ExpoBeaconModule: Module {
450
448
  scannedBeacons = []
451
449
  }
452
450
 
453
- // MARK: - Wildcard (CoreBluetooth) scan
454
-
455
- private func startWildcardScan(durationMs: Int) {
456
- wildcardScannedBeacons = []
457
-
458
- if centralManager == nil {
459
- // Creating CBCentralManager triggers a Bluetooth power-on check.
460
- // Once .poweredOn, the delegate calls beginWildcardScanning().
461
- centralManager = CBCentralManager(delegate: bluetoothDelegate, queue: .main)
462
- } else if centralManager?.state == .poweredOn {
463
- beginWildcardScanning()
464
- }
465
- // If state is not yet .poweredOn the delegate will call beginWildcardScanning()
466
- // when centralManagerDidUpdateState fires.
467
-
468
- let timer = DispatchWorkItem { [weak self] in
469
- self?.stopWildcardScanAndResolve()
470
- }
471
- wildcardScanTimer = timer
472
- DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(durationMs), execute: timer)
473
- }
474
-
475
- fileprivate func beginWildcardScanning() {
476
- guard scanPromise != nil, wildcardScanTimer != nil else { return }
477
- centralManager?.scanForPeripherals(
478
- withServices: nil,
479
- options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
480
- )
481
- }
482
-
483
- fileprivate func handleWildcardDiscovery(advertisementData: [String: Any], rssi: NSNumber) {
484
- guard let mfgData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data,
485
- mfgData.count >= 25 else { return }
486
-
487
- // iBeacon format: Apple company ID (0x4C 0x00) + type 0x02 + length 0x15
488
- guard mfgData[0] == 0x4C, mfgData[1] == 0x00,
489
- mfgData[2] == 0x02, mfgData[3] == 0x15 else { return }
490
-
491
- // Parse UUID (bytes 4–19)
492
- let uuidBytes = [UInt8](mfgData[4..<20])
493
- let uuid = NSUUID(uuidBytes: uuidBytes) as UUID
494
-
495
- // Parse major (bytes 20–21, big-endian) and minor (bytes 22–23, big-endian)
496
- let major = Int(UInt16(mfgData[20]) << 8 | UInt16(mfgData[21]))
497
- let minor = Int(UInt16(mfgData[22]) << 8 | UInt16(mfgData[23]))
498
-
499
- // TX power (byte 24, signed)
500
- let txPower = Int(Int8(bitPattern: mfgData[24]))
501
-
502
- let rssiValue = rssi.intValue
503
- let distance = Self.calculateDistance(rssi: rssiValue, txPower: txPower)
504
-
505
- let result: [String: Any] = [
506
- "uuid": uuid.uuidString.uppercased(),
507
- "major": major,
508
- "minor": minor,
509
- "rssi": rssiValue,
510
- "distance": distance,
511
- "txPower": txPower
512
- ]
513
- wildcardScannedBeacons.append(result)
514
- }
515
-
516
- private func stopWildcardScanAndResolve() {
517
- wildcardScanTimer?.cancel()
518
- wildcardScanTimer = nil
519
- stopBleScanIfUnneeded()
520
-
521
- // Deduplicate by uuid:major:minor, keeping the last (freshest) reading
522
- var seen = Set<String>()
523
- var deduped: [[String: Any]] = []
524
- for beacon in wildcardScannedBeacons.reversed() {
525
- let key = "\(beacon["uuid"] ?? ""):\(beacon["major"] ?? ""):\(beacon["minor"] ?? "")"
526
- guard !seen.contains(key) else { continue }
527
- seen.insert(key)
528
- deduped.append(beacon)
529
- }
530
-
531
- scanPromise?.resolve(deduped)
532
- scanPromise = nil
533
- wildcardScannedBeacons = []
534
- }
535
-
536
451
  // MARK: - Eddystone Scan
537
452
 
538
453
  private func startEddystoneScan(durationMs: Int) {
@@ -727,7 +642,7 @@ public class ExpoBeaconModule: Module {
727
642
  }
728
643
 
729
644
  private func stopBleScanIfUnneeded() {
730
- guard wildcardScanTimer == nil && eddystoneScanTimer == nil && !continuousScanActive && !eddystoneMonitoringActive else { return }
645
+ guard eddystoneScanTimer == nil && !continuousScanActive && !eddystoneMonitoringActive else { return }
731
646
  centralManager?.stopScan()
732
647
  }
733
648
 
@@ -1105,7 +1020,7 @@ private class LocationDelegate: NSObject, CLLocationManagerDelegate {
1105
1020
  }
1106
1021
  }
1107
1022
 
1108
- // MARK: - CBCentralManagerDelegate (wildcard iBeacon scanning)
1023
+ // MARK: - CBCentralManagerDelegate (Eddystone BLE scanning)
1109
1024
 
1110
1025
  private class BluetoothDelegate: NSObject, CBCentralManagerDelegate {
1111
1026
  private weak var module: ExpoBeaconModule?
@@ -1137,7 +1052,6 @@ private class BluetoothDelegate: NSObject, CBCentralManagerDelegate {
1137
1052
  didDiscover peripheral: CBPeripheral,
1138
1053
  advertisementData: [String: Any],
1139
1054
  rssi RSSI: NSNumber) {
1140
- module?.handleWildcardDiscovery(advertisementData: advertisementData, rssi: RSSI)
1141
1055
  module?.handleEddystoneDiscovery(advertisementData: advertisementData, rssi: RSSI)
1142
1056
  }
1143
1057
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-beacon",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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",
@@ -15,10 +15,12 @@ declare class ExpoBeaconModule extends NativeModule<ExpoBeaconModuleEvents> {
15
15
  * Start a one-shot iBeacon scan. Resolves with discovered beacons after scanDuration ms.
16
16
  *
17
17
  * Pass one or more UUIDs to scan for specific beacons (uses CoreLocation on iOS).
18
- * Pass an empty array or omit to perform a wildcard scan that discovers all nearby
19
- * iBeacons (uses CoreBluetooth on iOS foreground only).
18
+ * On iOS, at least one UUID is required Apple strips iBeacon data from BLE
19
+ * advertisements, making wildcard discovery impossible. When you pass an empty
20
+ * array, the module automatically uses UUIDs from paired beacons.
21
+ * On Android, pass an empty array to discover all nearby iBeacons.
20
22
  *
21
- * @param uuids Proximity UUIDs to filter by. Empty/omitted = wildcard scan.
23
+ * @param uuids Proximity UUIDs to filter by. Empty/omitted = use paired UUIDs (iOS) or wildcard (Android).
22
24
  * @param scanDuration Duration in ms (default 5000)
23
25
  */
24
26
  scanForBeaconsAsync(