1.5.2: send() never rejects (status, error, scene, change report: resolves the topic or "" and reports the failure through the bridge's error event when listened to - 1.5.1 rejected, and an un-caught .send() killed the host on any broker hiccup); types ship (declaration: true, dist/*.d.ts tracked; addSupportedModes takes {value, modeResources}, ActionMapping payload optional, addHealthProp accepts EndpointHealth or the string); disconnect()/connect() re-binds devices to the new client; registerDevice returns the existing device on a duplicate endpointId (warning via the log hook, console.warn without one); ThermostatController discovery lists targetSetpoint and drops adaptiveRecoveryStatus; the UNSUPORTED INTERFACE TYPE stderr spam goes through the log hook. Examples: require("alex2node"), an error listener in each, EndpointHealth.OK, neutral endpoint ids, BlindControl reads correlationToken from the header, the thermostat reports Fahrenheit as Fahrenheit, ExamplePowerController is power-only again + new ExamplePowerControllerWithBrightness. readme (install from Forgejo, 1.5.2 changelog, table syntax), LICENSE (MIT); tests for the non-rejecting send, reconnect, duplicate, thermostat discovery and the shipped declaration signatures (8/8 on an in-process broker).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
David 2026-09-28 04:58:39 +00:00
parent 717af632de
commit 275f00f8a7
35 changed files with 1011 additions and 176 deletions

View file

@ -9,6 +9,8 @@ import { AlexaErrorResponse } from "./AlexaErrorResponse";
class Device extends EventEmitter {
protected softwareVersion: string = "1.0.0";
private capabilities: AlexaInterface[] = [];
/** Where a failed publish from this device or a message it built is reported; Alex2MQTT sets it (1.5.2). */
public onPublishError?: (err: Error) => void;
constructor(
private mqttClient: MqttClient,
@ -24,6 +26,11 @@ class Device extends EventEmitter {
super();
}
/** Internal (1.5.2): Alex2MQTT.connect() re-binds every registered device to its new broker client after a disconnect(). */
setMqttClient(client: MqttClient): void {
this.mqttClient = client;
}
getName(): string {
return this.name;
}
@ -52,19 +59,21 @@ class Device extends EventEmitter {
return this.description;
}
getErrorMessage(correlationToken: string): AlexaErrorResponse {
return new AlexaErrorResponse(
const msg = new AlexaErrorResponse(
correlationToken,
this.rootTopic,
this.endpointId,
this.mqttClient
);
msg.onPublishError = this.onPublishError;
return msg;
}
getStatusMessage(
correlationToken: string,
isResponse = false,
isDeferred = false
): AlexaStatusMessage {
return new AlexaStatusMessage(
const msg = new AlexaStatusMessage(
correlationToken,
this.rootTopic,
this.endpointId,
@ -72,6 +81,8 @@ class Device extends EventEmitter {
isResponse,
isDeferred
);
msg.onPublishError = this.onPublishError;
return msg;
}
/**
* A proactive Alexa.ChangeReport (1.5.1): add the changed properties (the default target), optionally
@ -79,11 +90,13 @@ class Device extends EventEmitter {
* event gateway with the user's token. Alexa only accepts it for capabilities registered with proactivelyReported.
*/
getChangeReport(cause: ChangeCause = "PHYSICAL_INTERACTION"): AlexaStatusMessage {
return new AlexaStatusMessage("", this.rootTopic, this.endpointId, this.mqttClient, false, false, cause);
const msg = new AlexaStatusMessage("", this.rootTopic, this.endpointId, this.mqttClient, false, false, cause);
msg.onPublishError = this.onPublishError;
return msg;
}
/**
* Alexa.SceneController: answer an Activate / Deactivate directive with ActivationStarted / DeactivationStarted
* (1.5.1). Resolves with the topic published to.
* (1.5.1). Resolves with the topic published to, or "" when the publish failed (never rejects, 1.5.2).
*/
sendSceneResponse(correlationToken: string, activated: boolean, cause: ChangeCause = "VOICE_INTERACTION", sendAsync = false): Promise<string> {
const payload = {
@ -95,8 +108,12 @@ class Device extends EventEmitter {
},
};
const topic = `${this.rootTopic}/${this.endpointId}/${sendAsync ? "deferredResponse" : "alexaResponce"}`;
return new Promise((resolve, reject) => {
this.mqttClient.publish(topic, JSON.stringify(payload), (err) => (err ? reject(err) : resolve(topic)));
return new Promise((resolve) => {
this.mqttClient.publish(topic, JSON.stringify(payload), (err) => {
if (!err) return resolve(topic);
if (this.onPublishError) this.onPublishError(err); // -> the bridge's "error" event (when somebody listens)
resolve(""); // 1.5.1 rejected here, and an un-caught send() then killed the host on any broker hiccup
});
});
}
getCapabilities(): AlexaInterface[] {