@push.rocks/smartproxy 19.5.19 → 19.5.21

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.
Files changed (110) hide show
  1. package/dist_ts/core/models/index.d.ts +2 -0
  2. package/dist_ts/core/models/index.js +3 -1
  3. package/dist_ts/core/models/socket-types.d.ts +14 -0
  4. package/dist_ts/core/models/socket-types.js +15 -0
  5. package/dist_ts/core/models/wrapped-socket.d.ts +34 -0
  6. package/dist_ts/core/models/wrapped-socket.js +82 -0
  7. package/dist_ts/core/routing/index.d.ts +11 -0
  8. package/dist_ts/core/routing/index.js +17 -0
  9. package/dist_ts/core/routing/matchers/domain.d.ts +34 -0
  10. package/dist_ts/core/routing/matchers/domain.js +91 -0
  11. package/dist_ts/core/routing/matchers/header.d.ts +32 -0
  12. package/dist_ts/core/routing/matchers/header.js +94 -0
  13. package/dist_ts/core/routing/matchers/index.d.ts +18 -0
  14. package/dist_ts/core/routing/matchers/index.js +20 -0
  15. package/dist_ts/core/routing/matchers/ip.d.ts +53 -0
  16. package/dist_ts/core/routing/matchers/ip.js +169 -0
  17. package/dist_ts/core/routing/matchers/path.d.ts +44 -0
  18. package/dist_ts/core/routing/matchers/path.js +148 -0
  19. package/dist_ts/core/routing/route-manager.d.ts +88 -0
  20. package/dist_ts/core/routing/route-manager.js +342 -0
  21. package/dist_ts/core/routing/route-utils.d.ts +28 -0
  22. package/dist_ts/core/routing/route-utils.js +67 -0
  23. package/dist_ts/core/routing/specificity.d.ts +30 -0
  24. package/dist_ts/core/routing/specificity.js +115 -0
  25. package/dist_ts/core/routing/types.d.ts +41 -0
  26. package/dist_ts/core/routing/types.js +5 -0
  27. package/dist_ts/core/utils/index.d.ts +1 -2
  28. package/dist_ts/core/utils/index.js +2 -3
  29. package/dist_ts/core/utils/proxy-protocol.d.ts +45 -0
  30. package/dist_ts/core/utils/proxy-protocol.js +201 -0
  31. package/dist_ts/core/utils/route-manager.d.ts +0 -30
  32. package/dist_ts/core/utils/route-manager.js +6 -47
  33. package/dist_ts/core/utils/route-utils.d.ts +2 -68
  34. package/dist_ts/core/utils/route-utils.js +21 -218
  35. package/dist_ts/core/utils/security-utils.js +4 -4
  36. package/dist_ts/index.d.ts +2 -5
  37. package/dist_ts/index.js +5 -11
  38. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +0 -1
  39. package/dist_ts/proxies/http-proxy/http-proxy.js +15 -60
  40. package/dist_ts/proxies/http-proxy/models/types.d.ts +0 -90
  41. package/dist_ts/proxies/http-proxy/models/types.js +1 -242
  42. package/dist_ts/proxies/http-proxy/request-handler.d.ts +3 -5
  43. package/dist_ts/proxies/http-proxy/request-handler.js +20 -171
  44. package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +2 -5
  45. package/dist_ts/proxies/http-proxy/websocket-handler.js +15 -23
  46. package/dist_ts/proxies/index.d.ts +2 -2
  47. package/dist_ts/proxies/index.js +4 -3
  48. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +3 -1
  49. package/dist_ts/proxies/smart-proxy/connection-manager.js +17 -7
  50. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +2 -1
  51. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +5 -2
  52. package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
  53. package/dist_ts/proxies/smart-proxy/index.js +2 -2
  54. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +7 -2
  55. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +1 -0
  56. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  57. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +1 -1
  58. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +155 -35
  59. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -1
  60. package/dist_ts/proxies/smart-proxy/smart-proxy.js +15 -4
  61. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +10 -43
  62. package/dist_ts/routing/router/http-router.d.ts +89 -0
  63. package/dist_ts/routing/router/http-router.js +205 -0
  64. package/dist_ts/routing/router/index.d.ts +2 -5
  65. package/dist_ts/routing/router/index.js +3 -4
  66. package/package.json +1 -1
  67. package/readme.delete.md +187 -0
  68. package/readme.hints.md +196 -1
  69. package/readme.plan.md +625 -0
  70. package/readme.proxy-chain-summary.md +112 -0
  71. package/readme.proxy-protocol-example.md +462 -0
  72. package/readme.proxy-protocol.md +415 -0
  73. package/readme.routing.md +341 -0
  74. package/ts/core/models/index.ts +2 -0
  75. package/ts/core/models/socket-types.ts +21 -0
  76. package/ts/core/models/wrapped-socket.ts +99 -0
  77. package/ts/core/routing/index.ts +21 -0
  78. package/ts/core/routing/matchers/domain.ts +119 -0
  79. package/ts/core/routing/matchers/header.ts +120 -0
  80. package/ts/core/routing/matchers/index.ts +22 -0
  81. package/ts/core/routing/matchers/ip.ts +207 -0
  82. package/ts/core/routing/matchers/path.ts +184 -0
  83. package/ts/core/{utils → routing}/route-manager.ts +7 -57
  84. package/ts/core/routing/route-utils.ts +88 -0
  85. package/ts/core/routing/specificity.ts +141 -0
  86. package/ts/core/routing/types.ts +49 -0
  87. package/ts/core/utils/index.ts +1 -2
  88. package/ts/core/utils/proxy-protocol.ts +246 -0
  89. package/ts/core/utils/security-utils.ts +3 -7
  90. package/ts/index.ts +4 -14
  91. package/ts/proxies/http-proxy/http-proxy.ts +13 -68
  92. package/ts/proxies/http-proxy/models/types.ts +0 -324
  93. package/ts/proxies/http-proxy/request-handler.ts +15 -186
  94. package/ts/proxies/http-proxy/websocket-handler.ts +15 -26
  95. package/ts/proxies/index.ts +3 -2
  96. package/ts/proxies/smart-proxy/connection-manager.ts +17 -7
  97. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +6 -2
  98. package/ts/proxies/smart-proxy/index.ts +1 -1
  99. package/ts/proxies/smart-proxy/models/interfaces.ts +9 -2
  100. package/ts/proxies/smart-proxy/models/route-types.ts +3 -0
  101. package/ts/proxies/smart-proxy/route-connection-handler.ts +173 -42
  102. package/ts/proxies/smart-proxy/smart-proxy.ts +15 -3
  103. package/ts/proxies/smart-proxy/utils/route-utils.ts +11 -49
  104. package/ts/routing/router/http-router.ts +266 -0
  105. package/ts/routing/router/index.ts +3 -8
  106. package/readme.problems.md +0 -170
  107. package/ts/core/utils/route-utils.ts +0 -312
  108. package/ts/proxies/smart-proxy/route-manager.ts +0 -554
  109. package/ts/routing/router/proxy-router.ts +0 -437
  110. package/ts/routing/router/route-router.ts +0 -482
@@ -0,0 +1,120 @@
1
+ import type { IMatcher, IHeaderMatchOptions } from '../types.js';
2
+
3
+ /**
4
+ * HeaderMatcher provides HTTP header matching functionality
5
+ * Supporting exact matches, patterns, and case-insensitive matching
6
+ */
7
+ export class HeaderMatcher implements IMatcher<boolean, IHeaderMatchOptions> {
8
+ /**
9
+ * Match a header value against a pattern
10
+ * @param pattern The pattern to match
11
+ * @param value The header value to test
12
+ * @param options Matching options
13
+ * @returns true if the value matches the pattern
14
+ */
15
+ static match(
16
+ pattern: string,
17
+ value: string | undefined,
18
+ options: IHeaderMatchOptions = {}
19
+ ): boolean {
20
+ // Handle missing header
21
+ if (value === undefined || value === null) {
22
+ return pattern === '' || pattern === null || pattern === undefined;
23
+ }
24
+
25
+ // Convert to string and normalize
26
+ const normalizedPattern = String(pattern);
27
+ const normalizedValue = String(value);
28
+
29
+ // Apply case sensitivity
30
+ const comparePattern = options.caseInsensitive !== false
31
+ ? normalizedPattern.toLowerCase()
32
+ : normalizedPattern;
33
+ const compareValue = options.caseInsensitive !== false
34
+ ? normalizedValue.toLowerCase()
35
+ : normalizedValue;
36
+
37
+ // Exact match
38
+ if (options.exactMatch !== false) {
39
+ return comparePattern === compareValue;
40
+ }
41
+
42
+ // Pattern matching (simple wildcard support)
43
+ if (comparePattern.includes('*')) {
44
+ const regex = new RegExp(
45
+ '^' + comparePattern.replace(/\*/g, '.*') + '$',
46
+ options.caseInsensitive !== false ? 'i' : ''
47
+ );
48
+ return regex.test(normalizedValue);
49
+ }
50
+
51
+ // Contains match (if not exact match mode)
52
+ return compareValue.includes(comparePattern);
53
+ }
54
+
55
+ /**
56
+ * Match multiple headers against a set of required headers
57
+ * @param requiredHeaders Headers that must match
58
+ * @param actualHeaders Actual request headers
59
+ * @param options Matching options
60
+ * @returns true if all required headers match
61
+ */
62
+ static matchAll(
63
+ requiredHeaders: Record<string, string>,
64
+ actualHeaders: Record<string, string | string[] | undefined>,
65
+ options: IHeaderMatchOptions = {}
66
+ ): boolean {
67
+ for (const [name, pattern] of Object.entries(requiredHeaders)) {
68
+ const headerName = options.caseInsensitive !== false
69
+ ? name.toLowerCase()
70
+ : name;
71
+
72
+ // Find the actual header (case-insensitive search if needed)
73
+ let actualValue: string | undefined;
74
+ if (options.caseInsensitive !== false) {
75
+ const actualKey = Object.keys(actualHeaders).find(
76
+ key => key.toLowerCase() === headerName
77
+ );
78
+ const rawValue = actualKey ? actualHeaders[actualKey] : undefined;
79
+ // Handle array values (multiple headers with same name)
80
+ actualValue = Array.isArray(rawValue) ? rawValue.join(', ') : rawValue;
81
+ } else {
82
+ const rawValue = actualHeaders[name];
83
+ // Handle array values (multiple headers with same name)
84
+ actualValue = Array.isArray(rawValue) ? rawValue.join(', ') : rawValue;
85
+ }
86
+
87
+ // Check if this header matches
88
+ if (!this.match(pattern, actualValue, options)) {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ return true;
94
+ }
95
+
96
+ /**
97
+ * Calculate the specificity of header requirements
98
+ * More headers = more specific
99
+ */
100
+ static calculateSpecificity(headers: Record<string, string>): number {
101
+ const count = Object.keys(headers).length;
102
+ let score = count * 10;
103
+
104
+ // Bonus for headers without wildcards (more specific)
105
+ for (const value of Object.values(headers)) {
106
+ if (!value.includes('*')) {
107
+ score += 5;
108
+ }
109
+ }
110
+
111
+ return score;
112
+ }
113
+
114
+ /**
115
+ * Instance method for interface compliance
116
+ */
117
+ match(pattern: string, value: string, options?: IHeaderMatchOptions): boolean {
118
+ return HeaderMatcher.match(pattern, value, options);
119
+ }
120
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Unified matching utilities for the routing system
3
+ * All route matching logic should use these matchers for consistency
4
+ */
5
+
6
+ export * from './domain.js';
7
+ export * from './path.js';
8
+ export * from './ip.js';
9
+ export * from './header.js';
10
+
11
+ // Re-export for convenience
12
+ import { DomainMatcher } from './domain.js';
13
+ import { PathMatcher } from './path.js';
14
+ import { IpMatcher } from './ip.js';
15
+ import { HeaderMatcher } from './header.js';
16
+
17
+ export const matchers = {
18
+ domain: DomainMatcher,
19
+ path: PathMatcher,
20
+ ip: IpMatcher,
21
+ header: HeaderMatcher
22
+ } as const;
@@ -0,0 +1,207 @@
1
+ import type { IMatcher, IIpMatchOptions } from '../types.js';
2
+
3
+ /**
4
+ * IpMatcher provides comprehensive IP address matching functionality
5
+ * Supporting exact matches, CIDR notation, ranges, and wildcards
6
+ */
7
+ export class IpMatcher implements IMatcher<boolean, IIpMatchOptions> {
8
+ /**
9
+ * Check if a value is a valid IPv4 address
10
+ */
11
+ static isValidIpv4(ip: string): boolean {
12
+ const parts = ip.split('.');
13
+ if (parts.length !== 4) return false;
14
+
15
+ return parts.every(part => {
16
+ const num = parseInt(part, 10);
17
+ return !isNaN(num) && num >= 0 && num <= 255 && part === num.toString();
18
+ });
19
+ }
20
+
21
+ /**
22
+ * Check if a value is a valid IPv6 address (simplified check)
23
+ */
24
+ static isValidIpv6(ip: string): boolean {
25
+ // Basic IPv6 validation - can be enhanced
26
+ const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|(([0-9a-fA-F]{1,4}:){1,7}|:):|(([0-9a-fA-F]{1,4}:){1,6}|::):[0-9a-fA-F]{1,4})$/;
27
+ return ipv6Regex.test(ip);
28
+ }
29
+
30
+ /**
31
+ * Convert IP address to numeric value for comparison
32
+ */
33
+ private static ipToNumber(ip: string): number {
34
+ const parts = ip.split('.');
35
+ return parts.reduce((acc, part, index) => {
36
+ return acc + (parseInt(part, 10) << (8 * (3 - index)));
37
+ }, 0);
38
+ }
39
+
40
+ /**
41
+ * Match an IP against a CIDR notation pattern
42
+ */
43
+ static matchCidr(cidr: string, ip: string): boolean {
44
+ const [range, bits] = cidr.split('/');
45
+ if (!bits || !this.isValidIpv4(range) || !this.isValidIpv4(ip)) {
46
+ return false;
47
+ }
48
+
49
+ const rangeMask = parseInt(bits, 10);
50
+ if (isNaN(rangeMask) || rangeMask < 0 || rangeMask > 32) {
51
+ return false;
52
+ }
53
+
54
+ const rangeNum = this.ipToNumber(range);
55
+ const ipNum = this.ipToNumber(ip);
56
+ const mask = (-1 << (32 - rangeMask)) >>> 0;
57
+
58
+ return (rangeNum & mask) === (ipNum & mask);
59
+ }
60
+
61
+ /**
62
+ * Match an IP against a wildcard pattern
63
+ */
64
+ static matchWildcard(pattern: string, ip: string): boolean {
65
+ if (!this.isValidIpv4(ip)) return false;
66
+
67
+ const patternParts = pattern.split('.');
68
+ const ipParts = ip.split('.');
69
+
70
+ if (patternParts.length !== 4) return false;
71
+
72
+ return patternParts.every((part, index) => {
73
+ if (part === '*') return true;
74
+ return part === ipParts[index];
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Match an IP against a range (e.g., "192.168.1.1-192.168.1.100")
80
+ */
81
+ static matchRange(range: string, ip: string): boolean {
82
+ const [start, end] = range.split('-').map(s => s.trim());
83
+
84
+ if (!start || !end || !this.isValidIpv4(start) || !this.isValidIpv4(end) || !this.isValidIpv4(ip)) {
85
+ return false;
86
+ }
87
+
88
+ const startNum = this.ipToNumber(start);
89
+ const endNum = this.ipToNumber(end);
90
+ const ipNum = this.ipToNumber(ip);
91
+
92
+ return ipNum >= startNum && ipNum <= endNum;
93
+ }
94
+
95
+ /**
96
+ * Match an IP pattern against an IP address
97
+ * Supports multiple formats:
98
+ * - Exact match: "192.168.1.1"
99
+ * - CIDR: "192.168.1.0/24"
100
+ * - Wildcard: "192.168.1.*"
101
+ * - Range: "192.168.1.1-192.168.1.100"
102
+ */
103
+ static match(
104
+ pattern: string,
105
+ ip: string,
106
+ options: IIpMatchOptions = {}
107
+ ): boolean {
108
+ // Handle null/undefined cases
109
+ if (!pattern || !ip) {
110
+ return false;
111
+ }
112
+
113
+ // Normalize inputs
114
+ const normalizedPattern = pattern.trim();
115
+ const normalizedIp = ip.trim();
116
+
117
+ // Extract IPv4 from IPv6-mapped addresses (::ffff:192.168.1.1)
118
+ const ipv4Match = normalizedIp.match(/::ffff:(\d+\.\d+\.\d+\.\d+)/i);
119
+ const testIp = ipv4Match ? ipv4Match[1] : normalizedIp;
120
+
121
+ // Exact match
122
+ if (normalizedPattern === testIp) {
123
+ return true;
124
+ }
125
+
126
+ // CIDR notation
127
+ if (options.allowCidr !== false && normalizedPattern.includes('/')) {
128
+ return this.matchCidr(normalizedPattern, testIp);
129
+ }
130
+
131
+ // Wildcard matching
132
+ if (normalizedPattern.includes('*')) {
133
+ return this.matchWildcard(normalizedPattern, testIp);
134
+ }
135
+
136
+ // Range matching
137
+ if (options.allowRanges !== false && normalizedPattern.includes('-')) {
138
+ return this.matchRange(normalizedPattern, testIp);
139
+ }
140
+
141
+ return false;
142
+ }
143
+
144
+ /**
145
+ * Check if an IP is authorized based on allow and block lists
146
+ */
147
+ static isAuthorized(
148
+ ip: string,
149
+ allowList: string[] = [],
150
+ blockList: string[] = []
151
+ ): boolean {
152
+ // If IP is in block list, deny
153
+ if (blockList.some(pattern => this.match(pattern, ip))) {
154
+ return false;
155
+ }
156
+
157
+ // If allow list is empty, allow all (except blocked)
158
+ if (allowList.length === 0) {
159
+ return true;
160
+ }
161
+
162
+ // If allow list exists, IP must match
163
+ return allowList.some(pattern => this.match(pattern, ip));
164
+ }
165
+
166
+ /**
167
+ * Calculate the specificity of an IP pattern
168
+ * Higher values mean more specific patterns
169
+ */
170
+ static calculateSpecificity(pattern: string): number {
171
+ if (!pattern) return 0;
172
+
173
+ let score = 0;
174
+
175
+ // Exact IPs are most specific
176
+ if (this.isValidIpv4(pattern) || this.isValidIpv6(pattern)) {
177
+ score += 100;
178
+ }
179
+
180
+ // CIDR notation
181
+ if (pattern.includes('/')) {
182
+ const [, bits] = pattern.split('/');
183
+ const maskBits = parseInt(bits, 10);
184
+ if (!isNaN(maskBits)) {
185
+ score += maskBits; // Higher mask = more specific
186
+ }
187
+ }
188
+
189
+ // Wildcard patterns
190
+ const wildcards = (pattern.match(/\*/g) || []).length;
191
+ score -= wildcards * 20; // More wildcards = less specific
192
+
193
+ // Range patterns are somewhat specific
194
+ if (pattern.includes('-')) {
195
+ score += 30;
196
+ }
197
+
198
+ return score;
199
+ }
200
+
201
+ /**
202
+ * Instance method for interface compliance
203
+ */
204
+ match(pattern: string, ip: string, options?: IIpMatchOptions): boolean {
205
+ return IpMatcher.match(pattern, ip, options);
206
+ }
207
+ }
@@ -0,0 +1,184 @@
1
+ import type { IMatcher, IPathMatchResult } from '../types.js';
2
+
3
+ /**
4
+ * PathMatcher provides comprehensive path matching functionality
5
+ * Supporting exact matches, wildcards, and parameter extraction
6
+ */
7
+ export class PathMatcher implements IMatcher<IPathMatchResult> {
8
+ /**
9
+ * Convert a path pattern to a regex and extract parameter names
10
+ * Supports:
11
+ * - Exact paths: /api/users
12
+ * - Wildcards: /api/*
13
+ * - Parameters: /api/users/:id
14
+ * - Mixed: /api/users/:id/*
15
+ */
16
+ private static patternToRegex(pattern: string): {
17
+ regex: RegExp;
18
+ paramNames: string[]
19
+ } {
20
+ const paramNames: string[] = [];
21
+ let regexPattern = pattern;
22
+
23
+ // Escape special regex characters except : and *
24
+ regexPattern = regexPattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
25
+
26
+ // Handle path parameters (:param)
27
+ regexPattern = regexPattern.replace(/:(\w+)/g, (match, paramName) => {
28
+ paramNames.push(paramName);
29
+ return '([^/]+)'; // Match any non-slash characters
30
+ });
31
+
32
+ // Handle wildcards
33
+ regexPattern = regexPattern.replace(/\*/g, '(.*)');
34
+
35
+ // Ensure the pattern matches from start
36
+ regexPattern = `^${regexPattern}`;
37
+
38
+ // If pattern doesn't end with wildcard, ensure it matches to end
39
+ // But only for patterns that don't have parameters or wildcards
40
+ if (!pattern.includes('*') && !pattern.includes(':') && !pattern.endsWith('/')) {
41
+ regexPattern = `${regexPattern}$`;
42
+ }
43
+
44
+ return {
45
+ regex: new RegExp(regexPattern),
46
+ paramNames
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Match a path pattern against a request path
52
+ * @param pattern The pattern to match
53
+ * @param path The request path to test
54
+ * @returns Match result with params and remainder
55
+ */
56
+ static match(pattern: string, path: string): IPathMatchResult {
57
+ // Handle null/undefined cases
58
+ if (!pattern || !path) {
59
+ return { matches: false };
60
+ }
61
+
62
+ // Normalize paths (remove trailing slashes unless it's just "/")
63
+ const normalizedPattern = pattern === '/' ? '/' : pattern.replace(/\/$/, '');
64
+ const normalizedPath = path === '/' ? '/' : path.replace(/\/$/, '');
65
+
66
+ // Exact match (most common case)
67
+ if (normalizedPattern === normalizedPath) {
68
+ return {
69
+ matches: true,
70
+ pathMatch: normalizedPath,
71
+ pathRemainder: '',
72
+ params: {}
73
+ };
74
+ }
75
+
76
+ // Pattern matching (wildcards and parameters)
77
+ const { regex, paramNames } = this.patternToRegex(normalizedPattern);
78
+ const match = normalizedPath.match(regex);
79
+
80
+ if (!match) {
81
+ return { matches: false };
82
+ }
83
+
84
+ // Extract parameters
85
+ const params: Record<string, string> = {};
86
+ paramNames.forEach((name, index) => {
87
+ params[name] = match[index + 1];
88
+ });
89
+
90
+ // Calculate path match and remainder
91
+ let pathMatch = match[0];
92
+ let pathRemainder = normalizedPath.substring(pathMatch.length);
93
+
94
+ // Handle wildcard captures
95
+ if (normalizedPattern.includes('*') && match.length > paramNames.length + 1) {
96
+ const wildcardCapture = match[match.length - 1];
97
+ if (wildcardCapture) {
98
+ pathRemainder = wildcardCapture;
99
+ pathMatch = normalizedPath.substring(0, normalizedPath.length - wildcardCapture.length);
100
+ }
101
+ }
102
+
103
+ // Clean up path match (remove trailing slash if present)
104
+ if (pathMatch !== '/' && pathMatch.endsWith('/')) {
105
+ pathMatch = pathMatch.slice(0, -1);
106
+ }
107
+
108
+ return {
109
+ matches: true,
110
+ pathMatch,
111
+ pathRemainder,
112
+ params
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Check if a pattern contains parameters or wildcards
118
+ */
119
+ static isDynamicPattern(pattern: string): boolean {
120
+ return pattern.includes(':') || pattern.includes('*');
121
+ }
122
+
123
+ /**
124
+ * Calculate the specificity of a path pattern
125
+ * Higher values mean more specific patterns
126
+ */
127
+ static calculateSpecificity(pattern: string): number {
128
+ if (!pattern) return 0;
129
+
130
+ let score = 0;
131
+
132
+ // Exact paths are most specific
133
+ if (!this.isDynamicPattern(pattern)) {
134
+ score += 100;
135
+ }
136
+
137
+ // Count path segments
138
+ const segments = pattern.split('/').filter(s => s.length > 0);
139
+ score += segments.length * 10;
140
+
141
+ // Count static segments (more static = more specific)
142
+ const staticSegments = segments.filter(s => !s.startsWith(':') && s !== '*');
143
+ score += staticSegments.length * 20;
144
+
145
+ // Penalize wildcards and parameters
146
+ const wildcards = (pattern.match(/\*/g) || []).length;
147
+ const params = (pattern.match(/:/g) || []).length;
148
+ score -= wildcards * 30; // Wildcards are very generic
149
+ score -= params * 10; // Parameters are somewhat generic
150
+
151
+ // Bonus for longer patterns
152
+ score += pattern.length;
153
+
154
+ return score;
155
+ }
156
+
157
+ /**
158
+ * Find all matching patterns from a list
159
+ * Returns patterns sorted by specificity (most specific first)
160
+ */
161
+ static findAllMatches(patterns: string[], path: string): Array<{
162
+ pattern: string;
163
+ result: IPathMatchResult;
164
+ }> {
165
+ const matches = patterns
166
+ .map(pattern => ({
167
+ pattern,
168
+ result: this.match(pattern, path)
169
+ }))
170
+ .filter(({ result }) => result.matches);
171
+
172
+ // Sort by specificity (highest first)
173
+ return matches.sort((a, b) =>
174
+ this.calculateSpecificity(b.pattern) - this.calculateSpecificity(a.pattern)
175
+ );
176
+ }
177
+
178
+ /**
179
+ * Instance method for interface compliance
180
+ */
181
+ match(pattern: string, path: string): IPathMatchResult {
182
+ return PathMatcher.match(pattern, path);
183
+ }
184
+ }
@@ -7,20 +7,15 @@ import type {
7
7
  IRouteContext
8
8
  } from '../../proxies/smart-proxy/models/route-types.js';
9
9
  import {
10
- matchDomain,
11
10
  matchRouteDomain,
12
- matchPath,
13
- matchIpPattern,
14
- matchIpCidr,
15
- ipToNumber,
16
- isIpAuthorized,
17
11
  calculateRouteSpecificity
18
12
  } from './route-utils.js';
13
+ import { DomainMatcher, PathMatcher, IpMatcher } from './matchers/index.js';
19
14
 
20
15
  /**
21
- * Result of route matching
16
+ * Result of route lookup
22
17
  */
23
- export interface IRouteMatchResult {
18
+ export interface IRouteLookupResult {
24
19
  route: IRouteConfig;
25
20
  // Additional match parameters (path, query, etc.)
26
21
  params?: Record<string, string>;
@@ -219,7 +214,7 @@ export class SharedRouteManager extends plugins.EventEmitter {
219
214
  /**
220
215
  * Find the matching route for a connection
221
216
  */
222
- public findMatchingRoute(context: IRouteContext): IRouteMatchResult | null {
217
+ public findMatchingRoute(context: IRouteContext): IRouteLookupResult | null {
223
218
  // Get routes for this port if using port-based filtering
224
219
  const routesToCheck = context.port
225
220
  ? (this.portMap.get(context.port) || [])
@@ -258,21 +253,21 @@ export class SharedRouteManager extends plugins.EventEmitter {
258
253
  ? route.match.domains
259
254
  : [route.match.domains];
260
255
 
261
- if (!domains.some(domainPattern => this.matchDomain(domainPattern, context.domain!))) {
256
+ if (!domains.some(domainPattern => DomainMatcher.match(domainPattern, context.domain!))) {
262
257
  return false;
263
258
  }
264
259
  }
265
260
 
266
261
  // Check path match if specified
267
262
  if (route.match.path && context.path) {
268
- if (!this.matchPath(route.match.path, context.path)) {
263
+ if (!PathMatcher.match(route.match.path, context.path).matches) {
269
264
  return false;
270
265
  }
271
266
  }
272
267
 
273
268
  // Check client IP match if specified
274
269
  if (route.match.clientIp && context.clientIp) {
275
- if (!route.match.clientIp.some(ip => this.matchIpPattern(ip, context.clientIp))) {
270
+ if (!route.match.clientIp.some(ip => IpMatcher.match(ip, context.clientIp))) {
276
271
  return false;
277
272
  }
278
273
  }
@@ -311,45 +306,7 @@ export class SharedRouteManager extends plugins.EventEmitter {
311
306
  return true;
312
307
  }
313
308
 
314
- /**
315
- * Match a domain pattern against a domain
316
- * @deprecated Use the matchDomain function from route-utils.js instead
317
- */
318
- public matchDomain(pattern: string, domain: string): boolean {
319
- return matchDomain(pattern, domain);
320
- }
321
309
 
322
- /**
323
- * Match a path pattern against a path
324
- * @deprecated Use the matchPath function from route-utils.js instead
325
- */
326
- public matchPath(pattern: string, path: string): boolean {
327
- return matchPath(pattern, path);
328
- }
329
-
330
- /**
331
- * Match an IP pattern against a pattern
332
- * @deprecated Use the matchIpPattern function from route-utils.js instead
333
- */
334
- public matchIpPattern(pattern: string, ip: string): boolean {
335
- return matchIpPattern(pattern, ip);
336
- }
337
-
338
- /**
339
- * Match an IP against a CIDR pattern
340
- * @deprecated Use the matchIpCidr function from route-utils.js instead
341
- */
342
- public matchIpCidr(cidr: string, ip: string): boolean {
343
- return matchIpCidr(cidr, ip);
344
- }
345
-
346
- /**
347
- * Convert an IP address to a numeric value
348
- * @deprecated Use the ipToNumber function from route-utils.js instead
349
- */
350
- private ipToNumber(ip: string): number {
351
- return ipToNumber(ip);
352
- }
353
310
 
354
311
  /**
355
312
  * Validate the route configuration and return any warnings
@@ -479,11 +436,4 @@ export class SharedRouteManager extends plugins.EventEmitter {
479
436
  return true;
480
437
  }
481
438
 
482
- /**
483
- * Check if route1 is more specific than route2
484
- * @deprecated Use the calculateRouteSpecificity function from route-utils.js instead
485
- */
486
- private isRouteMoreSpecific(match1: IRouteMatch, match2: IRouteMatch): boolean {
487
- return calculateRouteSpecificity(match1) > calculateRouteSpecificity(match2);
488
- }
489
439
  }