Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa543160e2 | |||
| 94fa0f04d8 | |||
| 17deb481e0 | |||
| e452ffd38e | |||
| 865b4a53e6 | |||
| c07f3975e9 | |||
| 476505537a | |||
| 74ad5cec90 | |||
| 59a3f7978e | |||
| 7dc976b59e | |||
| 345effee13 | |||
| dee6897931 | |||
| 56f41d70b3 | |||
| 8f570ae8a0 | |||
| e58e24a92d | |||
| 12070bc7b5 | |||
| 37d62c51f3 | |||
| ea9427d46b | |||
| bc77321752 | |||
| 65aa546c1c | |||
| 54484518dc | |||
| 6fe1247d4d | |||
| e59d80a3b3 | |||
| 6c4feba711 | |||
| 006a9af20c | |||
| dfb3b0ac37 | |||
| 44c1a3a928 | |||
| 0c4e28455e | |||
| cfc4cf378f | |||
| a09e69a28b | |||
| 82dd19e274 | |||
| c1d8afdbf7 | |||
| 9b7426f1e6 | |||
| 3c9c865841 | |||
| 8421c9fe46 | |||
| 907e3df156 | |||
| aaa0956148 | |||
| 118019fcf5 |
146
changelog.md
146
changelog.md
@@ -1,5 +1,151 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-03-02 - 10.1.5 - fix(monitoring)
|
||||
use a per-second ring buffer for DNS query metrics, improve DNS logging rate limiting and security event aggregation, and bump smartmta dependency
|
||||
|
||||
- Replace unbounded query timestamp array with a fixed-size per-second Int32Array ring buffer (300s) to calculate queries-per-second with O(1) updates and bounded memory
|
||||
- Add incrementQueryRing and getQueryRingSum helpers to correctly zero stale slots and sum recent seconds
|
||||
- Change metrics cache interval from 200ms to 1000ms to better match dashboard polling and reduce update frequency
|
||||
- Refactor DNS adaptive logging to use per-second counters (dnsLogWindowSecond / dnsLogWindowCount) instead of timestamp arrays to avoid per-query array filtering and improve rate limiting accuracy; reset counters on flush
|
||||
- Security logger: avoid mutating source when sorting/filtering, and implement single-pass aggregation with optional time-window filtering for byLevel/byType/top lists
|
||||
- Bump dependency @push.rocks/smartmta from ^5.3.0 to ^5.3.1
|
||||
|
||||
## 2026-03-02 - 10.1.4 - fix(no-changes)
|
||||
no changes detected; no version bump required
|
||||
|
||||
- package version is 10.1.3
|
||||
- git diff contains no changes
|
||||
|
||||
## 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
|
||||
|
||||
- Server: added ApiTokenManager.rollToken(id) to regenerate a token secret, update its hash, persist it and log the action.
|
||||
- Server: added opsserver handler 'rollApiToken' which requires admin identity and returns the new raw token value (shown once) or error messages.
|
||||
- API: added typed request interface IReq_RollApiToken for the rollApiToken RPC.
|
||||
- Web: added appstate.rollApiToken wrapper to call the new typed request.
|
||||
- UI: ops-view-apitokens updated with a 'Roll' action and a modal flow to confirm rolling, call the API, refresh token list, and present the new token value to copy (token value is shown only once).
|
||||
- Security: operation is admin-only and the raw token is returned only once after rolling.
|
||||
|
||||
## 2026-02-27 - 10.0.0 - BREAKING CHANGE(remote-ingress)
|
||||
replace tlsConfigured boolean with tlsMode ('custom' | 'acme' | 'self-signed') and compute TLS mode server-side
|
||||
|
||||
- Server: compute remoteIngress.tlsMode = 'custom' when custom certPath/keyPath provided; else attempt to detect ACME by checking stored certs for hubDomain; default to 'self-signed' as fallback.
|
||||
- API: replaced remoteIngress.tlsConfigured:boolean with tlsMode:'custom'|'acme'|'self-signed' — this is a breaking change for consumers of the config API.
|
||||
- UI: ops view updated to display TLS Mode as a badge instead of a boolean "TLS Configured" field.
|
||||
- Action required: update clients and integrations to read remoteIngress.tlsMode instead of tlsConfigured.
|
||||
|
||||
## 2026-02-26 - 9.3.0 - feat(remoteingress)
|
||||
add TLS certificate resolution and passthrough for RemoteIngress tunnel
|
||||
|
||||
- Resolve TLS certs for the RemoteIngress tunnel with priority: explicit certPath/keyPath files → stored ACME cert for hubDomain → fallback to self-signed
|
||||
- Expose tls option on ITunnelManagerConfig and forward certPem/keyPem into hub.start so the hub can use the provided TLS materials
|
||||
- Add logging for cert selection and file read failures
|
||||
- Bump dependency @serve.zone/remoteingress from ^4.2.0 to ^4.3.0
|
||||
|
||||
## 2026-02-26 - 9.2.0 - feat(remoteingress)
|
||||
expose connected edge IPs and detected public IP; resolve proxy IPs from SmartProxy and improve ops UI
|
||||
|
||||
- Add detectedPublicIp to DC Router and populate it when a configured or auto-discovered public IP is chosen
|
||||
- Use dcRouter.detectedPublicIp as a fallback for system.publicIp in the config handler
|
||||
- Resolve proxy IPs from SmartProxy runtime settings when opts.proxyIps is not provided
|
||||
- TunnelManager: capture peerAddr on edgeConnected and from Rust heartbeats, store per-edge publicIp, and add getConnectedEdgeIps()
|
||||
- Expose connectedEdgeIps in the config API and return it in remoteIngress config
|
||||
- Ops UI: show Connected Edge IPs, annotate 127.0.0.1 proxy IP as 'Remote Ingress' when applicable, and refresh remote ingress data during combined refresh when viewing remoteingress
|
||||
- Bump dependency @serve.zone/remoteingress to ^4.2.0
|
||||
|
||||
## 2026-02-26 - 9.1.10 - fix(deps)
|
||||
bump @push.rocks/smartproxy to ^25.8.5
|
||||
|
||||
- package.json: @push.rocks/smartproxy version updated from ^25.8.4 to ^25.8.5
|
||||
- No other files changed
|
||||
|
||||
## 2026-02-26 - 9.1.9 - fix(deps(smartmta))
|
||||
bump @push.rocks/smartmta to ^5.3.0
|
||||
|
||||
- Updated @push.rocks/smartmta from ^5.2.6 to ^5.3.0 in package.json
|
||||
- Patch release recommended (no source code changes)
|
||||
|
||||
## 2026-02-26 - 9.1.8 - fix(deps)
|
||||
bump @serve.zone/remoteingress to ^4.1.0
|
||||
|
||||
- Updated dependency @serve.zone/remoteingress from ^4.0.1 to ^4.1.0 in package.json
|
||||
- Non-breaking dependency update; recommend patch version bump
|
||||
|
||||
## 2026-02-26 - 9.1.7 - fix(dcrouter)
|
||||
bump @push.rocks/smartproxy to ^25.8.4 and remove custom smartProxy timeout/connection lifetime settings from dcrouter
|
||||
|
||||
- Bumped dependency @push.rocks/smartproxy from ^25.8.3 to ^25.8.4 in package.json
|
||||
- Removed explicit smartProxy options: socketTimeout, inactivityTimeout, keepAliveInactivityMultiplier, extendedKeepAliveLifetime, and maxConnectionLifetime from ts/classes.dcrouter.ts
|
||||
|
||||
## 2026-02-26 - 9.1.6 - fix(cleanup)
|
||||
prevent event listener and log stream leaks, tighten smartProxy connection timeouts, and improve graceful shutdown behavior
|
||||
|
||||
- Tightened smartProxy connection timeouts and lifetimes (5m socketTimeout, 10m inactivityTimeout, keep-alive multiplier, 1h extendedKeepAliveLifetime, 4h maxConnectionLifetime).
|
||||
- Remove event listeners before stopping services to avoid leaks (smartProxy, emailServer, dnsServer, remote ingress hub).
|
||||
- OpsServer.stop now invokes logsHandler.cleanup to tear down active log streams and avoid duplicate push destinations.
|
||||
- LogsHandler rewritten to use a module-level singleton push destination, track active stream stop callbacks, add cleanup(), guard against hung VirtualStream.sendData with a 10s timeout, and ensure intervals are cleared on stop.
|
||||
- updateSmartProxyConfig removes listeners on the old instance before stopping it.
|
||||
- Dependency bumps: @api.global/typedsocket ^4.1.2, @push.rocks/smartdata ^7.1.0, @push.rocks/smartmta ^5.2.6, @push.rocks/smartproxy ^25.8.3.
|
||||
|
||||
## 2026-02-26 - 9.1.5 - fix(remoteingress)
|
||||
Reconcile tunnel manager edge statuses with authoritative Rust hub periodically; update active tunnel counts and heartbeats, add missed edges, remove stale entries, and clear reconcile interval on stop
|
||||
|
||||
- Add reconcile() to sync TS-side edgeStatuses with hub.getStatus and overwrite activeTunnels with the authoritative activeStreams.
|
||||
- Start a periodic reconcile (setInterval every 15s) and store the interval handle on the tunnel manager.
|
||||
- Clear the reconcile interval in stop() to avoid background timers; remove edgeStatuses entries that are no longer connected in Rust.
|
||||
- Bump dependency @serve.zone/remoteingress from ^4.0.0 to ^4.0.1.
|
||||
|
||||
## 2026-02-25 - 9.1.4 - fix(deps)
|
||||
bump @push.rocks/smartproxy to ^25.8.1
|
||||
|
||||
- Updated package.json dependency @push.rocks/smartproxy from ^25.8.0 to ^25.8.1
|
||||
|
||||
## 2026-02-24 - 9.1.3 - fix(deps)
|
||||
bump @api.global/typedserver to ^8.4.0 and @push.rocks/smartproxy to ^25.8.0
|
||||
|
||||
- Updated @api.global/typedserver from ^8.3.1 to ^8.4.0
|
||||
- Updated @push.rocks/smartproxy from ^25.7.9 to ^25.8.0
|
||||
|
||||
## 2026-02-24 - 9.1.2 - fix(deps)
|
||||
bump dependency versions for build and runtime packages
|
||||
|
||||
- @git.zone/tsbundle: ^2.8.3 -> ^2.9.0
|
||||
- @git.zone/tswatch: ^3.1.0 -> ^3.2.0
|
||||
- @api.global/typedserver: ^8.3.0 -> ^8.3.1
|
||||
- @design.estate/dees-catalog: ^3.43.2 -> ^3.43.3
|
||||
|
||||
## 2026-02-23 - 9.1.1 - fix(dcrouter)
|
||||
no changes detected — no files modified, no release necessary
|
||||
|
||||
- Git diff contained no changes
|
||||
- No files added, modified, or deleted
|
||||
|
||||
## 2026-02-23 - 9.1.0 - feat(ops-dashboard)
|
||||
add lucide icons to Ops dashboard view tabs
|
||||
|
||||
|
||||
26
package.json
26
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@serve.zone/dcrouter",
|
||||
"private": false,
|
||||
"version": "9.1.0",
|
||||
"version": "10.1.5",
|
||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -20,24 +20,24 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^4.1.2",
|
||||
"@git.zone/tsbundle": "^2.8.3",
|
||||
"@git.zone/tsbundle": "^2.9.0",
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.1.8",
|
||||
"@git.zone/tswatch": "^3.1.0",
|
||||
"@types/node": "^25.3.0"
|
||||
"@git.zone/tswatch": "^3.2.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.3.0",
|
||||
"@api.global/typedsocket": "^4.1.0",
|
||||
"@api.global/typedserver": "^8.4.0",
|
||||
"@api.global/typedsocket": "^4.1.2",
|
||||
"@apiclient.xyz/cloudflare": "^7.1.0",
|
||||
"@design.estate/dees-catalog": "^3.43.2",
|
||||
"@design.estate/dees-catalog": "^3.43.3",
|
||||
"@design.estate/dees-element": "^2.1.6",
|
||||
"@push.rocks/projectinfo": "^5.0.2",
|
||||
"@push.rocks/qenv": "^6.1.3",
|
||||
"@push.rocks/smartacme": "^9.1.3",
|
||||
"@push.rocks/smartdata": "^7.0.15",
|
||||
"@push.rocks/smartdata": "^7.1.0",
|
||||
"@push.rocks/smartdns": "^7.9.0",
|
||||
"@push.rocks/smartfile": "^13.1.2",
|
||||
"@push.rocks/smartguard": "^3.1.0",
|
||||
@@ -45,19 +45,19 @@
|
||||
"@push.rocks/smartlog": "^3.2.1",
|
||||
"@push.rocks/smartmetrics": "^3.0.1",
|
||||
"@push.rocks/smartmongo": "^5.1.0",
|
||||
"@push.rocks/smartmta": "^5.2.2",
|
||||
"@push.rocks/smartmta": "^5.3.1",
|
||||
"@push.rocks/smartnetwork": "^4.4.0",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartproxy": "^25.7.9",
|
||||
"@push.rocks/smartproxy": "^25.8.5",
|
||||
"@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",
|
||||
"@serve.zone/remoteingress": "^4.0.0",
|
||||
"@serve.zone/remoteingress": "^4.3.0",
|
||||
"@tsclass/tsclass": "^9.3.0",
|
||||
"lru-cache": "^11.2.6",
|
||||
"uuid": "^13.0.0"
|
||||
|
||||
384
pnpm-lock.yaml
generated
384
pnpm-lock.yaml
generated
@@ -9,23 +9,23 @@ 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
|
||||
'@api.global/typedserver':
|
||||
specifier: ^8.3.0
|
||||
version: 8.3.0(@tiptap/pm@2.27.2)
|
||||
specifier: ^8.4.0
|
||||
version: 8.4.0(@tiptap/pm@2.27.2)
|
||||
'@api.global/typedsocket':
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(@push.rocks/smartserve@2.0.1)
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2(@push.rocks/smartserve@2.0.1)
|
||||
'@apiclient.xyz/cloudflare':
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0
|
||||
'@design.estate/dees-catalog':
|
||||
specifier: ^3.43.2
|
||||
version: 3.43.2(@tiptap/pm@2.27.2)
|
||||
specifier: ^3.43.3
|
||||
version: 3.43.3(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-element':
|
||||
specifier: ^2.1.6
|
||||
version: 2.1.6
|
||||
@@ -39,8 +39,8 @@ importers:
|
||||
specifier: ^9.1.3
|
||||
version: 9.1.3(socks@2.8.7)
|
||||
'@push.rocks/smartdata':
|
||||
specifier: ^7.0.15
|
||||
version: 7.0.15(socks@2.8.7)
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0(socks@2.8.7)
|
||||
'@push.rocks/smartdns':
|
||||
specifier: ^7.9.0
|
||||
version: 7.9.0
|
||||
@@ -63,8 +63,8 @@ importers:
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0(socks@2.8.7)
|
||||
'@push.rocks/smartmta':
|
||||
specifier: ^5.2.2
|
||||
version: 5.2.2
|
||||
specifier: ^5.3.1
|
||||
version: 5.3.1
|
||||
'@push.rocks/smartnetwork':
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.0
|
||||
@@ -75,8 +75,8 @@ importers:
|
||||
specifier: ^4.2.3
|
||||
version: 4.2.3
|
||||
'@push.rocks/smartproxy':
|
||||
specifier: ^25.7.9
|
||||
version: 25.7.9
|
||||
specifier: ^25.8.5
|
||||
version: 25.8.5
|
||||
'@push.rocks/smartradius':
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
@@ -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
|
||||
@@ -99,8 +99,8 @@ importers:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
'@serve.zone/remoteingress':
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0
|
||||
'@tsclass/tsclass':
|
||||
specifier: ^9.3.0
|
||||
version: 9.3.0
|
||||
@@ -115,8 +115,8 @@ importers:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2
|
||||
'@git.zone/tsbundle':
|
||||
specifier: ^2.8.3
|
||||
version: 2.8.3
|
||||
specifier: ^2.9.0
|
||||
version: 2.9.0
|
||||
'@git.zone/tsrun':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
@@ -124,11 +124,11 @@ importers:
|
||||
specifier: ^3.1.8
|
||||
version: 3.1.8(@push.rocks/smartserve@2.0.1)(socks@2.8.7)(typescript@5.9.3)
|
||||
'@git.zone/tswatch':
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0(@tiptap/pm@2.27.2)
|
||||
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,14 +138,14 @@ 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==}
|
||||
|
||||
'@api.global/typedserver@8.3.0':
|
||||
resolution: {integrity: sha512-Uh2sQkoQXbsKFb/fhSm7P9oCCEnawGY7R5/9VgCLQUuFV30G0FL0oBTKZNqFli0CNNDDs0nQHE+dpdf4VHhlXQ==}
|
||||
'@api.global/typedserver@8.4.0':
|
||||
resolution: {integrity: sha512-qOa5jUwiuHEoY1ZuLZiuU1unPl5JNd99Vv+hNAwgEIgAZd4TTy/mjdTp7lyviBzkGf2pROeCmAbDJJF8YQFCSA==}
|
||||
|
||||
'@api.global/typedsocket@3.1.1':
|
||||
resolution: {integrity: sha512-Wkz3NlhmfdZMKqXXI2c2dMtGGmSmhdOegZiziL+9b2mqPYdc7Gd8AZRdEOKvbSoIvc9G22/5BEadIWHrfq66TA==}
|
||||
@@ -155,8 +155,8 @@ packages:
|
||||
'@push.rocks/smartserve':
|
||||
optional: true
|
||||
|
||||
'@api.global/typedsocket@4.1.0':
|
||||
resolution: {integrity: sha512-ttmoU5BNHmLAkAF/o+Ta8F5O4F7CUmkFo6LK7NKHQvuYJvodPMYWdhJ6yCINTF4pfCgljkMDUqoVKobm6ea4mQ==}
|
||||
'@api.global/typedsocket@4.1.2':
|
||||
resolution: {integrity: sha512-fZFuJY9ucFCICjF4wi6OvK8drsv6UcwVVsfamOT1HxFj7OBOYw6QHOceQ+cAQ8IrWbX817sf8gzlesl+jlG8JA==}
|
||||
peerDependencies:
|
||||
'@push.rocks/smartserve': '>=1.1.0'
|
||||
|
||||
@@ -351,11 +351,14 @@ packages:
|
||||
'@cloudflare/workers-types@4.20260210.0':
|
||||
resolution: {integrity: sha512-zHaF0RZVYUQwNCJCECnNAJdMur72Lk3FMiD6wU78Dx3Bv7DQRcuXNmPNuJmsGnosVZCcWintHlPTQ/4BEiDG5w==}
|
||||
|
||||
'@cloudflare/workers-types@4.20260303.0':
|
||||
resolution: {integrity: sha512-soUlr4NJVkh5dR09RwtziTMbBQ+lbdoEesTGw8WUlvmnQ2M4h7CmJzAjC6a7IivUodiiCSjbLcGV/8PyZpvZkA==}
|
||||
|
||||
'@configvault.io/interfaces@1.0.17':
|
||||
resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==}
|
||||
|
||||
'@design.estate/dees-catalog@3.43.2':
|
||||
resolution: {integrity: sha512-7pU+K+B70SxqR4DrBkpc/xvQGLDkxAV2jH7Qyh0TYgkCoxxjsxCuTMKM9JguA38wm6bEgBJVTvyg5S3wCwxm4Q==}
|
||||
'@design.estate/dees-catalog@3.43.3':
|
||||
resolution: {integrity: sha512-GjTePdwqNBL4isMOx4Ibei6pgK55H+DccbtgyNqjHRBz3LL14mo809ebjY2IZOVobswyzuTcNFvhfiqFP4/HLg==}
|
||||
|
||||
'@design.estate/dees-comms@1.0.30':
|
||||
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
|
||||
@@ -558,8 +561,8 @@ packages:
|
||||
resolution: {integrity: sha512-S518ulKveO76pS6jrAELrnFaCw5nDAIZD9j6QzVmLYDiZuJmlRwPK3/2E8ugQ+b7ffpkwJ9MT685ooEGDcWQ4Q==}
|
||||
hasBin: true
|
||||
|
||||
'@git.zone/tsbundle@2.8.3':
|
||||
resolution: {integrity: sha512-9q+KbVGKUTDNND+jDiJuk4bPH/mtiA2B0EWtV+/NyvgZfIbpe/ItHemyIvXB4RAqncMdBhzXquCFCvGjAhwVIQ==}
|
||||
'@git.zone/tsbundle@2.9.0':
|
||||
resolution: {integrity: sha512-itXX/oiJjrRHUlIGTHUEqSwPuGwsG4Cq8kh7aqFOm8mYzJwtXYE1gBqLJTWZma6gI5n+xAk5qTxTyfikuPgWQA==}
|
||||
hasBin: true
|
||||
|
||||
'@git.zone/tspublish@1.11.0':
|
||||
@@ -574,8 +577,8 @@ packages:
|
||||
resolution: {integrity: sha512-nmiLGeOkKMkLDyIk5BUBLx5ExskFbKHKlPdrWCARPVFkU4cAAiuIyJWVfLwISoS0TO/zSInLqArPwIc76yvaNw==}
|
||||
hasBin: true
|
||||
|
||||
'@git.zone/tswatch@3.1.0':
|
||||
resolution: {integrity: sha512-R2ZI+j1OKVgd0zTbtGtJjyt7r2kF0Z4nl8neolHuQL+jpr16i2NHVfVK92uIeeZDnJSqo5vf7Syt0XeQ4rz2HA==}
|
||||
'@git.zone/tswatch@3.2.0':
|
||||
resolution: {integrity: sha512-AlV2HPGPy1s91LwQ8x3a0UwHqe5/P2SRSXfHco5yo+D59KjkV7FaHLWwU+Dk03VNgmdkdUusPiB6/Cfkn1tPzw==}
|
||||
hasBin: true
|
||||
|
||||
'@happy-dom/global-registrator@15.11.7':
|
||||
@@ -684,74 +687,74 @@ packages:
|
||||
'@mongodb-js/saslprep@1.4.6':
|
||||
resolution: {integrity: sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==}
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.93':
|
||||
resolution: {integrity: sha512-xRIoOPFvneR29Dtq5d9p2AJbijDCFeV4jQ+5Ms/xVAXJVb8R0Jlu+pPr/SkhrG+Mouaml4roPSXugTIeRl6CMA==}
|
||||
'@napi-rs/canvas-android-arm64@0.1.95':
|
||||
resolution: {integrity: sha512-SqTh0wsYbetckMXEvHqmR7HKRJujVf1sYv1xdlhkifg6TlCSysz1opa49LlS3+xWuazcQcfRfmhA07HxxxGsAA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.93':
|
||||
resolution: {integrity: sha512-daNDi76HN5grC6GXDmpxdfP+N2mQPd3sCfg62VyHwUuvbZh32P7R/IUjkzAxtYMtTza+Zvx9hfLJ3J7ENL6WMA==}
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.95':
|
||||
resolution: {integrity: sha512-F7jT0Syu+B9DGBUBcMk3qCRIxAWiDXmvEjamwbYfbZl7asI1pmXZUnCOoIu49Wt0RNooToYfRDxU9omD6t5Xuw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@napi-rs/canvas-darwin-x64@0.1.93':
|
||||
resolution: {integrity: sha512-1YfuNPIQLawsg/gSNdJRk4kQWUy9M/Gy8FGsOI79nhQEJ2PZdqpSPl5UNzf4elfuNXuVbEbmmjP68EQdUunDuQ==}
|
||||
'@napi-rs/canvas-darwin-x64@0.1.95':
|
||||
resolution: {integrity: sha512-54eb2Ho15RDjYGXO/harjRznBrAvu+j5nQ85Z4Qd6Qg3slR8/Ja+Yvvy9G4yo7rdX6NR9GPkZeSTf2UcKXwaXw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.93':
|
||||
resolution: {integrity: sha512-8kEkOQPZjuyHjupvXExuJZiuiVNecdABGq3DLI7aO1EvQFOOlWMm2d/8Q5qXdV73Tn+nu3m16+kPajsN1oJefQ==}
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.95':
|
||||
resolution: {integrity: sha512-hYaLCSLx5bmbnclzQc3ado3PgZ66blJWzjXp0wJmdwpr/kH+Mwhj6vuytJIomgksyJoCdIqIa4N6aiqBGJtJ5Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.93':
|
||||
resolution: {integrity: sha512-qIKLKkBkYSyWSYAoDThoxf5y1gr4X0g7W8rDU7d2HDeAAcotdVHUwuKkMeNe6+5VNk7/95EIhbslQjSxiCu32g==}
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.95':
|
||||
resolution: {integrity: sha512-J7VipONahKsmScPZsipHVQBqpbZx4favaD8/enWzzlGcjiwycOoymL7f4tNeqdjK0su19bDOUt6mjp9gsPWYlw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.93':
|
||||
resolution: {integrity: sha512-mAwQBGM3qArS9XEO21AK4E1uGvCuUCXjhIZk0dlVvs49MQ6wAAuCkYKNFpSKeSicKrLWwBMfgWX4qZoPh+M00A==}
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.95':
|
||||
resolution: {integrity: sha512-PXy0UT1J/8MPG8UAkWp6Fd51ZtIZINFzIjGH909JjQrtCuJf3X6nanHYdz1A+Wq9o4aoPAw1YEUpFS1lelsVlg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.93':
|
||||
resolution: {integrity: sha512-kaIH5MpPzOZfkM+QMsBxGdM9jlJT+N+fwz2IEaju/S+DL65E5TgPOx4QcD5dQ8vsMxlak6uDrudBc4ns5xzZCw==}
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.95':
|
||||
resolution: {integrity: sha512-2IzCkW2RHRdcgF9W5/plHvYFpc6uikyjMb5SxjqmNxfyDFz9/HB89yhi8YQo0SNqrGRI7yBVDec7Pt+uMyRWsg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.93':
|
||||
resolution: {integrity: sha512-KtMZJqYWvOSeW5w3VSV2f5iGnwNdKJm4gwgVid4xNy1NFi+NJSyuglA1lX1u4wIPxizyxh8OW5c5Usf6oSOMNQ==}
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.95':
|
||||
resolution: {integrity: sha512-OV/ol/OtcUr4qDhQg8G7SdViZX8XyQeKpPsVv/j3+7U178FGoU4M+yIocdVo1ih/A8GQ63+LjF4jDoEjaVU8Pw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.93':
|
||||
resolution: {integrity: sha512-qRZhOvlDBooRLX6V3/t9X9B+plZK+OrPLgfFixu0A1RO/3VHbubOknfnMnocSDAqk/L6cRyKI83VP2ciR9UO7w==}
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.95':
|
||||
resolution: {integrity: sha512-Z5KzqBK/XzPz5+SFHKz7yKqClEQ8pOiEDdgk5SlphBLVNb8JFIJkxhtJKSvnJyHh2rjVgiFmvtJzMF0gNwwKyQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-win32-arm64-msvc@0.1.93':
|
||||
resolution: {integrity: sha512-um5XE44vF8bjkQEsH2iRSUP9fDeQGYbn/qjM/v4whXG83qsqapAXlOPOQqSARZB1SiNvPUAuXoRsJLlKFmAEFw==}
|
||||
'@napi-rs/canvas-win32-arm64-msvc@0.1.95':
|
||||
resolution: {integrity: sha512-aj0YbRpe8qVJ4OzMsK7NfNQePgcf9zkGFzNZ9mSuaxXzhpLHmlF2GivNdCdNOg8WzA/NxV6IU4c5XkXadUMLeA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.93':
|
||||
resolution: {integrity: sha512-maHlizZgmKsAPJwjwBZMnsWfq3Ca9QutoteQwKe7YqsmbECoylrLCCOGCDOredstW4BRWqRTfCl6NJaVVeAQvQ==}
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.95':
|
||||
resolution: {integrity: sha512-GA8leTTCfdjuHi8reICTIxU0081PhXvl3lzIniLUjeLACx9GubUiyzkwFb+oyeKLS5IAGZFLKnzAf4wm2epRlA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@napi-rs/canvas@0.1.93':
|
||||
resolution: {integrity: sha512-unVFo8CUlUeJCCxt50+j4yy91NF4x6n9zdGcvEsOFAWzowtZm3mgx8X2D7xjwV0cFSfxmpGPoe+JS77uzeFsxg==}
|
||||
'@napi-rs/canvas@0.1.95':
|
||||
resolution: {integrity: sha512-lkg23ge+rgyhgUwXmlbkPEhuhHq/hUi/gXKH+4I7vO+lJrbNfEYcQdJLIGjKyXLQzgFiiyDAwh5vAe/tITAE+w==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@napi-rs/wasm-runtime@1.0.7':
|
||||
@@ -842,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==}
|
||||
|
||||
@@ -891,8 +897,8 @@ packages:
|
||||
'@push.rocks/smartdata@5.16.7':
|
||||
resolution: {integrity: sha512-bu/YSIjQcwxWXkAsuhqE6zs7eT+bTIKV8+/H7TbbjpzeioLCyB3dZ/41cLZk37c/EYt4d4GHgZ0ww80OiKOUMg==}
|
||||
|
||||
'@push.rocks/smartdata@7.0.15':
|
||||
resolution: {integrity: sha512-j09BUekmjiGZuvXmdGBiIpBTXFFnxrzG4rOBjZvPO/hG1BwNrvSkIVq20mIwdYomn8JGgya6oJ4Y7NL+FKTqEA==}
|
||||
'@push.rocks/smartdata@7.1.0':
|
||||
resolution: {integrity: sha512-ots0g7/96R2xs4ww4F2/2rIwAOPT5AmzP3ciD31YsF02o5WA4Gg6C5laLBUjV3hXCjazhzFsRVQTfwbjmPQe4w==}
|
||||
|
||||
'@push.rocks/smartdelay@3.0.5':
|
||||
resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==}
|
||||
@@ -993,12 +999,11 @@ packages:
|
||||
'@push.rocks/smartmongo@5.1.0':
|
||||
resolution: {integrity: sha512-2tpKf8K+SMdLHOEpafgKPIN+ypWTLwHc33hCUDNMQ1KaL7vokkavA44+fHxQydOGPMtDi22tSMFeVMCcUSzs4w==}
|
||||
|
||||
'@push.rocks/smartmta@5.2.2':
|
||||
resolution: {integrity: sha512-0xKUi2BMM0HFYIPdNeNJZFitAiJ9CNbLlOJ8TenT+xInp7DKcSQ7ABER1rJKinPtvDjRDSiSqiF2iQR+O7299g==}
|
||||
'@push.rocks/smartmta@5.3.1':
|
||||
resolution: {integrity: sha512-cEuXO56i/zL9eZS79eAesEW16ikdBJKLlEv9pLKkt2cmaHBWADGHjeOzJmsszQ9CSFcuhd41aHYVGMZXVvsG2g==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [x64, arm64]
|
||||
os: [darwin, linux, win32]
|
||||
hasBin: true
|
||||
|
||||
'@push.rocks/smartmustache@3.0.2':
|
||||
resolution: {integrity: sha512-G3LyRXoJhyM+iQhkvP/MR/2WYMvC9U7zc2J44JxUM5tPdkQ+o3++FbfRtnZj6rz5X/A7q03//vsxPitVQwoi2Q==}
|
||||
@@ -1033,8 +1038,8 @@ packages:
|
||||
'@push.rocks/smartpromise@4.2.3':
|
||||
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
||||
|
||||
'@push.rocks/smartproxy@25.7.9':
|
||||
resolution: {integrity: sha512-5esFvD72TEyveaEQbDYRgD7C5hDfWMSBvurNx3KPi02CBKG1gnhx/WWT7RHDS3KRF5fEQh9YxvI9aMkOwjc7sQ==}
|
||||
'@push.rocks/smartproxy@25.8.5':
|
||||
resolution: {integrity: sha512-oLmV+Bq7sSgQP9McTao/imb6Xb62QM7wlTFt5kNynrS5WK2wAe8cEjDKOcyu8N/WmzNCEClT5f/0xAtI6JxtkA==}
|
||||
|
||||
'@push.rocks/smartpuppeteer@2.0.5':
|
||||
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
||||
@@ -1057,6 +1062,9 @@ packages:
|
||||
'@push.rocks/smartrust@1.2.1':
|
||||
resolution: {integrity: sha512-ANwXXibUwoHNWF1hhXhXVVrfzYlhgHYRa2205Jkd/s/wXzcWHftYZthilJj+52B7nkzSB76umfxKfK5eBYY2Ug==}
|
||||
|
||||
'@push.rocks/smartrust@1.3.1':
|
||||
resolution: {integrity: sha512-3ApbgF6yGeE2TRQxBY9Y48H1JlpcRheIp7QDBLSSfk80Uoe6fjdgBAfNz3Ir8hW3RZ3b7hA3sm1ZshCok58SEA==}
|
||||
|
||||
'@push.rocks/smartrx@3.0.10':
|
||||
resolution: {integrity: sha512-USjIYcsSfzn14cwOsxgq/bBmWDTTzy3ouWAnW5NdMyRRzEbmeNrvmy6TRqNeDlJ2PsYNTt1rr/zGUqvIy72ITg==}
|
||||
|
||||
@@ -1078,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==}
|
||||
@@ -1121,8 +1129,8 @@ packages:
|
||||
'@push.rocks/taskbuffer@3.5.0':
|
||||
resolution: {integrity: sha512-Y9WwIEIyp6oVFdj06j84tfrZIvjhbMb3DF52rYxlTeYLk3W7RPhSg1bGPCbtkXWeKdBrSe37V90BkOG7Qq8Pqg==}
|
||||
|
||||
'@push.rocks/taskbuffer@4.2.0':
|
||||
resolution: {integrity: sha512-ttoBe5y/WXkAo5/wSMcC/Y4Zbyw4XG8kwAsEaqnAPCxa3M9MI1oV/yM1e9gU1IH97HVPidzbTxRU5/PcHDdUsg==}
|
||||
'@push.rocks/taskbuffer@4.2.1':
|
||||
resolution: {integrity: sha512-F3aizWLGWdAz7wSJqOzjwVgo1VQJcxTbHUjDN/Pqxw0WMQUwODRGbhgy4zLag7bOyE4tc8Jv7yid7Bjmn5hKdg==}
|
||||
|
||||
'@push.rocks/taskbuffer@6.1.2':
|
||||
resolution: {integrity: sha512-sdqKd8N/GidztQ1k3r8A86rLvD8Afyir5FjYCNJXDD9837JLoqzHaOKGltUSBsCGh2gjsZn6GydsY6HhXQgvZQ==}
|
||||
@@ -1339,8 +1347,8 @@ packages:
|
||||
'@serve.zone/interfaces@5.3.0':
|
||||
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
|
||||
|
||||
'@serve.zone/remoteingress@4.0.0':
|
||||
resolution: {integrity: sha512-kcC9pnQdS5Mw0ypmwT3GIYwrjVQJOjWw5TJ+j46t6Y98S4Mb7wEAAPsU+YRXozpkqUxWUDBeDK1fQHLvoF9PmQ==}
|
||||
'@serve.zone/remoteingress@4.3.0':
|
||||
resolution: {integrity: sha512-yk14uS6oWIP83Zpem4hGf8zi3W9pefnxijtSWp45WvZ+u9XTXIADQNaUZBSTCId8CYkfPkfRGaaaARunVdjFXg==}
|
||||
|
||||
'@sindresorhus/is@5.6.0':
|
||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||
@@ -1830,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==}
|
||||
@@ -2006,6 +2014,10 @@ packages:
|
||||
resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
balanced-match@4.0.4:
|
||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
bare-events@2.8.2:
|
||||
resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==}
|
||||
peerDependencies:
|
||||
@@ -2072,6 +2084,10 @@ packages:
|
||||
resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
brace-expansion@5.0.3:
|
||||
resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
broadcast-channel@7.3.0:
|
||||
resolution: {integrity: sha512-UHPhLBQKfQ8OmMFMpmPfO5dRakyA1vsfiDGWTYNvChYol65tbuhivPEGgZZiuetorvExdvxaWiBy/ym1Ty08yA==}
|
||||
|
||||
@@ -3231,8 +3247,12 @@ packages:
|
||||
resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
minimatch@10.2.2:
|
||||
resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
minimatch@3.1.3:
|
||||
resolution: {integrity: sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==}
|
||||
|
||||
minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
@@ -4165,7 +4185,7 @@ packages:
|
||||
hasBin: true
|
||||
|
||||
wordwrap@1.0.0:
|
||||
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
|
||||
resolution: {integrity: sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
@@ -4274,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
|
||||
@@ -4288,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
|
||||
@@ -4334,13 +4354,13 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- vue
|
||||
|
||||
'@api.global/typedserver@8.3.0(@tiptap/pm@2.27.2)':
|
||||
'@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.0(@push.rocks/smartserve@2.0.1)
|
||||
'@cloudflare/workers-types': 4.20260210.0
|
||||
'@design.estate/dees-catalog': 3.43.2(@tiptap/pm@2.27.2)
|
||||
'@api.global/typedsocket': 4.1.2(@push.rocks/smartserve@2.0.1)
|
||||
'@cloudflare/workers-types': 4.20260303.0
|
||||
'@design.estate/dees-catalog': 3.43.3(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-comms': 1.0.30
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
@@ -4364,7 +4384,7 @@ snapshots:
|
||||
'@push.rocks/smartserve': 2.0.1
|
||||
'@push.rocks/smartsitemap': 2.0.4
|
||||
'@push.rocks/smartstream': 3.2.5
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/smartwatch': 6.3.0
|
||||
'@push.rocks/taskbuffer': 3.5.0
|
||||
'@push.rocks/webrequest': 4.0.2
|
||||
@@ -4382,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
|
||||
@@ -4400,9 +4420,9 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- vue
|
||||
|
||||
'@api.global/typedsocket@4.1.0(@push.rocks/smartserve@2.0.1)':
|
||||
'@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
|
||||
@@ -4934,11 +4954,13 @@ snapshots:
|
||||
|
||||
'@cloudflare/workers-types@4.20260210.0': {}
|
||||
|
||||
'@cloudflare/workers-types@4.20260303.0': {}
|
||||
|
||||
'@configvault.io/interfaces@1.0.17':
|
||||
dependencies:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
|
||||
'@design.estate/dees-catalog@3.43.2(@tiptap/pm@2.27.2)':
|
||||
'@design.estate/dees-catalog@3.43.3(@tiptap/pm@2.27.2)':
|
||||
dependencies:
|
||||
'@design.estate/dees-domtools': 2.3.8
|
||||
'@design.estate/dees-element': 2.1.6
|
||||
@@ -4975,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
|
||||
@@ -4991,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
|
||||
@@ -5163,7 +5185,7 @@ snapshots:
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
'@git.zone/tsbundle@2.8.3':
|
||||
'@git.zone/tsbundle@2.9.0':
|
||||
dependencies:
|
||||
'@push.rocks/early': 4.0.4
|
||||
'@push.rocks/npmextra': 5.3.3
|
||||
@@ -5220,7 +5242,7 @@ snapshots:
|
||||
'@git.zone/tstest@3.1.8(@push.rocks/smartserve@2.0.1)(socks@2.8.7)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@2.0.1)
|
||||
'@git.zone/tsbundle': 2.8.3
|
||||
'@git.zone/tsbundle': 2.9.0
|
||||
'@git.zone/tsrun': 2.0.1
|
||||
'@push.rocks/consolecolor': 2.0.3
|
||||
'@push.rocks/qenv': 6.1.3
|
||||
@@ -5266,10 +5288,10 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- vue
|
||||
|
||||
'@git.zone/tswatch@3.1.0(@tiptap/pm@2.27.2)':
|
||||
'@git.zone/tswatch@3.2.0(@tiptap/pm@2.27.2)':
|
||||
dependencies:
|
||||
'@api.global/typedserver': 8.3.0(@tiptap/pm@2.27.2)
|
||||
'@git.zone/tsbundle': 2.8.3
|
||||
'@api.global/typedserver': 8.4.0(@tiptap/pm@2.27.2)
|
||||
'@git.zone/tsbundle': 2.9.0
|
||||
'@git.zone/tsrun': 2.0.1
|
||||
'@push.rocks/early': 4.0.4
|
||||
'@push.rocks/lik': 6.2.2
|
||||
@@ -5282,7 +5304,7 @@ snapshots:
|
||||
'@push.rocks/smartlog-destination-local': 9.0.2
|
||||
'@push.rocks/smartshell': 3.3.0
|
||||
'@push.rocks/smartwatch': 6.3.0
|
||||
'@push.rocks/taskbuffer': 4.2.0
|
||||
'@push.rocks/taskbuffer': 4.2.1
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
- '@swc/helpers'
|
||||
@@ -5315,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
|
||||
@@ -5447,52 +5469,52 @@ snapshots:
|
||||
dependencies:
|
||||
sparse-bitfield: 3.0.3
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.93':
|
||||
'@napi-rs/canvas-android-arm64@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.93':
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-darwin-x64@0.1.93':
|
||||
'@napi-rs/canvas-darwin-x64@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.93':
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.93':
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.93':
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.93':
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.93':
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.93':
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-win32-arm64-msvc@0.1.93':
|
||||
'@napi-rs/canvas-win32-arm64-msvc@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.93':
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.95':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas@0.1.93':
|
||||
'@napi-rs/canvas@0.1.95':
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas-android-arm64': 0.1.93
|
||||
'@napi-rs/canvas-darwin-arm64': 0.1.93
|
||||
'@napi-rs/canvas-darwin-x64': 0.1.93
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.93
|
||||
'@napi-rs/canvas-linux-arm64-gnu': 0.1.93
|
||||
'@napi-rs/canvas-linux-arm64-musl': 0.1.93
|
||||
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.93
|
||||
'@napi-rs/canvas-linux-x64-gnu': 0.1.93
|
||||
'@napi-rs/canvas-linux-x64-musl': 0.1.93
|
||||
'@napi-rs/canvas-win32-arm64-msvc': 0.1.93
|
||||
'@napi-rs/canvas-win32-x64-msvc': 0.1.93
|
||||
'@napi-rs/canvas-android-arm64': 0.1.95
|
||||
'@napi-rs/canvas-darwin-arm64': 0.1.95
|
||||
'@napi-rs/canvas-darwin-x64': 0.1.95
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.95
|
||||
'@napi-rs/canvas-linux-arm64-gnu': 0.1.95
|
||||
'@napi-rs/canvas-linux-arm64-musl': 0.1.95
|
||||
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.95
|
||||
'@napi-rs/canvas-linux-x64-gnu': 0.1.95
|
||||
'@napi-rs/canvas-linux-x64-musl': 0.1.95
|
||||
'@napi-rs/canvas-win32-arm64-msvc': 0.1.95
|
||||
'@napi-rs/canvas-win32-x64-msvc': 0.1.95
|
||||
optional: true
|
||||
|
||||
'@napi-rs/wasm-runtime@1.0.7':
|
||||
@@ -5657,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
|
||||
@@ -5688,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
|
||||
@@ -5732,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
|
||||
@@ -5743,7 +5776,7 @@ snapshots:
|
||||
'@apiclient.xyz/cloudflare': 7.1.0
|
||||
'@peculiar/x509': 1.14.3
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdata': 7.0.15(socks@2.8.7)
|
||||
'@push.rocks/smartdata': 7.1.0(socks@2.8.7)
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartdns': 7.9.0
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
@@ -5906,7 +5939,7 @@ snapshots:
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
'@push.rocks/smartdata@7.0.15(socks@2.8.7)':
|
||||
'@push.rocks/smartdata@7.1.0(socks@2.8.7)':
|
||||
dependencies:
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
@@ -5915,7 +5948,7 @@ snapshots:
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smartstring': 4.1.0
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/smartunique': 3.0.9
|
||||
'@push.rocks/taskbuffer': 3.5.0
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
@@ -6214,14 +6247,14 @@ snapshots:
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
'@push.rocks/smartmta@5.2.2':
|
||||
'@push.rocks/smartmta@5.3.1':
|
||||
dependencies:
|
||||
'@push.rocks/smartfile': 13.1.2
|
||||
'@push.rocks/smartfs': 1.3.1
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartmail': 2.2.0
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartrust': 1.2.1
|
||||
'@push.rocks/smartrust': 1.3.1
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
lru-cache: 11.2.6
|
||||
mailparser: 3.9.3
|
||||
@@ -6321,13 +6354,13 @@ snapshots:
|
||||
|
||||
'@push.rocks/smartpromise@4.2.3': {}
|
||||
|
||||
'@push.rocks/smartproxy@25.7.9':
|
||||
'@push.rocks/smartproxy@25.8.5':
|
||||
dependencies:
|
||||
'@push.rocks/smartcrypto': 2.0.4
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartrust': 1.2.1
|
||||
'@push.rocks/smartrust': 1.3.1
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
minimatch: 10.2.1
|
||||
minimatch: 10.2.2
|
||||
|
||||
'@push.rocks/smartpuppeteer@2.0.5(typescript@5.9.3)':
|
||||
dependencies:
|
||||
@@ -6384,6 +6417,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
|
||||
'@push.rocks/smartrust@1.3.1':
|
||||
dependencies:
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
|
||||
'@push.rocks/smartrx@3.0.10':
|
||||
dependencies:
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
@@ -6401,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
|
||||
@@ -6464,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
|
||||
@@ -6566,7 +6602,7 @@ snapshots:
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
'@push.rocks/taskbuffer@4.2.0':
|
||||
'@push.rocks/taskbuffer@4.2.1':
|
||||
dependencies:
|
||||
'@design.estate/dees-element': 2.1.6
|
||||
'@push.rocks/lik': 6.2.2
|
||||
@@ -6574,7 +6610,7 @@ snapshots:
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/smartunique': 3.0.9
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
@@ -6787,7 +6823,7 @@ snapshots:
|
||||
|
||||
'@serve.zone/catalog@2.5.0(@tiptap/pm@2.27.2)':
|
||||
dependencies:
|
||||
'@design.estate/dees-catalog': 3.43.2(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-catalog': 3.43.3(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-domtools': 2.3.8
|
||||
'@design.estate/dees-element': 2.1.6
|
||||
'@design.estate/dees-wcctools': 3.8.0
|
||||
@@ -6804,10 +6840,10 @@ snapshots:
|
||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
|
||||
'@serve.zone/remoteingress@4.0.0':
|
||||
'@serve.zone/remoteingress@4.3.0':
|
||||
dependencies:
|
||||
'@push.rocks/qenv': 6.1.3
|
||||
'@push.rocks/smartrust': 1.2.1
|
||||
'@push.rocks/smartrust': 1.3.1
|
||||
|
||||
'@sindresorhus/is@5.6.0': {}
|
||||
|
||||
@@ -7336,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:
|
||||
@@ -7359,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
|
||||
@@ -7372,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:
|
||||
@@ -7404,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': {}
|
||||
|
||||
@@ -7432,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
|
||||
|
||||
@@ -7469,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': {}
|
||||
|
||||
@@ -7514,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': {}
|
||||
@@ -7621,6 +7657,8 @@ snapshots:
|
||||
dependencies:
|
||||
jackspeak: 4.2.3
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
|
||||
bare-events@2.8.2: {}
|
||||
|
||||
bare-fs@4.5.3:
|
||||
@@ -7693,6 +7731,10 @@ snapshots:
|
||||
dependencies:
|
||||
balanced-match: 4.0.2
|
||||
|
||||
brace-expansion@5.0.3:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
|
||||
broadcast-channel@7.3.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
@@ -7989,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
|
||||
@@ -8340,7 +8382,7 @@ snapshots:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.3
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
|
||||
@@ -9137,7 +9179,11 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.2
|
||||
|
||||
minimatch@3.1.2:
|
||||
minimatch@10.2.2:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.3
|
||||
|
||||
minimatch@3.1.3:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
@@ -9397,7 +9443,7 @@ snapshots:
|
||||
|
||||
pdfjs-dist@4.10.38:
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas': 0.1.93
|
||||
'@napi-rs/canvas': 0.1.95
|
||||
|
||||
peberminta@0.9.0: {}
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '9.1.0',
|
||||
version: '10.1.5',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -217,8 +218,12 @@ export class DcRouter {
|
||||
public routeConfigManager?: RouteConfigManager;
|
||||
public apiTokenManager?: ApiTokenManager;
|
||||
|
||||
// Auto-discovered public IP (populated by generateAuthoritativeRecords)
|
||||
public detectedPublicIp: string | null = null;
|
||||
|
||||
// DNS query logging rate limiter state
|
||||
private dnsLogWindow: number[] = [];
|
||||
private dnsLogWindowSecond: number = 0; // epoch second of current window
|
||||
private dnsLogWindowCount: number = 0; // queries logged this second
|
||||
private dnsBatchCount: number = 0;
|
||||
private dnsBatchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
@@ -897,12 +902,27 @@ export class DcRouter {
|
||||
}
|
||||
this.dnsBatchTimer = null;
|
||||
this.dnsBatchCount = 0;
|
||||
this.dnsLogWindow = [];
|
||||
this.dnsLogWindowSecond = 0;
|
||||
this.dnsLogWindowCount = 0;
|
||||
}
|
||||
|
||||
await this.opsServer.stop();
|
||||
|
||||
try {
|
||||
// Remove event listeners before stopping services to prevent leaks
|
||||
if (this.smartProxy) {
|
||||
this.smartProxy.removeAllListeners();
|
||||
}
|
||||
if (this.emailServer) {
|
||||
if ((this.emailServer as any).deliverySystem) {
|
||||
(this.emailServer as any).deliverySystem.removeAllListeners();
|
||||
}
|
||||
this.emailServer.removeAllListeners();
|
||||
}
|
||||
if (this.dnsServer) {
|
||||
this.dnsServer.removeAllListeners();
|
||||
}
|
||||
|
||||
// Stop all services in parallel for faster shutdown
|
||||
await Promise.all([
|
||||
// Stop cache cleaner if running
|
||||
@@ -939,6 +959,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
|
||||
@@ -962,6 +983,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) });
|
||||
@@ -976,6 +1002,7 @@ export class DcRouter {
|
||||
public async updateSmartProxyConfig(config: plugins.smartproxy.ISmartProxyOptions): Promise<void> {
|
||||
// Stop existing SmartProxy if running
|
||||
if (this.smartProxy) {
|
||||
this.smartProxy.removeAllListeners();
|
||||
await this.smartProxy.stop();
|
||||
this.smartProxy = undefined;
|
||||
}
|
||||
@@ -1103,6 +1130,11 @@ export class DcRouter {
|
||||
try {
|
||||
// Stop the unified email server which contains all components
|
||||
if (this.emailServer) {
|
||||
// Remove listeners before stopping to prevent leaks on config update cycles
|
||||
if ((this.emailServer as any).deliverySystem) {
|
||||
(this.emailServer as any).deliverySystem.removeAllListeners();
|
||||
}
|
||||
this.emailServer.removeAllListeners();
|
||||
await this.emailServer.stop();
|
||||
logger.log('info', 'Unified email server stopped');
|
||||
this.emailServer = undefined;
|
||||
@@ -1282,11 +1314,14 @@ export class DcRouter {
|
||||
}
|
||||
|
||||
// Adaptive logging: individual logs up to 2/sec, then batch
|
||||
const now = Date.now();
|
||||
this.dnsLogWindow = this.dnsLogWindow.filter(t => now - t < 1000);
|
||||
const nowSec = Math.floor(Date.now() / 1000);
|
||||
if (nowSec !== this.dnsLogWindowSecond) {
|
||||
this.dnsLogWindowSecond = nowSec;
|
||||
this.dnsLogWindowCount = 0;
|
||||
}
|
||||
|
||||
if (this.dnsLogWindow.length < 2) {
|
||||
this.dnsLogWindow.push(now);
|
||||
if (this.dnsLogWindowCount < 2) {
|
||||
this.dnsLogWindowCount++;
|
||||
const summary = event.questions.map(q => `${q.type} ${q.name}`).join(', ');
|
||||
logger.log('info', `DNS query: ${summary} (${event.responseTimeMs}ms, ${event.answered ? 'answered' : 'unanswered'})`, { zone: 'dns' });
|
||||
} else {
|
||||
@@ -1340,6 +1375,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 {
|
||||
@@ -1348,8 +1391,10 @@ export class DcRouter {
|
||||
await (this.dnsServer as any).handleHttpsSocket(socket);
|
||||
} catch (error) {
|
||||
logger.log('error', `DNS socket handler error: ${error.message}`);
|
||||
if (!socket.destroyed) {
|
||||
socket.destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1554,6 +1599,7 @@ export class DcRouter {
|
||||
} else if (this.options.publicIp) {
|
||||
// Use explicitly configured public IP
|
||||
publicIp = this.options.publicIp;
|
||||
this.detectedPublicIp = publicIp;
|
||||
logger.log('info', `Using configured public IP for nameserver A records: ${publicIp}`);
|
||||
} else {
|
||||
// Auto-discover public IP using smartnetwork
|
||||
@@ -1564,6 +1610,7 @@ export class DcRouter {
|
||||
|
||||
if (publicIps.v4) {
|
||||
publicIp = publicIps.v4;
|
||||
this.detectedPublicIp = publicIp;
|
||||
logger.log('info', `Auto-discovered public IPv4: ${publicIp}`);
|
||||
} else {
|
||||
logger.log('warn', 'Could not auto-discover public IPv4 address');
|
||||
@@ -1689,10 +1736,42 @@ export class DcRouter {
|
||||
const currentRoutes = this.options.smartProxyConfig?.routes || [];
|
||||
this.remoteIngressManager.setRoutes(currentRoutes as any[]);
|
||||
|
||||
// Resolve TLS certs for tunnel: explicit paths > ACME for hubDomain > self-signed (Rust default)
|
||||
const riCfg = this.options.remoteIngressConfig;
|
||||
let tlsConfig: { certPem: string; keyPem: string } | undefined;
|
||||
|
||||
// Priority 1: Explicit cert/key file paths
|
||||
if (riCfg.tls?.certPath && riCfg.tls?.keyPath) {
|
||||
try {
|
||||
const certPem = plugins.fs.readFileSync(riCfg.tls.certPath, 'utf8');
|
||||
const keyPem = plugins.fs.readFileSync(riCfg.tls.keyPath, 'utf8');
|
||||
tlsConfig = { certPem, keyPem };
|
||||
logger.log('info', 'Using explicit TLS cert/key for RemoteIngress tunnel');
|
||||
} catch (err) {
|
||||
logger.log('warn', `Failed to read RemoteIngress TLS cert/key files: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: Existing cert from SmartProxy cert store for hubDomain
|
||||
if (!tlsConfig && riCfg.hubDomain) {
|
||||
try {
|
||||
const stored = await this.storageManager.getJSON(`/proxy-certs/${riCfg.hubDomain}`);
|
||||
if (stored?.publicKey && stored?.privateKey) {
|
||||
tlsConfig = { certPem: stored.publicKey, keyPem: stored.privateKey };
|
||||
logger.log('info', `Using stored ACME cert for RemoteIngress tunnel TLS: ${riCfg.hubDomain}`);
|
||||
}
|
||||
} catch { /* no stored cert, fall through */ }
|
||||
}
|
||||
|
||||
if (!tlsConfig) {
|
||||
logger.log('info', 'No TLS cert configured for RemoteIngress tunnel — using auto-generated self-signed');
|
||||
}
|
||||
|
||||
// Create and start the tunnel manager
|
||||
this.tunnelManager = new TunnelManager(this.remoteIngressManager, {
|
||||
tunnelPort: this.options.remoteIngressConfig.tunnelPort ?? 8443,
|
||||
tunnelPort: riCfg.tunnelPort ?? 8443,
|
||||
targetHost: '127.0.0.1',
|
||||
tls: tlsConfig,
|
||||
});
|
||||
await this.tunnelManager.start();
|
||||
|
||||
|
||||
@@ -122,6 +122,24 @@ export class ApiTokenManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll (regenerate) a token's secret while keeping its identity.
|
||||
* Returns the new raw token value (shown once).
|
||||
*/
|
||||
public async rollToken(id: string): Promise<{ id: string; rawToken: string } | null> {
|
||||
const stored = this.tokens.get(id);
|
||||
if (!stored) return null;
|
||||
|
||||
const randomBytes = plugins.crypto.randomBytes(32);
|
||||
const rawPayload = `${id}:${randomBytes.toString('base64url')}`;
|
||||
const rawToken = `${TOKEN_PREFIX_STR}${rawPayload}`;
|
||||
|
||||
stored.tokenHash = plugins.crypto.createHash('sha256').update(rawToken).digest('hex');
|
||||
await this.persistToken(stored);
|
||||
logger.log('info', `API token '${stored.name}' rolled (id: ${id})`);
|
||||
return { id, rawToken };
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable a token.
|
||||
*/
|
||||
|
||||
@@ -35,7 +35,9 @@ export class MetricsManager {
|
||||
queryTypes: {} as Record<string, number>,
|
||||
topDomains: new Map<string, number>(),
|
||||
lastResetDate: new Date().toDateString(),
|
||||
queryTimestamps: [] as number[], // Track query timestamps for rate calculation
|
||||
// Per-second query count ring buffer (300 entries = 5 minutes)
|
||||
queryRing: new Int32Array(300),
|
||||
queryRingLastSecond: 0, // last epoch second that was written
|
||||
responseTimes: [] as number[], // Track response times in ms
|
||||
recentQueries: [] as Array<{ timestamp: number; domain: string; type: string; answered: boolean; responseTimeMs: number }>,
|
||||
};
|
||||
@@ -95,7 +97,8 @@ export class MetricsManager {
|
||||
this.dnsMetrics.cacheMisses = 0;
|
||||
this.dnsMetrics.queryTypes = {};
|
||||
this.dnsMetrics.topDomains.clear();
|
||||
this.dnsMetrics.queryTimestamps = [];
|
||||
this.dnsMetrics.queryRing.fill(0);
|
||||
this.dnsMetrics.queryRingLastSecond = 0;
|
||||
this.dnsMetrics.responseTimes = [];
|
||||
this.dnsMetrics.recentQueries = [];
|
||||
this.dnsMetrics.lastResetDate = currentDate;
|
||||
@@ -141,16 +144,16 @@ export class MetricsManager {
|
||||
const smartMetricsData = await this.smartMetrics.getMetrics();
|
||||
const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
|
||||
const proxyStats = this.dcRouter.smartProxy ? await this.dcRouter.smartProxy.getStatistics() : null;
|
||||
const { heapUsed, heapTotal, external, rss } = process.memoryUsage();
|
||||
|
||||
return {
|
||||
uptime: process.uptime(),
|
||||
startTime: Date.now() - (process.uptime() * 1000),
|
||||
memoryUsage: {
|
||||
heapUsed: process.memoryUsage().heapUsed,
|
||||
heapTotal: process.memoryUsage().heapTotal,
|
||||
external: process.memoryUsage().external,
|
||||
rss: process.memoryUsage().rss,
|
||||
// Add SmartMetrics memory data
|
||||
heapUsed,
|
||||
heapTotal,
|
||||
external,
|
||||
rss,
|
||||
maxMemoryMB: this.smartMetrics.maxMemoryMB,
|
||||
actualUsageBytes: smartMetricsData.memoryUsageBytes,
|
||||
actualUsagePercentage: smartMetricsData.memoryPercentage,
|
||||
@@ -219,11 +222,8 @@ export class MetricsManager {
|
||||
.slice(0, 10)
|
||||
.map(([domain, count]) => ({ domain, count }));
|
||||
|
||||
// Calculate queries per second from recent timestamps
|
||||
const now = Date.now();
|
||||
const oneMinuteAgo = now - 60000;
|
||||
const recentQueries = this.dnsMetrics.queryTimestamps.filter(ts => ts >= oneMinuteAgo);
|
||||
const queriesPerSecond = recentQueries.length / 60;
|
||||
// Calculate queries per second from ring buffer (sum last 60 seconds)
|
||||
const queriesPerSecond = this.getQueryRingSum(60) / 60;
|
||||
|
||||
// Calculate average response time
|
||||
const avgResponseTime = this.dnsMetrics.responseTimes.length > 0
|
||||
@@ -427,12 +427,8 @@ export class MetricsManager {
|
||||
this.dnsMetrics.cacheMisses++;
|
||||
}
|
||||
|
||||
// Track query timestamp
|
||||
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);
|
||||
// Increment per-second query counter in ring buffer
|
||||
this.incrementQueryRing();
|
||||
|
||||
// Track response time if provided
|
||||
if (responseTimeMs) {
|
||||
@@ -604,7 +600,7 @@ export class MetricsManager {
|
||||
requestsPerSecond,
|
||||
requestsTotal,
|
||||
};
|
||||
}, 200); // Use 200ms cache for more frequent updates
|
||||
}, 1000); // 1s cache — matches typical dashboard poll interval
|
||||
}
|
||||
|
||||
// --- Time-series helpers ---
|
||||
@@ -633,6 +629,63 @@ export class MetricsManager {
|
||||
bucket.queries++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the per-second query counter in the ring buffer.
|
||||
* Zeros any stale slots between the last write and the current second.
|
||||
*/
|
||||
private incrementQueryRing(): void {
|
||||
const currentSecond = Math.floor(Date.now() / 1000);
|
||||
const ring = this.dnsMetrics.queryRing;
|
||||
const last = this.dnsMetrics.queryRingLastSecond;
|
||||
|
||||
if (last === 0) {
|
||||
// First call — zero and anchor
|
||||
ring.fill(0);
|
||||
this.dnsMetrics.queryRingLastSecond = currentSecond;
|
||||
ring[currentSecond % ring.length] = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const gap = currentSecond - last;
|
||||
if (gap >= ring.length) {
|
||||
// Entire ring is stale — clear all
|
||||
ring.fill(0);
|
||||
} else if (gap > 0) {
|
||||
// Zero slots from (last+1) to currentSecond (inclusive)
|
||||
for (let s = last + 1; s <= currentSecond; s++) {
|
||||
ring[s % ring.length] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.dnsMetrics.queryRingLastSecond = currentSecond;
|
||||
ring[currentSecond % ring.length]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum query counts from the ring buffer for the last N seconds.
|
||||
*/
|
||||
private getQueryRingSum(seconds: number): number {
|
||||
const currentSecond = Math.floor(Date.now() / 1000);
|
||||
const ring = this.dnsMetrics.queryRing;
|
||||
const last = this.dnsMetrics.queryRingLastSecond;
|
||||
|
||||
if (last === 0) return 0;
|
||||
|
||||
// First, zero stale slots so reads are accurate even without writes
|
||||
const gap = currentSecond - last;
|
||||
if (gap >= ring.length) return 0; // all data is stale
|
||||
|
||||
let sum = 0;
|
||||
const limit = Math.min(seconds, ring.length);
|
||||
for (let i = 0; i < limit; i++) {
|
||||
const sec = currentSecond - i;
|
||||
if (sec < last - (ring.length - 1)) break; // slot is from older cycle
|
||||
if (sec > last) continue; // no writes yet for this second
|
||||
sum += ring[sec % ring.length];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
private pruneOldBuckets(): void {
|
||||
const cutoff = Date.now() - 86400000; // 24h
|
||||
for (const key of this.emailMinuteBuckets.keys()) {
|
||||
|
||||
@@ -70,6 +70,10 @@ export class OpsServer {
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
// Clean up log handler streams and push destination before stopping the server
|
||||
if (this.logsHandler) {
|
||||
this.logsHandler.cleanup();
|
||||
}
|
||||
if (this.server) {
|
||||
await this.server.stop();
|
||||
}
|
||||
|
||||
@@ -77,6 +77,25 @@ export class ApiTokenHandler {
|
||||
),
|
||||
);
|
||||
|
||||
// Roll API token
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RollApiToken>(
|
||||
'rollApiToken',
|
||||
async (dataArg) => {
|
||||
await this.requireAdmin(dataArg.identity);
|
||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||
if (!manager) {
|
||||
return { success: false, message: 'Token management not initialized' };
|
||||
}
|
||||
const result = await manager.rollToken(dataArg.id);
|
||||
if (!result) {
|
||||
return { success: false, message: 'Token not found' };
|
||||
}
|
||||
return { success: true, tokenValue: result.rawToken };
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Toggle API token
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleApiToken>(
|
||||
|
||||
@@ -40,11 +40,20 @@ export class ConfigHandler {
|
||||
? 'filesystem'
|
||||
: 'memory';
|
||||
|
||||
// Resolve proxy IPs: fall back to SmartProxy's runtime proxyIPs if not in opts
|
||||
let proxyIps = opts.proxyIps || [];
|
||||
if (proxyIps.length === 0 && dcRouter.smartProxy) {
|
||||
const spSettings = (dcRouter.smartProxy as any).settings;
|
||||
if (spSettings?.proxyIPs?.length > 0) {
|
||||
proxyIps = spSettings.proxyIPs;
|
||||
}
|
||||
}
|
||||
|
||||
const system: interfaces.requests.IConfigData['system'] = {
|
||||
baseDir: resolvedPaths.dcrouterHomeDir,
|
||||
dataDir: resolvedPaths.dataDir,
|
||||
publicIp: opts.publicIp || null,
|
||||
proxyIps: opts.proxyIps || [],
|
||||
publicIp: opts.publicIp || dcRouter.detectedPublicIp || null,
|
||||
proxyIps,
|
||||
uptime: Math.floor(process.uptime()),
|
||||
storageBackend,
|
||||
storagePath: opts.storage?.fsPath || null,
|
||||
@@ -169,11 +178,27 @@ export class ConfigHandler {
|
||||
|
||||
// --- Remote Ingress ---
|
||||
const riCfg = opts.remoteIngressConfig;
|
||||
const connectedEdgeIps = dcRouter.tunnelManager?.getConnectedEdgeIps() || [];
|
||||
|
||||
// Determine TLS mode: custom certs > ACME from cert store > self-signed fallback
|
||||
let tlsMode: 'custom' | 'acme' | 'self-signed' = 'self-signed';
|
||||
if (riCfg?.tls?.certPath && riCfg?.tls?.keyPath) {
|
||||
tlsMode = 'custom';
|
||||
} else if (riCfg?.hubDomain) {
|
||||
try {
|
||||
const stored = await dcRouter.storageManager.getJSON(`/proxy-certs/${riCfg.hubDomain}`);
|
||||
if (stored?.publicKey && stored?.privateKey) {
|
||||
tlsMode = 'acme';
|
||||
}
|
||||
} catch { /* no stored cert */ }
|
||||
}
|
||||
|
||||
const remoteIngress: interfaces.requests.IConfigData['remoteIngress'] = {
|
||||
enabled: !!dcRouter.remoteIngressManager,
|
||||
tunnelPort: riCfg?.tunnelPort || null,
|
||||
hubDomain: riCfg?.hubDomain || null,
|
||||
tlsConfigured: !!(riCfg?.tls?.certPath && riCfg?.tls?.keyPath),
|
||||
tlsMode,
|
||||
connectedEdgeIps,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -3,8 +3,15 @@ import type { OpsServer } from '../classes.opsserver.js';
|
||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
import { logBuffer, baseLogger } from '../../logger.js';
|
||||
|
||||
// Module-level singleton: the log push destination is added once and reuses
|
||||
// the current OpsServer reference so it survives OpsServer restarts without
|
||||
// accumulating duplicate destinations.
|
||||
let logPushDestinationInstalled = false;
|
||||
let currentOpsServerRef: OpsServer | null = null;
|
||||
|
||||
export class LogsHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
private activeStreamStops: Set<() => void> = new Set();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
// Add this handler's router to the parent
|
||||
@@ -13,6 +20,20 @@ export class LogsHandler {
|
||||
this.setupLogPushDestination();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all active log streams and deactivate the push destination.
|
||||
* Called when OpsServer stops.
|
||||
*/
|
||||
public cleanup(): void {
|
||||
// Stop all active follow-mode log streams
|
||||
for (const stop of this.activeStreamStops) {
|
||||
stop();
|
||||
}
|
||||
this.activeStreamStops.clear();
|
||||
// Deactivate the push destination (it stays registered but becomes a no-op)
|
||||
currentOpsServerRef = null;
|
||||
}
|
||||
|
||||
private registerHandlers(): void {
|
||||
// Get Recent Logs Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
@@ -30,8 +51,8 @@ export class LogsHandler {
|
||||
|
||||
return {
|
||||
logs,
|
||||
total: logs.length, // TODO: Implement proper total count
|
||||
hasMore: false, // TODO: Implement proper pagination
|
||||
total: logs.length,
|
||||
hasMore: false,
|
||||
};
|
||||
}
|
||||
)
|
||||
@@ -56,10 +77,11 @@ export class LogsHandler {
|
||||
// Start streaming
|
||||
streamLogs.start();
|
||||
|
||||
// VirtualStream handles cleanup automatically
|
||||
// Track the stop function so we can clean up on shutdown
|
||||
this.activeStreamStops.add(streamLogs.stop);
|
||||
|
||||
return {
|
||||
logStream: virtualStream as any, // Cast to IVirtualStream interface
|
||||
logStream: virtualStream as any,
|
||||
};
|
||||
}
|
||||
)
|
||||
@@ -169,14 +191,26 @@ export class LogsHandler {
|
||||
/**
|
||||
* Add a log destination to the base logger that pushes entries
|
||||
* to all connected ops_dashboard TypedSocket clients.
|
||||
*
|
||||
* Uses a module-level singleton so the destination is added only once,
|
||||
* even across OpsServer restart cycles. The destination reads
|
||||
* `currentOpsServerRef` dynamically so it always uses the active server.
|
||||
*/
|
||||
private setupLogPushDestination(): void {
|
||||
const opsServerRef = this.opsServerRef;
|
||||
// Update the module-level reference so the existing destination uses the new server
|
||||
currentOpsServerRef = this.opsServerRef;
|
||||
|
||||
if (logPushDestinationInstalled) {
|
||||
return; // destination already registered — just updated the ref
|
||||
}
|
||||
logPushDestinationInstalled = true;
|
||||
|
||||
baseLogger.addLogDestination({
|
||||
async handleLog(logPackage: any) {
|
||||
// Access the TypedSocket server instance from OpsServer
|
||||
const typedsocket = opsServerRef.server?.typedserver?.typedsocket;
|
||||
const opsServer = currentOpsServerRef;
|
||||
if (!opsServer) return;
|
||||
|
||||
const typedsocket = opsServer.server?.typedserver?.typedsocket;
|
||||
if (!typedsocket) return;
|
||||
|
||||
let connections: any[];
|
||||
@@ -220,8 +254,18 @@ export class LogsHandler {
|
||||
stop: () => void;
|
||||
} {
|
||||
let intervalId: NodeJS.Timeout | null = null;
|
||||
let stopped = false;
|
||||
let logIndex = 0;
|
||||
|
||||
const stop = () => {
|
||||
stopped = true;
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
this.activeStreamStops.delete(stop);
|
||||
};
|
||||
|
||||
const start = () => {
|
||||
if (!follow) {
|
||||
// Send existing logs and close
|
||||
@@ -236,13 +280,19 @@ export class LogsHandler {
|
||||
const encoder = new TextEncoder();
|
||||
virtualStream.sendData(encoder.encode(logData));
|
||||
});
|
||||
// VirtualStream doesn't have end() method - it closes automatically
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// For follow mode, simulate real-time log streaming
|
||||
intervalId = setInterval(async () => {
|
||||
if (stopped) {
|
||||
// Guard: clear interval if stop() was called between ticks
|
||||
clearInterval(intervalId!);
|
||||
intervalId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const categories: Array<'smtp' | 'dns' | 'security' | 'system' | 'email'> = ['smtp', 'dns', 'security', 'system', 'email'];
|
||||
const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['info', 'warn', 'error', 'debug'];
|
||||
|
||||
@@ -266,28 +316,23 @@ export class LogsHandler {
|
||||
const logData = JSON.stringify(logEntry);
|
||||
const encoder = new TextEncoder();
|
||||
try {
|
||||
await virtualStream.sendData(encoder.encode(logData));
|
||||
// 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)).then((result) => {
|
||||
clearTimeout(timeoutHandle);
|
||||
return result;
|
||||
}),
|
||||
new Promise<never>((_, reject) => {
|
||||
timeoutHandle = setTimeout(() => reject(new Error('stream send timeout')), 10_000);
|
||||
}),
|
||||
]);
|
||||
} catch {
|
||||
// Stream closed or errored — clean up to prevent interval leak
|
||||
clearInterval(intervalId!);
|
||||
intervalId = null;
|
||||
// Stream closed, errored, or timed out — clean up
|
||||
stop();
|
||||
}
|
||||
}, 2000); // Send a log every 2 seconds
|
||||
|
||||
// TODO: Hook into actual logger events
|
||||
// logger.on('log', (logEntry) => {
|
||||
// if (matchesCriteria(logEntry, level, service)) {
|
||||
// virtualStream.sendData(formatLogEntry(logEntry));
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
// TODO: Unhook from logger events
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return { start, stop };
|
||||
|
||||
@@ -5,6 +5,10 @@ import type { RemoteIngressManager } from './classes.remoteingress-manager.js';
|
||||
export interface ITunnelManagerConfig {
|
||||
tunnelPort?: number;
|
||||
targetHost?: string;
|
||||
tls?: {
|
||||
certPem?: string;
|
||||
keyPem?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15,6 +19,7 @@ export class TunnelManager {
|
||||
private manager: RemoteIngressManager;
|
||||
private config: ITunnelManagerConfig;
|
||||
private edgeStatuses: Map<string, IRemoteIngressStatus> = new Map();
|
||||
private reconcileInterval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
constructor(manager: RemoteIngressManager, config: ITunnelManagerConfig = {}) {
|
||||
this.manager = manager;
|
||||
@@ -22,12 +27,11 @@ export class TunnelManager {
|
||||
this.hub = new plugins.remoteingress.RemoteIngressHub();
|
||||
|
||||
// Listen for edge connect/disconnect events
|
||||
this.hub.on('edgeConnected', (data: { edgeId: string }) => {
|
||||
const existing = this.edgeStatuses.get(data.edgeId);
|
||||
this.hub.on('edgeConnected', (data: { edgeId: string; peerAddr: string }) => {
|
||||
this.edgeStatuses.set(data.edgeId, {
|
||||
edgeId: data.edgeId,
|
||||
connected: true,
|
||||
publicIp: existing?.publicIp ?? null,
|
||||
publicIp: data.peerAddr || null,
|
||||
activeTunnels: 0,
|
||||
lastHeartbeat: Date.now(),
|
||||
connectedAt: Date.now(),
|
||||
@@ -61,20 +65,73 @@ export class TunnelManager {
|
||||
await this.hub.start({
|
||||
tunnelPort: this.config.tunnelPort ?? 8443,
|
||||
targetHost: this.config.targetHost ?? '127.0.0.1',
|
||||
tls: this.config.tls,
|
||||
});
|
||||
|
||||
// Send allowed edges to the hub
|
||||
await this.syncAllowedEdges();
|
||||
|
||||
// Periodically reconcile with authoritative Rust hub status
|
||||
this.reconcileInterval = setInterval(() => {
|
||||
this.reconcile().catch(() => {});
|
||||
}, 15_000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the tunnel hub.
|
||||
*/
|
||||
public async stop(): Promise<void> {
|
||||
if (this.reconcileInterval) {
|
||||
clearInterval(this.reconcileInterval);
|
||||
this.reconcileInterval = null;
|
||||
}
|
||||
// Remove event listeners before stopping to prevent leaks
|
||||
this.hub.removeAllListeners();
|
||||
await this.hub.stop();
|
||||
this.edgeStatuses.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconcile TS-side edge statuses with the authoritative Rust hub status.
|
||||
* Overwrites event-derived activeTunnels with the real activeStreams count.
|
||||
*/
|
||||
private async reconcile(): Promise<void> {
|
||||
const hubStatus = await this.hub.getStatus();
|
||||
if (!hubStatus || !hubStatus.connectedEdges) return;
|
||||
|
||||
const rustEdgeIds = new Set<string>();
|
||||
|
||||
for (const rustEdge of hubStatus.connectedEdges) {
|
||||
rustEdgeIds.add(rustEdge.edgeId);
|
||||
const existing = this.edgeStatuses.get(rustEdge.edgeId);
|
||||
if (existing) {
|
||||
existing.activeTunnels = rustEdge.activeStreams;
|
||||
existing.lastHeartbeat = Date.now();
|
||||
// Update peer address if available from Rust hub
|
||||
if (rustEdge.peerAddr) {
|
||||
existing.publicIp = rustEdge.peerAddr;
|
||||
}
|
||||
} else {
|
||||
// Missed edgeConnected event — add entry
|
||||
this.edgeStatuses.set(rustEdge.edgeId, {
|
||||
edgeId: rustEdge.edgeId,
|
||||
connected: true,
|
||||
publicIp: rustEdge.peerAddr || null,
|
||||
activeTunnels: rustEdge.activeStreams,
|
||||
lastHeartbeat: Date.now(),
|
||||
connectedAt: rustEdge.connectedAt * 1000,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Remove entries for edges no longer connected in Rust (missed edgeDisconnected)
|
||||
for (const edgeId of this.edgeStatuses.keys()) {
|
||||
if (!rustEdgeIds.has(edgeId)) {
|
||||
this.edgeStatuses.delete(edgeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync allowed edges from the manager to the hub.
|
||||
* Call this after creating/deleting/updating edges.
|
||||
@@ -109,6 +166,19 @@ export class TunnelManager {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the public IPs of all connected edges.
|
||||
*/
|
||||
public getConnectedEdgeIps(): string[] {
|
||||
const ips: string[] = [];
|
||||
for (const status of this.edgeStatuses.values()) {
|
||||
if (status.connected && status.publicIp) {
|
||||
ips.push(status.publicIp);
|
||||
}
|
||||
}
|
||||
return ips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of active tunnels across all edges.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
@@ -155,8 +162,9 @@ export class SecurityLogger {
|
||||
}
|
||||
}
|
||||
|
||||
// Return most recent events up to limit
|
||||
// Return most recent events up to limit (slice first to avoid mutating source)
|
||||
return filteredEvents
|
||||
.slice()
|
||||
.sort((a, b) => b.timestamp - a.timestamp)
|
||||
.slice(0, limit);
|
||||
}
|
||||
@@ -242,40 +250,34 @@ export class SecurityLogger {
|
||||
topIPs: Array<{ ip: string; count: number }>;
|
||||
topDomains: Array<{ domain: string; count: number }>;
|
||||
} {
|
||||
// Filter by time window if provided
|
||||
let events = this.securityEvents;
|
||||
if (timeWindow) {
|
||||
const cutoff = Date.now() - timeWindow;
|
||||
events = events.filter(e => e.timestamp >= cutoff);
|
||||
const cutoff = timeWindow ? Date.now() - timeWindow : 0;
|
||||
|
||||
// Initialize counters
|
||||
const byLevel = {} as Record<SecurityLogLevel, number>;
|
||||
for (const level of Object.values(SecurityLogLevel)) {
|
||||
byLevel[level] = 0;
|
||||
}
|
||||
const byType = {} as Record<SecurityEventType, number>;
|
||||
for (const type of Object.values(SecurityEventType)) {
|
||||
byType[type] = 0;
|
||||
}
|
||||
|
||||
// Count by level
|
||||
const byLevel = Object.values(SecurityLogLevel).reduce((acc, level) => {
|
||||
acc[level] = events.filter(e => e.level === level).length;
|
||||
return acc;
|
||||
}, {} as Record<SecurityLogLevel, number>);
|
||||
|
||||
// Count by type
|
||||
const byType = Object.values(SecurityEventType).reduce((acc, type) => {
|
||||
acc[type] = events.filter(e => e.type === type).length;
|
||||
return acc;
|
||||
}, {} as Record<SecurityEventType, number>);
|
||||
|
||||
// Count by IP
|
||||
const ipCounts = new Map<string, number>();
|
||||
events.forEach(e => {
|
||||
const domainCounts = new Map<string, number>();
|
||||
|
||||
// Single pass over all events
|
||||
let total = 0;
|
||||
for (const e of this.securityEvents) {
|
||||
if (cutoff && e.timestamp < cutoff) continue;
|
||||
total++;
|
||||
byLevel[e.level]++;
|
||||
byType[e.type]++;
|
||||
if (e.ipAddress) {
|
||||
ipCounts.set(e.ipAddress, (ipCounts.get(e.ipAddress) || 0) + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Count by domain
|
||||
const domainCounts = new Map<string, number>();
|
||||
events.forEach(e => {
|
||||
if (e.domain) {
|
||||
domainCounts.set(e.domain, (domainCounts.get(e.domain) || 0) + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sort and limit top entries
|
||||
const topIPs = Array.from(ipCounts.entries())
|
||||
@@ -288,12 +290,6 @@ export class SecurityLogger {
|
||||
.sort((a, b) => b.count - a.count)
|
||||
.slice(0, 10);
|
||||
|
||||
return {
|
||||
total: events.length,
|
||||
byLevel,
|
||||
byType,
|
||||
topIPs,
|
||||
topDomains
|
||||
};
|
||||
return { total, byLevel, byType, topIPs, topDomains };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,26 @@ export interface IReq_RevokeApiToken extends plugins.typedrequestInterfaces.impl
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll (regenerate) an API token's secret. Returns the new raw token value once.
|
||||
* Admin JWT only.
|
||||
*/
|
||||
export interface IReq_RollApiToken extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedRequest,
|
||||
IReq_RollApiToken
|
||||
> {
|
||||
method: 'rollApiToken';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
id: string;
|
||||
};
|
||||
response: {
|
||||
success: boolean;
|
||||
tokenValue?: string;
|
||||
message?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable an API token.
|
||||
*/
|
||||
|
||||
@@ -69,7 +69,8 @@ export interface IConfigData {
|
||||
enabled: boolean;
|
||||
tunnelPort: number | null;
|
||||
hubDomain: string | null;
|
||||
tlsConfigured: boolean;
|
||||
tlsMode: 'custom' | 'acme' | 'self-signed';
|
||||
connectedEdgeIps: string[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '9.1.0',
|
||||
version: '10.1.5',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -1115,6 +1115,18 @@ export async function createApiToken(name: string, scopes: interfaces.data.TApiT
|
||||
});
|
||||
}
|
||||
|
||||
export async function rollApiToken(id: string) {
|
||||
const context = getActionContext();
|
||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_RollApiToken
|
||||
>('/typedrequest', 'rollApiToken');
|
||||
|
||||
return request.fire({
|
||||
identity: context.identity,
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
export const revokeApiTokenAction = routeManagementStatePart.createAction<string>(
|
||||
async (statePartArg, tokenId) => {
|
||||
const context = getActionContext();
|
||||
@@ -1321,6 +1333,15 @@ async function dispatchCombinedRefreshAction() {
|
||||
console.error('Certificate refresh failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh remote ingress data if on remoteingress view
|
||||
if (currentView === 'remoteingress') {
|
||||
try {
|
||||
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
||||
} catch (error) {
|
||||
console.error('Remote ingress refresh failed:', error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Combined refresh failed:', error);
|
||||
}
|
||||
|
||||
@@ -152,6 +152,15 @@ export class OpsViewApiTokens extends DeesElement {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Roll',
|
||||
iconName: 'lucide:rotate-cw',
|
||||
type: ['inRow', 'contextmenu'] as any,
|
||||
actionFunc: async (actionData: any) => {
|
||||
const token = actionData.item as interfaces.data.IApiTokenInfo;
|
||||
await this.showRollTokenDialog(token);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Revoke',
|
||||
iconName: 'lucide:trash2',
|
||||
@@ -279,6 +288,60 @@ export class OpsViewApiTokens extends DeesElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async showRollTokenDialog(token: interfaces.data.IApiTokenInfo) {
|
||||
const { DeesModal } = await import('@design.estate/dees-catalog');
|
||||
|
||||
await DeesModal.createAndShow({
|
||||
heading: 'Roll Token Secret',
|
||||
content: html`
|
||||
<div style="color: #ccc; padding: 8px 0;">
|
||||
<p>This will regenerate the secret for <strong>${token.name}</strong>. The old token value will stop working immediately.</p>
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'Cancel',
|
||||
iconName: 'lucide:x',
|
||||
action: async (modalArg: any) => await modalArg.destroy(),
|
||||
},
|
||||
{
|
||||
name: 'Roll Token',
|
||||
iconName: 'lucide:rotate-cw',
|
||||
action: async (modalArg: any) => {
|
||||
await modalArg.destroy();
|
||||
try {
|
||||
const response = await appstate.rollApiToken(token.id);
|
||||
if (response.success && response.tokenValue) {
|
||||
await appstate.routeManagementStatePart.dispatchAction(appstate.fetchApiTokensAction, null);
|
||||
|
||||
await DeesModal.createAndShow({
|
||||
heading: 'Token Rolled',
|
||||
content: html`
|
||||
<div style="color: #ccc; padding: 8px 0;">
|
||||
<p>Copy this token now. It will not be shown again.</p>
|
||||
<div style="background: #111; padding: 12px; border-radius: 6px; margin-top: 8px;">
|
||||
<code style="color: #0f8; word-break: break-all; font-size: 13px;">${response.tokenValue}</code>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'Done',
|
||||
iconName: 'lucide:check',
|
||||
action: async (m: any) => await m.destroy(),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to roll token:', error);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
await appstate.routeManagementStatePart.dispatchAction(appstate.fetchApiTokensAction, null);
|
||||
}
|
||||
|
||||
@@ -103,11 +103,20 @@ export class OpsViewConfig extends DeesElement {
|
||||
}
|
||||
|
||||
private renderSystemSection(sys: appstate.IConfigState['config']['system']): TemplateResult {
|
||||
// Annotate proxy IPs with source hint when Remote Ingress is active
|
||||
const ri = this.configState.config?.remoteIngress;
|
||||
let proxyIpValues: string[] | null = sys.proxyIps.length > 0 ? [...sys.proxyIps] : null;
|
||||
if (proxyIpValues && ri?.enabled && proxyIpValues.includes('127.0.0.1')) {
|
||||
proxyIpValues = proxyIpValues.map(ip =>
|
||||
ip === '127.0.0.1' ? '127.0.0.1 (Remote Ingress)' : ip
|
||||
);
|
||||
}
|
||||
|
||||
const fields: IConfigField[] = [
|
||||
{ key: 'Base Directory', value: sys.baseDir },
|
||||
{ key: 'Data Directory', value: sys.dataDir },
|
||||
{ key: 'Public IP', value: sys.publicIp },
|
||||
{ key: 'Proxy IPs', value: sys.proxyIps.length > 0 ? sys.proxyIps : null, type: 'pills' },
|
||||
{ key: 'Proxy IPs', value: proxyIpValues, type: 'pills' },
|
||||
{ key: 'Uptime', value: this.formatUptime(sys.uptime) },
|
||||
{ key: 'Storage Backend', value: sys.storageBackend, type: 'badge' },
|
||||
{ key: 'Storage Path', value: sys.storagePath },
|
||||
@@ -291,7 +300,8 @@ export class OpsViewConfig extends DeesElement {
|
||||
const fields: IConfigField[] = [
|
||||
{ key: 'Tunnel Port', value: ri.tunnelPort },
|
||||
{ key: 'Hub Domain', value: ri.hubDomain },
|
||||
{ key: 'TLS Configured', value: ri.tlsConfigured, type: 'boolean' },
|
||||
{ key: 'TLS Mode', value: ri.tlsMode, type: 'badge' },
|
||||
{ key: 'Connected Edge IPs', value: ri.connectedEdgeIps?.length > 0 ? ri.connectedEdgeIps : null, type: 'pills' },
|
||||
];
|
||||
|
||||
const actions: IConfigSectionAction[] = [
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user