@push.rocks/smartproxy 3.10.3 → 3.11.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.
@@ -0,0 +1,295 @@
1
+ import * as plugins from './plugins.js';
2
+ /**
3
+ * Extracts the SNI (Server Name Indication) from a TLS ClientHello packet.
4
+ * @param buffer - Buffer containing the TLS ClientHello.
5
+ * @returns The server name if found, otherwise undefined.
6
+ */
7
+ function extractSNI(buffer) {
8
+ let offset = 0;
9
+ if (buffer.length < 5)
10
+ return undefined;
11
+ const recordType = buffer.readUInt8(0);
12
+ if (recordType !== 22)
13
+ return undefined; // 22 = handshake
14
+ const recordLength = buffer.readUInt16BE(3);
15
+ if (buffer.length < 5 + recordLength)
16
+ return undefined;
17
+ offset = 5;
18
+ const handshakeType = buffer.readUInt8(offset);
19
+ if (handshakeType !== 1)
20
+ return undefined; // 1 = ClientHello
21
+ offset += 4; // Skip handshake header (type + length)
22
+ offset += 2 + 32; // Skip client version and random
23
+ const sessionIDLength = buffer.readUInt8(offset);
24
+ offset += 1 + sessionIDLength; // Skip session ID
25
+ const cipherSuitesLength = buffer.readUInt16BE(offset);
26
+ offset += 2 + cipherSuitesLength; // Skip cipher suites
27
+ const compressionMethodsLength = buffer.readUInt8(offset);
28
+ offset += 1 + compressionMethodsLength; // Skip compression methods
29
+ if (offset + 2 > buffer.length)
30
+ return undefined;
31
+ const extensionsLength = buffer.readUInt16BE(offset);
32
+ offset += 2;
33
+ const extensionsEnd = offset + extensionsLength;
34
+ while (offset + 4 <= extensionsEnd) {
35
+ const extensionType = buffer.readUInt16BE(offset);
36
+ const extensionLength = buffer.readUInt16BE(offset + 2);
37
+ offset += 4;
38
+ if (extensionType === 0x0000) { // SNI extension
39
+ if (offset + 2 > buffer.length)
40
+ return undefined;
41
+ const sniListLength = buffer.readUInt16BE(offset);
42
+ offset += 2;
43
+ const sniListEnd = offset + sniListLength;
44
+ while (offset + 3 < sniListEnd) {
45
+ const nameType = buffer.readUInt8(offset++);
46
+ const nameLen = buffer.readUInt16BE(offset);
47
+ offset += 2;
48
+ if (nameType === 0) { // host_name
49
+ if (offset + nameLen > buffer.length)
50
+ return undefined;
51
+ return buffer.toString('utf8', offset, offset + nameLen);
52
+ }
53
+ offset += nameLen;
54
+ }
55
+ break;
56
+ }
57
+ else {
58
+ offset += extensionLength;
59
+ }
60
+ }
61
+ return undefined;
62
+ }
63
+ export class PortProxy {
64
+ constructor(settings) {
65
+ // Unified record tracking each connection pair.
66
+ this.connectionRecords = new Set();
67
+ this.connectionLogger = null;
68
+ this.terminationStats = {
69
+ incoming: {},
70
+ outgoing: {},
71
+ };
72
+ this.settings = {
73
+ ...settings,
74
+ toHost: settings.toHost || 'localhost',
75
+ };
76
+ }
77
+ incrementTerminationStat(side, reason) {
78
+ this.terminationStats[side][reason] = (this.terminationStats[side][reason] || 0) + 1;
79
+ }
80
+ async start() {
81
+ // Helper to forcefully destroy sockets.
82
+ const cleanUpSockets = (socketA, socketB) => {
83
+ if (!socketA.destroyed)
84
+ socketA.destroy();
85
+ if (socketB && !socketB.destroyed)
86
+ socketB.destroy();
87
+ };
88
+ // Normalize an IP to include both IPv4 and IPv6 representations.
89
+ const normalizeIP = (ip) => {
90
+ if (ip.startsWith('::ffff:')) {
91
+ const ipv4 = ip.slice(7);
92
+ return [ip, ipv4];
93
+ }
94
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
95
+ return [ip, `::ffff:${ip}`];
96
+ }
97
+ return [ip];
98
+ };
99
+ // Check if a given IP matches any of the glob patterns.
100
+ const isAllowed = (ip, patterns) => {
101
+ const normalizedIPVariants = normalizeIP(ip);
102
+ const expandedPatterns = patterns.flatMap(normalizeIP);
103
+ return normalizedIPVariants.some(ipVariant => expandedPatterns.some(pattern => plugins.minimatch(ipVariant, pattern)));
104
+ };
105
+ // Find a matching domain config based on the SNI.
106
+ const findMatchingDomain = (serverName) => this.settings.domains.find(config => plugins.minimatch(serverName, config.domain));
107
+ this.netServer = plugins.net.createServer((socket) => {
108
+ const remoteIP = socket.remoteAddress || '';
109
+ const connectionRecord = {
110
+ incoming: socket,
111
+ outgoing: null,
112
+ incomingStartTime: Date.now(),
113
+ connectionClosed: false,
114
+ };
115
+ this.connectionRecords.add(connectionRecord);
116
+ console.log(`New connection from ${remoteIP}. Active connections: ${this.connectionRecords.size}`);
117
+ let initialDataReceived = false;
118
+ let incomingTerminationReason = null;
119
+ let outgoingTerminationReason = null;
120
+ // Ensure cleanup happens only once for the entire connection record.
121
+ const cleanupOnce = () => {
122
+ if (!connectionRecord.connectionClosed) {
123
+ connectionRecord.connectionClosed = true;
124
+ cleanUpSockets(connectionRecord.incoming, connectionRecord.outgoing || undefined);
125
+ this.connectionRecords.delete(connectionRecord);
126
+ console.log(`Connection from ${remoteIP} terminated. Active connections: ${this.connectionRecords.size}`);
127
+ }
128
+ };
129
+ // Helper to reject an incoming connection.
130
+ const rejectIncomingConnection = (reason, logMessage) => {
131
+ console.log(logMessage);
132
+ socket.end();
133
+ if (incomingTerminationReason === null) {
134
+ incomingTerminationReason = reason;
135
+ this.incrementTerminationStat('incoming', reason);
136
+ }
137
+ cleanupOnce();
138
+ };
139
+ socket.on('error', (err) => {
140
+ const errorMessage = initialDataReceived
141
+ ? `(Immediate) Incoming socket error from ${remoteIP}: ${err.message}`
142
+ : `(Premature) Incoming socket error from ${remoteIP} before data received: ${err.message}`;
143
+ console.log(errorMessage);
144
+ });
145
+ const handleError = (side) => (err) => {
146
+ const code = err.code;
147
+ let reason = 'error';
148
+ if (code === 'ECONNRESET') {
149
+ reason = 'econnreset';
150
+ console.log(`ECONNRESET on ${side} side from ${remoteIP}: ${err.message}`);
151
+ }
152
+ else {
153
+ console.log(`Error on ${side} side from ${remoteIP}: ${err.message}`);
154
+ }
155
+ if (side === 'incoming' && incomingTerminationReason === null) {
156
+ incomingTerminationReason = reason;
157
+ this.incrementTerminationStat('incoming', reason);
158
+ }
159
+ else if (side === 'outgoing' && outgoingTerminationReason === null) {
160
+ outgoingTerminationReason = reason;
161
+ this.incrementTerminationStat('outgoing', reason);
162
+ }
163
+ cleanupOnce();
164
+ };
165
+ const handleClose = (side) => () => {
166
+ console.log(`Connection closed on ${side} side from ${remoteIP}`);
167
+ if (side === 'incoming' && incomingTerminationReason === null) {
168
+ incomingTerminationReason = 'normal';
169
+ this.incrementTerminationStat('incoming', 'normal');
170
+ }
171
+ else if (side === 'outgoing' && outgoingTerminationReason === null) {
172
+ outgoingTerminationReason = 'normal';
173
+ this.incrementTerminationStat('outgoing', 'normal');
174
+ }
175
+ cleanupOnce();
176
+ };
177
+ const setupConnection = (serverName, initialChunk) => {
178
+ const defaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
179
+ if (!defaultAllowed && serverName) {
180
+ const domainConfig = findMatchingDomain(serverName);
181
+ if (!domainConfig) {
182
+ return rejectIncomingConnection('rejected', `Connection rejected: No matching domain config for ${serverName} from ${remoteIP}`);
183
+ }
184
+ if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
185
+ return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for domain ${serverName}`);
186
+ }
187
+ }
188
+ else if (!defaultAllowed && !serverName) {
189
+ return rejectIncomingConnection('rejected', `Connection rejected: No SNI and IP ${remoteIP} not in default allowed list`);
190
+ }
191
+ else if (defaultAllowed && !serverName) {
192
+ console.log(`Connection allowed: IP ${remoteIP} is in default allowed list`);
193
+ }
194
+ const domainConfig = serverName ? findMatchingDomain(serverName) : undefined;
195
+ const targetHost = domainConfig?.targetIP || this.settings.toHost;
196
+ const connectionOptions = {
197
+ host: targetHost,
198
+ port: this.settings.toPort,
199
+ };
200
+ if (this.settings.preserveSourceIP) {
201
+ connectionOptions.localAddress = remoteIP.replace('::ffff:', '');
202
+ }
203
+ const targetSocket = plugins.net.connect(connectionOptions);
204
+ connectionRecord.outgoing = targetSocket;
205
+ connectionRecord.outgoingStartTime = Date.now();
206
+ console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}` +
207
+ `${serverName ? ` (SNI: ${serverName})` : ''}`);
208
+ if (initialChunk) {
209
+ socket.unshift(initialChunk);
210
+ }
211
+ socket.setTimeout(120000);
212
+ socket.pipe(targetSocket);
213
+ targetSocket.pipe(socket);
214
+ socket.on('error', handleError('incoming'));
215
+ targetSocket.on('error', handleError('outgoing'));
216
+ socket.on('close', handleClose('incoming'));
217
+ targetSocket.on('close', handleClose('outgoing'));
218
+ socket.on('timeout', () => {
219
+ console.log(`Timeout on incoming side from ${remoteIP}`);
220
+ if (incomingTerminationReason === null) {
221
+ incomingTerminationReason = 'timeout';
222
+ this.incrementTerminationStat('incoming', 'timeout');
223
+ }
224
+ cleanupOnce();
225
+ });
226
+ targetSocket.on('timeout', () => {
227
+ console.log(`Timeout on outgoing side from ${remoteIP}`);
228
+ if (outgoingTerminationReason === null) {
229
+ outgoingTerminationReason = 'timeout';
230
+ this.incrementTerminationStat('outgoing', 'timeout');
231
+ }
232
+ cleanupOnce();
233
+ });
234
+ socket.on('end', handleClose('incoming'));
235
+ targetSocket.on('end', handleClose('outgoing'));
236
+ };
237
+ if (this.settings.sniEnabled) {
238
+ socket.setTimeout(5000, () => {
239
+ console.log(`Initial data timeout for ${remoteIP}`);
240
+ socket.end();
241
+ cleanupOnce();
242
+ });
243
+ socket.once('data', (chunk) => {
244
+ socket.setTimeout(0);
245
+ initialDataReceived = true;
246
+ const serverName = extractSNI(chunk) || '';
247
+ console.log(`Received connection from ${remoteIP} with SNI: ${serverName}`);
248
+ setupConnection(serverName, chunk);
249
+ });
250
+ }
251
+ else {
252
+ initialDataReceived = true;
253
+ if (!this.settings.defaultAllowedIPs || !isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
254
+ return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
255
+ }
256
+ setupConnection('');
257
+ }
258
+ })
259
+ .on('error', (err) => {
260
+ console.log(`Server Error: ${err.message}`);
261
+ })
262
+ .listen(this.settings.fromPort, () => {
263
+ console.log(`PortProxy -> OK: Now listening on port ${this.settings.fromPort}` +
264
+ `${this.settings.sniEnabled ? ' (SNI passthrough enabled)' : ''}`);
265
+ });
266
+ // Every 10 seconds log active connection count and longest running durations.
267
+ this.connectionLogger = setInterval(() => {
268
+ const now = Date.now();
269
+ let maxIncoming = 0;
270
+ let maxOutgoing = 0;
271
+ for (const record of this.connectionRecords) {
272
+ maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
273
+ if (record.outgoingStartTime) {
274
+ maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
275
+ }
276
+ }
277
+ console.log(`(Interval Log) Active connections: ${this.connectionRecords.size}. ` +
278
+ `Longest running incoming: ${plugins.prettyMs(maxIncoming)}, outgoing: ${plugins.prettyMs(maxOutgoing)}. ` +
279
+ `Termination stats (incoming): ${JSON.stringify(this.terminationStats.incoming)}, ` +
280
+ `(outgoing): ${JSON.stringify(this.terminationStats.outgoing)}`);
281
+ }, 10000);
282
+ }
283
+ async stop() {
284
+ const done = plugins.smartpromise.defer();
285
+ this.netServer.close(() => {
286
+ done.resolve();
287
+ });
288
+ if (this.connectionLogger) {
289
+ clearInterval(this.connectionLogger);
290
+ this.connectionLogger = null;
291
+ }
292
+ await done.promise;
293
+ }
294
+ }
295
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wb3J0cHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLnBvcnRwcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQWtCeEM7Ozs7R0FJRztBQUNILFNBQVMsVUFBVSxDQUFDLE1BQWM7SUFDaEMsSUFBSSxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBQ2YsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUV4QyxNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3ZDLElBQUksVUFBVSxLQUFLLEVBQUU7UUFBRSxPQUFPLFNBQVMsQ0FBQyxDQUFDLGlCQUFpQjtJQUUxRCxNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzVDLElBQUksTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLEdBQUcsWUFBWTtRQUFFLE9BQU8sU0FBUyxDQUFDO0lBRXZELE1BQU0sR0FBRyxDQUFDLENBQUM7SUFDWCxNQUFNLGFBQWEsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQy9DLElBQUksYUFBYSxLQUFLLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQyxDQUFDLGtCQUFrQjtJQUU3RCxNQUFNLElBQUksQ0FBQyxDQUFDLENBQUMsd0NBQXdDO0lBQ3JELE1BQU0sSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsaUNBQWlDO0lBRW5ELE1BQU0sZUFBZSxHQUFHLE1BQU0sQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDakQsTUFBTSxJQUFJLENBQUMsR0FBRyxlQUFlLENBQUMsQ0FBQyxrQkFBa0I7SUFFakQsTUFBTSxrQkFBa0IsR0FBRyxNQUFNLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FB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
@@ -0,0 +1,13 @@
1
+ import * as plugins from './plugins.js';
2
+ export declare class ProxyRouter {
3
+ reverseProxyConfigs: plugins.tsclass.network.IReverseProxyConfig[];
4
+ /**
5
+ * sets a new set of reverse configs to be routed to
6
+ * @param reverseCandidatesArg
7
+ */
8
+ setNewProxyConfigs(reverseCandidatesArg: plugins.tsclass.network.IReverseProxyConfig[]): void;
9
+ /**
10
+ * routes a request
11
+ */
12
+ routeReq(req: plugins.http.IncomingMessage): plugins.tsclass.network.IReverseProxyConfig;
13
+ }
@@ -0,0 +1,33 @@
1
+ import * as plugins from './plugins.js';
2
+ export class ProxyRouter {
3
+ constructor() {
4
+ this.reverseProxyConfigs = [];
5
+ }
6
+ /**
7
+ * sets a new set of reverse configs to be routed to
8
+ * @param reverseCandidatesArg
9
+ */
10
+ setNewProxyConfigs(reverseCandidatesArg) {
11
+ this.reverseProxyConfigs = reverseCandidatesArg;
12
+ }
13
+ /**
14
+ * routes a request
15
+ */
16
+ routeReq(req) {
17
+ const originalHost = req.headers.host;
18
+ if (!originalHost) {
19
+ console.error('No host header found in request');
20
+ return undefined;
21
+ }
22
+ // Strip port from host if present
23
+ const hostWithoutPort = originalHost.split(':')[0];
24
+ const correspodingReverseProxyConfig = this.reverseProxyConfigs.find((reverseConfig) => {
25
+ return reverseConfig.hostName === hostWithoutPort;
26
+ });
27
+ if (!correspodingReverseProxyConfig) {
28
+ console.error(`No config found for host: ${hostWithoutPort}`);
29
+ }
30
+ return correspodingReverseProxyConfig;
31
+ }
32
+ }
33
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yb3V0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLnJvdXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUV4QyxNQUFNLE9BQU8sV0FBVztJQUF4QjtRQUNTLHdCQUFtQixHQUFrRCxFQUFFLENBQUM7SUE2QmpGLENBQUM7SUEzQkM7OztPQUdHO0lBQ0ksa0JBQWtCLENBQUMsb0JBQW1FO1FBQzNGLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxvQkFBb0IsQ0FBQztJQUNsRCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxRQUFRLENBQUMsR0FBaUM7UUFDL0MsTUFBTSxZQUFZLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUM7UUFDdEMsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ2xCLE9BQU8sQ0FBQyxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztZQUNqRCxPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBQ0Qsa0NBQWtDO1FBQ2xDLE1BQU0sZUFBZSxHQUFHLFlBQVksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDbkQsTUFBTSw4QkFBOEIsR0FBRyxJQUFJLENBQUMsbUJBQW1CLENBQUMsSUFBSSxDQUFDLENBQUMsYUFBYSxFQUFFLEVBQUU7WUFDckYsT0FBTyxhQUFhLENBQUMsUUFBUSxLQUFLLGVBQWUsQ0FBQztRQUNwRCxDQUFDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyw4QkFBOEIsRUFBRSxDQUFDO1lBQ3BDLE9BQU8sQ0FBQyxLQUFLLENBQUMsNkJBQTZCLGVBQWUsRUFBRSxDQUFDLENBQUM7UUFDaEUsQ0FBQztRQUNELE9BQU8sOEJBQThCLENBQUM7SUFDeEMsQ0FBQztDQUNGIn0=
@@ -0,0 +1,8 @@
1
+ import * as plugins from './plugins.js';
2
+ export declare class SslRedirect {
3
+ httpServer: plugins.http.Server;
4
+ port: number;
5
+ constructor(portArg: number);
6
+ start(): Promise<void>;
7
+ stop(): Promise<void>;
8
+ }
@@ -0,0 +1,28 @@
1
+ import * as plugins from './plugins.js';
2
+ export class SslRedirect {
3
+ constructor(portArg) {
4
+ this.port = portArg;
5
+ }
6
+ async start() {
7
+ this.httpServer = plugins.http.createServer((request, response) => {
8
+ const requestUrl = new URL(request.url, `http://${request.headers.host}`);
9
+ const completeUrlWithoutProtocol = `${requestUrl.host}${requestUrl.pathname}${requestUrl.search}`;
10
+ const redirectUrl = `https://${completeUrlWithoutProtocol}`;
11
+ console.log(`Got http request for http://${completeUrlWithoutProtocol}`);
12
+ console.log(`Redirecting to ${redirectUrl}`);
13
+ response.writeHead(302, {
14
+ Location: redirectUrl,
15
+ });
16
+ response.end();
17
+ });
18
+ this.httpServer.listen(this.port);
19
+ }
20
+ async stop() {
21
+ const done = plugins.smartpromise.defer();
22
+ this.httpServer.close(() => {
23
+ done.resolve();
24
+ });
25
+ await done.promise;
26
+ }
27
+ }
28
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zc2xyZWRpcmVjdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMuc3NscmVkaXJlY3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFFeEMsTUFBTSxPQUFPLFdBQVc7SUFHdEIsWUFBWSxPQUFlO1FBQ3pCLElBQUksQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDO0lBQ3RCLENBQUM7SUFFTSxLQUFLLENBQUMsS0FBSztRQUNoQixJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxFQUFFO1lBQ2hFLE1BQU0sVUFBVSxHQUFHLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsVUFBVSxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7WUFDMUUsTUFBTSwwQkFBMEIsR0FBRyxHQUFHLFVBQVUsQ0FBQyxJQUFJLEdBQUcsVUFBVSxDQUFDLFFBQVEsR0FBRyxVQUFVLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDbEcsTUFBTSxXQUFXLEdBQUcsV0FBVywwQkFBMEIsRUFBRSxDQUFDO1lBQzVELE9BQU8sQ0FBQyxHQUFHLENBQUMsK0JBQStCLDBCQUEwQixFQUFFLENBQUMsQ0FBQztZQUN6RSxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixXQUFXLEVBQUUsQ0FBQyxDQUFDO1lBQzdDLFFBQVEsQ0FBQyxTQUFTLENBQUMsR0FBRyxFQUFFO2dCQUN0QixRQUFRLEVBQUUsV0FBVzthQUN0QixDQUFDLENBQUM7WUFDSCxRQUFRLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDakIsQ0FBQyxDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDcEMsQ0FBQztJQUVNLEtBQUssQ0FBQyxJQUFJO1FBQ2YsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMxQyxJQUFJLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUU7WUFDekIsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ2pCLENBQUMsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDO0lBQ3JCLENBQUM7Q0FDRiJ9
@@ -0,0 +1,5 @@
1
+ export interface ICertificates {
2
+ privateKey: string;
3
+ publicKey: string;
4
+ }
5
+ export declare function loadDefaultCertificates(): ICertificates;
@@ -0,0 +1,23 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ export function loadDefaultCertificates() {
6
+ try {
7
+ const certPath = path.join(__dirname, '..', 'assets', 'certs');
8
+ const privateKey = fs.readFileSync(path.join(certPath, 'key.pem'), 'utf8');
9
+ const publicKey = fs.readFileSync(path.join(certPath, 'cert.pem'), 'utf8');
10
+ if (!privateKey || !publicKey) {
11
+ throw new Error('Failed to load default certificates');
12
+ }
13
+ return {
14
+ privateKey,
15
+ publicKey
16
+ };
17
+ }
18
+ catch (error) {
19
+ console.error('Error loading default certificates:', error);
20
+ throw error;
21
+ }
22
+ }
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVycy5jZXJ0aWZpY2F0ZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9oZWxwZXJzLmNlcnRpZmljYXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLElBQUksQ0FBQztBQUN6QixPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUM3QixPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBRXBDLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztBQU8vRCxNQUFNLFVBQVUsdUJBQXVCO0lBQ3JDLElBQUksQ0FBQztRQUNILE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDL0QsTUFBTSxVQUFVLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxTQUFTLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUMzRSxNQUFNLFNBQVMsR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBRTNFLElBQUksQ0FBQyxVQUFVLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUM5QixNQUFNLElBQUksS0FBSyxDQUFDLHFDQUFxQyxDQUFDLENBQUM7UUFDekQsQ0FBQztRQUVELE9BQU87WUFDTCxVQUFVO1lBQ1YsU0FBUztTQUNWLENBQUM7SUFDSixDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLE9BQU8sQ0FBQyxLQUFLLENBQUMscUNBQXFDLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDNUQsTUFBTSxLQUFLLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQyJ9
@@ -1,3 +1,4 @@
1
- export * from './smartproxy.classes.networkproxy.js';
2
- export * from './smartproxy.portproxy.js';
3
- export * from './smartproxy.classes.sslredirect.js';
1
+ export * from './classes.networkproxy.js';
2
+ export * from './classes.portproxy.js';
3
+ export * from './classes.port80handler.js';
4
+ export * from './classes.sslredirect.js';
package/dist_ts/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export * from './smartproxy.classes.networkproxy.js';
2
- export * from './smartproxy.portproxy.js';
3
- export * from './smartproxy.classes.sslredirect.js';
4
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHNDQUFzQyxDQUFDO0FBQ3JELGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyxxQ0FBcUMsQ0FBQyJ9
1
+ export * from './classes.networkproxy.js';
2
+ export * from './classes.portproxy.js';
3
+ export * from './classes.port80handler.js';
4
+ export * from './classes.sslredirect.js';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyw0QkFBNEIsQ0FBQztBQUMzQyxjQUFjLDBCQUEwQixDQUFDIn0=
@@ -16,9 +16,7 @@ export interface IProxySettings extends plugins.tls.TlsOptions {
16
16
  export declare class PortProxy {
17
17
  netServer: plugins.net.Server;
18
18
  settings: IProxySettings;
19
- private activeConnections;
20
- private incomingConnectionTimes;
21
- private outgoingConnectionTimes;
19
+ private connectionRecords;
22
20
  private connectionLogger;
23
21
  private terminationStats;
24
22
  constructor(settings: IProxySettings);