@push.rocks/smartproxy 3.25.4 → 3.28.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.
@@ -1,6 +1,8 @@
1
- import * as http from 'http';
2
- import * as acme from 'acme-client';
1
+ import * as plugins from './plugins.js';
3
2
 
3
+ /**
4
+ * Represents a domain certificate with various status information
5
+ */
4
6
  interface IDomainCertificate {
5
7
  certObtained: boolean;
6
8
  obtainingInProgress: boolean;
@@ -8,27 +10,147 @@ interface IDomainCertificate {
8
10
  privateKey?: string;
9
11
  challengeToken?: string;
10
12
  challengeKeyAuthorization?: string;
13
+ expiryDate?: Date;
14
+ lastRenewalAttempt?: Date;
11
15
  }
12
16
 
13
- export class Port80Handler {
17
+ /**
18
+ * Configuration options for the ACME Certificate Manager
19
+ */
20
+ interface IAcmeCertManagerOptions {
21
+ port?: number;
22
+ contactEmail?: string;
23
+ useProduction?: boolean;
24
+ renewThresholdDays?: number;
25
+ httpsRedirectPort?: number;
26
+ renewCheckIntervalHours?: number;
27
+ }
28
+
29
+ /**
30
+ * Certificate data that can be emitted via events or set from outside
31
+ */
32
+ interface ICertificateData {
33
+ domain: string;
34
+ certificate: string;
35
+ privateKey: string;
36
+ expiryDate: Date;
37
+ }
38
+
39
+ /**
40
+ * Events emitted by the ACME Certificate Manager
41
+ */
42
+ export enum CertManagerEvents {
43
+ CERTIFICATE_ISSUED = 'certificate-issued',
44
+ CERTIFICATE_RENEWED = 'certificate-renewed',
45
+ CERTIFICATE_FAILED = 'certificate-failed',
46
+ CERTIFICATE_EXPIRING = 'certificate-expiring',
47
+ MANAGER_STARTED = 'manager-started',
48
+ MANAGER_STOPPED = 'manager-stopped',
49
+ }
50
+
51
+ /**
52
+ * Improved ACME Certificate Manager with event emission and external certificate management
53
+ */
54
+ export class AcmeCertManager extends plugins.EventEmitter {
14
55
  private domainCertificates: Map<string, IDomainCertificate>;
15
- private server: http.Server;
16
- private acmeClient: acme.Client | null = null;
56
+ private server: plugins.http.Server | null = null;
57
+ private acmeClient: plugins.acme.Client | null = null;
17
58
  private accountKey: string | null = null;
59
+ private renewalTimer: NodeJS.Timeout | null = null;
60
+ private isShuttingDown: boolean = false;
61
+ private options: Required<IAcmeCertManagerOptions>;
18
62
 
19
- constructor() {
63
+ /**
64
+ * Creates a new ACME Certificate Manager
65
+ * @param options Configuration options
66
+ */
67
+ constructor(options: IAcmeCertManagerOptions = {}) {
68
+ super();
20
69
  this.domainCertificates = new Map<string, IDomainCertificate>();
70
+
71
+ // Default options
72
+ this.options = {
73
+ port: options.port ?? 80,
74
+ contactEmail: options.contactEmail ?? 'admin@example.com',
75
+ useProduction: options.useProduction ?? false, // Safer default: staging
76
+ renewThresholdDays: options.renewThresholdDays ?? 30,
77
+ httpsRedirectPort: options.httpsRedirectPort ?? 443,
78
+ renewCheckIntervalHours: options.renewCheckIntervalHours ?? 24,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Starts the HTTP server for ACME challenges
84
+ */
85
+ public async start(): Promise<void> {
86
+ if (this.server) {
87
+ throw new Error('Server is already running');
88
+ }
89
+
90
+ if (this.isShuttingDown) {
91
+ throw new Error('Server is shutting down');
92
+ }
21
93
 
22
- // Create and start an HTTP server on port 80.
23
- this.server = http.createServer((req, res) => this.handleRequest(req, res));
24
- this.server.listen(80, () => {
25
- console.log('Port80Handler is listening on port 80');
94
+ return new Promise((resolve, reject) => {
95
+ try {
96
+ this.server = plugins.http.createServer((req, res) => this.handleRequest(req, res));
97
+
98
+ this.server.on('error', (error: NodeJS.ErrnoException) => {
99
+ if (error.code === 'EACCES') {
100
+ reject(new Error(`Permission denied to bind to port ${this.options.port}. Try running with elevated privileges or use a port > 1024.`));
101
+ } else if (error.code === 'EADDRINUSE') {
102
+ reject(new Error(`Port ${this.options.port} is already in use.`));
103
+ } else {
104
+ reject(error);
105
+ }
106
+ });
107
+
108
+ this.server.listen(this.options.port, () => {
109
+ console.log(`AcmeCertManager is listening on port ${this.options.port}`);
110
+ this.startRenewalTimer();
111
+ this.emit(CertManagerEvents.MANAGER_STARTED, this.options.port);
112
+ resolve();
113
+ });
114
+ } catch (error) {
115
+ reject(error);
116
+ }
26
117
  });
27
118
  }
28
119
 
29
120
  /**
30
- * Adds a domain to be managed.
31
- * @param domain The domain to add.
121
+ * Stops the HTTP server and renewal timer
122
+ */
123
+ public async stop(): Promise<void> {
124
+ if (!this.server) {
125
+ return;
126
+ }
127
+
128
+ this.isShuttingDown = true;
129
+
130
+ // Stop the renewal timer
131
+ if (this.renewalTimer) {
132
+ clearInterval(this.renewalTimer);
133
+ this.renewalTimer = null;
134
+ }
135
+
136
+ return new Promise<void>((resolve) => {
137
+ if (this.server) {
138
+ this.server.close(() => {
139
+ this.server = null;
140
+ this.isShuttingDown = false;
141
+ this.emit(CertManagerEvents.MANAGER_STOPPED);
142
+ resolve();
143
+ });
144
+ } else {
145
+ this.isShuttingDown = false;
146
+ resolve();
147
+ }
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Adds a domain to be managed for certificates
153
+ * @param domain The domain to add
32
154
  */
33
155
  public addDomain(domain: string): void {
34
156
  if (!this.domainCertificates.has(domain)) {
@@ -38,55 +160,126 @@ export class Port80Handler {
38
160
  }
39
161
 
40
162
  /**
41
- * Removes a domain from management.
42
- * @param domain The domain to remove.
163
+ * Removes a domain from management
164
+ * @param domain The domain to remove
43
165
  */
44
166
  public removeDomain(domain: string): void {
45
167
  if (this.domainCertificates.delete(domain)) {
46
168
  console.log(`Domain removed: ${domain}`);
47
169
  }
48
170
  }
171
+
172
+ /**
173
+ * Sets a certificate for a domain directly (for externally obtained certificates)
174
+ * @param domain The domain for the certificate
175
+ * @param certificate The certificate (PEM format)
176
+ * @param privateKey The private key (PEM format)
177
+ * @param expiryDate Optional expiry date
178
+ */
179
+ public setCertificate(domain: string, certificate: string, privateKey: string, expiryDate?: Date): void {
180
+ let domainInfo = this.domainCertificates.get(domain);
181
+
182
+ if (!domainInfo) {
183
+ domainInfo = { certObtained: false, obtainingInProgress: false };
184
+ this.domainCertificates.set(domain, domainInfo);
185
+ }
186
+
187
+ domainInfo.certificate = certificate;
188
+ domainInfo.privateKey = privateKey;
189
+ domainInfo.certObtained = true;
190
+ domainInfo.obtainingInProgress = false;
191
+
192
+ if (expiryDate) {
193
+ domainInfo.expiryDate = expiryDate;
194
+ } else {
195
+ // Try to extract expiry date from certificate
196
+ try {
197
+ // This is a simplistic approach - in a real implementation, use a proper
198
+ // certificate parsing library like node-forge or x509
199
+ const matches = certificate.match(/Not After\s*:\s*(.*?)(?:\n|$)/i);
200
+ if (matches && matches[1]) {
201
+ domainInfo.expiryDate = new Date(matches[1]);
202
+ }
203
+ } catch (error) {
204
+ console.warn(`Failed to extract expiry date from certificate for ${domain}`);
205
+ }
206
+ }
207
+
208
+ console.log(`Certificate set for ${domain}`);
209
+
210
+ // Emit certificate event
211
+ this.emitCertificateEvent(CertManagerEvents.CERTIFICATE_ISSUED, {
212
+ domain,
213
+ certificate,
214
+ privateKey,
215
+ expiryDate: domainInfo.expiryDate || new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) // 90 days default
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Gets the certificate for a domain if it exists
221
+ * @param domain The domain to get the certificate for
222
+ */
223
+ public getCertificate(domain: string): ICertificateData | null {
224
+ const domainInfo = this.domainCertificates.get(domain);
225
+
226
+ if (!domainInfo || !domainInfo.certObtained || !domainInfo.certificate || !domainInfo.privateKey) {
227
+ return null;
228
+ }
229
+
230
+ return {
231
+ domain,
232
+ certificate: domainInfo.certificate,
233
+ privateKey: domainInfo.privateKey,
234
+ expiryDate: domainInfo.expiryDate || new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) // 90 days default
235
+ };
236
+ }
49
237
 
50
238
  /**
51
- * Lazy initialization of the ACME client.
52
- * Uses Let’s Encrypt’s production directory (for testing you might switch to staging).
239
+ * Lazy initialization of the ACME client
240
+ * @returns An ACME client instance
53
241
  */
54
- private async getAcmeClient(): Promise<acme.Client> {
242
+ private async getAcmeClient(): Promise<plugins.acme.Client> {
55
243
  if (this.acmeClient) {
56
244
  return this.acmeClient;
57
245
  }
58
- // Generate a new account key and convert Buffer to string.
59
- this.accountKey = (await acme.forge.createPrivateKey()).toString();
60
- this.acmeClient = new acme.Client({
61
- directoryUrl: acme.directory.letsencrypt.production, // Use production for a real certificate
62
- // For testing, you could use:
63
- // directoryUrl: acme.directory.letsencrypt.staging,
246
+
247
+ // Generate a new account key
248
+ this.accountKey = (await plugins.acme.forge.createPrivateKey()).toString();
249
+
250
+ this.acmeClient = new plugins.acme.Client({
251
+ directoryUrl: this.options.useProduction
252
+ ? plugins.acme.directory.letsencrypt.production
253
+ : plugins.acme.directory.letsencrypt.staging,
64
254
  accountKey: this.accountKey,
65
255
  });
66
- // Create a new account. Make sure to update the contact email.
256
+
257
+ // Create a new account
67
258
  await this.acmeClient.createAccount({
68
259
  termsOfServiceAgreed: true,
69
- contact: ['mailto:admin@example.com'],
260
+ contact: [`mailto:${this.options.contactEmail}`],
70
261
  });
262
+
71
263
  return this.acmeClient;
72
264
  }
73
265
 
74
266
  /**
75
- * Handles incoming HTTP requests on port 80.
76
- * If the request is for an ACME challenge, it responds with the key authorization.
77
- * If the domain has a certificate, it redirects to HTTPS; otherwise, it initiates certificate issuance.
267
+ * Handles incoming HTTP requests
268
+ * @param req The HTTP request
269
+ * @param res The HTTP response
78
270
  */
79
- private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {
271
+ private handleRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
80
272
  const hostHeader = req.headers.host;
81
273
  if (!hostHeader) {
82
274
  res.statusCode = 400;
83
275
  res.end('Bad Request: Host header is missing');
84
276
  return;
85
277
  }
278
+
86
279
  // Extract domain (ignoring any port in the Host header)
87
280
  const domain = hostHeader.split(':')[0];
88
281
 
89
- // If the request is for an ACME HTTP-01 challenge, handle it.
282
+ // If the request is for an ACME HTTP-01 challenge, handle it
90
283
  if (req.url && req.url.startsWith('/.well-known/acme-challenge/')) {
91
284
  this.handleAcmeChallenge(req, res, domain);
92
285
  return;
@@ -100,38 +293,47 @@ export class Port80Handler {
100
293
 
101
294
  const domainInfo = this.domainCertificates.get(domain)!;
102
295
 
103
- // If certificate exists, redirect to HTTPS on port 443.
296
+ // If certificate exists, redirect to HTTPS
104
297
  if (domainInfo.certObtained) {
105
- const redirectUrl = `https://${domain}:443${req.url}`;
298
+ const httpsPort = this.options.httpsRedirectPort;
299
+ const portSuffix = httpsPort === 443 ? '' : `:${httpsPort}`;
300
+ const redirectUrl = `https://${domain}${portSuffix}${req.url || '/'}`;
301
+
106
302
  res.statusCode = 301;
107
303
  res.setHeader('Location', redirectUrl);
108
304
  res.end(`Redirecting to ${redirectUrl}`);
109
305
  } else {
110
- // Trigger certificate issuance if not already running.
306
+ // Trigger certificate issuance if not already running
111
307
  if (!domainInfo.obtainingInProgress) {
112
- domainInfo.obtainingInProgress = true;
113
308
  this.obtainCertificate(domain).catch(err => {
309
+ this.emit(CertManagerEvents.CERTIFICATE_FAILED, { domain, error: err.message });
114
310
  console.error(`Error obtaining certificate for ${domain}:`, err);
115
311
  });
116
312
  }
313
+
117
314
  res.statusCode = 503;
118
315
  res.end('Certificate issuance in progress, please try again later.');
119
316
  }
120
317
  }
121
318
 
122
319
  /**
123
- * Serves the ACME HTTP-01 challenge response.
320
+ * Serves the ACME HTTP-01 challenge response
321
+ * @param req The HTTP request
322
+ * @param res The HTTP response
323
+ * @param domain The domain for the challenge
124
324
  */
125
- private handleAcmeChallenge(req: http.IncomingMessage, res: http.ServerResponse, domain: string): void {
325
+ private handleAcmeChallenge(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse, domain: string): void {
126
326
  const domainInfo = this.domainCertificates.get(domain);
127
327
  if (!domainInfo) {
128
328
  res.statusCode = 404;
129
329
  res.end('Domain not configured');
130
330
  return;
131
331
  }
132
- // The token is the last part of the URL.
332
+
333
+ // The token is the last part of the URL
133
334
  const urlParts = req.url?.split('/');
134
335
  const token = urlParts ? urlParts[urlParts.length - 1] : '';
336
+
135
337
  if (domainInfo.challengeToken === token && domainInfo.challengeKeyAuthorization) {
136
338
  res.statusCode = 200;
137
339
  res.setHeader('Content-Type', 'text/plain');
@@ -144,71 +346,214 @@ export class Port80Handler {
144
346
  }
145
347
 
146
348
  /**
147
- * Uses acme-client to perform a full ACME HTTP-01 challenge to obtain a certificate.
148
- * On success, it stores the certificate and key in memory and clears challenge data.
349
+ * Obtains a certificate for a domain using ACME HTTP-01 challenge
350
+ * @param domain The domain to obtain a certificate for
351
+ * @param isRenewal Whether this is a renewal attempt
149
352
  */
150
- private async obtainCertificate(domain: string): Promise<void> {
353
+ private async obtainCertificate(domain: string, isRenewal: boolean = false): Promise<void> {
354
+ // Get the domain info
355
+ const domainInfo = this.domainCertificates.get(domain);
356
+ if (!domainInfo) {
357
+ throw new Error(`Domain not found: ${domain}`);
358
+ }
359
+
360
+ // Prevent concurrent certificate issuance
361
+ if (domainInfo.obtainingInProgress) {
362
+ console.log(`Certificate issuance already in progress for ${domain}`);
363
+ return;
364
+ }
365
+
366
+ domainInfo.obtainingInProgress = true;
367
+ domainInfo.lastRenewalAttempt = new Date();
368
+
151
369
  try {
152
370
  const client = await this.getAcmeClient();
153
371
 
154
- // Create a new order for the domain.
372
+ // Create a new order for the domain
155
373
  const order = await client.createOrder({
156
374
  identifiers: [{ type: 'dns', value: domain }],
157
375
  });
158
376
 
159
- // Get the authorizations for the order.
377
+ // Get the authorizations for the order
160
378
  const authorizations = await client.getAuthorizations(order);
379
+
161
380
  for (const authz of authorizations) {
162
381
  const challenge = authz.challenges.find(ch => ch.type === 'http-01');
163
382
  if (!challenge) {
164
383
  throw new Error('HTTP-01 challenge not found');
165
384
  }
166
- // Get the key authorization for the challenge.
385
+
386
+ // Get the key authorization for the challenge
167
387
  const keyAuthorization = await client.getChallengeKeyAuthorization(challenge);
168
- const domainInfo = this.domainCertificates.get(domain)!;
388
+
389
+ // Store the challenge data
169
390
  domainInfo.challengeToken = challenge.token;
170
391
  domainInfo.challengeKeyAuthorization = keyAuthorization;
171
392
 
172
- // Notify the ACME server that the challenge is ready.
173
- // The acme-client examples show that verifyChallenge takes three arguments:
174
- // (authorization, challenge, keyAuthorization). However, the official TypeScript
175
- // types appear to be out-of-sync. As a workaround, we cast client to 'any'.
176
- await (client as any).verifyChallenge(authz, challenge, keyAuthorization);
177
-
178
- await client.completeChallenge(challenge);
179
- // Wait until the challenge is validated.
180
- await client.waitForValidStatus(challenge);
181
- console.log(`HTTP-01 challenge completed for ${domain}`);
393
+ // ACME client type definition workaround - use compatible approach
394
+ // First check if challenge verification is needed
395
+ const authzUrl = authz.url;
396
+
397
+ try {
398
+ // Check if authzUrl exists and perform verification
399
+ if (authzUrl) {
400
+ await client.verifyChallenge(authz, challenge);
401
+ }
402
+
403
+ // Complete the challenge
404
+ await client.completeChallenge(challenge);
405
+
406
+ // Wait for validation
407
+ await client.waitForValidStatus(challenge);
408
+ console.log(`HTTP-01 challenge completed for ${domain}`);
409
+ } catch (error) {
410
+ console.error(`Challenge error for ${domain}:`, error);
411
+ throw error;
412
+ }
182
413
  }
183
414
 
184
- // Generate a CSR and a new private key for the domain.
185
- // Convert the resulting Buffers to strings.
186
- const [csrBuffer, privateKeyBuffer] = await acme.forge.createCsr({
415
+ // Generate a CSR and private key
416
+ const [csrBuffer, privateKeyBuffer] = await plugins.acme.forge.createCsr({
187
417
  commonName: domain,
188
418
  });
419
+
189
420
  const csr = csrBuffer.toString();
190
421
  const privateKey = privateKeyBuffer.toString();
191
422
 
192
- // Finalize the order and obtain the certificate.
423
+ // Finalize the order with our CSR
193
424
  await client.finalizeOrder(order, csr);
425
+
426
+ // Get the certificate with the full chain
194
427
  const certificate = await client.getCertificate(order);
195
428
 
196
- const domainInfo = this.domainCertificates.get(domain)!;
429
+ // Store the certificate and key
197
430
  domainInfo.certificate = certificate;
198
431
  domainInfo.privateKey = privateKey;
199
432
  domainInfo.certObtained = true;
200
- domainInfo.obtainingInProgress = false;
433
+
434
+ // Clear challenge data
201
435
  delete domainInfo.challengeToken;
202
436
  delete domainInfo.challengeKeyAuthorization;
437
+
438
+ // Extract expiry date from certificate
439
+ try {
440
+ const matches = certificate.match(/Not After\s*:\s*(.*?)(?:\n|$)/i);
441
+ if (matches && matches[1]) {
442
+ domainInfo.expiryDate = new Date(matches[1]);
443
+ console.log(`Certificate for ${domain} will expire on ${domainInfo.expiryDate.toISOString()}`);
444
+ }
445
+ } catch (error) {
446
+ console.warn(`Failed to extract expiry date from certificate for ${domain}`);
447
+ }
203
448
 
204
- console.log(`Certificate obtained for ${domain}`);
205
- // In a production system, persist the certificate and key and reload your TLS server.
206
- } catch (error) {
207
- console.error(`Error during certificate issuance for ${domain}:`, error);
208
- const domainInfo = this.domainCertificates.get(domain);
209
- if (domainInfo) {
210
- domainInfo.obtainingInProgress = false;
449
+ console.log(`Certificate ${isRenewal ? 'renewed' : 'obtained'} for ${domain}`);
450
+
451
+ // Emit the appropriate event
452
+ const eventType = isRenewal
453
+ ? CertManagerEvents.CERTIFICATE_RENEWED
454
+ : CertManagerEvents.CERTIFICATE_ISSUED;
455
+
456
+ this.emitCertificateEvent(eventType, {
457
+ domain,
458
+ certificate,
459
+ privateKey,
460
+ expiryDate: domainInfo.expiryDate || new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) // 90 days default
461
+ });
462
+
463
+ } catch (error: any) {
464
+ // Check for rate limit errors
465
+ if (error.message && (
466
+ error.message.includes('rateLimited') ||
467
+ error.message.includes('too many certificates') ||
468
+ error.message.includes('rate limit')
469
+ )) {
470
+ console.error(`Rate limit reached for ${domain}. Waiting before retry.`);
471
+ } else {
472
+ console.error(`Error during certificate issuance for ${domain}:`, error);
211
473
  }
474
+
475
+ // Emit failure event
476
+ this.emit(CertManagerEvents.CERTIFICATE_FAILED, {
477
+ domain,
478
+ error: error.message || 'Unknown error',
479
+ isRenewal
480
+ });
481
+ } finally {
482
+ // Reset flag whether successful or not
483
+ domainInfo.obtainingInProgress = false;
212
484
  }
213
485
  }
214
- }
486
+
487
+ /**
488
+ * Starts the certificate renewal timer
489
+ */
490
+ private startRenewalTimer(): void {
491
+ if (this.renewalTimer) {
492
+ clearInterval(this.renewalTimer);
493
+ }
494
+
495
+ // Convert hours to milliseconds
496
+ const checkInterval = this.options.renewCheckIntervalHours * 60 * 60 * 1000;
497
+
498
+ this.renewalTimer = setInterval(() => this.checkForRenewals(), checkInterval);
499
+
500
+ // Prevent the timer from keeping the process alive
501
+ if (this.renewalTimer.unref) {
502
+ this.renewalTimer.unref();
503
+ }
504
+
505
+ console.log(`Certificate renewal check scheduled every ${this.options.renewCheckIntervalHours} hours`);
506
+ }
507
+
508
+ /**
509
+ * Checks for certificates that need renewal
510
+ */
511
+ private checkForRenewals(): void {
512
+ if (this.isShuttingDown) {
513
+ return;
514
+ }
515
+
516
+ console.log('Checking for certificates that need renewal...');
517
+
518
+ const now = new Date();
519
+ const renewThresholdMs = this.options.renewThresholdDays * 24 * 60 * 60 * 1000;
520
+
521
+ for (const [domain, domainInfo] of this.domainCertificates.entries()) {
522
+ // Skip domains without certificates or already in renewal
523
+ if (!domainInfo.certObtained || domainInfo.obtainingInProgress) {
524
+ continue;
525
+ }
526
+
527
+ // Skip domains without expiry dates
528
+ if (!domainInfo.expiryDate) {
529
+ continue;
530
+ }
531
+
532
+ const timeUntilExpiry = domainInfo.expiryDate.getTime() - now.getTime();
533
+
534
+ // Check if certificate is near expiry
535
+ if (timeUntilExpiry <= renewThresholdMs) {
536
+ console.log(`Certificate for ${domain} expires soon, renewing...`);
537
+ this.emit(CertManagerEvents.CERTIFICATE_EXPIRING, {
538
+ domain,
539
+ expiryDate: domainInfo.expiryDate,
540
+ daysRemaining: Math.ceil(timeUntilExpiry / (24 * 60 * 60 * 1000))
541
+ });
542
+
543
+ // Start renewal process
544
+ this.obtainCertificate(domain, true).catch(err => {
545
+ console.error(`Error renewing certificate for ${domain}:`, err);
546
+ });
547
+ }
548
+ }
549
+ }
550
+
551
+ /**
552
+ * Emits a certificate event with the certificate data
553
+ * @param eventType The event type to emit
554
+ * @param data The certificate data
555
+ */
556
+ private emitCertificateEvent(eventType: CertManagerEvents, data: ICertificateData): void {
557
+ this.emit(eventType, data);
558
+ }
559
+ }