Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77d40985f3 | |||
| adf9262ded | |||
| e2d182ca03 | |||
| 8cd713447e | |||
| 2cf3dbdd95 | |||
| 1c75bac44f |
22
changelog.md
22
changelog.md
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-02-16 - 9.1.3 - fix(smartacme)
|
||||
Include base domain alongside wildcard when building identifiers for wildcard certificate requests
|
||||
|
||||
- When isWildcardRequest is true, the base domain (e.g. example.com) is now added in addition to the wildcard (*.example.com) so the issued certificate covers both apex and wildcard entries.
|
||||
- Prevents missing SAN for the apex domain when requesting wildcard certificates.
|
||||
|
||||
## 2026-02-15 - 9.1.2 - fix(docs)
|
||||
document built-in concurrency control, rate limiting, and request deduplication in README
|
||||
|
||||
- Added a new 'Concurrency Control & Rate Limiting' section to the README describing per-domain mutex, global concurrency cap, and sliding-window account rate limiting (defaults: 1 per domain, 5 global, 250 per 3 hours).
|
||||
- Documented new SmartAcme options in the interface: maxConcurrentIssuances, maxOrdersPerWindow, and orderWindowMs.
|
||||
- Added example code showing configuration of the limits and an example of request deduplication behavior (multiple subdomain requests resolving to a single ACME order).
|
||||
- Added an example subscription to certIssuanceEvents and updated the components table with TaskManager entry.
|
||||
- Change is documentation-only (README) — no code changes; safe patch release.
|
||||
|
||||
## 2026-02-15 - 9.1.1 - fix(deps)
|
||||
bump @push.rocks/smarttime to ^4.2.3 and @push.rocks/taskbuffer to ^6.1.2
|
||||
|
||||
- @push.rocks/smarttime: ^4.1.1 -> ^4.2.3
|
||||
- @push.rocks/taskbuffer: ^6.1.0 -> ^6.1.2
|
||||
- Only package.json dependency version updates; no code changes
|
||||
|
||||
## 2026-02-15 - 9.1.0 - feat(smartacme)
|
||||
Integrate @push.rocks/taskbuffer TaskManager to coordinate ACME certificate issuance with per-domain mutex, global concurrency cap, and account-level rate limiting; refactor issuance flow into a single reusable cert-issuance task, expose issuance events, and update lifecycle to start/stop the TaskManager. Add configuration for concurrent issuances and sliding-window order limits, export taskbuffer types/plugins, and update tests and docs accordingly.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartacme",
|
||||
"version": "9.1.0",
|
||||
"version": "9.1.3",
|
||||
"private": false,
|
||||
"description": "A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.",
|
||||
"main": "dist_ts/index.js",
|
||||
@@ -43,14 +43,14 @@
|
||||
"@peculiar/x509": "^1.14.3",
|
||||
"@push.rocks/lik": "^6.2.2",
|
||||
"@push.rocks/smartdata": "^7.0.15",
|
||||
"@push.rocks/taskbuffer": "^6.1.0",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartdns": "^7.8.1",
|
||||
"@push.rocks/smartlog": "^3.1.10",
|
||||
"@push.rocks/smartnetwork": "^4.4.0",
|
||||
"@push.rocks/smartstring": "^4.1.0",
|
||||
"@push.rocks/smarttime": "^4.1.1",
|
||||
"@push.rocks/smarttime": "^4.2.3",
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@push.rocks/taskbuffer": "^6.1.2",
|
||||
"@tsclass/tsclass": "^9.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
69
pnpm-lock.yaml
generated
69
pnpm-lock.yaml
generated
@@ -36,14 +36,14 @@ importers:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
'@push.rocks/smarttime':
|
||||
specifier: ^4.1.1
|
||||
version: 4.1.1
|
||||
specifier: ^4.2.3
|
||||
version: 4.2.3
|
||||
'@push.rocks/smartunique':
|
||||
specifier: ^3.0.9
|
||||
version: 3.0.9
|
||||
'@push.rocks/taskbuffer':
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.1
|
||||
specifier: ^6.1.2
|
||||
version: 6.1.2
|
||||
'@tsclass/tsclass':
|
||||
specifier: ^9.3.0
|
||||
version: 9.3.0
|
||||
@@ -1015,8 +1015,8 @@ packages:
|
||||
'@push.rocks/smartstring@4.1.0':
|
||||
resolution: {integrity: sha512-Q4py/Nm3KTDhQ9EiC75yBtSTLR0KLMwhKM+8gGcutgKotZT6wJ3gncjmtD8LKFfNhb4lSaFMgPJgLrCHTOH6Iw==}
|
||||
|
||||
'@push.rocks/smarttime@4.1.1':
|
||||
resolution: {integrity: sha512-Ha/3J/G+zfTl4ahpZgF6oUOZnUjpLhrBja0OQ2cloFxF9sKT8I1COaSqIfBGDtoK2Nly4UD4aTJ3JcJNOg/kgA==}
|
||||
'@push.rocks/smarttime@4.2.3':
|
||||
resolution: {integrity: sha512-8gMg8RUkrCG4p9NcEUZV7V6KpL24+jAMK02g7qyhfA6giz/JJWD0+8w8xjSR+G7qe16KVQ2y3RbvAL9TxmO36g==}
|
||||
|
||||
'@push.rocks/smartunique@3.0.9':
|
||||
resolution: {integrity: sha512-q6DYQgT7/dqdWi9HusvtWCjdsFzLFXY9LTtaZV6IYNJt6teZOonoygxTdNt9XLn6niBSbLYrHSKvJNTRH/uK+g==}
|
||||
@@ -1039,8 +1039,8 @@ packages:
|
||||
'@push.rocks/taskbuffer@3.5.0':
|
||||
resolution: {integrity: sha512-Y9WwIEIyp6oVFdj06j84tfrZIvjhbMb3DF52rYxlTeYLk3W7RPhSg1bGPCbtkXWeKdBrSe37V90BkOG7Qq8Pqg==}
|
||||
|
||||
'@push.rocks/taskbuffer@6.1.1':
|
||||
resolution: {integrity: sha512-rEJxf+yIbHwztNkrL5QJFinf0wai1Fzs1xgonEOo9LmG/DDCanfLWHSd5zCVG0kXxzz4sHv87fgkg+w/TIHLpg==}
|
||||
'@push.rocks/taskbuffer@6.1.2':
|
||||
resolution: {integrity: sha512-sdqKd8N/GidztQ1k3r8A86rLvD8Afyir5FjYCNJXDD9837JLoqzHaOKGltUSBsCGh2gjsZn6GydsY6HhXQgvZQ==}
|
||||
|
||||
'@push.rocks/webrequest@3.0.37':
|
||||
resolution: {integrity: sha512-fLN7kP6GeHFxE4UH4r9C9pjcQb0QkJxHeAMwXvbOqB9hh0MFNKhtGU7GoaTn8SVRGRMPc9UqZVNwo6u5l8Wn0A==}
|
||||
@@ -2293,6 +2293,10 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
croner@10.0.1:
|
||||
resolution: {integrity: sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g==}
|
||||
engines: {node: '>=18.0'}
|
||||
|
||||
croner@4.4.1:
|
||||
resolution: {integrity: sha512-aqVeeIPCf5/NZFlz4mN4MLEOs9xf4ODCmHQDs+577JFj8mK3RkKJz77h7+Rn94AijUqKdFNOUHM+v88d8p02UQ==}
|
||||
|
||||
@@ -2300,10 +2304,6 @@ packages:
|
||||
resolution: {integrity: sha512-9pSLe+tDJnmNak2JeMkz6ZmTCXP5p6vCxSd4kvDqrTJkqAP62j2uAEIZjf8cPDZIakStujqVzh5Y5MIWH3yYAw==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
||||
croner@9.0.0:
|
||||
resolution: {integrity: sha512-onMB0OkDjkXunhdW9htFjEhqrD54+M94i6ackoUkjHKbRnXdyEyKRelp4nJ1kAz32+s27jP1FsebpJCVl0BsvA==}
|
||||
engines: {node: '>=18.0'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -2322,6 +2322,9 @@ packages:
|
||||
dayjs@1.11.13:
|
||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||
|
||||
dayjs@1.11.19:
|
||||
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
|
||||
|
||||
debug@2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
@@ -3587,8 +3590,8 @@ packages:
|
||||
resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
pretty-ms@9.2.0:
|
||||
resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==}
|
||||
pretty-ms@9.3.0:
|
||||
resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
progress@2.0.3:
|
||||
@@ -4295,7 +4298,7 @@ snapshots:
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smartsitemap': 2.0.4
|
||||
'@push.rocks/smartstream': 3.2.5
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/taskbuffer': 3.5.0
|
||||
'@push.rocks/webrequest': 3.0.37
|
||||
'@push.rocks/webstore': 2.0.20
|
||||
@@ -5617,7 +5620,7 @@ snapshots:
|
||||
'@push.rocks/smartrequest': 5.0.1
|
||||
'@push.rocks/smarts3': 3.0.3
|
||||
'@push.rocks/smartshell': 3.3.0
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@types/ws': 8.18.1
|
||||
figures: 6.1.0
|
||||
ws: 8.19.0
|
||||
@@ -5972,7 +5975,7 @@ snapshots:
|
||||
'@push.rocks/smartmatch': 2.0.0
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@types/minimatch': 5.1.2
|
||||
'@types/symbol-tree': 3.2.5
|
||||
symbol-tree: 3.2.4
|
||||
@@ -6127,7 +6130,7 @@ snapshots:
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smartstring': 4.1.0
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/smartunique': 3.0.9
|
||||
'@push.rocks/taskbuffer': 3.5.0
|
||||
'@tsclass/tsclass': 8.2.1
|
||||
@@ -6155,7 +6158,7 @@ snapshots:
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smartstring': 4.1.0
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/smartunique': 3.0.9
|
||||
'@push.rocks/taskbuffer': 3.5.0
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
@@ -6326,7 +6329,7 @@ snapshots:
|
||||
'@push.rocks/smartfile': 11.2.7
|
||||
'@push.rocks/smarthash': 3.2.6
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/webrequest': 4.0.2
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
|
||||
@@ -6412,7 +6415,7 @@ snapshots:
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrequest': 4.4.2
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/smartversion': 3.0.5
|
||||
package-json: 8.1.1
|
||||
transitivePeerDependencies:
|
||||
@@ -6585,7 +6588,7 @@ snapshots:
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
engine.io: 6.6.4
|
||||
socket.io: 4.8.1
|
||||
socket.io-client: 4.8.1
|
||||
@@ -6635,16 +6638,16 @@ snapshots:
|
||||
dependencies:
|
||||
'@push.rocks/isounique': 1.0.5
|
||||
|
||||
'@push.rocks/smarttime@4.1.1':
|
||||
'@push.rocks/smarttime@4.2.3':
|
||||
dependencies:
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
croner: 9.0.0
|
||||
croner: 10.0.1
|
||||
date-fns: 4.1.0
|
||||
dayjs: 1.11.13
|
||||
dayjs: 1.11.19
|
||||
is-nan: 1.3.2
|
||||
pretty-ms: 9.2.0
|
||||
pretty-ms: 9.3.0
|
||||
|
||||
'@push.rocks/smartunique@3.0.9':
|
||||
dependencies:
|
||||
@@ -6680,7 +6683,7 @@ snapshots:
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/smartunique': 3.0.9
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
@@ -6688,7 +6691,7 @@ snapshots:
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
'@push.rocks/taskbuffer@6.1.1':
|
||||
'@push.rocks/taskbuffer@6.1.2':
|
||||
dependencies:
|
||||
'@design.estate/dees-element': 2.1.6
|
||||
'@push.rocks/lik': 6.2.2
|
||||
@@ -6696,7 +6699,7 @@ snapshots:
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/smartunique': 3.0.9
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
@@ -8260,12 +8263,12 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
croner@10.0.1: {}
|
||||
|
||||
croner@4.4.1: {}
|
||||
|
||||
croner@5.7.0: {}
|
||||
|
||||
croner@9.0.0: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -8282,6 +8285,8 @@ snapshots:
|
||||
|
||||
dayjs@1.11.13: {}
|
||||
|
||||
dayjs@1.11.19: {}
|
||||
|
||||
debug@2.6.9:
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
@@ -9765,7 +9770,7 @@ snapshots:
|
||||
dependencies:
|
||||
parse-ms: 3.0.0
|
||||
|
||||
pretty-ms@9.2.0:
|
||||
pretty-ms@9.3.0:
|
||||
dependencies:
|
||||
parse-ms: 4.0.0
|
||||
|
||||
|
||||
93
readme.md
93
readme.md
@@ -16,7 +16,7 @@ Ensure your project uses TypeScript and ECMAScript Modules (ESM).
|
||||
|
||||
## Usage
|
||||
|
||||
`@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), and structured error handling with smart retry logic.
|
||||
`@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.
|
||||
|
||||
### 🚀 Quick Start
|
||||
|
||||
@@ -58,18 +58,22 @@ await smartAcme.stop();
|
||||
|
||||
```typescript
|
||||
interface ISmartAcmeOptions {
|
||||
accountEmail: string; // ACME account email
|
||||
accountPrivateKey?: string; // Optional account key (auto-generated if omitted)
|
||||
certManager: ICertManager; // Certificate storage backend
|
||||
accountEmail: string; // ACME account email
|
||||
accountPrivateKey?: string; // Optional account key (auto-generated if omitted)
|
||||
certManager: ICertManager; // Certificate storage backend
|
||||
environment: 'production' | 'integration'; // Let's Encrypt environment
|
||||
challengeHandlers: IChallengeHandler[]; // At least one handler required
|
||||
challengePriority?: string[]; // e.g. ['dns-01', 'http-01']
|
||||
retryOptions?: { // Optional retry/backoff config
|
||||
retries?: number; // Default: 10
|
||||
factor?: number; // Default: 4
|
||||
minTimeoutMs?: number; // Default: 1000
|
||||
maxTimeoutMs?: number; // Default: 60000
|
||||
challengeHandlers: IChallengeHandler[]; // At least one handler required
|
||||
challengePriority?: string[]; // e.g. ['dns-01', 'http-01']
|
||||
retryOptions?: { // Optional retry/backoff config
|
||||
retries?: number; // Default: 10
|
||||
factor?: number; // Default: 4
|
||||
minTimeoutMs?: number; // Default: 1000
|
||||
maxTimeoutMs?: number; // Default: 60000
|
||||
};
|
||||
// Concurrency & rate limiting
|
||||
maxConcurrentIssuances?: number; // Global cap on parallel ACME ops (default: 5)
|
||||
maxOrdersPerWindow?: number; // Max orders in sliding window (default: 250)
|
||||
orderWindowMs?: number; // Sliding window duration in ms (default: 3 hours)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -112,6 +116,72 @@ cert.isStillValid(); // true if not expired
|
||||
cert.shouldBeRenewed(); // true if expires within 10 days
|
||||
```
|
||||
|
||||
## 🔀 Concurrency Control & Rate Limiting
|
||||
|
||||
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`.
|
||||
|
||||
### How It Works
|
||||
|
||||
Three constraint layers protect your ACME account:
|
||||
|
||||
| Layer | What It Does | Default |
|
||||
|-------|-------------|---------|
|
||||
| **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 |
|
||||
| **Global concurrency cap** | Limits total parallel ACME operations across all domains. | 5 concurrent |
|
||||
| **Account rate limit** | Sliding-window rate limiter that keeps you under Let's Encrypt's 300 orders/3h account limit. | 250 per 3 hours |
|
||||
|
||||
### 🛡️ Automatic Request Deduplication
|
||||
|
||||
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.
|
||||
|
||||
```typescript
|
||||
// These all resolve to the same certificate with a single ACME order:
|
||||
const results = await Promise.all([
|
||||
smartAcme.getCertificateForDomain('app.example.com'),
|
||||
smartAcme.getCertificateForDomain('api.example.com'),
|
||||
smartAcme.getCertificateForDomain('cdn.example.com'),
|
||||
]);
|
||||
```
|
||||
|
||||
### ⚡ Configuring Limits
|
||||
|
||||
```typescript
|
||||
const smartAcme = new SmartAcme({
|
||||
accountEmail: 'admin@example.com',
|
||||
certManager,
|
||||
environment: 'production',
|
||||
challengeHandlers: [dnsHandler],
|
||||
maxConcurrentIssuances: 10, // Allow up to 10 parallel ACME issuances
|
||||
maxOrdersPerWindow: 200, // Cap at 200 orders per window
|
||||
orderWindowMs: 2 * 60 * 60_000, // 2-hour sliding window
|
||||
});
|
||||
```
|
||||
|
||||
### 📊 Observing Issuance Progress
|
||||
|
||||
Subscribe to the `certIssuanceEvents` stream to observe certificate issuance progress in real-time:
|
||||
|
||||
```typescript
|
||||
smartAcme.certIssuanceEvents.subscribe((event) => {
|
||||
switch (event.type) {
|
||||
case 'started':
|
||||
console.log(`🔄 Issuance started: ${event.task.name}`);
|
||||
break;
|
||||
case 'step':
|
||||
console.log(`📍 Step: ${event.stepName} (${event.task.currentProgress}%)`);
|
||||
break;
|
||||
case 'completed':
|
||||
console.log(`✅ Issuance completed: ${event.task.name}`);
|
||||
break;
|
||||
case 'failed':
|
||||
console.log(`❌ Issuance failed: ${event.error}`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Each issuance goes through four steps: **prepare** (10%) → **authorize** (40%) → **finalize** (30%) → **store** (20%).
|
||||
|
||||
## Certificate Managers
|
||||
|
||||
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
|
||||
| `AcmeError` | Structured error class with type URN, subproblems, Retry-After, retryability |
|
||||
| `AcmeOrderManager` | Order lifecycle — create, poll, finalize, download certificate |
|
||||
| `AcmeChallengeManager` | Key authorization computation and challenge completion |
|
||||
| `TaskManager` | Constraint-based concurrency control, rate limiting, and request deduplication via `@push.rocks/taskbuffer` |
|
||||
|
||||
All cryptographic operations use `node:crypto`. The only external crypto dependency is `@peculiar/x509` for CSR generation.
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartacme',
|
||||
version: '9.1.0',
|
||||
version: '9.1.3',
|
||||
description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.'
|
||||
}
|
||||
|
||||
@@ -430,6 +430,7 @@ export class SmartAcme {
|
||||
|
||||
if (isWildcardRequest) {
|
||||
identifiers.push({ type: 'dns', value: `*.${certDomainName}` });
|
||||
identifiers.push({ type: 'dns', value: certDomainName });
|
||||
} else {
|
||||
identifiers.push({ type: 'dns', value: certDomainName });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user