@serve.zone/dcrouter 5.5.0 → 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.5.0",
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.3.0",
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.5.0',
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
+ }
@@ -14,6 +14,7 @@ import { logger } from './logger.js';
14
14
  // Import storage manager
15
15
  import { StorageManager, type IStorageConfig } from './storage/index.js';
16
16
  import { StorageBackedCertManager } from './classes.storage-cert-manager.js';
17
+ import { CertProvisionScheduler } from './classes.cert-provision-scheduler.js';
17
18
  // Import cache system
18
19
  import { CacheDb, CacheCleaner, type ICacheDbOptions } from './cache/index.js';
19
20
 
@@ -184,16 +185,19 @@ export class DcRouter {
184
185
  public cacheDb?: CacheDb;
185
186
  public cacheCleaner?: CacheCleaner;
186
187
 
187
- // Certificate status tracking from SmartProxy events
188
+ // Certificate status tracking from SmartProxy events (keyed by domain)
188
189
  public certificateStatusMap = new Map<string, {
189
190
  status: 'valid' | 'failed';
190
- domain: string;
191
+ routeNames: string[];
191
192
  expiryDate?: string;
192
193
  issuedAt?: string;
193
194
  source?: string;
194
195
  error?: string;
195
196
  }>();
196
197
 
198
+ // Certificate provisioning scheduler with backoff + stagger
199
+ public certProvisionScheduler?: CertProvisionScheduler;
200
+
197
201
  // TypedRouter for API endpoints
198
202
  public typedrouter = new plugins.typedrequest.TypedRouter();
199
203
 
@@ -467,6 +471,9 @@ export class DcRouter {
467
471
  },
468
472
  };
469
473
 
474
+ // Initialize cert provision scheduler
475
+ this.certProvisionScheduler = new CertProvisionScheduler(this.storageManager);
476
+
470
477
  // If we have DNS challenge handlers, create SmartAcme and wire to certProvisionFunction
471
478
  if (challengeHandlers.length > 0) {
472
479
  this.smartAcme = new plugins.smartacme.SmartAcme({
@@ -478,24 +485,41 @@ export class DcRouter {
478
485
  });
479
486
  await this.smartAcme.start();
480
487
 
488
+ const scheduler = this.certProvisionScheduler;
481
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
+
482
498
  try {
483
- eventComms.log(`Attempting DNS-01 via SmartAcme for ${domain}`);
484
- eventComms.setSource('smartacme-dns-01');
485
- const cert = await this.smartAcme.getCertificateForDomain(domain);
486
- if (cert.validUntil) {
487
- eventComms.setExpiryDate(new Date(cert.validUntil));
488
- }
489
- return {
490
- id: cert.id,
491
- domainName: cert.domainName,
492
- created: cert.created,
493
- validUntil: cert.validUntil,
494
- privateKey: cert.privateKey,
495
- publicKey: cert.publicKey,
496
- csr: cert.csr,
497
- };
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;
498
520
  } catch (err) {
521
+ // Record failure for backoff tracking
522
+ await scheduler.recordFailure(domain, err.message);
499
523
  eventComms.warn(`SmartAcme DNS-01 failed for ${domain}: ${err.message}, falling back to http-01`);
500
524
  return 'http01';
501
525
  }
@@ -519,39 +543,34 @@ export class DcRouter {
519
543
  });
520
544
 
521
545
  // Always listen for certificate events — emitted by both ACME and certProvisionFunction paths
546
+ // Events are keyed by domain for domain-centric certificate tracking
522
547
  this.smartProxy.on('certificate-issued', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
523
548
  console.log(`[DcRouter] Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
524
- const routeName = this.findRouteNameForDomain(event.domain);
525
- if (routeName) {
526
- this.certificateStatusMap.set(routeName, {
527
- status: 'valid', domain: event.domain,
528
- expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
529
- source: event.source,
530
- });
531
- }
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
+ });
532
555
  });
533
556
 
534
557
  this.smartProxy.on('certificate-renewed', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
535
558
  console.log(`[DcRouter] Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
536
- const routeName = this.findRouteNameForDomain(event.domain);
537
- if (routeName) {
538
- this.certificateStatusMap.set(routeName, {
539
- status: 'valid', domain: event.domain,
540
- expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
541
- source: event.source,
542
- });
543
- }
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
+ });
544
565
  });
545
566
 
546
567
  this.smartProxy.on('certificate-failed', (event: plugins.smartproxy.ICertificateFailedEvent) => {
547
568
  console.error(`[DcRouter] Certificate failed for ${event.domain} (${event.source}):`, event.error);
548
- const routeName = this.findRouteNameForDomain(event.domain);
549
- if (routeName) {
550
- this.certificateStatusMap.set(routeName, {
551
- status: 'failed', domain: event.domain, error: event.error,
552
- source: event.source,
553
- });
554
- }
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
+ });
555
574
  });
556
575
 
557
576
  // Start SmartProxy
@@ -724,7 +743,7 @@ export class DcRouter {
724
743
  }
725
744
 
726
745
  /**
727
- * Find the route name that matches a given domain
746
+ * Find the first route name that matches a given domain
728
747
  */
729
748
  private findRouteNameForDomain(domain: string): string | undefined {
730
749
  if (!this.smartProxy) return undefined;
@@ -740,6 +759,27 @@ export class DcRouter {
740
759
  return undefined;
741
760
  }
742
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
+
743
783
  public async stop() {
744
784
  console.log('Stopping DcRouter services...');
745
785