function ImapSimple(imap) {
var self = this;
self.imap = imap;
// flag to determine whether we should suppress ECONNRESET from bubbling up to listener
self.ending = false;
// pass most node-imap `Connection` events through 1:1
['alert', 'mail', 'expunge', 'uidvalidity', 'update', 'close', 'end'].forEach(function (event) {
self.imap.on(event, self.emit.bind(self, event));
});
// special handling for `error` event
self.imap.on('error', function (err) {
// if .end() has been called and an 'ECONNRESET' error is received, don't bubble
if (err && self.ending && (err.code.toUpperCase() === 'ECONNRESET')) {
return;
}
self.emit('error', err);
});
}n/a
function connect(options, callback) {
options = options || {};
options.imap = options.imap || {};
// support old connectTimeout config option. Remove in v2.0.0
if (options.hasOwnProperty('connectTimeout')) {
console.warn('[imap-simple] connect: options.connectTimeout is deprecated. ' +
'Please use options.imap.authTimeout instead.');
options.imap.authTimeout = options.connectTimeout;
}
// set default authTimeout
options.imap.authTimeout = options.imap.hasOwnProperty('authTimeout') ? options.imap.authTimeout : 2000;
if (callback) {
return nodeify(connect(options), callback);
}
return new Promise(function (resolve, reject) {
var imap = new Imap(options.imap);
function imapOnReady() {
imap.removeListener('error', imapOnError);
imap.removeListener('close', imapOnClose);
imap.removeListener('end', imapOnEnd);
resolve(new ImapSimple(imap));
}
function imapOnError(err) {
if (err.source === 'timeout-auth') {
err = new errors.ConnectionTimeoutError(options.imap.authTimeout);
}
imap.removeListener('ready', imapOnReady);
imap.removeListener('close', imapOnClose);
imap.removeListener('end', imapOnEnd);
reject(err);
}
function imapOnEnd() {
imap.removeListener('ready', imapOnReady);
imap.removeListener('error', imapOnError);
imap.removeListener('close', imapOnClose);
reject(new Error('Connection ended unexpectedly'));
}
function imapOnClose() {
imap.removeListener('ready', imapOnReady);
imap.removeListener('error', imapOnError);
imap.removeListener('end', imapOnEnd);
reject(new Error('Connection closed unexpectedly'));
}
imap.once('ready', imapOnReady);
imap.once('error', imapOnError);
imap.once('close', imapOnClose);
imap.once('end', imapOnEnd);
if (options.hasOwnProperty('onmail')) {
imap.on('mail', options.onmail);
}
if (options.hasOwnProperty('onexpunge')) {
imap.on('expunge', options.onexpunge);
}
if (options.hasOwnProperty('onupdate')) {
imap.on('update', options.onupdate);
}
imap.connect();
});
}...
host: 'imap.gmail.com',
port: 993,
tls: true,
authTimeout: 3000
}
};
imaps.connect(config).then(function (connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = [
'UNSEEN'
];
var fetchOptions = {
...function ConnectionTimeoutError(timeout) {
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.message = 'connection timed out';
if (timeout) {
this.message += '. timeout = ' + timeout + ' ms';
}
this.name = 'ConnectionTimeoutError';
}n/a
function getParts(struct, parts) {
parts = parts || [];
for (var i = 0; i < struct.length; i++) {
if (Array.isArray(struct[i])) {
getParts(struct[i], parts);
} else if (struct[i].partID) {
parts.push(struct[i]);
}
}
return parts;
}...
// retrieve only the headers of the messages
return connection.search(searchCriteria, fetchOptions);
}).then(function (messages) {
var attachments = [];
messages.forEach(function (message) {
var parts = imaps.getParts(message.attributes.struct);
attachments = attachments.concat(parts.filter(function (part) {
return part.disposition && part.disposition.type === 'ATTACHMENT';
}).map(function (part) {
// retrieve the attachments only of the messages with attachments
return connection.getPartData(message, part)
.then(function (partData) {
return {
...function parseHeader(str, noDecode) {
var lines = str.split(RE_CRLF),
len = lines.length,
header = {},
state = {
buffer: undefined,
encoding: undefined,
consecutive: false,
replaces: undefined,
curReplace: undefined,
remainder: undefined
},
m, h, i, val;
for (i = 0; i < len; ++i) {
if (lines[i].length === 0)
break; // empty line separates message's header and body
if (lines[i][0] === '\t' || lines[i][0] === ' ') {
if (!Array.isArray(header[h]))
continue; // ignore invalid first line
// folded header content
val = lines[i];
if (!noDecode) {
if (RE_ENCWORD_END.test(lines[i - 1])
&& RE_ENCWORD_BEGIN.test(val)) {
// RFC2047 says to *ignore* leading whitespace in folded header values
// for adjacent encoded-words ...
val = val.substring(1);
}
}
header[h][header[h].length - 1] += val;
} else {
m = RE_HDR.exec(lines[i]);
if (m) {
h = m[1].toLowerCase().trim();
if (m[2]) {
if (header[h] === undefined)
header[h] = [m[2]];
else
header[h].push(m[2]);
} else
header[h] = [''];
} else
break;
}
}
if (!noDecode) {
var hvs;
for (h in header) {
hvs = header[h];
for (i = 0, len = header[h].length; i < len; ++i)
hvs[i] = decodeWords(hvs[i], state);
}
}
return header;
}...
var part = {
which: info.which,
size: info.size,
body: body
};
if (isHeader.test(part.which)) {
part.body = Imap.parseHeader(part.body);
}
messageParts.push(part);
});
}
function messageOnAttributes(attrs) {
...function ImapSimple(imap) {
var self = this;
self.imap = imap;
// flag to determine whether we should suppress ECONNRESET from bubbling up to listener
self.ending = false;
// pass most node-imap `Connection` events through 1:1
['alert', 'mail', 'expunge', 'uidvalidity', 'update', 'close', 'end'].forEach(function (event) {
self.imap.on(event, self.emit.bind(self, event));
});
// special handling for `error` event
self.imap.on('error', function (err) {
// if .end() has been called and an 'ECONNRESET' error is received, don't bubble
if (err && self.ending && (err.code.toUpperCase() === 'ECONNRESET')) {
return;
}
self.emit('error', err);
});
}n/a
function EventEmitter() {
EventEmitter.init.call(this);
}n/a
addFlags = function (uid, flags, callback) {
var self = this;
if (callback) {
return nodeify(self.addFlags(uid, flags), callback);
}
return new Promise(function (resolve, reject) {
self.imap.addFlags(uid, flags, function (err) {
if (err) {
reject(err);
return;
}
resolve();
});
});
}...
* @returns {undefined|Promise} Returns a promise when no callback is specified, resolving when the action succeeds.
* @memberof ImapSimple
*/
ImapSimple.prototype.addFlags = function (uid, flags, callback) {
var self = this;
if (callback) {
return nodeify(self.addFlags(uid, flags), callback);
}
return new Promise(function (resolve, reject) {
self.imap.addFlags(uid, flags, function (err) {
if (err) {
reject(err);
return;
...addMessageLabel = function (source, labels, callback) {
var self = this;
if (callback) {
return nodeify(self.addMessageLabel(source, labels), callback);
}
return new Promise(function (resolve, reject) {
self.imap.addLabels(source, labels, function (err) {
if (err) {
reject(err);
return;
}
resolve();
});
});
}...
* @returns {undefined|Promise} Returns a promise when no callback is specified, resolving when the action succeeds.
* @memberof ImapSimple
*/
ImapSimple.prototype.addMessageLabel = function (source, labels, callback) {
var self = this;
if (callback) {
return nodeify(self.addMessageLabel(source, labels), callback);
}
return new Promise(function (resolve, reject) {
self.imap.addLabels(source, labels, function (err) {
if (err) {
reject(err);
return;
...delFlags = function (uid, flags, callback) {
var self = this;
if (callback) {
return nodeify(self.delFlags(uid, flags), callback);
}
return new Promise(function (resolve, reject) {
self.imap.delFlags(uid, flags, function (err) {
if (err) {
reject(err);
return;
}
resolve();
});
});
}...
* @returns {undefined|Promise} Returns a promise when no callback is specified, resolving when the action succeeds.
* @memberof ImapSimple
*/
ImapSimple.prototype.delFlags = function (uid, flags, callback) {
var self = this;
if (callback) {
return nodeify(self.delFlags(uid, flags), callback);
}
return new Promise(function (resolve, reject) {
self.imap.delFlags(uid, flags, function (err) {
if (err) {
reject(err);
return;
...end = function () {
var self = this;
// set state flag to suppress 'ECONNRESET' errors that are triggered when .end() is called.
// it is a known issue that has no known fix. This just temporarily ignores that error.
// https://github.com/mscdex/node-imap/issues/391
// https://github.com/mscdex/node-imap/issues/395
self.ending = true;
// using 'close' event to unbind ECONNRESET error handler, because the node-imap
// maintainer claims it is the more reliable event between 'end' and 'close'.
// https://github.com/mscdex/node-imap/issues/394
self.imap.once('close', function () {
self.ending = false;
});
self.imap.end();
}...
- made `ImapSimple` an event emitter
## 1.1.2 - 2015-03-02
#### Fixed
- Put ECONNRESET error in better place, and only ignored error when calling .end()
- 'ready' and 'error' event handlers will now only fire once when connecting
## 1.1.1 - 2015-02-27
#### Fixed
- Put in basic fix for ECONNRESET error when calling .end()
...getPartData = function (message, part, callback) {
var self = this;
if (callback) {
return nodeify(self.getPartData(message, part), callback);
}
return new Promise(function (resolve, reject) {
var fetch = self.imap.fetch(message.attributes.uid, {
bodies: [part.partID],
struct: true
});
function fetchOnMessage(msg) {
getMessage(msg).then(function (result) {
if (result.parts.length !== 1) {
reject(new Error('Got ' + result.parts.length + ' parts, should get 1'));
return;
}
var data = result.parts[0].body;
var encoding = part.encoding.toUpperCase();
if (encoding === 'BASE64') {
resolve(new Buffer(data, 'base64'));
return;
}
if (encoding === 'QUOTED-PRINTABLE') {
if (part.params && part.params.charset &&
part.params.charset.toUpperCase() === 'UTF-8') {
resolve((new Buffer(utf8.decode(qp.decode(data)))).toString());
} else {
resolve((new Buffer(qp.decode(data))).toString());
}
return;
}
if (encoding === '7BIT') {
resolve((new Buffer(data)).toString('ascii'));
return;
}
if (encoding === '8BIT' || encoding === 'BINARY') {
var charset = (part.params && part.params.charset) || 'utf-8';
resolve(iconvlite.decode(new Buffer(data), charset));
return;
}
// if it gets here, the encoding is not currently supported
reject(new Error('Unknown encoding ' + part.encoding));
});
}
function fetchOnError(err) {
fetch.removeListener('message', fetchOnMessage);
fetch.removeListener('end', fetchOnEnd);
reject(err);
}
function fetchOnEnd() {
fetch.removeListener('message', fetchOnMessage);
fetch.removeListener('error', fetchOnError);
}
fetch.once('message', fetchOnMessage);
fetch.once('error', fetchOnError);
fetch.once('end', fetchOnEnd);
});
}...
messages.forEach(function (message) {
var parts = imaps.getParts(message.attributes.struct);
attachments = attachments.concat(parts.filter(function (part) {
return part.disposition && part.disposition.type === 'ATTACHMENT';
}).map(function (part) {
// retrieve the attachments only of the messages with attachments
return connection.getPartData(message, part)
.then(function (partData) {
return {
filename: part.disposition.params.filename,
data: partData
};
});
}));
...moveMessage = function (source, boxName, callback) {
var self = this;
if (callback) {
return nodeify(self.moveMessage(source, boxName), callback);
}
return new Promise(function (resolve, reject) {
self.imap.move(source, boxName, function (err) {
if (err) {
reject(err);
return;
}
resolve();
});
});
}...
* @returns {undefined|Promise} Returns a promise when no callback is specified, resolving when the action succeeds.
* @memberof ImapSimple
*/
ImapSimple.prototype.moveMessage = function (source, boxName, callback) {
var self = this;
if (callback) {
return nodeify(self.moveMessage(source, boxName), callback);
}
return new Promise(function (resolve, reject) {
self.imap.move(source, boxName, function (err) {
if (err) {
reject(err);
return;
...openBox = function (boxName, callback) {
var self = this;
if (callback) {
return nodeify(this.openBox(boxName), callback);
}
return new Promise(function (resolve, reject) {
self.imap.openBox(boxName, function (err, result) {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
}...
tls: true,
authTimeout: 3000
}
};
imaps.connect(config).then(function (connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = [
'UNSEEN'
];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
markSeen: false
...search = function (searchCriteria, fetchOptions, callback) {
var self = this;
if (!callback && typeof fetchOptions === 'function') {
callback = fetchOptions;
fetchOptions = null;
}
if (callback) {
return nodeify(this.search(searchCriteria, fetchOptions), callback);
}
return new Promise(function (resolve, reject) {
self.imap.search(searchCriteria, function (err, uids) {
if (err) {
reject(err);
return;
}
if (!uids.length) {
resolve([]);
return;
}
var fetch = self.imap.fetch(uids, fetchOptions);
var messagesRetrieved = 0;
var messages = [];
function fetchOnMessage(message, seqNo) {
getMessage(message).then(function (message) {
message.seqNo = seqNo;
messages[seqNo] = message;
messagesRetrieved++;
if (messagesRetrieved === uids.length) {
fetchCompleted();
}
});
}
function fetchCompleted() {
// pare array down while keeping messages in order
var pared = messages.filter(function (m) { return !!m; });
resolve(pared);
}
function fetchOnError(err) {
fetch.removeListener('message', fetchOnMessage);
fetch.removeListener('end', fetchOnEnd);
reject(err);
}
function fetchOnEnd() {
fetch.removeListener('message', fetchOnMessage);
fetch.removeListener('error', fetchOnError);
}
fetch.on('message', fetchOnMessage);
fetch.once('error', fetchOnError);
fetch.once('end', fetchOnEnd);
});
});
}...
];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
markSeen: false
};
return connection.search(searchCriteria, fetchOptions).then(function (results
) {
var subjects = results.map(function (res) {
return res.parts.filter(function (part) {
return part.which === 'HEADER';
})[0].body.subject[0];
});
console.log(subjects);
...function ConnectionTimeoutError(timeout) {
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.message = 'connection timed out';
if (timeout) {
this.message += '. timeout = ' + timeout + ' ms';
}
this.name = 'ConnectionTimeoutError';
}...
imap.removeListener('close', imapOnClose);
imap.removeListener('end', imapOnEnd);
resolve(new ImapSimple(imap));
}
function imapOnError(err) {
if (err.source === 'timeout-auth') {
err = new errors.ConnectionTimeoutError(options.imap.authTimeout);
}
imap.removeListener('ready', imapOnReady);
imap.removeListener('close', imapOnClose);
imap.removeListener('end', imapOnEnd);
reject(err);
}
...function ConnectionTimeoutError(timeout) {
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.message = 'connection timed out';
if (timeout) {
this.message += '. timeout = ' + timeout + ' ms';
}
this.name = 'ConnectionTimeoutError';
}...
imap.removeListener('close', imapOnClose);
imap.removeListener('end', imapOnEnd);
resolve(new ImapSimple(imap));
}
function imapOnError(err) {
if (err.source === 'timeout-auth') {
err = new errors.ConnectionTimeoutError(options.imap.authTimeout);
}
imap.removeListener('ready', imapOnReady);
imap.removeListener('close', imapOnClose);
imap.removeListener('end', imapOnEnd);
reject(err);
}
...function Error() { [native code] }n/a