@@ -19,6 +19,7 @@ import {
1919import { COMMON_HEADER_SIZE } from '@onekeyfe/hd-transport' ;
2020import type { WebContents , IpcMainInvokeEvent } from 'electron' ;
2121import type { Peripheral , Service , Characteristic } from '@abandonware/noble' ;
22+ import pRetry from 'p-retry' ;
2223import type { NobleModule , Logger , DeviceInfo , CharacteristicPair } from './types/noble-extended' ;
2324import { safeLog } from './types/noble-extended' ;
2425
@@ -250,6 +251,9 @@ async function initializeNoble(): Promise<void> {
250251 return ;
251252 }
252253
254+ // Setup persistent state listener before initialization
255+ setupPersistentStateListener ( ) ;
256+
253257 const timeout = setTimeout ( ( ) => {
254258 reject (
255259 ERRORS . TypedError ( HardwareErrorCode . RuntimeError , 'Bluetooth initialization timeout' )
@@ -289,9 +293,6 @@ async function initializeNoble(): Promise<void> {
289293 handleDeviceDiscovered ( peripheral ) ;
290294 } ) ;
291295
292- // Setup persistent state listener after initialization
293- setupPersistentStateListener ( ) ;
294-
295296 logger ?. info ( '[NobleBLE] Noble initialized successfully' ) ;
296297 } catch ( error ) {
297298 logger ?. error ( '[NobleBLE] Failed to initialize Noble:' , error ) ;
@@ -563,7 +564,7 @@ function getDevice(deviceId: string): DeviceInfo | null {
563564 } ;
564565}
565566
566- // Discover services and characteristics for a connected device
567+ // Core service discovery function (single attempt)
567568async function discoverServicesAndCharacteristics (
568569 peripheral : Peripheral
569570) : Promise < CharacteristicPair > {
@@ -642,6 +643,168 @@ async function discoverServicesAndCharacteristics(
642643 } ) ;
643644}
644645
646+ // Force reconnect to clear potential connection state issues
647+ async function forceReconnectPeripheral ( peripheral : Peripheral , deviceId : string ) : Promise < void > {
648+ logger ?. info ( '[NobleBLE] Forcing connection reset for device:' , deviceId ) ;
649+
650+ // Step 1: Force disconnect if connected
651+ if ( peripheral . state === 'connected' ) {
652+ await new Promise < void > ( resolve => {
653+ peripheral . disconnect ( ( ) => {
654+ logger ?. info ( '[NobleBLE] Force disconnect completed' ) ;
655+ resolve ( ) ;
656+ } ) ;
657+ } ) ;
658+
659+ // Wait for complete disconnection
660+ await wait ( 1000 ) ;
661+ }
662+
663+ // Step 2: Clear device state
664+ connectedDevices . delete ( deviceId ) ;
665+ deviceCharacteristics . delete ( deviceId ) ;
666+ devicePacketStates . delete ( deviceId ) ;
667+ subscribedDevices . delete ( deviceId ) ;
668+ subscriptionOperations . delete ( deviceId ) ;
669+
670+ // Step 3: Re-establish connection
671+ await new Promise < void > ( ( resolve , reject ) => {
672+ peripheral . connect ( ( error : string ) => {
673+ if ( error ) {
674+ logger ?. error ( '[NobleBLE] Force reconnect failed:' , error ) ;
675+ reject ( new Error ( `Force reconnect failed: ${ error } ` ) ) ;
676+ } else {
677+ logger ?. info ( '[NobleBLE] Force reconnect successful' ) ;
678+ connectedDevices . set ( deviceId , peripheral ) ;
679+ resolve ( ) ;
680+ }
681+ } ) ;
682+ } ) ;
683+
684+ // Wait for connection to stabilize
685+ await wait ( 500 ) ;
686+ }
687+
688+ // Enhanced connection with fresh peripheral rescan as last resort
689+ async function connectAndDiscoverWithFreshScan ( deviceId : string ) : Promise < CharacteristicPair > {
690+ logger ?. info ( '[NobleBLE] Attempting connection with fresh peripheral scan as fallback' ) ;
691+
692+ const currentPeripheral = discoveredDevices . get ( deviceId ) ;
693+
694+ // First attempt with existing peripheral
695+ if ( currentPeripheral ) {
696+ try {
697+ return await discoverServicesAndCharacteristicsWithRetry ( currentPeripheral , deviceId ) ;
698+ } catch ( error ) {
699+ logger ?. error (
700+ '[NobleBLE] Service discovery failed with existing peripheral, attempting fresh scan...'
701+ ) ;
702+ }
703+ }
704+
705+ // Last resort: Fresh scan to get new peripheral object
706+ logger ?. info (
707+ '[NobleBLE] Performing fresh scan to get new peripheral object for device:' ,
708+ deviceId
709+ ) ;
710+
711+ try {
712+ const freshPeripheral = await performTargetedScan ( deviceId ) ;
713+ if ( ! freshPeripheral ) {
714+ throw new Error ( `Device ${ deviceId } not found in fresh scan` ) ;
715+ }
716+
717+ // Update device maps with fresh peripheral
718+ discoveredDevices . set ( deviceId , freshPeripheral ) ;
719+
720+ // Connect to fresh peripheral
721+ await new Promise < void > ( ( resolve , reject ) => {
722+ freshPeripheral . connect ( ( error : string ) => {
723+ if ( error ) {
724+ reject ( new Error ( `Fresh peripheral connection failed: ${ error } ` ) ) ;
725+ } else {
726+ connectedDevices . set ( deviceId , freshPeripheral ) ;
727+ resolve ( ) ;
728+ }
729+ } ) ;
730+ } ) ;
731+
732+ // Attempt service discovery with fresh peripheral (single attempt)
733+ logger ?. info ( '[NobleBLE] Attempting service discovery with fresh peripheral' ) ;
734+ await wait ( 1000 ) ; // Give fresh connection more time to stabilize
735+
736+ return await discoverServicesAndCharacteristics ( freshPeripheral ) ;
737+ } catch ( error ) {
738+ logger ?. error ( '[NobleBLE] Fresh scan and connection failed:' , error ) ;
739+ throw error ;
740+ }
741+ }
742+
743+ // Enhanced service discovery with p-retry for robust BLE connection
744+ async function discoverServicesAndCharacteristicsWithRetry (
745+ peripheral : Peripheral ,
746+ deviceId : string
747+ ) : Promise < CharacteristicPair > {
748+ return pRetry (
749+ async attemptNumber => {
750+ logger ?. info ( '[NobleBLE] Starting service discovery:' , {
751+ deviceId,
752+ peripheralState : peripheral . state ,
753+ attempt : attemptNumber ,
754+ maxRetries : 5 ,
755+ targetUUIDs : ONEKEY_SERVICE_UUIDS ,
756+ } ) ;
757+
758+ // Strategy: Force reconnect on 3rd attempt to clear potential state issues
759+ if ( attemptNumber === 3 ) {
760+ logger ?. info ( '[NobleBLE] Attempting force reconnect to clear connection state...' ) ;
761+ try {
762+ await forceReconnectPeripheral ( peripheral , deviceId ) ;
763+ } catch ( error ) {
764+ logger ?. error ( '[NobleBLE] Force reconnect failed:' , error ) ;
765+ throw error ;
766+ }
767+ }
768+
769+ // Progressive delay strategy - handled by p-retry, but add extra wait for higher attempts
770+ if ( attemptNumber > 1 ) {
771+ logger ?. info ( `[NobleBLE] Service discovery retry attempt ${ attemptNumber } /5` ) ;
772+ }
773+
774+ // Verify connection state before attempting service discovery
775+ if ( peripheral . state !== 'connected' ) {
776+ throw new Error ( `Device not connected: ${ peripheral . state } ` ) ;
777+ }
778+
779+ try {
780+ return await discoverServicesAndCharacteristics ( peripheral ) ;
781+ } catch ( error ) {
782+ logger ?. error ( `[NobleBLE] No services found (attempt ${ attemptNumber } /5)` ) ;
783+
784+ if ( attemptNumber < 5 ) {
785+ logger ?. error ( `[NobleBLE] Will retry service discovery (attempt ${ attemptNumber + 1 } /5)` ) ;
786+ }
787+
788+ throw error ; // p-retry will handle the retry logic
789+ }
790+ } ,
791+ {
792+ retries : 4 , // Total 5 attempts (initial + 4 retries)
793+ factor : 1.5 , // Exponential backoff: 1000ms → 1500ms → 2250ms → 3000ms
794+ minTimeout : 1000 , // Start with 1 second delay
795+ maxTimeout : 3000 , // Maximum 3 seconds delay
796+ onFailedAttempt : error => {
797+ // This runs after each failed attempt
798+ logger ?. error ( `[NobleBLE] Service discovery attempt ${ error . attemptNumber } failed:` , {
799+ message : error . message ,
800+ retriesLeft : error . retriesLeft ,
801+ nextRetryIn : `${ Math . min ( 1000 * 1.5 ** error . attemptNumber , 3000 ) } ms` ,
802+ } ) ;
803+ } ,
804+ }
805+ ) ;
806+ }
807+
645808// Connect to device - supports both discovered and direct connection modes
646809async function connectDevice ( deviceId : string , webContents : WebContents ) : Promise < void > {
647810 logger ?. info ( '[NobleBLE] Connect device request:' , {
@@ -746,14 +909,17 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
746909 // Continue to re-setup the connection properly
747910 }
748911
749- // Discover services and characteristics
912+ // Discover services and characteristics with enhanced retry including fresh scan
750913 try {
751- const characteristics = await discoverServicesAndCharacteristics ( peripheral ) ;
914+ const characteristics = await connectAndDiscoverWithFreshScan ( deviceId ) ;
752915 deviceCharacteristics . set ( deviceId , characteristics ) ;
753916 logger ?. info ( '[NobleBLE] Device ready for communication:' , deviceId ) ;
754917 return ;
755918 } catch ( error ) {
756- logger ?. error ( '[NobleBLE] Service/characteristic discovery failed:' , error ) ;
919+ logger ?. error (
920+ '[NobleBLE] Service/characteristic discovery failed after all attempts:' ,
921+ error
922+ ) ;
757923 throw error ;
758924 }
759925 }
@@ -780,15 +946,18 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
780946 // Set up unified disconnect listener
781947 setupDisconnectListener ( connectedPeripheral , deviceId , webContents ) ;
782948
783- // Discover services and characteristics
784- discoverServicesAndCharacteristics ( connectedPeripheral )
949+ // Discover services and characteristics with enhanced retry including fresh scan
950+ connectAndDiscoverWithFreshScan ( deviceId )
785951 . then ( characteristics => {
786952 deviceCharacteristics . set ( deviceId , characteristics ) ;
787953 logger ?. info ( '[NobleBLE] Device ready for communication:' , deviceId ) ;
788954 resolve ( ) ;
789955 } )
790956 . catch ( error => {
791- logger ?. error ( '[NobleBLE] Service/characteristic discovery failed:' , error ) ;
957+ logger ?. error (
958+ '[NobleBLE] Service/characteristic discovery failed after all attempts:' ,
959+ error
960+ ) ;
792961 // Disconnect on failure
793962 connectedPeripheral . disconnect ( ) ;
794963 reject ( error ) ;
@@ -1053,6 +1222,7 @@ async function unsubscribeNotifications(deviceId: string): Promise<void> {
10531222// Setup IPC handlers
10541223export function setupNobleBleHandlers ( webContents : WebContents ) : void {
10551224 try {
1225+ console . log ( 'NOBLE_VERSION_771' ) ;
10561226 // @ts -ignore – electron-log is only available at runtime
10571227 // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
10581228 logger = require ( 'electron-log' ) as Logger ;
0 commit comments