UNPKG

eufy-security-ws

Version:

Node WebSocket server implementation to integrate Eufy Security devices

713 lines 33.2 kB
import { DownloadAlreadyRunningError, DownloadNotRunningError, DownloadOnlyOneAtATimeError, LivestreamAlreadyRunningError, LivestreamNotRunningError, TalkbackAlreadyRunningError, TalkbackNotRunningError, TalkbackOnlyOneAtATimeError, UnknownCommandError, } from "../error.js"; import { convertCamelCaseToSnakeCase } from "../utils.js"; import { DeviceCommand } from "./command.js"; import { DeviceEvent } from "./event.js"; import { dumpDeviceProperties, dumpDevicePropertiesMetadata, } from "./properties.js"; export class DeviceMessageHandler { static streamingDevices = {}; static downloadingDevices = {}; static talkbackingDevices = {}; static talkbackStream; static getStreamingDevices(stationSN) { if (DeviceMessageHandler.streamingDevices[stationSN] !== undefined) return DeviceMessageHandler.streamingDevices[stationSN]; return []; } static removeStreamingDevice(stationSN, client) { if (DeviceMessageHandler.streamingDevices[stationSN] !== undefined) DeviceMessageHandler.streamingDevices[stationSN] = DeviceMessageHandler.streamingDevices[stationSN].filter((cl) => cl !== client); } static addStreamingDevice(stationSN, client) { if (DeviceMessageHandler.streamingDevices[stationSN] !== undefined && !DeviceMessageHandler.streamingDevices[stationSN].includes(client)) DeviceMessageHandler.streamingDevices[stationSN].push(client); else if (DeviceMessageHandler.streamingDevices[stationSN] === undefined) DeviceMessageHandler.streamingDevices[stationSN] = [client]; } static getDownloadingDevices(stationSN) { if (DeviceMessageHandler.downloadingDevices[stationSN] !== undefined) return DeviceMessageHandler.downloadingDevices[stationSN]; return []; } static removeDownloadingDevice(stationSN, client) { if (DeviceMessageHandler.downloadingDevices[stationSN] !== undefined) DeviceMessageHandler.downloadingDevices[stationSN] = DeviceMessageHandler.downloadingDevices[stationSN].filter((cl) => cl !== client); } static addDownloadingDevice(stationSN, client) { if (DeviceMessageHandler.downloadingDevices[stationSN] !== undefined && !DeviceMessageHandler.downloadingDevices[stationSN].includes(client)) DeviceMessageHandler.downloadingDevices[stationSN].push(client); else if (DeviceMessageHandler.downloadingDevices[stationSN] === undefined) DeviceMessageHandler.downloadingDevices[stationSN] = [client]; } static getTalkbackingDevices(stationSN) { if (DeviceMessageHandler.talkbackingDevices[stationSN] !== undefined) return DeviceMessageHandler.talkbackingDevices[stationSN]; return []; } static removeTalkbackingDevice(stationSN, client) { if (DeviceMessageHandler.talkbackingDevices[stationSN] !== undefined) DeviceMessageHandler.talkbackingDevices[stationSN] = DeviceMessageHandler.talkbackingDevices[stationSN].filter((cl) => cl !== client); } static addTalkbackingDevice(stationSN, client) { if (DeviceMessageHandler.talkbackingDevices[stationSN] !== undefined && !DeviceMessageHandler.talkbackingDevices[stationSN].includes(client)) DeviceMessageHandler.talkbackingDevices[stationSN].push(client); else if (DeviceMessageHandler.talkbackingDevices[stationSN] === undefined) DeviceMessageHandler.talkbackingDevices[stationSN] = [client]; } static async handle(message, driver, client) { const { serialNumber, command } = message; const device = await driver.getDevice(serialNumber); const station = await driver.getStation(device.getStationSerial()); switch (command) { case DeviceCommand.setStatusLed: if (client.schemaVersion <= 12) { station.setStatusLed(device, message.value); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.setAutoNightVision: if (client.schemaVersion <= 12) { station.setAutoNightVision(device, message.value); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.setMotionDetection: if (client.schemaVersion <= 12) { station.setMotionDetection(device, message.value); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.setSoundDetection: if (client.schemaVersion <= 12) { station.setSoundDetection(device, message.value); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.setPetDetection: if (client.schemaVersion <= 12) { station.setPetDetection(device, message.value); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.setRTSPStream: if (client.schemaVersion <= 12) { station.setRTSPStream(device, message.value); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.setAntiTheftDetection: if (client.schemaVersion <= 12) { station.setAntiTheftDetection(device, message.value); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.setWatermark: if (client.schemaVersion <= 12) { station.setWatermark(device, message.value); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.enableDevice: if (client.schemaVersion <= 12) { station.enableDevice(device, message.value); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.lockDevice: if (client.schemaVersion <= 12) { station.lockDevice(device, message.value); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.getPropertiesMetadata: { const properties = device.getPropertiesMetadata(); if (client.schemaVersion <= 3) { return { properties: properties }; } else if (client.schemaVersion >= 4 && client.schemaVersion <= 12) { return { serialNumber: device.getSerial(), properties: properties, }; } else { return { serialNumber: device.getSerial(), properties: dumpDevicePropertiesMetadata(device, client.schemaVersion), }; } } case DeviceCommand.getProperties: { const properties = device.getProperties(); if (client.schemaVersion <= 3) { return { properties: properties }; } else if (client.schemaVersion >= 4 && client.schemaVersion <= 12) { return { serialNumber: device.getSerial(), properties: properties, }; } else { return { serialNumber: device.getSerial(), properties: dumpDeviceProperties(device, client.schemaVersion), }; } } case DeviceCommand.setProperty: await driver .setDeviceProperty(serialNumber, message.name, message.value) .catch((error) => { throw error; }); return client.schemaVersion >= 13 ? { async: true } : {}; case DeviceCommand.startLivestream: if (client.schemaVersion >= 2) { if (!station.isLiveStreaming(device)) { station.startLivestream(device); client.receiveLivestream[serialNumber] = true; DeviceMessageHandler.addStreamingDevice(station.getSerial(), client); } else if (client.receiveLivestream[serialNumber] !== true) { //TODO: Cache last received I-Frame and send that with the rest to the new client client.sendEvent({ source: "device", event: DeviceEvent.livestreamStarted, serialNumber: serialNumber, }); client.receiveLivestream[serialNumber] = true; DeviceMessageHandler.addStreamingDevice(station.getSerial(), client); } else { throw new LivestreamAlreadyRunningError(`Livestream for device ${serialNumber} is already running`); } return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.stopLivestream: if (client.schemaVersion >= 2) { if (!station.isLiveStreaming(device)) { throw new LivestreamNotRunningError(`Livestream for device ${serialNumber} could not be stopped, because it is not running`); } if (client.receiveLivestream[serialNumber] !== true) { throw new LivestreamNotRunningError(`This client has not requested the start of the live stream for the device ${serialNumber} and therefore cannot request its termination`); } if (DeviceMessageHandler.streamingDevices[station.getSerial()] !== undefined && DeviceMessageHandler.streamingDevices[station.getSerial()].includes(client)) { if (DeviceMessageHandler.streamingDevices[station.getSerial()] .length === 1) { DeviceMessageHandler.removeStreamingDevice(station.getSerial(), client); station.stopLivestream(device); } else { client.receiveLivestream[serialNumber] = false; DeviceMessageHandler.removeStreamingDevice(station.getSerial(), client); client.sendEvent({ source: "device", event: DeviceEvent.livestreamStopped, serialNumber: serialNumber, }); } } return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.isLiveStreaming: { if (client.schemaVersion >= 2) { const result = station.isLiveStreaming(device); if (client.schemaVersion >= 2 && client.schemaVersion <= 3) { return { livestreaming: result }; } else if (client.schemaVersion >= 4) { return { serialNumber: device.getSerial(), livestreaming: result, }; } } else { throw new UnknownCommandError(command); } } case DeviceCommand.triggerAlarm: if (client.schemaVersion >= 3) { station.triggerDeviceAlarmSound(device, message.seconds); return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.resetAlarm: if (client.schemaVersion >= 3) { station.resetDeviceAlarmSound(device); return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.panAndTilt: if (client.schemaVersion >= 3) { station.panAndTilt(device, message.direction); return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.quickResponse: if (client.schemaVersion >= 3) { station.quickResponse(device, message.voiceId); return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.startDownload: if (client.schemaVersion >= 3) { if (!station.isDownloading(device)) { await station .startDownload(device, message.path, message.cipherId) .catch((error) => { throw error; }); client.receiveDownloadStream[serialNumber] = true; DeviceMessageHandler.addDownloadingDevice(station.getSerial(), client); } else if (client.receiveDownloadStream[serialNumber] !== true) { throw new DownloadOnlyOneAtATimeError(`Download for device ${serialNumber} is already running from another client`); } else { throw new DownloadAlreadyRunningError(`Download for device ${serialNumber} is already running`); } return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.cancelDownload: if (client.schemaVersion >= 3) { if (!station.isDownloading(device)) { throw new DownloadNotRunningError(`Download for device ${serialNumber} could not be cancelled, because it is not running`); } if (client.receiveDownloadStream[serialNumber] !== true) { throw new DownloadNotRunningError(`This client has not requested the start of the download for the device ${serialNumber} and therefore cannot request its cancellation`); } if (DeviceMessageHandler.downloadingDevices[station.getSerial()] !== undefined && DeviceMessageHandler.downloadingDevices[station.getSerial()].includes(client)) { if (DeviceMessageHandler.downloadingDevices[station.getSerial()] .length === 1) { DeviceMessageHandler.removeDownloadingDevice(station.getSerial(), client); station.cancelDownload(device); } } return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.isDownloading: { if (client.schemaVersion >= 13) { const result = station.isDownloading(device); return { serialNumber: device.getSerial(), downloading: result, }; } else { throw new UnknownCommandError(command); } } case DeviceCommand.getVoices: if (client.schemaVersion >= 3) { const voices = await driver .getApi() .getVoices(device.getSerial()) .catch((error) => { throw error; }); if (client.schemaVersion === 3) { return { serialNumber: device.getSerial(), voices: voices, }; } else if (client.schemaVersion >= 4) { return { serialNumber: device.getSerial(), voices: voices, }; } } else { throw new UnknownCommandError(command); } case DeviceCommand.hasProperty: { if (client.schemaVersion >= 3) { const result = device.hasProperty(message.propertyName); if (client.schemaVersion === 3) { return { exists: result }; } else if (client.schemaVersion >= 4) { return { serialNumber: device.getSerial(), exists: result, }; } } else { throw new UnknownCommandError(command); } } case DeviceCommand.hasCommand: { if (client.schemaVersion >= 3) { const result = device.hasCommand(message.commandName); if (client.schemaVersion === 3) { return { exists: result }; } else if (client.schemaVersion >= 4) { return { serialNumber: device.getSerial(), exists: result, }; } } else { throw new UnknownCommandError(command); } } case DeviceCommand.getCommands: { if (client.schemaVersion >= 3) { const result = device.getCommands(); const commands = []; result.forEach((command) => { commands.push(convertCamelCaseToSnakeCase(command.replace("device", ""))); }); if (client.schemaVersion === 3) { return { commands: result }; } else if (client.schemaVersion >= 4) { return { serialNumber: device.getSerial(), commands: commands, //TODO: Convert to correct type }; } } else { throw new UnknownCommandError(command); } } case DeviceCommand.startRTSPLivestream: if (client.schemaVersion >= 6) { station.startRTSPStream(device); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.stopRTSPLivestream: if (client.schemaVersion >= 6) { station.stopRTSPStream(device); return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.isRTSPLiveStreaming: { if (client.schemaVersion >= 6) { const result = station.isRTSPLiveStreaming(device); return { serialNumber: device.getSerial(), livestreaming: result, }; } else { throw new UnknownCommandError(command); } } case DeviceCommand.calibrateLock: if (client.schemaVersion >= 9) { station.calibrateLock(device); return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.calibrate: if (client.schemaVersion >= 10) { station.calibrate(device); return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.setDefaultAngle: if (client.schemaVersion >= 10) { station.setDefaultAngle(device); return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.setPrivacyAngle: if (client.schemaVersion >= 10) { station.setPrivacyAngle(device); return client.schemaVersion >= 13 ? { async: true } : {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.unlock: if (client.schemaVersion >= 13) { station.unlock(device); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.startTalkback: if (client.schemaVersion >= 13) { if (!station.isTalkbackOngoing(device)) { station.startTalkback(device); client.sendTalkbackStream[serialNumber] = true; DeviceMessageHandler.addTalkbackingDevice(station.getSerial(), client); } else if (client.sendTalkbackStream[serialNumber] !== true) { throw new TalkbackOnlyOneAtATimeError(`Talkback for device ${serialNumber} is already running from another client`); } else { throw new TalkbackAlreadyRunningError(`Talkback for device ${serialNumber} is already running`); } return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.stopTalkback: if (client.schemaVersion >= 13) { if (!station.isTalkbackOngoing(device)) { throw new TalkbackNotRunningError(`Talkback for device ${serialNumber} could not be cancelled, because it is not running`); } if (client.sendTalkbackStream[serialNumber] !== true) { throw new TalkbackNotRunningError(`This client has not requested the start of the talkback for the device ${serialNumber} and therefore cannot request its termination`); } if (DeviceMessageHandler.talkbackingDevices[station.getSerial()] !== undefined && DeviceMessageHandler.talkbackingDevices[station.getSerial()].includes(client)) { if (DeviceMessageHandler.talkbackingDevices[station.getSerial()] .length === 1) { DeviceMessageHandler.talkbackStream?.end(); DeviceMessageHandler.talkbackStream = undefined; DeviceMessageHandler.removeTalkbackingDevice(station.getSerial(), client); station.stopTalkback(device); } } return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.isTalkbackOngoing: { if (client.schemaVersion >= 13) { const result = station.isTalkbackOngoing(device); return { serialNumber: device.getSerial(), talkbackOngoing: result, }; } else { throw new UnknownCommandError(command); } } case DeviceCommand.talkbackAudioData: if (client.schemaVersion >= 13) { if (!station.isTalkbackOngoing(device)) { throw new TalkbackNotRunningError(`Talkback for device ${serialNumber} could not be cancelled, because it is not running`); } if (client.sendTalkbackStream[serialNumber] !== true) { throw new TalkbackNotRunningError(`This client has not requested the start of the talkback for the device ${serialNumber} and therefore cannot send audio data`); } if (DeviceMessageHandler.talkbackingDevices[station.getSerial()] !== undefined && DeviceMessageHandler.talkbackingDevices[station.getSerial()].includes(client)) { const audioDataBuffer = Buffer.from(message.buffer); this.talkbackStream?.write(audioDataBuffer); } return {}; } else { throw new UnknownCommandError(command); } case DeviceCommand.snooze: if (client.schemaVersion >= 13) { station.snooze(device, { snooze_time: message.snoozeTime, snooze_chime: message.snoozeChime, snooze_motion: message .snoozeMotion, snooze_homebase: message .snoozeHomebase, }); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.addUser: if (client.schemaVersion >= 13) { const addUser = message; await driver .addUser(device.getSerial(), addUser.username, addUser.passcode, addUser.schedule) .catch((error) => { throw error; }); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.deleteUser: if (client.schemaVersion >= 13) { const addUser = message; await driver .deleteUser(device.getSerial(), addUser.username) .catch((error) => { throw error; }); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.getUsers: if (client.schemaVersion >= 13) { const users = await driver .getApi() .getUsers(device.getSerial(), device.getStationSerial()) .catch((error) => { throw error; }); return { users: users === null ? [] : users }; } else { throw new UnknownCommandError(command); } case DeviceCommand.updateUser: if (client.schemaVersion >= 13) { const addUser = message; await driver .updateUser(device.getSerial(), addUser.username, addUser.newUsername) .catch((error) => { throw error; }); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.updateUserPasscode: if (client.schemaVersion >= 13) { const addUser = message; await driver .updateUserPasscode(device.getSerial(), addUser.username, addUser.passcode) .catch((error) => { throw error; }); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.updateUserSchedule: if (client.schemaVersion >= 13) { const addUser = message; await driver .updateUserSchedule(device.getSerial(), addUser.username, addUser.schedule) .catch((error) => { throw error; }); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.verifyPIN: if (client.schemaVersion >= 13) { const verifyPIN = message; station.verifyPIN(device, verifyPIN.pin); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.presetPosition: if (client.schemaVersion >= 21) { const presetPosition = message; station.presetPosition(device, presetPosition.position); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.savePresetPosition: if (client.schemaVersion >= 21) { const savePresetPosition = message; station.savePresetPosition(device, savePresetPosition.position); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.deletePresetPosition: if (client.schemaVersion >= 21) { const deletePresetPosition = message; station.deletePresetPosition(device, deletePresetPosition.position); return { async: true }; } else { throw new UnknownCommandError(command); } case DeviceCommand.open: if (client.schemaVersion >= 21) { station.open(device); return { async: true }; } else { throw new UnknownCommandError(command); } default: throw new UnknownCommandError(command); } } } //# sourceMappingURL=message_handler.js.map