Compare commits

...

35 Commits

Author SHA1 Message Date
e5c37b1801 2.0.10 2021-04-29 15:12:05 +00:00
5be0586790 fix(core): update 2021-04-29 15:12:05 +00:00
f5e5297d47 2.0.9 2021-04-28 14:32:56 +00:00
718fada493 fix(core): update 2021-04-28 14:32:56 +00:00
a42b1b48b5 2.0.8 2021-04-28 14:31:31 +00:00
5ec50975f3 fix(core): update 2021-04-28 14:31:30 +00:00
ad222abb6a 2.0.7 2021-04-28 14:27:23 +00:00
b29e13b162 fix(core): update 2021-04-28 14:27:22 +00:00
9544823401 2.0.6 2021-04-28 13:41:56 +00:00
260f000304 fix(core): update 2021-04-28 13:41:55 +00:00
d8044507ed 2.0.5 2021-04-27 09:46:32 +00:00
b9380be999 fix(core): update 2021-04-27 09:46:31 +00:00
1b9c354d69 2.0.4 2021-04-13 13:05:48 +00:00
a8f4ecf98f fix(core): update 2021-04-13 13:05:47 +00:00
6350088d2a 2.0.3 2021-04-13 10:29:43 +00:00
10ef1d0455 fix(core): update 2021-04-13 10:29:42 +00:00
f709238e50 2.0.2 2021-04-13 10:09:40 +00:00
49940635d5 fix(core): update 2021-04-13 10:09:39 +00:00
ec4121cbcf 2.0.1 2021-04-13 10:09:09 +00:00
ea9a2572f9 fix(core): update 2021-04-13 10:09:09 +00:00
cc0f1c40a6 2.0.0 2021-04-13 10:07:39 +00:00
9da04081e4 BREAKING CHANGE(core): update 2021-04-13 10:07:39 +00:00
4ae0925043 1.1.22 2020-08-13 02:34:06 +00:00
4e862e784b fix(core): update 2020-08-13 02:34:06 +00:00
cf8abfd4f0 1.1.21 2020-08-13 02:29:10 +00:00
93c4488b9b fix(core): update 2020-08-13 02:29:09 +00:00
39493465c6 1.1.20 2020-08-12 16:30:18 +00:00
cab696e45b fix(core): update 2020-08-12 16:30:17 +00:00
67682892ae 1.1.19 2020-08-12 16:29:04 +00:00
5c13987686 fix(core): update 2020-08-12 16:29:03 +00:00
97841e0973 1.1.18 2019-11-23 19:33:49 +00:00
c8ccde9d84 1.1.17 2019-11-23 16:07:05 +00:00
654a4c6b54 fix(core): update 2019-11-23 16:07:04 +00:00
12b8793c19 1.1.16 2019-11-19 23:01:22 +00:00
24e861e5b4 fix(security): add snyk config 2019-11-19 23:01:21 +00:00
15 changed files with 11458 additions and 1013 deletions

4
.gitignore vendored
View File

@ -15,8 +15,6 @@ node_modules/
# builds
dist/
dist_web/
dist_serve/
dist_ts_web/
dist_*/
# custom

View File

@ -19,22 +19,35 @@ mirror:
stage: security
script:
- npmci git mirror
only:
- tags
tags:
- lossless
- docker
- notpriv
snyk:
image: registry.gitlab.com/hosttoday/ht-docker-node:snyk
auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --production --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=prod --production
tags:
- docker
auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --ignore-scripts
- npmci command snyk test
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=dev
tags:
- lossless
- docker
- notpriv
allow_failure: true
# ====================
# test stage
@ -49,9 +62,7 @@ testStable:
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- lossless
- docker
- priv
testBuild:
stage: test
@ -62,9 +73,7 @@ testBuild:
- npmci command npm run build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- lossless
- docker
- notpriv
release:
stage: release
@ -84,6 +93,8 @@ release:
codequality:
stage: metadata
allow_failure: true
only:
- tags
script:
- npmci command npm install -g tslint typescript
- npmci npm prepare

4
.snyk Normal file
View File

@ -0,0 +1,4 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.13.5
ignore: {}
patch: {}

29
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,29 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "current file",
"type": "node",
"request": "launch",
"args": [
"${relativeFile}"
],
"runtimeArgs": ["-r", "@gitzone/tsrun"],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": "test.ts",
"type": "node",
"request": "launch",
"args": [
"test/test.ts"
],
"runtimeArgs": ["-r", "@gitzone/tsrun"],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
}
]
}

26
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"json.schemas": [
{
"fileMatch": ["/npmextra.json"],
"schema": {
"type": "object",
"properties": {
"npmci": {
"type": "object",
"description": "settings for npmci"
},
"gitzone": {
"type": "object",
"description": "settings for gitzone",
"properties": {
"projectType": {
"type": "string",
"enum": ["website", "element", "service", "npm", "wcc"]
}
}
}
}
}
}
]
}

23
license Normal file
View 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.

View File

@ -4,6 +4,7 @@
"npmAccessLevel": "public"
},
"gitzone": {
"projectType": "npm",
"module": {
"githost": "gitlab.com",
"gitscope": "pushrocks",

11894
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,44 @@
{
"name": "@pushrocks/smartnetwork",
"version": "1.1.15",
"version": "2.0.10",
"private": false,
"description": "network diagnostics",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"author": "Lossless GmbH",
"license": "MIT",
"scripts": {
"test": "(tstest test/)",
"build": "(tsbuild)"
"build": "(tsbuild --web)"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.1.17",
"@gitzone/tstest": "^1.0.28",
"@pushrocks/tapbundle": "^3.2.0",
"tslint": "^5.20.1",
"@gitzone/tsbuild": "^2.1.25",
"@gitzone/tstest": "^1.0.52",
"@pushrocks/tapbundle": "^3.2.14",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0"
},
"dependencies": {
"@pushrocks/smartpromise": "^3.0.6",
"@pushrocks/smartstring": "^3.0.14",
"@types/default-gateway": "^3.0.0",
"@types/portscanner": "^2.1.0",
"default-gateway": "^5.0.5",
"portscanner": "^2.2.0",
"speedtest-net": "^1.6.0",
"systeminformation": "^4.15.3"
"@pushrocks/smartpromise": "^3.1.3",
"@pushrocks/smartstring": "^3.0.24",
"@types/default-gateway": "^3.0.1",
"isopen": "^1.3.0",
"public-ip": "^4.0.3",
"systeminformation": "^5.6.12"
},
"files": [
"ts/**/*",
"ts_web/**/*",
"dist/**/*",
"dist_web/**/*",
"dist_*/**/*",
"dist_ts/**/*",
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
"readme.md"
],
"browserslist": [
"last 1 chrome versions"
]
}

View File

@ -8,13 +8,20 @@ network diagnostics
* [docs (typedoc)](https://pushrocks.gitlab.io/smartnetwork/)
## Status for master
[![build status](https://gitlab.com/pushrocks/smartnetwork/badges/master/build.svg)](https://gitlab.com/pushrocks/smartnetwork/commits/master)
[![coverage report](https://gitlab.com/pushrocks/smartnetwork/badges/master/coverage.svg)](https://gitlab.com/pushrocks/smartnetwork/commits/master)
[![npm downloads per month](https://img.shields.io/npm/dm/@pushrocks/smartnetwork.svg)](https://www.npmjs.com/package/@pushrocks/smartnetwork)
[![Known Vulnerabilities](https://snyk.io/test/npm/@pushrocks/smartnetwork/badge.svg)](https://snyk.io/test/npm/@pushrocks/smartnetwork)
[![TypeScript](https://img.shields.io/badge/TypeScript->=%203.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
[![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-prettier-ff69b4.svg)](https://prettier.io/)
Status Category | Status Badge
-- | --
GitLab Pipelines | [![pipeline status](https://gitlab.com/pushrocks/smartnetwork/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/pushrocks/smartnetwork/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@pushrocks/smartnetwork)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/pushrocks/smartnetwork)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/smartnetwork)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/smartnetwork)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/smartnetwork)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
## Usage
@ -24,13 +31,17 @@ const testSmartNetwork = new smartnetwork.SmartNetwork();
const run = async () => {
// measure average speed over a period of 5 seconds
// 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);
//
const isLocalPortAvailable: boolean = await testSmartNetwork.isLocalPortAvailable(1234);
// you can check for local ports before trying to bind to it from your nodejs program
const isLocalPortUnused: boolean = await testSmartNetwork.isLocalPortUnused(1234);
// you can run basic port checks on remote domains.
const isRemotePortAvailable: boolean = await testSmartNetwork.isRemotePortAvailable(
'google.com:80'
);
// just another way to call for the same thing as above
const isRemotePortAvailable: boolean = await testSmartNetwork.isRemotePortAvailable(
'google.com',
80

View File

@ -9,13 +9,13 @@ tap.test('should create a valid instance of SmartNetwork', async () => {
});
tap.test('should perform a speedtest', async () => {
let result = await testSmartNetwork.getSpeed();
console.log(`Download speed for this instance is ${result.speeds.download}`);
console.log(`Upload speed for this instance is ${result.speeds.upload}`);
const result = await testSmartNetwork.getSpeed();
console.log(`Download speed for this instance is ${result.downloadSpeed}`);
console.log(`Upload speed for this instance is ${result.uploadSpeed}`);
});
tap.test('should determine wether a port is free', async () => {
await expect(testSmartNetwork.isLocalPortAvailable(8080)).to.eventually.be.true;
await expect(testSmartNetwork.isLocalPortUnused(8080)).to.eventually.be.true;
});
tap.test('should scan a port', async () => {
@ -34,4 +34,9 @@ tap.test('should get the default gateway', async () => {
console.log(gatewayResult);
});
tap.test('should get public ips', async () => {
const ips = await testSmartNetwork.getPublicIps();
console.log(ips);
});
tap.start();

43
ts/helpers/stats.ts Normal file
View 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);
}

View File

@ -0,0 +1,258 @@
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();
// lets test the download speed
const testDown1 = await this.measureDownload(101000, 10);
const testDown2 = await this.measureDownload(1001000, 8);
const testDown3 = await this.measureDownload(10001000, 6);
const testDown4 = await this.measureDownload(25001000, 4);
const testDown5 = await this.measureDownload(100001000, 1);
const downloadTests = [...testDown1, ...testDown2, ...testDown3, ...testDown4, ...testDown5];
const speedDownload = stats.quartile(downloadTests, 0.9).toFixed(2);
// lets test the upload speed
const testUp1 = await this.measureUpload(11000, 10);
const testUp2 = await this.measureUpload(101000, 10);
const testUp3 = await this.measureUpload(1001000, 8);
const uploadTests = [...testUp1, ...testUp2, ...testUp3];
const speedUpload = stats.quartile(uploadTests, 0.9).toFixed(2);
return {
...latency,
ip: cgiData.ip,
serverLocation: {
shortId: cgiData.colo,
name: serverLocations[cgiData.colo],
availableLocations: serverLocations,
},
downloadSpeed: speedDownload,
uploadSpeed: speedUpload,
};
}
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 measureDownload(bytes: number, iterations: number) {
const measurements = [];
for (let i = 0; i < iterations; i += 1) {
await this.download(bytes).then(
async (response) => {
const transferTime = response[5] - response[4];
measurements.push(await this.measureSpeed(bytes, transferTime));
},
(error) => {
console.log(`Error: ${error}`);
}
);
}
return measurements;
}
public async measureUpload(bytes: number, iterations: number) {
const measurements = [];
for (let i = 0; i < iterations; i += 1) {
await this.upload(bytes).then(
async (response) => {
const transferTime = response[6];
measurements.push(await this.measureSpeed(bytes, transferTime));
},
(error) => {
console.log(`Error: ${error}`);
}
);
}
return measurements;
}
public async measureSpeed(bytes: number, duration: number) {
return (bytes * 8) / (duration / 1000) / 1e6;
}
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 upload(bytes: number) {
const data = '0'.repeat(bytes);
const options = {
hostname: 'speed.cloudflare.com',
path: '/__up',
method: 'POST',
headers: {
'Content-Length': Buffer.byteLength(data),
},
};
return this.request(options, data);
}
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);
}
}

View File

@ -1,36 +1,6 @@
import * as plugins from './smartnetwork.plugins';
export interface ISpeedtestData {
speeds: {
download: number;
upload: number;
originalDownload: number;
originalUpload: number;
};
client: {
ip: string;
lat: number;
lon: number;
isp: string;
isprating: string;
rating: number;
ispdlavg: number;
ispulavg: number;
};
server: {
host: string;
lat: number;
lon: number;
location: string;
country: string;
cc: string;
sponsor: string;
distance: number;
distanceMi: number;
ping: number;
id: string;
};
}
import { CloudflareSpeed } from './smartnetwork.classes.cloudflarespeed';
/**
* SmartNetwork simplifies actions within the network
@ -40,16 +10,10 @@ export class SmartNetwork {
* get network speed
* @param measurementTime
*/
public async getSpeed(measurementTime = 5000): Promise<ISpeedtestData> {
const done = plugins.smartpromise.defer<ISpeedtestData>();
const test = plugins.speedtestNet({ maxTime: measurementTime });
test.on('data', data => {
done.resolve(data);
});
test.on('error', err => {
done.reject(err);
});
return await done.promise;
public async getSpeed() {
const cloudflareSpeedInstance = new CloudflareSpeed();
const test = await cloudflareSpeedInstance.speedTest();
return test;
}
/**
@ -57,7 +21,7 @@ export class SmartNetwork {
* note: false also resolves with false as argument
* @param port
*/
public async isLocalPortAvailable(port: number): Promise<boolean> {
public async isLocalPortUnused(port: number): Promise<boolean> {
const doneIpV4 = plugins.smartpromise.defer<boolean>();
const doneIpV6 = plugins.smartpromise.defer<boolean>();
const net = await import('net'); // creates only one instance of net ;) even on multiple calls
@ -83,7 +47,7 @@ export class SmartNetwork {
// test IPv6 space
const ipv6Test = net.createServer();
ipv6Test.once('error', function(err: any) {
ipv6Test.once('error', function (err: any) {
if (err.code !== 'EADDRINUSE') {
doneIpV6.resolve(false);
return;
@ -114,12 +78,9 @@ export class SmartNetwork {
const domainPart = domainArg.split(':')[0];
const port = portArg ? portArg : parseInt(domainArg.split(':')[1], 10);
plugins.portscanner.checkPortStatus(port, domainPart, (err, status) => {
if (err) {
// console.log(err);
return done.resolve(false);
}
if (status === 'open') {
plugins.isopen(domainPart, port, (response) => {
console.log(response);
if (response[port.toString()].isOpen) {
done.resolve(true);
} else {
done.resolve(false);
@ -147,7 +108,16 @@ export class SmartNetwork {
const defaultGateway = gateways[defaultGatewayName];
return {
ipv4: defaultGateway[0],
ipv6: defaultGateway[1]
ipv6: defaultGateway[1],
};
}
public async getPublicIps() {
return {
v4: await plugins.publicIp.v4({
timeout: 1000,
onlyHttps: true,
}),
};
}
}

View File

@ -1,7 +1,9 @@
// native scope
import * as os from 'os';
import * as https from 'https';
import * as perfHooks from 'perf_hooks';
export { os };
export { os, https, perfHooks };
// @pushrocks scope
import * as smartpromise from '@pushrocks/smartpromise';
@ -10,8 +12,8 @@ import * as smartstring from '@pushrocks/smartstring';
export { smartpromise, smartstring };
// @third party scope
import * as portscanner from 'portscanner';
import speedtestNet from 'speedtest-net';
import isopen from 'isopen';
import publicIp from 'public-ip';
import * as systeminformation from 'systeminformation';
export { speedtestNet, portscanner, systeminformation };
export { isopen, publicIp, systeminformation };