@ubreu/homebridge-eufy-security
Version:
Control Eufy Security from homebridge.
365 lines • 15.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/no-var-requires */
const eufy_security_client_1 = require("eufy-security-client");
const plugin_ui_utils_1 = require("@homebridge/plugin-ui-utils");
const fs_1 = __importDefault(require("fs"));
const tslog_1 = require("tslog");
const rotating_file_stream_1 = require("rotating-file-stream");
const zip_lib_1 = require("zip-lib");
const types_1 = require("./configui/app/util/types");
class UiServer extends plugin_ui_utils_1.HomebridgePluginUiServer {
constructor() {
super();
this.accessories = [];
this.storagePath = this.homebridgeStoragePath + '/eufysecurity';
this.storedAccessories_file = this.storagePath + '/accessories.json';
this.logZipFilePath = this.storagePath + '/logs.zip';
this.eufyClient = null;
const plugin = require('../package.json');
const mainLogObj = {
name: `[EufySecurity-${plugin.version}]`,
// eslint-disable-next-line max-len
prettyLogTemplate: '[{{mm}}/{{dd}}/{{yyyy}} {{hh}}:{{MM}}:{{ss}}]\t{{name}}\t{{logLevelName}}\t[{{fileNameWithLine}}{{name}}]\t',
prettyErrorTemplate: '\n{{errorName}} {{errorMessage}}\nerror stack:\n{{errorStack}}',
prettyErrorStackTemplate: ' • {{fileName}}\t{{method}}\n\t{{fileNameWithLine}}',
prettyErrorParentNamesSeparator: ':',
prettyErrorLoggerNameDelimiter: '\t',
stylePrettyLogs: true,
minLevel: 2,
prettyLogTimeZone: 'local',
prettyLogStyles: {
logLevelName: {
'*': ['bold', 'black', 'bgWhiteBright', 'dim'],
SILLY: ['bold', 'white'],
TRACE: ['bold', 'whiteBright'],
DEBUG: ['bold', 'green'],
INFO: ['bold', 'blue'],
WARN: ['bold', 'yellow'],
ERROR: ['bold', 'red'],
FATAL: ['bold', 'redBright'],
},
dateIsoStr: 'gray',
filePathWithLine: 'white',
name: 'green',
nameWithDelimiterPrefix: ['white', 'bold'],
nameWithDelimiterSuffix: ['white', 'bold'],
errorName: ['bold', 'bgRedBright', 'whiteBright'],
fileName: ['yellow'],
},
};
this.log = new tslog_1.Logger(mainLogObj);
this.tsLog = new tslog_1.Logger({ type: 'hidden', minLevel: 2 });
if (!fs_1.default.existsSync(this.storagePath)) {
fs_1.default.mkdirSync(this.storagePath);
}
const pluginLogStream = (0, rotating_file_stream_1.createStream)('configui-server.log', {
path: this.storagePath,
interval: '1d',
rotate: 3,
maxSize: '200M',
});
const pluginLogLibStream = (0, rotating_file_stream_1.createStream)('configui-lib.log', {
path: this.storagePath,
interval: '1d',
rotate: 3,
maxSize: '200M',
});
this.log.attachTransport((logObj) => {
pluginLogStream.write(JSON.stringify(logObj) + '\n');
});
this.tsLog.attachTransport((logObj) => {
pluginLogLibStream.write(JSON.stringify(logObj) + '\n');
});
this.log.debug('Using bropats eufy-security-client library in version ' + eufy_security_client_1.libVersion);
this.config = {
username: '',
password: '',
language: 'en',
country: 'US',
trustedDeviceName: 'My Phone',
persistentDir: this.storagePath,
p2pConnectionSetup: 0,
pollingIntervalMinutes: 10,
eventDurationSeconds: 10,
acceptInvitations: true,
};
this.onRequest('/login', this.login.bind(this));
this.onRequest('/storedAccessories', this.loadStoredAccessories.bind(this));
this.onRequest('/reset', this.resetPlugin.bind(this));
this.onRequest('/downloadLogs', this.downloadLogs.bind(this));
this.ready();
}
async resetPersistentData() {
try {
fs_1.default.unlinkSync(this.storagePath + '/persistent.json');
}
catch (err) {
return Promise.reject(err);
}
}
async login(options) {
var _a, _b;
// delete persistent.json if new login
try {
if (options && options.username && options.password) {
this.log.info('deleting persistent.json due to new login');
await this.resetPersistentData();
}
}
catch (err) {
this.log.error('Could not delete persistent.json due to error: ' + err);
}
if (!this.eufyClient && options && options.username && options.password && options.country) {
this.accessories = []; // clear accessories array so that it can be filled with all devices after login
this.log.debug('init eufyClient');
this.config.username = options.username;
this.config.password = options.password;
this.config.country = options.country;
this.config.trustedDeviceName = options.deviceName;
try {
this.eufyClient = await eufy_security_client_1.EufySecurity.initialize(this.config, this.tsLog);
}
catch (err) {
this.log.error(err);
}
(_a = this.eufyClient) === null || _a === void 0 ? void 0 : _a.on('station added', this.addStation.bind(this));
(_b = this.eufyClient) === null || _b === void 0 ? void 0 : _b.on('device added', this.addDevice.bind(this));
}
return new Promise((resolve, reject) => {
var _a, _b, _c;
const connectionTimeout = setTimeout(() => {
resolve({
success: false,
failReason: types_1.LoginFailReason.TIMEOUT,
});
}, 25000);
if (options && options.username && options.password && options.country) {
// login with credentials
this.log.debug('login with credentials');
try {
this.loginHandlers(resolve);
(_a = this.eufyClient) === null || _a === void 0 ? void 0 : _a.connect().then(() => {
var _a;
this.log.debug('connected?: ' + ((_a = this.eufyClient) === null || _a === void 0 ? void 0 : _a.isConnected()));
}).catch((err) => this.log.error(err));
}
catch (err) {
this.log.error(err);
resolve({
success: false,
failReason: types_1.LoginFailReason.UNKNOWN,
data: {
error: err,
},
});
}
}
else if (options && options.verifyCode) {
// login with tfa code
try {
this.loginHandlers(resolve);
(_b = this.eufyClient) === null || _b === void 0 ? void 0 : _b.connect({
verifyCode: options.verifyCode,
force: false,
});
}
catch (err) {
resolve({
success: false,
failReason: types_1.LoginFailReason.UNKNOWN,
data: {
error: err,
},
});
}
}
else if (options && options.captcha) {
// login witch captcha
try {
this.loginHandlers(resolve);
(_c = this.eufyClient) === null || _c === void 0 ? void 0 : _c.connect({
captcha: {
captchaCode: options.captcha.captchaCode,
captchaId: options.captcha.captchaId,
},
force: false,
});
}
catch (err) {
resolve({
success: false,
failReason: types_1.LoginFailReason.UNKNOWN,
data: {
error: err,
},
});
}
}
else {
reject('unsupported login method');
}
});
}
async loadStoredAccessories() {
try {
const accessories = JSON.parse(fs_1.default.readFileSync(this.storedAccessories_file, { encoding: 'utf-8' }));
return Promise.resolve(accessories);
}
catch (err) {
this.log.error('Could not get stored accessories. Most likely no stored accessories yet: ' + err);
return Promise.reject([]);
}
}
loginHandlers(resolveCallback) {
var _a, _b, _c;
(_a = this.eufyClient) === null || _a === void 0 ? void 0 : _a.once('tfa request', () => {
this.log.debug('tfa request event');
resolveCallback({
success: false,
failReason: types_1.LoginFailReason.TFA, // TFA
});
});
(_b = this.eufyClient) === null || _b === void 0 ? void 0 : _b.once('captcha request', (id, captcha) => {
this.log.debug('captcha request event');
resolveCallback({
success: false,
failReason: types_1.LoginFailReason.CAPTCHA,
data: {
id: id,
captcha: captcha,
},
});
});
(_c = this.eufyClient) === null || _c === void 0 ? void 0 : _c.once('connect', () => {
this.log.debug('connect event');
resolveCallback({
success: true,
});
});
}
addStation(station) {
const s = {
uniqueId: station.getSerial(),
displayName: station.getName(),
station: true,
type: station.getDeviceType(),
};
this.accessories.push(s);
this.storeAccessories();
this.pushEvent('addAccessory', s);
}
addDevice(device) {
const d = {
uniqueId: device.getSerial(),
displayName: device.getName(),
station: false,
type: device.getDeviceType(),
};
this.accessories.push(d);
this.storeAccessories();
this.pushEvent('addAccessory', d);
}
storeAccessories() {
fs_1.default.writeFileSync(this.storedAccessories_file, JSON.stringify(this.accessories));
}
async resetPlugin() {
try {
fs_1.default.rmSync(this.storagePath, { recursive: true });
return { result: 1 }; //file removed
}
catch (err) {
return { result: 0 }; //error while removing the file
}
}
// TODO: test for different configurations (apt-install, npm -g install, windows, ...)
async downloadLogs() {
this.log.info(`compressing log files to ${this.logZipFilePath} and sending to client.`);
if (!this.removeCompressedLogs()) {
this.log.error('There were already old compressed log files that could not be removed!');
return Promise.reject('There were already old compressed log files that could not be removed!');
}
return new Promise((resolve, reject) => {
const zip = new zip_lib_1.Zip();
let numberOfFiles = 0;
if (fs_1.default.existsSync(this.storagePath + '/log-lib.log')) {
zip.addFile(this.storagePath + '/log-lib.log');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/log-lib.log.0')) {
zip.addFile(this.storagePath + '/log-lib.log.0');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/log-lib.log.1')) {
zip.addFile(this.storagePath + '/log-lib.log.1');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/log-lib.log.2')) {
zip.addFile(this.storagePath + '/log-lib.log.2');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/eufy-log.log')) {
zip.addFile(this.storagePath + '/eufy-log.log');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/eufy-log.log.0')) {
zip.addFile(this.storagePath + '/eufy-log.log.0');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/eufy-log.log.1')) {
zip.addFile(this.storagePath + '/eufy-log.log.1');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/eufy-log.log.2')) {
zip.addFile(this.storagePath + '/eufy-log.log.2');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/configui-server.log')) {
zip.addFile(this.storagePath + '/configui-server.log');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/configui-server.log.0')) {
zip.addFile(this.storagePath + '/configui-server.log.0');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/configui-server.log.1')) {
zip.addFile(this.storagePath + '/configui-server.log.1');
numberOfFiles++;
}
if (fs_1.default.existsSync(this.storagePath + '/configui-server.log.2')) {
zip.addFile(this.storagePath + '/configui-server.log.2');
numberOfFiles++;
}
if (numberOfFiles === 0) {
throw new Error('No log files were found');
}
this.pushEvent('downloadLogsFileCount', { numberOfFiles: numberOfFiles });
zip.archive(this.logZipFilePath).then(() => {
const fileBuffer = fs_1.default.readFileSync(this.logZipFilePath);
resolve(fileBuffer);
}).catch((err) => {
this.log.error('Error while generating log files: ' + err);
reject(err);
}).finally(() => this.removeCompressedLogs());
});
}
removeCompressedLogs() {
try {
if (fs_1.default.existsSync(this.logZipFilePath)) {
fs_1.default.unlinkSync(this.logZipFilePath);
}
return true;
}
catch (_a) {
return false;
}
}
}
// start the instance of the server
(() => {
return new UiServer();
})();
//# sourceMappingURL=server.js.map