homebridge-eufy-security-mikebcbc
Version:
Control Eufy Security from homebridge.
277 lines • 10.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EufyClientInteractor = void 0;
const events_1 = __importDefault(require("events"));
const fs_1 = __importDefault(require("fs"));
const net_1 = __importDefault(require("net"));
const eufy_security_client_1 = require("eufy-security-client");
const interfaces_1 = require("./interfaces");
const ffmpeg_params_1 = require("./ffmpeg-params");
var InteractorRequestType;
(function (InteractorRequestType) {
InteractorRequestType["DeviceChargingStatus"] = "deviceChargingStatus";
InteractorRequestType["DeviceHasProperty"] = "deviceHasProperty";
InteractorRequestType["GetStationDevicesMapping"] = "stationDevicesMapping";
})(InteractorRequestType || (InteractorRequestType = {}));
class EufyClientInteractor extends events_1.default {
constructor(path, log, client) {
super();
this.log = log;
this.storagePath = path;
this.client = client;
}
setClient(client) {
this.client = client;
}
async setupServer() {
const port = await ffmpeg_params_1.FFmpegParameters.allocateTCPPort();
if (!this.writePortToStoragePath(port)) {
return Promise.reject(new Error('Could not start interaction server'));
}
return new Promise((resolve, reject) => {
this.server = net_1.default.createServer((socket) => {
socket.on('data', (data) => {
const request = JSON.parse(data.toString('utf-8'));
this.log.debug(`incoming Interaction Request: for ${request.serialNumber}, ${request}`);
this.processIPCRequest(socket, request);
});
socket.on('error', this.onSocketError.bind(this));
});
this.server.on('error', this.onServerError.bind(this));
this.server.listen(port, () => {
this.log.debug(`Plugin-Config interaction server was started on port: ${port}`);
resolve();
});
});
}
stopServer() {
if (this.server) {
this.server.close();
}
}
writePortToStoragePath(port) {
try {
fs_1.default.writeFileSync(this.storagePath + '/interaction.port', `${port}`, { encoding: 'utf-8' });
return true;
}
catch (err) {
return false;
}
}
loadPort() {
try {
const port = fs_1.default.readFileSync(this.storagePath + '/interaction.port', { encoding: 'utf-8' });
return parseInt(port);
}
catch (err) {
return -1;
}
}
ipcRequest(request) {
this.log.debug(`Interaction Request: for ${request.serialNumber}, type: ${request.type}`);
return new Promise((resolve, reject) => {
const port = this.loadPort();
if (port <= 0) {
reject('Could not read port for interaction server');
}
const socket = net_1.default.createConnection(port, 'localhost', () => {
socket.write(JSON.stringify(request));
});
const timeout = setTimeout(() => {
socket.destroy();
reject('no answer was retrieved from server');
}, 10000);
socket.on('error', (err) => {
reject(err);
socket.destroy();
});
socket.on('data', (data) => {
const response = JSON.parse(data.toString('utf-8'));
if (response.serialNumber !== request.serialNumber || response.type !== request.type) {
reject(new Error('invalid ipc response'));
}
else {
resolve(response);
}
clearTimeout(timeout);
socket.destroy();
});
});
}
async processIPCRequest(socket, request) {
if (!this.client) {
const response = {
serialNumber: request.serialNumber,
type: request.type,
error: new interfaces_1.EufyClientNotRunningError('eufy client not running'),
};
socket.write(JSON.stringify(response));
return;
}
let response = {
serialNumber: request.serialNumber,
type: request.type,
};
try {
response = await this.processDirectRequest(request);
}
catch (err) {
response.error = err;
}
// eslint-disable-next-line max-len
this.log.debug(`outgoing Interaction Response: for ${response.serialNumber}, type: ${response.type}, result: ${response.result}, error: ${response.error}`);
socket.write(JSON.stringify(response));
}
async processDirectRequest(request) {
if (!this.client) {
// forward to interaction server
return this.ipcRequest(request);
}
const response = {
serialNumber: request.serialNumber,
type: request.type,
};
try {
switch (request.type) {
case InteractorRequestType.DeviceChargingStatus:
response.result = await this.getChargingStatus(request);
break;
case InteractorRequestType.DeviceHasProperty:
response.result = await this.hasProperty(request);
break;
case InteractorRequestType.GetStationDevicesMapping:
response.result = await this.getStationCamerasMap(request);
break;
default:
response.error = new Error('Request type not implemented.');
break;
}
}
catch (err) {
response.error = err;
}
// eslint-disable-next-line max-len
this.log.debug(`Interaction Response: for ${response.serialNumber}, type: ${response}, error: ${response.error}`);
return Promise.resolve(response);
}
async getChargingStatus(request) {
const device = await this.client.getDevice(request.serialNumber);
return new Promise((resolve, reject) => {
if (!device.hasBattery()) {
// device has no battery, so it is always powered with plug
resolve(3);
}
else if (device.hasProperty(eufy_security_client_1.PropertyName.DeviceChargingStatus)) {
resolve(device.getPropertyValue(eufy_security_client_1.PropertyName.DeviceChargingStatus));
}
else {
reject(new Error('battery charging property could not be retrieved'));
}
});
}
async hasProperty(request) {
const device = await this.client.getDevice(request.serialNumber);
return new Promise((resolve, reject) => {
if (request.propertyName !== undefined
&& device.hasProperty(request.propertyName)) {
resolve(true);
}
else {
resolve(false);
}
});
}
async getStationCamerasMap(request) {
try {
const stations = this.client.getStations();
const devices = await this.client.getDevices();
const result = {};
for (const device of devices) {
if (!device.isCamera()) {
continue;
}
const stationSN = device.getStationSerial();
const devicesArray = result[stationSN];
if (Array.isArray(devicesArray)) {
devicesArray.push(device.getSerial());
}
else {
result[stationSN] = [device.getSerial()];
}
}
return Promise.resolve(result);
}
catch (err) {
return Promise.reject(err);
}
}
onSocketError(err) {
this.log.error(`There was an error on the PluginConfigInteractor socket: ${err}`);
}
onServerError(err) {
this.log.error(`There was an error on the PluginConfigInteractor server: ${err}`);
}
async DeviceIsCharging(sn) {
const request = {
serialNumber: sn,
type: InteractorRequestType.DeviceChargingStatus,
};
try {
const response = await this.processDirectRequest(request);
if (response.error) {
return Promise.reject(response.error.message);
}
if (response.result === undefined) {
return Promise.reject('there was no result');
}
return Promise.resolve(response.result);
}
catch (err) {
return Promise.reject(err);
}
}
async DeviceHasProperty(sn, propertyName) {
const request = {
serialNumber: sn,
propertyName: propertyName,
type: InteractorRequestType.DeviceHasProperty,
};
try {
const response = await this.processDirectRequest(request);
if (response.error) {
return Promise.reject(response.error.message);
}
if (response.result === undefined) {
return Promise.reject('there was no result');
}
return Promise.resolve(response.result);
}
catch (err) {
return Promise.reject(err);
}
}
async GetStationCamerasMapping() {
const request = {
serialNumber: '',
type: InteractorRequestType.GetStationDevicesMapping,
};
try {
const response = await this.processDirectRequest(request);
if (response.error) {
return Promise.reject(response.error.message);
}
if (response.result === undefined) {
return Promise.reject('there was no result');
}
return Promise.resolve(response.result);
}
catch (err) {
return Promise.reject(err);
}
}
}
exports.EufyClientInteractor = EufyClientInteractor;
//# sourceMappingURL=EufyClientInteractor.js.map