203 lines
7.1 KiB
TypeScript
203 lines
7.1 KiB
TypeScript
import Imap, { Box } from "imap";
|
|
import { resolve } from "path";
|
|
import { getMailbox, getMailboxModseq, updateMailbox, updateMailboxModseq } from "../../db/imap/imap-db";
|
|
import { Attrs, AttrsWithEnvelope } from "../../interfaces/mail/attrs.interface";
|
|
import logger from "../../system/Logger";
|
|
import RegisterMessageInApp from "../message/saveMessage";
|
|
import { saveMessage } from "../message/storeMessage";
|
|
import updateMessage from "../message/updateMessage";
|
|
|
|
export interface ImapInfo {
|
|
uid: number;
|
|
modseq: string;
|
|
flags: string[];
|
|
}
|
|
|
|
export default class Mailbox {
|
|
imap: Imap;
|
|
boxName: string;
|
|
id: number;
|
|
box: Box;
|
|
msgToSync: number;
|
|
syncing: boolean;
|
|
|
|
constructor(_imap: Imap, _boxId: number, _boxName: string) {
|
|
this.imap = _imap;
|
|
this.boxName = _boxName;
|
|
this.id = _boxId;
|
|
this.box;
|
|
this.msgToSync = 0;
|
|
this.syncing = false;
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
// get mailbox from the database
|
|
this.box = (await getMailbox(this.id))[0];
|
|
const isReadOnly = false;
|
|
this.imap.openBox(this.boxName, isReadOnly, (err, box) => {
|
|
if (err) logger.err(err);
|
|
|
|
// sync messages and flags
|
|
this.initSync(box);
|
|
|
|
// wait for new mails
|
|
this.imap.on("mail", (numNewMsgs: number) => {
|
|
if (!this.syncing) {
|
|
// if not syncing restart a sync
|
|
this.syncManager(this.box.uidnext - 1, this.box.uidnext + numNewMsgs - 1);
|
|
} else {
|
|
// else save number of message to sync latter
|
|
this.msgToSync += numNewMsgs;
|
|
}
|
|
});
|
|
|
|
// wait for flags update
|
|
this.imap.on("update", (seqno: number, info: ImapInfo) => {
|
|
logger.log(`Update message ${info.uid} with ${info.flags}`);
|
|
const updateMsg = new updateMessage(info.uid, info.flags);
|
|
updateMsg.updateFlags();
|
|
});
|
|
|
|
// wait for deletion
|
|
this.imap.on("expunge", (seqno: number) => {
|
|
console.log("Message with sequence number " + seqno + " has been deleted from the server.");
|
|
});
|
|
});
|
|
}
|
|
|
|
async updateModseq(newModseq: number) {
|
|
updateMailboxModseq(this.id, newModseq).then(() => {
|
|
this.box.highestmodseq = newModseq.toString();
|
|
});
|
|
}
|
|
|
|
async initSync(box: Box) {
|
|
// sync mail only if has new messages
|
|
if (this.box.uidnext < box.uidnext) {
|
|
this.syncManager(this.box.uidnext, box.uidnext);
|
|
} else {
|
|
logger.log("Mail already up to date");
|
|
}
|
|
|
|
// sync flags
|
|
const lastModseq = (await getMailboxModseq(this.id))[0]?.modseq ?? 0;
|
|
if (parseInt(box.highestmodseq) > lastModseq) {
|
|
const fetchStream = this.imap.fetch("1:*", { bodies: "", modifiers: { changedsince: lastModseq } });
|
|
fetchStream.on("message", (message) => {
|
|
message.once("attributes", (attrs) => {
|
|
const updateMsg = new updateMessage(attrs.uid, attrs.flags);
|
|
updateMsg.updateFlags();
|
|
});
|
|
});
|
|
fetchStream.once("error", function (err) {
|
|
logger.err("Fetch error when syncing flags: " + err);
|
|
});
|
|
fetchStream.once("end", function () {
|
|
logger.log("Done fetching new flags");
|
|
});
|
|
} else {
|
|
logger.log("Flags already up to date");
|
|
}
|
|
this.updateModseq(parseInt(box.highestmodseq));
|
|
}
|
|
|
|
syncManager = async (savedUid: number, currentUid: number) => {
|
|
this.syncing = true;
|
|
logger.log(`Fetching from ${savedUid} to ${currentUid} uid`);
|
|
const nbMessageToSync = currentUid - savedUid;
|
|
let STEP = nbMessageToSync > 300 ? Math.floor(nbMessageToSync / 7) : nbMessageToSync;
|
|
let mails: AttrsWithEnvelope[] = [];
|
|
|
|
for (let i = 0; i < nbMessageToSync; i += STEP) {
|
|
mails = [];
|
|
try {
|
|
// fetch mails
|
|
let secondUid = savedUid + STEP < currentUid ? savedUid + STEP : currentUid;
|
|
await this.mailFetcher(savedUid, secondUid, mails);
|
|
logger.log(`Fetched ${STEP} uids (${mails.length} messages)`);
|
|
// save same in the database
|
|
for (let k = 0; k < mails.length; k++) {
|
|
try {
|
|
const messageId = await saveMessage(mails[k], this.id, this.imap);
|
|
const register = new RegisterMessageInApp(messageId, mails[k], this.id);
|
|
await register.save();
|
|
} catch (error) {
|
|
logger.err("Failed to save a message: " + error);
|
|
}
|
|
}
|
|
savedUid = secondUid;
|
|
this.box.uidnext += savedUid;
|
|
|
|
updateMailbox(this.id, savedUid);
|
|
} catch (error) {
|
|
logger.err("Failed to sync message " + error);
|
|
}
|
|
logger.log(`Saved messages ${i + STEP > nbMessageToSync ? nbMessageToSync : i + STEP}/${nbMessageToSync}`);
|
|
}
|
|
|
|
// if has receive new msg during last sync then start a new sync
|
|
if (this.msgToSync > 0) {
|
|
const currentUid = this.box.uidnext;
|
|
this.box.uidnext += this.msgToSync;
|
|
// reset value to allow to detect new incoming message while syncing
|
|
this.msgToSync = 0;
|
|
await this.syncManager(currentUid, this.box.uidnext);
|
|
}
|
|
this.syncing = false;
|
|
logger.log(`Finished syncing messages`);
|
|
};
|
|
|
|
async mailFetcher(startUid: number, endUid: number, mails: Attrs[]): Promise<any> {
|
|
return new Promise((resolve, reject) => {
|
|
const f = this.imap.fetch(`${startUid}:${endUid}`, {
|
|
size: true,
|
|
envelope: true,
|
|
});
|
|
|
|
f.on("message", (msg, seqno) => {
|
|
msg.once("attributes", (attrs: AttrsWithEnvelope) => {
|
|
mails.push(attrs);
|
|
});
|
|
});
|
|
|
|
f.once("error", (err) => {
|
|
logger.err("Fetch error: " + err);
|
|
reject(1);
|
|
});
|
|
|
|
f.once("end", async () => {
|
|
resolve(0);
|
|
});
|
|
});
|
|
}
|
|
|
|
addFlag(source: string, flags: string[]): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
this.imap.addFlags(source, flags, (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
removeFlag(source: string, flags: string[]): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
this.imap.delFlags(source, flags, (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
move(source: string, mailboxName: string, callback: (error: Error) => void) {
|
|
this.imap.move(source, mailboxName, callback);
|
|
}
|
|
}
|