98 أسطر
4.3 KiB
TypeScript
98 أسطر
4.3 KiB
TypeScript
import { readFileSync } from 'fs';
|
|
|
|
export interface RabbitConfigSource {
|
|
get(key: string): string | undefined;
|
|
}
|
|
|
|
export interface ResolvedRabbitMqConfig {
|
|
url: string;
|
|
sanitizedEndpoint: string;
|
|
socketOptions: {
|
|
heartbeatIntervalInSeconds: number;
|
|
reconnectTimeInSeconds: number;
|
|
connectionOptions: Record<string, unknown>;
|
|
};
|
|
tlsEnabled: boolean;
|
|
}
|
|
|
|
const bool = (value: string | undefined, fallback = false) => value === undefined ? fallback : value === 'true';
|
|
|
|
function material(source: RabbitConfigSource, base64Key: string, pathKey: string): Buffer | undefined {
|
|
const encoded = source.get(base64Key);
|
|
if (encoded) return Buffer.from(encoded, 'base64');
|
|
const path = source.get(pathKey);
|
|
return path ? readFileSync(path) : undefined;
|
|
}
|
|
|
|
export function resolveRabbitMqConfig(
|
|
source: RabbitConfigSource,
|
|
serviceName: string,
|
|
requireProductionSafety: boolean,
|
|
): ResolvedRabbitMqConfig {
|
|
const explicitUrl = source.get('RABBITMQ_URL');
|
|
const tlsEnabled = bool(source.get('RABBITMQ_TLS_ENABLED'), explicitUrl?.startsWith('amqps://'));
|
|
const protocol = tlsEnabled ? 'amqps' : 'amqp';
|
|
const host = source.get('RABBITMQ_HOST') || '127.0.0.1';
|
|
const port = source.get('RABBITMQ_PORT') || (tlsEnabled ? '5671' : '5672');
|
|
const username = source.get('RABBITMQ_USERNAME') || 'guest';
|
|
const password = source.get('RABBITMQ_PASSWORD') || 'guest';
|
|
const vhost = source.get('RABBITMQ_VHOST') || '/';
|
|
let url = explicitUrl
|
|
? (tlsEnabled ? explicitUrl.replace(/^amqp:\/\//, 'amqps://') : explicitUrl)
|
|
: `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}/${vhost === '/' ? '' : encodeURIComponent(vhost.replace(/^\//, ''))}`;
|
|
let parsed: URL;
|
|
try { parsed = new URL(url); }
|
|
catch { throw new Error('RabbitMQ configuration contains an invalid URL'); }
|
|
if (explicitUrl && !parsed.username && source.get('RABBITMQ_USERNAME')) {
|
|
parsed.username = username;
|
|
parsed.password = password;
|
|
url = parsed.toString();
|
|
}
|
|
const configuredUser = decodeURIComponent(parsed.username || username);
|
|
const configuredPassword = decodeURIComponent(parsed.password || password);
|
|
const production = source.get('NODE_ENV') === 'production';
|
|
const tlsRequired = bool(source.get('RABBITMQ_TLS_REQUIRED'), true);
|
|
if (production && requireProductionSafety) {
|
|
if (!configuredUser || !configuredPassword || configuredUser === 'guest' || configuredPassword === 'guest') {
|
|
throw new Error('RabbitMQ production credentials must be non-guest and non-empty');
|
|
}
|
|
if (tlsRequired && parsed.protocol !== 'amqps:' && !tlsEnabled) {
|
|
throw new Error('RabbitMQ TLS/amqps is required for production microservices');
|
|
}
|
|
}
|
|
|
|
const connectionOptions: Record<string, unknown> = {
|
|
timeout: Number(source.get('RABBITMQ_CONNECTION_TIMEOUT_MS') || 10_000),
|
|
clientProperties: { connection_name: `${serviceName}:${source.get('HOSTNAME') || process.pid}` },
|
|
};
|
|
if (tlsEnabled || parsed.protocol === 'amqps:') {
|
|
connectionOptions.rejectUnauthorized = bool(source.get('RABBITMQ_TLS_REJECT_UNAUTHORIZED'), true);
|
|
const servername = source.get('RABBITMQ_SERVERNAME');
|
|
if (servername) connectionOptions.servername = servername;
|
|
const ca = material(source, 'RABBITMQ_CA_CERT_BASE64', 'RABBITMQ_CA_CERT_PATH');
|
|
const cert = material(source, 'RABBITMQ_CLIENT_CERT_BASE64', 'RABBITMQ_CLIENT_CERT_PATH');
|
|
const key = material(source, 'RABBITMQ_CLIENT_KEY_BASE64', 'RABBITMQ_CLIENT_KEY_PATH');
|
|
if (ca) connectionOptions.ca = [ca];
|
|
if (cert) connectionOptions.cert = cert;
|
|
if (key) connectionOptions.key = key;
|
|
if ((cert && !key) || (!cert && key)) throw new Error('RabbitMQ client certificate and key must be configured together');
|
|
}
|
|
|
|
return {
|
|
url,
|
|
sanitizedEndpoint: `${parsed.protocol}//${parsed.hostname}:${parsed.port || (parsed.protocol === 'amqps:' ? '5671' : '5672')}${parsed.pathname}`,
|
|
tlsEnabled: tlsEnabled || parsed.protocol === 'amqps:',
|
|
socketOptions: {
|
|
heartbeatIntervalInSeconds: Number(source.get('RABBITMQ_HEARTBEAT_SECONDS') || 10),
|
|
reconnectTimeInSeconds: Number(source.get('RABBITMQ_RECONNECT_SECONDS') || 5),
|
|
connectionOptions,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function processEnvSource(): RabbitConfigSource {
|
|
return { get: (key) => process.env[key] };
|
|
}
|
|
|
|
|