diff --git a/packages/host/src/Host.ts b/packages/host/src/Host.ts index b99392dc..0d3a4185 100644 --- a/packages/host/src/Host.ts +++ b/packages/host/src/Host.ts @@ -182,6 +182,12 @@ export class HostRouter { if (this.host !== origin && (!plugin || this.host.serverPlugins.has(plugin))) { this.host.connector.forward(message); } + } else if (dst.id === lib.Address.host) { + // Sent up to the controller, which passes it on to the other + // hosts. Nothing left to do when it came from there. + if (this.host !== origin && (!plugin || this.host.serverPlugins.has(plugin))) { + this.host.connector.forward(message); + } } else if (dst.id === lib.Address.control) { if (this.host !== origin) { if (!plugin || this.host.serverPlugins.has(plugin)) { diff --git a/packages/lib/src/link/link.ts b/packages/lib/src/link/link.ts index b7bc8c02..a1ed20ca 100644 --- a/packages/lib/src/link/link.ts +++ b/packages/lib/src/link/link.ts @@ -225,6 +225,16 @@ export class Link { return; } + // A broadcast is addressed to every link of the type it targets, so + // being addressed to this one does not mean it stops here. + if ( + message.type === "event" + && message.dst.type === libData.Address.broadcast + && this.router + ) { + this._routeMessage(message, entry); + } + if (message.type === "request") { this._processRequest( message as libData.MessageRequest, diff --git a/test/integration/routing.js b/test/integration/routing.js index a4672305..2f40745a 100644 --- a/test/integration/routing.js +++ b/test/integration/routing.js @@ -235,8 +235,6 @@ describe("Integration of link routing", function() { }; for (const dstName of dsts[dst]) { if (srcName === dstName) { continue; } - // TODO remove when broadcasting from instance to hosts is fixed, see #575 - if (srcName === "instanceA1" && dstName === "hostB") { continue; } let called; handle(get(dstName), Event, async () => { called = true; }); const should = canReach(dstName); diff --git a/test/lib/link/link.js b/test/lib/link/link.js index c8bd06c4..4690944a 100644 --- a/test/lib/link/link.js +++ b/test/lib/link/link.js @@ -254,6 +254,42 @@ describe("lib/link/link", function() { await assert.rejects(pending, { message: "Session Lost" }); }); + describe("Broadcast handling", function() { + // A broadcast is addressed to every link of the type it targets, + // so a link it reaches has to pass it on as well as handle it, + // see #575. + function mockRouter(record) { + return { forwardMessage: () => { record.routed = true; return true; } }; + } + + it("should route a broadcast addressed to this link as well as handle it", function() { + const record = {}; + let handled = false; + testLink.router = mockRouter(record); + testLink.handle(SimpleEvent, async () => { handled = true; }); + testConnector.emit("message", new lib.MessageEvent(1, dst, addr("allControls"), "SimpleEvent")); + assert(record.routed, "broadcast was not routed onwards"); + assert(handled, "broadcast was not handled locally"); + }); + + it("should handle a broadcast on a link which does not route", function() { + // Instances have no router, so routing unconditionally would + // throw here and the event would never be handled. + let handled = false; + testLink.handle(SimpleEvent, async () => { handled = true; }); + testConnector.emit("message", new lib.MessageEvent(1, dst, addr("allControls"), "SimpleEvent")); + assert(handled, "broadcast was not handled locally"); + }); + + it("should not route a non broadcast addressed to this link", function() { + const record = {}; + testLink.router = mockRouter(record); + testLink.handle(SimpleEvent, async () => {}); + testConnector.emit("message", new lib.MessageEvent(1, dst, src, "SimpleEvent")); + assert(!record.routed, "message addressed to this link was routed onwards"); + }); + }); + describe(".send()", function() { it("should send request to the other side of the link", async function() { let message = events.once(testConnector, "send");