homebridge-config-ui-x
Version:
A web based management, configuration and control platform for Homebridge
1,454 lines (1,310 loc) • 1.43 MB
JavaScript
(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 71);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
module.exports = require("@nestjs/common");
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = __webpack_require__(0);
const os = __webpack_require__(8);
const path = __webpack_require__(7);
const fs = __webpack_require__(4);
const crypto = __webpack_require__(20);
const semver = __webpack_require__(26);
let ConfigService = class ConfigService {
constructor() {
this.name = 'homebridge-config-ui-x';
this.configPath = process.env.UIX_CONFIG_PATH || path.resolve(os.homedir(), '.homebridge/config.json');
this.storagePath = process.env.UIX_STORAGE_PATH || path.resolve(os.homedir(), '.homebridge');
this.customPluginPath = process.env.UIX_CUSTOM_PLUGIN_PATH;
this.secretPath = path.resolve(this.storagePath, '.uix-secrets');
this.authPath = path.resolve(this.storagePath, 'auth.json');
this.accessoryLayoutPath = path.resolve(this.storagePath, 'accessories', 'uiAccessoriesLayout.json');
this.homebridgeInsecureMode = Boolean(process.env.UIX_INSECURE_MODE === '1');
this.homebridgeNoTimestamps = Boolean(process.env.UIX_LOG_NO_TIMESTAMPS === '1');
this.minimumNodeVersion = '8.15.1';
this.serviceMode = (process.env.UIX_SERVICE_MODE === '1');
this.runningInDocker = Boolean(process.env.HOMEBRIDGE_CONFIG_UI === '1');
this.runningInLinux = (!this.runningInDocker && os.platform() === 'linux');
this.ableToConfigureSelf = (!this.runningInDocker || semver.satisfies(process.env.CONFIG_UI_VERSION, '>=3.5.5'), { includePrerelease: true });
this.enableTerminalAccess = this.runningInDocker || Boolean(process.env.HOMEBRIDGE_CONFIG_UI_TERMINAL === '1');
this.branding = process.env.CONFIG_UI_BRANDING || false;
this.startupScript = path.resolve(this.storagePath, 'startup.sh');
this.dockerEnvFile = path.resolve(this.storagePath, '.docker.env');
this.dockerOfflineUpdate = this.runningInDocker && semver.satisfies(process.env.CONFIG_UI_VERSION, '>=4.6.2', { includePrerelease: true });
this.package = fs.readJsonSync(path.resolve(process.env.UIX_BASE_PATH, 'package.json'));
this.homebridgeConfig = fs.readJSONSync(this.configPath);
this.ui = Array.isArray(this.homebridgeConfig.platforms) ? this.homebridgeConfig.platforms.find(x => x.platform === 'config') : undefined;
if (!this.ui) {
this.ui = {
name: 'Config',
};
}
process.env.UIX_PLUGIN_NAME = this.ui.name || 'homebridge-config-ui-x';
if (this.runningInDocker) {
this.setConfigForDocker();
}
if (this.serviceMode) {
this.setConfigForServiceMode();
}
if (!this.ui.port) {
this.ui.port = 8080;
}
if (!this.ui.sessionTimeout) {
this.ui.sessionTimeout = 28800;
}
this.secrets = this.getSecrets();
this.instanceId = this.getInstanceId();
}
uiSettings() {
return {
env: {
ableToConfigureSelf: this.ableToConfigureSelf,
enableAccessories: this.homebridgeInsecureMode,
enableTerminalAccess: this.enableTerminalAccess,
homebridgeInstanceName: this.homebridgeConfig.bridge.name,
nodeVersion: process.version,
packageName: this.package.name,
packageVersion: this.package.version,
runningInDocker: this.runningInDocker,
runningInLinux: this.runningInLinux,
dockerOfflineUpdate: this.dockerOfflineUpdate,
temperatureUnits: this.ui.tempUnits || 'c',
websocketCompatibilityMode: this.ui.websocketCompatibilityMode || false,
branding: this.branding,
instanceId: this.instanceId,
},
formAuth: Boolean(this.ui.auth !== 'none'),
theme: this.ui.theme || 'auto',
serverTimestamp: new Date().toISOString(),
};
}
setConfigForDocker() {
this.ui.restart = 'killall -9 homebridge && killall -9 homebridge-config-ui-x';
this.homebridgeInsecureMode = Boolean(process.env.HOMEBRIDGE_INSECURE === '1');
this.ui.sudo = false;
this.ui.log = {
method: 'file',
path: '/homebridge/logs/homebridge.log',
};
if (!this.ui.port && process.env.HOMEBRIDGE_CONFIG_UI_PORT) {
this.ui.port = parseInt(process.env.HOMEBRIDGE_CONFIG_UI_PORT, 10);
}
this.ui.theme = this.ui.theme || process.env.HOMEBRIDGE_CONFIG_UI_THEME || 'teal';
this.ui.auth = this.ui.auth || process.env.HOMEBRIDGE_CONFIG_UI_AUTH || 'form';
this.ui.temp = this.ui.temp || process.env.HOMEBRIDGE_CONFIG_UI_TEMP || undefined;
this.ui.loginWallpaper = this.ui.loginWallpaper || process.env.HOMEBRIDGE_CONFIG_UI_LOGIN_WALLPAPER || undefined;
}
setConfigForServiceMode() {
this.ui.restart = undefined;
this.homebridgeInsecureMode = true;
this.ui.log = {
method: 'file',
path: path.resolve(this.storagePath, 'homebridge.log'),
};
}
getSecrets() {
if (fs.pathExistsSync(this.secretPath)) {
try {
const secrets = fs.readJsonSync(this.secretPath);
if (!secrets.secretKey) {
return this.generateSecretToken();
}
else {
return secrets;
}
}
catch (e) {
return this.generateSecretToken();
}
}
else {
return this.generateSecretToken();
}
}
generateSecretToken() {
const secrets = {
secretKey: crypto.randomBytes(32).toString('hex'),
};
fs.writeJsonSync(this.secretPath, secrets);
return secrets;
}
getInstanceId() {
return crypto.createHash('sha256').update(this.secrets.secretKey).digest('hex');
}
};
ConfigService = __decorate([
common_1.Injectable(),
__metadata("design:paramtypes", [])
], ConfigService);
exports.ConfigService = ConfigService;
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
exports.__esModule = true;
__export(__webpack_require__(78));
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const color = __webpack_require__(14);
class Logger {
constructor() {
this.pluginName = (process.env.UIX_PLUGIN_NAME || 'homebridge-config-ui-x');
this.useTimestamps = (process.env.UIX_LOG_NO_TIMESTAMPS !== '1');
}
get prefix() {
if (this.useTimestamps) {
return color.white(`[${new Date().toLocaleString()}] `) + color.cyan(`[${this.pluginName}]`);
}
else {
return color.cyan(`[${this.pluginName}]`);
}
}
log(args) {
console.log(this.prefix, args);
}
error(args) {
console.error(this.prefix, color.red(args));
}
warn(args) {
console.warn(this.prefix, color.yellow(args));
}
debug(args) {
console.debug(this.prefix, args);
}
verbose(args) {
console.debug(this.prefix, args);
}
}
exports.Logger = Logger;
/***/ }),
/* 4 */
/***/ (function(module, exports) {
module.exports = require("fs-extra");
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = __webpack_require__(0);
const logger_service_1 = __webpack_require__(3);
let LoggerModule = class LoggerModule {
};
LoggerModule = __decorate([
common_1.Module({
providers: [logger_service_1.Logger],
exports: [logger_service_1.Logger],
})
], LoggerModule);
exports.LoggerModule = LoggerModule;
/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = __webpack_require__(0);
const config_service_1 = __webpack_require__(1);
let ConfigModule = class ConfigModule {
};
ConfigModule = __decorate([
common_1.Module({
providers: [config_service_1.ConfigService],
exports: [config_service_1.ConfigService],
})
], ConfigModule);
exports.ConfigModule = ConfigModule;
/***/ }),
/* 7 */
/***/ (function(module, exports) {
module.exports = require("path");
/***/ }),
/* 8 */
/***/ (function(module, exports) {
module.exports = require("os");
/***/ }),
/* 9 */
/***/ (function(module, exports) {
module.exports = require("util");
/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = __webpack_require__(0);
let AdminGuard = class AdminGuard {
constructor() { }
canActivate(context) {
const request = context.switchToHttp().getRequest();
return request.user.admin;
}
};
AdminGuard = __decorate([
common_1.Injectable(),
__metadata("design:paramtypes", [])
], AdminGuard);
exports.AdminGuard = AdminGuard;
/***/ }),
/* 11 */
/***/ (function(module, exports) {
module.exports = require("buffer");
/***/ }),
/* 12 */
/***/ (function(module, exports) {
module.exports = require("@nestjs/websockets");
/***/ }),
/* 13 */
/***/ (function(module, exports, __webpack_require__) {
/* eslint-disable node/no-deprecated-api */
var buffer = __webpack_require__(11)
var Buffer = buffer.Buffer
// alternative to using Object.keys for old browsers
function copyProps (src, dst) {
for (var key in src) {
dst[key] = src[key]
}
}
if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) {
module.exports = buffer
} else {
// Copy properties from require('buffer')
copyProps(buffer, exports)
exports.Buffer = SafeBuffer
}
function SafeBuffer (arg, encodingOrOffset, length) {
return Buffer(arg, encodingOrOffset, length)
}
// Copy static methods from Buffer
copyProps(Buffer, SafeBuffer)
SafeBuffer.from = function (arg, encodingOrOffset, length) {
if (typeof arg === 'number') {
throw new TypeError('Argument must not be a number')
}
return Buffer(arg, encodingOrOffset, length)
}
SafeBuffer.alloc = function (size, fill, encoding) {
if (typeof size !== 'number') {
throw new TypeError('Argument must be a number')
}
var buf = Buffer(size)
if (fill !== undefined) {
if (typeof encoding === 'string') {
buf.fill(fill, encoding)
} else {
buf.fill(fill)
}
} else {
buf.fill(0)
}
return buf
}
SafeBuffer.allocUnsafe = function (size) {
if (typeof size !== 'number') {
throw new TypeError('Argument must be a number')
}
return Buffer(size)
}
SafeBuffer.allocUnsafeSlow = function (size) {
if (typeof size !== 'number') {
throw new TypeError('Argument must be a number')
}
return buffer.SlowBuffer(size)
}
/***/ }),
/* 14 */
/***/ (function(module, exports) {
var bash_codes = exports.bash_codes = {
"BLACK" : {
"text" : "\033[0;30m",
"underline": "\033[4;30m",
"background": "\033[40m",
"bold":"\033[1;30m",
"hi_text":"\033[0;90m",
"hi_bold" : "\033[1;90m",
"hi_background" : "\033[0;100m"
},
"RED" : {
"text" : "\033[0;31m",
"bold":"\033[1;31m",
"underline": "\033[4;31m",
"background": "\033[41m",
"hi_text":"\033[0;91m",
"hi_bold" : "\033[1;91m",
"hi_background" : "\033[0;101m"
},
"GREEN" : {
"text" : "\033[0;32m",
"bold":"\033[1;32m",
"underline": "\033[4;32m",
"background": "\033[42m",
"hi_text":"\033[0;92m",
"hi_bold" : "\033[1;92m",
"hi_background" : "\033[0;102m"
},
"YELLOW" : {
"text" : "\033[0;33m",
"bold":"\033[1;33m",
"underline": "\033[4;33m",
"background": "\033[43m",
"hi_text":"\033[0;93m",
"hi_bold" : "\033[1;93m",
"hi_background" : "\033[0;103m"
},
"BLUE" : {
"text" : "\033[0;34m",
"bold":"\033[1;34m",
"underline": "\033[4;34m",
"background": "\033[44m",
"hi_text":"\033[0;94m",
"hi_bold" : "\033[1;94m",
"hi_background" : "\033[0;104m"
},
"PURPLE" : {
"text" : "\033[0;35m",
"bold":"\033[1;35m",
"underline": "\033[4;35m",
"background": "\033[45m",
"hi_text":"\033[0;95m",
"hi_bold" : "\033[1;95m",
"hi_background" : "\033[0;105m"
},
"CYAN" : {
"text" : "\033[0;36m",
"bold":"\033[1;36m",
"underline": "\033[4;36m",
"background": "\033[46m",
"hi_text":"\033[0;96m",
"hi_bold" : "\033[1;96m",
"hi_background" : "\033[0;106m"
},
"WHITE" : {
"text" : "\033[0;37m",
"bold":"\033[1;37m",
"underline": "\033[4;37m",
"background": "\033[47m",
"hi_text":"\033[0;97m",
"hi_bold" : "\033[1;97m",
"hi_background" : "\033[0;107m"
}
};
exports.colors = {
BLACK: "BLACK",
RED: "RED",
GREEN: "GREEN",
YELLOW: "YELLOW",
BLUE: "BLUE",
PURPLE: "PURPLE",
CYAN: "CYAN",
WHITE: "WHITE"
};
var styles = exports.styles = {
bold: "bold",
underline: "underline",
background: "background",
hi_text: "hi_text",
hi_bold: "hi_bold",
hi_background: "hi_background"
};
var REMOVE_COLOR = exports.REMOVE_COLOR = "\033[0m";
// various logical inconsistencies in the code below - renderColor and wrap seem like they should be combined, but I'm letting wrap basically stand on its own
// in case anyone wants access to explicitly handle background or underline stuff. I feel like those are a bit more special-casey, and generally speakign
// users are going to want to quickly turn a word or phrase into a single color without worrying about background or underline. So the .colorName methods
// are just syntactic sugar.
exports.wrap = function(str, color, style) {
var c = bash_codes[color.toUpperCase()];
var s = styles[style] || "text";
return render(c[s], str);
};
exports.black = function(str, hi) {
return renderColor(str, bash_codes.BLACK, hi);
};
exports.red = function(str, hi) {
return renderColor(str, bash_codes.RED, hi);
};
exports.green = function(str, hi) {
return renderColor(str, bash_codes.GREEN, hi);
};
exports.yellow = function(str, hi) {
return renderColor(str, bash_codes.YELLOW, hi);
};
exports.blue = function(str, hi) {
return renderColor(str, bash_codes.BLUE, hi);
};
exports.purple = function(str, hi) {
return renderColor(str, bash_codes.PURPLE, hi);
};
exports.cyan = function(str, hi) {
return renderColor(str, bash_codes.CYAN, hi);
};
exports.white = function(str, hi) {
return renderColor(str, bash_codes.WHITE, hi);
};
function renderColor(str, color, hi) {
return render(hi ? color.hi_text : color.text, str);
}
function render(code, str) {
return code + str + REMOVE_COLOR;
}
/***/ }),
/* 15 */
/***/ (function(module, exports) {
module.exports = require("child_process");
/***/ }),
/* 16 */
/***/ (function(module, exports, __webpack_require__) {
module.exports = {
decode: __webpack_require__(45),
verify: __webpack_require__(103),
sign: __webpack_require__(105),
JsonWebTokenError: __webpack_require__(23),
NotBeforeError: __webpack_require__(49),
TokenExpiredError: __webpack_require__(50),
};
/***/ }),
/* 17 */
/***/ (function(module, exports) {
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/**
* This is a helper function for getting values from parameter/options
* objects.
*
* @param args The object we are extracting values from
* @param name The name of the property we are getting.
* @param defaultValue An optional value to return if the property is missing
* from the object. If this is not specified and the property is missing, an
* error will be thrown.
*/
function getArg(aArgs, aName, aDefaultValue) {
if (aName in aArgs) {
return aArgs[aName];
} else if (arguments.length === 3) {
return aDefaultValue;
} else {
throw new Error('"' + aName + '" is a required argument.');
}
}
exports.getArg = getArg;
var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/;
var dataUrlRegexp = /^data:.+\,.+$/;
function urlParse(aUrl) {
var match = aUrl.match(urlRegexp);
if (!match) {
return null;
}
return {
scheme: match[1],
auth: match[2],
host: match[3],
port: match[4],
path: match[5]
};
}
exports.urlParse = urlParse;
function urlGenerate(aParsedUrl) {
var url = '';
if (aParsedUrl.scheme) {
url += aParsedUrl.scheme + ':';
}
url += '//';
if (aParsedUrl.auth) {
url += aParsedUrl.auth + '@';
}
if (aParsedUrl.host) {
url += aParsedUrl.host;
}
if (aParsedUrl.port) {
url += ":" + aParsedUrl.port
}
if (aParsedUrl.path) {
url += aParsedUrl.path;
}
return url;
}
exports.urlGenerate = urlGenerate;
/**
* Normalizes a path, or the path portion of a URL:
*
* - Replaces consecutive slashes with one slash.
* - Removes unnecessary '.' parts.
* - Removes unnecessary '<dir>/..' parts.
*
* Based on code in the Node.js 'path' core module.
*
* @param aPath The path or url to normalize.
*/
function normalize(aPath) {
var path = aPath;
var url = urlParse(aPath);
if (url) {
if (!url.path) {
return aPath;
}
path = url.path;
}
var isAbsolute = exports.isAbsolute(path);
var parts = path.split(/\/+/);
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
part = parts[i];
if (part === '.') {
parts.splice(i, 1);
} else if (part === '..') {
up++;
} else if (up > 0) {
if (part === '') {
// The first part is blank if the path is absolute. Trying to go
// above the root is a no-op. Therefore we can remove all '..' parts
// directly after the root.
parts.splice(i + 1, up);
up = 0;
} else {
parts.splice(i, 2);
up--;
}
}
}
path = parts.join('/');
if (path === '') {
path = isAbsolute ? '/' : '.';
}
if (url) {
url.path = path;
return urlGenerate(url);
}
return path;
}
exports.normalize = normalize;
/**
* Joins two paths/URLs.
*
* @param aRoot The root path or URL.
* @param aPath The path or URL to be joined with the root.
*
* - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
* scheme-relative URL: Then the scheme of aRoot, if any, is prepended
* first.
* - Otherwise aPath is a path. If aRoot is a URL, then its path portion
* is updated with the result and aRoot is returned. Otherwise the result
* is returned.
* - If aPath is absolute, the result is aPath.
* - Otherwise the two paths are joined with a slash.
* - Joining for example 'http://' and 'www.example.com' is also supported.
*/
function join(aRoot, aPath) {
if (aRoot === "") {
aRoot = ".";
}
if (aPath === "") {
aPath = ".";
}
var aPathUrl = urlParse(aPath);
var aRootUrl = urlParse(aRoot);
if (aRootUrl) {
aRoot = aRootUrl.path || '/';
}
// `join(foo, '//www.example.org')`
if (aPathUrl && !aPathUrl.scheme) {
if (aRootUrl) {
aPathUrl.scheme = aRootUrl.scheme;
}
return urlGenerate(aPathUrl);
}
if (aPathUrl || aPath.match(dataUrlRegexp)) {
return aPath;
}
// `join('http://', 'www.example.com')`
if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
aRootUrl.host = aPath;
return urlGenerate(aRootUrl);
}
var joined = aPath.charAt(0) === '/'
? aPath
: normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
if (aRootUrl) {
aRootUrl.path = joined;
return urlGenerate(aRootUrl);
}
return joined;
}
exports.join = join;
exports.isAbsolute = function (aPath) {
return aPath.charAt(0) === '/' || urlRegexp.test(aPath);
};
/**
* Make a path relative to a URL or another path.
*
* @param aRoot The root path or URL.
* @param aPath The path or URL to be made relative to aRoot.
*/
function relative(aRoot, aPath) {
if (aRoot === "") {
aRoot = ".";
}
aRoot = aRoot.replace(/\/$/, '');
// It is possible for the path to be above the root. In this case, simply
// checking whether the root is a prefix of the path won't work. Instead, we
// need to remove components from the root one by one, until either we find
// a prefix that fits, or we run out of components to remove.
var level = 0;
while (aPath.indexOf(aRoot + '/') !== 0) {
var index = aRoot.lastIndexOf("/");
if (index < 0) {
return aPath;
}
// If the only part of the root that is left is the scheme (i.e. http://,
// file:///, etc.), one or more slashes (/), or simply nothing at all, we
// have exhausted all components, so the path is not relative to the root.
aRoot = aRoot.slice(0, index);
if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
return aPath;
}
++level;
}
// Make sure we add a "../" for each component we removed from the root.
return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
}
exports.relative = relative;
var supportsNullProto = (function () {
var obj = Object.create(null);
return !('__proto__' in obj);
}());
function identity (s) {
return s;
}
/**
* Because behavior goes wacky when you set `__proto__` on objects, we
* have to prefix all the strings in our set with an arbitrary character.
*
* See https://github.com/mozilla/source-map/pull/31 and
* https://github.com/mozilla/source-map/issues/30
*
* @param String aStr
*/
function toSetString(aStr) {
if (isProtoString(aStr)) {
return '$' + aStr;
}
return aStr;
}
exports.toSetString = supportsNullProto ? identity : toSetString;
function fromSetString(aStr) {
if (isProtoString(aStr)) {
return aStr.slice(1);
}
return aStr;
}
exports.fromSetString = supportsNullProto ? identity : fromSetString;
function isProtoString(s) {
if (!s) {
return false;
}
var length = s.length;
if (length < 9 /* "__proto__".length */) {
return false;
}
if (s.charCodeAt(length - 1) !== 95 /* '_' */ ||
s.charCodeAt(length - 2) !== 95 /* '_' */ ||
s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
s.charCodeAt(length - 4) !== 116 /* 't' */ ||
s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
s.charCodeAt(length - 8) !== 95 /* '_' */ ||
s.charCodeAt(length - 9) !== 95 /* '_' */) {
return false;
}
for (var i = length - 10; i >= 0; i--) {
if (s.charCodeAt(i) !== 36 /* '$' */) {
return false;
}
}
return true;
}
/**
* Comparator between two mappings where the original positions are compared.
*
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
* mappings with the same original source/line/column, but different generated
* line and column the same. Useful when searching for a mapping with a
* stubbed out mapping.
*/
function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
var cmp = strcmp(mappingA.source, mappingB.source);
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalColumn - mappingB.originalColumn;
if (cmp !== 0 || onlyCompareOriginal) {
return cmp;
}
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp !== 0) {
return cmp;
}
return strcmp(mappingA.name, mappingB.name);
}
exports.compareByOriginalPositions = compareByOriginalPositions;
/**
* Comparator between two mappings with deflated source and name indices where
* the generated positions are compared.
*
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
* mappings with the same generated line and column, but different
* source/name/original line and column the same. Useful when searching for a
* mapping with a stubbed out mapping.
*/
function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
var cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if (cmp !== 0 || onlyCompareGenerated) {
return cmp;
}
cmp = strcmp(mappingA.source, mappingB.source);
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalColumn - mappingB.originalColumn;
if (cmp !== 0) {
return cmp;
}
return strcmp(mappingA.name, mappingB.name);
}
exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
function strcmp(aStr1, aStr2) {
if (aStr1 === aStr2) {
return 0;
}
if (aStr1 === null) {
return 1; // aStr2 !== null
}
if (aStr2 === null) {
return -1; // aStr1 !== null
}
if (aStr1 > aStr2) {
return 1;
}
return -1;
}
/**
* Comparator between two mappings with inflated source and name strings where
* the generated positions are compared.
*/
function compareByGeneratedPositionsInflated(mappingA, mappingB) {
var cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if (cmp !== 0) {
return cmp;
}
cmp = strcmp(mappingA.source, mappingB.source);
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalColumn - mappingB.originalColumn;
if (cmp !== 0) {
return cmp;
}
return strcmp(mappingA.name, mappingB.name);
}
exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
/**
* Strip any JSON XSSI avoidance prefix from the string (as documented
* in the source maps specification), and then parse the string as
* JSON.
*/
function parseSourceMapInput(str) {
return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, ''));
}
exports.parseSourceMapInput = parseSourceMapInput;
/**
* Compute the URL of a source given the the source root, the source's
* URL, and the source map's URL.
*/
function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) {
sourceURL = sourceURL || '';
if (sourceRoot) {
// This follows what Chrome does.
if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') {
sourceRoot += '/';
}
// The spec says:
// Line 4: An optional source root, useful for relocating source
// files on a server or removing repeated values in the
// “sources” entry. This value is prepended to the individual
// entries in the “source” field.
sourceURL = sourceRoot + sourceURL;
}
// Historically, SourceMapConsumer did not take the sourceMapURL as
// a parameter. This mode is still somewhat supported, which is why
// this code block is conditional. However, it's preferable to pass
// the source map URL to SourceMapConsumer, so that this function
// can implement the source URL resolution algorithm as outlined in
// the spec. This block is basically the equivalent of:
// new URL(sourceURL, sourceMapURL).toString()
// ... except it avoids using URL, which wasn't available in the
// older releases of node still supported by this library.
//
// The spec says:
// If the sources are not absolute URLs after prepending of the
// “sourceRoot”, the sources are resolved relative to the
// SourceMap (like resolving script src in a html document).
if (sourceMapURL) {
var parsed = urlParse(sourceMapURL);
if (!parsed) {
throw new Error("sourceMapURL could not be parsed");
}
if (parsed.path) {
// Strip the last path component, but keep the "/".
var index = parsed.path.lastIndexOf('/');
if (index >= 0) {
parsed.path = parsed.path.substring(0, index + 1);
}
}
sourceURL = join(urlGenerate(parsed), sourceURL);
}
return normalize(sourceURL);
}
exports.computeSourceURL = computeSourceURL;
/***/ }),
/* 18 */
/***/ (function(module, exports) {
module.exports = require("events");
/***/ }),
/* 19 */
/***/ (function(module, exports) {
module.exports = require("node-pty-prebuilt-multiarch");
/***/ }),
/* 20 */
/***/ (function(module, exports) {
module.exports = require("crypto");
/***/ }),
/* 21 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = __webpack_require__(0);
const os = __webpack_require__(8);
const _ = __webpack_require__(94);
const path = __webpack_require__(7);
const fs = __webpack_require__(4);
const child_process = __webpack_require__(15);
const semver = __webpack_require__(26);
const rp = __webpack_require__(30);
const color = __webpack_require__(14);
const pty = __webpack_require__(19);
const logger_service_1 = __webpack_require__(3);
const config_service_1 = __webpack_require__(1);
let PluginsService = class PluginsService {
constructor(configService, logger) {
this.configService = configService;
this.logger = logger;
this.npm = this.getNpmPath();
this.paths = this.getBasePaths();
this.rp = rp.defaults({
json: true,
headers: {
'User-Agent': this.configService.package.name,
},
timeout: 5000,
});
}
getInstalledPlugins() {
return __awaiter(this, void 0, void 0, function* () {
const plugins = [];
const modules = yield this.getInstalledModules();
const homebridgePlugins = modules
.filter(module => (module.name.indexOf('homebridge-') === 0))
.filter((module) => __awaiter(this, void 0, void 0, function* () { return (yield fs.pathExists(path.join(module.installPath, 'package.json')).catch(x => null)); }))
.filter(x => x);
yield Promise.all(homebridgePlugins.map((pkg) => __awaiter(this, void 0, void 0, function* () {
try {
const pjson = yield fs.readJson(path.join(pkg.installPath, 'package.json'));
if (pjson.keywords && pjson.keywords.includes('homebridge-plugin')) {
const plugin = yield this.parsePackageJson(pjson, pkg.path);
if (!plugins.find(x => plugin.name === x.name)) {
plugins.push(plugin);
}
else if (!plugin.globalInstall && plugins.find(x => plugin.name === x.name && x.globalInstall === true)) {
const index = plugins.findIndex(x => plugin.name === x.name && x.globalInstall === true);
plugins[index] = plugin;
}
}
}
catch (e) {
this.logger.error(`Failed to parse plugin "${pkg.name}": ${e.message}`);
}
})));
this.installedPlugins = plugins;
return _.orderBy(plugins, ['updateAvailable', 'name'], ['desc', 'asc']);
});
}
getOutOfDatePlugins() {
return __awaiter(this, void 0, void 0, function* () {
const plugins = yield this.getInstalledPlugins();
return plugins.filter(x => x.updateAvailable);
});
}
searchNpmRegistry(query) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.installedPlugins) {
yield this.getInstalledPlugins();
}
const q = ((!query || !query.length) ? '' : query + '+') + 'keywords:homebridge-plugin+not:deprecated&size=30';
const searchResults = yield this.rp.get(`https://registry.npmjs.org/-/v1/search?text=${q}`);
const result = searchResults.objects
.filter(x => x.package.name.indexOf('homebridge-') === 0)
.map((pkg) => {
let plugin = {
name: pkg.package.name,
};
const isInstalled = this.installedPlugins.find(x => x.name === plugin.name);
if (isInstalled) {
plugin = isInstalled;
return plugin;
}
plugin.publicPackage = true;
plugin.installedVersion = null;
plugin.latestVersion = pkg.package.version;
plugin.lastUpdated = pkg.package.date;
plugin.description = (pkg.package.description) ?
pkg.package.description.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '').trim() : pkg.package.name;
plugin.links = pkg.package.links;
plugin.author = (pkg.package.publisher) ? pkg.package.publisher.username : null;
plugin.certifiedPlugin = (pkg.package.name.indexOf('@homebridge/homebridge-') === 0);
return plugin;
});
if (!result.length && query.indexOf('homebridge-') === 0) {
return yield this.searchNpmRegistrySingle(query);
}
return result;
});
}
searchNpmRegistrySingle(query) {
return __awaiter(this, void 0, void 0, function* () {
try {
const pkg = yield this.rp.get(`https://registry.npmjs.org/${encodeURIComponent(query).replace('%40', '@')}`);
if (!pkg.keywords || !pkg.keywords.includes('homebridge-plugin')) {
return [];
}
let plugin;
const isInstalled = this.installedPlugins.find(x => x.name === pkg.name);
if (isInstalled) {
plugin = isInstalled;
return [plugin];
}
plugin = {
name: pkg.name,
description: (pkg.description) ?
pkg.description.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '').trim() : pkg.name,
certifiedPlugin: (pkg.name.indexOf('@homebridge/homebridge-') === 0),
};
plugin.publicPackage = true;
plugin.latestVersion = pkg['dist-tags'].latest;
plugin.lastUpdated = pkg.package.date;
plugin.updateAvailable = false;
plugin.links = {
npm: `https://www.npmjs.com/package/${plugin.name}`,
homepage: pkg.homepage,
bugs: (pkg.bugs) ? pkg.bugs.url : null,
};
plugin.author = (pkg.maintainers.length) ? pkg.maintainers[0].name : null;
plugin.certifiedPlugin = (pkg.name.indexOf('@homebridge/homebridge-') === 0);
return [plugin];
}
catch (e) {
if (e.statusCode !== 404) {
this.logger.error('Failed to search npm registry');
this.logger.error(e.message);
}
return [];
}
});
}
installPlugin(pluginName, client) {
return __awaiter(this, void 0, void 0, function* () {
yield this.getInstalledPlugins();
let installPath = (this.configService.customPluginPath) ?
this.configService.customPluginPath : this.installedPlugins.find(x => x.name === this.configService.name).installPath;
const installOptions = [];
if (installPath === this.configService.customPluginPath && (yield fs.pathExists(path.resolve(installPath, '../package.json')))) {
installOptions.push('--save');
}
installPath = path.resolve(installPath, '../');
yield this.runNpmCommand([...this.npm, 'install', '--unsafe-perm', ...installOptions, `${pluginName}@latest`], installPath, client);
return true;
});
}
uninstallPlugin(pluginName, client) {
return __awaiter(this, void 0, void 0, function* () {
yield this.getInstalledPlugins();
const plugin = this.installedPlugins.find(x => x.name === pluginName);
if (!plugin) {
throw new Error(`Plugin "${pluginName}" Not Found`);
}
let installPath = plugin.installPath;
const installOptions = [];
if (installPath === this.configService.customPluginPath && (yield fs.pathExists(path.resolve(installPath, '../package.json')))) {
installOptions.push('--save');
}
installPath = path.resolve(installPath, '../');
yield this.runNpmCommand([...this.npm, 'uninstall', '--unsafe-perm', ...installOptions, pluginName], installPath, client);
yield this.ensureCustomPluginDirExists();
return true;
});
}
updatePlugin(pluginName, client) {
return __awaiter(this, void 0, void 0, function* () {
if (pluginName === this.configService.name && this.configService.dockerOfflineUpdate) {
yield this.updateSelfOffline(client);
return true;
}
yield this.getInstalledPlugins();
const plugin = this.installedPlugins.find(x => x.name === pluginName);
if (!plugin) {
throw new Error(`Plugin "${pluginName}" Not Found`);
}
let installPath = plugin.installPath;
const installOptions = [];
if (installPath === this.configService.customPluginPath && (yield fs.pathExists(path.resolve(installPath, '../package.json')))) {
installOptions.push('--save');
}
installPath = path.resolve(installPath, '../');
yield this.runNpmCommand([...this.npm, 'install', '--unsafe-perm', ...installOptions, `${pluginName}@latest`], installPath, client);
return true;
});
}
getHomebridgePackage() {
return __awaiter(this, void 0, void 0, function* () {
if (this.configService.ui.homebridgePackagePath) {
const pjsonPath = path.join(this.configService.ui.homebridgePackagePath, 'package.json');
if (yield fs.pathExists(pjsonPath)) {
return yield this.parsePackageJson(yield fs.readJson(pjsonPath), this.configService.ui.homebridgePackagePath);
}
else {
this.logger.error(`"homebridgePath" (${this.configService.ui.homebridgePackagePath}) does not exist`);
}
}
const modules = yield this.getInstalledModules();
const homebridgeInstalls = modules.filter(x => x.name === 'homebridge');
if (homebridgeInstalls.length > 1) {
this.logger.warn('Multiple Instances Of Homebridge Found Installed');
homebridgeInstalls.forEach((instance) => {
this.logger.warn(instance.installPath);
});
}
if (!homebridgeInstalls.length) {
this.logger.error('Unable To Find Homebridge Installation');
throw new Error('Unable To Find Homebridge Installation');
}
const homebridgeModule = homebridgeInstalls[0];
const pjson = yield fs.readJson(path.join(homebridgeModule.installPath, 'package.json'));
const homebridge = yield this.parsePackageJson(pjson, homebridgeModule.path);
return homebridge;
});
}
updateHomebridgePackage(client) {
return __awaiter(this, void 0, void 0, function* () {
const homebridge = yield this.getHomebridgePackage();
let installPath = homebridge.installPath;
const installOptions = [];
if (installPath === this.configService.customPluginPath && (yield fs.pathExists(path.resolve(installPath, '../package.json')))) {
installOptions.push('--save');
}
installPath = path.resolve(installPath, '../');
yield this.runNpmCommand([...this.npm, 'install', '--unsafe-perm', ...installOptions, `${homebridge.name}@latest`], installPath, client);
return true;
});
}
updateSelfOffline(client) {
return __awaiter(this, void 0, void 0, function* () {
client.emit('stdout', color.yellow(`${this.configService.name} has been scheduled to update on the next container restart.\n\r\n\r`));
yield new Promise(resolve => setTimeout(resolve, 800));
client.emit('stdout', color.yellow(`The Docker container will now try and restart.\n\r\n\r`));
yield new Promise(resolve => setTimeout(resolve, 800));
client.emit('stdout', color.yellow(`If you have not started the Docker container with `) +
color.red('--restart=always') + color.yellow(` you may\n\rneed to manually start the container again.\n\r\n\r`));
yield new Promise(resolve => setTimeout(resolve, 800));
client.emit('stdout', color.yellow(`This process may take several minutes. Please be patient.\n\r`));
yield new Promise(resolve => setTimeout(resolve, 10000));
yield fs.createFile('/homebridge/.uix-upgrade-on-restart');
});
}
getPluginConfigSchema(pluginName) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.installedPlugins)
yield this.getInstalledPlugins();
const plugin = this.installedPlugins.find(x => x.name === pluginName);
if (!plugin) {
throw new common_1.NotFoundException();
}
if (!plugin.settingsSchema) {
throw new common_1.NotFoundException();
}
const schemaPath = path.resolve(plugin.installPath, pluginName, 'config.schema.json');
co