Skip to content

Commit 1028c15

Browse files
committed
feat(android): corrects Promise handling and introduces the ability to disable the plugin queue.
1 parent dff7981 commit 1028c15

File tree

4 files changed

+118
-82
lines changed

4 files changed

+118
-82
lines changed

src/bluetooth.android.ts

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
WriteOptions,
2525
bluetoothEnabled,
2626
prepareArgs,
27+
BluetoothOptions,
2728
} from './bluetooth.common';
2829
import PQueue from 'p-queue';
2930
import { Trace } from '@nativescript/core';
@@ -1100,7 +1101,7 @@ export class Bluetooth extends BluetoothCommon {
11001101
private LeScanCallback: LeScanCallback;
11011102

11021103
// with gatt all operations must be queued. Parallel operations will fail
1103-
gattQueue: PQueue;
1104+
gattQueue: PQueue | undefined;
11041105

11051106
static readonly android = {
11061107
ScanMode,
@@ -1135,7 +1136,7 @@ export class Bluetooth extends BluetoothCommon {
11351136
advertismentData?: AdvertismentData;
11361137
};
11371138
} = {};
1138-
constructor() {
1139+
constructor(restoreIdentifierOrOptions?: string | Partial<BluetoothOptions>) {
11391140
super();
11401141
if (Trace.isEnabled()) {
11411142
CLog(CLogTypes.info, '*** Android Bluetooth Constructor ***');
@@ -1149,11 +1150,17 @@ export class Bluetooth extends BluetoothCommon {
11491150
initLeScanCallback();
11501151
this.LeScanCallback = new LeScanCallbackVar(new WeakRef(this));
11511152
}
1152-
this.gattQueue = new PQueue({ concurrency: 1 });
1153+
if (typeof restoreIdentifierOrOptions === 'object' && !!restoreIdentifierOrOptions.disableAndroidQueue) {
1154+
this.gattQueue = undefined;
1155+
} else {
1156+
this.gattQueue = new PQueue({ concurrency: 1 });
1157+
}
11531158
}
11541159

11551160
clear() {
1156-
this.gattQueue.clear();
1161+
if (this.gattQueue) {
1162+
this.gattQueue.clear();
1163+
}
11571164
}
11581165
broadcastRegistered = false;
11591166
registerBroadcast() {
@@ -1661,7 +1668,7 @@ export class Bluetooth extends BluetoothCommon {
16611668
onDisconnected: args.onDisconnected,
16621669
// device: gatt // TODO rename device to gatt?
16631670
});
1664-
await new Promise<void>((resolve, reject) => {
1671+
return new Promise<void>((resolve, reject) => {
16651672
const clearListeners = () => {
16661673
this.bluetoothGattCallback.removeSubDelegate(subD);
16671674
this.removeDisconnectListener(onDisconnect);
@@ -1727,46 +1734,45 @@ export class Bluetooth extends BluetoothCommon {
17271734
// onDisconnected: args.onDisconnected,
17281735
device: gatt, // TODO rename device to gatt?
17291736
});
1737+
}).then(() => {
1738+
// This disconnects the Promise chain so these tasks can run independent of the successful connection response.
1739+
Promise.resolve()
1740+
.then(() => !!args.autoDiscoverAll ? this.discoverAll({ peripheralUUID: pUUID }).then((result) => result?.services) : undefined)
1741+
.then((services) => (!!args.auto2MegPhy ? this.select2MegPhy({ peripheralUUID: pUUID }) : Promise.resolve()).then(() => services))
1742+
.then((services) => (!!args.autoMaxMTU ? this.requestMtu({ peripheralUUID: pUUID, value: MAX_MTU }) : Promise.resolve(undefined))
1743+
.then((mtu?: number) => ({services, mtu})))
1744+
.then(({services, mtu}) => {
1745+
const stateObject = this.connections[pUUID];
1746+
if (!stateObject) {
1747+
return Promise.reject(
1748+
new BluetoothError(BluetoothCommon.msg_peripheral_not_connected, {
1749+
method: methodName,
1750+
arguments: args,
1751+
})
1752+
) as any;
1753+
}
1754+
stateObject.state = 'connected';
1755+
const adv = stateObject.advertismentData;
1756+
const dataToSend = {
1757+
UUID: pUUID, // TODO consider renaming to id (and iOS as well)
1758+
name: bluetoothDevice && bluetoothDevice.getName(),
1759+
state: stateObject.state,
1760+
services,
1761+
mtu,
1762+
localName: adv?.localName,
1763+
manufacturerId: adv?.manufacturerId,
1764+
advertismentData: adv,
1765+
};
1766+
if (stateObject.onConnected) {
1767+
stateObject.onConnected(dataToSend);
1768+
delete stateObject.onConnected;
1769+
}
1770+
this.sendEvent(Bluetooth.device_connected_event, dataToSend);
1771+
return dataToSend;
1772+
});
1773+
1774+
return Promise.resolve();
17301775
});
1731-
let services, mtu;
1732-
if(args.autoDiscoverAll !== false) {
1733-
services = (await this.discoverAll({ peripheralUUID: pUUID }))?.services;
1734-
}
1735-
if (!!args.auto2MegPhy) {
1736-
await this.select2MegPhy({ peripheralUUID: pUUID }) ;
1737-
}
1738-
if (!!args.autoMaxMTU) {
1739-
mtu = await this.requestMtu({ peripheralUUID: pUUID, value: MAX_MTU }) ;
1740-
}
1741-
// get the stateObject again to see if we got disconnected
1742-
stateObject = this.connections[pUUID];
1743-
if (!stateObject) {
1744-
return Promise.reject(
1745-
new BluetoothError(BluetoothCommon.msg_peripheral_not_connected, {
1746-
method: methodName,
1747-
arguments: args,
1748-
})
1749-
) as any;
1750-
}
1751-
stateObject.state = 'connected';
1752-
const adv = stateObject.advertismentData;
1753-
const dataToSend = {
1754-
UUID: pUUID, // TODO consider renaming to id (and iOS as well)
1755-
name: bluetoothDevice && bluetoothDevice.getName(),
1756-
state: stateObject.state,
1757-
services,
1758-
mtu,
1759-
nativeDevice: bluetoothDevice,
1760-
localName: adv?.localName,
1761-
manufacturerId: adv?.manufacturerId,
1762-
advertismentData: adv,
1763-
};
1764-
if (stateObject.onConnected) {
1765-
stateObject.onConnected(dataToSend);
1766-
delete stateObject.onConnected;
1767-
}
1768-
this.sendEvent(Bluetooth.device_connected_event, dataToSend);
1769-
return dataToSend;
17701776
}
17711777
}
17721778

@@ -1800,12 +1806,15 @@ export class Bluetooth extends BluetoothCommon {
18001806
});
18011807
}
18021808

1803-
private addToGatQueue(p: () => Promise<any>) {
1804-
return this.gattQueue.add(p);
1809+
private addToGattQueue(p: () => Promise<any>) {
1810+
if (this.gattQueue) {
1811+
return this.gattQueue.add(p);
1812+
}
1813+
return p();
18051814
}
18061815

18071816
private addToQueue(args: WrapperOptions, runner: (wrapper: WrapperResult) => any) {
1808-
return this.addToGatQueue(() => this._getWrapper(args).then((wrapper) => runner(wrapper)));
1817+
return this.addToGattQueue(() => this._getWrapper(args).then((wrapper) => runner(wrapper)));
18091818
}
18101819

18111820
@prepareArgs
@@ -1951,7 +1960,7 @@ export class Bluetooth extends BluetoothCommon {
19511960
if (Trace.isEnabled()) {
19521961
CLog(CLogTypes.info, methodName, args);
19531962
}
1954-
return this.addToGatQueue(
1963+
return this.addToGattQueue(
19551964
() =>
19561965
new Promise((resolve, reject) => {
19571966
if (Trace.isEnabled()) {
@@ -2452,7 +2461,7 @@ export class Bluetooth extends BluetoothCommon {
24522461
if (Trace.isEnabled()) {
24532462
CLog(CLogTypes.info, methodName, pUUID, stateObject);
24542463
}
2455-
return this.addToGatQueue(
2464+
return this.addToGattQueue(
24562465
() =>
24572466
new Promise((resolve, reject) => {
24582467
if (Trace.isEnabled()) {
@@ -2653,7 +2662,7 @@ export class Bluetooth extends BluetoothCommon {
26532662
if (Trace.isEnabled()) {
26542663
CLog(CLogTypes.info, methodName, pUUID, stateObject);
26552664
}
2656-
return this.addToGatQueue(
2665+
return this.addToGattQueue(
26572666
() =>
26582667
new Promise<void>((resolve, reject) => {
26592668
if (Trace.isEnabled()) {

src/bluetooth.common.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ export function prepareArgs(target: Object, propertyKey: string, descriptor: Typ
7474
return descriptor;
7575
}
7676

77+
export interface BluetoothOptions {
78+
restoreIdentifier: string;
79+
showPowerAlertPopup: boolean;
80+
disableAndroidQueue: boolean;
81+
}
82+
7783
export abstract class BluetoothCommon extends Observable {
7884
/*
7985
* error messages

src/bluetooth.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
AdvertismentData,
33
BluetoothCommon,
4+
BluetoothOptions,
45
CallbackType,
56
Characteristic,
67
ConnectOptions,
@@ -56,7 +57,7 @@ export class Bluetooth extends BluetoothCommon {
5657
/**
5758
* restoreIdentifier is optional and only used on iOS
5859
*/
59-
constructor(restoreIndentifier?: string);
60+
constructor(restoreIndentifierOrOptions?: string | Partial<BluetoothOptions>);
6061

6162
/**
6263
* everything needed to clear on app close

src/bluetooth.ios.ts

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
WriteOptions,
1818
bluetoothEnabled,
1919
prepareArgs,
20+
BluetoothOptions,
2021
} from './bluetooth.common';
2122
import { Trace } from '@nativescript/core';
2223

@@ -697,10 +698,19 @@ export class Bluetooth extends BluetoothCommon {
697698
return this._centralManager;
698699
}
699700

700-
constructor(private restoreIdentifier: string = 'ns_bluetooth', private showPowerAlertPopup = false) {
701+
private restoreIdentifier: string;
702+
703+
constructor(restoreIdentifierOrOptions: string | Partial<BluetoothOptions> = 'ns_bluetooth', private showPowerAlertPopup = false) {
701704
super();
705+
if (typeof restoreIdentifierOrOptions === 'string') {
706+
this.restoreIdentifier = restoreIdentifierOrOptions;
707+
} else if (!!restoreIdentifierOrOptions) {
708+
this.restoreIdentifier = restoreIdentifierOrOptions.restoreIdentifier || 'ns_bluetooth';
709+
this.showPowerAlertPopup = !!restoreIdentifierOrOptions.showPowerAlertPopup;
710+
}
711+
702712
if (Trace.isEnabled()) {
703-
CLog(CLogTypes.info, 'Creating Bluetooth instance', restoreIdentifier);
713+
CLog(CLogTypes.info, 'Creating Bluetooth instance', this.restoreIdentifier);
704714
}
705715
}
706716

@@ -942,7 +952,7 @@ export class Bluetooth extends BluetoothCommon {
942952
arguments: args,
943953
});
944954
} else {
945-
await new Promise<void>((resolve, reject) => {
955+
return new Promise<void>((resolve, reject) => {
946956
const subD = {
947957
centralManagerDidConnectPeripheral: (central: CBCentralManager, peripheral: CBPeripheral) => {
948958
const UUID = NSUUIDToString(peripheral.identifier);
@@ -974,34 +984,39 @@ export class Bluetooth extends BluetoothCommon {
974984
CLog(CLogTypes.info, methodName, '----about to connect:', connectingUUID, this._centralDelegate, this._centralManager);
975985
}
976986
this.centralManager.connectPeripheralOptions(peripheral, null);
987+
}).then(() => {
988+
// This disconnects the Promise chain so these tasks can run independent of the successful connection response.
989+
Promise.resolve()
990+
.then(() => {
991+
if (args.autoDiscoverAll !== false) {
992+
return this.discoverAll({ peripheralUUID: connectingUUID });
993+
}
994+
return undefined;
995+
})
996+
.then((result) => {
997+
const adv = this._advData[connectingUUID];
998+
const dataToSend = {
999+
UUID: connectingUUID,
1000+
name: peripheral.name,
1001+
state: this._getState(peripheral.state),
1002+
services: result?.services,
1003+
localName: adv?.localName,
1004+
manufacturerId: adv?.manufacturerId,
1005+
advertismentData: adv,
1006+
mtu: FIXED_IOS_MTU,
1007+
};
1008+
// delete this._advData[connectingUUID];
1009+
const cb = this._connectCallbacks[connectingUUID];
1010+
if (cb) {
1011+
cb(dataToSend);
1012+
delete this._connectCallbacks[connectingUUID];
1013+
}
1014+
this.sendEvent(Bluetooth.device_connected_event, dataToSend);
1015+
return dataToSend;
1016+
});
1017+
1018+
return Promise.resolve();
9771019
});
978-
let services, mtu = FIXED_IOS_MTU;
979-
if (args.autoDiscoverAll !== false) {
980-
services = (await this.discoverAll({ peripheralUUID: connectingUUID }))?.services;
981-
}
982-
if (!!args.autoMaxMTU) {
983-
mtu = await this.requestMtu({ peripheralUUID: connectingUUID, value: FIXED_IOS_MTU }) ;
984-
}
985-
const adv = this._advData[connectingUUID];
986-
const dataToSend = {
987-
UUID: connectingUUID,
988-
name: peripheral.name,
989-
state: this._getState(peripheral.state),
990-
services,
991-
nativeDevice: peripheral,
992-
localName: adv?.localName,
993-
manufacturerId: adv?.manufacturerId,
994-
advertismentData: adv,
995-
mtu,
996-
};
997-
// delete this._advData[connectingUUID];
998-
const cb = this._connectCallbacks[connectingUUID];
999-
if (cb) {
1000-
cb(dataToSend);
1001-
delete this._connectCallbacks[connectingUUID];
1002-
}
1003-
this.sendEvent(Bluetooth.device_connected_event, dataToSend);
1004-
return dataToSend;
10051020
}
10061021
} catch (ex) {
10071022
if (Trace.isEnabled()) {
@@ -1042,7 +1057,8 @@ export class Bluetooth extends BluetoothCommon {
10421057
} else {
10431058
// no need to send an error when already disconnected, but it's wise to check it
10441059
if (peripheral.state !== CBPeripheralState.Disconnected) {
1045-
return new Promise<void>((resolve, reject) => {
1060+
// This disconnects the Promise chain so these tasks can run independent of the successful connection response.
1061+
(new Promise<void>((resolve, reject) => {
10461062
const subD = {
10471063
centralManagerDidDisconnectPeripheralError: (central: CBCentralManager, peripheral: CBPeripheral, error?: NSError) => {
10481064
const UUID = NSUUIDToString(peripheral.identifier);
@@ -1066,6 +1082,10 @@ export class Bluetooth extends BluetoothCommon {
10661082
CLog(CLogTypes.info, methodName, '---- Disconnecting peripheral with UUID', pUUID);
10671083
}
10681084
this.centralManager.cancelPeripheralConnection(peripheral);
1085+
})).catch((ex) => {
1086+
if (Trace.isEnabled()) {
1087+
CLog(CLogTypes.error, methodName, '---- error:', ex);
1088+
}
10691089
});
10701090
}
10711091
return Promise.resolve();

0 commit comments

Comments
 (0)