@push.rocks/smartproxy 7.1.2 → 10.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.
Files changed (45) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/classes.router.d.ts +9 -10
  3. package/dist_ts/classes.router.js +3 -5
  4. package/dist_ts/common/acmeFactory.d.ts +9 -0
  5. package/dist_ts/common/acmeFactory.js +20 -0
  6. package/dist_ts/common/eventUtils.d.ts +15 -0
  7. package/dist_ts/common/eventUtils.js +19 -0
  8. package/dist_ts/common/types.d.ts +82 -0
  9. package/dist_ts/common/types.js +17 -0
  10. package/dist_ts/networkproxy/classes.np.certificatemanager.js +23 -19
  11. package/dist_ts/networkproxy/classes.np.networkproxy.d.ts +1 -0
  12. package/dist_ts/networkproxy/classes.np.networkproxy.js +5 -1
  13. package/dist_ts/networkproxy/classes.np.types.d.ts +5 -10
  14. package/dist_ts/networkproxy/classes.np.types.js +1 -1
  15. package/dist_ts/plugins.d.ts +6 -3
  16. package/dist_ts/plugins.js +7 -4
  17. package/dist_ts/port80handler/classes.port80handler.d.ts +14 -111
  18. package/dist_ts/port80handler/classes.port80handler.js +94 -373
  19. package/dist_ts/smartproxy/classes.pp.certprovisioner.d.ts +54 -0
  20. package/dist_ts/smartproxy/classes.pp.certprovisioner.js +166 -0
  21. package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +11 -33
  22. package/dist_ts/smartproxy/classes.pp.networkproxybridge.d.ts +5 -0
  23. package/dist_ts/smartproxy/classes.pp.networkproxybridge.js +21 -11
  24. package/dist_ts/smartproxy/classes.pp.portrangemanager.js +1 -11
  25. package/dist_ts/smartproxy/classes.smartproxy.d.ts +3 -5
  26. package/dist_ts/smartproxy/classes.smartproxy.js +94 -180
  27. package/package.json +12 -10
  28. package/readme.hints.md +64 -1
  29. package/readme.md +253 -408
  30. package/readme.plan.md +29 -0
  31. package/ts/00_commitinfo_data.ts +1 -1
  32. package/ts/classes.router.ts +13 -15
  33. package/ts/common/acmeFactory.ts +23 -0
  34. package/ts/common/eventUtils.ts +34 -0
  35. package/ts/common/types.ts +89 -0
  36. package/ts/networkproxy/classes.np.certificatemanager.ts +23 -19
  37. package/ts/networkproxy/classes.np.networkproxy.ts +4 -0
  38. package/ts/networkproxy/classes.np.types.ts +6 -10
  39. package/ts/plugins.ts +17 -4
  40. package/ts/port80handler/classes.port80handler.ts +108 -509
  41. package/ts/smartproxy/classes.pp.certprovisioner.ts +188 -0
  42. package/ts/smartproxy/classes.pp.interfaces.ts +13 -36
  43. package/ts/smartproxy/classes.pp.networkproxybridge.ts +22 -10
  44. package/ts/smartproxy/classes.pp.portrangemanager.ts +0 -10
  45. package/ts/smartproxy/classes.smartproxy.ts +103 -195
@@ -0,0 +1,188 @@
1
+ import * as plugins from '../plugins.js';
2
+ import type { IDomainConfig, ISmartProxyCertProvisionObject } from './classes.pp.interfaces.js';
3
+ import { Port80Handler } from '../port80handler/classes.port80handler.js';
4
+ import { Port80HandlerEvents } from '../common/types.js';
5
+ import { subscribeToPort80Handler } from '../common/eventUtils.js';
6
+ import type { ICertificateData } from '../common/types.js';
7
+ import type { NetworkProxyBridge } from './classes.pp.networkproxybridge.js';
8
+
9
+ /**
10
+ * CertProvisioner manages certificate provisioning and renewal workflows,
11
+ * unifying static certificates and HTTP-01 challenges via Port80Handler.
12
+ */
13
+ export class CertProvisioner extends plugins.EventEmitter {
14
+ private domainConfigs: IDomainConfig[];
15
+ private port80Handler: Port80Handler;
16
+ private networkProxyBridge: NetworkProxyBridge;
17
+ private certProvider?: (domain: string) => Promise<ISmartProxyCertProvisionObject>;
18
+ private forwardConfigs: Array<{ domain: string; forwardConfig?: { ip: string; port: number }; acmeForwardConfig?: { ip: string; port: number }; sslRedirect: boolean }>;
19
+ private renewThresholdDays: number;
20
+ private renewCheckIntervalHours: number;
21
+ private autoRenew: boolean;
22
+ private renewManager?: plugins.taskbuffer.TaskManager;
23
+ // Track provisioning type per domain: 'http01' or 'static'
24
+ private provisionMap: Map<string, 'http01' | 'static'>;
25
+
26
+ /**
27
+ * @param domainConfigs Array of domain configuration objects
28
+ * @param port80Handler HTTP-01 challenge handler instance
29
+ * @param networkProxyBridge Bridge for applying external certificates
30
+ * @param certProvider Optional callback returning a static cert or 'http01'
31
+ * @param renewThresholdDays Days before expiry to trigger renewals
32
+ * @param renewCheckIntervalHours Interval in hours to check for renewals
33
+ * @param autoRenew Whether to automatically schedule renewals
34
+ */
35
+ constructor(
36
+ domainConfigs: IDomainConfig[],
37
+ port80Handler: Port80Handler,
38
+ networkProxyBridge: NetworkProxyBridge,
39
+ certProvider?: (domain: string) => Promise<ISmartProxyCertProvisionObject>,
40
+ renewThresholdDays: number = 30,
41
+ renewCheckIntervalHours: number = 24,
42
+ autoRenew: boolean = true,
43
+ forwardConfigs: Array<{ domain: string; forwardConfig?: { ip: string; port: number }; acmeForwardConfig?: { ip: string; port: number }; sslRedirect: boolean }> = []
44
+ ) {
45
+ super();
46
+ this.domainConfigs = domainConfigs;
47
+ this.port80Handler = port80Handler;
48
+ this.networkProxyBridge = networkProxyBridge;
49
+ this.certProvider = certProvider;
50
+ this.renewThresholdDays = renewThresholdDays;
51
+ this.renewCheckIntervalHours = renewCheckIntervalHours;
52
+ this.autoRenew = autoRenew;
53
+ this.provisionMap = new Map();
54
+ this.forwardConfigs = forwardConfigs;
55
+ }
56
+
57
+ /**
58
+ * Start initial provisioning and schedule renewals.
59
+ */
60
+ public async start(): Promise<void> {
61
+ // Subscribe to Port80Handler certificate events
62
+ subscribeToPort80Handler(this.port80Handler, {
63
+ onCertificateIssued: (data: ICertificateData) => {
64
+ this.emit('certificate', { ...data, source: 'http01', isRenewal: false });
65
+ },
66
+ onCertificateRenewed: (data: ICertificateData) => {
67
+ this.emit('certificate', { ...data, source: 'http01', isRenewal: true });
68
+ }
69
+ });
70
+
71
+ // Apply external forwarding for ACME challenges (e.g. Synology)
72
+ for (const f of this.forwardConfigs) {
73
+ this.port80Handler.addDomain({
74
+ domainName: f.domain,
75
+ sslRedirect: f.sslRedirect,
76
+ acmeMaintenance: false,
77
+ forward: f.forwardConfig,
78
+ acmeForward: f.acmeForwardConfig
79
+ });
80
+ }
81
+ // Initial provisioning for all domains
82
+ const domains = this.domainConfigs.flatMap(cfg => cfg.domains);
83
+ for (const domain of domains) {
84
+ // Skip wildcard domains
85
+ if (domain.includes('*')) continue;
86
+ let provision: ISmartProxyCertProvisionObject | 'http01' = 'http01';
87
+ if (this.certProvider) {
88
+ try {
89
+ provision = await this.certProvider(domain);
90
+ } catch (err) {
91
+ console.error(`certProvider error for ${domain}:`, err);
92
+ }
93
+ }
94
+ if (provision === 'http01') {
95
+ this.provisionMap.set(domain, 'http01');
96
+ this.port80Handler.addDomain({ domainName: domain, sslRedirect: true, acmeMaintenance: true });
97
+ } else {
98
+ this.provisionMap.set(domain, 'static');
99
+ const certObj = provision as plugins.tsclass.network.ICert;
100
+ const certData: ICertificateData = {
101
+ domain: certObj.domainName,
102
+ certificate: certObj.publicKey,
103
+ privateKey: certObj.privateKey,
104
+ expiryDate: new Date(certObj.validUntil)
105
+ };
106
+ this.networkProxyBridge.applyExternalCertificate(certData);
107
+ this.emit('certificate', { ...certData, source: 'static', isRenewal: false });
108
+ }
109
+ }
110
+
111
+ // Schedule renewals if enabled
112
+ if (this.autoRenew) {
113
+ this.renewManager = new plugins.taskbuffer.TaskManager();
114
+ const renewTask = new plugins.taskbuffer.Task({
115
+ name: 'CertificateRenewals',
116
+ taskFunction: async () => {
117
+ for (const [domain, type] of this.provisionMap.entries()) {
118
+ // Skip wildcard domains
119
+ if (domain.includes('*')) continue;
120
+ try {
121
+ if (type === 'http01') {
122
+ await this.port80Handler.renewCertificate(domain);
123
+ } else if (type === 'static' && this.certProvider) {
124
+ const provision2 = await this.certProvider(domain);
125
+ if (provision2 !== 'http01') {
126
+ const certObj = provision2 as plugins.tsclass.network.ICert;
127
+ const certData: ICertificateData = {
128
+ domain: certObj.domainName,
129
+ certificate: certObj.publicKey,
130
+ privateKey: certObj.privateKey,
131
+ expiryDate: new Date(certObj.validUntil)
132
+ };
133
+ this.networkProxyBridge.applyExternalCertificate(certData);
134
+ this.emit('certificate', { ...certData, source: 'static', isRenewal: true });
135
+ }
136
+ }
137
+ } catch (err) {
138
+ console.error(`Renewal error for ${domain}:`, err);
139
+ }
140
+ }
141
+ }
142
+ });
143
+ const hours = this.renewCheckIntervalHours;
144
+ const cronExpr = `0 0 */${hours} * * *`;
145
+ this.renewManager.addAndScheduleTask(renewTask, cronExpr);
146
+ this.renewManager.start();
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Stop all scheduled renewal tasks.
152
+ */
153
+ public async stop(): Promise<void> {
154
+ // Stop scheduled renewals
155
+ if (this.renewManager) {
156
+ this.renewManager.stop();
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Request a certificate on-demand for the given domain.
162
+ * @param domain Domain name to provision
163
+ */
164
+ public async requestCertificate(domain: string): Promise<void> {
165
+ // Skip wildcard domains
166
+ if (domain.includes('*')) {
167
+ throw new Error(`Cannot request certificate for wildcard domain: ${domain}`);
168
+ }
169
+ // Determine provisioning method
170
+ let provision: ISmartProxyCertProvisionObject | 'http01' = 'http01';
171
+ if (this.certProvider) {
172
+ provision = await this.certProvider(domain);
173
+ }
174
+ if (provision === 'http01') {
175
+ await this.port80Handler.renewCertificate(domain);
176
+ } else {
177
+ const certObj = provision as plugins.tsclass.network.ICert;
178
+ const certData: ICertificateData = {
179
+ domain: certObj.domainName,
180
+ certificate: certObj.publicKey,
181
+ privateKey: certObj.privateKey,
182
+ expiryDate: new Date(certObj.validUntil)
183
+ };
184
+ this.networkProxyBridge.applyExternalCertificate(certData);
185
+ this.emit('certificate', { ...certData, source: 'static', isRenewal: false });
186
+ }
187
+ }
188
+ }
@@ -1,5 +1,10 @@
1
1
  import * as plugins from '../plugins.js';
2
2
 
3
+ /**
4
+ * Provision object for static or HTTP-01 certificate
5
+ */
6
+ export type ISmartProxyCertProvisionObject = plugins.tsclass.network.ICert | 'http01';
7
+
3
8
  /** Domain configuration with per-domain allowed port ranges */
4
9
  export interface IDomainConfig {
5
10
  domains: string[]; // Glob patterns for domain(s)
@@ -16,6 +21,7 @@ export interface IDomainConfig {
16
21
  }
17
22
 
18
23
  /** Port proxy settings including global allowed port ranges */
24
+ import type { IAcmeOptions } from '../common/types.js';
19
25
  export interface IPortProxySettings {
20
26
  fromPort: number;
21
27
  toPort: number;
@@ -78,43 +84,14 @@ export interface IPortProxySettings {
78
84
  useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy
79
85
  networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443)
80
86
 
81
- // Port80Handler configuration (replaces ACME configuration)
82
- port80HandlerConfig?: {
83
- enabled?: boolean; // Whether to enable automatic certificate management
84
- port?: number; // Port to listen on for ACME challenges (default: 80)
85
- contactEmail?: string; // Email for Let's Encrypt account
86
- useProduction?: boolean; // Whether to use Let's Encrypt production (default: false for staging)
87
- renewThresholdDays?: number; // Days before expiry to renew certificates (default: 30)
88
- autoRenew?: boolean; // Whether to automatically renew certificates (default: true)
89
- certificateStore?: string; // Directory to store certificates (default: ./certs)
90
- skipConfiguredCerts?: boolean; // Skip domains that already have certificates
91
- httpsRedirectPort?: number; // Port to redirect HTTP requests to HTTPS (default: 443)
92
- renewCheckIntervalHours?: number; // How often to check for renewals (default: 24)
93
- // Domain-specific forwarding configurations
94
- domainForwards?: Array<{
95
- domain: string;
96
- forwardConfig?: {
97
- ip: string;
98
- port: number;
99
- };
100
- acmeForwardConfig?: {
101
- ip: string;
102
- port: number;
103
- };
104
- }>;
105
- };
87
+ // ACME configuration options for SmartProxy
88
+ acme?: IAcmeOptions;
106
89
 
107
- // Legacy ACME configuration (deprecated, use port80HandlerConfig instead)
108
- acme?: {
109
- enabled?: boolean;
110
- port?: number;
111
- contactEmail?: string;
112
- useProduction?: boolean;
113
- renewThresholdDays?: number;
114
- autoRenew?: boolean;
115
- certificateStore?: string;
116
- skipConfiguredCerts?: boolean;
117
- };
90
+ /**
91
+ * Optional certificate provider callback. Return 'http01' to use HTTP-01 challenges,
92
+ * or a static certificate object for immediate provisioning.
93
+ */
94
+ certProvider?: (domain: string) => Promise<ISmartProxyCertProvisionObject>;
118
95
  }
119
96
 
120
97
  /**
@@ -1,6 +1,9 @@
1
1
  import * as plugins from '../plugins.js';
2
2
  import { NetworkProxy } from '../networkproxy/classes.np.networkproxy.js';
3
- import { Port80Handler, Port80HandlerEvents, type ICertificateData } from '../port80handler/classes.port80handler.js';
3
+ import { Port80Handler } from '../port80handler/classes.port80handler.js';
4
+ import { Port80HandlerEvents } from '../common/types.js';
5
+ import { subscribeToPort80Handler } from '../common/eventUtils.js';
6
+ import type { ICertificateData } from '../common/types.js';
4
7
  import type { IConnectionRecord, IPortProxySettings, IDomainConfig } from './classes.pp.interfaces.js';
5
8
 
6
9
  /**
@@ -18,9 +21,11 @@ export class NetworkProxyBridge {
18
21
  public setPort80Handler(handler: Port80Handler): void {
19
22
  this.port80Handler = handler;
20
23
 
21
- // Register for certificate events
22
- handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, this.handleCertificateEvent.bind(this));
23
- handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, this.handleCertificateEvent.bind(this));
24
+ // Subscribe to certificate events
25
+ subscribeToPort80Handler(handler, {
26
+ onCertificateIssued: this.handleCertificateEvent.bind(this),
27
+ onCertificateRenewed: this.handleCertificateEvent.bind(this)
28
+ });
24
29
 
25
30
  // If NetworkProxy is already initialized, connect it with Port80Handler
26
31
  if (this.networkProxy) {
@@ -43,10 +48,6 @@ export class NetworkProxyBridge {
43
48
  useExternalPort80Handler: !!this.port80Handler // Use Port80Handler if available
44
49
  };
45
50
 
46
- // Copy ACME settings for backward compatibility (if port80HandlerConfig not set)
47
- if (!this.settings.port80HandlerConfig && this.settings.acme) {
48
- networkProxyOptions.acme = { ...this.settings.acme };
49
- }
50
51
 
51
52
  this.networkProxy = new NetworkProxy(networkProxyOptions);
52
53
 
@@ -95,6 +96,17 @@ export class NetworkProxyBridge {
95
96
  }
96
97
  }
97
98
 
99
+ /**
100
+ * Apply an external (static) certificate into NetworkProxy
101
+ */
102
+ public applyExternalCertificate(data: ICertificateData): void {
103
+ if (!this.networkProxy) {
104
+ console.log(`NetworkProxy not initialized: cannot apply external certificate for ${data.domain}`);
105
+ return;
106
+ }
107
+ this.handleCertificateEvent(data);
108
+ }
109
+
98
110
  /**
99
111
  * Get the NetworkProxy instance
100
112
  */
@@ -277,7 +289,7 @@ export class NetworkProxyBridge {
277
289
  );
278
290
 
279
291
  // Log ACME-eligible domains
280
- const acmeEnabled = this.settings.port80HandlerConfig?.enabled || this.settings.acme?.enabled;
292
+ const acmeEnabled = !!this.settings.acme?.enabled;
281
293
  if (acmeEnabled) {
282
294
  const acmeEligibleDomains = proxyConfigs
283
295
  .filter((config) => !config.hostName.includes('*')) // Exclude wildcards
@@ -338,7 +350,7 @@ export class NetworkProxyBridge {
338
350
  return false;
339
351
  }
340
352
 
341
- if (!this.settings.port80HandlerConfig?.enabled && !this.settings.acme?.enabled) {
353
+ if (!this.settings.acme?.enabled) {
342
354
  console.log('Cannot request certificate - ACME is not enabled');
343
355
  return false;
344
356
  }
@@ -117,10 +117,6 @@ export class PortRangeManager {
117
117
  }
118
118
  }
119
119
 
120
- // Add ACME HTTP challenge port if enabled
121
- if (this.settings.acme?.enabled && this.settings.acme.port) {
122
- ports.add(this.settings.acme.port);
123
- }
124
120
 
125
121
  // Add global port ranges
126
122
  if (this.settings.globalPortRanges) {
@@ -202,12 +198,6 @@ export class PortRangeManager {
202
198
  warnings.push(`NetworkProxy port ${this.settings.networkProxyPort} is also used in port ranges`);
203
199
  }
204
200
 
205
- // Check ACME port
206
- if (this.settings.acme?.enabled && this.settings.acme.port) {
207
- if (portMappings.has(this.settings.acme.port)) {
208
- warnings.push(`ACME HTTP challenge port ${this.settings.acme.port} is also used in port ranges`);
209
- }
210
- }
211
201
 
212
202
  return warnings;
213
203
  }