function Speedtest() { this._serverList = []; this._selectedServer = null; this._settings = {}; this._state = 0; console.log( "LibreSpeed by Federico Dossena v5.2.2 - https://github.com/librespeed/speedtest" ); } Speedtest.prototype = { constructor: Speedtest, getState: function () { return this._state; }, setParameter: function (parameter, value) { if (this._state != 0) throw "You cannot change the test settings after adding server or starting the test"; this._settings[parameter] = value; if (parameter === "telemetry_extra") { this._originalExtra = this._settings.telemetry_extra; } }, _checkServerDefinition: function (server) { try { if (typeof server.name !== "string") throw "Name string missing from server definition (name)"; if (typeof server.server !== "string") throw "Server address string missing from server definition (server)"; if (server.server.charAt(server.server.length - 1) != "/") server.server += "/"; if (server.server.indexOf("//") == 0) server.server = location.protocol + server.server; if (typeof server.dlURL !== "string") throw "Download URL string missing from server definition (dlURL)"; if (typeof server.ulURL !== "string") throw "Upload URL string missing from server definition (ulURL)"; if (typeof server.pingURL !== "string") throw "Ping URL string missing from server definition (pingURL)"; if (typeof server.getIpURL !== "string") throw "GetIP URL string missing from server definition (getIpURL)"; } catch (e) { throw "Invalid server definition"; } }, addTestPoint: function (server) { this._checkServerDefinition(server); if (this._state == 0) this._state = 1; if (this._state != 1) throw "You can't add a server after server selection"; this._settings.mpot = true; this._serverList.push(server); }, addTestPoints: function (list) { for (var i = 0; i < list.length; i++) this.addTestPoint(list[i]); }, loadServerList: function (url, result) { if (this._state == 0) this._state = 1; if (this._state != 1) throw "You can't add a server after server selection"; this._settings.mpot = true; var xhr = new XMLHttpRequest(); xhr.onload = function () { try { var servers = JSON.parse(xhr.responseText); for (var i = 0; i < servers.length; i++) { this._checkServerDefinition(servers[i]); } this.addTestPoints(servers); result(servers); } catch (e) { result(null); } }.bind(this); xhr.onerror = function () { result(null); } xhr.open("GET", url); xhr.send(); }, getSelectedServer: function () { if (this._state < 2 || this._selectedServer == null) throw "No server is selected"; return this._selectedServer; }, setSelectedServer: function (server) { this._checkServerDefinition(server); if (this._state == 3) throw "You can't select a server while the test is running"; this._selectedServer = server; this._state = 2; }, selectServer: function (result) { if (this._state != 1) { if (this._state == 0) throw "No test points added"; if (this._state == 2) throw "Server already selected"; if (this._state >= 3) throw "You can't select a server while the test is running"; } if (this._selectServerCalled) throw "selectServer already called"; else this._selectServerCalled = true; var select = function (serverList, result) { var PING_TIMEOUT = 2000; var USE_PING_TIMEOUT = true; if (/MSIE.(\d+\.\d+)/i.test(navigator.userAgent)) { USE_PING_TIMEOUT = false; } var ping = function (url, result) { url += (url.match(/\?/) ? "&" : "?") + "cors=true"; var xhr = new XMLHttpRequest(); var t = new Date().getTime(); xhr.onload = function () { if (xhr.responseText.length == 0) { var instspd = new Date().getTime() - t; try { var p = performance.getEntriesByName(url); p = p[p.length - 1]; var d = p.responseStart - p.requestStart; if (d <= 0) d = p.duration; if (d > 0 && d < instspd) instspd = d; } catch (e) { } result(instspd); } else result(-1); }.bind(this); xhr.onerror = function () { result(-1); }.bind(this); xhr.open("GET", url); if (USE_PING_TIMEOUT) { try { xhr.timeout = PING_TIMEOUT; xhr.ontimeout = xhr.onerror; } catch (e) { } } xhr.send(); }.bind(this); var PINGS = 3, SLOW_THRESHOLD = 500; var checkServer = function (server, done) { var i = 0; server.pingT = -1; if (server.server.indexOf(location.protocol) == -1) done(); else { var nextPing = function () { if (i++ == PINGS) { done(); return; } ping( server.server + server.pingURL, function (t) { if (t >= 0) { if (t < server.pingT || server.pingT == -1) server.pingT = t; if (t < SLOW_THRESHOLD) nextPing(); else done(); } else done(); }.bind(this) ); }.bind(this); nextPing(); } }.bind(this); var i = 0; var done = function () { var bestServer = null; for (var i = 0; i < serverList.length; i++) { if ( serverList[i].pingT != -1 && (bestServer == null || serverList[i].pingT < bestServer.pingT) ) bestServer = serverList[i]; } result(bestServer); }.bind(this); var nextServer = function () { if (i == serverList.length) { done(); return; } checkServer(serverList[i++], nextServer); }.bind(this); nextServer(); }.bind(this); var CONCURRENCY = 6; var serverLists = []; for (var i = 0; i < CONCURRENCY; i++) { serverLists[i] = []; } for (var i = 0; i < this._serverList.length; i++) { serverLists[i % CONCURRENCY].push(this._serverList[i]); } var completed = 0; var bestServer = null; for (var i = 0; i < CONCURRENCY; i++) { select( serverLists[i], function (server) { if (server != null) { if (bestServer == null || server.pingT < bestServer.pingT) bestServer = server; } completed++; if (completed == CONCURRENCY) { this._selectedServer = bestServer; this._state = 2; if (result) result(bestServer); } }.bind(this) ); } }, start: function () { if (this._state == 3) throw "Test already running"; this.worker = new Worker("results/speedtest.js?r=" + Math.random()); this.worker.onmessage = function (e) { if (e.data === this._prevData) return; else this._prevData = e.data; var data = JSON.parse(e.data); try { if (this.onupdate) this.onupdate(data); } catch (e) { console.error("Speedtest onupdate event threw exception: " + e); } if (data.testState >= 4) { clearInterval(this.updater); this._state = 4; try { if (this.onend) this.onend(data.testState == 5); } catch (e) { console.error("Speedtest onend event threw exception: " + e); } } }.bind(this); this.updater = setInterval( function () { this.worker.postMessage("status"); }.bind(this), 200 ); if (this._state == 1) throw "When using multiple points of test, you must call selectServer before starting the test"; if (this._state == 2) { this._settings.url_dl = this._selectedServer.server + this._selectedServer.dlURL; this._settings.url_ul = this._selectedServer.server + this._selectedServer.ulURL; this._settings.url_ping = this._selectedServer.server + this._selectedServer.pingURL; this._settings.url_getIp = this._selectedServer.server + this._selectedServer.getIpURL; if (typeof this._originalExtra !== "undefined") { this._settings.telemetry_extra = JSON.stringify({ server: this._selectedServer.name, extra: this._originalExtra }); } else this._settings.telemetry_extra = JSON.stringify({ server: this._selectedServer.name }); } this._state = 3; this.worker.postMessage("start " + JSON.stringify(this._settings)); }, abort: function () { if (this._state < 3) throw "You cannot abort a test that's not started yet"; if (this._state < 4) this.worker.postMessage("abort"); } }; if (typeof window === "undefined") { var testState = -1; var dlStatus = ""; var ulStatus = ""; var pingStatus = ""; var jitterStatus = ""; var clientIp = ""; var dlProgress = 0; var ulProgress = 0; var pingProgress = 0; var testId = null; function tlog(s) { } function tverb(s) { } function twarn(s) { console.warn(s); } var settings = { mpot: false, test_order: "IP_D_U", time_ul_max: 15, time_dl_max: 15, time_auto: true, time_ulGraceTime: 3, time_dlGraceTime: 1.5, count_ping: 10, url_dl: "garbage.php", url_ul: "empty.php", url_ping: "empty.php", url_getIp: "getIP.php", getIp_ispInfo: true, getIp_ispInfo_distance: "km", xhr_dlMultistream: 6, xhr_ulMultistream: 3, xhr_multistreamDelay: 300, xhr_ignoreErrors: 1, xhr_dlUseBlob: false, xhr_ul_blob_megabytes: 20, garbagePhp_chunkSize: 100, enable_quirks: true, ping_allowPerformanceApi: true, overheadCompensationFactor: 1.06, useMebibits: false, data_ul_max: 0, data_dl_max: 0 }; var xhr = null; var interval = null; var test_pointer = 0; function url_sep(url) { return url.match(/\?/) ? "&" : "?"; } this.addEventListener("message", function (e) { var params = e.data.split(" "); if (params[0] === "status") { postMessage( JSON.stringify({ testState: testState, dlStatus: dlStatus, ulStatus: ulStatus, pingStatus: pingStatus, clientIp: clientIp, jitterStatus: jitterStatus, dlProgress: dlProgress, ulProgress: ulProgress, pingProgress: pingProgress, testId: testId }) ); } if (params[0] === "start" && testState === -1) { testState = 0; try { var s = {}; try { var ss = e.data.substring(5); if (ss) s = JSON.parse(ss); } catch (e) { twarn("Error parsing custom settings JSON. Please check your syntax"); } for (var key in s) { if (typeof settings[key] !== "undefined") settings[key] = s[key]; else twarn("Unknown setting ignored: " + key); } var ua = navigator.userAgent; if (settings.enable_quirks || (typeof s.enable_quirks !== "undefined" && s.enable_quirks)) { if (/Firefox.(\d+\.\d+)/i.test(ua)) { if (typeof s.ping_allowPerformanceApi === "undefined") { settings.ping_allowPerformanceApi = false; } } if (/Edge.(\d+\.\d+)/i.test(ua)) { if (typeof s.xhr_dlMultistream === "undefined") { settings.xhr_dlMultistream = 3; } } if (/Chrome.(\d+)/i.test(ua) && !!self.fetch) { if (typeof s.xhr_dlMultistream === "undefined") { settings.xhr_dlMultistream = 5; } } } if (/Edge.(\d+\.\d+)/i.test(ua)) { settings.forceIE11Workaround = true; } if (/PlayStation 4.(\d+\.\d+)/i.test(ua)) { settings.forceIE11Workaround = true; } if (/Chrome.(\d+)/i.test(ua) && /Android|iPhone|iPad|iPod|Windows Phone/i.test(ua)) { settings.xhr_ul_blob_megabytes = 4; } if (/^((?!chrome|android|crios|fxios).)*safari/i.test(ua)) { settings.forceIE11Workaround = true; } if (typeof s.telemetry_level !== "undefined") settings.telemetry_level = s.telemetry_level === "basic" ? 1 : s.telemetry_level === "full" ? 2 : s.telemetry_level === "debug" ? 3 : 0; settings.test_order = settings.test_order.toUpperCase(); } catch (e) { twarn("Possible error in custom test settings. Some settings might not have been applied. Exception: " + e); } tverb(JSON.stringify(settings)); test_pointer = 0; var iRun = false, dRun = false, uRun = false, pRun = false; var runNextTest = function () { if (testState == 5) return; if (test_pointer >= settings.test_order.length) { if (settings.telemetry_level > 0) sendTelemetry(function (id) { testState = 4; if (id != null) testId = id; }); else testState = 4; return; } switch (settings.test_order.charAt(test_pointer)) { case "I": { test_pointer++; if (iRun) { runNextTest(); return; } else iRun = true; getIp(runNextTest); } break; case "D": { test_pointer++; if (dRun) { runNextTest(); return; } else dRun = true; testState = 1; dlTest(runNextTest); } break; case "U": { test_pointer++; if (uRun) { runNextTest(); return; } else uRun = true; testState = 3; ulTest(runNextTest); } break; case "P": { test_pointer++; if (pRun) { runNextTest(); return; } else pRun = true; testState = 2; pingTest(runNextTest); } break; case "_": { test_pointer++; setTimeout(runNextTest, 1000); } break; default: test_pointer++; } }; runNextTest(); } if (params[0] === "abort") { if (testState >= 4) return; tlog("manually aborted"); clearRequests(); runNextTest = null; if (interval) clearInterval(interval); if (settings.telemetry_level > 1) sendTelemetry(function () { }); testState = 5; dlStatus = ""; ulStatus = ""; pingStatus = ""; jitterStatus = ""; clientIp = ""; dlProgress = 0; ulProgress = 0; pingProgress = 0; } }); function clearRequests() { tverb("stopping pending XHRs"); if (xhr) { for (var i = 0; i < xhr.length; i++) { try { xhr[i].onprogress = null; xhr[i].onload = null; xhr[i].onerror = null; } catch (e) { } try { xhr[i].upload.onprogress = null; xhr[i].upload.onload = null; xhr[i].upload.onerror = null; } catch (e) { } try { xhr[i].abort(); } catch (e) { } try { delete xhr[i]; } catch (e) { } } xhr = null; } } var ipCalled = false; var ispInfo = ""; function getIp(done) { tverb("getIp"); if (ipCalled) return; else ipCalled = true; var startT = new Date().getTime(); xhr = new XMLHttpRequest(); xhr.onload = function () { tlog("IP: " + xhr.responseText + ", took " + (new Date().getTime() - startT) + "ms"); try { var data = JSON.parse(xhr.responseText); clientIp = data.processedString; ispInfo = data.rawIspInfo; } catch (e) { clientIp = xhr.responseText; ispInfo = ""; } done(); }; xhr.onerror = function () { tlog("getIp failed, took " + (new Date().getTime() - startT) + "ms"); done(); }; xhr.open("GET", settings.url_getIp + url_sep(settings.url_getIp) + (settings.mpot ? "cors=true&" : "") + (settings.getIp_ispInfo ? "isp=true" + (settings.getIp_ispInfo_distance ? "&distance=" + settings.getIp_ispInfo_distance + "&" : "&") : "&") + "r=" + Math.random(), true); xhr.send(); } var dlCalled = false; function dlTest(done) { tverb("dlTest"); if (dlCalled) return; else dlCalled = true; var totLoaded = 0.0, startT = new Date().getTime(), bonusT = 0, graceTimeDone = false, failed = false; xhr = []; var testStream = function (i, delay) { setTimeout( function () { if (testState !== 1) return; tverb("dl test stream started " + i + " " + delay); var prevLoaded = 0; var x = new XMLHttpRequest(); xhr[i] = x; xhr[i].onprogress = function (event) { tverb("dl stream progress event " + i + " " + event.loaded); if (testState !== 1) { try { x.abort(); } catch (e) { } } var loadDiff = event.loaded <= 0 ? 0 : event.loaded - prevLoaded; if (isNaN(loadDiff) || !isFinite(loadDiff) || loadDiff < 0) return; totLoaded += loadDiff; prevLoaded = event.loaded; var t = new Date().getTime() - startT; if (graceTimeDone && ((settings.time_dl_max > 0 && (t + bonusT) / 1000.0 > settings.time_dl_max) || (settings.data_dl_max > 0 && totLoaded > settings.data_dl_max * 1048576))) { clearRequests(); clearInterval(interval); dlProgress = 1; var speed = totLoaded / (t / 1000.0); dlStatus = ((speed * 8 * settings.overheadCompensationFactor) / (settings.useMebibits ? 1048576 : 1000000)).toFixed(2); done(); } }.bind(this); xhr[i].onload = function () { tverb("dl stream finished " + i); try { xhr[i].abort(); } catch (e) { } testStream(i, 0); }.bind(this); xhr[i].onerror = function () { tverb("dl stream failed " + i); if (settings.xhr_ignoreErrors === 0) failed = true; try { xhr[i].abort(); } catch (e) { } delete xhr[i]; if (settings.xhr_ignoreErrors === 1) testStream(i, 0); }.bind(this); try { if (settings.xhr_dlUseBlob) xhr[i].responseType = "blob"; else xhr[i].responseType = "arraybuffer"; } catch (e) { } xhr[i].open("GET", settings.url_dl + url_sep(settings.url_dl) + (settings.mpot ? "cors=true&" : "") + "r=" + Math.random() + "&ckSize=" + settings.garbagePhp_chunkSize, true); xhr[i].send(); }.bind(this), 1 + delay ); }.bind(this); for (var i = 0; i < settings.xhr_dlMultistream; i++) { testStream(i, settings.xhr_multistreamDelay * i); } interval = setInterval( function () { tverb("DL: " + dlStatus + (graceTimeDone ? "" : " (in grace time)")); var t = new Date().getTime() - startT; if (graceTimeDone) { var timeProg = (t + bonusT) / (settings.time_dl_max * 1000); var dataProg = settings.data_dl_max > 0 ? (totLoaded / (settings.data_dl_max * 1048576)) : 0; dlProgress = Math.max(timeProg, dataProg); } if (t < 200) return; if (!graceTimeDone) { if (t > 1000 * settings.time_dlGraceTime) { if (totLoaded > 0) { startT = new Date().getTime(); bonusT = 0; totLoaded = 0.0; } graceTimeDone = true; } } else { var speed = totLoaded / (t / 1000.0); if (settings.time_auto) { var bonus = (5.0 * speed) / 100000; bonusT += bonus > 400 ? 400 : bonus; } dlStatus = ((speed * 8 * settings.overheadCompensationFactor) / (settings.useMebibits ? 1048576 : 1000000)).toFixed(2); if ((t + bonusT) / 1000.0 > settings.time_dl_max || (settings.data_dl_max > 0 && totLoaded > settings.data_dl_max * 1048576) || failed) { if (failed || isNaN(dlStatus)) dlStatus = "Fail"; clearRequests(); clearInterval(interval); dlProgress = 1; tlog("dlTest: " + dlStatus + ", took " + (new Date().getTime() - startT) + "ms"); done(); } } }.bind(this), 200 ); } var ulCalled = false; function ulTest(done) { tverb("ulTest"); if (ulCalled) return; else ulCalled = true; var r = new ArrayBuffer(1048576); var maxInt = Math.pow(2, 32) - 1; try { r = new Uint32Array(r); for (var i = 0; i < r.length; i++) r[i] = Math.random() * maxInt; } catch (e) { } var req = []; var reqsmall = []; for (var i = 0; i < settings.xhr_ul_blob_megabytes; i++) req.push(r); req = new Blob(req); r = new ArrayBuffer(262144); try { r = new Uint32Array(r); for (var i = 0; i < r.length; i++) r[i] = Math.random() * maxInt; } catch (e) { } reqsmall.push(r); reqsmall = new Blob(reqsmall); var testFunction = function () { var totLoaded = 0.0, startT = new Date().getTime(), bonusT = 0, graceTimeDone = false, failed = false; xhr = []; var testStream = function (i, delay) { setTimeout( function () { if (testState !== 3) return; tverb("ul test stream started " + i + " " + delay); var prevLoaded = 0; var x = new XMLHttpRequest(); xhr[i] = x; var ie11workaround; if (settings.forceIE11Workaround) ie11workaround = true; else { try { xhr[i].upload.onprogress; ie11workaround = false; } catch (e) { ie11workaround = true; } } if (ie11workaround) { xhr[i].onload = xhr[i].onerror = function () { tverb("ul stream progress event (ie11wa)"); totLoaded += reqsmall.size; testStream(i, 0); }; xhr[i].open("POST", settings.url_ul + url_sep(settings.url_ul) + (settings.mpot ? "cors=true&" : "") + "r=" + Math.random(), true); try { xhr[i].setRequestHeader("Content-Encoding", "identity"); } catch (e) { } xhr[i].send(reqsmall); } else { xhr[i].upload.onprogress = function (event) { tverb("ul stream progress event " + i + " " + event.loaded); if (testState !== 3) { try { x.abort(); } catch (e) { } } var loadDiff = event.loaded <= 0 ? 0 : event.loaded - prevLoaded; if (isNaN(loadDiff) || !isFinite(loadDiff) || loadDiff < 0) return; totLoaded += loadDiff; prevLoaded = event.loaded; var t = new Date().getTime() - startT; if (graceTimeDone && ((settings.time_ul_max > 0 && (t + bonusT) / 1000.0 > settings.time_ul_max) || (settings.data_ul_max > 0 && totLoaded > settings.data_ul_max * 1048576))) { clearRequests(); clearInterval(interval); ulProgress = 1; var speed = totLoaded / (t / 1000.0); ulStatus = ((speed * 8 * settings.overheadCompensationFactor) / (settings.useMebibits ? 1048576 : 1000000)).toFixed(2); done(); } }.bind(this); xhr[i].upload.onload = function () { tverb("ul stream finished " + i); testStream(i, 0); }.bind(this); xhr[i].upload.onerror = function () { tverb("ul stream failed " + i); if (settings.xhr_ignoreErrors === 0) failed = true; try { xhr[i].abort(); } catch (e) { } delete xhr[i]; if (settings.xhr_ignoreErrors === 1) testStream(i, 0); }.bind(this); xhr[i].open("POST", settings.url_ul + url_sep(settings.url_ul) + (settings.mpot ? "cors=true&" : "") + "r=" + Math.random(), true); try { xhr[i].setRequestHeader("Content-Encoding", "identity"); } catch (e) { } xhr[i].send(req); } }.bind(this), delay ); }.bind(this); for (var i = 0; i < settings.xhr_ulMultistream; i++) { testStream(i, settings.xhr_multistreamDelay * i); } interval = setInterval( function () { tverb("UL: " + ulStatus + (graceTimeDone ? "" : " (in grace time)")); var t = new Date().getTime() - startT; if (graceTimeDone) { var timeProg = (t + bonusT) / (settings.time_ul_max * 1000); var dataProg = settings.data_ul_max > 0 ? (totLoaded / (settings.data_ul_max * 1048576)) : 0; ulProgress = Math.max(timeProg, dataProg); } if (t < 200) return; if (!graceTimeDone) { if (t > 1000 * settings.time_ulGraceTime) { if (totLoaded > 0) { startT = new Date().getTime(); bonusT = 0; totLoaded = 0.0; } graceTimeDone = true; } } else { var speed = totLoaded / (t / 1000.0); if (settings.time_auto) { var bonus = (5.0 * speed) / 100000; bonusT += bonus > 400 ? 400 : bonus; } ulStatus = ((speed * 8 * settings.overheadCompensationFactor) / (settings.useMebibits ? 1048576 : 1000000)).toFixed(2); if ((t + bonusT) / 1000.0 > settings.time_ul_max || (settings.data_ul_max > 0 && totLoaded > settings.data_ul_max * 1048576) || failed) { if (failed || isNaN(ulStatus)) ulStatus = "Fail"; clearRequests(); clearInterval(interval); ulProgress = 1; tlog("ulTest: " + ulStatus + ", took " + (new Date().getTime() - startT) + "ms"); done(); } } }.bind(this), 200 ); }.bind(this); if (settings.mpot) { tverb("Sending POST request before performing upload test"); xhr = []; xhr[0] = new XMLHttpRequest(); xhr[0].onload = xhr[0].onerror = function () { tverb("POST request sent, starting upload test"); testFunction(); }.bind(this); xhr[0].open("POST", settings.url_ul); xhr[0].send(); } else testFunction(); } var ptCalled = false; function pingTest(done) { tverb("pingTest"); if (ptCalled) return; else ptCalled = true; var startT = new Date().getTime(); var prevT = null; var ping = 0.0; var jitter = 0.0; var i = 0; var prevInstspd = 0; xhr = []; var doPing = function () { tverb("ping"); pingProgress = i / settings.count_ping; prevT = new Date().getTime(); xhr[0] = new XMLHttpRequest(); xhr[0].onload = function () { tverb("pong"); if (i === 0) { prevT = new Date().getTime(); } else { var instspd = new Date().getTime() - prevT; if (settings.ping_allowPerformanceApi) { try { var p = performance.getEntries(); p = p[p.length - 1]; var d = p.responseStart - p.requestStart; if (d <= 0) d = p.duration; if (d > 0 && d < instspd) instspd = d; } catch (e) { tverb("Performance API not supported, using estimate"); } } if (instspd < 1) instspd = prevInstspd; if (instspd < 1) instspd = 1; var instjitter = Math.abs(instspd - prevInstspd); if (i === 1) ping = instspd; else { if (instspd < ping) ping = instspd; if (i === 2) jitter = instjitter; else jitter = instjitter > jitter ? jitter * 0.3 + instjitter * 0.7 : jitter * 0.8 + instjitter * 0.2; } prevInstspd = instspd; } pingStatus = ping.toFixed(2); jitterStatus = jitter.toFixed(2); i++; tverb("ping: " + pingStatus + " jitter: " + jitterStatus); if (i < settings.count_ping) doPing(); else { pingProgress = 1; tlog("ping: " + pingStatus + " jitter: " + jitterStatus + ", took " + (new Date().getTime() - startT) + "ms"); done(); } }.bind(this); xhr[0].onerror = function () { tverb("ping failed"); if (settings.xhr_ignoreErrors === 0) { pingStatus = "Fail"; jitterStatus = "Fail"; clearRequests(); tlog("ping test failed, took " + (new Date().getTime() - startT) + "ms"); pingProgress = 1; done(); } if (settings.xhr_ignoreErrors === 1) doPing(); if (settings.xhr_ignoreErrors === 2) { i++; if (i < settings.count_ping) doPing(); else { pingProgress = 1; tlog("ping: " + pingStatus + " jitter: " + jitterStatus + ", took " + (new Date().getTime() - startT) + "ms"); done(); } } }.bind(this); xhr[0].open("GET", settings.url_ping + url_sep(settings.url_ping) + (settings.mpot ? "cors=true&" : "") + "r=" + Math.random(), true); xhr[0].send(); }.bind(this); doPing(); } function sendTelemetry(done) { done(null); } }