Alex2Node/dist/cjs/messages/build.js
David 4a3dd81bcf messages: DoorbellPress is sent with the empty context of its page
proactiveEvent() built every proactive event without a context, so doorbellPress() and
device.raise(DoorbellEventSource, "DoorbellPress") left out the "context": {} that the example of
alexa-doorbelleventsource.html has. The Event of Alexa.SimpleEventSource is printed without one, so
the event descriptor says which it is (emptyContext) and raise() passes it on to the builder.

A raised DoorbellPress is now { event, context: {} }, 13 bytes longer; the Event of a button is
unchanged. The tests compare the keys and the context of the built message with the fixture of the
page, for the builder, the descriptor and raise(). 267 tests pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 21:08:10 +00:00

145 lines
6.6 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sameProperty = exports.MessageError = void 0;
exports.response = response;
exports.stateReport = stateReport;
exports.deferredResponse = deferredResponse;
exports.errorResponse = errorResponse;
exports.changeReport = changeReport;
exports.sceneEvent = sceneEvent;
exports.proactiveEvent = proactiveEvent;
exports.doorbellPress = doorbellPress;
exports.simpleEvent = simpleEvent;
// Every message the bridge publishes, built from plain values. Nothing here reads the clock or makes an id unless
// the caller leaves messageId or a time out, so a test that passes both compares whole objects.
const crypto_1 = require("crypto");
const errors_js_1 = require("./errors.js");
const property_js_1 = require("./property.js");
/** A message that would be dropped on its way to Alexa, refused where it is built. */
class MessageError extends Error {
constructor(endpointId, problem) {
super(`${endpointId}: ${problem}`);
this.endpointId = endpointId;
this.problem = problem;
this.name = "MessageError";
}
}
exports.MessageError = MessageError;
// The fields in the order of the examples
function header(namespace, name, fields) {
return {
namespace,
name,
...(fields.instance ? { instance: fields.instance } : {}),
messageId: fields.messageId ?? (0, crypto_1.randomUUID)(),
...(fields.correlationToken !== undefined ? { correlationToken: fields.correlationToken } : {}),
payloadVersion: fields.payloadVersion ?? "3",
};
}
/**
* The answer to a directive (alexa-response.html, "Synchronous response"). An endpoint with nothing to report
* answers with an empty list of properties, not without a context (state-reporting-for-smart-home-addons.html,
* "Directive response example for a property that isn't retrievable").
*/
function response(fields) {
return {
event: {
header: header(fields.namespace ?? "Alexa", fields.name ?? "Response", fields),
endpoint: { endpointId: fields.endpointId },
payload: fields.payload ?? {},
},
context: { properties: [...(fields.context ?? [])] },
};
}
/** The answer to ReportState: every retrievable property of the endpoint. */
function stateReport(fields) {
return response({ ...fields, name: "StateReport", namespace: "Alexa", payload: {} });
}
/**
* "The directive arrived, the answer follows": no context, the state is in the Response that follows
* (alexa-response.html, "Deferred response example").
*/
function deferredResponse(fields) {
const { estimatedDeferralInSeconds } = fields;
return {
event: {
header: header("Alexa", "DeferredResponse", fields),
endpoint: { endpointId: fields.endpointId },
payload: estimatedDeferralInSeconds === undefined ? {} : { estimatedDeferralInSeconds },
},
};
}
/** The answer to a directive the endpoint could not follow (alexa-errorresponse.html). */
function errorResponse(fields) {
return {
event: {
header: header(fields.namespace ?? (0, errors_js_1.errorNamespace)(fields.type), "ErrorResponse", fields),
endpoint: { endpointId: fields.endpointId },
payload: { type: fields.type, message: fields.message, ...fields.extra },
},
};
}
/** The same property of the same instance, whatever its value. */
const sameProperty = (a, b) => a.namespace === b.namespace && a.name === b.name && (a.instance ?? "") === (b.instance ?? "");
exports.sameProperty = sameProperty;
/**
* A change of state nobody asked for. The header has no correlationToken (message-guide.html, "Header object").
* A property that changed is left out of the context: it is reported in one of the two (same page, "Context
* object"). Throws MessageError when nothing changed: Alex2MQTT drops such a report without a word.
*/
function changeReport(fields) {
const { endpointId, changed, context = [] } = fields;
if (changed.length === 0) {
throw new MessageError(endpointId, "a ChangeReport needs at least one property that changed, this one has none. "
+ "Add the changed property before unchanged(), or send no report when nothing changed");
}
return {
event: {
header: header("Alexa", "ChangeReport", { messageId: fields.messageId }),
endpoint: { endpointId },
payload: { change: { cause: { type: fields.cause ?? "PHYSICAL_INTERACTION" }, properties: [...changed] } },
},
context: { properties: context.filter((property) => !changed.some((other) => (0, exports.sameProperty)(property, other))) },
};
}
/** The answer to Activate and Deactivate of a scene (alexa-scenecontroller.html). */
function sceneEvent(fields) {
return {
event: {
header: header("Alexa.SceneController", fields.activated ? "ActivationStarted" : "DeactivationStarted", fields),
endpoint: { endpointId: fields.endpointId },
payload: { cause: { type: fields.cause ?? "VOICE_INTERACTION" }, timestamp: (0, property_js_1.isoTime)(fields.timestamp) },
},
context: {},
};
}
/**
* An event a device raises by itself, of any interface. The header has no correlationToken: nobody asked. The
* pages differ on the context, so the caller says which it is: DoorbellPress is printed with an empty one
* (alexa-doorbelleventsource.html), the Event of Alexa.SimpleEventSource without.
*/
function proactiveEvent(fields) {
const { namespace, name, instance, messageId, payloadVersion, payload } = fields;
return {
event: {
header: header(namespace, name, { instance, messageId, payloadVersion }),
endpoint: { endpointId: fields.endpointId },
payload,
},
...(fields.emptyContext ? { context: {} } : {}),
};
}
/** Somebody rang (alexa-doorbelleventsource.html). */
function doorbellPress(fields) {
const { endpointId, messageId } = fields;
const payload = { cause: { type: fields.cause ?? "PHYSICAL_INTERACTION" }, timestamp: (0, property_js_1.isoTime)(fields.timestamp) };
return proactiveEvent({
endpointId, messageId, namespace: "Alexa.DoorbellEventSource", name: "DoorbellPress", payload, emptyContext: true,
});
}
/** An event of a button or a sensor that routines start on (alexa-simpleeventsource.html). */
function simpleEvent(fields) {
const { endpointId, messageId, instance } = fields;
const payload = { id: fields.id, timestamp: (0, property_js_1.isoTime)(fields.timestamp) };
return proactiveEvent({ endpointId, messageId, instance, namespace: "Alexa.SimpleEventSource", name: "Event", payloadVersion: "1.0", payload });
}