import { Socket } from 'net'; import { TLSSocket } from 'tls'; import { makeSMTPError, SMTPErrorStates } from './error.js'; export class SMTPResponse { private buffer = ''; public stop: (err?: Error) => void; /** * @param [stream] The open socket to stream a response from * @param [timeout] The time to wait (in milliseconds) before closing the socket * @param [onerror] The function to call on error */ constructor(private stream: Socket | TLSSocket, timeout: number, onerror: (err: Error) => void) { const watch = (data: Parameters[0]) => this.watch(data); const end = () => this.end(); const close = () => this.close(); const error = (data: Parameters[0]) => this.error(data); const timedout = (data: Parameters[0]) => this.timedout(data); this.stream.on('data', watch); this.stream.on('end', end); this.stream.on('close', close); this.stream.on('error', error); this.stream.setTimeout(timeout, timedout); this.stop = err => { this.stream.removeAllListeners('response'); this.stream.removeListener('data', watch); this.stream.removeListener('end', end); this.stream.removeListener('close', close); this.stream.removeListener('error', error); if (err != null && typeof onerror === 'function') { onerror(err); } }; } public notify() { if (this.buffer.length) { // parse buffer for response codes const line = this.buffer.replace('\r', ''); if ( !line .trim() .split(/\n/) .pop() .match(/^(\d{3})\s/) ) { return; } const match = line ? line.match(/(\d+)\s?(.*)/) : null; const data = match !== null ? { code: match[1], message: match[2], data: line } : { code: -1, data: line }; this.stream.emit('response', null, data); this.buffer = ''; } } protected error(err: Error) { this.stream.emit( 'response', makeSMTPError('connection encountered an error', SMTPErrorStates.ERROR, err) ); } protected watch(data: string | Buffer) { if (data !== null) { this.buffer += data.toString(); this.notify(); } } protected timedout(err: Error) { this.stream.end(); this.stream.emit( 'response', makeSMTPError( 'timedout while connecting to smtp server', SMTPErrorStates.TIMEDOUT, err ) ); } protected close() { this.stream.emit( 'response', makeSMTPError('connection has closed', SMTPErrorStates.CONNECTIONCLOSED) ); } protected end() { this.stream.emit( 'response', makeSMTPError('connection has ended', SMTPErrorStates.CONNECTIONENDED) ); } } /** * @param [stream] the open socket to stream a response from * @param [timeout] the time to wait (in milliseconds) before closing the socket * @param [onerror] the function to call on error */ export const monitor = (stream: Socket | TLSSocket, timeout: number, onerror: (err: Error) => void) => new SMTPResponse(stream, timeout, onerror);