UNPKG

eufy-security-client-fork

Version:

Client to comunicate with Eufy-Security devices

962 lines 92.3 kB
"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