mirror of https://github.com/eleith/emailjs.git
Compare commits
8 Commits
1e28b13f21
...
f2e53576bc
Author | SHA1 | Date |
---|---|---|
Zack Schuster | f2e53576bc | |
Zack Schuster | ba8966271b | |
Zack Schuster | 74700675a0 | |
Zack Schuster | 3abc9a198f | |
Zack Schuster | 7facfa31eb | |
Zack Schuster | f62ea0e3d9 | |
Zack Schuster | b23d2a527e | |
Zack Schuster | 06757cc38f |
|
@ -9,12 +9,6 @@
|
|||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": [
|
||||
"error",
|
||||
{
|
||||
"ignoreRestArgs": true
|
||||
}
|
||||
],
|
||||
"curly": [
|
||||
"error",
|
||||
"all"
|
||||
|
|
|
@ -54,17 +54,7 @@ export class SMTPClient {
|
|||
msg: T,
|
||||
callback: MessageCallback<T>
|
||||
): void {
|
||||
const message =
|
||||
msg instanceof Message
|
||||
? msg
|
||||
: this._canMakeMessage(msg)
|
||||
? new Message(msg)
|
||||
: null;
|
||||
|
||||
if (message == null) {
|
||||
callback(new Error('message is not a valid Message instance'), msg);
|
||||
return;
|
||||
}
|
||||
const message = msg instanceof Message ? msg : new Message(msg);
|
||||
|
||||
const { isValid, validationError } = message.checkValidity();
|
||||
|
||||
|
@ -189,13 +179,9 @@ export class SMTPClient {
|
|||
* @returns {void}
|
||||
*/
|
||||
protected _connect(stack: MessageStack) {
|
||||
/**
|
||||
* @param {Error} err callback error
|
||||
* @returns {void}
|
||||
*/
|
||||
const connect = (err: Error) => {
|
||||
const connect = (err: Error | null) => {
|
||||
if (!err) {
|
||||
const begin = (err: Error) => {
|
||||
const begin = (err: Error | null) => {
|
||||
if (!err) {
|
||||
this.ready = true;
|
||||
this._poll();
|
||||
|
@ -226,19 +212,6 @@ export class SMTPClient {
|
|||
this.smtp.connect(connect);
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @param {MessageStack} msg message stack
|
||||
* @returns {boolean} can make message
|
||||
*/
|
||||
protected _canMakeMessage(msg: MessageHeaders) {
|
||||
return (
|
||||
msg.from &&
|
||||
(msg.to || msg.cc || msg.bcc) &&
|
||||
(msg.text !== undefined || this._containsInlinedHtml(msg.attachment))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @param {MessageAttachment | MessageAttachment[]} attachment attachment
|
||||
|
@ -276,11 +249,7 @@ export class SMTPClient {
|
|||
* @returns {function(Error): void} callback
|
||||
*/
|
||||
protected _sendsmtp(stack: MessageStack, next: (msg: MessageStack) => void) {
|
||||
/**
|
||||
* @param {Error} [err] error
|
||||
* @returns {void}
|
||||
*/
|
||||
return (err: Error) => {
|
||||
return (err: Error | null) => {
|
||||
if (!err && next) {
|
||||
next.apply(this, [stack]);
|
||||
} else {
|
||||
|
|
|
@ -40,10 +40,10 @@ const GREYLIST_DELAY = 300 as const;
|
|||
let DEBUG: 0 | 1 = 0;
|
||||
|
||||
/**
|
||||
* @param {...any[]} args the message(s) to log
|
||||
* @param {...unknown[]} args the message(s) to log
|
||||
* @returns {void}
|
||||
*/
|
||||
const log = (...args: any[]) => {
|
||||
const log = (...args: unknown[]) => {
|
||||
if (DEBUG === 1) {
|
||||
args.forEach((d) =>
|
||||
console.log(
|
||||
|
@ -57,22 +57,20 @@ const log = (...args: any[]) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {function(...any[]): void} callback the function to call
|
||||
* @param {...any[]} args the arguments to apply to the function
|
||||
* @returns {void}
|
||||
*/
|
||||
const caller = (callback?: (...rest: any[]) => void, ...args: any[]) => {
|
||||
if (typeof callback === 'function') {
|
||||
callback(...args);
|
||||
}
|
||||
};
|
||||
|
||||
export type SMTPSocketOptions = Omit<
|
||||
ConnectionOptions,
|
||||
'port' | 'host' | 'path' | 'socket' | 'timeout' | 'secureContext'
|
||||
>;
|
||||
|
||||
export type SMTPCommandCallback = (
|
||||
err: Error | SMTPError | null,
|
||||
data?:
|
||||
| string
|
||||
| { code: string | number; data: string; message: string }
|
||||
| null,
|
||||
message?: string
|
||||
) => void;
|
||||
|
||||
export interface SMTPConnectionOptions {
|
||||
timeout: number | null;
|
||||
user: string;
|
||||
|
@ -83,7 +81,7 @@ export interface SMTPConnectionOptions {
|
|||
ssl: boolean | SMTPSocketOptions;
|
||||
tls: boolean | SMTPSocketOptions;
|
||||
authentication: (keyof typeof AUTH_METHODS)[];
|
||||
logger: (...args: any[]) => void;
|
||||
logger: (...args: unknown[]) => void;
|
||||
}
|
||||
|
||||
export interface ConnectOptions {
|
||||
|
@ -116,7 +114,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
protected tls: boolean | SMTPSocketOptions = false;
|
||||
protected port: number;
|
||||
|
||||
private greylistResponseTracker = new WeakSet<(...rest: any[]) => void>();
|
||||
private greylistResponseTracker = new WeakSet<SMTPCommandCallback>();
|
||||
|
||||
/**
|
||||
* SMTP class written using python's (2.7) smtplib.py as a base.
|
||||
|
@ -218,14 +216,14 @@ 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 {function(...any[]): void} callback function to call after response
|
||||
* @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
|
||||
* @returns {void}
|
||||
*/
|
||||
public connect(
|
||||
callback: (...rest: any[]) => void,
|
||||
callback: SMTPCommandCallback,
|
||||
port: number = this.port,
|
||||
host: string = this.host,
|
||||
options: ConnectOptions = {}
|
||||
|
@ -252,8 +250,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
!this.sock.authorized
|
||||
) {
|
||||
this.close(true);
|
||||
caller(
|
||||
callback,
|
||||
callback(
|
||||
SMTPError.create(
|
||||
'could not establish an ssl connection',
|
||||
SMTPErrorStates.CONNECTIONAUTH
|
||||
|
@ -275,8 +272,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
} else {
|
||||
this.close(true);
|
||||
this.log(err);
|
||||
caller(
|
||||
callback,
|
||||
callback(
|
||||
SMTPError.create(
|
||||
'could not connect',
|
||||
SMTPErrorStates.COULDNOTCONNECT,
|
||||
|
@ -295,18 +291,17 @@ export class SMTPConnection extends EventEmitter {
|
|||
return;
|
||||
}
|
||||
this.close(true);
|
||||
caller(callback, err);
|
||||
callback(err);
|
||||
} else if (msg.code == '220') {
|
||||
this.log(msg.data);
|
||||
|
||||
// might happen first, so no need to wait on connected()
|
||||
this._state = SMTPState.CONNECTED;
|
||||
caller(callback, null, msg.data);
|
||||
callback(null, msg.data);
|
||||
} else {
|
||||
this.log(`response (data): ${msg.data}`);
|
||||
this.quit(() => {
|
||||
caller(
|
||||
callback,
|
||||
callback(
|
||||
SMTPError.create(
|
||||
'bad response on connection',
|
||||
SMTPErrorStates.BADRESPONSE,
|
||||
|
@ -343,19 +338,19 @@ export class SMTPConnection extends EventEmitter {
|
|||
/**
|
||||
* @public
|
||||
* @param {string} str the string to send
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @returns {void}
|
||||
*/
|
||||
public send(str: string, callback: (...args: any[]) => void) {
|
||||
public send(str: string, callback: SMTPCommandCallback) {
|
||||
if (this.sock != null && this._state === SMTPState.CONNECTED) {
|
||||
this.log(str);
|
||||
|
||||
this.sock.once('response', (err, msg) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
callback(err, undefined);
|
||||
} else {
|
||||
this.log(msg.data);
|
||||
caller(callback, null, msg);
|
||||
callback(null, msg);
|
||||
}
|
||||
});
|
||||
if (this.sock.writable) {
|
||||
|
@ -363,12 +358,12 @@ export class SMTPConnection extends EventEmitter {
|
|||
}
|
||||
} else {
|
||||
this.close(true);
|
||||
caller(
|
||||
callback,
|
||||
callback(
|
||||
SMTPError.create(
|
||||
'no connection has been established',
|
||||
SMTPErrorStates.NOCONNECTION
|
||||
)
|
||||
),
|
||||
undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -376,13 +371,13 @@ export class SMTPConnection extends EventEmitter {
|
|||
/**
|
||||
* @public
|
||||
* @param {string} cmd command to issue
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {(number[] | number)} [codes=[250]] array codes
|
||||
* @returns {void}
|
||||
*/
|
||||
public command(
|
||||
cmd: string,
|
||||
callback: (...rest: any[]) => void,
|
||||
callback: SMTPCommandCallback,
|
||||
codes: number[] | number = [250]
|
||||
) {
|
||||
const codesArray = Array.isArray(codes)
|
||||
|
@ -391,16 +386,22 @@ export class SMTPConnection extends EventEmitter {
|
|||
? [codes]
|
||||
: [250];
|
||||
|
||||
const response = (
|
||||
err: Error | null | undefined,
|
||||
msg: { code: string | number; data: string; message: string }
|
||||
) => {
|
||||
const response: SMTPCommandCallback = (err, msg) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
callback(err);
|
||||
} else if (msg == null || typeof msg === 'string') {
|
||||
callback(
|
||||
SMTPError.create(
|
||||
'Invalid message in response',
|
||||
SMTPErrorStates.BADRESPONSE,
|
||||
err,
|
||||
msg
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const code = Number(msg.code);
|
||||
if (codesArray.indexOf(code) !== -1) {
|
||||
caller(callback, err, msg.data, msg.message);
|
||||
callback(err, msg.data, msg.message);
|
||||
} else if (
|
||||
(code === 450 || code === 451) &&
|
||||
msg.message.toLowerCase().includes('greylist') &&
|
||||
|
@ -415,8 +416,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
const errorMessage = `bad response on command '${
|
||||
cmd.split(' ')[0]
|
||||
}'${suffix}`;
|
||||
caller(
|
||||
callback,
|
||||
callback(
|
||||
SMTPError.create(
|
||||
errorMessage,
|
||||
SMTPErrorStates.BADRESPONSE,
|
||||
|
@ -442,35 +442,35 @@ 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 {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} domain the domain to associate with the 'helo' request
|
||||
* @returns {void}
|
||||
*/
|
||||
public helo(callback: (...rest: any[]) => void, domain?: string) {
|
||||
public helo(callback: SMTPCommandCallback, domain?: string) {
|
||||
this.command(`helo ${domain || this.domain}`, (err, data) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
callback(err);
|
||||
} else {
|
||||
this.parse_smtp_features(data);
|
||||
caller(callback, err, data);
|
||||
this.parse_smtp_features(data as string);
|
||||
callback(err, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @returns {void}
|
||||
*/
|
||||
public starttls(callback: (...rest: any[]) => void) {
|
||||
const response = (err: Error, msg: { data: unknown }) => {
|
||||
public starttls(callback: SMTPCommandCallback) {
|
||||
const response: SMTPCommandCallback = (err, msg) => {
|
||||
if (this.sock == null) {
|
||||
throw new Error('null socket');
|
||||
}
|
||||
|
||||
if (err) {
|
||||
err.message += ' while establishing a starttls session';
|
||||
caller(callback, err);
|
||||
callback(err);
|
||||
} else {
|
||||
const secureContext = createSecureContext(
|
||||
typeof this.tls === 'object' ? this.tls : {}
|
||||
|
@ -479,7 +479,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
secureSocket.on('error', (err: Error) => {
|
||||
this.close(true);
|
||||
caller(callback, err);
|
||||
callback(err);
|
||||
});
|
||||
|
||||
this._secure = true;
|
||||
|
@ -488,7 +488,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
new SMTPResponseMonitor(this.sock, this.timeout, () =>
|
||||
this.close(true)
|
||||
);
|
||||
caller(callback, msg.data);
|
||||
callback(null, typeof msg === 'string' ? msg : msg?.data);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -527,22 +527,22 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} domain the domain to associate with the 'ehlo' request
|
||||
* @returns {void}
|
||||
*/
|
||||
public ehlo(callback: (...rest: any[]) => void, domain?: string) {
|
||||
public ehlo(callback: SMTPCommandCallback, domain?: string) {
|
||||
this.features = {};
|
||||
this.command(`ehlo ${domain || this.domain}`, (err, data) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
callback(err);
|
||||
} else {
|
||||
this.parse_smtp_features(data);
|
||||
this.parse_smtp_features(data as string);
|
||||
|
||||
if (this.tls && !this._secure) {
|
||||
this.starttls(() => this.ehlo(callback, domain));
|
||||
} else {
|
||||
caller(callback, err, data);
|
||||
callback(err, data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -560,67 +560,67 @@ export class SMTPConnection extends EventEmitter {
|
|||
/**
|
||||
* @public
|
||||
* @description SMTP 'help' command, returns text from the server
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} domain the domain to associate with the 'help' request
|
||||
* @returns {void}
|
||||
*/
|
||||
public help(callback: (...rest: any[]) => void, domain: string) {
|
||||
public help(callback: SMTPCommandCallback, domain: string) {
|
||||
this.command(domain ? `help ${domain}` : 'help', callback, [211, 214]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @returns {void}
|
||||
*/
|
||||
public rset(callback: (...rest: any[]) => void) {
|
||||
public rset(callback: SMTPCommandCallback) {
|
||||
this.command('rset', callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @returns {void}
|
||||
*/
|
||||
public noop(callback: (...rest: any[]) => void) {
|
||||
public noop(callback: SMTPCommandCallback) {
|
||||
this.send('noop', callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} from the sender
|
||||
* @returns {void}
|
||||
*/
|
||||
public mail(callback: (...rest: any[]) => void, from: string) {
|
||||
public mail(callback: SMTPCommandCallback, from: string) {
|
||||
this.command(`mail FROM:${from}`, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} to the receiver
|
||||
* @returns {void}
|
||||
*/
|
||||
public rcpt(callback: (...rest: any[]) => void, to: string) {
|
||||
public rcpt(callback: SMTPCommandCallback, to: string) {
|
||||
this.command(`RCPT TO:${to}`, callback, [250, 251]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @returns {void}
|
||||
*/
|
||||
public data(callback: (...rest: any[]) => void) {
|
||||
public data(callback: SMTPCommandCallback) {
|
||||
this.command('data', callback, [354]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @returns {void}
|
||||
*/
|
||||
public data_end(callback: (...rest: any[]) => void) {
|
||||
public data_end(callback: SMTPCommandCallback) {
|
||||
this.command(`${CRLF}.`, callback);
|
||||
}
|
||||
|
||||
|
@ -638,10 +638,10 @@ export class SMTPConnection extends EventEmitter {
|
|||
* @public
|
||||
* @description SMTP 'verify' command -- checks for address validity.
|
||||
* @param {string} address the address to validate
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @returns {void}
|
||||
*/
|
||||
public verify(address: string, callback: (...rest: any[]) => void) {
|
||||
public verify(address: string, callback: SMTPCommandCallback) {
|
||||
this.command(`vrfy ${address}`, callback, [250, 251, 252]);
|
||||
}
|
||||
|
||||
|
@ -649,10 +649,10 @@ export class SMTPConnection extends EventEmitter {
|
|||
* @public
|
||||
* @description SMTP 'expn' command -- expands a mailing list.
|
||||
* @param {string} address the mailing list to expand
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @returns {void}
|
||||
*/
|
||||
public expn(address: string, callback: (...rest: any[]) => void) {
|
||||
public expn(address: string, callback: SMTPCommandCallback) {
|
||||
this.command(`expn ${address}`, callback);
|
||||
}
|
||||
|
||||
|
@ -663,23 +663,21 @@ export class SMTPConnection extends EventEmitter {
|
|||
* If there has been no previous EHLO or HELO command self session, self
|
||||
* method tries ESMTP EHLO first.
|
||||
*
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @param {SMTPCommandCallback} callback function to call after response
|
||||
* @param {string} [domain] the domain to associate with the command
|
||||
* @returns {void}
|
||||
*/
|
||||
public ehlo_or_helo_if_needed(
|
||||
callback: (...rest: any[]) => void,
|
||||
callback: SMTPCommandCallback,
|
||||
domain?: string
|
||||
) {
|
||||
// is this code callable...?
|
||||
if (!this.features) {
|
||||
const response = (err: Error, data: unknown) =>
|
||||
caller(callback, err, data);
|
||||
this.ehlo((err, data) => {
|
||||
if (err) {
|
||||
this.helo(response, domain);
|
||||
this.helo(callback, domain);
|
||||
} else {
|
||||
caller(callback, err, data);
|
||||
callback(err, data);
|
||||
}
|
||||
}, domain);
|
||||
}
|
||||
|
@ -695,14 +693,14 @@ export class SMTPConnection extends EventEmitter {
|
|||
*
|
||||
* This method will return normally if the authentication was successful.
|
||||
*
|
||||
* @param {function(...any[]): void} callback function to call after response
|
||||
* @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 {{ method: string, domain: string }} [options] login options
|
||||
* @returns {void}
|
||||
*/
|
||||
public login(
|
||||
callback: (...rest: any[]) => void,
|
||||
callback: SMTPCommandCallback,
|
||||
user?: string,
|
||||
password?: string,
|
||||
options: { method?: string; domain?: string } = {}
|
||||
|
@ -717,7 +715,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
const initiate = (err: Error | null | undefined, data: unknown) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -779,8 +777,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
const failed = (err: Error, data: unknown) => {
|
||||
this.loggedin = false;
|
||||
this.close(); // if auth is bad, close the connection, it won't get better by itself
|
||||
caller(
|
||||
callback,
|
||||
callback(
|
||||
SMTPError.create(
|
||||
'authorization.failed',
|
||||
SMTPErrorStates.AUTHFAILED,
|
||||
|
@ -790,36 +787,21 @@ export class SMTPConnection extends EventEmitter {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Error} err err
|
||||
* @param {unknown} data data
|
||||
* @returns {void}
|
||||
*/
|
||||
const response = (err: Error | null | undefined, data: unknown) => {
|
||||
const response: SMTPCommandCallback = (err, data) => {
|
||||
if (err) {
|
||||
failed(err, data);
|
||||
} else {
|
||||
this.loggedin = true;
|
||||
caller(callback, err, data);
|
||||
callback(err, data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Error} err err
|
||||
* @param {unknown} data data
|
||||
* @param {string} msg msg
|
||||
* @returns {void}
|
||||
*/
|
||||
const attempt = (
|
||||
err: Error | null | undefined,
|
||||
data: unknown,
|
||||
msg: string
|
||||
) => {
|
||||
const attempt: SMTPCommandCallback = (err, data, msg) => {
|
||||
if (err) {
|
||||
failed(err, data);
|
||||
} else {
|
||||
if (method === AUTH_METHODS['CRAM-MD5']) {
|
||||
this.command(encodeCramMd5(msg), response, [235, 503]);
|
||||
this.command(encodeCramMd5(msg as string), response, [235, 503]);
|
||||
} else if (method === AUTH_METHODS.LOGIN) {
|
||||
this.command(
|
||||
Buffer.from(login.password()).toString('base64'),
|
||||
|
@ -830,13 +812,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Error} err err
|
||||
* @param {unknown} data data
|
||||
* @param {string} msg msg
|
||||
* @returns {void}
|
||||
*/
|
||||
const attemptUser = (err: Error, data: unknown) => {
|
||||
const attemptUser: SMTPCommandCallback = (err, data) => {
|
||||
if (err) {
|
||||
failed(err, data);
|
||||
} else {
|
||||
|
@ -872,8 +848,7 @@ export class SMTPConnection extends EventEmitter {
|
|||
);
|
||||
break;
|
||||
default:
|
||||
caller(
|
||||
callback,
|
||||
callback(
|
||||
SMTPError.create(
|
||||
'no form of authorization supported',
|
||||
SMTPErrorStates.AUTHNOTSUPPORTED,
|
||||
|
@ -918,14 +893,14 @@ export class SMTPConnection extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @public
|
||||
* @param {function(...any[]): void} [callback] function to call after response
|
||||
* @param {SMTPCommandCallback} [callback] function to call after response
|
||||
* @returns {void}
|
||||
*/
|
||||
public quit(callback?: (...rest: any[]) => void) {
|
||||
public quit(callback?: SMTPCommandCallback) {
|
||||
this.command(
|
||||
'quit',
|
||||
(err, data) => {
|
||||
caller(callback, err, data);
|
||||
callback?.(err, data);
|
||||
this.close();
|
||||
},
|
||||
[221, 250]
|
||||
|
|
|
@ -76,7 +76,7 @@ export interface MessageHeaders {
|
|||
'return-path'?: string | null;
|
||||
date?: string;
|
||||
from: string | string[];
|
||||
to: string | string[];
|
||||
to?: string | string[];
|
||||
cc?: string | string[];
|
||||
bcc?: string | string[];
|
||||
subject: string;
|
||||
|
@ -137,7 +137,7 @@ export class Message {
|
|||
* @see https://tools.ietf.org/html/rfc2822
|
||||
* @param {Partial<MessageHeaders>} headers Message headers
|
||||
*/
|
||||
constructor(headers: Partial<MessageHeaders>) {
|
||||
constructor(headers: Partial<MessageHeaders> = {}) {
|
||||
for (const header in headers) {
|
||||
// allow user to override default content-type to override charset or send a single non-text message
|
||||
if (/^content-type$/i.test(header)) {
|
||||
|
|
66
test/auth.ts
66
test/auth.ts
|
@ -1,34 +1,20 @@
|
|||
import test from 'ava';
|
||||
import type { ExecutionContext } from 'ava';
|
||||
import { simpleParser } from 'mailparser';
|
||||
import type { AddressObject } from 'mailparser';
|
||||
import { SMTPServer } from 'smtp-server';
|
||||
|
||||
import { AUTH_METHODS, SMTPClient, Message } from '../email.js';
|
||||
import { AUTH_METHODS, SMTPConnection } from '../email.js';
|
||||
|
||||
let port = 2000;
|
||||
|
||||
function send(
|
||||
t: ExecutionContext,
|
||||
{
|
||||
authMethods = [],
|
||||
authOptional = false,
|
||||
secure = false,
|
||||
}: {
|
||||
authMethods?: (keyof typeof AUTH_METHODS)[];
|
||||
authOptional?: boolean;
|
||||
secure?: boolean;
|
||||
} = {}
|
||||
) {
|
||||
function connect({
|
||||
authMethods = [],
|
||||
authOptional = false,
|
||||
secure = false,
|
||||
}: {
|
||||
authMethods?: (keyof typeof AUTH_METHODS)[];
|
||||
authOptional?: boolean;
|
||||
secure?: boolean;
|
||||
} = {}) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
t.plan(5);
|
||||
|
||||
const msg = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'piglet@gmail.com',
|
||||
to: 'pooh@gmail.com',
|
||||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
||||
};
|
||||
const server = new SMTPServer({
|
||||
authMethods,
|
||||
secure: secure,
|
||||
|
@ -49,20 +35,6 @@ function send(
|
|||
return callback(new Error('invalid user / pass'));
|
||||
}
|
||||
},
|
||||
async onData(stream, _session, callback: () => void) {
|
||||
const mail = await simpleParser(stream, {
|
||||
skipHtmlToText: true,
|
||||
skipTextToHtml: true,
|
||||
skipImageLinks: true,
|
||||
} as Record<string, unknown>);
|
||||
|
||||
t.is(mail.text, msg.text + '\n\n\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
t.is(mail.from?.text, msg.from);
|
||||
t.is((mail.to as AddressObject).text, msg.to);
|
||||
|
||||
callback();
|
||||
},
|
||||
});
|
||||
const p = port++;
|
||||
server.listen(p, () => {
|
||||
|
@ -70,7 +42,7 @@ function send(
|
|||
{ port: p, ssl: secure, authentication: authMethods },
|
||||
authOptional ? {} : { user: 'pooh', password: 'honey' }
|
||||
);
|
||||
new SMTPClient(options).send(new Message(msg), (err) => {
|
||||
new SMTPConnection(options).connect((err) => {
|
||||
server.close(() => {
|
||||
if (err) {
|
||||
reject(err.message);
|
||||
|
@ -84,39 +56,39 @@ function send(
|
|||
}
|
||||
|
||||
test('no authentication (unencrypted) should succeed', async (t) => {
|
||||
await t.notThrowsAsync(send(t, { authOptional: true }));
|
||||
await t.notThrowsAsync(connect({ authOptional: true }));
|
||||
});
|
||||
|
||||
test('no authentication (encrypted) should succeed', async (t) => {
|
||||
await t.notThrowsAsync(send(t, { authOptional: true, secure: true }));
|
||||
await t.notThrowsAsync(connect({ authOptional: true, secure: true }));
|
||||
});
|
||||
|
||||
test('PLAIN authentication (unencrypted) should succeed', async (t) => {
|
||||
await t.notThrowsAsync(send(t, { authMethods: [AUTH_METHODS.PLAIN] }));
|
||||
await t.notThrowsAsync(connect({ authMethods: [AUTH_METHODS.PLAIN] }));
|
||||
});
|
||||
|
||||
test('PLAIN authentication (encrypted) should succeed', async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
send(t, { authMethods: [AUTH_METHODS.PLAIN], secure: true })
|
||||
connect({ authMethods: [AUTH_METHODS.PLAIN], secure: true })
|
||||
);
|
||||
});
|
||||
|
||||
test('LOGIN authentication (unencrypted) should succeed', async (t) => {
|
||||
await t.notThrowsAsync(send(t, { authMethods: [AUTH_METHODS.LOGIN] }));
|
||||
await t.notThrowsAsync(connect({ authMethods: [AUTH_METHODS.LOGIN] }));
|
||||
});
|
||||
|
||||
test('LOGIN authentication (encrypted) should succeed', async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
send(t, { authMethods: [AUTH_METHODS.LOGIN], secure: true })
|
||||
connect({ authMethods: [AUTH_METHODS.LOGIN], secure: true })
|
||||
);
|
||||
});
|
||||
|
||||
test('XOAUTH2 authentication (unencrypted) should succeed', async (t) => {
|
||||
await t.notThrowsAsync(send(t, { authMethods: [AUTH_METHODS.XOAUTH2] }));
|
||||
await t.notThrowsAsync(connect({ authMethods: [AUTH_METHODS.XOAUTH2] }));
|
||||
});
|
||||
|
||||
test('XOAUTH2 authentication (encrypted) should succeed', async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
send(t, { authMethods: [AUTH_METHODS.XOAUTH2], secure: true })
|
||||
connect({ authMethods: [AUTH_METHODS.XOAUTH2], secure: true })
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import { promisify } from 'util';
|
||||
|
||||
import test from 'ava';
|
||||
import { simpleParser } from 'mailparser';
|
||||
import type { ParsedMail, AddressObject } from 'mailparser';
|
||||
import { SMTPServer } from 'smtp-server';
|
||||
|
||||
import type { MessageHeaders } from '../email.js';
|
||||
import {
|
||||
DEFAULT_TIMEOUT,
|
||||
SMTPClient,
|
||||
|
@ -13,7 +10,6 @@ import {
|
|||
isRFC2822Date,
|
||||
} from '../email.js';
|
||||
|
||||
const parseMap = new Map<string, ParsedMail>();
|
||||
const port = 3333;
|
||||
let greylistPort = 4444;
|
||||
|
||||
|
@ -32,30 +28,8 @@ const server = new SMTPServer({
|
|||
return callback(new Error('invalid user / pass'));
|
||||
}
|
||||
},
|
||||
async onData(stream, _session, callback: () => void) {
|
||||
const mail = await simpleParser(stream, {
|
||||
skipHtmlToText: true,
|
||||
skipTextToHtml: true,
|
||||
skipImageLinks: true,
|
||||
} as Record<string, unknown>);
|
||||
|
||||
parseMap.set(mail.subject as string, mail);
|
||||
callback();
|
||||
},
|
||||
});
|
||||
|
||||
async function send(headers: Partial<MessageHeaders>) {
|
||||
return new Promise<ParsedMail>((resolve, reject) => {
|
||||
client.send(new Message(headers), (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(parseMap.get(headers.subject as string) as ParsedMail);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test.before(async (t) => {
|
||||
server.listen(port, t.pass);
|
||||
});
|
||||
|
@ -165,17 +139,17 @@ test('client accepts array sender', async (t) => {
|
|||
|
||||
test('client rejects message without `from` header', async (t) => {
|
||||
const error = await t.throwsAsync(
|
||||
send({
|
||||
client.sendAsync({
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
||||
})
|
||||
} as never)
|
||||
);
|
||||
t.is(error?.message, 'Message must have a `from` header');
|
||||
});
|
||||
|
||||
test('client rejects message without `to`, `cc`, or `bcc` header', async (t) => {
|
||||
const error = await t.throwsAsync(
|
||||
send({
|
||||
client.sendAsync({
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'piglet@gmail.com',
|
||||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
||||
|
@ -195,11 +169,12 @@ test('client allows message with only `cc` recipient header', async (t) => {
|
|||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
t.is(mail.text, msg.text + '\n\n\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
t.is(mail.from?.text, msg.from);
|
||||
t.is((mail.cc as AddressObject).text, msg.cc);
|
||||
const message = await client.sendAsync(msg);
|
||||
t.is(message.text, msg.text);
|
||||
t.is(message.header.from, msg.from);
|
||||
t.is(message.header.to, undefined);
|
||||
t.is(message.header.cc, msg.cc);
|
||||
t.is(message.header.bcc, undefined);
|
||||
});
|
||||
|
||||
test('client allows message with only `bcc` recipient header', async (t) => {
|
||||
|
@ -210,11 +185,12 @@ test('client allows message with only `bcc` recipient header', async (t) => {
|
|||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
||||
};
|
||||
|
||||
const mail = await send(msg);
|
||||
t.is(mail.text, msg.text + '\n\n\n');
|
||||
t.is(mail.subject, msg.subject);
|
||||
t.is(mail.from?.text, msg.from);
|
||||
t.is(mail.bcc, undefined);
|
||||
const message = await client.sendAsync(msg);
|
||||
t.is(message.text, msg.text);
|
||||
t.is(message.header.from, msg.from);
|
||||
t.is(message.header.to, undefined);
|
||||
t.is(message.header.cc, undefined);
|
||||
t.is(message.header.bcc, msg.bcc);
|
||||
});
|
||||
|
||||
test('client constructor throws if `password` supplied without `user`', async (t) => {
|
||||
|
|
|
@ -1,11 +1,50 @@
|
|||
import test from 'ava';
|
||||
import { SMTPServer } from 'smtp-server';
|
||||
|
||||
import { SMTPConnection } from '../email.js';
|
||||
|
||||
const port = 6666;
|
||||
|
||||
test('accepts a custom logger', async (t) => {
|
||||
const logger = () => {
|
||||
/** ø */
|
||||
};
|
||||
const connection = new SMTPConnection({ logger });
|
||||
t.is(Reflect.get(connection, 'log'), logger);
|
||||
t.is(Reflect.get(new SMTPConnection({ logger }), 'log'), logger);
|
||||
});
|
||||
|
||||
test('can connect without ssl', async (t) => {
|
||||
return await t.notThrowsAsync(
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const server = new SMTPServer().listen(port, () => {
|
||||
const connection = new SMTPConnection({ port });
|
||||
connection.connect((err) => {
|
||||
server.close();
|
||||
connection.close(true);
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}, port);
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
test('can connect with ssl', async (t) => {
|
||||
return await t.notThrowsAsync(
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const server = new SMTPServer({ secure: true }).listen(port + 1, () => {
|
||||
const connection = new SMTPConnection({ port: port + 1, tls: true });
|
||||
connection.connect((err) => {
|
||||
server.close();
|
||||
connection.close(true);
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}, port);
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue