@push.rocks/smartproxy 18.2.0 → 19.2.2

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 (63) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/common/eventUtils.d.ts +1 -2
  3. package/dist_ts/common/eventUtils.js +2 -1
  4. package/dist_ts/core/models/common-types.d.ts +1 -1
  5. package/dist_ts/core/models/common-types.js +1 -1
  6. package/dist_ts/core/utils/event-utils.d.ts +9 -9
  7. package/dist_ts/core/utils/event-utils.js +6 -14
  8. package/dist_ts/http/models/http-types.d.ts +13 -1
  9. package/dist_ts/http/models/http-types.js +1 -1
  10. package/dist_ts/index.d.ts +4 -6
  11. package/dist_ts/index.js +4 -10
  12. package/dist_ts/proxies/index.d.ts +3 -2
  13. package/dist_ts/proxies/index.js +4 -5
  14. package/dist_ts/proxies/network-proxy/certificate-manager.d.ts +31 -49
  15. package/dist_ts/proxies/network-proxy/certificate-manager.js +77 -374
  16. package/dist_ts/proxies/network-proxy/models/types.d.ts +12 -1
  17. package/dist_ts/proxies/network-proxy/models/types.js +1 -1
  18. package/dist_ts/proxies/network-proxy/network-proxy.d.ts +2 -7
  19. package/dist_ts/proxies/network-proxy/network-proxy.js +10 -19
  20. package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +6 -0
  21. package/dist_ts/proxies/smart-proxy/certificate-manager.js +24 -5
  22. package/dist_ts/proxies/smart-proxy/models/index.d.ts +1 -1
  23. package/dist_ts/proxies/smart-proxy/models/index.js +1 -5
  24. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +30 -1
  25. package/dist_ts/proxies/smart-proxy/route-manager.d.ts +4 -0
  26. package/dist_ts/proxies/smart-proxy/route-manager.js +7 -1
  27. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +4 -0
  28. package/dist_ts/proxies/smart-proxy/smart-proxy.js +112 -26
  29. package/package.json +1 -2
  30. package/readme.hints.md +31 -1
  31. package/readme.md +82 -6
  32. package/readme.plan.md +109 -1417
  33. package/ts/00_commitinfo_data.ts +1 -1
  34. package/ts/common/eventUtils.ts +2 -2
  35. package/ts/core/models/common-types.ts +1 -1
  36. package/ts/core/utils/event-utils.ts +12 -21
  37. package/ts/http/models/http-types.ts +8 -4
  38. package/ts/index.ts +11 -14
  39. package/ts/proxies/index.ts +7 -4
  40. package/ts/proxies/network-proxy/certificate-manager.ts +92 -417
  41. package/ts/proxies/network-proxy/models/types.ts +14 -2
  42. package/ts/proxies/network-proxy/network-proxy.ts +10 -19
  43. package/ts/proxies/smart-proxy/certificate-manager.ts +31 -4
  44. package/ts/proxies/smart-proxy/models/index.ts +2 -1
  45. package/ts/proxies/smart-proxy/models/interfaces.ts +31 -2
  46. package/ts/proxies/smart-proxy/models/route-types.ts +1 -1
  47. package/ts/proxies/smart-proxy/route-manager.ts +7 -0
  48. package/ts/proxies/smart-proxy/smart-proxy.ts +142 -25
  49. package/ts/certificate/acme/acme-factory.ts +0 -48
  50. package/ts/certificate/acme/challenge-handler.ts +0 -110
  51. package/ts/certificate/acme/index.ts +0 -3
  52. package/ts/certificate/events/certificate-events.ts +0 -36
  53. package/ts/certificate/index.ts +0 -75
  54. package/ts/certificate/models/certificate-types.ts +0 -109
  55. package/ts/certificate/providers/cert-provisioner.ts +0 -519
  56. package/ts/certificate/providers/index.ts +0 -3
  57. package/ts/certificate/storage/file-storage.ts +0 -234
  58. package/ts/certificate/storage/index.ts +0 -3
  59. package/ts/certificate/utils/certificate-helpers.ts +0 -50
  60. package/ts/http/port80/acme-interfaces.ts +0 -169
  61. package/ts/http/port80/challenge-responder.ts +0 -246
  62. package/ts/http/port80/index.ts +0 -13
  63. package/ts/http/port80/port80-handler.ts +0 -728
@@ -1,234 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import * as plugins from '../../plugins.js';
4
- import type { ICertificateData, ICertificates } from '../models/certificate-types.js';
5
- import { ensureCertificateDirectory } from '../utils/certificate-helpers.js';
6
-
7
- /**
8
- * FileStorage provides file system storage for certificates
9
- */
10
- export class FileStorage {
11
- private storageDir: string;
12
-
13
- /**
14
- * Creates a new file storage provider
15
- * @param storageDir Directory to store certificates
16
- */
17
- constructor(storageDir: string) {
18
- this.storageDir = path.resolve(storageDir);
19
- ensureCertificateDirectory(this.storageDir);
20
- }
21
-
22
- /**
23
- * Save a certificate to the file system
24
- * @param domain Domain name
25
- * @param certData Certificate data to save
26
- */
27
- public async saveCertificate(domain: string, certData: ICertificateData): Promise<void> {
28
- const sanitizedDomain = this.sanitizeDomain(domain);
29
- const certDir = path.join(this.storageDir, sanitizedDomain);
30
- ensureCertificateDirectory(certDir);
31
-
32
- const certPath = path.join(certDir, 'fullchain.pem');
33
- const keyPath = path.join(certDir, 'privkey.pem');
34
- const metaPath = path.join(certDir, 'metadata.json');
35
-
36
- // Write certificate and private key
37
- await fs.promises.writeFile(certPath, certData.certificate, 'utf8');
38
- await fs.promises.writeFile(keyPath, certData.privateKey, 'utf8');
39
-
40
- // Write metadata
41
- const metadata = {
42
- domain: certData.domain,
43
- expiryDate: certData.expiryDate.toISOString(),
44
- source: certData.source || 'unknown',
45
- issuedAt: new Date().toISOString()
46
- };
47
-
48
- await fs.promises.writeFile(
49
- metaPath,
50
- JSON.stringify(metadata, null, 2),
51
- 'utf8'
52
- );
53
- }
54
-
55
- /**
56
- * Load a certificate from the file system
57
- * @param domain Domain name
58
- * @returns Certificate data if found, null otherwise
59
- */
60
- public async loadCertificate(domain: string): Promise<ICertificateData | null> {
61
- const sanitizedDomain = this.sanitizeDomain(domain);
62
- const certDir = path.join(this.storageDir, sanitizedDomain);
63
-
64
- if (!fs.existsSync(certDir)) {
65
- return null;
66
- }
67
-
68
- const certPath = path.join(certDir, 'fullchain.pem');
69
- const keyPath = path.join(certDir, 'privkey.pem');
70
- const metaPath = path.join(certDir, 'metadata.json');
71
-
72
- try {
73
- // Check if all required files exist
74
- if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) {
75
- return null;
76
- }
77
-
78
- // Read certificate and private key
79
- const certificate = await fs.promises.readFile(certPath, 'utf8');
80
- const privateKey = await fs.promises.readFile(keyPath, 'utf8');
81
-
82
- // Try to read metadata if available
83
- let expiryDate = new Date();
84
- let source: 'static' | 'http01' | 'dns01' | undefined;
85
-
86
- if (fs.existsSync(metaPath)) {
87
- const metaContent = await fs.promises.readFile(metaPath, 'utf8');
88
- const metadata = JSON.parse(metaContent);
89
-
90
- if (metadata.expiryDate) {
91
- expiryDate = new Date(metadata.expiryDate);
92
- }
93
-
94
- if (metadata.source) {
95
- source = metadata.source as 'static' | 'http01' | 'dns01';
96
- }
97
- }
98
-
99
- return {
100
- domain,
101
- certificate,
102
- privateKey,
103
- expiryDate,
104
- source
105
- };
106
- } catch (error) {
107
- console.error(`Error loading certificate for ${domain}:`, error);
108
- return null;
109
- }
110
- }
111
-
112
- /**
113
- * Delete a certificate from the file system
114
- * @param domain Domain name
115
- */
116
- public async deleteCertificate(domain: string): Promise<boolean> {
117
- const sanitizedDomain = this.sanitizeDomain(domain);
118
- const certDir = path.join(this.storageDir, sanitizedDomain);
119
-
120
- if (!fs.existsSync(certDir)) {
121
- return false;
122
- }
123
-
124
- try {
125
- // Recursively delete the certificate directory
126
- await this.deleteDirectory(certDir);
127
- return true;
128
- } catch (error) {
129
- console.error(`Error deleting certificate for ${domain}:`, error);
130
- return false;
131
- }
132
- }
133
-
134
- /**
135
- * List all domains with stored certificates
136
- * @returns Array of domain names
137
- */
138
- public async listCertificates(): Promise<string[]> {
139
- try {
140
- const entries = await fs.promises.readdir(this.storageDir, { withFileTypes: true });
141
- return entries
142
- .filter(entry => entry.isDirectory())
143
- .map(entry => entry.name);
144
- } catch (error) {
145
- console.error('Error listing certificates:', error);
146
- return [];
147
- }
148
- }
149
-
150
- /**
151
- * Check if a certificate is expiring soon
152
- * @param domain Domain name
153
- * @param thresholdDays Days threshold to consider expiring
154
- * @returns Information about expiring certificate or null
155
- */
156
- public async isExpiringSoon(
157
- domain: string,
158
- thresholdDays: number = 30
159
- ): Promise<{ domain: string; expiryDate: Date; daysRemaining: number } | null> {
160
- const certData = await this.loadCertificate(domain);
161
-
162
- if (!certData) {
163
- return null;
164
- }
165
-
166
- const now = new Date();
167
- const expiryDate = certData.expiryDate;
168
- const timeRemaining = expiryDate.getTime() - now.getTime();
169
- const daysRemaining = Math.floor(timeRemaining / (1000 * 60 * 60 * 24));
170
-
171
- if (daysRemaining <= thresholdDays) {
172
- return {
173
- domain,
174
- expiryDate,
175
- daysRemaining
176
- };
177
- }
178
-
179
- return null;
180
- }
181
-
182
- /**
183
- * Check all certificates for expiration
184
- * @param thresholdDays Days threshold to consider expiring
185
- * @returns List of expiring certificates
186
- */
187
- public async getExpiringCertificates(
188
- thresholdDays: number = 30
189
- ): Promise<Array<{ domain: string; expiryDate: Date; daysRemaining: number }>> {
190
- const domains = await this.listCertificates();
191
- const expiringCerts = [];
192
-
193
- for (const domain of domains) {
194
- const expiring = await this.isExpiringSoon(domain, thresholdDays);
195
- if (expiring) {
196
- expiringCerts.push(expiring);
197
- }
198
- }
199
-
200
- return expiringCerts;
201
- }
202
-
203
- /**
204
- * Delete a directory recursively
205
- * @param directoryPath Directory to delete
206
- */
207
- private async deleteDirectory(directoryPath: string): Promise<void> {
208
- if (fs.existsSync(directoryPath)) {
209
- const entries = await fs.promises.readdir(directoryPath, { withFileTypes: true });
210
-
211
- for (const entry of entries) {
212
- const fullPath = path.join(directoryPath, entry.name);
213
-
214
- if (entry.isDirectory()) {
215
- await this.deleteDirectory(fullPath);
216
- } else {
217
- await fs.promises.unlink(fullPath);
218
- }
219
- }
220
-
221
- await fs.promises.rmdir(directoryPath);
222
- }
223
- }
224
-
225
- /**
226
- * Sanitize a domain name for use as a directory name
227
- * @param domain Domain name
228
- * @returns Sanitized domain name
229
- */
230
- private sanitizeDomain(domain: string): string {
231
- // Replace wildcard and any invalid filesystem characters
232
- return domain.replace(/\*/g, '_wildcard_').replace(/[/\\:*?"<>|]/g, '_');
233
- }
234
- }
@@ -1,3 +0,0 @@
1
- /**
2
- * Certificate storage mechanisms
3
- */
@@ -1,50 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import type { ICertificates } from '../models/certificate-types.js';
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
-
8
- /**
9
- * Loads the default SSL certificates from the assets directory
10
- * @returns The certificate key pair
11
- */
12
- export function loadDefaultCertificates(): ICertificates {
13
- try {
14
- // Need to adjust path from /ts/certificate/utils to /assets/certs
15
- const certPath = path.join(__dirname, '..', '..', '..', 'assets', 'certs');
16
- const privateKey = fs.readFileSync(path.join(certPath, 'key.pem'), 'utf8');
17
- const publicKey = fs.readFileSync(path.join(certPath, 'cert.pem'), 'utf8');
18
-
19
- if (!privateKey || !publicKey) {
20
- throw new Error('Failed to load default certificates');
21
- }
22
-
23
- return {
24
- privateKey,
25
- publicKey
26
- };
27
- } catch (error) {
28
- console.error('Error loading default certificates:', error);
29
- throw error;
30
- }
31
- }
32
-
33
- /**
34
- * Checks if a certificate file exists at the specified path
35
- * @param certPath Path to check for certificate
36
- * @returns True if the certificate exists, false otherwise
37
- */
38
- export function certificateExists(certPath: string): boolean {
39
- return fs.existsSync(certPath);
40
- }
41
-
42
- /**
43
- * Ensures the certificate directory exists
44
- * @param dirPath Path to the certificate directory
45
- */
46
- export function ensureCertificateDirectory(dirPath: string): void {
47
- if (!fs.existsSync(dirPath)) {
48
- fs.mkdirSync(dirPath, { recursive: true });
49
- }
50
- }
@@ -1,169 +0,0 @@
1
- /**
2
- * Type definitions for SmartAcme interfaces used by ChallengeResponder
3
- * These reflect the actual SmartAcme API based on the documentation
4
- *
5
- * Also includes route-based interfaces for Port80Handler to extract domains
6
- * that need certificate management from route configurations.
7
- */
8
- import * as plugins from '../../plugins.js';
9
- import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js';
10
-
11
- /**
12
- * Structure for SmartAcme certificate result
13
- */
14
- export interface ISmartAcmeCert {
15
- id?: string;
16
- domainName: string;
17
- created?: number | Date | string;
18
- privateKey: string;
19
- publicKey: string;
20
- csr?: string;
21
- validUntil: number | Date | string;
22
- }
23
-
24
- /**
25
- * Structure for SmartAcme options
26
- */
27
- export interface ISmartAcmeOptions {
28
- accountEmail: string;
29
- certManager: ICertManager;
30
- environment: 'production' | 'integration';
31
- challengeHandlers: IChallengeHandler<any>[];
32
- challengePriority?: string[];
33
- retryOptions?: {
34
- retries?: number;
35
- factor?: number;
36
- minTimeoutMs?: number;
37
- maxTimeoutMs?: number;
38
- };
39
- }
40
-
41
- /**
42
- * Interface for certificate manager
43
- */
44
- export interface ICertManager {
45
- init(): Promise<void>;
46
- get(domainName: string): Promise<ISmartAcmeCert | null>;
47
- put(cert: ISmartAcmeCert): Promise<ISmartAcmeCert>;
48
- delete(domainName: string): Promise<void>;
49
- close?(): Promise<void>;
50
- }
51
-
52
- /**
53
- * Interface for challenge handler
54
- */
55
- export interface IChallengeHandler<T> {
56
- getSupportedTypes(): string[];
57
- prepare(ch: T): Promise<void>;
58
- verify?(ch: T): Promise<void>;
59
- cleanup(ch: T): Promise<void>;
60
- checkWetherDomainIsSupported(domain: string): Promise<boolean>;
61
- }
62
-
63
- /**
64
- * HTTP-01 challenge type
65
- */
66
- export interface IHttp01Challenge {
67
- type: string; // 'http-01'
68
- token: string;
69
- keyAuthorization: string;
70
- webPath: string;
71
- }
72
-
73
- /**
74
- * HTTP-01 Memory Handler Interface
75
- */
76
- export interface IHttp01MemoryHandler extends IChallengeHandler<IHttp01Challenge> {
77
- handleRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse, next?: () => void): void;
78
- }
79
-
80
- /**
81
- * SmartAcme main class interface
82
- */
83
- export interface ISmartAcme {
84
- start(): Promise<void>;
85
- stop(): Promise<void>;
86
- getCertificateForDomain(domain: string): Promise<ISmartAcmeCert>;
87
- on?(event: string, listener: (data: any) => void): void;
88
- eventEmitter?: plugins.EventEmitter;
89
- }
90
-
91
- /**
92
- * Port80Handler route options
93
- */
94
- export interface IPort80RouteOptions {
95
- // The domain for the certificate
96
- domain: string;
97
-
98
- // Whether to redirect HTTP to HTTPS
99
- sslRedirect: boolean;
100
-
101
- // Whether to enable ACME certificate management
102
- acmeMaintenance: boolean;
103
-
104
- // Optional target for forwarding HTTP requests
105
- forward?: {
106
- ip: string;
107
- port: number;
108
- };
109
-
110
- // Optional target for forwarding ACME challenge requests
111
- acmeForward?: {
112
- ip: string;
113
- port: number;
114
- };
115
-
116
- // Reference to the route that requested this certificate
117
- routeReference?: {
118
- routeId?: string;
119
- routeName?: string;
120
- };
121
- }
122
-
123
- /**
124
- * Extract domains that need certificate management from routes
125
- * @param routes Route configurations to extract domains from
126
- * @returns Array of Port80RouteOptions for each domain
127
- */
128
- export function extractPort80RoutesFromRoutes(routes: IRouteConfig[]): IPort80RouteOptions[] {
129
- const result: IPort80RouteOptions[] = [];
130
-
131
- for (const route of routes) {
132
- // Skip routes that don't have domains or TLS configuration
133
- if (!route.match.domains || !route.action.tls) continue;
134
-
135
- // Skip routes that don't terminate TLS
136
- if (route.action.tls.mode !== 'terminate' && route.action.tls.mode !== 'terminate-and-reencrypt') continue;
137
-
138
- // Only routes with automatic certificates need ACME
139
- if (route.action.tls.certificate !== 'auto') continue;
140
-
141
- // Get domains from route
142
- const domains = Array.isArray(route.match.domains)
143
- ? route.match.domains
144
- : [route.match.domains];
145
-
146
- // Create Port80RouteOptions for each domain
147
- for (const domain of domains) {
148
- // Skip wildcards (we can't get certificates for them)
149
- if (domain.includes('*')) continue;
150
-
151
- // Create Port80RouteOptions
152
- const options: IPort80RouteOptions = {
153
- domain,
154
- sslRedirect: true, // Default to true for HTTPS routes
155
- acmeMaintenance: true, // Default to true for auto certificates
156
-
157
- // Add route reference
158
- routeReference: {
159
- routeName: route.name
160
- }
161
- };
162
-
163
- // Add domain to result
164
- result.push(options);
165
- }
166
- }
167
-
168
- return result;
169
- }
@@ -1,246 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import { IncomingMessage, ServerResponse } from 'http';
3
- import {
4
- CertificateEvents
5
- } from '../../certificate/events/certificate-events.js';
6
- import type {
7
- ICertificateData,
8
- ICertificateFailure,
9
- ICertificateExpiring
10
- } from '../../certificate/models/certificate-types.js';
11
- import type {
12
- ISmartAcme,
13
- ISmartAcmeCert,
14
- ISmartAcmeOptions,
15
- IHttp01MemoryHandler
16
- } from './acme-interfaces.js';
17
-
18
- /**
19
- * ChallengeResponder handles ACME HTTP-01 challenges by leveraging SmartAcme
20
- * It acts as a bridge between the HTTP server and the ACME challenge verification process
21
- */
22
- export class ChallengeResponder extends plugins.EventEmitter {
23
- private smartAcme: ISmartAcme | null = null;
24
- private http01Handler: IHttp01MemoryHandler | null = null;
25
-
26
- /**
27
- * Creates a new challenge responder
28
- * @param useProduction Whether to use production ACME servers
29
- * @param email Account email for ACME
30
- * @param certificateStore Directory to store certificates
31
- */
32
- constructor(
33
- private readonly useProduction: boolean = false,
34
- private readonly email: string = 'admin@example.com',
35
- private readonly certificateStore: string = './certs'
36
- ) {
37
- super();
38
- }
39
-
40
- /**
41
- * Initialize the ACME client
42
- */
43
- public async initialize(): Promise<void> {
44
- try {
45
- // Create the HTTP-01 memory handler from SmartACME
46
- this.http01Handler = new plugins.smartacme.handlers.Http01MemoryHandler();
47
-
48
- // Ensure certificate store directory exists
49
- await this.ensureCertificateStore();
50
-
51
- // Create a MemoryCertManager for certificate storage
52
- const certManager = new plugins.smartacme.certmanagers.MemoryCertManager();
53
-
54
- // Initialize the SmartACME client with appropriate options
55
- this.smartAcme = new plugins.smartacme.SmartAcme({
56
- accountEmail: this.email,
57
- certManager: certManager,
58
- environment: this.useProduction ? 'production' : 'integration',
59
- challengeHandlers: [this.http01Handler],
60
- challengePriority: ['http-01']
61
- });
62
-
63
- // Set up event forwarding from SmartAcme
64
- this.setupEventListeners();
65
-
66
- // Start the SmartACME client
67
- await this.smartAcme.start();
68
- console.log('ACME client initialized successfully');
69
- } catch (error) {
70
- const errorMessage = error instanceof Error ? error.message : String(error);
71
- throw new Error(`Failed to initialize ACME client: ${errorMessage}`);
72
- }
73
- }
74
-
75
- /**
76
- * Ensure the certificate store directory exists
77
- */
78
- private async ensureCertificateStore(): Promise<void> {
79
- try {
80
- await plugins.fs.promises.mkdir(this.certificateStore, { recursive: true });
81
- } catch (error) {
82
- const errorMessage = error instanceof Error ? error.message : String(error);
83
- throw new Error(`Failed to create certificate store: ${errorMessage}`);
84
- }
85
- }
86
-
87
- /**
88
- * Setup event listeners to forward SmartACME events to our own event emitter
89
- */
90
- private setupEventListeners(): void {
91
- if (!this.smartAcme) return;
92
-
93
- const setupEvents = (emitter: { on: (event: string, listener: (data: any) => void) => void }) => {
94
- // Forward certificate events
95
- emitter.on('certificate', (data: any) => {
96
- const isRenewal = !!data.isRenewal;
97
-
98
- const certData: ICertificateData = {
99
- domain: data.domainName || data.domain,
100
- certificate: data.publicKey || data.cert,
101
- privateKey: data.privateKey || data.key,
102
- expiryDate: new Date(data.validUntil || data.expiryDate || Date.now()),
103
- source: 'http01',
104
- isRenewal
105
- };
106
-
107
- const eventType = isRenewal
108
- ? CertificateEvents.CERTIFICATE_RENEWED
109
- : CertificateEvents.CERTIFICATE_ISSUED;
110
-
111
- this.emit(eventType, certData);
112
- });
113
-
114
- // Forward error events
115
- emitter.on('error', (error: any) => {
116
- const domain = error.domainName || error.domain || 'unknown';
117
- const failureData: ICertificateFailure = {
118
- domain,
119
- error: error.message || String(error),
120
- isRenewal: !!error.isRenewal
121
- };
122
-
123
- this.emit(CertificateEvents.CERTIFICATE_FAILED, failureData);
124
- });
125
- };
126
-
127
- // Check for direct event methods on SmartAcme
128
- if (typeof this.smartAcme.on === 'function') {
129
- setupEvents(this.smartAcme as any);
130
- }
131
- // Check for eventEmitter property
132
- else if (this.smartAcme.eventEmitter) {
133
- setupEvents(this.smartAcme.eventEmitter);
134
- }
135
- // If no proper event handling, log a warning
136
- else {
137
- console.warn('SmartAcme instance does not support expected event interface - events may not be forwarded');
138
- }
139
- }
140
-
141
- /**
142
- * Handle HTTP request by checking if it's an ACME challenge
143
- * @param req HTTP request object
144
- * @param res HTTP response object
145
- * @returns true if the request was handled, false otherwise
146
- */
147
- public handleRequest(req: IncomingMessage, res: ServerResponse): boolean {
148
- if (!this.http01Handler) return false;
149
-
150
- // Check if this is an ACME challenge request (/.well-known/acme-challenge/*)
151
- const url = req.url || '';
152
- if (url.startsWith('/.well-known/acme-challenge/')) {
153
- try {
154
- // Delegate to the HTTP-01 memory handler, which knows how to serve challenges
155
- this.http01Handler.handleRequest(req, res);
156
- return true;
157
- } catch (error) {
158
- console.error('Error handling ACME challenge:', error);
159
- // If there was an error, send a 404 response
160
- res.writeHead(404);
161
- res.end('Not found');
162
- return true;
163
- }
164
- }
165
-
166
- return false;
167
- }
168
-
169
- /**
170
- * Request a certificate for a domain
171
- * @param domain Domain name to request a certificate for
172
- * @param isRenewal Whether this is a renewal request
173
- */
174
- public async requestCertificate(domain: string, isRenewal: boolean = false): Promise<ICertificateData> {
175
- if (!this.smartAcme) {
176
- throw new Error('ACME client not initialized');
177
- }
178
-
179
- try {
180
- // Request certificate using SmartACME
181
- const certObj = await this.smartAcme.getCertificateForDomain(domain);
182
-
183
- // Convert the certificate object to our CertificateData format
184
- const certData: ICertificateData = {
185
- domain,
186
- certificate: certObj.publicKey,
187
- privateKey: certObj.privateKey,
188
- expiryDate: new Date(certObj.validUntil),
189
- source: 'http01',
190
- isRenewal
191
- };
192
-
193
- return certData;
194
- } catch (error) {
195
- // Create failure object
196
- const failure: ICertificateFailure = {
197
- domain,
198
- error: error instanceof Error ? error.message : String(error),
199
- isRenewal
200
- };
201
-
202
- // Emit failure event
203
- this.emit(CertificateEvents.CERTIFICATE_FAILED, failure);
204
-
205
- // Rethrow with more context
206
- throw new Error(`Failed to ${isRenewal ? 'renew' : 'obtain'} certificate for ${domain}: ${
207
- error instanceof Error ? error.message : String(error)
208
- }`);
209
- }
210
- }
211
-
212
- /**
213
- * Check if a certificate is expiring soon and trigger renewal if needed
214
- * @param domain Domain name
215
- * @param certificate Certificate data
216
- * @param thresholdDays Days before expiry to trigger renewal
217
- */
218
- public checkCertificateExpiry(
219
- domain: string,
220
- certificate: ICertificateData,
221
- thresholdDays: number = 30
222
- ): void {
223
- if (!certificate.expiryDate) return;
224
-
225
- const now = new Date();
226
- const expiryDate = certificate.expiryDate;
227
- const daysDifference = Math.floor((expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
228
-
229
- if (daysDifference <= thresholdDays) {
230
- const expiryInfo: ICertificateExpiring = {
231
- domain,
232
- expiryDate,
233
- daysRemaining: daysDifference
234
- };
235
-
236
- this.emit(CertificateEvents.CERTIFICATE_EXPIRING, expiryInfo);
237
-
238
- // Automatically attempt renewal if expiring
239
- if (this.smartAcme) {
240
- this.requestCertificate(domain, true).catch(error => {
241
- console.error(`Failed to auto-renew certificate for ${domain}:`, error);
242
- });
243
- }
244
- }
245
- }
246
- }