eufy-security-client-fork
Version:
Client to comunicate with Eufy-Security devices
962 lines • 92.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EufySecurity = void 0;
const tiny_typed_emitter_1 = require("tiny-typed-emitter");
const ts_log_1 = require("ts-log");
const fse = require("fs-extra");
const path = require("path");
const api_1 = require("./http/api");
const station_1 = require("./http/station");
const types_1 = require("./http/types");
const service_1 = require("./push/service");
const device_1 = require("./http/device");
const types_2 = require("./p2p/types");
const utils_1 = require("./utils");
const error_1 = require("./error");
const _1 = require(".");
const error_2 = require("./http/error");
const types_3 = require("./push/types");
const service_2 = require("./mqtt/service");
class EufySecurity extends tiny_typed_emitter_1.TypedEmitter {
constructor(config, log = ts_log_1.dummyLogger) {
super();
this.houses = {};
this.stations = {};
this.devices = {};
this.P2P_REFRESH_INTERVAL_MIN = 720;
this.cameraMaxLivestreamSeconds = 30;
this.cameraStationLivestreamTimeout = new Map();
this.cameraCloudLivestreamTimeout = new Map();
this.pushCloudRegistered = false;
this.pushCloudChecked = false;
this.persistentData = {
country: "",
openudid: "",
serial_number: "",
push_credentials: undefined,
push_persistentIds: [],
login_hash: "",
version: "",
httpApi: undefined
};
this.connected = false;
this.retries = 0;
this.refreshEufySecurityP2PTimeout = {};
this.deviceSnoozeTimeout = {};
this.config = config;
this.log = log;
}
static async initialize(config, log = ts_log_1.dummyLogger) {
const eufySecurity = new EufySecurity(config, log);
await eufySecurity._initializeInternals();
return eufySecurity;
}
async _initializeInternals() {
if (this.config.country === undefined) {
this.config.country = "US";
}
else {
this.config.country = this.config.country.toUpperCase();
}
if (this.config.language === undefined) {
this.config.language = "en";
}
if (this.config.trustedDeviceName === undefined) {
this.config.trustedDeviceName = "eufyclient";
}
if (this.config.eventDurationSeconds === undefined) {
this.config.eventDurationSeconds = 10;
}
if (this.config.p2pConnectionSetup === undefined) {
this.config.p2pConnectionSetup = types_2.P2PConnectionType.QUICKEST;
}
else if (!Object.values(types_2.P2PConnectionType).includes(this.config.p2pConnectionSetup)) {
this.config.p2pConnectionSetup = types_2.P2PConnectionType.QUICKEST;
}
if (this.config.pollingIntervalMinutes === undefined) {
this.config.pollingIntervalMinutes = 10;
}
if (this.config.acceptInvitations === undefined) {
this.config.acceptInvitations = false;
}
if (this.config.persistentDir === undefined) {
this.config.persistentDir = path.resolve(__dirname, "../../..");
}
else if (!fse.existsSync(this.config.persistentDir)) {
this.config.persistentDir = path.resolve(__dirname, "../../..");
}
this.persistentFile = path.join(this.config.persistentDir, "persistent.json");
try {
if (fse.statSync(this.persistentFile).isFile()) {
const fileContent = fse.readFileSync(this.persistentFile, "utf8");
this.persistentData = JSON.parse(fileContent);
}
}
catch (err) {
this.log.debug("No stored data from last exit found");
}
try {
if (this.persistentData.version !== _1.libVersion) {
const currentVersion = Number.parseFloat((0, utils_1.removeLastChar)(_1.libVersion, "."));
const previousVersion = this.persistentData.version !== "" && this.persistentData.version !== undefined ? Number.parseFloat((0, utils_1.removeLastChar)(this.persistentData.version, ".")) : 0;
this.log.debug("Handling of driver update", { currentVersion: currentVersion, previousVersion: previousVersion });
if (previousVersion < currentVersion) {
this.persistentData = (0, utils_1.handleUpdate)(this.persistentData, this.log, previousVersion);
this.persistentData.version = _1.libVersion;
this.writePersistentData();
}
}
}
catch (error) {
this.log.error("Handling update - Error:", error);
}
this.api = await api_1.HTTPApi.initialize(this.config.country, this.config.username, this.config.password, this.log, this.persistentData.httpApi);
this.api.setLanguage(this.config.language);
this.api.setPhoneModel(this.config.trustedDeviceName);
this.api.on("houses", (houses) => this.handleHouses(houses));
this.api.on("hubs", (hubs) => this.handleHubs(hubs));
this.api.on("devices", (devices) => this.handleDevices(devices));
this.api.on("close", () => this.onAPIClose());
this.api.on("connect", () => this.onAPIConnect());
this.api.on("captcha request", (id, captcha) => this.onCaptchaRequest(id, captcha));
this.api.on("auth token invalidated", () => this.onAuthTokenInvalidated());
this.api.on("tfa request", () => this.onTfaRequest());
if (this.persistentData.login_hash && this.persistentData.login_hash != "") {
this.log.debug("Load previous login_hash:", this.persistentData.login_hash);
if ((0, utils_1.md5)(`${this.config.username}:${this.config.password}`) != this.persistentData.login_hash) {
this.log.info("Authentication properties changed, invalidate saved cloud token.");
this.persistentData.cloud_token = "";
this.persistentData.cloud_token_expiration = 0;
this.persistentData.httpApi = undefined;
}
}
else {
this.persistentData.cloud_token = "";
this.persistentData.cloud_token_expiration = 0;
}
if (this.persistentData.country !== undefined && this.persistentData.country !== "" && this.persistentData.country !== this.config.country) {
this.log.info("Country property changed, invalidate saved cloud token.");
this.persistentData.cloud_token = "";
this.persistentData.cloud_token_expiration = 0;
this.persistentData.httpApi = undefined;
}
if (this.persistentData.cloud_token && this.persistentData.cloud_token != "" && this.persistentData.cloud_token_expiration) {
this.log.debug("Load previous token:", { token: this.persistentData.cloud_token, tokenExpiration: this.persistentData.cloud_token_expiration });
this.api.setToken(this.persistentData.cloud_token);
this.api.setTokenExpiration(new Date(this.persistentData.cloud_token_expiration));
}
if (!this.persistentData.openudid || this.persistentData.openudid == "") {
this.persistentData.openudid = (0, utils_1.generateUDID)();
this.log.debug("Generated new openudid:", this.persistentData.openudid);
}
this.api.setOpenUDID(this.persistentData.openudid);
if (!this.persistentData.serial_number || this.persistentData.serial_number == "") {
this.persistentData.serial_number = (0, utils_1.generateSerialnumber)(12);
this.log.debug("Generated new serial_number:", this.persistentData.serial_number);
}
this.api.setSerialNumber(this.persistentData.serial_number);
this.pushService = new service_1.PushNotificationService(this.log);
this.pushService.on("connect", async (token) => {
this.pushCloudRegistered = await this.api.registerPushToken(token);
this.pushCloudChecked = await this.api.checkPushToken();
//TODO: Retry if failed with max retry to not lock account
if (this.pushCloudRegistered && this.pushCloudChecked) {
this.log.info("Push notification connection successfully established");
this.emit("push connect");
}
else {
this.log.info("Push notification connection closed");
this.emit("push close");
}
});
this.pushService.on("credential", (credentials) => {
this.savePushCredentials(credentials);
});
this.pushService.on("message", (message) => this.onPushMessage(message));
this.pushService.on("close", () => {
this.log.info("Push notification connection closed");
this.emit("push close");
});
await this.initMQTT();
}
async initMQTT() {
this.mqttService = await service_2.MQTTService.init(this.log);
this.mqttService.on("connect", () => {
this.log.info("MQTT connection successfully established");
this.emit("mqtt connect");
});
this.mqttService.on("close", () => {
this.log.info("MQTT connection closed");
this.emit("mqtt close");
});
this.mqttService.on("lock message", (message) => {
this.getDevice(message.data.data.deviceSn).then((device) => {
device.processMQTTNotification(message.data.data, this.config.eventDurationSeconds);
}).catch((error) => {
if (error instanceof error_1.DeviceNotFoundError) {
}
else {
this.log.error("Lock MQTT Message Error", error);
}
}).finally(() => {
this.emit("mqtt lock message", message);
});
});
}
getPushService() {
return this.pushService;
}
addStation(station) {
const serial = station.getSerial();
if (serial && !Object.keys(this.stations).includes(serial)) {
this.stations[serial] = station;
this.emit("station added", station);
}
else {
this.log.debug(`Station with this serial ${station.getSerial()} exists already and couldn't be added again!`);
}
}
removeStation(station) {
const serial = station.getSerial();
if (serial && Object.keys(this.stations).includes(serial)) {
delete this.stations[serial];
station.removeAllListeners();
if (station.isConnected())
station.close();
this.emit("station removed", station);
}
else {
this.log.debug(`Station with this serial ${station.getSerial()} doesn't exists and couldn't be removed!`);
}
}
updateStation(hub) {
if (Object.keys(this.stations).includes(hub.station_sn)) {
this.stations[hub.station_sn].update(hub, this.stations[hub.station_sn] !== undefined && !this.stations[hub.station_sn].isIntegratedDevice() && this.stations[hub.station_sn].isConnected());
if (!this.stations[hub.station_sn].isConnected() && !this.stations[hub.station_sn].isEnergySavingDevice()) {
this.stations[hub.station_sn].setConnectionType(this.config.p2pConnectionSetup);
this.stations[hub.station_sn].connect();
}
}
else {
this.log.debug(`Station with this serial ${hub.station_sn} doesn't exists and couldn't be updated!`);
}
}
addDevice(device) {
const serial = device.getSerial();
if (serial && !Object.keys(this.devices).includes(serial)) {
this.devices[serial] = device;
this.emit("device added", device);
if (device.isLock())
this.mqttService.subscribeLock(device.getSerial());
}
else {
this.log.debug(`Device with this serial ${device.getSerial()} exists already and couldn't be added again!`);
}
}
removeDevice(device) {
const serial = device.getSerial();
if (serial && Object.keys(this.devices).includes(serial)) {
delete this.devices[serial];
device.removeAllListeners();
this.emit("device removed", device);
}
else {
this.log.debug(`Device with this serial ${device.getSerial()} doesn't exists and couldn't be removed!`);
}
}
async updateDevice(device) {
if (this.loadingDevices !== undefined)
await this.loadingDevices;
if (Object.keys(this.devices).includes(device.device_sn))
this.devices[device.device_sn].update(device, this.stations[device.station_sn] !== undefined && !this.stations[device.station_sn].isIntegratedDevice() && this.stations[device.station_sn].isConnected());
else
this.log.debug(`Device with this serial ${device.device_sn} doesn't exists and couldn't be updated!`);
}
async getDevices() {
if (this.loadingDevices !== undefined)
await this.loadingDevices;
const arr = [];
Object.keys(this.devices).forEach((serialNumber) => {
arr.push(this.devices[serialNumber]);
});
return arr;
}
async getDevice(deviceSN) {
if (this.loadingDevices !== undefined)
await this.loadingDevices;
if (Object.keys(this.devices).includes(deviceSN))
return this.devices[deviceSN];
throw new error_1.DeviceNotFoundError(`Device with this serial ${deviceSN} doesn't exists!`);
}
async getStationDevice(stationSN, channel) {
if (this.loadingDevices !== undefined)
await this.loadingDevices;
for (const device of Object.values(this.devices)) {
if ((device.getStationSerial() === stationSN && device.getChannel() === channel) || (device.getStationSerial() === stationSN && device.getSerial() === stationSN)) {
return device;
}
}
throw new error_1.DeviceNotFoundError(`No device with channel ${channel} found on station with serial number: ${stationSN}!`);
}
getStations() {
const arr = [];
Object.keys(this.stations).forEach((serialNumber) => {
arr.push(this.stations[serialNumber]);
});
return arr;
}
getStation(stationSN) {
if (Object.keys(this.stations).includes(stationSN))
return this.stations[stationSN];
throw new error_1.StationNotFoundError(`No station with serial number: ${stationSN}!`);
}
getApi() {
return this.api;
}
async connectToStation(stationSN, p2pConnectionType = types_2.P2PConnectionType.QUICKEST) {
const station = this.getStation(stationSN);
station.setConnectionType(p2pConnectionType);
station.connect();
}
isStationConnected(stationSN) {
const station = this.getStation(stationSN);
return station.isConnected();
}
isStationEnergySavingDevice(stationSN) {
const station = this.getStation(stationSN);
return station.isEnergySavingDevice();
}
handleHouses(houses) {
this.log.debug("Got houses:", houses);
//TODO: Finish implementation
this.houses = houses;
}
handleHubs(hubs) {
this.log.debug("Got hubs:", hubs);
const stationsSNs = Object.keys(this.stations);
const newStationsSNs = Object.keys(hubs);
for (const hub of Object.values(hubs)) {
if (stationsSNs.includes(hub.station_sn)) {
this.updateStation(hub);
}
else {
const station = new station_1.Station(this.api, hub);
station.on("connect", (station) => this.onStationConnect(station));
station.on("connection error", (station, error) => this.onStationConnectionError(station, error));
station.on("close", (station) => this.onStationClose(station));
station.on("raw device property changed", (deviceSN, params) => this.updateDeviceProperties(deviceSN, params));
station.on("livestream start", (station, channel, metadata, videostream, audiostream) => this.onStartStationLivestream(station, channel, metadata, videostream, audiostream));
station.on("livestream stop", (station, channel) => this.onStopStationLivestream(station, channel));
station.on("livestream error", (station, channel, error) => this.onErrorStationLivestream(station, channel, error));
station.on("download start", (station, channel, metadata, videoStream, audioStream) => this.onStationStartDownload(station, channel, metadata, videoStream, audioStream));
station.on("download finish", (station, channel) => this.onStationFinishDownload(station, channel));
station.on("command result", (station, result) => this.onStationCommandResult(station, result));
station.on("guard mode", (station, guardMode) => this.onStationGuardMode(station, guardMode));
station.on("current mode", (station, currentMode) => this.onStationCurrentMode(station, currentMode));
station.on("rtsp livestream start", (station, channel) => this.onStartStationRTSPLivestream(station, channel));
station.on("rtsp livestream stop", (station, channel) => this.onStopStationRTSPLivestream(station, channel));
station.on("rtsp url", (station, channel, value) => this.onStationRtspUrl(station, channel, value));
station.on("property changed", (station, name, value) => this.onStationPropertyChanged(station, name, value));
station.on("raw property changed", (station, type, value) => this.onStationRawPropertyChanged(station, type, value));
station.on("alarm event", (station, alarmEvent) => this.onStationAlarmEvent(station, alarmEvent));
station.on("runtime state", (station, channel, batteryLevel, temperature) => this.onStationRuntimeState(station, channel, batteryLevel, temperature));
station.on("charging state", (station, channel, chargeType, batteryLevel) => this.onStationChargingState(station, channel, chargeType, batteryLevel));
station.on("wifi rssi", (station, channel, rssi) => this.onStationWifiRssi(station, channel, rssi));
station.on("floodlight manual switch", (station, channel, enabled) => this.onFloodlightManualSwitch(station, channel, enabled));
station.on("alarm delay event", (station, alarmDelayEvent, alarmDelay) => this.onStationAlarmDelayEvent(station, alarmDelayEvent, alarmDelay));
station.on("talkback started", (station, channel, talkbackStream) => this.onStationTalkbackStart(station, channel, talkbackStream));
station.on("talkback stopped", (station, channel) => this.onStationTalkbackStop(station, channel));
station.on("talkback error", (station, channel, error) => this.onStationTalkbackError(station, channel, error));
station.on("alarm armed event", (station) => this.onStationAlarmArmedEvent(station));
station.on("alarm arm delay event", (station, armDelay) => this.onStationArmDelayEvent(station, armDelay));
station.on("secondary command result", (station, result) => this.onStationSecondaryCommandResult(station, result));
station.on("device shake alarm", (deviceSN, event) => this.onStationDeviceShakeAlarm(deviceSN, event));
station.on("device 911 alarm", (deviceSN, event) => this.onStationDevice911Alarm(deviceSN, event));
station.on("device jammed", (deviceSN) => this.onStationDeviceJammed(deviceSN));
station.on("device low battery", (deviceSN) => this.onStationDeviceLowBattery(deviceSN));
station.on("device wrong try-protect alarm", (deviceSN) => this.onStationDeviceWrongTryProtectAlarm(deviceSN));
this.addStation(station);
}
}
for (const stationSN of stationsSNs) {
if (!newStationsSNs.includes(stationSN)) {
this.removeStation(this.getStation(stationSN));
}
}
}
onStationConnect(station) {
this.emit("station connect", station);
if ((device_1.Device.isCamera(station.getDeviceType()) && !device_1.Device.isWiredDoorbell(station.getDeviceType()) || device_1.Device.isSmartSafe(station.getDeviceType()))) {
station.getCameraInfo().catch(error => {
this.log.error(`Error during station ${station.getSerial()} p2p data refreshing`, error);
});
if (this.refreshEufySecurityP2PTimeout[station.getSerial()] !== undefined) {
clearTimeout(this.refreshEufySecurityP2PTimeout[station.getSerial()]);
delete this.refreshEufySecurityP2PTimeout[station.getSerial()];
}
if (!station.isEnergySavingDevice()) {
this.refreshEufySecurityP2PTimeout[station.getSerial()] = setTimeout(() => {
station.getCameraInfo().catch(error => {
this.log.error(`Error during station ${station.getSerial()} p2p data refreshing`, error);
});
}, this.P2P_REFRESH_INTERVAL_MIN * 60 * 1000);
}
}
}
onStationConnectionError(station, error) {
this.emit("station connection error", station, error);
}
onStationClose(station) {
this.emit("station close", station);
for (const device_sn of this.cameraStationLivestreamTimeout.keys()) {
this.getDevice(device_sn).then((device) => {
if (device !== null && device.getStationSerial() === station.getSerial()) {
clearTimeout(this.cameraStationLivestreamTimeout.get(device_sn));
this.cameraStationLivestreamTimeout.delete(device_sn);
}
}).catch((error) => {
this.log.error(`Station ${station.getSerial()} - Error:`, error);
});
}
}
handleDevices(devices) {
this.log.debug("Got devices:", devices);
const deviceSNs = Object.keys(this.devices);
const newDeviceSNs = Object.keys(devices);
const promises = [];
for (const device of Object.values(devices)) {
if (deviceSNs.includes(device.device_sn)) {
this.updateDevice(device);
}
else {
let new_device;
if (device_1.Device.isIndoorCamera(device.device_type)) {
new_device = device_1.IndoorCamera.initialize(this.api, device);
}
else if (device_1.Device.isSoloCameras(device.device_type)) {
new_device = device_1.SoloCamera.initialize(this.api, device);
}
else if (device_1.Device.isBatteryDoorbell(device.device_type)) {
new_device = device_1.BatteryDoorbellCamera.initialize(this.api, device);
}
else if (device_1.Device.isWiredDoorbell(device.device_type) || device_1.Device.isWiredDoorbellDual(device.device_type)) {
new_device = device_1.WiredDoorbellCamera.initialize(this.api, device);
}
else if (device_1.Device.isFloodLight(device.device_type)) {
new_device = device_1.FloodlightCamera.initialize(this.api, device);
}
else if (device_1.Device.isCamera(device.device_type)) {
new_device = device_1.Camera.initialize(this.api, device);
}
else if (device_1.Device.isLock(device.device_type)) {
new_device = device_1.Lock.initialize(this.api, device);
}
else if (device_1.Device.isMotionSensor(device.device_type)) {
new_device = device_1.MotionSensor.initialize(this.api, device);
}
else if (device_1.Device.isEntrySensor(device.device_type)) {
new_device = device_1.EntrySensor.initialize(this.api, device);
}
else if (device_1.Device.isKeyPad(device.device_type)) {
new_device = device_1.Keypad.initialize(this.api, device);
}
else if (device_1.Device.isSmartSafe(device.device_type)) {
new_device = device_1.SmartSafe.initialize(this.api, device);
}
else {
new_device = device_1.UnknownDevice.initialize(this.api, device);
}
promises.push(new_device.then((device) => {
try {
device.on("property changed", (device, name, value) => this.onDevicePropertyChanged(device, name, value));
device.on("raw property changed", (device, type, value) => this.onDeviceRawPropertyChanged(device, type, value));
device.on("crying detected", (device, state) => this.onDeviceCryingDetected(device, state));
device.on("sound detected", (device, state) => this.onDeviceSoundDetected(device, state));
device.on("pet detected", (device, state) => this.onDevicePetDetected(device, state));
device.on("motion detected", (device, state) => this.onDeviceMotionDetected(device, state));
device.on("person detected", (device, state, person) => this.onDevicePersonDetected(device, state, person));
device.on("rings", (device, state) => this.onDeviceRings(device, state));
device.on("locked", (device, state) => this.onDeviceLocked(device, state));
device.on("open", (device, state) => this.onDeviceOpen(device, state));
device.on("ready", (device) => this.onDeviceReady(device));
device.on("package delivered", (device, state) => this.onDevicePackageDelivered(device, state));
device.on("package stranded", (device, state) => this.onDevicePackageStranded(device, state));
device.on("package taken", (device, state) => this.onDevicePackageTaken(device, state));
device.on("someone loitering", (device, state) => this.onDeviceSomeoneLoitering(device, state));
device.on("radar motion detected", (device, state) => this.onDeviceRadarMotionDetected(device, state));
device.on("911 alarm", (device, state, detail) => this.onDevice911Alarm(device, state, detail));
device.on("shake alarm", (device, state, detail) => this.onDeviceShakeAlarm(device, state, detail));
device.on("wrong try-protect alarm", (device, state) => this.onDeviceWrongTryProtectAlarm(device, state));
device.on("long time not close", (device, state) => this.onDeviceLongTimeNotClose(device, state));
device.on("low battery", (device, state) => this.onDeviceLowBattery(device, state));
device.on("jammed", (device, state) => this.onDeviceJammed(device, state));
this.addDevice(device);
}
catch (error) {
this.log.error("Error", error);
}
return device;
}));
}
}
this.loadingDevices = Promise.all(promises).then((devices) => {
devices.forEach((device) => {
const station = this.getStation(device.getStationSerial());
if (!station.isConnected()) {
station.setConnectionType(this.config.p2pConnectionSetup);
station.connect();
}
});
this.loadingDevices = undefined;
});
for (const deviceSN of deviceSNs) {
if (!newDeviceSNs.includes(deviceSN)) {
this.getDevice(deviceSN).then((device) => {
this.removeDevice(device);
}).catch((error) => {
this.log.error("Error removing device", error);
});
}
}
}
async refreshCloudData() {
if (this.config.acceptInvitations) {
await this.processInvitations().catch(error => {
this.log.error("Error in processing invitations", error);
});
}
await this.api.refreshAllData().catch(error => {
this.log.error("Error during API data refreshing", error);
});
if (this.refreshEufySecurityCloudTimeout !== undefined)
clearTimeout(this.refreshEufySecurityCloudTimeout);
this.refreshEufySecurityCloudTimeout = setTimeout(() => { this.refreshCloudData(); }, this.config.pollingIntervalMinutes * 60 * 1000);
}
close() {
for (const device_sn of this.cameraStationLivestreamTimeout.keys()) {
this.stopStationLivestream(device_sn);
}
for (const device_sn of this.cameraCloudLivestreamTimeout.keys()) {
this.stopCloudLivestream(device_sn);
}
if (this.refreshEufySecurityCloudTimeout !== undefined)
clearTimeout(this.refreshEufySecurityCloudTimeout);
Object.keys(this.refreshEufySecurityP2PTimeout).forEach(station_sn => {
clearTimeout(this.refreshEufySecurityP2PTimeout[station_sn]);
delete this.refreshEufySecurityP2PTimeout[station_sn];
});
Object.keys(this.deviceSnoozeTimeout).forEach(device_sn => {
clearTimeout(this.deviceSnoozeTimeout[device_sn]);
delete this.deviceSnoozeTimeout[device_sn];
});
this.savePushPersistentIds();
this.pushService.close();
this.mqttService.close();
Object.values(this.stations).forEach(station => {
station.close();
});
Object.values(this.devices).forEach(device => {
device.destroy();
});
if (this.connected)
this.emit("close");
this.connected = false;
}
setCameraMaxLivestreamDuration(seconds) {
this.cameraMaxLivestreamSeconds = seconds;
}
getCameraMaxLivestreamDuration() {
return this.cameraMaxLivestreamSeconds;
}
async registerPushNotifications(credentials, persistentIds) {
if (credentials)
this.pushService.setCredentials(credentials);
if (persistentIds)
this.pushService.setPersistentIds(persistentIds);
this.pushService.open();
}
async connect(options) {
await this.api.login(options)
.then(async () => {
if (options === null || options === void 0 ? void 0 : options.verifyCode) {
let trusted = false;
const trusted_devices = await this.api.listTrustDevice();
trusted_devices.forEach(trusted_device => {
if (trusted_device.is_current_device === 1) {
trusted = true;
}
});
if (!trusted)
return await this.api.addTrustDevice(options === null || options === void 0 ? void 0 : options.verifyCode);
}
})
.catch((error) => {
this.log.error("Connect Error", error);
});
}
getPushPersistentIds() {
return this.pushService.getPersistentIds();
}
updateDeviceProperties(deviceSN, values) {
this.getDevice(deviceSN).then((device) => {
device.updateRawProperties(values);
}).catch((error) => {
this.log.error(`Update device ${deviceSN} properties error`, error);
});
}
async onAPIClose() {
if (this.refreshEufySecurityCloudTimeout !== undefined)
clearTimeout(this.refreshEufySecurityCloudTimeout);
this.connected = false;
this.emit("close");
if (this.retries < 1) {
this.retries++;
await this.connect();
}
else {
this.log.error(`Tried to re-authenticate to Eufy cloud, but failed in the process. Manual intervention is required!`);
}
}
async onAPIConnect() {
this.connected = true;
this.retries = 0;
this.saveCloudToken();
await this.refreshCloudData();
this.emit("connect");
this.registerPushNotifications(this.persistentData.push_credentials, this.persistentData.push_persistentIds);
const loginData = this.api.getPersistentData();
if (loginData) {
this.mqttService.connect(loginData.user_id, this.persistentData.openudid, this.api.getAPIBase(), loginData.email);
}
else {
this.log.warn("No login data recevied to initialize MQTT connection...");
}
}
async startStationLivestream(deviceSN) {
const device = await this.getDevice(deviceSN);
const station = this.getStation(device.getStationSerial());
if (!device.hasCommand(types_1.CommandName.DeviceStartLivestream))
throw new error_1.NotSupportedError(`This functionality is not implemented or supported by ${device.getSerial()}`);
const camera = device;
if (!station.isLiveStreaming(camera)) {
station.startLivestream(camera);
this.cameraStationLivestreamTimeout.set(deviceSN, setTimeout(() => {
this.log.info(`Stopping the station stream for the device ${deviceSN}, because we have reached the configured maximum stream timeout (${this.cameraMaxLivestreamSeconds} seconds)`);
this.stopStationLivestream(deviceSN);
}, this.cameraMaxLivestreamSeconds * 1000));
}
else {
this.log.warn(`The station stream for the device ${deviceSN} cannot be started, because it is already streaming!`);
}
}
async startCloudLivestream(deviceSN) {
const device = await this.getDevice(deviceSN);
const station = this.getStation(device.getStationSerial());
if (!device.hasCommand(types_1.CommandName.DeviceStartLivestream))
throw new error_1.NotSupportedError(`This functionality is not implemented or supported by ${device.getSerial()}`);
const camera = device;
if (!camera.isStreaming()) {
const url = await camera.startStream();
if (url !== "") {
this.cameraCloudLivestreamTimeout.set(deviceSN, setTimeout(() => {
this.log.info(`Stopping the station stream for the device ${deviceSN}, because we have reached the configured maximum stream timeout (${this.cameraMaxLivestreamSeconds} seconds)`);
this.stopCloudLivestream(deviceSN);
}, this.cameraMaxLivestreamSeconds * 1000));
this.emit("cloud livestream start", station, camera, url);
}
else {
this.log.error(`Failed to start cloud stream for the device ${deviceSN}`);
}
}
else {
this.log.warn(`The cloud stream for the device ${deviceSN} cannot be started, because it is already streaming!`);
}
}
async stopStationLivestream(deviceSN) {
const device = await this.getDevice(deviceSN);
const station = this.getStation(device.getStationSerial());
if (!device.hasCommand(types_1.CommandName.DeviceStopLivestream))
throw new error_1.NotSupportedError(`This functionality is not implemented or supported by ${device.getSerial()}`);
if (station.isConnected() && station.isLiveStreaming(device)) {
await station.stopLivestream(device);
}
else {
this.log.warn(`The station stream for the device ${deviceSN} cannot be stopped, because it isn't streaming!`);
}
const timeout = this.cameraStationLivestreamTimeout.get(deviceSN);
if (timeout) {
clearTimeout(timeout);
this.cameraStationLivestreamTimeout.delete(deviceSN);
}
}
async stopCloudLivestream(deviceSN) {
const device = await this.getDevice(deviceSN);
const station = this.getStation(device.getStationSerial());
if (!device.hasCommand(types_1.CommandName.DeviceStopLivestream))
throw new error_1.NotSupportedError(`This functionality is not implemented or supported by ${device.getSerial()}`);
const camera = device;
if (camera.isStreaming()) {
await camera.stopStream();
this.emit("cloud livestream stop", station, camera);
}
else {
this.log.warn(`The cloud stream for the device ${deviceSN} cannot be stopped, because it isn't streaming!`);
}
const timeout = this.cameraCloudLivestreamTimeout.get(deviceSN);
if (timeout) {
clearTimeout(timeout);
this.cameraCloudLivestreamTimeout.delete(deviceSN);
}
}
writePersistentData() {
var _a, _b;
this.persistentData.login_hash = (0, utils_1.md5)(`${this.config.username}:${this.config.password}`);
this.persistentData.httpApi = (_a = this.api) === null || _a === void 0 ? void 0 : _a.getPersistentData();
this.persistentData.country = (_b = this.api) === null || _b === void 0 ? void 0 : _b.getCountry();
try {
fse.writeFileSync(this.persistentFile, JSON.stringify(this.persistentData));
}
catch (error) {
this.log.error("Error:", error);
}
}
saveCloudToken() {
const token = this.api.getToken();
const token_expiration = this.api.getTokenExpiration();
if (!!token && !!token_expiration) {
this.log.debug("Save cloud token and token expiration", { token: token, tokenExpiration: token_expiration });
this.persistentData.cloud_token = token;
this.persistentData.cloud_token_expiration = token_expiration.getTime();
this.writePersistentData();
}
}
savePushCredentials(credentials) {
this.persistentData.push_credentials = credentials;
this.writePersistentData();
}
savePushPersistentIds() {
this.persistentData.push_persistentIds = this.getPushPersistentIds();
this.writePersistentData();
}
getVersion() {
return _1.libVersion;
}
isPushConnected() {
return this.pushService.isConnected();
}
isMQTTConnected() {
return this.mqttService.isConnected();
}
isConnected() {
return this.connected;
}
async processInvitations() {
let refreshCloud = false;
const invites = await this.api.getInvites().catch(error => {
this.log.error("processInvitations - getInvites - Error:", error);
return error;
});
if (Object.keys(invites).length > 0) {
const confirmInvites = [];
for (const invite of Object.values(invites)) {
const devices = [];
invite.devices.forEach(device => {
devices.push(device.device_sn);
});
if (devices.length > 0) {
confirmInvites.push({
invite_id: invite.invite_id,
station_sn: invite.station_sn,
device_sns: devices
});
}
}
if (confirmInvites.length > 0) {
const result = await this.api.confirmInvites(confirmInvites).catch(error => {
this.log.error("processInvitations - confirmInvites - Error:", error);
return error;
});
if (result) {
this.log.info(`Accepted received invitations`, confirmInvites);
refreshCloud = true;
}
}
}
const houseInvites = await this.api.getHouseInviteList().catch(error => {
this.log.error("processInvitations - getHouseInviteList - Error:", error);
return error;
});
if (Object.keys(houseInvites).length > 0) {
for (const invite of Object.values(houseInvites)) {
const result = await this.api.confirmHouseInvite(invite.house_id, invite.id).catch(error => {
this.log.error("processInvitations - confirmHouseInvite - Error:", error);
return error;
});
if (result) {
this.log.info(`Accepted received house invitation from ${invite.action_user_email}`, invite);
refreshCloud = true;
}
}
}
if (refreshCloud)
this.refreshCloudData();
}
onPushMessage(message) {
this.emit("push message", message);
try {
this.log.debug("Received push message", message);
try {
if ((message.type === types_3.ServerPushEvent.INVITE_DEVICE || message.type === types_3.ServerPushEvent.HOUSE_INVITE) && this.config.acceptInvitations) {
if (this.isConnected())
this.processInvitations();
}
}
catch (error) {
this.log.error(`Error processing server push notification for device invitation`, error);
}
try {
if (message.type === types_3.ServerPushEvent.REMOVE_DEVICE || message.type === types_3.ServerPushEvent.REMOVE_HOMEBASE || message.type === types_3.ServerPushEvent.HOUSE_REMOVE) {
if (this.isConnected())
this.refreshCloudData();
}
}
catch (error) {
this.log.error(`Error processing server push notification for device/station/house removal`, error);
}
this.getStations().forEach(station => {
try {
station.processPushNotification(message);
}
catch (error) {
this.log.error(`Error processing push notification for station ${station.getSerial()}`, error);
}
});
this.getDevices().then((devices) => {
devices.forEach(device => {
try {
device.processPushNotification(message, this.config.eventDurationSeconds);
}
catch (error) {
this.log.error(`Error processing push notification for device ${device.getSerial()}`, error);
}
});
}).catch((error) => {
this.log.error("Process push notification for devices", error);
});
}
catch (error) {
this.log.error("Generic Error:", error);
}
}
async startStationDownload(deviceSN, path, cipherID) {
const device = await this.getDevice(deviceSN);
const station = this.getStation(device.getStationSerial());
if (!device.isCamera())
throw new error_1.NotSupportedError(`This functionality is not implemented or supported by ${device.getSerial()}`);
if (!station.isDownloading(device)) {
await station.startDownload(device, path, cipherID);
}
else {
this.log.warn(`The station is already downloading a video for the device ${deviceSN}!`);
}
}
async cancelStationDownload(deviceSN) {
const device = await this.getDevice(deviceSN);
const station = this.getStation(device.getStationSerial());
if (!device.isCamera())
throw new error_1.NotSupportedError(`This functionality is not implemented or supported by ${device.getSerial()}`);
if (station.isConnected() && station.isDownloading(device)) {
await station.cancelDownload(device);
}
else {
this.log.warn(`The station isn't downloading a video for the device ${deviceSN}!`);
}
}
getConfig() {
return this.config;
}
async setDeviceProperty(deviceSN, name, value) {
const device = await this.getDevice(deviceSN);
const station = this.getStation(device.getStationSerial());
const metadata = device.getPropertyMetadata(name);
value = (0, utils_1.parseValue)(metadata, value);
switch (name) {
case types_1.PropertyName.DeviceEnabled:
await station.enableDevice(device, value);
break;
case types_1.PropertyName.DeviceStatusLed:
await station.setStatusLed(device, value);
break;
case types_1.PropertyName.DeviceAutoNightvision:
await station.setAutoNightVision(device, value);
break;
case types_1.PropertyName.DeviceMotionDetection:
await station.setMotionDetection(device, value);
break;
case types_1.PropertyName.DeviceSoundDetection:
await station.setSoundDetection(device, value);
break;
case types_1.PropertyName.DevicePetDetection:
await station.setPetDetection(device, value);
break;
case types_1.PropertyName.DeviceRTSPStream:
await station.setRTSPStream(device, value);
break;
case types_1.PropertyName.DeviceAntitheftDetection:
await station.setAntiTheftDetection(device, value);
break;
case types_1.PropertyName.DeviceLocked:
await station.lockDevice(device, value);
break;
case types_1.PropertyName.DeviceWatermark:
await station.setWatermark(device, value);
break;
case types_1.PropertyName.DeviceLight:
await station.switchLight(device, value);
break;
case types_1.PropertyName.DeviceLightSettingsEnable:
await station.setFloodlightLightSettingsEnable(device, value);
break;
case types_1.PropertyName.DeviceLightSettingsBrightnessManual:
await station.setFloodlightLightSettingsBrightnessManual(device, value);
break;
case types_1.PropertyName.DeviceLightSettingsBrightnessMotion:
await station.setFloodlightLightSettingsBrightnessMotion(device, value);
break;
case types_1.PropertyName.DeviceLightSettingsBrightnessSchedule:
await station.setFloodlightLightSettingsBrightnessSchedule(device, value);
break;
case types_1.PropertyName.DeviceLightSettingsMotionTriggered:
await station.setFloodlightLightSettingsMotionTriggered(device, value);
break;
case types_1.PropertyName.DeviceLightSettingsMotionTriggeredDistance:
await station.setFloodlightLightSettingsMotionTriggeredDistance(device, value);
break;
case types_1.PropertyName.DeviceLightSettingsMotionTriggeredTimer:
await station.setFloodlightLightSettingsMotionTriggeredTimer(device, value);
break;
case types_1.PropertyName.DeviceMicrophone:
await station.setMicMute(device, value);
break;
case types_1.PropertyName.DeviceSpeaker:
await station.enableSpeaker(device, value);
break;
case types_1.PropertyName.DeviceSpeakerVolume:
await station.setSpeakerVolume(device, value);
break;
case types_1.PropertyName.DeviceAudioRecording:
await station.setAudioRecording(device, value);
break;
case types_1.PropertyName.DevicePowerSource:
await station.setPowerSource(device, value);
break;
case types_1.PropertyName.DevicePowerWorkingMode:
await station.setPowerWorkingMode(device, value);
break;
case types_1.PropertyName.DeviceRecordingEndClipMotionStops:
await station.setRecordingEndClipMotionStops(device, value);
break;
case types_1.PropertyName.DeviceRecordingClipLength:
await station.setRecordingClipLength(device, value);
break;
case types_1.PropertyName.DeviceRecordingRe