UNPKG

@spectralblu/eufy-security-client

Version:

Client to comunicate with Eufy-Security devices, forked from bropat/eufy-client-security

515 lines (514 loc) 19.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.decryptTrackerData = exports.isPrioritySourceType = exports.getImage = exports.getImagePath = exports.decodeImage = exports.getImageKey = exports.getImageSeed = exports.getImageBaseCode = exports.getIdSuffix = exports.randomNumber = exports.hexWeek = exports.hexTime = exports.hexDate = exports.encodePasscode = exports.SmartSafeByteWriter = exports.getAdvancedLockTimezone = exports.getEufyTimezone = exports.getHB3DetectionMode = exports.isHB3DetectionModeEnabled = exports.getDistances = exports.getBlocklist = exports.decryptAPIData = exports.encryptAPIData = exports.calculateCellularSignalLevel = exports.calculateWifiSignalLevel = exports.switchNotificationMode = exports.isNotificationSwitchMode = exports.getImageFilePath = exports.getAbsoluteFilePath = exports.getTimezoneGMTString = exports.pad = exports.isGreaterEqualMinVersion = void 0; const crypto_1 = require("crypto"); const const_1 = require("./const"); const md5_1 = __importDefault(require("crypto-js/md5")); const enc_hex_1 = __importDefault(require("crypto-js/enc-hex")); const sha256_1 = __importDefault(require("crypto-js/sha256")); const image_type_1 = __importDefault(require("image-type")); const types_1 = require("./types"); const error_1 = require("../error"); const error_2 = require("./error"); const normalizeVersionString = function (version) { const trimmed = version ? version.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1") : ""; const pieces = trimmed.split(RegExp("\\.")); const parts = []; let value, piece, num, i; for (i = 0; i < pieces.length; i += 1) { piece = pieces[i].replace(RegExp("\\D"), ""); num = parseInt(piece, 10); if (isNaN(num)) { num = 0; } parts.push(num); } const partsLength = parts.length; for (i = partsLength - 1; i >= 0; i -= 1) { value = parts[i]; if (value === 0) { parts.length -= 1; } else { break; } } return parts; }; const isGreaterEqualMinVersion = function (minimal_version, current_version) { const x = normalizeVersionString(minimal_version); const y = normalizeVersionString(current_version); const size = Math.min(x.length, y.length); let i; for (i = 0; i < size; i += 1) { if (x[i] !== y[i]) { return x[i] < y[i] ? true : false; } } if (x.length === y.length) { return true; } return (x.length < y.length) ? true : false; }; exports.isGreaterEqualMinVersion = isGreaterEqualMinVersion; const pad = function (num) { const norm = Math.floor(Math.abs(num)); return (norm < 10 ? "0" : "") + norm; }; exports.pad = pad; const getTimezoneGMTString = function () { const tzo = -new Date().getTimezoneOffset(); const dif = tzo >= 0 ? "+" : "-"; return `GMT${dif}${(0, exports.pad)(tzo / 60)}:${(0, exports.pad)(tzo % 60)}`; }; exports.getTimezoneGMTString = getTimezoneGMTString; const getAbsoluteFilePath = function (device_type, channel, filename) { if (device_type === types_1.DeviceType.FLOODLIGHT) { return `/mnt/data/Camera${String(channel).padStart(2, "0")}/${filename}.dat`; } return `/media/mmcblk0p1/Camera${String(channel).padStart(2, "0")}/${filename}.dat`; }; exports.getAbsoluteFilePath = getAbsoluteFilePath; const getImageFilePath = function (device_type, channel, filename) { if (device_type === types_1.DeviceType.FLOODLIGHT) { return `/mnt/data/video/${filename}_c${String(channel).padStart(2, "0")}.jpg`; } return `/media/mmcblk0p1/video/${filename}_c${String(channel).padStart(2, "0")}.jpg`; }; exports.getImageFilePath = getImageFilePath; const isNotificationSwitchMode = function (value, mode) { if (value === 1) value = 240; return (value & mode) !== 0; }; exports.isNotificationSwitchMode = isNotificationSwitchMode; const switchNotificationMode = function (currentValue, mode, enable) { let result = 0; if (!enable && currentValue === 1 /* ALL */) { currentValue = 240; } if (enable) { result = mode | currentValue; } else { result = ~mode & currentValue; } if ((0, exports.isNotificationSwitchMode)(result, types_1.NotificationSwitchMode.SCHEDULE) && (0, exports.isNotificationSwitchMode)(result, types_1.NotificationSwitchMode.APP) && (0, exports.isNotificationSwitchMode)(result, types_1.NotificationSwitchMode.GEOFENCE) && (0, exports.isNotificationSwitchMode)(result, types_1.NotificationSwitchMode.KEYPAD)) { result = 1; /* ALL */ } return result; }; exports.switchNotificationMode = switchNotificationMode; const calculateWifiSignalLevel = function (device, rssi) { if (device.isWiredDoorbell()) { if (rssi >= -65) { return types_1.SignalLevel.FULL; } if (rssi >= -75) { return types_1.SignalLevel.STRONG; } return rssi >= -80 ? types_1.SignalLevel.NORMAL : types_1.SignalLevel.WEAK; } else if (device.isCamera2Product()) { if (rssi >= 0) { return types_1.SignalLevel.NO_SIGNAL; } if (rssi >= -65) { return types_1.SignalLevel.FULL; } if (rssi >= -75) { return types_1.SignalLevel.STRONG; } return rssi >= -85 ? types_1.SignalLevel.NORMAL : types_1.SignalLevel.WEAK; } else if (device.isFloodLight()) { if (rssi >= 0) { return types_1.SignalLevel.NO_SIGNAL; } if (rssi >= -60) { return types_1.SignalLevel.FULL; } if (rssi >= -70) { return types_1.SignalLevel.STRONG; } return rssi >= -80 ? types_1.SignalLevel.NORMAL : types_1.SignalLevel.WEAK; } else if (device.isBatteryDoorbell()) { if (rssi >= -65) { return types_1.SignalLevel.FULL; } if (rssi >= -75) { return types_1.SignalLevel.STRONG; } return rssi >= -85 ? types_1.SignalLevel.NORMAL : types_1.SignalLevel.WEAK; } else { if (rssi >= 0) { return types_1.SignalLevel.NO_SIGNAL; } if (rssi >= -65) { return types_1.SignalLevel.FULL; } if (rssi >= -75) { return types_1.SignalLevel.STRONG; } return rssi >= -85 ? types_1.SignalLevel.NORMAL : types_1.SignalLevel.WEAK; } }; exports.calculateWifiSignalLevel = calculateWifiSignalLevel; const calculateCellularSignalLevel = function (rssi) { if (rssi >= 0) { return types_1.SignalLevel.NO_SIGNAL; } if (rssi >= -90) { return types_1.SignalLevel.FULL; } if (rssi >= -95) { return types_1.SignalLevel.STRONG; } return rssi >= -105 ? types_1.SignalLevel.NORMAL : types_1.SignalLevel.WEAK; }; exports.calculateCellularSignalLevel = calculateCellularSignalLevel; const encryptAPIData = (data, key) => { const cipher = (0, crypto_1.createCipheriv)("aes-256-cbc", key, key.subarray(0, 16)); return (cipher.update(data, "utf8", "base64") + cipher.final("base64")); }; exports.encryptAPIData = encryptAPIData; const decryptAPIData = (data, key) => { const cipher = (0, crypto_1.createDecipheriv)("aes-256-cbc", key, key.subarray(0, 16)); return Buffer.concat([ cipher.update(data, "base64"), cipher.final() ]); }; exports.decryptAPIData = decryptAPIData; const getBlocklist = function (directions) { const result = []; for (let distance = 1; distance <= 5; distance++) { let i = 0; let j = 0; let k = 1; for (const directionDistance of directions) { if (directionDistance >= distance) { j += k; } k <<= 1; } if (j == 0) { i = 65535; } else if (!(j == 255 || j == 65535)) { i = (j ^ 255) + 65280; } result.push(65535 & i); } return result; }; exports.getBlocklist = getBlocklist; const getDistances = function (blocklist) { const result = [3, 3, 3, 3, 3, 3, 3, 3]; let calcDistance = 0; for (const blockElement of blocklist) { let valueOf = blockElement ^ 65535; calcDistance++; if (valueOf !== 0) { for (let i = 0; i < result.length; i++) { const intValue = valueOf & 1; if (intValue > 0) { result[i] = calcDistance; } valueOf = valueOf >> 1; } } } return result; }; exports.getDistances = getDistances; const isHB3DetectionModeEnabled = function (value, type) { if (type === types_1.HB3DetectionTypes.HUMAN_RECOGNITION) { return (type & value) == type && (value & 65536) == 65536; } else if (type === types_1.HB3DetectionTypes.HUMAN_DETECTION) { return (type & value) == type && (value & 1) == 1; } return (type & value) == type; }; exports.isHB3DetectionModeEnabled = isHB3DetectionModeEnabled; const getHB3DetectionMode = function (value, type, enable) { let result = 0; if (!enable) { if (type === types_1.HB3DetectionTypes.HUMAN_RECOGNITION) { const tmp = (type & value) == type ? type ^ value : value; result = (value & 65536) == 65536 ? tmp ^ 65536 : tmp; } else if (type === types_1.HB3DetectionTypes.HUMAN_DETECTION) { const tmp = (type & value) == type ? type ^ value : value; result = (value & 1) == 1 ? tmp ^ 1 : tmp; } else { result = type ^ value; } } else { if (type === types_1.HB3DetectionTypes.HUMAN_RECOGNITION) { result = type | value | 65536; } else if (type === types_1.HB3DetectionTypes.HUMAN_DETECTION) { result = type | value | 1; } else { result = type | value; } } return result; }; exports.getHB3DetectionMode = getHB3DetectionMode; const getEufyTimezone = function () { for (const timezone of const_1.timeZoneData) { if (timezone.timeId === Intl.DateTimeFormat().resolvedOptions().timeZone) { return timezone; } } return undefined; }; exports.getEufyTimezone = getEufyTimezone; const getAdvancedLockTimezone = function (stationSN) { const timezone = (0, exports.getEufyTimezone)(); if (timezone !== undefined) { if (stationSN.startsWith("T8520") && (0, exports.isGreaterEqualMinVersion)("1.2.8.6", stationSN)) return `${timezone.timeZoneGMT}|1.${timezone.timeSn}`; else return timezone.timeZoneGMT; } return ""; }; exports.getAdvancedLockTimezone = getAdvancedLockTimezone; class SmartSafeByteWriter { split_byte = -95; data = Buffer.from([]); write(bytes) { const tmp_data = Buffer.from(bytes); this.data = Buffer.concat([this.data, Buffer.from([this.split_byte]), Buffer.from([tmp_data.length & 255]), tmp_data]); this.split_byte += 1; } getData() { return this.data; } } exports.SmartSafeByteWriter = SmartSafeByteWriter; /*export const generateHash = function(data: Buffer): number { let result = 0; for (const value of data) { result = result ^ value; } return result; } export const encodeSmartSafeData = function(command: number, payload: Buffer): Buffer { const header = Buffer.from(SmartSafe.DATA_HEADER); const size = Buffer.allocUnsafe(2); size.writeInt16LE(payload.length + 9); const versionCode = Buffer.from([SmartSafe.VERSION_CODE]); const dataType = Buffer.from([-1]); const commandCode = Buffer.from([command]); const packageFlag = Buffer.from([-64]); const data = Buffer.concat([header, size, versionCode, dataType, commandCode, packageFlag, payload]); const hash = generateHash(data); return Buffer.concat([data, Buffer.from([hash])]); }*/ const encodePasscode = function (pass) { let result = ""; for (let i = 0; i < pass.length; i++) result += pass.charCodeAt(i).toString(16); return result; }; exports.encodePasscode = encodePasscode; const hexDate = function (date) { const buf = Buffer.allocUnsafe(4); buf.writeUint8(date.getDate()); buf.writeUint8(date.getMonth() + 1, 1); buf.writeUint16BE(date.getFullYear(), 2); return buf.readUInt32LE().toString(16).padStart(8, "0"); }; exports.hexDate = hexDate; const hexTime = function (date) { const buf = Buffer.allocUnsafe(2); buf.writeUint8(date.getHours()); buf.writeUint8(date.getMinutes(), 1); return buf.readUInt16BE().toString(16).padStart(4, "0"); }; exports.hexTime = hexTime; const hexWeek = function (schedule) { const SUNDAY = 1; const MONDAY = 2; const TUESDAY = 4; const WEDNESDAY = 8; const THUERSDAY = 16; const FRIDAY = 32; const SATURDAY = 64; let result = 0; if (schedule.week !== undefined) { if (schedule.week.sunday) { result |= SUNDAY; } if (schedule.week.monday) { result |= MONDAY; } if (schedule.week.tuesday) { result |= TUESDAY; } if (schedule.week.wednesday) { result |= WEDNESDAY; } if (schedule.week.thursday) { result |= THUERSDAY; } if (schedule.week.friday) { result |= FRIDAY; } if (schedule.week.saturday) { result |= SATURDAY; } return result.toString(16); } return "ff"; }; exports.hexWeek = hexWeek; const randomNumber = function (min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }; exports.randomNumber = randomNumber; const getIdSuffix = function (p2pDid) { let result = 0; const match = p2pDid.match(/^[A-Z]+-(\d+)-[A-Z]+$/); if (match?.length == 2) { const num1 = Number.parseInt(match[1][0]); const num2 = Number.parseInt(match[1][1]); const num3 = Number.parseInt(match[1][3]); const num4 = Number.parseInt(match[1][5]); result = num1 + num2 + num3; if (num3 < 5) { result = result + num3; } result = result + num4; } return result; }; exports.getIdSuffix = getIdSuffix; const getImageBaseCode = function (serialnumber, p2pDid) { let nr = 0; try { nr = Number.parseInt(`0x${serialnumber[serialnumber.length - 1]}`); } catch (err) { const error = (0, error_1.ensureError)(err); throw new error_2.ImageBaseCodeError("Error generating image base code", { cause: error, context: { serialnumber: serialnumber, p2pDid: p2pDid } }); } nr = (nr + 10) % 10; const base = serialnumber.substring(nr); return `${base}${(0, exports.getIdSuffix)(p2pDid)}`; }; exports.getImageBaseCode = getImageBaseCode; const getImageSeed = function (p2pDid, code) { try { const ncode = Number.parseInt(code.substring(2)); const prefix = 1000 - (0, exports.getIdSuffix)(p2pDid); return (0, md5_1.default)(`${prefix}${ncode}`).toString(enc_hex_1.default).toUpperCase(); } catch (err) { const error = (0, error_1.ensureError)(err); throw new error_2.ImageBaseCodeError("Error generating image seed", { cause: error, context: { p2pDid: p2pDid, code: code } }); } }; exports.getImageSeed = getImageSeed; const getImageKey = function (serialnumber, p2pDid, code) { const basecode = (0, exports.getImageBaseCode)(serialnumber, p2pDid); const seed = (0, exports.getImageSeed)(p2pDid, code); const data = `01${basecode}${seed}`; const hash = (0, sha256_1.default)(data); const hashBytes = [...Buffer.from(hash.toString(enc_hex_1.default), "hex")]; const startByte = hashBytes[10]; for (let i = 0; i < 32; i++) { const byte = hashBytes[i]; let fixed_byte = startByte; if (i < 31) { fixed_byte = hashBytes[i + 1]; } if ((i == 31) || ((i & 1) != 0)) { hashBytes[10] = fixed_byte; if ((126 < byte) || (126 < hashBytes[10])) { if (byte < hashBytes[10] || (byte - hashBytes[10]) == 0) { hashBytes[i] = hashBytes[10] - byte; } else { hashBytes[i] = byte - hashBytes[10]; } } } else if ((byte < 125) || (fixed_byte < 125)) { hashBytes[i] = fixed_byte + byte; } } return `${Buffer.from(hashBytes.slice(16)).toString("hex").toUpperCase()}`; }; exports.getImageKey = getImageKey; const decodeImage = function (p2pDid, data) { if (data.length >= 12) { const header = data.subarray(0, 12).toString(); if (header === "eufysecurity") { const serialnumber = data.subarray(13, 29).toString(); const code = data.subarray(30, 40).toString(); const imageKey = (0, exports.getImageKey)(serialnumber, p2pDid, code); const otherData = data.subarray(41); const encryptedData = otherData.subarray(0, 256); const cipher = (0, crypto_1.createDecipheriv)("aes-128-ecb", Buffer.from(imageKey, "utf-8").subarray(0, 16), null); cipher.setAutoPadding(false); const decryptedData = Buffer.concat([ cipher.update(encryptedData), cipher.final() ]); decryptedData.copy(otherData); return otherData; } } return data; }; exports.decodeImage = decodeImage; const getImagePath = function (path) { const splittedPath = path.split("~"); if (splittedPath.length === 2) { return splittedPath[1]; } return path; }; exports.getImagePath = getImagePath; const getImage = async function (api, serial, url) { const image = await api.getImage(serial, url); const type = await (0, image_type_1.default)(image); return { data: image, type: type !== null ? type : { ext: "unknown", mime: "application/octet-stream" } }; }; exports.getImage = getImage; const isPrioritySourceType = function (current, update) { if (((current === "http" || current === "p2p" || current === "push" || current === "mqtt" || current === undefined) && (update === "p2p" || update === "push" || update === "mqtt")) || ((current === "http" || current === undefined) && update === "http")) { return true; } return false; }; exports.isPrioritySourceType = isPrioritySourceType; const decryptTrackerData = (data, key) => { const decipher = (0, crypto_1.createDecipheriv)("aes-128-ecb", key, null); decipher.setAutoPadding(false); return Buffer.concat([ decipher.update(data), decipher.final() ]); }; exports.decryptTrackerData = decryptTrackerData; //# sourceMappingURL=utils.js.map