emailjs-smtp-client
Version:
SMTP Client allows you to connect to an SMTP server in JS.
1,052 lines (884 loc) • 106 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* eslint-disable camelcase */
var _emailjsBase = require('emailjs-base64');
var _emailjsTcpSocket = require('emailjs-tcp-socket');
var _emailjsTcpSocket2 = _interopRequireDefault(_emailjsTcpSocket);
var _textEncoding = require('text-encoding');
var _parser = require('./parser');
var _parser2 = _interopRequireDefault(_parser);
var _logger = require('./logger');
var _logger2 = _interopRequireDefault(_logger);
var _common = require('./common');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var DEBUG_TAG = 'SMTP Client';
/**
* Lower Bound for socket timeout to wait since the last data was written to a socket
*/
var TIMEOUT_SOCKET_LOWER_BOUND = 10000;
/**
* Multiplier for socket timeout:
*
* We assume at least a GPRS connection with 115 kb/s = 14,375 kB/s tops, so 10 KB/s to be on
* the safe side. We can timeout after a lower bound of 10s + (n KB / 10 KB/s). A 1 MB message
* upload would be 110 seconds to wait for the timeout. 10 KB/s === 0.1 s/B
*/
var TIMEOUT_SOCKET_MULTIPLIER = 0.1;
var SmtpClient = function () {
/**
* Creates a connection object to a SMTP server and allows to send mail through it.
* Call `connect` method to inititate the actual connection, the constructor only
* defines the properties but does not actually connect.
*
* NB! The parameter order (host, port) differs from node.js "way" (port, host)
*
* @constructor
*
* @param {String} [host="localhost"] Hostname to conenct to
* @param {Number} [port=25] Port number to connect to
* @param {Object} [options] Optional options object
* @param {Boolean} [options.useSecureTransport] Set to true, to use encrypted connection
* @param {String} [options.name] Client hostname for introducing itself to the server
* @param {Object} [options.auth] Authentication options. Depends on the preferred authentication method. Usually {user, pass}
* @param {String} [options.authMethod] Force specific authentication method
* @param {Boolean} [options.disableEscaping] If set to true, do not escape dots on the beginning of the lines
*/
function SmtpClient(host, port) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
_classCallCheck(this, SmtpClient);
this.options = options;
this.timeoutSocketLowerBound = TIMEOUT_SOCKET_LOWER_BOUND;
this.timeoutSocketMultiplier = TIMEOUT_SOCKET_MULTIPLIER;
this.port = port || (this.options.useSecureTransport ? 465 : 25);
this.host = host || 'localhost';
/**
* If set to true, start an encrypted connection instead of the plaintext one
* (recommended if applicable). If useSecureTransport is not set but the port used is 465,
* then ecryption is used by default.
*/
this.options.useSecureTransport = 'useSecureTransport' in this.options ? !!this.options.useSecureTransport : this.port === 465;
this.options.auth = this.options.auth || false; // Authentication object. If not set, authentication step will be skipped.
this.options.name = this.options.name || 'localhost'; // Hostname of the client, this will be used for introducing to the server
this.socket = false; // Downstream TCP socket to the SMTP server, created with mozTCPSocket
this.destroyed = false; // Indicates if the connection has been closed and can't be used anymore
this.waitDrain = false; // Keeps track if the downstream socket is currently full and a drain event should be waited for or not
// Private properties
this._parser = new _parser2.default(); // SMTP response parser object. All data coming from the downstream server is feeded to this parser
this._authenticatedAs = null; // If authenticated successfully, stores the username
this._supportedAuth = []; // A list of authentication mechanisms detected from the EHLO response and which are compatible with this library
this._dataMode = false; // If true, accepts data from the upstream to be passed directly to the downstream socket. Used after the DATA command
this._lastDataBytes = ''; // Keep track of the last bytes to see how the terminating dot should be placed
this._envelope = null; // Envelope object for tracking who is sending mail to whom
this._currentAction = null; // Stores the function that should be run after a response has been received from the server
this._secureMode = !!this.options.useSecureTransport; // Indicates if the connection is secured or plaintext
this._socketTimeoutTimer = false; // Timer waiting to declare the socket dead starting from the last write
this._socketTimeoutStart = false; // Start time of sending the first packet in data mode
this._socketTimeoutPeriod = false; // Timeout for sending in data mode, gets extended with every send()
// Activate logging
this.createLogger();
// Event placeholders
this.onerror = function (e) {}; // Will be run when an error occurs. The `onclose` event will fire subsequently.
this.ondrain = function () {}; // More data can be buffered in the socket.
this.onclose = function () {}; // The connection to the server has been closed
this.onidle = function () {}; // The connection is established and idle, you can send mail now
this.onready = function (failedRecipients) {}; // Waiting for mail body, lists addresses that were not accepted as recipients
this.ondone = function (success) {}; // The mail has been sent. Wait for `onidle` next. Indicates if the message was queued by the server.
}
/**
* Initiate a connection to the server
*/
_createClass(SmtpClient, [{
key: 'connect',
value: function connect() {
var SocketContructor = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _emailjsTcpSocket2.default;
this.socket = SocketContructor.open(this.host, this.port, {
binaryType: 'arraybuffer',
useSecureTransport: this._secureMode,
ca: this.options.ca,
tlsWorkerPath: this.options.tlsWorkerPath,
ws: this.options.ws
});
// allows certificate handling for platform w/o native tls support
// oncert is non standard so setting it might throw if the socket object is immutable
try {
this.socket.oncert = this.oncert;
} catch (E) {}
this.socket.onerror = this._onError.bind(this);
this.socket.onopen = this._onOpen.bind(this);
}
/**
* Pauses `data` events from the downstream SMTP server
*/
}, {
key: 'suspend',
value: function suspend() {
if (this.socket && this.socket.readyState === 'open') {
this.socket.suspend();
}
}
/**
* Resumes `data` events from the downstream SMTP server. Be careful of not
* resuming something that is not suspended - an error is thrown in this case
*/
}, {
key: 'resume',
value: function resume() {
if (this.socket && this.socket.readyState === 'open') {
this.socket.resume();
}
}
/**
* Sends QUIT
*/
}, {
key: 'quit',
value: function quit() {
this.logger.debug(DEBUG_TAG, 'Sending QUIT...');
this._sendCommand('QUIT');
this._currentAction = this.close;
}
/**
* Reset authentication
*
* @param {Object} [auth] Use this if you want to authenticate as another user
*/
}, {
key: 'reset',
value: function reset(auth) {
this.options.auth = auth || this.options.auth;
this.logger.debug(DEBUG_TAG, 'Sending RSET...');
this._sendCommand('RSET');
this._currentAction = this._actionRSET;
}
/**
* Closes the connection to the server
*/
}, {
key: 'close',
value: function close() {
this.logger.debug(DEBUG_TAG, 'Closing connection...');
if (this.socket && this.socket.readyState === 'open') {
this.socket.close();
} else {
this._destroy();
}
}
// Mail related methods
/**
* Initiates a new message by submitting envelope data, starting with
* `MAIL FROM:` command. Use after `onidle` event
*
* @param {Object} envelope Envelope object in the form of {from:"...", to:["..."]}
*/
}, {
key: 'useEnvelope',
value: function useEnvelope(envelope) {
this._envelope = envelope || {};
this._envelope.from = [].concat(this._envelope.from || 'anonymous@' + this.options.name)[0];
this._envelope.to = [].concat(this._envelope.to || []);
// clone the recipients array for latter manipulation
this._envelope.rcptQueue = [].concat(this._envelope.to);
this._envelope.rcptFailed = [];
this._envelope.responseQueue = [];
this._currentAction = this._actionMAIL;
this.logger.debug(DEBUG_TAG, 'Sending MAIL FROM...');
this._sendCommand('MAIL FROM:<' + this._envelope.from + '>');
}
/**
* Send ASCII data to the server. Works only in data mode (after `onready` event), ignored
* otherwise
*
* @param {String} chunk ASCII string (quoted-printable, base64 etc.) to be sent to the server
* @return {Boolean} If true, it is safe to send more data, if false, you *should* wait for the ondrain event before sending more
*/
}, {
key: 'send',
value: function send(chunk) {
// works only in data mode
if (!this._dataMode) {
// this line should never be reached but if it does,
// act like everything's normal.
return true;
}
// TODO: if the chunk is an arraybuffer, use a separate function to send the data
return this._sendString(chunk);
}
/**
* Indicates that a data stream for the socket is ended. Works only in data
* mode (after `onready` event), ignored otherwise. Use it when you are done
* with sending the mail. This method does not close the socket. Once the mail
* has been queued by the server, `ondone` and `onidle` are emitted.
*
* @param {Buffer} [chunk] Chunk of data to be sent to the server
*/
}, {
key: 'end',
value: function end(chunk) {
// works only in data mode
if (!this._dataMode) {
// this line should never be reached but if it does,
// act like everything's normal.
return true;
}
if (chunk && chunk.length) {
this.send(chunk);
}
// redirect output from the server to _actionStream
this._currentAction = this._actionStream;
// indicate that the stream has ended by sending a single dot on its own line
// if the client already closed the data with \r\n no need to do it again
if (this._lastDataBytes === '\r\n') {
this.waitDrain = this._send(new Uint8Array([0x2E, 0x0D, 0x0A]).buffer); // .\r\n
} else if (this._lastDataBytes.substr(-1) === '\r') {
this.waitDrain = this._send(new Uint8Array([0x0A, 0x2E, 0x0D, 0x0A]).buffer); // \n.\r\n
} else {
this.waitDrain = this._send(new Uint8Array([0x0D, 0x0A, 0x2E, 0x0D, 0x0A]).buffer); // \r\n.\r\n
}
// end data mode, reset the variables for extending the timeout in data mode
this._dataMode = false;
this._socketTimeoutStart = false;
this._socketTimeoutPeriod = false;
return this.waitDrain;
}
// PRIVATE METHODS
// EVENT HANDLERS FOR THE SOCKET
/**
* Connection listener that is run when the connection to the server is opened.
* Sets up different event handlers for the opened socket
*
* @event
* @param {Event} evt Event object. Not used
*/
}, {
key: '_onOpen',
value: function _onOpen(event) {
if (event && event.data && event.data.proxyHostname) {
this.options.name = event.data.proxyHostname;
}
this.socket.ondata = this._onData.bind(this);
this.socket.onclose = this._onClose.bind(this);
this.socket.ondrain = this._onDrain.bind(this);
this._parser.ondata = this._onCommand.bind(this);
this._currentAction = this._actionGreeting;
}
/**
* Data listener for chunks of data emitted by the server
*
* @event
* @param {Event} evt Event object. See `evt.data` for the chunk received
*/
}, {
key: '_onData',
value: function _onData(evt) {
clearTimeout(this._socketTimeoutTimer);
var stringPayload = new _textEncoding.TextDecoder('UTF-8').decode(new Uint8Array(evt.data));
this.logger.debug(DEBUG_TAG, 'SERVER: ' + stringPayload);
this._parser.send(stringPayload);
}
/**
* More data can be buffered in the socket, `waitDrain` is reset to false
*
* @event
* @param {Event} evt Event object. Not used
*/
}, {
key: '_onDrain',
value: function _onDrain() {
this.waitDrain = false;
this.ondrain();
}
/**
* Error handler for the socket
*
* @event
* @param {Event} evt Event object. See evt.data for the error
*/
}, {
key: '_onError',
value: function _onError(evt) {
if (evt instanceof Error && evt.message) {
this.logger.error(DEBUG_TAG, evt);
this.onerror(evt);
} else if (evt && evt.data instanceof Error) {
this.logger.error(DEBUG_TAG, evt.data);
this.onerror(evt.data);
} else {
this.logger.error(DEBUG_TAG, new Error(evt && evt.data && evt.data.message || evt.data || evt || 'Error'));
this.onerror(new Error(evt && evt.data && evt.data.message || evt.data || evt || 'Error'));
}
this.close();
}
/**
* Indicates that the socket has been closed
*
* @event
* @param {Event} evt Event object. Not used
*/
}, {
key: '_onClose',
value: function _onClose() {
this.logger.debug(DEBUG_TAG, 'Socket closed.');
this._destroy();
}
/**
* This is not a socket data handler but the handler for data emitted by the parser,
* so this data is safe to use as it is always complete (server might send partial chunks)
*
* @event
* @param {Object} command Parsed data
*/
}, {
key: '_onCommand',
value: function _onCommand(command) {
if (typeof this._currentAction === 'function') {
this._currentAction(command);
}
}
}, {
key: '_onTimeout',
value: function _onTimeout() {
// inform about the timeout and shut down
var error = new Error('Socket timed out!');
this._onError(error);
}
/**
* Ensures that the connection is closed and such
*/
}, {
key: '_destroy',
value: function _destroy() {
clearTimeout(this._socketTimeoutTimer);
if (!this.destroyed) {
this.destroyed = true;
this.onclose();
}
}
/**
* Sends a string to the socket.
*
* @param {String} chunk ASCII string (quoted-printable, base64 etc.) to be sent to the server
* @return {Boolean} If true, it is safe to send more data, if false, you *should* wait for the ondrain event before sending more
*/
}, {
key: '_sendString',
value: function _sendString(chunk) {
// escape dots
if (!this.options.disableEscaping) {
chunk = chunk.replace(/\n\./g, '\n..');
if ((this._lastDataBytes.substr(-1) === '\n' || !this._lastDataBytes) && chunk.charAt(0) === '.') {
chunk = '.' + chunk;
}
}
// Keeping eye on the last bytes sent, to see if there is a <CR><LF> sequence
// at the end which is needed to end the data stream
if (chunk.length > 2) {
this._lastDataBytes = chunk.substr(-2);
} else if (chunk.length === 1) {
this._lastDataBytes = this._lastDataBytes.substr(-1) + chunk;
}
this.logger.debug(DEBUG_TAG, 'Sending ' + chunk.length + ' bytes of payload');
// pass the chunk to the socket
this.waitDrain = this._send(new _textEncoding.TextEncoder('UTF-8').encode(chunk).buffer);
return this.waitDrain;
}
/**
* Send a string command to the server, also append \r\n if needed
*
* @param {String} str String to be sent to the server
*/
}, {
key: '_sendCommand',
value: function _sendCommand(str) {
this.waitDrain = this._send(new _textEncoding.TextEncoder('UTF-8').encode(str + (str.substr(-2) !== '\r\n' ? '\r\n' : '')).buffer);
}
}, {
key: '_send',
value: function _send(buffer) {
this._setTimeout(buffer.byteLength);
return this.socket.send(buffer);
}
}, {
key: '_setTimeout',
value: function _setTimeout(byteLength) {
var prolongPeriod = Math.floor(byteLength * this.timeoutSocketMultiplier);
var timeout;
if (this._dataMode) {
// we're in data mode, so we count only one timeout that get extended for every send().
var now = Date.now();
// the old timeout start time
this._socketTimeoutStart = this._socketTimeoutStart || now;
// the old timeout period, normalized to a minimum of TIMEOUT_SOCKET_LOWER_BOUND
this._socketTimeoutPeriod = (this._socketTimeoutPeriod || this.timeoutSocketLowerBound) + prolongPeriod;
// the new timeout is the delta between the new firing time (= timeout period + timeout start time) and now
timeout = this._socketTimeoutStart + this._socketTimeoutPeriod - now;
} else {
// set new timout
timeout = this.timeoutSocketLowerBound + prolongPeriod;
}
clearTimeout(this._socketTimeoutTimer); // clear pending timeouts
this._socketTimeoutTimer = setTimeout(this._onTimeout.bind(this), timeout); // arm the next timeout
}
/**
* Intitiate authentication sequence if needed
*/
}, {
key: '_authenticateUser',
value: function _authenticateUser() {
if (!this.options.auth) {
// no need to authenticate, at least no data given
this._currentAction = this._actionIdle;
this.onidle(); // ready to take orders
return;
}
var auth;
if (!this.options.authMethod && this.options.auth.xoauth2) {
this.options.authMethod = 'XOAUTH2';
}
if (this.options.authMethod) {
auth = this.options.authMethod.toUpperCase().trim();
} else {
// use first supported
auth = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
}
switch (auth) {
case 'LOGIN':
// LOGIN is a 3 step authentication process
// C: AUTH LOGIN
// C: BASE64(USER)
// C: BASE64(PASS)
this.logger.debug(DEBUG_TAG, 'Authentication via AUTH LOGIN');
this._currentAction = this._actionAUTH_LOGIN_USER;
this._sendCommand('AUTH LOGIN');
return;
case 'PLAIN':
// AUTH PLAIN is a 1 step authentication process
// C: AUTH PLAIN BASE64(\0 USER \0 PASS)
this.logger.debug(DEBUG_TAG, 'Authentication via AUTH PLAIN');
this._currentAction = this._actionAUTHComplete;
this._sendCommand(
// convert to BASE64
'AUTH PLAIN ' + (0, _emailjsBase.encode)(
// this.options.auth.user+'\u0000'+
'\0' + // skip authorization identity as it causes problems with some servers
this.options.auth.user + '\0' + this.options.auth.pass));
return;
case 'XOAUTH2':
// See https://developers.google.com/gmail/xoauth2_protocol#smtp_protocol_exchange
this.logger.debug(DEBUG_TAG, 'Authentication via AUTH XOAUTH2');
this._currentAction = this._actionAUTH_XOAUTH2;
this._sendCommand('AUTH XOAUTH2 ' + this._buildXOAuth2Token(this.options.auth.user, this.options.auth.xoauth2));
return;
}
this._onError(new Error('Unknown authentication method ' + auth));
}
// ACTIONS FOR RESPONSES FROM THE SMTP SERVER
/**
* Initial response from the server, must have a status 220
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionGreeting',
value: function _actionGreeting(command) {
if (command.statusCode !== 220) {
this._onError(new Error('Invalid greeting: ' + command.data));
return;
}
if (this.options.lmtp) {
this.logger.debug(DEBUG_TAG, 'Sending LHLO ' + this.options.name);
this._currentAction = this._actionLHLO;
this._sendCommand('LHLO ' + this.options.name);
} else {
this.logger.debug(DEBUG_TAG, 'Sending EHLO ' + this.options.name);
this._currentAction = this._actionEHLO;
this._sendCommand('EHLO ' + this.options.name);
}
}
/**
* Response to LHLO
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionLHLO',
value: function _actionLHLO(command) {
if (!command.success) {
this.logger.error(DEBUG_TAG, 'LHLO not successful');
this._onError(new Error(command.data));
return;
}
// Process as EHLO response
this._actionEHLO(command);
}
/**
* Response to EHLO. If the response is an error, try HELO instead
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionEHLO',
value: function _actionEHLO(command) {
var match;
if (!command.success) {
if (!this._secureMode && this.options.requireTLS) {
var errMsg = 'STARTTLS not supported without EHLO';
this.logger.error(DEBUG_TAG, errMsg);
this._onError(new Error(errMsg));
return;
}
// Try HELO instead
this.logger.warn(DEBUG_TAG, 'EHLO not successful, trying HELO ' + this.options.name);
this._currentAction = this._actionHELO;
this._sendCommand('HELO ' + this.options.name);
return;
}
// Detect if the server supports PLAIN auth
if (command.line.match(/AUTH(?:\s+[^\n]*\s+|\s+)PLAIN/i)) {
this.logger.debug(DEBUG_TAG, 'Server supports AUTH PLAIN');
this._supportedAuth.push('PLAIN');
}
// Detect if the server supports LOGIN auth
if (command.line.match(/AUTH(?:\s+[^\n]*\s+|\s+)LOGIN/i)) {
this.logger.debug(DEBUG_TAG, 'Server supports AUTH LOGIN');
this._supportedAuth.push('LOGIN');
}
// Detect if the server supports XOAUTH2 auth
if (command.line.match(/AUTH(?:\s+[^\n]*\s+|\s+)XOAUTH2/i)) {
this.logger.debug(DEBUG_TAG, 'Server supports AUTH XOAUTH2');
this._supportedAuth.push('XOAUTH2');
}
// Detect maximum allowed message size
if ((match = command.line.match(/SIZE (\d+)/i)) && Number(match[1])) {
var maxAllowedSize = Number(match[1]);
this.logger.debug(DEBUG_TAG, 'Maximum allowd message size: ' + maxAllowedSize);
}
// Detect if the server supports STARTTLS
if (!this._secureMode) {
if (command.line.match(/[ -]STARTTLS\s?$/mi) && !this.options.ignoreTLS || !!this.options.requireTLS) {
this._currentAction = this._actionSTARTTLS;
this.logger.debug(DEBUG_TAG, 'Sending STARTTLS');
this._sendCommand('STARTTLS');
return;
}
}
this._authenticateUser();
}
/**
* Handles server response for STARTTLS command. If there's an error
* try HELO instead, otherwise initiate TLS upgrade. If the upgrade
* succeedes restart the EHLO
*
* @param {String} str Message from the server
*/
}, {
key: '_actionSTARTTLS',
value: function _actionSTARTTLS(command) {
if (!command.success) {
this.logger.error(DEBUG_TAG, 'STARTTLS not successful');
this._onError(new Error(command.data));
return;
}
this._secureMode = true;
this.socket.upgradeToSecure();
// restart protocol flow
this._currentAction = this._actionEHLO;
this._sendCommand('EHLO ' + this.options.name);
}
/**
* Response to HELO
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionHELO',
value: function _actionHELO(command) {
if (!command.success) {
this.logger.error(DEBUG_TAG, 'HELO not successful');
this._onError(new Error(command.data));
return;
}
this._authenticateUser();
}
/**
* Response to AUTH LOGIN, if successful expects base64 encoded username
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionAUTH_LOGIN_USER',
value: function _actionAUTH_LOGIN_USER(command) {
if (command.statusCode !== 334 || command.data !== 'VXNlcm5hbWU6') {
this.logger.error(DEBUG_TAG, 'AUTH LOGIN USER not successful: ' + command.data);
this._onError(new Error('Invalid login sequence while waiting for "334 VXNlcm5hbWU6 ": ' + command.data));
return;
}
this.logger.debug(DEBUG_TAG, 'AUTH LOGIN USER successful');
this._currentAction = this._actionAUTH_LOGIN_PASS;
this._sendCommand((0, _emailjsBase.encode)(this.options.auth.user));
}
/**
* Response to AUTH LOGIN username, if successful expects base64 encoded password
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionAUTH_LOGIN_PASS',
value: function _actionAUTH_LOGIN_PASS(command) {
if (command.statusCode !== 334 || command.data !== 'UGFzc3dvcmQ6') {
this.logger.error(DEBUG_TAG, 'AUTH LOGIN PASS not successful: ' + command.data);
this._onError(new Error('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6 ": ' + command.data));
return;
}
this.logger.debug(DEBUG_TAG, 'AUTH LOGIN PASS successful');
this._currentAction = this._actionAUTHComplete;
this._sendCommand((0, _emailjsBase.encode)(this.options.auth.pass));
}
/**
* Response to AUTH XOAUTH2 token, if error occurs send empty response
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionAUTH_XOAUTH2',
value: function _actionAUTH_XOAUTH2(command) {
if (!command.success) {
this.logger.warn(DEBUG_TAG, 'Error during AUTH XOAUTH2, sending empty response');
this._sendCommand('');
this._currentAction = this._actionAUTHComplete;
} else {
this._actionAUTHComplete(command);
}
}
/**
* Checks if authentication succeeded or not. If successfully authenticated
* emit `idle` to indicate that an e-mail can be sent using this connection
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionAUTHComplete',
value: function _actionAUTHComplete(command) {
if (!command.success) {
this.logger.debug(DEBUG_TAG, 'Authentication failed: ' + command.data);
this._onError(new Error(command.data));
return;
}
this.logger.debug(DEBUG_TAG, 'Authentication successful.');
this._authenticatedAs = this.options.auth.user;
this._currentAction = this._actionIdle;
this.onidle(); // ready to take orders
}
/**
* Used when the connection is idle and the server emits timeout
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionIdle',
value: function _actionIdle(command) {
if (command.statusCode > 300) {
this._onError(new Error(command.line));
return;
}
this._onError(new Error(command.data));
}
/**
* Response to MAIL FROM command. Proceed to defining RCPT TO list if successful
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionMAIL',
value: function _actionMAIL(command) {
if (!command.success) {
this.logger.debug(DEBUG_TAG, 'MAIL FROM unsuccessful: ' + command.data);
this._onError(new Error(command.data));
return;
}
if (!this._envelope.rcptQueue.length) {
this._onError(new Error('Can\'t send mail - no recipients defined'));
} else {
this.logger.debug(DEBUG_TAG, 'MAIL FROM successful, proceeding with ' + this._envelope.rcptQueue.length + ' recipients');
this.logger.debug(DEBUG_TAG, 'Adding recipient...');
this._envelope.curRecipient = this._envelope.rcptQueue.shift();
this._currentAction = this._actionRCPT;
this._sendCommand('RCPT TO:<' + this._envelope.curRecipient + '>');
}
}
/**
* Response to a RCPT TO command. If the command is unsuccessful, try the next one,
* as this might be related only to the current recipient, not a global error, so
* the following recipients might still be valid
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionRCPT',
value: function _actionRCPT(command) {
if (!command.success) {
this.logger.warn(DEBUG_TAG, 'RCPT TO failed for: ' + this._envelope.curRecipient);
// this is a soft error
this._envelope.rcptFailed.push(this._envelope.curRecipient);
} else {
this._envelope.responseQueue.push(this._envelope.curRecipient);
}
if (!this._envelope.rcptQueue.length) {
if (this._envelope.rcptFailed.length < this._envelope.to.length) {
this._currentAction = this._actionDATA;
this.logger.debug(DEBUG_TAG, 'RCPT TO done, proceeding with payload');
this._sendCommand('DATA');
} else {
this._onError(new Error('Can\'t send mail - all recipients were rejected'));
this._currentAction = this._actionIdle;
}
} else {
this.logger.debug(DEBUG_TAG, 'Adding recipient...');
this._envelope.curRecipient = this._envelope.rcptQueue.shift();
this._currentAction = this._actionRCPT;
this._sendCommand('RCPT TO:<' + this._envelope.curRecipient + '>');
}
}
/**
* Response to the RSET command. If successful, clear the current authentication
* information and reauthenticate.
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionRSET',
value: function _actionRSET(command) {
if (!command.success) {
this.logger.error(DEBUG_TAG, 'RSET unsuccessful ' + command.data);
this._onError(new Error(command.data));
return;
}
this._authenticatedAs = null;
this._authenticateUser();
}
/**
* Response to the DATA command. Server is now waiting for a message, so emit `onready`
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionDATA',
value: function _actionDATA(command) {
// response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
// some servers might use 250 instead
if ([250, 354].indexOf(command.statusCode) < 0) {
this.logger.error(DEBUG_TAG, 'DATA unsuccessful ' + command.data);
this._onError(new Error(command.data));
return;
}
this._dataMode = true;
this._currentAction = this._actionIdle;
this.onready(this._envelope.rcptFailed);
}
/**
* Response from the server, once the message stream has ended with <CR><LF>.<CR><LF>
* Emits `ondone`.
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
}, {
key: '_actionStream',
value: function _actionStream(command) {
var rcpt;
if (this.options.lmtp) {
// LMTP returns a response code for *every* successfully set recipient
// For every recipient the message might succeed or fail individually
rcpt = this._envelope.responseQueue.shift();
if (!command.success) {
this.logger.error(DEBUG_TAG, 'Local delivery to ' + rcpt + ' failed.');
this._envelope.rcptFailed.push(rcpt);
} else {
this.logger.error(DEBUG_TAG, 'Local delivery to ' + rcpt + ' succeeded.');
}
if (this._envelope.responseQueue.length) {
this._currentAction = this._actionStream;
return;
}
this._currentAction = this._actionIdle;
this.ondone(true);
} else {
// For SMTP the message either fails or succeeds, there is no information
// about individual recipients
if (!command.success) {
this.logger.error(DEBUG_TAG, 'Message sending failed.');
} else {
this.logger.debug(DEBUG_TAG, 'Message sent successfully.');
}
this._currentAction = this._actionIdle;
this.ondone(!!command.success);
}
// If the client wanted to do something else (eg. to quit), do not force idle
if (this._currentAction === this._actionIdle) {
// Waiting for new connections
this.logger.debug(DEBUG_TAG, 'Idling while waiting for new connections...');
this.onidle();
}
}
/**
* Builds a login token for XOAUTH2 authentication command
*
* @param {String} user E-mail address of the user
* @param {String} token Valid access token for the user
* @return {String} Base64 formatted login token
*/
}, {
key: '_buildXOAuth2Token',
value: function _buildXOAuth2Token(user, token) {
var authData = ['user=' + (user || ''), 'auth=Bearer ' + token, '', ''];
// base64("user={User}\x00auth=Bearer {Token}\x00\x00")
return (0, _emailjsBase.encode)(authData.join('\x01'));
}
}, {
key: 'createLogger',
value: function createLogger() {
var _this = this;
var creator = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _logger2.default;
var logger = creator((this.options.auth || {}).user || '', this.host);
this.logLevel = this.LOG_LEVEL_ALL;
this.logger = {
debug: function debug() {
for (var _len = arguments.length, msgs = Array(_len), _key = 0; _key < _len; _key++) {
msgs[_key] = arguments[_key];
}
if (_common.LOG_LEVEL_DEBUG >= _this.logLevel) {
logger.debug(msgs);
}
},
info: function info() {
for (var _len2 = arguments.length, msgs = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
msgs[_key2] = arguments[_key2];
}
if (_common.LOG_LEVEL_INFO >= _this.logLevel) {
logger.info(msgs);
}
},
warn: function warn() {
for (var _len3 = arguments.length, msgs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
msgs[_key3] = arguments[_key3];
}
if (_common.LOG_LEVEL_WARN >= _this.logLevel) {
logger.warn(msgs);
}
},
error: function error() {
for (var _len4 = arguments.length, msgs = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
msgs[_key4] = arguments[_key4];
}
if (_common.LOG_LEVEL_ERROR >= _this.logLevel) {
logger.error(msgs);
}
}
};
}
}]);
return SmtpClient;
}();
exports.default = SmtpClient;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9jbGllbnQuanMiXSwibmFtZXMiOlsiREVCVUdfVEFHIiwiVElNRU9VVF9TT0NLRVRfTE9XRVJfQk9VTkQiLCJUSU1FT1VUX1NPQ0tFVF9NVUxUSVBMSUVSIiwiU210cENsaWVudCIsImhvc3QiLCJwb3J0Iiwib3B0aW9ucyIsInRpbWVvdXRTb2NrZXRMb3dlckJvdW5kIiwidGltZW91dFNvY2tldE11bHRpcGxpZXIiLCJ1c2VTZWN1cmVUcmFuc3BvcnQiLCJhdXRoIiwibmFtZSIsInNvY2tldCIsImRlc3Ryb3llZCIsIndhaXREcmFpbiIsIl9wYXJzZXIiLCJTbXRwQ2xpZW50UmVzcG9uc2VQYXJzZXIiLCJfYXV0aGVudGljYXRlZEFzIiwiX3N1cHBvcnRlZEF1dGgiLCJfZGF0YU1vZGUiLCJfbGFzdERhdGFCeXRlcyIsIl9lbnZlbG9wZSIsIl9jdXJyZW50QWN0aW9uIiwiX3NlY3VyZU1vZGUiLCJfc29ja2V0VGltZW91dFRpbWVyIiwiX3NvY2tldFRpbWVvdXRTdGFydCIsIl9zb2NrZXRUaW1lb3V0UGVyaW9kIiwiY3JlYXRlTG9nZ2VyIiwib25lcnJvciIsImUiLCJvbmRyYWluIiwib25jbG9zZSIsIm9uaWRsZSIsIm9ucmVhZHkiLCJmYWlsZWRSZWNpcGllbnRzIiwib25kb25lIiwic3VjY2VzcyIsIlNvY2tldENvbnRydWN0b3IiLCJUQ1BTb2NrZXQiLCJvcGVuIiwiYmluYXJ5VHlwZSIsImNhIiwidGxzV29ya2VyUGF0aCIsIndzIiwib25jZXJ0IiwiRSIsIl9vbkVycm9yIiwiYmluZCIsIm9ub3BlbiIsIl9vbk9wZW4iLCJyZWFkeVN0YXRlIiwic3VzcGVuZCIsInJlc3VtZSIsImxvZ2dlciIsImRlYnVnIiwiX3NlbmRDb21tYW5kIiwiY2xvc2UiLCJfYWN0aW9uUlNFVCIsIl9kZXN0cm95IiwiZW52ZWxvcGUiLCJmcm9tIiwiY29uY2F0IiwidG8iLCJyY3B0UXVldWUiLCJyY3B0RmFpbGVkIiwicmVzcG9uc2VRdWV1ZSIsIl9hY3Rpb25NQUlMIiwiY2h1bmsiLCJfc2VuZFN0cmluZyIsImxlbmd0aCIsInNlbmQiLCJfYWN0aW9uU3RyZWFtIiwiX3NlbmQiLCJVaW50OEFycmF5IiwiYnVmZmVyIiwic3Vic3RyIiwiZXZlbnQiLCJkYXRhIiwicHJveHlIb3N0bmFtZSIsIm9uZGF0YSIsIl9vbkRhdGEiLCJfb25DbG9zZSIsIl9vbkRyYWluIiwiX29uQ29tbWFuZCIsIl9hY3Rpb25HcmVldGluZyIsImV2dCIsImNsZWFyVGltZW91dCIsInN0cmluZ1BheWxvYWQiLCJUZXh0RGVjb2RlciIsImRlY29kZSIsIkVycm9yIiwibWVzc2FnZSIsImVycm9yIiwiY29tbWFuZCIsImRpc2FibGVFc2NhcGluZyIsInJlcGxhY2UiLCJjaGFyQXQiLCJUZXh0RW5jb2RlciIsImVuY29kZSIsInN0ciIsIl9zZXRUaW1lb3V0IiwiYnl0ZUxlbmd0aCIsInByb2xvbmdQZXJpb2QiLCJNYXRoIiwiZmxvb3IiLCJ0aW1lb3V0Iiwibm93IiwiRGF0ZSIsInNldFRpbWVvdXQiLCJfb25UaW1lb3V0IiwiX2FjdGlvbklkbGUiLCJhdXRoTWV0aG9kIiwieG9hdXRoMiIsInRvVXBwZXJDYXNlIiwidHJpbSIsIl9hY3Rpb25BVVRIX0xPR0lOX1VTRVIiLCJfYWN0aW9uQVVUSENvbXBsZXRlIiwidXNlciIsInBhc3MiLCJfYWN0aW9uQVVUSF9YT0FVVEgyIiwiX2J1aWxkWE9BdXRoMlRva2VuIiwic3RhdHVzQ29kZSIsImxtdHAiLCJfYWN0aW9uTEhMTyIsIl9hY3Rpb25FSExPIiwibWF0Y2giLCJyZXF1aXJlVExTIiwiZXJyTXNnIiwid2FybiIsIl9hY3Rpb25IRUxPIiwibGluZSIsInB1c2giLCJOdW1iZXIiLCJtYXhBbGxvd2VkU2l6ZSIsImlnbm9yZVRMUyIsIl9hY3Rpb25TVEFSVFRMUyIsIl9hdXRoZW50aWNhdGVVc2VyIiwidXBncmFkZVRvU2VjdXJlIiwiX2FjdGlvbkFVVEhfTE9HSU5fUEFTUyIsImN1clJlY2lwaWVudCIsInNoaWZ0IiwiX2FjdGlvblJDUFQiLCJfYWN0aW9uREFUQSIsImluZGV4T2YiLCJyY3B0IiwidG9rZW4iLCJhdXRoRGF0YSIsImpvaW4iLCJjcmVhdG9yIiwiY3JlYXRlRGVmYXVsdExvZ2dlciIsImxvZ0xldmVsIiwiTE9HX0xFVkVMX0FMTCIsIm1zZ3MiLCJMT0dfTEVWRUxfREVCVUciLCJpbmZvIiwiTE9HX0xFVkVMX0lORk8iLCJMT0dfTEVWRUxfV0FSTiIsIkxPR19MRVZFTF9FUlJPUiJdLCJtYXBwaW5ncyI6Ijs7Ozs7O3FqQkFBQTs7QUFFQTs7QUFDQTs7OztBQUNBOztBQUNBOzs7O0FBQ0E7Ozs7QUFDQTs7Ozs7O0FBT0EsSUFBSUEsWUFBWSxhQUFoQjs7QUFFQTs7O0FBR0EsSUFBTUMsNkJBQTZCLEtBQW5DOztBQUVBOzs7Ozs7O0FBT0EsSUFBTUMsNEJBQTRCLEdBQWxDOztJQUVNQyxVO0FBQ0o7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQWtCQSxzQkFBYUMsSUFBYixFQUFtQkMsSUFBbkIsRUFBdUM7QUFBQSxRQUFkQyxPQUFjLHVFQUFKLEVBQUk7O0FBQUE7O0FBQ3JDLFNBQUtBLE9BQUwsR0FBZUEsT0FBZjs7QUFFQSxTQUFLQyx1QkFBTCxHQUErQk4sMEJBQS9CO0FBQ0EsU0FBS08sdUJBQUwsR0FBK0JOLHlCQUEvQjs7QUFFQSxTQUFLRyxJQUFMLEdBQVlBLFNBQVMsS0FBS0MsT0FBTCxDQUFhRyxrQkFBYixHQUFrQyxHQUFsQyxHQUF3QyxFQUFqRCxDQUFaO0FBQ0EsU0FBS0wsSUFBTCxHQUFZQSxRQUFRLFdBQXBCOztBQUVBOzs7OztBQUtBLFNBQUtFLE9BQUwsQ0FBYUcsa0JBQWIsR0FBa0Msd0JBQXdCLEtBQUtILE9BQTdCLEdBQXVDLENBQUMsQ0FBQyxLQUFLQSxPQUFMLENBQWFHLGtCQUF0RCxHQUEyRSxLQUFLSixJQUFMLEtBQWMsR0FBM0g7O0FBRUEsU0FBS0MsT0FBTCxDQUFhSSxJQUFiLEdBQW9CLEtBQUtKLE9BQUwsQ0FBYUksSUFBYixJQUFxQixLQUF6QyxDQWhCcUMsQ0FnQlU7QUFDL0MsU0FBS0osT0FB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