emailjs-imap-client
Version:
JavaScript IMAP client
1,233 lines (1,008 loc) • 117 kB
JavaScript
"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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