1
0
mirror of https://github.com/eleith/emailjs.git synced 2024-07-04 19:58:51 +00:00

smtp: repair differences with pre-conversion code

This commit is contained in:
Zack Schuster 2020-04-24 19:45:21 -07:00
parent 70620710bc
commit 1727412b98
4 changed files with 140 additions and 247 deletions

View File

@ -18,51 +18,16 @@ export interface MessageStack {
export class Client { export class Client {
public smtp: SMTP; public smtp: SMTP;
public queue: MessageStack[] = []; public queue: MessageStack[] = [];
public timer: any; public timer: NodeJS.Timer | null = null;
public sending: boolean; public sending = false;
public ready: boolean; public ready = false;
/** /**
* @typedef {Object} MessageStack * @param {*} server smtp options
*
* @typedef {Object} SMTPSocketOptions
* @property {string} key
* @property {string} ca
* @property {string} cert
*
* @typedef {Object} SMTPOptions
* @property {number} [timeout]
* @property {string} [user]
* @property {string} [password]
* @property {string} [domain]
* @property {string} [host]
* @property {number} [port]
* @property {boolean|SMTPSocketOptions} [ssl]
* @property {boolean|SMTPSocketOptions} [tls]
* @property {string[]} [authentication]
* @property {function(...any): void} [logger]
*
* @constructor
* @param {SMTPOptions} server smtp options
*/ */
constructor(server: Partial<import('./smtp').SMTPOptions>) { constructor(server: Partial<import('./smtp').SMTPOptions>) {
this.smtp = new SMTP(server); this.smtp = new SMTP(server);
//this.smtp.debug(1); //this.smtp.debug(1);
/**
* @type {NodeJS.Timer}
*/
this.timer = null;
/**
* @type {boolean}
*/
this.sending = false;
/**
* @type {boolean}
*/
this.ready = false;
} }
send(msg: Message, callback: (err: Error, msg: Message) => void): void { send(msg: Message, callback: (err: Error, msg: Message) => void): void {
@ -107,7 +72,7 @@ export class Client {
this.queue.push(stack); this.queue.push(stack);
this._poll(); this._poll();
} else { } else {
callback(new Error(why), /** @type {MessageStack} */ msg); callback(new Error(why), msg);
} }
}); });
} }
@ -117,13 +82,15 @@ export class Client {
* @returns {void} * @returns {void}
*/ */
_poll(): void { _poll(): void {
clearTimeout(this.timer); if (this.timer != null) {
clearTimeout(this.timer);
}
if (this.queue.length > 0) { if (this.queue.length > 0) {
if (this.smtp.state == SMTPState.NOTCONNECTED) { if (this.smtp.state() == SMTPState.NOTCONNECTED) {
this._connect(this.queue[0]); this._connect(this.queue[0]);
} else if ( } else if (
this.smtp.state == SMTPState.CONNECTED && this.smtp.state() == SMTPState.CONNECTED &&
!this.sending && !this.sending &&
this.ready this.ready
) { ) {
@ -132,7 +99,7 @@ export class Client {
} }
// wait around 1 seconds in case something does come in, // wait around 1 seconds in case something does come in,
// otherwise close out SMTP connection if still open // otherwise close out SMTP connection if still open
else if (this.smtp.state == SMTPState.CONNECTED) { else if (this.smtp.state() == SMTPState.CONNECTED) {
this.timer = setTimeout(() => this.smtp.quit(), 1000); this.timer = setTimeout(() => this.smtp.quit(), 1000);
} }
} }
@ -162,10 +129,10 @@ export class Client {
} }
}; };
if (!this.smtp.isAuthorized) { if (this.smtp.authorized()) {
this.smtp.login(begin);
} else {
this.smtp.ehlo_or_helo_if_needed(begin); this.smtp.ehlo_or_helo_if_needed(begin);
} else {
this.smtp.login(begin);
} }
} else { } else {
stack.callback(err, stack.message); stack.callback(err, stack.message);
@ -189,16 +156,16 @@ export class Client {
return !!( return !!(
msg.from && msg.from &&
(msg.to || msg.cc || msg.bcc) && (msg.to || msg.cc || msg.bcc) &&
(msg.text != null || this._containsInlinedHtml(msg.attachment)) (msg.text !== undefined || this._containsInlinedHtml(msg.attachment))
); );
} }
/** /**
* @private * @private
* @param {*} attachment attachment * @param {*} attachment attachment
* @returns {boolean} does contain * @returns {*} whether the attachment contains inlined html
*/ */
_containsInlinedHtml(attachment: any): boolean { _containsInlinedHtml(attachment: any) {
if (Array.isArray(attachment)) { if (Array.isArray(attachment)) {
return attachment.some((att) => { return attachment.some((att) => {
return this._isAttachmentInlinedHtml(att); return this._isAttachmentInlinedHtml(att);
@ -211,9 +178,9 @@ export class Client {
/** /**
* @private * @private
* @param {*} attachment attachment * @param {*} attachment attachment
* @returns {boolean} is inlined * @returns {boolean} whether the attachment is inlined html
*/ */
_isAttachmentInlinedHtml(attachment: any): boolean { _isAttachmentInlinedHtml(attachment: any) {
return ( return (
attachment && attachment &&
(attachment.data || attachment.path) && (attachment.data || attachment.path) &&
@ -267,9 +234,12 @@ export class Client {
throw new TypeError('stack.to must be array'); throw new TypeError('stack.to must be array');
} }
const { address: to } = stack.to.shift() ?? {}; const to = stack.to.shift()?.address;
this.smtp.rcpt( this.smtp.rcpt(
this._sendsmtp(stack, stack.to.length ? this._sendrcpt : this._senddata), this._sendsmtp(
stack,
stack.to.length > 0 ? this._sendrcpt : this._senddata
),
`<${to}>` `<${to}>`
); );
} }

View File

@ -10,22 +10,22 @@ import { getRFC2822Date } from './date';
type Indexed = import('@ledge/types').Indexed; type Indexed = import('@ledge/types').Indexed;
const CRLF = '\r\n'; const CRLF = '\r\n' as const;
/** /**
* MIME standard wants 76 char chunks when sending out. * MIME standard wants 76 char chunks when sending out.
*/ */
export const MIMECHUNK: 76 = 76; export const MIMECHUNK = 76 as const;
/** /**
* meets both base64 and mime divisibility * meets both base64 and mime divisibility
*/ */
export const MIME64CHUNK: 456 = (MIMECHUNK * 6) as 456; export const MIME64CHUNK = (MIMECHUNK * 6) as 456;
/** /**
* size of the message stream buffer * size of the message stream buffer
*/ */
export const BUFFERSIZE: 12768 = (MIMECHUNK * 24 * 7) as 12768; export const BUFFERSIZE = (MIMECHUNK * 24 * 7) as 12768;
export interface MessageAttachmentHeaders extends Indexed { export interface MessageAttachmentHeaders extends Indexed {
'content-type'?: string; 'content-type'?: string;
@ -34,7 +34,7 @@ export interface MessageAttachmentHeaders extends Indexed {
} }
export interface AlternateMessageAttachment extends Indexed { export interface AlternateMessageAttachment extends Indexed {
headers: MessageAttachmentHeaders; headers?: MessageAttachmentHeaders;
inline: boolean; inline: boolean;
alternative?: MessageAttachment; alternative?: MessageAttachment;
related?: MessageAttachment[]; related?: MessageAttachment[];
@ -97,35 +97,33 @@ function convertDashDelimitedTextToSnakeCase(text: string) {
export class Message { export class Message {
attachments: any[] = []; attachments: any[] = [];
alternative: AlternateMessageAttachment | null = null; alternative: AlternateMessageAttachment | null = null;
header: Partial<MessageHeaders>; header: Partial<MessageHeaders> = {
content: string; 'message-id': `<${new Date().getTime()}.${counter++}.${
process.pid
}@${hostname()}>`,
date: getRFC2822Date(),
};
content = 'text/plain; charset=utf-8';
text: any; text: any;
constructor(headers: Partial<MessageHeaders>) { constructor(headers: Partial<MessageHeaders>) {
this.header = {
'message-id': `<${new Date().getTime()}.${counter++}.${
process.pid
}@${hostname()}>`,
date: getRFC2822Date(),
};
this.content = 'text/plain; charset=utf-8';
for (const header in headers) { for (const header in headers) {
// allow user to override default content-type to override charset or send a single non-text message // allow user to override default content-type to override charset or send a single non-text message
if (/^content-type$/i.test(header)) { if (/^content-type$/i.test(header)) {
this.content = headers[header]; this.content = headers[header];
} else if (header === 'text') { } else if (header === 'text') {
this.text = headers[header]; this.text = headers[header];
} else if (header === 'attachment') { } else if (
header === 'attachment' &&
typeof headers[header] === 'object'
) {
const attachment = headers[header]; const attachment = headers[header];
if (attachment != null) { if (Array.isArray(attachment)) {
if (Array.isArray(attachment)) { for (let i = 0; i < attachment.length; i++) {
for (let i = 0; i < attachment.length; i++) { this.attach(attachment[i]);
this.attach(attachment[i]);
}
} else {
this.attach(attachment);
} }
} else if (attachment != null) {
this.attach(attachment);
} }
} else if (header === 'subject') { } else if (header === 'subject') {
this.header.subject = mimeWordEncode(headers.subject); this.header.subject = mimeWordEncode(headers.subject);
@ -166,11 +164,10 @@ export class Message {
* @param {string} [charset='utf-8'] the charset to encode as * @param {string} [charset='utf-8'] the charset to encode as
* @returns {Message} the current Message instance * @returns {Message} the current Message instance
*/ */
attach_alternative(html: string, charset = 'utf-8'): Message { attach_alternative(html: string, charset: string): Message {
this.alternative = { this.alternative = {
headers: {},
data: html, data: html,
charset, charset: charset || 'utf-8',
type: 'text/html', type: 'text/html',
inline: true, inline: true,
}; };
@ -182,7 +179,7 @@ export class Message {
* @param {function(boolean, string): void} callback This callback is displayed as part of the Requester class. * @param {function(boolean, string): void} callback This callback is displayed as part of the Requester class.
* @returns {void} * @returns {void}
*/ */
valid(callback: (arg0: boolean, arg1?: string) => void): void { valid(callback: (arg0: boolean, arg1?: string) => void) {
if (!this.header.from) { if (!this.header.from) {
callback(false, 'message does not have a valid sender'); callback(false, 'message does not have a valid sender');
} }
@ -213,10 +210,9 @@ export class Message {
} }
/** /**
* returns a stream of the current message * @returns {*} a stream of the current message
* @returns {MessageStream} a stream of the current message
*/ */
stream(): MessageStream { stream() {
return new MessageStream(this); return new MessageStream(this);
} }
@ -224,7 +220,7 @@ export class Message {
* @param {function(Error, string): void} callback the function to call with the error and buffer * @param {function(Error, string): void} callback the function to call with the error and buffer
* @returns {void} * @returns {void}
*/ */
read(callback: (arg0: Error, arg1: string) => void): void { read(callback: (err: Error, buffer: string) => void) {
let buffer = ''; let buffer = '';
const str = this.stream(); const str = this.stream();
str.on('data', (data) => (buffer += data)); str.on('data', (data) => (buffer += data));
@ -234,46 +230,18 @@ export class Message {
} }
class MessageStream extends Stream { class MessageStream extends Stream {
message: Message; readable = true;
readable: boolean; paused = false;
paused: boolean; buffer: Buffer | null = Buffer.alloc(MIMECHUNK * 24 * 7);
buffer: Buffer | null; bufferIndex = 0;
bufferIndex: number;
/** /**
* @param {Message} message the message to stream * @param {*} message the message to stream
*/ */
constructor(message: Message) { constructor(private message: Message) {
super(); super();
/** const output_mixed = () => {
* @type {Message}
*/
this.message = message;
/**
* @type {boolean}
*/
this.readable = true;
/**
* @type {boolean}
*/
this.paused = false;
/**
* @type {Buffer}
*/
this.buffer = Buffer.alloc(MIMECHUNK * 24 * 7);
/**
* @type {number}
*/
this.bufferIndex = 0;
/**
* @returns {void}
*/
const output_mixed = (): void => {
const boundary = generate_boundary(); const boundary = generate_boundary();
output( output(
`Content-Type: multipart/mixed; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}` `Content-Type: multipart/mixed; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
@ -303,7 +271,7 @@ class MessageStream extends Stream {
list: MessageAttachment[], list: MessageAttachment[],
index: number, index: number,
callback: () => void callback: () => void
): void => { ) => {
if (index < list.length) { if (index < list.length) {
output(`--${boundary}${CRLF}`); output(`--${boundary}${CRLF}`);
if (list[index].related) { if (list[index].related) {
@ -327,7 +295,7 @@ class MessageStream extends Stream {
*/ */
const output_attachment_headers = ( const output_attachment_headers = (
attachment: MessageAttachment | AlternateMessageAttachment attachment: MessageAttachment | AlternateMessageAttachment
): void => { ) => {
let data: string[] = []; let data: string[] = [];
const headers: Partial<MessageHeaders> = { const headers: Partial<MessageHeaders> = {
'content-type': 'content-type':
@ -341,8 +309,10 @@ class MessageStream extends Stream {
}; };
// allow sender to override default headers // allow sender to override default headers
for (const header in attachment.headers || {}) { if (attachment.headers != null) {
headers[header.toLowerCase()] = attachment.headers[header]; for (const header in attachment.headers) {
headers[header.toLowerCase()] = attachment.headers[header];
}
} }
for (const header in headers) { for (const header in headers) {
@ -360,7 +330,7 @@ class MessageStream extends Stream {
const output_attachment = ( const output_attachment = (
attachment: MessageAttachment | AlternateMessageAttachment, attachment: MessageAttachment | AlternateMessageAttachment,
callback: () => void callback: () => void
): void => { ) => {
const build = attachment.path const build = attachment.path
? output_file ? output_file
: attachment.stream : attachment.stream
@ -378,7 +348,7 @@ class MessageStream extends Stream {
const output_data = ( const output_data = (
attachment: MessageAttachment | AlternateMessageAttachment, attachment: MessageAttachment | AlternateMessageAttachment,
callback: () => void callback: () => void
): void => { ) => {
output_base64( output_base64(
attachment.encoded attachment.encoded
? attachment.data ? attachment.data
@ -390,7 +360,7 @@ class MessageStream extends Stream {
const output_file = ( const output_file = (
attachment: MessageAttachment | AlternateMessageAttachment, attachment: MessageAttachment | AlternateMessageAttachment,
next: (err: NodeJS.ErrnoException) => void next: (err: NodeJS.ErrnoException) => void
): void => { ) => {
const chunk = MIME64CHUNK * 16; const chunk = MIME64CHUNK * 16;
const buffer = Buffer.alloc(chunk); const buffer = Buffer.alloc(chunk);
const closed = (fd: number) => fs.closeSync(fd); const closed = (fd: number) => fs.closeSync(fd);
@ -400,7 +370,7 @@ class MessageStream extends Stream {
* @param {number} fd the file descriptor * @param {number} fd the file descriptor
* @returns {void} * @returns {void}
*/ */
const opened = (err: Error, fd: number): void => { const opened = (err: Error, fd: number) => {
if (!err) { if (!err) {
const read = (err: Error, bytes: number) => { const read = (err: Error, bytes: number) => {
if (!err && this.readable) { if (!err && this.readable) {
@ -452,13 +422,13 @@ class MessageStream extends Stream {
const output_stream = ( const output_stream = (
attachment: MessageAttachment | AlternateMessageAttachment, attachment: MessageAttachment | AlternateMessageAttachment,
callback: () => void callback: () => void
): void => { ) => {
if (attachment.stream.readable) { if (attachment.stream.readable) {
let previous = Buffer.alloc(0); let previous = Buffer.alloc(0);
attachment.stream.resume(); attachment.stream.resume();
(attachment as MessageAttachment).on('end', () => { (attachment as MessageAttachment).stream.on('end', () => {
output_base64(previous.toString('base64'), callback); output_base64(previous.toString('base64'), callback);
this.removeListener('pause', attachment.stream.pause); this.removeListener('pause', attachment.stream.pause);
this.removeListener('resume', attachment.stream.resume); this.removeListener('resume', attachment.stream.resume);
@ -497,7 +467,7 @@ class MessageStream extends Stream {
* @param {function(): void} [callback] the function to call after output is finished * @param {function(): void} [callback] the function to call after output is finished
* @returns {void} * @returns {void}
*/ */
const output_base64 = (data: string, callback?: () => void): void => { const output_base64 = (data: string, callback?: () => void) => {
const loops = Math.ceil(data.length / MIMECHUNK); const loops = Math.ceil(data.length / MIMECHUNK);
let loop = 0; let loop = 0;
while (loop < loops) { while (loop < loops) {
@ -513,7 +483,7 @@ class MessageStream extends Stream {
* @param {Message} message the message to output * @param {Message} message the message to output
* @returns {void} * @returns {void}
*/ */
const output_text = (message: Message): void => { const output_text = (message: Message) => {
let data: string[] = []; let data: string[] = [];
data = data.concat([ data = data.concat([
@ -537,7 +507,7 @@ class MessageStream extends Stream {
const output_alternative = ( const output_alternative = (
message: Message & { alternative: AlternateMessageAttachment }, message: Message & { alternative: AlternateMessageAttachment },
callback: () => void callback: () => void
): void => { ) => {
const boundary = generate_boundary(); const boundary = generate_boundary();
output( output(
`Content-Type: multipart/alternative; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}` `Content-Type: multipart/alternative; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
@ -548,7 +518,7 @@ class MessageStream extends Stream {
/** /**
* @returns {void} * @returns {void}
*/ */
const finish = (): void => { const finish = () => {
output([CRLF, '--', boundary, '--', CRLF, CRLF].join('')); output([CRLF, '--', boundary, '--', CRLF, CRLF].join(''));
callback(); callback();
}; };
@ -568,7 +538,7 @@ class MessageStream extends Stream {
const output_related = ( const output_related = (
message: AlternateMessageAttachment, message: AlternateMessageAttachment,
callback: () => void callback: () => void
): void => { ) => {
const boundary = generate_boundary(); const boundary = generate_boundary();
output( output(
`Content-Type: multipart/related; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}` `Content-Type: multipart/related; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
@ -584,7 +554,7 @@ class MessageStream extends Stream {
/** /**
* @returns {void} * @returns {void}
*/ */
const output_header_data = (): void => { const output_header_data = () => {
if (this.message.attachments.length || this.message.alternative) { if (this.message.attachments.length || this.message.alternative) {
output(`MIME-Version: 1.0${CRLF}`); output(`MIME-Version: 1.0${CRLF}`);
output_mixed(); output_mixed();
@ -598,14 +568,15 @@ class MessageStream extends Stream {
/** /**
* @returns {void} * @returns {void}
*/ */
const output_header = (): void => { const output_header = () => {
let data: string[] = []; let data: string[] = [];
for (const header in this.message.header) { for (const header in this.message.header) {
// do not output BCC in the headers (regex) nor custom Object.prototype functions... // do not output BCC in the headers (regex) nor custom Object.prototype functions...
if ( if (
!/bcc/i.test(header) && !/bcc/i.test(header) &&
Object.prototype.hasOwnProperty.call(this.message.header, header) // eslint-disable-next-line no-prototype-builtins
this.message.header.hasOwnProperty(header)
) { ) {
data = data.concat([ data = data.concat([
convertDashDelimitedTextToSnakeCase(header), convertDashDelimitedTextToSnakeCase(header),
@ -626,11 +597,7 @@ class MessageStream extends Stream {
* @param {any[]} [args] array of arguments to pass to the callback * @param {any[]} [args] array of arguments to pass to the callback
* @returns {void} * @returns {void}
*/ */
const output = ( const output = (data: string) => {
data: string,
callback?: (...args: any[]) => void,
args: any[] = []
) => {
// can we buffer the data? // can we buffer the data?
if (this.buffer != null) { if (this.buffer != null) {
const bytes = Buffer.byteLength(data); const bytes = Buffer.byteLength(data);
@ -638,9 +605,6 @@ class MessageStream extends Stream {
if (bytes + this.bufferIndex < this.buffer.length) { if (bytes + this.bufferIndex < this.buffer.length) {
this.buffer.write(data, this.bufferIndex); this.buffer.write(data, this.bufferIndex);
this.bufferIndex += bytes; this.bufferIndex += bytes;
if (callback) {
callback.apply(null, args);
}
} }
// we can't buffer the data, so ship it out! // we can't buffer the data, so ship it out!
else if (bytes > this.buffer.length) { else if (bytes > this.buffer.length) {
@ -673,24 +637,15 @@ class MessageStream extends Stream {
); );
this.buffer.write(data, 0); this.buffer.write(data, 0);
this.bufferIndex = bytes; this.bufferIndex = bytes;
// we could get paused after emitting data... } else {
// we can't empty out the buffer, so let's wait till we resume before adding to it
if (typeof callback === 'function') { this.once('resume', () => output(data));
if (this.paused) {
this.once('resume', () => callback.apply(null, args));
} else {
callback.apply(null, args);
}
}
} // we can't empty out the buffer, so let's wait till we resume before adding to it
else {
this.once('resume', () => output(data, callback, args));
} }
} }
} }
}; };
const close = (err?: any): void => { const close = (err?: any) => {
if (err) { if (err) {
this.emit('error', err); this.emit('error', err);
} else { } else {
@ -718,7 +673,7 @@ class MessageStream extends Stream {
* pause the stream * pause the stream
* @returns {void} * @returns {void}
*/ */
pause(): void { pause() {
this.paused = true; this.paused = true;
this.emit('pause'); this.emit('pause');
} }
@ -727,7 +682,7 @@ class MessageStream extends Stream {
* resume the stream * resume the stream
* @returns {void} * @returns {void}
*/ */
resume(): void { resume() {
this.paused = false; this.paused = false;
this.emit('resume'); this.emit('resume');
} }
@ -736,7 +691,7 @@ class MessageStream extends Stream {
* destroy the stream * destroy the stream
* @returns {void} * @returns {void}
*/ */
destroy(): void { destroy() {
this.emit( this.emit(
'destroy', 'destroy',
this.bufferIndex > 0 ? { message: 'message stream destroyed' } : null this.bufferIndex > 0 ? { message: 'message stream destroyed' } : null
@ -747,7 +702,7 @@ class MessageStream extends Stream {
* destroy the stream at first opportunity * destroy the stream at first opportunity
* @returns {void} * @returns {void}
*/ */
destroySoon(): void { destroySoon() {
this.emit('destroy'); this.emit('destroy');
} }
} }

View File

@ -12,8 +12,8 @@ export class SMTPResponse {
) { ) {
const watch = (data: Parameters<SMTPResponse['watch']>[0]) => const watch = (data: Parameters<SMTPResponse['watch']>[0]) =>
this.watch(data); this.watch(data);
const end = () => this.end(); const end = (err: Error) => this.end(err);
const close = () => this.close(); const close = (err: Error) => this.close(err);
const error = (data: Parameters<SMTPResponse['error']>[0]) => const error = (data: Parameters<SMTPResponse['error']>[0]) =>
this.error(data); this.error(data);
const timedout = (data: Parameters<SMTPResponse['timedout']>[0]) => const timedout = (data: Parameters<SMTPResponse['timedout']>[0]) =>
@ -43,12 +43,13 @@ export class SMTPResponse {
// parse buffer for response codes // parse buffer for response codes
const line = this.buffer.replace('\r', ''); const line = this.buffer.replace('\r', '');
if ( if (
!line !(
.trim() line
.split(/\n/) .trim()
.pop() .split(/\n/)
?.match(/^(\d{3})\s/) ?? .pop()
false ?.match(/^(\d{3})\s/) ?? false
)
) { ) {
return; return;
} }
@ -75,13 +76,6 @@ export class SMTPResponse {
); );
} }
protected watch(data: string | Buffer) {
if (data !== null) {
this.buffer += data.toString();
this.notify();
}
}
protected timedout(err: Error) { protected timedout(err: Error) {
this.stream.end(); this.stream.end();
this.stream.emit( this.stream.emit(
@ -94,17 +88,32 @@ export class SMTPResponse {
); );
} }
protected close() { protected watch(data: string | Buffer) {
if (data !== null) {
this.buffer += data.toString();
this.notify();
}
}
protected close(err: Error) {
this.stream.emit( this.stream.emit(
'response', 'response',
makeSMTPError('connection has closed', SMTPErrorStates.CONNECTIONCLOSED) makeSMTPError(
'connection has closed',
SMTPErrorStates.CONNECTIONCLOSED,
err
)
); );
} }
protected end() { protected end(err: Error) {
this.stream.emit( this.stream.emit(
'response', 'response',
makeSMTPError('connection has ended', SMTPErrorStates.CONNECTIONENDED) makeSMTPError(
'connection has ended',
SMTPErrorStates.CONNECTIONENDED,
err
)
); );
} }
} }

View File

@ -8,10 +8,6 @@ import { SMTPResponse } from './response';
import { makeSMTPError, SMTPErrorStates } from './error'; import { makeSMTPError, SMTPErrorStates } from './error';
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
/**
* @readonly
* @enum
*/
export enum AUTH_METHODS { export enum AUTH_METHODS {
PLAIN = 'PLAIN', PLAIN = 'PLAIN',
CRAM_MD5 = 'CRAM-MD5', CRAM_MD5 = 'CRAM-MD5',
@ -19,10 +15,6 @@ export enum AUTH_METHODS {
XOAUTH2 = 'XOAUTH2', XOAUTH2 = 'XOAUTH2',
} }
/**
* @readonly
* @enum
*/
export enum SMTPState { export enum SMTPState {
NOTCONNECTED = 0, NOTCONNECTED = 0,
CONNECTING = 1, CONNECTING = 1,
@ -30,39 +22,11 @@ export enum SMTPState {
} }
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */
/** export const DEFAULT_TIMEOUT = 5000 as const;
* @readonly const SMTP_PORT = 25 as const;
* @type {5000} const SMTP_SSL_PORT = 465 as const;
*/ const SMTP_TLS_PORT = 587 as const;
export const DEFAULT_TIMEOUT: 5000 = 5000; const CRLF = '\r\n' as const;
/**
* @readonly
* @type {25}
*/
const SMTP_PORT: 25 = 25;
/**
* @readonly
* @type {465}
*/
const SMTP_SSL_PORT: 465 = 465;
/**
* @readonly
* @type {587}
*/
const SMTP_TLS_PORT: 587 = 587;
/**
* @readonly
* @type {'\r\n'}
*/
const CRLF: '\r\n' = '\r\n';
/**
* @type {0 | 1}
*/
let DEBUG: 0 | 1 = 0; let DEBUG: 0 | 1 = 0;
/** /**
@ -118,34 +82,28 @@ export interface ConnectOptions {
} }
export class SMTP extends EventEmitter { export class SMTP extends EventEmitter {
private _state: 0 | 1 | 2 = SMTPState.NOTCONNECTED; private _state: SMTPState = SMTPState.NOTCONNECTED;
private _isAuthorized = false; private _isAuthorized = false;
private _isSecure = false; private _isSecure = false;
private _user?: string = ''; private _user?: string = '';
private _password?: string = ''; private _password?: string = '';
private _timeout: number = DEFAULT_TIMEOUT; public timeout: number = DEFAULT_TIMEOUT;
public set debug(level: 0 | 1) { public set debug(level: 0 | 1) {
DEBUG = level; DEBUG = level;
} }
public get state() { public state() {
return this._state; return this._state;
} }
public get timeout() { public user: () => string;
return this._timeout; public password: () => string;
}
public get user() { /**
return this._user; * @returns {boolean} whether or not the instance is authorized
} */
public authorized() {
public get password() {
return this._password;
}
public get isAuthorized() {
return this._isAuthorized; return this._isAuthorized;
} }
@ -177,9 +135,6 @@ export class SMTP extends EventEmitter {
}: Partial<SMTPOptions> = {}) { }: Partial<SMTPOptions> = {}) {
super(); super();
this._user = user;
this._password = password;
this.authentication = Array.isArray(authentication) this.authentication = Array.isArray(authentication)
? authentication ? authentication
: [ : [
@ -190,7 +145,7 @@ export class SMTP extends EventEmitter {
]; ];
if (typeof timeout === 'number') { if (typeof timeout === 'number') {
this._timeout = timeout; this.timeout = timeout;
} }
if (typeof domain === 'string') { if (typeof domain === 'string') {
@ -230,6 +185,10 @@ export class SMTP extends EventEmitter {
} }
this._isAuthorized = user && password ? false : true; this._isAuthorized = user && password ? false : true;
// keep these strings hidden when quicky debugging/logging
this.user = () => user as string;
this.password = () => password as string;
} }
/** /**
@ -351,7 +310,7 @@ export class SMTP extends EventEmitter {
this.sock.connect(this.port, this.host, connectedErrBack); this.sock.connect(this.port, this.host, connectedErrBack);
} }
this.monitor = new SMTPResponse(this.sock, this._timeout, () => this.monitor = new SMTPResponse(this.sock, this.timeout, () =>
this.close(true) this.close(true)
); );
this.sock.once('response', response); this.sock.once('response', response);
@ -483,7 +442,7 @@ export class SMTP extends EventEmitter {
this._isSecure = true; this._isSecure = true;
this.sock = secureSocket; this.sock = secureSocket;
new SMTPResponse(this.sock, this._timeout, () => this.close(true)); new SMTPResponse(this.sock, this.timeout, () => this.close(true));
caller(callback, msg.data); caller(callback, msg.data);
} }
}; };
@ -689,8 +648,8 @@ export class SMTP extends EventEmitter {
options: { method?: string; domain?: string } = {} options: { method?: string; domain?: string } = {}
): void { ): void {
const login = { const login = {
user: () => user || this.user || '', user: (user?.length ?? 0) > 0 ? () => user : this.user,
password: () => password || this.password || '', password: (password?.length ?? 0) > 0 ? () => password : this.password,
method: options && options.method ? options.method.toUpperCase() : '', method: options && options.method ? options.method.toUpperCase() : '',
}; };