@@ -71,6 +71,54 @@ export class AcmeCA {
7171 return cachedCert ;
7272 }
7373
74+ // Returns an ACME cert only if it has actually expired. Issues once, never renews.
75+ // Returns undefined if no expired ACME cert is available (caller should use LocalCA fallback).
76+ tryGetExpiredCertificateSync ( domain : string ) {
77+ const cacheKey = `${ domain } :expired` ;
78+ const cachedCert = this . certCache . getCert ( cacheKey ) ;
79+
80+ if ( cachedCert ) {
81+ const isExpired = cachedCert . expiry <= Date . now ( ) ;
82+ console . log ( `Found cached expired-mode cert for ${ domain } (expiry: ${ new Date ( cachedCert . expiry ) . toISOString ( ) } , actually expired: ${ isExpired } )` ) ;
83+
84+ if ( isExpired ) {
85+ return cachedCert ;
86+ }
87+ // Not yet expired - caller should use LocalCA fallback
88+ return undefined ;
89+ }
90+
91+ // No cached cert - issue one in the background (will be expired eventually)
92+ const attemptId = Math . random ( ) . toString ( 16 ) . slice ( 2 ) ;
93+ console . log ( `No expired-mode cert cached for ${ domain } , issuing new one (${ attemptId } )` ) ;
94+ this . issueExpiredModeCertificate ( domain , cacheKey , attemptId ) ;
95+
96+ return undefined ;
97+ }
98+
99+ private async issueExpiredModeCertificate ( domain : string , cacheKey : string , attemptId : string ) {
100+ if ( this . pendingCertRenewals [ cacheKey ] ) {
101+ console . log ( `Expired-mode cert already being issued for ${ domain } (${ attemptId } )` ) ;
102+ return ;
103+ }
104+
105+ const refreshPromise = Object . assign (
106+ this . requestNewCertificate ( domain , { attemptId } ) . then ( ( certData ) => {
107+ delete this . pendingCertRenewals [ cacheKey ] ;
108+ this . certCache . cacheCert ( { ...certData , domain : cacheKey } ) ;
109+ console . log ( `Expired-mode cert issued for ${ domain } (${ attemptId } ), will expire: ${ new Date ( certData . expiry ) . toISOString ( ) } ` ) ;
110+ return certData ;
111+ } ) . catch ( ( e ) => {
112+ delete this . pendingCertRenewals [ cacheKey ] ;
113+ console . log ( `Expired-mode cert generation failed for ${ domain } (${ attemptId } ):` , e . message ) ;
114+ throw e ;
115+ } ) ,
116+ { id : attemptId }
117+ ) ;
118+
119+ this . pendingCertRenewals [ cacheKey ] = refreshPromise ;
120+ }
121+
74122 private async getCertificate (
75123 domain : string ,
76124 options : { forceRegenerate ?: boolean , attemptId : string }
0 commit comments