2017-08-28 19:27:45 +03:00
/ * *
* @ description Mesh Agent Transport Module - using websocket relay
* @ author Ylian Saint - Hilaire
* @ version v0 . 0.1 f
* /
// Construct a MeshServer agent direction object
2019-10-16 01:50:11 +03:00
var CreateAgentRedirect = function ( meshserver , module , serverPublicNamePort , authCookie , rauthCookie , domainUrl ) {
2017-08-28 19:27:45 +03:00
var obj = { } ;
obj . m = module ; // This is the inner module (Terminal or Desktop)
module . parent = obj ;
obj . meshserver = meshserver ;
2019-01-29 02:47:54 +03:00
obj . authCookie = authCookie ;
2019-10-16 01:50:11 +03:00
obj . rauthCookie = rauthCookie ;
2020-05-07 09:23:27 +03:00
obj . State = 0 ; // 0 = Disconnected, 1 = Connected, 2 = Connected to server, 3 = End-to-end connection.
2018-02-12 04:13:26 +03:00
obj . nodeid = null ;
2019-12-13 04:45:42 +03:00
obj . options = null ;
2017-08-28 19:27:45 +03:00
obj . socket = null ;
obj . connectstate = - 1 ;
obj . tunnelid = Math . random ( ) . toString ( 36 ) . substring ( 2 ) ; // Generate a random client tunnel id
obj . protocol = module . protocol ; // 1 = SOL, 2 = KVM, 3 = IDER, 4 = Files, 5 = FileTransfer
2018-02-12 04:13:26 +03:00
obj . onStateChanged = null ;
obj . ctrlMsgAllowed = true ;
2017-10-15 09:22:19 +03:00
obj . attemptWebRTC = false ;
2018-01-17 04:30:34 +03:00
obj . webRtcActive = false ;
2018-02-05 22:56:29 +03:00
obj . webSwitchOk = false ;
2017-10-15 09:22:19 +03:00
obj . webchannel = null ;
2018-02-12 04:13:26 +03:00
obj . webrtc = null ;
2018-01-10 07:13:41 +03:00
obj . debugmode = 0 ;
2019-08-13 21:49:05 +03:00
obj . serverIsRecording = false ;
2020-04-14 12:53:40 +03:00
obj . latency = { lastSend : null , current : - 1 , callback : null } ;
2019-05-22 00:19:32 +03:00
if ( domainUrl == null ) { domainUrl = '/' ; }
2017-08-28 19:27:45 +03:00
2019-05-21 02:00:33 +03:00
// Console Message
obj . consoleMessage = null ;
obj . onConsoleMessageChange = null ;
2020-05-07 09:23:27 +03:00
// Session Metadata
obj . metadata = null ;
obj . onMetadataChange = null ;
2017-08-28 19:27:45 +03:00
// Private method
//obj.debug = function (msg) { console.log(msg); }
obj . Start = function ( nodeid ) {
2019-12-18 23:00:08 +03:00
var url2 , url = window . location . protocol . replace ( 'http' , 'ws' ) + '//' + window . location . host + window . location . pathname . substring ( 0 , window . location . pathname . lastIndexOf ( '/' ) ) + '/meshrelay.ashx?browser=1&p=' + obj . protocol + '&nodeid=' + nodeid + '&id=' + obj . tunnelid ;
//if (serverPublicNamePort) { url2 = window.location.protocol.replace('http', 'ws') + '//' + serverPublicNamePort + '/meshrelay.ashx?id=' + obj.tunnelid; } else { url2 = url; }
2019-01-29 02:47:54 +03:00
if ( ( authCookie != null ) && ( authCookie != '' ) ) { url += '&auth=' + authCookie ; }
2020-04-28 23:22:49 +03:00
if ( ( urlargs != null ) && ( urlargs . slowrelay != null ) ) { url += '&slowrelay=' + urlargs . slowrelay ; }
2017-08-28 19:27:45 +03:00
obj . nodeid = nodeid ;
obj . connectstate = 0 ;
obj . socket = new WebSocket ( url ) ;
obj . socket . onopen = obj . xxOnSocketConnected ;
obj . socket . onmessage = obj . xxOnMessage ;
2019-02-08 02:00:10 +03:00
//obj.socket.onmessage = function (e) { console.log('Websocket data', e.data); obj.xxOnMessage(e); }
2019-02-21 02:26:27 +03:00
obj . socket . onerror = function ( e ) { /* console.error(e); */ }
2017-08-28 19:27:45 +03:00
obj . socket . onclose = obj . xxOnSocketClosed ;
obj . xxStateChange ( 1 ) ;
2018-02-12 04:13:26 +03:00
//obj.meshserver.send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: url2 });
2019-12-18 23:00:08 +03:00
var rurl = '*' + domainUrl + 'meshrelay.ashx?p=' + obj . protocol + '&nodeid=' + nodeid + '&id=' + obj . tunnelid ;
2019-10-16 01:59:33 +03:00
if ( ( rauthCookie != null ) && ( rauthCookie != '' ) ) { rurl += ( '&rauth=' + rauthCookie ) ; }
2019-10-16 01:50:11 +03:00
obj . meshserver . send ( { action : 'msg' , type : 'tunnel' , nodeid : obj . nodeid , value : rurl , usage : obj . protocol } ) ;
2019-12-18 23:00:08 +03:00
//obj.debug('Agent Redir Start: ' + url);
2017-08-28 19:27:45 +03:00
}
obj . xxOnSocketConnected = function ( ) {
2018-01-10 07:13:41 +03:00
if ( obj . debugmode == 1 ) { console . log ( 'onSocketConnected' ) ; }
2019-12-18 23:00:08 +03:00
//obj.debug('Agent Redir Socket Connected');
2017-08-28 19:27:45 +03:00
obj . xxStateChange ( 2 ) ;
}
2017-10-15 09:22:19 +03:00
// Called to pass websocket control messages
obj . xxOnControlCommand = function ( msg ) {
2018-01-19 02:43:43 +03:00
var controlMsg ;
try { controlMsg = JSON . parse ( msg ) ; } catch ( e ) { return ; }
2018-02-12 04:13:26 +03:00
if ( controlMsg . ctrlChannel != '102938' ) { obj . xxOnSocketData ( msg ) ; return ; }
2018-02-02 23:46:09 +03:00
//console.log(controlMsg);
2019-12-17 04:20:39 +03:00
if ( ( typeof args != 'undefined' ) && args . redirtrace ) { console . log ( 'RedirRecv' , controlMsg ) ; }
2019-05-21 02:00:33 +03:00
if ( controlMsg . type == 'console' ) {
2020-05-04 00:04:40 +03:00
obj . setConsoleMessage ( controlMsg . msg , controlMsg . msgid , controlMsg . msgargs , controlMsg . timeout ) ;
2020-05-07 09:23:27 +03:00
} else if ( controlMsg . type == 'metadata' ) {
obj . metadata = controlMsg ;
if ( obj . onMetadataChange ) obj . onMetadataChange ( obj . metadata ) ;
2020-04-16 02:20:55 +03:00
} else if ( ( controlMsg . type == 'rtt' ) && ( typeof controlMsg . time == 'number' ) ) {
2020-04-14 08:48:20 +03:00
obj . latency . current = ( new Date ( ) . getTime ( ) ) - controlMsg . time ;
2020-04-14 12:53:40 +03:00
if ( obj . latency . callbacks != null ) { obj . latency . callback ( obj . latency . current ) ; }
2019-05-21 02:00:33 +03:00
} else if ( obj . webrtc != null ) {
2018-01-19 02:43:43 +03:00
if ( controlMsg . type == 'answer' ) {
obj . webrtc . setRemoteDescription ( new RTCSessionDescription ( controlMsg ) , function ( ) { /*console.log('WebRTC remote ok');*/ } , obj . xxCloseWebRTC ) ;
2018-02-05 22:56:29 +03:00
} else if ( controlMsg . type == 'webrtc0' ) {
obj . webSwitchOk = true ; // Other side is ready for switch over
performWebRtcSwitch ( ) ;
2018-01-19 02:43:43 +03:00
} else if ( controlMsg . type == 'webrtc1' ) {
2019-12-18 23:00:08 +03:00
obj . sendCtrlMsg ( '{"ctrlChannel":"102938","type":"webrtc2"}' ) ; // Confirm we got end of data marker, indicates data will no longer be received on websocket.
2018-01-19 02:43:43 +03:00
} else if ( controlMsg . type == 'webrtc2' ) {
// TODO: Resume/Start sending data over WebRTC
}
2017-10-15 09:22:19 +03:00
}
}
2020-04-19 05:44:07 +03:00
// Set the console message
2020-05-04 00:04:40 +03:00
obj . setConsoleMessage = function ( str , id , args , timeout ) {
2020-04-19 05:44:07 +03:00
if ( obj . consoleMessage == str ) return ;
obj . consoleMessage = str ;
2020-05-03 22:54:51 +03:00
obj . consoleMessageId = id ;
obj . consoleMessageArgs = args ;
2020-05-04 00:04:40 +03:00
obj . consoleMessageTimeout = timeout ;
2020-05-03 22:54:51 +03:00
if ( obj . onConsoleMessageChange ) { obj . onConsoleMessageChange ( obj , obj . consoleMessage , obj . consoleMessageId ) ; }
2020-04-19 05:44:07 +03:00
}
2018-07-31 00:21:03 +03:00
obj . sendCtrlMsg = function ( x ) { if ( obj . ctrlMsgAllowed == true ) { if ( ( typeof args != 'undefined' ) && args . redirtrace ) { console . log ( 'RedirSend' , typeof x , x ) ; } try { obj . socket . send ( x ) ; } catch ( ex ) { } } }
2018-02-12 04:13:26 +03:00
2018-02-05 22:56:29 +03:00
function performWebRtcSwitch ( ) {
if ( ( obj . webSwitchOk == true ) && ( obj . webRtcActive == true ) ) {
2020-04-14 13:08:53 +03:00
obj . latency . current = - 1 ; // RTT will no longer be calculated when WebRTC is enabled
2019-12-18 23:00:08 +03:00
obj . sendCtrlMsg ( '{"ctrlChannel":"102938","type":"webrtc0"}' ) ; // Indicate to the meshagent that it can start traffic switchover
obj . sendCtrlMsg ( '{"ctrlChannel":"102938","type":"webrtc1"}' ) ; // Indicate to the meshagent that data traffic will no longer be sent over websocket.
2018-02-05 22:56:29 +03:00
// TODO: Hold/Stop sending data over websocket
if ( obj . onStateChanged != null ) { obj . onStateChanged ( obj , obj . State ) ; }
}
}
2020-04-14 12:53:40 +03:00
2017-08-28 19:27:45 +03:00
obj . xxOnMessage = function ( e ) {
2019-02-08 02:00:10 +03:00
//console.log('Recv', e.data, e.data.byteLength, obj.State);
2017-10-15 09:22:19 +03:00
if ( obj . State < 3 ) {
2019-08-13 21:49:05 +03:00
if ( ( e . data == 'c' ) || ( e . data == 'cr' ) ) {
if ( e . data == 'cr' ) { obj . serverIsRecording = true ; }
2019-12-17 04:20:39 +03:00
if ( obj . options != null ) { delete obj . options . action ; obj . options . type = 'options' ; try { obj . sendCtrlMsg ( JSON . stringify ( obj . options ) ) ; } catch ( ex ) { } }
2018-03-31 01:26:36 +03:00
try { obj . socket . send ( obj . protocol ) ; } catch ( ex ) { }
2017-10-15 09:22:19 +03:00
obj . xxStateChange ( 3 ) ;
if ( obj . attemptWebRTC == true ) {
// Try to get WebRTC setup
var configuration = null ; //{ "iceServers": [ { 'urls': 'stun:stun.services.mozilla.com' }, { 'urls': 'stun:stun.l.google.com:19302' } ] };
if ( typeof RTCPeerConnection !== 'undefined' ) { obj . webrtc = new RTCPeerConnection ( configuration ) ; }
else if ( typeof webkitRTCPeerConnection !== 'undefined' ) { obj . webrtc = new webkitRTCPeerConnection ( configuration ) ; }
2019-12-18 23:00:08 +03:00
if ( ( obj . webrtc != null ) && ( obj . webrtc . createDataChannel ) ) {
obj . webchannel = obj . webrtc . createDataChannel ( 'DataChannel' , { } ) ; // { ordered: false, maxRetransmits: 2 }
2019-02-08 02:00:10 +03:00
obj . webchannel . onmessage = obj . xxOnMessage ;
//obj.webchannel.onmessage = function (e) { console.log('WebRTC data', e.data); obj.xxOnMessage(e); }
2018-02-08 05:45:14 +03:00
obj . webchannel . onopen = function ( ) { obj . webRtcActive = true ; performWebRtcSwitch ( ) ; } ;
2019-02-05 05:06:01 +03:00
obj . webchannel . onclose = function ( event ) { if ( obj . webRtcActive ) { obj . Stop ( ) ; } }
2017-10-15 09:22:19 +03:00
obj . webrtc . onicecandidate = function ( e ) {
if ( e . candidate == null ) {
2019-12-17 04:20:39 +03:00
try { obj . sendCtrlMsg ( JSON . stringify ( obj . webrtcoffer ) ) ; } catch ( ex ) { } // End of candidates, send the offer
2017-10-15 09:22:19 +03:00
} else {
2019-12-18 23:00:08 +03:00
obj . webrtcoffer . sdp += ( 'a=' + e . candidate . candidate + '\r\n' ) ; // New candidate, add it to the SDP
2017-10-15 09:22:19 +03:00
}
}
2018-02-02 23:46:09 +03:00
obj . webrtc . oniceconnectionstatechange = function ( ) {
if ( obj . webrtc != null ) {
2018-12-20 23:12:24 +03:00
if ( obj . webrtc . iceConnectionState == 'disconnected' ) { if ( obj . webRtcActive == true ) { obj . Stop ( ) ; } else { obj . xxCloseWebRTC ( ) ; } }
2018-04-20 04:19:15 +03:00
else if ( obj . webrtc . iceConnectionState == 'failed' ) { obj . xxCloseWebRTC ( ) ; }
2018-02-02 23:46:09 +03:00
}
}
2017-10-15 09:22:19 +03:00
obj . webrtc . createOffer ( function ( offer ) {
// Got the offer
obj . webrtcoffer = offer ;
2018-01-17 04:30:34 +03:00
obj . webrtc . setLocalDescription ( offer , function ( ) { /*console.log('WebRTC local ok');*/ } , obj . xxCloseWebRTC ) ;
2017-10-15 09:22:19 +03:00
} , obj . xxCloseWebRTC , { mandatory : { OfferToReceiveAudio : false , OfferToReceiveVideo : false } } ) ;
}
}
2020-04-14 12:53:40 +03:00
2017-10-15 09:22:19 +03:00
return ;
}
}
2018-01-17 04:30:34 +03:00
2017-10-15 09:22:19 +03:00
if ( typeof e . data == 'string' ) {
// Control messages, most likely WebRTC setup
obj . xxOnControlCommand ( e . data ) ;
2018-01-17 04:30:34 +03:00
return ;
}
2019-02-08 02:00:10 +03:00
if ( typeof e . data == 'object' ) {
if ( fileReaderInuse == true ) { fileReaderAcc . push ( e . data ) ; return ; }
2020-01-24 02:15:56 +03:00
if ( fileReader . readAsBinaryString && ( obj . m . ProcessBinaryData == null ) ) {
2019-02-08 02:00:10 +03:00
// Chrome & Firefox (Draft)
fileReaderInuse = true ;
fileReader . readAsBinaryString ( new Blob ( [ e . data ] ) ) ;
} else if ( fileReader . readAsArrayBuffer ) {
// Chrome & Firefox (Spec)
fileReaderInuse = true ;
fileReader . readAsArrayBuffer ( e . data ) ;
} else {
// IE10, readAsBinaryString does not exist, use an alternative.
2019-12-18 23:00:08 +03:00
var binary = '' , bytes = new Uint8Array ( e . data ) , length = bytes . byteLength ;
2019-02-08 02:00:10 +03:00
for ( var i = 0 ; i < length ; i ++ ) { binary += String . fromCharCode ( bytes [ i ] ) ; }
obj . xxOnSocketData ( binary ) ;
}
} else {
// If we get a string object, it maybe the WebRTC confirm. Ignore it.
obj . xxOnSocketData ( e . data ) ;
}
2020-04-14 12:53:40 +03:00
// Request RTT mesure, don't use this if WebRTC is active
if ( obj . webRtcActive != true ) {
var ticks = new Date ( ) . getTime ( ) ;
2020-04-14 13:08:53 +03:00
if ( ( obj . latency . lastSend == null ) || ( ( ticks - obj . latency . lastSend ) > 5000 ) ) { obj . latency . lastSend = ticks ; obj . sendCtrlMsg ( '{"ctrlChannel":"102938","type":"rtt","time":' + ticks + '}' ) ; }
2020-04-14 12:53:40 +03:00
}
2017-08-28 19:27:45 +03:00
} ;
2019-02-08 02:00:10 +03:00
// Setup the file reader
var fileReader = new FileReader ( ) ;
var fileReaderInuse = false , fileReaderAcc = [ ] ;
2020-05-03 00:44:25 +03:00
if ( fileReader . readAsBinaryString && ( obj . m . ProcessBinaryData == null ) ) {
2019-02-08 02:00:10 +03:00
// Chrome & Firefox (Draft)
fileReader . onload = function ( e ) { obj . xxOnSocketData ( e . target . result ) ; if ( fileReaderAcc . length == 0 ) { fileReaderInuse = false ; } else { fileReader . readAsBinaryString ( new Blob ( [ fileReaderAcc . shift ( ) ] ) ) ; } }
} else if ( fileReader . readAsArrayBuffer ) {
// Chrome & Firefox (Spec)
fileReader . onloadend = function ( e ) { obj . xxOnSocketData ( e . target . result ) ; if ( fileReaderAcc . length == 0 ) { fileReaderInuse = false ; } else { fileReader . readAsArrayBuffer ( fileReaderAcc . shift ( ) ) ; } }
}
2017-08-28 19:27:45 +03:00
obj . xxOnSocketData = function ( data ) {
if ( ! data || obj . connectstate == - 1 ) return ;
if ( typeof data === 'object' ) {
2020-01-24 02:15:56 +03:00
if ( obj . m . ProcessBinaryData ) { return obj . m . ProcessBinaryData ( data ) ; }
2017-08-28 19:27:45 +03:00
// This is an ArrayBuffer, convert it to a string array (used in IE)
2019-12-18 23:00:08 +03:00
var binary = '' , bytes = new Uint8Array ( data ) , length = bytes . byteLength ;
2017-08-28 19:27:45 +03:00
for ( var i = 0 ; i < length ; i ++ ) { binary += String . fromCharCode ( bytes [ i ] ) ; }
data = binary ;
}
else if ( typeof data !== 'string' ) return ;
2019-12-18 23:00:08 +03:00
//console.log('xxOnSocketData', rstr2hex(data));
2019-12-17 04:20:39 +03:00
if ( ( typeof args != 'undefined' ) && args . redirtrace ) { console . log ( 'RedirRecv' , typeof data , data . length , ( data [ 0 ] == '{' ) ? data : rstr2hex ( data ) . substring ( 0 , 64 ) ) ; }
2017-08-28 19:27:45 +03:00
return obj . m . ProcessData ( data ) ;
}
2018-07-06 20:13:19 +03:00
obj . sendText = function ( x ) {
if ( typeof x != 'string' ) { x = JSON . stringify ( x ) ; } // Turn into a string if needed
obj . send ( encode _utf8 ( x ) ) ; // Encode UTF8 correctly
}
2018-02-12 04:13:26 +03:00
obj . send = function ( x ) {
2019-12-18 23:00:08 +03:00
//obj.debug('Agent Redir Send(' + obj.webRtcActive + ', ' + x.length + '): ' + rstr2hex(x));
//console.log('Agent Redir Send(' + obj.webRtcActive + ', ' + x.length + '): ' + ((typeof x == 'string')?x:rstr2hex(x)));
2019-12-17 04:20:39 +03:00
if ( ( typeof args != 'undefined' ) && args . redirtrace ) { console . log ( 'RedirSend' , typeof x , x . length , ( x [ 0 ] == '{' ) ? x : rstr2hex ( x ) . substring ( 0 , 64 ) ) ; }
2018-03-31 01:26:36 +03:00
try {
if ( obj . socket != null && obj . socket . readyState == WebSocket . OPEN ) {
if ( typeof x == 'string' ) {
if ( obj . debugmode == 1 ) {
var b = new Uint8Array ( x . length ) , c = [ ] ;
for ( var i = 0 ; i < x . length ; ++ i ) { b [ i ] = x . charCodeAt ( i ) ; c . push ( x . charCodeAt ( i ) ) ; }
if ( obj . webRtcActive == true ) { obj . webchannel . send ( b . buffer ) ; } else { obj . socket . send ( b . buffer ) ; }
//console.log('Send', c);
} else {
var b = new Uint8Array ( x . length ) ;
for ( var i = 0 ; i < x . length ; ++ i ) { b [ i ] = x . charCodeAt ( i ) ; }
if ( obj . webRtcActive == true ) { obj . webchannel . send ( b . buffer ) ; } else { obj . socket . send ( b . buffer ) ; }
}
2018-01-10 07:13:41 +03:00
} else {
2018-03-31 01:26:36 +03:00
//if (obj.debugmode == 1) { console.log('Send', x); }
if ( obj . webRtcActive == true ) { obj . webchannel . send ( x ) ; } else { obj . socket . send ( x ) ; }
2018-01-10 07:13:41 +03:00
}
2017-08-28 19:27:45 +03:00
}
2018-03-31 01:26:36 +03:00
} catch ( ex ) { }
2017-08-28 19:27:45 +03:00
}
obj . xxOnSocketClosed = function ( ) {
2019-12-18 23:00:08 +03:00
//obj.debug('Agent Redir Socket Closed');
2018-01-17 04:30:34 +03:00
//if (obj.debugmode == 1) { console.log('onSocketClosed'); }
2018-01-10 07:13:41 +03:00
obj . Stop ( 1 ) ;
2017-08-28 19:27:45 +03:00
}
obj . xxStateChange = function ( newstate ) {
if ( obj . State == newstate ) return ;
obj . State = newstate ;
obj . m . xxStateChange ( obj . State ) ;
if ( obj . onStateChanged != null ) obj . onStateChanged ( obj , obj . State ) ;
}
2018-04-20 04:19:15 +03:00
// Close the WebRTC connection, should be called if a problem occurs during WebRTC setup.
obj . xxCloseWebRTC = function ( ) {
2018-04-18 05:00:31 +03:00
if ( obj . webchannel != null ) { try { obj . webchannel . close ( ) ; } catch ( e ) { } obj . webchannel = null ; }
if ( obj . webrtc != null ) { try { obj . webrtc . close ( ) ; } catch ( e ) { } obj . webrtc = null ; }
obj . webRtcActive = false ;
2018-04-20 04:19:15 +03:00
}
obj . Stop = function ( x ) {
if ( obj . debugmode == 1 ) { console . log ( 'stop' , x ) ; }
2020-04-14 12:53:40 +03:00
2018-04-20 04:19:15 +03:00
// Clean up WebRTC
obj . xxCloseWebRTC ( ) ;
2018-04-18 05:00:31 +03:00
2019-12-18 23:00:08 +03:00
//obj.debug('Agent Redir Socket Stopped');
2017-08-28 19:27:45 +03:00
obj . connectstate = - 1 ;
2018-01-19 02:43:43 +03:00
if ( obj . socket != null ) {
2019-12-18 23:00:08 +03:00
try { if ( obj . socket . readyState == 1 ) { obj . sendCtrlMsg ( '{"ctrlChannel":"102938","type":"close"}' ) ; obj . socket . close ( ) ; } } catch ( e ) { }
2018-01-19 02:43:43 +03:00
obj . socket = null ;
}
obj . xxStateChange ( 0 ) ;
2017-08-28 19:27:45 +03:00
}
return obj ;
}