@@ -46,10 +46,16 @@ export class ReactNativeBleTransport implements BleTransport {
4646 scan ( onDesktop : ( desktop : DiscoveredDesktop ) => void , onError : ( error : Error ) => void ) : Unsubscribe {
4747 const operation = ++ this . #operation;
4848 let active = true ;
49- this . #managerOrCreate( ) . startDeviceScan ( [ BLE_UUIDS . service ] , null , ( error , device ) => {
49+ const waiting = new Map < string , Device > ( ) ;
50+ const onAdvertisement = ( error : Error | null , device : Device | null ) => {
5051 if ( ! active || operation !== this . #operation) return ;
5152 if ( error ) { onError ( error ) ; return ; }
52- if ( ! device || this . #scanDevices. has ( device . id ) || this . #scanKeys. has ( this . #scanKey( device ) ) || this . #scanDevices. size >= 4 ) return ;
53+ if ( ! device || this . #scanDevices. has ( device . id ) || this . #scanKeys. has ( this . #scanKey( device ) ) ) return ;
54+ if ( this . #scanDevices. size >= 4 ) {
55+ if ( waiting . size < 32 ) waiting . set ( device . id , device ) ;
56+ return ;
57+ }
58+ waiting . delete ( device . id ) ;
5359 const scanKey = this . #scanKey( device ) ;
5460 let retainCompletedKey = false ;
5561 this . #scanDevices. set ( device . id , device ) ;
@@ -58,20 +64,28 @@ export class ReactNativeBleTransport implements BleTransport {
5864 // Suppress repeats only for this peripheral. A rotated address must be
5965 // probed again: neither a shared nor a cached name establishes identity.
6066 // Cap retained keys so long scans cannot accumulate unbounded state.
61- retainCompletedKey = desktop ?. platform === 'windows' && this . #scanKeys. size <= 256 ;
67+ retainCompletedKey = desktop ?. platform === 'windows' ;
68+ if ( active && operation === this . #operation && retainCompletedKey && this . #scanKeys. size > 256 ) {
69+ const oldest = [ ...this . #scanKeys] . find ( ( key ) => ! [ ...this . #scanDevices. values ( ) ] . some ( ( peer ) => this . #scanKey( peer ) === key ) ) ;
70+ if ( oldest ) this . #scanKeys. delete ( oldest ) ;
71+ }
6272 if ( active && operation === this . #operation && desktop ) onDesktop ( desktop ) ;
6373 } ) . catch ( ( ) => undefined ) . finally ( ( ) => {
6474 if ( operation === this . #operation) {
6575 this . #scanDevices. delete ( device . id ) ;
6676 if ( ! retainCompletedKey ) this . #scanKeys. delete ( scanKey ) ;
77+ const next = waiting . values ( ) . next ( ) . value ;
78+ if ( next ) onAdvertisement ( null , next ) ;
6779 }
6880 } ) ;
6981 this . #scanTasks. add ( task ) ;
7082 void task . finally ( ( ) => this . #scanTasks. delete ( task ) ) ;
71- } ) ;
83+ } ;
84+ this . #managerOrCreate( ) . startDeviceScan ( [ BLE_UUIDS . service ] , null , onAdvertisement ) ;
7285 return ( ) => {
7386 if ( ! active ) return ;
7487 active = false ;
88+ waiting . clear ( ) ;
7589 if ( operation === this . #operation) this . #operation += 1 ;
7690 this . #managerOrCreate( ) . stopDeviceScan ( ) ;
7791 this . #cancelNativeOperations( ) ;
@@ -98,10 +112,12 @@ export class ReactNativeBleTransport implements BleTransport {
98112 let active = true ;
99113 let claimedDeviceId : string | null = null ;
100114 let cancellation : Promise < void > | null = null ;
115+ const waiting = new Map < string , Device > ( ) ;
101116 const succeed = ( desktop : DiscoveredDesktop ) => {
102117 if ( ! active ) return ;
103118 this . #recordStage( 'resolution' , 'succeeded' , operation ) ;
104119 active = false ;
120+ waiting . clear ( ) ;
105121 clearTimeout ( timer ) ;
106122 this . #managerOrCreate( ) . stopDeviceScan ( ) ;
107123 this . #scanKeys. clear ( ) ;
@@ -112,6 +128,7 @@ export class ReactNativeBleTransport implements BleTransport {
112128 if ( ! active ) return cancellation ?? Promise . resolve ( ) ;
113129 this . #recordStage( 'resolution' , outcome , operation ) ;
114130 active = false ;
131+ waiting . clear ( ) ;
115132 clearTimeout ( timer ) ;
116133 this . #managerOrCreate( ) . stopDeviceScan ( ) ;
117134 const probes = [ ...this . #scanDevices. values ( ) ] ;
@@ -135,7 +152,12 @@ export class ReactNativeBleTransport implements BleTransport {
135152 const onAdvertisement = ( error : Error | null , device : Device | null ) => {
136153 if ( ! active || operation !== this . #operation) return ;
137154 if ( error ) { void cancel ( new Error ( 'Saved PC discovery failed.' ) ) ; return ; }
138- if ( ! device || this . #scanDevices. has ( device . id ) || this . #scanKeys. has ( this . #scanKey( device ) ) || this . #scanDevices. size >= 4 ) return ;
155+ if ( ! device || this . #scanDevices. has ( device . id ) || this . #scanKeys. has ( this . #scanKey( device ) ) ) return ;
156+ if ( this . #scanDevices. size >= 4 ) {
157+ if ( waiting . size < 32 ) waiting . set ( device . id , device ) ;
158+ return ;
159+ }
160+ waiting . delete ( device . id ) ;
139161 this . #scanDevices. set ( device . id , device ) ;
140162 this . #scanKeys. add ( this . #scanKey( device ) ) ;
141163 const task = this . #readStatus( device , ( desktop ) => {
@@ -174,6 +196,8 @@ export class ReactNativeBleTransport implements BleTransport {
174196 if ( operation === this . #operation) {
175197 this . #scanDevices. delete ( device . id ) ;
176198 this . #scanKeys. delete ( this . #scanKey( device ) ) ;
199+ const next = waiting . values ( ) . next ( ) . value ;
200+ if ( next ) onAdvertisement ( null , next ) ;
177201 }
178202 } ) ;
179203 this . #scanTasks. add ( task ) ;
0 commit comments