vpn.email.client
Version:
Vpn.Email client IMAP core
357 lines (264 loc) • 9.14 kB
text/typescript
import * as Imap from "imap";
import * as Net from 'net'
import * as Async from "async";
import * as mailcomposer from "mailcomposer";
import * as mailparser from 'mailparser';
import * as shotrtID from 'shortid'
import * as Compress from './compress'
const idleInterval = 1000 * 60 * 60 * 24
const connectCheckTimeOut = 1000 * 15
const openBox = ( imap: startImap, mailBox, cb: ( err?: Error ) => void ) => {
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: IinputData, debug: boolean, forever: boolean ) => {
const ret : IMAP.Config = {
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;
}
export default class startImap {
private destroy = false;
private AUTHENTICATIONFAILED_error = false
private fetching = false
private fetchWait = false
private busy = false
private delayTime = null
private idle = true
private reserting = false
private saveing = false
public imap: IMAP.Connection = null
private yyy: IMAP.Config = null
private debug = true
private Endprocess = false
private lastNewMailID = null
private savefunctioncallBack = null
private scanEmail = ( imap: IMAP.Connection ) => {
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:number = 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: mailparser.ParsedMail ) => {
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 ()
})
})
})
}
private 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 )
}
private idleSupport () {
this.idle = this.imap.serverSupports( 'IDLE' )
if ( !this.idle ) {
this.debug ? console.log ( 'idle not Support' ) : null
return this.noopProcess ()
}
}
public save = ( enCtypeMessage: Buffer|string , writeFolder: string, CallBack: ICallBack ) => {
this.saveing = this.busy = true
if ( CallBack && typeof CallBack === 'function')
this.savefunctioncallBack = CallBack
const socket: Net.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: Buffer, 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 );
})
}
public 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
}
}
public 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 )
}
private 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 ();
}
constructor ( private imapAcc: IinputData, private listeningFolder: string, private delBox: boolean,
private newMailFunction: ( msg: Buffer ) => void, private ready: ( ready: boolean ) => void, private CallBack: ICallBack ) {
this.yyy = getImapConnect ( imapAcc, false, true );
this.connectImap ()
}
}