@capgo/capacitor-ibeacon 8.1.32 → 8.1.33

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.
@@ -44,7 +44,7 @@ import org.altbeacon.beacon.Settings;
44
44
  )
45
45
  public class CapacitorIbeaconPlugin extends Plugin implements BeaconConsumer {
46
46
 
47
- private final String pluginVersion = "8.1.32";
47
+ private final String pluginVersion = "8.1.33";
48
48
  private static final String FOREGROUND_CHANNEL_ID = "beacon_service_channel";
49
49
  private static final int FOREGROUND_NOTIFICATION_ID = 456;
50
50
  private BeaconManager beaconManager;
@@ -80,8 +80,7 @@ public class CapacitorIbeaconPlugin extends Plugin implements BeaconConsumer {
80
80
  beaconManager.setForegroundBetweenScanPeriod(0L); // Continuous scanning in foreground
81
81
  beaconManager.setForegroundScanPeriod(1100L); // Standard scan period
82
82
 
83
- // Bind to beacon service
84
- beaconManager.bind(this);
83
+ // bind() is deferred to bindIfNeeded().
85
84
 
86
85
  // Set up monitoring and ranging notifiers
87
86
  beaconManager.addMonitorNotifier(
@@ -144,7 +143,7 @@ public class CapacitorIbeaconPlugin extends Plugin implements BeaconConsumer {
144
143
 
145
144
  @Override
146
145
  public void onBeaconServiceConnect() {
147
- beaconManagerBound = true;
146
+ // beaconManagerBound is already set synchronously wherever bind() is called.
148
147
  }
149
148
 
150
149
  @Override
@@ -179,6 +178,7 @@ public class CapacitorIbeaconPlugin extends Plugin implements BeaconConsumer {
179
178
  if (enableBackgroundMode != null) {
180
179
  setBackgroundModeEnabled(enableBackgroundMode);
181
180
  }
181
+ bindIfNeeded();
182
182
  Region region = createRegion(identifier, uuid, major, minor);
183
183
  monitoredRegions.put(identifier, region);
184
184
  beaconManager.startMonitoring(region);
@@ -227,6 +227,7 @@ public class CapacitorIbeaconPlugin extends Plugin implements BeaconConsumer {
227
227
  if (enableBackgroundMode != null) {
228
228
  setBackgroundModeEnabled(enableBackgroundMode);
229
229
  }
230
+ bindIfNeeded();
230
231
  Region region = createRegion(identifier, uuid, major, minor);
231
232
  rangedRegions.put(identifier, region);
232
233
  beaconManager.startRangingBeacons(region);
@@ -567,8 +568,17 @@ public class CapacitorIbeaconPlugin extends Plugin implements BeaconConsumer {
567
568
  @PluginMethod
568
569
  public void enableBackgroundMode(PluginCall call) {
569
570
  Boolean enabled = call.getBoolean("enabled", true);
570
- setBackgroundModeEnabled(enabled != null && enabled);
571
- call.resolve();
571
+ boolean wantBackground = enabled != null && enabled;
572
+ try {
573
+ if (!ensureForegroundServiceMatches(wantBackground)) {
574
+ call.reject("Failed to enable foreground service scanning");
575
+ return;
576
+ }
577
+ setBackgroundModeEnabled(wantBackground);
578
+ call.resolve();
579
+ } catch (Exception e) {
580
+ call.reject("Failed to enable background mode", e);
581
+ }
572
582
  }
573
583
 
574
584
  @PluginMethod
@@ -579,6 +589,9 @@ public class CapacitorIbeaconPlugin extends Plugin implements BeaconConsumer {
579
589
  try {
580
590
  beaconManager.setBackgroundScanPeriod(scanPeriod);
581
591
  beaconManager.setBackgroundBetweenScanPeriod(betweenScanPeriod);
592
+ if (beaconManagerBound) {
593
+ beaconManager.updateScanPeriods();
594
+ }
582
595
  call.resolve();
583
596
  } catch (Exception e) {
584
597
  call.reject("Failed to set background scan period", e);
@@ -703,6 +716,17 @@ public class CapacitorIbeaconPlugin extends Plugin implements BeaconConsumer {
703
716
  }
704
717
  }
705
718
 
719
+ private void bindIfNeeded() {
720
+ ensureForegroundServiceMatches(backgroundModeEnabled);
721
+ if (beaconManagerBound || beaconManager == null) {
722
+ return;
723
+ }
724
+ // Set before bind() - AltBeacon is synchronously bound inside bind() itself, not only
725
+ // once onBeaconServiceConnect() fires.
726
+ beaconManagerBound = true;
727
+ beaconManager.bind(this);
728
+ }
729
+
706
730
  private void setBackgroundModeEnabled(boolean enabled) {
707
731
  backgroundModeEnabled = enabled;
708
732
  applyBackgroundMode(enabled);
@@ -712,16 +736,43 @@ public class CapacitorIbeaconPlugin extends Plugin implements BeaconConsumer {
712
736
  if (beaconManager == null) {
713
737
  return;
714
738
  }
739
+ beaconManager.setBackgroundMode(enabled && isInBackground);
740
+ }
715
741
 
716
- boolean shouldEnableBackgroundMode = enabled && isInBackground;
742
+ // Covers both a fresh bind and a later call (e.g. a per-region enableBackgroundMode option)
743
+ // that raises the requirement while already bound.
744
+ private boolean ensureForegroundServiceMatches(boolean wantForegroundService) {
745
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || wantForegroundService == foregroundServiceEnabled) {
746
+ return true;
747
+ }
748
+ return reconfigureForegroundService(wantForegroundService);
749
+ }
717
750
 
718
- if (shouldEnableBackgroundMode) {
719
- enableForegroundServiceIfNeeded();
720
- beaconManager.setBackgroundMode(true);
751
+ // enableForegroundServiceScanning()/disableForegroundServiceScanning() may only be called
752
+ // while unbound. Re-registers any regions that were actively monitored/ranged before the cycle.
753
+ private boolean reconfigureForegroundService(boolean wantForegroundService) {
754
+ boolean wasBound = beaconManagerBound;
755
+ if (wasBound) {
756
+ beaconManager.unbind(this);
757
+ beaconManagerBound = false;
758
+ }
759
+ boolean success = true;
760
+ if (wantForegroundService) {
761
+ success = enableForegroundServiceIfNeeded();
721
762
  } else {
722
763
  disableForegroundServiceIfNeeded();
723
- beaconManager.setBackgroundMode(false);
724
764
  }
765
+ if (wasBound) {
766
+ beaconManagerBound = true;
767
+ beaconManager.bind(this);
768
+ for (Region region : monitoredRegions.values()) {
769
+ beaconManager.startMonitoring(region);
770
+ }
771
+ for (Region region : rangedRegions.values()) {
772
+ beaconManager.startRangingBeacons(region);
773
+ }
774
+ }
775
+ return success;
725
776
  }
726
777
 
727
778
  private boolean enableForegroundServiceIfNeeded() {
@@ -8,18 +8,44 @@ public class CapacitorIbeacon: NSObject, CLLocationManagerDelegate, CBPeripheral
8
8
  private weak var plugin: CapacitorIbeaconPlugin?
9
9
  private var monitoredRegions: [String: CLBeaconRegion] = [:]
10
10
  private var rangedRegions: [String: CLBeaconRegion] = [:]
11
+ private var peripheralManagerReadyCallbacks: [(CBPeripheralManager) -> Void] = []
12
+ private let peripheralManagerStateTimeout: TimeInterval = 3.0
11
13
 
12
14
  override public init() {
13
15
  super.init()
14
16
  locationManager = CLLocationManager()
15
17
  locationManager.delegate = self
16
- peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
18
+ // peripheralManager intentionally not constructed here - see withReadyPeripheralManager().
17
19
  }
18
20
 
19
21
  public func setPlugin(_ plugin: CapacitorIbeaconPlugin) {
20
22
  self.plugin = plugin
21
23
  }
22
24
 
25
+ // Merely instantiating CBPeripheralManager triggers the Bluetooth permission prompt, so
26
+ // construction is deferred to here rather than init(). Its state also starts .unknown and
27
+ // only settles asynchronously via peripheralManagerDidUpdateState() below, so `body` waits
28
+ // for that instead of seeing a stale .unknown - with a timeout in case it never settles.
29
+ private func withReadyPeripheralManager(_ body: @escaping (CBPeripheralManager) -> Void) {
30
+ if peripheralManager == nil {
31
+ peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
32
+ }
33
+
34
+ if peripheralManager.state != .unknown {
35
+ body(peripheralManager)
36
+ return
37
+ }
38
+
39
+ peripheralManagerReadyCallbacks.append(body)
40
+
41
+ DispatchQueue.main.asyncAfter(deadline: .now() + peripheralManagerStateTimeout) { [weak self] in
42
+ guard let self = self, !self.peripheralManagerReadyCallbacks.isEmpty else { return }
43
+ let callbacks = self.peripheralManagerReadyCallbacks
44
+ self.peripheralManagerReadyCallbacks.removeAll()
45
+ callbacks.forEach { $0(self.peripheralManager) }
46
+ }
47
+ }
48
+
23
49
  public func startMonitoringForRegion(identifier: String, uuid: String, major: Int?, minor: Int?, completion: @escaping (Result<Void, Error>) -> Void) {
24
50
  guard let beaconUUID = UUID(uuidString: uuid) else {
25
51
  completion(.failure(NSError(domain: "CapacitorIbeacon", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid UUID"])))
@@ -90,17 +116,27 @@ public class CapacitorIbeacon: NSObject, CLLocationManagerDelegate, CBPeripheral
90
116
 
91
117
  let beaconRegion = CLBeaconRegion(uuid: beaconUUID, major: CLBeaconMajorValue(major), minor: CLBeaconMinorValue(minor), identifier: identifier)
92
118
 
93
- if let power = measuredPower {
94
- peripheralManager.startAdvertising(beaconRegion.peripheralData(withMeasuredPower: NSNumber(value: power)) as? [String: Any])
95
- } else {
96
- peripheralManager.startAdvertising(beaconRegion.peripheralData(withMeasuredPower: nil) as? [String: Any])
97
- }
119
+ withReadyPeripheralManager { manager in
120
+ guard manager.state == .poweredOn else {
121
+ let error = NSError(domain: "CapacitorIbeacon", code: -2, userInfo: [NSLocalizedDescriptionKey: "Bluetooth is not powered on"])
122
+ completion(.failure(error))
123
+ return
124
+ }
98
125
 
99
- completion(.success(()))
126
+ if let power = measuredPower {
127
+ manager.startAdvertising(beaconRegion.peripheralData(withMeasuredPower: NSNumber(value: power)) as? [String: Any])
128
+ } else {
129
+ manager.startAdvertising(beaconRegion.peripheralData(withMeasuredPower: nil) as? [String: Any])
130
+ }
131
+
132
+ completion(.success(()))
133
+ }
100
134
  }
101
135
 
102
136
  public func stopAdvertising() {
103
- peripheralManager.stopAdvertising()
137
+ // Not ensurePeripheralManager(): stopping something that was never started shouldn't
138
+ // newly construct (and trigger a permission prompt for) a manager that doesn't exist yet.
139
+ peripheralManager?.stopAdvertising()
104
140
  }
105
141
 
106
142
  public func requestWhenInUseAuthorization() {
@@ -129,8 +165,10 @@ public class CapacitorIbeacon: NSObject, CLLocationManagerDelegate, CBPeripheral
129
165
  }
130
166
  }
131
167
 
132
- public func isBluetoothEnabled() -> Bool {
133
- return peripheralManager.state == .poweredOn
168
+ public func isBluetoothEnabled(completion: @escaping (Bool) -> Void) {
169
+ withReadyPeripheralManager { manager in
170
+ completion(manager.state == .poweredOn)
171
+ }
134
172
  }
135
173
 
136
174
  public func isRangingAvailable() -> Bool {
@@ -229,6 +267,11 @@ public class CapacitorIbeacon: NSObject, CLLocationManagerDelegate, CBPeripheral
229
267
  // MARK: - CBPeripheralManagerDelegate
230
268
 
231
269
  public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
232
- // Handle Bluetooth state changes if needed
270
+ guard !peripheralManagerReadyCallbacks.isEmpty else {
271
+ return
272
+ }
273
+ let callbacks = peripheralManagerReadyCallbacks
274
+ peripheralManagerReadyCallbacks.removeAll()
275
+ callbacks.forEach { $0(peripheral) }
233
276
  }
234
277
  }
@@ -4,7 +4,7 @@ import CoreLocation
4
4
 
5
5
  @objc(CapacitorIbeaconPlugin)
6
6
  public class CapacitorIbeaconPlugin: CAPPlugin, CAPBridgedPlugin {
7
- private let pluginVersion: String = "8.1.32"
7
+ private let pluginVersion: String = "8.1.33"
8
8
  public let identifier = "CapacitorIbeaconPlugin"
9
9
  public let jsName = "CapacitorIbeacon"
10
10
  public let pluginMethods: [CAPPluginMethod] = [
@@ -144,8 +144,9 @@ public class CapacitorIbeaconPlugin: CAPPlugin, CAPBridgedPlugin {
144
144
  }
145
145
 
146
146
  @objc func isBluetoothEnabled(_ call: CAPPluginCall) {
147
- let enabled = implementation.isBluetoothEnabled()
148
- call.resolve(["enabled": enabled])
147
+ implementation.isBluetoothEnabled { enabled in
148
+ call.resolve(["enabled": enabled])
149
+ }
149
150
  }
150
151
 
151
152
  @objc func isRangingAvailable(_ call: CAPPluginCall) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-ibeacon",
3
- "version": "8.1.32",
3
+ "version": "8.1.33",
4
4
  "description": "iBeacon plugin for Capacitor - proximity detection and beacon region monitoring",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",