@onekeyfe/hd-transport-electron 1.2.2-alpha.9 → 1.2.2

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.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-00297050.js');
3
+ var index = require('./index-474dbbae.js');
4
4
  var hdShared = require('@onekeyfe/hd-shared');
5
5
  var pRetry = require('p-retry');
6
6
 
@@ -96,10 +96,66 @@ function softRefreshSubscription(params) {
96
96
  }
97
97
 
98
98
  const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5000;
99
- const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10000;
99
+ 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 pendingEnumerations = new Set();
110
+ const nativeConnections = new Set();
111
+ function trackNativeConnection(operation, cancel) {
112
+ const connection = {
113
+ cancel,
114
+ settled: operation.then(() => undefined, () => undefined),
115
+ };
116
+ nativeConnections.add(connection);
117
+ connection.settled = connection.settled.finally(() => nativeConnections.delete(connection));
118
+ }
119
+ function assertBleActive() {
120
+ if (disposing) {
121
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'Noble BLE is shutting down');
122
+ }
123
+ }
124
+ function createNobleBleConnectionError(error, messagePrefix = '') {
125
+ const errorMessage = error.message;
126
+ const isInvalidMacOsBond = (error.nativeErrorCode === 14 && error.nativeErrorDomain === 'CBErrorDomain') ||
127
+ (error.nativeErrorCode === 15 && error.nativeErrorDomain === 'CBATTErrorDomain');
128
+ if (isInvalidMacOsBond) {
129
+ const nativeErrorMessage = `${messagePrefix}${errorMessage}`;
130
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleBondInvalid, `${hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleBondInvalid]} (${nativeErrorMessage})`, {
131
+ nativeErrorMessage,
132
+ });
133
+ }
134
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `${messagePrefix}${errorMessage}`);
135
+ }
136
+ function createNobleBleIpcErrorResponse(error) {
137
+ const candidate = error;
138
+ const errorCode = typeof (candidate === null || candidate === void 0 ? void 0 : candidate.errorCode) === 'number' ? candidate.errorCode : hdShared.HardwareErrorCode.UnknownError;
139
+ const message = typeof (candidate === null || candidate === void 0 ? void 0 : candidate.message) === 'string' ? candidate.message : String(error !== null && error !== void 0 ? error : 'Unknown error');
140
+ const name = typeof (candidate === null || candidate === void 0 ? void 0 : candidate.name) === 'string' ? candidate.name : 'Error';
141
+ let params;
142
+ if ((candidate === null || candidate === void 0 ? void 0 : candidate.params) !== undefined) {
143
+ try {
144
+ const serializedParams = JSON.stringify(candidate.params);
145
+ params = serializedParams === undefined ? undefined : JSON.parse(serializedParams);
146
+ }
147
+ catch (_a) {
148
+ params = undefined;
149
+ }
150
+ }
151
+ return {
152
+ type: 'NobleBleIpcError',
153
+ success: false,
154
+ error: Object.assign({ name,
155
+ message,
156
+ errorCode }, (params !== undefined ? { params } : {})),
157
+ };
158
+ }
103
159
  const bluetoothState = {
104
160
  available: false,
105
161
  unsupported: false,
@@ -109,6 +165,86 @@ let persistentStateListener = null;
109
165
  let persistentDiscoverListener = null;
110
166
  const discoveredDevices = new Map();
111
167
  const connectedDevices = new Map();
168
+ const connectingDevices = new Map();
169
+ let nextConnectionGeneration = 0;
170
+ function connectPeripheralWithCancellation(peripheral, deviceId, messagePrefix = '') {
171
+ assertBleActive();
172
+ return new Promise((resolve, reject) => {
173
+ var _a;
174
+ const generation = ++nextConnectionGeneration;
175
+ let settled = false;
176
+ const settle = (settler) => {
177
+ var _a;
178
+ if (settled)
179
+ return false;
180
+ settled = true;
181
+ if (((_a = connectingDevices.get(deviceId)) === null || _a === void 0 ? void 0 : _a.generation) === generation) {
182
+ connectingDevices.delete(deviceId);
183
+ }
184
+ settler();
185
+ return true;
186
+ };
187
+ const pendingConnection = {
188
+ peripheral,
189
+ generation,
190
+ cancel: error => {
191
+ settle(() => reject(error));
192
+ },
193
+ };
194
+ (_a = connectingDevices
195
+ .get(deviceId)) === null || _a === void 0 ? void 0 : _a.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, `Device ${deviceId} connect attempt was superseded`));
196
+ connectingDevices.set(deviceId, pendingConnection);
197
+ let completeNative;
198
+ trackNativeConnection(new Promise(resolve => {
199
+ completeNative = resolve;
200
+ }), () => {
201
+ var _a;
202
+ if (typeof peripheral.cancelConnect === 'function')
203
+ peripheral.cancelConnect();
204
+ else
205
+ (_a = noble === null || noble === void 0 ? void 0 : noble.cancelConnect) === null || _a === void 0 ? void 0 : _a.call(noble, deviceId);
206
+ });
207
+ const onConnect = (error) => {
208
+ if (nativeReleased) {
209
+ completeNative();
210
+ return;
211
+ }
212
+ if (error) {
213
+ settle(() => reject(createNobleBleConnectionError(error, messagePrefix)));
214
+ completeNative();
215
+ return;
216
+ }
217
+ if (!settle(() => {
218
+ connectedDevices.set(deviceId, peripheral);
219
+ resolve();
220
+ })) {
221
+ const activeConnection = connectingDevices.get(deviceId);
222
+ if (!activeConnection || activeConnection.peripheral !== peripheral) {
223
+ try {
224
+ peripheral.removeAllListeners('disconnect');
225
+ runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
226
+ timeoutMs: BLE_DISCONNECT_CONFIRM_TIMEOUT_MS,
227
+ timeoutBehavior: 'resolve',
228
+ })
229
+ .catch(() => undefined)
230
+ .finally(completeNative);
231
+ return;
232
+ }
233
+ catch (_a) {
234
+ }
235
+ }
236
+ }
237
+ completeNative();
238
+ };
239
+ try {
240
+ peripheral.connect(onConnect);
241
+ }
242
+ catch (error) {
243
+ completeNative();
244
+ settle(() => reject(error));
245
+ }
246
+ });
247
+ }
112
248
  const pairedDevices = new Set();
113
249
  const deviceCharacteristics = new Map();
114
250
  const notificationCallbacks = new Map();
@@ -247,6 +383,7 @@ function updateBluetoothState(state) {
247
383
  }
248
384
  function initializeNoble() {
249
385
  return index.__awaiter(this, void 0, void 0, function* () {
386
+ assertBleActive();
250
387
  if (noble)
251
388
  return;
252
389
  try {
@@ -263,9 +400,11 @@ function initializeNoble() {
263
400
  return;
264
401
  }
265
402
  const timeout = setTimeout(() => {
403
+ cleanup();
266
404
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Bluetooth initialization timeout'));
267
405
  }, BLUETOOTH_INIT_TIMEOUT);
268
406
  const cleanup = () => {
407
+ pendingCancellations.delete(cancel);
269
408
  clearTimeout(timeout);
270
409
  if (noble) {
271
410
  noble.removeListener('stateChange', onStateChange);
@@ -290,8 +429,14 @@ function initializeNoble() {
290
429
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
291
430
  }
292
431
  };
432
+ const cancel = () => {
433
+ cleanup();
434
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'Noble BLE is shutting down'));
435
+ };
436
+ pendingCancellations.add(cancel);
293
437
  noble.on('stateChange', onStateChange);
294
438
  });
439
+ assertBleActive();
295
440
  if (!persistentDiscoverListener) {
296
441
  persistentDiscoverListener = (peripheral) => {
297
442
  handleDeviceDiscovered(peripheral);
@@ -338,6 +483,8 @@ function broadcastToAllWebContents(channel, payload) {
338
483
  }
339
484
  function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle') {
340
485
  clearIdleDisconnect(deviceId);
486
+ if (disposing)
487
+ return;
341
488
  idleDisconnectTimers.set(deviceId, setTimeout(() => {
342
489
  var _a;
343
490
  idleDisconnectTimers.delete(deviceId);
@@ -373,7 +520,7 @@ function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle
373
520
  }, ms));
374
521
  }
375
522
  function cleanupDevice(deviceId, webContents, options = {}) {
376
- var _a;
523
+ var _a, _b;
377
524
  const { cleanupConnection = true, cleanupDiscoveredCache = false, sendDisconnectEvent = false, cancelOperations = true, reason = 'unknown', disconnectReason = hdShared.EBleDisconnectReason.DeviceDisconnected, } = options;
378
525
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting device cleanup', {
379
526
  deviceId,
@@ -387,6 +534,8 @@ function cleanupDevice(deviceId, webContents, options = {}) {
387
534
  const peripheral = connectedDevices.get(deviceId);
388
535
  const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
389
536
  if (cleanupConnection) {
537
+ (_b = connectingDevices
538
+ .get(deviceId)) === null || _b === void 0 ? void 0 : _b.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, `Device ${deviceId} disconnected while connecting`));
390
539
  const disconnectEntry = deviceDisconnectListeners.get(deviceId);
391
540
  if (disconnectEntry) {
392
541
  disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
@@ -465,6 +614,7 @@ function setupMtuListener(peripheral, deviceId, webContents) {
465
614
  }
466
615
  function writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, buffer) {
467
616
  return index.__awaiter(this, void 0, void 0, function* () {
617
+ assertBleActive();
468
618
  return new Promise((resolve, reject) => {
469
619
  writeCharacteristic.write(buffer, true, (error) => {
470
620
  if (error) {
@@ -667,6 +817,7 @@ function waitForNobleScanStop(nobleInstance) {
667
817
  }
668
818
  function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS) {
669
819
  return index.__awaiter(this, void 0, void 0, function* () {
820
+ assertBleActive();
670
821
  if (!noble) {
671
822
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
672
823
  }
@@ -678,6 +829,7 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
678
829
  if (settled)
679
830
  return;
680
831
  settled = true;
832
+ pendingCancellations.delete(cancel);
681
833
  if (timeoutId)
682
834
  clearTimeout(timeoutId);
683
835
  nobleInstance.removeListener('discover', onDiscover);
@@ -687,6 +839,7 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
687
839
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
688
840
  return;
689
841
  }
842
+ assertBleActive();
690
843
  if (peripheral) {
691
844
  discoveredDevices.set(peripheral.id, peripheral);
692
845
  }
@@ -706,6 +859,10 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
706
859
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
707
860
  finish(null).catch(reject);
708
861
  }, timeoutMs);
862
+ const cancel = () => {
863
+ finish(null, new Error('Noble BLE is shutting down')).catch(reject);
864
+ };
865
+ pendingCancellations.add(cancel);
709
866
  nobleInstance.on('discover', onDiscover);
710
867
  nobleInstance.startScanning([], true, (error) => {
711
868
  if (error) {
@@ -717,7 +874,7 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
717
874
  });
718
875
  });
719
876
  }
720
- function enumerateDevices() {
877
+ function enumerateDevices(isWindowDestroyed) {
721
878
  return index.__awaiter(this, void 0, void 0, function* () {
722
879
  if (!noble) {
723
880
  yield initializeNoble();
@@ -725,19 +882,30 @@ function enumerateDevices() {
725
882
  if (!noble) {
726
883
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
727
884
  }
885
+ assertBleActive();
886
+ if (isWindowDestroyed())
887
+ return [];
728
888
  const nobleInstance = noble;
729
889
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting device enumeration');
730
- discoveredDevices.clear();
731
890
  ensureDiscoverListener();
732
891
  return new Promise((resolve, reject) => {
733
892
  const devices = [];
893
+ let settled = false;
734
894
  let intervalId;
735
- const cleanup = () => index.__awaiter(this, void 0, void 0, function* () {
736
- clearTimeout(timeoutId);
737
- if (intervalId)
738
- clearInterval(intervalId);
739
- yield waitForNobleScanStop(nobleInstance);
740
- });
895
+ let cleanupPromise;
896
+ const cleanup = () => {
897
+ if (!cleanupPromise) {
898
+ settled = true;
899
+ pendingCancellations.delete(cancel);
900
+ clearTimeout(timeoutId);
901
+ if (intervalId)
902
+ clearInterval(intervalId);
903
+ cleanupPromise = waitForNobleScanStop(nobleInstance).finally(() => {
904
+ pendingEnumerations.delete(cancel);
905
+ });
906
+ }
907
+ return cleanupPromise;
908
+ };
741
909
  const pushDevice = (peripheral, id) => {
742
910
  var _a;
743
911
  if (devices.some(d => d.id === id))
@@ -754,13 +922,20 @@ function enumerateDevices() {
754
922
  connectedDevices.forEach(pushDevice);
755
923
  };
756
924
  const timeoutId = setTimeout(() => index.__awaiter(this, void 0, void 0, function* () {
925
+ if (settled)
926
+ return;
757
927
  checkDevices();
758
928
  yield cleanup();
759
929
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
760
930
  resolve(devices);
761
931
  }), DEVICE_SCAN_TIMEOUT);
932
+ const cancel = () => cleanup().then(() => resolve([]), reject);
933
+ pendingCancellations.add(cancel);
934
+ pendingEnumerations.add(cancel);
762
935
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning for OneKey BLE devices');
763
936
  nobleInstance.startScanning([], true, (error) => index.__awaiter(this, void 0, void 0, function* () {
937
+ if (settled)
938
+ return;
764
939
  if (error) {
765
940
  yield cleanup();
766
941
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
@@ -775,7 +950,7 @@ function enumerateDevices() {
775
950
  }
776
951
  function stopScanning() {
777
952
  return index.__awaiter(this, void 0, void 0, function* () {
778
- if (!noble)
953
+ if (!noble || nativeReleased)
779
954
  return;
780
955
  const nobleInstance = noble;
781
956
  yield waitForNobleScanStop(nobleInstance);
@@ -819,13 +994,16 @@ function getDevice(deviceId) {
819
994
  }
820
995
  function discoverServicesAndCharacteristics(peripheral) {
821
996
  return index.__awaiter(this, void 0, void 0, function* () {
997
+ assertBleActive();
822
998
  let timeoutId;
823
999
  let onDisconnect;
824
1000
  const cleanup = () => {
825
1001
  if (timeoutId)
826
1002
  clearTimeout(timeoutId);
827
- if (onDisconnect)
1003
+ if (onDisconnect) {
828
1004
  peripheral.removeListener('disconnect', onDisconnect);
1005
+ pendingCancellations.delete(onDisconnect);
1006
+ }
829
1007
  };
830
1008
  const timeoutPromise = new Promise((_, reject) => {
831
1009
  timeoutId = setTimeout(() => {
@@ -838,6 +1016,7 @@ function discoverServicesAndCharacteristics(peripheral) {
838
1016
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Device disconnected during service discovery');
839
1017
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'Device disconnected during service discovery'));
840
1018
  };
1019
+ pendingCancellations.add(onDisconnect);
841
1020
  peripheral.once('disconnect', onDisconnect);
842
1021
  });
843
1022
  const discoveryPromise = (() => index.__awaiter(this, void 0, void 0, function* () {
@@ -852,6 +1031,7 @@ function discoverServicesAndCharacteristics(peripheral) {
852
1031
  }
853
1032
  });
854
1033
  });
1034
+ assertBleActive();
855
1035
  if (!services || services.length === 0) {
856
1036
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
857
1037
  }
@@ -924,12 +1104,8 @@ function forceReconnectPeripheral(peripheral, deviceId) {
924
1104
  callback();
925
1105
  }), { timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' });
926
1106
  }
927
- yield runBleCallbackOperation(callback => peripheral.connect(callback), {
928
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
929
- timeoutBehavior: 'reject',
930
- });
1107
+ yield connectPeripheralWithCancellation(peripheral, deviceId);
931
1108
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
932
- connectedDevices.set(deviceId, peripheral);
933
1109
  yield hdShared.wait(500);
934
1110
  });
935
1111
  }
@@ -946,17 +1122,7 @@ function freshScanAndDiscover(deviceId, webContents) {
946
1122
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found in fresh scan`);
947
1123
  }
948
1124
  discoveredDevices.set(deviceId, freshPeripheral);
949
- yield new Promise((resolve, reject) => {
950
- freshPeripheral.connect((error) => {
951
- if (error) {
952
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Fresh peripheral connection failed: ${error.message}`));
953
- }
954
- else {
955
- connectedDevices.set(deviceId, freshPeripheral);
956
- resolve();
957
- }
958
- });
959
- });
1125
+ yield connectPeripheralWithCancellation(freshPeripheral, deviceId, 'Fresh peripheral connection failed: ');
960
1126
  setupDisconnectListener(freshPeripheral, deviceId, webContents);
961
1127
  yield hdShared.wait(500);
962
1128
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Attempting service discovery with fresh peripheral');
@@ -995,6 +1161,7 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
995
1161
  minTimeout: 500,
996
1162
  maxTimeout: 3000,
997
1163
  onFailedAttempt: error => {
1164
+ assertBleActive();
998
1165
  logger === null || logger === void 0 ? void 0 : logger.error(`[NobleBLE] Service discovery attempt ${error.attemptNumber} failed:`, {
999
1166
  message: error.message,
1000
1167
  retriesLeft: error.retriesLeft,
@@ -1010,8 +1177,13 @@ function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
1010
1177
  yield forceReconnectPeripheral(peripheral, deviceId);
1011
1178
  }
1012
1179
  catch (resetError) {
1180
+ if (hdShared.isBleStaleBondHardwareError(resetError) ||
1181
+ (resetError === null || resetError === void 0 ? void 0 : resetError.errorCode) === hdShared.HardwareErrorCode.BleDeviceDisconnected) {
1182
+ throw resetError;
1183
+ }
1013
1184
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection reset before discovery failed, continuing', resetError);
1014
1185
  }
1186
+ assertBleActive();
1015
1187
  setupDisconnectListener(peripheral, deviceId, webContents);
1016
1188
  try {
1017
1189
  return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
@@ -1034,36 +1206,46 @@ const directConnectCooldownUntil = new Map();
1034
1206
  function tryDirectConnectById(deviceId) {
1035
1207
  var _a;
1036
1208
  return index.__awaiter(this, void 0, void 0, function* () {
1209
+ assertBleActive();
1037
1210
  const nobleInstance = noble;
1038
1211
  if (!(nobleInstance === null || nobleInstance === void 0 ? void 0 : nobleInstance.connectAsync))
1039
1212
  return undefined;
1040
1213
  const cooldownUntil = (_a = directConnectCooldownUntil.get(deviceId)) !== null && _a !== void 0 ? _a : 0;
1041
1214
  if (Date.now() < cooldownUntil)
1042
1215
  return undefined;
1216
+ let timer;
1217
+ let cancel;
1218
+ let abandoned = false;
1043
1219
  try {
1044
1220
  const directPromise = nobleInstance.connectAsync(deviceId);
1221
+ trackNativeConnection(directPromise.then((late) => index.__awaiter(this, void 0, void 0, function* () {
1222
+ if ((!abandoned && !disposing) || nativeReleased)
1223
+ return;
1224
+ const peripheral = late !== null && late !== void 0 ? late : discoveredDevices.get(deviceId);
1225
+ if ((peripheral === null || peripheral === void 0 ? void 0 : peripheral.state) === 'connected' && !connectedDevices.has(deviceId)) {
1226
+ peripheral.removeAllListeners('disconnect');
1227
+ yield runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
1228
+ timeoutMs: BLE_DISCONNECT_CONFIRM_TIMEOUT_MS,
1229
+ timeoutBehavior: 'resolve',
1230
+ });
1231
+ }
1232
+ })), () => { var _a; return (_a = nobleInstance.cancelConnect) === null || _a === void 0 ? void 0 : _a.call(nobleInstance, deviceId); });
1045
1233
  const raced = yield Promise.race([
1046
1234
  directPromise,
1047
1235
  new Promise(resolve => {
1048
- setTimeout(() => resolve('timeout'), DIRECT_CONNECT_TIMEOUT_MS);
1236
+ cancel = () => {
1237
+ abandoned = true;
1238
+ resolve('timeout');
1239
+ };
1240
+ pendingCancellations.add(cancel);
1241
+ timer = setTimeout(cancel, DIRECT_CONNECT_TIMEOUT_MS);
1049
1242
  }),
1050
1243
  ]);
1051
- if (raced === 'timeout') {
1244
+ if (raced === 'timeout' || disposing) {
1052
1245
  directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
1053
1246
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id timed out, falling back to scan', {
1054
1247
  deviceId,
1055
1248
  });
1056
- directPromise
1057
- .then(late => {
1058
- const latePeripheral = late !== null && late !== void 0 ? late : discoveredDevices.get(deviceId);
1059
- if (latePeripheral &&
1060
- latePeripheral.state === 'connected' &&
1061
- !connectedDevices.has(deviceId)) {
1062
- latePeripheral.removeAllListeners('disconnect');
1063
- latePeripheral.disconnect(() => undefined);
1064
- }
1065
- })
1066
- .catch(() => undefined);
1067
1249
  return undefined;
1068
1250
  }
1069
1251
  const peripheral = raced !== null && raced !== void 0 ? raced : discoveredDevices.get(deviceId);
@@ -1080,6 +1262,11 @@ function tryDirectConnectById(deviceId) {
1080
1262
  });
1081
1263
  return undefined;
1082
1264
  }
1265
+ finally {
1266
+ clearTimeout(timer);
1267
+ if (cancel)
1268
+ pendingCancellations.delete(cancel);
1269
+ }
1083
1270
  });
1084
1271
  }
1085
1272
  function connectDevice(deviceId, webContents) {
@@ -1093,7 +1280,10 @@ function connectDevice(deviceId, webContents) {
1093
1280
  totalDiscovered: discoveredDevices.size,
1094
1281
  totalConnected: connectedDevices.size,
1095
1282
  });
1096
- let peripheral = (_a = discoveredDevices.get(deviceId)) !== null && _a !== void 0 ? _a : connectedDevices.get(deviceId);
1283
+ let peripheral = (_a = connectedDevices.get(deviceId)) !== null && _a !== void 0 ? _a : discoveredDevices.get(deviceId);
1284
+ if ((peripheral === null || peripheral === void 0 ? void 0 : peripheral.state) !== 'connected') {
1285
+ peripheral = undefined;
1286
+ }
1097
1287
  if (!peripheral) {
1098
1288
  if (!noble) {
1099
1289
  yield initializeNoble();
@@ -1132,6 +1322,7 @@ function connectDevice(deviceId, webContents) {
1132
1322
  peripheral = byIdFirst ? yield scanForPeripheral() : yield connectById();
1133
1323
  }
1134
1324
  }
1325
+ assertBleActive();
1135
1326
  if (!peripheral) {
1136
1327
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found`);
1137
1328
  }
@@ -1142,6 +1333,7 @@ function connectDevice(deviceId, webContents) {
1142
1333
  if (!connectedDevices.has(deviceId)) {
1143
1334
  connectedDevices.set(deviceId, peripheral);
1144
1335
  }
1336
+ assertBleActive();
1145
1337
  setupDisconnectListener(peripheral, deviceId, webContents);
1146
1338
  if (deviceCharacteristics.has(deviceId)) {
1147
1339
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device characteristics already available');
@@ -1166,6 +1358,7 @@ function connectDevice(deviceId, webContents) {
1166
1358
  }
1167
1359
  try {
1168
1360
  const characteristics = yield setupConnectionAndDiscoverServices(peripheral, deviceId, webContents);
1361
+ assertBleActive();
1169
1362
  deviceCharacteristics.set(deviceId, characteristics);
1170
1363
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
1171
1364
  }
@@ -1176,54 +1369,33 @@ function connectDevice(deviceId, webContents) {
1176
1369
  }
1177
1370
  return;
1178
1371
  }
1179
- return new Promise((resolve, reject) => {
1180
- let connectionTimedOut = false;
1181
- const timeout = setTimeout(() => {
1182
- connectionTimedOut = true;
1183
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
1184
- }, NOBLE_BLE_CONNECTION_TIMEOUT_MS);
1185
- const connectedPeripheral = peripheral;
1186
- connectedPeripheral.connect((error) => index.__awaiter(this, void 0, void 0, function* () {
1187
- clearTimeout(timeout);
1188
- if (connectionTimedOut) {
1189
- if (!error) {
1190
- try {
1191
- connectedPeripheral.disconnect(() => undefined);
1192
- }
1193
- catch (_a) {
1194
- }
1195
- }
1196
- return;
1197
- }
1198
- if (error) {
1199
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
1200
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
1201
- return;
1202
- }
1203
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connected to device:', deviceId);
1204
- connectedDevices.set(deviceId, connectedPeripheral);
1205
- try {
1206
- const characteristics = yield setupConnectionAndDiscoverServices(connectedPeripheral, deviceId, webContents);
1207
- deviceCharacteristics.set(deviceId, characteristics);
1208
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
1209
- resolve();
1210
- }
1211
- catch (setupError) {
1212
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection setup failed:', setupError);
1213
- disconnectDevice(deviceId)
1214
- .catch(() => undefined)
1215
- .then(() => reject(setupError));
1216
- }
1217
- }));
1218
- });
1372
+ const connectedPeripheral = peripheral;
1373
+ yield connectPeripheralWithCancellation(connectedPeripheral, deviceId);
1374
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connected to device:', deviceId);
1375
+ try {
1376
+ const characteristics = yield setupConnectionAndDiscoverServices(connectedPeripheral, deviceId, webContents);
1377
+ assertBleActive();
1378
+ deviceCharacteristics.set(deviceId, characteristics);
1379
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
1380
+ }
1381
+ catch (setupError) {
1382
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection setup failed:', setupError);
1383
+ yield disconnectDevice(deviceId).catch(() => undefined);
1384
+ throw setupError;
1385
+ }
1219
1386
  });
1220
1387
  }
1221
1388
  function disconnectDevice(deviceId) {
1389
+ var _a;
1222
1390
  return index.__awaiter(this, void 0, void 0, function* () {
1223
- const peripheral = connectedDevices.get(deviceId);
1391
+ if (nativeReleased)
1392
+ return;
1393
+ const pendingConnection = connectingDevices.get(deviceId);
1394
+ const peripheral = (_a = connectedDevices.get(deviceId)) !== null && _a !== void 0 ? _a : pendingConnection === null || pendingConnection === void 0 ? void 0 : pendingConnection.peripheral;
1224
1395
  if (!peripheral) {
1225
1396
  return;
1226
1397
  }
1398
+ pendingConnection === null || pendingConnection === void 0 ? void 0 : pendingConnection.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, `Device ${deviceId} connection cancelled`));
1227
1399
  const disconnectEntry = deviceDisconnectListeners.get(deviceId);
1228
1400
  if (disconnectEntry) {
1229
1401
  disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
@@ -1247,6 +1419,8 @@ function disconnectDevice(deviceId) {
1247
1419
  }
1248
1420
  function unsubscribeNotifications(deviceId) {
1249
1421
  return index.__awaiter(this, void 0, void 0, function* () {
1422
+ if (nativeReleased)
1423
+ return;
1250
1424
  const peripheral = connectedDevices.get(deviceId);
1251
1425
  const characteristics = deviceCharacteristics.get(deviceId);
1252
1426
  if (!peripheral || !characteristics) {
@@ -1266,7 +1440,8 @@ function unsubscribeNotifications(deviceId) {
1266
1440
  subscribedDevices.delete(deviceId);
1267
1441
  }
1268
1442
  finally {
1269
- subscriptionOperations.set(deviceId, 'idle');
1443
+ if (!disposing)
1444
+ subscriptionOperations.set(deviceId, 'idle');
1270
1445
  }
1271
1446
  });
1272
1447
  }
@@ -1316,10 +1491,12 @@ function subscribeNotifications(deviceId, callback) {
1316
1491
  timeoutMs: BLE_CLEANUP_TIMEOUT,
1317
1492
  timeoutBehavior: 'resolve',
1318
1493
  });
1494
+ assertBleActive();
1319
1495
  yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
1320
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1496
+ timeoutMs: NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
1321
1497
  timeoutBehavior: 'reject',
1322
1498
  });
1499
+ assertBleActive();
1323
1500
  notifyCharacteristic.on('data', (data) => {
1324
1501
  if (!pairedDevices.has(deviceId)) {
1325
1502
  pairedDevices.add(deviceId);
@@ -1332,29 +1509,53 @@ function subscribeNotifications(deviceId, callback) {
1332
1509
  const subscribeStartedAt = Date.now();
1333
1510
  try {
1334
1511
  yield rebuildAppSubscription(deviceId, notifyCharacteristic);
1512
+ assertBleActive();
1335
1513
  subscribedDevices.set(deviceId, true);
1336
1514
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification subscription active', {
1337
1515
  deviceId,
1338
1516
  ms: Date.now() - subscribeStartedAt,
1339
1517
  });
1340
1518
  }
1519
+ catch (error) {
1520
+ throw createNobleBleConnectionError(error, 'Notification subscription failed: ');
1521
+ }
1341
1522
  finally {
1342
- subscriptionOperations.set(deviceId, 'idle');
1523
+ if (!disposing)
1524
+ subscriptionOperations.set(deviceId, 'idle');
1343
1525
  }
1344
1526
  });
1345
1527
  }
1346
1528
  function setupNobleBleHandlers(webContents) {
1529
+ if (disposing)
1530
+ return;
1531
+ let windowDestroyed = false;
1347
1532
  try {
1348
1533
  logger = require('electron-log');
1349
1534
  const { ipcMain } = require('electron');
1535
+ const channels = new Set();
1536
+ removeIpcHandlers = () => channels.forEach(channel => ipcMain.removeHandler(channel));
1350
1537
  const handle = (channel, listener) => {
1538
+ channels.add(channel);
1351
1539
  ipcMain.removeHandler(channel);
1352
- ipcMain.handle(channel, listener);
1540
+ ipcMain.handle(channel, (...args) => index.__awaiter(this, void 0, void 0, function* () {
1541
+ try {
1542
+ if (windowCleanup)
1543
+ yield windowCleanup;
1544
+ assertBleActive();
1545
+ if (windowDestroyed) {
1546
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'BLE window destroyed');
1547
+ }
1548
+ return yield Promise.resolve(listener(...args));
1549
+ }
1550
+ catch (error) {
1551
+ return createNobleBleIpcErrorResponse(error);
1552
+ }
1553
+ }));
1353
1554
  };
1354
1555
  safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
1355
1556
  handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, () => index.__awaiter(this, void 0, void 0, function* () {
1356
1557
  try {
1357
- const devices = yield enumerateDevices();
1558
+ const devices = yield enumerateDevices(() => windowDestroyed);
1358
1559
  safeLog(logger, 'debug', 'Enumeration completed', {
1359
1560
  count: devices.length,
1360
1561
  devices: devices.map(device => ({ id: device.id, name: device.name })),
@@ -1446,13 +1647,29 @@ function setupNobleBleHandlers(webContents) {
1446
1647
  }
1447
1648
  }));
1448
1649
  webContents.on('destroyed', () => {
1650
+ windowDestroyed = true;
1651
+ if (disposing)
1652
+ return;
1449
1653
  safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
1450
- (() => index.__awaiter(this, void 0, void 0, function* () {
1654
+ const previousCleanup = windowCleanup;
1655
+ const enumerations = Array.from(pendingEnumerations, cancel => cancel());
1656
+ windowCleanup = (() => index.__awaiter(this, void 0, void 0, function* () {
1657
+ if (previousCleanup)
1658
+ yield previousCleanup;
1659
+ yield Promise.allSettled(enumerations);
1660
+ if (disposing)
1661
+ return;
1451
1662
  const deviceIds = Array.from(connectedDevices.keys());
1452
1663
  for (const deviceId of deviceIds) {
1664
+ if (disposing)
1665
+ return;
1453
1666
  yield unsubscribeNotifications(deviceId).catch(() => undefined);
1667
+ if (disposing)
1668
+ return;
1454
1669
  yield disconnectDevice(deviceId).catch(() => undefined);
1455
1670
  }
1671
+ if (disposing)
1672
+ return;
1456
1673
  yield stopScanning().catch(() => undefined);
1457
1674
  cleanupNobleListeners();
1458
1675
  discoveredDevices.clear();
@@ -1468,6 +1685,77 @@ function setupNobleBleHandlers(webContents) {
1468
1685
  throw error;
1469
1686
  }
1470
1687
  }
1688
+ function disposeNobleBleSupport(releaseNoble = instance => instance.stop()) {
1689
+ if (disposePromise)
1690
+ return disposePromise;
1691
+ disposing = true;
1692
+ removeIpcHandlers === null || removeIpcHandlers === void 0 ? void 0 : removeIpcHandlers();
1693
+ const instance = noble;
1694
+ const deviceIds = new Set([...connectedDevices.keys(), ...connectingDevices.keys()]);
1695
+ const pendingPeripherals = Array.from(connectingDevices.values(), pending => pending.peripheral);
1696
+ for (const cancel of pendingCancellations)
1697
+ cancel();
1698
+ for (const id of idleDisconnectTimers.keys())
1699
+ clearIdleDisconnect(id);
1700
+ for (const pending of connectingDevices.values()) {
1701
+ pending.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'Noble BLE is shutting down'));
1702
+ }
1703
+ const connections = Array.from(nativeConnections);
1704
+ for (const connection of connections) {
1705
+ try {
1706
+ connection.cancel();
1707
+ }
1708
+ catch (error) {
1709
+ logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Native connect cancellation failed', error);
1710
+ }
1711
+ }
1712
+ let timeout;
1713
+ disposePromise = (() => index.__awaiter(this, void 0, void 0, function* () {
1714
+ try {
1715
+ yield Promise.race([
1716
+ Promise.allSettled([
1717
+ windowCleanup,
1718
+ ...connections.map(connection => connection.settled),
1719
+ stopScanning(),
1720
+ ...pendingPeripherals.map(peripheral => runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
1721
+ timeoutMs: BLE_DISCONNECT_CONFIRM_TIMEOUT_MS,
1722
+ timeoutBehavior: 'resolve',
1723
+ })),
1724
+ ...Array.from(deviceIds, (id) => index.__awaiter(this, void 0, void 0, function* () {
1725
+ yield unsubscribeNotifications(id).catch(() => undefined);
1726
+ yield disconnectDevice(id).catch(() => undefined);
1727
+ })),
1728
+ ]),
1729
+ new Promise(resolve => {
1730
+ timeout = setTimeout(() => {
1731
+ logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Process dispose timed out; releasing native manager');
1732
+ resolve();
1733
+ }, 3500);
1734
+ }),
1735
+ ]);
1736
+ }
1737
+ finally {
1738
+ clearTimeout(timeout);
1739
+ nativeReleased = true;
1740
+ cleanupNobleListeners();
1741
+ if (instance && persistentStateListener) {
1742
+ instance.removeListener('stateChange', persistentStateListener);
1743
+ persistentStateListener = null;
1744
+ }
1745
+ for (const id of deviceIds)
1746
+ cleanupDevice(id);
1747
+ discoveredDevices.clear();
1748
+ directConnectCooldownUntil.clear();
1749
+ if (instance)
1750
+ releaseNoble(instance);
1751
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Process dispose completed');
1752
+ }
1753
+ }))();
1754
+ return disposePromise;
1755
+ }
1471
1756
 
1757
+ exports.createNobleBleConnectionError = createNobleBleConnectionError;
1758
+ exports.createNobleBleIpcErrorResponse = createNobleBleIpcErrorResponse;
1759
+ exports.disposeNobleBleSupport = disposeNobleBleSupport;
1472
1760
  exports.resolveNobleBleWritePacingDelay = resolveNobleBleWritePacingDelay;
1473
1761
  exports.setupNobleBleHandlers = setupNobleBleHandlers;