Compare commits

..

6 Commits

Author SHA1 Message Date
865b4a53e6 v10.1.3
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-02 09:43:08 +00:00
c07f3975e9 fix(deps): bump @api.global/typedrequest to ^3.2.7 2026-03-02 09:43:08 +00:00
476505537a v10.1.2
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-01 00:44:01 +00:00
74ad5cec90 fix(core): improve shutdown cleanup, socket/stream robustness, and memory/cache handling 2026-03-01 00:44:01 +00:00
59a3f7978e v10.1.1
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-02-27 10:29:20 +00:00
7dc976b59e fix(ops-view-apitokens): replace lucide:refresh-cw with lucide:rotate-cw for Roll action icon 2026-02-27 10:29:20 +00:00
14 changed files with 197 additions and 78 deletions

View File

@@ -1,5 +1,31 @@
# Changelog
## 2026-03-02 - 10.1.3 - fix(deps)
bump @api.global/typedrequest to ^3.2.7
- Updated @api.global/typedrequest from ^3.2.6 to ^3.2.7 in package.json
- Dependency patch bump only — no source code changes detected
- Current package version 10.1.2 -> recommended next version 10.1.3 (patch)
## 2026-03-01 - 10.1.2 - fix(core)
improve shutdown cleanup, socket/stream robustness, and memory/cache handling
- Reset security singletons and CacheDb on shutdown to allow GC (SecurityLogger, ContentScanner, IPReputationChecker, CacheDb).
- Add DNS socket 'error' handler and only destroy socket when not already destroyed to avoid uncaught exceptions.
- Move pruning of dnsMetrics.queryTimestamps to a periodic interval to avoid O(n) work on every query.
- Debounce IPReputationChecker cache saves (save timer + reset on instance reset) to reduce IO and prevent duplicate saves.
- Fix virtualStream send timeout handling by keeping/clearing a timeout handle to avoid leaks and hung promises.
- Add memory store eviction in StorageManager to cap entries (MAX_MEMORY_ENTRIES) and evict oldest entries when exceeded.
- Add terminal-ready timeout in ops-view-logs to avoid blocking UI initialization if xterm CDN fails to initialize.
- Bump dev dependency @types/node and push.rocks/smartstate versions.
## 2026-02-27 - 10.1.1 - fix(ops-view-apitokens)
replace lucide:refresh-cw with lucide:rotate-cw for Roll action icon
- Updated ts_web/elements/ops-view-apitokens.ts: changed iconName in two locations to 'lucide:rotate-cw' for the Roll/Roll Token actions.
- UI-only change — no functional or API behavior modified.
- Current package version is 10.1.0; recommended patch bump to 10.1.1.
## 2026-02-27 - 10.1.0 - feat(api-tokens)
add ability to roll (regenerate) API token secrets and UI to display the newly generated token once

View File

@@ -1,7 +1,7 @@
{
"name": "@serve.zone/dcrouter",
"private": false,
"version": "10.1.0",
"version": "10.1.3",
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
"type": "module",
"exports": {
@@ -24,10 +24,10 @@
"@git.zone/tsrun": "^2.0.1",
"@git.zone/tstest": "^3.1.8",
"@git.zone/tswatch": "^3.2.0",
"@types/node": "^25.3.0"
"@types/node": "^25.3.3"
},
"dependencies": {
"@api.global/typedrequest": "^3.2.6",
"@api.global/typedrequest": "^3.2.7",
"@api.global/typedrequest-interfaces": "^3.0.19",
"@api.global/typedserver": "^8.4.0",
"@api.global/typedsocket": "^4.1.2",
@@ -53,7 +53,7 @@
"@push.rocks/smartradius": "^1.1.1",
"@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartrx": "^3.0.10",
"@push.rocks/smartstate": "^2.0.30",
"@push.rocks/smartstate": "^2.1.1",
"@push.rocks/smartunique": "^3.0.9",
"@serve.zone/catalog": "^2.5.0",
"@serve.zone/interfaces": "^5.3.0",

115
pnpm-lock.yaml generated
View File

@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@api.global/typedrequest':
specifier: ^3.2.6
version: 3.2.6
specifier: ^3.2.7
version: 3.2.7
'@api.global/typedrequest-interfaces':
specifier: ^3.0.19
version: 3.0.19
@@ -87,8 +87,8 @@ importers:
specifier: ^3.0.10
version: 3.0.10
'@push.rocks/smartstate':
specifier: ^2.0.30
version: 2.0.30
specifier: ^2.1.1
version: 2.1.1
'@push.rocks/smartunique':
specifier: ^3.0.9
version: 3.0.9
@@ -127,8 +127,8 @@ importers:
specifier: ^3.2.0
version: 3.2.0(@tiptap/pm@2.27.2)
'@types/node':
specifier: ^25.3.0
version: 25.3.0
specifier: ^25.3.3
version: 25.3.3
packages:
@@ -138,8 +138,8 @@ packages:
'@api.global/typedrequest-interfaces@3.0.19':
resolution: {integrity: sha512-uuHUXJeOy/inWSDrwD0Cwax2rovpxYllDhM2RWh+6mVpQuNmZ3uw6IVg6dA2G1rOe24Ebs+Y9SzEogo+jYN7vw==}
'@api.global/typedrequest@3.2.6':
resolution: {integrity: sha512-CnvbjYjnGGw3rwL+7bTHSgRHEpDujzhs3cv7l1xgCXMPQe3DcPg74+9ep1Y5cu21T/w0pxNnDCJpbb0SHqHzAw==}
'@api.global/typedrequest@3.2.7':
resolution: {integrity: sha512-9CC8EojPDraKlwWK3ZjM8/wJ9jguY/kc+pCgcd61epHFXTIKC8jYts3vKPmEkBPno5Ejn3JZgqp/GRzplCC51w==}
'@api.global/typedserver@3.0.80':
resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==}
@@ -845,6 +845,9 @@ packages:
'@push.rocks/lik@6.2.2':
resolution: {integrity: sha512-j64FFPPyMXeeUorjKJVF6PWaJUfiIrF3pc41iJH4lOh0UUpBAHpcNzHVxTR58orwbVA/h3Hz+DQd4b1Rq0dFDQ==}
'@push.rocks/lik@6.3.1':
resolution: {integrity: sha512-UWDwGBaVx5yPtAFXqDDBtQZCzETUOA/7myQIXb+YBsuiIw4yQuhNZ23uY2ChQH2Zn6DLqdNSgQcYC0WywMZBNQ==}
'@push.rocks/mongodump@1.1.0':
resolution: {integrity: sha512-kW0ZUGyf1e4nwloVwBQjNId+MzgTcNS834C+RxH21i1NqyOubbpWZtJtPP+K+s35nSJRyCTy3ICfBMdDBTAm2w==}
@@ -1083,8 +1086,8 @@ packages:
'@push.rocks/smartspawn@3.0.3':
resolution: {integrity: sha512-DyrGPV69wwOiJgKkyruk5hS3UEGZ99xFAqBE9O2nM8VXCRLbbty3xt1Ug5Z092ZZmJYaaGMSnMw3ijyZJFCT0Q==}
'@push.rocks/smartstate@2.0.30':
resolution: {integrity: sha512-IuNW8XtSumXIr7g7MIFyWg5PBwLF2mwsymTJbSEycK2Pa9ZLk4yjRHnR907xCilxgiMU9ixQZyNdpa5MMF999A==}
'@push.rocks/smartstate@2.1.1':
resolution: {integrity: sha512-4OM9TXfiiSYIgVz2pQdM2UCTurXwd8o9LCtyZ/o+rnntnXp/X8UTWZ+WyTxgnfuzXhpIYXt83t34bVBJ2EPUOw==}
'@push.rocks/smartstream@2.0.8':
resolution: {integrity: sha512-GlF/9cCkvBHwKa3DK4DO5wjfSgqkj6gAS4TrY9uD5NMHu9RQv4WiNrElTYj7iCEpnZgUnLO3tzw1JA3NRIMnnA==}
@@ -1835,11 +1838,11 @@ packages:
'@types/node@18.19.130':
resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==}
'@types/node@22.19.11':
resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==}
'@types/node@22.19.13':
resolution: {integrity: sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==}
'@types/node@25.3.0':
resolution: {integrity: sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==}
'@types/node@25.3.3':
resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==}
'@types/ping@0.4.4':
resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==}
@@ -4291,11 +4294,11 @@ snapshots:
'@api.global/typedrequest-interfaces@3.0.19': {}
'@api.global/typedrequest@3.2.6':
'@api.global/typedrequest@3.2.7':
dependencies:
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/isounique': 1.0.5
'@push.rocks/lik': 6.2.2
'@push.rocks/lik': 6.3.1
'@push.rocks/smartbuffer': 3.0.5
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartguard': 3.1.0
@@ -4305,7 +4308,7 @@ snapshots:
'@api.global/typedserver@3.0.80(@push.rocks/smartserve@2.0.1)':
dependencies:
'@api.global/typedrequest': 3.2.6
'@api.global/typedrequest': 3.2.7
'@api.global/typedrequest-interfaces': 3.0.19
'@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@2.0.1)
'@cloudflare/workers-types': 4.20260210.0
@@ -4353,7 +4356,7 @@ snapshots:
'@api.global/typedserver@8.4.0(@tiptap/pm@2.27.2)':
dependencies:
'@api.global/typedrequest': 3.2.6
'@api.global/typedrequest': 3.2.7
'@api.global/typedrequest-interfaces': 3.0.19
'@api.global/typedsocket': 4.1.2(@push.rocks/smartserve@2.0.1)
'@cloudflare/workers-types': 4.20260303.0
@@ -4399,7 +4402,7 @@ snapshots:
'@api.global/typedsocket@3.1.1(@push.rocks/smartserve@2.0.1)':
dependencies:
'@api.global/typedrequest': 3.2.6
'@api.global/typedrequest': 3.2.7
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/isohash': 2.0.1
'@push.rocks/smartjson': 5.2.0
@@ -4419,7 +4422,7 @@ snapshots:
'@api.global/typedsocket@4.1.2(@push.rocks/smartserve@2.0.1)':
dependencies:
'@api.global/typedrequest': 3.2.6
'@api.global/typedrequest': 3.2.7
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/isohash': 2.0.1
'@push.rocks/smartdelay': 3.0.5
@@ -4994,14 +4997,14 @@ snapshots:
'@design.estate/dees-comms@1.0.30':
dependencies:
'@api.global/typedrequest': 3.2.6
'@api.global/typedrequest': 3.2.7
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/smartdelay': 3.0.5
broadcast-channel: 7.3.0
'@design.estate/dees-domtools@2.3.8':
dependencies:
'@api.global/typedrequest': 3.2.6
'@api.global/typedrequest': 3.2.7
'@design.estate/dees-comms': 1.0.30
'@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5
@@ -5010,7 +5013,7 @@ snapshots:
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrouter': 1.3.3
'@push.rocks/smartrx': 3.0.10
'@push.rocks/smartstate': 2.0.30
'@push.rocks/smartstate': 2.1.1
'@push.rocks/smartstring': 4.1.0
'@push.rocks/smarturl': 3.1.0
'@push.rocks/webrequest': 3.0.37
@@ -5334,7 +5337,7 @@ snapshots:
'@inquirer/figures': 1.0.15
'@inquirer/type': 2.0.0
'@types/mute-stream': 0.0.4
'@types/node': 22.19.11
'@types/node': 22.19.13
'@types/wrap-ansi': 3.0.0
ansi-escapes: 4.3.2
cli-width: 4.1.0
@@ -5676,7 +5679,7 @@ snapshots:
'@push.rocks/levelcache@3.2.0':
dependencies:
'@push.rocks/lik': 6.2.2
'@push.rocks/lik': 6.3.1
'@push.rocks/smartbucket': 3.3.10
'@push.rocks/smartcache': 1.0.18
'@push.rocks/smartenv': 5.0.13
@@ -5707,6 +5710,17 @@ snapshots:
'@types/symbol-tree': 3.2.5
symbol-tree: 3.2.4
'@push.rocks/lik@6.3.1':
dependencies:
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartmatch': 2.0.0
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrx': 3.0.10
'@push.rocks/smarttime': 4.2.3
'@types/minimatch': 5.1.2
'@types/symbol-tree': 3.2.5
symbol-tree: 3.2.4
'@push.rocks/mongodump@1.1.0(socks@2.8.7)':
dependencies:
'@push.rocks/lik': 6.2.2
@@ -5751,7 +5765,7 @@ snapshots:
'@push.rocks/qenv@6.1.3':
dependencies:
'@api.global/typedrequest': 3.2.6
'@api.global/typedrequest': 3.2.7
'@configvault.io/interfaces': 1.0.17
'@push.rocks/smartfile': 11.2.7
'@push.rocks/smartlog': 3.2.1
@@ -6424,7 +6438,7 @@ snapshots:
'@push.rocks/smartserve@2.0.1':
dependencies:
'@api.global/typedrequest': 3.2.6
'@api.global/typedrequest': 3.2.7
'@cfworker/json-schema': 4.1.1
'@push.rocks/lik': 6.2.2
'@push.rocks/smartenv': 6.0.0
@@ -6487,9 +6501,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@push.rocks/smartstate@2.0.30':
'@push.rocks/smartstate@2.1.1':
dependencies:
'@push.rocks/lik': 6.2.2
'@push.rocks/smarthash': 3.2.6
'@push.rocks/smartjson': 6.0.0
'@push.rocks/smartpromise': 4.2.3
@@ -7359,22 +7372,22 @@ snapshots:
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/buffer-json@2.0.3': {}
'@types/clean-css@4.2.11':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
source-map: 0.6.1
'@types/connect@3.4.38':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/cors@2.8.19':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/debug@4.1.12':
dependencies:
@@ -7382,7 +7395,7 @@ snapshots:
'@types/express-serve-static-core@5.1.1':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/qs': 6.14.0
'@types/range-parser': 1.2.7
'@types/send': 1.2.1
@@ -7395,17 +7408,17 @@ snapshots:
'@types/from2@2.3.6':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/fs-extra@11.0.4':
dependencies:
'@types/jsonfile': 6.1.4
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/glob@8.1.0':
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/hast@3.0.4':
dependencies:
@@ -7427,12 +7440,12 @@ snapshots:
'@types/jsonfile@6.1.4':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/jsonwebtoken@9.0.10':
dependencies:
'@types/ms': 2.1.0
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/linkify-it@5.0.0': {}
@@ -7455,26 +7468,26 @@ snapshots:
'@types/mute-stream@0.0.4':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/node-fetch@2.6.13':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
form-data: 4.0.5
'@types/node-forge@1.3.14':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/node@18.19.130':
dependencies:
undici-types: 5.26.5
'@types/node@22.19.11':
'@types/node@22.19.13':
dependencies:
undici-types: 6.21.0
'@types/node@25.3.0':
'@types/node@25.3.3':
dependencies:
undici-types: 7.18.2
@@ -7492,22 +7505,22 @@ snapshots:
'@types/send@1.2.1':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/serve-static@2.2.0':
dependencies:
'@types/http-errors': 2.0.5
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/symbol-tree@3.2.5': {}
'@types/tar-stream@3.1.4':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/through2@2.0.41':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/trusted-types@2.0.7': {}
@@ -7537,11 +7550,11 @@ snapshots:
'@types/ws@8.18.1':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 25.3.0
'@types/node': 25.3.3
optional: true
'@ungap/structured-clone@1.3.0': {}
@@ -8018,7 +8031,7 @@ snapshots:
engine.io@6.6.4:
dependencies:
'@types/cors': 2.8.19
'@types/node': 25.3.0
'@types/node': 25.3.3
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.7.2

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
version: '10.1.0',
version: '10.1.3',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}

View File

@@ -23,6 +23,7 @@ import { MetricsManager } from './monitoring/index.js';
import { RadiusServer, type IRadiusServerConfig } from './radius/index.js';
import { RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
import { RouteConfigManager, ApiTokenManager } from './config/index.js';
import { SecurityLogger, ContentScanner, IPReputationChecker } from './security/index.js';
export interface IDcRouterOptions {
/** Base directory for all dcrouter data. Defaults to ~/.serve.zone/dcrouter */
@@ -956,6 +957,7 @@ export class DcRouter {
// Stop cache database after other services (they may need it during shutdown)
if (this.cacheDb) {
await this.cacheDb.stop().catch(err => logger.log('error', 'Error stopping CacheDb', { error: String(err) }));
CacheDb.resetInstance();
}
// Clear backoff cache in cert scheduler
@@ -979,6 +981,11 @@ export class DcRouter {
this.apiTokenManager = undefined;
this.certificateStatusMap.clear();
// Reset security singletons to allow GC
SecurityLogger.resetInstance();
ContentScanner.resetInstance();
IPReputationChecker.resetInstance();
logger.log('info', 'All DcRouter services stopped');
} catch (error) {
logger.log('error', 'Error during DcRouter shutdown', { error: String(error) });
@@ -1363,6 +1370,14 @@ export class DcRouter {
return;
}
// Prevent uncaught exception from socket 'error' events
socket.on('error', (err) => {
logger.log('error', `DNS socket error: ${err.message}`);
if (!socket.destroyed) {
socket.destroy();
}
});
logger.log('debug', 'DNS socket handler: passing socket to DnsServer');
try {
@@ -1371,7 +1386,9 @@ export class DcRouter {
await (this.dnsServer as any).handleHttpsSocket(socket);
} catch (error) {
logger.log('error', `DNS socket handler error: ${error.message}`);
socket.destroy();
if (!socket.destroyed) {
socket.destroy();
}
}
};
}

View File

@@ -111,6 +111,15 @@ export class MetricsManager {
this.securityMetrics.lastResetDate = currentDate;
}
// Prune old query timestamps (keep last 5 minutes)
const fiveMinutesAgo = Date.now() - 300000;
const idx = this.dnsMetrics.queryTimestamps.findIndex(ts => ts >= fiveMinutesAgo);
if (idx > 0) {
this.dnsMetrics.queryTimestamps = this.dnsMetrics.queryTimestamps.slice(idx);
} else if (idx === -1) {
this.dnsMetrics.queryTimestamps = [];
}
// Prune old time-series buckets every minute (don't wait for lazy query)
this.pruneOldBuckets();
}, 60000); // Check every minute
@@ -427,13 +436,9 @@ export class MetricsManager {
this.dnsMetrics.cacheMisses++;
}
// Track query timestamp
// Track query timestamp (pruning moved to resetInterval to avoid O(n) per query)
this.dnsMetrics.queryTimestamps.push(Date.now());
// Keep only timestamps from last 5 minutes
const fiveMinutesAgo = Date.now() - 300000;
this.dnsMetrics.queryTimestamps = this.dnsMetrics.queryTimestamps.filter(ts => ts >= fiveMinutesAgo);
// Track response time if provided
if (responseTimeMs) {
this.dnsMetrics.responseTimes.push(responseTimeMs);

View File

@@ -318,11 +318,15 @@ export class LogsHandler {
try {
// Use a timeout to detect hung streams (sendData can hang if the
// VirtualStream's keepAlive loop has ended)
let timeoutHandle: ReturnType<typeof setTimeout>;
await Promise.race([
virtualStream.sendData(encoder.encode(logData)),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('stream send timeout')), 10_000)
),
virtualStream.sendData(encoder.encode(logData)).then((result) => {
clearTimeout(timeoutHandle);
return result;
}),
new Promise<never>((_, reject) => {
timeoutHandle = setTimeout(() => reject(new Error('stream send timeout')), 10_000);
}),
]);
} catch {
// Stream closed, errored, or timed out — clean up

View File

@@ -183,6 +183,13 @@ export class ContentScanner {
return ContentScanner.instance;
}
/**
* Reset the singleton instance (for shutdown/testing)
*/
public static resetInstance(): void {
ContentScanner.instance = undefined;
}
/**
* Scan an email for malicious content
* @param email The email to scan

View File

@@ -65,6 +65,8 @@ export class IPReputationChecker {
private reputationCache: LRUCache<string, IReputationResult>;
private options: Required<IIPReputationOptions>;
private storageManager?: any; // StorageManager instance
private saveCacheTimer: ReturnType<typeof setTimeout> | null = null;
private static readonly SAVE_CACHE_DEBOUNCE_MS = 30_000;
// Default DNSBL servers
private static readonly DEFAULT_DNSBL_SERVERS = [
@@ -144,6 +146,19 @@ export class IPReputationChecker {
return IPReputationChecker.instance;
}
/**
* Reset the singleton instance (for shutdown/testing)
*/
public static resetInstance(): void {
if (IPReputationChecker.instance) {
if (IPReputationChecker.instance.saveCacheTimer) {
clearTimeout(IPReputationChecker.instance.saveCacheTimer);
IPReputationChecker.instance.saveCacheTimer = null;
}
}
IPReputationChecker.instance = undefined;
}
/**
* Check an IP address's reputation
* @param ip IP address to check
@@ -213,12 +228,9 @@ export class IPReputationChecker {
// Update cache with result
this.reputationCache.set(ip, result);
// Save cache if enabled
// Schedule debounced cache save if enabled
if (this.options.enableLocalCache) {
// Fire and forget the save operation
this.saveCache().catch(error => {
logger.log('error', `Failed to save IP reputation cache: ${error.message}`);
});
this.debouncedSaveCache();
}
// Log the reputation check
@@ -447,6 +459,21 @@ export class IPReputationChecker {
});
}
/**
* Schedule a debounced cache save (at most once per SAVE_CACHE_DEBOUNCE_MS)
*/
private debouncedSaveCache(): void {
if (this.saveCacheTimer) {
return; // already scheduled
}
this.saveCacheTimer = setTimeout(() => {
this.saveCacheTimer = null;
this.saveCache().catch(error => {
logger.log('error', `Failed to save IP reputation cache: ${error.message}`);
});
}, IPReputationChecker.SAVE_CACHE_DEBOUNCE_MS);
}
/**
* Save cache to disk or storage manager
*/

View File

@@ -84,6 +84,13 @@ export class SecurityLogger {
return SecurityLogger.instance;
}
/**
* Reset the singleton instance (for shutdown/testing)
*/
public static resetInstance(): void {
SecurityLogger.instance = undefined;
}
/**
* Log a security event
* @param event The security event to log

View File

@@ -30,6 +30,7 @@ export type StorageBackend = 'filesystem' | 'custom' | 'memory';
* Provides unified key-value storage with multiple backend support
*/
export class StorageManager {
private static readonly MAX_MEMORY_ENTRIES = 10_000;
private backend: StorageBackend;
private memoryStore: Map<string, string> = new Map();
private config: IStorageConfig;
@@ -227,6 +228,11 @@ export class StorageManager {
case 'memory': {
this.memoryStore.set(key, value);
// Evict oldest entries if memory store exceeds limit
while (this.memoryStore.size > StorageManager.MAX_MEMORY_ENTRIES) {
const firstKey = this.memoryStore.keys().next().value;
this.memoryStore.delete(firstKey);
}
break;
}

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
version: '10.1.0',
version: '10.1.3',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}

View File

@@ -154,7 +154,7 @@ export class OpsViewApiTokens extends DeesElement {
},
{
name: 'Roll',
iconName: 'lucide:refresh-cw',
iconName: 'lucide:rotate-cw',
type: ['inRow', 'contextmenu'] as any,
actionFunc: async (actionData: any) => {
const token = actionData.item as interfaces.data.IApiTokenInfo;
@@ -306,7 +306,7 @@ export class OpsViewApiTokens extends DeesElement {
},
{
name: 'Roll Token',
iconName: 'lucide:refresh-cw',
iconName: 'lucide:rotate-cw',
action: async (modalArg: any) => {
await modalArg.destroy();
try {

View File

@@ -76,8 +76,15 @@ export class OpsViewLogs extends DeesElement {
// Wait for xterm terminal to finish initializing (CDN load)
if (!chartLog.terminalReady) {
await new Promise<void>((resolve) => {
let attempts = 0;
const maxAttempts = 200; // 200 * 50ms = 10 seconds
const check = () => {
if (chartLog.terminalReady) { resolve(); return; }
if (++attempts >= maxAttempts) {
console.warn('ops-view-logs: terminal ready timeout after 10s');
resolve(); // resolve gracefully to avoid blocking
return;
}
setTimeout(check, 50);
};
check();