fix(core): update
This commit is contained in:
parent
d8044507ed
commit
260f000304
23
license
Normal file
23
license
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Lossless GmbH
|
||||||
|
Copyright (c) 2020 Tomás Arias
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
@ -11866,11 +11866,6 @@
|
|||||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"speed-cloudflare-cli": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://verdaccio.lossless.one/speed-cloudflare-cli/-/speed-cloudflare-cli-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-aYnaj7ZhasoW+zhsVrO0tmGTxAfBY6eYFoIdoBJdJ0yZE1F/g11pt2AJqIuST0Qbe0x/pzwsEed267tCR6HuOA=="
|
|
||||||
},
|
|
||||||
"speedtest-net": {
|
"speedtest-net": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://verdaccio.lossless.one/speedtest-net/-/speedtest-net-2.1.1.tgz",
|
"resolved": "https://verdaccio.lossless.one/speedtest-net/-/speedtest-net-2.1.1.tgz",
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
"@types/default-gateway": "^3.0.1",
|
"@types/default-gateway": "^3.0.1",
|
||||||
"isopen": "^1.3.0",
|
"isopen": "^1.3.0",
|
||||||
"public-ip": "^4.0.3",
|
"public-ip": "^4.0.3",
|
||||||
"speed-cloudflare-cli": "^2.0.3",
|
|
||||||
"speedtest-net": "^2.1.1",
|
"speedtest-net": "^2.1.1",
|
||||||
"systeminformation": "^5.6.12"
|
"systeminformation": "^5.6.12"
|
||||||
},
|
},
|
||||||
@ -43,4 +42,4 @@
|
|||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 1 chrome versions"
|
"last 1 chrome versions"
|
||||||
]
|
]
|
||||||
}
|
}
|
10
readme.md
10
readme.md
@ -31,13 +31,17 @@ const testSmartNetwork = new smartnetwork.SmartNetwork();
|
|||||||
const run = async () => {
|
const run = async () => {
|
||||||
// measure average speed over a period of 5 seconds
|
// measure average speed over a period of 5 seconds
|
||||||
// the structure of speedResult is self explanatory using TypeScript (or the linked TypeDoc above)
|
// the structure of speedResult is self explanatory using TypeScript (or the linked TypeDoc above)
|
||||||
const speedResult = testSmartNetwork.getSpeed(5000);
|
const speedResult: smartnetwork.SpeedResult = testSmartNetwork.getSpeed(5000);
|
||||||
|
|
||||||
//
|
// you can check for local ports before trying to bind to it from your nodejs program
|
||||||
const isLocalPortAvailable: boolean = await testSmartNetwork.isLocalPortAvailable(1234);
|
const isLocalPortUnused: boolean = await testSmartNetwork.isLocalPortUnused(1234);
|
||||||
|
|
||||||
|
// you can run basic port checks on remote domains.
|
||||||
const isRemotePortAvailable: boolean = await testSmartNetwork.isRemotePortAvailable(
|
const isRemotePortAvailable: boolean = await testSmartNetwork.isRemotePortAvailable(
|
||||||
'google.com:80'
|
'google.com:80'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// just another way to call for the same thing as above
|
||||||
const isRemotePortAvailable: boolean = await testSmartNetwork.isRemotePortAvailable(
|
const isRemotePortAvailable: boolean = await testSmartNetwork.isRemotePortAvailable(
|
||||||
'google.com',
|
'google.com',
|
||||||
80
|
80
|
||||||
|
@ -10,8 +10,7 @@ tap.test('should create a valid instance of SmartNetwork', async () => {
|
|||||||
|
|
||||||
tap.test('should perform a speedtest', async () => {
|
tap.test('should perform a speedtest', async () => {
|
||||||
const result = await testSmartNetwork.getSpeed();
|
const result = await testSmartNetwork.getSpeed();
|
||||||
// console.log(`Download speed for this instance is ${result.download.bandwidth}`);
|
console.log(result);
|
||||||
// console.log(`Upload speed for this instance is ${result.download.bandwidth}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should determine wether a port is free', async () => {
|
tap.test('should determine wether a port is free', async () => {
|
||||||
|
43
ts/helpers/stats.ts
Normal file
43
ts/helpers/stats.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
export function average(values) {
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < values.length; i += 1) {
|
||||||
|
total += values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return total / values.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function median(values) {
|
||||||
|
const half = Math.floor(values.length / 2);
|
||||||
|
|
||||||
|
values.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
if (values.length % 2) return values[half];
|
||||||
|
|
||||||
|
return (values[half - 1] + values[half]) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function quartile(values, percentile) {
|
||||||
|
values.sort((a, b) => a - b);
|
||||||
|
const pos = (values.length - 1) * percentile;
|
||||||
|
const base = Math.floor(pos);
|
||||||
|
const rest = pos - base;
|
||||||
|
|
||||||
|
if (values[base + 1] !== undefined) {
|
||||||
|
return values[base] + rest * (values[base + 1] - values[base]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return values[base];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jitter(values) {
|
||||||
|
// Average distance between consecutive latency measurements...
|
||||||
|
let jitters = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < values.length - 1; i += 1) {
|
||||||
|
jitters.push(Math.abs(values[i] - values[i + 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return average(jitters);
|
||||||
|
}
|
188
ts/smartnetwork.classes.cloudflarespeed.ts
Normal file
188
ts/smartnetwork.classes.cloudflarespeed.ts
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
import * as plugins from './smartnetwork.plugins';
|
||||||
|
import * as stats from './helpers/stats';
|
||||||
|
|
||||||
|
export class CloudflareSpeed {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public async speedTest() {
|
||||||
|
const latency = await this.measureLatency();
|
||||||
|
|
||||||
|
const serverLocations = await this.fetchServerLocations();
|
||||||
|
const cgiData = await this.fetchCfCdnCgiTrace();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...latency,
|
||||||
|
ip: cgiData.ip,
|
||||||
|
serverLocation: {
|
||||||
|
shortId: cgiData.colo,
|
||||||
|
name: serverLocations[cgiData.colo],
|
||||||
|
availableLocations: serverLocations,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async measureLatency() {
|
||||||
|
const measurements: number[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 20; i += 1) {
|
||||||
|
await this.download(1000).then(
|
||||||
|
(response) => {
|
||||||
|
// TTFB - Server processing time
|
||||||
|
measurements.push(response[4] - response[0] - response[6]);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.log(`Error: ${error}`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
maxTime: Math.max(...measurements),
|
||||||
|
minTime: Math.min(...measurements),
|
||||||
|
averageTime: stats.average(measurements),
|
||||||
|
medianTime: stats.median(measurements),
|
||||||
|
jitter: stats.jitter(measurements),
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchServerLocations(): Promise<{[key: string]: string}> {
|
||||||
|
const res = JSON.parse(await this.get('speed.cloudflare.com', '/locations'));
|
||||||
|
|
||||||
|
return res.reduce((data, { iata, city }) => {
|
||||||
|
// Bypass prettier "no-assign-param" rules
|
||||||
|
const data1 = data;
|
||||||
|
|
||||||
|
data1[iata] = city;
|
||||||
|
return data1;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(hostname: string, path: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = plugins.https.request(
|
||||||
|
{
|
||||||
|
hostname,
|
||||||
|
path,
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
const body = [];
|
||||||
|
res.on('data', (chunk) => {
|
||||||
|
body.push(chunk);
|
||||||
|
});
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
resolve(Buffer.concat(body).toString());
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
req.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async download(bytes) {
|
||||||
|
const options = {
|
||||||
|
hostname: 'speed.cloudflare.com',
|
||||||
|
path: `/__down?bytes=${bytes}`,
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.request(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async request(options, data = '') {
|
||||||
|
let started;
|
||||||
|
let dnsLookup;
|
||||||
|
let tcpHandshake;
|
||||||
|
let sslHandshake;
|
||||||
|
let ttfb;
|
||||||
|
let ended;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
started = plugins.perfHooks.performance.now();
|
||||||
|
const req = plugins.https.request(options, (res) => {
|
||||||
|
res.once('readable', () => {
|
||||||
|
ttfb = plugins.perfHooks.performance.now();
|
||||||
|
});
|
||||||
|
res.on('data', () => {});
|
||||||
|
res.on('end', () => {
|
||||||
|
ended = plugins.perfHooks.performance.now();
|
||||||
|
resolve([
|
||||||
|
started,
|
||||||
|
dnsLookup,
|
||||||
|
tcpHandshake,
|
||||||
|
sslHandshake,
|
||||||
|
ttfb,
|
||||||
|
ended,
|
||||||
|
parseFloat(res.headers['server-timing'].slice(22) as any),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('socket', (socket) => {
|
||||||
|
socket.on('lookup', () => {
|
||||||
|
dnsLookup = plugins.perfHooks.performance.now();
|
||||||
|
});
|
||||||
|
socket.on('connect', () => {
|
||||||
|
tcpHandshake = plugins.perfHooks.performance.now();
|
||||||
|
});
|
||||||
|
socket.on('secureConnect', () => {
|
||||||
|
sslHandshake = plugins.perfHooks.performance.now();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', (error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.write(data);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchCfCdnCgiTrace(): Promise<{
|
||||||
|
fl: string,
|
||||||
|
h: string,
|
||||||
|
ip: string,
|
||||||
|
ts: string,
|
||||||
|
visit_scheme: string,
|
||||||
|
uag: string,
|
||||||
|
colo: string,
|
||||||
|
http: string,
|
||||||
|
loc: string,
|
||||||
|
tls: string,
|
||||||
|
sni: string,
|
||||||
|
warp: string,
|
||||||
|
gateway: string,
|
||||||
|
}> {
|
||||||
|
const parseCfCdnCgiTrace = (text) =>
|
||||||
|
text
|
||||||
|
.split('\n')
|
||||||
|
.map((i) => {
|
||||||
|
const j = i.split('=');
|
||||||
|
|
||||||
|
return [j[0], j[1]];
|
||||||
|
})
|
||||||
|
.reduce((data, [k, v]) => {
|
||||||
|
if (v === undefined) return data;
|
||||||
|
|
||||||
|
// Bypass prettier "no-assign-param" rules
|
||||||
|
const data1 = data;
|
||||||
|
// Object.fromEntries is only supported by Node.js 12 or newer
|
||||||
|
data1[k] = v;
|
||||||
|
|
||||||
|
return data1;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return this.get('speed.cloudflare.com', '/cdn-cgi/trace').then(parseCfCdnCgiTrace);
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +1,6 @@
|
|||||||
import * as plugins from './smartnetwork.plugins';
|
import * as plugins from './smartnetwork.plugins';
|
||||||
|
|
||||||
export interface ISpeedtestData {
|
import { CloudflareSpeed } from './smartnetwork.classes.cloudflarespeed';
|
||||||
timestamp: Date;
|
|
||||||
ping: { jitter: number; latency: number };
|
|
||||||
download: { bandwidth: number; bytes: number; elapsed: number };
|
|
||||||
upload: { bandwidth: number; bytes: number; elapsed: number };
|
|
||||||
packetLoss: number;
|
|
||||||
isp: string;
|
|
||||||
interface: {
|
|
||||||
internalIp: string;
|
|
||||||
name: string;
|
|
||||||
macAddr: string;
|
|
||||||
isVpn: false;
|
|
||||||
externalIp: string;
|
|
||||||
};
|
|
||||||
server: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
location: string;
|
|
||||||
country: string;
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
ip: string;
|
|
||||||
};
|
|
||||||
result: {
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SmartNetwork simplifies actions within the network
|
* SmartNetwork simplifies actions within the network
|
||||||
@ -38,7 +11,8 @@ export class SmartNetwork {
|
|||||||
* @param measurementTime
|
* @param measurementTime
|
||||||
*/
|
*/
|
||||||
public async getSpeed() {
|
public async getSpeed() {
|
||||||
const test = null;
|
const cloudflareSpeedInstance = new CloudflareSpeed();
|
||||||
|
const test = await cloudflareSpeedInstance.speedTest();
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// native scope
|
// native scope
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
|
import * as https from 'https';
|
||||||
|
import * as perfHooks from 'perf_hooks';
|
||||||
|
|
||||||
export { os };
|
export { os, https, perfHooks };
|
||||||
|
|
||||||
// @pushrocks scope
|
// @pushrocks scope
|
||||||
import * as smartpromise from '@pushrocks/smartpromise';
|
import * as smartpromise from '@pushrocks/smartpromise';
|
||||||
|
Loading…
Reference in New Issue
Block a user