Skip to content

Commit b418954

Browse files
authored
Merge pull request #235 from facetious/facetious/allow-app-managed-queue
feat(android): corrects Promise handling and introduces the ability to disable the plugin queue.
2 parents 64a4603 + 9beb319 commit b418954

File tree

4 files changed

+122
-82
lines changed

4 files changed

+122
-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 | null;
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: 55 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,23 @@ 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> | null = 'ns_bluetooth', private showPowerAlertPopup = false) {
701704
super();
705+
if (typeof restoreIdentifierOrOptions === 'object') {
706+
if (restoreIdentifierOrOptions.restoreIdentifier === undefined) {
707+
this.restoreIdentifier = 'ns_bluetooth';
708+
} else {
709+
this.restoreIdentifier = restoreIdentifierOrOptions.restoreIdentifier;
710+
}
711+
this.showPowerAlertPopup = !!restoreIdentifierOrOptions.showPowerAlertPopup;
712+
} else {
713+
this.restoreIdentifier = restoreIdentifierOrOptions;
714+
}
715+
702716
if (Trace.isEnabled()) {
703-
CLog(CLogTypes.info, 'Creating Bluetooth instance', restoreIdentifier);
717+
CLog(CLogTypes.info, 'Creating Bluetooth instance', this.restoreIdentifier);
704718
}
705719
}
706720

@@ -943,7 +957,7 @@ export class Bluetooth extends BluetoothCommon {
943957
arguments: args,
944958
});
945959
} else {
946-
await new Promise<void>((resolve, reject) => {
960+
return new Promise<void>((resolve, reject) => {
947961
const subD = {
948962
centralManagerDidConnectPeripheral: (central: CBCentralManager, peripheral: CBPeripheral) => {
949963
const UUID = NSUUIDToString(peripheral.identifier);
@@ -975,34 +989,39 @@ export class Bluetooth extends BluetoothCommon {
975989
CLog(CLogTypes.info, methodName, '----about to connect:', connectingUUID, this._centralDelegate, this._centralManager);
976990
}
977991
this.centralManager.connectPeripheralOptions(peripheral, null);
992+
}).then(() => {
993+
// This disconnects the Promise chain so these tasks can run independent of the successful connection response.
994+
Promise.resolve()
995+
.then(() => {
996+
if (args.autoDiscoverAll !== false) {
997+
return this.discoverAll({ peripheralUUID: connectingUUID });
998+
}
999+
return undefined;
1000+
})
1001+
.then((result) => {
1002+
const adv = this._advData[connectingUUID];
1003+
const dataToSend = {
1004+
UUID: connectingUUID,
1005+
name: peripheral.name,
1006+
state: this._getState(peripheral.state),
1007+
services: result?.services,
1008+
localName: adv?.localName,
1009+
manufacturerId: adv?.manufacturerId,
1010+
advertismentData: adv,
1011+
mtu: FIXED_IOS_MTU,
1012+
};
1013+
// delete this._advData[connectingUUID];
1014+
const cb = this._connectCallbacks[connectingUUID];
1015+
if (cb) {
1016+
cb(dataToSend);
1017+
delete this._connectCallbacks[connectingUUID];
1018+
}
1019+
this.sendEvent(Bluetooth.device_connected_event, dataToSend);
1020+
return dataToSend;
1021+
});
1022+
1023+
return Promise.resolve();
9781024
});
979-
let services, mtu = FIXED_IOS_MTU;
980-
if (args.autoDiscoverAll !== false) {
981-
services = (await this.discoverAll({ peripheralUUID: connectingUUID }))?.services;
982-
}
983-
if (!!args.autoMaxMTU) {
984-
mtu = await this.requestMtu({ peripheralUUID: connectingUUID, value: FIXED_IOS_MTU }) ;
985-
}
986-
const adv = this._advData[connectingUUID];
987-
const dataToSend = {
988-
UUID: connectingUUID,
989-
name: peripheral.name,
990-
state: this._getState(peripheral.state),
991-
services,
992-
nativeDevice: peripheral,
993-
localName: adv?.localName,
994-
manufacturerId: adv?.manufacturerId,
995-
advertismentData: adv,
996-
mtu,
997-
};
998-
// delete this._advData[connectingUUID];
999-
const cb = this._connectCallbacks[connectingUUID];
1000-
if (cb) {
1001-
cb(dataToSend);
1002-
delete this._connectCallbacks[connectingUUID];
1003-
}
1004-
this.sendEvent(Bluetooth.device_connected_event, dataToSend);
1005-
return dataToSend;
10061025
}
10071026
} catch (ex) {
10081027
if (Trace.isEnabled()) {
@@ -1043,7 +1062,8 @@ export class Bluetooth extends BluetoothCommon {
10431062
} else {
10441063
// no need to send an error when already disconnected, but it's wise to check it
10451064
if (peripheral.state !== CBPeripheralState.Disconnected) {
1046-
return new Promise<void>((resolve, reject) => {
1065+
// This disconnects the Promise chain so these tasks can run independent of the successful connection response.
1066+
(new Promise<void>((resolve, reject) => {
10471067
const subD = {
10481068
centralManagerDidDisconnectPeripheralError: (central: CBCentralManager, peripheral: CBPeripheral, error?: NSError) => {
10491069
const UUID = NSUUIDToString(peripheral.identifier);
@@ -1067,6 +1087,10 @@ export class Bluetooth extends BluetoothCommon {
10671087
CLog(CLogTypes.info, methodName, '---- Disconnecting peripheral with UUID', pUUID);
10681088
}
10691089
this.centralManager.cancelPeripheralConnection(peripheral);
1090+
})).catch((ex) => {
1091+
if (Trace.isEnabled()) {
1092+
CLog(CLogTypes.error, methodName, '---- error:', ex);
1093+
}
10701094
});
10711095
}
10721096
return Promise.resolve();

0 commit comments

Comments
 (0)