@push.rocks/smartproxy 3.41.8 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/classes.pp.acmemanager.d.ts +34 -0
- package/dist_ts/classes.pp.acmemanager.js +123 -0
- package/dist_ts/classes.pp.connectionhandler.d.ts +39 -0
- package/dist_ts/classes.pp.connectionhandler.js +693 -0
- package/dist_ts/classes.pp.connectionmanager.d.ts +78 -0
- package/dist_ts/classes.pp.connectionmanager.js +378 -0
- package/dist_ts/classes.pp.domainconfigmanager.d.ts +55 -0
- package/dist_ts/classes.pp.domainconfigmanager.js +103 -0
- package/dist_ts/classes.pp.interfaces.d.ts +109 -0
- package/dist_ts/classes.pp.interfaces.js +2 -0
- package/dist_ts/classes.pp.networkproxybridge.d.ts +43 -0
- package/dist_ts/classes.pp.networkproxybridge.js +211 -0
- package/dist_ts/classes.pp.portproxy.d.ts +48 -0
- package/dist_ts/classes.pp.portproxy.js +268 -0
- package/dist_ts/classes.pp.portrangemanager.d.ts +56 -0
- package/dist_ts/classes.pp.portrangemanager.js +179 -0
- package/dist_ts/classes.pp.securitymanager.d.ts +47 -0
- package/dist_ts/classes.pp.securitymanager.js +126 -0
- package/dist_ts/classes.pp.snihandler.d.ts +198 -0
- package/dist_ts/classes.pp.snihandler.js +1210 -0
- package/dist_ts/classes.pp.timeoutmanager.d.ts +47 -0
- package/dist_ts/classes.pp.timeoutmanager.js +154 -0
- package/dist_ts/classes.pp.tlsmanager.d.ts +57 -0
- package/dist_ts/classes.pp.tlsmanager.js +132 -0
- package/dist_ts/index.d.ts +2 -2
- package/dist_ts/index.js +3 -3
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.pp.acmemanager.ts +149 -0
- package/ts/classes.pp.connectionhandler.ts +982 -0
- package/ts/classes.pp.connectionmanager.ts +446 -0
- package/ts/classes.pp.domainconfigmanager.ts +123 -0
- package/ts/classes.pp.interfaces.ts +136 -0
- package/ts/classes.pp.networkproxybridge.ts +258 -0
- package/ts/classes.pp.portproxy.ts +344 -0
- package/ts/classes.pp.portrangemanager.ts +214 -0
- package/ts/classes.pp.securitymanager.ts +147 -0
- package/ts/{classes.snihandler.ts → classes.pp.snihandler.ts} +1 -1
- package/ts/classes.pp.timeoutmanager.ts +190 -0
- package/ts/classes.pp.tlsmanager.ts +206 -0
- package/ts/index.ts +2 -2
- package/ts/classes.portproxy.ts +0 -2503
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type{ IPortProxySettings } from './classes.pp.interfaces.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Manages port ranges and port-based configuration
|
|
5
|
+
*/
|
|
6
|
+
export class PortRangeManager {
|
|
7
|
+
constructor(private settings: IPortProxySettings) {}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get all ports that should be listened on
|
|
11
|
+
*/
|
|
12
|
+
public getListeningPorts(): Set<number> {
|
|
13
|
+
const listeningPorts = new Set<number>();
|
|
14
|
+
|
|
15
|
+
// Always include the main fromPort
|
|
16
|
+
listeningPorts.add(this.settings.fromPort);
|
|
17
|
+
|
|
18
|
+
// Add ports from global port ranges if defined
|
|
19
|
+
if (this.settings.globalPortRanges && this.settings.globalPortRanges.length > 0) {
|
|
20
|
+
for (const range of this.settings.globalPortRanges) {
|
|
21
|
+
for (let port = range.from; port <= range.to; port++) {
|
|
22
|
+
listeningPorts.add(port);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return listeningPorts;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if a port should use NetworkProxy for forwarding
|
|
32
|
+
*/
|
|
33
|
+
public shouldUseNetworkProxy(port: number): boolean {
|
|
34
|
+
return !!this.settings.useNetworkProxy && this.settings.useNetworkProxy.includes(port);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if port should use global forwarding
|
|
39
|
+
*/
|
|
40
|
+
public shouldUseGlobalForwarding(port: number): boolean {
|
|
41
|
+
return (
|
|
42
|
+
!!this.settings.forwardAllGlobalRanges &&
|
|
43
|
+
this.isPortInGlobalRanges(port)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if a port is in global ranges
|
|
49
|
+
*/
|
|
50
|
+
public isPortInGlobalRanges(port: number): boolean {
|
|
51
|
+
return (
|
|
52
|
+
this.settings.globalPortRanges &&
|
|
53
|
+
this.isPortInRanges(port, this.settings.globalPortRanges)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if a port falls within the specified ranges
|
|
59
|
+
*/
|
|
60
|
+
public isPortInRanges(port: number, ranges: Array<{ from: number; to: number }>): boolean {
|
|
61
|
+
return ranges.some((range) => port >= range.from && port <= range.to);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get forwarding port for a specific listening port
|
|
66
|
+
* This determines what port to connect to on the target
|
|
67
|
+
*/
|
|
68
|
+
public getForwardingPort(listeningPort: number): number {
|
|
69
|
+
// If using global forwarding, forward to the original port
|
|
70
|
+
if (this.settings.forwardAllGlobalRanges && this.isPortInGlobalRanges(listeningPort)) {
|
|
71
|
+
return listeningPort;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Otherwise use the configured toPort
|
|
75
|
+
return this.settings.toPort;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find domain-specific port ranges that include a given port
|
|
80
|
+
*/
|
|
81
|
+
public findDomainPortRange(port: number): {
|
|
82
|
+
domainIndex: number,
|
|
83
|
+
range: { from: number, to: number }
|
|
84
|
+
} | undefined {
|
|
85
|
+
for (let i = 0; i < this.settings.domainConfigs.length; i++) {
|
|
86
|
+
const domain = this.settings.domainConfigs[i];
|
|
87
|
+
if (domain.portRanges) {
|
|
88
|
+
for (const range of domain.portRanges) {
|
|
89
|
+
if (port >= range.from && port <= range.to) {
|
|
90
|
+
return { domainIndex: i, range };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get a list of all configured ports
|
|
100
|
+
* This includes the fromPort, NetworkProxy ports, and ports from all ranges
|
|
101
|
+
*/
|
|
102
|
+
public getAllConfiguredPorts(): number[] {
|
|
103
|
+
const ports = new Set<number>();
|
|
104
|
+
|
|
105
|
+
// Add main listening port
|
|
106
|
+
ports.add(this.settings.fromPort);
|
|
107
|
+
|
|
108
|
+
// Add NetworkProxy port if configured
|
|
109
|
+
if (this.settings.networkProxyPort) {
|
|
110
|
+
ports.add(this.settings.networkProxyPort);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Add NetworkProxy ports
|
|
114
|
+
if (this.settings.useNetworkProxy) {
|
|
115
|
+
for (const port of this.settings.useNetworkProxy) {
|
|
116
|
+
ports.add(port);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Add ACME HTTP challenge port if enabled
|
|
121
|
+
if (this.settings.acme?.enabled && this.settings.acme.port) {
|
|
122
|
+
ports.add(this.settings.acme.port);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Add global port ranges
|
|
126
|
+
if (this.settings.globalPortRanges) {
|
|
127
|
+
for (const range of this.settings.globalPortRanges) {
|
|
128
|
+
for (let port = range.from; port <= range.to; port++) {
|
|
129
|
+
ports.add(port);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Add domain-specific port ranges
|
|
135
|
+
for (const domain of this.settings.domainConfigs) {
|
|
136
|
+
if (domain.portRanges) {
|
|
137
|
+
for (const range of domain.portRanges) {
|
|
138
|
+
for (let port = range.from; port <= range.to; port++) {
|
|
139
|
+
ports.add(port);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Add domain-specific NetworkProxy port if configured
|
|
145
|
+
if (domain.useNetworkProxy && domain.networkProxyPort) {
|
|
146
|
+
ports.add(domain.networkProxyPort);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return Array.from(ports);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Validate port configuration
|
|
155
|
+
* Returns array of warning messages
|
|
156
|
+
*/
|
|
157
|
+
public validateConfiguration(): string[] {
|
|
158
|
+
const warnings: string[] = [];
|
|
159
|
+
|
|
160
|
+
// Check for overlapping port ranges
|
|
161
|
+
const portMappings = new Map<number, string[]>();
|
|
162
|
+
|
|
163
|
+
// Track global port ranges
|
|
164
|
+
if (this.settings.globalPortRanges) {
|
|
165
|
+
for (const range of this.settings.globalPortRanges) {
|
|
166
|
+
for (let port = range.from; port <= range.to; port++) {
|
|
167
|
+
if (!portMappings.has(port)) {
|
|
168
|
+
portMappings.set(port, []);
|
|
169
|
+
}
|
|
170
|
+
portMappings.get(port)!.push('Global Port Range');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Track domain-specific port ranges
|
|
176
|
+
for (const domain of this.settings.domainConfigs) {
|
|
177
|
+
if (domain.portRanges) {
|
|
178
|
+
for (const range of domain.portRanges) {
|
|
179
|
+
for (let port = range.from; port <= range.to; port++) {
|
|
180
|
+
if (!portMappings.has(port)) {
|
|
181
|
+
portMappings.set(port, []);
|
|
182
|
+
}
|
|
183
|
+
portMappings.get(port)!.push(`Domain: ${domain.domains.join(', ')}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check for ports with multiple mappings
|
|
190
|
+
for (const [port, mappings] of portMappings.entries()) {
|
|
191
|
+
if (mappings.length > 1) {
|
|
192
|
+
warnings.push(`Port ${port} has multiple mappings: ${mappings.join(', ')}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check if main ports are used elsewhere
|
|
197
|
+
if (portMappings.has(this.settings.fromPort) && portMappings.get(this.settings.fromPort)!.length > 0) {
|
|
198
|
+
warnings.push(`Main listening port ${this.settings.fromPort} is also used in port ranges`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (this.settings.networkProxyPort && portMappings.has(this.settings.networkProxyPort)) {
|
|
202
|
+
warnings.push(`NetworkProxy port ${this.settings.networkProxyPort} is also used in port ranges`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check ACME port
|
|
206
|
+
if (this.settings.acme?.enabled && this.settings.acme.port) {
|
|
207
|
+
if (portMappings.has(this.settings.acme.port)) {
|
|
208
|
+
warnings.push(`ACME HTTP challenge port ${this.settings.acme.port} is also used in port ranges`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return warnings;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import type { IPortProxySettings } from './classes.pp.interfaces.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handles security aspects like IP tracking, rate limiting, and authorization
|
|
6
|
+
*/
|
|
7
|
+
export class SecurityManager {
|
|
8
|
+
private connectionsByIP: Map<string, Set<string>> = new Map();
|
|
9
|
+
private connectionRateByIP: Map<string, number[]> = new Map();
|
|
10
|
+
|
|
11
|
+
constructor(private settings: IPortProxySettings) {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get connections count by IP
|
|
15
|
+
*/
|
|
16
|
+
public getConnectionCountByIP(ip: string): number {
|
|
17
|
+
return this.connectionsByIP.get(ip)?.size || 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check and update connection rate for an IP
|
|
22
|
+
* @returns true if within rate limit, false if exceeding limit
|
|
23
|
+
*/
|
|
24
|
+
public checkConnectionRate(ip: string): boolean {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const minute = 60 * 1000;
|
|
27
|
+
|
|
28
|
+
if (!this.connectionRateByIP.has(ip)) {
|
|
29
|
+
this.connectionRateByIP.set(ip, [now]);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get timestamps and filter out entries older than 1 minute
|
|
34
|
+
const timestamps = this.connectionRateByIP.get(ip)!.filter((time) => now - time < minute);
|
|
35
|
+
timestamps.push(now);
|
|
36
|
+
this.connectionRateByIP.set(ip, timestamps);
|
|
37
|
+
|
|
38
|
+
// Check if rate exceeds limit
|
|
39
|
+
return timestamps.length <= this.settings.connectionRateLimitPerMinute!;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Track connection by IP
|
|
44
|
+
*/
|
|
45
|
+
public trackConnectionByIP(ip: string, connectionId: string): void {
|
|
46
|
+
if (!this.connectionsByIP.has(ip)) {
|
|
47
|
+
this.connectionsByIP.set(ip, new Set());
|
|
48
|
+
}
|
|
49
|
+
this.connectionsByIP.get(ip)!.add(connectionId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Remove connection tracking for an IP
|
|
54
|
+
*/
|
|
55
|
+
public removeConnectionByIP(ip: string, connectionId: string): void {
|
|
56
|
+
if (this.connectionsByIP.has(ip)) {
|
|
57
|
+
const connections = this.connectionsByIP.get(ip)!;
|
|
58
|
+
connections.delete(connectionId);
|
|
59
|
+
if (connections.size === 0) {
|
|
60
|
+
this.connectionsByIP.delete(ip);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if an IP is allowed using glob patterns
|
|
67
|
+
*/
|
|
68
|
+
public isIPAuthorized(ip: string, allowedIPs: string[], blockedIPs: string[] = []): boolean {
|
|
69
|
+
// Skip IP validation if allowedIPs is empty
|
|
70
|
+
if (!ip || (allowedIPs.length === 0 && blockedIPs.length === 0)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// First check if IP is blocked
|
|
75
|
+
if (blockedIPs.length > 0 && this.isGlobIPMatch(ip, blockedIPs)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Then check if IP is allowed
|
|
80
|
+
return this.isGlobIPMatch(ip, allowedIPs);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if the IP matches any of the glob patterns
|
|
85
|
+
*/
|
|
86
|
+
private isGlobIPMatch(ip: string, patterns: string[]): boolean {
|
|
87
|
+
if (!ip || !patterns || patterns.length === 0) return false;
|
|
88
|
+
|
|
89
|
+
const normalizeIP = (ip: string): string[] => {
|
|
90
|
+
if (!ip) return [];
|
|
91
|
+
if (ip.startsWith('::ffff:')) {
|
|
92
|
+
const ipv4 = ip.slice(7);
|
|
93
|
+
return [ip, ipv4];
|
|
94
|
+
}
|
|
95
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
|
96
|
+
return [ip, `::ffff:${ip}`];
|
|
97
|
+
}
|
|
98
|
+
return [ip];
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const normalizedIPVariants = normalizeIP(ip);
|
|
102
|
+
if (normalizedIPVariants.length === 0) return false;
|
|
103
|
+
|
|
104
|
+
const expandedPatterns = patterns.flatMap(normalizeIP);
|
|
105
|
+
return normalizedIPVariants.some((ipVariant) =>
|
|
106
|
+
expandedPatterns.some((pattern) => plugins.minimatch(ipVariant, pattern))
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if IP should be allowed considering connection rate and max connections
|
|
112
|
+
* @returns Object with result and reason
|
|
113
|
+
*/
|
|
114
|
+
public validateIP(ip: string): { allowed: boolean; reason?: string } {
|
|
115
|
+
// Check connection count limit
|
|
116
|
+
if (
|
|
117
|
+
this.settings.maxConnectionsPerIP &&
|
|
118
|
+
this.getConnectionCountByIP(ip) >= this.settings.maxConnectionsPerIP
|
|
119
|
+
) {
|
|
120
|
+
return {
|
|
121
|
+
allowed: false,
|
|
122
|
+
reason: `Maximum connections per IP (${this.settings.maxConnectionsPerIP}) exceeded`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check connection rate limit
|
|
127
|
+
if (
|
|
128
|
+
this.settings.connectionRateLimitPerMinute &&
|
|
129
|
+
!this.checkConnectionRate(ip)
|
|
130
|
+
) {
|
|
131
|
+
return {
|
|
132
|
+
allowed: false,
|
|
133
|
+
reason: `Connection rate limit (${this.settings.connectionRateLimitPerMinute}/min) exceeded`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { allowed: true };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Clears all IP tracking data (for shutdown)
|
|
142
|
+
*/
|
|
143
|
+
public clearIPTracking(): void {
|
|
144
|
+
this.connectionsByIP.clear();
|
|
145
|
+
this.connectionRateByIP.clear();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { IConnectionRecord, IPortProxySettings } from './classes.pp.interfaces.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Manages timeouts and inactivity tracking for connections
|
|
5
|
+
*/
|
|
6
|
+
export class TimeoutManager {
|
|
7
|
+
constructor(private settings: IPortProxySettings) {}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Ensure timeout values don't exceed Node.js max safe integer
|
|
11
|
+
*/
|
|
12
|
+
public ensureSafeTimeout(timeout: number): number {
|
|
13
|
+
const MAX_SAFE_TIMEOUT = 2147483647; // Maximum safe value (2^31 - 1)
|
|
14
|
+
return Math.min(Math.floor(timeout), MAX_SAFE_TIMEOUT);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generate a slightly randomized timeout to prevent thundering herd
|
|
19
|
+
*/
|
|
20
|
+
public randomizeTimeout(baseTimeout: number, variationPercent: number = 5): number {
|
|
21
|
+
const safeBaseTimeout = this.ensureSafeTimeout(baseTimeout);
|
|
22
|
+
const variation = safeBaseTimeout * (variationPercent / 100);
|
|
23
|
+
return this.ensureSafeTimeout(
|
|
24
|
+
safeBaseTimeout + Math.floor(Math.random() * variation * 2) - variation
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Update connection activity timestamp
|
|
30
|
+
*/
|
|
31
|
+
public updateActivity(record: IConnectionRecord): void {
|
|
32
|
+
record.lastActivity = Date.now();
|
|
33
|
+
|
|
34
|
+
// Clear any inactivity warning
|
|
35
|
+
if (record.inactivityWarningIssued) {
|
|
36
|
+
record.inactivityWarningIssued = false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Calculate effective inactivity timeout based on connection type
|
|
42
|
+
*/
|
|
43
|
+
public getEffectiveInactivityTimeout(record: IConnectionRecord): number {
|
|
44
|
+
let effectiveTimeout = this.settings.inactivityTimeout || 14400000; // 4 hours default
|
|
45
|
+
|
|
46
|
+
// For immortal keep-alive connections, use an extremely long timeout
|
|
47
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
48
|
+
return Number.MAX_SAFE_INTEGER;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// For extended keep-alive connections, apply multiplier
|
|
52
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
53
|
+
const multiplier = this.settings.keepAliveInactivityMultiplier || 6;
|
|
54
|
+
effectiveTimeout = effectiveTimeout * multiplier;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return this.ensureSafeTimeout(effectiveTimeout);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Calculate effective max lifetime based on connection type
|
|
62
|
+
*/
|
|
63
|
+
public getEffectiveMaxLifetime(record: IConnectionRecord): number {
|
|
64
|
+
// Use domain-specific timeout if available
|
|
65
|
+
const baseTimeout = record.domainConfig?.connectionTimeout ||
|
|
66
|
+
this.settings.maxConnectionLifetime ||
|
|
67
|
+
86400000; // 24 hours default
|
|
68
|
+
|
|
69
|
+
// For immortal keep-alive connections, use an extremely long lifetime
|
|
70
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
71
|
+
return Number.MAX_SAFE_INTEGER;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// For extended keep-alive connections, use the extended lifetime setting
|
|
75
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
76
|
+
return this.ensureSafeTimeout(
|
|
77
|
+
this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000 // 7 days default
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Apply randomization if enabled
|
|
82
|
+
if (this.settings.enableRandomizedTimeouts) {
|
|
83
|
+
return this.randomizeTimeout(baseTimeout);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return this.ensureSafeTimeout(baseTimeout);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Setup connection timeout
|
|
91
|
+
* @returns The cleanup timer
|
|
92
|
+
*/
|
|
93
|
+
public setupConnectionTimeout(
|
|
94
|
+
record: IConnectionRecord,
|
|
95
|
+
onTimeout: (record: IConnectionRecord, reason: string) => void
|
|
96
|
+
): NodeJS.Timeout {
|
|
97
|
+
// Clear any existing timer
|
|
98
|
+
if (record.cleanupTimer) {
|
|
99
|
+
clearTimeout(record.cleanupTimer);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Calculate effective timeout
|
|
103
|
+
const effectiveLifetime = this.getEffectiveMaxLifetime(record);
|
|
104
|
+
|
|
105
|
+
// Set up the timeout
|
|
106
|
+
const timer = setTimeout(() => {
|
|
107
|
+
// Call the provided callback
|
|
108
|
+
onTimeout(record, 'connection_timeout');
|
|
109
|
+
}, effectiveLifetime);
|
|
110
|
+
|
|
111
|
+
// Make sure timeout doesn't keep the process alive
|
|
112
|
+
if (timer.unref) {
|
|
113
|
+
timer.unref();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return timer;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check for inactivity on a connection
|
|
121
|
+
* @returns Object with check results
|
|
122
|
+
*/
|
|
123
|
+
public checkInactivity(record: IConnectionRecord): {
|
|
124
|
+
isInactive: boolean;
|
|
125
|
+
shouldWarn: boolean;
|
|
126
|
+
inactivityTime: number;
|
|
127
|
+
effectiveTimeout: number;
|
|
128
|
+
} {
|
|
129
|
+
// Skip for connections with inactivity check disabled
|
|
130
|
+
if (this.settings.disableInactivityCheck) {
|
|
131
|
+
return {
|
|
132
|
+
isInactive: false,
|
|
133
|
+
shouldWarn: false,
|
|
134
|
+
inactivityTime: 0,
|
|
135
|
+
effectiveTimeout: 0
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Skip for immortal keep-alive connections
|
|
140
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
141
|
+
return {
|
|
142
|
+
isInactive: false,
|
|
143
|
+
shouldWarn: false,
|
|
144
|
+
inactivityTime: 0,
|
|
145
|
+
effectiveTimeout: 0
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const inactivityTime = now - record.lastActivity;
|
|
151
|
+
const effectiveTimeout = this.getEffectiveInactivityTimeout(record);
|
|
152
|
+
|
|
153
|
+
// Check if inactive
|
|
154
|
+
const isInactive = inactivityTime > effectiveTimeout;
|
|
155
|
+
|
|
156
|
+
// For keep-alive connections, we should warn first
|
|
157
|
+
const shouldWarn = record.hasKeepAlive &&
|
|
158
|
+
isInactive &&
|
|
159
|
+
!record.inactivityWarningIssued;
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
isInactive,
|
|
163
|
+
shouldWarn,
|
|
164
|
+
inactivityTime,
|
|
165
|
+
effectiveTimeout
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Apply socket timeout settings
|
|
171
|
+
*/
|
|
172
|
+
public applySocketTimeouts(record: IConnectionRecord): void {
|
|
173
|
+
// Skip for immortal keep-alive connections
|
|
174
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
175
|
+
// Disable timeouts completely for immortal connections
|
|
176
|
+
record.incoming.setTimeout(0);
|
|
177
|
+
if (record.outgoing) {
|
|
178
|
+
record.outgoing.setTimeout(0);
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Apply normal timeouts
|
|
184
|
+
const timeout = this.ensureSafeTimeout(this.settings.socketTimeout || 3600000); // 1 hour default
|
|
185
|
+
record.incoming.setTimeout(timeout);
|
|
186
|
+
if (record.outgoing) {
|
|
187
|
+
record.outgoing.setTimeout(timeout);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|