mirror of
https://github.com/eleith/emailjs.git
synced 2024-07-07 12:40:37 +00:00
smtp: repair differences with pre-conversion code
This commit is contained in:
parent
70620710bc
commit
1727412b98
@ -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 {
|
||||||
|
if (this.timer != null) {
|
||||||
clearTimeout(this.timer);
|
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}>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
157
smtp/message.ts
157
smtp/message.ts
@ -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,36 +97,34 @@ 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;
|
|
||||||
text: any;
|
|
||||||
|
|
||||||
constructor(headers: Partial<MessageHeaders>) {
|
|
||||||
this.header = {
|
|
||||||
'message-id': `<${new Date().getTime()}.${counter++}.${
|
'message-id': `<${new Date().getTime()}.${counter++}.${
|
||||||
process.pid
|
process.pid
|
||||||
}@${hostname()}>`,
|
}@${hostname()}>`,
|
||||||
date: getRFC2822Date(),
|
date: getRFC2822Date(),
|
||||||
};
|
};
|
||||||
|
content = 'text/plain; charset=utf-8';
|
||||||
|
text: any;
|
||||||
|
|
||||||
this.content = 'text/plain; charset=utf-8';
|
constructor(headers: Partial<MessageHeaders>) {
|
||||||
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 {
|
} else if (attachment != null) {
|
||||||
this.attach(attachment);
|
this.attach(attachment);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (header === 'subject') {
|
} else if (header === 'subject') {
|
||||||
this.header.subject = mimeWordEncode(headers.subject);
|
this.header.subject = mimeWordEncode(headers.subject);
|
||||||
} else if (/^(cc|bcc|to|from)/i.test(header)) {
|
} else if (/^(cc|bcc|to|from)/i.test(header)) {
|
||||||
@ -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,9 +309,11 @@ 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) {
|
||||||
|
for (const header in attachment.headers) {
|
||||||
headers[header.toLowerCase()] = attachment.headers[header];
|
headers[header.toLowerCase()] = attachment.headers[header];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const header in headers) {
|
for (const header in headers) {
|
||||||
data = data.concat([
|
data = data.concat([
|
||||||
@ -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...
|
|
||||||
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
if (this.paused) {
|
|
||||||
this.once('resume', () => callback.apply(null, args));
|
|
||||||
} else {
|
} else {
|
||||||
callback.apply(null, args);
|
// we can't empty out the buffer, so let's wait till we resume before adding to it
|
||||||
}
|
this.once('resume', () => output(data));
|
||||||
}
|
|
||||||
} // 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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
!(
|
||||||
|
line
|
||||||
.trim()
|
.trim()
|
||||||
.split(/\n/)
|
.split(/\n/)
|
||||||
.pop()
|
.pop()
|
||||||
?.match(/^(\d{3})\s/) ??
|
?.match(/^(\d{3})\s/) ?? false
|
||||||
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
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
87
smtp/smtp.ts
87
smtp/smtp.ts
@ -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() : '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user