Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
d6be2e27b0 | |||
d6c0af35fa | |||
bc19c21949 | |||
dba1855eb6 |
66
.gitea/workflows/default_nottags.yaml
Normal file
66
.gitea/workflows/default_nottags.yaml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
name: Default (not tags)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags-ignore:
|
||||||
|
- '**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||||
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||||
|
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||||
|
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||||
|
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||||
|
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
security:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install pnpm and npmci
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @ship.zone/npmci
|
||||||
|
|
||||||
|
- name: Run npm prepare
|
||||||
|
run: npmci npm prepare
|
||||||
|
|
||||||
|
- name: Audit production dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --prod
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Audit development dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --dev
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
test:
|
||||||
|
if: ${{ always() }}
|
||||||
|
needs: security
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Test stable
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci npm test
|
||||||
|
|
||||||
|
- name: Test build
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci npm build
|
124
.gitea/workflows/default_tags.yaml
Normal file
124
.gitea/workflows/default_tags.yaml
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
name: Default (tags)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||||
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||||
|
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||||
|
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||||
|
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||||
|
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
security:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @ship.zone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Audit production dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --prod
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Audit development dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --dev
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
test:
|
||||||
|
if: ${{ always() }}
|
||||||
|
needs: security
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @ship.zone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Test stable
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci npm test
|
||||||
|
|
||||||
|
- name: Test build
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci npm build
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: test
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @ship.zone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm publish
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
needs: test
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @ship.zone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Code quality
|
||||||
|
run: |
|
||||||
|
npmci command npm install -g typescript
|
||||||
|
npmci npm install
|
||||||
|
|
||||||
|
- name: Trigger
|
||||||
|
run: npmci trigger
|
||||||
|
|
||||||
|
- name: Build docs and upload artifacts
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
pnpm install -g @git.zone/tsdoc
|
||||||
|
npmci command tsdoc
|
||||||
|
continue-on-error: true
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,7 +3,6 @@
|
|||||||
# artifacts
|
# artifacts
|
||||||
coverage/
|
coverage/
|
||||||
public/
|
public/
|
||||||
pages/
|
|
||||||
|
|
||||||
# installs
|
# installs
|
||||||
node_modules/
|
node_modules/
|
||||||
@ -17,4 +16,4 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
dist_*/
|
dist_*/
|
||||||
|
|
||||||
# custom
|
#------# custom
|
141
.gitlab-ci.yml
141
.gitlab-ci.yml
@ -1,141 +0,0 @@
|
|||||||
# gitzone ci_default
|
|
||||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
|
||||||
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- .npmci_cache/
|
|
||||||
key: '$CI_BUILD_STAGE'
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- security
|
|
||||||
- test
|
|
||||||
- release
|
|
||||||
- metadata
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- npm install -g @shipzone/npmci
|
|
||||||
|
|
||||||
# ====================
|
|
||||||
# security stage
|
|
||||||
# ====================
|
|
||||||
mirror:
|
|
||||||
stage: security
|
|
||||||
script:
|
|
||||||
- npmci git mirror
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
|
||||||
# ====================
|
|
||||||
|
|
||||||
testStable:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci npm prepare
|
|
||||||
- npmci node install stable
|
|
||||||
- npmci npm install
|
|
||||||
- npmci npm test
|
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
testBuild:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci npm prepare
|
|
||||||
- npmci node install stable
|
|
||||||
- npmci npm install
|
|
||||||
- npmci command npm run build
|
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
release:
|
|
||||||
stage: release
|
|
||||||
script:
|
|
||||||
- npmci node install stable
|
|
||||||
- npmci npm publish
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
|
|
||||||
# ====================
|
|
||||||
# metadata stage
|
|
||||||
# ====================
|
|
||||||
codequality:
|
|
||||||
stage: metadata
|
|
||||||
allow_failure: true
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
script:
|
|
||||||
- npmci command npm install -g tslint typescript
|
|
||||||
- npmci npm prepare
|
|
||||||
- npmci npm install
|
|
||||||
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
- priv
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
stage: metadata
|
|
||||||
script:
|
|
||||||
- npmci trigger
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
|
|
||||||
pages:
|
|
||||||
stage: metadata
|
|
||||||
script:
|
|
||||||
- npmci node install lts
|
|
||||||
- npmci command npm install -g @git.zone/tsdoc
|
|
||||||
- npmci npm prepare
|
|
||||||
- npmci npm install
|
|
||||||
- npmci command tsdoc
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
artifacts:
|
|
||||||
expire_in: 1 week
|
|
||||||
paths:
|
|
||||||
- public
|
|
||||||
allow_failure: true
|
|
17
changelog.md
17
changelog.md
@ -1,5 +1,22 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-04-28 - 3.0.5 - fix(core)
|
||||||
|
Improve logging and error handling by introducing custom error classes and a global logging interface while refactoring network diagnostics methods.
|
||||||
|
|
||||||
|
- Added custom error classes (NetworkError, TimeoutError) for network operations.
|
||||||
|
- Introduced a global logging interface to replace direct console logging.
|
||||||
|
- Updated CloudflareSpeed and SmartNetwork classes to use getLogger for improved error reporting.
|
||||||
|
- Disabled connection pooling in HTTP requests to prevent listener accumulation.
|
||||||
|
|
||||||
|
## 2025-04-28 - 3.0.4 - fix(ci/config)
|
||||||
|
Improve CI workflows, update project configuration, and clean up code formatting
|
||||||
|
|
||||||
|
- Added new Gitea workflow files (default_nottags.yaml and default_tags.yaml) to replace GitLab CI
|
||||||
|
- Updated package.json with new buildDocs script, revised homepage URL, bug tracking info, and pnpm overrides
|
||||||
|
- Refined code formatting in TypeScript files, including improved error handling in Cloudflare speed tests and consistent callback structure
|
||||||
|
- Enhanced tsconfig.json by adding baseUrl and paths for better module resolution
|
||||||
|
- Introduced readme.plan.md outlining future improvements and feature enhancements
|
||||||
|
|
||||||
## 2025-04-28 - 3.0.3 - fix(deps)
|
## 2025-04-28 - 3.0.3 - fix(deps)
|
||||||
Update dependency namespaces and bump package versions in CI configuration and source imports
|
Update dependency namespaces and bump package versions in CI configuration and source imports
|
||||||
|
|
||||||
|
15
package.json
15
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartnetwork",
|
"name": "@push.rocks/smartnetwork",
|
||||||
"version": "3.0.3",
|
"version": "3.0.5",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A toolkit for network diagnostics including speed tests, port availability checks, and more.",
|
"description": "A toolkit for network diagnostics including speed tests, port availability checks, and more.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
@ -10,7 +10,8 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/)",
|
"test": "(tstest test/)",
|
||||||
"build": "(tsbuild --web --allowimplicitany)"
|
"build": "(tsbuild --web --allowimplicitany)",
|
||||||
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.1.61",
|
"@git.zone/tsbuild": "^2.1.61",
|
||||||
@ -56,10 +57,16 @@
|
|||||||
"network utility",
|
"network utility",
|
||||||
"TypeScript"
|
"TypeScript"
|
||||||
],
|
],
|
||||||
"homepage": "https://code.foss.global/push.rocks/smartnetwork",
|
"homepage": "https://code.foss.global/push.rocks/smartnetwork#readme",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://code.foss.global/push.rocks/smartnetwork.git"
|
"url": "https://code.foss.global/push.rocks/smartnetwork.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://code.foss.global/push.rocks/smartnetwork/issues"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# @push.rocks/smartnetwork
|
# @push.rocks/smartnetwork
|
||||||
|
|
||||||
network diagnostics
|
network diagnostics
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
47
readme.plan.md
Normal file
47
readme.plan.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Plan to Enhance Code Quality, Feature Set & Documentation
|
||||||
|
|
||||||
|
This plan focuses on three pillars to elevate `@push.rocks/smartnetwork`: 1) Code Quality, 2) Feature Enhancements, and 3) Documentation.
|
||||||
|
|
||||||
|
## 1. Code Quality Improvements
|
||||||
|
- Enable strict TypeScript (`strict`, `noImplicitAny`, `strictNullChecks`).
|
||||||
|
- Enforce linting (ESLint) and formatting (Prettier) with pre-commit hooks.
|
||||||
|
- Audit and refactor core modules for:
|
||||||
|
- Clear separation of concerns (IO, business logic, helpers).
|
||||||
|
- Removal of duplicated logic and dead code.
|
||||||
|
- Consistent use of async/await and error propagation.
|
||||||
|
- Introduce custom error classes (e.g., `NetworkError`, `TimeoutError`) for predictable failure handling.
|
||||||
|
- Augment logging support via injectable logger interface.
|
||||||
|
- Establish a baseline of ≥90% unit-test coverage and enforce via CI.
|
||||||
|
|
||||||
|
## 2. Feature Enhancements
|
||||||
|
- Expand diagnostics:
|
||||||
|
- Traceroute functionality with hop-by-hop latency.
|
||||||
|
- DNS lookup (A, AAAA, MX records).
|
||||||
|
- HTTP(s) endpoint health check (status codes, headers, latency).
|
||||||
|
- Improve existing methods:
|
||||||
|
- `getSpeed`: allow configurable test duration and parallel streams.
|
||||||
|
- `ping`: add statistical summary (min, max, stddev) and continuous mode.
|
||||||
|
- `isRemotePortAvailable`: support TCP/UDP checks with timeout and retry.
|
||||||
|
- Introduce plugin architecture:
|
||||||
|
- Define `Plugin` interface for third-party extensions.
|
||||||
|
- Enable runtime registration/unregistration.
|
||||||
|
- Provide sample plugins (e.g., custom ping strategies, alternate speed providers).
|
||||||
|
- Optional in-memory caching with TTL for expensive calls (`getPublicIps`, `getGateways`).
|
||||||
|
|
||||||
|
## 3. Documentation & Examples
|
||||||
|
- Upgrade README:
|
||||||
|
- Detailed API reference with method signatures and option parameters.
|
||||||
|
- Real-world usage snippets and full example projects.
|
||||||
|
- Add TSDoc comments to all public classes, methods, and types.
|
||||||
|
- Create a `docs/` folder with:
|
||||||
|
- Getting Started guide.
|
||||||
|
- Advanced topics (plugin development, custom error handling).
|
||||||
|
- FAQ and troubleshooting section.
|
||||||
|
- Integrate TypeDoc for automated documentation site generation.
|
||||||
|
- Update `CONTRIBUTING.md` and `CHANGELOG.md` to reflect development and release practices.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
1. Review and prioritize high-impact items per pillar.
|
||||||
|
2. Kick off Phase 1 (Code Quality) with linting, TS config, and core refactor.
|
||||||
|
3. Schedule sprints for Feature and Documentation phases.
|
||||||
|
4. Configure CI pipeline to enforce quality gates and publish docs.
|
@ -20,6 +20,6 @@ tap.test('should state when a ping is not alive ', async () => {
|
|||||||
|
|
||||||
tap.test('should send a ping to an IP', async () => {
|
tap.test('should send a ping to an IP', async () => {
|
||||||
await expectAsync(testSmartnetwork.ping('192.168.186.999')).property('alive').toBeFalse();
|
await expectAsync(testSmartnetwork.ping('192.168.186.999')).property('alive').toBeFalse();
|
||||||
})
|
});
|
||||||
|
|
||||||
tap.start();
|
tap.start();
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartnetwork',
|
name: '@push.rocks/smartnetwork',
|
||||||
version: '3.0.3',
|
version: '3.0.5',
|
||||||
description: 'A toolkit for network diagnostics including speed tests, port availability checks, and more.'
|
description: 'A toolkit for network diagnostics including speed tests, port availability checks, and more.'
|
||||||
}
|
}
|
||||||
|
20
ts/errors.ts
Normal file
20
ts/errors.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Custom error classes for network operations
|
||||||
|
*/
|
||||||
|
export class NetworkError extends Error {
|
||||||
|
public code?: string;
|
||||||
|
constructor(message?: string, code?: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'NetworkError';
|
||||||
|
this.code = code;
|
||||||
|
Object.setPrototypeOf(this, new.target.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TimeoutError extends NetworkError {
|
||||||
|
constructor(message?: string) {
|
||||||
|
super(message, 'ETIMEOUT');
|
||||||
|
this.name = 'TimeoutError';
|
||||||
|
Object.setPrototypeOf(this, new.target.prototype);
|
||||||
|
}
|
||||||
|
}
|
30
ts/logging.ts
Normal file
30
ts/logging.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Injectable logging interface and global logger
|
||||||
|
*/
|
||||||
|
export interface Logger {
|
||||||
|
/** Debug-level messages */
|
||||||
|
debug?(...args: unknown[]): void;
|
||||||
|
/** Informational messages */
|
||||||
|
info(...args: unknown[]): void;
|
||||||
|
/** Warning messages */
|
||||||
|
warn?(...args: unknown[]): void;
|
||||||
|
/** Error messages */
|
||||||
|
error(...args: unknown[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let globalLogger: Logger = console;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the global logger implementation
|
||||||
|
* @param logger Custom logger adhering to Logger interface
|
||||||
|
*/
|
||||||
|
export function setLogger(logger: Logger): void {
|
||||||
|
globalLogger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the current global logger
|
||||||
|
*/
|
||||||
|
export function getLogger(): Logger {
|
||||||
|
return globalLogger;
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
import * as plugins from './smartnetwork.plugins.js';
|
import * as plugins from './smartnetwork.plugins.js';
|
||||||
|
import { getLogger } from './logging.js';
|
||||||
|
import { NetworkError, TimeoutError } from './errors.js';
|
||||||
import * as stats from './helpers/stats.js';
|
import * as stats from './helpers/stats.js';
|
||||||
|
|
||||||
export class CloudflareSpeed {
|
export class CloudflareSpeed {
|
||||||
@ -49,8 +51,8 @@ export class CloudflareSpeed {
|
|||||||
measurements.push(response[4] - response[0] - response[6]);
|
measurements.push(response[4] - response[0] - response[6]);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.log(`Error: ${error}`);
|
getLogger().error('Error measuring latency:', error);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,8 +75,8 @@ export class CloudflareSpeed {
|
|||||||
measurements.push(await this.measureSpeed(bytes, transferTime));
|
measurements.push(await this.measureSpeed(bytes, transferTime));
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.log(`Error: ${error}`);
|
getLogger().error('Error measuring download chunk:', error);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,8 +93,8 @@ export class CloudflareSpeed {
|
|||||||
measurements.push(await this.measureSpeed(bytes, transferTime));
|
measurements.push(await this.measureSpeed(bytes, transferTime));
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.log(`Error: ${error}`);
|
getLogger().error('Error measuring upload chunk:', error);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,15 +106,16 @@ export class CloudflareSpeed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async fetchServerLocations(): Promise<{ [key: string]: string }> {
|
public async fetchServerLocations(): Promise<{ [key: string]: string }> {
|
||||||
const res = JSON.parse(await this.get('speed.cloudflare.com', '/locations'));
|
const res = JSON.parse(
|
||||||
|
await this.get('speed.cloudflare.com', '/locations'),
|
||||||
return res.reduce((data: any, optionsArg: { iata: string; city: string }) => {
|
) as Array<{ iata: string; city: string }>;
|
||||||
// Bypass prettier "no-assign-param" rules
|
return res.reduce(
|
||||||
const data1 = data;
|
(data: Record<string, string>, optionsArg) => {
|
||||||
|
data[optionsArg.iata] = optionsArg.city;
|
||||||
data1[optionsArg.iata] = optionsArg.city;
|
return data;
|
||||||
return data1;
|
},
|
||||||
}, {});
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async get(hostname: string, path: string): Promise<string> {
|
public async get(hostname: string, path: string): Promise<string> {
|
||||||
@ -122,6 +125,8 @@ export class CloudflareSpeed {
|
|||||||
hostname,
|
hostname,
|
||||||
path,
|
path,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
// disable connection pooling to avoid listener accumulation
|
||||||
|
agent: false,
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
const body: Array<Buffer> = [];
|
const body: Array<Buffer> = [];
|
||||||
@ -135,10 +140,10 @@ export class CloudflareSpeed {
|
|||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
req.on('error', (err) => {
|
req.on('error', (err: Error & { code?: string }) => {
|
||||||
reject(err);
|
reject(new NetworkError(err.message, err.code));
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
req.end();
|
req.end();
|
||||||
@ -179,7 +184,9 @@ export class CloudflareSpeed {
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
started = plugins.perfHooks.performance.now();
|
started = plugins.perfHooks.performance.now();
|
||||||
const req = plugins.https.request(options, (res) => {
|
// disable connection pooling to avoid listener accumulation across requests
|
||||||
|
const reqOptions = { ...options, agent: false };
|
||||||
|
const req = plugins.https.request(reqOptions, (res) => {
|
||||||
res.once('readable', () => {
|
res.once('readable', () => {
|
||||||
ttfb = plugins.perfHooks.performance.now();
|
ttfb = plugins.perfHooks.performance.now();
|
||||||
});
|
});
|
||||||
@ -193,19 +200,20 @@ export class CloudflareSpeed {
|
|||||||
sslHandshake,
|
sslHandshake,
|
||||||
ttfb,
|
ttfb,
|
||||||
ended,
|
ended,
|
||||||
parseFloat(res.headers['server-timing'].slice(22) as any),
|
parseFloat((res.headers['server-timing'] as string).slice(22)),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on('socket', (socket) => {
|
// Listen for timing events once per new socket
|
||||||
socket.on('lookup', () => {
|
req.once('socket', (socket) => {
|
||||||
|
socket.once('lookup', () => {
|
||||||
dnsLookup = plugins.perfHooks.performance.now();
|
dnsLookup = plugins.perfHooks.performance.now();
|
||||||
});
|
});
|
||||||
socket.on('connect', () => {
|
socket.once('connect', () => {
|
||||||
tcpHandshake = plugins.perfHooks.performance.now();
|
tcpHandshake = plugins.perfHooks.performance.now();
|
||||||
});
|
});
|
||||||
socket.on('secureConnect', () => {
|
socket.once('secureConnect', () => {
|
||||||
sslHandshake = plugins.perfHooks.performance.now();
|
sslHandshake = plugins.perfHooks.performance.now();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -238,20 +246,14 @@ export class CloudflareSpeed {
|
|||||||
text
|
text
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((i) => {
|
.map((i) => {
|
||||||
const j = i.split('=');
|
const parts = i.split('=');
|
||||||
|
return [parts[0], parts[1]];
|
||||||
return [j[0], j[1]];
|
|
||||||
})
|
})
|
||||||
.reduce((data: any, [k, v]) => {
|
.reduce((data: Record<string, string>, [k, v]) => {
|
||||||
if (v === undefined) return data;
|
if (v === undefined) return data;
|
||||||
|
data[k] = v;
|
||||||
// Bypass prettier "no-assign-param" rules
|
return data;
|
||||||
const data1 = data;
|
}, {} as Record<string, string>);
|
||||||
// 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);
|
return this.get('speed.cloudflare.com', '/cdn-cgi/trace').then(parseCfCdnCgiTrace);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as plugins from './smartnetwork.plugins.js';
|
import * as plugins from './smartnetwork.plugins.js';
|
||||||
|
|
||||||
import { CloudflareSpeed } from './smartnetwork.classes.cloudflarespeed.js';
|
import { CloudflareSpeed } from './smartnetwork.classes.cloudflarespeed.js';
|
||||||
|
import { getLogger } from './logging.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SmartNetwork simplifies actions within the network
|
* SmartNetwork simplifies actions within the network
|
||||||
@ -16,7 +17,10 @@ export class SmartNetwork {
|
|||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ping(hostArg: string, timeoutArg: number = 500): Promise<ReturnType<typeof plugins.smartping.Smartping.prototype.ping>> {
|
public async ping(
|
||||||
|
hostArg: string,
|
||||||
|
timeoutArg: number = 500,
|
||||||
|
): Promise<ReturnType<typeof plugins.smartping.Smartping.prototype.ping>> {
|
||||||
const smartpingInstance = new plugins.smartping.Smartping();
|
const smartpingInstance = new plugins.smartping.Smartping();
|
||||||
const pingResult = await smartpingInstance.ping(hostArg, timeoutArg);
|
const pingResult = await smartpingInstance.ping(hostArg, timeoutArg);
|
||||||
return pingResult;
|
return pingResult;
|
||||||
@ -34,11 +38,7 @@ export class SmartNetwork {
|
|||||||
|
|
||||||
// test IPv4 space
|
// test IPv4 space
|
||||||
const ipv4Test = net.createServer();
|
const ipv4Test = net.createServer();
|
||||||
ipv4Test.once('error', (err: any) => {
|
ipv4Test.once('error', () => {
|
||||||
if (err.code !== 'EADDRINUSE') {
|
|
||||||
doneIpV4.resolve(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
doneIpV4.resolve(false);
|
doneIpV4.resolve(false);
|
||||||
});
|
});
|
||||||
ipv4Test.once('listening', () => {
|
ipv4Test.once('listening', () => {
|
||||||
@ -53,11 +53,7 @@ export class SmartNetwork {
|
|||||||
|
|
||||||
// test IPv6 space
|
// test IPv6 space
|
||||||
const ipv6Test = net.createServer();
|
const ipv6Test = net.createServer();
|
||||||
ipv6Test.once('error', function (err: any) {
|
ipv6Test.once('error', () => {
|
||||||
if (err.code !== 'EADDRINUSE') {
|
|
||||||
doneIpV6.resolve(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
doneIpV6.resolve(false);
|
doneIpV6.resolve(false);
|
||||||
});
|
});
|
||||||
ipv6Test.once('listening', () => {
|
ipv6Test.once('listening', () => {
|
||||||
@ -84,14 +80,15 @@ export class SmartNetwork {
|
|||||||
const domainPart = domainArg.split(':')[0];
|
const domainPart = domainArg.split(':')[0];
|
||||||
const port = portArg ? portArg : parseInt(domainArg.split(':')[1], 10);
|
const port = portArg ? portArg : parseInt(domainArg.split(':')[1], 10);
|
||||||
|
|
||||||
plugins.isopen(domainPart, port, (response: any) => {
|
plugins.isopen(
|
||||||
console.log(response);
|
domainPart,
|
||||||
if (response[port.toString()].isOpen) {
|
port,
|
||||||
done.resolve(true);
|
(response: Record<string, { isOpen: boolean }>) => {
|
||||||
} else {
|
getLogger().debug(response);
|
||||||
done.resolve(false);
|
const portInfo = response[port.toString()];
|
||||||
}
|
done.resolve(Boolean(portInfo?.isOpen));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
const result = await done.promise;
|
const result = await done.promise;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -107,7 +104,7 @@ export class SmartNetwork {
|
|||||||
}> {
|
}> {
|
||||||
const defaultGatewayName = await plugins.systeminformation.networkInterfaceDefault();
|
const defaultGatewayName = await plugins.systeminformation.networkInterfaceDefault();
|
||||||
if (!defaultGatewayName) {
|
if (!defaultGatewayName) {
|
||||||
console.log('Cannot determine default gateway');
|
getLogger().warn?.('Cannot determine default gateway');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const gateways = await this.getGateways();
|
const gateways = await this.getGateways();
|
||||||
@ -120,18 +117,22 @@ export class SmartNetwork {
|
|||||||
|
|
||||||
public async getPublicIps() {
|
public async getPublicIps() {
|
||||||
return {
|
return {
|
||||||
v4: await plugins.publicIp.publicIpv4({
|
v4: await plugins.publicIp
|
||||||
|
.publicIpv4({
|
||||||
timeout: 1000,
|
timeout: 1000,
|
||||||
onlyHttps: true,
|
onlyHttps: true,
|
||||||
}).catch(async (err) => {
|
|
||||||
return null
|
|
||||||
}),
|
|
||||||
v6: await plugins.publicIp.publicIpv6({
|
|
||||||
timeout: 1000,
|
|
||||||
onlyHttps: true,
|
|
||||||
}).catch(async (err) => {
|
|
||||||
return null
|
|
||||||
})
|
})
|
||||||
|
.catch(async (err) => {
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
v6: await plugins.publicIp
|
||||||
|
.publicIpv6({
|
||||||
|
timeout: 1000,
|
||||||
|
onlyHttps: true,
|
||||||
|
})
|
||||||
|
.catch(async (err) => {
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"verbatimModuleSyntax": true
|
"verbatimModuleSyntax": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {}
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"dist_*/**/*.d.ts"
|
"dist_*/**/*.d.ts"
|
||||||
|
Reference in New Issue
Block a user