imap-mailbox
Version:
Simple library to use and watch IMAP mailboxes
321 lines (320 loc) • 13.6 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const imapflow_1 = require("imapflow");
const Logger_1 = __importDefault(require("./Logger"));
const Mail_1 = __importDefault(require("./Mail"));
const mailparser_1 = __importDefault(require("mailparser"));
const utils_1 = require("./utils");
const DEFAULT_RECONNECT_INTERVAL = 1000 * 60;
const DEFAULT_MAILBOXES_INTERVAL = 1000 * 60;
class Imap {
/**
* Await run() method before any action on this object
* @param {ImapConfig} config Imap configuration
*/
constructor(config) {
this.config = config;
this.eventEmitter = new events_1.EventEmitter();
this.logger = new Logger_1.default(config.logging);
this.mailboxes = new Map();
}
/**
* Run IMAP service
* You should await this method before any action on Imap object
*/
run() {
return __awaiter(this, void 0, void 0, function* () {
yield this.connect();
this.watchErrors();
yield this.loadMailboxes();
yield this.watchMailboxes();
});
}
on(event, callback) {
switch (event) {
case 'newMail':
this.eventEmitter.addListener('newMail', callback);
break;
case 'loadedMail':
this.eventEmitter.addListener('loadedMail', callback);
break;
case 'deletedMail':
this.eventEmitter.addListener('deletedMail', callback);
break;
}
}
/**
* Delete mails from uids or mail objects
* @param {string} mailboxPath Mailbox path where mails to delete are
* @param {UidsList} toDelete Mails or uids of mails to delete
* @returns {boolean} true if mails are deleted
*/
deleteMails(mailboxPath, toDelete) {
return __awaiter(this, void 0, void 0, function* () {
const uids = (0, utils_1.uidsAndMailsToUids)(toDelete);
const mailbox = yield this.client.getMailboxLock(mailboxPath);
if (mailbox) {
const result = yield this.client.messageDelete(uids.join(','), {
uid: true,
});
if (result === true) {
uids.map((uid) => this.eventEmitter.emit('deletedMail', uid));
}
mailbox.release();
return result;
}
return false;
});
}
/**
* Mark mails as seen from uids or mail object
* @param {string} mailboxPath Mailbox path where mails to see are
* @param {UidsList} toSee Mails or uids of mails to see
* @returns {boolean} true if mails are marked seen
*/
seeMails(mailboxPath, toSee) {
return __awaiter(this, void 0, void 0, function* () {
const uids = (0, utils_1.uidsAndMailsToUids)(toSee);
const mailbox = yield this.client.getMailboxLock(mailboxPath);
if (mailbox) {
const result = yield this.client.messageFlagsAdd(uids.join(','), ['\\Seen'], {
uid: true,
});
mailbox.release();
return result;
}
return false;
});
}
/**
* Mark mails as unseen from uids or mail object
* @param {string} mailboxPath Mailbox path where mails to unsee are
* @param {UidsList} toUnsee Mails or uids of mails to unsee
* @returns {boolean} true if mails are marked unseen
*/
unseeMails(mailboxPath, toUnsee) {
return __awaiter(this, void 0, void 0, function* () {
const uids = (0, utils_1.uidsAndMailsToUids)(toUnsee);
const mailbox = yield this.client.getMailboxLock(mailboxPath);
if (mailbox && uids.length > 0) {
const result = yield this.client.messageFlagsRemove(uids.join(','), ['\\Seen'], {
uid: true,
});
mailbox.release();
return result;
}
return false;
});
}
/**
* Get a list of unseen mails from a mailbox
* @param {string} mailboxPath Path to mailbox (or name)
* @returns {Mail[]} List of mails
*/
getUnseenMails(mailboxPath) {
return __awaiter(this, void 0, void 0, function* () {
const mails = [];
for (const msg of yield this.search(mailboxPath, { seen: false })) {
const mail = yield this.getNewMail(mailboxPath, msg);
mails.push(mail);
}
return mails;
});
}
/**
* Get a list of seen mails from a mailbox
* @param {string} mailboxPath Path to mailbox (or name)
* @returns {Mail[]} List of mails
*/
getSeenMails(mailboxPath) {
return __awaiter(this, void 0, void 0, function* () {
const mails = [];
for (const msg of yield this.search(mailboxPath, { seen: true })) {
const mail = yield this.getNewMail(mailboxPath, msg);
mails.push(mail);
}
return mails;
});
}
/**
* Get all the mails from a mailbox
* @param {string} mailboxPath Path to mailbox (or name)
* @returns {Mail[]} List of mails
*/
getAllMails(mailboxPath) {
return __awaiter(this, void 0, void 0, function* () {
const mails = [];
for (const msg of yield this.search(mailboxPath, '1:*')) {
const mail = yield this.getNewMail(mailboxPath, msg);
mails.push(mail);
}
return mails;
});
}
/**
* Get list of mailboxes
* @returns {Map<string, Mailbox>} Map of mailboxes
*/
getMailboxes() {
return this.mailboxes;
}
connect() {
return __awaiter(this, void 0, void 0, function* () {
try {
this.logger.log(`Trying to connect to <${this.config.auth.user}>...`, 'white');
this.client = new imapflow_1.ImapFlow(this.config);
yield this.client.connect();
this.logger.log(`Connected to <${this.config.auth.user}>`, 'green');
}
catch (e) {
this.restart();
}
});
}
restart() {
this.logger.log(`Could not connect to <${this.config.auth.user}>, check credentials and host`, 'red');
setTimeout(this.run.bind(this), this.config.reconnectInterval || DEFAULT_RECONNECT_INTERVAL);
}
watchErrors() {
return __awaiter(this, void 0, void 0, function* () {
this.client.on('error', () => {
const timeout = (this.config.reconnectInterval || DEFAULT_RECONNECT_INTERVAL) / 1000;
this.logger.log(`Error detected. Restarting in ${timeout} seconds...`, 'red');
setTimeout(this.run.bind(this), timeout);
});
});
}
loadMailboxes() {
return __awaiter(this, void 0, void 0, function* () {
const listResponses = yield this.client.list();
for (const list of listResponses) {
const mailbox = {
path: list.path,
lastUid: yield this.getLastUid(list.path, true),
};
this.mailboxes.set(mailbox.path, mailbox);
}
this.logger.log(`Loaded ${this.mailboxes.size} mailboxes for <${this.config.auth.user}>`, 'green');
});
}
watchMailboxes() {
return __awaiter(this, void 0, void 0, function* () {
if (this.config.mailboxesToWatch) {
for (const mailboxToWatch of this.config.mailboxesToWatch) {
this.logger.log(`Watching mailbox [${mailboxToWatch}] every ${(this.config.mailboxesWatchInterval || DEFAULT_MAILBOXES_INTERVAL) /
1000} seconds for <${this.config.auth.user}>`, 'yellow');
yield this.watchMailbox(mailboxToWatch);
setInterval(this.watchMailbox.bind(this, mailboxToWatch), this.config.mailboxesWatchInterval || DEFAULT_MAILBOXES_INTERVAL);
}
}
});
}
watchMailbox(mailboxToWatch) {
return __awaiter(this, void 0, void 0, function* () {
try {
const mailbox = this.mailboxes.get(mailboxToWatch);
if (mailbox) {
const mails = yield this.getLastMails(mailbox);
if (mails && mails.length > 0) {
mailbox.lastUid = mails[mails.length - 1].uid;
this.mailboxes.set(mailbox.path, mailbox);
for (const mail of mails) {
this.eventEmitter.emit('newMail', mail);
}
}
}
}
catch (e) {
this.logger.log(`Error while watching "${mailboxToWatch}" for <${this.config.auth.user}>, but still watching`, 'yellow');
}
});
}
getNewMail(mailboxPath, msg) {
return __awaiter(this, void 0, void 0, function* () {
const headers = yield mailparser_1.default.simpleParser(msg.headers);
const bodyPart = msg.bodyParts.get('text');
let parsedMail = headers;
if (bodyPart) {
const body = yield mailparser_1.default.simpleParser(bodyPart);
parsedMail = Object.assign(headers, body);
}
return new Mail_1.default(this, mailboxPath, msg, parsedMail);
});
}
getLastUid(mailboxPath, loading = false) {
return __awaiter(this, void 0, void 0, function* () {
let lastUid = 1;
for (const msg of yield this.search(mailboxPath, `1:*`)) {
if (msg.uid > lastUid) {
lastUid = msg.uid;
}
if (loading) {
const mail = yield this.getNewMail(mailboxPath, msg);
this.eventEmitter.emit('loadedMail', mail);
}
}
return lastUid;
});
}
getLastMails(mailbox) {
return __awaiter(this, void 0, void 0, function* () {
const mails = [];
for (const msg of yield this.search(mailbox.path, `1:*`)) {
if (msg.uid > mailbox.lastUid) {
const mail = yield this.getNewMail(mailbox.path, msg);
mails.push(mail);
}
}
return mails;
});
}
search(mailboxPath, range) {
var e_1, _a;
return __awaiter(this, void 0, void 0, function* () {
const msgs = [];
const mailbox = yield this.client.getMailboxLock(mailboxPath);
if (mailbox) {
try {
for (var _b = __asyncValues(this.client.fetch(range, {
uid: true,
headers: true,
bodyParts: ['TEXT'],
})), _c; _c = yield _b.next(), !_c.done;) {
let msg = _c.value;
msgs.push(msg);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
mailbox.release();
}
return msgs;
});
}
}
exports.default = Imap;