BREAKING CHANGE(certs): accept a second eventComms argument in certProvisionFunction, add cert provisioning event types, and emit certificate lifecycle events

This commit is contained in:
2026-02-13 21:24:16 +00:00
parent 998662e137
commit 7afa4c4c58
7 changed files with 75 additions and 175 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartproxy',
version: '24.0.1',
version: '25.0.0',
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
}

View File

@@ -8,7 +8,7 @@ export { SharedRouteManager as RouteManager } from './core/routing/route-manager
// Export smart-proxy models
export type { ISmartProxyOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteTls, IRouteContext } from './proxies/smart-proxy/models/index.js';
export type { TSmartProxyCertProvisionObject } from './proxies/smart-proxy/models/interfaces.js';
export type { TSmartProxyCertProvisionObject, ICertProvisionEventComms, ICertificateIssuedEvent, ICertificateFailedEvent } from './proxies/smart-proxy/models/interfaces.js';
export * from './proxies/smart-proxy/utils/index.js';
// Original: export * from './smartproxy/classes.pp.snihandler.js'

View File

@@ -2,6 +2,6 @@
* SmartProxy models
*/
// Export everything except IAcmeOptions from interfaces
export type { ISmartProxyOptions, ISmartProxyCertStore, IConnectionRecord, TSmartProxyCertProvisionObject } from './interfaces.js';
export type { ISmartProxyOptions, ISmartProxyCertStore, IConnectionRecord, TSmartProxyCertProvisionObject, ICertProvisionEventComms, ICertificateIssuedEvent, ICertificateFailedEvent } from './interfaces.js';
export * from './route-types.js';
export * from './metrics-types.js';

View File

@@ -34,6 +34,38 @@ import type { IRouteConfig } from './route-types.js';
*/
export type TSmartProxyCertProvisionObject = plugins.tsclass.network.ICert | 'http01';
/**
* Communication channel passed as second argument to certProvisionFunction.
* Allows the callback to report metadata back to SmartProxy for event emission.
*/
export interface ICertProvisionEventComms {
/** Informational log */
log: (message: string) => void;
/** Warning (non-fatal) */
warn: (message: string) => void;
/** Error */
error: (message: string) => void;
/** Set the certificate expiry date (for the issued event) */
setExpiryDate: (date: Date) => void;
/** Set the source/method used for provisioning (e.g. 'smartacme-dns-01') */
setSource: (source: string) => void;
}
/** Payload for 'certificate-issued' and 'certificate-renewed' events */
export interface ICertificateIssuedEvent {
domain: string;
expiryDate?: string; // ISO 8601
source: string; // e.g. 'certProvisionFunction', 'smartacme-dns-01'
isRenewal?: boolean;
}
/** Payload for 'certificate-failed' event */
export interface ICertificateFailedEvent {
domain: string;
error: string;
source: string;
}
// Legacy options and type checking functions have been removed
/**
@@ -140,7 +172,7 @@ export interface ISmartProxyOptions {
* Optional certificate provider callback. Return 'http01' to use HTTP-01 challenges,
* or a static certificate object for immediate provisioning.
*/
certProvisionFunction?: (domain: string) => Promise<TSmartProxyCertProvisionObject>;
certProvisionFunction?: (domain: string, eventComms: ICertProvisionEventComms) => Promise<TSmartProxyCertProvisionObject>;
/**
* Whether to fallback to ACME if custom certificate provision fails.

View File

@@ -14,7 +14,7 @@ import { generateDefaultCertificate } from './utils/default-cert-generator.js';
import { Mutex } from './utils/mutex.js';
// Types
import type { ISmartProxyOptions, TSmartProxyCertProvisionObject, IAcmeOptions } from './models/interfaces.js';
import type { ISmartProxyOptions, TSmartProxyCertProvisionObject, IAcmeOptions, ICertProvisionEventComms, ICertificateIssuedEvent, ICertificateFailedEvent } from './models/interfaces.js';
import type { IRouteConfig } from './models/route-types.js';
import type { IMetrics } from './models/metrics-types.js';
@@ -420,8 +420,21 @@ export class SmartProxy extends plugins.EventEmitter {
for (const domain of certDomains) {
if (provisionedDomains.has(domain)) continue;
provisionedDomains.add(domain);
// Build eventComms channel for this domain
let expiryDate: string | undefined;
let source = 'certProvisionFunction';
const eventComms: ICertProvisionEventComms = {
log: (msg) => logger.log('info', `[certProvision ${domain}] ${msg}`, { component: 'smart-proxy' }),
warn: (msg) => logger.log('warn', `[certProvision ${domain}] ${msg}`, { component: 'smart-proxy' }),
error: (msg) => logger.log('error', `[certProvision ${domain}] ${msg}`, { component: 'smart-proxy' }),
setExpiryDate: (date) => { expiryDate = date.toISOString(); },
setSource: (s) => { source = s; },
};
try {
const result: TSmartProxyCertProvisionObject = await provisionFn(domain);
const result: TSmartProxyCertProvisionObject = await provisionFn(domain, eventComms);
if (result === 'http01') {
// Callback wants HTTP-01 for this domain — trigger Rust ACME explicitly
@@ -455,10 +468,24 @@ export class SmartProxy extends plugins.EventEmitter {
logger.log('warn', `certStore.save() failed for ${domain}: ${storeErr.message}`, { component: 'smart-proxy' });
}
}
// Emit certificate-issued event
this.emit('certificate-issued', {
domain,
expiryDate: expiryDate || (certObj.validUntil ? new Date(certObj.validUntil).toISOString() : undefined),
source,
} satisfies ICertificateIssuedEvent);
}
} catch (err: any) {
logger.log('warn', `certProvisionFunction failed for ${domain}: ${err.message}`, { component: 'smart-proxy' });
// Emit certificate-failed event
this.emit('certificate-failed', {
domain,
error: err.message,
source,
} satisfies ICertificateFailedEvent);
// Fallback to ACME if enabled and route has a name
if (this.settings.certProvisionFallbackToAcme !== false && route.name) {
try {