'use strict' ; export enum SerialPolyfillProtocol { UsbCdcAcm } export interface SerialPolyfillOptions { protocol ? : SerialPolyfillProtocol ; usbControlInterfaceClass ? : number ; usbTransferInterfaceClass ? : number ; } const kSetLineCoding = 0x20 ; const kSetControlLineState = 0x22 ; const kSendBreak = 0x23 ; const kDefaultBufferSize = 255 ; const kDefaultDataBits = 8 ; const kDefaultParity = 'none' ; const kDefaultStopBits = 1 ; const kAcceptableDataBits = [ 16 , 8 , 7 , 6 , 5 ] ; const kAcceptableStopBits = [ 1 , 2 ] ; const kAcceptableParity = [ 'none' , 'even' , 'odd' ] ; const kParityIndexMapping = [ 'none' , 'odd' , 'even' ] ; const kStopBitsIndexMapping = [ 1 , 1.5 , 2 ] ; const kDefaultPolyfillOptions = { protocol : SerialPolyfillProtocol . UsbCdcAcm , usbControlInterfaceClass : 2 , usbTransferInterfaceClass : 10 , } ; param { USBDevice } device param { number } classCode return { USBInterface } throws function findInterface ( device , classCode ) { const configuration = device . configurations [ 0 ] ; for ( const iface of configuration . interfaces ) { const alternate = iface . alternates [ 0 ] ; if ( alternate . interfaceClass === classCode ) { return iface ; } } throw new TypeError ( `Unable to find interface with class ${ classCode }.` ) ; } param { USBInterface } iface param { USBDirection } direction return { USBEndpoint } throws function findEndpoint ( iface , direction ) { const alternate = iface . alternates [ 0 ] ; for ( const endpoint of alternate . endpoints ) { if ( endpoint . direction == direction ) { return endpoint ; } } throw new TypeError ( `Interface ${ iface . interfaceNumber } does not have an ` + `${ direction } endpoint.` ) ; } /**
 * Implementation of the underlying source API[1] which reads data from a USB
 * endpoint. This can be used to construct a ReadableStream.
 *
 * [1]: https://streams.spec.whatwg.org/#underlying-source-api
 */ class UsbEndpointUnderlyingSource implements UnderlyingSource < Uint8Array > { private device_ : USBDevice ; private endpoint_ : USBEndpoint ; private onError_ : ( ) => void ; param { USBDevice } device param { USBEndpoint } endpoint param { function } onError constructor ( device , endpoint , onError ) { this . device_ = device ; this . endpoint_ = endpoint ; this . onError_ = onError ; } param { ReadableStreamDefaultController } controller pull ( controller ) { ( async ( ) : Promise < void > => { let chunkSize ; if ( controller . desiredSize ) { const d = controller . desiredSize / this . endpoint_ . packetSize ; chunkSize = Math . ceil ( d ) * this . endpoint_ . packetSize ; } else { chunkSize = this . endpoint_ . packetSize ; } try { const result = await this . device_ . transferIn ( this . endpoint_ . endpointNumber , chunkSize ) ; if ( result . status != 'ok' ) { controller . error ( `USB error: ${ result . status }` ) ; this . onError_ ( ) ; } if ( result . data ?. buffer ) { const chunk = new Uint8Array ( result . data . buffer , result . data . byteOffset , result . data . byteLength ) ; controller . enqueue ( chunk ) ; } } catch ( error ) { controller . error ( error . toString ( ) ) ; this . onError_ ( ) ; } } ) ( ) ; } } /**
 * Implementation of the underlying sink API[2] which writes data to a USB
 * endpoint. This can be used to construct a WritableStream.
 *
 * [2]: https://streams.spec.whatwg.org/#underlying-sink-api
 */ class UsbEndpointUnderlyingSink implements UnderlyingSink < Uint8Array > { private device_ : USBDevice ; private endpoint_ : USBEndpoint ; private onError_ : ( ) => void ; param { USBDevice } device param { USBEndpoint } endpoint param { function } onError constructor ( device , endpoint , onError ) { this . device_ = device ; this . endpoint_ = endpoint ; this . onError_ = onError ; } param { Uint8Array } chunk param { WritableStreamDefaultController } controller async write ( chunk , controller ) { try { const result = await this . device_ . transferOut ( this . endpoint_ . endpointNumber , chunk ) ; if ( result . status != 'ok' ) { controller . error ( result . status ) ; this . onError_ ( ) ; } } catch ( error ) { controller . error ( error . toString ( ) ) ; this . onError_ ( ) ; } } } /** a class used to control serial devices over WebUSB */ export class SerialPort { private polyfillOptions_ : SerialPolyfillOptions ; private device_ : USBDevice ; private controlInterface_ : USBInterface ; private transferInterface_ : USBInterface ; private inEndpoint_ : USBEndpoint ; private outEndpoint_ : USBEndpoint ; private serialOptions_ : SerialOptions ; private readable_ : ReadableStream < Uint8Array > | null ; private writable_ : WritableStream < Uint8Array > | null ; private outputSignals_ : SerialOutputSignals ; param { USBDevice } device param { SerialPolyfillOptions } polyfillOptions public constructor ( device , polyfillOptions ? ) { this . polyfillOptions_ = { ... kDefaultPolyfillOptions , ... polyfillOptions } ; this . outputSignals_ = { dataTerminalReady : false , requestToSend : false , break : false , } ; this . device_ = device ; this . controlInterface_ = findInterface ( this . device_ , this . polyfillOptions_ . usbControlInterfaceClass as number ) ; this . transferInterface_ = findInterface ( this . device_ , this . polyfillOptions_ . usbTransferInterfaceClass as number ) ; this . inEndpoint_ = findEndpoint ( this . transferInterface_ , 'in' ) ; this . outEndpoint_ = findEndpoint ( this . transferInterface_ , 'out' ) ; } return { ReadableStream } public get readable ( ) : ReadableStream < Uint8Array > | null { if ( ! this . readable_ && this . device_ . opened ) { this . readable_ = new ReadableStream < Uint8Array > ( new UsbEndpointUnderlyingSource ( this . device_ , this . inEndpoint_ , ( ) => { this . readable_ = null ; } ) , new ByteLengthQueuingStrategy ( { highWaterMark : this . serialOptions_ . bufferSize ?? kDefaultBufferSize , } ) ) ; } return this . readable_ ; } return { WritableStream } public get writable ( ) : WritableStream < Uint8Array > | null { if ( ! this . writable_ && this . device_ . opened ) { this . writable_ = new WritableStream ( new UsbEndpointUnderlyingSink ( this . device_ , this . outEndpoint_ , ( ) => { this . writable_ = null ; } ) , new ByteLengthQueuingStrategy ( { highWaterMark : this . serialOptions_ . bufferSize ?? kDefaultBufferSize , } ) ) ; } return this . writable_ ; } param { SerialOptions } options return { Promise < void > } public async open ( options ) { this . serialOptions_ = options ; this . validateOptions ( ) ; try { await this . device_ . open ( ) ; if ( this . device_ . configuration === null ) { await this . device_ . selectConfiguration ( 1 ) ; } await this . device_ . claimInterface ( this . controlInterface_ . interfaceNumber ) ; if ( this . controlInterface_ !== this . transferInterface_ ) { await this . device_ . claimInterface ( this . transferInterface_ . interfaceNumber ) ; } await this . setLineCoding ( ) ; await this . setSignals ( { dataTerminalReady : true } ) ; } catch ( error ) { if ( this . device_ . opened ) { await this . device_ . close ( ) ; } throw new Error ( 'Error setting up device: ' + error . toString ( ) ) ; } } return { Promise < void > } public async close ( ) { const promises = [ ] ; if ( this . readable_ ) { promises . push ( this . readable_ . cancel ( ) ) ; } if ( this . writable_ ) { promises . push ( this . writable_ . abort ( ) ) ; } await Promise . all ( promises ) ; this . readable_ = null ; this . writable_ = null ; if ( this . device_ . opened ) { await this . setSignals ( { dataTerminalReady : false , requestToSend : false } ) ; await this . device_ . close ( ) ; } } return { SerialPortInfo } public getInfo ( ) { return { usbVendorId : this . device_ . vendorId , usbProductId : this . device_ . productId , } ; } param { object } options return { Promise < void > } public reconfigure ( options ) { this . serialOptions_ = { ... this . serialOptions_ , ... options } ; this . validateOptions ( ) ; return this . setLineCoding ( ) ; } param { SerialOutputSignals } signals return { Promise < void > } public async setSignals ( signals ) { this . outputSignals_ = { ... this . outputSignals_ , ... signals } ; if ( signals . dataTerminalReady !== undefined || signals . requestToSend !== undefined ) { const value = ( this . outputSignals_ . dataTerminalReady ? 1 << 0 : 0 ) | ( this . outputSignals_ . requestToSend ? 1 << 1 : 0 ) ; await this . device_ . controlTransferOut ( { 'requestType' : 'class' , 'recipient' : 'interface' , 'request' : kSetControlLineState , 'value' : value , 'index' : this . controlInterface_ . interfaceNumber , } ) ; } if ( signals . break !== undefined ) { const value = this . outputSignals_ . break ? 0xFFFF : 0x0000 ; await this . device_ . controlTransferOut ( { 'requestType' : 'class' , 'recipient' : 'interface' , 'request' : kSendBreak , 'value' : value , 'index' : this . controlInterface_ . interfaceNumber , } ) ; } } /**
     * Checks the serial options for validity and throws an error if it is
     * not valid
     */ private validateOptions ( ) { if ( ! this . isValidBaudRate ( this . serialOptions_ . baudRate ) ) { throw new RangeError ( 'invalid Baud Rate ' + this . serialOptions_ . baudRate ) ; } if ( ! this . isValidDataBits ( this . serialOptions_ . dataBits ) ) { throw new RangeError ( 'invalid dataBits ' + this . serialOptions_ . dataBits ) ; } if ( ! this . isValidStopBits ( this . serialOptions_ . stopBits ) ) { throw new RangeError ( 'invalid stopBits ' + this . serialOptions_ . stopBits ) ; } if ( ! this . isValidParity ( this . serialOptions_ . parity ) ) { throw new RangeError ( 'invalid parity ' + this . serialOptions_ . parity ) ; } } param { number } baudRate return { boolean } private isValidBaudRate ( baudRate ) { return baudRate % 1 === 0 ; } param { number } dataBits return { boolean } private isValidDataBits ( dataBits ) { if ( typeof dataBits === 'undefined' ) { return true ; } return kAcceptableDataBits . includes ( dataBits ) ; } param { number } stopBits return { boolean } private isValidStopBits ( stopBits ) { if ( typeof stopBits === 'undefined' ) { return true ; } return kAcceptableStopBits . includes ( stopBits ) ; } param { string } parity return { boolean } private isValidParity ( parity ) { if ( typeof parity === 'undefined' ) { return true ; } return kAcceptableParity . includes ( parity ) ; } return { Promise } private async setLineCoding ( ) { const buffer = new ArrayBuffer ( 7 ) ; const view = new DataView ( buffer ) ; view . setUint32 ( 0 , this . serialOptions_ . baudRate , true ) ; view . setUint8 ( 4 , kStopBitsIndexMapping . indexOf ( this . serialOptions_ . stopBits ?? kDefaultStopBits ) ) ; view . setUint8 ( 5 , kParityIndexMapping . indexOf ( this . serialOptions_ . parity ?? kDefaultParity ) ) ; view . setUint8 ( 6 , this . serialOptions_ . dataBits ?? kDefaultDataBits ) ; const result = await this . device_ . controlTransferOut ( { 'requestType' : 'class' , 'recipient' : 'interface' , 'request' : kSetLineCoding , 'value' : 0x00 , 'index' : this . controlInterface_ . interfaceNumber , } , buffer ) ; if ( result . status != 'ok' ) { throw new DOMException ( 'NetworkError' , 'Failed to set line coding.' ) ; } } } /** implementation of the global navigator.serial object */ class Serial { param { SerialPortRequestOptions } options param { SerialPolyfillOptions } polyfillOptions return { Promise < SerialPort > } async requestPort ( options ? , polyfillOptions ? ) { polyfillOptions = { ... kDefaultPolyfillOptions , ... polyfillOptions } ; const usbFilters = [ ] ; if ( options && options . filters ) { for ( const filter of options . filters ) { const usbFilter = { classCode : polyfillOptions . usbControlInterfaceClass , } ; if ( filter . usbVendorId !== undefined ) { usbFilter . vendorId = filter . usbVendorId ; } if ( filter . usbProductId !== undefined ) { usbFilter . productId = filter . usbProductId ; } usbFilters . push ( usbFilter ) ; } } if ( usbFilters . length === 0 ) { usbFilters . push ( { classCode : polyfillOptions . usbControlInterfaceClass , } ) ; } const device = await navigator . usb . requestDevice ( { 'filters' : usbFilters } ) ; const port = new SerialPort ( device , polyfillOptions ) ; return port ; } param { SerialPolyfillOptions } polyfillOptions return { Promise < SerialPort [ ] > } async getPorts ( polyfillOptions ? ) { polyfillOptions = { ... kDefaultPolyfillOptions , ... polyfillOptions } ; const devices = await navigator . usb . getDevices ( ) ; const ports = [ ] ; devices . forEach ( ( device ) => { try { const port = new SerialPort ( device , polyfillOptions ) ; ports . push ( port ) ; } catch ( e ) { } } ) ; return ports ; } } export const serial = new Serial ( ) ;