2020-09-18 21:33:45 +00:00
|
|
|
/**
|
|
|
|
* @file LibreSpeed - Main
|
|
|
|
* @author Federico Dossena
|
|
|
|
* @license LGPL-3.0-only
|
|
|
|
* https://github.com/librespeed/speedtest/
|
2019-06-30 05:03:06 +00:00
|
|
|
*/
|
|
|
|
|
2020-09-18 21:33:45 +00:00
|
|
|
/**
|
|
|
|
* This is the main interface between your webpage and the speedtest.
|
|
|
|
* It hides the speedtest web worker to the page, and provides many convenient functions to control the test.
|
|
|
|
*
|
|
|
|
* The best way to learn how to use this is to look at the basic example, but here's some documentation.
|
|
|
|
*
|
|
|
|
* - To initialize the test, create a new Speedtest object: `const s = new Speedtest();`.
|
|
|
|
*
|
|
|
|
* You can think of this as a finite state machine. These are the states (use getState() to see them):
|
|
|
|
* - __0__: here you can change the speedtest settings (such as test duration) with the setParameter("parameter",value)
|
|
|
|
* method. From here you can either start the test using start() (goes to state 3) or you can add multiple test
|
|
|
|
* points using addTestPoint(server) or addTestPoints(serverList) (goes to state 1). Additionally, this is the
|
|
|
|
* perfect moment to set up callbacks for the onupdate(data) and onend(aborted) events.
|
|
|
|
* - __1__: here you can add test points. You only need to do this if you want to use multiple test points.
|
|
|
|
* A server is defined as an object like this:
|
|
|
|
* ```
|
|
|
|
* {
|
|
|
|
* name: "User friendly name",
|
|
|
|
* server: "http://yourBackend.com/", // server URL. If both http & https are supported, just use // without protocol
|
|
|
|
* dlURL: "garbage.php", // path to garbage.php or its replacement on the server
|
|
|
|
* ulURL: "empty.php", // path to empty.php or its replacement on the server
|
|
|
|
* pingURL: "empty.php", // path to empty.php or its replacement on the server
|
|
|
|
* getIpURL: "getIP.php", // path to getIP.php or its replacement on the server
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
* While in state 1, you can only add test points, you cannot change the test settings. When you're done, use
|
|
|
|
* selectServer(callback) to select the test point with the lowest ping. This is asynchronous, when it's done,
|
|
|
|
* it will call your callback function and move to state 2. Calling setSelectedServer(server) will manually
|
|
|
|
* select a server and move to state 2.
|
|
|
|
* - __2__: test point selected, ready to start the test. Use start() to begin, this will move to state 3.
|
|
|
|
* - __3__: test running. Here, your onupdate event calback will be called periodically, with data coming
|
|
|
|
* from the worker about speed and progress. A data object will be passed to your onupdate function,
|
|
|
|
* with the following items:
|
|
|
|
* ```
|
|
|
|
* {
|
|
|
|
* dlStatus: number, // download speed in mbps
|
|
|
|
* ulStatus: number, // upload speed in mbps
|
|
|
|
* pingStatus: number, // ping in ms
|
|
|
|
* jitterStatus: number, // jitter in ms
|
|
|
|
* dlProgress: number, // progress of the download test as a float 0-1
|
|
|
|
* ulProgress: number, // progress of the upload test as a float 0-1
|
|
|
|
* pingProgress: number, // progress of the ping/jitter test as a float 0-1
|
|
|
|
* testState: number, // -1: not started, 0: starting, 1: download, 2: ping+jitter, 3: upload, 4: finished, 5: aborted
|
|
|
|
* clientIp: string, // IP address of the client performing the test (and optionally ISP and distance)
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
* At the end of the test, the onend function will be called, with a boolean specifying whether the test was
|
|
|
|
* aborted or if it ended normally. The test can be aborted at any time with abort().
|
|
|
|
* At the end of the test, it will move to state 4.
|
|
|
|
* - __4__: test finished. You can run it again by calling start() if you want.
|
|
|
|
*/
|
|
|
|
class Speedtest {
|
|
|
|
constructor() {
|
|
|
|
this._serverList = []; //when using multiple points of test, this is a list of test points
|
|
|
|
this._selectedServer = null; //when using multiple points of test, this is the selected server
|
|
|
|
this._settings = {}; //settings for the speedtest worker
|
|
|
|
this._state = 0; //0=adding settings, 1=adding servers, 2=server selection done, 3=test running, 4=done
|
|
|
|
this.onupdate = undefined;
|
|
|
|
this.onend = undefined;
|
|
|
|
console.log("LibreSpeed by Federico Dossena v5.2 - https://github.com/librespeed/speedtest");
|
|
|
|
}
|
2019-06-30 05:03:06 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the state of the test: 0=adding settings, 1=adding servers, 2=server selection done, 3=test running, 4=done
|
2020-09-18 21:33:45 +00:00
|
|
|
* @returns {number} 0=adding settings, 1=adding servers, 2=server selection done, 3=test running, 4=done
|
2019-06-30 05:03:06 +00:00
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
getState() {
|
2019-06-30 05:03:06 +00:00
|
|
|
return this._state;
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
|
|
|
|
2019-06-30 05:03:06 +00:00
|
|
|
/**
|
|
|
|
* Change one of the test settings from their defaults.
|
|
|
|
* Invalid values or nonexistant parameters will be ignored by the speedtest worker.
|
2020-09-18 21:33:45 +00:00
|
|
|
* @param {string} parameter - string with the name of the parameter that you want to set
|
|
|
|
* @param value - new value for the parameter
|
2019-06-30 05:03:06 +00:00
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
setParameter(parameter, value) {
|
|
|
|
if (this._state !== 0)
|
|
|
|
throw new Error("You cannot change the test settings after adding server or starting the test");
|
2019-06-30 05:03:06 +00:00
|
|
|
this._settings[parameter] = value;
|
2020-09-18 21:33:45 +00:00
|
|
|
if (parameter === "telemetry_extra") {
|
|
|
|
this._originalExtra = this._settings.telemetry_extra;
|
2019-08-31 17:22:42 +00:00
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
|
|
|
|
2019-06-30 05:03:06 +00:00
|
|
|
/**
|
|
|
|
* Used internally to check if a server object contains all the required elements.
|
|
|
|
* Also fixes the server URL if needed.
|
2020-09-18 21:33:45 +00:00
|
|
|
* @param {Server} server
|
2019-06-30 05:03:06 +00:00
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
_checkServerDefinition(server) {
|
2019-06-30 05:03:06 +00:00
|
|
|
try {
|
|
|
|
if (typeof server.name !== "string")
|
2020-09-18 21:33:45 +00:00
|
|
|
throw new Error("Name string missing from server definition (name)");
|
2019-06-30 05:03:06 +00:00
|
|
|
if (typeof server.server !== "string")
|
2020-09-18 21:33:45 +00:00
|
|
|
throw new Error("Server address string missing from server definition (server)");
|
|
|
|
if (server.server.charAt(server.server.length - 1) !== "/")
|
2019-06-30 05:03:06 +00:00
|
|
|
server.server += "/";
|
2020-09-18 21:33:45 +00:00
|
|
|
if (server.server.indexOf("//") === 0)
|
2019-06-30 05:03:06 +00:00
|
|
|
server.server = location.protocol + server.server;
|
|
|
|
if (typeof server.dlURL !== "string")
|
2020-09-18 21:33:45 +00:00
|
|
|
throw new Error("Download URL string missing from server definition (dlURL)");
|
2019-06-30 05:03:06 +00:00
|
|
|
if (typeof server.ulURL !== "string")
|
2020-09-18 21:33:45 +00:00
|
|
|
throw new Error("Upload URL string missing from server definition (ulURL)");
|
2019-06-30 05:03:06 +00:00
|
|
|
if (typeof server.pingURL !== "string")
|
2020-09-18 21:33:45 +00:00
|
|
|
throw new Error("Ping URL string missing from server definition (pingURL)");
|
2019-06-30 05:03:06 +00:00
|
|
|
if (typeof server.getIpURL !== "string")
|
2020-09-18 21:33:45 +00:00
|
|
|
throw new Error("GetIP URL string missing from server definition (getIpURL)");
|
2019-06-30 05:03:06 +00:00
|
|
|
} catch (e) {
|
2020-09-18 21:33:45 +00:00
|
|
|
throw new Error(`Invalid server definition: ${e.message}`);
|
2019-06-30 05:03:06 +00:00
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
|
|
|
|
2019-06-30 05:03:06 +00:00
|
|
|
/**
|
|
|
|
* Add a test point (multiple points of test)
|
2020-09-18 21:33:45 +00:00
|
|
|
* @param {Server} server - the server to be added as an object. Must contain the following elements:
|
|
|
|
* ```
|
|
|
|
* name: "User friendly name"
|
|
|
|
* server: "http://yourBackend.com/" // server URL. If both http & https are supported, just use // without protocol
|
|
|
|
* dlURL: "garbage.php" // path to garbage.php or its replacement on the server
|
|
|
|
* ulURL: "empty.php" // path to empty.php or its replacement on the server
|
|
|
|
* pingURL: "empty.php" // path to empty.php or its replacement on the server
|
|
|
|
* getIpURL: "getIP.php" // path to getIP.php or its replacement on the server
|
|
|
|
* ```
|
2019-06-30 05:03:06 +00:00
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
addTestPoint(server) {
|
2019-06-30 05:03:06 +00:00
|
|
|
this._checkServerDefinition(server);
|
2020-09-18 21:33:45 +00:00
|
|
|
if (this._state === 0) this._state = 1;
|
|
|
|
if (this._state !== 1) throw new Error("You can't add a server after server selection");
|
2019-06-30 05:03:06 +00:00
|
|
|
this._settings.mpot = true;
|
|
|
|
this._serverList.push(server);
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
|
|
|
|
2019-06-30 05:03:06 +00:00
|
|
|
/**
|
2020-09-18 21:33:45 +00:00
|
|
|
* Same as `addTestPoint`, but you can pass an array of servers
|
|
|
|
* @param {Server[]} list - array of server objects
|
2019-06-30 05:03:06 +00:00
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
addTestPoints(list) {
|
|
|
|
for (let i = 0; i < list.length; i++) {
|
|
|
|
this.addTestPoint(list[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-09 07:16:06 +00:00
|
|
|
/**
|
|
|
|
* Load a JSON server list from URL (multiple points of test)
|
2020-09-18 21:33:45 +00:00
|
|
|
* @param {string} url - the url where the server list can be fetched.
|
|
|
|
* Must be an array with objects containing the following elements:
|
|
|
|
* ```
|
|
|
|
* name: "User friendly name",
|
|
|
|
* server: "http://yourBackend.com/", // server URL. If both http & https are supported, just use // without protocol
|
|
|
|
* dlURL: "garbage.php", // path to garbage.php or its replacement on the server
|
|
|
|
* ulURL: "empty.php", // path to empty.php or its replacement on the server
|
|
|
|
* pingURL: "empty.php", // path to empty.php or its replacement on the server
|
|
|
|
* getIpURL: "getIP.php", // path to getIP.php or its replacement on the server
|
|
|
|
* ```
|
|
|
|
* @param {(x: Server[] | null) => void} result - callback to be called when the list is loaded correctly.
|
|
|
|
* An array with the loaded servers will be passed to this function, or null if it failed.
|
2020-03-09 07:16:06 +00:00
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
loadServerList(url, result) {
|
|
|
|
if (this._state === 0) this._state = 1;
|
|
|
|
if (this._state !== 1) throw new Error("You can't add a server after server selection");
|
2020-03-09 07:16:06 +00:00
|
|
|
this._settings.mpot = true;
|
2020-09-18 21:33:45 +00:00
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
xhr.onload = () => {
|
|
|
|
try {
|
|
|
|
/** @type {Server[]} */
|
|
|
|
const servers = JSON.parse(xhr.responseText);
|
|
|
|
for (let i = 0; i < servers.length; i++){
|
2020-03-09 07:16:06 +00:00
|
|
|
this._checkServerDefinition(servers[i]);
|
|
|
|
}
|
|
|
|
this.addTestPoints(servers);
|
|
|
|
result(servers);
|
2020-09-18 21:33:45 +00:00
|
|
|
} catch (e) {
|
2020-03-09 07:16:06 +00:00
|
|
|
result(null);
|
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
};
|
|
|
|
xhr.onerror = () => { result(null); };
|
|
|
|
xhr.open("GET", url);
|
2020-03-09 07:16:06 +00:00
|
|
|
xhr.send();
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
|
|
|
|
2019-06-30 05:03:06 +00:00
|
|
|
/**
|
|
|
|
* Returns the selected server (multiple points of test)
|
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
getSelectedServer() {
|
|
|
|
if (this._state < 2 || this._selectedServer == null) throw new Error("No server is selected");
|
2019-06-30 05:03:06 +00:00
|
|
|
return this._selectedServer;
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef {Object} Server
|
|
|
|
* @property {string} name user friendly name
|
|
|
|
* @property {string} server URL to your server. You can specify http:// or https://. If your server supports both, just write // without the protocol
|
|
|
|
* @property {string} dlURL path to __garbage.php__ or its replacement on the server
|
|
|
|
* @property {string} ulURL path to __empty.php__ or its replacement on the server
|
|
|
|
* @property {string} pingURL path to __empty.php__ or its replacement on the server. This is used to ping the server by this selector
|
|
|
|
* @property {string} getIpURL path to __getIP.php__ or its replacement on the server
|
|
|
|
* @property {number} pingT calculated (do not set). either the best ping we got from the server or -1 if something went wrong.
|
|
|
|
*/
|
2019-06-30 05:03:06 +00:00
|
|
|
/**
|
|
|
|
* Manually selects one of the test points (multiple points of test)
|
2020-09-18 21:33:45 +00:00
|
|
|
* @param {Server} server
|
2019-06-30 05:03:06 +00:00
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
setSelectedServer(server) {
|
2019-06-30 05:03:06 +00:00
|
|
|
this._checkServerDefinition(server);
|
2020-09-18 21:33:45 +00:00
|
|
|
if (this._state === 3) throw new Error("You can't select a server while the test is running");
|
2019-06-30 05:03:06 +00:00
|
|
|
this._selectedServer = server;
|
|
|
|
this._state = 2;
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
|
|
|
|
2019-06-30 05:03:06 +00:00
|
|
|
/**
|
2020-09-18 21:33:45 +00:00
|
|
|
* Automatically selects a server from the list of added test points.
|
|
|
|
* The server with the lowest ping will be chosen (multiple points of test).
|
|
|
|
* The process is asynchronous and the passed result callback function will
|
|
|
|
* be called when it's done, then the test can be started.
|
|
|
|
* @param {(x: Server) => void} result
|
2019-06-30 05:03:06 +00:00
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
selectServer(result) {
|
|
|
|
if (this._state !== 1) {
|
|
|
|
if (this._state === 0) throw new Error("No test points added");
|
|
|
|
if (this._state === 2) throw new Error("Server already selected");
|
|
|
|
if (this._state >= 3) throw new Error("You can't select a server while the test is running");
|
2019-06-30 05:03:06 +00:00
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
if (this._selectServerCalled) throw new Error("selectServer already called");
|
|
|
|
else this._selectServerCalled = true;
|
|
|
|
/**
|
|
|
|
* This function goes through a list of servers. For each server, the ping is measured,
|
|
|
|
* then the server with the function result is called with the best server,
|
|
|
|
* or null if all the servers were down.
|
|
|
|
* @param {Server[]} serverList
|
|
|
|
* @param {(x: Server | null) => void} result parameter is either the best server or null if all servers were down
|
2019-06-30 05:03:06 +00:00
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
const select = (serverList, result) => {
|
|
|
|
const PING_TIMEOUT = 2000;
|
|
|
|
// will be disabled on unsupported browsers
|
|
|
|
let USE_PING_TIMEOUT = true;
|
2019-06-30 05:03:06 +00:00
|
|
|
if (/MSIE.(\d+\.\d+)/i.test(navigator.userAgent)) {
|
2020-09-18 21:33:45 +00:00
|
|
|
// IE11 doesn't support XHR timeout
|
2019-06-30 05:03:06 +00:00
|
|
|
USE_PING_TIMEOUT = false;
|
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
/**
|
|
|
|
* Pings the specified URL, then calls the function result. Result will receive a parameter
|
|
|
|
* which is either the time it took to ping the URL, or -1 if something went wrong.
|
|
|
|
* @param {string} url
|
|
|
|
* @param {(pingMs: number) => void} result parameter is either the time it took to ping the URL, or -1 if something went wrong
|
|
|
|
*/
|
|
|
|
const ping = (url, result) => {
|
2019-06-30 05:03:06 +00:00
|
|
|
url += (url.match(/\?/) ? "&" : "?") + "cors=true";
|
2020-09-18 21:33:45 +00:00
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
const t = new Date().getTime();
|
|
|
|
xhr.onload = () => {
|
|
|
|
// We expect an empty response
|
|
|
|
if (xhr.responseText.length === 0) {
|
|
|
|
// Rough timing estimate
|
|
|
|
let instspd = new Date().getTime() - t;
|
2019-06-30 05:03:06 +00:00
|
|
|
try {
|
2020-09-18 21:33:45 +00:00
|
|
|
// Try to get more accurate timing using Performance API
|
|
|
|
const pArr = performance.getEntriesByName(url);
|
|
|
|
const p = pArr[pArr.length - 1];
|
|
|
|
let d = p.responseStart - p.requestStart;
|
2019-06-30 05:03:06 +00:00
|
|
|
if (d <= 0) d = p.duration;
|
|
|
|
if (d > 0 && d < instspd) instspd = d;
|
|
|
|
} catch (e) {}
|
|
|
|
result(instspd);
|
2020-09-18 21:33:45 +00:00
|
|
|
} else {
|
|
|
|
result(-1);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
xhr.onerror = () => { result(-1); };
|
2019-06-30 05:03:06 +00:00
|
|
|
xhr.open("GET", url);
|
|
|
|
if (USE_PING_TIMEOUT) {
|
|
|
|
try {
|
|
|
|
xhr.timeout = PING_TIMEOUT;
|
|
|
|
xhr.ontimeout = xhr.onerror;
|
|
|
|
} catch (e) {}
|
|
|
|
}
|
|
|
|
xhr.send();
|
2020-09-18 21:33:45 +00:00
|
|
|
};
|
|
|
|
const PINGS = 3; // up to 3 pings are performed, unless the server is down...
|
|
|
|
const SLOW_THRESHOLD = 500; // ...or one of the pings is above this threshold
|
|
|
|
/**
|
|
|
|
* This function repeatedly pings a server to get a good estimate of the ping.
|
|
|
|
* When it's done, it calls the done function without parameters.
|
|
|
|
* At the end of the execution, the server will have a new parameter called pingT,
|
|
|
|
* which is either the best ping we got from the server or -1 if something went wrong.
|
|
|
|
* @param {Server} server
|
|
|
|
* @param {() => void} done
|
|
|
|
*/
|
|
|
|
const checkServer = (server, done) => {
|
|
|
|
let i = 0;
|
2019-06-30 05:03:06 +00:00
|
|
|
server.pingT = -1;
|
2020-09-18 21:33:45 +00:00
|
|
|
if (server.server.indexOf(location.protocol) === -1) return void done();
|
|
|
|
const nextPing = () => {
|
|
|
|
if (i++ === PINGS) return void done();
|
|
|
|
ping(
|
|
|
|
server.server + server.pingURL,
|
|
|
|
(t) => {
|
|
|
|
if (t >= 0) {
|
|
|
|
if (t < server.pingT || server.pingT === -1) server.pingT = t;
|
|
|
|
if (t < SLOW_THRESHOLD) nextPing();
|
|
|
|
else done();
|
|
|
|
} else {
|
|
|
|
done();
|
|
|
|
}
|
2019-06-30 05:03:06 +00:00
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
nextPing();
|
|
|
|
};
|
|
|
|
let index = 0;
|
|
|
|
/**
|
|
|
|
* Check servers in list, one by one
|
|
|
|
*/
|
|
|
|
const done = () => {
|
|
|
|
let bestServer = null;
|
|
|
|
for (let i = 0; i < serverList.length; i++) {
|
|
|
|
if (serverList[i].pingT !== -1 && (!bestServer || serverList[i].pingT < bestServer.pingT)) {
|
2019-06-30 05:03:06 +00:00
|
|
|
bestServer = serverList[i];
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
|
|
|
index++;
|
2019-06-30 05:03:06 +00:00
|
|
|
}
|
|
|
|
result(bestServer);
|
2020-09-18 21:33:45 +00:00
|
|
|
};
|
|
|
|
const nextServer = () => {
|
|
|
|
if (index === serverList.length) return void done();
|
|
|
|
checkServer(serverList[index++], nextServer);
|
|
|
|
};
|
2019-06-30 05:03:06 +00:00
|
|
|
nextServer();
|
2020-09-18 21:33:45 +00:00
|
|
|
};
|
|
|
|
// Parallel server selection
|
|
|
|
const CONCURRENCY = 6;
|
|
|
|
const serverLists = [];
|
|
|
|
for (let i = 0; i < CONCURRENCY; i++) {
|
2019-06-30 05:03:06 +00:00
|
|
|
serverLists[i] = [];
|
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
for (let i = 0; i < this._serverList.length; i++) {
|
2019-06-30 05:03:06 +00:00
|
|
|
serverLists[i % CONCURRENCY].push(this._serverList[i]);
|
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
let completed = 0;
|
|
|
|
/** @type {Server} */
|
|
|
|
let bestServer = null;
|
|
|
|
for (let i = 0; i < CONCURRENCY; i++) {
|
2019-06-30 05:03:06 +00:00
|
|
|
select(
|
|
|
|
serverLists[i],
|
2020-09-18 21:33:45 +00:00
|
|
|
(server) => {
|
|
|
|
if (server && (!bestServer || server.pingT < bestServer.pingT)) {
|
|
|
|
bestServer = server;
|
2019-06-30 05:03:06 +00:00
|
|
|
}
|
|
|
|
completed++;
|
2020-09-18 21:33:45 +00:00
|
|
|
if (completed === CONCURRENCY) {
|
2019-06-30 05:03:06 +00:00
|
|
|
this._selectedServer = bestServer;
|
|
|
|
this._state = 2;
|
|
|
|
if (result) result(bestServer);
|
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
2019-06-30 05:03:06 +00:00
|
|
|
);
|
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
|
|
|
|
2019-06-30 05:03:06 +00:00
|
|
|
/**
|
|
|
|
* Starts the test.
|
2020-09-18 21:33:45 +00:00
|
|
|
* During the test, the onupdate(data) callback function will be called periodically
|
|
|
|
* with data from the worker. At the end of the test, the onend(aborted) function will
|
|
|
|
* be called with a boolean telling you if the test was aborted or if it ended normally.
|
2019-06-30 05:03:06 +00:00
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
start() {
|
|
|
|
if (this._state === 3) throw new Error("Test already running");
|
2019-06-30 05:03:06 +00:00
|
|
|
this.worker = new Worker("speedtest_worker.js?r=" + Math.random());
|
2020-09-18 21:33:45 +00:00
|
|
|
this.worker.onmessage = (e) => {
|
2019-06-30 05:03:06 +00:00
|
|
|
if (e.data === this._prevData) return;
|
2020-09-18 21:33:45 +00:00
|
|
|
this._prevData = e.data;
|
|
|
|
const data = JSON.parse(e.data);
|
2019-06-30 05:03:06 +00:00
|
|
|
try {
|
|
|
|
if (this.onupdate) this.onupdate(data);
|
|
|
|
} catch (e) {
|
|
|
|
console.error("Speedtest onupdate event threw exception: " + e);
|
|
|
|
}
|
|
|
|
if (data.testState >= 4) {
|
|
|
|
try {
|
2020-09-18 21:33:45 +00:00
|
|
|
if (this.onend) this.onend(data.testState === 5);
|
2019-06-30 05:03:06 +00:00
|
|
|
} catch (e) {
|
|
|
|
console.error("Speedtest onend event threw exception: " + e);
|
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
clearInterval(this.updater);
|
|
|
|
this._state = 4;
|
2019-06-30 05:03:06 +00:00
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
};
|
|
|
|
this.updater = setInterval(() => { this.worker.postMessage("status"); }, 200);
|
|
|
|
if (this._state === 1) {
|
|
|
|
throw new Error("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;
|
|
|
|
this._settings.telemetry_extra = JSON.stringify({
|
|
|
|
server: this._selectedServer.name,
|
|
|
|
extra: this._originalExtra ? this._originalExtra : undefined
|
|
|
|
});
|
2019-06-30 05:03:06 +00:00
|
|
|
}
|
|
|
|
this._state = 3;
|
|
|
|
this.worker.postMessage("start " + JSON.stringify(this._settings));
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|
|
|
|
|
2019-06-30 05:03:06 +00:00
|
|
|
/**
|
|
|
|
* Aborts the test while it's running.
|
|
|
|
*/
|
2020-09-18 21:33:45 +00:00
|
|
|
abort() {
|
|
|
|
if (this._state < 3) throw new Error("You cannot abort a test that's not started yet");
|
2019-06-30 05:03:06 +00:00
|
|
|
if (this._state < 4) this.worker.postMessage("abort");
|
|
|
|
}
|
2020-09-18 21:33:45 +00:00
|
|
|
}
|