node-red-contrib-msginspector 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rpostkg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # node-red-msginspector
2
+
3
+ A Node-RED package that aims to be a compromise between the debug node and the node-red-debugger package.
4
+
5
+ ## Features
6
+
7
+ Adds a single node (msginspector) that acts as a drop-in solution for checking incoming messages in a flow, while also letting them pass through to the next node without any changes.
8
+ The node displays the message type, topic and the payload itself. There are two ways to display the information:
9
+ - Right above the node (fancy, slightly janky, informative)
10
+ - Inline (underneath the node, more native but also way way more limiting)
11
+
12
+ The fancy display can be toggled off in the node's settings.
13
+
14
+ ## Issues
15
+
16
+ The aforementioned fancy display goes over some of the UI elements of node red. Not great but not annoying enough to bother fixing.
17
+
18
+ Due to the way the fancy display works, it updates at a set interval, which will make it look off when you're panning around the flows. Once again it's not big enough of a deal for myself personally.
19
+
20
+ ## Why?
21
+
22
+ The debug output tab gets messy at times, node-red-debugger is sometimes too in-depth and can be frustrating to use if you have a constant stream of messages. This is the middle ground
23
+
24
+ **You probably shouldn't use this in prod or anywhere for that matter. It's only public for ease of installation for myself and I'm content with what stage things are at right now.**
25
+
26
+ ## License
27
+
28
+ MIT I guess
@@ -0,0 +1 @@
1
+ <svg viewBox="0 -0.5 17 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="si-glyph si-glyph-magnifier" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>1142</title> <defs> </defs> <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g transform="translate(1.000000, 0.000000)" fill="#f6f5f4"> <path d="M16,5.954 C16,2.665 13.317,0 10.009,0 C6.698,0 4.016,2.665 4.016,5.954 C4.016,9.241 6.699,11.906 10.009,11.906 C13.317,11.906 16,9.241 16,5.954 L16,5.954 Z M4.934,6.019 C4.934,3.213 7.213,0.943 10.026,0.943 C12.837,0.943 15.114,3.214 15.114,6.019 C15.114,8.823 12.837,11.094 10.026,11.094 C7.213,11.094 4.934,8.822 4.934,6.019 L4.934,6.019 Z" class="si-glyph-fill"> </path> <path d="M1.822,15.964 L0,14.142 L4.037,10.104 C4.037,10.104 4.133,10.869 4.617,11.351 C5.099,11.835 5.859,11.927 5.859,11.927 L1.822,15.964 L1.822,15.964 Z" class="si-glyph-fill"> </path> <path d="M13.398,5.073 C13.398,5.645 13.838,5.429 13.838,4.634 C13.838,3.264 12.729,2.154 11.359,2.154 C10.562,2.154 10.347,2.593 10.92,2.593 C12.29,2.593 13.398,3.704 13.398,5.073 L13.398,5.073 Z" class="si-glyph-fill"> </path> </g> </g> </g></svg>
@@ -0,0 +1,245 @@
1
+ <script type="text/html" data-template-name="msginspector">
2
+ <div class="form-row">
3
+ <label for="node-input-name">
4
+ <i class="fa fa-tag"></i> Name
5
+ </label>
6
+ <input type="text" id="node-input-name" placeholder="Message Inspector" />
7
+ </div>
8
+
9
+ <div class="form-row">
10
+ <label for="node-input-showBubble">
11
+ <i class="fa fa-eye"></i> Show Bubble
12
+ <span style="font-size:0.8em; color:#666; margin-left:8px;">(Display message above node)</span>
13
+ </label>
14
+ <input
15
+ type="checkbox"
16
+ id="node-input-showBubble"
17
+ checked
18
+ style="display:inline-block; width:auto; vertical-align:top;"
19
+ />
20
+ </div>
21
+
22
+ <div class="form-tips" style="font-size:0.83em; color:#666; margin-top:8px;">
23
+ <i class="fa fa-info-circle"></i> Displays comprehensive message information with a minimalist speech bubble above the node.
24
+ </div>
25
+ </script>
26
+
27
+ <style>
28
+ .msginspector-bubble {
29
+ position: fixed;
30
+ background: #fff;
31
+ border: 2px solid #2D9596;
32
+ border-radius: 8px;
33
+ padding: 0;
34
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
35
+ font-size: 11px;
36
+ color: #333;
37
+ width: max-content;
38
+ max-width: 350px;
39
+ z-index: 9999;
40
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
41
+ pointer-events: auto;
42
+ transform: translate(-50%, -100%) translateY(-10px);
43
+ transition: opacity 0.2s ease-out;
44
+ display: none;
45
+ flex-direction: column;
46
+ overflow: hidden;
47
+ }
48
+
49
+ .msginspector-bubble-content::-webkit-scrollbar {
50
+ width: 8px;
51
+ }
52
+
53
+ .msginspector-bubble-content::-webkit-scrollbar-track {
54
+ background: #f1f1f1;
55
+ }
56
+
57
+ .msginspector-bubble-content::-webkit-scrollbar-thumb {
58
+ background: #2D9596;
59
+ border-radius: 4px;
60
+ }
61
+
62
+ .msginspector-bubble-content::-webkit-scrollbar-thumb:hover {
63
+ background: #247a7b;
64
+ }
65
+
66
+ .msginspector-bubble::after {
67
+ content: "";
68
+ position: absolute;
69
+ bottom: -10px;
70
+ left: 50%;
71
+ margin-left: -10px;
72
+ border-width: 10px 10px 0 10px;
73
+ border-style: solid;
74
+ border-color: #2D9596 transparent transparent transparent;
75
+ }
76
+
77
+ .msginspector-bubble-header {
78
+ background: #2D9596;
79
+ color: #fff;
80
+ padding: 6px 10px;
81
+ display: flex;
82
+ justify-content: space-between;
83
+ align-items: center;
84
+ font-weight: bold;
85
+ text-transform: uppercase;
86
+ font-size: 10px;
87
+ letter-spacing: 0.5px;
88
+ }
89
+
90
+ .msginspector-bubble-content {
91
+ background: #fff;
92
+ padding: 10px;
93
+ white-space: pre-wrap;
94
+ word-break: break-all;
95
+ max-height: 300px;
96
+ overflow-y: auto;
97
+ line-height: 1.4;
98
+ color: #333;
99
+ }
100
+
101
+ .msginspector-bubble-type-tag {
102
+ background: rgba(255, 255, 255, 0.2);
103
+ color: #fff;
104
+ padding: 1px 4px;
105
+ border-radius: 3px;
106
+ font-size: 9px;
107
+ }
108
+ </style>
109
+
110
+ <div id="msginspector-display" style="position: relative;"></div>
111
+
112
+ <script type="text/html" data-help-name="msginspector">
113
+ <p>A Node-RED node that displays comprehensive message information with a visual bubble above the node.</p>
114
+
115
+ <h3>Features</h3>
116
+ <ul>
117
+ <li>Shows all message properties in a collapsible format</li>
118
+ <li>Displays message type, topic, and payload</li>
119
+ <li>Minimalist speech bubble design</li>
120
+ <li>Configurable to show/hide bubble display</li>
121
+ </ul>
122
+
123
+ <h3>Usage</h3>
124
+ <p>Drag this node into your flow to inspect messages passing through. The bubble will display:</p>
125
+ <ul>
126
+ <li>Message type (string, number, object, etc.)</li>
127
+ <li>Topic (if present)</li>
128
+ <li>Payload content</li>
129
+ <li>Custom properties in collapsible sections</li>
130
+ </ul>
131
+
132
+ <h3>Formatting</h3>
133
+ <p>Payloads and properties are displayed using JSON formatting for readability. Complex nested structures are automatically handled.</p>
134
+ </script>
135
+
136
+ <script type="text/javascript">
137
+ RED.nodes.registerType("msginspector", {
138
+ category: "debug",
139
+ color: "#2D9596",
140
+ defaults: {
141
+ name: { value: "Message Inspector" },
142
+ showBubble: { value: true }
143
+ },
144
+ inputs: 1,
145
+ outputs: 1,
146
+ icon: "icon-msginspector.svg",
147
+ label: function () {
148
+ return this.name || "Message Inspector";
149
+ },
150
+ labelStyle: function () {
151
+ return this.name ? "node_label_italic" : "";
152
+ }
153
+ });
154
+ </script>
155
+
156
+ <script type="text/javascript">
157
+ (function () {
158
+ const bubbles = {};
159
+
160
+ RED.comms.subscribe("msginspector:update", function (topic, data) {
161
+ const nodeId = data.id;
162
+ const node = RED.nodes.node(nodeId);
163
+ if (!node) return;
164
+
165
+ let bubble = bubbles[nodeId];
166
+ if (!data.showBubble && bubble) {
167
+ bubble.remove();
168
+ delete bubbles[nodeId];
169
+ return;
170
+ }
171
+ if (!data.showBubble) return;
172
+
173
+ if (!bubble) {
174
+ bubble = document.createElement("div");
175
+ bubble.className = "msginspector-bubble";
176
+ bubble.id = "msginspector-bubble-" + nodeId;
177
+ bubble.style.opacity = "0";
178
+ document.body.appendChild(bubble);
179
+ bubbles[nodeId] = bubble;
180
+ requestAnimationFrame(() => bubble.style.opacity = "1");
181
+ }
182
+
183
+ const header = `<div class="msginspector-bubble-header">
184
+ <span>${data.topic || "INSPECTOR"}</span>
185
+ <span class="msginspector-bubble-type-tag">${data.type}</span>
186
+ </div>`;
187
+
188
+ const contentDiv = document.createElement("div");
189
+ contentDiv.className = "msginspector-bubble-content";
190
+ contentDiv.textContent = data.content;
191
+
192
+ bubble.innerHTML = header;
193
+ bubble.appendChild(contentDiv);
194
+
195
+ // Position the bubble
196
+ updateBubblePosition(node, bubble);
197
+ });
198
+
199
+ function updateBubblePosition(node, bubble) {
200
+ if (!node || !bubble) return;
201
+
202
+ const nodeElement = document.getElementById(node.id);
203
+ if (!nodeElement) {
204
+ bubble.style.display = "none";
205
+ return;
206
+ }
207
+
208
+ const rect = nodeElement.getBoundingClientRect();
209
+
210
+ // If the node is off-screen, hide the bubble
211
+ if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) {
212
+ bubble.style.display = "none";
213
+ return;
214
+ }
215
+
216
+ bubble.style.display = "flex";
217
+ bubble.style.left = (rect.left + rect.width / 2) + "px";
218
+ bubble.style.top = rect.top + "px";
219
+ }
220
+
221
+ // Clean up when node is removed
222
+ RED.events.on("nodes:remove", function (node) {
223
+ if (bubbles[node.id]) {
224
+ bubbles[node.id].remove();
225
+ delete bubbles[node.id];
226
+ }
227
+ });
228
+
229
+ // Update positions on scroll/zoom/drag
230
+ setInterval(() => {
231
+ for (const nodeId in bubbles) {
232
+ const node = RED.nodes.node(nodeId);
233
+ const bubble = bubbles[nodeId];
234
+ if (node && bubble) {
235
+ if (node.showBubble === false || node.showBubble === "false") {
236
+ bubble.style.display = "none";
237
+ continue;
238
+ }
239
+ updateBubblePosition(node, bubble);
240
+ }
241
+ }
242
+ }, 50);
243
+
244
+ })();
245
+ </script>
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+
3
+ module.exports = function (RED) {
4
+ function msginspectorNode(config) {
5
+ const node = this;
6
+ RED.nodes.createNode(this, config);
7
+
8
+ this.showBubble = config.showBubble !== undefined ? config.showBubble : true;
9
+ this.messageLimit = config.messageLimit || 10;
10
+
11
+ node.on("input", function (msg, send, done) {
12
+ try {
13
+ if (msg === null || msg === undefined) {
14
+ node.warn("Received null or undefined message");
15
+ send(msg);
16
+ done();
17
+ return;
18
+ }
19
+
20
+ node.lastMessage = {
21
+ message: msg,
22
+ timestamp: new Date().toISOString(),
23
+ nodeId: node.id
24
+ };
25
+
26
+ updateBubble();
27
+
28
+ send(msg);
29
+
30
+ } catch (error) {
31
+ node.error(`Error processing message: ${error.message}`, msg);
32
+ send(msg);
33
+ }
34
+
35
+ done();
36
+ });
37
+
38
+ node.on("close", function (removed, done) {
39
+ node.status({});
40
+ done();
41
+ });
42
+
43
+ function updateBubble() {
44
+ if (!node.lastMessage) return;
45
+
46
+ const message = node.lastMessage.message;
47
+ const displayContent = formatMessage(message);
48
+
49
+ // Publish to client for the rich bubble display
50
+ if (node.showBubble) {
51
+ RED.comms.publish("msginspector:update", {
52
+ id: node.id,
53
+ content: displayContent,
54
+ type: typeof message.payload,
55
+ topic: message.topic || "",
56
+ showBubble: node.showBubble
57
+ });
58
+ }
59
+
60
+ // Restore useful status for the default display
61
+ const maxLength = 120;
62
+ let statusText = displayContent.replace(/\n\n/g, " | ").replace(/\n/g, " ");
63
+ if (statusText.length > maxLength) {
64
+ statusText = statusText.substring(0, maxLength) + "...";
65
+ }
66
+
67
+ node.status({
68
+ fill: "green",
69
+ shape: "dot",
70
+ text: statusText
71
+ });
72
+ }
73
+
74
+ function formatMessage(msg) {
75
+ if (!msg) return "No message";
76
+
77
+ const formatValue = (val, indent = 0) => {
78
+ const indentStr = " ".repeat(indent);
79
+
80
+ if (val === null || val === undefined) return "null";
81
+ if (typeof val === "string") return `"${val}"`;
82
+ if (typeof val === "boolean") return val.toString();
83
+
84
+ try {
85
+ const str = JSON.stringify(val, null, 2);
86
+ if (str === "{}" && typeof val === "object") return "object";
87
+ if (str === "[]" && Array.isArray(val)) return "array";
88
+ return str;
89
+ } catch (e) {
90
+ return String(val);
91
+ }
92
+ };
93
+
94
+ const getTypeName = (obj) => {
95
+ if (obj === null) return "null";
96
+ if (Array.isArray(obj)) return `array[${obj.length}]`;
97
+ return typeof obj;
98
+ };
99
+
100
+ const msgType = getTypeName(msg);
101
+ const msgTopic = msg.topic ? msg.topic : "no topic";
102
+ const msgPayload = formatValue(msg.payload);
103
+
104
+ let properties = "";
105
+ if (typeof msg === "object" && msg !== null) {
106
+ const props = Object.keys(msg)
107
+ .filter(key => key !== "payload" && key !== "topic")
108
+ .map(key => {
109
+ const val = msg[key];
110
+ return `${key}: ${formatValue(val, 1)}`;
111
+ })
112
+ .join("\n");
113
+
114
+ if (props) {
115
+ properties = props;
116
+ }
117
+ }
118
+
119
+ let output = `Type: ${msgType}\n`;
120
+ output += `Topic: ${msgTopic}\n`;
121
+ output += `Payload: ${msgPayload}`;
122
+
123
+ if (properties) {
124
+ output += `\n\nProperties:\n${properties}`;
125
+ }
126
+
127
+ return output;
128
+ }
129
+
130
+ updateBubble();
131
+ }
132
+
133
+ RED.nodes.registerType("msginspector", msginspectorNode);
134
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "node-red-contrib-msginspector",
3
+ "version": "1.0.0",
4
+ "description": "A node that displays incoming message information with a visual display above the node.",
5
+ "author": "rpostkg <190353914+rpostkg@users.noreply.github.com>",
6
+ "license": "MIT",
7
+ "files": [
8
+ "msginspector/",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/rpostkg/nr-msginspector.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/rpostkg/nr-msginspector/issues"
18
+ },
19
+ "homepage": "https://github.com/rpostkg/nr-msginspector#readme",
20
+ "keywords": [
21
+ "node-red",
22
+ "inspector",
23
+ "debug",
24
+ "message"
25
+ ],
26
+ "node-red": {
27
+ "version": ">=3.0.0",
28
+ "nodes": {
29
+ "msginspector": "msginspector/msginspector.js"
30
+ }
31
+ },
32
+ "engines": {
33
+ "node": ">=14.14"
34
+ }
35
+ }