mirror of https://github.com/eleith/emailjs.git
Compare commits
21 Commits
94d41b2dec
...
c7a4de9557
Author | SHA1 | Date |
---|---|---|
Zack Schuster | c7a4de9557 | |
Zack Schuster | 64734da2f8 | |
Zack Schuster | 3ea0c716e1 | |
Zack Schuster | 39a4b765a9 | |
Zack Schuster | 9427305919 | |
Zack Schuster | 69806921d1 | |
Zack Schuster | f9db729f6e | |
Zack Schuster | 207de70fa9 | |
Zack Schuster | 8608b929db | |
Zack Schuster | fabbca8de5 | |
Zack Schuster | f0728717b9 | |
Zack Schuster | 8cf66b9adf | |
Zack Schuster | 8eb674dc83 | |
Zack Schuster | 69d78cf6fe | |
Zack Schuster | d4b15d1e74 | |
Zack Schuster | 1073f165ce | |
Zack Schuster | f481adbe80 | |
Zack Schuster | ad2e3231a3 | |
Zack Schuster | 73c15c6f42 | |
Zack Schuster | 24c17bffa6 | |
Zack Schuster | e5fd4ed8af |
|
@ -17,6 +17,12 @@
|
|||
"error",
|
||||
"unix"
|
||||
],
|
||||
"valid-jsdoc": "error"
|
||||
"valid-jsdoc": [
|
||||
"error",
|
||||
{
|
||||
"requireParamDescription": false,
|
||||
"requireReturnDescription": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,4 @@ export default {
|
|||
},
|
||||
files: ['test/*.ts'],
|
||||
nodeArguments: ['--loader=ts-node/esm'],
|
||||
// makes tests far slower
|
||||
workerThreads: false,
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "5.22.0",
|
||||
"@typescript-eslint/parser": "5.22.0",
|
||||
"ava": "4.2.0",
|
||||
"eslint": "8.14.0",
|
||||
"eslint": "8.15.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"mailparser": "3.5.0",
|
||||
|
@ -62,6 +62,7 @@
|
|||
"scripts": {
|
||||
"build": "rollup -c rollup.config.ts",
|
||||
"lint": "eslint *.ts \"+(smtp|test)/*.ts\"",
|
||||
"prepublishOnly": "eslint --fix email.js",
|
||||
"pretest": "yarn build",
|
||||
"test": "ava"
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { builtinModules } from 'module';
|
||||
import module from 'module';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
|
||||
export default {
|
||||
|
@ -7,7 +7,6 @@ export default {
|
|||
file: 'email.js',
|
||||
format: 'es',
|
||||
sourcemap: true,
|
||||
banner: '/* eslint-disable no-undef */',
|
||||
footer: `
|
||||
/**
|
||||
* @typedef {{ [index: string]: any }} AddressObject
|
||||
|
@ -27,7 +26,7 @@ export default {
|
|||
.trim()
|
||||
.replace(/\t/g, ''),
|
||||
},
|
||||
external: builtinModules,
|
||||
external: module.builtinModules,
|
||||
plugins: [
|
||||
typescript({ removeComments: false, include: ['email.ts', 'smtp/*'] }),
|
||||
],
|
||||
|
|
|
@ -9,7 +9,7 @@ export interface AddressObject {
|
|||
group?: AddressObject[];
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Operator tokens and which tokens are expected to end the sequence
|
||||
*/
|
||||
const OPERATORS = new Map([
|
||||
|
@ -32,14 +32,14 @@ const OPERATORS = new Map([
|
|||
* Tokenizes the original input string
|
||||
*
|
||||
* @param {string | string[] | undefined} address string(s) to tokenize
|
||||
* @return {AddressToken[]} An array of operator|text tokens
|
||||
* @return {AddressToken[]} An array of operator & text tokens
|
||||
*/
|
||||
function tokenizeAddress(address: string | string[] = '') {
|
||||
const tokens: AddressToken[] = [];
|
||||
let token: AddressToken | undefined = undefined;
|
||||
let operator: string | undefined = undefined;
|
||||
|
||||
for (const character of address.toString()) {
|
||||
for (const character of address.toString().split('')) {
|
||||
if ((operator?.length ?? 0) > 0 && character === operator) {
|
||||
tokens.push({ type: 'operator', value: character });
|
||||
token = undefined;
|
||||
|
@ -69,8 +69,8 @@ function tokenizeAddress(address: string | string[] = '') {
|
|||
/**
|
||||
* Converts tokens for a single address into an address object
|
||||
*
|
||||
* @param {AddressToken[]} tokens Tokens object
|
||||
* @return {AddressObject[]} addresses object array
|
||||
* @param {AddressToken[]} tokens
|
||||
* @return {AddressObject[]}
|
||||
*/
|
||||
function convertAddressTokens(tokens: AddressToken[]) {
|
||||
const addressObjects: AddressObject[] = [];
|
||||
|
@ -208,8 +208,8 @@ function convertAddressTokens(tokens: AddressToken[]) {
|
|||
*
|
||||
* [{name: "Name", address: "address@domain"}]
|
||||
*
|
||||
* @param {string | string[] | undefined} address Address field
|
||||
* @return {AddressObject[]} An array of address objects
|
||||
* @param {string | string[] | undefined} address
|
||||
* @return {AddressObject[]}
|
||||
*/
|
||||
export function addressparser(address?: string | string[]) {
|
||||
const addresses: AddressObject[] = [];
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { clearTimeout, setTimeout } from 'timers';
|
||||
|
||||
import { addressparser } from './address.js';
|
||||
import type { MessageAttachment, MessageHeaders } from './message.js';
|
||||
import { Message } from './message.js';
|
||||
|
@ -15,13 +17,9 @@ export type MessageCallback<T = Message | MessageHeaders> = <
|
|||
export interface MessageStack {
|
||||
callback: MessageCallback;
|
||||
message: Message;
|
||||
attachment: MessageAttachment;
|
||||
text: string;
|
||||
returnPath: string;
|
||||
returnPath?: string;
|
||||
from: string;
|
||||
to: ReturnType<typeof addressparser>;
|
||||
cc: string[];
|
||||
bcc: string[];
|
||||
}
|
||||
|
||||
export class SMTPClient {
|
||||
|
@ -46,7 +44,7 @@ export class SMTPClient {
|
|||
/**
|
||||
* @public
|
||||
* @template {Message | MessageHeaders} T
|
||||
* @param {T} msg the message to send
|
||||
* @param {T} msg
|
||||
* @param {MessageCallback<T>} callback receiver for the error (if any) as well as the passed-in message / headers
|
||||
* @returns {void}
|
||||
*/
|
||||
|
@ -73,12 +71,12 @@ export class SMTPClient {
|
|||
/**
|
||||
* @public
|
||||
* @template {Message | MessageHeaders} T
|
||||
* @param {T} msg the message to send
|
||||
* @returns {Promise<T>} a promise that resolves to the passed-in message / headers
|
||||
* @param {T} msg
|
||||
* @returns {Promise<Message>} a promise that resolves to the message / headers
|
||||
*/
|
||||
public sendAsync<T extends Message | MessageHeaders>(msg: T) {
|
||||
return new Promise<Message>((resolve, reject) => {
|
||||
this.send(msg, (err, message) => {
|
||||
this.send(msg, (err, /** @type {any} */ message) => {
|
||||
if (err != null) {
|
||||
reject(err);
|
||||
} else {
|
||||
|
@ -103,18 +101,21 @@ export class SMTPClient {
|
|||
/* ø */
|
||||
}
|
||||
) {
|
||||
const [{ address: from }] = addressparser(message.header.from);
|
||||
const stack = {
|
||||
message,
|
||||
to: [] as ReturnType<typeof addressparser>,
|
||||
from,
|
||||
callback: callback.bind(this),
|
||||
} as MessageStack;
|
||||
|
||||
const [{ address: from = '' }] = addressparser(message.header.from);
|
||||
const {
|
||||
header: { to, cc, bcc, 'return-path': returnPath },
|
||||
} = message;
|
||||
|
||||
const stack: MessageStack = {
|
||||
message,
|
||||
to:
|
||||
(typeof to === 'string' || Array.isArray(to)) && to.length > 0
|
||||
? addressparser(to)
|
||||
: [],
|
||||
from,
|
||||
callback: callback.bind(this),
|
||||
};
|
||||
|
||||
if ((typeof to === 'string' || Array.isArray(to)) && to.length > 0) {
|
||||
stack.to = addressparser(to);
|
||||
}
|
||||
|
@ -139,7 +140,7 @@ export class SMTPClient {
|
|||
const parsedReturnPath = addressparser(returnPath);
|
||||
if (parsedReturnPath.length > 0) {
|
||||
const [{ address: returnPathAddress }] = parsedReturnPath;
|
||||
stack.returnPath = returnPathAddress as string;
|
||||
stack.returnPath = returnPathAddress;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,12 +176,20 @@ export class SMTPClient {
|
|||
|
||||
/**
|
||||
* @protected
|
||||
* @param {MessageStack} stack stack
|
||||
* @param {MessageStack} stack
|
||||
* @returns {void}
|
||||
*/
|
||||
protected _connect(stack: MessageStack) {
|
||||
/**
|
||||
* @param {Error | null} err
|
||||
* @returns {void}
|
||||
*/
|
||||
const connect = (err: Error | null) => {
|
||||
if (!err) {
|
||||
/**
|
||||
* @param {Error | null} err
|
||||
* @returns {void}
|
||||
*/
|
||||
const begin = (err: Error | null) => {
|
||||
if (!err) {
|
||||
this.ready = true;
|
||||
|
@ -214,8 +223,8 @@ export class SMTPClient {
|
|||
|
||||
/**
|
||||
* @protected
|
||||
* @param {MessageAttachment | MessageAttachment[]} attachment attachment
|
||||
* @returns {boolean} whether the attachment contains inlined html
|
||||
* @param {MessageAttachment | MessageAttachment[]} attachment
|
||||
* @returns {boolean}
|
||||
*/
|
||||
protected _containsInlinedHtml(
|
||||
attachment?: MessageAttachment | MessageAttachment[]
|
||||
|
@ -231,8 +240,8 @@ export class SMTPClient {
|
|||
|
||||
/**
|
||||
* @protected
|
||||
* @param {MessageAttachment} attachment attachment
|
||||
* @returns {boolean} whether the attachment is inlined html
|
||||
* @param {MessageAttachment} attachment
|
||||
* @returns {boolean}
|
||||
*/
|
||||
protected _isAttachmentInlinedHtml(attachment?: MessageAttachment) {
|
||||
return (
|
||||
|
@ -244,9 +253,9 @@ export class SMTPClient {
|
|||
|
||||
/**
|
||||
* @protected
|
||||
* @param {MessageStack} stack stack
|
||||
* @param {function(MessageStack): void} next next
|
||||
* @returns {function(Error): void} callback
|
||||
* @param {MessageStack} stack
|
||||
* @param {function(MessageStack): void} next
|
||||
* @returns {function(Error): void}
|
||||
*/
|
||||
protected _sendsmtp(stack: MessageStack, next: (msg: MessageStack) => void) {
|
||||
return (err: Error | null) => {
|
||||
|
@ -262,7 +271,7 @@ export class SMTPClient {
|
|||
|
||||
/**
|
||||
* @protected
|
||||
* @param {MessageStack} stack stack
|
||||
* @param {MessageStack} stack
|
||||
* @returns {void}
|
||||
*/
|
||||
protected _sendmail(stack: MessageStack) {
|
||||
|
@ -273,7 +282,7 @@ export class SMTPClient {
|
|||
|
||||
/**
|
||||
* @protected
|
||||
* @param {MessageStack} stack stack
|
||||
* @param {MessageStack} stack
|
||||
* @returns {void}
|
||||
*/
|
||||
protected _sendrcpt(stack: MessageStack) {
|
||||
|
@ -290,7 +299,7 @@ export class SMTPClient {
|
|||
|
||||
/**
|
||||
* @protected
|
||||
* @param {MessageStack} stack stack
|
||||
* @param {MessageStack} stack
|
||||
* @returns {void}
|
||||
*/
|
||||
protected _senddata(stack: MessageStack) {
|
||||
|
@ -299,7 +308,7 @@ export class SMTPClient {
|
|||
|
||||
/**
|
||||
* @protected
|
||||
* @param {MessageStack} stack stack
|
||||
* @param {MessageStack} stack
|
||||
* @returns {void}
|
||||
*/
|
||||
protected _sendmessage(stack: MessageStack) {
|
||||
|
@ -322,8 +331,8 @@ export class SMTPClient {
|
|||
|
||||
/**
|
||||
* @protected
|
||||
* @param {Error | null} err err
|
||||
* @param {MessageStack} stack stack
|
||||
* @param {Error | null} err
|
||||
* @param {MessageStack} stack
|
||||
* @returns {void}
|
||||
*/
|
||||
protected _senddone(err: Error | null, stack: MessageStack) {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { Buffer } from 'buffer';
|
||||
import console from 'console';
|
||||
import { createHmac } from 'crypto';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Socket } from 'net';
|
||||
import { hostname } from 'os';
|
||||
import { setTimeout } from 'timers';
|
||||
import { connect, createSecureContext, TLSSocket } from 'tls';
|
||||
import type { ConnectionOptions } from 'tls';
|
||||
|
||||
|
@ -101,6 +104,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
AUTH_METHODS.XOAUTH2,
|
||||
];
|
||||
|
||||
/** @type {SMTPState} */
|
||||
protected _state: 0 | 1 | 2 = SMTPState.NOTCONNECTED;
|
||||
protected _secure = false;
|
||||
protected loggedin = false;
|
||||
|
@ -123,7 +127,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
*
|
||||
* NOTE: `host` is trimmed before being used to establish a connection; however, the original untrimmed value will still be visible in configuration.
|
||||
*
|
||||
* @param {Partial<SMTPConnectionOptions>} options options
|
||||
* @param {Partial<SMTPConnectionOptions>} options
|
||||
*/
|
||||
constructor({
|
||||
timeout,
|
||||
|
@ -189,7 +193,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {0 | 1} level -
|
||||
* @param {0 | 1} level
|
||||
* @returns {void}
|
||||
*/
|
||||
public debug(level: 0 | 1) {
|
||||
|
@ -198,7 +202,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @returns {0 | 1 | 2} the current state
|
||||
* @returns {SMTPState}
|
||||
*/
|
||||
public state() {
|
||||
return this._state;
|
||||
|
@ -206,7 +210,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @returns {boolean} whether or not the instance is authorized
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public authorized() {
|
||||
return this.loggedin;
|
||||
|
@ -218,10 +222,10 @@ export class SMTPConnection extends EventEmitter {
|
|||
* NOTE: `host` is trimmed before being used to establish a connection; however, the original untrimmed value will still be visible in configuration.
|
||||
*
|
||||
* @public
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {number} [port] the port to use for the connection
|
||||
* @param {string} [host] the hostname to use for the connection
|
||||
* @param {ConnectOptions} [options={}] the options
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @param {number} [port]
|
||||
* @param {string} [host]
|
||||
* @param {ConnectOptions} [options={}]
|
||||
* @returns {void}
|
||||
*/
|
||||
public connect(
|
||||
|
@ -265,7 +269,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {Error} err err
|
||||
* @param {Error} err
|
||||
* @returns {void}
|
||||
*/
|
||||
const connectedErrBack = (err?: Error) => {
|
||||
|
@ -339,8 +343,8 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {string} str the string to send
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} str
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
public send(str: string, callback: SMTPCommandCallback) {
|
||||
|
@ -372,9 +376,9 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {string} cmd command to issue
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {(number[] | number)} [codes=[250]] array codes
|
||||
* @param {string} cmd
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @param {(number[] | number)} [codes=[250]] SMTP response code(s)
|
||||
* @returns {void}
|
||||
*/
|
||||
public command(
|
||||
|
@ -444,8 +448,8 @@ export class SMTPConnection extends EventEmitter {
|
|||
* As this command was deprecated by rfc2821, it should only be used for compatibility with non-compliant servers.
|
||||
* @see https://tools.ietf.org/html/rfc2821#appendix-F.3
|
||||
*
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} [domain] the domain to associate with the 'helo' request
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @param {string} [domain]
|
||||
* @returns {void}
|
||||
*/
|
||||
public helo(callback: SMTPCommandCallback, domain?: string) {
|
||||
|
@ -461,7 +465,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
public starttls(callback: SMTPCommandCallback) {
|
||||
|
@ -499,7 +503,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {string} data the string to parse for features
|
||||
* @param {string} data
|
||||
* @returns {void}
|
||||
*/
|
||||
public parse_smtp_features(data: string) {
|
||||
|
@ -529,8 +533,8 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} [domain] the domain to associate with the 'ehlo' request
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @param {string} [domain]
|
||||
* @returns {void}
|
||||
*/
|
||||
public ehlo(callback: SMTPCommandCallback, domain?: string) {
|
||||
|
@ -553,7 +557,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
/**
|
||||
* @public
|
||||
* @param {string} opt the features keyname to check
|
||||
* @returns {boolean} whether the extension exists
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public has_extn(opt: string) {
|
||||
return (this.features ?? {})[opt.toLowerCase()] === undefined;
|
||||
|
@ -562,8 +566,8 @@ export class SMTPConnection extends EventEmitter {
|
|||
/**
|
||||
* @public
|
||||
* @description SMTP 'help' command, returns text from the server
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} domain the domain to associate with the 'help' request
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @param {string} domain
|
||||
* @returns {void}
|
||||
*/
|
||||
public help(callback: SMTPCommandCallback, domain: string) {
|
||||
|
@ -572,7 +576,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
public rset(callback: SMTPCommandCallback) {
|
||||
|
@ -581,7 +585,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
public noop(callback: SMTPCommandCallback) {
|
||||
|
@ -590,7 +594,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @param {string} from the sender
|
||||
* @returns {void}
|
||||
*/
|
||||
|
@ -600,7 +604,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @param {string} to the receiver
|
||||
* @returns {void}
|
||||
*/
|
||||
|
@ -610,7 +614,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
public data(callback: SMTPCommandCallback) {
|
||||
|
@ -619,7 +623,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
public data_end(callback: SMTPCommandCallback) {
|
||||
|
@ -639,8 +643,8 @@ export class SMTPConnection extends EventEmitter {
|
|||
/**
|
||||
* @public
|
||||
* @description SMTP 'verify' command -- checks for address validity.
|
||||
* @param {string} address the address to validate
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} address
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
public verify(address: string, callback: SMTPCommandCallback) {
|
||||
|
@ -650,8 +654,8 @@ export class SMTPConnection extends EventEmitter {
|
|||
/**
|
||||
* @public
|
||||
* @description SMTP 'expn' command -- expands a mailing list.
|
||||
* @param {string} address the mailing list to expand
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} address
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
public expn(address: string, callback: SMTPCommandCallback) {
|
||||
|
@ -665,7 +669,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
* If there has been no previous EHLO or HELO command self session, self
|
||||
* method tries ESMTP EHLO first.
|
||||
*
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @param {string} [domain] the domain to associate with the command
|
||||
* @returns {void}
|
||||
*/
|
||||
|
@ -694,12 +698,12 @@ export class SMTPConnection extends EventEmitter {
|
|||
*
|
||||
* This method will return normally if the authentication was successful.
|
||||
*
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} [user] the username to authenticate with
|
||||
* @param {string} [password] the password for the authentication
|
||||
* @param {Object} [options] login options
|
||||
* @param {string} [options.method] login method
|
||||
* @param {string} [options.domain] login domain
|
||||
* @param {SMTPCommandCallback} callback
|
||||
* @param {string} [user]
|
||||
* @param {string} [password]
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.method]
|
||||
* @param {string} [options.domain]
|
||||
* @returns {void}
|
||||
*/
|
||||
public login(
|
||||
|
@ -716,6 +720,11 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
const domain = options?.domain || this.domain;
|
||||
|
||||
/**
|
||||
* @param {Error | null} err
|
||||
* @param {unknown} data
|
||||
* @returns {void}
|
||||
*/
|
||||
const initiate = (err: Error | null | undefined, data: unknown) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
|
@ -725,7 +734,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
let method: keyof typeof AUTH_METHODS | null = null;
|
||||
|
||||
/**
|
||||
* @param {string} challenge challenge
|
||||
* @param {string} challenge
|
||||
* @returns {string} base64 cram hash
|
||||
*/
|
||||
const encodeCramMd5 = (challenge: string) => {
|
||||
|
@ -776,8 +785,8 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* handle bad responses from command differently
|
||||
* @param {Error} err err
|
||||
* @param {unknown} data data
|
||||
* @param {Error} err
|
||||
* @param {unknown} data
|
||||
* @returns {void}
|
||||
*/
|
||||
const failed = (err: Error, data: unknown) => {
|
||||
|
@ -793,6 +802,15 @@ export class SMTPConnection extends EventEmitter {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Error | SMTPError | null} err
|
||||
* @param {(
|
||||
* string |
|
||||
* { code: (string | number), data: string, message: string } |
|
||||
* null
|
||||
* )} [data]
|
||||
* @returns {void}
|
||||
*/
|
||||
const response: SMTPCommandCallback = (err, data) => {
|
||||
if (err) {
|
||||
failed(err, data);
|
||||
|
@ -802,6 +820,16 @@ export class SMTPConnection extends EventEmitter {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Error | SMTPError | null} err
|
||||
* @param {(
|
||||
* string |
|
||||
* { code: (string | number), data: string, message: string } |
|
||||
* null
|
||||
* )} data
|
||||
* @param {string} msg
|
||||
* @returns {void}
|
||||
*/
|
||||
const attempt: SMTPCommandCallback = (err, data, msg) => {
|
||||
if (err) {
|
||||
failed(err, data);
|
||||
|
@ -818,6 +846,15 @@ export class SMTPConnection extends EventEmitter {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Error | SMTPError | null} err
|
||||
* @param {(
|
||||
* string |
|
||||
* { code: (string | number), data: string, message: string } |
|
||||
* null
|
||||
* )} [data]
|
||||
* @returns {void}
|
||||
*/
|
||||
const attemptUser: SMTPCommandCallback = (err, data) => {
|
||||
if (err) {
|
||||
failed(err, data);
|
||||
|
@ -871,7 +908,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {boolean} [force=false] whether or not to force destroy the connection
|
||||
* @param {boolean} [force=false]
|
||||
* @returns {void}
|
||||
*/
|
||||
public close(force = false) {
|
||||
|
@ -899,7 +936,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {SMTPCommandCallback} [callback] function to call after response
|
||||
* @param {SMTPCommandCallback} [callback]
|
||||
* @returns {void}
|
||||
*/
|
||||
public quit(callback?: SMTPCommandCallback) {
|
||||
|
|
|
@ -44,7 +44,7 @@ const rfc2822re =
|
|||
|
||||
/**
|
||||
* @param {string} date a string to check for conformance to the [rfc2822](https://tools.ietf.org/html/rfc2822#section-3.3) standard
|
||||
* @returns {boolean} the result of the conformance check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isRFC2822Date(date: string) {
|
||||
return rfc2822re.test(date);
|
||||
|
|
|
@ -22,7 +22,7 @@ export class SMTPError extends Error {
|
|||
|
||||
/**
|
||||
* @protected
|
||||
* @param {string} message error message
|
||||
* @param {string} message
|
||||
*/
|
||||
protected constructor(message: string) {
|
||||
super(message);
|
||||
|
@ -30,11 +30,11 @@ export class SMTPError extends Error {
|
|||
|
||||
/**
|
||||
*
|
||||
* @param {string} message error message
|
||||
* @param {string} message
|
||||
* @param {number} code smtp error state
|
||||
* @param {Error | null} [error] previous error
|
||||
* @param {unknown} [smtp] arbitrary data
|
||||
* @returns {SMTPError} error
|
||||
* @returns {SMTPError}
|
||||
*/
|
||||
public static create(
|
||||
message: string,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Buffer } from 'buffer';
|
||||
import type { PathLike } from 'fs';
|
||||
import {
|
||||
existsSync,
|
||||
|
@ -7,6 +8,7 @@ import {
|
|||
read as readFile,
|
||||
} from 'fs';
|
||||
import { hostname } from 'os';
|
||||
import { nextTick, pid } from 'process';
|
||||
import { Stream } from 'stream';
|
||||
import type { Readable } from 'stream';
|
||||
|
||||
|
@ -18,18 +20,21 @@ const CRLF = '\r\n' as const;
|
|||
|
||||
/**
|
||||
* MIME standard wants 76 char chunks when sending out.
|
||||
* @type {76}
|
||||
*/
|
||||
export const MIMECHUNK = 76 as const;
|
||||
|
||||
/**
|
||||
* meets both base64 and mime divisibility
|
||||
* @type {456}
|
||||
*/
|
||||
export const MIME64CHUNK = (MIMECHUNK * 6) as 456;
|
||||
export const MIME64CHUNK = 456 as const; // MIMECHUNK * 6
|
||||
|
||||
/**
|
||||
* size of the message stream buffer
|
||||
* @type {12768}
|
||||
*/
|
||||
export const BUFFERSIZE = (MIMECHUNK * 24 * 7) as 12768;
|
||||
export const BUFFERSIZE = 12768 as const; // MIMECHUNK * 24 * 7;
|
||||
|
||||
export interface MessageAttachmentHeaders {
|
||||
[index: string]: string | undefined;
|
||||
|
@ -117,9 +122,7 @@ function convertDashDelimitedTextToSnakeCase(text: string) {
|
|||
export class Message {
|
||||
public readonly attachments: MessageAttachment[] = [];
|
||||
public readonly header: Partial<MessageHeaders> = {
|
||||
'message-id': `<${new Date().getTime()}.${counter++}.${
|
||||
process.pid
|
||||
}@${hostname()}>`,
|
||||
'message-id': `<${new Date().getTime()}.${counter++}.${pid}@${hostname()}>`,
|
||||
date: getRFC2822Date(),
|
||||
};
|
||||
public readonly content: string = 'text/plain; charset=utf-8';
|
||||
|
@ -135,7 +138,7 @@ export class Message {
|
|||
* - You can also add whatever other headers you want.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc2822
|
||||
* @param {Partial<MessageHeaders>} headers Message headers
|
||||
* @param {Partial<MessageHeaders>} headers
|
||||
*/
|
||||
constructor(headers: Partial<MessageHeaders> = {}) {
|
||||
for (const header in headers) {
|
||||
|
@ -175,7 +178,7 @@ export class Message {
|
|||
* Can be called multiple times, each adding a new attachment.
|
||||
*
|
||||
* @public
|
||||
* @param {MessageAttachment} options attachment options
|
||||
* @param {MessageAttachment} options
|
||||
* @returns {Message} the current instance for chaining
|
||||
*/
|
||||
public attach(options: MessageAttachment) {
|
||||
|
@ -257,7 +260,7 @@ export class Message {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {function(Error, string): void} callback the function to call with the error and buffer
|
||||
* @param {function(Error, string): void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
public read(callback: (err: Error, buffer: string) => void) {
|
||||
|
@ -268,6 +271,10 @@ export class Message {
|
|||
str.on('error', (err) => callback(err, buffer));
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public readAsync() {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
this.read((err, buffer) => {
|
||||
|
@ -282,19 +289,20 @@ export class Message {
|
|||
}
|
||||
|
||||
class MessageStream extends Stream {
|
||||
readable = true;
|
||||
paused = false;
|
||||
/** @type {Buffer | null} */
|
||||
buffer: Buffer | null = Buffer.alloc(MIMECHUNK * 24 * 7);
|
||||
bufferIndex = 0;
|
||||
paused = false;
|
||||
readable = true;
|
||||
|
||||
/**
|
||||
* @param {Message} message the message to stream
|
||||
* @param {Message} message
|
||||
*/
|
||||
constructor(private message: Message) {
|
||||
super();
|
||||
|
||||
/**
|
||||
* @param {string} data the data to output
|
||||
* @param {string} data
|
||||
* @returns {void}
|
||||
*/
|
||||
const output = (data: string) => {
|
||||
|
@ -346,7 +354,7 @@ class MessageStream extends Stream {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {MessageAttachment} attachment the attachment whose headers you would like to output
|
||||
* @param {MessageAttachment} attachment
|
||||
* @returns {void}
|
||||
*/
|
||||
const outputAttachmentHeaders = (attachment: MessageAttachment) => {
|
||||
|
@ -384,8 +392,8 @@ class MessageStream extends Stream {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {string} data the data to output as base64
|
||||
* @param {function(): void} [callback] the function to call after output is finished
|
||||
* @param {string} data
|
||||
* @param {function(): void} [callback]
|
||||
* @returns {void}
|
||||
*/
|
||||
const outputBase64 = (data: string, callback?: () => void) => {
|
||||
|
@ -400,6 +408,11 @@ class MessageStream extends Stream {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {MessageAttachment} attachment
|
||||
* @param {function((NodeJS.ErrnoException | null)): void} next
|
||||
* @returns {void}
|
||||
*/
|
||||
const outputFile = (
|
||||
attachment: MessageAttachment,
|
||||
next: (err: NodeJS.ErrnoException | null) => void
|
||||
|
@ -417,7 +430,7 @@ class MessageStream extends Stream {
|
|||
: inputEncoding;
|
||||
|
||||
/**
|
||||
* @param {NodeJS.ErrnoException | null} err the error to emit
|
||||
* @param {NodeJS.ErrnoException | null} err
|
||||
* @param {number} fd the file descriptor
|
||||
* @returns {void}
|
||||
*/
|
||||
|
@ -457,8 +470,8 @@ class MessageStream extends Stream {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {MessageAttachment} attachment the metadata to use as headers
|
||||
* @param {function(): void} callback the function to call after output is finished
|
||||
* @param {MessageAttachment} attachment
|
||||
* @param {function(): void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
const outputStream = (
|
||||
|
@ -505,6 +518,11 @@ class MessageStream extends Stream {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {MessageAttachment} attachment
|
||||
* @param {function(): void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
const outputAttachment = (
|
||||
attachment: MessageAttachment,
|
||||
callback: () => void
|
||||
|
@ -548,6 +566,9 @@ class MessageStream extends Stream {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {void}
|
||||
*/
|
||||
const outputMixed = () => {
|
||||
const boundary = generateBoundary();
|
||||
output(
|
||||
|
@ -567,8 +588,8 @@ class MessageStream extends Stream {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {MessageAttachment} attachment the metadata to use as headers
|
||||
* @param {function(): void} callback the function to call after output is finished
|
||||
* @param {MessageAttachment} attachment
|
||||
* @param {function(): void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
const outputData = (
|
||||
|
@ -584,7 +605,7 @@ class MessageStream extends Stream {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {Message} message the message to output
|
||||
* @param {Message} message
|
||||
* @returns {void}
|
||||
*/
|
||||
const outputText = (message: Message) => {
|
||||
|
@ -604,8 +625,8 @@ class MessageStream extends Stream {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {MessageAttachment} message the message to output
|
||||
* @param {function(): void} callback the function to call after output is finished
|
||||
* @param {MessageAttachment} message
|
||||
* @param {function(): void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
const outputRelated = (
|
||||
|
@ -625,14 +646,18 @@ class MessageStream extends Stream {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {Message} message the message to output
|
||||
* @param {function(): void} callback the function to call after output is finished
|
||||
* @param {Message} message
|
||||
* @param {function(): void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
const outputAlternative = (
|
||||
message: Message & { alternative: MessageAttachment },
|
||||
callback: () => void
|
||||
) => {
|
||||
const outputAlternative = (message: Message, callback: () => void) => {
|
||||
const { alternative } = message;
|
||||
if (alternative == null) {
|
||||
throw new Error(
|
||||
`Message passed to outputAlternative without its "alternative" property set: ${message.header.subject}`
|
||||
);
|
||||
}
|
||||
|
||||
const boundary = generateBoundary();
|
||||
output(
|
||||
`Content-Type: multipart/alternative; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
|
||||
|
@ -648,7 +673,6 @@ class MessageStream extends Stream {
|
|||
callback();
|
||||
};
|
||||
|
||||
const { alternative } = message;
|
||||
if (alternative.related) {
|
||||
outputRelated(alternative, finish);
|
||||
} else {
|
||||
|
@ -656,6 +680,10 @@ class MessageStream extends Stream {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Error} [err]
|
||||
* @returns {void}
|
||||
*/
|
||||
const close = (err?: Error) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
|
@ -716,12 +744,12 @@ class MessageStream extends Stream {
|
|||
};
|
||||
|
||||
this.once('destroy', close);
|
||||
process.nextTick(outputHeader);
|
||||
nextTick(outputHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* pause the stream
|
||||
* @description pause the stream
|
||||
* @returns {void}
|
||||
*/
|
||||
public pause() {
|
||||
|
@ -731,7 +759,7 @@ class MessageStream extends Stream {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* resume the stream
|
||||
* @description resume the stream
|
||||
* @returns {void}
|
||||
*/
|
||||
public resume() {
|
||||
|
@ -741,7 +769,7 @@ class MessageStream extends Stream {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* destroy the stream
|
||||
* @description destroy the stream
|
||||
* @returns {void}
|
||||
*/
|
||||
public destroy() {
|
||||
|
@ -753,7 +781,7 @@ class MessageStream extends Stream {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* destroy the stream at first opportunity
|
||||
* @description destroy the stream at first opportunity
|
||||
* @returns {void}
|
||||
*/
|
||||
public destroySoon() {
|
||||
|
|
|
@ -3,9 +3,7 @@ import { TextDecoder, TextEncoder } from 'util';
|
|||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
* @see https://tools.ietf.org/html/rfc2045#section-6.7
|
||||
*/
|
||||
/** @see https://tools.ietf.org/html/rfc2045#section-6.7 */
|
||||
const RANGES = [
|
||||
[0x09], // <TAB>
|
||||
[0x0a], // <LF>
|
||||
|
@ -114,8 +112,7 @@ function splitMimeEncodedString(str: string, maxlen = 12) {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} nr number
|
||||
* @param {number} nr
|
||||
* @returns {boolean} if number is in range
|
||||
*/
|
||||
function checkRanges(nr: number) {
|
||||
|
|
127
test/message.ts
127
test/message.ts
|
@ -1,7 +1,9 @@
|
|||
import { createReadStream, readFileSync } from 'fs';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { URL } from 'url';
|
||||
|
||||
import test from 'ava';
|
||||
import type { ExecutionContext, LogFn } from 'ava';
|
||||
import { simpleParser } from 'mailparser';
|
||||
import type { AddressObject, ParsedMail } from 'mailparser';
|
||||
import { SMTPServer } from 'smtp-server';
|
||||
|
@ -25,20 +27,15 @@ const tarFixtureUrl = new URL(
|
|||
const tarFixture = readFileSync(tarFixtureUrl, 'base64');
|
||||
|
||||
/**
|
||||
* \@types/mailparser@3.0.2 breaks our code
|
||||
* \@types/mailparser\@3.0.2 breaks our code
|
||||
* @see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/50744
|
||||
*/
|
||||
type ParsedMailCompat = Omit<ParsedMail, 'to'> & { to?: AddressObject };
|
||||
|
||||
const port = 5555;
|
||||
const parseMap = new Map<string, ParsedMailCompat>();
|
||||
|
||||
const client = new SMTPClient({
|
||||
port,
|
||||
user: 'pooh',
|
||||
password: 'honey',
|
||||
ssl: true,
|
||||
});
|
||||
const parseMap = new Map<string, ParsedMailCompat>();
|
||||
const logFnMap = new Map<string, LogFn>();
|
||||
const server = new SMTPServer({
|
||||
secure: true,
|
||||
onAuth(auth, _session, callback) {
|
||||
|
@ -48,49 +45,57 @@ const server = new SMTPServer({
|
|||
return callback(new Error('invalid user / pass'));
|
||||
}
|
||||
},
|
||||
async onData(stream, _session, callback: () => void) {
|
||||
async onData(stream, _session, callback) {
|
||||
const now = performance.now();
|
||||
const mail = (await simpleParser(stream, {
|
||||
skipHtmlToText: true,
|
||||
skipTextToHtml: true,
|
||||
skipImageLinks: true,
|
||||
} as Record<string, unknown>)) as ParsedMailCompat;
|
||||
|
||||
parseMap.set(mail.subject as string, mail);
|
||||
})) as ParsedMailCompat;
|
||||
const { subject = '' } = mail;
|
||||
(logFnMap.get(subject) as LogFn)(
|
||||
`Time to parse message: ${Math.round(performance.now() - now)}ms`
|
||||
);
|
||||
parseMap.set(subject, mail);
|
||||
callback();
|
||||
},
|
||||
});
|
||||
|
||||
function send(headers: Partial<MessageHeaders>) {
|
||||
test.before((t) => server.listen(port, t.pass));
|
||||
test.after((t) => server.close(t.pass));
|
||||
|
||||
function send(t: ExecutionContext, headers: Partial<MessageHeaders>) {
|
||||
return new Promise<ParsedMailCompat>((resolve, reject) => {
|
||||
const { subject = '' } = headers;
|
||||
logFnMap.set(subject, t.log);
|
||||
const client = new SMTPClient({
|
||||
port,
|
||||
user: 'pooh',
|
||||
password: 'honey',
|
||||
ssl: true,
|
||||
});
|
||||
client.send(new Message(headers), (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(parseMap.get(headers.subject as string) as ParsedMailCompat);
|
||||
resolve(parseMap.get(subject) as ParsedMailCompat);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test.before(async (t) => {
|
||||
server.listen(port, t.pass);
|
||||
});
|
||||
test.after(async (t) => {
|
||||
server.close(t.pass);
|
||||
});
|
||||
|
||||
test('simple text message', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'zelda@gmail.com',
|
||||
to: 'gannon@gmail.com',
|
||||
cc: 'gannon@gmail.com',
|
||||
bcc: 'gannon@gmail.com',
|
||||
text: 'hello friend, i hope this message finds you well.',
|
||||
'message-id': 'this is a special id',
|
||||
'message-id': 'special id',
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.text, msg.text + '\n\n\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
t.is(mail.from?.text, msg.from);
|
||||
|
@ -100,39 +105,39 @@ test('simple text message', async (t) => {
|
|||
|
||||
test('null text message', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'zelda@gmail.com',
|
||||
to: 'gannon@gmail.com',
|
||||
text: null,
|
||||
'message-id': 'this is a special id',
|
||||
'message-id': 'special id',
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.text, '\n\n\n');
|
||||
});
|
||||
|
||||
test('empty text message', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'zelda@gmail.com',
|
||||
to: 'gannon@gmail.com',
|
||||
text: '',
|
||||
'message-id': 'this is a special id',
|
||||
'message-id': 'special id',
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.text, '\n\n\n');
|
||||
});
|
||||
|
||||
test('simple unicode text message', async (t) => {
|
||||
test('simple ✓ unicode ✓ text message', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this ✓ is a test ✓ TEXT message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'zelda✓ <zelda@gmail.com>',
|
||||
to: 'gannon✓ <gannon@gmail.com>',
|
||||
text: 'hello ✓ friend, i hope this message finds you well.',
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.text, msg.text + '\n\n\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
t.is(mail.from?.text, msg.from);
|
||||
|
@ -142,24 +147,24 @@ test('simple unicode text message', async (t) => {
|
|||
test('very large text message', async (t) => {
|
||||
// thanks to jart+loberstech for this one!
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'ninjas@gmail.com',
|
||||
to: 'pirates@gmail.com',
|
||||
text: textFixture,
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.text, msg.text.replace(/\r/g, '') + '\n\n\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
t.is(mail.from?.text, msg.from);
|
||||
t.is(mail.to?.text, msg.to);
|
||||
});
|
||||
|
||||
test('very large text data message', async (t) => {
|
||||
test('very large text + data message', async (t) => {
|
||||
const text = '<html><body><pre>' + textFixture + '</pre></body></html>';
|
||||
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT+DATA message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'lobsters@gmail.com',
|
||||
to: 'lizards@gmail.com',
|
||||
text: 'hello friend if you are seeing this, you can not view html emails. it is attached inline.',
|
||||
|
@ -169,7 +174,7 @@ test('very large text data message', async (t) => {
|
|||
},
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.html, text.replace(/\r/g, ''));
|
||||
t.is(mail.text, msg.text + '\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
|
@ -177,9 +182,9 @@ test('very large text data message', async (t) => {
|
|||
t.is(mail.to?.text, msg.to);
|
||||
});
|
||||
|
||||
test('html data message', async (t) => {
|
||||
test('text + html + data message', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT+HTML+DATA message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'obama@gmail.com',
|
||||
to: 'mitt@gmail.com',
|
||||
attachment: {
|
||||
|
@ -188,7 +193,7 @@ test('html data message', async (t) => {
|
|||
},
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.html, htmlFixture.replace(/\r/g, ''));
|
||||
t.is(mail.text, '\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
|
@ -198,7 +203,7 @@ test('html data message', async (t) => {
|
|||
|
||||
test('html file message', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT+HTML+FILE message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'thomas@gmail.com',
|
||||
to: 'nikolas@gmail.com',
|
||||
attachment: {
|
||||
|
@ -207,7 +212,7 @@ test('html file message', async (t) => {
|
|||
},
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.html, htmlFixture.replace(/\r/g, ''));
|
||||
t.is(mail.text, '\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
|
@ -215,11 +220,11 @@ test('html file message', async (t) => {
|
|||
t.is(mail.to?.text, msg.to);
|
||||
});
|
||||
|
||||
test('html with image embed message', async (t) => {
|
||||
test('html + image embed message', async (t) => {
|
||||
const htmlFixture2Url = new URL('attachments/smtp2.html', import.meta.url);
|
||||
const imageFixtureUrl = new URL('attachments/smtp.gif', import.meta.url);
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT+HTML+IMAGE message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'ninja@gmail.com',
|
||||
to: 'pirate@gmail.com',
|
||||
attachment: {
|
||||
|
@ -236,7 +241,7 @@ test('html with image embed message', async (t) => {
|
|||
},
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(
|
||||
mail.attachments[0].content.toString('base64'),
|
||||
readFileSync(imageFixtureUrl, 'base64')
|
||||
|
@ -248,9 +253,9 @@ test('html with image embed message', async (t) => {
|
|||
t.is(mail.to?.text, msg.to);
|
||||
});
|
||||
|
||||
test('html data and attachment message', async (t) => {
|
||||
test('html + data + two attachments message', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT+HTML+FILE message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'thomas@gmail.com',
|
||||
to: 'nikolas@gmail.com',
|
||||
attachment: [
|
||||
|
@ -262,7 +267,7 @@ test('html data and attachment message', async (t) => {
|
|||
] as MessageAttachment[],
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.html, htmlFixture.replace(/\r/g, ''));
|
||||
t.is(mail.text, '\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
|
@ -270,9 +275,9 @@ test('html data and attachment message', async (t) => {
|
|||
t.is(mail.to?.text, msg.to);
|
||||
});
|
||||
|
||||
test('attachment message', async (t) => {
|
||||
test('text + attachment message', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT+ATTACHMENT message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'washing@gmail.com',
|
||||
to: 'lincoln@gmail.com',
|
||||
text: 'hello friend, i hope this message and pdf finds you well.',
|
||||
|
@ -283,7 +288,7 @@ test('attachment message', async (t) => {
|
|||
} as MessageAttachment,
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.attachments[0].content.toString('base64'), pdfFixture);
|
||||
t.is(mail.text, msg.text + '\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
|
@ -291,9 +296,9 @@ test('attachment message', async (t) => {
|
|||
t.is(mail.to?.text, msg.to);
|
||||
});
|
||||
|
||||
test('attachment sent with unicode filename message', async (t) => {
|
||||
test('text + attachment + unicode filename message', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT+ATTACHMENT message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'washing@gmail.com',
|
||||
to: 'lincoln@gmail.com',
|
||||
text: 'hello friend, i hope this message and pdf finds you well.',
|
||||
|
@ -304,7 +309,7 @@ test('attachment sent with unicode filename message', async (t) => {
|
|||
} as MessageAttachment,
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.attachments[0].content.toString('base64'), pdfFixture);
|
||||
t.is(mail.attachments[0].filename, 'smtp-✓-info.pdf');
|
||||
t.is(mail.text, msg.text + '\n');
|
||||
|
@ -313,9 +318,9 @@ test('attachment sent with unicode filename message', async (t) => {
|
|||
t.is(mail.to?.text, msg.to);
|
||||
});
|
||||
|
||||
test('attachments message', async (t) => {
|
||||
test('text + two attachments message', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT+2+ATTACHMENTS message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'sergey@gmail.com',
|
||||
to: 'jobs@gmail.com',
|
||||
text: 'hello friend, i hope this message and attachments finds you well.',
|
||||
|
@ -333,7 +338,7 @@ test('attachments message', async (t) => {
|
|||
] as MessageAttachment[],
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.attachments[0].content.toString('base64'), pdfFixture);
|
||||
t.is(mail.attachments[1].content.toString('base64'), tarFixture);
|
||||
t.is(mail.text, msg.text + '\n');
|
||||
|
@ -342,9 +347,9 @@ test('attachments message', async (t) => {
|
|||
t.is(mail.to?.text, msg.to);
|
||||
});
|
||||
|
||||
test('streams message', async (t) => {
|
||||
test('text + two attachments message (streams)', async (t) => {
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT+2+STREAMED+ATTACHMENTS message from emailjs',
|
||||
subject: t.title,
|
||||
from: 'stanford@gmail.com',
|
||||
to: 'mit@gmail.com',
|
||||
text: 'hello friend, i hope this message and streamed attachments finds you well.',
|
||||
|
@ -366,7 +371,7 @@ test('streams message', async (t) => {
|
|||
stream.pause();
|
||||
}
|
||||
|
||||
const mail = await send(msg);
|
||||
const mail = await send(t, msg);
|
||||
t.is(mail.attachments[0].content.toString('base64'), pdfFixture);
|
||||
t.is(mail.attachments[1].content.toString('base64'), tarFixture);
|
||||
t.is(mail.text, msg.text + '\n');
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { performance } from 'perf_hooks';
|
||||
|
||||
import test from 'ava';
|
||||
import { SMTPServer } from 'smtp-server';
|
||||
|
||||
import { SMTPClient, Message } from '../email.js';
|
||||
|
||||
const port = 7777;
|
||||
|
||||
test('queue failures are handled gracefully by client', async (t) => {
|
||||
test('synchronous queue failures are handled gracefully by client', async (t) => {
|
||||
const tlsClient = new SMTPClient({ port, timeout: 200, tls: true });
|
||||
const secureServer = new SMTPServer({ secure: true });
|
||||
|
||||
|
|
44
yarn.lock
44
yarn.lock
|
@ -14,19 +14,19 @@
|
|||
dependencies:
|
||||
"@cspotcode/source-map-consumer" "0.8.0"
|
||||
|
||||
"@eslint/eslintrc@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz#4989b9e8c0216747ee7cca314ae73791bb281aae"
|
||||
integrity sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==
|
||||
"@eslint/eslintrc@^1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz#fcaa2bcef39e13d6e9e7f6271f4cc7cae1174886"
|
||||
integrity sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==
|
||||
dependencies:
|
||||
ajv "^6.12.4"
|
||||
debug "^4.3.2"
|
||||
espree "^9.3.1"
|
||||
espree "^9.3.2"
|
||||
globals "^13.9.0"
|
||||
ignore "^5.2.0"
|
||||
import-fresh "^3.2.1"
|
||||
js-yaml "^4.1.0"
|
||||
minimatch "^3.0.4"
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@humanwhocodes/config-array@^0.9.2":
|
||||
|
@ -232,7 +232,7 @@
|
|||
"@typescript-eslint/types" "5.22.0"
|
||||
eslint-visitor-keys "^3.0.0"
|
||||
|
||||
acorn-jsx@^5.3.1:
|
||||
acorn-jsx@^5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
@ -242,7 +242,7 @@ acorn-walk@^8.1.1, acorn-walk@^8.2.0:
|
|||
resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
|
||||
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
|
||||
|
||||
acorn@^8.4.1, acorn@^8.7.0:
|
||||
acorn@^8.4.1, acorn@^8.7.0, acorn@^8.7.1:
|
||||
version "8.7.1"
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
|
||||
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
|
||||
|
@ -781,12 +781,12 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0:
|
|||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
|
||||
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
|
||||
|
||||
eslint@8.14.0:
|
||||
version "8.14.0"
|
||||
resolved "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz#62741f159d9eb4a79695b28ec4989fcdec623239"
|
||||
integrity sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==
|
||||
eslint@8.15.0:
|
||||
version "8.15.0"
|
||||
resolved "https://registry.npmjs.org/eslint/-/eslint-8.15.0.tgz#fea1d55a7062da48d82600d2e0974c55612a11e9"
|
||||
integrity sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==
|
||||
dependencies:
|
||||
"@eslint/eslintrc" "^1.2.2"
|
||||
"@eslint/eslintrc" "^1.2.3"
|
||||
"@humanwhocodes/config-array" "^0.9.2"
|
||||
ajv "^6.10.0"
|
||||
chalk "^4.0.0"
|
||||
|
@ -797,7 +797,7 @@ eslint@8.14.0:
|
|||
eslint-scope "^7.1.1"
|
||||
eslint-utils "^3.0.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
espree "^9.3.1"
|
||||
espree "^9.3.2"
|
||||
esquery "^1.4.0"
|
||||
esutils "^2.0.2"
|
||||
fast-deep-equal "^3.1.3"
|
||||
|
@ -813,7 +813,7 @@ eslint@8.14.0:
|
|||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.4.1"
|
||||
lodash.merge "^4.6.2"
|
||||
minimatch "^3.0.4"
|
||||
minimatch "^3.1.2"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.9.1"
|
||||
regexpp "^3.2.0"
|
||||
|
@ -822,13 +822,13 @@ eslint@8.14.0:
|
|||
text-table "^0.2.0"
|
||||
v8-compile-cache "^2.0.3"
|
||||
|
||||
espree@^9.3.1:
|
||||
version "9.3.1"
|
||||
resolved "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd"
|
||||
integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==
|
||||
espree@^9.3.2:
|
||||
version "9.3.2"
|
||||
resolved "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596"
|
||||
integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==
|
||||
dependencies:
|
||||
acorn "^8.7.0"
|
||||
acorn-jsx "^5.3.1"
|
||||
acorn "^8.7.1"
|
||||
acorn-jsx "^5.3.2"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
esprima@^4.0.0:
|
||||
|
@ -1385,7 +1385,7 @@ mimic-fn@^4.0.0:
|
|||
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
|
||||
integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==
|
||||
|
||||
minimatch@^3.0.4:
|
||||
minimatch@^3.0.4, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
|
|
Loading…
Reference in New Issue