@onekeyfe/hd-transport-electron 1.2.2-alpha.101 → 1.2.2-alpha.103
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/dist/{index-d51c7da6.js → index-052b4b5b.js} +33 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/{noble-ble-handler-0b455c4f.js → noble-ble-handler-eff26dd3.js} +309 -68
- package/dist/noble-ble-handler.d.ts +3 -0
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/dist/types/desktop-api.d.ts +2 -0
- package/dist/types/desktop-api.d.ts.map +1 -1
- package/dist/types/noble-extended.d.ts +2 -0
- package/dist/types/noble-extended.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/noble-ble-handler.test.ts +517 -0
- package/src/index.ts +7 -0
- package/src/noble-ble-handler.ts +352 -80
- package/src/types/desktop-api.ts +31 -0
- package/src/types/noble-extended.ts +2 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index = require('./index-
|
|
3
|
+
var index = require('./index-052b4b5b.js');
|
|
4
4
|
var hdShared = require('@onekeyfe/hd-shared');
|
|
5
5
|
var pRetry = require('p-retry');
|
|
6
6
|
|
|
@@ -100,9 +100,30 @@ const NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS = 10000;
|
|
|
100
100
|
|
|
101
101
|
let noble = null;
|
|
102
102
|
let logger = null;
|
|
103
|
+
let disposing = false;
|
|
104
|
+
let nativeReleased = false;
|
|
105
|
+
let disposePromise;
|
|
106
|
+
let windowCleanup;
|
|
107
|
+
let removeIpcHandlers;
|
|
108
|
+
const pendingCancellations = new Set();
|
|
109
|
+
const nativeConnections = new Set();
|
|
110
|
+
function trackNativeConnection(operation, cancel) {
|
|
111
|
+
const connection = {
|
|
112
|
+
cancel,
|
|
113
|
+
settled: operation.then(() => undefined, () => undefined),
|
|
114
|
+
};
|
|
115
|
+
nativeConnections.add(connection);
|
|
116
|
+
connection.settled = connection.settled.finally(() => nativeConnections.delete(connection));
|
|
117
|
+
}
|
|
118
|
+
function assertBleActive() {
|
|
119
|
+
if (disposing) {
|
|
120
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'Noble BLE is shutting down');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
103
123
|
function createNobleBleConnectionError(error, messagePrefix = '') {
|
|
104
124
|
const errorMessage = error.message;
|
|
105
|
-
const isInvalidMacOsBond = error.nativeErrorCode === 14 && error.nativeErrorDomain === 'CBErrorDomain'
|
|
125
|
+
const isInvalidMacOsBond = (error.nativeErrorCode === 14 && error.nativeErrorDomain === 'CBErrorDomain') ||
|
|
126
|
+
(error.nativeErrorCode === 15 && error.nativeErrorDomain === 'CBATTErrorDomain');
|
|
106
127
|
if (isInvalidMacOsBond) {
|
|
107
128
|
const nativeErrorMessage = `${messagePrefix}${errorMessage}`;
|
|
108
129
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleBondInvalid, `${hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleBondInvalid]} (${nativeErrorMessage})`, {
|
|
@@ -143,6 +164,86 @@ let persistentStateListener = null;
|
|
|
143
164
|
let persistentDiscoverListener = null;
|
|
144
165
|
const discoveredDevices = new Map();
|
|
145
166
|
const connectedDevices = new Map();
|
|
167
|
+
const connectingDevices = new Map();
|
|
168
|
+
let nextConnectionGeneration = 0;
|
|
169
|
+
function connectPeripheralWithCancellation(peripheral, deviceId, messagePrefix = '') {
|
|
170
|
+
assertBleActive();
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
var _a;
|
|
173
|
+
const generation = ++nextConnectionGeneration;
|
|
174
|
+
let settled = false;
|
|
175
|
+
const settle = (settler) => {
|
|
176
|
+
var _a;
|
|
177
|
+
if (settled)
|
|
178
|
+
return false;
|
|
179
|
+
settled = true;
|
|
180
|
+
if (((_a = connectingDevices.get(deviceId)) === null || _a === void 0 ? void 0 : _a.generation) === generation) {
|
|
181
|
+
connectingDevices.delete(deviceId);
|
|
182
|
+
}
|
|
183
|
+
settler();
|
|
184
|
+
return true;
|
|
185
|
+
};
|
|
186
|
+
const pendingConnection = {
|
|
187
|
+
peripheral,
|
|
188
|
+
generation,
|
|
189
|
+
cancel: error => {
|
|
190
|
+
settle(() => reject(error));
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
(_a = connectingDevices
|
|
194
|
+
.get(deviceId)) === null || _a === void 0 ? void 0 : _a.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, `Device ${deviceId} connect attempt was superseded`));
|
|
195
|
+
connectingDevices.set(deviceId, pendingConnection);
|
|
196
|
+
let completeNative;
|
|
197
|
+
trackNativeConnection(new Promise(resolve => {
|
|
198
|
+
completeNative = resolve;
|
|
199
|
+
}), () => {
|
|
200
|
+
var _a;
|
|
201
|
+
if (typeof peripheral.cancelConnect === 'function')
|
|
202
|
+
peripheral.cancelConnect();
|
|
203
|
+
else
|
|
204
|
+
(_a = noble === null || noble === void 0 ? void 0 : noble.cancelConnect) === null || _a === void 0 ? void 0 : _a.call(noble, deviceId);
|
|
205
|
+
});
|
|
206
|
+
const onConnect = (error) => {
|
|
207
|
+
if (nativeReleased) {
|
|
208
|
+
completeNative();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (error) {
|
|
212
|
+
settle(() => reject(createNobleBleConnectionError(error, messagePrefix)));
|
|
213
|
+
completeNative();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!settle(() => {
|
|
217
|
+
connectedDevices.set(deviceId, peripheral);
|
|
218
|
+
resolve();
|
|
219
|
+
})) {
|
|
220
|
+
const activeConnection = connectingDevices.get(deviceId);
|
|
221
|
+
if (!activeConnection || activeConnection.peripheral !== peripheral) {
|
|
222
|
+
try {
|
|
223
|
+
peripheral.removeAllListeners('disconnect');
|
|
224
|
+
runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
|
|
225
|
+
timeoutMs: BLE_DISCONNECT_CONFIRM_TIMEOUT_MS,
|
|
226
|
+
timeoutBehavior: 'resolve',
|
|
227
|
+
})
|
|
228
|
+
.catch(() => undefined)
|
|
229
|
+
.finally(completeNative);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
catch (_a) {
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
completeNative();
|
|
237
|
+
};
|
|
238
|
+
try {
|
|
239
|
+
peripheral.connect(onConnect);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
completeNative();
|
|
243
|
+
settle(() => reject(error));
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
146
247
|
const pairedDevices = new Set();
|
|
147
248
|
const deviceCharacteristics = new Map();
|
|
148
249
|
const notificationCallbacks = new Map();
|
|
@@ -281,6 +382,7 @@ function updateBluetoothState(state) {
|
|
|
281
382
|
}
|
|
282
383
|
function initializeNoble() {
|
|
283
384
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
385
|
+
assertBleActive();
|
|
284
386
|
if (noble)
|
|
285
387
|
return;
|
|
286
388
|
try {
|
|
@@ -297,9 +399,11 @@ function initializeNoble() {
|
|
|
297
399
|
return;
|
|
298
400
|
}
|
|
299
401
|
const timeout = setTimeout(() => {
|
|
402
|
+
cleanup();
|
|
300
403
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Bluetooth initialization timeout'));
|
|
301
404
|
}, BLUETOOTH_INIT_TIMEOUT);
|
|
302
405
|
const cleanup = () => {
|
|
406
|
+
pendingCancellations.delete(cancel);
|
|
303
407
|
clearTimeout(timeout);
|
|
304
408
|
if (noble) {
|
|
305
409
|
noble.removeListener('stateChange', onStateChange);
|
|
@@ -324,8 +428,14 @@ function initializeNoble() {
|
|
|
324
428
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
|
|
325
429
|
}
|
|
326
430
|
};
|
|
431
|
+
const cancel = () => {
|
|
432
|
+
cleanup();
|
|
433
|
+
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'Noble BLE is shutting down'));
|
|
434
|
+
};
|
|
435
|
+
pendingCancellations.add(cancel);
|
|
327
436
|
noble.on('stateChange', onStateChange);
|
|
328
437
|
});
|
|
438
|
+
assertBleActive();
|
|
329
439
|
if (!persistentDiscoverListener) {
|
|
330
440
|
persistentDiscoverListener = (peripheral) => {
|
|
331
441
|
handleDeviceDiscovered(peripheral);
|
|
@@ -372,6 +482,8 @@ function broadcastToAllWebContents(channel, payload) {
|
|
|
372
482
|
}
|
|
373
483
|
function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle') {
|
|
374
484
|
clearIdleDisconnect(deviceId);
|
|
485
|
+
if (disposing)
|
|
486
|
+
return;
|
|
375
487
|
idleDisconnectTimers.set(deviceId, setTimeout(() => {
|
|
376
488
|
var _a;
|
|
377
489
|
idleDisconnectTimers.delete(deviceId);
|
|
@@ -407,7 +519,7 @@ function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle
|
|
|
407
519
|
}, ms));
|
|
408
520
|
}
|
|
409
521
|
function cleanupDevice(deviceId, webContents, options = {}) {
|
|
410
|
-
var _a;
|
|
522
|
+
var _a, _b;
|
|
411
523
|
const { cleanupConnection = true, cleanupDiscoveredCache = false, sendDisconnectEvent = false, cancelOperations = true, reason = 'unknown', disconnectReason = hdShared.EBleDisconnectReason.DeviceDisconnected, } = options;
|
|
412
524
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting device cleanup', {
|
|
413
525
|
deviceId,
|
|
@@ -421,6 +533,8 @@ function cleanupDevice(deviceId, webContents, options = {}) {
|
|
|
421
533
|
const peripheral = connectedDevices.get(deviceId);
|
|
422
534
|
const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
423
535
|
if (cleanupConnection) {
|
|
536
|
+
(_b = connectingDevices
|
|
537
|
+
.get(deviceId)) === null || _b === void 0 ? void 0 : _b.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, `Device ${deviceId} disconnected while connecting`));
|
|
424
538
|
const disconnectEntry = deviceDisconnectListeners.get(deviceId);
|
|
425
539
|
if (disconnectEntry) {
|
|
426
540
|
disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
|
|
@@ -499,6 +613,7 @@ function setupMtuListener(peripheral, deviceId, webContents) {
|
|
|
499
613
|
}
|
|
500
614
|
function writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, buffer) {
|
|
501
615
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
616
|
+
assertBleActive();
|
|
502
617
|
return new Promise((resolve, reject) => {
|
|
503
618
|
writeCharacteristic.write(buffer, true, (error) => {
|
|
504
619
|
if (error) {
|
|
@@ -701,6 +816,7 @@ function waitForNobleScanStop(nobleInstance) {
|
|
|
701
816
|
}
|
|
702
817
|
function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS) {
|
|
703
818
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
819
|
+
assertBleActive();
|
|
704
820
|
if (!noble) {
|
|
705
821
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
706
822
|
}
|
|
@@ -712,6 +828,7 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
|
|
|
712
828
|
if (settled)
|
|
713
829
|
return;
|
|
714
830
|
settled = true;
|
|
831
|
+
pendingCancellations.delete(cancel);
|
|
715
832
|
if (timeoutId)
|
|
716
833
|
clearTimeout(timeoutId);
|
|
717
834
|
nobleInstance.removeListener('discover', onDiscover);
|
|
@@ -721,6 +838,7 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
|
|
|
721
838
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
|
|
722
839
|
return;
|
|
723
840
|
}
|
|
841
|
+
assertBleActive();
|
|
724
842
|
if (peripheral) {
|
|
725
843
|
discoveredDevices.set(peripheral.id, peripheral);
|
|
726
844
|
}
|
|
@@ -740,6 +858,10 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
|
|
|
740
858
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
|
|
741
859
|
finish(null).catch(reject);
|
|
742
860
|
}, timeoutMs);
|
|
861
|
+
const cancel = () => {
|
|
862
|
+
finish(null, new Error('Noble BLE is shutting down')).catch(reject);
|
|
863
|
+
};
|
|
864
|
+
pendingCancellations.add(cancel);
|
|
743
865
|
nobleInstance.on('discover', onDiscover);
|
|
744
866
|
nobleInstance.startScanning([], true, (error) => {
|
|
745
867
|
if (error) {
|
|
@@ -759,14 +881,18 @@ function enumerateDevices() {
|
|
|
759
881
|
if (!noble) {
|
|
760
882
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
761
883
|
}
|
|
884
|
+
assertBleActive();
|
|
762
885
|
const nobleInstance = noble;
|
|
763
886
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting device enumeration');
|
|
764
887
|
discoveredDevices.clear();
|
|
765
888
|
ensureDiscoverListener();
|
|
766
889
|
return new Promise((resolve, reject) => {
|
|
767
890
|
const devices = [];
|
|
891
|
+
let settled = false;
|
|
768
892
|
let intervalId;
|
|
769
893
|
const cleanup = () => index.__awaiter(this, void 0, void 0, function* () {
|
|
894
|
+
settled = true;
|
|
895
|
+
pendingCancellations.delete(cancel);
|
|
770
896
|
clearTimeout(timeoutId);
|
|
771
897
|
if (intervalId)
|
|
772
898
|
clearInterval(intervalId);
|
|
@@ -788,13 +914,21 @@ function enumerateDevices() {
|
|
|
788
914
|
connectedDevices.forEach(pushDevice);
|
|
789
915
|
};
|
|
790
916
|
const timeoutId = setTimeout(() => index.__awaiter(this, void 0, void 0, function* () {
|
|
917
|
+
if (settled)
|
|
918
|
+
return;
|
|
791
919
|
checkDevices();
|
|
792
920
|
yield cleanup();
|
|
793
921
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
|
|
794
922
|
resolve(devices);
|
|
795
923
|
}), DEVICE_SCAN_TIMEOUT);
|
|
924
|
+
const cancel = () => {
|
|
925
|
+
cleanup().then(() => resolve([]), reject);
|
|
926
|
+
};
|
|
927
|
+
pendingCancellations.add(cancel);
|
|
796
928
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning for OneKey BLE devices');
|
|
797
929
|
nobleInstance.startScanning([], true, (error) => index.__awaiter(this, void 0, void 0, function* () {
|
|
930
|
+
if (settled)
|
|
931
|
+
return;
|
|
798
932
|
if (error) {
|
|
799
933
|
yield cleanup();
|
|
800
934
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
|
|
@@ -809,7 +943,7 @@ function enumerateDevices() {
|
|
|
809
943
|
}
|
|
810
944
|
function stopScanning() {
|
|
811
945
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
812
|
-
if (!noble)
|
|
946
|
+
if (!noble || nativeReleased)
|
|
813
947
|
return;
|
|
814
948
|
const nobleInstance = noble;
|
|
815
949
|
yield waitForNobleScanStop(nobleInstance);
|
|
@@ -853,13 +987,16 @@ function getDevice(deviceId) {
|
|
|
853
987
|
}
|
|
854
988
|
function discoverServicesAndCharacteristics(peripheral) {
|
|
855
989
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
990
|
+
assertBleActive();
|
|
856
991
|
let timeoutId;
|
|
857
992
|
let onDisconnect;
|
|
858
993
|
const cleanup = () => {
|
|
859
994
|
if (timeoutId)
|
|
860
995
|
clearTimeout(timeoutId);
|
|
861
|
-
if (onDisconnect)
|
|
996
|
+
if (onDisconnect) {
|
|
862
997
|
peripheral.removeListener('disconnect', onDisconnect);
|
|
998
|
+
pendingCancellations.delete(onDisconnect);
|
|
999
|
+
}
|
|
863
1000
|
};
|
|
864
1001
|
const timeoutPromise = new Promise((_, reject) => {
|
|
865
1002
|
timeoutId = setTimeout(() => {
|
|
@@ -872,6 +1009,7 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
872
1009
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Device disconnected during service discovery');
|
|
873
1010
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'Device disconnected during service discovery'));
|
|
874
1011
|
};
|
|
1012
|
+
pendingCancellations.add(onDisconnect);
|
|
875
1013
|
peripheral.once('disconnect', onDisconnect);
|
|
876
1014
|
});
|
|
877
1015
|
const discoveryPromise = (() => index.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -886,6 +1024,7 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
886
1024
|
}
|
|
887
1025
|
});
|
|
888
1026
|
});
|
|
1027
|
+
assertBleActive();
|
|
889
1028
|
if (!services || services.length === 0) {
|
|
890
1029
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
|
|
891
1030
|
}
|
|
@@ -958,17 +1097,8 @@ function forceReconnectPeripheral(peripheral, deviceId) {
|
|
|
958
1097
|
callback();
|
|
959
1098
|
}), { timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' });
|
|
960
1099
|
}
|
|
961
|
-
yield
|
|
962
|
-
peripheral.connect(error => {
|
|
963
|
-
if (error) {
|
|
964
|
-
reject(createNobleBleConnectionError(error));
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
resolve();
|
|
968
|
-
});
|
|
969
|
-
});
|
|
1100
|
+
yield connectPeripheralWithCancellation(peripheral, deviceId);
|
|
970
1101
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
|
|
971
|
-
connectedDevices.set(deviceId, peripheral);
|
|
972
1102
|
yield hdShared.wait(500);
|
|
973
1103
|
});
|
|
974
1104
|
}
|
|
@@ -985,17 +1115,7 @@ function freshScanAndDiscover(deviceId, webContents) {
|
|
|
985
1115
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found in fresh scan`);
|
|
986
1116
|
}
|
|
987
1117
|
discoveredDevices.set(deviceId, freshPeripheral);
|
|
988
|
-
yield
|
|
989
|
-
freshPeripheral.connect((error) => {
|
|
990
|
-
if (error) {
|
|
991
|
-
reject(createNobleBleConnectionError(error, 'Fresh peripheral connection failed: '));
|
|
992
|
-
}
|
|
993
|
-
else {
|
|
994
|
-
connectedDevices.set(deviceId, freshPeripheral);
|
|
995
|
-
resolve();
|
|
996
|
-
}
|
|
997
|
-
});
|
|
998
|
-
});
|
|
1118
|
+
yield connectPeripheralWithCancellation(freshPeripheral, deviceId, 'Fresh peripheral connection failed: ');
|
|
999
1119
|
setupDisconnectListener(freshPeripheral, deviceId, webContents);
|
|
1000
1120
|
yield hdShared.wait(500);
|
|
1001
1121
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Attempting service discovery with fresh peripheral');
|
|
@@ -1034,6 +1154,7 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
|
|
|
1034
1154
|
minTimeout: 500,
|
|
1035
1155
|
maxTimeout: 3000,
|
|
1036
1156
|
onFailedAttempt: error => {
|
|
1157
|
+
assertBleActive();
|
|
1037
1158
|
logger === null || logger === void 0 ? void 0 : logger.error(`[NobleBLE] Service discovery attempt ${error.attemptNumber} failed:`, {
|
|
1038
1159
|
message: error.message,
|
|
1039
1160
|
retriesLeft: error.retriesLeft,
|
|
@@ -1049,11 +1170,13 @@ function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
|
|
|
1049
1170
|
yield forceReconnectPeripheral(peripheral, deviceId);
|
|
1050
1171
|
}
|
|
1051
1172
|
catch (resetError) {
|
|
1052
|
-
if (hdShared.isBleStaleBondHardwareError(resetError)
|
|
1173
|
+
if (hdShared.isBleStaleBondHardwareError(resetError) ||
|
|
1174
|
+
(resetError === null || resetError === void 0 ? void 0 : resetError.errorCode) === hdShared.HardwareErrorCode.BleDeviceDisconnected) {
|
|
1053
1175
|
throw resetError;
|
|
1054
1176
|
}
|
|
1055
1177
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection reset before discovery failed, continuing', resetError);
|
|
1056
1178
|
}
|
|
1179
|
+
assertBleActive();
|
|
1057
1180
|
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1058
1181
|
try {
|
|
1059
1182
|
return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
@@ -1076,36 +1199,46 @@ const directConnectCooldownUntil = new Map();
|
|
|
1076
1199
|
function tryDirectConnectById(deviceId) {
|
|
1077
1200
|
var _a;
|
|
1078
1201
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1202
|
+
assertBleActive();
|
|
1079
1203
|
const nobleInstance = noble;
|
|
1080
1204
|
if (!(nobleInstance === null || nobleInstance === void 0 ? void 0 : nobleInstance.connectAsync))
|
|
1081
1205
|
return undefined;
|
|
1082
1206
|
const cooldownUntil = (_a = directConnectCooldownUntil.get(deviceId)) !== null && _a !== void 0 ? _a : 0;
|
|
1083
1207
|
if (Date.now() < cooldownUntil)
|
|
1084
1208
|
return undefined;
|
|
1209
|
+
let timer;
|
|
1210
|
+
let cancel;
|
|
1211
|
+
let abandoned = false;
|
|
1085
1212
|
try {
|
|
1086
1213
|
const directPromise = nobleInstance.connectAsync(deviceId);
|
|
1214
|
+
trackNativeConnection(directPromise.then((late) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1215
|
+
if ((!abandoned && !disposing) || nativeReleased)
|
|
1216
|
+
return;
|
|
1217
|
+
const peripheral = late !== null && late !== void 0 ? late : discoveredDevices.get(deviceId);
|
|
1218
|
+
if ((peripheral === null || peripheral === void 0 ? void 0 : peripheral.state) === 'connected' && !connectedDevices.has(deviceId)) {
|
|
1219
|
+
peripheral.removeAllListeners('disconnect');
|
|
1220
|
+
yield runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
|
|
1221
|
+
timeoutMs: BLE_DISCONNECT_CONFIRM_TIMEOUT_MS,
|
|
1222
|
+
timeoutBehavior: 'resolve',
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
})), () => { var _a; return (_a = nobleInstance.cancelConnect) === null || _a === void 0 ? void 0 : _a.call(nobleInstance, deviceId); });
|
|
1087
1226
|
const raced = yield Promise.race([
|
|
1088
1227
|
directPromise,
|
|
1089
1228
|
new Promise(resolve => {
|
|
1090
|
-
|
|
1229
|
+
cancel = () => {
|
|
1230
|
+
abandoned = true;
|
|
1231
|
+
resolve('timeout');
|
|
1232
|
+
};
|
|
1233
|
+
pendingCancellations.add(cancel);
|
|
1234
|
+
timer = setTimeout(cancel, DIRECT_CONNECT_TIMEOUT_MS);
|
|
1091
1235
|
}),
|
|
1092
1236
|
]);
|
|
1093
|
-
if (raced === 'timeout') {
|
|
1237
|
+
if (raced === 'timeout' || disposing) {
|
|
1094
1238
|
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
1095
1239
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id timed out, falling back to scan', {
|
|
1096
1240
|
deviceId,
|
|
1097
1241
|
});
|
|
1098
|
-
directPromise
|
|
1099
|
-
.then(late => {
|
|
1100
|
-
const latePeripheral = late !== null && late !== void 0 ? late : discoveredDevices.get(deviceId);
|
|
1101
|
-
if (latePeripheral &&
|
|
1102
|
-
latePeripheral.state === 'connected' &&
|
|
1103
|
-
!connectedDevices.has(deviceId)) {
|
|
1104
|
-
latePeripheral.removeAllListeners('disconnect');
|
|
1105
|
-
latePeripheral.disconnect(() => undefined);
|
|
1106
|
-
}
|
|
1107
|
-
})
|
|
1108
|
-
.catch(() => undefined);
|
|
1109
1242
|
return undefined;
|
|
1110
1243
|
}
|
|
1111
1244
|
const peripheral = raced !== null && raced !== void 0 ? raced : discoveredDevices.get(deviceId);
|
|
@@ -1122,6 +1255,11 @@ function tryDirectConnectById(deviceId) {
|
|
|
1122
1255
|
});
|
|
1123
1256
|
return undefined;
|
|
1124
1257
|
}
|
|
1258
|
+
finally {
|
|
1259
|
+
clearTimeout(timer);
|
|
1260
|
+
if (cancel)
|
|
1261
|
+
pendingCancellations.delete(cancel);
|
|
1262
|
+
}
|
|
1125
1263
|
});
|
|
1126
1264
|
}
|
|
1127
1265
|
function connectDevice(deviceId, webContents) {
|
|
@@ -1174,6 +1312,7 @@ function connectDevice(deviceId, webContents) {
|
|
|
1174
1312
|
peripheral = byIdFirst ? yield scanForPeripheral() : yield connectById();
|
|
1175
1313
|
}
|
|
1176
1314
|
}
|
|
1315
|
+
assertBleActive();
|
|
1177
1316
|
if (!peripheral) {
|
|
1178
1317
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found`);
|
|
1179
1318
|
}
|
|
@@ -1184,6 +1323,7 @@ function connectDevice(deviceId, webContents) {
|
|
|
1184
1323
|
if (!connectedDevices.has(deviceId)) {
|
|
1185
1324
|
connectedDevices.set(deviceId, peripheral);
|
|
1186
1325
|
}
|
|
1326
|
+
assertBleActive();
|
|
1187
1327
|
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1188
1328
|
if (deviceCharacteristics.has(deviceId)) {
|
|
1189
1329
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device characteristics already available');
|
|
@@ -1208,6 +1348,7 @@ function connectDevice(deviceId, webContents) {
|
|
|
1208
1348
|
}
|
|
1209
1349
|
try {
|
|
1210
1350
|
const characteristics = yield setupConnectionAndDiscoverServices(peripheral, deviceId, webContents);
|
|
1351
|
+
assertBleActive();
|
|
1211
1352
|
deviceCharacteristics.set(deviceId, characteristics);
|
|
1212
1353
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
|
|
1213
1354
|
}
|
|
@@ -1218,38 +1359,33 @@ function connectDevice(deviceId, webContents) {
|
|
|
1218
1359
|
}
|
|
1219
1360
|
return;
|
|
1220
1361
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
resolve();
|
|
1236
|
-
}
|
|
1237
|
-
catch (setupError) {
|
|
1238
|
-
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection setup failed:', setupError);
|
|
1239
|
-
disconnectDevice(deviceId)
|
|
1240
|
-
.catch(() => undefined)
|
|
1241
|
-
.then(() => reject(setupError));
|
|
1242
|
-
}
|
|
1243
|
-
}));
|
|
1244
|
-
});
|
|
1362
|
+
const connectedPeripheral = peripheral;
|
|
1363
|
+
yield connectPeripheralWithCancellation(connectedPeripheral, deviceId);
|
|
1364
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connected to device:', deviceId);
|
|
1365
|
+
try {
|
|
1366
|
+
const characteristics = yield setupConnectionAndDiscoverServices(connectedPeripheral, deviceId, webContents);
|
|
1367
|
+
assertBleActive();
|
|
1368
|
+
deviceCharacteristics.set(deviceId, characteristics);
|
|
1369
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
|
|
1370
|
+
}
|
|
1371
|
+
catch (setupError) {
|
|
1372
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection setup failed:', setupError);
|
|
1373
|
+
yield disconnectDevice(deviceId).catch(() => undefined);
|
|
1374
|
+
throw setupError;
|
|
1375
|
+
}
|
|
1245
1376
|
});
|
|
1246
1377
|
}
|
|
1247
1378
|
function disconnectDevice(deviceId) {
|
|
1379
|
+
var _a;
|
|
1248
1380
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1249
|
-
|
|
1381
|
+
if (nativeReleased)
|
|
1382
|
+
return;
|
|
1383
|
+
const pendingConnection = connectingDevices.get(deviceId);
|
|
1384
|
+
const peripheral = (_a = connectedDevices.get(deviceId)) !== null && _a !== void 0 ? _a : pendingConnection === null || pendingConnection === void 0 ? void 0 : pendingConnection.peripheral;
|
|
1250
1385
|
if (!peripheral) {
|
|
1251
1386
|
return;
|
|
1252
1387
|
}
|
|
1388
|
+
pendingConnection === null || pendingConnection === void 0 ? void 0 : pendingConnection.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, `Device ${deviceId} connection cancelled`));
|
|
1253
1389
|
const disconnectEntry = deviceDisconnectListeners.get(deviceId);
|
|
1254
1390
|
if (disconnectEntry) {
|
|
1255
1391
|
disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
|
|
@@ -1273,6 +1409,8 @@ function disconnectDevice(deviceId) {
|
|
|
1273
1409
|
}
|
|
1274
1410
|
function unsubscribeNotifications(deviceId) {
|
|
1275
1411
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1412
|
+
if (nativeReleased)
|
|
1413
|
+
return;
|
|
1276
1414
|
const peripheral = connectedDevices.get(deviceId);
|
|
1277
1415
|
const characteristics = deviceCharacteristics.get(deviceId);
|
|
1278
1416
|
if (!peripheral || !characteristics) {
|
|
@@ -1292,7 +1430,8 @@ function unsubscribeNotifications(deviceId) {
|
|
|
1292
1430
|
subscribedDevices.delete(deviceId);
|
|
1293
1431
|
}
|
|
1294
1432
|
finally {
|
|
1295
|
-
|
|
1433
|
+
if (!disposing)
|
|
1434
|
+
subscriptionOperations.set(deviceId, 'idle');
|
|
1296
1435
|
}
|
|
1297
1436
|
});
|
|
1298
1437
|
}
|
|
@@ -1342,10 +1481,12 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1342
1481
|
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1343
1482
|
timeoutBehavior: 'resolve',
|
|
1344
1483
|
});
|
|
1484
|
+
assertBleActive();
|
|
1345
1485
|
yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
|
|
1346
1486
|
timeoutMs: NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
|
|
1347
1487
|
timeoutBehavior: 'reject',
|
|
1348
1488
|
});
|
|
1489
|
+
assertBleActive();
|
|
1349
1490
|
notifyCharacteristic.on('data', (data) => {
|
|
1350
1491
|
if (!pairedDevices.has(deviceId)) {
|
|
1351
1492
|
pairedDevices.add(deviceId);
|
|
@@ -1358,25 +1499,42 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1358
1499
|
const subscribeStartedAt = Date.now();
|
|
1359
1500
|
try {
|
|
1360
1501
|
yield rebuildAppSubscription(deviceId, notifyCharacteristic);
|
|
1502
|
+
assertBleActive();
|
|
1361
1503
|
subscribedDevices.set(deviceId, true);
|
|
1362
1504
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification subscription active', {
|
|
1363
1505
|
deviceId,
|
|
1364
1506
|
ms: Date.now() - subscribeStartedAt,
|
|
1365
1507
|
});
|
|
1366
1508
|
}
|
|
1509
|
+
catch (error) {
|
|
1510
|
+
throw createNobleBleConnectionError(error, 'Notification subscription failed: ');
|
|
1511
|
+
}
|
|
1367
1512
|
finally {
|
|
1368
|
-
|
|
1513
|
+
if (!disposing)
|
|
1514
|
+
subscriptionOperations.set(deviceId, 'idle');
|
|
1369
1515
|
}
|
|
1370
1516
|
});
|
|
1371
1517
|
}
|
|
1372
1518
|
function setupNobleBleHandlers(webContents) {
|
|
1519
|
+
if (disposing)
|
|
1520
|
+
return;
|
|
1521
|
+
let windowDestroyed = false;
|
|
1373
1522
|
try {
|
|
1374
1523
|
logger = require('electron-log');
|
|
1375
1524
|
const { ipcMain } = require('electron');
|
|
1525
|
+
const channels = new Set();
|
|
1526
|
+
removeIpcHandlers = () => channels.forEach(channel => ipcMain.removeHandler(channel));
|
|
1376
1527
|
const handle = (channel, listener) => {
|
|
1528
|
+
channels.add(channel);
|
|
1377
1529
|
ipcMain.removeHandler(channel);
|
|
1378
1530
|
ipcMain.handle(channel, (...args) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1379
1531
|
try {
|
|
1532
|
+
if (windowCleanup)
|
|
1533
|
+
yield windowCleanup;
|
|
1534
|
+
assertBleActive();
|
|
1535
|
+
if (windowDestroyed) {
|
|
1536
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'BLE window destroyed');
|
|
1537
|
+
}
|
|
1380
1538
|
return yield Promise.resolve(listener(...args));
|
|
1381
1539
|
}
|
|
1382
1540
|
catch (error) {
|
|
@@ -1479,13 +1637,27 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1479
1637
|
}
|
|
1480
1638
|
}));
|
|
1481
1639
|
webContents.on('destroyed', () => {
|
|
1640
|
+
windowDestroyed = true;
|
|
1641
|
+
if (disposing)
|
|
1642
|
+
return;
|
|
1482
1643
|
safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
|
|
1483
|
-
|
|
1644
|
+
const previousCleanup = windowCleanup;
|
|
1645
|
+
windowCleanup = (() => index.__awaiter(this, void 0, void 0, function* () {
|
|
1646
|
+
if (previousCleanup)
|
|
1647
|
+
yield previousCleanup;
|
|
1648
|
+
if (disposing)
|
|
1649
|
+
return;
|
|
1484
1650
|
const deviceIds = Array.from(connectedDevices.keys());
|
|
1485
1651
|
for (const deviceId of deviceIds) {
|
|
1652
|
+
if (disposing)
|
|
1653
|
+
return;
|
|
1486
1654
|
yield unsubscribeNotifications(deviceId).catch(() => undefined);
|
|
1655
|
+
if (disposing)
|
|
1656
|
+
return;
|
|
1487
1657
|
yield disconnectDevice(deviceId).catch(() => undefined);
|
|
1488
1658
|
}
|
|
1659
|
+
if (disposing)
|
|
1660
|
+
return;
|
|
1489
1661
|
yield stopScanning().catch(() => undefined);
|
|
1490
1662
|
cleanupNobleListeners();
|
|
1491
1663
|
discoveredDevices.clear();
|
|
@@ -1501,8 +1673,77 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1501
1673
|
throw error;
|
|
1502
1674
|
}
|
|
1503
1675
|
}
|
|
1676
|
+
function disposeNobleBleSupport(releaseNoble = instance => instance.stop()) {
|
|
1677
|
+
if (disposePromise)
|
|
1678
|
+
return disposePromise;
|
|
1679
|
+
disposing = true;
|
|
1680
|
+
removeIpcHandlers === null || removeIpcHandlers === void 0 ? void 0 : removeIpcHandlers();
|
|
1681
|
+
const instance = noble;
|
|
1682
|
+
const deviceIds = new Set([...connectedDevices.keys(), ...connectingDevices.keys()]);
|
|
1683
|
+
const pendingPeripherals = Array.from(connectingDevices.values(), pending => pending.peripheral);
|
|
1684
|
+
for (const cancel of pendingCancellations)
|
|
1685
|
+
cancel();
|
|
1686
|
+
for (const id of idleDisconnectTimers.keys())
|
|
1687
|
+
clearIdleDisconnect(id);
|
|
1688
|
+
for (const pending of connectingDevices.values()) {
|
|
1689
|
+
pending.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'Noble BLE is shutting down'));
|
|
1690
|
+
}
|
|
1691
|
+
const connections = Array.from(nativeConnections);
|
|
1692
|
+
for (const connection of connections) {
|
|
1693
|
+
try {
|
|
1694
|
+
connection.cancel();
|
|
1695
|
+
}
|
|
1696
|
+
catch (error) {
|
|
1697
|
+
logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Native connect cancellation failed', error);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
let timeout;
|
|
1701
|
+
disposePromise = (() => index.__awaiter(this, void 0, void 0, function* () {
|
|
1702
|
+
try {
|
|
1703
|
+
yield Promise.race([
|
|
1704
|
+
Promise.allSettled([
|
|
1705
|
+
windowCleanup,
|
|
1706
|
+
...connections.map(connection => connection.settled),
|
|
1707
|
+
stopScanning(),
|
|
1708
|
+
...pendingPeripherals.map(peripheral => runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
|
|
1709
|
+
timeoutMs: BLE_DISCONNECT_CONFIRM_TIMEOUT_MS,
|
|
1710
|
+
timeoutBehavior: 'resolve',
|
|
1711
|
+
})),
|
|
1712
|
+
...Array.from(deviceIds, (id) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1713
|
+
yield unsubscribeNotifications(id).catch(() => undefined);
|
|
1714
|
+
yield disconnectDevice(id).catch(() => undefined);
|
|
1715
|
+
})),
|
|
1716
|
+
]),
|
|
1717
|
+
new Promise(resolve => {
|
|
1718
|
+
timeout = setTimeout(() => {
|
|
1719
|
+
logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Process dispose timed out; releasing native manager');
|
|
1720
|
+
resolve();
|
|
1721
|
+
}, 3500);
|
|
1722
|
+
}),
|
|
1723
|
+
]);
|
|
1724
|
+
}
|
|
1725
|
+
finally {
|
|
1726
|
+
clearTimeout(timeout);
|
|
1727
|
+
nativeReleased = true;
|
|
1728
|
+
cleanupNobleListeners();
|
|
1729
|
+
if (instance && persistentStateListener) {
|
|
1730
|
+
instance.removeListener('stateChange', persistentStateListener);
|
|
1731
|
+
persistentStateListener = null;
|
|
1732
|
+
}
|
|
1733
|
+
for (const id of deviceIds)
|
|
1734
|
+
cleanupDevice(id);
|
|
1735
|
+
discoveredDevices.clear();
|
|
1736
|
+
directConnectCooldownUntil.clear();
|
|
1737
|
+
if (instance)
|
|
1738
|
+
releaseNoble(instance);
|
|
1739
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Process dispose completed');
|
|
1740
|
+
}
|
|
1741
|
+
}))();
|
|
1742
|
+
return disposePromise;
|
|
1743
|
+
}
|
|
1504
1744
|
|
|
1505
1745
|
exports.createNobleBleConnectionError = createNobleBleConnectionError;
|
|
1506
1746
|
exports.createNobleBleIpcErrorResponse = createNobleBleIpcErrorResponse;
|
|
1747
|
+
exports.disposeNobleBleSupport = disposeNobleBleSupport;
|
|
1507
1748
|
exports.resolveNobleBleWritePacingDelay = resolveNobleBleWritePacingDelay;
|
|
1508
1749
|
exports.setupNobleBleHandlers = setupNobleBleHandlers;
|