124 lines
3.3 KiB
TypeScript
124 lines
3.3 KiB
TypeScript
import net from 'node:net';
|
|
import { env } from '../config/env.js';
|
|
|
|
export interface EmailDiagnosticsResult {
|
|
host: string;
|
|
port: number;
|
|
from: string;
|
|
smtpAuth: boolean;
|
|
startTls: boolean;
|
|
usernameConfigured: boolean;
|
|
passwordConfigured: boolean;
|
|
ok: boolean;
|
|
reachable: boolean;
|
|
elapsedMs: number;
|
|
checkedAt: string;
|
|
error: string | null;
|
|
}
|
|
|
|
function mailHost(): string {
|
|
return String(process.env.ACS_MAIL_HOST ?? env.mailHost ?? '').trim();
|
|
}
|
|
|
|
function mailPort(): number {
|
|
const port = Number(process.env.ACS_MAIL_PORT ?? env.mailPort);
|
|
return Number.isFinite(port) && port > 0 ? port : 25;
|
|
}
|
|
|
|
function mailFrom(): string {
|
|
return String(process.env.ACS_MAIL_FROM ?? env.mailFrom ?? '').trim();
|
|
}
|
|
|
|
function mailUsername(): string {
|
|
return String(process.env.ACS_MAIL_USERNAME ?? env.mailUsername ?? '').trim();
|
|
}
|
|
|
|
function mailPassword(): string {
|
|
return String(process.env.ACS_MAIL_PASSWORD ?? env.mailPassword ?? '').trim();
|
|
}
|
|
|
|
function mailSmtpAuth(): boolean {
|
|
return (process.env.ACS_MAIL_SMTP_AUTH ?? String(env.mailSmtpAuth)).toLowerCase() === 'true';
|
|
}
|
|
|
|
function mailStartTls(): boolean {
|
|
return (process.env.ACS_MAIL_STARTTLS ?? String(env.mailStartTls)).toLowerCase() === 'true';
|
|
}
|
|
|
|
function mailTimeoutMs(): number {
|
|
const timeout = Number(process.env.ACS_MAIL_TIMEOUT_MS ?? env.mailTimeoutMs);
|
|
return Number.isFinite(timeout) && timeout > 0 ? timeout : 10000;
|
|
}
|
|
|
|
function result(partial: Partial<EmailDiagnosticsResult>): EmailDiagnosticsResult {
|
|
return {
|
|
host: mailHost(),
|
|
port: mailPort(),
|
|
from: mailFrom(),
|
|
smtpAuth: mailSmtpAuth(),
|
|
startTls: mailStartTls(),
|
|
usernameConfigured: Boolean(mailUsername()),
|
|
passwordConfigured: Boolean(mailPassword()),
|
|
ok: false,
|
|
reachable: false,
|
|
elapsedMs: 0,
|
|
checkedAt: new Date().toISOString(),
|
|
error: null,
|
|
...partial,
|
|
};
|
|
}
|
|
|
|
function connectSmtp(host: string, port: number, timeoutMs: number): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
const socket = net.createConnection({ host, port });
|
|
let settled = false;
|
|
|
|
const finish = (error?: Error) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
socket.destroy();
|
|
if (error) reject(error);
|
|
else resolve();
|
|
};
|
|
|
|
socket.setTimeout(timeoutMs);
|
|
socket.once('connect', () => finish());
|
|
socket.once('timeout', () => finish(new Error(`SMTP connection timed out after ${timeoutMs} ms`)));
|
|
socket.once('error', (error) => finish(error));
|
|
});
|
|
}
|
|
|
|
export async function diagnoseEmailConnectivity(): Promise<EmailDiagnosticsResult> {
|
|
const host = mailHost();
|
|
const port = mailPort();
|
|
const startedAt = Date.now();
|
|
|
|
if (!host) {
|
|
return result({
|
|
ok: false,
|
|
reachable: false,
|
|
checkedAt: new Date().toISOString(),
|
|
error: 'ACS_MAIL_HOST가 설정되지 않았습니다.',
|
|
});
|
|
}
|
|
|
|
try {
|
|
await connectSmtp(host, port, mailTimeoutMs());
|
|
return result({
|
|
ok: true,
|
|
reachable: true,
|
|
elapsedMs: Date.now() - startedAt,
|
|
checkedAt: new Date().toISOString(),
|
|
});
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
return result({
|
|
ok: false,
|
|
reachable: false,
|
|
elapsedMs: Date.now() - startedAt,
|
|
checkedAt: new Date().toISOString(),
|
|
error: message.slice(0, 500),
|
|
});
|
|
}
|
|
}
|