# @push.rocks/smartacme

A TypeScript-based ACME client with an easy yet powerful interface for LetsEncrypt certificate management.

# readme.md for @push.rocks/smartacme

A TypeScript-based ACME client and server for certificate management with a focus on simplicity and power. Includes a full RFC 8555-compliant ACME client for Let's Encrypt and a built-in ACME Directory Server for running your own Certificate Authority.

## Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.

## Install

```bash
pnpm add @push.rocks/smartacme
```

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), 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

```typescript
import { SmartAcme, certmanagers, handlers } from '@push.rocks/smartacme';
import * as cloudflare from '@apiclient.xyz/cloudflare';

// 1. Set up a certificate manager (MongoDB or in-memory)
const certManager = new certmanagers.MongoCertManager({
  mongoDbUrl: 'mongodb://localhost:27017',
  mongoDbName: 'myapp',
  mongoDbPass: 'secret',
});

// 2. Set up challenge handlers
const cfAccount = new cloudflare.CloudflareAccount('YOUR_CF_API_TOKEN');
const dnsHandler = new handlers.Dns01Handler(cfAccount);

// 3. Create and start SmartAcme
const smartAcme = new SmartAcme({
  accountEmail: 'admin@example.com',
  certManager,
  environment: 'production', // or 'integration' for staging
  challengeHandlers: [dnsHandler],
});

await smartAcme.start();

// 4. Get a certificate
const cert = await smartAcme.getCertificateForDomain('example.com');
console.log(cert.publicKey);  // PEM certificate chain
console.log(cert.privateKey); // PEM private key

// 5. Clean up
await smartAcme.stop();
```

### ⚙️ SmartAcme Options

```typescript
interface ISmartAcmeOptions {
  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
  };
  // 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)
}
```

### 📜 Getting Certificates

```typescript
// Standard certificate for a single domain
const cert = await smartAcme.getCertificateForDomain('example.com');

// Include wildcard coverage (requires DNS-01 handler)
// Issues a single cert covering example.com AND *.example.com
const certWithWildcard = await smartAcme.getCertificateForDomain('example.com', {
  includeWildcard: true,
});

// Request wildcard only
const wildcardCert = await smartAcme.getCertificateForDomain('*.example.com');
```

Certificates are automatically cached and reused when still valid. Renewal happens automatically when a certificate is within 10 days of expiration. The actual X.509 expiry date is parsed from the issued certificate, ensuring renewal timing is precise.

### 📦 Certificate Object

The returned `SmartacmeCert` (also exported as `Cert`) object has these properties:

| Property     | Type     | Description                          |
|-------------|----------|--------------------------------------|
| `id`        | `string` | Unique certificate identifier        |
| `domainName`| `string` | Domain the cert is issued for        |
| `publicKey` | `string` | PEM-encoded certificate chain        |
| `privateKey`| `string` | PEM-encoded private key              |
| `csr`       | `string` | Certificate Signing Request          |
| `created`   | `number` | Timestamp of creation                |
| `validUntil`| `number` | Timestamp of expiration              |

Useful methods:

```typescript
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.

### 🗄️ MongoCertManager

Persistent storage backed by MongoDB using `@push.rocks/smartdata`:

```typescript
import { certmanagers } from '@push.rocks/smartacme';

const certManager = new certmanagers.MongoCertManager({
  mongoDbUrl: 'mongodb://localhost:27017',
  mongoDbName: 'myapp',
  mongoDbPass: 'secret',
});
```

### 🧪 MemoryCertManager

In-memory storage, ideal for testing or ephemeral workloads:

```typescript
import { certmanagers } from '@push.rocks/smartacme';

const certManager = new certmanagers.MemoryCertManager();
```

### 🔧 Custom Certificate Manager

Implement the `ICertManager` interface for your own storage backend:

```typescript
import type { ICertManager, Cert } from '@push.rocks/smartacme';

class RedisCertManager implements ICertManager {
  async init(): Promise<void> { /* connect */ }
  async retrieveCertificate(domainName: string): Promise<Cert | null> { /* lookup */ }
  async storeCertificate(cert: Cert): Promise<void> { /* save */ }
  async deleteCertificate(domainName: string): Promise<void> { /* remove */ }
  async close(): Promise<void> { /* disconnect */ }
  async wipe(): Promise<void> { /* clear all */ }
}
```

## Challenge Handlers

SmartAcme ships with three built-in ACME challenge handlers. All implement `IChallengeHandler<T>`.

### 🌐 Dns01Handler

Uses Cloudflare (or any `IConvenientDnsProvider`) to set and remove DNS TXT records for `dns-01` challenges:

```typescript
import { handlers } from '@push.rocks/smartacme';
import * as cloudflare from '@apiclient.xyz/cloudflare';

const cfAccount = new cloudflare.CloudflareAccount('YOUR_CF_TOKEN');
const dnsHandler = new handlers.Dns01Handler(cfAccount);
```

DNS-01 is **required** for wildcard certificates and works regardless of server accessibility.

### 📁 Http01Webroot

Writes challenge response files to a filesystem webroot for `http-01` validation:

```typescript
import { handlers } from '@push.rocks/smartacme';

const httpHandler = new handlers.Http01Webroot({
  webroot: '/var/www/html',
});
```

The handler writes to `<webroot>/.well-known/acme-challenge/<token>` and cleans up after validation.

### 🧠 Http01MemoryHandler

In-memory HTTP-01 handler — stores challenge tokens in memory and serves them via `handleRequest()`:

```typescript
import { handlers } from '@push.rocks/smartacme';

const memHandler = new handlers.Http01MemoryHandler();

// Integrate with any HTTP server (Express, Koa, raw http, etc.)
app.use((req, res, next) => memHandler.handleRequest(req, res, next));
```

Perfect for serverless or container environments where filesystem access is limited.

### 🔧 Custom Challenge Handler

Implement `IChallengeHandler<T>` for custom challenge types:

```typescript
import type { handlers } from '@push.rocks/smartacme';

interface MyChallenge {
  type: string;
  token: string;
  keyAuthorization: string;
}

class MyHandler implements handlers.IChallengeHandler<MyChallenge> {
  getSupportedTypes(): string[] { return ['http-01']; }
  async prepare(ch: MyChallenge): Promise<void> { /* set up challenge response */ }
  async cleanup(ch: MyChallenge): Promise<void> { /* tear down */ }
  async checkWetherDomainIsSupported(domain: string): Promise<boolean> { return true; }
}
```

## Error Handling

SmartAcme provides structured ACME error handling via the `AcmeError` class, which carries full RFC 8555 error information:

```typescript
import { AcmeError } from '@push.rocks/smartacme/ts/acme/acme.classes.error.js';

try {
  const cert = await smartAcme.getCertificateForDomain('example.com');
} catch (err) {
  if (err instanceof AcmeError) {
    console.log(err.status);        // HTTP status code (e.g. 429)
    console.log(err.type);          // ACME error URN (e.g. 'urn:ietf:params:acme:error:rateLimited')
    console.log(err.detail);        // Human-readable message
    console.log(err.subproblems);   // Per-identifier sub-errors (RFC 8555 §6.7.1)
    console.log(err.retryAfter);    // Retry-After value in seconds
    console.log(err.isRateLimited); // true for 429 or rateLimited type
    console.log(err.isRetryable);   // true for 429, 503, 5xx, badNonce; false for 403/404/409
  }
}
```

The built-in retry logic is **error-aware**: non-retryable errors (403, 404, 409) are thrown immediately without wasting retry attempts, and rate-limited responses respect the server's `Retry-After` header instead of using blind exponential backoff.

## Domain Matching

SmartAcme automatically maps subdomains to their base domain for certificate lookups:

```
subdomain.example.com → certificate for example.com  ✅
*.example.com         → certificate for example.com  ✅
a.b.example.com       → not supported (4+ levels)    ❌
```

## Environment

| Environment     | Description |
|----------------|-------------|
| `production`   | Let's Encrypt production servers. Certificates are browser-trusted. [Rate limits](https://letsencrypt.org/docs/rate-limits/) apply. |
| `integration`  | Let's Encrypt staging servers. No rate limits, but certificates are **not** browser-trusted. Use for testing. |

## Complete Example with HTTP-01

```typescript
import { SmartAcme, certmanagers, handlers } from '@push.rocks/smartacme';
import * as http from 'http';

// In-memory handler for HTTP-01 challenges
const memHandler = new handlers.Http01MemoryHandler();

// Create HTTP server that serves ACME challenges
const server = http.createServer((req, res) => {
  memHandler.handleRequest(req, res, () => {
    res.statusCode = 200;
    res.end('OK');
  });
});
server.listen(80);

// Set up SmartAcme with in-memory storage and HTTP-01
const smartAcme = new SmartAcme({
  accountEmail: 'admin@example.com',
  certManager: new certmanagers.MemoryCertManager(),
  environment: 'production',
  challengeHandlers: [memHandler],
  challengePriority: ['http-01'],
});

await smartAcme.start();

const cert = await smartAcme.getCertificateForDomain('example.com');
// Use cert.publicKey and cert.privateKey with your HTTPS server

await smartAcme.stop();
server.close();
```

## 🏗️ ACME Directory Server (Built-in CA)

SmartAcme includes a full RFC 8555-compliant ACME Directory Server, allowing you to run your own Certificate Authority. This is useful for internal PKI, development/testing environments, and air-gapped networks.

### Quick Start — ACME Server

```typescript
import { server } from '@push.rocks/smartacme';

const acmeServer = new server.AcmeServer({
  port: 14000,
  challengeVerification: false, // Auto-approve challenges (for testing)
  caOptions: {
    commonName: 'My Internal CA',
    certValidityDays: 365,
  },
});

await acmeServer.start();
console.log(acmeServer.getDirectoryUrl()); // http://localhost:14000/directory
console.log(acmeServer.getCaCertPem());    // Root CA certificate in PEM format

// ... use it, then shut down
await acmeServer.stop();
```

### Server Options

```typescript
interface IAcmeServerOptions {
  port?: number;                    // Default: 14000
  hostname?: string;                // Default: '0.0.0.0'
  baseUrl?: string;                 // Auto-built from hostname:port if not provided
  challengeVerification?: boolean;  // Default: true. Set false to auto-approve challenges
  caOptions?: {
    commonName?: string;            // CA subject CN (default: 'SmartACME Test CA')
    validityDays?: number;          // Root cert validity in days (default: 3650)
    certValidityDays?: number;      // Issued cert validity in days (default: 90)
  };
}
```

### Using the Server with the Low-Level ACME Client

The `SmartAcme` class connects to Let's Encrypt by default. To use a custom ACME directory (like your own server), use the lower-level `AcmeClient` directly:

```typescript
import { server } from '@push.rocks/smartacme';
import { AcmeCrypto, AcmeClient } from '@push.rocks/smartacme/ts/acme/index.js';

// 1. Start your own CA
const acmeServer = new server.AcmeServer({
  port: 14000,
  challengeVerification: false, // auto-approve for testing
});
await acmeServer.start();

// 2. Create an ACME client pointing at your CA
const accountKey = AcmeCrypto.createRsaPrivateKey();
const client = new AcmeClient({
  directoryUrl: acmeServer.getDirectoryUrl(),
  accountKeyPem: accountKey,
});

// 3. Register an account
await client.createAccount({ termsOfServiceAgreed: true, contact: ['mailto:admin@internal.example.com'] });

// 4. Create an order and issue a certificate
const order = await client.createOrder({
  identifiers: [{ type: 'dns', value: 'myapp.internal' }],
});

// ... complete challenges, finalize, and download cert
// (challenges auto-approved since challengeVerification is false)

await acmeServer.stop();
```

### Server Endpoints

The ACME server implements all RFC 8555 endpoints:

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/directory` | GET | ACME directory with all endpoint URLs |
| `/new-nonce` | HEAD/GET | Fresh replay nonce |
| `/new-account` | POST | Account registration/lookup |
| `/new-order` | POST | Create certificate order |
| `/order/:id` | POST | Poll order status |
| `/authz/:id` | POST | Get authorization with challenges |
| `/challenge/:id` | POST | Trigger or poll challenge validation |
| `/finalize/:id` | POST | Submit CSR and issue certificate |
| `/cert/:id` | POST | Download PEM certificate chain |

### Challenge Verification

By default, the server performs real challenge verification (HTTP-01 fetches the token, DNS-01 queries TXT records). Set `challengeVerification: false` to auto-approve all challenges — useful for testing or internal environments where domain validation isn't needed.

### Root CA Certificate

Use `getCaCertPem()` to retrieve the root CA certificate for trust configuration:

```typescript
import * as fs from 'fs';
fs.writeFileSync('/usr/local/share/ca-certificates/my-ca.crt', acmeServer.getCaCertPem());
// Then: sudo update-ca-certificates
```

## 🏛️ Architecture

Under the hood, SmartAcme uses a fully custom RFC 8555-compliant ACME protocol implementation (no external ACME libraries). Key internal modules:

### Client Modules (`ts/acme/`)

| Module | Purpose |
|--------|---------|
| `AcmeClient` | Top-level ACME facade — orders, authorizations, finalization |
| `AcmeCrypto` | RSA key generation, JWK/JWS (RFC 7515/7638), CSR via `@peculiar/x509` |
| `AcmeHttpClient` | JWS-signed HTTP transport with nonce management and structured logging |
| `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` |

### Server Modules (`ts_server/`)

| Module | Purpose |
|--------|---------|
| `AcmeServer` | Top-level server facade — start, stop, configuration |
| `AcmeServerCA` | Self-signed root CA generation and certificate signing via `@peculiar/x509` |
| `JwsVerifier` | JWS signature verification (inverse of `AcmeCrypto.createJws`) |
| `NonceManager` | Single-use replay nonce generation and validation |
| `ChallengeVerifier` | HTTP-01 and DNS-01 challenge verification (with bypass mode) |
| `AcmeRouter` | Minimal HTTP router with parameterized path support |
| `MemoryAccountStore` | In-memory ACME account storage |
| `MemoryOrderStore` | In-memory order, authorization, challenge, and certificate storage |

All cryptographic operations use `node:crypto`. The only external crypto dependency is `@peculiar/x509` for CSR generation and certificate signing.

## License and Legal Information

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license.md) file.

**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

### Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

### Company Information

Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

# changelog.md for @push.rocks/smartacme

## 2026-03-27 - 9.3.1 - fix(acme)
parse issued certificate expiry from X.509 metadata and update build compatibility for dependency upgrades

- Store certificate validity using the actual X.509 expiration date instead of a fixed 90-day estimate, with a fallback if PEM parsing fails.
- Add reflect-metadata imports and declare it as a direct dependency to support @peculiar/x509 v2.
- Update TypeScript and build configuration for newer toolchain requirements, including Node types and renamed project config file.

## 2026-03-19 - 9.3.0 - feat(readme)
document built-in ACME directory server and CA capabilities

- Update package metadata to describe client and server support, including built-in CA functionality
- Add README sections for ACME server quick start, configuration, endpoints, challenge verification, and trust setup
- Expand architecture notes and project hints to cover ts_server modules and tsbuild tsfolders usage

## 2026-03-19 - 9.2.0 - feat(server)
add an embedded ACME directory server and certificate authority with challenge, order, and certificate endpoints

- exports a new server module with AcmeServer, AcmeServerCA, server error types, and related interfaces
- implements in-memory account and order storage, nonce management, JWS verification, routing, challenge validation, and CSR signing for RFC 8555 style flows
- adds end-to-end tests for account creation, order processing, challenge handling, certificate issuance, and error scenarios
- updates the build configuration to include tsfolders and package file patterns for ts_* sources

## 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.

- Added dependency @push.rocks/taskbuffer and re-exported ITaskEvent/ITaskMetadata in ts/index.ts; also imported/exported taskbuffer in ts/plugins.ts.
- Replaced interestMap coordination with TaskManager + TaskConstraintGroup(s): 'cert-domain-mutex' (per-domain mutex, resultSharingMode: 'share-latest'), 'acme-global-concurrency' (global concurrency cap), and 'acme-account-rate-limit' (sliding-window rate limiter).
- Introduced a single reusable Task named 'cert-issuance' and moved the ACME issuance flow into performCertificateIssuance(), splitting progress into named steps (prepare/authorize/finalize/store) and using notifyStep() for observable progress.
- Exposed certIssuanceEvents via SmartAcme.certIssuanceEvents and wired TaskManager.start()/stop() into SmartAcme.start()/stop().
- Added new ISmartAcmeOptions: maxConcurrentIssuances, maxOrdersPerWindow, orderWindowMs to control concurrency and rate limiting.
- Updated tests to remove interestMap stubs and adapt to the taskbuffer-based flow; cleaned up client/retry stubbing in tests.
- Updated readme.hints.md with guidance on concurrency, rate limiting, and taskbuffer integration.

## 2026-02-15 - 9.0.1 - fix(acme-http-client)
Destroy keep-alive HTTP agents and DNS client on shutdown to allow process exit; add destroy() on AcmeHttpClient and AcmeClient, wire agents into requests, and call client/smartdns destroy during SmartAcme.stop; documentation clarifications and expanded README (error handling, examples, default retry values).

- ts/acme/acme.classes.http-client.ts: added per-protocol http/https agents (keepAlive: false), use agent for outgoing requests, and added destroy() to explicitly destroy agents and free sockets.
- ts/acme/acme.classes.client.ts: added destroy() that forwards to the HTTP client to allow transport cleanup.
- ts/smartacme.classes.smartacme.ts: SmartAcme.stop now calls client.destroy() and smartdns.destroy() (when present) to ensure child processes and sockets are terminated before exit; also ensures certmanager.close() is awaited.
- readme.md: documentation improvements and clarifications (Let’s Encrypt spelling, added RFC 8555 compliance note, error handling / AcmeError usage examples, default retry parameter docs, UI/emoji improvements, and other wording/formatting updates).

## 2026-02-15 - 9.0.0 - BREAKING CHANGE(acme)
Replace external acme-client with a built-in RFC8555-compliant ACME implementation and update public APIs accordingly

- Add complete TypeScript ACME implementation under ts/acme (AcmeClient, AcmeCrypto, AcmeHttpClient, AcmeAccount, AcmeOrderManager, AcmeChallengeManager, AcmeError, interfaces, ACME_DIRECTORY_URLS).
- Implement JWK/JWK-thumbprint, JWS creation, nonce management, bad-nonce retries, Retry-After handling, CSR generation via @peculiar/x509 and node:crypto.
- Update SmartAcme to use the new AcmeClient/AcmeCrypto API (e.g. plugins.acme.AcmeClient, accountKeyPem) and add AcmeError-aware retry/backoff logic.
- Remove dependency on the external acme-client and other unused packages; add @peculiar/x509 and bump multiple dependency/devDependency versions.
- Add/adjust tests (unit tests for crypto, challenge, error handling) and update test imports to @git.zone/tstest; update README/readme.hints and npmextra.json to reflect implementation and publishing changes.

## 2025-05-19 - 8.0.0 - BREAKING CHANGE(smartacme)
Make wildcard certificates opt-in to fix HTTP-01 only configurations

- BREAKING CHANGE: Wildcard certificates are no longer automatically requested for all domains
- Added 'includeWildcard' option to getCertificateForDomain() to explicitly request wildcard certificates
- HTTP-01 only configurations now work correctly as they do not try to request wildcard certificates automatically
- Updated certificate CSR generation to match the requested domain configuration

## 2025-05-19 - 7.4.0 - feat(smartacme)
Make wildcard certificates opt-in to fix HTTP-01 only configurations

- BREAKING CHANGE: Wildcard certificates are no longer automatically requested for all domains
- Added `includeWildcard` option to `getCertificateForDomain()` to explicitly request wildcards
- HTTP-01 only configurations now work correctly as they no longer attempt wildcard certificates
- Wildcard certificates require DNS-01 handler and must be explicitly requested
- Updated certificate CSR generation to match the requested domain configuration

## 2025-05-18 - 7.3.4 - fix(smartacme)
Refine documentation and tests for improved clarity in ACME certificate management

- Enhanced the README with detailed usage, configuration, and example sections
- Refined test cases for certificate matching and challenge handlers across DNS-01 and HTTP-01
- Updated TypeScript definitions and inline comments for better developer experience

## 2025-05-05 - 7.3.3 - fix(SmartAcme)
Remove duplicate challengeHandlers declaration from SmartAcme class

- Eliminated the redundant private declaration of challengeHandlers since it is already defined as a public property
- Ensures a single source of truth and clearer interface for challenge handler configuration

## 2025-05-05 - 7.3.2 - fix(test)
Add missing checkWetherDomainIsSupported implementation to DummyHandler for interface compliance in tests

- Implemented the missing checkWetherDomainIsSupported method in the DummyHandler to satisfy IChallengeHandler interface requirements
- Ensured that tests now correctly instantiate the DummyHandler without interface errors

## 2025-05-05 - 7.3.1 - fix(core)
Refactor import paths and update dependency references

- Replaced deprecated 'smartacme.plugins.js' with the new 'plugins.js' across cert managers, handlers, and core classes
- Added missing dependencies (@push.rocks/smartfile and @push.rocks/smartnetwork) in package.json
- Updated HTTP challenge handlers to include domain support checks via checkWetherDomainIsSupported
- Adjusted import paths in MongoCertManager, MemoryCertManager, and DNS-01 handler for consistency

## 2025-05-05 - 7.3.0 - feat(index)
Bump @tsclass/tsclass to 9.2.0 and update module exports to include handlers

- Upgrade @tsclass/tsclass dependency from 9.1.0 to 9.2.0 in package.json
- Add explicit export of handlers in ts/index.ts to improve module accessibility

## 2025-05-05 - 7.2.5 - fix(smartacme)
Refactor module exports and update wildcard certificate support documentation

- Updated readme.plan.md to streamline and remove obsolete wildcard plan details
- Normalized certmanager imports by consolidating exports in ts/index.ts and updating tests accordingly
- Reordered ISmartAcmeOptions interface properties for clarity (accountEmail moved to the top)

## 2025-05-04 - 7.2.4 - fix(test)
Refactor wildcard certificate test to properly stub SmartAcme.start and getCertificateForDomain for robust integration.

- Temporarily override SmartAcme.start and getCertificateForDomain to simulate wildcard certificate behavior.
- Restore original prototype methods post-test to prevent side effects.
- Improve test clarity for wildcard certificate integration.

## 2025-05-01 - 7.2.3 - fix(docs)
Improve certificate manager documentation with detailed examples and custom implementation guide

- Added usage examples for MemoryCertManager and MongoCertManager
- Provided a custom ICertManager implementation guide
- Enhanced overall documentation clarity for certificate storage configuration

## 2025-05-01 - 7.2.2 - fix(readme)
Update readme documentation: switch installation instructions to pnpm and clarify usage with MongoCertManager and updated SmartAcme options

- Replaced npm/yarn commands with pnpm commands for installation and testing.
- Added guidance to ensure the project is set up for TypeScript and ECMAScript Modules.
- Updated usage examples to include initialization of MongoCertManager instead of legacy mongoDescriptor.
- Revised challenge handlers examples to reference the current API signatures.

## 2025-05-01 - 7.2.1 - fix(smartacme)
Centralize interest map coordination and remove redundant interestMap from cert managers

- Removed interestMap property and related logic from MemoryCertManager and MongoCertManager
- Refactored SmartAcme to instantiate its own interestMap for coordinating certificate requests
- Updated getCertificateForDomain to use the new interestMap for checking and adding certificate interests

## 2025-05-01 - 7.2.0 - feat(core)
Refactor SmartAcme core to centralize interest coordination and update dependencies

- Moved interest coordination mechanism out of ICertManager implementations and into SmartAcme core
- Updated certificate managers (MemoryCertManager and MongoCertManager) to remove redundant interestMap handling
- Upgraded @push.rocks/tapbundle from 6.0.1 to 6.0.3 in package.json
- Revised readme.plan.md to reflect the new interest coordination approach

## 2025-04-30 - 7.1.0 - feat(certmanagers/integration)
Add optional wipe methods to certificate managers and update integration tests, plus bump tapbundle dependency

- Introduce wipe() in ICertManager to support integration testing by clearing stored certificates
- Implement wipe() in MemoryCertManager and MongoCertManager for resetting internal state
- Refactor SmartAcme constructor to consider wiping certificates in integration mode (commented out for now)
- Update integration test assertions and add console logging for domain certificate retrieval
- Upgrade @push.rocks/tapbundle from ^6.0.0 to ^6.0.1

## 2025-04-30 - 7.0.0 - BREAKING CHANGE(SmartAcme (Cert Management))
Refactor certificate management and challenge handling API to use a unified certManager interface, remove legacy storage, and update challenge workflows.

- Introduce ICertManager interface with MemoryCertManager and MongoCertManager implementations.
- Remove the legacy SmartacmeCertManager and update SmartAcme to require a certManager option instead of mongoDescriptor.
- Adjust certificate renewal logic to delete and store certificates through the new certManager API.
- Refine DNS-01 challenge handling by removing in-handler DNS propagation waiting and relying on external checks.
- Increase retry settings for robustness during challenge verification and certificate issuance.
- Update integration and unit tests to use the new certManager configuration.

## 2025-04-30 - 6.2.0 - feat(handlers)
Add in-memory HTTP-01 challenge handler and rename file-based handler to Http01Webroot

- Renamed Http01Handler to Http01Webroot in both implementation and documentation
- Introduced Http01MemoryHandler for diskless HTTP-01 challenges
- Updated tests and README examples to reflect handler name changes and new feature

## 2025-04-30 - 6.1.3 - fix(Dns01Handler)
Update dependency versions and refine Dns01Handler implementation

- Bump '@apiclient.xyz/cloudflare' to ^6.4.1 and '@tsclass/tsclass' to ^9.1.0 in package.json
- Remove duplicate Cloudflare import in smartacme.plugins.ts
- Refactor Dns01Handler to use IConvenientDnsProvider and add checkWetherDomainIsSupported method
- Align devDependencies versions for improved consistency

## 2025-04-27 - 6.1.2 - fix(repo)
Update repository metadata by replacing the LICENSE file with a license.md file for improved consistency.

- Removed the old LICENSE file.
- Introduced license.md as the new license documentation file.

## 2025-04-27 - 6.1.1 - fix(readme)
Fix license link reference in documentation

- Updated the license link from [license](license) to [license.md](license.md) in the License and Legal Information section

## 2025-04-27 - 6.1.0 - feat(readme)
Update documentation with detailed built-in challenge handlers and custom handler examples

- Expanded readme to include sections on Dns01Handler and Http01Handler usage
- Added examples for creating and registering custom ACME challenge handlers
- Improved clarity of ACME certificate management instructions using SmartAcme

## 2025-04-27 - 6.0.1 - fix(readme)
Remove extraneous code fence markers from license section in readme

- Removed unnecessary triple backticks wrapping the license information
- Improved clarity of the license section in the documentation

## 2025-04-27 - 6.0.0 - BREAKING CHANGE(SmartAcme)
Refactor challenge handling by removing legacy setChallenge/removeChallenge in favor of pluggable challengeHandlers and update documentation and tests accordingly

- Removed legacy challenge methods and introduced new 'challengeHandlers' and 'challengePriority' options
- Updated readme examples to demonstrate usage with DNS-01 (and HTTP-01) handlers
- Refactored internal SmartAcme flow to select and process challenges via the new handler interface
- Adjusted tests (including integration tests) to align with the updated challenge handling mechanism

## 2025-04-27 - 5.1.0 - feat(smartacme)
Implement exponential backoff retry logic and graceful shutdown handling in SmartAcme; update acme-client dependency to v5.4.0

- Added retry helper with exponential backoff for ACME client operations
- Introduced retryOptions in ISmartAcmeOptions for configurable retry parameters
- Enhanced graceful shutdown handling by cleaning up pending DNS challenges on signal
- Updated acme-client dependency from v4.2.5 to v5.4.0

## 2025-04-26 - 5.0.1 - fix(build)
Update CI workflows, bump dependency versions, and refine import and TypeScript configuration

- Changed CI workflow image and npmci package from '@shipzone/npmci' to '@ship.zone/npmci', and updated repository URLs
- Bumped several dependency versions in package.json (e.g. @api.global/typedserver, @push.rocks/lik, @push.rocks/smartdata, @push.rocks/smartdns, @tsclass/tsclass) to newer releases
- Adjusted smartdns import to use the smartdnsClient module for proper module resolution
- Updated tsconfig.json to add emitDecoratorMetadata and baseUrl settings
- Minor markdown and formatting tweaks in readme and gitignore files, and slight improvements in test async handling

## 2024-06-16 - 5.0.0 - No significant changes  
This release contains no user‑facing changes.

## 2024-06-16 - 4.0.8 - Structure and configuration updates  
- BREAKING CHANGE(structure): renamed classes to avoid confusion  
- update description  
- update tsconfig  
- update npmextra.json: githost

## 2024-01-28 - 4.0.7–4.0.6 - Internal fixes and updates  
- A series of releases with routine bug fixes and maintenance updates.

## 2023-07-21 - 4.0.5–4.0.4 - Internal fixes and updates  
- Multiple releases addressing internal issues and maintenance improvements.

## 2023-07-10 - 4.0.3 - Organizational changes  
- switch to new org scheme

## 2022-09-27 - 4.0.0–4.0.2 - Internal fixes and updates  
- Routine maintenance and internal bug fixes.

## 2022-09-27 - 3.0.15 - Breaking changes  
- BREAKING CHANGE(core): update

## 2021-01-22 - 3.0.9–3.0.14 - Internal fixes and updates  
- A range of releases focused on routine internal updates.

## 2020-11-18 - 3.0.0–3.0.8 - Internal fixes and updates  
- Routine maintenance and internal bug fixes.

## 2020-02-10 - 2.1.2 - Breaking changes  
- BREAKING CHANGE(core): streamline scope to certificate retrieval using dns challenge

## 2020-02-10 - 2.1.0–2.1.1 - Internal fixes and updates  
- Routine fixes and updates.

## 2019-02-06 - 2.0.36 - New feature  
- feat(Cert): now has validity check

## 2019-01-18 - 2.0.2–2.0.35 - Internal fixes and updates  
- Routine internal updates and maintenance.

## 2018-10-07 - 2.0.0–2.0.1 - Internal fixes and updates  
- Routine internal updates and maintenance.

## 2018-10-07 - 1.1.4 - Breaking changes  
- BREAKING CHANGE(scope): change to @pushrocks

## 2018-08-12 - 1.1.1 - NPM publishing fix  
- fix(npm publishing): update

## 2018-08-11 - 1.1.0 - Certificate issuance update  
- fix(core): now creating certs all right

## 2018-08-11 - 1.0.11 - Feature update  
- feat(swaitch to acme-v2): switch to letsencrypt v2

## 2017-04-28 - 1.0.10 - CI improvements  
- add updated ci config

## 2017-04-28 - 1.0.9 - Standards update  
- update to latest standards

## 2017-01-27 - 1.0.8 - Basic functionality  
- basic functionality

## 2017-01-25 - 1.0.7 - Response and validation improvements  
- now getting a valid response  
- update validation  
- improve README

## 2017-01-15 - 1.0.6 - Async and documentation improvements  
- improve README  
- add async checkDNS

## 2017-01-15 - 1.0.5 - Standards and process updates  
- update to new standards  
- now has working requestValidation method  
- fix som things  
- start better segregation of concerns  
- start with certificate signing process

## 2017-01-01 - 1.0.4 - Certificate acquisition improvements  
- now getting certificates  
- can now agree to TOS  
- remove test keys

## 2017-01-01 - 1.0.3 - NPM extra configuration  
- add npmextra.json

## 2017-01-01 - 1.0.2 - README and integration update  
- add better readme  
- switch to rawacme for more basic letsencrypt access

## 2016-11-17 - 1.0.1 - Promise fix  
- fix promise

## 2016-11-17 - 1.0.0 - Major initial release changes  
- remove superflouous key creation  
- switch to acme core  
- prepare switch to le‑acme‑core  
- improve upon keyCreation  
- update to use more promises  
- add README  
- first version