UNPKG

emailjs-smtp-client

Version:

SMTP Client allows you to connect to an SMTP server in JS.

1,052 lines (884 loc) 106 kB
'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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