UNPKG

vpn.email.client

Version:
357 lines (264 loc) 9.14 kB
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 () } }