@push.rocks/smartproxy 16.0.2 → 16.0.3

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 (115) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/core/models/index.d.ts +2 -0
  3. package/dist_ts/core/models/index.js +3 -1
  4. package/dist_ts/core/models/route-context.d.ts +62 -0
  5. package/dist_ts/core/models/route-context.js +43 -0
  6. package/dist_ts/core/models/socket-augmentation.d.ts +12 -0
  7. package/dist_ts/core/models/socket-augmentation.js +18 -0
  8. package/dist_ts/core/utils/event-system.d.ts +200 -0
  9. package/dist_ts/core/utils/event-system.js +224 -0
  10. package/dist_ts/core/utils/index.d.ts +7 -0
  11. package/dist_ts/core/utils/index.js +8 -1
  12. package/dist_ts/core/utils/route-manager.d.ts +118 -0
  13. package/dist_ts/core/utils/route-manager.js +383 -0
  14. package/dist_ts/core/utils/route-utils.d.ts +94 -0
  15. package/dist_ts/core/utils/route-utils.js +264 -0
  16. package/dist_ts/core/utils/security-utils.d.ts +111 -0
  17. package/dist_ts/core/utils/security-utils.js +212 -0
  18. package/dist_ts/core/utils/shared-security-manager.d.ts +110 -0
  19. package/dist_ts/core/utils/shared-security-manager.js +252 -0
  20. package/dist_ts/core/utils/template-utils.d.ts +37 -0
  21. package/dist_ts/core/utils/template-utils.js +104 -0
  22. package/dist_ts/core/utils/websocket-utils.d.ts +23 -0
  23. package/dist_ts/core/utils/websocket-utils.js +86 -0
  24. package/dist_ts/http/router/index.d.ts +5 -1
  25. package/dist_ts/http/router/index.js +4 -2
  26. package/dist_ts/http/router/route-router.d.ts +108 -0
  27. package/dist_ts/http/router/route-router.js +393 -0
  28. package/dist_ts/index.d.ts +8 -2
  29. package/dist_ts/index.js +10 -3
  30. package/dist_ts/proxies/index.d.ts +7 -2
  31. package/dist_ts/proxies/index.js +10 -4
  32. package/dist_ts/proxies/network-proxy/certificate-manager.d.ts +21 -0
  33. package/dist_ts/proxies/network-proxy/certificate-manager.js +92 -1
  34. package/dist_ts/proxies/network-proxy/context-creator.d.ts +34 -0
  35. package/dist_ts/proxies/network-proxy/context-creator.js +108 -0
  36. package/dist_ts/proxies/network-proxy/function-cache.d.ts +90 -0
  37. package/dist_ts/proxies/network-proxy/function-cache.js +198 -0
  38. package/dist_ts/proxies/network-proxy/http-request-handler.d.ts +40 -0
  39. package/dist_ts/proxies/network-proxy/http-request-handler.js +256 -0
  40. package/dist_ts/proxies/network-proxy/http2-request-handler.d.ts +24 -0
  41. package/dist_ts/proxies/network-proxy/http2-request-handler.js +201 -0
  42. package/dist_ts/proxies/network-proxy/models/types.d.ts +73 -1
  43. package/dist_ts/proxies/network-proxy/models/types.js +242 -1
  44. package/dist_ts/proxies/network-proxy/network-proxy.d.ts +23 -20
  45. package/dist_ts/proxies/network-proxy/network-proxy.js +147 -60
  46. package/dist_ts/proxies/network-proxy/request-handler.d.ts +38 -5
  47. package/dist_ts/proxies/network-proxy/request-handler.js +584 -198
  48. package/dist_ts/proxies/network-proxy/security-manager.d.ts +65 -0
  49. package/dist_ts/proxies/network-proxy/security-manager.js +255 -0
  50. package/dist_ts/proxies/network-proxy/websocket-handler.d.ts +13 -2
  51. package/dist_ts/proxies/network-proxy/websocket-handler.js +238 -20
  52. package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
  53. package/dist_ts/proxies/smart-proxy/index.js +3 -3
  54. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +3 -5
  55. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +56 -3
  56. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +4 -57
  57. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +19 -228
  58. package/dist_ts/proxies/smart-proxy/port-manager.d.ts +81 -0
  59. package/dist_ts/proxies/smart-proxy/port-manager.js +166 -0
  60. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +5 -0
  61. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +131 -15
  62. package/dist_ts/proxies/smart-proxy/route-helpers/index.d.ts +3 -1
  63. package/dist_ts/proxies/smart-proxy/route-helpers/index.js +5 -3
  64. package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +5 -178
  65. package/dist_ts/proxies/smart-proxy/route-helpers.js +8 -296
  66. package/dist_ts/proxies/smart-proxy/route-manager.d.ts +11 -2
  67. package/dist_ts/proxies/smart-proxy/route-manager.js +79 -10
  68. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +29 -2
  69. package/dist_ts/proxies/smart-proxy/smart-proxy.js +48 -43
  70. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +67 -1
  71. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +120 -1
  72. package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +3 -3
  73. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +27 -5
  74. package/package.json +1 -1
  75. package/readme.md +102 -14
  76. package/readme.plan.md +103 -168
  77. package/ts/00_commitinfo_data.ts +1 -1
  78. package/ts/core/models/index.ts +2 -0
  79. package/ts/core/models/route-context.ts +113 -0
  80. package/ts/core/models/socket-augmentation.ts +33 -0
  81. package/ts/core/utils/event-system.ts +376 -0
  82. package/ts/core/utils/index.ts +7 -0
  83. package/ts/core/utils/route-manager.ts +489 -0
  84. package/ts/core/utils/route-utils.ts +312 -0
  85. package/ts/core/utils/security-utils.ts +309 -0
  86. package/ts/core/utils/shared-security-manager.ts +333 -0
  87. package/ts/core/utils/template-utils.ts +124 -0
  88. package/ts/core/utils/websocket-utils.ts +81 -0
  89. package/ts/http/router/index.ts +8 -1
  90. package/ts/http/router/route-router.ts +482 -0
  91. package/ts/index.ts +14 -2
  92. package/ts/proxies/index.ts +12 -3
  93. package/ts/proxies/network-proxy/certificate-manager.ts +114 -10
  94. package/ts/proxies/network-proxy/context-creator.ts +145 -0
  95. package/ts/proxies/network-proxy/function-cache.ts +259 -0
  96. package/ts/proxies/network-proxy/http-request-handler.ts +330 -0
  97. package/ts/proxies/network-proxy/http2-request-handler.ts +255 -0
  98. package/ts/proxies/network-proxy/models/types.ts +312 -1
  99. package/ts/proxies/network-proxy/network-proxy.ts +195 -86
  100. package/ts/proxies/network-proxy/request-handler.ts +698 -246
  101. package/ts/proxies/network-proxy/security-manager.ts +298 -0
  102. package/ts/proxies/network-proxy/websocket-handler.ts +276 -33
  103. package/ts/proxies/smart-proxy/index.ts +2 -12
  104. package/ts/proxies/smart-proxy/models/interfaces.ts +7 -4
  105. package/ts/proxies/smart-proxy/models/route-types.ts +78 -10
  106. package/ts/proxies/smart-proxy/network-proxy-bridge.ts +20 -257
  107. package/ts/proxies/smart-proxy/port-manager.ts +195 -0
  108. package/ts/proxies/smart-proxy/route-connection-handler.ts +156 -21
  109. package/ts/proxies/smart-proxy/route-manager.ts +98 -14
  110. package/ts/proxies/smart-proxy/smart-proxy.ts +56 -55
  111. package/ts/proxies/smart-proxy/utils/route-helpers.ts +167 -1
  112. package/ts/proxies/smart-proxy/utils/route-validators.ts +24 -5
  113. package/ts/proxies/smart-proxy/domain-config-manager.ts.bak +0 -441
  114. package/ts/proxies/smart-proxy/route-helpers/index.ts +0 -9
  115. package/ts/proxies/smart-proxy/route-helpers.ts +0 -498
@@ -8,6 +8,7 @@ import { CertificateEvents } from '../../certificate/events/certificate-events.j
8
8
  import { buildPort80Handler } from '../../certificate/acme/acme-factory.js';
9
9
  import { subscribeToPort80Handler } from '../../core/utils/event-utils.js';
10
10
  import type { IDomainOptions } from '../../certificate/models/certificate-types.js';
11
+ import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
11
12
 
12
13
  /**
13
14
  * Manages SSL certificates for NetworkProxy including ACME integration
@@ -91,7 +92,7 @@ export class CertificateManager {
91
92
  public setExternalPort80Handler(handler: Port80Handler): void {
92
93
  if (this.port80Handler && !this.externalPort80Handler) {
93
94
  this.logger.warn('Replacing existing internal Port80Handler with external handler');
94
-
95
+
95
96
  // Clean up existing handler if needed
96
97
  if (this.port80Handler !== handler) {
97
98
  // Unregister event handlers to avoid memory leaks
@@ -101,11 +102,11 @@ export class CertificateManager {
101
102
  this.port80Handler.removeAllListeners(CertificateEvents.CERTIFICATE_EXPIRING);
102
103
  }
103
104
  }
104
-
105
+
105
106
  // Set the external handler
106
107
  this.port80Handler = handler;
107
108
  this.externalPort80Handler = true;
108
-
109
+
109
110
  // Subscribe to Port80Handler events
110
111
  subscribeToPort80Handler(this.port80Handler, {
111
112
  onCertificateIssued: this.handleCertificateIssued.bind(this),
@@ -115,17 +116,40 @@ export class CertificateManager {
115
116
  this.logger.info(`Certificate for ${data.domain} expires in ${data.daysRemaining} days`);
116
117
  }
117
118
  });
118
-
119
+
119
120
  this.logger.info('External Port80Handler connected to CertificateManager');
120
-
121
+
121
122
  // Register domains with Port80Handler if we have any certificates cached
122
123
  if (this.certificateCache.size > 0) {
123
124
  const domains = Array.from(this.certificateCache.keys())
124
125
  .filter(domain => !domain.includes('*')); // Skip wildcard domains
125
-
126
+
126
127
  this.registerDomainsWithPort80Handler(domains);
127
128
  }
128
129
  }
130
+
131
+ /**
132
+ * Update route configurations managed by this certificate manager
133
+ * This method is called when route configurations change
134
+ *
135
+ * @param routes Array of route configurations
136
+ */
137
+ public updateRouteConfigs(routes: IRouteConfig[]): void {
138
+ if (!this.port80Handler) {
139
+ this.logger.warn('Cannot update routes - Port80Handler is not initialized');
140
+ return;
141
+ }
142
+
143
+ // Register domains from routes with Port80Handler
144
+ this.registerRoutesWithPort80Handler(routes);
145
+
146
+ // Process individual routes for certificate requirements
147
+ for (const route of routes) {
148
+ this.processRouteForCertificates(route);
149
+ }
150
+
151
+ this.logger.info(`Updated certificate management for ${routes.length} routes`);
152
+ }
129
153
 
130
154
  /**
131
155
  * Handle newly issued or renewed certificates from Port80Handler
@@ -317,20 +341,21 @@ export class CertificateManager {
317
341
 
318
342
  /**
319
343
  * Registers domains with Port80Handler for ACME certificate management
344
+ * @param domains String array of domains to register
320
345
  */
321
346
  public registerDomainsWithPort80Handler(domains: string[]): void {
322
347
  if (!this.port80Handler) {
323
348
  this.logger.warn('Port80Handler is not initialized');
324
349
  return;
325
350
  }
326
-
351
+
327
352
  for (const domain of domains) {
328
353
  // Skip wildcard domains - can't get certs for these with HTTP-01 validation
329
354
  if (domain.includes('*')) {
330
355
  this.logger.info(`Skipping wildcard domain for ACME: ${domain}`);
331
356
  continue;
332
357
  }
333
-
358
+
334
359
  // Skip domains already with certificates if configured to do so
335
360
  if (this.options.acme?.skipConfiguredCerts) {
336
361
  const cachedCert = this.certificateCache.get(domain);
@@ -339,18 +364,97 @@ export class CertificateManager {
339
364
  continue;
340
365
  }
341
366
  }
342
-
367
+
343
368
  // Register the domain for certificate issuance with new domain options format
344
369
  const domainOptions: IDomainOptions = {
345
370
  domainName: domain,
346
371
  sslRedirect: true,
347
372
  acmeMaintenance: true
348
373
  };
349
-
374
+
350
375
  this.port80Handler.addDomain(domainOptions);
351
376
  this.logger.info(`Registered domain for ACME certificate issuance: ${domain}`);
352
377
  }
353
378
  }
379
+
380
+ /**
381
+ * Extract domains from route configurations and register with Port80Handler
382
+ * This method enables direct integration with route-based configuration
383
+ *
384
+ * @param routes Array of route configurations
385
+ */
386
+ public registerRoutesWithPort80Handler(routes: IRouteConfig[]): void {
387
+ if (!this.port80Handler) {
388
+ this.logger.warn('Port80Handler is not initialized');
389
+ return;
390
+ }
391
+
392
+ // Extract domains from route configurations
393
+ const domains: Set<string> = new Set();
394
+
395
+ for (const route of routes) {
396
+ // Skip disabled routes
397
+ if (route.enabled === false) {
398
+ continue;
399
+ }
400
+
401
+ // Skip routes without HTTPS termination
402
+ if (route.action.type !== 'forward' || route.action.tls?.mode !== 'terminate') {
403
+ continue;
404
+ }
405
+
406
+ // Extract domains from match criteria
407
+ if (route.match.domains) {
408
+ if (typeof route.match.domains === 'string') {
409
+ domains.add(route.match.domains);
410
+ } else if (Array.isArray(route.match.domains)) {
411
+ for (const domain of route.match.domains) {
412
+ domains.add(domain);
413
+ }
414
+ }
415
+ }
416
+ }
417
+
418
+ // Register extracted domains
419
+ this.registerDomainsWithPort80Handler(Array.from(domains));
420
+ }
421
+
422
+ /**
423
+ * Process a route config to determine if it requires automatic certificate provisioning
424
+ * @param route Route configuration to process
425
+ */
426
+ public processRouteForCertificates(route: IRouteConfig): void {
427
+ // Skip disabled routes
428
+ if (route.enabled === false) {
429
+ return;
430
+ }
431
+
432
+ // Skip routes without HTTPS termination or auto certificate
433
+ if (route.action.type !== 'forward' ||
434
+ route.action.tls?.mode !== 'terminate' ||
435
+ route.action.tls?.certificate !== 'auto') {
436
+ return;
437
+ }
438
+
439
+ // Extract domains from match criteria
440
+ const domains: string[] = [];
441
+ if (route.match.domains) {
442
+ if (typeof route.match.domains === 'string') {
443
+ domains.push(route.match.domains);
444
+ } else if (Array.isArray(route.match.domains)) {
445
+ domains.push(...route.match.domains);
446
+ }
447
+ }
448
+
449
+ // Request certificates for the domains
450
+ for (const domain of domains) {
451
+ if (!domain.includes('*')) { // Skip wildcard domains
452
+ this.requestCertificate(domain).catch(err => {
453
+ this.logger.error(`Error requesting certificate for domain ${domain}:`, err);
454
+ });
455
+ }
456
+ }
457
+ }
354
458
 
355
459
  /**
356
460
  * Initialize internal Port80Handler
@@ -0,0 +1,145 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import '../../core/models/socket-augmentation.js';
3
+ import type { IRouteContext, IHttpRouteContext, IHttp2RouteContext } from '../../core/models/route-context.js';
4
+
5
+ /**
6
+ * Context creator for NetworkProxy
7
+ * Creates route contexts for matching and function evaluation
8
+ */
9
+ export class ContextCreator {
10
+ /**
11
+ * Create a route context from HTTP request information
12
+ */
13
+ public createHttpRouteContext(req: any, options: {
14
+ tlsVersion?: string;
15
+ connectionId: string;
16
+ clientIp: string;
17
+ serverIp: string;
18
+ }): IHttpRouteContext {
19
+ // Parse headers
20
+ const headers: Record<string, string> = {};
21
+ for (const [key, value] of Object.entries(req.headers)) {
22
+ if (typeof value === 'string') {
23
+ headers[key.toLowerCase()] = value;
24
+ } else if (Array.isArray(value) && value.length > 0) {
25
+ headers[key.toLowerCase()] = value[0];
26
+ }
27
+ }
28
+
29
+ // Parse domain from Host header
30
+ const domain = headers['host']?.split(':')[0] || '';
31
+
32
+ // Parse URL
33
+ const url = new URL(`http://${domain}${req.url || '/'}`);
34
+
35
+ return {
36
+ // Connection basics
37
+ port: req.socket.localPort || 0,
38
+ domain,
39
+ clientIp: options.clientIp,
40
+ serverIp: options.serverIp,
41
+
42
+ // HTTP specifics
43
+ path: url.pathname,
44
+ query: url.search ? url.search.substring(1) : '',
45
+ headers,
46
+
47
+ // TLS information
48
+ isTls: !!req.socket.encrypted,
49
+ tlsVersion: options.tlsVersion,
50
+
51
+ // Request objects
52
+ req,
53
+
54
+ // Metadata
55
+ timestamp: Date.now(),
56
+ connectionId: options.connectionId
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Create a route context from HTTP/2 stream and headers
62
+ */
63
+ public createHttp2RouteContext(
64
+ stream: plugins.http2.ServerHttp2Stream,
65
+ headers: plugins.http2.IncomingHttpHeaders,
66
+ options: {
67
+ connectionId: string;
68
+ clientIp: string;
69
+ serverIp: string;
70
+ }
71
+ ): IHttp2RouteContext {
72
+ // Parse headers, excluding HTTP/2 pseudo-headers
73
+ const processedHeaders: Record<string, string> = {};
74
+ for (const [key, value] of Object.entries(headers)) {
75
+ if (!key.startsWith(':') && typeof value === 'string') {
76
+ processedHeaders[key.toLowerCase()] = value;
77
+ }
78
+ }
79
+
80
+ // Get domain from :authority pseudo-header
81
+ const authority = headers[':authority'] as string || '';
82
+ const domain = authority.split(':')[0];
83
+
84
+ // Get path from :path pseudo-header
85
+ const path = headers[':path'] as string || '/';
86
+
87
+ // Parse the path to extract query string
88
+ const pathParts = path.split('?');
89
+ const pathname = pathParts[0];
90
+ const query = pathParts.length > 1 ? pathParts[1] : '';
91
+
92
+ // Get the socket from the session
93
+ const socket = (stream.session as any)?.socket;
94
+
95
+ return {
96
+ // Connection basics
97
+ port: socket?.localPort || 0,
98
+ domain,
99
+ clientIp: options.clientIp,
100
+ serverIp: options.serverIp,
101
+
102
+ // HTTP specifics
103
+ path: pathname,
104
+ query,
105
+ headers: processedHeaders,
106
+
107
+ // HTTP/2 specific properties
108
+ method: headers[':method'] as string,
109
+ stream,
110
+
111
+ // TLS information - HTTP/2 is always on TLS in browsers
112
+ isTls: true,
113
+ tlsVersion: socket?.getTLSVersion?.() || 'TLSv1.3',
114
+
115
+ // Metadata
116
+ timestamp: Date.now(),
117
+ connectionId: options.connectionId
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Create a basic route context from socket information
123
+ */
124
+ public createSocketRouteContext(socket: plugins.net.Socket, options: {
125
+ domain?: string;
126
+ tlsVersion?: string;
127
+ connectionId: string;
128
+ }): IRouteContext {
129
+ return {
130
+ // Connection basics
131
+ port: socket.localPort || 0,
132
+ domain: options.domain,
133
+ clientIp: socket.remoteAddress?.replace('::ffff:', '') || '0.0.0.0',
134
+ serverIp: socket.localAddress?.replace('::ffff:', '') || '0.0.0.0',
135
+
136
+ // TLS information
137
+ isTls: options.tlsVersion !== undefined,
138
+ tlsVersion: options.tlsVersion,
139
+
140
+ // Metadata
141
+ timestamp: Date.now(),
142
+ connectionId: options.connectionId
143
+ };
144
+ }
145
+ }
@@ -0,0 +1,259 @@
1
+ import type { IRouteContext } from '../../core/models/route-context.js';
2
+ import type { ILogger } from './models/types.js';
3
+
4
+ /**
5
+ * Interface for cached function result
6
+ */
7
+ interface ICachedResult<T> {
8
+ value: T;
9
+ expiry: number;
10
+ hash: string;
11
+ }
12
+
13
+ /**
14
+ * Function cache for NetworkProxy function-based targets
15
+ *
16
+ * This cache improves performance for function-based targets by storing
17
+ * the results of function evaluations and reusing them for similar contexts.
18
+ */
19
+ export class FunctionCache {
20
+ // Cache storage
21
+ private hostCache: Map<string, ICachedResult<string | string[]>> = new Map();
22
+ private portCache: Map<string, ICachedResult<number>> = new Map();
23
+
24
+ // Maximum number of entries to store in each cache
25
+ private maxCacheSize: number;
26
+
27
+ // Default TTL for cache entries in milliseconds (default: 5 seconds)
28
+ private defaultTtl: number;
29
+
30
+ // Logger
31
+ private logger: ILogger;
32
+
33
+ /**
34
+ * Creates a new function cache
35
+ *
36
+ * @param logger Logger for debug output
37
+ * @param options Cache options
38
+ */
39
+ constructor(
40
+ logger: ILogger,
41
+ options: {
42
+ maxCacheSize?: number;
43
+ defaultTtl?: number;
44
+ } = {}
45
+ ) {
46
+ this.logger = logger;
47
+ this.maxCacheSize = options.maxCacheSize || 1000;
48
+ this.defaultTtl = options.defaultTtl || 5000; // 5 seconds default
49
+
50
+ // Start the cache cleanup timer
51
+ setInterval(() => this.cleanupCache(), 30000); // Cleanup every 30 seconds
52
+ }
53
+
54
+ /**
55
+ * Compute a hash for a context object
56
+ * This is used to identify similar contexts for caching
57
+ *
58
+ * @param context The route context to hash
59
+ * @param functionId Identifier for the function (usually route name or ID)
60
+ * @returns A string hash
61
+ */
62
+ private computeContextHash(context: IRouteContext, functionId: string): string {
63
+ // Extract relevant properties for the hash
64
+ const hashBase = {
65
+ functionId,
66
+ port: context.port,
67
+ domain: context.domain,
68
+ clientIp: context.clientIp,
69
+ path: context.path,
70
+ query: context.query,
71
+ isTls: context.isTls,
72
+ tlsVersion: context.tlsVersion
73
+ };
74
+
75
+ // Generate a hash string
76
+ return JSON.stringify(hashBase);
77
+ }
78
+
79
+ /**
80
+ * Get cached host result for a function and context
81
+ *
82
+ * @param context Route context
83
+ * @param functionId Identifier for the function
84
+ * @returns Cached host value or undefined if not found
85
+ */
86
+ public getCachedHost(context: IRouteContext, functionId: string): string | string[] | undefined {
87
+ const hash = this.computeContextHash(context, functionId);
88
+ const cached = this.hostCache.get(hash);
89
+
90
+ // Return if no cached value or expired
91
+ if (!cached || cached.expiry < Date.now()) {
92
+ if (cached) {
93
+ // If expired, remove from cache
94
+ this.hostCache.delete(hash);
95
+ this.logger.debug(`Cache miss (expired) for host function: ${functionId}`);
96
+ } else {
97
+ this.logger.debug(`Cache miss for host function: ${functionId}`);
98
+ }
99
+ return undefined;
100
+ }
101
+
102
+ this.logger.debug(`Cache hit for host function: ${functionId}`);
103
+ return cached.value;
104
+ }
105
+
106
+ /**
107
+ * Get cached port result for a function and context
108
+ *
109
+ * @param context Route context
110
+ * @param functionId Identifier for the function
111
+ * @returns Cached port value or undefined if not found
112
+ */
113
+ public getCachedPort(context: IRouteContext, functionId: string): number | undefined {
114
+ const hash = this.computeContextHash(context, functionId);
115
+ const cached = this.portCache.get(hash);
116
+
117
+ // Return if no cached value or expired
118
+ if (!cached || cached.expiry < Date.now()) {
119
+ if (cached) {
120
+ // If expired, remove from cache
121
+ this.portCache.delete(hash);
122
+ this.logger.debug(`Cache miss (expired) for port function: ${functionId}`);
123
+ } else {
124
+ this.logger.debug(`Cache miss for port function: ${functionId}`);
125
+ }
126
+ return undefined;
127
+ }
128
+
129
+ this.logger.debug(`Cache hit for port function: ${functionId}`);
130
+ return cached.value;
131
+ }
132
+
133
+ /**
134
+ * Store a host function result in the cache
135
+ *
136
+ * @param context Route context
137
+ * @param functionId Identifier for the function
138
+ * @param value Host value to cache
139
+ * @param ttl Optional TTL in milliseconds
140
+ */
141
+ public cacheHost(
142
+ context: IRouteContext,
143
+ functionId: string,
144
+ value: string | string[],
145
+ ttl?: number
146
+ ): void {
147
+ const hash = this.computeContextHash(context, functionId);
148
+ const expiry = Date.now() + (ttl || this.defaultTtl);
149
+
150
+ // Check if we need to prune the cache before adding
151
+ if (this.hostCache.size >= this.maxCacheSize) {
152
+ this.pruneOldestEntries(this.hostCache);
153
+ }
154
+
155
+ // Store the result
156
+ this.hostCache.set(hash, { value, expiry, hash });
157
+ this.logger.debug(`Cached host function result for: ${functionId}`);
158
+ }
159
+
160
+ /**
161
+ * Store a port function result in the cache
162
+ *
163
+ * @param context Route context
164
+ * @param functionId Identifier for the function
165
+ * @param value Port value to cache
166
+ * @param ttl Optional TTL in milliseconds
167
+ */
168
+ public cachePort(
169
+ context: IRouteContext,
170
+ functionId: string,
171
+ value: number,
172
+ ttl?: number
173
+ ): void {
174
+ const hash = this.computeContextHash(context, functionId);
175
+ const expiry = Date.now() + (ttl || this.defaultTtl);
176
+
177
+ // Check if we need to prune the cache before adding
178
+ if (this.portCache.size >= this.maxCacheSize) {
179
+ this.pruneOldestEntries(this.portCache);
180
+ }
181
+
182
+ // Store the result
183
+ this.portCache.set(hash, { value, expiry, hash });
184
+ this.logger.debug(`Cached port function result for: ${functionId}`);
185
+ }
186
+
187
+ /**
188
+ * Remove expired entries from the cache
189
+ */
190
+ private cleanupCache(): void {
191
+ const now = Date.now();
192
+ let expiredCount = 0;
193
+
194
+ // Clean up host cache
195
+ for (const [hash, cached] of this.hostCache.entries()) {
196
+ if (cached.expiry < now) {
197
+ this.hostCache.delete(hash);
198
+ expiredCount++;
199
+ }
200
+ }
201
+
202
+ // Clean up port cache
203
+ for (const [hash, cached] of this.portCache.entries()) {
204
+ if (cached.expiry < now) {
205
+ this.portCache.delete(hash);
206
+ expiredCount++;
207
+ }
208
+ }
209
+
210
+ if (expiredCount > 0) {
211
+ this.logger.debug(`Cleaned up ${expiredCount} expired cache entries`);
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Prune oldest entries from a cache map
217
+ * Used when the cache exceeds the maximum size
218
+ *
219
+ * @param cache The cache map to prune
220
+ */
221
+ private pruneOldestEntries<T>(cache: Map<string, ICachedResult<T>>): void {
222
+ // Find the oldest entries
223
+ const now = Date.now();
224
+ const itemsToRemove = Math.floor(this.maxCacheSize * 0.2); // Remove 20% of the cache
225
+
226
+ // Convert to array for sorting
227
+ const entries = Array.from(cache.entries());
228
+
229
+ // Sort by expiry (oldest first)
230
+ entries.sort((a, b) => a[1].expiry - b[1].expiry);
231
+
232
+ // Remove oldest entries
233
+ const toRemove = entries.slice(0, itemsToRemove);
234
+ for (const [hash] of toRemove) {
235
+ cache.delete(hash);
236
+ }
237
+
238
+ this.logger.debug(`Pruned ${toRemove.length} oldest cache entries`);
239
+ }
240
+
241
+ /**
242
+ * Get current cache stats
243
+ */
244
+ public getStats(): { hostCacheSize: number; portCacheSize: number } {
245
+ return {
246
+ hostCacheSize: this.hostCache.size,
247
+ portCacheSize: this.portCache.size
248
+ };
249
+ }
250
+
251
+ /**
252
+ * Clear all cached entries
253
+ */
254
+ public clearCache(): void {
255
+ this.hostCache.clear();
256
+ this.portCache.clear();
257
+ this.logger.info('Function cache cleared');
258
+ }
259
+ }