mirror of
https://github.com/mtan93/homebridge.git
synced 2026-03-08 21:02:38 +00:00
Convert to ES5, add web server
* No compilation step * Beginnings of web interface * Simple express server; React-based frontend * CommonJS style across codebase; auto-converts to RequireJS for browser * Using diffsync for realtime UI * "Provider" -> "Plugin" * Plugins expose one or more Providers
This commit is contained in:
184
lib/Plugin.js
Normal file
184
lib/Plugin.js
Normal file
@@ -0,0 +1,184 @@
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var semver = require('semver');
|
||||
var User = require('./user').User;
|
||||
var version = require('./version');
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
Plugin: Plugin
|
||||
}
|
||||
|
||||
/**
|
||||
* Homebridge Plugin.
|
||||
*
|
||||
* Allows for discovering and loading installed Homebridge plugins.
|
||||
*/
|
||||
|
||||
function Plugin(pluginPath) {
|
||||
this.pluginPath = pluginPath; // like "/usr/local/lib/node_modules/plugin-lockitron"
|
||||
this.providers = []; // the provider constructors pulled from the loaded plugin module
|
||||
}
|
||||
|
||||
Plugin.prototype.name = function() {
|
||||
return path.basename(this.pluginPath);
|
||||
}
|
||||
|
||||
Plugin.prototype.load = function(options) {
|
||||
options = options || {};
|
||||
|
||||
// does this plugin exist at all?
|
||||
if (!fs.existsSync(this.pluginPath)) {
|
||||
throw new Error("Plugin " + this.pluginPath + " was not found. Make sure the module '" + this.pluginPath + "' is installed.");
|
||||
}
|
||||
|
||||
// attempt to load package.json
|
||||
var pjson = Plugin.loadPackageJSON(this.pluginPath);
|
||||
|
||||
// pluck out the HomeBridge version requirement
|
||||
if (!pjson.peerDepdendencies || !pjson.peerDepdendencies.homebridge) {
|
||||
throw new Error("Plugin " + this.pluginPath + " does not contain the 'homebridge' package in 'peerDepdendencies'.");
|
||||
}
|
||||
|
||||
var versionRequired = pjson.peerDepdendencies.homebridge;
|
||||
|
||||
// make sure the version is satisfied by the currently running version of HomeBridge
|
||||
if (!semver.satisfies(version, versionRequired)) {
|
||||
throw new Error("Plugin " + this.pluginPath + " requires a HomeBridge version of " + versionRequired + " which does not satisfy the current HomeBridge version of " + version + ". You may need to upgrade your installation of HomeBridge.");
|
||||
}
|
||||
|
||||
// figure out the main module - index.js unless otherwise specified
|
||||
var main = pjson.main || "./index.js";
|
||||
|
||||
var mainPath = path.join(this.pluginPath, main);
|
||||
|
||||
// try to require() it
|
||||
var pluginModule = require(mainPath);
|
||||
|
||||
// pull out the configuration data, if any
|
||||
// var pluginConfig = loadedPlugin.config;
|
||||
//
|
||||
// // verify that all required values are present
|
||||
// if (pluginConfig && !options.skipConfigCheck) {
|
||||
// for (var key in pluginConfig) {
|
||||
//
|
||||
// var configParams = pluginConfig[key];
|
||||
//
|
||||
// if (configParams.required && !User.config().get(this.name() + '.' + key)) {
|
||||
// throw new Error("Plugin " + this.pluginPath + " requires the config value " + key + " to be set.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
this.providers = pluginModule.providers;
|
||||
}
|
||||
|
||||
Plugin.loadPackageJSON = function(pluginPath) {
|
||||
// check for a package.json
|
||||
var pjsonPath = path.join(pluginPath, "package.json");
|
||||
var pjson = null;
|
||||
|
||||
if (!fs.existsSync(pjsonPath)) {
|
||||
throw new Error("Plugin " + pluginPath + " does not contain a package.json.");
|
||||
}
|
||||
|
||||
try {
|
||||
// attempt to parse package.json
|
||||
pjson = JSON.parse(fs.readFileSync(pjsonPath));
|
||||
}
|
||||
catch (err) {
|
||||
throw new Error("Plugin " + pluginPath + " contains an invalid package.json. Error: " + err);
|
||||
}
|
||||
|
||||
// verify that it's tagged with the correct keyword
|
||||
if (!pjson.keywords || pjson.keywords.indexOf("homebridge-plugin") == -1) {
|
||||
throw new Error("Plugin " + pluginPath + " package.json does not contain the keyword 'homebridge-plugin'.");
|
||||
}
|
||||
|
||||
return pjson;
|
||||
}
|
||||
|
||||
Plugin.getDefaultPaths = function() {
|
||||
var win32 = process.platform === 'win32';
|
||||
var paths = [];
|
||||
|
||||
// add the paths used by require()
|
||||
paths = paths.concat(require.main.paths);
|
||||
|
||||
// THIS SECTION FROM: https://github.com/yeoman/environment/blob/master/lib/resolver.js
|
||||
|
||||
// Adding global npm directories
|
||||
// We tried using npm to get the global modules path, but it haven't work out
|
||||
// because of bugs in the parseable implementation of `ls` command and mostly
|
||||
// performance issues. So, we go with our best bet for now.
|
||||
if (process.env.NODE_PATH) {
|
||||
paths = process.env.NODE_PATH.split(path.delimiter)
|
||||
.filter(function(p) { return !!p; }) // trim out empty values
|
||||
.concat(paths);
|
||||
} else {
|
||||
// Default paths for each system
|
||||
if (win32) {
|
||||
paths.push(path.join(process.env.APPDATA, 'npm/node_modules'));
|
||||
} else {
|
||||
paths.push('/usr/local/lib/node_modules');
|
||||
paths.push('/usr/lib/node_modules');
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
// All search paths we will use to discover installed plugins
|
||||
Plugin.paths = Plugin.getDefaultPaths();
|
||||
|
||||
Plugin.addPluginPath = function(pluginPath) {
|
||||
Plugin.paths.unshift(path.resolve(process.cwd(), pluginPath));
|
||||
}
|
||||
|
||||
// Gets all plugins installed on the local system
|
||||
Plugin.installed = function() {
|
||||
|
||||
var plugins = [];
|
||||
var pluginsByName = {}; // don't add duplicate plugins
|
||||
|
||||
// search for plugins among all known paths, in order
|
||||
for (var index in Plugin.paths) {
|
||||
var requirePath = Plugin.paths[index];
|
||||
|
||||
// just because this path is in require.main.paths doesn't mean it necessarily exists!
|
||||
if (!fs.existsSync(requirePath))
|
||||
continue;
|
||||
|
||||
var names = fs.readdirSync(requirePath);
|
||||
|
||||
// read through each directory in this node_modules folder
|
||||
for (var index2 in names) {
|
||||
var name = names[index2];
|
||||
|
||||
// reconstruct full path
|
||||
var pluginPath = path.join(requirePath, name);
|
||||
|
||||
// we only care about directories
|
||||
if (!fs.statSync(pluginPath).isDirectory()) continue;
|
||||
|
||||
// does this module contain a package.json?
|
||||
try {
|
||||
// throws an Error if this isn't a homebridge plugin
|
||||
Plugin.loadPackageJSON(pluginPath);
|
||||
}
|
||||
catch (err) {
|
||||
// swallow error and skip this module
|
||||
continue;
|
||||
}
|
||||
|
||||
// add it to the return list
|
||||
if (!pluginsByName[name]) {
|
||||
pluginsByName[name] = true;
|
||||
plugins.push(new Plugin(pluginPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
158
lib/cli.js
158
lib/cli.js
@@ -1,151 +1,17 @@
|
||||
import program from 'commander';
|
||||
import prompt from 'prompt';
|
||||
import { HOMEBRIDGE_VERSION } from './homebridge';
|
||||
import { User } from './user';
|
||||
import { Server } from './server';
|
||||
import { Provider } from './provider';
|
||||
import { log, setDebugEnabled } from './logger';
|
||||
import { camelCaseToRegularForm } from './util';
|
||||
var program = require('commander');
|
||||
var version = require('./version');
|
||||
var Server = require('./server').Server;
|
||||
var Plugin = require('./Plugin').Plugin;
|
||||
|
||||
export default function() {
|
||||
'use strict';
|
||||
|
||||
// Global options (none currently) and version printout
|
||||
program
|
||||
.version(HOMEBRIDGE_VERSION)
|
||||
.option('-D, --debug', 'turn on debug level logging', () => setDebugEnabled(true));
|
||||
|
||||
// Run the HomeBridge server
|
||||
program
|
||||
.command('server')
|
||||
.description('Run the HomeBridge server.')
|
||||
.action(runServer);
|
||||
|
||||
program
|
||||
.command('providers')
|
||||
.description('List installed providers.')
|
||||
.action(listInstalledProviders);
|
||||
module.exports = function() {
|
||||
|
||||
program
|
||||
.command('setup [provider]')
|
||||
.description('Sets up a new HomeBridge provider or re-configures an existing one.')
|
||||
.action((providerName, options) => new CliProviderSetup(providerName, options).setup());
|
||||
|
||||
// Parse options and execute HomeBridge
|
||||
program.parse(process.argv);
|
||||
|
||||
// Display help by default if no commands or options given
|
||||
if (!process.argv.slice(2).length) {
|
||||
program.help();
|
||||
}
|
||||
}
|
||||
|
||||
function runServer(options) {
|
||||
|
||||
// get all installed providers
|
||||
let providers = Provider.installed(); // array of Provider
|
||||
|
||||
// load and validate providers - check for valid package.json, etc.
|
||||
try {
|
||||
this.providerModules = providers.map((provider) => provider.load());
|
||||
}
|
||||
catch (err) {
|
||||
log.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function listInstalledProviders(options) {
|
||||
Provider.installed().forEach((provider) => log.info(provider.name));
|
||||
}
|
||||
|
||||
// Encapsulates configuring a provider via the command line.
|
||||
class CliProviderSetup {
|
||||
constructor(providerName, options) {
|
||||
|
||||
// if you didn't specify a provider, print help
|
||||
if (!providerName) {
|
||||
log.error("You must specify the name of the provider to setup. Type 'homebridge providers' to list the providers currently installed.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
this.providerName = providerName;
|
||||
this.options = options; // command-line options (currently none)
|
||||
}
|
||||
|
||||
setup() {
|
||||
try {
|
||||
let provider = new Provider(this.providerName);
|
||||
this.providerModule = provider.load({skipConfigCheck: true});
|
||||
|
||||
if (this.providerModule.config) {
|
||||
|
||||
prompt.message = "";
|
||||
prompt.delimiter = "";
|
||||
prompt.start();
|
||||
prompt.get(this.buildPromptSchema(), (err, result) => {
|
||||
|
||||
// add a linebreak after our last prompt
|
||||
console.log('');
|
||||
|
||||
// apply configuration values entered by the user
|
||||
for (let key in result) {
|
||||
let value = result[key];
|
||||
|
||||
User.config.set(`${this.providerName}.${key}`, value);
|
||||
}
|
||||
|
||||
this.validateProviderConfig();
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.validateProviderConfig();
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
log.error(`Setup failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
validateProviderConfig() {
|
||||
|
||||
let currentlyInsideValidateConfigCall = false;
|
||||
|
||||
// we allow for the provider's validateConfig to call our callback immediately/synchronously
|
||||
// from inside validateConfig() itself.
|
||||
let callback = (err) => {
|
||||
|
||||
if (!err) {
|
||||
log.info(`Setup complete.`);
|
||||
}
|
||||
else {
|
||||
log.error(`Setup failed: ${err.message || err}`);
|
||||
}
|
||||
}
|
||||
|
||||
currentlyInsideValidateConfigCall = true;
|
||||
this.providerModule.validateConfig(callback);
|
||||
currentlyInsideValidateConfigCall = false;
|
||||
}
|
||||
|
||||
// builds a "schema" obejct for the prompt lib based on the provider's config spec
|
||||
buildPromptSchema() {
|
||||
let properties = {};
|
||||
|
||||
for (let key in this.providerModule.config) {
|
||||
let spec = this.providerModule.config[key];
|
||||
|
||||
// do we have a value for this config key currently?
|
||||
let currentValue = User.config.get(`${this.providerName}.${key}`);
|
||||
|
||||
// copy over config spec with some modifications
|
||||
properties[key] = {
|
||||
description: `\n${spec.description}\n${camelCaseToRegularForm(key).white}:`,
|
||||
type: spec.type,
|
||||
required: spec.required,
|
||||
default: currentValue
|
||||
}
|
||||
}
|
||||
|
||||
return { properties };
|
||||
}
|
||||
.version(version)
|
||||
.option('-P, --plugin-path [path]', 'look for plugins installed at [path] as well as node_modules', function(p) { Plugin.addPluginPath(p); })
|
||||
.option('-D, --debug', 'turn on debug level logging', function() { logger.setDebugEnabled(true) })
|
||||
.parse(process.argv);
|
||||
|
||||
new Server().run();
|
||||
}
|
||||
|
||||
@@ -1,40 +1,49 @@
|
||||
import fs from 'fs';
|
||||
var fs = require('fs');
|
||||
|
||||
export class Config {
|
||||
|
||||
constructor(path, data = {}) {
|
||||
this.path = path;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
this.validateKey(key);
|
||||
let [providerName, keyName] = key.split(".");
|
||||
return this.data[providerName] && this.data[providerName][keyName];
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this.validateKey(key);
|
||||
let [providerName, keyName] = key.split(".");
|
||||
this.data[providerName] = this.data[providerName] || {};
|
||||
this.data[providerName][keyName] = value;
|
||||
this.save();
|
||||
}
|
||||
|
||||
validateKey(key) {
|
||||
if (key.split(".").length != 2)
|
||||
throw new Error(`The config key '${key}' is invalid. Configuration keys must be in the form [my-provider].[myKey]`);
|
||||
}
|
||||
|
||||
static load(configPath) {
|
||||
// load up the previous config if found
|
||||
if (fs.existsSync(configPath))
|
||||
return new Config(configPath, JSON.parse(fs.readFileSync(configPath)));
|
||||
else
|
||||
return new Config(configPath); // empty initial config
|
||||
}
|
||||
|
||||
save() {
|
||||
fs.writeFileSync(this.path, JSON.stringify(this.data, null, 2));
|
||||
}
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
Config: Config
|
||||
}
|
||||
|
||||
/**
|
||||
* API for plugins to manage their own configuration settings
|
||||
*/
|
||||
|
||||
function Config(path, data) {
|
||||
this.path = path;
|
||||
this.data = data || {};
|
||||
}
|
||||
|
||||
Config.prototype.get = function(key) {
|
||||
this._validateKey(key);
|
||||
var pluginName = key.split('.')[0];
|
||||
var keyName = key.split('.')[1];
|
||||
return this.data[pluginName] && this.data[pluginName][keyName];
|
||||
}
|
||||
|
||||
Config.prototype.set = function(key, value) {
|
||||
this._validateKey(key);
|
||||
var pluginName = key.split('.')[0];
|
||||
var keyName = key.split('.')[1];
|
||||
this.data[pluginName] = this.data[pluginName] || {};
|
||||
this.data[pluginName][keyName] = value;
|
||||
this.save();
|
||||
}
|
||||
|
||||
Config.prototype._validateKey = function(key) {
|
||||
if (key.split(".").length != 2)
|
||||
throw new Error("The config key '" + key + "' is invalid. Configuration keys must be in the form [my-plugin].[myKey]");
|
||||
}
|
||||
|
||||
Config.load = function(configPath) {
|
||||
// load up the previous config if found
|
||||
if (fs.existsSync(configPath))
|
||||
return new Config(configPath, JSON.parse(fs.readFileSync(configPath)));
|
||||
else
|
||||
return new Config(configPath); // empty initial config
|
||||
}
|
||||
|
||||
Config.prototype.save = function() {
|
||||
fs.writeFileSync(this.path, JSON.stringify(this.data, null, 2));
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import { User } from './user';
|
||||
import { Logger } from './logger';
|
||||
|
||||
//
|
||||
// Main HomeBridge Module with global exports.
|
||||
//
|
||||
|
||||
// HomeBridge version
|
||||
export const HOMEBRIDGE_VERSION = JSON.parse(fs.readFileSync('package.json')).version;
|
||||
|
||||
// HomeBridge API
|
||||
export let config = User.config; // instance of Config
|
||||
export let logger = Logger.forProvider; // logger('provider-name') -> instance of Logger
|
||||
102
lib/logger.js
102
lib/logger.js
@@ -1,58 +1,64 @@
|
||||
import chalk from 'chalk';
|
||||
var chalk = require('chalk');
|
||||
|
||||
let DEBUG_ENABLED = false;
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
Logger: Logger,
|
||||
setDebugEnabled: setDebugEnabled,
|
||||
_system: new Logger() // system logger, for internal use only
|
||||
}
|
||||
|
||||
var DEBUG_ENABLED = false;
|
||||
|
||||
// Turns on debug level logging
|
||||
export function setDebugEnabled(enabled) {
|
||||
function setDebugEnabled(enabled) {
|
||||
DEBUG_ENABLED = enabled;
|
||||
}
|
||||
|
||||
// global cache of logger instances by provider name
|
||||
let loggerCache = {};
|
||||
// global cache of logger instances by plugin name
|
||||
var loggerCache = {};
|
||||
|
||||
export class Logger {
|
||||
|
||||
constructor(providerName) {
|
||||
this.providerName = providerName;
|
||||
}
|
||||
/**
|
||||
* Logger class
|
||||
*/
|
||||
|
||||
debug(msg) {
|
||||
if (DEBUG_ENABLED)
|
||||
this.log('debug', msg);
|
||||
}
|
||||
|
||||
info(msg) {
|
||||
this.log('info', msg);
|
||||
}
|
||||
|
||||
warn(msg) {
|
||||
this.log('warn', msg);
|
||||
}
|
||||
|
||||
error(msg) {
|
||||
this.log('error', msg);
|
||||
}
|
||||
|
||||
log(level, msg) {
|
||||
|
||||
if (level == 'debug')
|
||||
msg = chalk.gray(msg);
|
||||
else if (level == 'warn')
|
||||
msg = chalk.yellow(msg);
|
||||
else if (level == 'error')
|
||||
msg = chalk.bold.red(msg);
|
||||
|
||||
// prepend provider name if applicable
|
||||
if (this.providerName)
|
||||
msg = chalk.cyan(`[${this.providerName}]`) + " " + msg;
|
||||
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
static forProvider(providerName) {
|
||||
return loggerCache[providerName] || (loggerCache[providerName] = new Logger(providerName));
|
||||
}
|
||||
function Logger(pluginName) {
|
||||
this.pluginName = pluginName;
|
||||
}
|
||||
|
||||
// system logger, for internal use only
|
||||
export let log = new Logger();
|
||||
Logger.prototype.debug = function(msg) {
|
||||
if (DEBUG_ENABLED)
|
||||
this.log('debug', msg);
|
||||
}
|
||||
|
||||
Logger.prototype.info = function(msg) {
|
||||
this.log('info', msg);
|
||||
}
|
||||
|
||||
Logger.prototype.warn = function(msg) {
|
||||
this.log('warn', msg);
|
||||
}
|
||||
|
||||
Logger.prototype.error = function(msg) {
|
||||
this.log('error', msg);
|
||||
}
|
||||
|
||||
Logger.prototype.log = function(level, msg) {
|
||||
|
||||
if (level == 'debug')
|
||||
msg = chalk.gray(msg);
|
||||
else if (level == 'warn')
|
||||
msg = chalk.yellow(msg);
|
||||
else if (level == 'error')
|
||||
msg = chalk.bold.red(msg);
|
||||
|
||||
// prepend plugin name if applicable
|
||||
if (this.pluginName)
|
||||
msg = chalk.cyan("[" + this.pluginName + "]") + " " + msg;
|
||||
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
Logger.forPlugin = function(pluginName) {
|
||||
return loggerCache[pluginName] || (loggerCache[pluginName] = new Logger(pluginName));
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import semver from 'semver';
|
||||
import { User } from './user';
|
||||
import { HOMEBRIDGE_VERSION } from './homebridge';
|
||||
|
||||
// This class represents a HomeBridge Provider that may or may not be installed.
|
||||
export class Provider {
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
get path() {
|
||||
return path.join(User.providersPath, this.name);
|
||||
}
|
||||
|
||||
load(options = {}) {
|
||||
|
||||
// does this provider exist at all?
|
||||
if (!fs.existsSync(this.path)) {
|
||||
throw new Error(`Provider ${this.name} was not found. Make sure the directory '~/.homebridge/providers/${this.name}' exists.`)
|
||||
}
|
||||
|
||||
// check for a package.json
|
||||
let pjsonPath = path.join(this.path, "package.json");
|
||||
let pjson = null;
|
||||
|
||||
if (!fs.existsSync(pjsonPath)) {
|
||||
throw new Error(`Provider ${this.name} does not contain a package.json.`);
|
||||
}
|
||||
|
||||
try {
|
||||
// attempt to parse package.json
|
||||
pjson = JSON.parse(fs.readFileSync(pjsonPath));
|
||||
}
|
||||
catch (err) {
|
||||
throw new Error(`Provider ${this.name} contains an invalid package.json. Error: ${err}`);
|
||||
}
|
||||
|
||||
// pluck out the HomeBridge version requirement
|
||||
if (!pjson.engines || !pjson.engines.homebridge) {
|
||||
throw new Error(`Provider ${this.name} does not contain a valid HomeBridge version requirement.`);
|
||||
}
|
||||
|
||||
let versionRequired = pjson.engines.homebridge;
|
||||
|
||||
// make sure the version is satisfied by the currently running version of HomeBridge
|
||||
if (!semver.satisfies(HOMEBRIDGE_VERSION, versionRequired)) {
|
||||
throw new Error(`Provider ${this.name} requires a HomeBridge version of "${versionRequired}" which does not satisfy the current HomeBridge version of ${HOMEBRIDGE_VERSION}. You may need to upgrade your installation of HomeBridge.`);
|
||||
}
|
||||
|
||||
// figure out the main module - index.js unless otherwise specified
|
||||
let main = pjson.main || "./index.js";
|
||||
|
||||
let mainPath = path.join(this.path, main);
|
||||
|
||||
// try to require() it
|
||||
let loadedProvider = require(mainPath);
|
||||
|
||||
// pull out the configuration data, if any
|
||||
let providerConfig = loadedProvider.config;
|
||||
|
||||
// verify that all required values are present
|
||||
if (providerConfig && !options.skipConfigCheck) {
|
||||
for (let key in providerConfig) {
|
||||
|
||||
let configParams = providerConfig[key];
|
||||
|
||||
if (configParams.required && !User.config.get(`${this.name}-${key}`)) {
|
||||
throw new Error(`Provider ${this.name} requires the config value ${key} to be set.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loadedProvider;
|
||||
}
|
||||
|
||||
// Gets all providers installed on the local system
|
||||
static installed() {
|
||||
|
||||
let providers = [];
|
||||
let names = fs.readdirSync(User.providersPath);
|
||||
|
||||
for (let name of names) {
|
||||
|
||||
// reconstruct full path
|
||||
let fullPath = path.join(User.providersPath, name);
|
||||
|
||||
// we only care about directories
|
||||
if (!fs.statSync(fullPath).isDirectory()) continue;
|
||||
|
||||
providers.push(new Provider(name));
|
||||
}
|
||||
|
||||
return providers;
|
||||
}
|
||||
}
|
||||
125
lib/server.js
125
lib/server.js
@@ -1,12 +1,119 @@
|
||||
import { Provider } from './provider';
|
||||
import { User, Config } from './user';
|
||||
var path = require('path');
|
||||
var http = require('http');
|
||||
var express = require('express');
|
||||
var jsxtransform = require('express-jsxtransform');
|
||||
var autoamd = require('./util/autoamd');
|
||||
var io = require('socket.io');
|
||||
var diffsync = require('diffsync');
|
||||
var Plugin = require('./Plugin').Plugin;
|
||||
var User = require('./User').User;
|
||||
|
||||
export class Server {
|
||||
'use strict';
|
||||
|
||||
constructor(providers) {
|
||||
this.providers = providers; // providers[name] = loaded provider JS module
|
||||
}
|
||||
|
||||
run() {
|
||||
}
|
||||
module.exports = {
|
||||
Server: Server
|
||||
}
|
||||
|
||||
function Server() {
|
||||
this._plugins = {}; // plugins[name] = plugin
|
||||
this._httpServer = null; // http.Server
|
||||
this._dataAdapter = new diffsync.InMemoryDataAdapter(); // our "database"
|
||||
this._diffsyncServer = null; // diffsync.Server
|
||||
|
||||
// load and validate plugins - check for valid package.json, etc.
|
||||
Plugin.installed().forEach(function(plugin) {
|
||||
|
||||
// add it to our dict for easy lookup later
|
||||
this._plugins[plugin.name()] = plugin;
|
||||
|
||||
// attempt to load it
|
||||
try {
|
||||
plugin.load();
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
plugin.loadError = err;
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
Server.prototype.run = function() {
|
||||
|
||||
// setting up express and socket.io
|
||||
var app = express();
|
||||
|
||||
// our web assets are all located in a sibling folder 'public'
|
||||
var root = path.dirname(__dirname);
|
||||
var pub = path.join(root, 'public');
|
||||
|
||||
// middleware to convert our JS (written in CommonJS style) to AMD (require.js style)
|
||||
app.use(autoamd('/public/js/'));
|
||||
|
||||
// middleware to compile JSX on the fly
|
||||
app.use(jsxtransform());
|
||||
|
||||
// middleware to serve static files in the public directory
|
||||
app.use('/public', express.static(pub));
|
||||
|
||||
// match any path without a period (assuming period means you're asking for a static file)
|
||||
app.get(/^[^\.]*$/, function(req, res){
|
||||
res.sendFile(path.join(pub, 'index.html'));
|
||||
});
|
||||
|
||||
// HTTP web server
|
||||
this._httpServer = http.createServer(app);
|
||||
|
||||
// diffsync server
|
||||
this._diffsyncServer = new diffsync.Server(this._dataAdapter, io(this._httpServer));
|
||||
|
||||
// grab our global "root" data object and fill it out with inital data for the browser
|
||||
this._dataAdapter.getData("root", this._onRootLoaded.bind(this));
|
||||
}
|
||||
|
||||
Server.prototype._onRootLoaded = function(err, root) {
|
||||
|
||||
// we've loaded our "root" object from the DB - now fill it out before we make it available
|
||||
// to clients.
|
||||
|
||||
root.plugins = Object.keys(this._plugins).map(function(name) {
|
||||
var plugin = this._plugins[name];
|
||||
var dict = { name: name };
|
||||
if (plugin.loadError)
|
||||
dict.loadError = loadError;
|
||||
|
||||
dict.providers = plugin.providers.map(function(provider) {
|
||||
return {
|
||||
name: provider.name,
|
||||
title: provider.title,
|
||||
config: provider.config,
|
||||
}
|
||||
});
|
||||
|
||||
return dict;
|
||||
}.bind(this));
|
||||
|
||||
root.providers = root.providers || [];
|
||||
|
||||
root.notifications = [];
|
||||
|
||||
// if we're using browser-refresh for development, pass on the refresh script URL for the browser to load
|
||||
root.browserRefreshURL = process.env.BROWSER_REFRESH_URL;
|
||||
|
||||
// start the server!
|
||||
this._httpServer.listen(4000, this._onHttpServerListen.bind(this));
|
||||
}
|
||||
|
||||
Server.prototype._onHttpServerListen = function() {
|
||||
|
||||
// we are now fully online - if we're using browser-refresh to auto-reload the browser during
|
||||
// development, then it expects to receive this signal
|
||||
if (process.send)
|
||||
process.send('online');
|
||||
}
|
||||
|
||||
// Forces diffsync to persist the latest version of the data under the given id (which may have been
|
||||
// changed without its knowledge), and notify any connected clients about the change.
|
||||
Server.prototype._forceSync = function(id) {
|
||||
this._diffsyncServer.transport.to(id).emit(diffsync.COMMANDS.remoteUpdateIncoming, null);
|
||||
}
|
||||
55
lib/user.js
55
lib/user.js
@@ -1,31 +1,32 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { Config } from './config';
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var Config = require('./Config').Config;
|
||||
|
||||
//
|
||||
// Manages user settings and storage locations.
|
||||
//
|
||||
'use strict';
|
||||
|
||||
// global cached config
|
||||
let config;
|
||||
|
||||
export class User {
|
||||
|
||||
static get config() {
|
||||
return config || (config = Config.load(User.configPath));
|
||||
}
|
||||
|
||||
static get storagePath() {
|
||||
let home = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
|
||||
return path.join(home, ".homebridge");
|
||||
}
|
||||
|
||||
static get configPath() {
|
||||
return path.join(User.storagePath, "config.json");
|
||||
}
|
||||
|
||||
static get providersPath() {
|
||||
return path.join(User.storagePath, "providers");
|
||||
}
|
||||
module.exports = {
|
||||
User: User
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages user settings and storage locations.
|
||||
*/
|
||||
|
||||
// global cached config
|
||||
var config;
|
||||
|
||||
function User() {
|
||||
}
|
||||
|
||||
User.config = function() {
|
||||
return config || (config = Config.load(User.configPath()));
|
||||
}
|
||||
|
||||
User.storagePath = function() {
|
||||
var home = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
|
||||
return path.join(home, ".homebridge");
|
||||
}
|
||||
|
||||
User.configPath = function() {
|
||||
return path.join(User.storagePath(), "config.json");
|
||||
}
|
||||
|
||||
30
lib/util/autoamd.js
Normal file
30
lib/util/autoamd.js
Normal file
@@ -0,0 +1,30 @@
|
||||
var interceptor = require('express-interceptor');
|
||||
|
||||
/**
|
||||
* Express middleware that converts CommonJS-style code to RequireJS style for the browser (assuming require.js is loaded).
|
||||
*/
|
||||
|
||||
module.exports = function(urlPrefix) {
|
||||
return interceptor(function(req, res){
|
||||
return {
|
||||
// Only URLs with the given prefix will be converted to require.js style
|
||||
isInterceptable: function(){
|
||||
return req.originalUrl.indexOf(urlPrefix) == 0;
|
||||
},
|
||||
intercept: function(body, send) {
|
||||
send(toRequireJS(body));
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// From https://github.com/shovon/connect-commonjs-amd/blob/master/src/middleware.coffee
|
||||
function toRequireJS(str) {
|
||||
var requireCalls = str.match(/require\((\s+)?('[^'\\]*(?:\\.[^'\\]*)*'|"[^"\\]*(?:\\.[^"\\]*)*")(\s+)?\)/g) || [];
|
||||
requireCalls = requireCalls.map(function(str) {
|
||||
return (str.match(/('[^'\\]*(?:\\.[^'\\]*)*'|"[^"\\]*(?:\\.[^"\\]*)*")/))[0];
|
||||
});
|
||||
requireCalls.unshift("'require'");
|
||||
str = "define([" + (requireCalls.join(', ')) + "], function (require) {\nvar module = { exports: {} }\n , exports = module.exports;\n\n(function () {\n\n" + str + "\n\n})();\n\nreturn module.exports;\n});";
|
||||
return str;
|
||||
};
|
||||
@@ -1,9 +1,13 @@
|
||||
|
||||
module.exports = {
|
||||
camelCaseToRegularForm: camelCaseToRegularForm
|
||||
}
|
||||
|
||||
// Converts "accessToken" to "Access Token"
|
||||
export function camelCaseToRegularForm(camelCase) {
|
||||
function camelCaseToRegularForm(camelCase) {
|
||||
return camelCase
|
||||
// insert a space before all caps
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
// uppercase the first character
|
||||
.replace(/^./, function(str){ return str.toUpperCase(); })
|
||||
}
|
||||
}
|
||||
12
lib/version.js
Normal file
12
lib/version.js
Normal file
@@ -0,0 +1,12 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = getVersion();
|
||||
|
||||
function getVersion() {
|
||||
var packageJSONPath = path.join(__dirname, '../package.json');
|
||||
var packageJSON = JSON.parse(fs.readFileSync(packageJSONPath));
|
||||
return packageJSON.version;
|
||||
}
|
||||
Reference in New Issue
Block a user