@cemiar/cemiarlink-sdk 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ export interface EmailAttachment {
2
+ filename: string;
3
+ path: string;
4
+ cid?: string;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,10 @@
1
+ export interface EmailReport {
2
+ emailId: string;
3
+ recipient: string;
4
+ buffer?: Buffer;
5
+ subject: string;
6
+ date: string;
7
+ extension?: string;
8
+ textContent: string;
9
+ from: string;
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export default interface GetEmailReportOptions {
2
+ includeRead?: boolean;
3
+ skipRead?: boolean;
4
+ subfolders?: string[];
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,8 @@
1
+ export interface GmailElement {
2
+ id: string;
3
+ threadId: string;
4
+ }
5
+ export interface GMailFolder {
6
+ id: string;
7
+ name: string;
8
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ export interface TransactionWatch {
2
+ itemNumbers: string[];
3
+ clientCode: string;
4
+ financement: string;
5
+ from: string;
6
+ transfert: boolean;
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,48 @@
1
+ import { AxiosInstance } from 'axios';
2
+ import nodemailer from 'nodemailer';
3
+ import { EmailAttachment } from '../models/mail/EmailAttachment';
4
+ import { EmailReport } from '../models/mail/EmailReport';
5
+ import GetEmailReportOptions from '../models/mail/GetEmailReportOptions';
6
+ import { GMailFolder } from '../models/mail/GmailMail';
7
+ import { TransactionWatch } from '../models/mail/TransactionWatchList';
8
+ export declare class MailService {
9
+ user?: string;
10
+ axiosInstance: AxiosInstance;
11
+ transporter: nodemailer.Transporter;
12
+ constructor(mailUrl: string | undefined, headers: any, user?: string, password?: string);
13
+ getEmailFolders(): Promise<GMailFolder[]>;
14
+ getEmailMessageIds(filter: object): Promise<string[]>;
15
+ updateEmailMetadata(mailId: string, payload: object): Promise<void>;
16
+ private getEmailContent;
17
+ private findAttachment;
18
+ private findData;
19
+ private fetchAttachment;
20
+ private extractSubject;
21
+ private extractTextContent;
22
+ private extractRecipientEmail;
23
+ private extractReturnAddress;
24
+ private extractTransactionWatchData;
25
+ private buildQueryString;
26
+ /**
27
+ * Asynchronously retrieves an email report based on the provided mail object and attachment type.
28
+ *
29
+ * @param to - email recipient
30
+ * @param subjectType
31
+ * @param {GetEmailReportOptions | undefined} options - The options for fetching email report.
32
+ * @returns {object | undefined} - The data of the retrieved report or undefined if not found or an error occurs.
33
+ * @throws {Error} - If an error occurs during the process.
34
+ */
35
+ getEmailReport(to: string, subjectType: string, options?: GetEmailReportOptions): Promise<void>;
36
+ /**
37
+ * Asynchronously retrieves all emails' reports based on a date.
38
+ *
39
+ * @param {Date} date - The date of email reports to fetch.
40
+ * @param {GetEmailReportOptions | undefined} options - The options for fetching email report.
41
+ * @returns {EmailReport[]} - The list of reports.
42
+ * @throws {Error} - If an error occurs during the process.
43
+ */
44
+ getEmailReports(date: Date, options?: GetEmailReportOptions): Promise<EmailReport[]>;
45
+ getTransactionWatchList(to: string, subjectType: string): Promise<TransactionWatch[]>;
46
+ deleteEmail(id: string): Promise<void>;
47
+ sendMail(subject: string, text: string, html: string, to: string[], attachments?: EmailAttachment[], cc?: string[]): Promise<void>;
48
+ }
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MailService = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const nodemailer_1 = __importDefault(require("nodemailer"));
9
+ const AxiosClient_1 = require("../utils/AxiosClient");
10
+ class MailService {
11
+ constructor(mailUrl, headers, user, password) {
12
+ this.axiosInstance = new AxiosClient_1.AxiosClient(mailUrl, headers).getInstance();
13
+ this.transporter = nodemailer_1.default.createTransport({
14
+ service: 'gmail',
15
+ auth: {
16
+ user: user,
17
+ pass: password,
18
+ },
19
+ });
20
+ }
21
+ async getEmailFolders() {
22
+ const folders = await this.axiosInstance.get('gmail/folders');
23
+ return folders.data.labels;
24
+ }
25
+ async getEmailMessageIds(filter) {
26
+ // get messages id from gmail
27
+ const queryString = this.buildQueryString(filter);
28
+ const url = `gmail?${queryString}`;
29
+ const res = await this.axiosInstance.get(url);
30
+ if (res.data.resultSizeEstimate == 0) {
31
+ return [];
32
+ }
33
+ return res.data.messages.map((message) => message.id);
34
+ }
35
+ async updateEmailMetadata(mailId, payload) {
36
+ // set mail as read
37
+ await this.axiosInstance.put('gmail/' + mailId, payload);
38
+ }
39
+ async getEmailContent(mailId) {
40
+ const { data } = await this.axiosInstance.get(`gmail/${mailId}?format=FULL`);
41
+ return data;
42
+ }
43
+ findAttachment(parts, attachmentTypes) {
44
+ if (!parts)
45
+ return null;
46
+ let attachment = null;
47
+ for (const part of parts) {
48
+ if (part.mimeType === 'multipart/mixed') {
49
+ for (const subPart of part.parts) {
50
+ if (attachmentTypes.some((type) => subPart.filename.includes(`.${type}`))) {
51
+ attachment = subPart;
52
+ break;
53
+ }
54
+ }
55
+ }
56
+ else {
57
+ if (attachmentTypes.some((type) => part.filename.includes(`.${type}`))) {
58
+ attachment = part;
59
+ break;
60
+ }
61
+ }
62
+ }
63
+ return attachment;
64
+ }
65
+ async findData(parts) {
66
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
67
+ let data = '';
68
+ if (parts) {
69
+ if ((_b = (_a = parts[0]) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.data) {
70
+ data = parts[0].body.data;
71
+ }
72
+ if ((_c = parts[0]) === null || _c === void 0 ? void 0 : _c.parts) {
73
+ if ((_f = (_e = (_d = parts[0]) === null || _d === void 0 ? void 0 : _d.parts[0]) === null || _e === void 0 ? void 0 : _e.body) === null || _f === void 0 ? void 0 : _f.data) {
74
+ data = parts[0].parts[0].body.data;
75
+ }
76
+ if ((_h = (_g = parts[0]) === null || _g === void 0 ? void 0 : _g.parts[0]) === null || _h === void 0 ? void 0 : _h.parts) {
77
+ if ((_m = (_l = (_k = (_j = parts[0]) === null || _j === void 0 ? void 0 : _j.parts[0]) === null || _k === void 0 ? void 0 : _k.parts[0]) === null || _l === void 0 ? void 0 : _l.body) === null || _m === void 0 ? void 0 : _m.data) {
78
+ data = parts[0].parts[0].parts[0].body.data;
79
+ }
80
+ }
81
+ }
82
+ }
83
+ return data;
84
+ }
85
+ async fetchAttachment(mailId, attachmentId) {
86
+ const { data } = await this.axiosInstance.get(`gmail/attachment/${mailId}/${attachmentId}`);
87
+ return data;
88
+ }
89
+ extractSubject(headers) {
90
+ return headers.find((header) => header.name === 'Subject')['value'].toLowerCase();
91
+ }
92
+ extractTextContent(parts) {
93
+ const textPart = parts ? parts.find((p) => p.mimeType === 'text/plain') : undefined;
94
+ return textPart ? Buffer.from(textPart.body.data, 'base64').toString('binary') : '';
95
+ }
96
+ extractRecipientEmail(headers) {
97
+ const from = headers.find((header) => header.name === 'To')['value'];
98
+ const emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
99
+ const match = from.match(emailPattern);
100
+ return match ? match[0] : null;
101
+ }
102
+ extractReturnAddress(headers) {
103
+ const from = headers.find((header) => header.name === 'From')['value'].toLowerCase();
104
+ try {
105
+ return from.match(/<(.*)>/)[1];
106
+ }
107
+ catch (_a) {
108
+ return from;
109
+ }
110
+ }
111
+ async extractTransactionWatchData(data, returnAddress) {
112
+ let transactionWatch = {
113
+ financement: '',
114
+ transfert: false,
115
+ itemNumbers: [],
116
+ clientCode: '',
117
+ from: returnAddress,
118
+ };
119
+ let buffer = Buffer.from(data, 'base64');
120
+ let rows = buffer
121
+ .toString()
122
+ .split('[')[0]
123
+ .replace(/[\r ()*]+/g, '')
124
+ .toLowerCase()
125
+ .split('\n');
126
+ for (let row of rows) {
127
+ if (row.indexOf('codeclient:') !== -1) {
128
+ transactionWatch.clientCode = row.split(':')[1];
129
+ }
130
+ if (row.indexOf('transfertdecrédits') !== -1) {
131
+ transactionWatch.transfert = true;
132
+ }
133
+ if (row.indexOf('financement:') !== -1) {
134
+ transactionWatch.financement = row.split(':')[1];
135
+ }
136
+ if (row.indexOf('numérostransactions:') !== -1) {
137
+ transactionWatch.itemNumbers = row.split(':')[1].split(',');
138
+ }
139
+ }
140
+ return transactionWatch;
141
+ }
142
+ buildQueryString(queryParams) {
143
+ return Object.entries(queryParams)
144
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
145
+ .join('&');
146
+ }
147
+ /**
148
+ * Asynchronously retrieves an email report based on the provided mail object and attachment type.
149
+ *
150
+ * @param to - email recipient
151
+ * @param subjectType
152
+ * @param {GetEmailReportOptions | undefined} options - The options for fetching email report.
153
+ * @returns {object | undefined} - The data of the retrieved report or undefined if not found or an error occurs.
154
+ * @throws {Error} - If an error occurs during the process.
155
+ */
156
+ async getEmailReport(to, subjectType, options) {
157
+ const date = new Date();
158
+ date.setDate(date.getDate());
159
+ const today = date.toISOString().split('T')[0];
160
+ date.setDate(date.getDate() + 1);
161
+ const tomorrow = date.toISOString().split('T')[0];
162
+ const filter = Object.assign({ q: `to: ${to} after:${today} before:${tomorrow}` }, ((options === null || options === void 0 ? void 0 : options.includeRead) ? {} : { labelIds: 'UNREAD' }));
163
+ const mails = await this.getEmailMessageIds(filter);
164
+ for (const mail of mails) {
165
+ const emailContent = await this.getEmailContent(mail);
166
+ const subject = this.extractSubject(emailContent.payload.headers);
167
+ if (subject.indexOf(subjectType) === -1) {
168
+ continue;
169
+ }
170
+ const { payload: { parts }, } = emailContent;
171
+ const attachment = this.findAttachment(parts, ['xlsx']);
172
+ if (!attachment || attachment.body.size === 0) {
173
+ throw new Error(`Could not find the attachment ending in 'xlsx' in the email: ${mail}.`);
174
+ }
175
+ const attachmentData = await this.fetchAttachment(mail, attachment.body.attachmentId);
176
+ let fileType = attachment.filename.split('.')[1];
177
+ fileType = '.' + fileType;
178
+ const buffer = Buffer.from(attachmentData.data, 'base64');
179
+ if (!fs_1.default.existsSync('./temp/')) {
180
+ fs_1.default.mkdirSync('./temp/');
181
+ }
182
+ fs_1.default.writeFileSync(`./temp/Rapport ${subjectType} du ${today}${fileType}`, buffer);
183
+ if (!(options === null || options === void 0 ? void 0 : options.skipRead))
184
+ await this.updateEmailMetadata(mail, {
185
+ removeLabelIds: ['UNREAD'],
186
+ });
187
+ }
188
+ }
189
+ /**
190
+ * Asynchronously retrieves all emails' reports based on a date.
191
+ *
192
+ * @param {Date} date - The date of email reports to fetch.
193
+ * @param {GetEmailReportOptions | undefined} options - The options for fetching email report.
194
+ * @returns {EmailReport[]} - The list of reports.
195
+ * @throws {Error} - If an error occurs during the process.
196
+ */
197
+ async getEmailReports(date, options) {
198
+ const today = date.toISOString().split('T')[0];
199
+ date.setDate(date.getDate() + 1);
200
+ const tomorrow = date.toISOString().split('T')[0];
201
+ let q = `after:${today} before:${tomorrow} in:inbox`;
202
+ if (!(options === null || options === void 0 ? void 0 : options.includeRead))
203
+ q += ' ' + 'in:unread';
204
+ let emailIds = await this.getEmailMessageIds({
205
+ q,
206
+ });
207
+ if (options === null || options === void 0 ? void 0 : options.subfolders) {
208
+ const gMailFolders = await this.getEmailFolders();
209
+ const folders = gMailFolders.filter((f) => { var _a; return (_a = options === null || options === void 0 ? void 0 : options.subfolders) === null || _a === void 0 ? void 0 : _a.includes(f.name); }).map((f) => f.id);
210
+ if (folders) {
211
+ let qu = `after:${today} before:${tomorrow}`;
212
+ if (!(options === null || options === void 0 ? void 0 : options.includeRead)) {
213
+ qu += ' ' + 'in:unread';
214
+ }
215
+ const folderEmails = await this.getEmailMessageIds({
216
+ q: qu,
217
+ labelIds: folders.join(','),
218
+ });
219
+ emailIds.push(...folderEmails);
220
+ }
221
+ }
222
+ const emailReports = [];
223
+ for (const emailId of emailIds) {
224
+ const emailContent = await this.getEmailContent(emailId);
225
+ const subject = this.extractSubject(emailContent.payload.headers);
226
+ const recipient = this.extractRecipientEmail(emailContent.payload.headers);
227
+ const date = new Date(parseInt(emailContent.internalDate)).toISOString().split('T')[0];
228
+ const { payload: { parts }, } = emailContent;
229
+ const textContent = this.extractTextContent(parts);
230
+ const attachment = this.findAttachment(parts, ['xlsx', 'csv', 'pdf']);
231
+ if (!recipient)
232
+ continue;
233
+ if (!attachment || attachment.body.size === 0) {
234
+ emailReports.push({
235
+ emailId,
236
+ recipient: recipient,
237
+ subject: subject,
238
+ date: date,
239
+ textContent: textContent,
240
+ extension: undefined,
241
+ buffer: undefined,
242
+ from: this.extractReturnAddress(emailContent.payload.headers),
243
+ });
244
+ }
245
+ else {
246
+ const attachmentData = await this.fetchAttachment(emailId, attachment.body.attachmentId);
247
+ const buffer = Buffer.from(attachmentData.data, 'base64');
248
+ emailReports.push({
249
+ emailId,
250
+ recipient,
251
+ subject,
252
+ buffer,
253
+ date,
254
+ extension: attachment.filename.split('.').pop(),
255
+ textContent,
256
+ from: this.extractReturnAddress(emailContent.payload.headers),
257
+ });
258
+ }
259
+ if (!(options === null || options === void 0 ? void 0 : options.skipRead))
260
+ await this.updateEmailMetadata(emailId, {
261
+ removeLabelIds: ['UNREAD'],
262
+ });
263
+ }
264
+ return emailReports.filter((item, index, self) => index ===
265
+ self.findIndex((t) => t.recipient === item.recipient && t.subject === item.subject && t.date === item.date));
266
+ }
267
+ async getTransactionWatchList(to, subjectType) {
268
+ const date = new Date();
269
+ date.setDate(date.getDate());
270
+ const today = date.toISOString().split('T')[0];
271
+ date.setDate(date.getDate() + 1);
272
+ const tomorrow = date.toISOString().split('T')[0];
273
+ const mails = await this.getEmailMessageIds({
274
+ labelIds: 'UNREAD',
275
+ q: `to: ${to} OR cc:${to} after:${today} before:${tomorrow}`,
276
+ });
277
+ let transactionWatchList = [];
278
+ for (const mail of mails) {
279
+ const emailContent = await this.getEmailContent(mail);
280
+ const subject = this.extractSubject(emailContent.payload.headers);
281
+ if (subject.indexOf(subjectType) === -1) {
282
+ continue;
283
+ }
284
+ const returnAddress = this.extractReturnAddress(emailContent.payload.headers);
285
+ const { payload: { parts }, } = emailContent;
286
+ const data = await this.findData(parts);
287
+ transactionWatchList.push(await this.extractTransactionWatchData(data, returnAddress));
288
+ await this.updateEmailMetadata(mail, {
289
+ removeLabelIds: ['UNREAD'],
290
+ });
291
+ }
292
+ return transactionWatchList;
293
+ }
294
+ async deleteEmail(id) {
295
+ await this.axiosInstance.delete(`gmail/${id}`);
296
+ }
297
+ async sendMail(subject, text, html, to, attachments, cc) {
298
+ const mailOptions = {
299
+ from: this.user,
300
+ to,
301
+ subject,
302
+ text,
303
+ html,
304
+ cc,
305
+ attachments,
306
+ };
307
+ await this.transporter.sendMail(mailOptions);
308
+ }
309
+ }
310
+ exports.MailService = MailService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cemiar/cemiarlink-sdk",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "CemiarLink SDK to access CemiarLink API services",
5
5
  "engines": {
6
6
  "npm": ">=9.5.1",
@@ -17,7 +17,10 @@
17
17
  "dependencies": {
18
18
  "axios": "^1.13.5",
19
19
  "path": "^0.12.7",
20
- "xlsx": "^0.18.5"
20
+ "xlsx": "^0.18.5",
21
+ "nodemailer": "^7.0.11",
22
+ "fs": "^0.0.1-security",
23
+ "@types/nodemailer": "^7.0.11"
21
24
  },
22
25
  "devDependencies": {
23
26
  "ts-node": "^10.9.2"