"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AlexaErrors = exports.AlexaError = void 0; exports.errorNamespace = errorNamespace; // The errors a device answers a directive with (alexa-errorresponse.html): a helper for each type of the table. // The helper of a type whose payload has fields of its own takes them, so that they cannot be misspelt or left out. const catalog_js_1 = require("../registry/catalog.js"); /** * The namespace an ErrorResponse of this type goes under: the one the table of error types lists it with * (THERMOSTAT_IS_OFF: Alexa.ThermostatController, OBSTACLE_DETECTED: Alexa.Safety), Alexa for a type the table * does not have. */ function errorNamespace(type) { return Object.prototype.hasOwnProperty.call(catalog_js_1.ERROR_TYPES, type) ? catalog_js_1.ERROR_TYPES[type] : "Alexa"; } /** Thrown by a directive handler, or built to be sent: the directive is answered with this ErrorResponse. */ class AlexaError extends Error { constructor(type, message, extra = {}, namespace = errorNamespace(type)) { super(`${type}: ${message}`); this.name = "AlexaError"; this.type = type; this.alexaMessage = message; this.extra = extra; this.namespace = namespace; } } exports.AlexaError = AlexaError; // alexa-errorresponse.html, "Properties and objects": the values a field takes const CURRENT_DEVICE_MODES = ["ASLEEP", "COLOR", "DEMAND_RESPONSE", "ECO", "NOT_PROVISIONED", "VACATION", "OTHER"]; const REASONS = ["DEEP_SLEEP_MODE", "OUT_OF_NETWORK_CONNECTIVITY", "NO_CONNECTIVITY_PACKAGE_ENABLED", "UNKNOWN"]; const CHARGE_STATES = ["ALREADY_CHARGED_TO_REQUIRED_LEVEL", "CURRENTLY_CHARGING", "FULLY_CHARGED", "NOT_CONNECTED_TO_POWER"]; const RESOURCE_TYPES = ["WATER"]; const MAINTENANCE_ACTIONS = ["EMPTY_BIN"]; // A field that was not given is left out of the payload function given(fields) { return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined)); } // TypeScript refuses a call without a required field. A JavaScript caller hears of it here, where the helper is // called: the payload would reach Alexa without the field. function required(helper, field, value, values) { if (values.includes(value)) return value; throw new TypeError(`AlexaErrors.${helper}() got ${JSON.stringify(value)} as ${field}: pass one of ${values.join(", ")}`); } function range(helper, validRange) { if (validRange === undefined) return undefined; const { minimumValue, maximumValue } = validRange; if (minimumValue === undefined || maximumValue === undefined) { throw new TypeError(`AlexaErrors.${helper}() got a validRange without ${minimumValue === undefined ? "minimumValue" : "maximumValue"}: pass both ends, or no range`); } return { minimumValue, maximumValue }; } // The helpers of the types whose payload has fields next to type and message. A required field is a required // argument; a field the page calls optional can be left out. const withFields = { /** The value is outside what the endpoint takes. For a temperature: temperatureValueOutOfRange(). */ valueOutOfRange(message, validRange) { return new AlexaError("VALUE_OUT_OF_RANGE", message, given({ validRange: range("valueOutOfRange", validRange) })); }, temperatureValueOutOfRange(message, validRange) { const helper = "temperatureValueOutOfRange"; return new AlexaError("TEMPERATURE_VALUE_OUT_OF_RANGE", message, given({ validRange: range(helper, validRange) })); }, /** A light showing a color asked for a color temperature: "COLOR". */ notSupportedInCurrentMode(message, currentDeviceMode) { const mode = required("notSupportedInCurrentMode", "currentDeviceMode", currentDeviceMode, CURRENT_DEVICE_MODES); return new AlexaError("NOT_SUPPORTED_IN_CURRENT_MODE", message, { currentDeviceMode: mode }); }, /** percentageState: what is left of the battery, 0 to 100. */ endpointLowPower(message, percentageState) { return new AlexaError("ENDPOINT_LOW_POWER", message, given({ percentageState })); }, endpointControlUnavailable(message, reason) { return new AlexaError("ENDPOINT_CONTROL_UNAVAILABLE", message, { reason: required("endpointControlUnavailable", "reason", reason, REASONS), }); }, /** currentChargeLevelInPercentage: the battery level, 0 to 100. */ notSupportedWithCurrentBatteryChargeState(message, currentChargeState, currentChargeLevelInPercentage) { const helper = "notSupportedWithCurrentBatteryChargeState"; return new AlexaError("NOT_SUPPORTED_WITH_CURRENT_BATTERY_CHARGE_STATE", message, given({ currentChargeState: required(helper, "currentChargeState", currentChargeState, CHARGE_STATES), currentChargeLevelInPercentage, })); }, /** What has to be refilled. */ insufficientResource(message, resourceType) { return new AlexaError("INSUFFICIENT_RESOURCE", message, { resourceType: required("insufficientResource", "resourceType", resourceType, RESOURCE_TYPES), }); }, /** What the user has to do first. */ maintenanceRequired(message, maintenanceAction) { const action = maintenanceAction === undefined ? undefined : required("maintenanceRequired", "maintenanceAction", maintenanceAction, MAINTENANCE_ACTIONS); return new AlexaError("MAINTENANCE_REQUIRED", message, given({ maintenanceAction: action })); }, /** Goes under Alexa.ThermostatController. minimumTemperatureDelta: how far apart the setpoints have to be. */ requestedSetpointsTooClose(message, minimumTemperatureDelta) { return new AlexaError("REQUESTED_SETPOINTS_TOO_CLOSE", message, given({ minimumTemperatureDelta })); }, /** Goes under Alexa.SecurityPanelController. With the endpoints listed, the user can bypass them by voice. */ bypassNeeded(message, endpointsNeedingBypass) { return new AlexaError("BYPASS_NEEDED", message, given({ endpointsNeedingBypass })); }, }; const camelCase = (type) => type.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); const plain = Object.fromEntries(Object.keys(catalog_js_1.ERROR_TYPES) .filter((type) => !Object.prototype.hasOwnProperty.call(withFields, camelCase(type))) .map((type) => [camelCase(type), (message) => new AlexaError(type, message)])); /** * An error for every type of the table of alexa-errorresponse.html, under the name of the type in camel case: * * throw AlexaErrors.endpointUnreachable("the lamp does not answer"); * throw AlexaErrors.valueOutOfRange("the lift goes from 0 to 100", { minimumValue: 0, maximumValue: 100 }); * throw AlexaErrors.thermostatIsOff("the thermostat is off"); // sent under Alexa.ThermostatController */ exports.AlexaErrors = { ...plain, ...withFields, /** An error of any type, one the table does not have too. */ of(type, message, extra = {}) { return new AlexaError(type, message, extra); }, /** temperatureValueOutOfRange() under its shorter name. */ temperatureOutOfRange: withFields.temperatureValueOutOfRange, /** requestedSetpointsTooClose() under its shorter name. */ setpointsTooClose: withFields.requestedSetpointsTooClose, };