function LinkmeterClient(ui_callback) { var sessionId = '667146f55f9c2875', widget_style = '$STYLE', ip_local = '216.73.216.92', conn_timeout = '60000'; var sockets = [], rxPending = true, stopped = false, io_started = false, time_meter0, time_w0, time_lastRx, time_last_stats, ip_remote; var responder_cmt = '', responder_banner = ''; var weight_https, tx_enabled = false; var weight = new ArrayBuffer(128*1024); var nStreams = 10; var rx__mbps = 0.0, tx__mbps = 0.0, rtt__ms = 0; var udp_sdp_remote, udp_n_rx_total = 0, udp = null; let ctrl = new WebSocket(webSocketPath_ui('e')); ctrl.onopen = function() {query_location(); ctrl_keepalive();}; ctrl.onmessage = function(e) { let m = JSON.parse(e.data); for (a in m) switch (a) { case 'provider': ui_callback({action:'provider_info', comment: m[a]}); break; case 'city': ui_callback({action:'location_info', city: m[a]}); break; case 'responder_cmt': responder_cmt = m[a]; ui_callback({action:'selected_responder_info', comment:responder_cmt, banner:responder_banner}); break; case 'responder_banner': responder_banner = m[a]; ui_callback({action:'selected_responder_info', comment:responder_cmt, banner:responder_banner}); break; case 'responder_https': responder_https = m[a]; break; case 'responder_webrtc': udp_sdp_remote = atob(m[a]); break; case 'stats': if (rxPending) {rx__mbps = m[a].rx; ui_callback({action:'receive_speed', value__mbps:rx__mbps});} else {tx__mbps = m[a].tx; ui_callback({action:'transmit_speed', value__mbps:tx__mbps});} rtt__ms = m[a].rtt; ui_callback({action:'round_trip_time', value__ms:rtt__ms}); if (ip_local!=m[a].c) {ip_local = m[a].c; ui_callback({action:'local_ip_info', ip:ip_local});} if (ip_remote!=m[a].r) {ip_remote = m[a].r; ui_callback({action:'remote_ip_info', ip:ip_remote});} time_last_stats = Date.now(); break; case 'available_responders': ui_callback({action:'available_responders_list', items:m[a]}); break; } } function state(s) {ui_callback({action:'running_state', state:s});} function ctrl_send(data) {if (ctrl!=null) ctrl.send(JSON.stringify(data));} function ctrl_keepalive() {if (ctrl==null || ctrl.readyState!=1) return; ctrl.send('{}'); setTimeout(function() {ctrl_keepalive();}, 5000);} function webSocketPath_ui(n) {let l = window.location; let p; if (l.protocol === "https:") p = "wss:"; else p = "ws:"; return p+"//" + l.host + "/"+n+"?id="+sessionId;} function webSocketPath_w(n) {return 'wss://'+responder_https+'/'+sessionId;} function streams_count() {return Math.abs(sockets.length);} function add_stream() { if (io_started || streams_count()>=nStreams) return; console.log('WS #',streams_count()+1,': STARTING, PATH=', webSocketPath_w()); let s = new WebSocket(webSocketPath_w()); s.binaryType = 'arraybuffer'; s.onopen = function() { if (!io_started) state('responder_connect'); let i = streams_count(); if (!i) time_w0 = Date.now(); sockets.length = i+1; sockets[i] = s; console.log('WS OPENED, TOTAL=', sockets.length); if (Date.now()-time_w0>=10000 || streams_count()==nStreams) run_rx(); add_stream(); } s.onclose = function(e) {if (s.readyState==3 && streams_count()>0) run_rx();}; s.onmessage = function(e) {if (time_lastRx==undefined) state('measuring_receive_speed'); time_lastRx = Date.now();}; s.onerror = function() {console.log('WS OPEN ERROR'); add_stream();} } function run() { if (stopped) return; time_meter0 = Date.now(); add_stream(); setTimeout(function() { if (io_started || streams_count()) return; console.log('WS: NO OPENED TIMEOUT: RETRY'); add_stream(); }, 10000); run_udp(); } function run_rx() {if (io_started) return; console.log('RX STARTED');io_started = true; let t = new ArrayBuffer(1); for (i=0; i=3000) {console.log('RECV: STOP'); start_tx();} else setTimeout(wait_stop_rx, 100); } function start_tx() { stop_udp(); rxPending = false; ui_callback({action:'receive_speed', value__mbps:rx__mbps, final:true}); state('measuring_transmit_speed'); tx_enabled = true; console.log('SEND: START'); handle_tx(); } function handle_tx() { for (i=0; i=2000; if (!tx_active) for (i=0; i0) tx_active = true; if (!tx_active) finalize(); else setTimeout(wait_stop_tx, 200); } function finalize() { if (stopped) return; stopped = true; ui_callback({action:'transmit_speed', value__mbps:tx__mbps, final:true}); ui_callback({action:'round_trip_time', value__ms:rtt__ms, final:true}); ctrl = null; state('completed'); } return { run() {ctrl_send({action:'run'}); run();}, update_responders_list() {ctrl_send({action:'update_responders_list'});}, set_custom_responder(hash) {ctrl_send({action:'set_custom_responder', 'hash':hash});}, get_local_ip() {return ip_local;} }; function udp_dbg(msg) {ctrl_send({action:'udp_dbg',data:msg});} function run_udp() { if (!('RTCPeerConnection' in window)) {udp_dbg('no_support');console.log('NO UDP SUPPORT'); return;} udp = new RTCPeerConnection({"iceServers":[]}); udp.oniceconnectionstatechange = function() {udp_dbg('ice='+udp.iceConnectionState);}; udp.onicecandidateerror = function(e) {udp_dbg('ice_candidate_error='+e);} try { data = udp.createDataChannel("data", {ordered:false, reliable:false, maxPacketLiveTime:0, negotiated:true, id:0}); data.binaryType = "arraybuffer"; data.onmessage = function(e) { if (stopped) { if (data!=null) {data.close(); data = null; udp_dbg('data_closed'); console.log("UDP: DATA CLOSED");} if (udp!=null) {udp.close(); udp = null; udp_dbg('peer_closed'); console.log("UDP: PEER CLOSED");} return; } function write_U32(dst, offset, u32) {for (var i=0; i<4; i++) dst[offset+i] = (u32>>>(i*8)) & 0xff;} function write_U64(u8_array, offset, u64) {write_U32(u8_array, offset , u64%4294967296); write_U32(u8_array, offset+4, u64/4294967296);} udp_n_rx_total++; let b = new Uint8Array(e.data); write_U64(b, 8, udp_n_rx_total); t = performance.now()*1000000; write_U64(b, 8*3, t); data.send(b); }; data.onopen = function() {udp_dbg('data=open');console.log("UDP: OPENED");} data.onclose = function() {udp_dbg('data=close');console.log("UDP: CLOSED");}; data.onerror = function(e) {udp_dbg('data.error='+e);}; udp.setRemoteDescription({type: "offer", sdp: udp_sdp_remote}); udp.createAnswer( function(sdp) { var lines = sdp.sdp.split('\n'); var i; var ice_pwd = 'a=ice-pwd:eNVfdS2lqA1SNXl2yTXs4WOM\r'; for (i=0; i