@serve.zone/dcrouter 5.4.6 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -129,13 +129,13 @@ let OpsViewCertificates = (() => {
129
129
  color: ${cssManager.bdTheme('#374151', '#d1d5db')};
130
130
  }
131
131
 
132
- .domainPills {
132
+ .routePills {
133
133
  display: flex;
134
134
  flex-wrap: wrap;
135
135
  gap: 4px;
136
136
  }
137
137
 
138
- .domainPill {
138
+ .routePill {
139
139
  display: inline-flex;
140
140
  align-items: center;
141
141
  padding: 2px 8px;
@@ -160,6 +160,17 @@ let OpsViewCertificates = (() => {
160
160
  white-space: nowrap;
161
161
  }
162
162
 
163
+ .backoffIndicator {
164
+ display: inline-flex;
165
+ align-items: center;
166
+ gap: 4px;
167
+ font-size: 11px;
168
+ color: ${cssManager.bdTheme('#9a3412', '#fb923c')};
169
+ padding: 2px 6px;
170
+ border-radius: 4px;
171
+ background: ${cssManager.bdTheme('#fff7ed', '#431407')};
172
+ }
173
+
163
174
  .expiryInfo {
164
175
  font-size: 12px;
165
176
  }
@@ -245,14 +256,16 @@ let OpsViewCertificates = (() => {
245
256
  <dees-table
246
257
  .data=${this.certState.certificates}
247
258
  .displayFunction=${(cert) => ({
248
- Route: cert.routeName,
249
- Domains: this.renderDomainPills(cert.domains),
259
+ Domain: cert.domain,
260
+ Routes: this.renderRoutePills(cert.routeNames),
250
261
  Status: this.renderStatusBadge(cert.status),
251
262
  Source: this.renderSourceBadge(cert.source),
252
263
  Expires: this.renderExpiry(cert.expiryDate),
253
- Error: cert.error
254
- ? html `<span class="errorText" title="${cert.error}">${cert.error}</span>`
255
- : '',
264
+ Error: cert.backoffInfo
265
+ ? html `<span class="backoffIndicator">${cert.backoffInfo.failures} failures, retry ${this.formatRetryTime(cert.backoffInfo.retryAfter)}</span>`
266
+ : cert.error
267
+ ? html `<span class="errorText" title="${cert.error}">${cert.error}</span>`
268
+ : '',
256
269
  })}
257
270
  .dataActions=${[
258
271
  {
@@ -270,10 +283,10 @@ let OpsViewCertificates = (() => {
270
283
  });
271
284
  return;
272
285
  }
273
- await appstate.certificateStatePart.dispatchAction(appstate.reprovisionCertificateAction, cert.routeName);
286
+ await appstate.certificateStatePart.dispatchAction(appstate.reprovisionCertificateAction, cert.domain);
274
287
  const { DeesToast } = await import('@design.estate/dees-catalog');
275
288
  DeesToast.show({
276
- message: `Reprovisioning triggered for ${cert.routeName}`,
289
+ message: `Reprovisioning triggered for ${cert.domain}`,
277
290
  type: 'success',
278
291
  duration: 3000,
279
292
  });
@@ -287,7 +300,7 @@ let OpsViewCertificates = (() => {
287
300
  const cert = actionData.item;
288
301
  const { DeesModal } = await import('@design.estate/dees-catalog');
289
302
  await DeesModal.createAndShow({
290
- heading: `Certificate: ${cert.routeName}`,
303
+ heading: `Certificate: ${cert.domain}`,
291
304
  content: html `
292
305
  <div style="padding: 20px;">
293
306
  <dees-dataview-codebox
@@ -299,10 +312,10 @@ let OpsViewCertificates = (() => {
299
312
  `,
300
313
  menuOptions: [
301
314
  {
302
- name: 'Copy Route Name',
315
+ name: 'Copy Domain',
303
316
  iconName: 'copy',
304
317
  action: async () => {
305
- await navigator.clipboard.writeText(cert.routeName);
318
+ await navigator.clipboard.writeText(cert.domain);
306
319
  },
307
320
  },
308
321
  ],
@@ -311,7 +324,7 @@ let OpsViewCertificates = (() => {
311
324
  },
312
325
  ]}
313
326
  heading1="Certificate Status"
314
- heading2="TLS certificates across all routes"
327
+ heading2="TLS certificates by domain"
315
328
  searchable
316
329
  .pagination=${true}
317
330
  .paginationSize=${50}
@@ -319,13 +332,13 @@ let OpsViewCertificates = (() => {
319
332
  ></dees-table>
320
333
  `;
321
334
  }
322
- renderDomainPills(domains) {
335
+ renderRoutePills(routeNames) {
323
336
  const maxShow = 3;
324
- const visible = domains.slice(0, maxShow);
325
- const remaining = domains.length - maxShow;
337
+ const visible = routeNames.slice(0, maxShow);
338
+ const remaining = routeNames.length - maxShow;
326
339
  return html `
327
- <span class="domainPills">
328
- ${visible.map((d) => html `<span class="domainPill">${d}</span>`)}
340
+ <span class="routePills">
341
+ ${visible.map((r) => html `<span class="routePill">${r}</span>`)}
329
342
  ${remaining > 0 ? html `<span class="moreCount">+${remaining} more</span>` : ''}
330
343
  </span>
331
344
  `;
@@ -369,6 +382,20 @@ let OpsViewCertificates = (() => {
369
382
  </span>
370
383
  `;
371
384
  }
385
+ formatRetryTime(retryAfter) {
386
+ if (!retryAfter)
387
+ return 'soon';
388
+ const retryDate = new Date(retryAfter);
389
+ const now = new Date();
390
+ const diffMs = retryDate.getTime() - now.getTime();
391
+ if (diffMs <= 0)
392
+ return 'now';
393
+ const diffMin = Math.ceil(diffMs / 60000);
394
+ if (diffMin < 60)
395
+ return `in ${diffMin}m`;
396
+ const diffHours = Math.ceil(diffMin / 60);
397
+ return `in ${diffHours}h`;
398
+ }
372
399
  static {
373
400
  __runInitializers(_classThis, _classExtraInitializers);
374
401
  }
@@ -376,4 +403,4 @@ let OpsViewCertificates = (() => {
376
403
  return OpsViewCertificates = _classThis;
377
404
  })();
378
405
  export { OpsViewCertificates };
379
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctY2VydGlmaWNhdGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViL2VsZW1lbnRzL29wcy12aWV3LWNlcnRpZmljYXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUNMLFdBQVcsRUFDWCxJQUFJLEVBQ0osYUFBYSxFQUViLEdBQUcsRUFDSCxLQUFLLEVBQ0wsVUFBVSxHQUNYLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxLQUFLLFFBQVEsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEtBQUssVUFBVSxNQUFNLG1DQUFtQyxDQUFDO0FBQ2hFLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUM5QyxPQUFPLEVBQW1CLE1BQU0sNkJBQTZCLENBQUM7SUFTakQsbUJBQW1COzRCQUQvQixhQUFhLENBQUMsdUJBQXVCLENBQUM7Ozs7c0JBQ0UsV0FBVzs7OzttQ0FBbkIsU0FBUSxXQUFXOzs7O3FDQUNqRCxLQUFLLEVBQUU7WUFDUixnTEFBUyxTQUFTLDZCQUFULFNBQVMsNkZBQXdFO1lBRjVGLDZLQTZVQzs7OztRQTNVQywrRUFBaUQsUUFBUSxDQUFDLG9CQUFvQixDQUFDLFFBQVEsRUFBRSxFQUFDO1FBQTFGLElBQVMsU0FBUywrQ0FBd0U7UUFBMUYsSUFBUyxTQUFTLHFEQUF3RTtRQUUxRjtZQUNFLEtBQUssRUFBRSxDQUFDOztZQUNSLE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7Z0JBQ3JFLElBQUksQ0FBQyxTQUFTLEdBQUcsUUFBUSxDQUFDO1lBQzVCLENBQUMsQ0FBQyxDQUFDO1lBQ0gsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDaEM7UUFFRCxLQUFL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
406
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctY2VydGlmaWNhdGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViL2VsZW1lbnRzL29wcy12aWV3LWNlcnRpZmljYXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUNMLFdBQVcsRUFDWCxJQUFJLEVBQ0osYUFBYSxFQUViLEdBQUcsRUFDSCxLQUFLLEVBQ0wsVUFBVSxHQUNYLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxLQUFLLFFBQVEsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEtBQUssVUFBVSxNQUFNLG1DQUFtQyxDQUFDO0FBQ2hFLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUM5QyxPQUFPLEVBQW1CLE1BQU0sNkJBQTZCLENBQUM7SUFTakQsbUJBQW1COzRCQUQvQixhQUFhLENBQUMsdUJBQXVCLENBQUM7Ozs7c0JBQ0UsV0FBVzs7OzttQ0FBbkIsU0FBUSxXQUFXOzs7O3FDQUNqRCxLQUFLLEVBQUU7WUFDUixnTEFBUyxTQUFTLDZCQUFULFNBQVMsNkZBQXdFO1lBRjVGLDZLQXNXQzs7OztRQXBXQywrRUFBaUQsUUFBUSxDQUFDLG9CQUFvQixDQUFDLFFBQVEsRUFBRSxFQUFDO1FBQTFGLElBQVMsU0FBUywrQ0FBd0U7UUFBMUYsSUFBUyxTQUFTLHFEQUF3RTtRQUUxRjtZQUNFLEtBQUssRUFBRSxDQUFDOztZQUNSLE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7Z0JBQ3JFLElBQUksQ0FBQyxTQUFTLEdBQUcsUUFBUSxDQUFDO1lBQzVCLENBQUMsQ0FBQyxDQUFDO1lBQ0gsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDaEM7UUFFRCxLQUFL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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "5.4.6",
4
+ "version": "6.0.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "exports": {
@@ -42,14 +42,14 @@
42
42
  "@push.rocks/smartfile": "^13.1.2",
43
43
  "@push.rocks/smartguard": "^3.1.0",
44
44
  "@push.rocks/smartjwt": "^2.2.1",
45
- "@push.rocks/smartlog": "^3.1.10",
45
+ "@push.rocks/smartlog": "^3.1.11",
46
46
  "@push.rocks/smartmetrics": "^2.0.10",
47
47
  "@push.rocks/smartmongo": "^5.1.0",
48
48
  "@push.rocks/smartmta": "^5.2.2",
49
49
  "@push.rocks/smartnetwork": "^4.4.0",
50
50
  "@push.rocks/smartpath": "^6.0.0",
51
51
  "@push.rocks/smartpromise": "^4.2.3",
52
- "@push.rocks/smartproxy": "^25.2.2",
52
+ "@push.rocks/smartproxy": "^25.3.1",
53
53
  "@push.rocks/smartradius": "^1.1.1",
54
54
  "@push.rocks/smartrequest": "^5.0.1",
55
55
  "@push.rocks/smartrx": "^3.0.10",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '5.4.6',
6
+ version: '6.0.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -0,0 +1,176 @@
1
+ import { logger } from './logger.js';
2
+ import type { StorageManager } from './storage/index.js';
3
+
4
+ interface IBackoffEntry {
5
+ failures: number;
6
+ lastFailure: string; // ISO string
7
+ retryAfter: string; // ISO string
8
+ lastError?: string;
9
+ }
10
+
11
+ /**
12
+ * Manages certificate provisioning scheduling with:
13
+ * - Per-domain exponential backoff persisted in StorageManager
14
+ * - Serial stagger queue with configurable delay between provisions
15
+ */
16
+ export class CertProvisionScheduler {
17
+ private storageManager: StorageManager;
18
+ private staggerDelayMs: number;
19
+ private maxBackoffHours: number;
20
+
21
+ // In-memory serial queue
22
+ private queue: Array<{
23
+ domain: string;
24
+ fn: () => Promise<any>;
25
+ resolve: (value: any) => void;
26
+ reject: (err: any) => void;
27
+ }> = [];
28
+ private processing = false;
29
+
30
+ // In-memory backoff cache (mirrors storage for fast lookups)
31
+ private backoffCache = new Map<string, IBackoffEntry>();
32
+
33
+ constructor(
34
+ storageManager: StorageManager,
35
+ options?: { staggerDelayMs?: number; maxBackoffHours?: number }
36
+ ) {
37
+ this.storageManager = storageManager;
38
+ this.staggerDelayMs = options?.staggerDelayMs ?? 3000;
39
+ this.maxBackoffHours = options?.maxBackoffHours ?? 24;
40
+ }
41
+
42
+ /**
43
+ * Storage key for a domain's backoff entry
44
+ */
45
+ private backoffKey(domain: string): string {
46
+ const clean = domain.replace(/\*/g, '_wildcard_').replace(/[^a-zA-Z0-9._-]/g, '_');
47
+ return `/cert-backoff/${clean}`;
48
+ }
49
+
50
+ /**
51
+ * Load backoff entry from storage (with in-memory cache)
52
+ */
53
+ private async loadBackoff(domain: string): Promise<IBackoffEntry | null> {
54
+ const cached = this.backoffCache.get(domain);
55
+ if (cached) return cached;
56
+
57
+ const entry = await this.storageManager.getJSON<IBackoffEntry>(this.backoffKey(domain));
58
+ if (entry) {
59
+ this.backoffCache.set(domain, entry);
60
+ }
61
+ return entry;
62
+ }
63
+
64
+ /**
65
+ * Save backoff entry to both cache and storage
66
+ */
67
+ private async saveBackoff(domain: string, entry: IBackoffEntry): Promise<void> {
68
+ this.backoffCache.set(domain, entry);
69
+ await this.storageManager.setJSON(this.backoffKey(domain), entry);
70
+ }
71
+
72
+ /**
73
+ * Check if a domain is currently in backoff
74
+ */
75
+ async isInBackoff(domain: string): Promise<boolean> {
76
+ const entry = await this.loadBackoff(domain);
77
+ if (!entry) return false;
78
+
79
+ const retryAfter = new Date(entry.retryAfter);
80
+ return retryAfter.getTime() > Date.now();
81
+ }
82
+
83
+ /**
84
+ * Record a provisioning failure for a domain.
85
+ * Sets exponential backoff: min(failures^2 * 1h, maxBackoffHours)
86
+ */
87
+ async recordFailure(domain: string, error?: string): Promise<void> {
88
+ const existing = await this.loadBackoff(domain);
89
+ const failures = (existing?.failures ?? 0) + 1;
90
+
91
+ // Exponential backoff: failures^2 hours, capped
92
+ const backoffHours = Math.min(failures * failures, this.maxBackoffHours);
93
+ const retryAfter = new Date(Date.now() + backoffHours * 60 * 60 * 1000);
94
+
95
+ const entry: IBackoffEntry = {
96
+ failures,
97
+ lastFailure: new Date().toISOString(),
98
+ retryAfter: retryAfter.toISOString(),
99
+ lastError: error,
100
+ };
101
+
102
+ await this.saveBackoff(domain, entry);
103
+ logger.log('warn', `Cert backoff for ${domain}: ${failures} failures, retry after ${retryAfter.toISOString()}`);
104
+ }
105
+
106
+ /**
107
+ * Clear backoff for a domain (on success or manual override)
108
+ */
109
+ async clearBackoff(domain: string): Promise<void> {
110
+ this.backoffCache.delete(domain);
111
+ try {
112
+ await this.storageManager.delete(this.backoffKey(domain));
113
+ } catch {
114
+ // Ignore delete errors (key may not exist)
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Get backoff info for UI display
120
+ */
121
+ async getBackoffInfo(domain: string): Promise<{
122
+ failures: number;
123
+ retryAfter?: string;
124
+ lastError?: string;
125
+ } | null> {
126
+ const entry = await this.loadBackoff(domain);
127
+ if (!entry) return null;
128
+
129
+ // Only return if still in backoff
130
+ const retryAfter = new Date(entry.retryAfter);
131
+ if (retryAfter.getTime() <= Date.now()) return null;
132
+
133
+ return {
134
+ failures: entry.failures,
135
+ retryAfter: entry.retryAfter,
136
+ lastError: entry.lastError,
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Enqueue a provision operation for serial execution with stagger delay.
142
+ * Returns the result of the provision function.
143
+ */
144
+ enqueueProvision<T>(domain: string, fn: () => Promise<T>): Promise<T> {
145
+ return new Promise<T>((resolve, reject) => {
146
+ this.queue.push({ domain, fn, resolve, reject });
147
+ this.processQueue();
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Process the stagger queue serially
153
+ */
154
+ private async processQueue(): Promise<void> {
155
+ if (this.processing) return;
156
+ this.processing = true;
157
+
158
+ while (this.queue.length > 0) {
159
+ const item = this.queue.shift()!;
160
+ try {
161
+ logger.log('info', `Processing cert provision for ${item.domain}`);
162
+ const result = await item.fn();
163
+ item.resolve(result);
164
+ } catch (err) {
165
+ item.reject(err);
166
+ }
167
+
168
+ // Stagger delay between provisions
169
+ if (this.queue.length > 0) {
170
+ await new Promise<void>((r) => setTimeout(r, this.staggerDelayMs));
171
+ }
172
+ }
173
+
174
+ this.processing = false;
175
+ }
176
+ }
@@ -13,6 +13,8 @@ import {
13
13
  import { logger } from './logger.js';
14
14
  // Import storage manager
15
15
  import { StorageManager, type IStorageConfig } from './storage/index.js';
16
+ import { StorageBackedCertManager } from './classes.storage-cert-manager.js';
17
+ import { CertProvisionScheduler } from './classes.cert-provision-scheduler.js';
16
18
  // Import cache system
17
19
  import { CacheDb, CacheCleaner, type ICacheDbOptions } from './cache/index.js';
18
20
 
@@ -183,16 +185,19 @@ export class DcRouter {
183
185
  public cacheDb?: CacheDb;
184
186
  public cacheCleaner?: CacheCleaner;
185
187
 
186
- // Certificate status tracking from SmartProxy events
188
+ // Certificate status tracking from SmartProxy events (keyed by domain)
187
189
  public certificateStatusMap = new Map<string, {
188
190
  status: 'valid' | 'failed';
189
- domain: string;
191
+ routeNames: string[];
190
192
  expiryDate?: string;
191
193
  issuedAt?: string;
192
194
  source?: string;
193
195
  error?: string;
194
196
  }>();
195
197
 
198
+ // Certificate provisioning scheduler with backoff + stagger
199
+ public certProvisionScheduler?: CertProvisionScheduler;
200
+
196
201
  // TypedRouter for API endpoints
197
202
  public typedrouter = new plugins.typedrequest.TypedRouter();
198
203
 
@@ -204,7 +209,14 @@ export class DcRouter {
204
209
  this.options = {
205
210
  ...optionsArg
206
211
  };
207
-
212
+
213
+ // Default storage to filesystem if not configured
214
+ if (!this.options.storage) {
215
+ this.options.storage = {
216
+ fsPath: plugins.path.join(paths.dcrouterHomeDir, 'storage'),
217
+ };
218
+ }
219
+
208
220
  // Initialize storage manager
209
221
  this.storageManager = new StorageManager(this.options.storage);
210
222
  }
@@ -437,38 +449,77 @@ export class DcRouter {
437
449
  const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = {
438
450
  ...this.options.smartProxyConfig,
439
451
  routes,
440
- acme: acmeConfig
452
+ acme: acmeConfig,
453
+ certStore: {
454
+ loadAll: async () => {
455
+ const keys = await this.storageManager.list('/proxy-certs/');
456
+ const certs: Array<{ domain: string; publicKey: string; privateKey: string; ca?: string }> = [];
457
+ for (const key of keys) {
458
+ const data = await this.storageManager.getJSON(key);
459
+ if (data) certs.push(data);
460
+ }
461
+ return certs;
462
+ },
463
+ save: async (domain: string, publicKey: string, privateKey: string, ca?: string) => {
464
+ await this.storageManager.setJSON(`/proxy-certs/${domain}`, {
465
+ domain, publicKey, privateKey, ca,
466
+ });
467
+ },
468
+ remove: async (domain: string) => {
469
+ await this.storageManager.delete(`/proxy-certs/${domain}`);
470
+ },
471
+ },
441
472
  };
442
473
 
474
+ // Initialize cert provision scheduler
475
+ this.certProvisionScheduler = new CertProvisionScheduler(this.storageManager);
476
+
443
477
  // If we have DNS challenge handlers, create SmartAcme and wire to certProvisionFunction
444
478
  if (challengeHandlers.length > 0) {
445
479
  this.smartAcme = new plugins.smartacme.SmartAcme({
446
480
  accountEmail: acmeConfig?.accountEmail || this.options.tls?.contactEmail || 'admin@example.com',
447
- certManager: new plugins.smartacme.certmanagers.MemoryCertManager(),
481
+ certManager: new StorageBackedCertManager(this.storageManager),
448
482
  environment: 'production',
449
483
  challengeHandlers: challengeHandlers,
450
484
  challengePriority: ['dns-01'],
451
485
  });
452
486
  await this.smartAcme.start();
453
487
 
488
+ const scheduler = this.certProvisionScheduler;
454
489
  smartProxyConfig.certProvisionFunction = async (domain, eventComms) => {
490
+ // Check backoff before attempting provision
491
+ if (await scheduler.isInBackoff(domain)) {
492
+ const info = await scheduler.getBackoffInfo(domain);
493
+ const msg = `Domain ${domain} is in backoff (${info?.failures} failures), retry after ${info?.retryAfter}`;
494
+ eventComms.warn(msg);
495
+ throw new Error(msg);
496
+ }
497
+
455
498
  try {
456
- eventComms.log(`Attempting DNS-01 via SmartAcme for ${domain}`);
457
- eventComms.setSource('smartacme-dns-01');
458
- const cert = await this.smartAcme.getCertificateForDomain(domain);
459
- if (cert.validUntil) {
460
- eventComms.setExpiryDate(new Date(cert.validUntil));
461
- }
462
- return {
463
- id: cert.id,
464
- domainName: cert.domainName,
465
- created: cert.created,
466
- validUntil: cert.validUntil,
467
- privateKey: cert.privateKey,
468
- publicKey: cert.publicKey,
469
- csr: cert.csr,
470
- };
499
+ const result = await scheduler.enqueueProvision(domain, async () => {
500
+ eventComms.log(`Attempting DNS-01 via SmartAcme for ${domain}`);
501
+ eventComms.setSource('smartacme-dns-01');
502
+ const cert = await this.smartAcme.getCertificateForDomain(domain);
503
+ if (cert.validUntil) {
504
+ eventComms.setExpiryDate(new Date(cert.validUntil));
505
+ }
506
+ return {
507
+ id: cert.id,
508
+ domainName: cert.domainName,
509
+ created: cert.created,
510
+ validUntil: cert.validUntil,
511
+ privateKey: cert.privateKey,
512
+ publicKey: cert.publicKey,
513
+ csr: cert.csr,
514
+ };
515
+ });
516
+
517
+ // Success — clear any backoff
518
+ await scheduler.clearBackoff(domain);
519
+ return result;
471
520
  } catch (err) {
521
+ // Record failure for backoff tracking
522
+ await scheduler.recordFailure(domain, err.message);
472
523
  eventComms.warn(`SmartAcme DNS-01 failed for ${domain}: ${err.message}, falling back to http-01`);
473
524
  return 'http01';
474
525
  }
@@ -492,39 +543,34 @@ export class DcRouter {
492
543
  });
493
544
 
494
545
  // Always listen for certificate events — emitted by both ACME and certProvisionFunction paths
546
+ // Events are keyed by domain for domain-centric certificate tracking
495
547
  this.smartProxy.on('certificate-issued', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
496
548
  console.log(`[DcRouter] Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
497
- const routeName = this.findRouteNameForDomain(event.domain);
498
- if (routeName) {
499
- this.certificateStatusMap.set(routeName, {
500
- status: 'valid', domain: event.domain,
501
- expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
502
- source: event.source,
503
- });
504
- }
549
+ const routeNames = this.findRouteNamesForDomain(event.domain);
550
+ this.certificateStatusMap.set(event.domain, {
551
+ status: 'valid', routeNames,
552
+ expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
553
+ source: event.source,
554
+ });
505
555
  });
506
556
 
507
557
  this.smartProxy.on('certificate-renewed', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
508
558
  console.log(`[DcRouter] Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
509
- const routeName = this.findRouteNameForDomain(event.domain);
510
- if (routeName) {
511
- this.certificateStatusMap.set(routeName, {
512
- status: 'valid', domain: event.domain,
513
- expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
514
- source: event.source,
515
- });
516
- }
559
+ const routeNames = this.findRouteNamesForDomain(event.domain);
560
+ this.certificateStatusMap.set(event.domain, {
561
+ status: 'valid', routeNames,
562
+ expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
563
+ source: event.source,
564
+ });
517
565
  });
518
566
 
519
567
  this.smartProxy.on('certificate-failed', (event: plugins.smartproxy.ICertificateFailedEvent) => {
520
568
  console.error(`[DcRouter] Certificate failed for ${event.domain} (${event.source}):`, event.error);
521
- const routeName = this.findRouteNameForDomain(event.domain);
522
- if (routeName) {
523
- this.certificateStatusMap.set(routeName, {
524
- status: 'failed', domain: event.domain, error: event.error,
525
- source: event.source,
526
- });
527
- }
569
+ const routeNames = this.findRouteNamesForDomain(event.domain);
570
+ this.certificateStatusMap.set(event.domain, {
571
+ status: 'failed', routeNames, error: event.error,
572
+ source: event.source,
573
+ });
528
574
  });
529
575
 
530
576
  // Start SmartProxy
@@ -697,7 +743,7 @@ export class DcRouter {
697
743
  }
698
744
 
699
745
  /**
700
- * Find the route name that matches a given domain
746
+ * Find the first route name that matches a given domain
701
747
  */
702
748
  private findRouteNameForDomain(domain: string): string | undefined {
703
749
  if (!this.smartProxy) return undefined;
@@ -713,6 +759,27 @@ export class DcRouter {
713
759
  return undefined;
714
760
  }
715
761
 
762
+ /**
763
+ * Find ALL route names that match a given domain
764
+ */
765
+ public findRouteNamesForDomain(domain: string): string[] {
766
+ if (!this.smartProxy) return [];
767
+ const names: string[] = [];
768
+ for (const route of this.smartProxy.routeManager.getRoutes()) {
769
+ if (!route.match.domains || !route.name) continue;
770
+ const routeDomains = Array.isArray(route.match.domains)
771
+ ? route.match.domains
772
+ : [route.match.domains];
773
+ for (const pattern of routeDomains) {
774
+ if (this.isDomainMatch(domain, pattern)) {
775
+ names.push(route.name);
776
+ break; // This route already matched, no need to check other patterns
777
+ }
778
+ }
779
+ }
780
+ return names;
781
+ }
782
+
716
783
  public async stop() {
717
784
  console.log('Stopping DcRouter services...');
718
785