2016-06-19 18:02:32 +08:00
|
|
|
const Imap = require('imap');
|
2016-06-20 15:19:16 +08:00
|
|
|
const EventEmitter = require('events');
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
const RefreshMailboxesOperation = require('./imap/refresh-mailboxes-operation')
|
|
|
|
const DiscoverMessagesOperation = require('./imap/discover-messages-operation')
|
|
|
|
const ScanUIDsOperation = require('./imap/scan-uids-operation')
|
2016-06-19 18:02:32 +08:00
|
|
|
|
|
|
|
const Capabilities = {
|
|
|
|
Gmail: 'X-GM-EXT-1',
|
|
|
|
Quota: 'QUOTA',
|
|
|
|
UIDPlus: 'UIDPLUS',
|
|
|
|
Condstore: 'CONDSTORE',
|
|
|
|
Search: 'ESEARCH',
|
|
|
|
Sort: 'SORT',
|
|
|
|
}
|
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
class IMAPConnectionStateMachine extends EventEmitter {
|
|
|
|
constructor(db, settings) {
|
|
|
|
super();
|
|
|
|
|
|
|
|
this._db = db;
|
2016-06-19 18:02:32 +08:00
|
|
|
this._queue = [];
|
|
|
|
this._current = null;
|
|
|
|
this._capabilities = [];
|
|
|
|
this._imap = Promise.promisifyAll(new Imap(settings));
|
|
|
|
|
|
|
|
this._imap.once('ready', () => {
|
|
|
|
for (const key of Object.keys(Capabilities)) {
|
|
|
|
const val = Capabilities[key];
|
|
|
|
if (this._imap.serverSupports(val)) {
|
|
|
|
this._capabilities.push(val);
|
|
|
|
}
|
|
|
|
}
|
2016-06-20 15:19:16 +08:00
|
|
|
this.emit('ready');
|
2016-06-19 18:02:32 +08:00
|
|
|
});
|
2016-06-20 15:19:16 +08:00
|
|
|
|
2016-06-19 18:02:32 +08:00
|
|
|
this._imap.once('error', (err) => {
|
|
|
|
console.log(err);
|
|
|
|
});
|
2016-06-20 15:19:16 +08:00
|
|
|
|
2016-06-19 18:02:32 +08:00
|
|
|
this._imap.once('end', () => {
|
|
|
|
console.log('Connection ended');
|
|
|
|
});
|
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
this._imap.on('alert', (msg) => {
|
|
|
|
console.log(`IMAP SERVER SAYS: ${msg}`)
|
|
|
|
})
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
// Emitted when new mail arrives in the currently open mailbox.
|
|
|
|
// Fix https://github.com/mscdex/node-imap/issues/445
|
|
|
|
let lastMailEventBox = null;
|
|
|
|
this._imap.on('mail', () => {
|
|
|
|
if (lastMailEventBox === this._imap._box.name) {
|
|
|
|
this.emit('mail');
|
2016-06-19 18:02:32 +08:00
|
|
|
}
|
2016-06-20 15:19:16 +08:00
|
|
|
lastMailEventBox = this._imap._box.name
|
2016-06-19 18:02:32 +08:00
|
|
|
});
|
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
// Emitted if the UID validity value for the currently open mailbox
|
|
|
|
// changes during the current session.
|
|
|
|
this._imap.on('uidvalidity', () => this.emit('uidvalidity'))
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
// Emitted when message metadata (e.g. flags) changes externally.
|
|
|
|
this._imap.on('update', () => this.emit('update'))
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
this._imap.connect();
|
2016-06-19 18:02:32 +08:00
|
|
|
}
|
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
getIMAP() {
|
|
|
|
return this._imap;
|
|
|
|
}
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
runOperation(operation) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
this._queue.push({operation, resolve, reject});
|
|
|
|
if (this._imap.state === 'authenticated' && !this._current) {
|
|
|
|
this.processNextOperation();
|
|
|
|
}
|
2016-06-19 18:02:32 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
processNextOperation() {
|
|
|
|
if (this._current) { return; }
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
this._current = this._queue.shift();
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
if (!this._current) {
|
|
|
|
this.emit('queue-empty');
|
|
|
|
return;
|
|
|
|
}
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
const {operation, resolve, reject} = this._current;
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
console.log(`Starting task ${operation.description()}`)
|
|
|
|
const result = operation.run(this._db, this._imap);
|
|
|
|
if (result instanceof Promise === false) {
|
|
|
|
throw new Error(`Expected ${operation.constructor.name} to return promise.`);
|
|
|
|
}
|
|
|
|
result.catch((err) => {
|
|
|
|
this._current = null;
|
|
|
|
console.error(err);
|
|
|
|
reject();
|
2016-06-19 18:02:32 +08:00
|
|
|
})
|
|
|
|
.then(() => {
|
2016-06-20 15:19:16 +08:00
|
|
|
this._current = null;
|
|
|
|
console.log(`Finished task ${operation.description()}`)
|
|
|
|
resolve();
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
this.processNextOperation();
|
2016-06-19 18:02:32 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
class SyncWorker {
|
|
|
|
constructor(account, db) {
|
|
|
|
const main = new IMAPConnectionStateMachine(db, {
|
|
|
|
user: 'inboxapptest1@fastmail.fm',
|
|
|
|
password: 'trar2e',
|
|
|
|
host: 'mail.messagingengine.com',
|
|
|
|
port: 993,
|
|
|
|
tls: true,
|
2016-06-19 18:02:32 +08:00
|
|
|
});
|
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
// Todo: SyncWorker should decide what operations to queue and what params
|
|
|
|
// to pass them, and how often, based on SyncPolicy model (TBD).
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
main.on('ready', () => {
|
|
|
|
main.runOperation(new RefreshMailboxesOperation())
|
|
|
|
.then(() =>
|
|
|
|
this._db.Category.find({where: {role: 'inbox'}})
|
|
|
|
).then((inboxCategory) => {
|
|
|
|
if (!inboxCategory) {
|
|
|
|
throw new Error("Unable to find an inbox category.")
|
|
|
|
}
|
|
|
|
main.on('mail', () => {
|
|
|
|
main.runOperation(new DiscoverMessagesOperation(inboxCategory));
|
|
|
|
})
|
|
|
|
main.on('update', () => {
|
|
|
|
main.runOperation(new ScanUIDsOperation(inboxCategory));
|
|
|
|
})
|
|
|
|
main.on('queue-empty', () => {
|
|
|
|
main.getIMAP().openBoxAsync(inboxCategory.name, true).then(() => {
|
|
|
|
console.log("Idling on inbox category");
|
|
|
|
});
|
2016-06-19 18:02:32 +08:00
|
|
|
});
|
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
setInterval(() => this.syncAllMailboxes(), 120 * 1000);
|
|
|
|
this.syncAllMailboxes();
|
2016-06-19 18:02:32 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
this._db = db;
|
|
|
|
this._main = main;
|
|
|
|
}
|
2016-06-19 18:02:32 +08:00
|
|
|
|
2016-06-20 15:19:16 +08:00
|
|
|
syncAllMailboxes() {
|
|
|
|
const {Category} = this._db;
|
|
|
|
Category.findAll().then((categories) => {
|
|
|
|
const priority = ['inbox', 'drafts', 'sent'];
|
|
|
|
const sorted = categories.sort((a, b) => {
|
|
|
|
return priority.indexOf(b.role) - priority.indexOf(a.role);
|
|
|
|
})
|
|
|
|
for (const cat of sorted) {
|
|
|
|
this._main.runOperation(new DiscoverMessagesOperation(cat));
|
|
|
|
this._main.runOperation(new ScanUIDsOperation(cat));
|
|
|
|
}
|
|
|
|
});
|
2016-06-19 18:02:32 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = SyncWorker;
|