import assert from 'assert' ; import nullthrows from 'nullthrows' ; import pcap from 'pcap' ; import * as MacAddresses from './MacAddresses' ; import * as NetworkInterfaces from './NetworkInterfaces' ; import * as Packets from './Packets' ; export type DashButtonOptions = { networkInterface ? : string ; } ; export type DashButtonListener = ( packet : Object ) => void | Promise < void > ; type GuardedListener = ( packet : Object ) => Promise < Error | undefined > ; let pcapSession ; function getPcapSession ( interfaceName ) { if ( ! pcapSession ) { pcapSession = Packets . createCaptureSession ( interfaceName ) ; } else { assert . equal ( interfaceName , pcapSession . device_name , 'The existing pcap session must be listening on the specified interface' ) ; } return pcapSession ; } export class DashButton { _macAddress : string ; _networkInterface : string ; _packetListener : ( rawPacket : unknown ) => void ; _dashListeners : Set < GuardedListener > ; _isResponding : boolean ; constructor ( macAddress , options = { } ) { this . _macAddress = macAddress . toLowerCase ( ) ; this . _networkInterface = options . networkInterface || nullthrows ( NetworkInterfaces . getDefault ( ) ) ; this . _packetListener = this . _handlePacket . bind ( this ) ; this . _dashListeners = new Set ( ) ; this . _isResponding = false ; } addListener ( listener ) { if ( ! this . _dashListeners . size ) { let session = getPcapSession ( this . _networkInterface ) ; session . addListener ( 'packet' , this . _packetListener ) ; } let guardedListener = this . _createGuardedListener ( listener ) ; this . _dashListeners . add ( guardedListener ) ; return new Subscription ( ( ) => { this . _dashListeners . delete ( guardedListener ) ; if ( ! this . _dashListeners . size ) { let session = getPcapSession ( this . _networkInterface ) ; session . removeListener ( 'packet' , this . _packetListener ) ; if ( ! session . listenerCount ( 'packet' ) ) { session . close ( ) ; } } } ) ; } _createGuardedListener ( listener ) { return async ( ... args ) : Promise < Error | undefined > => { try { await listener ( ... args ) ; return undefined ; } catch ( error ) { return error ; } } ; } async _handlePacket ( rawPacket ) { if ( this . _isResponding ) { return ; } let packet = pcap . decode ( rawPacket ) ; let macAddress = MacAddresses . getEthernetSource ( packet ) ; if ( macAddress !== this . _macAddress ) { return ; } this . _isResponding = true ; try { let listeners = Array . from ( this . _dashListeners ) ; let errors = await Promise . all ( listeners . map ( listener => listener ( packet ) ) ) ; for ( let error of errors ) { if ( error ) { console . error ( `Listener threw an uncaught error:\n${ error . stack }` ) ; } } } finally { this . _isResponding = false ; } } } class Subscription { _remove : ( ) => void ; constructor ( onRemove ) { this . _remove = onRemove ; } remove ( ) { if ( ! this . _remove ) { return ; } this . _remove ( ) ; delete this . _remove ; } }