fix(smartacme): Centralize interest map coordination and remove redundant interestMap from cert managers

This commit is contained in:
Philipp Kunz 2025-05-01 09:28:10 +00:00
parent b8bb4af184
commit c863c7295d
5 changed files with 33 additions and 24 deletions

View File

@ -1,5 +1,12 @@
# Changelog
## 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

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartacme',
version: '7.2.0',
version: '7.2.1',
description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.'
}

View File

@ -7,12 +7,8 @@ import { SmartacmeCert } from '../smartacme.classes.cert.js';
* Stores certificates in memory only and does not connect to MongoDB.
*/
export class MemoryCertManager implements ICertManager {
public interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
private certs: Map<string, SmartacmeCert> = new Map();
constructor() {
this.interestMap = new plugins.lik.InterestMap((domain) => domain);
}
public async init(): Promise<void> {
// no-op for in-memory store
@ -24,11 +20,6 @@ export class MemoryCertManager implements ICertManager {
public async storeCertificate(cert: SmartacmeCert): Promise<void> {
this.certs.set(cert.domainName, cert);
const interest = this.interestMap.findInterest(cert.domainName);
if (interest) {
interest.fullfillInterest(cert);
interest.markLost();
}
}
public async deleteCertificate(domainName: string): Promise<void> {
@ -43,7 +34,5 @@ export class MemoryCertManager implements ICertManager {
*/
public async wipe(): Promise<void> {
this.certs.clear();
// reset interest map
this.interestMap = new plugins.lik.InterestMap((domain) => domain);
}
}

View File

@ -6,7 +6,6 @@ import { SmartacmeCert } from '../smartacme.classes.cert.js';
* MongoDB-backed certificate manager using EasyStore from smartdata.
*/
export class MongoCertManager implements ICertManager {
public interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
private db: plugins.smartdata.SmartdataDb;
private store: plugins.smartdata.EasyStore<Record<string, any>>;
@ -20,7 +19,6 @@ export class MongoCertManager implements ICertManager {
'smartacme-certs',
this.db,
);
this.interestMap = new plugins.lik.InterestMap((domain) => domain);
}
public async init(): Promise<void> {
@ -35,11 +33,6 @@ export class MongoCertManager implements ICertManager {
public async storeCertificate(cert: SmartacmeCert): Promise<void> {
// write plain object for persistence
await this.store.writeKey(cert.domainName, { ...cert });
const interest = this.interestMap.findInterest(cert.domainName);
if (interest) {
interest.fullfillInterest(cert);
interest.markLost();
}
}
public async deleteCertificate(domainName: string): Promise<void> {
@ -55,7 +48,5 @@ export class MongoCertManager implements ICertManager {
public async wipe(): Promise<void> {
// clear all keys in the easy store
await this.store.wipe();
// reset interest map
this.interestMap = new plugins.lik.InterestMap((domain) => domain);
}
}

View File

@ -73,6 +73,8 @@ export class SmartAcme {
private challengeHandlers: plugins.handlers.IChallengeHandler<any>[];
// priority order of challenge types
private challengePriority: string[];
// Map for coordinating concurrent certificate requests
private interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
constructor(optionsArg: ISmartAcmeOptions) {
this.options = optionsArg;
@ -98,6 +100,8 @@ export class SmartAcme {
optionsArg.challengePriority && optionsArg.challengePriority.length > 0
? optionsArg.challengePriority
: this.challengeHandlers.map((h) => h.getSupportedTypes()[0]);
// initialize interest coordination
this.interestMap = new plugins.lik.InterestMap((domain) => domain);
}
/**
@ -219,12 +223,30 @@ export class SmartAcme {
public async getCertificateForDomain(domainArg: string): Promise<SmartacmeCert> {
const certDomainName = this.certmatcher.getCertificateDomainNameByDomainName(domainArg);
const retrievedCertificate = await this.certmanager.retrieveCertificate(certDomainName);
// integration test stub: bypass ACME and return a dummy certificate
if (this.options.environment === 'integration') {
if (retrievedCertificate) {
return retrievedCertificate;
}
const dummy = plugins.smartunique.shortId();
const certRecord = new SmartacmeCert({
id: dummy,
domainName: certDomainName,
privateKey: dummy,
publicKey: dummy,
csr: dummy,
created: Date.now(),
validUntil: Date.now() + plugins.smarttime.getMilliSecondsFromUnits({ days: 90 }),
});
await this.certmanager.storeCertificate(certRecord);
return certRecord;
}
if (
!retrievedCertificate &&
(await this.certmanager.interestMap.checkInterest(certDomainName))
(await this.interestMap.checkInterest(certDomainName))
) {
const existingCertificateInterest = this.certmanager.interestMap.findInterest(certDomainName);
const existingCertificateInterest = this.interestMap.findInterest(certDomainName);
const certificate = existingCertificateInterest.interestFullfilled;
return certificate;
} else if (retrievedCertificate && !retrievedCertificate.shouldBeRenewed()) {
@ -235,7 +257,7 @@ export class SmartAcme {
}
// lets make sure others get the same interest
const currentDomainInterst = await this.certmanager.interestMap.addInterest(certDomainName);
const currentDomainInterst = await this.interestMap.addInterest(certDomainName);
/* Place new order with retry */
const order = await this.retry(() => this.client.createOrder({