@5minds/node-red-contrib-processcube-tools 1.0.1 → 1.0.2-develop-e3d5d9-mfm8oea8
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.
- package/.env.template +4 -0
- package/README.md +1 -1
- package/email-receiver/email-receiver.html +219 -0
- package/email-receiver/email-receiver.js +250 -0
- package/email-sender/email-sender.html +306 -0
- package/email-sender/email-sender.js +174 -0
- package/package.json +25 -4
- package/{processcube-html-to-text.html → processcube-html-to-text/processcube-html-to-text.html} +9 -9
- package/{processcube-html-to-text.js → processcube-html-to-text/processcube-html-to-text.js} +0 -3
- package/test/helpers/email-receiver.mocks.js +447 -0
- package/test/helpers/email-sender.mocks.js +368 -0
- package/test/integration/email-receiver.integration.test.js +515 -0
- package/test/integration/email-sender.integration.test.js +239 -0
- package/test/unit/email-receiver.unit.test.js +304 -0
- package/test/unit/email-sender.unit.test.js +570 -0
package/.env.template
ADDED
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Some Nodes to efficent with the ProcessCube LowCode component (Node-RED)
|
|
2
2
|
|
|
3
|
-
This repository contains
|
|
3
|
+
This repository contains nodes for working efficiently with the ProcessCube Low-Code component (Node-RED).
|
|
4
4
|
|
|
5
5
|
Details see @ [ProcessCub.io - LowCode Integration Nodes](https://processcube.io/docs/node-red/integration-nodes) and [ProcessCub.io - LowCode Event Nodes](https://processcube.io/docs/node-red/event-nodes).
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('email-receiver', {
|
|
3
|
+
category: 'ProcessCube Tools',
|
|
4
|
+
color: '#02AFD6',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
host: { value: '', required: true, validate: RED.validators.typedInput('hostType') },
|
|
8
|
+
hostType: { value: 'str' },
|
|
9
|
+
port: { value: '', required: true, validate: RED.validators.typedInput('portType') },
|
|
10
|
+
portType: { value: 'num' },
|
|
11
|
+
tls: { value: true, required: true, validate: RED.validators.typedInput('tlsType') },
|
|
12
|
+
tlsType: { value: 'bool' },
|
|
13
|
+
user: { value: '', required: true, validate: RED.validators.typedInput('userType') },
|
|
14
|
+
userType: { value: 'str' },
|
|
15
|
+
password: { value: '', required: true, type: 'password' },
|
|
16
|
+
passwordType: { value: 'env', required: true },
|
|
17
|
+
folder: { value: '', required: true, validate: RED.validators.typedInput('folderType') },
|
|
18
|
+
folderType: { value: 'json' },
|
|
19
|
+
markseen: { value: true, validate: RED.validators.typedInput('markseenType') },
|
|
20
|
+
markseenType: { value: 'bool' },
|
|
21
|
+
},
|
|
22
|
+
inputs: 1,
|
|
23
|
+
outputs: 1,
|
|
24
|
+
icon: 'font-awesome/fa-inbox',
|
|
25
|
+
label: function () {
|
|
26
|
+
return this.name || 'E-Mail Receiver';
|
|
27
|
+
},
|
|
28
|
+
oneditprepare: function () {
|
|
29
|
+
$('#node-input-host').typedInput({
|
|
30
|
+
default: 'str',
|
|
31
|
+
types: ['str', 'msg', 'flow', 'global', 'env'],
|
|
32
|
+
typeField: '#node-input-hostType',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
$('#node-input-port').typedInput({
|
|
36
|
+
default: 'num',
|
|
37
|
+
types: ['num', 'msg', 'flow', 'global', 'env'],
|
|
38
|
+
typeField: '#node-input-portType',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
$('#node-input-tls').typedInput({
|
|
42
|
+
default: 'bool',
|
|
43
|
+
types: ['bool', 'msg', 'flow', 'global', 'env'],
|
|
44
|
+
typeField: '#node-input-tlsType',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
$('#node-input-user').typedInput({
|
|
48
|
+
default: 'str',
|
|
49
|
+
types: ['str', 'msg', 'flow', 'global', 'env'],
|
|
50
|
+
typeField: '#node-input-userType',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
$('#node-input-password').typedInput({
|
|
54
|
+
default: 'env',
|
|
55
|
+
types: ['msg', 'flow', 'global', 'env'],
|
|
56
|
+
typeField: '#node-input-passwordType',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
$('#node-input-folder').typedInput({
|
|
60
|
+
default: 'json',
|
|
61
|
+
types: ['msg', 'flow', 'global', 'json', 'jsonata', 'env'],
|
|
62
|
+
typeField: '#node-input-folderType',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
$('#node-input-markseen').typedInput({
|
|
66
|
+
default: 'bool',
|
|
67
|
+
types: ['bool', 'msg', 'flow', 'global', 'env'],
|
|
68
|
+
typeField: '#node-input-markseenType',
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<script type="text/html" data-template-name="email-receiver">
|
|
75
|
+
<div class="form-row">
|
|
76
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
77
|
+
<input type="text" id="node-input-name" placeholder="Name" />
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div class="form-row">
|
|
81
|
+
<label for="node-input-host"><i class="fa fa-server"></i> IMAP Host</label>
|
|
82
|
+
<input type="text" id="node-input-host" placeholder="imap.gmail.com" />
|
|
83
|
+
<input type="hidden" id="node-input-hostType" />
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="form-row">
|
|
87
|
+
<label for="node-input-port"><i class="fa fa-terminal"></i> Port</label>
|
|
88
|
+
<input type="text" id="node-input-port" placeholder="993" />
|
|
89
|
+
<input type="hidden" id="node-input-portType" />
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="form-row">
|
|
93
|
+
<label for="node-input-tls"><i class="fa fa-lock"></i> Use TLS</label>
|
|
94
|
+
<input type="text" id="node-input-tls" />
|
|
95
|
+
<input type="hidden" id="node-input-tlsType" />
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="form-row">
|
|
99
|
+
<label for="node-input-user"><i class="fa fa-user"></i> User</label>
|
|
100
|
+
<input type="text" id="node-input-user" />
|
|
101
|
+
<input type="hidden" id="node-input-userType" />
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div class="form-row">
|
|
105
|
+
<label for="node-input-password"><i class="fa fa-key"></i> Password</label>
|
|
106
|
+
<input type="text" id="node-input-password" />
|
|
107
|
+
<input type="hidden" id="node-input-passwordType" />
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div class="form-row">
|
|
111
|
+
<label for="node-input-folder"><i class="fa fa-folder-open"></i> Folder(s)</label>
|
|
112
|
+
<input type="text" id="node-input-folder" placeholder="[INBOX]" />
|
|
113
|
+
<input type="hidden" id="node-input-folderType" />
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div class="form-row">
|
|
117
|
+
<label for="node-input-markseen"><i class="fa fa-eye"></i> Mark as seen</label>
|
|
118
|
+
<input type="text" id="node-input-markseen" />
|
|
119
|
+
<input type="hidden" id="node-input-markseenType" />
|
|
120
|
+
</div>
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<script type="text/html" data-help-name="email-receiver">
|
|
124
|
+
<p>
|
|
125
|
+
A Node-RED node that fetches unseen emails from a specified IMAP server. Each fetched email is sent as a
|
|
126
|
+
separate message on the output.
|
|
127
|
+
</p>
|
|
128
|
+
|
|
129
|
+
<p>
|
|
130
|
+
All fields can be configured as a <strong>string</strong>, from a <strong>message property (msg)</strong>,
|
|
131
|
+
<strong>flow</strong> or <strong>global</strong> context, or an <strong>environment variable</strong>.
|
|
132
|
+
</p>
|
|
133
|
+
|
|
134
|
+
<p>
|
|
135
|
+
<strong>Security Tip:</strong> It's a best practice to avoid storing sensitive information like passwords
|
|
136
|
+
directly in your Node-RED flow. This prevents them from being exposed in your flow file. Instead, use an
|
|
137
|
+
<strong>environment variable</strong>. You can define these variables in a <code>.env</code> file, which is
|
|
138
|
+
especially useful when deploying your application with Docker.
|
|
139
|
+
</p>
|
|
140
|
+
|
|
141
|
+
<p>A <code>.env</code> file should look like this:</p>
|
|
142
|
+
<pre>
|
|
143
|
+
EMAIL_SEND_PORT=123
|
|
144
|
+
EMAIL_SEND_HOST=smtp.gmail.com
|
|
145
|
+
EMAIL_SEND_USER=myTestMail@company.com
|
|
146
|
+
EMAIL_SEND_PASSWORD=mySecretPassword
|
|
147
|
+
</pre
|
|
148
|
+
>
|
|
149
|
+
|
|
150
|
+
<p>
|
|
151
|
+
To ensure Docker loads these variables from the <code>.env</code> file, you need to add the following line to
|
|
152
|
+
your <code>docker-compose.yaml</code> file:
|
|
153
|
+
</p>
|
|
154
|
+
<pre>
|
|
155
|
+
services:
|
|
156
|
+
your_service_name:
|
|
157
|
+
...
|
|
158
|
+
env_file:
|
|
159
|
+
- .env
|
|
160
|
+
</pre
|
|
161
|
+
>
|
|
162
|
+
|
|
163
|
+
<p>
|
|
164
|
+
In your flow, you can then access the password using the environment variable <code>EMAIL_SEND_PASSWORD</code>.
|
|
165
|
+
</p>
|
|
166
|
+
|
|
167
|
+
Inputs
|
|
168
|
+
<dl class="message-properties">
|
|
169
|
+
<dt>payload</dt>
|
|
170
|
+
<dd>
|
|
171
|
+
The node is triggered by any incoming message. The node's configuration can be overridden by properties of
|
|
172
|
+
the incoming <code>msg</code> object.
|
|
173
|
+
</dd>
|
|
174
|
+
</dl>
|
|
175
|
+
|
|
176
|
+
Outputs
|
|
177
|
+
<dl class="message-properties">
|
|
178
|
+
<dt>
|
|
179
|
+
payload
|
|
180
|
+
<span class="property-type">string</span>
|
|
181
|
+
</dt>
|
|
182
|
+
<dd>The text body of the email.</dd>
|
|
183
|
+
</dl>
|
|
184
|
+
|
|
185
|
+
Optional Message Properties
|
|
186
|
+
<p>
|
|
187
|
+
You can override default settings by passing the following properties in the incoming <code>msg</code> object:
|
|
188
|
+
</p>
|
|
189
|
+
<dl class="message-properties">
|
|
190
|
+
<dt>
|
|
191
|
+
msg.imap_connTimeout
|
|
192
|
+
<span class="property-type">number</span>
|
|
193
|
+
</dt>
|
|
194
|
+
<dd>The connection timeout in milliseconds (default: 10000).</dd>
|
|
195
|
+
<dt>
|
|
196
|
+
msg.imap_authTimeout
|
|
197
|
+
<span class="property-type">number</span>
|
|
198
|
+
</dt>
|
|
199
|
+
<dd>The authentication timeout in milliseconds (default: 5000).</dd>
|
|
200
|
+
<dt>
|
|
201
|
+
msg.imap_keepalive
|
|
202
|
+
<span class="property-type">boolean</span>
|
|
203
|
+
</dt>
|
|
204
|
+
<dd>
|
|
205
|
+
If set to <code>true</code>, a periodic NOOP command is sent to keep the connection alive (default:
|
|
206
|
+
<code>true</code>).
|
|
207
|
+
</dd>
|
|
208
|
+
<dt>
|
|
209
|
+
msg.imap_autotls
|
|
210
|
+
<span class="property-type">string</span>
|
|
211
|
+
</dt>
|
|
212
|
+
<dd>Controls STARTTLS behavior. Set to <code>never</code> to disable it (default: <code>never</code>).</dd>
|
|
213
|
+
<dt>
|
|
214
|
+
msg.imap_tlsOptions
|
|
215
|
+
<span class="property-type">object</span>
|
|
216
|
+
</dt>
|
|
217
|
+
<dd>An object containing TLS options for the connection.</dd>
|
|
218
|
+
</dl>
|
|
219
|
+
</script>
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
const Imap = require('node-imap');
|
|
3
|
+
const mailparser = require('mailparser');
|
|
4
|
+
|
|
5
|
+
function EmailReceiverNode(config) {
|
|
6
|
+
RED.nodes.createNode(this, config);
|
|
7
|
+
const node = this;
|
|
8
|
+
|
|
9
|
+
node.on('input', function (msg) {
|
|
10
|
+
// Retrieve and validate configuration values
|
|
11
|
+
const imap_host = RED.util.evaluateNodeProperty(config.host, config.hostType, node, msg);
|
|
12
|
+
const imap_port = RED.util.evaluateNodeProperty(config.port, config.portType, node, msg);
|
|
13
|
+
const imap_tls = RED.util.evaluateNodeProperty(config.tls, config.tlsType, node, msg);
|
|
14
|
+
const imap_user = RED.util.evaluateNodeProperty(config.user, config.userType, node, msg);
|
|
15
|
+
const imap_password = RED.util.evaluateNodeProperty(config.password, config.passwordType, node, msg);
|
|
16
|
+
|
|
17
|
+
// Check if the folder is actually an array
|
|
18
|
+
const imap_folder = RED.util.evaluateNodeProperty(config.folder, config.folderType, node, msg);
|
|
19
|
+
let folders;
|
|
20
|
+
if (Array.isArray(imap_folder)) {
|
|
21
|
+
folders = imap_folder;
|
|
22
|
+
} else {
|
|
23
|
+
const errorMsg = "The 'folders' property must be an array of strings.";
|
|
24
|
+
node.status({ fill: 'red', shape: 'ring', text: errorMsg });
|
|
25
|
+
node.error(errorMsg, msg);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const imap_markSeen = RED.util.evaluateNodeProperty(config.markseen, config.markseenType, node, msg);
|
|
29
|
+
|
|
30
|
+
const finalConfig = {
|
|
31
|
+
host: imap_host,
|
|
32
|
+
port: typeof imap_port === 'string' ? parseInt(imap_port, 10) : imap_port,
|
|
33
|
+
tls: imap_tls,
|
|
34
|
+
user: imap_user,
|
|
35
|
+
password: imap_password,
|
|
36
|
+
folders: Array.isArray(imap_folder)
|
|
37
|
+
? imap_folder
|
|
38
|
+
: imap_folder
|
|
39
|
+
.split(',')
|
|
40
|
+
.map((f) => f.trim())
|
|
41
|
+
.filter((f) => f.length > 0),
|
|
42
|
+
markSeen: imap_markSeen,
|
|
43
|
+
connTimeout: msg.imap_connTimeout || 10000,
|
|
44
|
+
authTimeout: msg.imap_authTimeout || 5000,
|
|
45
|
+
keepalive: msg.imap_keepalive !== undefined ? msg.imap_keepalive : true,
|
|
46
|
+
autotls: msg.imap_autotls || 'never',
|
|
47
|
+
tlsOptions: msg.imap_tlsOptions || { rejectUnauthorized: false },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
!finalConfig.user ||
|
|
52
|
+
!finalConfig.password ||
|
|
53
|
+
!finalConfig.port ||
|
|
54
|
+
!finalConfig.host ||
|
|
55
|
+
!finalConfig.folders
|
|
56
|
+
) {
|
|
57
|
+
const missingFields = [];
|
|
58
|
+
if (!finalConfig.user) missingFields.push('user');
|
|
59
|
+
if (!finalConfig.password) missingFields.push('password');
|
|
60
|
+
if (!finalConfig.port) missingFields.push('port');
|
|
61
|
+
if (!finalConfig.host) missingFields.push('host');
|
|
62
|
+
if (!finalConfig.folders) missingFields.push('folders');
|
|
63
|
+
|
|
64
|
+
const errorMessage = `Missing required IMAP config: ${missingFields.join(', ')}. Aborting.`;
|
|
65
|
+
node.status({ fill: 'red', shape: 'ring', text: 'missing config' });
|
|
66
|
+
node.error(errorMessage);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fetchEmails = (
|
|
71
|
+
{
|
|
72
|
+
host,
|
|
73
|
+
port,
|
|
74
|
+
tls,
|
|
75
|
+
user,
|
|
76
|
+
password,
|
|
77
|
+
folders,
|
|
78
|
+
markSeen = true,
|
|
79
|
+
connTimeout = 10000,
|
|
80
|
+
authTimeout = 5000,
|
|
81
|
+
keepalive = true,
|
|
82
|
+
autotls = 'never',
|
|
83
|
+
tlsOptions = { rejectUnauthorized: false },
|
|
84
|
+
},
|
|
85
|
+
onMail,
|
|
86
|
+
) => {
|
|
87
|
+
const imap = new Imap({
|
|
88
|
+
user,
|
|
89
|
+
password,
|
|
90
|
+
host,
|
|
91
|
+
port,
|
|
92
|
+
tls,
|
|
93
|
+
connTimeout,
|
|
94
|
+
authTimeout,
|
|
95
|
+
keepalive,
|
|
96
|
+
autotls,
|
|
97
|
+
tlsOptions,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const state = {
|
|
101
|
+
totalFolders: folders.length,
|
|
102
|
+
processedFolders: 0,
|
|
103
|
+
successes: 0,
|
|
104
|
+
failures: 0,
|
|
105
|
+
totalMails: 0,
|
|
106
|
+
errors: [],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Helper to update Node-RED status
|
|
110
|
+
const updateStatus = (color, text) => {
|
|
111
|
+
node.status({ fill: color, shape: 'dot', text });
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Helper to finalize status and clean up
|
|
115
|
+
const finalizeSession = (error = null) => {
|
|
116
|
+
if (error) {
|
|
117
|
+
node.error('IMAP session terminated: ' + error.message);
|
|
118
|
+
node.status({ fill: 'red', shape: 'ring', text: 'connection error' });
|
|
119
|
+
} else if (state.failures > 0) {
|
|
120
|
+
node.status({
|
|
121
|
+
fill: 'red',
|
|
122
|
+
shape: 'dot',
|
|
123
|
+
text: `Done, ${state.totalMails} mails from ${state.successes}/${state.totalFolders} folders. ${state.failures} failed.`,
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
node.status({
|
|
127
|
+
fill: 'green',
|
|
128
|
+
shape: 'dot',
|
|
129
|
+
text: `Done, fetched ${state.totalMails} mails from ${folders.join(', ')}.`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (imap && imap.state !== 'disconnected') {
|
|
133
|
+
imap.end();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const fetchFromFolder = (folder) => {
|
|
138
|
+
updateStatus('yellow', `Fetching from "${folder}"...`);
|
|
139
|
+
|
|
140
|
+
imap.openBox(folder, false, (err, box) => {
|
|
141
|
+
if (err) {
|
|
142
|
+
node.error(`Could not open folder "${folder}": ${err.message}`);
|
|
143
|
+
state.failures++;
|
|
144
|
+
state.processedFolders++;
|
|
145
|
+
return startNextFolder();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
imap.search(['UNSEEN'], (err, results) => {
|
|
149
|
+
if (err) {
|
|
150
|
+
node.error(`Search failed in folder "${folder}": ${err.message}`);
|
|
151
|
+
state.failures++;
|
|
152
|
+
state.processedFolders++;
|
|
153
|
+
return startNextFolder();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!results || !results.length) {
|
|
157
|
+
state.successes++;
|
|
158
|
+
state.processedFolders++;
|
|
159
|
+
return startNextFolder();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
state.totalMails += results.length;
|
|
163
|
+
|
|
164
|
+
const fetch = imap.fetch(results, { bodies: '' });
|
|
165
|
+
|
|
166
|
+
fetch.on('message', (msg) => {
|
|
167
|
+
msg.on('body', (stream) => {
|
|
168
|
+
mailparser.simpleParser(stream, (err, parsed) => {
|
|
169
|
+
if (err) {
|
|
170
|
+
node.error(`Parse error for email from folder "${folder}": ${err.message}`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const outMsg = {
|
|
175
|
+
topic: parsed.subject,
|
|
176
|
+
payload: parsed.text,
|
|
177
|
+
html: parsed.html,
|
|
178
|
+
from: parsed.replyTo?.text || parsed.from?.text,
|
|
179
|
+
date: parsed.date,
|
|
180
|
+
folder,
|
|
181
|
+
header: parsed.headers,
|
|
182
|
+
attachments: parsed.attachments.map((att) => ({
|
|
183
|
+
contentType: att.contentType,
|
|
184
|
+
fileName: att.filename,
|
|
185
|
+
transferEncoding: att.transferEncoding,
|
|
186
|
+
contentDisposition: att.contentDisposition,
|
|
187
|
+
generatedFileName: att.cid || att.checksum,
|
|
188
|
+
contentId: att.cid,
|
|
189
|
+
checksum: att.checksum,
|
|
190
|
+
length: att.size,
|
|
191
|
+
content: att.content,
|
|
192
|
+
})),
|
|
193
|
+
};
|
|
194
|
+
onMail(outMsg);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
fetch.once('error', (err) => {
|
|
200
|
+
node.error(`Fetch error in folder "${folder}": ${err.message}`);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
fetch.once('end', () => {
|
|
204
|
+
state.successes++;
|
|
205
|
+
state.processedFolders++;
|
|
206
|
+
updateStatus('green', `Fetched ${results.length} from "${folder}".`);
|
|
207
|
+
startNextFolder();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const startNextFolder = () => {
|
|
214
|
+
if (state.processedFolders >= state.totalFolders) {
|
|
215
|
+
finalizeSession();
|
|
216
|
+
} else {
|
|
217
|
+
fetchFromFolder(folders[state.processedFolders]);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Centralized event listeners for the IMAP connection
|
|
222
|
+
imap.once('ready', () => {
|
|
223
|
+
node.status({ fill: 'green', shape: 'dot', text: 'connected' });
|
|
224
|
+
startNextFolder();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
imap.once('error', (err) => {
|
|
228
|
+
finalizeSession(err);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
imap.once('end', () => {
|
|
232
|
+
node.log('IMAP connection ended.');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
updateStatus('yellow', 'Connecting to IMAP...');
|
|
236
|
+
imap.connect();
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
fetchEmails(finalConfig, (mail) => {
|
|
240
|
+
node.send(mail);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
RED.nodes.registerType('email-receiver', EmailReceiverNode, {
|
|
246
|
+
credentials: {
|
|
247
|
+
password: { type: 'password' },
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
};
|