@push.rocks/smartproxy 10.2.0 → 12.0.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 +1 -1
- package/dist_ts/common/port80-adapter.d.ts +11 -0
- package/dist_ts/common/port80-adapter.js +61 -0
- package/dist_ts/examples/forwarding-example.d.ts +1 -0
- package/dist_ts/examples/forwarding-example.js +96 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/smartproxy/classes.pp.connectionhandler.js +179 -30
- package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +39 -0
- package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +172 -20
- package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +3 -11
- package/dist_ts/smartproxy/classes.pp.portrangemanager.js +17 -10
- package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +19 -2
- package/dist_ts/smartproxy/classes.pp.securitymanager.js +27 -4
- package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +3 -3
- package/dist_ts/smartproxy/classes.smartproxy.js +45 -13
- package/dist_ts/smartproxy/forwarding/domain-config.d.ts +12 -0
- package/dist_ts/smartproxy/forwarding/domain-config.js +12 -0
- package/dist_ts/smartproxy/forwarding/domain-manager.d.ts +86 -0
- package/dist_ts/smartproxy/forwarding/domain-manager.js +241 -0
- package/dist_ts/smartproxy/forwarding/forwarding.factory.d.ts +24 -0
- package/dist_ts/smartproxy/forwarding/forwarding.factory.js +137 -0
- package/dist_ts/smartproxy/forwarding/forwarding.handler.d.ts +55 -0
- package/dist_ts/smartproxy/forwarding/forwarding.handler.js +94 -0
- package/dist_ts/smartproxy/forwarding/http.handler.d.ts +25 -0
- package/dist_ts/smartproxy/forwarding/http.handler.js +123 -0
- package/dist_ts/smartproxy/forwarding/https-passthrough.handler.d.ts +24 -0
- package/dist_ts/smartproxy/forwarding/https-passthrough.handler.js +154 -0
- package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.d.ts +36 -0
- package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.js +229 -0
- package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.d.ts +35 -0
- package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.js +254 -0
- package/dist_ts/smartproxy/forwarding/index.d.ts +16 -0
- package/dist_ts/smartproxy/forwarding/index.js +23 -0
- package/dist_ts/smartproxy/types/forwarding.types.d.ts +104 -0
- package/dist_ts/smartproxy/types/forwarding.types.js +50 -0
- package/package.json +2 -2
- package/readme.md +158 -8
- package/readme.plan.md +471 -42
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/common/port80-adapter.ts +87 -0
- package/ts/index.ts +3 -0
- package/ts/smartproxy/classes.pp.connectionhandler.ts +231 -44
- package/ts/smartproxy/classes.pp.domainconfigmanager.ts +198 -24
- package/ts/smartproxy/classes.pp.interfaces.ts +3 -11
- package/ts/smartproxy/classes.pp.portrangemanager.ts +17 -10
- package/ts/smartproxy/classes.pp.securitymanager.ts +29 -5
- package/ts/smartproxy/classes.pp.timeoutmanager.ts +3 -3
- package/ts/smartproxy/classes.smartproxy.ts +68 -15
- package/ts/smartproxy/forwarding/domain-config.ts +28 -0
- package/ts/smartproxy/forwarding/domain-manager.ts +283 -0
- package/ts/smartproxy/forwarding/forwarding.factory.ts +155 -0
- package/ts/smartproxy/forwarding/forwarding.handler.ts +127 -0
- package/ts/smartproxy/forwarding/http.handler.ts +140 -0
- package/ts/smartproxy/forwarding/https-passthrough.handler.ts +182 -0
- package/ts/smartproxy/forwarding/https-terminate-to-http.handler.ts +264 -0
- package/ts/smartproxy/forwarding/https-terminate-to-https.handler.ts +292 -0
- package/ts/smartproxy/forwarding/index.ts +52 -0
- package/ts/smartproxy/types/forwarding.types.ts +162 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { HttpForwardingHandler } from './http.handler.js';
|
|
2
|
+
import { HttpsPassthroughHandler } from './https-passthrough.handler.js';
|
|
3
|
+
import { HttpsTerminateToHttpHandler } from './https-terminate-to-http.handler.js';
|
|
4
|
+
import { HttpsTerminateToHttpsHandler } from './https-terminate-to-https.handler.js';
|
|
5
|
+
/**
|
|
6
|
+
* Factory for creating forwarding handlers based on the configuration type
|
|
7
|
+
*/
|
|
8
|
+
export class ForwardingHandlerFactory {
|
|
9
|
+
/**
|
|
10
|
+
* Create a forwarding handler based on the configuration
|
|
11
|
+
* @param config The forwarding configuration
|
|
12
|
+
* @returns The appropriate forwarding handler
|
|
13
|
+
*/
|
|
14
|
+
static createHandler(config) {
|
|
15
|
+
// Create the appropriate handler based on the forwarding type
|
|
16
|
+
switch (config.type) {
|
|
17
|
+
case 'http-only':
|
|
18
|
+
return new HttpForwardingHandler(config);
|
|
19
|
+
case 'https-passthrough':
|
|
20
|
+
return new HttpsPassthroughHandler(config);
|
|
21
|
+
case 'https-terminate-to-http':
|
|
22
|
+
return new HttpsTerminateToHttpHandler(config);
|
|
23
|
+
case 'https-terminate-to-https':
|
|
24
|
+
return new HttpsTerminateToHttpsHandler(config);
|
|
25
|
+
default:
|
|
26
|
+
// Type system should prevent this, but just in case:
|
|
27
|
+
throw new Error(`Unknown forwarding type: ${config.type}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Apply default values to a forwarding configuration based on its type
|
|
32
|
+
* @param config The original forwarding configuration
|
|
33
|
+
* @returns A configuration with defaults applied
|
|
34
|
+
*/
|
|
35
|
+
static applyDefaults(config) {
|
|
36
|
+
// Create a deep copy of the configuration
|
|
37
|
+
const result = JSON.parse(JSON.stringify(config));
|
|
38
|
+
// Apply defaults based on forwarding type
|
|
39
|
+
switch (config.type) {
|
|
40
|
+
case 'http-only':
|
|
41
|
+
// Set defaults for HTTP-only mode
|
|
42
|
+
result.http = {
|
|
43
|
+
enabled: true,
|
|
44
|
+
...config.http
|
|
45
|
+
};
|
|
46
|
+
break;
|
|
47
|
+
case 'https-passthrough':
|
|
48
|
+
// Set defaults for HTTPS passthrough
|
|
49
|
+
result.https = {
|
|
50
|
+
forwardSni: true,
|
|
51
|
+
...config.https
|
|
52
|
+
};
|
|
53
|
+
// SNI forwarding doesn't do HTTP
|
|
54
|
+
result.http = {
|
|
55
|
+
enabled: false,
|
|
56
|
+
...config.http
|
|
57
|
+
};
|
|
58
|
+
break;
|
|
59
|
+
case 'https-terminate-to-http':
|
|
60
|
+
// Set defaults for HTTPS termination to HTTP
|
|
61
|
+
result.https = {
|
|
62
|
+
...config.https
|
|
63
|
+
};
|
|
64
|
+
// Support HTTP access by default in this mode
|
|
65
|
+
result.http = {
|
|
66
|
+
enabled: true,
|
|
67
|
+
redirectToHttps: true,
|
|
68
|
+
...config.http
|
|
69
|
+
};
|
|
70
|
+
// Enable ACME by default
|
|
71
|
+
result.acme = {
|
|
72
|
+
enabled: true,
|
|
73
|
+
maintenance: true,
|
|
74
|
+
...config.acme
|
|
75
|
+
};
|
|
76
|
+
break;
|
|
77
|
+
case 'https-terminate-to-https':
|
|
78
|
+
// Similar to terminate-to-http but with different target handling
|
|
79
|
+
result.https = {
|
|
80
|
+
...config.https
|
|
81
|
+
};
|
|
82
|
+
result.http = {
|
|
83
|
+
enabled: true,
|
|
84
|
+
redirectToHttps: true,
|
|
85
|
+
...config.http
|
|
86
|
+
};
|
|
87
|
+
result.acme = {
|
|
88
|
+
enabled: true,
|
|
89
|
+
maintenance: true,
|
|
90
|
+
...config.acme
|
|
91
|
+
};
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Validate a forwarding configuration
|
|
98
|
+
* @param config The configuration to validate
|
|
99
|
+
* @throws Error if the configuration is invalid
|
|
100
|
+
*/
|
|
101
|
+
static validateConfig(config) {
|
|
102
|
+
// Validate common properties
|
|
103
|
+
if (!config.target) {
|
|
104
|
+
throw new Error('Forwarding configuration must include a target');
|
|
105
|
+
}
|
|
106
|
+
if (!config.target.host || (Array.isArray(config.target.host) && config.target.host.length === 0)) {
|
|
107
|
+
throw new Error('Target must include a host or array of hosts');
|
|
108
|
+
}
|
|
109
|
+
if (!config.target.port || config.target.port <= 0 || config.target.port > 65535) {
|
|
110
|
+
throw new Error('Target must include a valid port (1-65535)');
|
|
111
|
+
}
|
|
112
|
+
// Type-specific validation
|
|
113
|
+
switch (config.type) {
|
|
114
|
+
case 'http-only':
|
|
115
|
+
// HTTP-only needs http.enabled to be true
|
|
116
|
+
if (config.http?.enabled === false) {
|
|
117
|
+
throw new Error('HTTP-only forwarding must have HTTP enabled');
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
case 'https-passthrough':
|
|
121
|
+
// HTTPS passthrough doesn't support HTTP
|
|
122
|
+
if (config.http?.enabled === true) {
|
|
123
|
+
throw new Error('HTTPS passthrough does not support HTTP');
|
|
124
|
+
}
|
|
125
|
+
// HTTPS passthrough doesn't work with ACME
|
|
126
|
+
if (config.acme?.enabled === true) {
|
|
127
|
+
throw new Error('HTTPS passthrough does not support ACME');
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
case 'https-terminate-to-http':
|
|
131
|
+
case 'https-terminate-to-https':
|
|
132
|
+
// These modes support all options, nothing specific to validate
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yd2FyZGluZy5mYWN0b3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvc21hcnRwcm94eS9mb3J3YXJkaW5nL2ZvcndhcmRpbmcuZmFjdG9yeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUMxRCxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUN6RSxPQUFPLEVBQUUsMkJBQTJCLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQUNuRixPQUFPLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSx1Q0FBdUMsQ0FBQztBQUVyRjs7R0FFRztBQUNILE1BQU0sT0FBTyx3QkFBd0I7SUFDbkM7Ozs7T0FJRztJQUNJLE1BQU0sQ0FBQyxhQUFhLENBQUMsTUFBc0I7UUFDaEQsOERBQThEO1FBQzlELFFBQVEsTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ3BCLEtBQUssV0FBVztnQkFDZCxPQUFPLElBQUkscUJBQXFCLENBQUMsTUFBTSxDQUFDLENBQUM7WUFFM0MsS0FBSyxtQkFBbUI7Z0JBQ3RCLE9BQU8sSUFBSSx1QkFBdUIsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUU3QyxLQUFLLHlCQUF5QjtnQkFDNUIsT0FBTyxJQUFJLDJCQUEyQixDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBRWpELEtBQUssMEJBQTBCO2dCQUM3QixPQUFPLElBQUksNEJBQTRCLENBQUMsTUFBTSxDQUFDLENBQUM7WUFFbEQ7Z0JBQ0UscURBQXFEO2dCQUNyRCxNQUFNLElBQUksS0FBSyxDQUFDLDRCQUE2QixNQUFjLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUN4RSxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxNQUFNLENBQUMsYUFBYSxDQUFDLE1BQXNCO1FBQ2hELDBDQUEwQztRQUMxQyxNQUFNLE1BQU0sR0FBbUIsSUFBSSxDQUFDLEtB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
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import type { IForwardConfig, IForwardingHandler } from '../types/forwarding.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Base class for all forwarding handlers
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class ForwardingHandler extends plugins.EventEmitter implements IForwardingHandler {
|
|
7
|
+
protected config: IForwardConfig;
|
|
8
|
+
/**
|
|
9
|
+
* Create a new ForwardingHandler
|
|
10
|
+
* @param config The forwarding configuration
|
|
11
|
+
*/
|
|
12
|
+
constructor(config: IForwardConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Initialize the handler
|
|
15
|
+
* Base implementation does nothing, subclasses should override as needed
|
|
16
|
+
*/
|
|
17
|
+
initialize(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Handle a new socket connection
|
|
20
|
+
* @param socket The incoming socket connection
|
|
21
|
+
*/
|
|
22
|
+
abstract handleConnection(socket: plugins.net.Socket): void;
|
|
23
|
+
/**
|
|
24
|
+
* Handle an HTTP request
|
|
25
|
+
* @param req The HTTP request
|
|
26
|
+
* @param res The HTTP response
|
|
27
|
+
*/
|
|
28
|
+
abstract handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void;
|
|
29
|
+
/**
|
|
30
|
+
* Get a target from the configuration, supporting round-robin selection
|
|
31
|
+
* @returns A resolved target object with host and port
|
|
32
|
+
*/
|
|
33
|
+
protected getTargetFromConfig(): {
|
|
34
|
+
host: string;
|
|
35
|
+
port: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Redirect an HTTP request to HTTPS
|
|
39
|
+
* @param req The HTTP request
|
|
40
|
+
* @param res The HTTP response
|
|
41
|
+
*/
|
|
42
|
+
protected redirectToHttps(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void;
|
|
43
|
+
/**
|
|
44
|
+
* Apply custom headers from configuration
|
|
45
|
+
* @param headers The original headers
|
|
46
|
+
* @param variables Variables to replace in the headers
|
|
47
|
+
* @returns The headers with custom values applied
|
|
48
|
+
*/
|
|
49
|
+
protected applyCustomHeaders(headers: Record<string, string | string[] | undefined>, variables: Record<string, string>): Record<string, string | string[] | undefined>;
|
|
50
|
+
/**
|
|
51
|
+
* Get the timeout for this connection from configuration
|
|
52
|
+
* @returns Timeout in milliseconds
|
|
53
|
+
*/
|
|
54
|
+
protected getTimeout(): number;
|
|
55
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { ForwardingHandlerEvents } from '../types/forwarding.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Base class for all forwarding handlers
|
|
5
|
+
*/
|
|
6
|
+
export class ForwardingHandler extends plugins.EventEmitter {
|
|
7
|
+
/**
|
|
8
|
+
* Create a new ForwardingHandler
|
|
9
|
+
* @param config The forwarding configuration
|
|
10
|
+
*/
|
|
11
|
+
constructor(config) {
|
|
12
|
+
super();
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the handler
|
|
17
|
+
* Base implementation does nothing, subclasses should override as needed
|
|
18
|
+
*/
|
|
19
|
+
async initialize() {
|
|
20
|
+
// Base implementation - no initialization needed
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get a target from the configuration, supporting round-robin selection
|
|
24
|
+
* @returns A resolved target object with host and port
|
|
25
|
+
*/
|
|
26
|
+
getTargetFromConfig() {
|
|
27
|
+
const { target } = this.config;
|
|
28
|
+
// Handle round-robin host selection
|
|
29
|
+
if (Array.isArray(target.host)) {
|
|
30
|
+
if (target.host.length === 0) {
|
|
31
|
+
throw new Error('No target hosts specified');
|
|
32
|
+
}
|
|
33
|
+
// Simple round-robin selection
|
|
34
|
+
const randomIndex = Math.floor(Math.random() * target.host.length);
|
|
35
|
+
return {
|
|
36
|
+
host: target.host[randomIndex],
|
|
37
|
+
port: target.port
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Single host
|
|
41
|
+
return {
|
|
42
|
+
host: target.host,
|
|
43
|
+
port: target.port
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Redirect an HTTP request to HTTPS
|
|
48
|
+
* @param req The HTTP request
|
|
49
|
+
* @param res The HTTP response
|
|
50
|
+
*/
|
|
51
|
+
redirectToHttps(req, res) {
|
|
52
|
+
const host = req.headers.host || '';
|
|
53
|
+
const path = req.url || '/';
|
|
54
|
+
const redirectUrl = `https://${host}${path}`;
|
|
55
|
+
res.writeHead(301, {
|
|
56
|
+
'Location': redirectUrl,
|
|
57
|
+
'Cache-Control': 'no-cache'
|
|
58
|
+
});
|
|
59
|
+
res.end(`Redirecting to ${redirectUrl}`);
|
|
60
|
+
this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, {
|
|
61
|
+
statusCode: 301,
|
|
62
|
+
headers: { 'Location': redirectUrl },
|
|
63
|
+
size: 0
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Apply custom headers from configuration
|
|
68
|
+
* @param headers The original headers
|
|
69
|
+
* @param variables Variables to replace in the headers
|
|
70
|
+
* @returns The headers with custom values applied
|
|
71
|
+
*/
|
|
72
|
+
applyCustomHeaders(headers, variables) {
|
|
73
|
+
const customHeaders = this.config.advanced?.headers || {};
|
|
74
|
+
const result = { ...headers };
|
|
75
|
+
// Apply custom headers with variable substitution
|
|
76
|
+
for (const [key, value] of Object.entries(customHeaders)) {
|
|
77
|
+
let processedValue = value;
|
|
78
|
+
// Replace variables in the header value
|
|
79
|
+
for (const [varName, varValue] of Object.entries(variables)) {
|
|
80
|
+
processedValue = processedValue.replace(`{${varName}}`, varValue);
|
|
81
|
+
}
|
|
82
|
+
result[key] = processedValue;
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get the timeout for this connection from configuration
|
|
88
|
+
* @returns Timeout in milliseconds
|
|
89
|
+
*/
|
|
90
|
+
getTimeout() {
|
|
91
|
+
return this.config.advanced?.timeout || 60000; // Default: 60 seconds
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yd2FyZGluZy5oYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvc21hcnRwcm94eS9mb3J3YXJkaW5nL2ZvcndhcmRpbmcuaGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBSzVDLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRXZFOztHQUVHO0FBQ0gsTUFBTSxPQUFnQixpQkFBa0IsU0FBUSxPQUFPLENBQUMsWUFBWTtJQUNsRTs7O09BR0c7SUFDSCxZQUFzQixNQUFzQjtRQUMxQyxLQUFLLEVBQUUsQ0FBQztRQURZLFdBQU0sR0FBTixNQUFNLENBQWdCO0lBRTVDLENBQUM7SUFFRDs7O09BR0c7SUFDSSxLQUFLLENBQUMsVUFBVTtRQUNyQixpREFBaUQ7SUFDbkQsQ0FBQztJQWVEOzs7T0FHRztJQUNPLG1CQUFtQjtRQUMzQixNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUUvQixvQ0FBb0M7UUFDcEMsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQy9CLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQzdCLE1BQU0sSUFBSSxLQUFLLENBQUMsMkJBQTJCLENBQUMsQ0FBQztZQUMvQyxDQUFDO1lBRUQsK0JBQStCO1lBQy9CLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDbkUsT0FBTztnQkFDTCxJQUFJLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUM7Z0JBQzlCLElBQUksRUFBRSxNQUFNLENBQUMsSUFBSTthQUNsQixDQUFDO1FBQ0osQ0FBQztRQUVELGNBQWM7UUFDZCxPQUFPO1lBQ0wsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJO1lBQ2pCLElBQUksRUFBRSxNQUFNLENBQUMsSUFBSTtTQUNsQixDQUFDO0lBQ0osQ0FBQztJQUVEOzs7O09BSUc7SUFDTyxlQUFlLENBQUMsR0FBaUMsRUFBRSxHQUFnQztRQUMzRixNQUFNLElBQUksR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7UUFDcEMsTUFBTSxJQUFJLEdBQUcsR0FBRyxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUM7UUFDNUIsTUFBTSxXQUFXLEdBQUcsV0FBVyxJQUFJLEdBQUcsSUFBSSxFQUFFLENBQUM7UUFFN0MsR0FBRyxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUU7WUFDakIsVUFBVSxFQUFFLFdBQVc7WUFDdkIsZUFBZSxFQUFFLFVBQVU7U0FDNUIsQ0FBQyxDQUFDO1FBQ0gsR0FBRyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsV0FBVyxFQUFFLENBQUMsQ0FBQztRQUV6QyxJQUFJLENBQUMsSUFBSSxDQUFDLHVCQUF1QixDQUFDLGFBQWEsRUFBRTtZQUMvQyxVQUFVLEVBQUUsR0FBRztZQUNmLE9BQU8sRUFBRSxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUU7WUFDcEMsSUFBSSxFQUFFLENBQUM7U0FDUixDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDTyxrQkFBa0IsQ0FDMUIsT0FBc0QsRUFDdEQsU0FBaUM7UUFFakMsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLEVBQUUsT0FBTyxJQUFJLEVBQUUsQ0FBQztRQUMxRCxNQUFNLE1BQU0sR0FBRyxFQUFFLEdBQUcsT0FBTyxFQUFFLENBQUM7UUFFOUIsa0RBQWtEO1FBQ2xELEtBQUssTUFBTSxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQUM7WUFDekQsSUFBSSxjQUFjLEdBQUcsS0FBSyxDQUFDO1lBRTNCLHdDQUF3QztZQUN4QyxLQUFLLE1BQU0sQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO2dCQUM1RCxjQUFjLEdBQUcsY0FBYyxDQUFDLE9BQU8sQ0FBQyxJQUFJLE9BQU8sR0FBRyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1lBQ3BFLENBQUM7WUFFRCxNQUFNLENBQUMsR0FBRyxDQUFDLEdBQUcsY0FBYyxDQUFDO1FBQy9CLENBQUM7UUFFRCxPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDO0lBRUQ7OztPQUdHO0lBQ08sVUFBVTtRQUNsQixPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFLE9BQU8sSUFBSSxLQUFLLENBQUMsQ0FBQyxzQkFBc0I7SUFDdkUsQ0FBQztDQUNGIn0=
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { ForwardingHandler } from './forwarding.handler.js';
|
|
3
|
+
import type { IForwardConfig } from '../types/forwarding.types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handler for HTTP-only forwarding
|
|
6
|
+
*/
|
|
7
|
+
export declare class HttpForwardingHandler extends ForwardingHandler {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new HTTP forwarding handler
|
|
10
|
+
* @param config The forwarding configuration
|
|
11
|
+
*/
|
|
12
|
+
constructor(config: IForwardConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Handle a raw socket connection
|
|
15
|
+
* HTTP handler doesn't do much with raw sockets as it mainly processes
|
|
16
|
+
* parsed HTTP requests
|
|
17
|
+
*/
|
|
18
|
+
handleConnection(socket: plugins.net.Socket): void;
|
|
19
|
+
/**
|
|
20
|
+
* Handle an HTTP request
|
|
21
|
+
* @param req The HTTP request
|
|
22
|
+
* @param res The HTTP response
|
|
23
|
+
*/
|
|
24
|
+
handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { ForwardingHandler } from './forwarding.handler.js';
|
|
3
|
+
import { ForwardingHandlerEvents } from '../types/forwarding.types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handler for HTTP-only forwarding
|
|
6
|
+
*/
|
|
7
|
+
export class HttpForwardingHandler extends ForwardingHandler {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new HTTP forwarding handler
|
|
10
|
+
* @param config The forwarding configuration
|
|
11
|
+
*/
|
|
12
|
+
constructor(config) {
|
|
13
|
+
super(config);
|
|
14
|
+
// Validate that this is an HTTP-only configuration
|
|
15
|
+
if (config.type !== 'http-only') {
|
|
16
|
+
throw new Error(`Invalid configuration type for HttpForwardingHandler: ${config.type}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Handle a raw socket connection
|
|
21
|
+
* HTTP handler doesn't do much with raw sockets as it mainly processes
|
|
22
|
+
* parsed HTTP requests
|
|
23
|
+
*/
|
|
24
|
+
handleConnection(socket) {
|
|
25
|
+
// For HTTP, we mainly handle parsed requests, but we can still set up
|
|
26
|
+
// some basic connection tracking
|
|
27
|
+
const remoteAddress = socket.remoteAddress || 'unknown';
|
|
28
|
+
socket.on('close', (hadError) => {
|
|
29
|
+
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
|
30
|
+
remoteAddress,
|
|
31
|
+
hadError
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
socket.on('error', (error) => {
|
|
35
|
+
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
36
|
+
remoteAddress,
|
|
37
|
+
error: error.message
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
this.emit(ForwardingHandlerEvents.CONNECTED, {
|
|
41
|
+
remoteAddress
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Handle an HTTP request
|
|
46
|
+
* @param req The HTTP request
|
|
47
|
+
* @param res The HTTP response
|
|
48
|
+
*/
|
|
49
|
+
handleHttpRequest(req, res) {
|
|
50
|
+
// Get the target from configuration
|
|
51
|
+
const target = this.getTargetFromConfig();
|
|
52
|
+
// Create a custom headers object with variables for substitution
|
|
53
|
+
const variables = {
|
|
54
|
+
clientIp: req.socket.remoteAddress || 'unknown'
|
|
55
|
+
};
|
|
56
|
+
// Prepare headers, merging with any custom headers from config
|
|
57
|
+
const headers = this.applyCustomHeaders(req.headers, variables);
|
|
58
|
+
// Create the proxy request options
|
|
59
|
+
const options = {
|
|
60
|
+
hostname: target.host,
|
|
61
|
+
port: target.port,
|
|
62
|
+
path: req.url,
|
|
63
|
+
method: req.method,
|
|
64
|
+
headers
|
|
65
|
+
};
|
|
66
|
+
// Create the proxy request
|
|
67
|
+
const proxyReq = plugins.http.request(options, (proxyRes) => {
|
|
68
|
+
// Copy status code and headers from the proxied response
|
|
69
|
+
res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);
|
|
70
|
+
// Pipe the proxy response to the client response
|
|
71
|
+
proxyRes.pipe(res);
|
|
72
|
+
// Track bytes for logging
|
|
73
|
+
let responseSize = 0;
|
|
74
|
+
proxyRes.on('data', (chunk) => {
|
|
75
|
+
responseSize += chunk.length;
|
|
76
|
+
});
|
|
77
|
+
proxyRes.on('end', () => {
|
|
78
|
+
this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, {
|
|
79
|
+
statusCode: proxyRes.statusCode,
|
|
80
|
+
headers: proxyRes.headers,
|
|
81
|
+
size: responseSize
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
// Handle errors in the proxy request
|
|
86
|
+
proxyReq.on('error', (error) => {
|
|
87
|
+
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
88
|
+
remoteAddress: req.socket.remoteAddress,
|
|
89
|
+
error: `Proxy request error: ${error.message}`
|
|
90
|
+
});
|
|
91
|
+
// Send an error response if headers haven't been sent yet
|
|
92
|
+
if (!res.headersSent) {
|
|
93
|
+
res.writeHead(502, { 'Content-Type': 'text/plain' });
|
|
94
|
+
res.end(`Error forwarding request: ${error.message}`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Just end the response if headers have already been sent
|
|
98
|
+
res.end();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// Track request details for logging
|
|
102
|
+
let requestSize = 0;
|
|
103
|
+
req.on('data', (chunk) => {
|
|
104
|
+
requestSize += chunk.length;
|
|
105
|
+
});
|
|
106
|
+
// Log the request
|
|
107
|
+
this.emit(ForwardingHandlerEvents.HTTP_REQUEST, {
|
|
108
|
+
method: req.method,
|
|
109
|
+
url: req.url,
|
|
110
|
+
headers: req.headers,
|
|
111
|
+
remoteAddress: req.socket.remoteAddress,
|
|
112
|
+
target: `${target.host}:${target.port}`
|
|
113
|
+
});
|
|
114
|
+
// Pipe the client request to the proxy request
|
|
115
|
+
if (req.readable) {
|
|
116
|
+
req.pipe(proxyReq);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
proxyReq.end();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC5oYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvc21hcnRwcm94eS9mb3J3YXJkaW5nL2h0dHAuaGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRTVELE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRXZFOztHQUVHO0FBQ0gsTUFBTSxPQUFPLHFCQUFzQixTQUFRLGlCQUFpQjtJQUMxRDs7O09BR0c7SUFDSCxZQUFZLE1BQXNCO1FBQ2hDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUVkLG1EQUFtRDtRQUNuRCxJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssV0FBVyxFQUFFLENBQUM7WUFDaEMsTUFBTSxJQUFJLEtBQUssQ0FBQyx5REFBeUQsTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7UUFDMUYsQ0FBQztJQUNILENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksZ0JBQWdCLENBQUMsTUFBMEI7UUFDaEQsc0VBQXNFO1FBQ3RFLGlDQUFpQztRQUNqQyxNQUFNLGFBQWEsR0FBRyxNQUFNLENBQUMsYUFBYSxJQUFJLFNBQVMsQ0FBQztRQUV4RCxNQUFNLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDLFFBQVEsRUFBRSxFQUFFO1lBQzlCLElBQUksQ0FBQyxJQUFJLENBQUMsdUJBQXVCLENBQUMsWUFBWSxFQUFFO2dCQUM5QyxhQUFhO2dCQUNiLFFBQVE7YUFDVCxDQUFDLENBQUM7UUFDTCxDQUFDLENBQUMsQ0FBQztRQUVILE1BQU0sQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUU7WUFDM0IsSUFBSSxDQUFDLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxLQUFLLEVBQUU7Z0JBQ3ZD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
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { ForwardingHandler } from './forwarding.handler.js';
|
|
3
|
+
import type { IForwardConfig } from '../types/forwarding.types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handler for HTTPS passthrough (SNI forwarding without termination)
|
|
6
|
+
*/
|
|
7
|
+
export declare class HttpsPassthroughHandler extends ForwardingHandler {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new HTTPS passthrough handler
|
|
10
|
+
* @param config The forwarding configuration
|
|
11
|
+
*/
|
|
12
|
+
constructor(config: IForwardConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Handle a TLS/SSL socket connection by forwarding it without termination
|
|
15
|
+
* @param clientSocket The incoming socket from the client
|
|
16
|
+
*/
|
|
17
|
+
handleConnection(clientSocket: plugins.net.Socket): void;
|
|
18
|
+
/**
|
|
19
|
+
* Handle an HTTP request - HTTPS passthrough doesn't support HTTP
|
|
20
|
+
* @param req The HTTP request
|
|
21
|
+
* @param res The HTTP response
|
|
22
|
+
*/
|
|
23
|
+
handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { ForwardingHandler } from './forwarding.handler.js';
|
|
3
|
+
import { ForwardingHandlerEvents } from '../types/forwarding.types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handler for HTTPS passthrough (SNI forwarding without termination)
|
|
6
|
+
*/
|
|
7
|
+
export class HttpsPassthroughHandler extends ForwardingHandler {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new HTTPS passthrough handler
|
|
10
|
+
* @param config The forwarding configuration
|
|
11
|
+
*/
|
|
12
|
+
constructor(config) {
|
|
13
|
+
super(config);
|
|
14
|
+
// Validate that this is an HTTPS passthrough configuration
|
|
15
|
+
if (config.type !== 'https-passthrough') {
|
|
16
|
+
throw new Error(`Invalid configuration type for HttpsPassthroughHandler: ${config.type}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Handle a TLS/SSL socket connection by forwarding it without termination
|
|
21
|
+
* @param clientSocket The incoming socket from the client
|
|
22
|
+
*/
|
|
23
|
+
handleConnection(clientSocket) {
|
|
24
|
+
// Get the target from configuration
|
|
25
|
+
const target = this.getTargetFromConfig();
|
|
26
|
+
// Log the connection
|
|
27
|
+
const remoteAddress = clientSocket.remoteAddress || 'unknown';
|
|
28
|
+
const remotePort = clientSocket.remotePort || 0;
|
|
29
|
+
this.emit(ForwardingHandlerEvents.CONNECTED, {
|
|
30
|
+
remoteAddress,
|
|
31
|
+
remotePort,
|
|
32
|
+
target: `${target.host}:${target.port}`
|
|
33
|
+
});
|
|
34
|
+
// Create a connection to the target server
|
|
35
|
+
const serverSocket = plugins.net.connect(target.port, target.host);
|
|
36
|
+
// Handle errors on the server socket
|
|
37
|
+
serverSocket.on('error', (error) => {
|
|
38
|
+
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
39
|
+
remoteAddress,
|
|
40
|
+
error: `Target connection error: ${error.message}`
|
|
41
|
+
});
|
|
42
|
+
// Close the client socket if it's still open
|
|
43
|
+
if (!clientSocket.destroyed) {
|
|
44
|
+
clientSocket.destroy();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
// Handle errors on the client socket
|
|
48
|
+
clientSocket.on('error', (error) => {
|
|
49
|
+
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
50
|
+
remoteAddress,
|
|
51
|
+
error: `Client connection error: ${error.message}`
|
|
52
|
+
});
|
|
53
|
+
// Close the server socket if it's still open
|
|
54
|
+
if (!serverSocket.destroyed) {
|
|
55
|
+
serverSocket.destroy();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// Track data transfer for logging
|
|
59
|
+
let bytesSent = 0;
|
|
60
|
+
let bytesReceived = 0;
|
|
61
|
+
// Forward data from client to server
|
|
62
|
+
clientSocket.on('data', (data) => {
|
|
63
|
+
bytesSent += data.length;
|
|
64
|
+
// Check if server socket is writable
|
|
65
|
+
if (serverSocket.writable) {
|
|
66
|
+
const flushed = serverSocket.write(data);
|
|
67
|
+
// Handle backpressure
|
|
68
|
+
if (!flushed) {
|
|
69
|
+
clientSocket.pause();
|
|
70
|
+
serverSocket.once('drain', () => {
|
|
71
|
+
clientSocket.resume();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
|
|
76
|
+
direction: 'outbound',
|
|
77
|
+
bytes: data.length,
|
|
78
|
+
total: bytesSent
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// Forward data from server to client
|
|
82
|
+
serverSocket.on('data', (data) => {
|
|
83
|
+
bytesReceived += data.length;
|
|
84
|
+
// Check if client socket is writable
|
|
85
|
+
if (clientSocket.writable) {
|
|
86
|
+
const flushed = clientSocket.write(data);
|
|
87
|
+
// Handle backpressure
|
|
88
|
+
if (!flushed) {
|
|
89
|
+
serverSocket.pause();
|
|
90
|
+
clientSocket.once('drain', () => {
|
|
91
|
+
serverSocket.resume();
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
|
|
96
|
+
direction: 'inbound',
|
|
97
|
+
bytes: data.length,
|
|
98
|
+
total: bytesReceived
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
// Handle connection close
|
|
102
|
+
const handleClose = () => {
|
|
103
|
+
if (!clientSocket.destroyed) {
|
|
104
|
+
clientSocket.destroy();
|
|
105
|
+
}
|
|
106
|
+
if (!serverSocket.destroyed) {
|
|
107
|
+
serverSocket.destroy();
|
|
108
|
+
}
|
|
109
|
+
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
|
110
|
+
remoteAddress,
|
|
111
|
+
bytesSent,
|
|
112
|
+
bytesReceived
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
// Set up close handlers
|
|
116
|
+
clientSocket.on('close', handleClose);
|
|
117
|
+
serverSocket.on('close', handleClose);
|
|
118
|
+
// Set timeouts
|
|
119
|
+
const timeout = this.getTimeout();
|
|
120
|
+
clientSocket.setTimeout(timeout);
|
|
121
|
+
serverSocket.setTimeout(timeout);
|
|
122
|
+
// Handle timeouts
|
|
123
|
+
clientSocket.on('timeout', () => {
|
|
124
|
+
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
125
|
+
remoteAddress,
|
|
126
|
+
error: 'Client connection timeout'
|
|
127
|
+
});
|
|
128
|
+
handleClose();
|
|
129
|
+
});
|
|
130
|
+
serverSocket.on('timeout', () => {
|
|
131
|
+
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
132
|
+
remoteAddress,
|
|
133
|
+
error: 'Server connection timeout'
|
|
134
|
+
});
|
|
135
|
+
handleClose();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Handle an HTTP request - HTTPS passthrough doesn't support HTTP
|
|
140
|
+
* @param req The HTTP request
|
|
141
|
+
* @param res The HTTP response
|
|
142
|
+
*/
|
|
143
|
+
handleHttpRequest(req, res) {
|
|
144
|
+
// HTTPS passthrough doesn't support HTTP requests
|
|
145
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
146
|
+
res.end('HTTP not supported for this domain');
|
|
147
|
+
this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, {
|
|
148
|
+
statusCode: 404,
|
|
149
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
150
|
+
size: 'HTTP not supported for this domain'.length
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cHMtcGFzc3Rocm91Z2guaGFuZGxlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL3NtYXJ0cHJveHkvZm9yd2FyZGluZy9odHRwcy1wYXNzdGhyb3VnaC5oYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFFNUQsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFFdkU7O0dBRUc7QUFDSCxNQUFNLE9BQU8sdUJBQXdCLFNBQVEsaUJBQWlCO0lBQzVEOzs7T0FHRztJQUNILFlBQVksTUFBc0I7UUFDaEMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBRWQsMkRBQTJEO1FBQzNELElBQUksTUFBTSxDQUFDLElBQUksS0FBSyxtQkFBbUIsRUFBRSxDQUFDO1lBQ3hDLE1BQU0sSUFBSSxLQUFLLENBQUMsMkRBQTJELE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzVGLENBQUM7SUFDSCxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksZ0JBQWdCLENBQUMsWUFBZ0M7UUFDdEQsb0NBQW9DO1FBQ3BDLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO1FBRTFDLHFCQUFxQjtRQUNyQixNQUFNLGFBQWEsR0FBRyxZQUFZLENBQUMsYUFBYSxJQUFJLFNBQVMsQ0FBQztRQUM5RCxNQUFNLFVBQVUsR0FBRyxZQUFZLENBQUMsVUFBVSxJQUFJLENBQUMsQ0FBQztRQUVoRCxJQUFJLENBQUMsSUFBSSxDQUFDLHVCQUF1QixDQUFDLFNBQVMsRUFBRTtZQUMzQyxhQUFhO1lBQ2IsVUFBVTtZQUNWLE1BQU0sRUFBRSxHQUFHLE1BQU0sQ0FBQyxJQUFJLElBQUksTUFBTSxDQUFDLElBQUksRUFBRTtTQUN4QyxDQUFDLENBQUM7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
|