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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
37
README.md
37
README.md
|
@ -3,8 +3,9 @@
|
|||
send emails, html and attachments (files, streams and strings) from node.js to any smtp server
|
||||
|
||||
## INSTALLING
|
||||
|
||||
npm install emailjs
|
||||
```console
|
||||
$ npm install emailjs # or yarn, pnpm, etc.
|
||||
```
|
||||
|
||||
## FEATURES
|
||||
|
||||
|
@ -22,6 +23,29 @@ send emails, html and attachments (files, streams and strings) from node.js to a
|
|||
- auth access to an SMTP Server
|
||||
- if your service (ex: gmail) uses two-step authentication, use an application specific password
|
||||
|
||||
## DEVELOPMENT
|
||||
|
||||
issues and pull requests are welcome!
|
||||
|
||||
### Setup
|
||||
#### node 14+
|
||||
```console
|
||||
$ corepack prepare # if yarn is not installed
|
||||
$ yarn
|
||||
```
|
||||
|
||||
#### node 12
|
||||
```console
|
||||
$ npm install --global yarn # if yarn is not installed; see https://classic.yarnpkg.com/en/docs/install
|
||||
$ yarn
|
||||
```
|
||||
|
||||
### Testing
|
||||
```console
|
||||
$ yarn test
|
||||
```
|
||||
|
||||
|
||||
## EXAMPLE USAGE - text only emails
|
||||
|
||||
```js
|
||||
|
@ -309,12 +333,3 @@ associative array of currently supported SMTP authentication mechanisms
|
|||
|
||||
eleith
|
||||
zackschuster
|
||||
|
||||
## Testing
|
||||
|
||||
npm install -d
|
||||
npm test
|
||||
|
||||
## Contributions
|
||||
|
||||
issues and pull requests are welcome
|
||||
|
|
|
@ -7,6 +7,4 @@ export default {
|
|||
},
|
||||
files: ['test/*.ts'],
|
||||
nodeArguments: ['--loader=ts-node/esm'],
|
||||
// makes tests far slower
|
||||
workerThreads: false,
|
||||
};
|
||||
|
|
12
package.json
12
package.json
|
@ -15,21 +15,22 @@
|
|||
"url": "http://github.com/eleith/emailjs.git"
|
||||
},
|
||||
"type": "module",
|
||||
"packageManager": "yarn@1.22.18",
|
||||
"devDependencies": {
|
||||
"@ledge/configs": "23.3.23322",
|
||||
"@rollup/plugin-typescript": "8.3.2",
|
||||
"@types/mailparser": "3.4.0",
|
||||
"@types/node": "12.12.6",
|
||||
"@types/smtp-server": "3.5.7",
|
||||
"@typescript-eslint/eslint-plugin": "5.21.0",
|
||||
"@typescript-eslint/parser": "5.21.0",
|
||||
"@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",
|
||||
"prettier": "2.6.2",
|
||||
"rollup": "2.70.2",
|
||||
"rollup": "2.72.0",
|
||||
"smtp-server": "3.11.0",
|
||||
"ts-node": "10.7.0",
|
||||
"tslib": "2.4.0",
|
||||
|
@ -44,7 +45,7 @@
|
|||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"nodemailer": "6.7.4"
|
||||
"nodemailer": "6.7.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
|
@ -61,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) {
|
||||
|
|
13
test/auth.ts
13
test/auth.ts
|
@ -46,13 +46,12 @@ function connect({
|
|||
: { ssl: secure, user: 'pooh', password: 'honey' }
|
||||
);
|
||||
new SMTPConnection(options).connect((err) => {
|
||||
server.close(() => {
|
||||
if (err) {
|
||||
reject(err.message);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
server.close();
|
||||
if (err) {
|
||||
reject(err.message);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
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');
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import { performance } from 'perf_hooks';
|
||||
|
||||
import test from 'ava';
|
||||
import { SMTPServer } from 'smtp-server';
|
||||
|
||||
import { SMTPClient, Message } from '../email.js';
|
||||
|
||||
const port = 7777;
|
||||
|
||||
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 });
|
||||
|
||||
let attemptCount = 0;
|
||||
let failureCount = 0;
|
||||
|
||||
const mailQueue: (() => Promise<void>)[] = [];
|
||||
function* mailQueueGenerator() {
|
||||
while (mailQueue.length > 0) {
|
||||
yield mailQueue.shift();
|
||||
}
|
||||
}
|
||||
|
||||
await t.throwsAsync(
|
||||
new Promise<void>((resolve, reject) => {
|
||||
secureServer
|
||||
.on('error', () => {
|
||||
/** intentionally swallow errors */
|
||||
})
|
||||
.listen(port, async () => {
|
||||
const mailTask = async () => {
|
||||
try {
|
||||
await tlsClient.sendAsync(
|
||||
new Message({
|
||||
from: 'piglet@gmail.com',
|
||||
to: 'pooh@gmail.com',
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
text: 'hello friend, i hope this message finds you well.',
|
||||
})
|
||||
);
|
||||
resolve();
|
||||
} catch (err) {
|
||||
if (attemptCount < 5) {
|
||||
void mailQueue.push(mailTask);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
void mailQueue.push(mailTask);
|
||||
for (const task of mailQueueGenerator()) {
|
||||
const now = performance.now();
|
||||
const initialAttemptCount = attemptCount++;
|
||||
try {
|
||||
t.log(
|
||||
`Attempting task #${attemptCount}...${
|
||||
attemptCount > 1
|
||||
? ` (succeeded: ${
|
||||
initialAttemptCount - failureCount
|
||||
} / ${initialAttemptCount})`
|
||||
: ''
|
||||
}`
|
||||
);
|
||||
await task?.();
|
||||
t.log(
|
||||
`Task succeeded (${Math.round(performance.now() - now)}ms).`
|
||||
);
|
||||
} catch (err) {
|
||||
failureCount++;
|
||||
t.log(
|
||||
`Task failed: ${err.message} (${Math.round(
|
||||
performance.now() - now
|
||||
)}ms)`
|
||||
);
|
||||
}
|
||||
}
|
||||
t.log(
|
||||
`Finished after ${attemptCount} attempts (succeeded: ${
|
||||
attemptCount - failureCount
|
||||
} / ${attemptCount}).`
|
||||
);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
t.log(
|
||||
`SMTPClient ${JSON.stringify(
|
||||
{
|
||||
// @ts-expect-error need to check protected prop
|
||||
ready: tlsClient.ready,
|
||||
// @ts-expect-error need to check protected prop
|
||||
sending: tlsClient.sending,
|
||||
state: tlsClient.smtp.state(),
|
||||
},
|
||||
null,
|
||||
'\t'
|
||||
).replace(/"/g, '')}`
|
||||
);
|
||||
|
||||
// @ts-expect-error need to check protected prop
|
||||
t.false(tlsClient.ready);
|
||||
// @ts-expect-error need to check protected prop
|
||||
t.false(tlsClient.sending);
|
||||
t.is(tlsClient.smtp.state(), 0);
|
||||
});
|
166
yarn.lock
166
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":
|
||||
|
@ -152,14 +152,14 @@
|
|||
"@types/node" "*"
|
||||
"@types/nodemailer" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@5.21.0":
|
||||
version "5.21.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.21.0.tgz#bfc22e0191e6404ab1192973b3b4ea0461c1e878"
|
||||
integrity sha512-fTU85q8v5ZLpoZEyn/u1S2qrFOhi33Edo2CZ0+q1gDaWWm0JuPh3bgOyU8lM0edIEYgKLDkPFiZX2MOupgjlyg==
|
||||
"@typescript-eslint/eslint-plugin@5.22.0":
|
||||
version "5.22.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.22.0.tgz#7b52a0de2e664044f28b36419210aea4ab619e2a"
|
||||
integrity sha512-YCiy5PUzpAeOPGQ7VSGDEY2NeYUV1B0swde2e0HzokRsHBYjSdF6DZ51OuRZxVPHx0032lXGLvOMls91D8FXlg==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.21.0"
|
||||
"@typescript-eslint/type-utils" "5.21.0"
|
||||
"@typescript-eslint/utils" "5.21.0"
|
||||
"@typescript-eslint/scope-manager" "5.22.0"
|
||||
"@typescript-eslint/type-utils" "5.22.0"
|
||||
"@typescript-eslint/utils" "5.22.0"
|
||||
debug "^4.3.2"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
ignore "^5.1.8"
|
||||
|
@ -167,72 +167,72 @@
|
|||
semver "^7.3.5"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/parser@5.21.0":
|
||||
version "5.21.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.21.0.tgz#6cb72673dbf3e1905b9c432175a3c86cdaf2071f"
|
||||
integrity sha512-8RUwTO77hstXUr3pZoWZbRQUxXcSXafZ8/5gpnQCfXvgmP9gpNlRGlWzvfbEQ14TLjmtU8eGnONkff8U2ui2Eg==
|
||||
"@typescript-eslint/parser@5.22.0":
|
||||
version "5.22.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.22.0.tgz#7bedf8784ef0d5d60567c5ba4ce162460e70c178"
|
||||
integrity sha512-piwC4krUpRDqPaPbFaycN70KCP87+PC5WZmrWs+DlVOxxmF+zI6b6hETv7Quy4s9wbkV16ikMeZgXsvzwI3icQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.21.0"
|
||||
"@typescript-eslint/types" "5.21.0"
|
||||
"@typescript-eslint/typescript-estree" "5.21.0"
|
||||
"@typescript-eslint/scope-manager" "5.22.0"
|
||||
"@typescript-eslint/types" "5.22.0"
|
||||
"@typescript-eslint/typescript-estree" "5.22.0"
|
||||
debug "^4.3.2"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.21.0":
|
||||
version "5.21.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.21.0.tgz#a4b7ed1618f09f95e3d17d1c0ff7a341dac7862e"
|
||||
integrity sha512-XTX0g0IhvzcH/e3393SvjRCfYQxgxtYzL3UREteUneo72EFlt7UNoiYnikUtmGVobTbhUDByhJ4xRBNe+34kOQ==
|
||||
"@typescript-eslint/scope-manager@5.22.0":
|
||||
version "5.22.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.22.0.tgz#590865f244ebe6e46dc3e9cab7976fc2afa8af24"
|
||||
integrity sha512-yA9G5NJgV5esANJCO0oF15MkBO20mIskbZ8ijfmlKIvQKg0ynVKfHZ15/nhAJN5m8Jn3X5qkwriQCiUntC9AbA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.21.0"
|
||||
"@typescript-eslint/visitor-keys" "5.21.0"
|
||||
"@typescript-eslint/types" "5.22.0"
|
||||
"@typescript-eslint/visitor-keys" "5.22.0"
|
||||
|
||||
"@typescript-eslint/type-utils@5.21.0":
|
||||
version "5.21.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.21.0.tgz#ff89668786ad596d904c21b215e5285da1b6262e"
|
||||
integrity sha512-MxmLZj0tkGlkcZCSE17ORaHl8Th3JQwBzyXL/uvC6sNmu128LsgjTX0NIzy+wdH2J7Pd02GN8FaoudJntFvSOw==
|
||||
"@typescript-eslint/type-utils@5.22.0":
|
||||
version "5.22.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.22.0.tgz#0c0e93b34210e334fbe1bcb7250c470f4a537c19"
|
||||
integrity sha512-iqfLZIsZhK2OEJ4cQ01xOq3NaCuG5FQRKyHicA3xhZxMgaxQazLUHbH/B2k9y5i7l3+o+B5ND9Mf1AWETeMISA==
|
||||
dependencies:
|
||||
"@typescript-eslint/utils" "5.21.0"
|
||||
"@typescript-eslint/utils" "5.22.0"
|
||||
debug "^4.3.2"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/types@5.21.0":
|
||||
version "5.21.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.21.0.tgz#8cdb9253c0dfce3f2ab655b9d36c03f72e684017"
|
||||
integrity sha512-XnOOo5Wc2cBlq8Lh5WNvAgHzpjnEzxn4CJBwGkcau7b/tZ556qrWXQz4DJyChYg8JZAD06kczrdgFPpEQZfDsA==
|
||||
"@typescript-eslint/types@5.22.0":
|
||||
version "5.22.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.22.0.tgz#50a4266e457a5d4c4b87ac31903b28b06b2c3ed0"
|
||||
integrity sha512-T7owcXW4l0v7NTijmjGWwWf/1JqdlWiBzPqzAWhobxft0SiEvMJB56QXmeCQjrPuM8zEfGUKyPQr/L8+cFUBLw==
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.21.0":
|
||||
version "5.21.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.21.0.tgz#9f0c233e28be2540eaed3df050f0d54fb5aa52de"
|
||||
integrity sha512-Y8Y2T2FNvm08qlcoSMoNchh9y2Uj3QmjtwNMdRQkcFG7Muz//wfJBGBxh8R7HAGQFpgYpdHqUpEoPQk+q9Kjfg==
|
||||
"@typescript-eslint/typescript-estree@5.22.0":
|
||||
version "5.22.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.22.0.tgz#e2116fd644c3e2fda7f4395158cddd38c0c6df97"
|
||||
integrity sha512-EyBEQxvNjg80yinGE2xdhpDYm41so/1kOItl0qrjIiJ1kX/L/L8WWGmJg8ni6eG3DwqmOzDqOhe6763bF92nOw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.21.0"
|
||||
"@typescript-eslint/visitor-keys" "5.21.0"
|
||||
"@typescript-eslint/types" "5.22.0"
|
||||
"@typescript-eslint/visitor-keys" "5.22.0"
|
||||
debug "^4.3.2"
|
||||
globby "^11.0.4"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.3.5"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/utils@5.21.0":
|
||||
version "5.21.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.21.0.tgz#51d7886a6f0575e23706e5548c7e87bce42d7c18"
|
||||
integrity sha512-q/emogbND9wry7zxy7VYri+7ydawo2HDZhRZ5k6yggIvXa7PvBbAAZ4PFH/oZLem72ezC4Pr63rJvDK/sTlL8Q==
|
||||
"@typescript-eslint/utils@5.22.0":
|
||||
version "5.22.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.22.0.tgz#1f2c4897e2cf7e44443c848a13c60407861babd8"
|
||||
integrity sha512-HodsGb037iobrWSUMS7QH6Hl1kppikjA1ELiJlNSTYf/UdMEwzgj0WIp+lBNb6WZ3zTwb0tEz51j0Wee3iJ3wQ==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.9"
|
||||
"@typescript-eslint/scope-manager" "5.21.0"
|
||||
"@typescript-eslint/types" "5.21.0"
|
||||
"@typescript-eslint/typescript-estree" "5.21.0"
|
||||
"@typescript-eslint/scope-manager" "5.22.0"
|
||||
"@typescript-eslint/types" "5.22.0"
|
||||
"@typescript-eslint/typescript-estree" "5.22.0"
|
||||
eslint-scope "^5.1.1"
|
||||
eslint-utils "^3.0.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.21.0":
|
||||
version "5.21.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.21.0.tgz#453fb3662409abaf2f8b1f65d515699c888dd8ae"
|
||||
integrity sha512-SX8jNN+iHqAF0riZQMkm7e8+POXa/fXw5cxL+gjpyP+FI+JVNhii53EmQgDAfDcBpFekYSlO0fGytMQwRiMQCA==
|
||||
"@typescript-eslint/visitor-keys@5.22.0":
|
||||
version "5.22.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.22.0.tgz#f49c0ce406944ffa331a1cfabeed451ea4d0909c"
|
||||
integrity sha512-DbgTqn2Dv5RFWluG88tn0pP6Ex0ROF+dpDO1TNNZdRtLjUr6bdznjA6f/qNqJLjd2PgguAES2Zgxh/JzwzETDg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.21.0"
|
||||
"@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==
|
||||
|
@ -492,9 +492,9 @@ clean-stack@^2.0.0:
|
|||
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
|
||||
|
||||
clean-stack@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-4.1.0.tgz#5ce5a2fd19a12aecdce8570daefddb7ac94b6b4e"
|
||||
integrity sha512-dxXQYI7mfQVcaF12s6sjNFoZ6ZPDQuBBLp3QJ5156k9EvUFClUoZ11fo8HnLQO241DDVntHEug8MOuFO5PSfRg==
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz#c464e4cde4ac789f4e0735c5d75beb49d7b30b31"
|
||||
integrity sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==
|
||||
dependencies:
|
||||
escape-string-regexp "5.0.0"
|
||||
|
||||
|
@ -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:
|
||||
|
@ -1084,9 +1084,9 @@ iconv-lite@0.6.3, iconv-lite@^0.6.3:
|
|||
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||
|
||||
ignore-by-default@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-2.0.0.tgz#537092018540640459569fe7c8c7a408af581146"
|
||||
integrity sha512-+mQSgMRiFD3L3AOxLYOCxjIq4OnAmo5CIuC+lj5ehCJcPtV++QacEV7FdpzvYxH6DaOySWzQU6RR0lPLy37ckA==
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-2.1.0.tgz#c0e0de1a99b6065bdc93315a6f728867981464db"
|
||||
integrity sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==
|
||||
|
||||
ignore@^5.1.8, ignore@^5.2.0:
|
||||
version "5.2.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==
|
||||
|
@ -1427,10 +1427,10 @@ nearley@^2.20.1:
|
|||
railroad-diagrams "^1.0.0"
|
||||
randexp "0.4.6"
|
||||
|
||||
nodemailer@6.7.3, nodemailer@6.7.4:
|
||||
version "6.7.4"
|
||||
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.4.tgz#28771bda3dda8f2dad1912aca0f8727ce7f09d89"
|
||||
integrity sha512-TBSS3qS8WG45ycUwEvEA/3UM1o3sLz9jUl4TPUKPz4ImWWM6UgRCb5pLO+HOouDKEj57yNLOrzQlO8+9IjWZoA==
|
||||
nodemailer@6.7.3, nodemailer@6.7.5:
|
||||
version "6.7.5"
|
||||
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.5.tgz#b30b1566f5fa2249f7bd49ced4c58bec6b25915e"
|
||||
integrity sha512-6VtMpwhsrixq1HDYSBBHvW0GwiWawE75dS3oal48VqRhUvKJNnKnJo2RI/bCVQubj1vgrgscMNW4DHaD6xtMCg==
|
||||
|
||||
nofilter@^3.1.0:
|
||||
version "3.1.0"
|
||||
|
@ -1678,10 +1678,10 @@ rimraf@^3.0.2:
|
|||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rollup@2.70.2:
|
||||
version "2.70.2"
|
||||
resolved "https://registry.npmjs.org/rollup/-/rollup-2.70.2.tgz#808d206a8851628a065097b7ba2053bd83ba0c0d"
|
||||
integrity sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg==
|
||||
rollup@2.72.0:
|
||||
version "2.72.0"
|
||||
resolved "https://registry.npmjs.org/rollup/-/rollup-2.72.0.tgz#f94280b003bcf9f2f1f2594059a9db5abced371e"
|
||||
integrity sha512-KqtR2YcO35/KKijg4nx4STO3569aqCUeGRkKWnJ6r+AvBBrVY9L4pmf4NHVrQr4mTOq6msbohflxr2kpihhaOA==
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
|
|
Loading…
Reference in New Issue