vpn.email.client
Version:
Vpn.Email client IMAP core
270 lines (268 loc) • 11 kB
JavaScript
"use strict";
const Imap = require("imap");
const Async = require("async");
const mailcomposer = require("mailcomposer");
const mailparser = require("mailparser");
const shotrtID = require("shortid");
const idleInterval = 1000 * 60 * 60 * 24;
const connectCheckTimeOut = 1000 * 15;
const openBox = (imap, mailBox, cb) => {
if (imap.imap.state !== 'authenticated') {
return setTimeout(() => {
openBox(imap, mailBox, cb);
}, 1000);
}
imap.imap.openBox(mailBox, false, err => {
if (err) {
return imap.imap.addBox(mailBox, err1 => {
if (err1)
return cb(err);
openBox(imap, mailBox, cb);
});
}
return cb();
});
};
/*
const _sockWriteAppendData = appendData => {
let val = appendData;
if ( Buffer.isBuffer( appendData ))
val = val.toString( 'utf8' );
this.debug && this.debug ( '=> ' + inspect(val));
this._sock.write ( val );
this._sock.write ( CRLF );
this.emit ( 'endSave' )
}
*/
const getImapConnect = (nodeImap, debug, forever) => {
const ret = {
user: nodeImap.imapUserName,
host: nodeImap.imapServer,
password: nodeImap.imapUserPassword,
port: parseInt(nodeImap.imapPortNumber),
socketTimeout: 0,
authTimeout: 25000,
connTimeout: 30000,
tls: nodeImap.imapSsl,
debug: debug
? (err) => { console.log(new Date(), ' = ', err); }
: null,
keepalive: {
idleInterval: idleInterval
}
};
return ret;
};
class startImap {
constructor(imapAcc, listeningFolder, delBox, newMailFunction, ready, CallBack) {
this.imapAcc = imapAcc;
this.listeningFolder = listeningFolder;
this.delBox = delBox;
this.newMailFunction = newMailFunction;
this.ready = ready;
this.CallBack = CallBack;
this.destroy = false;
this.AUTHENTICATIONFAILED_error = false;
this.fetching = false;
this.fetchWait = false;
this.busy = false;
this.delayTime = null;
this.idle = true;
this.reserting = false;
this.saveing = false;
this.imap = null;
this.yyy = null;
this.debug = true;
this.Endprocess = false;
this.lastNewMailID = null;
this.savefunctioncallBack = null;
this.scanEmail = (imap) => {
if (this.imap.state !== 'authenticated' || this.busy) {
//console.log ( '=====> scanEmail cancel :', `listenFolder[${this.listeningFolder}],fetching[${this.fetching}], this.imap.state [${this.imap.state}]`)
return this.fetchWait = true;
}
this.fetching = this.busy = true;
this.fetchWait = false;
const endFetch = () => {
this.fetching = this.busy = false;
if (this.Endprocess) {
//console.log ('skip scan email Endprocess!')
return this.endProcess();
}
if (this.fetchWait) {
//console.log ('this.fetchWait true, run scanEmail again!!!!', `listenFolder[${this.listeningFolder}]`)
return this.scanEmail(imap);
}
};
return imap.search(['UNSEEN'], (err, results) => {
if (err) {
//console.log ( '=================================> imap.search error ', err )
return endFetch();
}
if (!results || !results.length) {
//console.log ('no results ')
return endFetch();
}
if (!this.lastNewMailID) {
this.lastNewMailID = parseInt(results[0]) - 1;
}
const startId = parseInt(results[0]);
const stopId = parseInt(results[results.length - 1]);
if (startId !== this.lastNewMailID + 1) {
results = [];
for (let i = this.lastNewMailID + 1; i <= stopId; i++) {
results.push(i.toString());
}
}
this.lastNewMailID = stopId;
let fetch = imap.fetch(results, { markSeen: true, bodies: '' });
fetch.on('message', (msg, seqno) => {
let mp = new mailparser.MailParser();
mp.once('end', (msg) => {
if (msg.attachments && msg.attachments.length) {
const buffer = msg.attachments[0].content;
this.debug ? console.log(`new mail {${buffer.length}}`) : null;
return this.newMailFunction(buffer);
}
this.debug ? console.log('get new mail but have not attachments') : null;
});
msg.on('body', (data) => {
return data.pipe(mp);
});
});
fetch.once('error', err1 => {
console.log('=================================> imap.search fetch error:', err1);
return endFetch();
});
fetch.once('end', () => {
return Async.series([
next => imap.addFlags(results, ['\\Deleted'], next),
next => imap.expunge(results, next)
], () => {
fetch.removeAllListeners();
fetch = null;
this.busy = false;
this.debug ? console.log(`new mail: [${results}]`) : null;
if (this.fetchWait)
return this.scanEmail(imap);
return endFetch();
});
});
});
};
this.save = (enCtypeMessage, writeFolder, CallBack) => {
this.saveing = this.busy = true;
if (CallBack && typeof CallBack === 'function')
this.savefunctioncallBack = CallBack;
const socket = this.imap._sock;
const email = mailcomposer({
date: ' ',
messageId: shotrtID.generate(),
attachments: [{
filename: false,
content: enCtypeMessage
}]
});
let TimeOut = null;
return Async.waterfall([
next => email.build(next),
(_data, next) => {
this.debug ? console.time(`append{${enCtypeMessage.length}}`) : null;
TimeOut = setTimeout(() => {
this.debug ? console.log(`[${enCtypeMessage.length}]`, '============================> save time out') : null;
this.imap.end();
}, enCtypeMessage.length < 65536 ? 5000 : 15000);
this.imap.append(_data, { mailbox: writeFolder }, next);
}
], (err, no) => {
clearTimeout(TimeOut);
this.saveing = this.busy = false;
this.savefunctioncallBack = null;
this.debug ? console.timeEnd(`append{${enCtypeMessage.length}}`) : null;
this.debug ? console.log(`[${no}]`) : null;
return CallBack(err);
});
};
this.yyy = getImapConnect(imapAcc, false, true);
this.connectImap();
}
noopProcess() {
if (this.Endprocess || this.idle || !this.listeningFolder)
return this.debug ? console.log(`[${this.Endprocess}][${this.idle}][${this.listeningFolder.length}]`) : null;
setTimeout(() => {
if (this.imap.state !== 'authenticated' || !this.imap._queue || this.busy || this.imap._queue.length)
return this.noopProcess();
this.imap._enqueue('NOOP', true);
return this.noopProcess();
}, 10 + Math.random() * 90);
}
idleSupport() {
this.idle = this.imap.serverSupports('IDLE');
if (!this.idle) {
this.debug ? console.log('idle not Support') : null;
return this.noopProcess();
}
}
checkBusy() {
if (!this.imap || this.imap.state !== 'authenticated' || !this.imap._sock.writable || !this.imap._sock.readable) {
this.endProcess();
return false;
}
if (this.imap._queue) {
if (this.imap._queue.length)
return false;
return this.busy = true;
}
}
endProcess() {
if (this.Endprocess)
return;
//console.log ('imap endProcess ', this.listeningFolder)
this.Endprocess = true;
this.ready(false);
this.imap.end();
if (this.savefunctioncallBack) {
this.savefunctioncallBack(new Error('socket end'));
}
if (this.CallBack && typeof this.CallBack === 'function')
return this.CallBack(this.AUTHENTICATIONFAILED_error ? new Error('AUTHENTICATIONFAILED') : null);
return process.exit(this.AUTHENTICATIONFAILED_error ? 1 : 0);
}
connectImap() {
this.ready(false);
this.imap = new Imap(this.yyy);
this.destroy = this.fetching = this.AUTHENTICATIONFAILED_error = this.fetchWait = this.busy = false;
this.imap.once('ready', () => {
this.reserting = false;
// this.imap._sockWriteAppendData = _sockWriteAppendData
if (this.listeningFolder && this.listeningFolder.length) {
return openBox(this, this.listeningFolder, err => {
clearTimeout(this.delayTime);
if (err) {
this.debug ? console.log('imap openBox error!') : null;
return this.imap.end();
}
this.imap.on('mail', mail => {
return this.scanEmail(this.imap);
});
this.idleSupport();
this.ready(true);
});
}
this.ready(true);
});
this.imap.once('error', (err) => {
this.debug ? console.log('this.imap.on ERROR', `listen:[${this.listeningFolder}]`, err.message) : null;
this.AUTHENTICATIONFAILED_error = /AUTHENTICATIONFAILED/.test(err.textCode);
this.imap.end();
this.endProcess();
});
this.imap.once('end', () => {
//console.log ( 'this.imap.on END',`listen:[${this.listeningFolder}], saveing:[${this.saveing}], busy:[${this.busy}]`)
this.endProcess();
});
return this.imap.connect();
}
}
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = startImap;