bridge: publish through a Publisher; devices no longer hold the broker client
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>
This commit is contained in:
parent
46fa06728c
commit
6789a1a077
39 changed files with 839 additions and 246 deletions
51
dist/cjs/compat/AlexaErrorResponse.js
vendored
51
dist/cjs/compat/AlexaErrorResponse.js
vendored
|
|
@ -1,16 +1,51 @@
|
|||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AlexaErrorResponse = void 0;
|
||||
const crypto_1 = require("crypto");
|
||||
const build_js_1 = require("../messages/build.js");
|
||||
const errors_js_1 = require("../messages/errors.js");
|
||||
const topics = __importStar(require("../topics.js"));
|
||||
const transport_js_1 = require("../transport.js");
|
||||
/** An ErrorResponse as 1.x builds it: device.getErrorMessage(token), setErrorMessage(), send(). */
|
||||
class AlexaErrorResponse {
|
||||
constructor(correlationToken, rootTopic, endpointId, mqttClient) {
|
||||
constructor(correlationToken, rootTopic, endpointId, publisher) {
|
||||
this.correlationToken = correlationToken;
|
||||
this.rootTopic = rootTopic;
|
||||
this.endpointId = endpointId;
|
||||
this.mqttClient = mqttClient;
|
||||
this.publisher = publisher;
|
||||
// Sent when setErrorMessage() was not called: 1.x published an empty payload, which is no ErrorResponse to Alexa
|
||||
this.error = errors_js_1.AlexaErrors.of("INTERNAL_ERROR", "the device answered with an error and did not say which");
|
||||
// One id for the message, however often toJSON() is called
|
||||
|
|
@ -39,16 +74,8 @@ class AlexaErrorResponse {
|
|||
}
|
||||
/** Resolves with the topic published to, or "" when the publish failed. Never rejects. */
|
||||
send(sendAsync = false) {
|
||||
const topic = `${this.rootTopic}/${this.endpointId}/${sendAsync ? "deferredResponse" : "alexaResponce"}`; // "alexaResponce" is how Alex2MQTT and Alex2ESP spell the topic
|
||||
return new Promise((resolve) => {
|
||||
this.mqttClient.publish(topic, JSON.stringify(this.toJSON()), (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
|
||||
});
|
||||
});
|
||||
const answer = sendAsync ? topics.deferred : topics.response;
|
||||
return (0, transport_js_1.send)(this.publisher, answer(this.rootTopic, this.endpointId), () => this.toJSON(), this.onPublishError);
|
||||
}
|
||||
}
|
||||
exports.AlexaErrorResponse = AlexaErrorResponse;
|
||||
|
|
|
|||
60
dist/cjs/compat/AlexaStatusMessage.js
vendored
60
dist/cjs/compat/AlexaStatusMessage.js
vendored
|
|
@ -1,9 +1,44 @@
|
|||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AlexaStatusMessage = void 0;
|
||||
const crypto_1 = require("crypto");
|
||||
const build_js_1 = require("../messages/build.js");
|
||||
const StateBuilder_js_1 = require("../messages/StateBuilder.js");
|
||||
const topics = __importStar(require("../topics.js"));
|
||||
const transport_js_1 = require("../transport.js");
|
||||
const enums_js_1 = require("./enums.js");
|
||||
// The 1.x helpers report every temperature in Celsius, whatever scale the device works in
|
||||
function celsius(scale, value) {
|
||||
|
|
@ -14,11 +49,11 @@ function celsius(scale, value) {
|
|||
* device.getChangeReport(), the add...Prop() calls, send(). The values are not checked.
|
||||
*/
|
||||
class AlexaStatusMessage {
|
||||
constructor(correlationToken, rootTopic, endpointId, mqttClient, isResponse = false, isDeferred = false, changeCause = null) {
|
||||
constructor(correlationToken, rootTopic, endpointId, publisher, isResponse = false, isDeferred = false, changeCause = null) {
|
||||
this.correlationToken = correlationToken;
|
||||
this.rootTopic = rootTopic;
|
||||
this.endpointId = endpointId;
|
||||
this.mqttClient = mqttClient;
|
||||
this.publisher = publisher;
|
||||
this.isResponse = isResponse;
|
||||
this.isDeferred = isDeferred;
|
||||
this.changeCause = changeCause;
|
||||
|
|
@ -109,24 +144,9 @@ class AlexaStatusMessage {
|
|||
* to). Never rejects (1.5.1 did, so an un-caught send() could kill the host).
|
||||
*/
|
||||
send(sendAsync = false) {
|
||||
const topic = this.changeCause
|
||||
? `${this.rootTopic}/changeReport`
|
||||
: `${this.rootTopic}/${this.endpointId}/${sendAsync ? "deferredResponse" : "alexaResponce"}`; // "alexaResponce" is how Alex2MQTT and Alex2ESP spell the topic
|
||||
return new Promise((resolve) => {
|
||||
const failed = (err) => {
|
||||
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
|
||||
};
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.stringify(this.toJSON());
|
||||
}
|
||||
catch (err) {
|
||||
return failed(err);
|
||||
}
|
||||
this.mqttClient.publish(topic, payload, (err) => (err ? failed(err) : resolve(topic)));
|
||||
});
|
||||
const answer = sendAsync ? topics.deferred : topics.response;
|
||||
const topic = this.changeCause ? topics.changeReport(this.rootTopic) : answer(this.rootTopic, this.endpointId);
|
||||
return (0, transport_js_1.send)(this.publisher, topic, () => this.toJSON(), this.onPublishError);
|
||||
}
|
||||
}
|
||||
exports.AlexaStatusMessage = AlexaStatusMessage;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue