node-red-contrib-aws-sqs-variable 1.0.0

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/aws-sqs.js ADDED
@@ -0,0 +1,260 @@
1
+ /**
2
+ * AWS SQS Node for Node-RED
3
+ *
4
+ * Supports sending and receiving messages from SQS with flexible inputs.
5
+ */
6
+
7
+ const {
8
+ SQSClient,
9
+ SendMessageCommand,
10
+ ReceiveMessageCommand,
11
+ DeleteMessageCommand
12
+ } = require("@aws-sdk/client-sqs");
13
+
14
+ module.exports = function(RED) {
15
+ /**
16
+ * Get value from different input types
17
+ * @param {string} value - Value to get
18
+ * @param {string} type - Type of value (str, msg, flow, global, env)
19
+ * @param {Object} msg - Message object
20
+ * @param {Object} node - Node instance
21
+ * @returns {string|null} Retrieved value
22
+ */
23
+ function getValue(value, type, msg, node) {
24
+ if (value === null || value === undefined) return null;
25
+
26
+ try {
27
+ let result;
28
+ switch (type) {
29
+ case 'msg':
30
+ result = RED.util.getMessageProperty(msg, value);
31
+ break;
32
+ case 'flow':
33
+ result = getNestedValue(node.context().flow, value);
34
+ break;
35
+ case 'global':
36
+ result = getNestedValue(node.context().global, value);
37
+ break;
38
+ case 'env':
39
+ result = process.env[value];
40
+ break;
41
+ default:
42
+ result = value;
43
+ }
44
+ return result;
45
+ } catch (err) {
46
+ throw new Error(`Failed to get value for type: ${type}, value: ${value}. Error: ${err.message}`);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Helper function to get nested values like "all_vars.host"
52
+ * @param {Object} context - Context object
53
+ * @param {string} path - Dot-separated path
54
+ * @returns {*} Retrieved value
55
+ */
56
+ function getNestedValue(context, path) {
57
+ if (!context) return undefined;
58
+ if (path.includes('.')) {
59
+ const parts = path.split('.');
60
+ let result = context.get(parts[0]);
61
+ for (let i = 1; i < parts.length; i++) {
62
+ if (result && typeof result === 'object') {
63
+ result = result[parts[i]];
64
+ } else {
65
+ return undefined;
66
+ }
67
+ }
68
+ return result;
69
+ }
70
+ return context.get(path);
71
+ }
72
+
73
+ function AWSSQSNode(config) {
74
+ RED.nodes.createNode(this, config);
75
+ const node = this;
76
+ const sqsConfig = RED.nodes.getNode(config.awsConfig);
77
+
78
+ if (!sqsConfig) {
79
+ node.error("AWS SQS configuration not found.");
80
+ return;
81
+ }
82
+
83
+ // Store configuration
84
+ node.operation = config.operation;
85
+ node.queueUrl = config.queueUrl;
86
+ node.queueUrlType = config.queueUrlType;
87
+ node.messageBody = config.messageBody;
88
+ node.messageBodyType = config.messageBodyType;
89
+ node.messageGroupId = config.messageGroupId;
90
+ node.messageGroupIdType = config.messageGroupIdType;
91
+ node.messageDeduplicationId = config.messageDeduplicationId;
92
+ node.messageDeduplicationIdType = config.messageDeduplicationIdType;
93
+ node.maxNumberOfMessages = parseInt(config.maxNumberOfMessages, 10) || 1;
94
+ node.waitTimeSeconds = parseInt(config.waitTimeSeconds, 10) || 0;
95
+ node.visibilityTimeout = parseInt(config.visibilityTimeout, 10) || 30;
96
+ node.deleteAfterReceive = config.deleteAfterReceive === true || config.deleteAfterReceive === "true";
97
+ node.parseJsonBody = config.parseJsonBody === true || config.parseJsonBody === "true";
98
+ node.pollEnabled = config.pollEnabled === true || config.pollEnabled === "true";
99
+ node.pollIntervalSeconds = parseInt(config.pollIntervalSeconds, 10) || 30;
100
+
101
+ let pollTimer = null;
102
+ let isPolling = false;
103
+
104
+ function setPollingStatus() {
105
+ if (node.operation === "receive" && node.pollEnabled) {
106
+ const interval = Math.max(node.pollIntervalSeconds, 1);
107
+ node.status({ fill: "yellow", shape: "dot", text: `Waiting (${interval}s)` });
108
+ }
109
+ }
110
+
111
+ async function handleMessage(msg, send, done, fromPolling) {
112
+ try {
113
+ const client = sqsConfig.getClient(msg, node);
114
+
115
+ if (!client || !(client instanceof SQSClient)) {
116
+ if (sqsConfig.deferMissingConfig) {
117
+ node.status({ fill: "yellow", shape: "ring", text: "Waiting for configuration" });
118
+ return;
119
+ }
120
+ throw new Error("Failed to initialize SQS client");
121
+ }
122
+
123
+ const queueUrl = getValue(node.queueUrl, node.queueUrlType, msg, node) || msg.queueUrl;
124
+ if (!queueUrl) {
125
+ throw new Error("Queue URL is required. Provide it in node configuration or msg.queueUrl");
126
+ }
127
+
128
+ if (node.operation === "send") {
129
+ let bodyValue = getValue(node.messageBody, node.messageBodyType, msg, node);
130
+ if (bodyValue === null || bodyValue === undefined) {
131
+ bodyValue = msg.payload;
132
+ }
133
+
134
+ if (bodyValue === null || bodyValue === undefined) {
135
+ throw new Error("Message body is required");
136
+ }
137
+
138
+ const bodyString = typeof bodyValue === "string" ? bodyValue : JSON.stringify(bodyValue);
139
+
140
+ node.status({ fill: "blue", shape: "dot", text: "Sending message..." });
141
+
142
+ const messageGroupId = getValue(node.messageGroupId, node.messageGroupIdType, msg, node) || msg.messageGroupId;
143
+ const messageDeduplicationId = getValue(node.messageDeduplicationId, node.messageDeduplicationIdType, msg, node) || msg.messageDeduplicationId;
144
+
145
+ const sendParams = {
146
+ QueueUrl: queueUrl,
147
+ MessageBody: bodyString
148
+ };
149
+
150
+ if (messageGroupId) {
151
+ sendParams.MessageGroupId = messageGroupId;
152
+ }
153
+ if (messageDeduplicationId) {
154
+ sendParams.MessageDeduplicationId = messageDeduplicationId;
155
+ }
156
+
157
+ const response = await client.send(new SendMessageCommand(sendParams));
158
+
159
+ msg.payload = {
160
+ messageId: response.MessageId,
161
+ md5OfMessageBody: response.MD5OfMessageBody
162
+ };
163
+
164
+ node.status({ fill: "green", shape: "dot", text: "Message sent" });
165
+ send(msg);
166
+ done();
167
+ return;
168
+ }
169
+
170
+ // Receive messages
171
+ if (fromPolling) {
172
+ node.status({ fill: "blue", shape: "dot", text: "Polling..." });
173
+ } else {
174
+ node.status({ fill: "blue", shape: "dot", text: "Receiving messages..." });
175
+ }
176
+
177
+ const receiveParams = {
178
+ QueueUrl: queueUrl,
179
+ MaxNumberOfMessages: Math.min(Math.max(node.maxNumberOfMessages, 1), 10),
180
+ WaitTimeSeconds: Math.min(Math.max(node.waitTimeSeconds, 0), 20),
181
+ VisibilityTimeout: Math.min(Math.max(node.visibilityTimeout, 0), 43200)
182
+ };
183
+
184
+ const response = await client.send(new ReceiveMessageCommand(receiveParams));
185
+ const messages = response.Messages || [];
186
+
187
+ if (node.parseJsonBody && messages.length > 0) {
188
+ for (const message of messages) {
189
+ if (typeof message.Body === "string") {
190
+ try {
191
+ message.Body = JSON.parse(message.Body);
192
+ } catch (parseError) {
193
+ // Keep string body if parsing fails
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ if (node.deleteAfterReceive && messages.length > 0) {
200
+ for (const message of messages) {
201
+ if (message.ReceiptHandle) {
202
+ await client.send(new DeleteMessageCommand({
203
+ QueueUrl: queueUrl,
204
+ ReceiptHandle: message.ReceiptHandle
205
+ }));
206
+ }
207
+ }
208
+ }
209
+
210
+ msg.payload = messages;
211
+ node.status({ fill: "green", shape: "dot", text: "Messages received" });
212
+ if (fromPolling) {
213
+ setPollingStatus();
214
+ }
215
+ send(msg);
216
+ done();
217
+
218
+ } catch (err) {
219
+ node.error(err.message, msg);
220
+ node.status({ fill: "red", shape: "ring", text: err.message });
221
+
222
+ msg.payload = { error: err.message };
223
+ send(msg);
224
+ done();
225
+ if (fromPolling) {
226
+ setPollingStatus();
227
+ }
228
+ }
229
+ }
230
+
231
+ node.on('input', async (msg, send, done) => {
232
+ await handleMessage(msg, send, done, false);
233
+ });
234
+
235
+ node.status({});
236
+
237
+ if (node.operation === "receive" && node.pollEnabled) {
238
+ const intervalMs = Math.max(node.pollIntervalSeconds, 1) * 1000;
239
+ setPollingStatus();
240
+ pollTimer = setInterval(async () => {
241
+ if (isPolling) return;
242
+ isPolling = true;
243
+ try {
244
+ await handleMessage({}, node.send.bind(node), () => {}, true);
245
+ } finally {
246
+ isPolling = false;
247
+ }
248
+ }, intervalMs);
249
+ }
250
+
251
+ node.on('close', () => {
252
+ if (pollTimer) {
253
+ clearInterval(pollTimer);
254
+ pollTimer = null;
255
+ }
256
+ });
257
+ }
258
+
259
+ RED.nodes.registerType("aws-sqs", AWSSQSNode);
260
+ };
@@ -0,0 +1,267 @@
1
+ [
2
+ {
3
+ "id": "sqs-advanced-flow",
4
+ "type": "tab",
5
+ "label": "AWS SQS Advanced Examples",
6
+ "disabled": false,
7
+ "info": "Advanced examples showing different configuration types, FIFO usage, and polling."
8
+ },
9
+ {
10
+ "id": "inject-fifo-send",
11
+ "type": "inject",
12
+ "z": "sqs-advanced-flow",
13
+ "name": "FIFO Send",
14
+ "props": [
15
+ {
16
+ "p": "payload"
17
+ },
18
+ {
19
+ "p": "messageGroupId",
20
+ "v": "orders",
21
+ "vt": "str"
22
+ },
23
+ {
24
+ "p": "messageDeduplicationId",
25
+ "v": "msg-{{{_msgid}}}",
26
+ "vt": "str"
27
+ }
28
+ ],
29
+ "repeat": "",
30
+ "crontab": "",
31
+ "once": false,
32
+ "onceDelay": 0.1,
33
+ "topic": "",
34
+ "payload": "{\"orderId\":123,\"status\":\"created\"}",
35
+ "payloadType": "json",
36
+ "x": 140,
37
+ "y": 80,
38
+ "wires": [
39
+ [
40
+ "sqs-fifo-send-node"
41
+ ]
42
+ ]
43
+ },
44
+ {
45
+ "id": "sqs-fifo-send-node",
46
+ "type": "aws-sqs",
47
+ "z": "sqs-advanced-flow",
48
+ "name": "FIFO Send",
49
+ "awsConfig": "sqs-config-env",
50
+ "operation": "send",
51
+ "queueUrl": "https://sqs.eu-central-1.amazonaws.com/123456789012/my-queue.fifo",
52
+ "queueUrlType": "str",
53
+ "messageBody": "payload",
54
+ "messageBodyType": "msg",
55
+ "messageGroupId": "messageGroupId",
56
+ "messageGroupIdType": "msg",
57
+ "messageDeduplicationId": "messageDeduplicationId",
58
+ "messageDeduplicationIdType": "msg",
59
+ "maxNumberOfMessages": 1,
60
+ "waitTimeSeconds": 0,
61
+ "visibilityTimeout": 30,
62
+ "deleteAfterReceive": true,
63
+ "parseJsonBody": false,
64
+ "pollEnabled": false,
65
+ "pollIntervalSeconds": 30,
66
+ "x": 380,
67
+ "y": 80,
68
+ "wires": [
69
+ [
70
+ "debug-fifo-send"
71
+ ]
72
+ ]
73
+ },
74
+ {
75
+ "id": "debug-fifo-send",
76
+ "type": "debug",
77
+ "z": "sqs-advanced-flow",
78
+ "name": "FIFO Send Result",
79
+ "active": true,
80
+ "tosidebar": true,
81
+ "console": false,
82
+ "tostatus": false,
83
+ "complete": "payload",
84
+ "targetType": "msg",
85
+ "x": 610,
86
+ "y": 80,
87
+ "wires": []
88
+ },
89
+ {
90
+ "id": "inject-poll-start",
91
+ "type": "inject",
92
+ "z": "sqs-advanced-flow",
93
+ "name": "Start Polling",
94
+ "props": [
95
+ {
96
+ "p": "payload"
97
+ }
98
+ ],
99
+ "repeat": "",
100
+ "crontab": "",
101
+ "once": false,
102
+ "onceDelay": 0.1,
103
+ "topic": "",
104
+ "payload": "{}",
105
+ "payloadType": "json",
106
+ "x": 140,
107
+ "y": 160,
108
+ "wires": [
109
+ [
110
+ "sqs-poll-receive-node"
111
+ ]
112
+ ]
113
+ },
114
+ {
115
+ "id": "sqs-poll-receive-node",
116
+ "type": "aws-sqs",
117
+ "z": "sqs-advanced-flow",
118
+ "name": "Polling Receive",
119
+ "awsConfig": "sqs-config-context",
120
+ "operation": "receive",
121
+ "queueUrl": "sqs.queueUrl",
122
+ "queueUrlType": "global",
123
+ "messageBody": "payload",
124
+ "messageBodyType": "msg",
125
+ "messageGroupId": "",
126
+ "messageGroupIdType": "str",
127
+ "messageDeduplicationId": "",
128
+ "messageDeduplicationIdType": "str",
129
+ "maxNumberOfMessages": 10,
130
+ "waitTimeSeconds": 20,
131
+ "visibilityTimeout": 60,
132
+ "deleteAfterReceive": true,
133
+ "parseJsonBody": true,
134
+ "pollEnabled": true,
135
+ "pollIntervalSeconds": 30,
136
+ "x": 390,
137
+ "y": 160,
138
+ "wires": [
139
+ [
140
+ "debug-polling"
141
+ ]
142
+ ]
143
+ },
144
+ {
145
+ "id": "debug-polling",
146
+ "type": "debug",
147
+ "z": "sqs-advanced-flow",
148
+ "name": "Polling Receive Result",
149
+ "active": true,
150
+ "tosidebar": true,
151
+ "console": false,
152
+ "tostatus": false,
153
+ "complete": "payload",
154
+ "targetType": "msg",
155
+ "x": 640,
156
+ "y": 160,
157
+ "wires": []
158
+ },
159
+ {
160
+ "id": "inject-dynamic-queue",
161
+ "type": "inject",
162
+ "z": "sqs-advanced-flow",
163
+ "name": "Dynamic Queue URL",
164
+ "props": [
165
+ {
166
+ "p": "payload"
167
+ },
168
+ {
169
+ "p": "queueUrl",
170
+ "v": "https://sqs.eu-central-1.amazonaws.com/123456789012/another-queue",
171
+ "vt": "str"
172
+ }
173
+ ],
174
+ "repeat": "",
175
+ "crontab": "",
176
+ "once": false,
177
+ "onceDelay": 0.1,
178
+ "topic": "",
179
+ "payload": "{\"message\":\"dynamic queue\"}",
180
+ "payloadType": "json",
181
+ "x": 150,
182
+ "y": 240,
183
+ "wires": [
184
+ [
185
+ "sqs-dynamic-send-node"
186
+ ]
187
+ ]
188
+ },
189
+ {
190
+ "id": "sqs-dynamic-send-node",
191
+ "type": "aws-sqs",
192
+ "z": "sqs-advanced-flow",
193
+ "name": "Dynamic Queue Send",
194
+ "awsConfig": "sqs-config-iam",
195
+ "operation": "send",
196
+ "queueUrl": "queueUrl",
197
+ "queueUrlType": "msg",
198
+ "messageBody": "payload",
199
+ "messageBodyType": "msg",
200
+ "messageGroupId": "",
201
+ "messageGroupIdType": "str",
202
+ "messageDeduplicationId": "",
203
+ "messageDeduplicationIdType": "str",
204
+ "maxNumberOfMessages": 1,
205
+ "waitTimeSeconds": 0,
206
+ "visibilityTimeout": 30,
207
+ "deleteAfterReceive": true,
208
+ "parseJsonBody": false,
209
+ "pollEnabled": false,
210
+ "pollIntervalSeconds": 30,
211
+ "x": 390,
212
+ "y": 240,
213
+ "wires": [
214
+ [
215
+ "debug-dynamic-send"
216
+ ]
217
+ ]
218
+ },
219
+ {
220
+ "id": "debug-dynamic-send",
221
+ "type": "debug",
222
+ "z": "sqs-advanced-flow",
223
+ "name": "Dynamic Send Result",
224
+ "active": true,
225
+ "tosidebar": true,
226
+ "console": false,
227
+ "tostatus": false,
228
+ "complete": "payload",
229
+ "targetType": "msg",
230
+ "x": 610,
231
+ "y": 240,
232
+ "wires": []
233
+ },
234
+ {
235
+ "id": "sqs-config-env",
236
+ "type": "aws-sqs-config",
237
+ "name": "Environment Config",
238
+ "region": "eu-central-1",
239
+ "useIAMRole": false,
240
+ "accessKeyIdType": "env",
241
+ "accessKeyIdContext": "AWS_ACCESS_KEY_ID",
242
+ "secretAccessKeyType": "env",
243
+ "secretAccessKeyContext": "AWS_SECRET_ACCESS_KEY"
244
+ },
245
+ {
246
+ "id": "sqs-config-context",
247
+ "type": "aws-sqs-config",
248
+ "name": "Context Config",
249
+ "region": "eu-central-1",
250
+ "useIAMRole": false,
251
+ "accessKeyIdType": "global",
252
+ "accessKeyIdContext": "aws.accessKeyId",
253
+ "secretAccessKeyType": "global",
254
+ "secretAccessKeyContext": "aws.secretAccessKey"
255
+ },
256
+ {
257
+ "id": "sqs-config-iam",
258
+ "type": "aws-sqs-config",
259
+ "name": "IAM Role Config",
260
+ "region": "eu-central-1",
261
+ "useIAMRole": true,
262
+ "accessKeyIdType": "str",
263
+ "accessKeyIdContext": "",
264
+ "secretAccessKeyType": "str",
265
+ "secretAccessKeyContext": ""
266
+ }
267
+ ]
@@ -0,0 +1,160 @@
1
+ [
2
+ {
3
+ "id": "sqs-basic-flow",
4
+ "type": "tab",
5
+ "label": "AWS SQS Basic Example",
6
+ "disabled": false,
7
+ "info": "Basic example showing how to send and receive messages with AWS SQS."
8
+ },
9
+ {
10
+ "id": "inject-send-basic",
11
+ "type": "inject",
12
+ "z": "sqs-basic-flow",
13
+ "name": "Send Message",
14
+ "props": [
15
+ {
16
+ "p": "payload"
17
+ }
18
+ ],
19
+ "repeat": "",
20
+ "crontab": "",
21
+ "once": false,
22
+ "onceDelay": 0.1,
23
+ "topic": "",
24
+ "payload": "{\"message\":\"Hello from Node-RED\"}",
25
+ "payloadType": "json",
26
+ "x": 140,
27
+ "y": 120,
28
+ "wires": [
29
+ [
30
+ "sqs-send-node"
31
+ ]
32
+ ]
33
+ },
34
+ {
35
+ "id": "sqs-send-node",
36
+ "type": "aws-sqs",
37
+ "z": "sqs-basic-flow",
38
+ "name": "Send to Queue",
39
+ "awsConfig": "sqs-config-basic",
40
+ "operation": "send",
41
+ "queueUrl": "https://sqs.eu-central-1.amazonaws.com/123456789012/my-queue",
42
+ "queueUrlType": "str",
43
+ "messageBody": "payload",
44
+ "messageBodyType": "msg",
45
+ "messageGroupId": "",
46
+ "messageGroupIdType": "str",
47
+ "messageDeduplicationId": "",
48
+ "messageDeduplicationIdType": "str",
49
+ "maxNumberOfMessages": 1,
50
+ "waitTimeSeconds": 0,
51
+ "visibilityTimeout": 30,
52
+ "deleteAfterReceive": true,
53
+ "parseJsonBody": false,
54
+ "pollEnabled": false,
55
+ "pollIntervalSeconds": 30,
56
+ "x": 380,
57
+ "y": 120,
58
+ "wires": [
59
+ [
60
+ "debug-send-basic"
61
+ ]
62
+ ]
63
+ },
64
+ {
65
+ "id": "debug-send-basic",
66
+ "type": "debug",
67
+ "z": "sqs-basic-flow",
68
+ "name": "Send Result",
69
+ "active": true,
70
+ "tosidebar": true,
71
+ "console": false,
72
+ "tostatus": false,
73
+ "complete": "payload",
74
+ "targetType": "msg",
75
+ "x": 610,
76
+ "y": 120,
77
+ "wires": []
78
+ },
79
+ {
80
+ "id": "inject-receive-basic",
81
+ "type": "inject",
82
+ "z": "sqs-basic-flow",
83
+ "name": "Receive Once",
84
+ "props": [
85
+ {
86
+ "p": "payload"
87
+ }
88
+ ],
89
+ "repeat": "",
90
+ "crontab": "",
91
+ "once": false,
92
+ "onceDelay": 0.1,
93
+ "topic": "",
94
+ "payload": "{}",
95
+ "payloadType": "json",
96
+ "x": 140,
97
+ "y": 200,
98
+ "wires": [
99
+ [
100
+ "sqs-receive-node"
101
+ ]
102
+ ]
103
+ },
104
+ {
105
+ "id": "sqs-receive-node",
106
+ "type": "aws-sqs",
107
+ "z": "sqs-basic-flow",
108
+ "name": "Receive from Queue",
109
+ "awsConfig": "sqs-config-basic",
110
+ "operation": "receive",
111
+ "queueUrl": "https://sqs.eu-central-1.amazonaws.com/123456789012/my-queue",
112
+ "queueUrlType": "str",
113
+ "messageBody": "payload",
114
+ "messageBodyType": "msg",
115
+ "messageGroupId": "",
116
+ "messageGroupIdType": "str",
117
+ "messageDeduplicationId": "",
118
+ "messageDeduplicationIdType": "str",
119
+ "maxNumberOfMessages": 5,
120
+ "waitTimeSeconds": 10,
121
+ "visibilityTimeout": 30,
122
+ "deleteAfterReceive": true,
123
+ "parseJsonBody": true,
124
+ "pollEnabled": false,
125
+ "pollIntervalSeconds": 30,
126
+ "x": 390,
127
+ "y": 200,
128
+ "wires": [
129
+ [
130
+ "debug-receive-basic"
131
+ ]
132
+ ]
133
+ },
134
+ {
135
+ "id": "debug-receive-basic",
136
+ "type": "debug",
137
+ "z": "sqs-basic-flow",
138
+ "name": "Receive Result",
139
+ "active": true,
140
+ "tosidebar": true,
141
+ "console": false,
142
+ "tostatus": false,
143
+ "complete": "payload",
144
+ "targetType": "msg",
145
+ "x": 620,
146
+ "y": 200,
147
+ "wires": []
148
+ },
149
+ {
150
+ "id": "sqs-config-basic",
151
+ "type": "aws-sqs-config",
152
+ "name": "AWS SQS Config",
153
+ "region": "eu-central-1",
154
+ "useIAMRole": false,
155
+ "accessKeyIdType": "env",
156
+ "accessKeyIdContext": "AWS_ACCESS_KEY_ID",
157
+ "secretAccessKeyType": "env",
158
+ "secretAccessKeyContext": "AWS_SECRET_ACCESS_KEY"
159
+ }
160
+ ]