The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/server/email/IMAPInterceptor.ts

148 lines
3.1 KiB

import { EventEmitter } from 'events';
import IMAP from 'imap';
import type Connection from 'imap';
import { simpleParser, ParsedMail } from 'mailparser';
type IMAPOptions = {
deleteAfterRead: boolean;
filter: any[];
rejectBeforeTS?: Date;
markSeen: boolean;
};
export declare interface IMAPInterceptor {
on(event: 'email', listener: (email: ParsedMail) => void): this;
on(event: string, listener: Function): this;
}
export class IMAPInterceptor extends EventEmitter {
private imap: IMAP;
constructor(
imapConfig: IMAP.Config,
private options: IMAPOptions = {
deleteAfterRead: false,
filter: ['UNSEEN'],
markSeen: true,
},
) {
super();
this.imap = new IMAP({
connTimeout: 30000,
keepalive: true,
...imapConfig,
});
// On successfully connected.
this.imap.on('ready', () => {
if (this.imap.state !== 'disconnected') {
this.openInbox((err) => {
if (err) {
throw err;
}
// fetch new emails & wait [IDLE]
this.getEmails();
// If new message arrived, fetch them
this.imap.on('mail', () => {
this.getEmails();
});
});
} else {
this.log('IMAP did not connected.');
this.imap.end();
}
});
this.imap.on('error', (err: Error) => {
this.log('Error occurred: ', err);
throw err;
});
}
log(...msg: any[]): void {
console.log(...msg);
}
openInbox(cb: (error: Error, mailbox: Connection.Box) => void): void {
this.imap.openBox('INBOX', false, cb);
}
start(): void {
this.imap.connect();
}
isActive(): boolean {
if (this.imap && this.imap.state && this.imap.state === 'disconnected') {
return false;
}
return true;
}
stop(callback = new Function()): void {
this.imap.end();
this.imap.once('end', callback);
}
restart(): void {
this.stop(() => {
this.log('Restarting IMAP ....');
this.start();
});
}
// Fetch all UNSEEN messages and pass them for further processing
getEmails(): void {
this.imap.search(this.options.filter, (err, newEmails) => {
if (err) {
this.log(err);
throw err;
}
// newEmails => array containing serials of unseen messages
if (newEmails.length > 0) {
const fetch = this.imap.fetch(newEmails, {
bodies: ['HEADER', 'TEXT', ''],
struct: true,
markSeen: this.options.markSeen,
});
fetch.on('message', (msg, seqno) => {
msg.on('body', (stream, type) => {
if (type.which !== '') {
return;
}
simpleParser(stream, (_err, email) => {
if (this.options.rejectBeforeTS && email.date && email.date < this.options.rejectBeforeTS) {
this.log('Rejecting email', email.subject);
return;
}
this.emit('email', email);
});
});
// On fetched each message, pass it further
msg.once('end', () => {
// delete message from inbox
if (this.options.deleteAfterRead) {
this.imap.seq.addFlags(seqno, 'Deleted', (err) => {
if (err) {
this.log(`Mark deleted error: ${err}`);
}
});
}
});
});
fetch.once('error', (err) => {
this.log(`Fetch error: ${err}`);
});
}
});
}
}