Skip to content

Commit e915ace

Browse files
committed
chore: refactor imap service
1 parent 95d941b commit e915ace

File tree

6 files changed

+280
-241
lines changed

6 files changed

+280
-241
lines changed

apps/api/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
"@types/bcrypt": "^5.0.0",
1818
"@types/email-reply-parser": "^1",
1919
"@types/formidable": "^3.4.5",
20+
"@types/imap": "^0.8.42",
2021
"@types/jsonwebtoken": "^8.5.8",
22+
"@types/mailparser": "^3.4.5",
2123
"@types/node": "^17.0.23",
2224
"@types/nodemailer": "^6.4.14",
2325
"@types/passport-local": "^1.0.35",

apps/api/src/lib/imap.ts

Lines changed: 5 additions & 240 deletions
Original file line numberDiff line numberDiff line change
@@ -1,245 +1,10 @@
1-
const Imap = require("imap");
2-
var EmailReplyParser = require("email-reply-parser");
1+
import { ImapService } from "./services/imap.service";
32

4-
import { GoogleAuth } from "google-auth-library";
5-
import { prisma } from "../prisma";
6-
7-
const { simpleParser } = require("mailparser");
8-
9-
require("dotenv").config();
10-
11-
const client = prisma;
12-
13-
const date = new Date();
14-
const today = date.getDate();
15-
const month = date.getMonth();
16-
const year = date.getFullYear();
17-
//@ts-ignore
18-
const d = new Date([year, month, today]);
19-
20-
// Function to get or refresh the access token
21-
async function getValidAccessToken(queue: any) {
22-
const {
23-
clientId,
24-
clientSecret,
25-
refreshToken,
26-
accessToken,
27-
expiresIn,
28-
username,
29-
} = queue;
30-
31-
// Check if token is still valid
32-
const now = Math.floor(Date.now() / 1000);
33-
if (accessToken && expiresIn && now < expiresIn) {
34-
return accessToken;
35-
}
36-
37-
// Initialize GoogleAuth client
38-
const auth = new GoogleAuth({
39-
clientOptions: {
40-
clientId: clientId,
41-
clientSecret: clientSecret,
42-
},
43-
});
44-
45-
const oauth2Client = auth.fromJSON({
46-
client_id: clientId,
47-
client_secret: clientSecret,
48-
refresh_token: refreshToken,
49-
});
50-
51-
// Refresh the token if expired
52-
const tokenInfo = await oauth2Client.getAccessToken();
53-
54-
const expiryDate = queue.expiresIn + 3600;
55-
56-
if (tokenInfo.token) {
57-
await prisma.emailQueue.update({
58-
where: { id: queue.id },
59-
data: {
60-
accessToken: tokenInfo.token,
61-
expiresIn: expiryDate,
62-
},
63-
});
64-
return tokenInfo.token;
65-
} else {
66-
throw new Error("Unable to refresh access token.");
67-
}
68-
}
69-
70-
// Function to generate XOAUTH2 string
71-
function generateXOAuth2Token(user: string, accessToken: string) {
72-
const authString = [
73-
"user=" + user,
74-
"auth=Bearer " + accessToken,
75-
"",
76-
"",
77-
].join("\x01");
78-
return Buffer.from(authString).toString("base64");
79-
}
80-
81-
async function returnImapConfig(queue: any) {
82-
switch (queue.serviceType) {
83-
case "gmail":
84-
const validatedAccessToken = await getValidAccessToken(queue);
85-
return {
86-
user: queue.username,
87-
host: queue.hostname,
88-
port: 993,
89-
tls: true,
90-
xoauth2: generateXOAuth2Token(queue.username, validatedAccessToken),
91-
tlsOptions: { rejectUnauthorized: false, servername: queue.hostname },
92-
};
93-
case "other":
94-
return {
95-
user: queue.username,
96-
password: queue.password,
97-
host: queue.hostname,
98-
port: queue.tls ? 993 : 143,
99-
tls: queue.tls,
100-
tlsOptions: { rejectUnauthorized: false, servername: queue.hostname },
101-
};
102-
default:
103-
throw new Error("Unsupported service type");
104-
}
105-
}
106-
107-
export const getEmails = async () => {
3+
export const getEmails = async (): Promise<void> => {
1084
try {
109-
const queues = await client.emailQueue.findMany({});
110-
111-
for (let i = 0; i < queues.length; i++) {
112-
var imapConfig = await returnImapConfig(queues[i]);
113-
114-
if (!imapConfig) {
115-
continue;
116-
}
117-
118-
const imap = new Imap(imapConfig);
119-
imap.connect();
120-
121-
imap.once("ready", () => {
122-
imap.openBox("INBOX", false, () => {
123-
imap.search(["UNSEEN", ["ON", [date]]], (err: any, results: any) => {
124-
if (err) {
125-
console.log(err);
126-
return;
127-
}
128-
129-
if (!results || !results.length) {
130-
console.log("No new messages");
131-
imap.end();
132-
return;
133-
}
134-
135-
console.log(results.length + " num of emails");
136-
137-
const f = imap.fetch(results, { bodies: "" });
138-
f.on("message", (msg: any) => {
139-
msg.on("body", (stream: any) => {
140-
simpleParser(stream, async (err: any, parsed: any) => {
141-
const { from, subject, textAsHtml, text, html } = parsed;
142-
143-
var reply_text = new EmailReplyParser().read(text);
144-
145-
if (subject?.includes("Re:")) {
146-
const ticketIdMatch = subject.match(/#(\d+)/);
147-
if (!ticketIdMatch) {
148-
console.log(
149-
"Could not extract ticket ID from subject:",
150-
subject
151-
);
152-
return;
153-
}
154-
155-
const ticketId = ticketIdMatch[1];
156-
157-
const find = await client.ticket.findFirst({
158-
where: {
159-
Number: Number(ticketId),
160-
},
161-
});
162-
163-
if (find) {
164-
return await client.comment.create({
165-
data: {
166-
text: text
167-
? reply_text.fragments[0]._content
168-
: "No Body",
169-
userId: null,
170-
ticketId: find.id,
171-
reply: true,
172-
replyEmail: from.value[0].address,
173-
public: true,
174-
},
175-
});
176-
} else {
177-
console.log("Ticket not found");
178-
}
179-
} else {
180-
const imap = await client.imap_Email.create({
181-
data: {
182-
from: from.value[0].address,
183-
subject: subject ? subject : "No Subject",
184-
body: text ? text : "No Body",
185-
html: html ? html : "",
186-
text: textAsHtml,
187-
},
188-
});
189-
190-
const ticket = await client.ticket.create({
191-
data: {
192-
email: from.value[0].address,
193-
name: from.value[0].name,
194-
title: imap.subject ? imap.subject : "-",
195-
isComplete: Boolean(false),
196-
priority: "Low",
197-
fromImap: Boolean(true),
198-
detail: html ? html : textAsHtml,
199-
},
200-
});
201-
202-
console.log(imap, ticket);
203-
}
204-
});
205-
});
206-
msg.once("attributes", (attrs: any) => {
207-
const { uid } = attrs;
208-
imap.addFlags(uid, ["\\Seen"], () => {
209-
// Mark the email as read after reading it
210-
console.log("Marked as read!");
211-
});
212-
});
213-
});
214-
f.once("error", (ex: any) => {
215-
return Promise.reject(ex);
216-
});
217-
f.once("end", () => {
218-
console.log("Done fetching all messages!");
219-
imap.end();
220-
});
221-
});
222-
});
223-
});
224-
225-
imap.once("error", (err: any) => {
226-
console.log(err);
227-
});
228-
229-
imap.once("end", () => {
230-
console.log("Connection ended");
231-
});
232-
}
233-
234-
console.log("loop completed");
5+
await ImapService.fetchEmails();
6+
console.log('Email fetch completed');
2357
} catch (error) {
236-
console.log("an error occurred ", error);
8+
console.error('An error occurred while fetching emails:', error);
2379
}
23810
};
239-
240-
// Helper function to extract reply text
241-
function extractReplyText(emailBody: string): string {
242-
// Implement logic to extract reply text from the email body
243-
// This might involve removing quoted text from previous emails
244-
return emailBody; // Placeholder, replace with actual logic
245-
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { GoogleAuth } from 'google-auth-library';
2+
import { prisma } from '../../prisma';
3+
import { EmailQueue } from '../types/email';
4+
5+
export class AuthService {
6+
public static generateXOAuth2Token(username: string, accessToken: string): string {
7+
const authString = [
8+
`user=${username}`,
9+
`auth=Bearer ${accessToken}`,
10+
'',
11+
''
12+
].join('\x01');
13+
return Buffer.from(authString).toString('base64');
14+
}
15+
16+
static async getValidAccessToken(queue: EmailQueue): Promise<string> {
17+
const { clientId, clientSecret, refreshToken, accessToken, expiresIn } = queue;
18+
19+
const now = Math.floor(Date.now() / 1000);
20+
if (accessToken && expiresIn && now < expiresIn) {
21+
return accessToken;
22+
}
23+
24+
const auth = new GoogleAuth({
25+
clientOptions: { clientId, clientSecret }
26+
});
27+
28+
const oauth2Client = auth.fromJSON({
29+
client_id: clientId,
30+
client_secret: clientSecret,
31+
refresh_token: refreshToken
32+
});
33+
34+
const tokenInfo = await oauth2Client.getAccessToken();
35+
if (!tokenInfo.token) {
36+
throw new Error('Unable to refresh access token.');
37+
}
38+
39+
const expiryDate = (expiresIn || 0) + 3600;
40+
await prisma.emailQueue.update({
41+
where: { id: queue.id },
42+
data: {
43+
accessToken: tokenInfo.token,
44+
expiresIn: expiryDate
45+
}
46+
});
47+
48+
return tokenInfo.token;
49+
}
50+
}

0 commit comments

Comments
 (0)