@flowfuse/nr-mqtt-nodes 0.1.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.
@@ -0,0 +1,488 @@
1
+ <!--
2
+ This is a fork of the MQTT nodes from Node-RED.
3
+ The original code can be found at:
4
+ https://github.com/node-red/node-red/blob/b428e24ea51e00e2260739987dfab74872765a9f/packages/node_modules/@node-red/nodes/core/network/21-httpin.html
5
+ Below is the copyright notice for the original code.
6
+ The copyright notice for this fork is the same as the original.
7
+ ### Changes:
8
+ - Hide advanced features
9
+ - Remove the config node for MQTT broker
10
+ - Remove the dynamic connection control
11
+ - lint errors fixed up
12
+ -->
13
+ <!--
14
+ Copyright JS Foundation and other contributors, http://js.foundation
15
+ Licensed under the Apache License, Version 2.0 (the "License");
16
+ you may not use this file except in compliance with the License.
17
+ You may obtain a copy of the License at
18
+ http://www.apache.org/licenses/LICENSE-2.0
19
+ Unless required by applicable law or agreed to in writing, software
20
+ distributed under the License is distributed on an "AS IS" BASIS,
21
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
+ See the License for the specific language governing permissions and
23
+ limitations under the License.
24
+ -->
25
+ <style>
26
+
27
+ .mqtt-form-row-cols2 > input.mqtt-form-row-col1 {
28
+ width: calc(35% - 75px);
29
+ }
30
+ .mqtt-form-row-cols2 > select.mqtt-form-row-col1 {
31
+ width: calc(35% - 75px);
32
+ }
33
+
34
+ .mqtt-form-row-cols2 > label.mqtt-form-row-col2 {
35
+ width: 100px;
36
+ margin-left: 42px;
37
+ display: inline-block;
38
+ }
39
+ .mqtt-form-row-cols2 > input.mqtt-form-row-col2 {
40
+ width: calc(35% - 75px);
41
+ display: inline-block;
42
+ }
43
+ .mqtt-form-row-cols2 > select.mqtt-form-row-col2 {
44
+ width: calc(35% - 75px);
45
+ display: inline-block;
46
+ }
47
+ .form-row.mqtt5-out > label {
48
+ width: 130px;
49
+ }
50
+ .form-row.mqtt-flags-row > label {
51
+ vertical-align: top;
52
+ }
53
+ .form-row.mqtt-flags-row > .mqtt-flags {
54
+ display: inline-block;
55
+ width: 70%
56
+ }
57
+
58
+ .form-row.mqtt-flags-row > .mqtt-flags > .mqtt-flag > label {
59
+ display: block;
60
+ width: 100%;
61
+ }
62
+ .form-row.mqtt-flags-row > .mqtt-flags > .mqtt-flag > label > input {
63
+ position: relative;
64
+ vertical-align: bottom;
65
+ top: -2px;
66
+ width: 15px;
67
+ height: 15px;
68
+ }
69
+ .form-row-mqtt5 {
70
+ display: none;
71
+ }
72
+ .form-row-mqtt5.form-row-mqtt5-active:not(.form-row-mqtt-static-disabled) {
73
+ display: block
74
+ }
75
+ .form-row-mqtt-static-disabled {
76
+ display: none;
77
+ /* opacity: 0.3;
78
+ pointer-events: none; */
79
+ }
80
+ .form-row.form-row-mqtt-datatype-tip > .form-tips {
81
+ width: calc(70% - 18px);
82
+ display: inline-block;
83
+ margin-top: -8px;
84
+ }
85
+
86
+ /* Hide advanced options in initial offering */
87
+ .form-row.ff-no-use {
88
+ display: none !important;
89
+ }
90
+
91
+ </style>
92
+
93
+ <script type="text/html" data-template-name="ff-mqtt-in">
94
+ <div class="form-row" id="ff-mqtt-client-link-feature-disabled" style="color: var(--red-ui-text-color-error);">
95
+ <label><i class="fa fa-warning"></i> <span></span></label>
96
+ <span data-i18n="ff-mqtt.label.featureDisabled"></span>
97
+ </div>
98
+ <div class="form-row">
99
+ <label><i class="fa fa-globe"></i> <span data-i18n="ff-mqtt.label.broker"></span></label>
100
+ <span class="red-ui-help">
101
+ <a href="#" class="red-ui-link" id="ff-mqtt-client-link" target="_blank">
102
+ <span>Configure Access Control </span><i class="fa fa-external-link"></i>
103
+ </a>
104
+ </span>
105
+ </div>
106
+ <div class="form-row">
107
+ <label for="node-input-topicType" data-i18n="ff-mqtt.label.action"></label>
108
+ <select id="node-input-topicType" style="width: 70%">
109
+ <option value="topic" data-i18n="ff-mqtt.label.staticTopic"></option>
110
+ <option value="dynamic" data-i18n="ff-mqtt.label.dynamicTopic"></option>
111
+ </select>
112
+ <input type="hidden" id="node-input-inputs">
113
+ </div>
114
+ <div class="form-row form-row-mqtt-static">
115
+ <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
116
+ <input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
117
+ </div>
118
+ <div class="form-row form-row-mqtt-static">
119
+ <label for="node-input-qos"><i class="fa fa-empire"></i> <span data-i18n="ff-mqtt.label.qos"></span></label>
120
+ <select id="node-input-qos" style="width:125px !important">
121
+ <option value="0">0</option>
122
+ <option value="1">1</option>
123
+ <option value="2">2</option>
124
+ </select>
125
+ </div>
126
+ <div class="form-row mqtt-flags-row form-row-mqtt5 form-row-mqtt-static ff-no-use">
127
+ <label for="node-input-nl" ><i class="fa fa-flag"></i> <span data-i18n="ff-mqtt.label.flags">Flags</span></label>
128
+ <div class="mqtt-flags">
129
+ <div class="mqtt-flag">
130
+ <label for="node-input-nl">
131
+ <input type="checkbox" id="node-input-nl">
132
+ <span data-i18n="ff-mqtt.label.nl"></span>
133
+ </label>
134
+ </div>
135
+ <div class="mqtt-flag">
136
+ <label for="node-input-rap">
137
+ <input type="checkbox" id="node-input-rap">
138
+ <span data-i18n="ff-mqtt.label.rap"></span>
139
+ </label>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ <div class="form-row form-row-mqtt5 form-row-mqtt-static ff-no-use">
144
+ <label for="node-input-rh" style="width:100%"><i class="fa fa-tag"></i> <span data-i18n="ff-mqtt.label.rh"></span></label>
145
+ <select id="node-input-rh" style="margin-left: 104px; width: 70%">
146
+ <option value="0" data-i18n="ff-mqtt.label.rh0"></option>
147
+ <option value="1" data-i18n="ff-mqtt.label.rh1"></option>
148
+ <option value="2" data-i18n="ff-mqtt.label.rh2"></option>
149
+ </select>
150
+ </div>
151
+ <div class="form-row">
152
+ <label for="node-input-datatype"><i class="fa fa-sign-out"></i> <span data-i18n="ff-mqtt.label.output"></span></label>
153
+ <select id="node-input-datatype" style="width:70%;">
154
+ <option value="auto-detect" data-i18n="ff-mqtt.output.auto-detect"></option>
155
+ <option value="buffer" data-i18n="ff-mqtt.output.buffer"></option>
156
+ <option value="utf8" data-i18n="ff-mqtt.output.string"></option>
157
+ <option value="json" data-i18n="ff-mqtt.output.json"></option>
158
+ <option value="base64" data-i18n="ff-mqtt.output.base64"></option>
159
+ </select>
160
+ </div>
161
+ <div class="form-row">
162
+ <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
163
+ <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
164
+ </div>
165
+ </script>
166
+
167
+ <script type="text/html" data-template-name="ff-mqtt-out">
168
+ <div class="form-row" id="ff-mqtt-client-link-feature-disabled" style="color: var(--red-ui-text-color-error);">
169
+ <label><i class="fa fa-warning"></i> <span></span></label>
170
+ <span data-i18n="ff-mqtt.label.featureDisabled"></span>
171
+ </div>
172
+ <div class="form-row">
173
+ <label><i class="fa fa-globe"></i> <span data-i18n="ff-mqtt.label.broker"></span></label>
174
+ <span class="red-ui-help">
175
+ <a href="#" class="red-ui-link" id="ff-mqtt-client-link" target="_blank">
176
+ <span>Configure Access Control </span><i class="fa fa-external-link"></i>
177
+ </a>
178
+ </span>
179
+ </div>
180
+ <div class="form-row">
181
+ <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
182
+ <input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
183
+ </div>
184
+
185
+ <div class="form-row mqtt-form-row-cols2">
186
+ <label for="node-input-qos" class="mqtt-form-row-col1"><i class="fa fa-empire"></i> <span data-i18n="ff-mqtt.label.qos"></span></label>
187
+ <select id="node-input-qos" class="mqtt-form-row-col1">
188
+ <option value=""></option>
189
+ <option value="0">0</option>
190
+ <option value="1">1</option>
191
+ <option value="2">2</option>
192
+ </select>
193
+
194
+ <label for="node-input-retain" class="mqtt-form-row-col2"><i class="fa fa-history"></i> <span data-i18n="ff-mqtt.retain"></span></label>
195
+ <select id="node-input-retain" class="mqtt-form-row-col2" >
196
+ <option value=""></option>
197
+ <option value="false" data-i18n="ff-mqtt.false"></option>
198
+ <option value="true" data-i18n="ff-mqtt.true"></option>
199
+ </select>
200
+ </div>
201
+ <div class="form-row mqtt5 mqtt5-out ff-no-use">
202
+ <label for="node-input-userProps"><span data-i18n="ff-mqtt.label.userProperties"></span></label>
203
+ <input type="text" id="node-input-userProps" style="width: calc(100% - 166px);">
204
+ </div>
205
+ <div class="form-row mqtt5 mqtt5-out ff-no-use">
206
+ <label for="node-input-respTopic"><span data-i18n="ff-mqtt.label.responseTopic"></span></label>
207
+ <input type="text" id="node-input-respTopic" style="width: calc(100% - 166px);">
208
+ </div>
209
+ <div class="form-row mqtt5 mqtt5-out ff-no-use">
210
+ <label for="node-input-correl"><span data-i18n="ff-mqtt.label.correlationData"></span></label>
211
+ <input type="text" id="node-input-correl" style="width: calc(100% - 166px);">
212
+ </div>
213
+ <div class="form-row mqtt5 mqtt5-out ff-no-use">
214
+ <label for="node-input-contentType"><span data-i18n="ff-mqtt.label.contentType"></span></label>
215
+ <input type="text" id="node-input-contentType" style="width: calc(100% - 166px);">
216
+ </div>
217
+
218
+ <div class="form-row mqtt-form-row-cols2 mqtt5 mqtt5-out ff-no-use">
219
+ <label for="node-input-expiry" class="mqtt-form-row-col1"><span data-i18n="ff-mqtt.label.expiry"></span></label>
220
+ <input id="node-input-expiry" style="width: calc(100% - 166px);" class="mqtt-form-row-col1" >
221
+ </div>
222
+
223
+ <div class="form-row">
224
+ <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
225
+ <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
226
+ </div>
227
+ <div class="form-tips"><span data-i18n="ff-mqtt.tip"></span></div>
228
+ </script>
229
+
230
+ <script type="text/javascript">
231
+ /* global RED, $ */
232
+ (function () {
233
+ const typedInputNoneOpt = {
234
+ value: 'none',
235
+ label: RED._('ff-mqtt.label.none'),
236
+ hasValue: false
237
+ }
238
+ const makeTypedInputOpt = function (value) {
239
+ return {
240
+ value,
241
+ label: value,
242
+ hasValue: false
243
+ }
244
+ }
245
+ const contentTypeOpts = [
246
+ typedInputNoneOpt,
247
+ makeTypedInputOpt('application/json'),
248
+ makeTypedInputOpt('application/octet-stream'),
249
+ makeTypedInputOpt('text/csv'),
250
+ makeTypedInputOpt('text/html'),
251
+ makeTypedInputOpt('text/plain'),
252
+ {
253
+ value: 'other',
254
+ label: RED._('ff-mqtt.label.other'),
255
+ icon: 'red/images/typedInput/az.svg'
256
+ }
257
+ ]
258
+
259
+ function getDefaultContentType (value) {
260
+ let defaultContentType
261
+ const matchedContentType = contentTypeOpts.filter(function (v) {
262
+ return v.value === value
263
+ })
264
+ if (matchedContentType.length > 0) {
265
+ defaultContentType = matchedContentType[0].value
266
+ }
267
+ if (value && !defaultContentType) {
268
+ defaultContentType = 'other'
269
+ }
270
+ return defaultContentType || 'none'
271
+ }
272
+ /**
273
+ * Test a topic string is valid for publishing
274
+ * @param {string} topic
275
+ * @returns `true` if it is a valid topic
276
+ */
277
+ function validateMQTTPublishTopic (topic, opts) {
278
+ if (!topic || topic === '' || !/[\\+#\b\f\n\r\t\v\0]/.test(topic)) {
279
+ return true
280
+ }
281
+ return RED._('ff-mqtt.errors.invalid-topic')
282
+ }
283
+ function setupTopicField (node, input) {
284
+ input.autoComplete({
285
+ minLength: 0,
286
+ completionPluginType: 'node-red-mqtt-topic-autocomplete-source',
287
+ context: {
288
+ node
289
+ }
290
+ })
291
+ }
292
+ RED.nodes.registerType('ff-mqtt-in', {
293
+ category: 'FlowFuse',
294
+ defaults: {
295
+ name: { value: '' },
296
+ topic: {
297
+ value: '',
298
+ validate: function (v, opt) {
299
+ let isDynamic = this.inputs === 1
300
+ const topicTypeSelect = $('#node-input-topicType')
301
+ if (topicTypeSelect.length) {
302
+ isDynamic = topicTypeSelect.val() === 'dynamic'
303
+ }
304
+ if (isDynamic || ((!!v) && RED.validators.regex(/^(#$|(\\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/)(v))) {
305
+ return true
306
+ }
307
+ return RED._('ff-mqtt.errors.invalid-topic')
308
+ }
309
+ },
310
+ qos: { value: '2' },
311
+ datatype: { value: 'auto-detect', required: true },
312
+ nl: { value: false },
313
+ rap: { value: true },
314
+ rh: { value: 0 },
315
+ inputs: { value: 0 }
316
+ },
317
+ color: '#d8bfd8',
318
+ inputs: 0,
319
+ outputs: 1,
320
+ icon: 'ff-logo.svg',
321
+ paletteLabel: 'ff mqtt in',
322
+ label: function () {
323
+ let label = 'ff mqtt in'
324
+ if (this.topicType !== 'dynamic' && this.topic) {
325
+ label = this.topic
326
+ }
327
+ return this.name || label
328
+ },
329
+ labelStyle: function () {
330
+ return this.name ? 'node_label_italic' : ''
331
+ },
332
+ oneditprepare: function () {
333
+ const node = this
334
+
335
+ const forgeUrl = RED.settings.ffMqttInUserTeamBrokerClientUrl || RED.settings.ffMqttInForgeUrl || ''
336
+ $('#ff-mqtt-client-link').attr('href', forgeUrl)
337
+ const featureEnabled = RED.settings.ffMqttInFeatureEnabled !== false || RED.settings.ffMqttOutFeatureEnabled !== false
338
+ if (featureEnabled === false) {
339
+ $('#ff-mqtt-client-link-feature-disabled').show()
340
+ } else {
341
+ $('#ff-mqtt-client-link-feature-disabled').hide()
342
+ }
343
+
344
+ setupTopicField(node, $('#node-input-topic'))
345
+ const isV5Broker = function () {
346
+ // const confNode = RED.nodes.node($('#node-input-broker').val())
347
+ // return confNode && confNode.protocolVersion === '5'
348
+ return true
349
+ }
350
+ const isDynamic = function () {
351
+ return $('#node-input-topicType').val() === 'dynamic'
352
+ }
353
+ const updateVisibility = function () {
354
+ const v5 = isV5Broker()
355
+ const dynamic = isDynamic()
356
+ $('div.form-row-mqtt5').toggleClass('form-row-mqtt5-active', !!v5)
357
+ $('div.form-row.form-row-mqtt-static').toggleClass('form-row-mqtt-static-disabled', !!dynamic)
358
+ }
359
+
360
+ // $('#node-input-broker').on('change', function (d) {
361
+ // updateVisibility()
362
+ // })
363
+
364
+ $('#node-input-topicType').on('change', function () {
365
+ $('#node-input-inputs').val(isDynamic() ? 1 : 0)
366
+ updateVisibility()
367
+ })
368
+
369
+ if (node.inputs === 1) {
370
+ $('#node-input-topicType').val('dynamic')
371
+ } else {
372
+ $('#node-input-topicType').val('topic')
373
+ }
374
+ $('#node-input-topicType').trigger('change')
375
+
376
+ if (node.qos === undefined) {
377
+ $('#node-input-qos').val('2')
378
+ }
379
+ if (node.datatype === undefined) {
380
+ $('#node-input-datatype').val('auto-detect')
381
+ }
382
+ updateVisibility()
383
+ },
384
+ oneditsave: function () {
385
+ if ($('#node-input-topicType').val() === 'dynamic') {
386
+ $('#node-input-topic').val('')
387
+ }
388
+ }
389
+ })
390
+
391
+ RED.nodes.registerType('ff-mqtt-out', {
392
+ category: 'FlowFuse',
393
+ defaults: {
394
+ name: { value: '' },
395
+ topic: { value: '', validate: validateMQTTPublishTopic },
396
+ qos: { value: '' },
397
+ retain: { value: '' },
398
+ respTopic: { value: '' },
399
+ contentType: { value: '' },
400
+ userProps: { value: '' },
401
+ correl: { value: '' },
402
+ expiry: { value: '' }
403
+ },
404
+ color: '#d8bfd8',
405
+ inputs: 1,
406
+ outputs: 0,
407
+ icon: 'ff-logo.svg',
408
+ paletteLabel: 'ff mqtt out',
409
+ align: 'right',
410
+ label: function () {
411
+ return this.name || this.topic || 'ff mqtt out'
412
+ },
413
+ oneditprepare: function () {
414
+ const that = this
415
+ const forgeUrl = RED.settings.ffMqttOutUserTeamBrokerClientUrl || RED.settings.ffMqttOutForgeUrl || ''
416
+ $('#ff-mqtt-client-link').attr('href', forgeUrl)
417
+ const featureEnabled = RED.settings.ffMqttInFeatureEnabled !== false || RED.settings.ffMqttOutFeatureEnabled !== false
418
+ if (featureEnabled === false) {
419
+ $('#ff-mqtt-client-link-feature-disabled').show()
420
+ } else {
421
+ $('#ff-mqtt-client-link-feature-disabled').hide()
422
+ }
423
+
424
+ setupTopicField(that, $('#node-input-topic'))
425
+
426
+ function showHideDynamicFields () {
427
+ const confNode = RED.nodes.node($('#node-input-broker').val())
428
+ const v5 = confNode && confNode.protocolVersion === '5'
429
+ if (v5) {
430
+ $('div.form-row.mqtt5').show()
431
+ const t = $('#node-input-respTopic').typedInput('type')
432
+ if (t === 'none') {
433
+ $('#node-input-correl').parent().hide()
434
+ } else {
435
+ $('#node-input-correl').parent().show()
436
+ }
437
+ } else {
438
+ $('div.form-row.mqtt5').hide()
439
+ }
440
+ }
441
+
442
+ // $('#node-input-broker').on('change', function (d) {
443
+ showHideDynamicFields()
444
+ // })
445
+
446
+ const respTopicTI = $('#node-input-respTopic').typedInput({
447
+ default: !this.respTopic ? 'none' : 'str',
448
+ types: [typedInputNoneOpt, 'str']
449
+ })
450
+
451
+ $('#node-input-correl').typedInput({
452
+ default: !this.correl ? 'none' : 'str',
453
+ types: [typedInputNoneOpt, 'str']
454
+ })
455
+ // show / hide correlation data depending on respTopic
456
+ respTopicTI.on('change', showHideDynamicFields)
457
+ respTopicTI.triggerHandler('change')
458
+
459
+ $('#node-input-userProps').typedInput({
460
+ default: !this.userProps ? 'none' : 'json',
461
+ types: [typedInputNoneOpt, 'json']
462
+ })
463
+ $('#node-input-expiry').typedInput({
464
+ default: !this.expiry ? 'none' : 'num',
465
+ types: [typedInputNoneOpt, 'num']
466
+ })
467
+ $('#node-input-contentType').typedInput({
468
+ default: getDefaultContentType(this.contentType),
469
+ types: contentTypeOpts
470
+ })
471
+ },
472
+ oneditsave: function () {
473
+ let contentType = $('#node-input-contentType').val().trim()
474
+ if (contentType === '') {
475
+ contentType = $('#node-input-contentType').typedInput('type')
476
+ if (contentType === 'none' || contentType === 'other') {
477
+ contentType = ''
478
+ }
479
+ }
480
+ $('#node-input-contentType').val(contentType)
481
+ },
482
+ labelStyle: function () {
483
+ return this.name ? 'node_label_italic' : ''
484
+ }
485
+ })
486
+ })()
487
+ </script>
488
+