UNPKG

homebridge-xfinityhome

Version:

A homebridge plugin to control your Xfinity Home security system.

271 lines 13.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* eslint-disable no-console */ const debug_1 = __importDefault(require("debug")); const events_1 = require("events"); const fs_1 = require("fs"); const http_mitm_proxy_1 = require("http-mitm-proxy"); const os_1 = __importDefault(require("os")); const path_1 = __importDefault(require("path")); const qrcode_1 = __importDefault(require("qrcode")); const plugin_ui_utils_1 = require("@homebridge/plugin-ui-utils"); class PluginUiServer extends plugin_ui_utils_1.HomebridgePluginUiServer { constructor() { var _a, _b, _c, _d, _e; super(); const events = new events_1.EventEmitter(); const plugin = 'homebridge-xfinityhome'; const platform = 'XfinityHomePlatform'; const storagePath = (_a = this.homebridgeStoragePath) !== null && _a !== void 0 ? _a : ''; const configPath = (_b = this.homebridgeConfigPath) !== null && _b !== void 0 ? _b : ''; const config = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8')).platforms.find((plugin) => plugin.platform === platform); /* A native method getCachedAccessories() was introduced in config-ui-x v4.37.0 The following is for users who have a lower version of config-ui-x */ const cachedAccessoriesDir = path_1.default.join(storagePath, '/accessories/cachedAccessories') + (((_c = config === null || config === void 0 ? void 0 : config._bridge) === null || _c === void 0 ? void 0 : _c.username) ? ('.' + ((_e = (_d = config === null || config === void 0 ? void 0 : config._bridge) === null || _d === void 0 ? void 0 : _d.username) === null || _e === void 0 ? void 0 : _e.split(':').join(''))) : ''); this.onRequest('/getCachedAccessories', async () => { try { // Define the plugin and create the array to return if ((0, fs_1.existsSync)(cachedAccessoriesDir)) { return JSON.parse((0, fs_1.readFileSync)(cachedAccessoriesDir, 'utf-8')).filter(accessory => accessory.plugin === plugin); } else { return []; } } catch (err) { // Just return an empty accessory list in case of any errors console.log(err); return []; } }); this.onRequest('/getGeneralLog', async () => { return path_1.default.join(storagePath, 'XfinityHome', 'General.log'); }); this.onRequest('/getLogs', async (payload) => { try { return (0, fs_1.readFileSync)(payload.logPath).toString().replace(/\n/g, '<br>'); } catch (err) { return `Failed To Load Logs From ${payload.logPath}`; } }); this.onRequest('/getRelativePath', (payload) => { return path_1.default.relative(path_1.default.join(__dirname, '/public/index.html'), payload.path); }); this.onRequest('/deleteLog', async (payload) => { try { return (0, fs_1.rmSync)(payload.logPath, { force: true }); } catch (err) { return err; } }); this.onRequest('/watchLog', async (payload) => { try { return await watchFilePromise(payload.path); } catch (err) { return Promise.reject(err); } }); const watchFilePromise = async (file) => { return new Promise((resolve, reject) => { if (!(0, fs_1.existsSync)(file)) { reject('File does not exist: ' + file); return; } try { const aborter = new AbortController(); const watcher = (0, fs_1.watch)(file, { signal: aborter.signal }); watcher.once('change', () => { aborter.abort(); resolve((0, fs_1.readFileSync)(file)); }); watcher.once('error', err => { console.error(err); aborter.abort(); (0, fs_1.watchFile)(file, () => { (0, fs_1.unwatchFile)(file); resolve((0, fs_1.readFileSync)(file)); }); }); } catch (_a) { (0, fs_1.watchFile)(file, () => { (0, fs_1.unwatchFile)(file); resolve((0, fs_1.readFileSync)(file)); }); } }); }; this.onRequest('/watchAccessory', async (payload) => { const loop = async () => { const oldFile = JSON.parse((0, fs_1.readFileSync)(cachedAccessoriesDir, 'utf-8')).filter(accessory => accessory.plugin === plugin); await watchFilePromise(cachedAccessoriesDir); const newFile = JSON.parse((0, fs_1.readFileSync)(cachedAccessoriesDir, 'utf-8')).filter(accessory => accessory.plugin === plugin); const oldAccessory = oldFile.find(accessory => accessory.UUID === payload.accessory.UUID); const newAccessory = newFile.find(accessory => accessory.UUID === payload.accessory.UUID); if (JSON.stringify(oldAccessory) !== JSON.stringify(newAccessory)) { return newAccessory; } else { loop(); } }; return loop(); }); this.onRequest('/proxyActive', async () => { return new Promise((resolve) => { events.on('proxy', () => resolve('')); }); }); /*this.onRequest('/sslActive', async () => { return new Promise((resolve) => { events.on('ssl', () => resolve()); }); });*/ this.onRequest('/token', async () => { return new Promise((resolve) => { events.on('token', token => resolve(token)); }); }); this.onRequest('/startProxy', async () => { // Disable debug messages from the proxy try { debug_1.default.disable(); } catch (err) { //Do nothing } const ROOT = path_1.default.join(storagePath, 'XfinityHome'); if (!(0, fs_1.existsSync)(ROOT)) { (0, fs_1.mkdirSync)(ROOT); } const pemFile = path_1.default.join(ROOT, 'certs', 'ca.pem'); const localIPs = []; const ifaces = os_1.default.networkInterfaces(); Object.keys(ifaces).forEach(name => { var _a; (_a = ifaces[name]) === null || _a === void 0 ? void 0 : _a.forEach(network => { const familyV4Value = typeof network.family === 'string' ? 'IPv4' : 4; if (network.family === familyV4Value && !network.internal) { localIPs.push(network.address); } }); }); localIPs.push(os_1.default.hostname() + os_1.default.hostname().endsWith('.local') ? '' : '.local'); const proxy = new http_mitm_proxy_1.Proxy(); const localIPPorts = localIPs.map(ip => `${ip}:${585}`); proxy.onError((ctx, err) => { switch (err === null || err === void 0 ? void 0 : err.name) { case 'ERR_STREAM_DESTROYED': case 'ECONNRESET': return; case 'ECONNREFUSED': console.error('Failed to intercept secure communications. This could happen due to bad CA certificate.'); return; case 'EACCES': console.error(`Permission was denied to use port ${585}.`); return; default: //console.error('Error:', err.code, err); } }); proxy.onRequest((ctx, callback) => { var _a; if (ctx.clientToProxyRequest.method === 'GET' && ctx.clientToProxyRequest.url === '/cert' && localIPPorts.includes((_a = ctx.clientToProxyRequest.headers.host) !== null && _a !== void 0 ? _a : '')) { ctx.use(http_mitm_proxy_1.Proxy.gunzip); console.log('Intercepted certificate request'); ctx.proxyToClientResponse.writeHead(200, { 'Accept-Ranges': 'bytes', 'Cache-Control': 'public, max-age=0', 'Content-Type': 'application/x-x509-ca-cert', 'Content-Disposition': 'attachment; filename=cert.pem', 'Content-Transfer-Encoding': 'binary', 'Content-Length': (0, fs_1.statSync)(pemFile).size, 'Connection': 'keep-alive', }); //ctx.proxyToClientResponse.end(fs.readFileSync(path.join(ROOT, 'certs', 'ca.pem'))); ctx.proxyToClientResponse.write((0, fs_1.readFileSync)(pemFile)); ctx.proxyToClientResponse.end(); return; } else if (ctx.clientToProxyRequest.method === 'POST' && ctx.clientToProxyRequest.headers.host === 'oauth.xfinity.com' && ctx.clientToProxyRequest.url === '/oauth/token') { ctx.use(http_mitm_proxy_1.Proxy.gunzip); ctx.onRequestData((ctx, chunk, callback) => { return callback(undefined, chunk); }); ctx.onRequestEnd((ctx, callback) => { callback(); }); const chunks = []; ctx.onResponseData((ctx, chunk, callback) => { chunks.push(chunk); return callback(undefined, chunk); }); ctx.onResponseEnd((ctx, callback) => { events.emit('token', JSON.parse(Buffer.concat(chunks).toString()).refresh_token); //token = JSON.parse(Buffer.concat(chunks).toString()).refresh_token; //this.pushEvent('token', { refreshToken: JSON.parse(Buffer.concat(chunks).toString()).refresh_token }); //emitter.emit('tuya-config', Buffer.concat(chunks).toString()); callback(); }); } else { //this.pushEvent('proxy', {}); events.emit('proxy'); /*ctx.onRequestData(function (ctx, chunk, callback) { ctx.onResponseData(function (ctx, chunk, callback) { //this.pushEvent('sslProxy', {}); events.emit('ssl'); }); });*/ } return callback(); }); /*emitter.on('tuya-config', body => { //if (body.indexOf('tuya.m.my.group.device.list') === -1) return; console.log('Intercepted token from Xfinity Home'); try { console.log('Your refresh token is: ' + JSON.parse(body).refresh_token); } catch (err) { console.error(err); } });*/ this.onRequest('/stopProxy', () => { if (proxy && typeof proxy.close === 'function') { proxy.close(); } if ((0, fs_1.existsSync)(path_1.default.join(ROOT, 'certs'))) { (0, fs_1.rmSync)(path_1.default.join(ROOT, 'certs'), { recursive: true, force: true }); } if ((0, fs_1.existsSync)(path_1.default.join(ROOT, 'keys'))) { (0, fs_1.rmSync)(path_1.default.join(ROOT, 'keys'), { recursive: true, force: true }); } return ''; }); return new Promise((resolve) => { proxy.listen({ host: '::', port: 585, sslCaDir: ROOT }, async (err) => { if (err) { console.error('Error starting proxy: ' + err); } const address = localIPs[0]; const port = 585; qrcode_1.default.toString(`http://${address}:${port}/cert`, { type: 'svg' }) .then(qrcode => resolve({ ip: address, port: port, qrcode: qrcode })); }); }); }); this.ready(); } } (() => new PluginUiServer())(); //# sourceMappingURL=server.js.map