UNPKG

emailjs-imap-client

Version:
1,233 lines (1,008 loc) 117 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.DEFAULT_CLIENT_ID = exports.STATE_LOGOUT = exports.STATE_SELECTED = exports.STATE_AUTHENTICATED = exports.STATE_NOT_AUTHENTICATED = exports.STATE_CONNECTING = exports.TIMEOUT_IDLE = exports.TIMEOUT_NOOP = exports.TIMEOUT_CONNECTION = void 0; var _ramda = require("ramda"); var _emailjsUtf = require("emailjs-utf7"); var _commandParser = require("./command-parser"); var _commandBuilder = require("./command-builder"); var _logger = _interopRequireDefault(require("./logger")); var _imap = _interopRequireDefault(require("./imap")); var _common = require("./common"); var _specialUse = require("./special-use"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } const TIMEOUT_CONNECTION = 90 * 1000; // Milliseconds to wait for the IMAP greeting from the server exports.TIMEOUT_CONNECTION = TIMEOUT_CONNECTION; const TIMEOUT_NOOP = 60 * 1000; // Milliseconds between NOOP commands while idling exports.TIMEOUT_NOOP = TIMEOUT_NOOP; const TIMEOUT_IDLE = 60 * 1000; // Milliseconds until IDLE command is cancelled exports.TIMEOUT_IDLE = TIMEOUT_IDLE; const STATE_CONNECTING = 1; exports.STATE_CONNECTING = STATE_CONNECTING; const STATE_NOT_AUTHENTICATED = 2; exports.STATE_NOT_AUTHENTICATED = STATE_NOT_AUTHENTICATED; const STATE_AUTHENTICATED = 3; exports.STATE_AUTHENTICATED = STATE_AUTHENTICATED; const STATE_SELECTED = 4; exports.STATE_SELECTED = STATE_SELECTED; const STATE_LOGOUT = 5; exports.STATE_LOGOUT = STATE_LOGOUT; const DEFAULT_CLIENT_ID = { name: 'emailjs-imap-client' }; /** * emailjs IMAP client * * @constructor * * @param {String} [host='localhost'] Hostname to conenct to * @param {Number} [port=143] Port number to connect to * @param {Object} [options] Optional options object */ exports.DEFAULT_CLIENT_ID = DEFAULT_CLIENT_ID; class Client { constructor(host, port, options = {}) { this.timeoutConnection = TIMEOUT_CONNECTION; this.timeoutNoop = TIMEOUT_NOOP; this.timeoutIdle = TIMEOUT_IDLE; this.serverId = false; // RFC 2971 Server ID as key value pairs // Event placeholders this.oncert = null; this.onupdate = null; this.onselectmailbox = null; this.onclosemailbox = null; this._host = host; this._clientId = (0, _ramda.propOr)(DEFAULT_CLIENT_ID, 'id', options); this._state = false; // Current state this._authenticated = false; // Is the connection authenticated this._capability = []; // List of extensions the server supports this._selectedMailbox = false; // Selected mailbox this._enteredIdle = false; this._idleTimeout = false; this._enableCompression = !!options.enableCompression; this._auth = options.auth; this._requireTLS = !!options.requireTLS; this._ignoreTLS = !!options.ignoreTLS; this.client = new _imap.default(host, port, options); // IMAP client object // Event Handlers this.client.onerror = this._onError.bind(this); this.client.oncert = cert => this.oncert && this.oncert(cert); // allows certificate handling for platforms w/o native tls support this.client.onidle = () => this._onIdle(); // start idling // Default handlers for untagged responses this.client.setHandler('capability', response => this._untaggedCapabilityHandler(response)); // capability updates this.client.setHandler('ok', response => this._untaggedOkHandler(response)); // notifications this.client.setHandler('exists', response => this._untaggedExistsHandler(response)); // message count has changed this.client.setHandler('expunge', response => this._untaggedExpungeHandler(response)); // message has been deleted this.client.setHandler('fetch', response => this._untaggedFetchHandler(response)); // message has been updated (eg. flag change) // Activate logging this.createLogger(); this.logLevel = (0, _ramda.propOr)(_common.LOG_LEVEL_ALL, 'logLevel', options); } /** * Called if the lower-level ImapClient has encountered an unrecoverable * error during operation. Cleans up and propagates the error upwards. */ _onError(err) { // make sure no idle timeout is pending anymore clearTimeout(this._idleTimeout); // propagate the error upwards this.onerror && this.onerror(err); } // // // PUBLIC API // // /** * Initiate connection and login to the IMAP server * * @returns {Promise} Promise when login procedure is complete */ connect() { var _this = this; return _asyncToGenerator(function* () { try { yield _this.openConnection(); yield _this.upgradeConnection(); try { yield _this.updateId(_this._clientId); } catch (err) { _this.logger.warn('Failed to update server id!', err.message); } yield _this.login(_this._auth); yield _this.compressConnection(); _this.logger.debug('Connection established, ready to roll!'); _this.client.onerror = _this._onError.bind(_this); } catch (err) { _this.logger.error('Could not connect to server', err); _this.close(err); // we don't really care whether this works or not throw err; } })(); } /** * Initiate connection to the IMAP server * * @returns {Promise} capability of server without login */ openConnection() { return new Promise((resolve, reject) => { const connectionTimeout = setTimeout(() => reject(new Error('Timeout connecting to server')), this.timeoutConnection); this.logger.debug('Connecting to', this.client.host, ':', this.client.port); this._changeState(STATE_CONNECTING); this.client.connect().then(() => { this.logger.debug('Socket opened, waiting for greeting from the server...'); this.client.onready = () => { clearTimeout(connectionTimeout); this._changeState(STATE_NOT_AUTHENTICATED); this.updateCapability().then(() => resolve(this._capability)); }; this.client.onerror = err => { clearTimeout(connectionTimeout); reject(err); }; }).catch(reject); }); } /** * Logout * * Send LOGOUT, to which the server responds by closing the connection. * Use is discouraged if network status is unclear! If networks status is * unclear, please use #close instead! * * LOGOUT details: * https://tools.ietf.org/html/rfc3501#section-6.1.3 * * @returns {Promise} Resolves when server has closed the connection */ logout() { var _this2 = this; return _asyncToGenerator(function* () { _this2._changeState(STATE_LOGOUT); _this2.logger.debug('Logging out...'); yield _this2.client.logout(); clearTimeout(_this2._idleTimeout); })(); } /** * Force-closes the current connection by closing the TCP socket. * * @returns {Promise} Resolves when socket is closed */ close(err) { var _this3 = this; return _asyncToGenerator(function* () { _this3._changeState(STATE_LOGOUT); clearTimeout(_this3._idleTimeout); _this3.logger.debug('Closing connection...'); yield _this3.client.close(err); clearTimeout(_this3._idleTimeout); })(); } /** * Runs ID command, parses ID response, sets this.serverId * * ID details: * http://tools.ietf.org/html/rfc2971 * * @param {Object} id ID as JSON object. See http://tools.ietf.org/html/rfc2971#section-3.3 for possible values * @returns {Promise} Resolves when response has been parsed */ updateId(id) { var _this4 = this; return _asyncToGenerator(function* () { if (_this4._capability.indexOf('ID') < 0) return; _this4.logger.debug('Updating id...'); const command = 'ID'; const attributes = id ? [(0, _ramda.flatten)(Object.entries(id))] : [null]; const response = yield _this4.exec({ command, attributes }, 'ID'); const list = (0, _ramda.flatten)((0, _ramda.pathOr)([], ['payload', 'ID', '0', 'attributes', '0'], response).map(Object.values)); const keys = list.filter((_, i) => i % 2 === 0); const values = list.filter((_, i) => i % 2 === 1); _this4.serverId = (0, _ramda.fromPairs)((0, _ramda.zip)(keys, values)); _this4.logger.debug('Server id updated!', _this4.serverId); })(); } _shouldSelectMailbox(path, ctx) { if (!ctx) { return true; } const previousSelect = this.client.getPreviouslyQueued(['SELECT', 'EXAMINE'], ctx); if (previousSelect && previousSelect.request.attributes) { const pathAttribute = previousSelect.request.attributes.find(attribute => attribute.type === 'STRING'); if (pathAttribute) { return pathAttribute.value !== path; } } return this._selectedMailbox !== path; } /** * Runs SELECT or EXAMINE to open a mailbox * * SELECT details: * http://tools.ietf.org/html/rfc3501#section-6.3.1 * EXAMINE details: * http://tools.ietf.org/html/rfc3501#section-6.3.2 * * @param {String} path Full path to mailbox * @param {Object} [options] Options object * @returns {Promise} Promise with information about the selected mailbox */ selectMailbox(path, options = {}) { var _this5 = this; return _asyncToGenerator(function* () { const query = { command: options.readOnly ? 'EXAMINE' : 'SELECT', attributes: [{ type: 'STRING', value: path }] }; if (options.condstore && _this5._capability.indexOf('CONDSTORE') >= 0) { query.attributes.push([{ type: 'ATOM', value: 'CONDSTORE' }]); } _this5.logger.debug('Opening', path, '...'); const response = yield _this5.exec(query, ['EXISTS', 'FLAGS', 'OK'], { ctx: options.ctx }); const mailboxInfo = (0, _commandParser.parseSELECT)(response); _this5._changeState(STATE_SELECTED); if (_this5._selectedMailbox !== path && _this5.onclosemailbox) { yield _this5.onclosemailbox(_this5._selectedMailbox); } _this5._selectedMailbox = path; if (_this5.onselectmailbox) { yield _this5.onselectmailbox(path, mailboxInfo); } return mailboxInfo; })(); } /** * Runs NAMESPACE command * * NAMESPACE details: * https://tools.ietf.org/html/rfc2342 * * @returns {Promise} Promise with namespace object */ listNamespaces() { var _this6 = this; return _asyncToGenerator(function* () { if (_this6._capability.indexOf('NAMESPACE') < 0) return false; _this6.logger.debug('Listing namespaces...'); const response = yield _this6.exec('NAMESPACE', 'NAMESPACE'); return (0, _commandParser.parseNAMESPACE)(response); })(); } /** * Runs LIST and LSUB commands. Retrieves a tree of available mailboxes * * LIST details: * http://tools.ietf.org/html/rfc3501#section-6.3.8 * LSUB details: * http://tools.ietf.org/html/rfc3501#section-6.3.9 * * @returns {Promise} Promise with list of mailboxes */ listMailboxes() { var _this7 = this; return _asyncToGenerator(function* () { const tree = { root: true, children: [] }; _this7.logger.debug('Listing mailboxes...'); const listResponse = yield _this7.exec({ command: 'LIST', attributes: ['', '*'] }, 'LIST'); const list = (0, _ramda.pathOr)([], ['payload', 'LIST'], listResponse); list.forEach(item => { const attr = (0, _ramda.propOr)([], 'attributes', item); if (attr.length < 3) return; const path = (0, _ramda.pathOr)('', ['2', 'value'], attr); const delim = (0, _ramda.pathOr)('/', ['1', 'value'], attr); const branch = _this7._ensurePath(tree, path, delim); branch.flags = (0, _ramda.propOr)([], '0', attr).map(({ value }) => value || ''); branch.listed = true; (0, _specialUse.checkSpecialUse)(branch); }); const lsubResponse = yield _this7.exec({ command: 'LSUB', attributes: ['', '*'] }, 'LSUB'); const lsub = (0, _ramda.pathOr)([], ['payload', 'LSUB'], lsubResponse); lsub.forEach(item => { const attr = (0, _ramda.propOr)([], 'attributes', item); if (attr.length < 3) return; const path = (0, _ramda.pathOr)('', ['2', 'value'], attr); const delim = (0, _ramda.pathOr)('/', ['1', 'value'], attr); const branch = _this7._ensurePath(tree, path, delim); (0, _ramda.propOr)([], '0', attr).map((flag = '') => { branch.flags = (0, _ramda.union)(branch.flags, [flag]); }); branch.subscribed = true; }); return tree; })(); } /** * Create a mailbox with the given path. * * CREATE details: * http://tools.ietf.org/html/rfc3501#section-6.3.3 * * @param {String} path * The path of the mailbox you would like to create. This method will * handle utf7 encoding for you. * @returns {Promise} * Promise resolves if mailbox was created. * In the event the server says NO [ALREADYEXISTS], we treat that as success. */ createMailbox(path) { var _this8 = this; return _asyncToGenerator(function* () { _this8.logger.debug('Creating mailbox', path, '...'); try { yield _this8.exec({ command: 'CREATE', attributes: [(0, _emailjsUtf.imapEncode)(path)] }); } catch (err) { if (err && err.code === 'ALREADYEXISTS') { return; } throw err; } })(); } /** * Delete a mailbox with the given path. * * DELETE details: * https://tools.ietf.org/html/rfc3501#section-6.3.4 * * @param {String} path * The path of the mailbox you would like to delete. This method will * handle utf7 encoding for you. * @returns {Promise} * Promise resolves if mailbox was deleted. */ deleteMailbox(path) { this.logger.debug('Deleting mailbox', path, '...'); return this.exec({ command: 'DELETE', attributes: [(0, _emailjsUtf.imapEncode)(path)] }); } /** * Runs FETCH command * * FETCH details: * http://tools.ietf.org/html/rfc3501#section-6.4.5 * CHANGEDSINCE details: * https://tools.ietf.org/html/rfc4551#section-3.3 * * @param {String} path The path for the mailbox which should be selected for the command. Selects mailbox if necessary * @param {String} sequence Sequence set, eg 1:* for all messages * @param {Object} [items] Message data item names or macro * @param {Object} [options] Query modifiers * @returns {Promise} Promise with the fetched message info */ listMessages(path, sequence, items = [{ fast: true }], options = {}) { var _this9 = this; return _asyncToGenerator(function* () { _this9.logger.debug('Fetching messages', sequence, 'from', path, '...'); const command = (0, _commandBuilder.buildFETCHCommand)(sequence, items, options); const response = yield _this9.exec(command, 'FETCH', { precheck: ctx => _this9._shouldSelectMailbox(path, ctx) ? _this9.selectMailbox(path, { ctx }) : Promise.resolve() }); return (0, _commandParser.parseFETCH)(response); })(); } /** * Runs SEARCH command * * SEARCH details: * http://tools.ietf.org/html/rfc3501#section-6.4.4 * * @param {String} path The path for the mailbox which should be selected for the command. Selects mailbox if necessary * @param {Object} query Search terms * @param {Object} [options] Query modifiers * @returns {Promise} Promise with the array of matching seq. or uid numbers */ search(path, query, options = {}) { var _this10 = this; return _asyncToGenerator(function* () { _this10.logger.debug('Searching in', path, '...'); const command = (0, _commandBuilder.buildSEARCHCommand)(query, options); const response = yield _this10.exec(command, 'SEARCH', { precheck: ctx => _this10._shouldSelectMailbox(path, ctx) ? _this10.selectMailbox(path, { ctx }) : Promise.resolve() }); return (0, _commandParser.parseSEARCH)(response); })(); } /** * Runs STORE command * * STORE details: * http://tools.ietf.org/html/rfc3501#section-6.4.6 * * @param {String} path The path for the mailbox which should be selected for the command. Selects mailbox if necessary * @param {String} sequence Message selector which the flag change is applied to * @param {Array} flags * @param {Object} [options] Query modifiers * @returns {Promise} Promise with the array of matching seq. or uid numbers */ setFlags(path, sequence, flags, options) { let key = ''; let list = []; if (Array.isArray(flags) || typeof flags !== 'object') { list = [].concat(flags || []); key = ''; } else if (flags.add) { list = [].concat(flags.add || []); key = '+'; } else if (flags.set) { key = ''; list = [].concat(flags.set || []); } else if (flags.remove) { key = '-'; list = [].concat(flags.remove || []); } this.logger.debug('Setting flags on', sequence, 'in', path, '...'); return this.store(path, sequence, key + 'FLAGS', list, options); } /** * Runs STORE command * * STORE details: * http://tools.ietf.org/html/rfc3501#section-6.4.6 * * @param {String} path The path for the mailbox which should be selected for the command. Selects mailbox if necessary * @param {String} sequence Message selector which the flag change is applied to * @param {String} action STORE method to call, eg "+FLAGS" * @param {Array} flags * @param {Object} [options] Query modifiers * @returns {Promise} Promise with the array of matching seq. or uid numbers */ store(path, sequence, action, flags, options = {}) { var _this11 = this; return _asyncToGenerator(function* () { const command = (0, _commandBuilder.buildSTORECommand)(sequence, action, flags, options); const response = yield _this11.exec(command, 'FETCH', { precheck: ctx => _this11._shouldSelectMailbox(path, ctx) ? _this11.selectMailbox(path, { ctx }) : Promise.resolve() }); return (0, _commandParser.parseFETCH)(response); })(); } /** * Runs APPEND command * * APPEND details: * http://tools.ietf.org/html/rfc3501#section-6.3.11 * * @param {String} destination The mailbox where to append the message * @param {String} message The message to append * @param {Array} options.flags Any flags you want to set on the uploaded message. Defaults to [\Seen]. (optional) * @returns {Promise} Promise with the array of matching seq. or uid numbers */ upload(destination, message, options = {}) { var _this12 = this; return _asyncToGenerator(function* () { const flags = (0, _ramda.propOr)(['\\Seen'], 'flags', options).map(value => ({ type: 'atom', value })); const command = { command: 'APPEND', attributes: [{ type: 'atom', value: destination }, flags, { type: 'literal', value: message }] }; _this12.logger.debug('Uploading message to', destination, '...'); const response = yield _this12.exec(command); return (0, _commandParser.parseAPPEND)(response); })(); } /** * Deletes messages from a selected mailbox * * EXPUNGE details: * http://tools.ietf.org/html/rfc3501#section-6.4.3 * UID EXPUNGE details: * https://tools.ietf.org/html/rfc4315#section-2.1 * * If possible (byUid:true and UIDPLUS extension supported), uses UID EXPUNGE * command to delete a range of messages, otherwise falls back to EXPUNGE. * * NB! This method might be destructive - if EXPUNGE is used, then any messages * with \Deleted flag set are deleted * * @param {String} path The path for the mailbox which should be selected for the command. Selects mailbox if necessary * @param {String} sequence Message range to be deleted * @param {Object} [options] Query modifiers * @returns {Promise} Promise */ deleteMessages(path, sequence, options = {}) { var _this13 = this; return _asyncToGenerator(function* () { // add \Deleted flag to the messages and run EXPUNGE or UID EXPUNGE _this13.logger.debug('Deleting messages', sequence, 'in', path, '...'); const useUidPlus = options.byUid && _this13._capability.indexOf('UIDPLUS') >= 0; const uidExpungeCommand = { command: 'UID EXPUNGE', attributes: [{ type: 'sequence', value: sequence }] }; yield _this13.setFlags(path, sequence, { add: '\\Deleted' }, options); const cmd = useUidPlus ? uidExpungeCommand : 'EXPUNGE'; return _this13.exec(cmd, null, { precheck: ctx => _this13._shouldSelectMailbox(path, ctx) ? _this13.selectMailbox(path, { ctx }) : Promise.resolve() }); })(); } /** * Copies a range of messages from the active mailbox to the destination mailbox. * Silent method (unless an error occurs), by default returns no information. * * COPY details: * http://tools.ietf.org/html/rfc3501#section-6.4.7 * * @param {String} path The path for the mailbox which should be selected for the command. Selects mailbox if necessary * @param {String} sequence Message range to be copied * @param {String} destination Destination mailbox path * @param {Object} [options] Query modifiers * @param {Boolean} [options.byUid] If true, uses UID COPY instead of COPY * @returns {Promise} Promise */ copyMessages(path, sequence, destination, options = {}) { var _this14 = this; return _asyncToGenerator(function* () { _this14.logger.debug('Copying messages', sequence, 'from', path, 'to', destination, '...'); const response = yield _this14.exec({ command: options.byUid ? 'UID COPY' : 'COPY', attributes: [{ type: 'sequence', value: sequence }, { type: 'atom', value: destination }] }, null, { precheck: ctx => _this14._shouldSelectMailbox(path, ctx) ? _this14.selectMailbox(path, { ctx }) : Promise.resolve() }); return (0, _commandParser.parseCOPY)(response); })(); } /** * Moves a range of messages from the active mailbox to the destination mailbox. * Prefers the MOVE extension but if not available, falls back to * COPY + EXPUNGE * * MOVE details: * http://tools.ietf.org/html/rfc6851 * * @param {String} path The path for the mailbox which should be selected for the command. Selects mailbox if necessary * @param {String} sequence Message range to be moved * @param {String} destination Destination mailbox path * @param {Object} [options] Query modifiers * @returns {Promise} Promise */ moveMessages(path, sequence, destination, options = {}) { var _this15 = this; return _asyncToGenerator(function* () { _this15.logger.debug('Moving messages', sequence, 'from', path, 'to', destination, '...'); if (_this15._capability.indexOf('MOVE') === -1) { // Fallback to COPY + EXPUNGE yield _this15.copyMessages(path, sequence, destination, options); return _this15.deleteMessages(path, sequence, options); } // If possible, use MOVE return _this15.exec({ command: options.byUid ? 'UID MOVE' : 'MOVE', attributes: [{ type: 'sequence', value: sequence }, { type: 'atom', value: destination }] }, ['OK'], { precheck: ctx => _this15._shouldSelectMailbox(path, ctx) ? _this15.selectMailbox(path, { ctx }) : Promise.resolve() }); })(); } /** * Runs COMPRESS command * * COMPRESS details: * https://tools.ietf.org/html/rfc4978 */ compressConnection() { var _this16 = this; return _asyncToGenerator(function* () { if (!_this16._enableCompression || _this16._capability.indexOf('COMPRESS=DEFLATE') < 0 || _this16.client.compressed) { return false; } _this16.logger.debug('Enabling compression...'); yield _this16.exec({ command: 'COMPRESS', attributes: [{ type: 'ATOM', value: 'DEFLATE' }] }); _this16.client.enableCompression(); _this16.logger.debug('Compression enabled, all data sent and received is deflated!'); })(); } /** * Runs LOGIN or AUTHENTICATE XOAUTH2 command * * LOGIN details: * http://tools.ietf.org/html/rfc3501#section-6.2.3 * XOAUTH2 details: * https://developers.google.com/gmail/xoauth2_protocol#imap_protocol_exchange * * @param {String} auth.user * @param {String} auth.pass * @param {String} auth.xoauth2 */ login(auth) { var _this17 = this; return _asyncToGenerator(function* () { let command; const options = {}; if (!auth) { throw new Error('Authentication information not provided'); } if (_this17._capability.indexOf('AUTH=XOAUTH2') >= 0 && auth && auth.xoauth2) { command = { command: 'AUTHENTICATE', attributes: [{ type: 'ATOM', value: 'XOAUTH2' }, { type: 'ATOM', value: (0, _commandBuilder.buildXOAuth2Token)(auth.user, auth.xoauth2), sensitive: true }] }; options.errorResponseExpectsEmptyLine = true; // + tagged error response expects an empty line in return } else { command = { command: 'login', attributes: [{ type: 'STRING', value: auth.user || '' }, { type: 'STRING', value: auth.pass || '', sensitive: true }] }; } _this17.logger.debug('Logging in...'); const response = yield _this17.exec(command, 'capability', options); /* * update post-auth capabilites * capability list shouldn't contain auth related stuff anymore * but some new extensions might have popped up that do not * make much sense in the non-auth state */ if (response.capability && response.capability.length) { // capabilites were listed with the OK [CAPABILITY ...] response _this17._capability = response.capability; } else if (response.payload && response.payload.CAPABILITY && response.payload.CAPABILITY.length) { // capabilites were listed with * CAPABILITY ... response _this17._capability = response.payload.CAPABILITY.pop().attributes.map((capa = '') => capa.value.toUpperCase().trim()); } else { // capabilities were not automatically listed, reload yield _this17.updateCapability(true); } _this17._changeState(STATE_AUTHENTICATED); _this17._authenticated = true; _this17.logger.debug('Login successful, post-auth capabilites updated!', _this17._capability); })(); } /** * Run an IMAP command. * * @param {Object} request Structured request object * @param {Array} acceptUntagged a list of untagged responses that will be included in 'payload' property */ exec(request, acceptUntagged, options) { var _this18 = this; return _asyncToGenerator(function* () { _this18.breakIdle(); const response = yield _this18.client.enqueueCommand(request, acceptUntagged, options); if (response && response.capability) { _this18._capability = response.capability; } return response; })(); } /** * The connection is idling. Sends a NOOP or IDLE command * * IDLE details: * https://tools.ietf.org/html/rfc2177 */ enterIdle() { if (this._enteredIdle) { return; } const supportsIdle = this._capability.indexOf('IDLE') >= 0; this._enteredIdle = supportsIdle && this._selectedMailbox ? 'IDLE' : 'NOOP'; this.logger.debug('Entering idle with ' + this._enteredIdle); if (this._enteredIdle === 'NOOP') { this._idleTimeout = setTimeout(() => { this.logger.debug('Sending NOOP'); this.exec('NOOP'); }, this.timeoutNoop); } else if (this._enteredIdle === 'IDLE') { this.client.enqueueCommand({ command: 'IDLE' }); this._idleTimeout = setTimeout(() => { this.client.send('DONE\r\n'); this._enteredIdle = false; this.logger.debug('Idle terminated'); }, this.timeoutIdle); } } /** * Stops actions related idling, if IDLE is supported, sends DONE to stop it */ breakIdle() { if (!this._enteredIdle) { return; } clearTimeout(this._idleTimeout); if (this._enteredIdle === 'IDLE') { this.client.send('DONE\r\n'); this.logger.debug('Idle terminated'); } this._enteredIdle = false; } /** * Runs STARTTLS command if needed * * STARTTLS details: * http://tools.ietf.org/html/rfc3501#section-6.2.1 * * @param {Boolean} [forced] By default the command is not run if capability is already listed. Set to true to skip this validation */ upgradeConnection() { var _this19 = this; return _asyncToGenerator(function* () { // skip request, if already secured if (_this19.client.secureMode) { return false; } // skip if STARTTLS not available or starttls support disabled if ((_this19._capability.indexOf('STARTTLS') < 0 || _this19._ignoreTLS) && !_this19._requireTLS) { return false; } _this19.logger.debug('Encrypting connection...'); yield _this19.exec('STARTTLS'); _this19._capability = []; _this19.client.upgrade(); return _this19.updateCapability(); })(); } /** * Runs CAPABILITY command * * CAPABILITY details: * http://tools.ietf.org/html/rfc3501#section-6.1.1 * * Doesn't register untagged CAPABILITY handler as this is already * handled by global handler * * @param {Boolean} [forced] By default the command is not run if capability is already listed. Set to true to skip this validation */ updateCapability(forced) { var _this20 = this; return _asyncToGenerator(function* () { // skip request, if not forced update and capabilities are already loaded if (!forced && _this20._capability.length) { return; } // If STARTTLS is required then skip capability listing as we are going to try // STARTTLS anyway and we re-check capabilities after connection is secured if (!_this20.client.secureMode && _this20._requireTLS) { return; } _this20.logger.debug('Updating capability...'); return _this20.exec('CAPABILITY'); })(); } hasCapability(capa = '') { return this._capability.indexOf(capa.toUpperCase().trim()) >= 0; } // Default handlers for untagged responses /** * Checks if an untagged OK includes [CAPABILITY] tag and updates capability object * * @param {Object} response Parsed server response * @param {Function} next Until called, server responses are not processed */ _untaggedOkHandler(response) { if (response && response.capability) { this._capability = response.capability; } } /** * Updates capability object * * @param {Object} response Parsed server response * @param {Function} next Until called, server responses are not processed */ _untaggedCapabilityHandler(response) { this._capability = (0, _ramda.pipe)((0, _ramda.propOr)([], 'attributes'), (0, _ramda.map)(({ value }) => (value || '').toUpperCase().trim()))(response); } /** * Updates existing message count * * @param {Object} response Parsed server response * @param {Function} next Until called, server responses are not processed */ _untaggedExistsHandler(response) { if (response && Object.prototype.hasOwnProperty.call(response, 'nr')) { this.onupdate && this.onupdate(this._selectedMailbox, 'exists', response.nr); } } /** * Indicates a message has been deleted * * @param {Object} response Parsed server response * @param {Function} next Until called, server responses are not processed */ _untaggedExpungeHandler(response) { if (response && Object.prototype.hasOwnProperty.call(response, 'nr')) { this.onupdate && this.onupdate(this._selectedMailbox, 'expunge', response.nr); } } /** * Indicates that flags have been updated for a message * * @param {Object} response Parsed server response * @param {Function} next Until called, server responses are not processed */ _untaggedFetchHandler(response) { this.onupdate && this.onupdate(this._selectedMailbox, 'fetch', [].concat((0, _commandParser.parseFETCH)({ payload: { FETCH: [response] } }) || []).shift()); } // Private helpers /** * Indicates that the connection started idling. Initiates a cycle * of NOOPs or IDLEs to receive notifications about updates in the server */ _onIdle() { if (!this._authenticated || this._enteredIdle) { // No need to IDLE when not logged in or already idling return; } this.logger.debug('Client started idling'); this.enterIdle(); } /** * Updates the IMAP state value for the current connection * * @param {Number} newState The state you want to change to */ _changeState(newState) { if (newState === this._state) { return; } this.logger.debug('Entering state: ' + newState); // if a mailbox was opened, emit onclosemailbox and clear selectedMailbox value if (this._state === STATE_SELECTED && this._selectedMailbox) { this.onclosemailbox && this.onclosemailbox(this._selectedMailbox); this._selectedMailbox = false; } this._state = newState; } /** * Ensures a path exists in the Mailbox tree * * @param {Object} tree Mailbox tree * @param {String} path * @param {String} delimiter * @return {Object} branch for used path */ _ensurePath(tree, path, delimiter) { const names = path.split(delimiter); let branch = tree; for (let i = 0; i < names.length; i++) { let found = false; for (let j = 0; j < branch.children.length; j++) { if (this._compareMailboxNames(branch.children[j].name, (0, _emailjsUtf.imapDecode)(names[i]))) { branch = branch.children[j]; found = true; break; } } if (!found) { branch.children.push({ name: (0, _emailjsUtf.imapDecode)(names[i]), delimiter: delimiter, path: names.slice(0, i + 1).join(delimiter), children: [] }); branch = branch.children[branch.children.length - 1]; } } return branch; } /** * Compares two mailbox names. Case insensitive in case of INBOX, otherwise case sensitive * * @param {String} a Mailbox name * @param {String} b Mailbox name * @returns {Boolean} True if the folder names match */ _compareMailboxNames(a, b) { return (a.toUpperCase() === 'INBOX' ? 'INBOX' : a) === (b.toUpperCase() === 'INBOX' ? 'INBOX' : b); } createLogger(creator = _logger.default) { const logger = creator((this._auth || {}).user || '', this._host); this.logger = this.client.logger = { debug: (...msgs) => { if (_common.LOG_LEVEL_DEBUG >= this.logLevel) { logger.debug(msgs); } }, info: (...msgs) => { if (_common.LOG_LEVEL_INFO >= this.logLevel) { logger.info(msgs); } }, warn: (...msgs) => { if (_common.LOG_LEVEL_WARN >= this.logLevel) { logger.warn(msgs); } }, error: (...msgs) => { if (_common.LOG_LEVEL_ERROR >= this.logLevel) { logger.error(msgs); } } }; } } exports.default = Client; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9jbGllbnQuanMiXSwibmFtZXMiOlsiVElNRU9VVF9DT05ORUNUSU9OIiwiVElNRU9VVF9OT09QIiwiVElNRU9VVF9JRExFIiwiU1RBVEVfQ09OTkVDVElORyIsIlNUQVRFX05PVF9BVVRIRU5USUNBVEVEIiwiU1RBVEVfQVVUSEVOVElDQVRFRCIsIlNUQVRFX1NFTEVDVEVEIiwiU1RBVEVfTE9HT1VUIiwiREVGQVVMVF9DTElFTlRfSUQiLCJuYW1lIiwiQ2xpZW50IiwiY29uc3RydWN0b3IiLCJob3N0IiwicG9ydCIsIm9wdGlvbnMiLCJ0aW1lb3V0Q29ubmVjdGlvbiIsInRpbWVvdXROb29wIiwidGltZW91dElkbGUiLCJzZXJ2ZXJJZCIsIm9uY2VydCIsIm9udXBkYXRlIiwib25zZWxlY3RtYWlsYm94Iiwib25jbG9zZW1haWxib3giLCJfaG9zdCIsIl9jbGllbnRJZCIsIl9zdGF0ZSIsIl9hdXRoZW50aWNhdGVkIiwiX2NhcGFiaWxpdHkiLCJfc2VsZWN0ZWRNYWlsYm94IiwiX2VudGVyZWRJZGxlIiwiX2lkbGVUaW1lb3V0IiwiX2VuYWJsZUNvbXByZXNzaW9uIiwiZW5hYmxlQ29tcHJlc3Npb24iLCJfYXV0aCIsImF1dGgiLCJfcmVxdWlyZVRMUyIsInJlcXVpcmVUTFMiLCJfaWdub3JlVExTIiwiaWdub3JlVExTIiwiY2xpZW50IiwiSW1hcENsaWVudCIsIm9uZXJyb3IiLCJfb25FcnJvciIsImJpbmQiLCJjZXJ0Iiwib25pZGxlIiwiX29uSWRsZSIsInNldEhhbmRsZXIiLCJyZXNwb25zZSIsIl91bnRhZ2dlZENhcGFiaWxpdHlIYW5kbGVyIiwiX3VudGFnZ2VkT2tIYW5kbGVyIiwiX3VudGFnZ2VkRXhpc3RzSGFuZGxlciIsIl91bnRhZ2dlZEV4cHVuZ2VIYW5kbGVyIiwiX3VudGFnZ2VkRmV0Y2hIYW5kbGVyIiwiY3JlYXRlTG9nZ2VyIiwibG9nTGV2ZWwiLCJMT0dfTEVWRUxfQUxMIiwiZXJyIiwiY2xlYXJUaW1lb3V0IiwiY29ubmVjdCIsIm9wZW5Db25uZWN0aW9uIiwidXBncmFkZUNvbm5lY3Rpb24iLCJ1cGRhdGVJZCIsImxvZ2dlciIsIndhcm4iLCJtZXNzYWdlIiwibG9naW4iLCJjb21wcmVzc0Nvbm5lY3Rpb24iLCJkZWJ1ZyIsImVycm9yIiwiY2xvc2UiLCJQcm9taXNlIiwicmVzb2x2ZSIsInJlamVjdCIsImNvbm5lY3Rpb25UaW1lb3V0Iiwic2V0VGltZW91dCIsIkVycm9yIiwiX2NoYW5nZVN0YXRlIiwidGhlbiIsIm9ucmVhZHkiLCJ1cGRhdGVDYXBhYmlsaXR5IiwiY2F0Y2giLCJsb2dvdXQiLCJpZCIsImluZGV4T2YiLCJjb21tYW5kIiwiYXR0cmlidXRlcyIsIk9iamVjdCIsImVudHJpZXMiLCJleGVjIiwibGlzdCIsIm1hcCIsInZhbHVlcyIsImtleXMiLCJmaWx0ZXIiLCJfIiwiaSIsIl9zaG91bGRTZWxlY3RNYWlsYm94IiwicGF0aCIsImN0eCIsInByZXZpb3VzU2VsZWN0IiwiZ2V0UHJldmlvdXNseVF1ZXVlZCIsInJlcXVlc3QiLCJwYXRoQXR0cmlidXRlIiwiZmluZCIsImF0dHJpYnV0ZSIsInR5cGUiLCJ2YWx1ZSIsInNlbGVjdE1haWxib3giLCJxdWVyeSIsInJlYWRPbmx5IiwiY29uZHN0b3JlIiwicHVzaCIsIm1haWxib3hJbmZvIiwibGlzdE5hbWVzcGFjZXMiLCJsaXN0TWFpbGJveGVzIiwidHJlZSIsInJvb3QiLCJjaGlsZHJlbiIsImxpc3RSZXNwb25zZSIsImZvckVhY2giLCJpdGVtIiwiYXR0ciIsImxlbmd0aCIsImRlbGltIiwiYnJhbmNoIiwiX2Vuc3VyZVBhdGgiLCJmbGFncyIsImxpc3RlZCIsImxzdWJSZXNwb25zZSIsImxzdWIiLCJmbGFnIiwic3Vic2NyaWJlZCIsImNyZWF0ZU1haWxib3giLCJjb2RlIiwiZGVsZXRlTWFpbGJveCIsImxpc3RNZXNzYWdlcyIsInNlcXVlbmNlIiwiaXRlbXMiLCJmYXN0IiwicHJlY2hlY2siLCJzZWFyY2giLCJzZXRGbGFncyIsImtleSIsIkFycmF5IiwiaXNBcnJheSIsImNvbmNhdCIsImFkZCIsInNldCIsInJlbW92ZSIsInN0b3JlIiwiYWN0aW9uIiwidXBsb2FkIiwiZGVzdGluYXRpb24iLCJkZWxldGVNZXNzYWdlcyIsInVzZVVpZFBsdXMiLCJieVVpZCIsInVpZEV4cHVuZ2VDb21tYW5kIiwiY21kIiwiY29weU1lc3NhZ2VzIiwibW92ZU1lc3NhZ2VzIiwiY29tcHJlc3NlZCIsInhvYXV0aDIiLCJ1c2VyIiwic2Vuc2l0aXZlIiwiZXJyb3JSZXNwb25zZUV4cGVjdHNFbXB0eUxpbmUiLCJwYXNzIiwiY2FwYWJpbGl0eSIsInBheWxvYWQiLCJDQVBBQklMSVRZIiwicG9wIiwiY2FwYSIsInRvVXBwZXJDYXNlIiwidHJpbSIsImFjY2VwdFVudGFnZ2VkIiwiYnJlYWtJZGxlIiwiZW5xdWV1ZUNvbW1hbmQiLCJlbnRlcklkbGUiLCJzdXBwb3J0c0lkbGUiLCJzZW5kIiwic2VjdXJlTW9kZSIsInVwZ3JhZGUiLCJmb3JjZWQiLCJoYXNDYXBhYmlsaXR5IiwicHJvdG90eXBlIiwiaGFzT3duUHJvcGVydHkiLCJjYWxsIiwibnIiLCJGRVRDSCIsInNoaWZ0IiwibmV3U3RhdGUiLCJkZWxpbWl0ZXIiLCJuYW1lcyIsInNwbGl0IiwiZm91bmQiLCJqIiwiX2NvbXBhcmVNYWlsYm94TmFtZXMiLCJzbGljZSIsImpvaW4iLCJhIiwiYiIsImNyZWF0b3IiLCJjcmVhdGVEZWZhdWx0TG9nZ2VyIiwibXNncyIsIkxPR19MRVZFTF9ERUJVRyIsImluZm8iLCJMT0dfTEVWRUxfSU5GTyIsIkxPR19MRVZFTF9XQVJOIiwiTE9HX0xFVkVMX0VSUk9SIl0sIm1hcHBpbmdzIjoiOzs7Ozs7O0FBQUE7O0FBQ0E7O0FBQ0E7O0FBUUE7O0FBT0E7O0FBQ0E7O0FBQ0E7O0FBUUE7Ozs7Ozs7O0FBSU8sTUFBTUEsa0JBQWtCLEdBQUcsS0FBSyxJQUFoQyxDLENBQXFDOzs7QUFDckMsTUFBTUMsWUFBWSxHQUFHLEtBQUssSUFBMUIsQyxDQUErQjs7O0FBQy9CLE1BQU1D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