@push.rocks/smartproxy
Version:
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.
224 lines • 15.3 kB
JavaScript
import * as plugins from '../../plugins.js';
import { Port80HandlerEvents } from '../models/common-types.js';
/**
* Standardized event names used throughout the system
*/
export var ProxyEvents;
(function (ProxyEvents) {
// Certificate events
ProxyEvents["CERTIFICATE_ISSUED"] = "certificate:issued";
ProxyEvents["CERTIFICATE_RENEWED"] = "certificate:renewed";
ProxyEvents["CERTIFICATE_FAILED"] = "certificate:failed";
ProxyEvents["CERTIFICATE_EXPIRING"] = "certificate:expiring";
// Component lifecycle events
ProxyEvents["COMPONENT_STARTED"] = "component:started";
ProxyEvents["COMPONENT_STOPPED"] = "component:stopped";
// Connection events
ProxyEvents["CONNECTION_ESTABLISHED"] = "connection:established";
ProxyEvents["CONNECTION_CLOSED"] = "connection:closed";
ProxyEvents["CONNECTION_ERROR"] = "connection:error";
// Request events
ProxyEvents["REQUEST_RECEIVED"] = "request:received";
ProxyEvents["REQUEST_COMPLETED"] = "request:completed";
ProxyEvents["REQUEST_ERROR"] = "request:error";
// Route events
ProxyEvents["ROUTE_MATCHED"] = "route:matched";
ProxyEvents["ROUTE_UPDATED"] = "route:updated";
ProxyEvents["ROUTE_ERROR"] = "route:error";
// Security events
ProxyEvents["SECURITY_BLOCKED"] = "security:blocked";
ProxyEvents["SECURITY_BREACH_ATTEMPT"] = "security:breach-attempt";
// TLS events
ProxyEvents["TLS_HANDSHAKE_STARTED"] = "tls:handshake-started";
ProxyEvents["TLS_HANDSHAKE_COMPLETED"] = "tls:handshake-completed";
ProxyEvents["TLS_HANDSHAKE_FAILED"] = "tls:handshake-failed";
})(ProxyEvents || (ProxyEvents = {}));
/**
* Component types for event metadata
*/
export var ComponentType;
(function (ComponentType) {
ComponentType["SMART_PROXY"] = "smart-proxy";
ComponentType["NETWORK_PROXY"] = "network-proxy";
ComponentType["NFTABLES_PROXY"] = "nftables-proxy";
ComponentType["PORT80_HANDLER"] = "port80-handler";
ComponentType["CERTIFICATE_MANAGER"] = "certificate-manager";
ComponentType["ROUTE_MANAGER"] = "route-manager";
ComponentType["CONNECTION_MANAGER"] = "connection-manager";
ComponentType["TLS_MANAGER"] = "tls-manager";
ComponentType["SECURITY_MANAGER"] = "security-manager";
})(ComponentType || (ComponentType = {}));
/**
* Helper class to standardize event emission and handling
* across all system components
*/
export class EventSystem {
constructor(componentType, componentId = '', logger) {
this.emitter = new plugins.EventEmitter();
this.componentType = componentType;
this.componentId = componentId;
this.logger = logger;
}
/**
* Emit a certificate issued event
*/
emitCertificateIssued(data) {
const eventData = {
...data,
timestamp: Date.now(),
componentType: this.componentType,
componentId: this.componentId
};
this.logger?.info?.(`Certificate issued for ${data.domain}`);
this.emitter.emit(ProxyEvents.CERTIFICATE_ISSUED, eventData);
}
/**
* Emit a certificate renewed event
*/
emitCertificateRenewed(data) {
const eventData = {
...data,
timestamp: Date.now(),
componentType: this.componentType,
componentId: this.componentId
};
this.logger?.info?.(`Certificate renewed for ${data.domain}`);
this.emitter.emit(ProxyEvents.CERTIFICATE_RENEWED, eventData);
}
/**
* Emit a certificate failed event
*/
emitCertificateFailed(data) {
const eventData = {
...data,
timestamp: Date.now(),
componentType: this.componentType,
componentId: this.componentId
};
this.logger?.error?.(`Certificate issuance failed for ${data.domain}: ${data.error}`);
this.emitter.emit(ProxyEvents.CERTIFICATE_FAILED, eventData);
}
/**
* Emit a certificate expiring event
*/
emitCertificateExpiring(data) {
const eventData = {
...data,
timestamp: Date.now(),
componentType: this.componentType,
componentId: this.componentId
};
this.logger?.warn?.(`Certificate expiring for ${data.domain} in ${data.daysRemaining} days`);
this.emitter.emit(ProxyEvents.CERTIFICATE_EXPIRING, eventData);
}
/**
* Emit a component started event
*/
emitComponentStarted(name, version) {
const eventData = {
name,
version,
timestamp: Date.now(),
componentType: this.componentType,
componentId: this.componentId
};
this.logger?.info?.(`Component ${name} started${version ? ` (v${version})` : ''}`);
this.emitter.emit(ProxyEvents.COMPONENT_STARTED, eventData);
}
/**
* Emit a component stopped event
*/
emitComponentStopped(name) {
const eventData = {
name,
timestamp: Date.now(),
componentType: this.componentType,
componentId: this.componentId
};
this.logger?.info?.(`Component ${name} stopped`);
this.emitter.emit(ProxyEvents.COMPONENT_STOPPED, eventData);
}
/**
* Emit a connection established event
*/
emitConnectionEstablished(data) {
const eventData = {
...data,
timestamp: Date.now(),
componentType: this.componentType,
componentId: this.componentId
};
this.logger?.debug?.(`Connection ${data.connectionId} established from ${data.clientIp} on port ${data.port}`);
this.emitter.emit(ProxyEvents.CONNECTION_ESTABLISHED, eventData);
}
/**
* Emit a connection closed event
*/
emitConnectionClosed(data) {
const eventData = {
...data,
timestamp: Date.now(),
componentType: this.componentType,
componentId: this.componentId
};
this.logger?.debug?.(`Connection ${data.connectionId} closed`);
this.emitter.emit(ProxyEvents.CONNECTION_CLOSED, eventData);
}
/**
* Emit a route matched event
*/
emitRouteMatched(data) {
const eventData = {
...data,
timestamp: Date.now(),
componentType: this.componentType,
componentId: this.componentId
};
this.logger?.debug?.(`Route matched: ${data.route.name || data.route.id || 'unnamed'}`);
this.emitter.emit(ProxyEvents.ROUTE_MATCHED, eventData);
}
/**
* Subscribe to an event
*/
on(event, handler) {
this.emitter.on(event, handler);
}
/**
* Subscribe to an event once
*/
once(event, handler) {
this.emitter.once(event, handler);
}
/**
* Unsubscribe from an event
*/
off(event, handler) {
this.emitter.off(event, handler);
}
/**
* Map Port80Handler events to standard proxy events
*/
subscribePort80HandlerEvents(handler) {
handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, (data) => {
this.emitCertificateIssued({
...data,
isRenewal: false,
source: 'port80handler'
});
});
handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, (data) => {
this.emitCertificateRenewed({
...data,
isRenewal: true,
source: 'port80handler'
});
});
handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, (data) => {
this.emitCertificateFailed(data);
});
handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, (data) => {
this.emitCertificateExpiring(data);
});
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXZlbnQtc3lzdGVtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvY29yZS91dGlscy9ldmVudC1zeXN0ZW0udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQU81QyxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUVoRTs7R0FFRztBQUNILE1BQU0sQ0FBTixJQUFZLFdBa0NYO0FBbENELFdBQVksV0FBVztJQUNyQixxQkFBcUI7SUFDckIsd0RBQXlDLENBQUE7SUFDekMsMERBQTJDLENBQUE7SUFDM0Msd0RBQXlDLENBQUE7SUFDekMsNERBQTZDLENBQUE7SUFFN0MsNkJBQTZCO0lBQzdCLHNEQUF1QyxDQUFBO0lBQ3ZDLHNEQUF1QyxDQUFBO0lBRXZDLG9CQUFvQjtJQUNwQixnRUFBaUQsQ0FBQTtJQUNqRCxzREFBdUMsQ0FBQTtJQUN2QyxvREFBcUMsQ0FBQTtJQUVyQyxpQkFBaUI7SUFDakIsb0RBQXFDLENBQUE7SUFDckMsc0RBQXVDLENBQUE7SUFDdkMsOENBQStCLENBQUE7SUFFL0IsZUFBZTtJQUNmLDhDQUErQixDQUFBO0lBQy9CLDhDQUErQixDQUFBO0lBQy9CLDBDQUEyQixDQUFBO0lBRTNCLGtCQUFrQjtJQUNsQixvREFBcUMsQ0FBQTtJQUNyQyxrRUFBbUQsQ0FBQTtJQUVuRCxhQUFhO0lBQ2IsOERBQStDLENBQUE7SUFDL0Msa0VBQW1ELENBQUE7SUFDbkQsNERBQTZDLENBQUE7QUFDL0MsQ0FBQyxFQWxDVyxXQUFXLEtBQVgsV0FBVyxRQWtDdEI7QUFFRDs7R0FFRztBQUNILE1BQU0sQ0FBTixJQUFZLGFBVVg7QUFWRCxXQUFZLGFBQWE7SUFDdkIsNENBQTJCLENBQUE7SUFDM0IsZ0RBQStCLENBQUE7SUFDL0Isa0RBQWlDLENBQUE7SUFDakMsa0RBQWlDLENBQUE7SUFDakMsNERBQTJDLENBQUE7SUFDM0MsZ0RB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