diff --git a/packages/node_modules/@node-red/runtime/lib/hooks.js b/packages/node_modules/@node-red/runtime/lib/hooks.js index 10303ed55..4ae537cc4 100644 --- a/packages/node_modules/@node-red/runtime/lib/hooks.js +++ b/packages/node_modules/@node-red/runtime/lib/hooks.js @@ -1,5 +1,17 @@ const Log = require("@node-red/util").log; +const VALID_HOOKS = [ + // Message Routing Path + "onSend", + "preRoute", + "preDeliver", + "postDeliver", + "onReceive", + "postReceive", + "onComplete" +] + + // Flags for what hooks have handlers registered let states = { } @@ -34,6 +46,9 @@ let labelledHooks = { } */ function add(hookId, callback) { let [id, label] = hookId.split("."); + if (VALID_HOOKS.indexOf(id) === -1) { + throw new Error(`Invalid hook '${id}'`); + } if (label) { if (labelledHooks[label] && labelledHooks[label][id]) { throw new Error("Hook "+hookId+" already registered") @@ -149,8 +164,17 @@ function clear() { labelledHooks = {} states = {} } + +function has(hookId) { + let [id, label] = hookId.split("."); + if (label) { + return !!(labelledHooks[label] && labelledHooks[label][id]) + } + return !!states[id] +} + module.exports = { - get states() { return states }, + has, clear, add, remove, diff --git a/test/unit/@node-red/runtime/lib/hooks_spec.js b/test/unit/@node-red/runtime/lib/hooks_spec.js index 79c8c6bce..4fb95b68a 100644 --- a/test/unit/@node-red/runtime/lib/hooks_spec.js +++ b/test/unit/@node-red/runtime/lib/hooks_spec.js @@ -9,48 +9,79 @@ describe("runtime/hooks", function() { }) it("allows a hook to be registered", function(done) { let calledWith = null; - should.not.exist(hooks.states.foo); - hooks.add("foo", function(payload) { calledWith = payload } ) - hooks.states.foo.should.be.true(); + hooks.has("onSend").should.be.false(); + hooks.add("onSend", function(payload) { calledWith = payload } ) + hooks.has("onSend").should.be.true(); let data = { a: 1 }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { calledWith.should.equal(data); done(err); }) }) - + it("rejects invalid hook id", function(done) { + try { + hooks.add("foo", function(payload) {}) + done(new Error("Invalid hook accepted")) + } catch(err) { + done(); + } + }) it("calls hooks in the order they were registered", function(done) { - hooks.add("foo", function(payload) { payload.order.push("A") } ) - hooks.add("foo", function(payload) { payload.order.push("B") } ) + hooks.add("onSend", function(payload) { payload.order.push("A") } ) + hooks.add("onSend", function(payload) { payload.order.push("B") } ) let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql(["A","B"]) done(err); }) }) it("does not allow multiple hooks with same id.label", function() { - hooks.add("foo.one", function(payload) { payload.order.push("A") } ); + hooks.has("onSend.one").should.be.false(); + hooks.has("onSend").should.be.false(); + hooks.add("onSend.one", function(payload) { payload.order.push("A") } ); + hooks.has("onSend.one").should.be.true(); + hooks.has("onSend").should.be.true(); (function() { - hooks.add("foo.one", function(payload) { payload.order.push("B") } ) + hooks.add("onSend.one", function(payload) { payload.order.push("B") } ) }).should.throw(); }) it("removes labelled hook", function(done) { - hooks.add("foo.A", function(payload) { payload.order.push("A") } ) - hooks.add("foo.B", function(payload) { payload.order.push("B") } ) + hooks.has("onSend.A").should.be.false(); + hooks.has("onSend.B").should.be.false(); + hooks.has("onSend").should.be.false(); + + hooks.add("onSend.A", function(payload) { payload.order.push("A") } ) + + hooks.has("onSend.A").should.be.true(); + hooks.has("onSend.B").should.be.false(); + hooks.has("onSend").should.be.true(); + + hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) + + hooks.has("onSend.A").should.be.true(); + hooks.has("onSend.B").should.be.true(); + hooks.has("onSend").should.be.true(); + + hooks.remove("onSend.A"); + + hooks.has("onSend.A").should.be.false(); + hooks.has("onSend.B").should.be.true(); + hooks.has("onSend").should.be.true(); - hooks.remove("foo.A"); - hooks.states.foo.should.be.true(); let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { try { data.order.should.eql(["B"]) - hooks.remove("foo.B"); - should.not.exist(hooks.states.foo); + hooks.remove("onSend.B"); + hooks.has("onSend.A").should.be.false(); + hooks.has("onSend.B").should.be.false(); + hooks.has("onSend").should.be.false(); + done(err); } catch(err2) { done(err2); @@ -59,30 +90,30 @@ describe("runtime/hooks", function() { }) it("cannot remove unlabelled hook", function() { - hooks.add("foo", function(payload) { payload.order.push("A") } ); + hooks.add("onSend", function(payload) { payload.order.push("A") } ); (function() { - hooks.remove("foo") + hooks.remove("onSend") }).should.throw(); }) it("removes all hooks with same label", function(done) { - hooks.add("foo.A", function(payload) { payload.order.push("A") } ) - hooks.add("foo.B", function(payload) { payload.order.push("B") } ) - hooks.add("bar.A", function(payload) { payload.order.push("C") } ) - hooks.add("bar.B", function(payload) { payload.order.push("D") } ) + hooks.add("onSend.A", function(payload) { payload.order.push("A") } ) + hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) + hooks.add("preRoute.A", function(payload) { payload.order.push("C") } ) + hooks.add("preRoute.B", function(payload) { payload.order.push("D") } ) let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql(["A","B"]) - hooks.trigger("bar", data, err => { + hooks.trigger("preRoute", data, err => { data.order.should.eql(["A","B","C","D"]) data.order = []; hooks.remove("*.A"); - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql(["B"]) - hooks.trigger("bar", data, err => { + hooks.trigger("preRoute", data, err => { data.order.should.eql(["B","D"]) }) done(err); @@ -93,22 +124,22 @@ describe("runtime/hooks", function() { it("halts execution on return false", function(done) { - hooks.add("foo.A", function(payload) { payload.order.push("A"); return false } ) - hooks.add("foo.B", function(payload) { payload.order.push("B") } ) + hooks.add("onSend.A", function(payload) { payload.order.push("A"); return false } ) + hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql(["A"]) err.should.be.false(); done(); }) }) it("halts execution on thrown error", function(done) { - hooks.add("foo.A", function(payload) { payload.order.push("A"); throw new Error("error") } ) - hooks.add("foo.B", function(payload) { payload.order.push("B") } ) + hooks.add("onSend.A", function(payload) { payload.order.push("A"); throw new Error("error") } ) + hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql(["A"]) should.exist(err); err.should.not.be.false() @@ -117,47 +148,47 @@ describe("runtime/hooks", function() { }) it("handler can use callback function", function(done) { - hooks.add("foo.A", function(payload, done) { + hooks.add("onSend.A", function(payload, done) { setTimeout(function() { payload.order.push("A") done() },30) }) - hooks.add("foo.B", function(payload) { payload.order.push("B") } ) + hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql(["A","B"]) done(err); }) }) it("handler can use callback function - halt execution", function(done) { - hooks.add("foo.A", function(payload, done) { + hooks.add("onSend.A", function(payload, done) { setTimeout(function() { payload.order.push("A") done(false) },30) }) - hooks.add("foo.B", function(payload) { payload.order.push("B") } ) + hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql(["A"]) err.should.be.false() done(); }) }) it("handler can use callback function - halt on error", function(done) { - hooks.add("foo.A", function(payload, done) { + hooks.add("onSend.A", function(payload, done) { setTimeout(function() { done(new Error("test error")) },30) }) - hooks.add("foo.B", function(payload) { payload.order.push("B") } ) + hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql([]) should.exist(err); err.should.not.be.false() @@ -166,7 +197,7 @@ describe("runtime/hooks", function() { }) it("handler be an async function", function(done) { - hooks.add("foo.A", async function(payload) { + hooks.add("onSend.A", async function(payload) { return new Promise(resolve => { setTimeout(function() { payload.order.push("A") @@ -174,17 +205,17 @@ describe("runtime/hooks", function() { },30) }); }) - hooks.add("foo.B", function(payload) { payload.order.push("B") } ) + hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql(["A","B"]) done(err); }) }) it("handler be an async function - halt execution", function(done) { - hooks.add("foo.A", async function(payload) { + hooks.add("onSend.A", async function(payload) { return new Promise(resolve => { setTimeout(function() { payload.order.push("A") @@ -192,26 +223,26 @@ describe("runtime/hooks", function() { },30) }); }) - hooks.add("foo.B", function(payload) { payload.order.push("B") } ) + hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql(["A"]) done(err); }) }) it("handler be an async function - halt on error", function(done) { - hooks.add("foo.A", async function(payload) { + hooks.add("onSend.A", async function(payload) { return new Promise((resolve,reject) => { setTimeout(function() { reject(new Error("test error")) },30) }); }) - hooks.add("foo.B", function(payload) { payload.order.push("B") } ) + hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; - hooks.trigger("foo",data,err => { + hooks.trigger("onSend",data,err => { data.order.should.eql([]) should.exist(err); err.should.not.be.false()