A broker that mirrors another delivers every message twice, and the bridge
answered both copies of a discovery request. A request is now remembered
by its messageId for 60 s, as a directive is, and its copy is dropped with
a log line. The messageId is read from the header of the Discover
directive or from the request itself when that is the header alone.
A request without a messageId is answered every time, as before.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The answer on <root>/discover_r called the publisher directly; a publisher
that threw or rejected was only caught by the handler meant for listeners
of "discover". It now takes the path of every device message: send()
resolves, and a failure goes to the "error" event when somebody listens.
A test covers a publish that fails, throws and rejects, with and without
an "error" listener: receive() resolves each time and "discover" is not
emitted.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
src/transport.ts has the Publisher interface, MqttPublisher (the client the bridge has at the time) and
MemoryPublisher (tests without a broker); src/topics.ts names the four topics the library publishes to.
Device, AlexaStatusMessage, AlexaErrorResponse and sendSceneResponse shared three copies of the
publish-and-report code: they now call one send() that resolves the topic or "" and never rejects.
registerDevice() and addDevice() work before connect(); a send() without a connection resolves "" and the
"error" event says to call connect(). unregisterDevice() and clearDevices() take the publisher from the device.
Device.setMqttClient() is gone, the first constructor argument of Device is ignored, and the message classes
take a Publisher where they took the client. Tests: 108 -> 113.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>