node-red-debugger
Advanced tools
Comparing version
@@ -11,4 +11,4 @@ "use strict"; | ||
const routeAuthHandler = RED.auth.needsPermission("flow-debugger.write"); | ||
RED.comms.publish("flow-debugger/connected", true, true); | ||
function publishState() { | ||
// Do not retain as we don't want stale state to be saved | ||
RED.comms.publish("flow-debugger/state", flowDebugger.getState()); | ||
@@ -23,9 +23,7 @@ } | ||
flowDebugger.on("messageQueued", (event) => { | ||
// msg = RED.util.encodeObject(msg,{maxLength:debuglength}); | ||
// RED.comms.publish("debug",msg); | ||
event.msg = RED.util.encodeObject({ msg: event.msg }, { maxLength: 100 }); | ||
// Don't include the full message on the event | ||
// event.msg = RED.util.encodeObject({msg:event.msg}, {maxLength: 100}); | ||
RED.comms.publish("flow-debugger/messageQueued", event); | ||
}); | ||
flowDebugger.on("messageDispatched", (event) => { | ||
event.msg = RED.util.encodeObject({ msg: event.msg }, { maxLength: 100 }); | ||
RED.comms.publish("flow-debugger/messageDispatched", event); | ||
@@ -36,22 +34,25 @@ }); | ||
// }); | ||
RED.httpAdmin.get(`${apiRoot}/state`, (_, res) => { | ||
RED.httpAdmin.get(`${apiRoot}`, (_, res) => { | ||
res.json(flowDebugger.getState()); | ||
}); | ||
RED.httpAdmin.put(`${apiRoot}/state`, routeAuthHandler, (req, res) => { | ||
RED.httpAdmin.put(`${apiRoot}`, routeAuthHandler, (req, res) => { | ||
let stateChanged = false; | ||
if (req.body.hasOwnProperty("enabled")) { | ||
const enabled = !!req.body.enabled; | ||
let stateChanged = false; | ||
if (enabled && flowDebugger.state === debugger_1.State.DISABLED) { | ||
if (enabled && !flowDebugger.enabled) { | ||
flowDebugger.enable(); | ||
stateChanged = true; | ||
} | ||
else if (!enabled && flowDebugger.state !== debugger_1.State.DISABLED) { | ||
else if (!enabled && flowDebugger.enabled) { | ||
flowDebugger.disable(); | ||
stateChanged = true; | ||
} | ||
if (stateChanged) { | ||
publishState(); | ||
} | ||
} | ||
res.sendStatus(200); | ||
if (req.body.hasOwnProperty("config")) { | ||
stateChanged = flowDebugger.setConfig(req.body.config); | ||
} | ||
if (stateChanged) { | ||
publishState(); | ||
} | ||
res.json(flowDebugger.getState()); | ||
}); | ||
@@ -63,8 +64,6 @@ RED.httpAdmin.get(`${apiRoot}/breakpoints`, routeAuthHandler, (_, res) => { | ||
flowDebugger.setBreakpointActive(req.params.id, req.body.active); | ||
publishState(); | ||
res.sendStatus(200); | ||
res.json(flowDebugger.getBreakpoint(req.params.id)); | ||
}); | ||
RED.httpAdmin.delete(`${apiRoot}/breakpoints/:id`, routeAuthHandler, (req, res) => { | ||
flowDebugger.clearBreakpoint(req.params.id); | ||
publishState(); | ||
res.sendStatus(200); | ||
@@ -81,3 +80,3 @@ }); | ||
id: m.id, | ||
location: m.location, | ||
location: m.location.toString(), | ||
destination: undefined, | ||
@@ -84,0 +83,0 @@ msg: RED.util.encodeObject({ msg: m.event.msg }, { maxLength: 100 }) |
@@ -22,12 +22,7 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Debugger = exports.State = void 0; | ||
exports.Debugger = void 0; | ||
const Location = __importStar(require("./location")); | ||
const MessageQueue_1 = require("./MessageQueue"); | ||
const events_1 = require("events"); | ||
var State; | ||
(function (State) { | ||
State[State["DISABLED"] = 0] = "DISABLED"; | ||
State[State["ENABLED"] = 1] = "ENABLED"; | ||
State[State["PAUSED"] = 2] = "PAUSED"; | ||
})(State = exports.State || (exports.State = {})); | ||
const DEBUGGER_PAUSED = Symbol("node-red-debugger: paused"); | ||
let BREAKPOINT_ID = 1; | ||
@@ -39,5 +34,9 @@ class Debugger extends events_1.EventEmitter { | ||
super(); | ||
this.config = { | ||
breakpointAction: "pause-all" | ||
}; | ||
this.RED = RED; | ||
this.state = State.DISABLED; | ||
this.enabled = false; | ||
this.breakpoints = new Map(); | ||
this.pausedLocations = new Set(); | ||
this.breakpointsByLocation = new Map(); | ||
@@ -53,23 +52,32 @@ this.queuesByLocation = {}; | ||
const breakpointId = location.getBreakpointLocation(); | ||
const locationId = location.toString(); | ||
if (this.state === State.ENABLED) { | ||
const bp = this.breakpointsByLocation.get(breakpointId); | ||
if (bp && bp.active) { | ||
if (this.isNodePaused(location.id)) { | ||
this.queueEvent(location, event, done); | ||
} | ||
else { | ||
if (event.msg && event.msg[DEBUGGER_PAUSED]) { | ||
this.pause({ | ||
reason: "breakpoint", | ||
breakpoint: bp.id | ||
reason: "step", | ||
node: location.id | ||
}); | ||
this.queueEvent(locationId, event, done); | ||
this.queueEvent(location, event, done); | ||
} | ||
else { | ||
done(); | ||
const bp = this.breakpointsByLocation.get(breakpointId); | ||
if (bp && bp.active) { | ||
this.pause({ | ||
reason: "breakpoint", | ||
node: location.id, | ||
breakpoint: bp.id | ||
}); | ||
this.queueEvent(location, event, done); | ||
} | ||
else { | ||
done(); | ||
} | ||
} | ||
} | ||
else if (this.state === State.PAUSED) { | ||
this.queueEvent(locationId, event, done); | ||
} | ||
} | ||
enable() { | ||
this.log("Enabled"); | ||
this.state = State.ENABLED; | ||
this.enabled = true; | ||
this.RED.hooks.add("preRoute.flow-debugger", (sendEvent, done) => { | ||
@@ -97,4 +105,4 @@ if (isNodeInSubflowModule(sendEvent.source.node)) { | ||
this.RED.hooks.add("onReceive.flow-debugger", (receiveEvent, done) => { | ||
if (this.state === State.PAUSED && receiveEvent.destination.node.type === "inject") { | ||
// Inside a subflow module - don't pause the event | ||
if (receiveEvent.destination.node.type === "inject") { | ||
// Never pause an Inject node's internal receive event | ||
done(); | ||
@@ -104,2 +112,3 @@ return; | ||
if (isNodeInSubflowModule(receiveEvent.destination.node)) { | ||
// Inside a subflow module - don't pause the event | ||
done(); | ||
@@ -115,21 +124,54 @@ return; | ||
this.log("Disabled"); | ||
this.state = State.DISABLED; | ||
this.enabled = false; | ||
this.RED.hooks.remove("*.flow-debugger"); | ||
this.pausedLocations.clear(); | ||
this.drainQueues(true); | ||
} | ||
pause(event) { | ||
if (this.state === State.ENABLED) { | ||
this.state = State.PAUSED; | ||
const logReason = event ? ("@" + this.breakpoints.get(event.breakpoint).location.toString()) : "manual"; | ||
if (this.enabled) { | ||
let logReason; | ||
if (event) { | ||
if (this.config.breakpointAction === "pause-all") { | ||
this.pausedLocations.clear(); | ||
this.pausedLocations.add("*"); | ||
} | ||
else { | ||
this.pausedLocations.add(event.node); | ||
} | ||
if (event.reason === "breakpoint") { | ||
logReason = "@" + this.breakpoints.get(event.breakpoint).location.toString(); | ||
} | ||
else if (event.reason === "step") { | ||
logReason = "@" + event.node; | ||
} | ||
event.pausedLocations = [...this.pausedLocations]; | ||
} | ||
else { | ||
// Manual pause | ||
this.pausedLocations.clear(); | ||
this.pausedLocations.add("*"); | ||
logReason = "manual"; | ||
} | ||
this.log(`Flows paused: ${logReason}`); | ||
this.emit("paused", event || { reason: "manual", breakpoint: null }); | ||
this.emit("paused", event || { reason: "manual" }); | ||
} | ||
} | ||
resume() { | ||
if (this.state === State.PAUSED) { | ||
this.log("Flows resumed"); | ||
this.state = State.ENABLED; | ||
this.emit("resumed", {}); | ||
this.drainQueues(); | ||
resume(nodeId) { | ||
if (this.pausedLocations.size === 0) { | ||
return; | ||
} | ||
if (!nodeId || nodeId === "*") { | ||
console.log("resume - clear all locations"); | ||
this.pausedLocations.clear(); | ||
} | ||
else if (nodeId && this.pausedLocations.has(nodeId)) { | ||
this.pausedLocations.delete(nodeId); | ||
} | ||
else { | ||
// Nothing has been unpaused | ||
return; | ||
} | ||
this.log("Flows resumed"); | ||
this.emit("resumed", { node: nodeId }); | ||
this.drainQueues(); | ||
} | ||
@@ -140,8 +182,9 @@ deleteMessage(messageId) { | ||
this.messageQueue.remove(nextEvent); | ||
this.queuesByLocation[nextEvent.location].remove(nextEvent); | ||
const queueDepth = this.queuesByLocation[nextEvent.location].length; | ||
const nextEventLocation = nextEvent.location.toString(); | ||
this.queuesByLocation[nextEventLocation].remove(nextEvent); | ||
const queueDepth = this.queuesByLocation[nextEventLocation].length; | ||
if (queueDepth === 0) { | ||
delete this.queuesByLocation[nextEvent.location]; | ||
delete this.queuesByLocation[nextEventLocation]; | ||
} | ||
this.emit("messageDispatched", { id: nextEvent.id, location: nextEvent.location, depth: queueDepth }); | ||
this.emit("messageDispatched", { id: nextEvent.id, location: nextEventLocation, depth: queueDepth }); | ||
// Call done with false to prevent any further processing | ||
@@ -151,18 +194,25 @@ nextEvent.done(false); | ||
} | ||
isNodePaused(nodeId) { | ||
return this.pausedLocations.has("*") || this.pausedLocations.has(nodeId); | ||
} | ||
drainQueues(quiet) { | ||
let nextEvent; | ||
do { | ||
nextEvent = this.messageQueue.next(); | ||
if (nextEvent) { | ||
this.queuesByLocation[nextEvent.location].remove(nextEvent); | ||
const queueDepth = this.queuesByLocation[nextEvent.location].length; | ||
for (const nextEvent of this.messageQueue) { | ||
const eventNodeId = nextEvent.location.id; | ||
if (!this.isNodePaused(eventNodeId)) { | ||
const nextEventLocation = nextEvent.location.toString(); | ||
this.queuesByLocation[nextEventLocation].remove(nextEvent); | ||
const queueDepth = this.queuesByLocation[nextEventLocation].length; | ||
if (queueDepth === 0) { | ||
delete this.queuesByLocation[nextEvent.location]; | ||
delete this.queuesByLocation[nextEventLocation]; | ||
} | ||
if (!quiet) { | ||
this.emit("messageDispatched", { id: nextEvent.id, location: nextEvent.location, depth: queueDepth }); | ||
this.emit("messageDispatched", { id: nextEvent.id, location: nextEventLocation, depth: queueDepth }); | ||
} | ||
if (nextEvent.event.msg[DEBUGGER_PAUSED]) { | ||
delete nextEvent.event.msg[DEBUGGER_PAUSED]; | ||
} | ||
nextEvent.done(); | ||
this.messageQueue.remove(nextEvent); | ||
} | ||
} while (this.state !== State.PAUSED && nextEvent); | ||
} | ||
} | ||
@@ -173,3 +223,4 @@ setBreakpoint(location) { | ||
location, | ||
active: true | ||
active: true, | ||
mode: "all" | ||
}; | ||
@@ -200,3 +251,3 @@ this.breakpoints.set(bp.id, bp); | ||
step(messageId) { | ||
if (this.state === State.PAUSED) { | ||
if (this.enabled) { | ||
let nextEvent; | ||
@@ -213,9 +264,11 @@ if (messageId) { | ||
if (nextEvent) { | ||
this.log("Step: " + nextEvent.location.toString()); | ||
this.queuesByLocation[nextEvent.location].remove(nextEvent); | ||
const queueDepth = this.queuesByLocation[nextEvent.location].length; | ||
const nextEventLocation = nextEvent.location.toString(); | ||
this.log("Step: " + nextEventLocation); | ||
this.queuesByLocation[nextEventLocation].remove(nextEvent); | ||
const queueDepth = this.queuesByLocation[nextEventLocation].length; | ||
if (queueDepth === 0) { | ||
delete this.queuesByLocation[nextEvent.location]; | ||
delete this.queuesByLocation[nextEventLocation]; | ||
} | ||
this.emit("messageDispatched", { id: nextEvent.id, location: nextEvent.location, depth: queueDepth }); | ||
nextEvent.event.msg[DEBUGGER_PAUSED] = true; | ||
this.emit("messageDispatched", { id: nextEvent.id, location: nextEventLocation, depth: queueDepth }); | ||
nextEvent.done(); | ||
@@ -225,4 +278,14 @@ } | ||
} | ||
setConfig(newConfig) { | ||
let changed = false; | ||
for (const key in this.config) { | ||
if (newConfig.hasOwnProperty(key) && this.config[key] !== newConfig[key]) { | ||
changed = true; | ||
this.config[key] = newConfig[key]; | ||
} | ||
} | ||
return changed; | ||
} | ||
getState() { | ||
if (this.state === State.DISABLED) { | ||
if (!this.enabled) { | ||
return { enabled: false }; | ||
@@ -232,3 +295,4 @@ } | ||
enabled: true, | ||
paused: this.state === State.PAUSED, | ||
pausedLocations: [...this.pausedLocations], | ||
config: this.config, | ||
breakpoints: this.getBreakpoints(), | ||
@@ -250,3 +314,3 @@ queues: this.getMessageQueueDepths() | ||
getMessageQueueDepths() { | ||
if (this.state === State.DISABLED) { | ||
if (!this.enabled) { | ||
return {}; | ||
@@ -274,3 +338,4 @@ } | ||
} | ||
queueEvent(locationId, event, done) { | ||
queueEvent(location, event, done) { | ||
const locationId = location.toString(); | ||
if (!this.queuesByLocation[locationId]) { | ||
@@ -282,3 +347,3 @@ this.queuesByLocation[locationId] = new MessageQueue_1.MessageQueue("Location"); | ||
event, | ||
location: locationId, | ||
location, | ||
done, | ||
@@ -285,0 +350,0 @@ nextByLocation: null, |
@@ -14,4 +14,15 @@ { | ||
"deleteMessage": "Delete message", | ||
"stepMessage": "Step message" | ||
"stepMessage": "Step message", | ||
"filter": { | ||
"label": "Filter messages", | ||
"all": "all nodes", | ||
"flow": "current flow" | ||
}, | ||
"settings": "Debugger options", | ||
"breakpointAction": { | ||
"label": "Breakpoint action", | ||
"pause-all": "pause all nodes", | ||
"pause-bp": "pause at breakpoint" | ||
} | ||
} | ||
} |
{ | ||
"name": "node-red-debugger", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "A flow debugger for Node-RED 2.x", | ||
@@ -13,3 +13,3 @@ "repository": { | ||
"copyAssets": "node scripts/copy-static-assets.js", | ||
"dev": "nodemon --exec 'npm run build' -i dist -e 'ts html'", | ||
"dev": "nodemon --exec 'npm run build' -i dist -i resources -e 'ts html css'", | ||
"test": "npm run build" | ||
@@ -20,3 +20,4 @@ }, | ||
"files": [ | ||
"dist" | ||
"dist", | ||
"resources" | ||
], | ||
@@ -23,0 +24,0 @@ "license": "Apache-2", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
87423
18.54%20
17.65%688
17.41%