feat(smartproxy): Migrate internal module paths and update HTTP/ACME components for SmartProxy

This commit is contained in:
Philipp Kunz 2025-05-09 17:10:19 +00:00
parent f1c0b8bfb7
commit 5a3bf2cae6
11 changed files with 363 additions and 194 deletions

View File

@ -1,5 +1,16 @@
# Changelog
## 2025-05-09 - 12.1.0 - feat(smartproxy)
Migrate internal module paths and update HTTP/ACME components for SmartProxy
- Mark migration tasks as complete in readme.plan.md (checkboxes updated to ✅)
- Moved Port80Handler from ts/port80handler to ts/http/port80 (and extracted challenge responder)
- Migrated redirect handlers and router components to ts/http/redirects and ts/http/router respectively
- Updated re-exports in ts/index.ts and ts/plugins.ts to expose new module paths and additional exports
- Refactored CertificateEvents to include deprecation notes on Port80HandlerEvents
- Adjusted internal module organization for TLS, ACME, and forwarding (SNI extraction, client-hello parsing, etc.)
- Added minor logging and formatting improvements in several modules
## 2025-05-09 - 12.0.0 - BREAKING CHANGE(forwarding)
Rename 'sniPassthrough' export to 'httpsPassthrough' for consistent naming and remove outdated forwarding example

View File

@ -136,19 +136,19 @@ This component has the cleanest design, so we'll start migration here:
- [x] Extract SNI extraction to `ts/tls/sni/sni-extraction.ts`
- [x] Extract ClientHello parsing to `ts/tls/sni/client-hello-parser.ts`
### Phase 5: HTTP Component Migration (Week 3)
### Phase 5: HTTP Component Migration (Week 3)
- [ ] Migrate Port80Handler
- [ ] Move `ts/port80handler/classes.port80handler.ts``ts/http/port80/port80-handler.ts`
- [ ] Extract ACME challenge handling to `ts/http/port80/challenge-responder.ts`
- [x] Migrate Port80Handler
- [x] Move `ts/port80handler/classes.port80handler.ts``ts/http/port80/port80-handler.ts`
- [x] Extract ACME challenge handling to `ts/http/port80/challenge-responder.ts`
- [ ] Migrate redirect handlers
- [ ] Move `ts/redirect/classes.redirect.ts``ts/http/redirects/redirect-handler.ts`
- [ ] Create `ts/http/redirects/ssl-redirect.ts` for specialized redirects
- [x] Migrate redirect handlers
- [x] Move `ts/redirect/classes.redirect.ts``ts/http/redirects/redirect-handler.ts`
- [x] Create `ts/http/redirects/ssl-redirect.ts` for specialized redirects
- [ ] Migrate router components
- [ ] Move `ts/classes.router.ts``ts/http/router/proxy-router.ts`
- [ ] Extract route matching to `ts/http/router/route-matcher.ts`
- [x] Migrate router components
- [x] Move `ts/classes.router.ts``ts/http/router/proxy-router.ts`
- [x] Extract route matching to `ts/http/router/route-matcher.ts`
### Phase 6: Proxy Implementation Migration (Weeks 3-4)
@ -259,9 +259,9 @@ This component has the cleanest design, so we'll start migration here:
| (new) | ts/tls/sni/sni-extraction.ts | ✅ |
| (new) | ts/tls/sni/client-hello-parser.ts | ✅ |
| **HTTP Components** | | |
| ts/port80handler/classes.port80handler.ts | ts/http/port80/port80-handler.ts | |
| ts/redirect/classes.redirect.ts | ts/http/redirects/redirect-handler.ts | |
| ts/classes.router.ts | ts/http/router/proxy-router.ts | |
| ts/port80handler/classes.port80handler.ts | ts/http/port80/port80-handler.ts | |
| ts/redirect/classes.redirect.ts | ts/http/redirects/redirect-handler.ts | |
| ts/classes.router.ts | ts/http/router/proxy-router.ts | |
| **SmartProxy Components** | | |
| ts/smartproxy/classes.smartproxy.ts | ts/proxies/smart-proxy/smart-proxy.ts | ❌ |
| ts/smartproxy/classes.pp.interfaces.ts | ts/proxies/smart-proxy/models/interfaces.ts | ❌ |

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartproxy',
version: '12.0.0',
version: '12.1.0',
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
}

View File

@ -7,10 +7,14 @@ export enum CertificateEvents {
CERTIFICATE_FAILED = 'certificate-failed',
CERTIFICATE_EXPIRING = 'certificate-expiring',
CERTIFICATE_APPLIED = 'certificate-applied',
// Events moved from Port80Handler for compatibility
MANAGER_STARTED = 'manager-started',
MANAGER_STOPPED = 'manager-stopped',
}
/**
* Port80Handler-specific events including certificate-related ones
* @deprecated Use CertificateEvents and HttpEvents instead
*/
export enum Port80HandlerEvents {
CERTIFICATE_ISSUED = 'certificate-issued',

View File

@ -2,7 +2,18 @@
* HTTP functionality module
*/
// Export types and models
export * from './models/http-types.js';
// Export submodules
export * from './port80/index.js';
export * from './router/index.js';
export * from './redirects/index.js';
// Convenience namespace exports
export const Http = {
Port80: {
Handler: require('./port80/port80-handler.js').Port80Handler,
ChallengeResponder: require('./port80/challenge-responder.js').ChallengeResponder
}
};

View File

@ -35,58 +35,92 @@ export class ChallengeResponder extends plugins.EventEmitter {
*/
public async initialize(): Promise<void> {
try {
// Initialize SmartAcme
this.smartAcme = new plugins.smartacme.SmartAcme({
useProduction: this.useProduction,
accountEmail: this.email,
directoryUrl: this.useProduction
? 'https://acme-v02.api.letsencrypt.org/directory' // Production
: 'https://acme-staging-v02.api.letsencrypt.org/directory', // Staging
});
// Initialize HTTP-01 challenge handler
this.http01Handler = new plugins.smartacme.handlers.Http01MemoryHandler();
this.smartAcme.useHttpChallenge(this.http01Handler);
// Initialize SmartAcme with proper options
this.smartAcme = new plugins.smartacme.SmartAcme({
accountEmail: this.email,
certManager: new plugins.smartacme.certmanagers.MemoryCertManager(),
environment: this.useProduction ? 'production' : 'integration',
challengeHandlers: [this.http01Handler],
challengePriority: ['http-01'],
});
// Ensure certificate store directory exists
await this.ensureCertificateStore();
// Subscribe to SmartAcme events
this.smartAcme.on('certificate-issued', (data: any) => {
const certData: CertificateData = {
domain: data.domain,
certificate: data.cert,
privateKey: data.key,
expiryDate: new Date(data.expiryDate),
};
this.emit(CertificateEvents.CERTIFICATE_ISSUED, certData);
});
// Set up event forwarding from SmartAcme
this.setupEventForwarding();
this.smartAcme.on('certificate-renewed', (data: any) => {
const certData: CertificateData = {
domain: data.domain,
certificate: data.cert,
privateKey: data.key,
expiryDate: new Date(data.expiryDate),
};
this.emit(CertificateEvents.CERTIFICATE_RENEWED, certData);
});
this.smartAcme.on('certificate-error', (data: any) => {
const error: CertificateFailure = {
domain: data.domain,
error: data.error instanceof Error ? data.error.message : String(data.error),
isRenewal: data.isRenewal || false,
};
this.emit(CertificateEvents.CERTIFICATE_FAILED, error);
});
await this.smartAcme.initialize();
// Start SmartAcme
await this.smartAcme.start();
} catch (error) {
throw new Error(`Failed to initialize ACME client: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Sets up event forwarding from SmartAcme to this component
*/
private setupEventForwarding(): void {
if (!this.smartAcme) return;
// Cast smartAcme to any since different versions have different event APIs
const smartAcmeAny = this.smartAcme as any;
// Forward certificate events to our own emitter
if (typeof smartAcmeAny.on === 'function') {
smartAcmeAny.on('certificate', (data: any) => {
const certData: CertificateData = {
domain: data.domain,
certificate: data.cert || data.publicKey,
privateKey: data.key || data.privateKey,
expiryDate: new Date(data.expiryDate || data.validUntil),
source: 'http01'
};
// Emit as issued or renewed based on the renewal flag
const eventType = data.isRenewal
? CertificateEvents.CERTIFICATE_RENEWED
: CertificateEvents.CERTIFICATE_ISSUED;
this.emit(eventType, certData);
});
smartAcmeAny.on('error', (data: any) => {
const failure: CertificateFailure = {
domain: data.domain || 'unknown',
error: data.message || data.toString(),
isRenewal: false
};
this.emit(CertificateEvents.CERTIFICATE_FAILED, failure);
});
} else if (smartAcmeAny.eventEmitter && typeof smartAcmeAny.eventEmitter.on === 'function') {
// Alternative event emitter approach for newer versions
smartAcmeAny.eventEmitter.on('certificate', (data: any) => {
const certData: CertificateData = {
domain: data.domain,
certificate: data.cert || data.publicKey,
privateKey: data.key || data.privateKey,
expiryDate: new Date(data.expiryDate || data.validUntil),
source: 'http01'
};
const eventType = data.isRenewal
? CertificateEvents.CERTIFICATE_RENEWED
: CertificateEvents.CERTIFICATE_ISSUED;
this.emit(eventType, certData);
});
smartAcmeAny.eventEmitter.on('error', (data: any) => {
const failure: CertificateFailure = {
domain: data.domain || 'unknown',
error: data.message || data.toString(),
isRenewal: false
};
this.emit(CertificateEvents.CERTIFICATE_FAILED, failure);
});
}
}
/**
* Ensure certificate store directory exists
*/
@ -115,16 +149,32 @@ export class ChallengeResponder extends plugins.EventEmitter {
if (url.startsWith('/.well-known/acme-challenge/')) {
const token = url.split('/').pop() || '';
if (token) {
const response = this.http01Handler.getResponse(token);
if (token && this.http01Handler) {
try {
// Try to delegate to the handler - casting to any for flexibility
const handler = this.http01Handler as any;
if (response) {
// This is a valid ACME challenge
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
res.writeHead(200);
res.end(response);
return true;
// Different versions may have different handler methods
if (typeof handler.handleChallenge === 'function') {
handler.handleChallenge(req, res);
return true;
} else if (typeof handler.handleRequest === 'function') {
// Some versions use handleRequest instead
handler.handleRequest(req, res);
return true;
} else {
// Fall back to manual response
const resp = this.getTokenResponse(token);
if (resp) {
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
res.writeHead(200);
res.end(resp);
return true;
}
}
} catch (err) {
// Challenge not found
}
}
@ -137,6 +187,42 @@ export class ChallengeResponder extends plugins.EventEmitter {
return false;
}
/**
* Get the response for a specific token if available
* This is a fallback method in case direct handler access isn't available
*/
private getTokenResponse(token: string): string | null {
if (!this.http01Handler) return null;
try {
// Cast to any to handle different versions of the API
const handler = this.http01Handler as any;
// Try different methods that might be available in different versions
if (typeof handler.getResponse === 'function') {
return handler.getResponse(token);
}
if (typeof handler.getChallengeVerification === 'function') {
return handler.getChallengeVerification(token);
}
// Try to access the challenges directly from the handler's internal state
if (handler.challenges && typeof handler.challenges === 'object' && handler.challenges[token]) {
return handler.challenges[token];
}
// Try the token map if it exists (another common pattern)
if (handler.tokenMap && typeof handler.tokenMap === 'object' && handler.tokenMap[token]) {
return handler.tokenMap[token];
}
} catch (err) {
console.error('Error getting token response:', err);
}
return null;
}
/**
* Request a certificate for a domain
* @param domain Domain name
@ -148,16 +234,20 @@ export class ChallengeResponder extends plugins.EventEmitter {
}
try {
const result = await this.smartAcme.getCertificate(domain);
// Request certificate via SmartAcme
const certObj = await this.smartAcme.getCertificateForDomain(domain);
const certData: CertificateData = {
domain,
certificate: result.cert,
privateKey: result.key,
expiryDate: new Date(result.expiryDate),
certificate: certObj.publicKey,
privateKey: certObj.privateKey,
expiryDate: new Date(certObj.validUntil),
source: 'http01',
isRenewal
};
// Emit appropriate event
// SmartACME will emit its own events, but we'll emit our own too
// for consistency with the rest of the system
if (isRenewal) {
this.emit(CertificateEvents.CERTIFICATE_RENEWED, certData);
} else {

View File

@ -1,3 +1,13 @@
/**
* Port 80 handling
*/
// Export the main components
export { Port80Handler } from './port80-handler.js';
export { ChallengeResponder } from './challenge-responder.js';
// Export backward compatibility interfaces and types
export {
HttpError as Port80HandlerError,
CertificateError as CertError
} from '../models/http-types.js';

View File

@ -15,8 +15,8 @@ import {
HttpError,
CertificateError,
ServerError,
DomainCertificate
} from '../models/http-types.js';
import type { DomainCertificate } from '../models/http-types.js';
import { ChallengeResponder } from './challenge-responder.js';
// Re-export for backward compatibility
@ -115,22 +115,20 @@ export class Port80Handler extends plugins.EventEmitter {
console.log('Port80Handler is disabled, skipping start');
return;
}
// Initialize SmartAcme with in-memory HTTP-01 challenge handler
if (this.options.enabled) {
this.smartAcmeHttp01Handler = new plugins.smartacme.handlers.Http01MemoryHandler();
this.smartAcme = new plugins.smartacme.SmartAcme({
accountEmail: this.options.accountEmail,
certManager: new plugins.smartacme.certmanagers.MemoryCertManager(),
environment: this.options.useProduction ? 'production' : 'integration',
challengeHandlers: [ this.smartAcmeHttp01Handler ],
challengePriority: ['http-01'],
});
await this.smartAcme.start();
// Initialize the challenge responder if enabled
if (this.options.enabled && this.challengeResponder) {
try {
await this.challengeResponder.initialize();
} catch (error) {
throw new ServerError(`Failed to initialize challenge responder: ${
error instanceof Error ? error.message : String(error)
}`);
}
}
return new Promise((resolve, reject) => {
try {
this.server = plugins.http.createServer((req, res) => this.handleRequest(req, res));
this.server.on('error', (error: NodeJS.ErrnoException) => {
@ -145,7 +143,7 @@ export class Port80Handler extends plugins.EventEmitter {
this.server.listen(this.options.port, () => {
console.log(`Port80Handler is listening on port ${this.options.port}`);
this.emit(Port80HandlerEvents.MANAGER_STARTED, this.options.port);
this.emit(CertificateEvents.MANAGER_STARTED, this.options.port);
// Start certificate process for domains with acmeMaintenance enabled
for (const [domain, domainInfo] of this.domainCertificates.entries()) {
@ -172,7 +170,7 @@ export class Port80Handler extends plugins.EventEmitter {
}
/**
* Stops the HTTP server and renewal timer
* Stops the HTTP server and cleanup resources
*/
public async stop(): Promise<void> {
if (!this.server) {
@ -181,13 +179,12 @@ export class Port80Handler extends plugins.EventEmitter {
this.isShuttingDown = true;
return new Promise<void>((resolve) => {
if (this.server) {
this.server.close(() => {
this.server = null;
this.isShuttingDown = false;
this.emit(Port80HandlerEvents.MANAGER_STOPPED);
this.emit(CertificateEvents.MANAGER_STOPPED);
resolve();
});
} else {
@ -201,9 +198,9 @@ export class Port80Handler extends plugins.EventEmitter {
* Adds a domain with configuration options
* @param options Domain configuration options
*/
public addDomain(options: IDomainOptions): void {
public addDomain(options: DomainOptions): void {
if (!options.domainName || typeof options.domainName !== 'string') {
throw new Port80HandlerError('Invalid domain name');
throw new HttpError('Invalid domain name');
}
const domainName = options.domainName;
@ -250,7 +247,7 @@ export class Port80Handler extends plugins.EventEmitter {
* Gets the certificate for a domain if it exists
* @param domain The domain to get the certificate for
*/
public getCertificate(domain: string): ICertificateData | null {
public getCertificate(domain: string): CertificateData | null {
// Can't get certificates for glob patterns
if (this.isGlobPattern(domain)) {
return null;
@ -286,7 +283,7 @@ export class Port80Handler extends plugins.EventEmitter {
* @param requestDomain The actual domain from the request
* @returns The domain info or null if not found
*/
private getDomainInfoForRequest(requestDomain: string): { domainInfo: IDomainCertificate, pattern: string } | null {
private getDomainInfoForRequest(requestDomain: string): { domainInfo: DomainCertificate, pattern: string } | null {
// Try direct match first
if (this.domainCertificates.has(requestDomain)) {
return {
@ -338,9 +335,16 @@ export class Port80Handler extends plugins.EventEmitter {
* @param res The HTTP response
*/
private handleRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
// Emit request received event with basic info
this.emit(HttpEvents.REQUEST_RECEIVED, {
url: req.url,
method: req.method,
headers: req.headers
});
const hostHeader = req.headers.host;
if (!hostHeader) {
res.statusCode = 400;
res.statusCode = HttpStatus.BAD_REQUEST;
res.end('Bad Request: Host header is missing');
return;
}
@ -348,6 +352,28 @@ export class Port80Handler extends plugins.EventEmitter {
// Extract domain (ignoring any port in the Host header)
const domain = hostHeader.split(':')[0];
// Check if this is an ACME challenge request that our ChallengeResponder can handle
if (this.challengeResponder && req.url?.startsWith('/.well-known/acme-challenge/')) {
// Handle ACME HTTP-01 challenge with the challenge responder
const domainMatch = this.getDomainInfoForRequest(domain);
// If there's a specific ACME forwarding config for this domain, use that instead
if (domainMatch?.domainInfo.options.acmeForward) {
this.forwardRequest(req, res, domainMatch.domainInfo.options.acmeForward, 'ACME challenge');
return;
}
// If domain exists and has acmeMaintenance enabled, or we don't have the domain yet
// (for auto-provisioning), try to handle the ACME challenge
if (!domainMatch || domainMatch.domainInfo.options.acmeMaintenance) {
// Let the challenge responder try to handle this request
if (this.challengeResponder.handleRequest(req, res)) {
// Challenge was handled
return;
}
}
}
// Dynamic provisioning: if domain not yet managed, register for ACME and return 503
if (!this.domainCertificates.has(domain)) {
try {
@ -355,14 +381,15 @@ export class Port80Handler extends plugins.EventEmitter {
} catch (err) {
console.error(`Error registering domain for on-demand provisioning: ${err}`);
}
res.statusCode = 503;
res.statusCode = HttpStatus.SERVICE_UNAVAILABLE;
res.end('Certificate issuance in progress');
return;
}
// Get domain config, using glob pattern matching if needed
const domainMatch = this.getDomainInfoForRequest(domain);
if (!domainMatch) {
res.statusCode = 404;
res.statusCode = HttpStatus.NOT_FOUND;
res.end('Domain not configured');
return;
}
@ -370,29 +397,6 @@ export class Port80Handler extends plugins.EventEmitter {
const { domainInfo, pattern } = domainMatch;
const options = domainInfo.options;
// Handle ACME HTTP-01 challenge requests or forwarding
if (req.url && req.url.startsWith('/.well-known/acme-challenge/')) {
// Forward ACME requests if configured
if (options.acmeForward) {
this.forwardRequest(req, res, options.acmeForward, 'ACME challenge');
return;
}
// If not managing ACME for this domain, return 404
if (!options.acmeMaintenance) {
res.statusCode = 404;
res.end('Not found');
return;
}
// Delegate to Http01MemoryHandler
if (this.smartAcmeHttp01Handler) {
this.smartAcmeHttp01Handler.handleRequest(req, res);
} else {
res.statusCode = 500;
res.end('ACME HTTP-01 handler not initialized');
}
return;
}
// Check if we should forward non-ACME requests
if (options.forward) {
this.forwardRequest(req, res, options.forward, 'HTTP');
@ -406,7 +410,7 @@ export class Port80Handler extends plugins.EventEmitter {
const portSuffix = httpsPort === 443 ? '' : `:${httpsPort}`;
const redirectUrl = `https://${domain}${portSuffix}${req.url || '/'}`;
res.statusCode = 301;
res.statusCode = HttpStatus.MOVED_PERMANENTLY;
res.setHeader('Location', redirectUrl);
res.end(`Redirecting to ${redirectUrl}`);
return;
@ -419,7 +423,7 @@ export class Port80Handler extends plugins.EventEmitter {
if (!domainInfo.obtainingInProgress) {
this.obtainCertificate(domain).catch(err => {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
this.emit(Port80HandlerEvents.CERTIFICATE_FAILED, {
this.emit(CertificateEvents.CERTIFICATE_FAILED, {
domain,
error: errorMessage,
isRenewal: false
@ -428,14 +432,21 @@ export class Port80Handler extends plugins.EventEmitter {
});
}
res.statusCode = 503;
res.statusCode = HttpStatus.SERVICE_UNAVAILABLE;
res.end('Certificate issuance in progress, please try again later.');
return;
}
// Default response for unhandled request
res.statusCode = 404;
res.statusCode = HttpStatus.NOT_FOUND;
res.end('No handlers configured for this request');
// Emit request handled event
this.emit(HttpEvents.REQUEST_HANDLED, {
domain,
url: req.url,
statusCode: res.statusCode
});
}
/**
@ -448,7 +459,7 @@ export class Port80Handler extends plugins.EventEmitter {
private forwardRequest(
req: plugins.http.IncomingMessage,
res: plugins.http.ServerResponse,
target: IForwardConfig,
target: ForwardConfig,
requestType: string
): void {
const options = {
@ -464,7 +475,7 @@ export class Port80Handler extends plugins.EventEmitter {
const proxyReq = plugins.http.request(options, (proxyRes) => {
// Copy status code
res.statusCode = proxyRes.statusCode || 500;
res.statusCode = proxyRes.statusCode || HttpStatus.INTERNAL_SERVER_ERROR;
// Copy headers
for (const [key, value] of Object.entries(proxyRes.headers)) {
@ -474,7 +485,7 @@ export class Port80Handler extends plugins.EventEmitter {
// Pipe response data
proxyRes.pipe(res);
this.emit(Port80HandlerEvents.REQUEST_FORWARDED, {
this.emit(HttpEvents.REQUEST_FORWARDED, {
domain,
requestType,
target: `${target.ip}:${target.port}`,
@ -484,8 +495,15 @@ export class Port80Handler extends plugins.EventEmitter {
proxyReq.on('error', (error) => {
console.error(`Error forwarding request to ${target.ip}:${target.port}:`, error);
this.emit(HttpEvents.REQUEST_ERROR, {
domain,
error: error.message,
target: `${target.ip}:${target.port}`
});
if (!res.headersSent) {
res.statusCode = 502;
res.statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
res.end(`Proxy error: ${error.message}`);
} else {
res.end();
@ -506,59 +524,48 @@ export class Port80Handler extends plugins.EventEmitter {
* @param domain The domain to obtain a certificate for
* @param isRenewal Whether this is a renewal attempt
*/
/**
* Obtains a certificate for a domain using SmartAcme HTTP-01 challenges
* @param domain The domain to obtain a certificate for
* @param isRenewal Whether this is a renewal attempt
*/
private async obtainCertificate(domain: string, isRenewal: boolean = false): Promise<void> {
if (this.isGlobPattern(domain)) {
throw new CertificateError('Cannot obtain certificates for glob pattern domains', domain, isRenewal);
}
const domainInfo = this.domainCertificates.get(domain)!;
if (!domainInfo.options.acmeMaintenance) {
console.log(`Skipping certificate issuance for ${domain} - acmeMaintenance is disabled`);
return;
}
if (domainInfo.obtainingInProgress) {
console.log(`Certificate issuance already in progress for ${domain}`);
return;
}
if (!this.smartAcme) {
throw new Port80HandlerError('SmartAcme is not initialized');
if (!this.challengeResponder) {
throw new HttpError('Challenge responder is not initialized');
}
domainInfo.obtainingInProgress = true;
domainInfo.lastRenewalAttempt = new Date();
try {
// Request certificate via SmartAcme
const certObj = await this.smartAcme.getCertificateForDomain(domain);
const certificate = certObj.publicKey;
const privateKey = certObj.privateKey;
const expiryDate = new Date(certObj.validUntil);
domainInfo.certificate = certificate;
domainInfo.privateKey = privateKey;
// Request certificate via ChallengeResponder
const certData = await this.challengeResponder.requestCertificate(domain, isRenewal);
// Update domain info with certificate data
domainInfo.certificate = certData.certificate;
domainInfo.privateKey = certData.privateKey;
domainInfo.certObtained = true;
domainInfo.expiryDate = expiryDate;
domainInfo.expiryDate = certData.expiryDate;
console.log(`Certificate ${isRenewal ? 'renewed' : 'obtained'} for ${domain}`);
// Persistence moved to CertProvisioner
const eventType = isRenewal
? Port80HandlerEvents.CERTIFICATE_RENEWED
: Port80HandlerEvents.CERTIFICATE_ISSUED;
this.emitCertificateEvent(eventType, {
domain,
certificate,
privateKey,
expiryDate: expiryDate || this.getDefaultExpiryDate()
});
// The event will be emitted by the ChallengeResponder, we just store the certificate
} catch (error: any) {
const errorMsg = error?.message || 'Unknown error';
const errorMsg = error instanceof Error ? error.message : String(error);
console.error(`Error during certificate issuance for ${domain}:`, error);
this.emit(Port80HandlerEvents.CERTIFICATE_FAILED, {
domain,
error: errorMsg,
isRenewal
} as ICertificateFailure);
// The failure event will be emitted by the ChallengeResponder
throw new CertificateError(errorMsg, domain, isRenewal);
} finally {
domainInfo.obtainingInProgress = false;
@ -608,7 +615,7 @@ export class Port80Handler extends plugins.EventEmitter {
* @param eventType The event type to emit
* @param data The certificate data
*/
private emitCertificateEvent(eventType: Port80HandlerEvents, data: ICertificateData): void {
private emitCertificateEvent(eventType: CertificateEvents, data: CertificateData): void {
this.emit(eventType, data);
}
@ -670,7 +677,7 @@ export class Port80Handler extends plugins.EventEmitter {
*/
public async renewCertificate(domain: string): Promise<void> {
if (!this.domainCertificates.has(domain)) {
throw new Port80HandlerError(`Domain not managed: ${domain}`);
throw new HttpError(`Domain not managed: ${domain}`);
}
// Trigger renewal via ACME
await this.obtainCertificate(domain, true);

View File

@ -5,7 +5,17 @@
// Legacy exports (to maintain backward compatibility)
export * from './nfttablesproxy/classes.nftablesproxy.js';
export * from './networkproxy/index.js';
export * from './port80handler/classes.port80handler.js';
// Export port80handler elements selectively to avoid conflicts
export {
Port80Handler,
default as Port80HandlerDefault,
HttpError,
ServerError,
CertificateError
} from './port80handler/classes.port80handler.js';
// Use re-export to control the names
export { Port80HandlerEvents } from './certificate/events/certificate-events.js';
export * from './redirect/classes.redirect.js';
export * from './smartproxy/classes.smartproxy.js';
// Original: export * from './smartproxy/classes.pp.snihandler.js'
@ -20,3 +30,4 @@ export * from './core/models/common-types.js';
export * as forwarding from './forwarding/index.js';
export * as certificate from './certificate/index.js';
export * as tls from './tls/index.js';
export * as http from './http/index.js';

View File

@ -1,5 +1,6 @@
// node native scope
import { EventEmitter } from 'events';
import * as fs from 'fs';
import * as http from 'http';
import * as https from 'https';
import * as net from 'net';
@ -7,7 +8,7 @@ import * as tls from 'tls';
import * as url from 'url';
import * as http2 from 'http2';
export { EventEmitter, http, https, net, tls, url, http2 };
export { EventEmitter, fs, http, https, net, tls, url, http2 };
// tsclass scope
import * as tsclass from '@tsclass/tsclass';

View File

@ -0,0 +1,24 @@
/**
* TEMPORARY FILE FOR BACKWARD COMPATIBILITY
* This will be removed in a future version when all imports are updated
* @deprecated Use the new HTTP module instead
*/
// Re-export the Port80Handler from its new location
export * from '../http/port80/port80-handler.js';
// Re-export HTTP error types for backward compatibility
export * from '../http/models/http-types.js';
// Re-export selected events to avoid name conflicts
export {
CertificateEvents,
Port80HandlerEvents,
CertProvisionerEvents
} from '../certificate/events/certificate-events.js';
// Import the new Port80Handler
import { Port80Handler } from '../http/port80/port80-handler.js';
// Export it as the default export for backward compatibility
export default Port80Handler;