mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
✨ Add Force node reconnect option to IMAP trigger node (#1792)
* Adding reconnect feature to IMAP nodes * Fixing clear interval and persisting last UID in static data
This commit is contained in:
parent
0877b0779d
commit
3e98612881
|
@ -24,6 +24,10 @@ import {
|
||||||
|
|
||||||
import * as lodash from 'lodash';
|
import * as lodash from 'lodash';
|
||||||
|
|
||||||
|
import {
|
||||||
|
LoggerProxy as Logger
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export class EmailReadImap implements INodeType {
|
export class EmailReadImap implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'EmailReadImap',
|
displayName: 'EmailReadImap',
|
||||||
|
@ -158,6 +162,13 @@ export class EmailReadImap implements INodeType {
|
||||||
default: false,
|
default: false,
|
||||||
description: 'Do connect even if SSL certificate validation is not possible.',
|
description: 'Do connect even if SSL certificate validation is not possible.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Force reconnect',
|
||||||
|
name: 'forceReconnect',
|
||||||
|
type: 'number',
|
||||||
|
default: 60,
|
||||||
|
description: 'Sets an interval (in minutes) to force a reconnection.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -176,16 +187,8 @@ export class EmailReadImap implements INodeType {
|
||||||
const postProcessAction = this.getNodeParameter('postProcessAction') as string;
|
const postProcessAction = this.getNodeParameter('postProcessAction') as string;
|
||||||
const options = this.getNodeParameter('options', {}) as IDataObject;
|
const options = this.getNodeParameter('options', {}) as IDataObject;
|
||||||
|
|
||||||
let searchCriteria = [
|
const staticData = this.getWorkflowStaticData('node');
|
||||||
'UNSEEN',
|
Logger.debug('Loaded static data for node "EmailReadImap"', {staticData});
|
||||||
];
|
|
||||||
if (options.customEmailConfig !== undefined) {
|
|
||||||
try {
|
|
||||||
searchCriteria = JSON.parse(options.customEmailConfig as string);
|
|
||||||
} catch (error) {
|
|
||||||
throw new NodeOperationError(this.getNode(), `Custom email config is not valid JSON.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the email text
|
// Returns the email text
|
||||||
const getText = async (parts: any[], message: Message, subtype: string) => { // tslint:disable-line:no-any
|
const getText = async (parts: any[], message: Message, subtype: string) => { // tslint:disable-line:no-any
|
||||||
|
@ -237,7 +240,7 @@ export class EmailReadImap implements INodeType {
|
||||||
|
|
||||||
|
|
||||||
// Returns all the new unseen messages
|
// Returns all the new unseen messages
|
||||||
const getNewEmails = async (connection: ImapSimple, searchCriteria: string[]): Promise<INodeExecutionData[]> => {
|
const getNewEmails = async (connection: ImapSimple, searchCriteria: Array<string | string[]>): Promise<INodeExecutionData[]> => {
|
||||||
const format = this.getNodeParameter('format', 0) as string;
|
const format = this.getNodeParameter('format', 0) as string;
|
||||||
|
|
||||||
let fetchOptions = {};
|
let fetchOptions = {};
|
||||||
|
@ -277,6 +280,12 @@ export class EmailReadImap implements INodeType {
|
||||||
const dataPropertyAttachmentsPrefixName = this.getNodeParameter('dataPropertyAttachmentsPrefixName') as string;
|
const dataPropertyAttachmentsPrefixName = this.getNodeParameter('dataPropertyAttachmentsPrefixName') as string;
|
||||||
|
|
||||||
for (const message of results) {
|
for (const message of results) {
|
||||||
|
if (staticData.lastMessageUid !== undefined && message.attributes.uid <= (staticData.lastMessageUid as number)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (staticData.lastMessageUid === undefined || staticData.lastMessageUid as number < message.attributes.uid) {
|
||||||
|
staticData.lastMessageUid = message.attributes.uid;
|
||||||
|
}
|
||||||
const part = lodash.find(message.parts, { which: '' });
|
const part = lodash.find(message.parts, { which: '' });
|
||||||
|
|
||||||
if (part === undefined) {
|
if (part === undefined) {
|
||||||
|
@ -295,6 +304,12 @@ export class EmailReadImap implements INodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const message of results) {
|
for (const message of results) {
|
||||||
|
if (staticData.lastMessageUid !== undefined && message.attributes.uid <= (staticData.lastMessageUid as number)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (staticData.lastMessageUid === undefined || staticData.lastMessageUid as number < message.attributes.uid) {
|
||||||
|
staticData.lastMessageUid = message.attributes.uid;
|
||||||
|
}
|
||||||
const parts = getParts(message.attributes.struct!);
|
const parts = getParts(message.attributes.struct!);
|
||||||
|
|
||||||
newEmail = {
|
newEmail = {
|
||||||
|
@ -335,6 +350,12 @@ export class EmailReadImap implements INodeType {
|
||||||
}
|
}
|
||||||
} else if (format === 'raw') {
|
} else if (format === 'raw') {
|
||||||
for (const message of results) {
|
for (const message of results) {
|
||||||
|
if (staticData.lastMessageUid !== undefined && message.attributes.uid <= (staticData.lastMessageUid as number)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (staticData.lastMessageUid === undefined || staticData.lastMessageUid as number < message.attributes.uid) {
|
||||||
|
staticData.lastMessageUid = message.attributes.uid;
|
||||||
|
}
|
||||||
const part = lodash.find(message.parts, { which: 'TEXT' });
|
const part = lodash.find(message.parts, { which: 'TEXT' });
|
||||||
|
|
||||||
if (part === undefined) {
|
if (part === undefined) {
|
||||||
|
@ -366,6 +387,33 @@ export class EmailReadImap implements INodeType {
|
||||||
},
|
},
|
||||||
onmail: async () => {
|
onmail: async () => {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
|
let searchCriteria = [
|
||||||
|
'UNSEEN',
|
||||||
|
] as Array<string | string[]>;
|
||||||
|
if (options.customEmailConfig !== undefined) {
|
||||||
|
try {
|
||||||
|
searchCriteria = JSON.parse(options.customEmailConfig as string);
|
||||||
|
} catch (error) {
|
||||||
|
throw new NodeOperationError(this.getNode(), `Custom email config is not valid JSON.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (staticData.lastMessageUid !== undefined) {
|
||||||
|
searchCriteria.push(['UID', `${staticData.lastMessageUid as number}:*`]);
|
||||||
|
/**
|
||||||
|
* A short explanation about UIDs and how they work
|
||||||
|
* can be found here: https://dev.to/kehers/imap-new-messages-since-last-check-44gm
|
||||||
|
* TL;DR:
|
||||||
|
* - You cannot filter using ['UID', 'CURRENT ID + 1:*'] because IMAP
|
||||||
|
* won't return correct results if current id + 1 does not yet exist.
|
||||||
|
* - UIDs can change but this is not being treated here.
|
||||||
|
* If the mailbox is recreated (lets say you remove all emails, remove
|
||||||
|
* the mail box and create another with same name, UIDs will change)
|
||||||
|
* - You can check if UIDs changed in the above example
|
||||||
|
* by checking UIDValidity.
|
||||||
|
*/
|
||||||
|
Logger.debug('Querying for new messages on node "EmailReadImap"', {searchCriteria});
|
||||||
|
}
|
||||||
|
|
||||||
const returnData = await getNewEmails(connection, searchCriteria);
|
const returnData = await getNewEmails(connection, searchCriteria);
|
||||||
|
|
||||||
if (returnData.length) {
|
if (returnData.length) {
|
||||||
|
@ -386,7 +434,9 @@ export class EmailReadImap implements INodeType {
|
||||||
return imapConnect(config).then(async conn => {
|
return imapConnect(config).then(async conn => {
|
||||||
conn.on('error', async err => {
|
conn.on('error', async err => {
|
||||||
if (err.code.toUpperCase() === 'ECONNRESET') {
|
if (err.code.toUpperCase() === 'ECONNRESET') {
|
||||||
|
Logger.verbose('IMAP connection was reset - reconnecting.');
|
||||||
connection = await establishConnection();
|
connection = await establishConnection();
|
||||||
|
await connection.openBox(mailbox);
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
@ -398,8 +448,22 @@ export class EmailReadImap implements INodeType {
|
||||||
|
|
||||||
await connection.openBox(mailbox);
|
await connection.openBox(mailbox);
|
||||||
|
|
||||||
|
let reconnectionInterval: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
|
if (options.forceReconnect !== undefined) {
|
||||||
|
reconnectionInterval = setInterval(async () => {
|
||||||
|
Logger.verbose('Forcing reconnection of IMAP node.');
|
||||||
|
await connection.end();
|
||||||
|
connection = await establishConnection();
|
||||||
|
await connection.openBox(mailbox);
|
||||||
|
}, options.forceReconnect as number * 1000 * 60);
|
||||||
|
}
|
||||||
|
|
||||||
// When workflow and so node gets set to inactive close the connectoin
|
// When workflow and so node gets set to inactive close the connectoin
|
||||||
async function closeFunction() {
|
async function closeFunction() {
|
||||||
|
if (reconnectionInterval) {
|
||||||
|
clearInterval(reconnectionInterval);
|
||||||
|
}
|
||||||
await connection.end();
|
await connection.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue