@push.rocks/smartproxy 6.0.1 → 7.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.
@@ -0,0 +1,295 @@
1
+ import * as plugins from '../plugins.js';
2
+
3
+ export interface RedirectRule {
4
+ /**
5
+ * Optional protocol to match (http or https). If not specified, matches both.
6
+ */
7
+ fromProtocol?: 'http' | 'https';
8
+
9
+ /**
10
+ * Optional hostname pattern to match. Can use * as wildcard.
11
+ * If not specified, matches all hosts.
12
+ */
13
+ fromHost?: string;
14
+
15
+ /**
16
+ * Optional path prefix to match. If not specified, matches all paths.
17
+ */
18
+ fromPath?: string;
19
+
20
+ /**
21
+ * Target protocol for the redirect (http or https)
22
+ */
23
+ toProtocol: 'http' | 'https';
24
+
25
+ /**
26
+ * Target hostname for the redirect. Can use $1, $2, etc. to reference
27
+ * captured groups from wildcard matches in fromHost.
28
+ */
29
+ toHost: string;
30
+
31
+ /**
32
+ * Optional target path prefix. If not specified, keeps original path.
33
+ * Can use $path to reference the original path.
34
+ */
35
+ toPath?: string;
36
+
37
+ /**
38
+ * HTTP status code for the redirect (301 for permanent, 302 for temporary)
39
+ */
40
+ statusCode?: 301 | 302 | 307 | 308;
41
+ }
42
+
43
+ export class Redirect {
44
+ private httpServer?: plugins.http.Server;
45
+ private httpsServer?: plugins.https.Server;
46
+ private rules: RedirectRule[] = [];
47
+ private httpPort: number = 80;
48
+ private httpsPort: number = 443;
49
+ private sslOptions?: {
50
+ key: Buffer;
51
+ cert: Buffer;
52
+ };
53
+
54
+ /**
55
+ * Create a new Redirect instance
56
+ * @param options Configuration options
57
+ */
58
+ constructor(options: {
59
+ httpPort?: number;
60
+ httpsPort?: number;
61
+ sslOptions?: {
62
+ key: Buffer;
63
+ cert: Buffer;
64
+ };
65
+ rules?: RedirectRule[];
66
+ } = {}) {
67
+ if (options.httpPort) this.httpPort = options.httpPort;
68
+ if (options.httpsPort) this.httpsPort = options.httpsPort;
69
+ if (options.sslOptions) this.sslOptions = options.sslOptions;
70
+ if (options.rules) this.rules = options.rules;
71
+ }
72
+
73
+ /**
74
+ * Add a redirect rule
75
+ */
76
+ public addRule(rule: RedirectRule): void {
77
+ this.rules.push(rule);
78
+ }
79
+
80
+ /**
81
+ * Remove all redirect rules
82
+ */
83
+ public clearRules(): void {
84
+ this.rules = [];
85
+ }
86
+
87
+ /**
88
+ * Set SSL options for HTTPS redirects
89
+ */
90
+ public setSslOptions(options: { key: Buffer; cert: Buffer }): void {
91
+ this.sslOptions = options;
92
+ }
93
+
94
+ /**
95
+ * Process a request according to the configured rules
96
+ */
97
+ private handleRequest(
98
+ request: plugins.http.IncomingMessage,
99
+ response: plugins.http.ServerResponse,
100
+ protocol: 'http' | 'https'
101
+ ): void {
102
+ const requestUrl = new URL(
103
+ request.url || '/',
104
+ `${protocol}://${request.headers.host || 'localhost'}`
105
+ );
106
+
107
+ const host = requestUrl.hostname;
108
+ const path = requestUrl.pathname + requestUrl.search;
109
+
110
+ // Find matching rule
111
+ const matchedRule = this.findMatchingRule(protocol, host, path);
112
+
113
+ if (matchedRule) {
114
+ const targetUrl = this.buildTargetUrl(matchedRule, host, path);
115
+
116
+ console.log(`Redirecting ${protocol}://${host}${path} to ${targetUrl}`);
117
+
118
+ response.writeHead(matchedRule.statusCode || 302, {
119
+ Location: targetUrl,
120
+ });
121
+ response.end();
122
+ } else {
123
+ // No matching rule, send 404
124
+ response.writeHead(404, { 'Content-Type': 'text/plain' });
125
+ response.end('Not Found');
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Find a matching redirect rule for the given request
131
+ */
132
+ private findMatchingRule(
133
+ protocol: 'http' | 'https',
134
+ host: string,
135
+ path: string
136
+ ): RedirectRule | undefined {
137
+ return this.rules.find((rule) => {
138
+ // Check protocol match
139
+ if (rule.fromProtocol && rule.fromProtocol !== protocol) {
140
+ return false;
141
+ }
142
+
143
+ // Check host match
144
+ if (rule.fromHost) {
145
+ const pattern = rule.fromHost.replace(/\*/g, '(.*)');
146
+ const regex = new RegExp(`^${pattern}$`);
147
+ if (!regex.test(host)) {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ // Check path match
153
+ if (rule.fromPath && !path.startsWith(rule.fromPath)) {
154
+ return false;
155
+ }
156
+
157
+ return true;
158
+ });
159
+ }
160
+
161
+ /**
162
+ * Build the target URL for a redirect
163
+ */
164
+ private buildTargetUrl(rule: RedirectRule, originalHost: string, originalPath: string): string {
165
+ let targetHost = rule.toHost;
166
+
167
+ // Replace wildcards in host
168
+ if (rule.fromHost && rule.fromHost.includes('*')) {
169
+ const pattern = rule.fromHost.replace(/\*/g, '(.*)');
170
+ const regex = new RegExp(`^${pattern}$`);
171
+ const matches = originalHost.match(regex);
172
+
173
+ if (matches) {
174
+ for (let i = 1; i < matches.length; i++) {
175
+ targetHost = targetHost.replace(`$${i}`, matches[i]);
176
+ }
177
+ }
178
+ }
179
+
180
+ // Build target path
181
+ let targetPath = originalPath;
182
+ if (rule.toPath) {
183
+ if (rule.toPath.includes('$path')) {
184
+ // Replace $path with original path, optionally removing the fromPath prefix
185
+ const pathSuffix = rule.fromPath ?
186
+ originalPath.substring(rule.fromPath.length) :
187
+ originalPath;
188
+
189
+ targetPath = rule.toPath.replace('$path', pathSuffix);
190
+ } else {
191
+ targetPath = rule.toPath;
192
+ }
193
+ }
194
+
195
+ return `${rule.toProtocol}://${targetHost}${targetPath}`;
196
+ }
197
+
198
+ /**
199
+ * Start the redirect server(s)
200
+ */
201
+ public async start(): Promise<void> {
202
+ const tasks = [];
203
+
204
+ // Create and start HTTP server if we have a port
205
+ if (this.httpPort) {
206
+ this.httpServer = plugins.http.createServer((req, res) =>
207
+ this.handleRequest(req, res, 'http')
208
+ );
209
+
210
+ const httpStartPromise = new Promise<void>((resolve) => {
211
+ this.httpServer?.listen(this.httpPort, () => {
212
+ console.log(`HTTP redirect server started on port ${this.httpPort}`);
213
+ resolve();
214
+ });
215
+ });
216
+
217
+ tasks.push(httpStartPromise);
218
+ }
219
+
220
+ // Create and start HTTPS server if we have SSL options and a port
221
+ if (this.httpsPort && this.sslOptions) {
222
+ this.httpsServer = plugins.https.createServer(this.sslOptions, (req, res) =>
223
+ this.handleRequest(req, res, 'https')
224
+ );
225
+
226
+ const httpsStartPromise = new Promise<void>((resolve) => {
227
+ this.httpsServer?.listen(this.httpsPort, () => {
228
+ console.log(`HTTPS redirect server started on port ${this.httpsPort}`);
229
+ resolve();
230
+ });
231
+ });
232
+
233
+ tasks.push(httpsStartPromise);
234
+ }
235
+
236
+ // Wait for all servers to start
237
+ await Promise.all(tasks);
238
+ }
239
+
240
+ /**
241
+ * Stop the redirect server(s)
242
+ */
243
+ public async stop(): Promise<void> {
244
+ const tasks = [];
245
+
246
+ if (this.httpServer) {
247
+ const httpStopPromise = new Promise<void>((resolve) => {
248
+ this.httpServer?.close(() => {
249
+ console.log('HTTP redirect server stopped');
250
+ resolve();
251
+ });
252
+ });
253
+ tasks.push(httpStopPromise);
254
+ }
255
+
256
+ if (this.httpsServer) {
257
+ const httpsStopPromise = new Promise<void>((resolve) => {
258
+ this.httpsServer?.close(() => {
259
+ console.log('HTTPS redirect server stopped');
260
+ resolve();
261
+ });
262
+ });
263
+ tasks.push(httpsStopPromise);
264
+ }
265
+
266
+ await Promise.all(tasks);
267
+ }
268
+ }
269
+
270
+ // For backward compatibility
271
+ export class SslRedirect {
272
+ private redirect: Redirect;
273
+ port: number;
274
+
275
+ constructor(portArg: number) {
276
+ this.port = portArg;
277
+ this.redirect = new Redirect({
278
+ httpPort: portArg,
279
+ rules: [{
280
+ fromProtocol: 'http',
281
+ toProtocol: 'https',
282
+ toHost: '$1',
283
+ statusCode: 302
284
+ }]
285
+ });
286
+ }
287
+
288
+ public async start() {
289
+ await this.redirect.start();
290
+ }
291
+
292
+ public async stop() {
293
+ await this.redirect.stop();
294
+ }
295
+ }
@@ -1,32 +0,0 @@
1
- import * as plugins from './plugins.js';
2
-
3
- export class SslRedirect {
4
- httpServer: plugins.http.Server;
5
- port: number;
6
- constructor(portArg: number) {
7
- this.port = portArg;
8
- }
9
-
10
- public async start() {
11
- this.httpServer = plugins.http.createServer((request, response) => {
12
- const requestUrl = new URL(request.url, `http://${request.headers.host}`);
13
- const completeUrlWithoutProtocol = `${requestUrl.host}${requestUrl.pathname}${requestUrl.search}`;
14
- const redirectUrl = `https://${completeUrlWithoutProtocol}`;
15
- console.log(`Got http request for http://${completeUrlWithoutProtocol}`);
16
- console.log(`Redirecting to ${redirectUrl}`);
17
- response.writeHead(302, {
18
- Location: redirectUrl,
19
- });
20
- response.end();
21
- });
22
- this.httpServer.listen(this.port);
23
- }
24
-
25
- public async stop() {
26
- const done = plugins.smartpromise.defer();
27
- this.httpServer.close(() => {
28
- done.resolve();
29
- });
30
- await done.promise;
31
- }
32
- }