Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
15744d3c4e | |||
8b2f541150 | |||
b52bb4b474 | |||
42f5d66fc4 | |||
54bb9549a1 | |||
95c3314400 | |||
695d047200 | |||
c308589d28 | |||
068177b09d | |||
4a299cf3cb | |||
e5c37b1801 | |||
5be0586790 | |||
f5e5297d47 | |||
718fada493 | |||
a42b1b48b5 | |||
5ec50975f3 | |||
ad222abb6a | |||
b29e13b162 | |||
9544823401 | |||
260f000304 | |||
d8044507ed | |||
b9380be999 | |||
1b9c354d69 | |||
a8f4ecf98f | |||
6350088d2a | |||
10ef1d0455 | |||
f709238e50 | |||
49940635d5 | |||
ec4121cbcf | |||
ea9a2572f9 | |||
cc0f1c40a6 | |||
9da04081e4 | |||
4ae0925043 | |||
4e862e784b | |||
cf8abfd4f0 | |||
93c4488b9b | |||
39493465c6 | |||
cab696e45b | |||
67682892ae | |||
5c13987686 | |||
97841e0973 | |||
c8ccde9d84 | |||
654a4c6b54 | |||
12b8793c19 | |||
24e861e5b4 | |||
c7d2b2c031 | |||
f08713bb45 | |||
ad0fa8c65a | |||
88a9bfc20d | |||
0248d6f253 | |||
8d1b302e70 | |||
34bee225d5 | |||
ccece078a2 | |||
a04151e537 | |||
d71346e763 | |||
817894b6ce | |||
bba219ddef | |||
9af53a5b58 | |||
4fcdeb8c3d | |||
5c7d2de902 | |||
f105cdc806 | |||
8f71c68d8c | |||
beb6680856 | |||
311852efe8 | |||
d3a507c3ff | |||
05bc8fb72c | |||
df861590c8 | |||
2ce69f4a8e | |||
7625866ca9 | |||
b88f52ba90 | |||
43c8f63ce6 | |||
b5eac3c54f | |||
fda63e4f95 |
17
.gitignore
vendored
17
.gitignore
vendored
@ -1,5 +1,20 @@
|
||||
.nogit/
|
||||
node_modules/
|
||||
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
|
||||
# caches
|
||||
.yarn/
|
||||
.cache/
|
||||
.rpt2_cache
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
132
.gitlab-ci.yml
132
.gitlab-ci.yml
@ -1,16 +1,19 @@
|
||||
# gitzone standard
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
# gitzone ci_default
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .npmci_cache/
|
||||
key: "$CI_BUILD_STAGE"
|
||||
- .npmci_cache/
|
||||
key: '$CI_BUILD_STAGE'
|
||||
|
||||
stages:
|
||||
- security
|
||||
- test
|
||||
- release
|
||||
- metadata
|
||||
- security
|
||||
- test
|
||||
- release
|
||||
- metadata
|
||||
|
||||
before_script:
|
||||
- npm install -g @shipzone/npmci
|
||||
|
||||
# ====================
|
||||
# security stage
|
||||
@ -18,102 +21,115 @@ stages:
|
||||
mirror:
|
||||
stage: security
|
||||
script:
|
||||
- npmci git mirror
|
||||
- npmci git mirror
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
snyk:
|
||||
auditProductionDependencies:
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
stage: security
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci command npm install -g snyk
|
||||
- npmci command npm install --ignore-scripts
|
||||
- npmci command snyk test
|
||||
- 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
|
||||
- notpriv
|
||||
- docker
|
||||
allow_failure: true
|
||||
|
||||
auditDevDependencies:
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
stage: security
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci command npm install --ignore-scripts
|
||||
- npmci command npm config set registry https://registry.npmjs.org
|
||||
- npmci command npm audit --audit-level=high --only=dev
|
||||
tags:
|
||||
- docker
|
||||
allow_failure: true
|
||||
|
||||
# ====================
|
||||
# test stage
|
||||
# ====================
|
||||
|
||||
testLTS:
|
||||
testStable:
|
||||
stage: test
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci node install lts
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
- npmci npm prepare
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
testSTABLE:
|
||||
- docker
|
||||
|
||||
testBuild:
|
||||
stage: test
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
- npmci npm prepare
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci command npm run build
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
- docker
|
||||
|
||||
release:
|
||||
stage: release
|
||||
script:
|
||||
- npmci node install stable
|
||||
- npmci npm publish
|
||||
- npmci node install stable
|
||||
- npmci npm publish
|
||||
only:
|
||||
- tags
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
# ====================
|
||||
# metadata stage
|
||||
# ====================
|
||||
codequality:
|
||||
stage: metadata
|
||||
image: docker:stable
|
||||
allow_failure: true
|
||||
services:
|
||||
- docker:stable-dind
|
||||
only:
|
||||
- tags
|
||||
script:
|
||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
||||
- docker run
|
||||
--env SOURCE_CODE="$PWD"
|
||||
--volume "$PWD":/code
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock
|
||||
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
|
||||
artifacts:
|
||||
paths: [codeclimate.json]
|
||||
- npmci command npm install -g tslint typescript
|
||||
- npmci npm prepare
|
||||
- npmci npm install
|
||||
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
|
||||
tags:
|
||||
- docker
|
||||
- priv
|
||||
- lossless
|
||||
- docker
|
||||
- priv
|
||||
|
||||
trigger:
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci trigger
|
||||
- npmci trigger
|
||||
only:
|
||||
- tags
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
pages:
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci command npm install -g typedoc typescript
|
||||
- npmci node install lts
|
||||
- npmci command npm install -g @gitzone/tsdoc
|
||||
- npmci npm prepare
|
||||
- npmci npm install
|
||||
- npmci command typedoc --module "commonjs" --target "ES2016" --out public/ ts/
|
||||
- npmci command tsdoc
|
||||
tags:
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
only:
|
||||
@ -121,5 +137,5 @@ pages:
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- public
|
||||
- public
|
||||
allow_failure: true
|
||||
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "npm test",
|
||||
"name": "Run npm test",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
26
.vscode/settings.json
vendored
Normal file
26
.vscode/settings.json
vendored
Normal 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
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.
|
||||
|
@ -1,16 +1,15 @@
|
||||
{
|
||||
"npmci": {
|
||||
"npmGlobalTools": [
|
||||
"npmts"
|
||||
],
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public"
|
||||
},
|
||||
"gitzone": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "gitlab.com",
|
||||
"gitscope": "pushrocks",
|
||||
"gitrepo": "smartnetwork",
|
||||
"shortDescription": "network diagnostics",
|
||||
"description": "network diagnostics",
|
||||
"npmPackagename": "@pushrocks/smartnetwork",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
18694
package-lock.json
generated
18694
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
@ -1,25 +1,45 @@
|
||||
{
|
||||
"name": "@pushrocks/smartnetwork",
|
||||
"version": "1.1.1",
|
||||
"version": "3.0.0",
|
||||
"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",
|
||||
"type": "module",
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tsrun test/test.ts)",
|
||||
"build": "(npmts)"
|
||||
"test": "(tstest test/)",
|
||||
"build": "(tsbuild --web --allowimplicitany)"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gitzone/tsrun": "^1.2.5",
|
||||
"@pushrocks/tapbundle": "^3.0.9"
|
||||
"@gitzone/tsbuild": "^2.1.61",
|
||||
"@gitzone/tstest": "^1.0.69",
|
||||
"@pushrocks/smartenv": "^5.0.0",
|
||||
"@pushrocks/tapbundle": "^5.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pushrocks/smartpromise": "^3.0.2",
|
||||
"@pushrocks/smartstring": "^3.0.10",
|
||||
"@types/portscanner": "^2.1.0",
|
||||
"portscanner": "^2.2.0",
|
||||
"speedtest-net": "^1.5.1"
|
||||
}
|
||||
"@pushrocks/smartpromise": "^3.1.7",
|
||||
"@pushrocks/smartstring": "^4.0.2",
|
||||
"@types/default-gateway": "^3.0.1",
|
||||
"icmp": "^2.0.1",
|
||||
"isopen": "^1.3.0",
|
||||
"public-ip": "^4.0.4",
|
||||
"systeminformation": "^5.11.9"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
"ts_web/**/*",
|
||||
"dist/**/*",
|
||||
"dist_*/**/*",
|
||||
"dist_ts/**/*",
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
]
|
||||
}
|
||||
|
51
readme.md
51
readme.md
@ -8,25 +8,54 @@ network diagnostics
|
||||
* [docs (typedoc)](https://pushrocks.gitlab.io/smartnetwork/)
|
||||
|
||||
## Status for master
|
||||
[](https://gitlab.com/pushrocks/smartnetwork/commits/master)
|
||||
[](https://gitlab.com/pushrocks/smartnetwork/commits/master)
|
||||
[](https://www.npmjs.com/package/@pushrocks/smartnetwork)
|
||||
[](https://snyk.io/test/npm/@pushrocks/smartnetwork)
|
||||
[](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
||||
[](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
||||
[](https://prettier.io/)
|
||||
|
||||
Status Category | Status Badge
|
||||
-- | --
|
||||
GitLab Pipelines | [](https://lossless.cloud)
|
||||
GitLab Pipline Test Coverage | [](https://lossless.cloud)
|
||||
npm | [](https://lossless.cloud)
|
||||
Snyk | [](https://lossless.cloud)
|
||||
TypeScript Support | [](https://lossless.cloud)
|
||||
node Support | [](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
||||
Code Style | [](https://lossless.cloud)
|
||||
PackagePhobia (total standalone install weight) | [](https://lossless.cloud)
|
||||
PackagePhobia (package size on registry) | [](https://lossless.cloud)
|
||||
BundlePhobia (total size when bundled) | [](https://lossless.cloud)
|
||||
Platform support | [](https://lossless.cloud) [](https://lossless.cloud)
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import * as smartnetwork from 'smartnetwork';
|
||||
testSmartNetwork = new smartnetwork.SmartNetwork();
|
||||
testSmartNetwork.getSpeed
|
||||
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: smartnetwork.SpeedResult = testSmartNetwork.getSpeed(5000);
|
||||
|
||||
// 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
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
||||
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)
|
||||
|
||||
For further information read the linked docs at the top of this readme.
|
||||
|
||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
|
||||
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
||||
|
||||
[](https://maintainedby.lossless.com)
|
||||
[](https://maintainedby.lossless.com)
|
||||
|
18
test/test.ping.ts
Normal file
18
test/test.ping.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { tap, expect, expectAsync } from '@pushrocks/tapbundle';
|
||||
|
||||
import * as smartnetwork from '../ts/index.js';
|
||||
|
||||
let testSmartnetwork: smartnetwork.SmartNetwork;
|
||||
|
||||
tap.test('should create a vlid instance of SmartNetwork', async () => {
|
||||
testSmartnetwork = new smartnetwork.SmartNetwork();
|
||||
expect(testSmartnetwork).toBeInstanceOf(smartnetwork.SmartNetwork);
|
||||
});
|
||||
|
||||
tap.test('should send a ping to Google', async () => {
|
||||
expectAsync(testSmartnetwork.ping('https://lossless.com')).toBeTrue();
|
||||
expectAsync(testSmartnetwork.ping('https://notthere.lossless.com')).toBeTrue();
|
||||
expectAsync(testSmartnetwork.ping('192.168.186.999')).toBeFalse();
|
||||
});
|
||||
|
||||
tap.start();
|
34
test/test.ts
34
test/test.ts
@ -1,26 +1,42 @@
|
||||
import { expect, tap } from '@pushrocks/tapbundle';
|
||||
import * as smartnetwork from '../ts/index';
|
||||
import { expect, expectAsync, tap } from '@pushrocks/tapbundle';
|
||||
import * as smartnetwork from '../ts/index.js';
|
||||
|
||||
let testSmartNetwork: smartnetwork.SmartNetwork;
|
||||
|
||||
tap.test('should create a valid instance of SmartNetwork', async () => {
|
||||
testSmartNetwork = new smartnetwork.SmartNetwork();
|
||||
expect(testSmartNetwork).to.be.instanceOf(smartnetwork.SmartNetwork);
|
||||
expect(testSmartNetwork).toBeInstanceOf(smartnetwork.SmartNetwork);
|
||||
});
|
||||
|
||||
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 expectAsync(testSmartNetwork.isLocalPortUnused(8080)).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('should scan a port', async () => {
|
||||
const portscanner = new smartnetwork.PortScanner();
|
||||
expect(portscanner.checkPortStatus('google.com:80')).to.eventually.be.true;
|
||||
await expectAsync(testSmartNetwork.isRemotePortAvailable('lossless.com:443')).toBeTrue();
|
||||
await expectAsync(testSmartNetwork.isRemotePortAvailable('lossless.com', 443)).toBeTrue();
|
||||
await expectAsync(testSmartNetwork.isRemotePortAvailable('lossless.com:444')).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('should get gateways', async () => {
|
||||
const gatewayResult = await testSmartNetwork.getGateways();
|
||||
console.log(gatewayResult);
|
||||
});
|
||||
|
||||
tap.test('should get the default gateway', async () => {
|
||||
const gatewayResult = await testSmartNetwork.getDefaultGateway();
|
||||
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
43
ts/helpers/stats.ts
Normal file
@ -0,0 +1,43 @@
|
||||
export function average(values: number[]) {
|
||||
let total = 0;
|
||||
|
||||
for (let i = 0; i < values.length; i += 1) {
|
||||
total += values[i];
|
||||
}
|
||||
|
||||
return total / values.length;
|
||||
}
|
||||
|
||||
export function median(values: number[]) {
|
||||
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: number[], percentile: number) {
|
||||
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: number[]) {
|
||||
// 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);
|
||||
}
|
@ -1,2 +1 @@
|
||||
export * from './smartnetwork.classes.portscanner';
|
||||
export * from './smartnetwork.classes.speedtest';
|
||||
export * from './smartnetwork.classes.smartnetwork.js';
|
||||
|
258
ts/smartnetwork.classes.cloudflarespeed.ts
Normal file
258
ts/smartnetwork.classes.cloudflarespeed.ts
Normal file
@ -0,0 +1,258 @@
|
||||
import * as plugins from './smartnetwork.plugins.js';
|
||||
import * as stats from './helpers/stats.js';
|
||||
|
||||
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: number[] = [];
|
||||
|
||||
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: number[] = [];
|
||||
|
||||
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: any, optionsArg: { iata: string; city: string }) => {
|
||||
// Bypass prettier "no-assign-param" rules
|
||||
const data1 = data;
|
||||
|
||||
data1[optionsArg.iata] = optionsArg.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: Array<Buffer> = [];
|
||||
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: number) {
|
||||
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: plugins.https.RequestOptions, data = ''): Promise<number[]> {
|
||||
let started: number;
|
||||
let dnsLookup: number;
|
||||
let tcpHandshake: number;
|
||||
let sslHandshake: number;
|
||||
let ttfb: number;
|
||||
let ended: number;
|
||||
|
||||
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: string) =>
|
||||
text
|
||||
.split('\n')
|
||||
.map((i) => {
|
||||
const j = i.split('=');
|
||||
|
||||
return [j[0], j[1]];
|
||||
})
|
||||
.reduce((data: any, [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,22 +0,0 @@
|
||||
import * as plugins from './smartnetwork.plugins';
|
||||
|
||||
export class PortScanner {
|
||||
public async checkPortStatus(domainArg: string): Promise<boolean> {
|
||||
const done = plugins.smartpromise.defer<boolean>();
|
||||
const domainPart = domainArg.split(':')[0];
|
||||
const port = parseInt(domainArg.split(':')[1], 10);
|
||||
|
||||
plugins.portscanner.checkPortStatus(port, domainPart, (err, status ) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
if (status === 'open') {
|
||||
done.resolve(true);
|
||||
} else {
|
||||
done.resolve(false)
|
||||
}
|
||||
})
|
||||
const result = await done.promise;
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,51 +1,32 @@
|
||||
import * as plugins from './smartnetwork.plugins';
|
||||
import * as plugins from './smartnetwork.plugins.js';
|
||||
|
||||
export class 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.js';
|
||||
|
||||
/**
|
||||
* SmartNetwork simplifies actions within the network
|
||||
*/
|
||||
export class SmartNetwork {
|
||||
async getSpeed(measurementTime = 5000): Promise<ISpeedtestData> {
|
||||
let 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;
|
||||
/**
|
||||
* get network speed
|
||||
* @param measurementTime
|
||||
*/
|
||||
public async getSpeed() {
|
||||
const cloudflareSpeedInstance = new CloudflareSpeed();
|
||||
const test = await cloudflareSpeedInstance.speedTest();
|
||||
return test;
|
||||
}
|
||||
|
||||
public async ping(hostArg: string, timeoutArg: number = 500): Promise<boolean> {
|
||||
if (process.getuid() !== 0) {
|
||||
console.log('icmp not allowed for nonroot!');
|
||||
return;
|
||||
}
|
||||
const result = await plugins.icmp.ping(hostArg, timeoutArg).catch();
|
||||
if (result) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,21 +34,21 @@ export class SmartNetwork {
|
||||
* note: false also resolves with false as argument
|
||||
* @param port
|
||||
*/
|
||||
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
|
||||
|
||||
// test IPv4 space
|
||||
const ipv4Test = net.createServer();
|
||||
ipv4Test.once('error', function(err: any) {
|
||||
ipv4Test.once('error', (err: any) => {
|
||||
if (err.code !== 'EADDRINUSE') {
|
||||
doneIpV4.resolve(false);
|
||||
return;
|
||||
}
|
||||
doneIpV4.resolve(false);
|
||||
});
|
||||
ipv4Test.once('listening', function() {
|
||||
ipv4Test.once('listening', () => {
|
||||
ipv4Test.once('close', () => {
|
||||
doneIpV4.resolve(true);
|
||||
});
|
||||
@ -78,21 +59,21 @@ export class SmartNetwork {
|
||||
await doneIpV4.promise;
|
||||
|
||||
// test IPv6 space
|
||||
const test_ipv6 = net.createServer();
|
||||
test_ipv6.once('error', function(err: any) {
|
||||
const ipv6Test = net.createServer();
|
||||
ipv6Test.once('error', function (err: any) {
|
||||
if (err.code !== 'EADDRINUSE') {
|
||||
doneIpV6.resolve(false);
|
||||
return;
|
||||
}
|
||||
doneIpV6.resolve(false);
|
||||
});
|
||||
test_ipv6.once('listening', function() {
|
||||
test_ipv6.once('close', () => {
|
||||
ipv6Test.once('listening', () => {
|
||||
ipv6Test.once('close', () => {
|
||||
doneIpV6.resolve(true);
|
||||
});
|
||||
test_ipv6.close();
|
||||
ipv6Test.close();
|
||||
});
|
||||
test_ipv6.listen(port, '::');
|
||||
ipv6Test.listen(port, '::');
|
||||
|
||||
// lets wait for the result
|
||||
const resultIpV4 = await doneIpV4.promise;
|
||||
@ -108,21 +89,48 @@ export class SmartNetwork {
|
||||
public async isRemotePortAvailable(domainArg: string, portArg?: number): Promise<boolean> {
|
||||
const done = plugins.smartpromise.defer<boolean>();
|
||||
const domainPart = domainArg.split(':')[0];
|
||||
const port = (() => {
|
||||
return portArg ? portArg : parseInt(domainArg.split(':')[1], 10);
|
||||
})()
|
||||
const port = portArg ? portArg : parseInt(domainArg.split(':')[1], 10);
|
||||
|
||||
plugins.portscanner.checkPortStatus(port, domainPart, (err, status ) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
if (status === 'open') {
|
||||
plugins.isopen(domainPart, port, (response: any) => {
|
||||
console.log(response);
|
||||
if (response[port.toString()].isOpen) {
|
||||
done.resolve(true);
|
||||
} else {
|
||||
done.resolve(false)
|
||||
done.resolve(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
const result = await done.promise;
|
||||
return result;
|
||||
}
|
||||
|
||||
public async getGateways() {
|
||||
const result = plugins.os.networkInterfaces();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async getDefaultGateway(): Promise<{
|
||||
ipv4: plugins.os.NetworkInterfaceInfo;
|
||||
ipv6: plugins.os.NetworkInterfaceInfo;
|
||||
}> {
|
||||
const defaultGatewayName = await plugins.systeminformation.networkInterfaceDefault();
|
||||
if (!defaultGatewayName) {
|
||||
console.log('Cannot determine default gateway');
|
||||
return null;
|
||||
}
|
||||
const gateways = await this.getGateways();
|
||||
const defaultGateway = gateways[defaultGatewayName];
|
||||
return {
|
||||
ipv4: defaultGateway[0],
|
||||
ipv6: defaultGateway[1],
|
||||
};
|
||||
}
|
||||
|
||||
public async getPublicIps() {
|
||||
return {
|
||||
v4: await plugins.publicIp.v4({
|
||||
timeout: 1000,
|
||||
onlyHttps: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,103 +0,0 @@
|
||||
import * as plugins from './smartnetwork.plugins';
|
||||
|
||||
export class 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;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* SmartNetwork simplifies actions within the network
|
||||
*/
|
||||
export class SmartNetwork {
|
||||
async getSpeed(measurementTime = 5000): Promise<ISpeedtestData> {
|
||||
let 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a promise with a boolean answer
|
||||
* note: false also resolves with false as argument
|
||||
* @param port
|
||||
*/
|
||||
async isLocalPortAvailable(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
|
||||
|
||||
// test IPv4 space
|
||||
const ipv4Test = net.createServer();
|
||||
ipv4Test.once('error', function(err: any) {
|
||||
if (err.code !== 'EADDRINUSE') {
|
||||
doneIpV4.resolve(false);
|
||||
return;
|
||||
}
|
||||
doneIpV4.resolve(false);
|
||||
});
|
||||
ipv4Test.once('listening', function() {
|
||||
ipv4Test.once('close', () => {
|
||||
doneIpV4.resolve(true);
|
||||
});
|
||||
ipv4Test.close();
|
||||
});
|
||||
ipv4Test.listen(port, '0.0.0.0');
|
||||
|
||||
await doneIpV4.promise;
|
||||
|
||||
// test IPv6 space
|
||||
const test_ipv6 = net.createServer();
|
||||
test_ipv6.once('error', function(err: any) {
|
||||
if (err.code !== 'EADDRINUSE') {
|
||||
doneIpV6.resolve(false);
|
||||
return;
|
||||
}
|
||||
doneIpV6.resolve(false);
|
||||
});
|
||||
test_ipv6.once('listening', function() {
|
||||
test_ipv6.once('close', () => {
|
||||
doneIpV6.resolve(true);
|
||||
});
|
||||
test_ipv6.close();
|
||||
});
|
||||
test_ipv6.listen(port, '::');
|
||||
|
||||
// lets wait for the result
|
||||
const resultIpV4 = await doneIpV4.promise;
|
||||
const resultIpV6 = await doneIpV6.promise;
|
||||
const result = resultIpV4 === true && resultIpV6 === true;
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,4 +1,9 @@
|
||||
// native scope
|
||||
import * as os from 'os';
|
||||
import * as https from 'https';
|
||||
import * as perfHooks from 'perf_hooks';
|
||||
|
||||
export { os, https, perfHooks };
|
||||
|
||||
// @pushrocks scope
|
||||
import * as smartpromise from '@pushrocks/smartpromise';
|
||||
@ -7,7 +12,12 @@ import * as smartstring from '@pushrocks/smartstring';
|
||||
export { smartpromise, smartstring };
|
||||
|
||||
// @third party scope
|
||||
let speedtestNet = require('speedtest-net');
|
||||
import * as portscanner from 'portscanner';
|
||||
// @ts-ignore
|
||||
import isopen from 'isopen';
|
||||
// @ts-ignore
|
||||
import icmp from 'icmp';
|
||||
// @ts-ignore
|
||||
import publicIp from 'public-ip';
|
||||
import * as systeminformation from 'systeminformation';
|
||||
|
||||
export { speedtestNet, portscanner };
|
||||
export { isopen, icmp, publicIp, systeminformation };
|
||||
|
5
ts/tsconfig.json
Normal file
5
ts/tsconfig.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": true
|
||||
}
|
||||
}
|
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "nodenext"
|
||||
}
|
||||
}
|
17
tslint.json
17
tslint.json
@ -1,17 +0,0 @@
|
||||
{
|
||||
"extends": ["tslint:latest", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"ordered-imports": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"member-ordering": {
|
||||
"options":{
|
||||
"order": [
|
||||
"static-method"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultSeverity": "warning"
|
||||
}
|
Reference in New Issue
Block a user