@@ -37,11 +37,26 @@ export const isChromeOS105 = (): boolean => {
3737 return / C r O S / . test ( userAgent ) && / C h r o m e \/ 1 0 5 \b / . test ( userAgent ) ;
3838} ;
3939
40+ export enum DeviceConnectMode {
41+ /**
42+ * First tries to connect to stored device id, if no device id stored
43+ * trigger device selection and pairing flow.
44+ */
45+ TryInitialPair = "try initial pair" ,
46+ /**
47+ * First tries to connect to stored device id, if no device id stored attempt
48+ * connection with previously paired devices. If all fail, trigger
49+ * device selection and pairing flow.
50+ */
51+ TryInitialAndPrevPair = "try initial and prev pair" ,
52+ }
53+
4054export interface MicrobitWebUSBConnectionOptions {
4155 // We should copy this type when extracting a library, and make it optional.
4256 // Coupling for now to make it easy to evolve.
4357
44- logging : Logging ;
58+ logging ?: Logging ;
59+ deviceConnectMode ?: DeviceConnectMode ;
4560}
4661
4762export interface MicrobitWebUSBConnection
@@ -176,16 +191,17 @@ class MicrobitWebUSBConnectionImpl
176191 } ;
177192
178193 private logging : Logging ;
194+ private deviceConnectMode : DeviceConnectMode ;
179195
180196 private addedListeners : Record < string , number > = {
181197 serialdata : 0 ,
182198 } ;
183199
184- constructor (
185- options : MicrobitWebUSBConnectionOptions = { logging : new NullLogging ( ) } ,
186- ) {
200+ constructor ( options : MicrobitWebUSBConnectionOptions = { } ) {
187201 super ( ) ;
188- this . logging = options . logging ;
202+ this . logging = options . logging || new NullLogging ( ) ;
203+ this . deviceConnectMode =
204+ options . deviceConnectMode || DeviceConnectMode . TryInitialPair ;
189205 }
190206
191207 private log ( v : any ) {
@@ -460,21 +476,49 @@ class MicrobitWebUSBConnectionImpl
460476 }
461477
462478 private async connectInternal ( ) : Promise < void > {
463- if ( ! this . connection ) {
464- const device = await this . chooseDevice ( ) ;
465- this . connection = new DAPWrapper ( device , this . logging ) ;
479+ if ( ! this . connection && this . device ) {
480+ this . connection = new DAPWrapper ( this . device , this . logging ) ;
481+ await withTimeout ( this . connection . reconnectAsync ( ) , 10_000 ) ;
482+ } else if ( ! this . connection ) {
483+ if ( this . deviceConnectMode === DeviceConnectMode . TryInitialAndPrevPair ) {
484+ await this . tryPrevConnectedDevices ( ) ;
485+ }
486+ if ( ! this . connection ) {
487+ this . device = await this . chooseDevice ( ) ;
488+ this . connection = new DAPWrapper ( this . device , this . logging ) ;
489+ await withTimeout ( this . connection . reconnectAsync ( ) , 10_000 ) ;
490+ }
491+ } else {
492+ await withTimeout ( this . connection . reconnectAsync ( ) , 10_000 ) ;
466493 }
467- await withTimeout ( this . connection . reconnectAsync ( ) , 10_000 ) ;
468494 if ( this . addedListeners . serialdata && ! this . flashing ) {
469495 this . startSerialInternal ( ) ;
470496 }
471497 this . setStatus ( ConnectionStatus . CONNECTED ) ;
472498 }
473499
474- private async chooseDevice ( ) : Promise < USBDevice > {
475- if ( this . device ) {
476- return this . device ;
500+ // Drawn from https://github.com/microsoft/pxt/blob/ab97a2422879824c730f009b15d4bf446b0e8547/pxtlib/webusb.ts#L361
501+ private async tryPrevConnectedDevices ( ) : Promise < void > {
502+ const prevPairedDevices = await this . tryGetDevicesAsync ( ) ;
503+ for ( let i = 0 ; i < prevPairedDevices . length ; ++ i ) {
504+ const d = prevPairedDevices [ i ] ;
505+ this . device = d ;
506+ this . log ( `connect device: ${ d . manufacturerName } ${ d . productName } ` ) ;
507+ this . log ( `serial number: ${ d . serialNumber } ` ) ;
508+ try {
509+ this . connection = new DAPWrapper ( this . device , this . logging ) ;
510+ await withTimeout ( this . connection . reconnectAsync ( ) , 10_000 ) ;
511+ // Success, stop trying.
512+ } catch ( e : any ) {
513+ // Clean slate and try next one.
514+ this . device = undefined ;
515+ this . connection = undefined ;
516+ this . log ( `connection attempt failed, ${ e . message } ` ) ;
517+ }
477518 }
519+ }
520+
521+ private async chooseDevice ( ) : Promise < USBDevice > {
478522 this . dispatchTypedEvent ( "beforerequestdevice" , new BeforeRequestDevice ( ) ) ;
479523 this . device = await navigator . usb . requestDevice ( {
480524 exclusionFilters : this . exclusionFilters ,
@@ -484,6 +528,19 @@ class MicrobitWebUSBConnectionImpl
484528 return this . device ;
485529 }
486530
531+ // Drawn from https://github.com/microsoft/pxt/blob/ab97a2422879824c730f009b15d4bf446b0e8547/pxtlib/webusb.ts#L530
532+ private async tryGetDevicesAsync ( ) : Promise < USBDevice [ ] > {
533+ this . log ( "Getting web usb devices" ) ;
534+ try {
535+ const devs = await this . withEnrichedErrors ( ( ) =>
536+ navigator . usb ?. getDevices ( ) ,
537+ ) ;
538+ return devs || [ ] ;
539+ } catch ( e : any ) {
540+ return [ ] ;
541+ }
542+ }
543+
487544 protected eventActivated ( type : string ) : void {
488545 switch ( type as keyof SerialConnectionEventMap ) {
489546 case "serialdata" : {
0 commit comments