@ukeyfe/hardware-transport-electron 1.1.13

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.
@@ -0,0 +1,1214 @@
1
+ 'use strict';
2
+
3
+ var index = require('./index-60176982.js');
4
+ var hardwareShared = require('@ukeyfe/hardware-shared');
5
+ var hardwareTransport = require('@ukeyfe/hardware-transport');
6
+ var pRetry = require('p-retry');
7
+
8
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
+
10
+ var pRetry__default = /*#__PURE__*/_interopDefaultLegacy(pRetry);
11
+
12
+ function safeLog(logger, level, message, ...args) {
13
+ if (logger) {
14
+ logger[level](message, ...args);
15
+ }
16
+ else {
17
+ console[level](`[NobleBLE] ${message}`, ...args);
18
+ }
19
+ }
20
+
21
+ function softRefreshSubscription(params) {
22
+ return index.__awaiter(this, void 0, void 0, function* () {
23
+ const { deviceId, notifyCharacteristic, subscriptionOperations, subscribedDevices, pairedDevices, notificationCallbacks, processNotificationData, logger, } = params;
24
+ if (!notifyCharacteristic) {
25
+ throw new Error(`Notify characteristic not available for device ${deviceId}`);
26
+ }
27
+ logger === null || logger === void 0 ? void 0 : logger.info('[BLE-OPS] Starting subscription refresh', { deviceId });
28
+ subscriptionOperations.set(deviceId, 'subscribing');
29
+ yield new Promise(resolve => {
30
+ notifyCharacteristic.unsubscribe(() => resolve());
31
+ });
32
+ yield new Promise((resolve, reject) => {
33
+ notifyCharacteristic.subscribe((error) => {
34
+ if (error) {
35
+ reject(error);
36
+ return;
37
+ }
38
+ resolve();
39
+ });
40
+ });
41
+ notifyCharacteristic.removeAllListeners('data');
42
+ notifyCharacteristic.on('data', (data) => {
43
+ if (!pairedDevices.has(deviceId)) {
44
+ pairedDevices.add(deviceId);
45
+ logger === null || logger === void 0 ? void 0 : logger.info('[BLE-OPS] Device paired successfully', { deviceId });
46
+ }
47
+ const result = processNotificationData(deviceId, data);
48
+ if (result.error) {
49
+ logger === null || logger === void 0 ? void 0 : logger.error('[BLE-OPS] Packet processing error:', result.error);
50
+ return;
51
+ }
52
+ if (result.isComplete && result.completePacket) {
53
+ const appCb = notificationCallbacks.get(deviceId);
54
+ if (appCb)
55
+ appCb(result.completePacket);
56
+ }
57
+ });
58
+ subscribedDevices.set(deviceId, true);
59
+ subscriptionOperations.set(deviceId, 'idle');
60
+ logger === null || logger === void 0 ? void 0 : logger.info('[BLE-OPS] Subscription refresh completed', { deviceId });
61
+ });
62
+ }
63
+
64
+ let noble = null;
65
+ let logger = null;
66
+ const bluetoothState = {
67
+ available: false,
68
+ unsupported: false,
69
+ initialized: false,
70
+ };
71
+ let persistentStateListener = null;
72
+ const discoveredDevices = new Map();
73
+ const connectedDevices = new Map();
74
+ const pairedDevices = new Set();
75
+ const deviceCharacteristics = new Map();
76
+ const notificationCallbacks = new Map();
77
+ const subscribedDevices = new Map();
78
+ const subscriptionOperations = new Map();
79
+ const devicePacketStates = new Map();
80
+ const UKEY_SERVICE_UUIDS = [hardwareShared.UKEY_SERVICE_UUID];
81
+ const NORMALIZED_WRITE_UUID = '0002';
82
+ const NORMALIZED_NOTIFY_UUID = '0003';
83
+ const BLUETOOTH_INIT_TIMEOUT = 10000;
84
+ const DEVICE_SCAN_TIMEOUT = 5000;
85
+ const FAST_SCAN_TIMEOUT = 1500;
86
+ const DEVICE_CHECK_INTERVAL = 500;
87
+ const CONNECTION_TIMEOUT = 3000;
88
+ const BLE_PACKET_SIZE = 192;
89
+ const UNIFIED_WRITE_DELAY = 5;
90
+ const RETRY_CONFIG = { MAX_ATTEMPTS: 15, WRITE_TIMEOUT: 2000 };
91
+ const IS_WINDOWS = process.platform === 'win32';
92
+ const ABORTABLE_WRITE_ERROR_PATTERNS = [
93
+ /status:\s*3/i,
94
+ ];
95
+ const MIN_HEADER_LENGTH = 9;
96
+ function processNotificationData(deviceId, data) {
97
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification', {
98
+ deviceId,
99
+ dataLength: data.length,
100
+ });
101
+ let packetState = devicePacketStates.get(deviceId);
102
+ if (!packetState) {
103
+ packetState = { bufferLength: 0, buffer: [], packetCount: 0 };
104
+ devicePacketStates.set(deviceId, packetState);
105
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Initialized new packet state for device:', deviceId);
106
+ }
107
+ try {
108
+ if (hardwareShared.isHeaderChunk(data)) {
109
+ if (data.length < MIN_HEADER_LENGTH) {
110
+ return { isComplete: false, error: 'Invalid header chunk: too short' };
111
+ }
112
+ const messageId = `${deviceId}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
113
+ packetState.bufferLength = data.readInt32BE(5);
114
+ packetState.buffer = [...data.subarray(3)];
115
+ packetState.packetCount = 1;
116
+ packetState.messageId = messageId;
117
+ if (packetState.bufferLength < 0) {
118
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Invalid negative packet length detected:', {
119
+ length: packetState.bufferLength,
120
+ dataLength: data.length,
121
+ rawHeader: data.subarray(0, Math.min(16, data.length)).toString('hex'),
122
+ lengthBytes: data.subarray(5, 9).toString('hex'),
123
+ });
124
+ resetPacketState(packetState);
125
+ return { isComplete: false, error: 'Invalid packet length in header' };
126
+ }
127
+ }
128
+ else {
129
+ if (packetState.bufferLength === 0) {
130
+ return { isComplete: false, error: 'Received data chunk without header' };
131
+ }
132
+ packetState.packetCount += 1;
133
+ packetState.buffer = packetState.buffer.concat([...data]);
134
+ }
135
+ if (packetState.buffer.length - hardwareTransport.COMMON_HEADER_SIZE >= packetState.bufferLength) {
136
+ const completeBuffer = Buffer.from(packetState.buffer);
137
+ const hexString = completeBuffer.toString('hex');
138
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Packet assembled', {
139
+ deviceId,
140
+ totalPackets: packetState.packetCount,
141
+ expectedLength: packetState.bufferLength,
142
+ actualLength: packetState.buffer.length - hardwareTransport.COMMON_HEADER_SIZE,
143
+ });
144
+ resetPacketState(packetState);
145
+ return { isComplete: true, completePacket: hexString };
146
+ }
147
+ return { isComplete: false };
148
+ }
149
+ catch (error) {
150
+ resetPacketState(packetState);
151
+ return { isComplete: false, error: `Packet processing error: ${error}` };
152
+ }
153
+ }
154
+ function resetPacketState(packetState) {
155
+ packetState.bufferLength = 0;
156
+ packetState.buffer = [];
157
+ packetState.packetCount = 0;
158
+ packetState.messageId = undefined;
159
+ }
160
+ function checkBluetoothAvailability() {
161
+ return index.__awaiter(this, void 0, void 0, function* () {
162
+ if (!bluetoothState.initialized) {
163
+ yield initializeNoble();
164
+ }
165
+ const currentState = (noble === null || noble === void 0 ? void 0 : noble.state) || 'unknown';
166
+ return {
167
+ available: bluetoothState.available,
168
+ state: currentState,
169
+ unsupported: bluetoothState.unsupported,
170
+ initialized: bluetoothState.initialized,
171
+ };
172
+ });
173
+ }
174
+ function setupPersistentStateListener() {
175
+ if (!noble || persistentStateListener)
176
+ return;
177
+ persistentStateListener = (state) => {
178
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Persistent state change:', state);
179
+ updateBluetoothState(state);
180
+ if (state === 'poweredOff') {
181
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Bluetooth powered off - clearing device caches and resetting state');
182
+ const connectedIds = Array.from(connectedDevices.keys());
183
+ for (const deviceId of connectedIds) {
184
+ try {
185
+ cleanupDevice(deviceId, undefined, {
186
+ cleanupConnection: true,
187
+ sendDisconnectEvent: true,
188
+ cancelOperations: true,
189
+ reason: 'bluetooth-poweredOff',
190
+ });
191
+ }
192
+ catch (e) {
193
+ safeLog(logger, 'error', 'Failed to cleanup device during poweredOff', {
194
+ deviceId,
195
+ error: e instanceof Error ? e.message : String(e),
196
+ });
197
+ }
198
+ }
199
+ discoveredDevices.clear();
200
+ deviceCharacteristics.clear();
201
+ subscribedDevices.clear();
202
+ notificationCallbacks.clear();
203
+ devicePacketStates.clear();
204
+ subscriptionOperations.clear();
205
+ pairedDevices.clear();
206
+ if (noble) {
207
+ try {
208
+ noble.stopScanning();
209
+ }
210
+ catch (e) {
211
+ safeLog(logger, 'error', 'Failed to stop scanning on poweredOff', e instanceof Error ? e.message : String(e));
212
+ }
213
+ }
214
+ }
215
+ };
216
+ noble.on('stateChange', persistentStateListener);
217
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Persistent state listener setup');
218
+ const currentState = noble.state;
219
+ if (currentState) {
220
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Initial state detected:', currentState);
221
+ updateBluetoothState(currentState);
222
+ }
223
+ }
224
+ function updateBluetoothState(state) {
225
+ if (state === 'poweredOn') {
226
+ bluetoothState.available = true;
227
+ bluetoothState.unsupported = false;
228
+ bluetoothState.initialized = true;
229
+ }
230
+ else if (state === 'unsupported') {
231
+ bluetoothState.available = false;
232
+ bluetoothState.unsupported = true;
233
+ bluetoothState.initialized = true;
234
+ }
235
+ else if (state === 'poweredOff') {
236
+ bluetoothState.available = false;
237
+ bluetoothState.unsupported = false;
238
+ bluetoothState.initialized = true;
239
+ }
240
+ else if (state === 'unauthorized') {
241
+ bluetoothState.available = false;
242
+ bluetoothState.unsupported = false;
243
+ bluetoothState.initialized = true;
244
+ }
245
+ }
246
+ function initializeNoble() {
247
+ return index.__awaiter(this, void 0, void 0, function* () {
248
+ if (noble)
249
+ return;
250
+ try {
251
+ noble = require('@stoprocent/noble');
252
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Noble library loaded');
253
+ yield new Promise((resolve, reject) => {
254
+ if (!noble) {
255
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.RuntimeError, 'Noble not initialized'));
256
+ return;
257
+ }
258
+ if (noble.state === 'poweredOn') {
259
+ resolve();
260
+ return;
261
+ }
262
+ setupPersistentStateListener();
263
+ const timeout = setTimeout(() => {
264
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.RuntimeError, 'Bluetooth initialization timeout'));
265
+ }, BLUETOOTH_INIT_TIMEOUT);
266
+ const cleanup = () => {
267
+ clearTimeout(timeout);
268
+ if (noble) {
269
+ noble.removeListener('stateChange', onStateChange);
270
+ }
271
+ };
272
+ const onStateChange = (state) => {
273
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Bluetooth state:', state);
274
+ if (state === 'poweredOn') {
275
+ cleanup();
276
+ resolve();
277
+ }
278
+ else if (state === 'unsupported') {
279
+ cleanup();
280
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleUnsupported));
281
+ }
282
+ else if (state === 'poweredOff') {
283
+ cleanup();
284
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BlePoweredOff));
285
+ }
286
+ else if (state === 'unauthorized') {
287
+ cleanup();
288
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BlePermissionError));
289
+ }
290
+ };
291
+ noble.on('stateChange', onStateChange);
292
+ });
293
+ noble.on('discover', (peripheral) => {
294
+ handleDeviceDiscovered(peripheral);
295
+ });
296
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Noble initialized successfully');
297
+ }
298
+ catch (error) {
299
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to initialize Noble:', error);
300
+ bluetoothState.unsupported = true;
301
+ bluetoothState.initialized = true;
302
+ throw error;
303
+ }
304
+ });
305
+ }
306
+ function cleanupDevice(deviceId, webContents, options = {}) {
307
+ var _a;
308
+ const { cleanupConnection = true, sendDisconnectEvent = false, cancelOperations = true, reason = 'unknown', } = options;
309
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting device cleanup', {
310
+ deviceId,
311
+ reason,
312
+ cleanupConnection,
313
+ sendDisconnectEvent,
314
+ cancelOperations,
315
+ });
316
+ const peripheral = connectedDevices.get(deviceId);
317
+ const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
318
+ if (cleanupConnection) {
319
+ connectedDevices.delete(deviceId);
320
+ deviceCharacteristics.delete(deviceId);
321
+ notificationCallbacks.delete(deviceId);
322
+ devicePacketStates.delete(deviceId);
323
+ subscribedDevices.delete(deviceId);
324
+ subscriptionOperations.delete(deviceId);
325
+ pairedDevices.delete(deviceId);
326
+ }
327
+ if (sendDisconnectEvent && webContents) {
328
+ webContents.send(hardwareShared.EUKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
329
+ id: deviceId,
330
+ name: deviceName,
331
+ });
332
+ }
333
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device cleanup completed', { deviceId, reason });
334
+ }
335
+ function handleDeviceDisconnect(deviceId, webContents) {
336
+ var _a;
337
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] ⚠️ DEVICE DISCONNECT DETECTED:', {
338
+ deviceId,
339
+ hasPeripheral: connectedDevices.has(deviceId),
340
+ hasCharacteristics: deviceCharacteristics.has(deviceId),
341
+ stackTrace: (_a = new Error().stack) === null || _a === void 0 ? void 0 : _a.split('\n').slice(1, 5),
342
+ });
343
+ cleanupDevice(deviceId, webContents, {
344
+ cleanupConnection: true,
345
+ sendDisconnectEvent: true,
346
+ cancelOperations: true,
347
+ reason: 'auto-disconnect',
348
+ });
349
+ }
350
+ function setupDisconnectListener(peripheral, deviceId, webContents) {
351
+ peripheral.removeAllListeners('disconnect');
352
+ peripheral.on('disconnect', () => {
353
+ handleDeviceDisconnect(deviceId, webContents);
354
+ });
355
+ }
356
+ function writeCharacteristicWithAck(deviceId, writeCharacteristic, buffer) {
357
+ return index.__awaiter(this, void 0, void 0, function* () {
358
+ return new Promise((resolve, reject) => {
359
+ writeCharacteristic.write(buffer, true, (error) => {
360
+ if (error) {
361
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Write failed', { deviceId, error: String(error) });
362
+ reject(error);
363
+ return;
364
+ }
365
+ resolve();
366
+ });
367
+ });
368
+ });
369
+ }
370
+ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payload, contextLabel) {
371
+ var _a;
372
+ return index.__awaiter(this, void 0, void 0, function* () {
373
+ const timeoutMs = RETRY_CONFIG.WRITE_TIMEOUT;
374
+ for (let attempt = 1; attempt <= RETRY_CONFIG.MAX_ATTEMPTS; attempt++) {
375
+ if (!connectedDevices.has(deviceId)) {
376
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleConnectedError, `Device ${deviceId} disconnected during retry`);
377
+ }
378
+ logger === null || logger === void 0 ? void 0 : logger.debug('[BLE-Write] Windows write attempt', {
379
+ deviceId,
380
+ attempt,
381
+ context: contextLabel,
382
+ });
383
+ const latestWrite = doGetWriteCharacteristic();
384
+ if (!latestWrite) {
385
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.RuntimeError, `Write characteristic not available for ${deviceId}`);
386
+ }
387
+ try {
388
+ yield writeCharacteristicWithAck(deviceId, latestWrite, payload);
389
+ }
390
+ catch (e) {
391
+ const errorMessage = e instanceof Error ? e.message : String(e);
392
+ logger === null || logger === void 0 ? void 0 : logger.error('[BLE-Write] Windows write error', {
393
+ deviceId,
394
+ attempt,
395
+ context: contextLabel,
396
+ error: errorMessage,
397
+ });
398
+ if (ABORTABLE_WRITE_ERROR_PATTERNS.some(p => p.test(errorMessage))) {
399
+ yield unsubscribeNotifications(deviceId).catch(() => { });
400
+ yield disconnectDevice(deviceId).catch(() => { });
401
+ discoveredDevices.delete(deviceId);
402
+ subscriptionOperations.set(deviceId, 'idle');
403
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Deep cleanup to reset device state to initial', { deviceId });
404
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleConnectedError, `Write failed with abortable error for device ${deviceId}: ${errorMessage}`);
405
+ }
406
+ }
407
+ if (pairedDevices.has(deviceId)) {
408
+ logger === null || logger === void 0 ? void 0 : logger.info('[BLE-Write] Windows write success (paired, exiting loop)', {
409
+ deviceId,
410
+ attempt,
411
+ context: contextLabel,
412
+ });
413
+ return;
414
+ }
415
+ if (attempt < RETRY_CONFIG.MAX_ATTEMPTS) {
416
+ yield hardwareShared.wait(timeoutMs);
417
+ }
418
+ if (pairedDevices.has(deviceId)) {
419
+ logger === null || logger === void 0 ? void 0 : logger.info('[BLE-Write] Notification observed during wait (paired), exiting loop', {
420
+ deviceId,
421
+ attempt,
422
+ context: contextLabel,
423
+ });
424
+ return;
425
+ }
426
+ try {
427
+ const notifyCharacteristic = (_a = deviceCharacteristics.get(deviceId)) === null || _a === void 0 ? void 0 : _a.notify;
428
+ yield softRefreshSubscription({
429
+ deviceId,
430
+ notifyCharacteristic,
431
+ subscriptionOperations,
432
+ subscribedDevices,
433
+ pairedDevices,
434
+ notificationCallbacks,
435
+ processNotificationData,
436
+ logger,
437
+ });
438
+ logger === null || logger === void 0 ? void 0 : logger.info('[BLE-Write] Subscription refresh completed', { deviceId });
439
+ }
440
+ catch (refreshError) {
441
+ const errMsg = refreshError instanceof Error ? refreshError.message : String(refreshError);
442
+ logger === null || logger === void 0 ? void 0 : logger.error('[BLE-Write] Subscription refresh failed', { deviceId, error: errMsg });
443
+ }
444
+ }
445
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.DeviceNotFound, `No response observed after ${RETRY_CONFIG.MAX_ATTEMPTS} writes: ${deviceId}`);
446
+ });
447
+ }
448
+ function transmitHexDataToDevice(deviceId, hexData) {
449
+ return index.__awaiter(this, void 0, void 0, function* () {
450
+ const characteristics = deviceCharacteristics.get(deviceId);
451
+ const peripheral = connectedDevices.get(deviceId);
452
+ if (!peripheral || !characteristics) {
453
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleCharacteristicNotFound, `Device ${deviceId} not connected or characteristics not available`);
454
+ }
455
+ const toBuffer = Buffer.from(hexData, 'hex');
456
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Writing data:', {
457
+ deviceId,
458
+ dataLength: toBuffer.length,
459
+ firstBytes: toBuffer.subarray(0, 8).toString('hex'),
460
+ });
461
+ const doGetWriteCharacteristic = () => { var _a; return (_a = deviceCharacteristics.get(deviceId)) === null || _a === void 0 ? void 0 : _a.write; };
462
+ if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
463
+ const writeCharacteristic = doGetWriteCharacteristic();
464
+ if (!writeCharacteristic) {
465
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleCharacteristicNotFound, `Write characteristic not available for ${deviceId}`);
466
+ }
467
+ if (toBuffer.length <= BLE_PACKET_SIZE) {
468
+ yield hardwareShared.wait(UNIFIED_WRITE_DELAY);
469
+ yield writeCharacteristicWithAck(deviceId, writeCharacteristic, toBuffer);
470
+ return;
471
+ }
472
+ for (let offset = 0, idx = 0; offset < toBuffer.length; idx++) {
473
+ const chunkSize = Math.min(BLE_PACKET_SIZE, toBuffer.length - offset);
474
+ const chunk = toBuffer.subarray(offset, offset + chunkSize);
475
+ offset += chunkSize;
476
+ const latest = doGetWriteCharacteristic();
477
+ if (!latest) {
478
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleCharacteristicNotFound, `Write characteristic not available for ${deviceId}`);
479
+ }
480
+ yield writeCharacteristicWithAck(deviceId, latest, chunk);
481
+ if (offset < toBuffer.length) {
482
+ yield hardwareShared.wait(UNIFIED_WRITE_DELAY);
483
+ }
484
+ }
485
+ return;
486
+ }
487
+ if (toBuffer.length <= BLE_PACKET_SIZE) {
488
+ yield hardwareShared.wait(UNIFIED_WRITE_DELAY);
489
+ yield attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, toBuffer, 'single');
490
+ return;
491
+ }
492
+ for (let offset = 0, idx = 0; offset < toBuffer.length; idx++) {
493
+ const chunkSize = Math.min(BLE_PACKET_SIZE, toBuffer.length - offset);
494
+ const chunk = toBuffer.subarray(offset, offset + chunkSize);
495
+ offset += chunkSize;
496
+ yield attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, chunk, `chunk-${idx + 1}`);
497
+ if (offset < toBuffer.length) {
498
+ yield hardwareShared.wait(UNIFIED_WRITE_DELAY);
499
+ }
500
+ }
501
+ });
502
+ }
503
+ function handleDeviceDiscovered(peripheral) {
504
+ var _a;
505
+ const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
506
+ if (!hardwareShared.isUkeyDevice(deviceName)) {
507
+ return;
508
+ }
509
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discovered UKey device:', deviceName);
510
+ discoveredDevices.set(peripheral.id, peripheral);
511
+ }
512
+ function ensureDiscoverListener() {
513
+ if (!noble)
514
+ return;
515
+ const listenerCount = noble.listenerCount('discover');
516
+ if (listenerCount === 0) {
517
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discover listener missing, re-adding it');
518
+ noble.on('discover', (peripheral) => {
519
+ handleDeviceDiscovered(peripheral);
520
+ });
521
+ }
522
+ else {
523
+ logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] Discover listener already exists, count:', listenerCount);
524
+ }
525
+ }
526
+ function performTargetedScan(targetDeviceId) {
527
+ return index.__awaiter(this, void 0, void 0, function* () {
528
+ if (!noble) {
529
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.RuntimeError, 'Noble not available');
530
+ }
531
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting targeted scan for device:', targetDeviceId);
532
+ ensureDiscoverListener();
533
+ return new Promise((resolve, reject) => {
534
+ const timeout = setTimeout(() => {
535
+ if (noble) {
536
+ noble.stopScanning();
537
+ }
538
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
539
+ resolve(null);
540
+ }, FAST_SCAN_TIMEOUT);
541
+ const onDiscover = (peripheral) => {
542
+ var _a;
543
+ if (peripheral.id === targetDeviceId) {
544
+ clearTimeout(timeout);
545
+ if (noble) {
546
+ noble.stopScanning();
547
+ noble.removeListener('discover', onDiscover);
548
+ }
549
+ discoveredDevices.set(peripheral.id, peripheral);
550
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] UKey device found during targeted scan:', {
551
+ id: peripheral.id,
552
+ name: ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown',
553
+ });
554
+ resolve(peripheral);
555
+ }
556
+ };
557
+ if (noble) {
558
+ noble.removeListener('discover', onDiscover);
559
+ noble.on('discover', onDiscover);
560
+ }
561
+ if (noble) {
562
+ noble.startScanning(UKEY_SERVICE_UUIDS, false, (error) => {
563
+ if (error) {
564
+ clearTimeout(timeout);
565
+ if (noble) {
566
+ noble.removeListener('discover', onDiscover);
567
+ }
568
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start targeted scan:', error);
569
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleScanError, error.message));
570
+ return;
571
+ }
572
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan started for device:', targetDeviceId);
573
+ });
574
+ }
575
+ });
576
+ });
577
+ }
578
+ function enumerateDevices() {
579
+ return index.__awaiter(this, void 0, void 0, function* () {
580
+ if (!noble) {
581
+ yield initializeNoble();
582
+ }
583
+ if (!noble) {
584
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.RuntimeError, 'Noble not available');
585
+ }
586
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting device enumeration');
587
+ discoveredDevices.clear();
588
+ ensureDiscoverListener();
589
+ return new Promise((resolve, reject) => {
590
+ const devices = [];
591
+ if (!noble) {
592
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.RuntimeError, 'Noble not available'));
593
+ return;
594
+ }
595
+ const timeout = setTimeout(() => {
596
+ if (noble) {
597
+ noble.stopScanning();
598
+ }
599
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
600
+ resolve(devices);
601
+ }, DEVICE_SCAN_TIMEOUT);
602
+ noble.startScanning(UKEY_SERVICE_UUIDS, false, (error) => {
603
+ if (error) {
604
+ clearTimeout(timeout);
605
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
606
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleScanError, error.message));
607
+ return;
608
+ }
609
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning started for UKey devices');
610
+ const checkDevices = () => {
611
+ discoveredDevices.forEach((peripheral, id) => {
612
+ var _a;
613
+ const existingDevice = devices.find(d => d.id === id);
614
+ if (!existingDevice) {
615
+ const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
616
+ devices.push({
617
+ id,
618
+ name: deviceName,
619
+ state: peripheral.state || 'disconnected',
620
+ });
621
+ }
622
+ });
623
+ };
624
+ const interval = setInterval(checkDevices, DEVICE_CHECK_INTERVAL);
625
+ setTimeout(() => {
626
+ clearInterval(interval);
627
+ }, DEVICE_SCAN_TIMEOUT);
628
+ });
629
+ });
630
+ });
631
+ }
632
+ function stopScanning() {
633
+ return index.__awaiter(this, void 0, void 0, function* () {
634
+ if (!noble)
635
+ return;
636
+ return new Promise(resolve => {
637
+ if (!noble) {
638
+ resolve();
639
+ return;
640
+ }
641
+ noble.stopScanning(() => {
642
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning stopped');
643
+ resolve();
644
+ });
645
+ });
646
+ });
647
+ }
648
+ function cleanupNobleListeners() {
649
+ if (!noble)
650
+ return;
651
+ try {
652
+ noble.removeAllListeners('discover');
653
+ noble.removeAllListeners('stateChange');
654
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] All Noble listeners cleaned up');
655
+ }
656
+ catch (error) {
657
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to clean up some listeners:', error);
658
+ }
659
+ }
660
+ function getDevice(deviceId) {
661
+ var _a, _b;
662
+ const peripheral = discoveredDevices.get(deviceId);
663
+ if (peripheral) {
664
+ const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
665
+ return {
666
+ id: peripheral.id,
667
+ name: deviceName,
668
+ state: peripheral.state || 'disconnected',
669
+ };
670
+ }
671
+ const connectedPeripheral = connectedDevices.get(deviceId);
672
+ if (connectedPeripheral) {
673
+ const deviceName = ((_b = connectedPeripheral.advertisement) === null || _b === void 0 ? void 0 : _b.localName) || 'Unknown Device';
674
+ return {
675
+ id: connectedPeripheral.id,
676
+ name: deviceName,
677
+ state: connectedPeripheral.state || 'connected',
678
+ };
679
+ }
680
+ return {
681
+ id: deviceId,
682
+ name: 'UKey Device',
683
+ state: 'disconnected',
684
+ };
685
+ }
686
+ function discoverServicesAndCharacteristics(peripheral) {
687
+ return index.__awaiter(this, void 0, void 0, function* () {
688
+ return new Promise((resolve, reject) => {
689
+ peripheral.discoverServices(UKEY_SERVICE_UUIDS, (error, services) => {
690
+ if (error) {
691
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed:', error);
692
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleServiceNotFound, error.message));
693
+ return;
694
+ }
695
+ if (!services || services.length === 0) {
696
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleServiceNotFound, 'No UKey services found'));
697
+ return;
698
+ }
699
+ const service = services[0];
700
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Found service:', service.uuid);
701
+ service.discoverCharacteristics([hardwareShared.UKEY_WRITE_CHARACTERISTIC_UUID, hardwareShared.UKEY_NOTIFY_CHARACTERISTIC_UUID], (error, characteristics) => {
702
+ if (error) {
703
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Characteristic discovery failed:', error);
704
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleCharacteristicNotFound, error.message));
705
+ return;
706
+ }
707
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discovered characteristics:', {
708
+ count: (characteristics === null || characteristics === void 0 ? void 0 : characteristics.length) || 0,
709
+ uuids: (characteristics === null || characteristics === void 0 ? void 0 : characteristics.map(c => c.uuid)) || [],
710
+ });
711
+ let writeCharacteristic = null;
712
+ let notifyCharacteristic = null;
713
+ for (const characteristic of characteristics) {
714
+ const uuid = characteristic.uuid.replace(/-/g, '').toLowerCase();
715
+ const uuidKey = uuid.length >= 8 ? uuid.substring(4, 8) : uuid;
716
+ if (uuidKey === NORMALIZED_WRITE_UUID) {
717
+ writeCharacteristic = characteristic;
718
+ }
719
+ else if (uuidKey === NORMALIZED_NOTIFY_UUID) {
720
+ notifyCharacteristic = characteristic;
721
+ }
722
+ }
723
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Characteristic discovery result:', {
724
+ writeFound: !!writeCharacteristic,
725
+ notifyFound: !!notifyCharacteristic,
726
+ });
727
+ if (!writeCharacteristic || !notifyCharacteristic) {
728
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Missing characteristics - write:', !!writeCharacteristic, 'notify:', !!notifyCharacteristic);
729
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleCharacteristicNotFound, 'Required characteristics not found'));
730
+ return;
731
+ }
732
+ resolve({ write: writeCharacteristic, notify: notifyCharacteristic });
733
+ });
734
+ });
735
+ });
736
+ });
737
+ }
738
+ function forceReconnectPeripheral(peripheral, deviceId) {
739
+ return index.__awaiter(this, void 0, void 0, function* () {
740
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Forcing connection reset for device:', deviceId);
741
+ cleanupDevice(deviceId, undefined, {
742
+ cleanupConnection: true,
743
+ sendDisconnectEvent: false,
744
+ cancelOperations: true,
745
+ reason: 'force-reconnect',
746
+ });
747
+ if (peripheral.state === 'connected') {
748
+ yield new Promise(resolve => {
749
+ peripheral.disconnect(() => {
750
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force disconnect completed');
751
+ resolve();
752
+ });
753
+ });
754
+ yield hardwareShared.wait(1000);
755
+ }
756
+ peripheral.removeAllListeners();
757
+ yield new Promise((resolve, reject) => {
758
+ peripheral.connect((error) => {
759
+ if (error) {
760
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Force reconnect failed:', error);
761
+ reject(new Error(`Force reconnect failed: ${error.message}`));
762
+ }
763
+ else {
764
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
765
+ connectedDevices.set(deviceId, peripheral);
766
+ resolve();
767
+ }
768
+ });
769
+ });
770
+ yield hardwareShared.wait(500);
771
+ });
772
+ }
773
+ function connectAndDiscoverWithFreshScan(deviceId) {
774
+ return index.__awaiter(this, void 0, void 0, function* () {
775
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Attempting connection with fresh peripheral scan as fallback');
776
+ const currentPeripheral = discoveredDevices.get(deviceId);
777
+ if (currentPeripheral) {
778
+ try {
779
+ return yield discoverServicesAndCharacteristicsWithRetry(currentPeripheral, deviceId);
780
+ }
781
+ catch (error) {
782
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed with existing peripheral, attempting fresh scan...');
783
+ }
784
+ }
785
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Performing fresh scan to get new peripheral object for device:', deviceId);
786
+ try {
787
+ const freshPeripheral = yield performTargetedScan(deviceId);
788
+ if (!freshPeripheral) {
789
+ discoveredDevices.delete(deviceId);
790
+ subscriptionOperations.set(deviceId, 'idle');
791
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Deep cleanup before throwing DeviceNotFound (fresh scan null)', {
792
+ deviceId,
793
+ });
794
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found in fresh scan`);
795
+ }
796
+ discoveredDevices.set(deviceId, freshPeripheral);
797
+ yield new Promise((resolve, reject) => {
798
+ freshPeripheral.connect((error) => {
799
+ if (error) {
800
+ reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
801
+ }
802
+ else {
803
+ connectedDevices.set(deviceId, freshPeripheral);
804
+ resolve();
805
+ }
806
+ });
807
+ });
808
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Attempting service discovery with fresh peripheral');
809
+ yield hardwareShared.wait(1000);
810
+ return yield discoverServicesAndCharacteristics(freshPeripheral);
811
+ }
812
+ catch (error) {
813
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Fresh scan and connection failed:', error);
814
+ throw error;
815
+ }
816
+ });
817
+ }
818
+ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
819
+ return index.__awaiter(this, void 0, void 0, function* () {
820
+ return pRetry__default["default"]((attemptNumber) => index.__awaiter(this, void 0, void 0, function* () {
821
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting service discovery:', {
822
+ deviceId,
823
+ peripheralState: peripheral.state,
824
+ attempt: attemptNumber,
825
+ maxRetries: 5,
826
+ targetUUIDs: UKEY_SERVICE_UUIDS,
827
+ });
828
+ if (attemptNumber === 3) {
829
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Attempting force reconnect to clear connection state...');
830
+ try {
831
+ yield forceReconnectPeripheral(peripheral, deviceId);
832
+ }
833
+ catch (error) {
834
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Force reconnect failed:', error);
835
+ throw error;
836
+ }
837
+ }
838
+ if (attemptNumber > 1) {
839
+ logger === null || logger === void 0 ? void 0 : logger.info(`[NobleBLE] Service discovery retry attempt ${attemptNumber}/5`);
840
+ }
841
+ if (peripheral.state !== 'connected') {
842
+ throw new Error(`Device not connected: ${peripheral.state}`);
843
+ }
844
+ try {
845
+ return yield discoverServicesAndCharacteristics(peripheral);
846
+ }
847
+ catch (error) {
848
+ logger === null || logger === void 0 ? void 0 : logger.error(`[NobleBLE] No services found (attempt ${attemptNumber}/5)`);
849
+ if (attemptNumber < 5) {
850
+ logger === null || logger === void 0 ? void 0 : logger.error(`[NobleBLE] Will retry service discovery (attempt ${attemptNumber + 1}/5)`);
851
+ }
852
+ throw error;
853
+ }
854
+ }), {
855
+ retries: 4,
856
+ factor: 1.5,
857
+ minTimeout: 1000,
858
+ maxTimeout: 3000,
859
+ onFailedAttempt: error => {
860
+ logger === null || logger === void 0 ? void 0 : logger.error(`[NobleBLE] Service discovery attempt ${error.attemptNumber} failed:`, {
861
+ message: error.message,
862
+ retriesLeft: error.retriesLeft,
863
+ nextRetryIn: `${Math.min(1000 * Math.pow(1.5, error.attemptNumber), 3000)}ms`,
864
+ });
865
+ },
866
+ });
867
+ });
868
+ }
869
+ function connectDevice(deviceId, webContents) {
870
+ return index.__awaiter(this, void 0, void 0, function* () {
871
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
872
+ deviceId,
873
+ hasDiscovered: discoveredDevices.has(deviceId),
874
+ hasConnected: connectedDevices.has(deviceId),
875
+ hasCharacteristics: deviceCharacteristics.has(deviceId),
876
+ totalDiscovered: discoveredDevices.size,
877
+ totalConnected: connectedDevices.size,
878
+ });
879
+ let peripheral = discoveredDevices.get(deviceId);
880
+ if (!peripheral) {
881
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device not discovered, attempting targeted scan for:', deviceId);
882
+ if (!noble) {
883
+ yield initializeNoble();
884
+ }
885
+ if (!noble) {
886
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.RuntimeError, 'Noble not available');
887
+ }
888
+ try {
889
+ const foundPeripheral = yield performTargetedScan(deviceId);
890
+ if (!foundPeripheral) {
891
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found even after targeted scan`);
892
+ }
893
+ peripheral = foundPeripheral;
894
+ }
895
+ catch (error) {
896
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Targeted scan failed:', error);
897
+ throw error;
898
+ }
899
+ }
900
+ if (!peripheral) {
901
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found`);
902
+ }
903
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connecting to device:', deviceId);
904
+ if (peripheral.state === 'connected') {
905
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already connected, skipping connection step');
906
+ if (!connectedDevices.has(deviceId)) {
907
+ connectedDevices.set(deviceId, peripheral);
908
+ setupDisconnectListener(peripheral, deviceId, webContents);
909
+ }
910
+ if (deviceCharacteristics.has(deviceId)) {
911
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device characteristics already available');
912
+ const ongoingOperation = subscriptionOperations.get(deviceId);
913
+ if (ongoingOperation && ongoingOperation !== 'idle') {
914
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device has ongoing subscription operation:', ongoingOperation, 'skip reconnect');
915
+ return;
916
+ }
917
+ const hasActiveSubscription = subscribedDevices.has(deviceId);
918
+ const hasCallback = notificationCallbacks.has(deviceId);
919
+ if (hasActiveSubscription && hasCallback) {
920
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already has active notification subscription, reusing connection');
921
+ return;
922
+ }
923
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Found orphaned characteristics without active subscription, cleaning up');
924
+ const existingCharacteristics = deviceCharacteristics.get(deviceId);
925
+ if (existingCharacteristics) {
926
+ existingCharacteristics.notify.removeAllListeners('data');
927
+ }
928
+ notificationCallbacks.delete(deviceId);
929
+ devicePacketStates.delete(deviceId);
930
+ subscribedDevices.delete(deviceId);
931
+ }
932
+ yield hardwareShared.wait(300);
933
+ try {
934
+ const characteristics = yield connectAndDiscoverWithFreshScan(deviceId);
935
+ deviceCharacteristics.set(deviceId, characteristics);
936
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
937
+ return;
938
+ }
939
+ catch (error) {
940
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service/characteristic discovery failed after all attempts:', error);
941
+ throw error;
942
+ }
943
+ }
944
+ return new Promise((resolve, reject) => {
945
+ const timeout = setTimeout(() => {
946
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
947
+ }, CONNECTION_TIMEOUT);
948
+ const connectedPeripheral = peripheral;
949
+ connectedPeripheral.connect((error) => index.__awaiter(this, void 0, void 0, function* () {
950
+ clearTimeout(timeout);
951
+ if (error) {
952
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
953
+ reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleConnectedError, error.message));
954
+ return;
955
+ }
956
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connected to device:', deviceId);
957
+ connectedDevices.set(deviceId, connectedPeripheral);
958
+ setupDisconnectListener(connectedPeripheral, deviceId, webContents);
959
+ try {
960
+ const characteristics = yield connectAndDiscoverWithFreshScan(deviceId);
961
+ deviceCharacteristics.set(deviceId, characteristics);
962
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
963
+ resolve();
964
+ }
965
+ catch (discoveryError) {
966
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service/characteristic discovery failed after all attempts:', discoveryError);
967
+ connectedPeripheral.disconnect();
968
+ reject(discoveryError);
969
+ }
970
+ }));
971
+ });
972
+ });
973
+ }
974
+ function disconnectDevice(deviceId) {
975
+ return index.__awaiter(this, void 0, void 0, function* () {
976
+ const peripheral = connectedDevices.get(deviceId);
977
+ if (!peripheral) {
978
+ return;
979
+ }
980
+ return new Promise(resolve => {
981
+ peripheral.removeAllListeners('disconnect');
982
+ peripheral.disconnect(() => {
983
+ cleanupDevice(deviceId, undefined, {
984
+ cleanupConnection: true,
985
+ sendDisconnectEvent: false,
986
+ cancelOperations: true,
987
+ reason: 'manual-disconnect',
988
+ });
989
+ resolve();
990
+ });
991
+ });
992
+ });
993
+ }
994
+ function unsubscribeNotifications(deviceId) {
995
+ return index.__awaiter(this, void 0, void 0, function* () {
996
+ const peripheral = connectedDevices.get(deviceId);
997
+ const characteristics = deviceCharacteristics.get(deviceId);
998
+ if (!peripheral || !characteristics) {
999
+ return;
1000
+ }
1001
+ const { notify: notifyCharacteristic } = characteristics;
1002
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Unsubscribing from notifications for device:', deviceId);
1003
+ subscriptionOperations.set(deviceId, 'unsubscribing');
1004
+ return new Promise(resolve => {
1005
+ notifyCharacteristic.unsubscribe((error) => {
1006
+ if (error) {
1007
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Notification unsubscription failed:', error);
1008
+ }
1009
+ else {
1010
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification unsubscription successful');
1011
+ }
1012
+ notifyCharacteristic.removeAllListeners('data');
1013
+ notificationCallbacks.delete(deviceId);
1014
+ devicePacketStates.delete(deviceId);
1015
+ subscribedDevices.delete(deviceId);
1016
+ subscriptionOperations.set(deviceId, 'idle');
1017
+ resolve();
1018
+ });
1019
+ });
1020
+ });
1021
+ }
1022
+ function subscribeNotifications(deviceId, callback) {
1023
+ return index.__awaiter(this, void 0, void 0, function* () {
1024
+ const peripheral = connectedDevices.get(deviceId);
1025
+ const characteristics = deviceCharacteristics.get(deviceId);
1026
+ if (!peripheral || !characteristics) {
1027
+ throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleCharacteristicNotFound, `Device ${deviceId} not connected or characteristics not available`);
1028
+ }
1029
+ const { notify: notifyCharacteristic } = characteristics;
1030
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Subscribing to notifications for device:', deviceId);
1031
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Subscribe context', {
1032
+ deviceId,
1033
+ opStateBefore: subscriptionOperations.get(deviceId) || 'idle',
1034
+ paired: false,
1035
+ hasController: false,
1036
+ });
1037
+ const opState = subscriptionOperations.get(deviceId);
1038
+ if (opState === 'subscribing') {
1039
+ notificationCallbacks.set(deviceId, callback);
1040
+ return Promise.resolve();
1041
+ }
1042
+ subscriptionOperations.set(deviceId, 'subscribing');
1043
+ if (subscribedDevices.get(deviceId)) {
1044
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already subscribed to characteristic, updating callback only');
1045
+ notificationCallbacks.set(deviceId, callback);
1046
+ devicePacketStates.set(deviceId, {
1047
+ bufferLength: 0,
1048
+ buffer: [],
1049
+ packetCount: 0,
1050
+ messageId: undefined,
1051
+ });
1052
+ subscriptionOperations.set(deviceId, 'idle');
1053
+ return Promise.resolve();
1054
+ }
1055
+ if (notificationCallbacks.has(deviceId)) {
1056
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Cleaning up previous notification listeners');
1057
+ }
1058
+ notifyCharacteristic.removeAllListeners('data');
1059
+ notificationCallbacks.set(deviceId, callback);
1060
+ devicePacketStates.set(deviceId, {
1061
+ bufferLength: 0,
1062
+ buffer: [],
1063
+ packetCount: 0,
1064
+ messageId: undefined,
1065
+ });
1066
+ function rebuildAppSubscription(deviceId, notifyCharacteristic) {
1067
+ return index.__awaiter(this, void 0, void 0, function* () {
1068
+ yield new Promise(resolve => {
1069
+ notifyCharacteristic.unsubscribe(() => {
1070
+ resolve();
1071
+ });
1072
+ });
1073
+ yield new Promise((resolve, reject) => {
1074
+ notifyCharacteristic.subscribe((error) => {
1075
+ if (error) {
1076
+ reject(error);
1077
+ return;
1078
+ }
1079
+ resolve();
1080
+ });
1081
+ });
1082
+ notifyCharacteristic.on('data', (data) => {
1083
+ if (!pairedDevices.has(deviceId)) {
1084
+ pairedDevices.add(deviceId);
1085
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device paired successfully', { deviceId });
1086
+ }
1087
+ const result = processNotificationData(deviceId, data);
1088
+ if (result.error) {
1089
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Packet processing error:', result.error);
1090
+ return;
1091
+ }
1092
+ if (result.isComplete && result.completePacket) {
1093
+ const appCb = notificationCallbacks.get(deviceId);
1094
+ if (appCb)
1095
+ appCb(result.completePacket);
1096
+ }
1097
+ });
1098
+ });
1099
+ }
1100
+ yield rebuildAppSubscription(deviceId, notifyCharacteristic);
1101
+ subscribedDevices.set(deviceId, true);
1102
+ subscriptionOperations.set(deviceId, 'idle');
1103
+ });
1104
+ }
1105
+ function setupNobleBleHandlers(webContents) {
1106
+ console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
1107
+ try {
1108
+ console.log('[NobleBLE] NOBLE_VERSION_771');
1109
+ logger = require('electron-log');
1110
+ console.log('[NobleBLE] electron-log loaded successfully.');
1111
+ const { ipcMain } = require('electron');
1112
+ console.log('[NobleBLE] electron.ipcMain loaded successfully.');
1113
+ safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
1114
+ console.log(`[NobleBLE] Registering handler for: ${hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
1115
+ ipcMain.handle(hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, () => index.__awaiter(this, void 0, void 0, function* () {
1116
+ try {
1117
+ const devices = yield enumerateDevices();
1118
+ safeLog(logger, 'info', 'Enumeration completed, devices:', devices);
1119
+ return devices;
1120
+ }
1121
+ catch (error) {
1122
+ safeLog(logger, 'error', 'Enumeration failed:', error);
1123
+ throw error;
1124
+ }
1125
+ }));
1126
+ ipcMain.handle(hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_STOP_SCAN, () => index.__awaiter(this, void 0, void 0, function* () {
1127
+ yield stopScanning();
1128
+ }));
1129
+ ipcMain.handle(hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_GET_DEVICE, (_event, deviceId) => getDevice(deviceId));
1130
+ ipcMain.handle(hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_CONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1131
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] IPC CONNECT request received:', {
1132
+ deviceId,
1133
+ hasPeripheral: connectedDevices.has(deviceId),
1134
+ hasCharacteristics: deviceCharacteristics.has(deviceId),
1135
+ totalConnectedDevices: connectedDevices.size,
1136
+ });
1137
+ yield connectDevice(deviceId, webContents);
1138
+ }));
1139
+ ipcMain.handle(hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_DISCONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1140
+ yield disconnectDevice(deviceId);
1141
+ }));
1142
+ ipcMain.handle(hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_WRITE, (_event, deviceId, hexData) => index.__awaiter(this, void 0, void 0, function* () {
1143
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
1144
+ yield transmitHexDataToDevice(deviceId, hexData);
1145
+ }));
1146
+ ipcMain.handle(hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_SUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1147
+ yield subscribeNotifications(deviceId, (data) => {
1148
+ webContents.send(hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_NOTIFICATION, deviceId, data);
1149
+ });
1150
+ }));
1151
+ ipcMain.handle(hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_UNSUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1152
+ yield unsubscribeNotifications(deviceId);
1153
+ }));
1154
+ ipcMain.handle(hardwareShared.EUKeyBleMessageKeys.NOBLE_BLE_CANCEL_PAIRING, () => index.__awaiter(this, void 0, void 0, function* () {
1155
+ const deviceIds = Array.from(connectedDevices.keys());
1156
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Cancel pairing invoked', {
1157
+ platform: process.platform,
1158
+ deviceCount: deviceIds.length,
1159
+ });
1160
+ for (const deviceId of deviceIds) {
1161
+ try {
1162
+ yield unsubscribeNotifications(deviceId).catch(() => { });
1163
+ yield disconnectDevice(deviceId).catch(() => { });
1164
+ }
1165
+ catch (e) {
1166
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Cancel pairing cleanup failed', { deviceId, error: e });
1167
+ }
1168
+ }
1169
+ }));
1170
+ ipcMain.handle(hardwareShared.EUKeyBleMessageKeys.BLE_AVAILABILITY_CHECK, () => index.__awaiter(this, void 0, void 0, function* () {
1171
+ try {
1172
+ const bluetoothStatus = yield checkBluetoothAvailability();
1173
+ safeLog(logger, 'info', 'Bluetooth availability check completed:', bluetoothStatus);
1174
+ return bluetoothStatus;
1175
+ }
1176
+ catch (error) {
1177
+ safeLog(logger, 'error', 'Bluetooth availability check failed:', error);
1178
+ return {
1179
+ available: false,
1180
+ state: 'error',
1181
+ unsupported: false,
1182
+ initialized: false,
1183
+ };
1184
+ }
1185
+ }));
1186
+ webContents.on('destroyed', () => {
1187
+ safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
1188
+ const deviceIds = Array.from(connectedDevices.keys());
1189
+ deviceIds.forEach(deviceId => {
1190
+ cleanupDevice(deviceId, undefined, {
1191
+ cleanupConnection: true,
1192
+ sendDisconnectEvent: false,
1193
+ cancelOperations: true,
1194
+ reason: 'app-quit',
1195
+ });
1196
+ });
1197
+ stopScanning();
1198
+ if (noble && persistentStateListener) {
1199
+ noble.removeListener('stateChange', persistentStateListener);
1200
+ persistentStateListener = null;
1201
+ }
1202
+ cleanupNobleListeners();
1203
+ discoveredDevices.clear();
1204
+ safeLog(logger, 'info', 'Noble BLE cleanup completed');
1205
+ });
1206
+ safeLog(logger, 'info', 'Noble BLE IPC handlers setup completed');
1207
+ }
1208
+ catch (error) {
1209
+ console.error('[NobleBLE] Failed to setup IPC handlers:', error);
1210
+ throw error;
1211
+ }
1212
+ }
1213
+
1214
+ exports.setupNobleBleHandlers = setupNobleBleHandlers;