homebridge-hatch-baby-rest
Version:
Homebridge plugin for Hatch Rest/Restore WiFi sound machines
184 lines (183 loc) • 8.31 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HatchBabyApi = void 0;
const rest_client_1 = require("./rest-client");
const aws_iot_device_sdk_1 = require("aws-iot-device-sdk");
const util_1 = require("../shared/util");
const rest_plus_1 = require("./rest-plus");
const rest_iot_1 = require("./rest-iot");
const rest_mini_1 = require("./rest-mini");
const restore_1 = require("./restore");
const rxjs_1 = require("rxjs");
const iot_device_1 = require("./iot-device");
const operators_1 = require("rxjs/operators");
const knownProducts = [
"restPlus" /* Product.restPlus */,
"riot" /* Product.riot */,
"riotPlus" /* Product.riotPlus */,
"restMini" /* Product.restMini */,
"restore" /* Product.restore */,
"restoreIot" /* Product.restoreIot */,
], ignoredProducts = [
"rest" /* Product.rest */,
"alexa" /* Product.alexa */,
"grow" /* Product.grow */,
"answeredReader" /* Product.answeredReader */,
], iotClientRefreshPeriod = 8 * 60 * 60 * 1000; // refresh client every 8 hours
class HatchBabyApi {
constructor(config) {
this.config = config;
this.restClient = new rest_client_1.RestClient(this.config);
}
getAccount() {
return this.restClient.getAccount();
}
getMember() {
return this.restClient.request({
url: (0, rest_client_1.apiPath)('service/app/v2/member'),
});
}
async getIotDevices(...products) {
// Combine the user's products with the known products to ensure we get all devices
const productFetchQueryString = products
.map((product) => 'iotProducts=' + product)
.join('&'), devices = (await this.restClient.request({
url: (0, rest_client_1.apiPath)('service/app/iotDevice/v2/fetch?' + productFetchQueryString),
})) || [];
return devices;
}
async createAwsIotClient() {
const iotResponse = await this.restClient.request({
url: (0, rest_client_1.apiPath)('service/app/restPlus/token/v1/fetch'),
}), { Credentials: credentials } = await (0, rest_client_1.requestWithRetry)({
url: `https://cognito-identity.${iotResponse.region}.amazonaws.com`,
method: 'POST',
headers: {
'content-type': 'application/x-amz-json-1.1',
'X-Amz-Target': 'AWSCognitoIdentityService.GetCredentialsForIdentity',
},
json: {
IdentityId: iotResponse.identityId,
Logins: {
'cognito-identity.amazonaws.com': iotResponse.token,
},
},
}), mqttClient = new aws_iot_device_sdk_1.thingShadow({
protocol: 'wss',
host: iotResponse.endpoint.replace('https://', ''),
accessKeyId: credentials.AccessKeyId,
secretKey: credentials.SecretKey,
sessionToken: credentials.SessionToken,
});
return mqttClient;
}
async getOnIotClient() {
// eslint-disable-next-line prefer-const
let onIotClient;
const createNewIotClient = async () => {
try {
// eslint-disable-next-line no-use-before-define
const previousMqttClient = onIotClient?.getValue();
if (previousMqttClient) {
try {
previousMqttClient.end();
}
catch (e) {
(0, util_1.logError)('Failed to end previous MQTT Client');
(0, util_1.logError)(e);
}
}
(0, util_1.logDebug)('Creating new MQTT Client');
const mqttClient = await this.createAwsIotClient();
mqttClient.on('error', async (error) => {
if (error.message.includes('(403)')) {
(0, util_1.logError)('MQTT Client No Longer Authorized');
}
else {
(0, util_1.logError)('MQTT Error:');
(0, util_1.logError)(error);
}
try {
// eslint-disable-next-line no-use-before-define
onIotClient?.next(await createNewIotClient());
}
catch (_) {
// ignore, already logged
}
});
(0, util_1.logDebug)('Created new MQTT Client');
return mqttClient;
}
catch (e) {
(0, util_1.logError)('Failed to Create an MQTT Client');
(0, util_1.logError)(e);
throw e;
}
};
onIotClient = new rxjs_1.BehaviorSubject(await createNewIotClient());
onIotClient.pipe((0, operators_1.debounceTime)(iotClientRefreshPeriod)).subscribe(() => {
createNewIotClient()
.then((client) => onIotClient?.next(client))
.catch(util_1.logError);
});
return onIotClient;
}
async getDevices() {
const [devices, member, onIotClient] = await Promise.all([
this.getIotDevices(...knownProducts),
this.getMember(),
this.getOnIotClient(),
]), createDevices = (product, Device) => {
return devices
.filter((device) => device.product === product)
.map((info) => new Device(info, onIotClient, this.restClient));
};
for (const product of member.products) {
if (!knownProducts.includes(product) &&
!ignoredProducts.includes(product)) {
const debugMessage = this.config.debug
? ''
: '. Set `"debug": true` in your config to see more device details.';
(0, util_1.logInfo)('Unsupported Product Found: ' + product + debugMessage);
if (this.config.debug) {
this.getIotDevices(product)
.then(async (unknownDevices) => {
const debugDevices = unknownDevices.map((device) => new iot_device_1.IotDevice(device, onIotClient));
for (const device of debugDevices) {
try {
(0, util_1.logInfo)(`Debug info for ${product} ${device.info.name}:`);
(0, util_1.logInfo)(JSON.stringify({
...device.info,
id: '***',
macAddress: '***',
thingName: '***',
memberId: '***',
email: '***',
}, null, 2));
(0, util_1.logInfo)(`State for ${product} ${device.info.name}:`);
(0, util_1.logInfo)(JSON.stringify(await device.getCurrentState(), null, 2));
}
catch (e) {
(0, util_1.logError)(`Failed to get debug info for ${product} ${device.info.name}`);
(0, util_1.logError)(e);
}
}
})
.catch((error) => {
(0, util_1.logError)('Failed to get debug info for ' + product);
(0, util_1.logError)(error);
});
}
}
}
return {
restPluses: createDevices("restPlus" /* Product.restPlus */, rest_plus_1.RestPlus),
restIots: createDevices("riot" /* Product.riot */, rest_iot_1.RestIot),
restIotPluses: createDevices("riotPlus" /* Product.riotPlus */, rest_iot_1.RestIot),
restMinis: createDevices("restMini" /* Product.restMini */, rest_mini_1.RestMini),
restores: createDevices("restore" /* Product.restore */, restore_1.Restore),
restoreIots: createDevices("restoreIot" /* Product.restoreIot */, rest_iot_1.RestIot),
};
}
}
exports.HatchBabyApi = HatchBabyApi;