import SimplePeer from 'simple-peer' ; import debugging from 'debug' ; import socket from 'socket.io-client' ; import Transfer from './Transfer' ; import analytics from '../analytics' ; const debug = debugging ( 'blymp:Connection' ) ; /**
 * Connection provides the connections for the transfer to allow communicating with
 * the main server and the partner client.
 * It also handles basic events that could be received from the server
 */ export default class Connection { type String method = SimplePeer . WEBRTC_SUPPORT ? 'webrtc' : 'socket' ; /**
     * Current socket.io connection
     */ socket : SocketIOClient . Socket ; type String socketId : string | null = null ; /**
     * WebRTC peer that we are connected to
     */ peer : SimplePeer . Instance | null = null ; type Object peerSignal : object | null = null ; /**
     * Current transfer
     */ transfer : Transfer ; param transfer constructor ( transfer ) { this . transfer = transfer ; if ( process . env . NODE_ENV === 'development' ) { this . socket = socket ( 'localhost:8080' ) ; } else { this . socket = socket ( ) ; } this . initSocket ( ) ; this . socket . emit ( 'new receiver id' , this . method , ( id ) => { debug ( 'Received own ID' , id ) ; analytics ( "generated-id" ) ; this . transfer . receiverId = id ; this . transfer . triggerUpdate ( ) ; } ) ; } /**
     * Initialize the socket to listen for status changes
     */ initSocket ( ) { const that = this ; this . socket . on ( 'connect' , ( ) => { debug ( 'Connected socket' ) ; if ( ! this . socketId ) { this . socketId = this . socket . id ; } } ) ; this . socket . on ( 'pair partner found' , ( method ) => { debug ( 'Found a pair partner' , method ) ; analytics ( "found-partner" ) ; this . transfer . openPage ( '/connecting' ) ; this . method = method ; if ( method === 'webrtc' ) { debug ( 'Opening connection for WebRTC connection' ) ; } this . connectPeers ( ) ; } ) ; this . socket . on ( 'got partner peer' , ( partner ) => { debug ( 'Got a partner peer' , partner ) ; if ( ! this . peer ) { throw new Error ( 'Internal error: Peer is undefined' ) ; } this . peer . signal ( partner ) ; } ) ; this . socket . on ( 'set transfer method' , ( method ) => { debug ( 'Setting transfer method to' , method ) ; this . method = method ; } ) ; this . socket . on ( 'partner selected file' , ( ) => { this . transfer . openPage ( '/transfer' ) ; analytics ( "partner-selected-file" ) ; this . transfer . downloadFiles ( ) ; } ) ; this . socket . on ( 'partner disconnected' , ( ) => { debug ( 'Partner disconnected from this transfer' ) ; analytics ( "partner-disconnected" ) ; if ( ! this . transfer . finishedTransfer ) { this . transfer . openPage ( '/disconnected' ) ; } } ) ; this . socket . on ( 'proxy to partner' , ( data ) => { if ( data . type && data . type === "transferStatusText" ) { debug ( 'Got transfer status text' , data . text ) ; that . transfer . transferStatusText = data . text ; that . transfer . triggerUpdate ( ) ; } } ) ; } useReceiverId ( id ) { this . socket . emit ( 'use receiver id' , id , this . method , ( response , method ) => { if ( response === true ) { debug ( 'Is valid ID, using' , id , method ) ; analytics ( "entered-valid-id" ) ; this . transfer . isSender = true ; this . transfer . receiverId = id ; this . method = method ; this . transfer . openPage ( '/connecting' ) ; this . connectPeers ( ) ; } else { analytics ( "entered-invalid-id" ) ; this . transfer . isValidId = false ; this . transfer . triggerUpdate ( ) ; debug ( 'Invalid ID' ) ; } } ) ; } /**
     * Lock transfer
     * When selecting files on iOS, the browser will close the socket
     * connection after a few seconds. This is why the browser will
     * "lock" the transfer, allowing the iOS device to reconnect
     * after selecting a file.
     * "Locking" the transfer results in the partner peer not getting
     * informed about this peer disconnecting and is a signal for the server's
     * garbage collector to not remove this transfer yet.
     */ lockTransfer ( ) { debug ( "Locking transfer" ) ; this . socket . emit ( 'lock transfer' , this . transfer . receiverId ) ; } /**
     * Connect to the other peer using WebRTC or socket.
     * This function should be executed after we know who our partner is but before we transfer
     * files.
     */ connectPeers ( ) { debug ( 'Connecting peers' ) ; const openFileSelect = ( ) => { this . transfer . openPage ( '/select-file' ) ; if ( this . transfer . isSender ) { this . lockTransfer ( ) ; setTimeout ( ( ) => { const input = document . querySelector ( 'input[type=file]' ) ; if ( input ) { input . click ( ) ; } } , 500 ) ; } } ; if ( this . method === 'webrtc' ) { const fallbackToSockets = ( ) => { this . method = 'socket' ; this . socket . emit ( 'set transfer method' , 'socket' , this . transfer . receiverId ) ; openFileSelect ( ) ; } ; this . peer = new SimplePeer ( { initiator : this . transfer . isSender , trickle : false , objectMode : true } ) ; let abortConnection ; this . peer . on ( 'signal' , ( data ) => { debug ( 'Got signal from peer: ' , data ) ; this . peerSignal = data ; abortConnection = setTimeout ( ( ) => { if ( ! this . transfer . isSender ) { debug ( 'Peer connection timed out, using sockets instead' ) ; fallbackToSockets ( ) ; } } , 3000 ) ; debug ( 'Sending back peer signal' ) ; this . socket . emit ( 'set peer signal' , data , this . transfer . receiverId , this . transfer . isSender ) ; } ) ; this . peer . on ( 'connect' , ( ) => { debug ( 'Connected to peer' ) ; clearTimeout ( abortConnection as NodeJS . Timeout ) ; openFileSelect ( ) ; } ) ; this . peer . on ( 'error' , ( err ) => { debug ( 'Got error while connecting WebRTC' , err ) ; fallbackToSockets ( ) ; } ) ; } else { debug ( "Using Sockets to transfer so we don't need to connect" ) ; openFileSelect ( ) ; } } /**
     * Prepare our partner for the upcomming transfer
     */ prepareUpload ( ) { this . socket . emit ( 'reconnect' , this . socketId , this . transfer . receiverId ) ; debug ( "Reconnected to socket if connection was lost during file selection" ) ; this . socket . emit ( 'selected file' , this . transfer . receiverId ) ; debug ( "Informed partner about our file selection" ) ; } param value return prepareFileSliceForCurrentMethod ( value ) { let data = value ; if ( this . transfer . connection . method === 'socket' ) { data = '' ; const uintArray = new Uint8Array ( value as ArrayBuffer ) ; uintArray . forEach ( ( byte ) => { data += String . fromCharCode ( byte ) ; } ) ; return data ; } return data ; } /**
     * Send text for transfer status to partner to show the same text
     * across broth devices
     */ sendTransferStatusText ( ) { this . socket . emit ( 'proxy to partner' , this . transfer . receiverId , { type : 'transferStatusText' , text : this . transfer . transferStatusText } ) ; } /**
     * Disconnect from connections so we don't accidentally trigger some events
     */ disconnectAll ( ) { debug ( "Disconnecting all connnections" ) ; if ( this . peer ) { this . peer . destroy ( ) ; } } }