@push.rocks/smartacme 9.1.1 → 9.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/package.json +1 -1
- package/readme.md +82 -11
- package/ts/00_commitinfo_data.ts +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartacme',
|
|
6
|
-
version: '9.1.
|
|
6
|
+
version: '9.1.2',
|
|
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=
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -16,7 +16,7 @@ Ensure your project uses TypeScript and ECMAScript Modules (ESM).
|
|
|
16
16
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
|
-
`@push.rocks/smartacme` automates the full ACME certificate lifecycle — obtaining, renewing, and storing SSL/TLS certificates from Let's Encrypt. It features a built-in RFC 8555-compliant ACME protocol implementation, pluggable challenge handlers (DNS-01, HTTP-01), pluggable certificate storage backends (MongoDB, in-memory, or your own),
|
|
19
|
+
`@push.rocks/smartacme` automates the full ACME certificate lifecycle — obtaining, renewing, and storing SSL/TLS certificates from Let's Encrypt. It features a built-in RFC 8555-compliant ACME protocol implementation, pluggable challenge handlers (DNS-01, HTTP-01), pluggable certificate storage backends (MongoDB, in-memory, or your own), structured error handling with smart retry logic, and built-in concurrency control with rate limiting to keep you safely within Let's Encrypt limits.
|
|
20
20
|
|
|
21
21
|
### 🚀 Quick Start
|
|
22
22
|
|
|
@@ -58,18 +58,22 @@ await smartAcme.stop();
|
|
|
58
58
|
|
|
59
59
|
```typescript
|
|
60
60
|
interface ISmartAcmeOptions {
|
|
61
|
-
accountEmail: string;
|
|
62
|
-
accountPrivateKey?: string;
|
|
63
|
-
certManager: ICertManager;
|
|
61
|
+
accountEmail: string; // ACME account email
|
|
62
|
+
accountPrivateKey?: string; // Optional account key (auto-generated if omitted)
|
|
63
|
+
certManager: ICertManager; // Certificate storage backend
|
|
64
64
|
environment: 'production' | 'integration'; // Let's Encrypt environment
|
|
65
|
-
challengeHandlers: IChallengeHandler[];
|
|
66
|
-
challengePriority?: string[];
|
|
67
|
-
retryOptions?: {
|
|
68
|
-
retries?: number;
|
|
69
|
-
factor?: number;
|
|
70
|
-
minTimeoutMs?: number;
|
|
71
|
-
maxTimeoutMs?: number;
|
|
65
|
+
challengeHandlers: IChallengeHandler[]; // At least one handler required
|
|
66
|
+
challengePriority?: string[]; // e.g. ['dns-01', 'http-01']
|
|
67
|
+
retryOptions?: { // Optional retry/backoff config
|
|
68
|
+
retries?: number; // Default: 10
|
|
69
|
+
factor?: number; // Default: 4
|
|
70
|
+
minTimeoutMs?: number; // Default: 1000
|
|
71
|
+
maxTimeoutMs?: number; // Default: 60000
|
|
72
72
|
};
|
|
73
|
+
// Concurrency & rate limiting
|
|
74
|
+
maxConcurrentIssuances?: number; // Global cap on parallel ACME ops (default: 5)
|
|
75
|
+
maxOrdersPerWindow?: number; // Max orders in sliding window (default: 250)
|
|
76
|
+
orderWindowMs?: number; // Sliding window duration in ms (default: 3 hours)
|
|
73
77
|
}
|
|
74
78
|
```
|
|
75
79
|
|
|
@@ -112,6 +116,72 @@ cert.isStillValid(); // true if not expired
|
|
|
112
116
|
cert.shouldBeRenewed(); // true if expires within 10 days
|
|
113
117
|
```
|
|
114
118
|
|
|
119
|
+
## 🔀 Concurrency Control & Rate Limiting
|
|
120
|
+
|
|
121
|
+
When many callers request certificates concurrently (e.g., hundreds of subdomains under the same TLD), SmartAcme automatically handles deduplication, concurrency, and rate limiting using a built-in task manager powered by `@push.rocks/taskbuffer`.
|
|
122
|
+
|
|
123
|
+
### How It Works
|
|
124
|
+
|
|
125
|
+
Three constraint layers protect your ACME account:
|
|
126
|
+
|
|
127
|
+
| Layer | What It Does | Default |
|
|
128
|
+
|-------|-------------|---------|
|
|
129
|
+
| **Per-domain mutex** | Only one issuance runs per base domain at a time. Concurrent requests for the same domain automatically wait and receive the same certificate result. | 1 concurrent per domain |
|
|
130
|
+
| **Global concurrency cap** | Limits total parallel ACME operations across all domains. | 5 concurrent |
|
|
131
|
+
| **Account rate limit** | Sliding-window rate limiter that keeps you under Let's Encrypt's 300 orders/3h account limit. | 250 per 3 hours |
|
|
132
|
+
|
|
133
|
+
### 🛡️ Automatic Request Deduplication
|
|
134
|
+
|
|
135
|
+
If 100 requests come in for subdomains of `example.com` simultaneously, only **one** ACME issuance runs. All other callers automatically wait and receive the same certificate — no duplicate orders, no wasted rate limit budget.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// These all resolve to the same certificate with a single ACME order:
|
|
139
|
+
const results = await Promise.all([
|
|
140
|
+
smartAcme.getCertificateForDomain('app.example.com'),
|
|
141
|
+
smartAcme.getCertificateForDomain('api.example.com'),
|
|
142
|
+
smartAcme.getCertificateForDomain('cdn.example.com'),
|
|
143
|
+
]);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### ⚡ Configuring Limits
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const smartAcme = new SmartAcme({
|
|
150
|
+
accountEmail: 'admin@example.com',
|
|
151
|
+
certManager,
|
|
152
|
+
environment: 'production',
|
|
153
|
+
challengeHandlers: [dnsHandler],
|
|
154
|
+
maxConcurrentIssuances: 10, // Allow up to 10 parallel ACME issuances
|
|
155
|
+
maxOrdersPerWindow: 200, // Cap at 200 orders per window
|
|
156
|
+
orderWindowMs: 2 * 60 * 60_000, // 2-hour sliding window
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 📊 Observing Issuance Progress
|
|
161
|
+
|
|
162
|
+
Subscribe to the `certIssuanceEvents` stream to observe certificate issuance progress in real-time:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
smartAcme.certIssuanceEvents.subscribe((event) => {
|
|
166
|
+
switch (event.type) {
|
|
167
|
+
case 'started':
|
|
168
|
+
console.log(`🔄 Issuance started: ${event.task.name}`);
|
|
169
|
+
break;
|
|
170
|
+
case 'step':
|
|
171
|
+
console.log(`📍 Step: ${event.stepName} (${event.task.currentProgress}%)`);
|
|
172
|
+
break;
|
|
173
|
+
case 'completed':
|
|
174
|
+
console.log(`✅ Issuance completed: ${event.task.name}`);
|
|
175
|
+
break;
|
|
176
|
+
case 'failed':
|
|
177
|
+
console.log(`❌ Issuance failed: ${event.error}`);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Each issuance goes through four steps: **prepare** (10%) → **authorize** (40%) → **finalize** (30%) → **store** (20%).
|
|
184
|
+
|
|
115
185
|
## Certificate Managers
|
|
116
186
|
|
|
117
187
|
SmartAcme uses the `ICertManager` interface for pluggable certificate storage.
|
|
@@ -314,6 +384,7 @@ Under the hood, SmartAcme uses a fully custom RFC 8555-compliant ACME protocol i
|
|
|
314
384
|
| `AcmeError` | Structured error class with type URN, subproblems, Retry-After, retryability |
|
|
315
385
|
| `AcmeOrderManager` | Order lifecycle — create, poll, finalize, download certificate |
|
|
316
386
|
| `AcmeChallengeManager` | Key authorization computation and challenge completion |
|
|
387
|
+
| `TaskManager` | Constraint-based concurrency control, rate limiting, and request deduplication via `@push.rocks/taskbuffer` |
|
|
317
388
|
|
|
318
389
|
All cryptographic operations use `node:crypto`. The only external crypto dependency is `@peculiar/x509` for CSR generation.
|
|
319
390
|
|
package/ts/00_commitinfo_data.ts
CHANGED