177 lines
6.1 KiB
TypeScript
177 lines
6.1 KiB
TypeScript
import Imap, { ImapMessageAttributes, Box } from "imap";
|
|
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.syncMail(this.box.uidnext, this.box.uidnext + numNewMsgs);
|
|
} 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();
|
|
});
|
|
});
|
|
}
|
|
|
|
async updateModseq(newModseq: number) {
|
|
updateMailboxModseq(this.id, newModseq).then(() => {
|
|
this.box.highestmodseq = newModseq;
|
|
});
|
|
}
|
|
|
|
async initSync(box: Box) {
|
|
// sync mail only if has new messages
|
|
if (this.box.uidnext < box.uidnext) {
|
|
this.syncMail(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 (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(box.highestmodseq);
|
|
}
|
|
|
|
async syncMail(savedUid: number, currentUid: number) {
|
|
this.syncing = true;
|
|
const promises: Promise<unknown>[] = [];
|
|
const mails: Attrs[] = [];
|
|
logger.log(`Syncing from ${savedUid} to ${currentUid} uid`);
|
|
const f = this.imap.seq.fetch(`${savedUid}:${currentUid}`, {
|
|
size: true,
|
|
envelope: true,
|
|
});
|
|
|
|
f.on("message", (msg, seqno) => {
|
|
msg.once("attributes", (attrs: AttrsWithEnvelope) => {
|
|
mails.push(attrs);
|
|
promises.push(saveMessage(attrs, this.id, this.imap));
|
|
});
|
|
});
|
|
|
|
f.once("error", (err) => {
|
|
logger.err("Fetch error: " + err);
|
|
});
|
|
|
|
f.once("end", async () => {
|
|
let step = 20;
|
|
for (let i = 0; i < promises.length; i += step) {
|
|
for (let j = i; j < (i + step && promises.length); j++) {
|
|
await new Promise((resolve, reject) => {
|
|
promises[j]
|
|
.then(async (res: number) => {
|
|
const register = new RegisterMessageInApp(res, mails[j], this.id);
|
|
await register.save();
|
|
resolve("");
|
|
})
|
|
.catch((err) => {
|
|
reject(err);
|
|
});
|
|
});
|
|
}
|
|
logger.log(`Saved messages ${i + step > promises.length ? promises.length : i + step}/${mails.length}`);
|
|
updateMailbox(this.id, mails[i].uid);
|
|
}
|
|
updateMailbox(this.id, currentUid);
|
|
this.syncing = false;
|
|
|
|
// 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;
|
|
this.syncMail(currentUid, this.box.uidnext);
|
|
}
|
|
});
|
|
}
|
|
|
|
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();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|