@push.rocks/smartacme 9.0.1 → 9.1.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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartacme',
6
- version: '9.0.1',
6
+ version: '9.1.0',
7
7
  description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx1QkFBdUI7SUFDN0IsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLDZHQUE2RztDQUMzSCxDQUFBIn0=
@@ -5,3 +5,4 @@ import * as certmanagers from './certmanagers/index.js';
5
5
  export { certmanagers };
6
6
  import * as handlers from './handlers/index.js';
7
7
  export { handlers };
8
+ export type { ITaskEvent, ITaskMetadata } from '@push.rocks/taskbuffer';
@@ -12,7 +12,8 @@ import * as smartnetwork from '@push.rocks/smartnetwork';
12
12
  import * as smartunique from '@push.rocks/smartunique';
13
13
  import * as smartstring from '@push.rocks/smartstring';
14
14
  import * as smarttime from '@push.rocks/smarttime';
15
- export { lik, smartdata, smartdelay, smartdnsClient, smartlog, smartnetwork, smartunique, smartstring, smarttime, };
15
+ import * as taskbuffer from '@push.rocks/taskbuffer';
16
+ export { lik, smartdata, smartdelay, smartdnsClient, smartlog, smartnetwork, smartunique, smartstring, smarttime, taskbuffer, };
16
17
  import * as tsclass from '@tsclass/tsclass';
17
18
  export { tsclass };
18
19
  import * as acme from './acme/index.js';
@@ -15,7 +15,8 @@ import * as smartnetwork from '@push.rocks/smartnetwork';
15
15
  import * as smartunique from '@push.rocks/smartunique';
16
16
  import * as smartstring from '@push.rocks/smartstring';
17
17
  import * as smarttime from '@push.rocks/smarttime';
18
- export { lik, smartdata, smartdelay, smartdnsClient, smartlog, smartnetwork, smartunique, smartstring, smarttime, };
18
+ import * as taskbuffer from '@push.rocks/taskbuffer';
19
+ export { lik, smartdata, smartdelay, smartdnsClient, smartlog, smartnetwork, smartunique, smartstring, smarttime, taskbuffer, };
19
20
  // @tsclass scope
20
21
  import * as tsclass from '@tsclass/tsclass';
21
22
  export { tsclass };
@@ -25,4 +26,4 @@ export { acme };
25
26
  // local handlers for challenge types
26
27
  import * as handlers from './handlers/index.js';
27
28
  export { handlers };
28
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYztBQUNkLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLE9BQU8sRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUM7QUFFcEIsdUJBQXVCO0FBQ3ZCLE9BQU8sS0FBSyxVQUFVLE1BQU0sMkJBQTJCLENBQUM7QUFFeEQsT0FBTyxFQUFFLFVBQVUsRUFBRSxDQUFDO0FBRXRCLG9CQUFvQjtBQUNwQixPQUFPLEtBQUssR0FBRyxNQUFNLGlCQUFpQixDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFVBQVUsTUFBTSx3QkFBd0IsQ0FBQztBQUNyRCxPQUFPLEtBQUssY0FBYyxNQUFNLDZCQUE2QixDQUFDO0FBQzlELE9BQU8sS0FBSyxRQUFRLE1BQU0sc0JBQXNCLENBQUM7QUFDakQsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssV0FBVyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFDdkQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUVuRCxPQUFPLEVBQ0wsR0FBRyxFQUNILFNBQVMsRUFDVCxVQUFVLEVBQ1YsY0FBYyxFQUNkLFFBQVEsRUFDUixZQUFZLEVBQ1osV0FBVyxFQUNYLFdBQVcsRUFDWCxTQUFTLEdBQ1YsQ0FBQztBQUVGLGlCQUFpQjtBQUNqQixPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBRTVDLE9BQU8sRUFBRSxPQUFPLEVBQUUsQ0FBQztBQUVuQix3Q0FBd0M7QUFDeEMsT0FBTyxLQUFLLElBQUksTUFBTSxpQkFBaUIsQ0FBQztBQUV4QyxPQUFPLEVBQUUsSUFBSSxFQUFFLENBQUM7QUFDaEIscUNBQXFDO0FBQ3JDLE9BQU8sS0FBSyxRQUFRLE1BQU0scUJBQXFCLENBQUM7QUFDaEQsT0FBTyxFQUFFLFFBQVEsRUFBRSxDQUFDIn0=
29
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYztBQUNkLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLE9BQU8sRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUM7QUFFcEIsdUJBQXVCO0FBQ3ZCLE9BQU8sS0FBSyxVQUFVLE1BQU0sMkJBQTJCLENBQUM7QUFFeEQsT0FBTyxFQUFFLFVBQVUsRUFBRSxDQUFDO0FBRXRCLG9CQUFvQjtBQUNwQixPQUFPLEtBQUssR0FBRyxNQUFNLGlCQUFpQixDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFVBQVUsTUFBTSx3QkFBd0IsQ0FBQztBQUNyRCxPQUFPLEtBQUssY0FBYyxNQUFNLDZCQUE2QixDQUFDO0FBQzlELE9BQU8sS0FBSyxRQUFRLE1BQU0sc0JBQXNCLENBQUM7QUFDakQsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssV0FBVyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFDdkQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssVUFBVSxNQUFNLHdCQUF3QixDQUFDO0FBRXJELE9BQU8sRUFDTCxHQUFHLEVBQ0gsU0FBUyxFQUNULFVBQVUsRUFDVixjQUFjLEVBQ2QsUUFBUSxFQUNSLFlBQVksRUFDWixXQUFXLEVBQ1gsV0FBVyxFQUNYLFNBQVMsRUFDVCxVQUFVLEdBQ1gsQ0FBQztBQUVGLGlCQUFpQjtBQUNqQixPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBRTVDLE9BQU8sRUFBRSxPQUFPLEVBQUUsQ0FBQztBQUVuQix3Q0FBd0M7QUFDeEMsT0FBTyxLQUFLLElBQUksTUFBTSxpQkFBaUIsQ0FBQztBQUV4QyxPQUFPLEVBQUUsSUFBSSxFQUFFLENBQUM7QUFDaEIscUNBQXFDO0FBQ3JDLE9BQU8sS0FBSyxRQUFRLE1BQU0scUJBQXFCLENBQUM7QUFDaEQsT0FBTyxFQUFFLFFBQVEsRUFBRSxDQUFDIn0=
@@ -34,6 +34,21 @@ export interface ISmartAcmeOptions {
34
34
  * Defaults to ['dns-01'] or first supported type from handlers.
35
35
  */
36
36
  challengePriority?: string[];
37
+ /**
38
+ * Maximum number of concurrent ACME issuances across all domains.
39
+ * Defaults to 5.
40
+ */
41
+ maxConcurrentIssuances?: number;
42
+ /**
43
+ * Maximum ACME orders allowed within the sliding window.
44
+ * Defaults to 250 (conservative limit under Let's Encrypt's 300/3h).
45
+ */
46
+ maxOrdersPerWindow?: number;
47
+ /**
48
+ * Sliding window duration in milliseconds for rate limiting.
49
+ * Defaults to 3 hours (10_800_000 ms).
50
+ */
51
+ orderWindowMs?: number;
37
52
  }
38
53
  /**
39
54
  * class SmartAcme
@@ -57,9 +72,14 @@ export declare class SmartAcme {
57
72
  private retryOptions;
58
73
  private pendingChallenges;
59
74
  private challengePriority;
60
- private interestMap;
75
+ private taskManager;
76
+ private certIssuanceTask;
61
77
  private boundSigintHandler;
62
78
  private boundSigtermHandler;
79
+ /**
80
+ * Exposes the aggregated task event stream for observing certificate issuance progress.
81
+ */
82
+ get certIssuanceEvents(): plugins.taskbuffer.TaskManager['taskSubject'];
63
83
  constructor(optionsArg: ISmartAcmeOptions);
64
84
  /**
65
85
  * starts the instance
@@ -87,8 +107,7 @@ export declare class SmartAcme {
87
107
  * * if not in the database announce it
88
108
  * * then get it from letsencrypt
89
109
  * * store it
90
- * * remove it from the pending map (which it go onto by announcing it)
91
- * * retrieve it from the databse and return it
110
+ * * retrieve it from the database and return it
92
111
  *
93
112
  * @param domainArg
94
113
  * @param options Optional configuration for certificate generation
@@ -96,4 +115,9 @@ export declare class SmartAcme {
96
115
  getCertificateForDomain(domainArg: string, options?: {
97
116
  includeWildcard?: boolean;
98
117
  }): Promise<SmartacmeCert>;
118
+ /**
119
+ * Performs the actual ACME certificate issuance flow.
120
+ * Called by the certIssuanceTask's taskFunction.
121
+ */
122
+ private performCertificateIssuance;
99
123
  }
@@ -2,6 +2,12 @@ import * as plugins from './plugins.js';
2
2
  import { SmartacmeCertMatcher } from './smartacme.classes.certmatcher.js';
3
3
  import { commitinfo } from './00_commitinfo_data.js';
4
4
  import { SmartacmeCert } from './smartacme.classes.cert.js';
5
+ const CERT_ISSUANCE_STEPS = [
6
+ { name: 'prepare', description: 'Creating ACME order', percentage: 10 },
7
+ { name: 'authorize', description: 'Solving ACME challenges', percentage: 40 },
8
+ { name: 'finalize', description: 'Finalizing and getting cert', percentage: 30 },
9
+ { name: 'store', description: 'Storing certificate', percentage: 20 },
10
+ ];
5
11
  /**
6
12
  * class SmartAcme
7
13
  * can be used for setting up communication with an ACME authority
@@ -13,6 +19,12 @@ import { SmartacmeCert } from './smartacme.classes.cert.js';
13
19
  * ```
14
20
  */
15
21
  export class SmartAcme {
22
+ /**
23
+ * Exposes the aggregated task event stream for observing certificate issuance progress.
24
+ */
25
+ get certIssuanceEvents() {
26
+ return this.taskManager.taskSubject;
27
+ }
16
28
  constructor(optionsArg) {
17
29
  this.smartdns = new plugins.smartdnsClient.Smartdns({});
18
30
  // track pending DNS challenges for graceful shutdown
@@ -41,8 +53,54 @@ export class SmartAcme {
41
53
  optionsArg.challengePriority && optionsArg.challengePriority.length > 0
42
54
  ? optionsArg.challengePriority
43
55
  : this.challengeHandlers.map((h) => h.getSupportedTypes()[0]);
44
- // initialize interest coordination
45
- this.interestMap = new plugins.lik.InterestMap((domain) => domain);
56
+ // ── TaskManager setup ──────────────────────────────────────────────────
57
+ this.taskManager = new plugins.taskbuffer.TaskManager();
58
+ // Constraint 1: Per-domain mutex — one issuance at a time per TLD, with result sharing
59
+ const certDomainMutex = new plugins.taskbuffer.TaskConstraintGroup({
60
+ name: 'cert-domain-mutex',
61
+ maxConcurrent: 1,
62
+ resultSharingMode: 'share-latest',
63
+ constraintKeyForExecution: (_task, input) => {
64
+ return input?.certDomainName ?? null;
65
+ },
66
+ shouldExecute: async (_task, input) => {
67
+ if (!input?.certDomainName || !this.certmanager)
68
+ return true;
69
+ // Safety net: if a valid cert is already cached, skip re-issuance
70
+ const existing = await this.certmanager.retrieveCertificate(input.certDomainName);
71
+ if (existing && !existing.shouldBeRenewed()) {
72
+ return false;
73
+ }
74
+ return true;
75
+ },
76
+ });
77
+ // Constraint 2: Global concurrency cap
78
+ const acmeGlobalConcurrency = new plugins.taskbuffer.TaskConstraintGroup({
79
+ name: 'acme-global-concurrency',
80
+ maxConcurrent: optionsArg.maxConcurrentIssuances ?? 5,
81
+ constraintKeyForExecution: () => 'global',
82
+ });
83
+ // Constraint 3: Account-level rate limiting
84
+ const acmeAccountRateLimit = new plugins.taskbuffer.TaskConstraintGroup({
85
+ name: 'acme-account-rate-limit',
86
+ rateLimit: {
87
+ maxPerWindow: optionsArg.maxOrdersPerWindow ?? 250,
88
+ windowMs: optionsArg.orderWindowMs ?? 10_800_000,
89
+ },
90
+ constraintKeyForExecution: () => 'account',
91
+ });
92
+ this.taskManager.addConstraintGroup(certDomainMutex);
93
+ this.taskManager.addConstraintGroup(acmeGlobalConcurrency);
94
+ this.taskManager.addConstraintGroup(acmeAccountRateLimit);
95
+ // Create the single reusable certificate issuance task
96
+ this.certIssuanceTask = new plugins.taskbuffer.Task({
97
+ name: 'cert-issuance',
98
+ steps: CERT_ISSUANCE_STEPS,
99
+ taskFunction: async (input) => {
100
+ return this.performCertificateIssuance(input);
101
+ },
102
+ });
103
+ this.taskManager.addTask(this.certIssuanceTask);
46
104
  }
47
105
  /**
48
106
  * starts the instance
@@ -81,6 +139,8 @@ export class SmartAcme {
81
139
  termsOfServiceAgreed: true,
82
140
  contact: [`mailto:${this.options.accountEmail}`],
83
141
  });
142
+ // Start the task manager
143
+ await this.taskManager.start();
84
144
  // Setup graceful shutdown handlers (store references for removal in stop())
85
145
  this.boundSigintHandler = () => this.handleSignal('SIGINT');
86
146
  this.boundSigtermHandler = () => this.handleSignal('SIGTERM');
@@ -100,6 +160,8 @@ export class SmartAcme {
100
160
  process.removeListener('SIGTERM', this.boundSigtermHandler);
101
161
  this.boundSigtermHandler = null;
102
162
  }
163
+ // Stop the task manager
164
+ await this.taskManager.stop();
103
165
  // Destroy ACME HTTP transport (closes keep-alive sockets)
104
166
  if (this.client) {
105
167
  this.client.destroy();
@@ -183,8 +245,7 @@ export class SmartAcme {
183
245
  * * if not in the database announce it
184
246
  * * then get it from letsencrypt
185
247
  * * store it
186
- * * remove it from the pending map (which it go onto by announcing it)
187
- * * retrieve it from the databse and return it
248
+ * * retrieve it from the database and return it
188
249
  *
189
250
  * @param domainArg
190
251
  * @param options Optional configuration for certificate generation
@@ -206,32 +267,48 @@ export class SmartAcme {
206
267
  }
207
268
  // Retrieve any existing certificate record by base domain.
208
269
  const retrievedCertificate = await this.certmanager.retrieveCertificate(certDomainName);
209
- if (!retrievedCertificate &&
210
- (await this.interestMap.checkInterest(certDomainName))) {
211
- const existingCertificateInterest = this.interestMap.findInterest(certDomainName);
212
- const certificate = existingCertificateInterest.interestFullfilled;
213
- return certificate;
214
- }
215
- else if (retrievedCertificate && !retrievedCertificate.shouldBeRenewed()) {
270
+ if (retrievedCertificate && !retrievedCertificate.shouldBeRenewed()) {
216
271
  return retrievedCertificate;
217
272
  }
218
273
  else if (retrievedCertificate && retrievedCertificate.shouldBeRenewed()) {
219
274
  // Remove old certificate via certManager
220
275
  await this.certmanager.deleteCertificate(certDomainName);
221
276
  }
222
- // lets make sure others get the same interest
223
- const currentDomainInterst = await this.interestMap.addInterest(certDomainName);
277
+ // Build issuance input and trigger the constrained task
278
+ const issuanceInput = {
279
+ certDomainName,
280
+ domainArg,
281
+ isWildcardRequest,
282
+ includeWildcard: options?.includeWildcard ?? false,
283
+ };
284
+ const result = await this.taskManager.triggerTaskConstrained(this.certIssuanceTask, issuanceInput);
285
+ // If we got a cert directly (either from execution or result sharing), return it
286
+ if (result != null) {
287
+ return result;
288
+ }
289
+ // If shouldExecute returned false (cert appeared in cache), read from cache
290
+ const cachedCert = await this.certmanager.retrieveCertificate(certDomainName);
291
+ if (cachedCert) {
292
+ return cachedCert;
293
+ }
294
+ throw new Error(`Certificate issuance failed for ${certDomainName}`);
295
+ }
296
+ /**
297
+ * Performs the actual ACME certificate issuance flow.
298
+ * Called by the certIssuanceTask's taskFunction.
299
+ */
300
+ async performCertificateIssuance(input) {
301
+ const { certDomainName, isWildcardRequest, includeWildcard } = input;
302
+ // ── Step: prepare ─────────────────────────────────────────────────────
303
+ this.certIssuanceTask.notifyStep('prepare');
224
304
  // Build identifiers array based on request
225
305
  const identifiers = [];
226
306
  if (isWildcardRequest) {
227
- // If requesting a wildcard directly, only add the wildcard
228
307
  identifiers.push({ type: 'dns', value: `*.${certDomainName}` });
229
308
  }
230
309
  else {
231
- // Add the regular domain
232
310
  identifiers.push({ type: 'dns', value: certDomainName });
233
- // Only add wildcard if explicitly requested
234
- if (options?.includeWildcard) {
311
+ if (includeWildcard) {
235
312
  const hasDnsHandler = this.challengeHandlers.some((h) => h.getSupportedTypes().includes('dns-01'));
236
313
  if (!hasDnsHandler) {
237
314
  this.logger.log('warn', 'Wildcard certificate requested but no DNS-01 handler available. Skipping wildcard.');
@@ -245,6 +322,8 @@ export class SmartAcme {
245
322
  const order = await this.retry(() => this.client.createOrder({
246
323
  identifiers,
247
324
  }), 'createOrder');
325
+ // ── Step: authorize ───────────────────────────────────────────────────
326
+ this.certIssuanceTask.notifyStep('authorize');
248
327
  /* Get authorizations and select challenges */
249
328
  const authorizations = await this.retry(() => this.client.getAuthorizations(order), 'getAuthorizations');
250
329
  for (const authz of authorizations) {
@@ -268,15 +347,14 @@ export class SmartAcme {
268
347
  }
269
348
  const { type, handler } = selectedHandler;
270
349
  // build handler input with keyAuthorization
271
- let input;
350
+ let challengeInput;
272
351
  // retrieve keyAuthorization for challenge
273
352
  const keyAuth = await this.client.getChallengeKeyAuthorization(selectedChallengeArg);
274
353
  if (type === 'dns-01') {
275
- input = { type, hostName: `_acme-challenge.${authz.identifier.value}`, challenge: keyAuth };
354
+ challengeInput = { type, hostName: `_acme-challenge.${authz.identifier.value}`, challenge: keyAuth };
276
355
  }
277
356
  else if (type === 'http-01') {
278
- // HTTP-01 requires serving token at webPath
279
- input = {
357
+ challengeInput = {
280
358
  type,
281
359
  token: selectedChallengeArg.token,
282
360
  keyAuthorization: keyAuth,
@@ -284,25 +362,18 @@ export class SmartAcme {
284
362
  };
285
363
  }
286
364
  else {
287
- // generic challenge input: include raw challenge properties
288
- input = { type, keyAuthorization: keyAuth, ...selectedChallengeArg };
365
+ challengeInput = { type, keyAuthorization: keyAuth, ...selectedChallengeArg };
289
366
  }
290
- this.pendingChallenges.push(input);
367
+ this.pendingChallenges.push(challengeInput);
291
368
  try {
292
- // Prepare the challenge (set DNS record, write file, etc.)
293
- await this.retry(() => handler.prepare(input), `${type}.prepare`);
294
- // For DNS-01, wait for propagation before verification
369
+ await this.retry(() => handler.prepare(challengeInput), `${type}.prepare`);
295
370
  if (type === 'dns-01') {
296
- const dnsInput = input;
297
- // Wait for authoritative DNS propagation before ACME verify
371
+ const dnsInput = challengeInput;
298
372
  await this.retry(() => this.smartdns.checkUntilAvailable(dnsInput.hostName, 'TXT', dnsInput.challenge, 100, 5000), `${type}.propagation`);
299
- // Extra cool-down to ensure ACME server sees the new TXT record
300
373
  this.logger.log('info', 'Cooling down for 1 minute before ACME verification');
301
374
  await plugins.smartdelay.delayFor(60000);
302
375
  }
303
- // Notify ACME server to complete the challenge
304
376
  await this.retry(() => this.client.completeChallenge(selectedChallengeArg), `${type}.completeChallenge`);
305
- // Wait for valid status (warnings on staging timeouts)
306
377
  try {
307
378
  await this.retry(() => this.client.waitForValidStatus(selectedChallengeArg), `${type}.waitForValidStatus`);
308
379
  }
@@ -311,31 +382,28 @@ export class SmartAcme {
311
382
  }
312
383
  }
313
384
  finally {
314
- // Always cleanup resource
315
385
  try {
316
- await this.retry(() => handler.cleanup(input), `${type}.cleanup`);
386
+ await this.retry(() => handler.cleanup(challengeInput), `${type}.cleanup`);
317
387
  }
318
388
  catch (err) {
319
389
  await this.logger.log('error', `Error during ${type}.cleanup`, err);
320
390
  }
321
391
  finally {
322
- this.pendingChallenges = this.pendingChallenges.filter((c) => c !== input);
392
+ this.pendingChallenges = this.pendingChallenges.filter((c) => c !== challengeInput);
323
393
  }
324
394
  }
325
395
  }
326
- /* Finalize order */
396
+ // ── Step: finalize ────────────────────────────────────────────────────
397
+ this.certIssuanceTask.notifyStep('finalize');
327
398
  const csrDomains = [];
328
399
  let commonName;
329
400
  if (isWildcardRequest) {
330
- // For wildcard requests, use wildcard as common name
331
401
  commonName = `*.${certDomainName}`;
332
- csrDomains.push(certDomainName); // Add base domain as alt name
402
+ csrDomains.push(certDomainName);
333
403
  }
334
404
  else {
335
- // For regular requests, use base domain as common name
336
405
  commonName = certDomainName;
337
- if (options?.includeWildcard && identifiers.some(id => id.value === `*.${certDomainName}`)) {
338
- // If wildcard was successfully added, include it as alt name
406
+ if (includeWildcard && identifiers.some(id => id.value === `*.${certDomainName}`)) {
339
407
  csrDomains.push(`*.${certDomainName}`);
340
408
  }
341
409
  }
@@ -345,8 +413,8 @@ export class SmartAcme {
345
413
  });
346
414
  await this.retry(() => this.client.finalizeOrder(order, csr), 'finalizeOrder');
347
415
  const cert = await this.retry(() => this.client.getCertificate(order), 'getCertificate');
348
- /* Done */
349
- // Store the new certificate record
416
+ // ── Step: store ───────────────────────────────────────────────────────
417
+ this.certIssuanceTask.notifyStep('store');
350
418
  const certRecord = new SmartacmeCert({
351
419
  id: plugins.smartunique.shortId(),
352
420
  domainName: certDomainName,
@@ -358,9 +426,7 @@ export class SmartAcme {
358
426
  });
359
427
  await this.certmanager.storeCertificate(certRecord);
360
428
  const newCertificate = await this.certmanager.retrieveCertificate(certDomainName);
361
- currentDomainInterst.fullfillInterest(newCertificate);
362
- currentDomainInterst.destroy();
363
- return newCertificate;
429
+ return newCertificate ?? certRecord;
364
430
  }
365
431
  }
366
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmNsYXNzZXMuc21hcnRhY21lLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRhY21lLmNsYXNzZXMuc21hcnRhY21lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQzFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFzQzVEOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sT0FBTyxTQUFTO0lBK0JwQixZQUFZLFVBQTZCO1FBMUJqQyxhQUFRLEdBQUcsSUFBSSxPQUFPLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQWdCM0QscURBQXFEO1FBQzdDLHNCQUFpQixHQUE0QyxFQUFFLENBQUM7UUFLeEUseURBQXlEO1FBQ2pELHVCQUFrQixHQUF3QixJQUFJLENBQUM7UUFDL0Msd0JBQW1CLEdBQXdCLElBQUksQ0FBQztRQUd0RCxJQUFJLENBQUMsT0FBTyxHQUFHLFVBQVUsQ0FBQztRQUMxQixJQUFJLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLG1CQUFtQixDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ3hFLCtDQUErQztRQUMvQyxJQUFJLENBQUMsTUFBTSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBQzVCLG1DQUFtQztRQUNuQyxJQUFJLENBQUMsWUFBWSxHQUFHO1lBQ2xCLE9BQU8sRUFBRSxVQUFVLENBQUMsWUFBWSxFQUFFLE9BQU8sSUFBSSxFQUFFO1lBQy9DLE1BQU0sRUFBRSxVQUFVLENBQUMsWUFBWSxFQUFFLE1BQU0sSUFBSSxDQUFDO1lBQzVDLFlBQVksRUFBRSxVQUFVLENBQUMsWUFBWSxFQUFFLFlB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
432
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmNsYXNzZXMuc21hcnRhY21lLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRhY21lLmNsYXNzZXMuc21hcnRhY21lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQzFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFXNUQsTUFBTSxtQkFBbUIsR0FBRztJQUMxQixFQUFFLElBQUksRUFBRSxTQUFTLEVBQUksV0FBVyxFQUFFLHFCQUFxQixFQUFVLFVBQVUsRUFBRSxFQUFFLEVBQUU7SUFDakYsRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSx5QkFBeUIsRUFBTSxVQUFVLEVBQUUsRUFBRSxFQUFFO0lBQ2pGLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRyxXQUFXLEVBQUUsNkJBQTZCLEVBQUUsVUFBVSxFQUFFLEVBQUUsRUFBRTtJQUNqRixFQUFFLElBQUksRUFBRSxPQUFPLEVBQU0sV0FBVyxFQUFFLHFCQUFxQixFQUFVLFVBQVUsRUFBRSxFQUFFLEVBQUU7Q0FDekUsQ0FBQztBQXFEWDs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFNLE9BQU8sU0FBUztJQWlDcEI7O09BRUc7SUFDSCxJQUFXLGtCQUFrQjtRQUMzQixPQUFPLElBQUksQ0FBQyxXQUFXLENBQUMsV0FBVyxDQUFDO0lBQ3RDLENBQUM7SUFFRCxZQUFZLFVBQTZCO1FBbkNqQyxhQUFRLEdBQUcsSUFBSSxPQUFPLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQWdCM0QscURBQXFEO1FBQzdDLHNCQUFpQixHQUE0QyxFQUFFLENBQUM7UUFPeEUseURBQXlEO1FBQ2pE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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartacme",
3
- "version": "9.0.1",
3
+ "version": "9.1.0",
4
4
  "private": false,
5
5
  "description": "A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.",
6
6
  "main": "dist_ts/index.js",
@@ -43,6 +43,7 @@
43
43
  "@peculiar/x509": "^1.14.3",
44
44
  "@push.rocks/lik": "^6.2.2",
45
45
  "@push.rocks/smartdata": "^7.0.15",
46
+ "@push.rocks/taskbuffer": "^6.1.0",
46
47
  "@push.rocks/smartdelay": "^3.0.5",
47
48
  "@push.rocks/smartdns": "^7.8.1",
48
49
  "@push.rocks/smartlog": "^3.1.10",
package/readme.hints.md CHANGED
@@ -28,6 +28,21 @@ Key files:
28
28
 
29
29
  Usage in `ts/plugins.ts`: `import * as acme from './acme/index.js'` (replaces `acme-client`)
30
30
 
31
+ ## Concurrency & Rate Limiting (taskbuffer integration)
32
+
33
+ As of v9.1.0, `@push.rocks/lik.InterestMap` was replaced with `@push.rocks/taskbuffer.TaskManager` for coordinating concurrent certificate requests. This provides:
34
+
35
+ - **Per-domain mutex** (`cert-domain-mutex`): Only one ACME issuance per TLD at a time, with `resultSharingMode: 'share-latest'` so queued callers get the same result without re-issuing.
36
+ - **Global concurrency cap** (`acme-global-concurrency`): Limits total parallel ACME operations (default 5, configurable via `maxConcurrentIssuances`).
37
+ - **Account-level rate limiting** (`acme-account-rate-limit`): Sliding-window rate limit (default 250 orders per 3 hours, configurable via `maxOrdersPerWindow`/`orderWindowMs`) to stay under Let's Encrypt limits.
38
+ - **Step-based progress**: The cert issuance task uses `notifyStep()` for prepare/authorize/finalize/store phases, observable via `smartAcme.certIssuanceEvents`.
39
+
40
+ Key implementation details:
41
+ - A single reusable `Task` named `cert-issuance` handles all domains via `triggerTaskConstrained()` with different inputs.
42
+ - The `shouldExecute` callback on the domain mutex checks the certmanager cache as a safety net.
43
+ - `TaskManager.start()` is called in `SmartAcme.start()` and `TaskManager.stop()` in `SmartAcme.stop()`.
44
+ - The "no cronjobs specified" log messages during tests come from taskbuffer's internal CronManager polling — harmless noise when no cron tasks are scheduled.
45
+
31
46
  ## Dependency Notes
32
47
 
33
48
  - `acme-client` was replaced with custom implementation in `ts/acme/` + `@peculiar/x509` for CSR generation
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartacme',
6
- version: '9.0.1',
6
+ version: '9.1.0',
7
7
  description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.'
8
8
  }
package/ts/index.ts CHANGED
@@ -9,3 +9,6 @@ export { certmanagers };
9
9
  // handlers
10
10
  import * as handlers from './handlers/index.js';
11
11
  export { handlers };
12
+
13
+ // re-export taskbuffer event types for consumers
14
+ export type { ITaskEvent, ITaskMetadata } from '@push.rocks/taskbuffer';
package/ts/plugins.ts CHANGED
@@ -19,6 +19,7 @@ import * as smartnetwork from '@push.rocks/smartnetwork';
19
19
  import * as smartunique from '@push.rocks/smartunique';
20
20
  import * as smartstring from '@push.rocks/smartstring';
21
21
  import * as smarttime from '@push.rocks/smarttime';
22
+ import * as taskbuffer from '@push.rocks/taskbuffer';
22
23
 
23
24
  export {
24
25
  lik,
@@ -30,6 +31,7 @@ export {
30
31
  smartunique,
31
32
  smartstring,
32
33
  smarttime,
34
+ taskbuffer,
33
35
  };
34
36
 
35
37
  // @tsclass scope
@@ -4,6 +4,22 @@ import { SmartacmeCertMatcher } from './smartacme.classes.certmatcher.js';
4
4
  import { commitinfo } from './00_commitinfo_data.js';
5
5
  import { SmartacmeCert } from './smartacme.classes.cert.js';
6
6
 
7
+ // ── Types & constants for certificate issuance task ──────────────────────────
8
+
9
+ interface ICertIssuanceInput {
10
+ certDomainName: string;
11
+ domainArg: string;
12
+ isWildcardRequest: boolean;
13
+ includeWildcard: boolean;
14
+ }
15
+
16
+ const CERT_ISSUANCE_STEPS = [
17
+ { name: 'prepare', description: 'Creating ACME order', percentage: 10 },
18
+ { name: 'authorize', description: 'Solving ACME challenges', percentage: 40 },
19
+ { name: 'finalize', description: 'Finalizing and getting cert', percentage: 30 },
20
+ { name: 'store', description: 'Storing certificate', percentage: 20 },
21
+ ] as const;
22
+
7
23
  /**
8
24
  * the options for the class @see SmartAcme
9
25
  */
@@ -38,6 +54,21 @@ export interface ISmartAcmeOptions {
38
54
  * Defaults to ['dns-01'] or first supported type from handlers.
39
55
  */
40
56
  challengePriority?: string[];
57
+ /**
58
+ * Maximum number of concurrent ACME issuances across all domains.
59
+ * Defaults to 5.
60
+ */
61
+ maxConcurrentIssuances?: number;
62
+ /**
63
+ * Maximum ACME orders allowed within the sliding window.
64
+ * Defaults to 250 (conservative limit under Let's Encrypt's 300/3h).
65
+ */
66
+ maxOrdersPerWindow?: number;
67
+ /**
68
+ * Sliding window duration in milliseconds for rate limiting.
69
+ * Defaults to 3 hours (10_800_000 ms).
70
+ */
71
+ orderWindowMs?: number;
41
72
  }
42
73
 
43
74
  /**
@@ -75,12 +106,21 @@ export class SmartAcme {
75
106
  private pendingChallenges: plugins.tsclass.network.IDnsChallenge[] = [];
76
107
  // priority order of challenge types
77
108
  private challengePriority: string[];
78
- // Map for coordinating concurrent certificate requests
79
- private interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
109
+ // TaskManager for coordinating concurrent certificate requests
110
+ private taskManager: plugins.taskbuffer.TaskManager;
111
+ // Single reusable task for certificate issuance
112
+ private certIssuanceTask: plugins.taskbuffer.Task<undefined, typeof CERT_ISSUANCE_STEPS>;
80
113
  // bound signal handlers so they can be removed on stop()
81
114
  private boundSigintHandler: (() => void) | null = null;
82
115
  private boundSigtermHandler: (() => void) | null = null;
83
116
 
117
+ /**
118
+ * Exposes the aggregated task event stream for observing certificate issuance progress.
119
+ */
120
+ public get certIssuanceEvents(): plugins.taskbuffer.TaskManager['taskSubject'] {
121
+ return this.taskManager.taskSubject;
122
+ }
123
+
84
124
  constructor(optionsArg: ISmartAcmeOptions) {
85
125
  this.options = optionsArg;
86
126
  this.logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo);
@@ -105,8 +145,60 @@ export class SmartAcme {
105
145
  optionsArg.challengePriority && optionsArg.challengePriority.length > 0
106
146
  ? optionsArg.challengePriority
107
147
  : this.challengeHandlers.map((h) => h.getSupportedTypes()[0]);
108
- // initialize interest coordination
109
- this.interestMap = new plugins.lik.InterestMap((domain) => domain);
148
+
149
+ // ── TaskManager setup ──────────────────────────────────────────────────
150
+ this.taskManager = new plugins.taskbuffer.TaskManager();
151
+
152
+ // Constraint 1: Per-domain mutex — one issuance at a time per TLD, with result sharing
153
+ const certDomainMutex = new plugins.taskbuffer.TaskConstraintGroup({
154
+ name: 'cert-domain-mutex',
155
+ maxConcurrent: 1,
156
+ resultSharingMode: 'share-latest',
157
+ constraintKeyForExecution: (_task, input?: ICertIssuanceInput) => {
158
+ return input?.certDomainName ?? null;
159
+ },
160
+ shouldExecute: async (_task, input?: ICertIssuanceInput) => {
161
+ if (!input?.certDomainName || !this.certmanager) return true;
162
+ // Safety net: if a valid cert is already cached, skip re-issuance
163
+ const existing = await this.certmanager.retrieveCertificate(input.certDomainName);
164
+ if (existing && !existing.shouldBeRenewed()) {
165
+ return false;
166
+ }
167
+ return true;
168
+ },
169
+ });
170
+
171
+ // Constraint 2: Global concurrency cap
172
+ const acmeGlobalConcurrency = new plugins.taskbuffer.TaskConstraintGroup({
173
+ name: 'acme-global-concurrency',
174
+ maxConcurrent: optionsArg.maxConcurrentIssuances ?? 5,
175
+ constraintKeyForExecution: () => 'global',
176
+ });
177
+
178
+ // Constraint 3: Account-level rate limiting
179
+ const acmeAccountRateLimit = new plugins.taskbuffer.TaskConstraintGroup({
180
+ name: 'acme-account-rate-limit',
181
+ rateLimit: {
182
+ maxPerWindow: optionsArg.maxOrdersPerWindow ?? 250,
183
+ windowMs: optionsArg.orderWindowMs ?? 10_800_000,
184
+ },
185
+ constraintKeyForExecution: () => 'account',
186
+ });
187
+
188
+ this.taskManager.addConstraintGroup(certDomainMutex);
189
+ this.taskManager.addConstraintGroup(acmeGlobalConcurrency);
190
+ this.taskManager.addConstraintGroup(acmeAccountRateLimit);
191
+
192
+ // Create the single reusable certificate issuance task
193
+ this.certIssuanceTask = new plugins.taskbuffer.Task({
194
+ name: 'cert-issuance',
195
+ steps: CERT_ISSUANCE_STEPS,
196
+ taskFunction: async (input: ICertIssuanceInput) => {
197
+ return this.performCertificateIssuance(input);
198
+ },
199
+ });
200
+
201
+ this.taskManager.addTask(this.certIssuanceTask);
110
202
  }
111
203
 
112
204
  /**
@@ -149,6 +241,10 @@ export class SmartAcme {
149
241
  termsOfServiceAgreed: true,
150
242
  contact: [`mailto:${this.options.accountEmail}`],
151
243
  });
244
+
245
+ // Start the task manager
246
+ await this.taskManager.start();
247
+
152
248
  // Setup graceful shutdown handlers (store references for removal in stop())
153
249
  this.boundSigintHandler = () => this.handleSignal('SIGINT');
154
250
  this.boundSigtermHandler = () => this.handleSignal('SIGTERM');
@@ -169,6 +265,8 @@ export class SmartAcme {
169
265
  process.removeListener('SIGTERM', this.boundSigtermHandler);
170
266
  this.boundSigtermHandler = null;
171
267
  }
268
+ // Stop the task manager
269
+ await this.taskManager.stop();
172
270
  // Destroy ACME HTTP transport (closes keep-alive sockets)
173
271
  if (this.client) {
174
272
  this.client.destroy();
@@ -255,8 +353,7 @@ export class SmartAcme {
255
353
  * * if not in the database announce it
256
354
  * * then get it from letsencrypt
257
355
  * * store it
258
- * * remove it from the pending map (which it go onto by announcing it)
259
- * * retrieve it from the databse and return it
356
+ * * retrieve it from the database and return it
260
357
  *
261
358
  * @param domainArg
262
359
  * @param options Optional configuration for certificate generation
@@ -284,35 +381,59 @@ export class SmartAcme {
284
381
  // Retrieve any existing certificate record by base domain.
285
382
  const retrievedCertificate = await this.certmanager.retrieveCertificate(certDomainName);
286
383
 
287
- if (
288
- !retrievedCertificate &&
289
- (await this.interestMap.checkInterest(certDomainName))
290
- ) {
291
- const existingCertificateInterest = this.interestMap.findInterest(certDomainName);
292
- const certificate = existingCertificateInterest.interestFullfilled;
293
- return certificate;
294
- } else if (retrievedCertificate && !retrievedCertificate.shouldBeRenewed()) {
384
+ if (retrievedCertificate && !retrievedCertificate.shouldBeRenewed()) {
295
385
  return retrievedCertificate;
296
386
  } else if (retrievedCertificate && retrievedCertificate.shouldBeRenewed()) {
297
387
  // Remove old certificate via certManager
298
388
  await this.certmanager.deleteCertificate(certDomainName);
299
389
  }
300
390
 
301
- // lets make sure others get the same interest
302
- const currentDomainInterst = await this.interestMap.addInterest(certDomainName);
391
+ // Build issuance input and trigger the constrained task
392
+ const issuanceInput: ICertIssuanceInput = {
393
+ certDomainName,
394
+ domainArg,
395
+ isWildcardRequest,
396
+ includeWildcard: options?.includeWildcard ?? false,
397
+ };
398
+
399
+ const result = await this.taskManager.triggerTaskConstrained(
400
+ this.certIssuanceTask,
401
+ issuanceInput,
402
+ );
403
+
404
+ // If we got a cert directly (either from execution or result sharing), return it
405
+ if (result != null) {
406
+ return result;
407
+ }
408
+
409
+ // If shouldExecute returned false (cert appeared in cache), read from cache
410
+ const cachedCert = await this.certmanager.retrieveCertificate(certDomainName);
411
+ if (cachedCert) {
412
+ return cachedCert;
413
+ }
414
+
415
+ throw new Error(`Certificate issuance failed for ${certDomainName}`);
416
+ }
417
+
418
+ /**
419
+ * Performs the actual ACME certificate issuance flow.
420
+ * Called by the certIssuanceTask's taskFunction.
421
+ */
422
+ private async performCertificateIssuance(input: ICertIssuanceInput): Promise<SmartacmeCert> {
423
+ const { certDomainName, isWildcardRequest, includeWildcard } = input;
424
+
425
+ // ── Step: prepare ─────────────────────────────────────────────────────
426
+ this.certIssuanceTask.notifyStep('prepare');
303
427
 
304
428
  // Build identifiers array based on request
305
- const identifiers = [];
306
-
429
+ const identifiers: Array<{ type: 'dns'; value: string }> = [];
430
+
307
431
  if (isWildcardRequest) {
308
- // If requesting a wildcard directly, only add the wildcard
309
432
  identifiers.push({ type: 'dns', value: `*.${certDomainName}` });
310
433
  } else {
311
- // Add the regular domain
312
434
  identifiers.push({ type: 'dns', value: certDomainName });
313
-
314
- // Only add wildcard if explicitly requested
315
- if (options?.includeWildcard) {
435
+
436
+ if (includeWildcard) {
316
437
  const hasDnsHandler = this.challengeHandlers.some((h) =>
317
438
  h.getSupportedTypes().includes('dns-01'),
318
439
  );
@@ -329,6 +450,9 @@ export class SmartAcme {
329
450
  identifiers,
330
451
  }), 'createOrder');
331
452
 
453
+ // ── Step: authorize ───────────────────────────────────────────────────
454
+ this.certIssuanceTask.notifyStep('authorize');
455
+
332
456
  /* Get authorizations and select challenges */
333
457
  const authorizations = await this.retry(() => this.client.getAuthorizations(order), 'getAuthorizations');
334
458
 
@@ -352,45 +476,37 @@ export class SmartAcme {
352
476
  }
353
477
  const { type, handler } = selectedHandler;
354
478
  // build handler input with keyAuthorization
355
- let input: any;
479
+ let challengeInput: any;
356
480
  // retrieve keyAuthorization for challenge
357
481
  const keyAuth = await this.client.getChallengeKeyAuthorization(selectedChallengeArg);
358
482
  if (type === 'dns-01') {
359
- input = { type, hostName: `_acme-challenge.${authz.identifier.value}`, challenge: keyAuth };
483
+ challengeInput = { type, hostName: `_acme-challenge.${authz.identifier.value}`, challenge: keyAuth };
360
484
  } else if (type === 'http-01') {
361
- // HTTP-01 requires serving token at webPath
362
- input = {
485
+ challengeInput = {
363
486
  type,
364
487
  token: (selectedChallengeArg as any).token,
365
488
  keyAuthorization: keyAuth,
366
489
  webPath: `/.well-known/acme-challenge/${(selectedChallengeArg as any).token}`,
367
490
  };
368
491
  } else {
369
- // generic challenge input: include raw challenge properties
370
- input = { type, keyAuthorization: keyAuth, ...selectedChallengeArg };
492
+ challengeInput = { type, keyAuthorization: keyAuth, ...selectedChallengeArg };
371
493
  }
372
- this.pendingChallenges.push(input);
494
+ this.pendingChallenges.push(challengeInput);
373
495
  try {
374
- // Prepare the challenge (set DNS record, write file, etc.)
375
- await this.retry(() => handler.prepare(input), `${type}.prepare`);
376
- // For DNS-01, wait for propagation before verification
496
+ await this.retry(() => handler.prepare(challengeInput), `${type}.prepare`);
377
497
  if (type === 'dns-01') {
378
- const dnsInput = input as { hostName: string; challenge: string };
379
- // Wait for authoritative DNS propagation before ACME verify
498
+ const dnsInput = challengeInput as { hostName: string; challenge: string };
380
499
  await this.retry(
381
500
  () => this.smartdns.checkUntilAvailable(dnsInput.hostName, 'TXT', dnsInput.challenge, 100, 5000),
382
501
  `${type}.propagation`,
383
502
  );
384
- // Extra cool-down to ensure ACME server sees the new TXT record
385
503
  this.logger.log('info', 'Cooling down for 1 minute before ACME verification');
386
504
  await plugins.smartdelay.delayFor(60000);
387
505
  }
388
- // Notify ACME server to complete the challenge
389
506
  await this.retry(
390
507
  () => this.client.completeChallenge(selectedChallengeArg),
391
508
  `${type}.completeChallenge`,
392
509
  );
393
- // Wait for valid status (warnings on staging timeouts)
394
510
  try {
395
511
  await this.retry(
396
512
  () => this.client.waitForValidStatus(selectedChallengeArg),
@@ -404,34 +520,32 @@ export class SmartAcme {
404
520
  );
405
521
  }
406
522
  } finally {
407
- // Always cleanup resource
408
523
  try {
409
- await this.retry(() => handler.cleanup(input), `${type}.cleanup`);
524
+ await this.retry(() => handler.cleanup(challengeInput), `${type}.cleanup`);
410
525
  } catch (err) {
411
526
  await this.logger.log('error', `Error during ${type}.cleanup`, err);
412
527
  } finally {
413
- this.pendingChallenges = this.pendingChallenges.filter((c) => c !== input);
528
+ this.pendingChallenges = this.pendingChallenges.filter((c) => c !== challengeInput);
414
529
  }
415
530
  }
416
531
  }
417
532
 
418
- /* Finalize order */
419
- const csrDomains = [];
533
+ // ── Step: finalize ────────────────────────────────────────────────────
534
+ this.certIssuanceTask.notifyStep('finalize');
535
+
536
+ const csrDomains: string[] = [];
420
537
  let commonName: string;
421
-
538
+
422
539
  if (isWildcardRequest) {
423
- // For wildcard requests, use wildcard as common name
424
540
  commonName = `*.${certDomainName}`;
425
- csrDomains.push(certDomainName); // Add base domain as alt name
541
+ csrDomains.push(certDomainName);
426
542
  } else {
427
- // For regular requests, use base domain as common name
428
543
  commonName = certDomainName;
429
- if (options?.includeWildcard && identifiers.some(id => id.value === `*.${certDomainName}`)) {
430
- // If wildcard was successfully added, include it as alt name
544
+ if (includeWildcard && identifiers.some(id => id.value === `*.${certDomainName}`)) {
431
545
  csrDomains.push(`*.${certDomainName}`);
432
546
  }
433
547
  }
434
-
548
+
435
549
  const [key, csr] = await plugins.acme.AcmeCrypto.createCsr({
436
550
  commonName,
437
551
  altNames: csrDomains,
@@ -440,9 +554,9 @@ export class SmartAcme {
440
554
  await this.retry(() => this.client.finalizeOrder(order, csr), 'finalizeOrder');
441
555
  const cert = await this.retry(() => this.client.getCertificate(order), 'getCertificate');
442
556
 
443
- /* Done */
557
+ // ── Step: store ───────────────────────────────────────────────────────
558
+ this.certIssuanceTask.notifyStep('store');
444
559
 
445
- // Store the new certificate record
446
560
  const certRecord = new SmartacmeCert({
447
561
  id: plugins.smartunique.shortId(),
448
562
  domainName: certDomainName,
@@ -455,9 +569,7 @@ export class SmartAcme {
455
569
  await this.certmanager.storeCertificate(certRecord);
456
570
 
457
571
  const newCertificate = await this.certmanager.retrieveCertificate(certDomainName);
458
- currentDomainInterst.fullfillInterest(newCertificate);
459
- currentDomainInterst.destroy();
460
- return newCertificate;
572
+ return newCertificate ?? certRecord;
461
573
  }
462
574
 
463
575
  }