implemented better management of arrays in the config
This commit is contained in:
parent
5761ab9d62
commit
485a223f86
|
@ -403,13 +403,108 @@ module.exports.create = function (deps, conf) {
|
|||
next();
|
||||
}
|
||||
};
|
||||
config.restful.saveConfig = function (req, res) {
|
||||
console.log('config POST body', req.body);
|
||||
|
||||
// Since we are sending the changes to another process we don't really
|
||||
// have a good way of seeing if it worked, so always report success
|
||||
deps.storage.config.save(req.body);
|
||||
res.send({ success: true });
|
||||
config.restful.saveBaseConfig = function (req, res) {
|
||||
console.log('config POST body', JSON.stringify(req.body));
|
||||
|
||||
deps.PromiseA.resolve().then(function () {
|
||||
var update;
|
||||
if (req.params.group) {
|
||||
update = {};
|
||||
update[req.params.group] = req.body;
|
||||
} else {
|
||||
update = req.body;
|
||||
}
|
||||
|
||||
var changer = new (require('./config').ConfigChanger)(conf);
|
||||
var errors = changer.update(update);
|
||||
if (errors.length) {
|
||||
throw Object.assign(new Error(), errors[0], {statusCode: 400});
|
||||
}
|
||||
|
||||
return deps.storage.config.save(changer);
|
||||
}).then(function (config) {
|
||||
if (req.params.group) {
|
||||
config = config[req.params.group];
|
||||
}
|
||||
res.send(deps.recase.snakeCopy(config));
|
||||
}, function (err) {
|
||||
res.statusCode = err.statusCode || 500;
|
||||
err.message = err.message || err.toString();
|
||||
res.end(JSON.stringify({error: {message: err.message, code: err.code}}));
|
||||
});
|
||||
};
|
||||
config.restful.createModule = function (req, res) {
|
||||
var group = req.params.group;
|
||||
var err;
|
||||
deps.PromiseA.resolve().then(function () {
|
||||
var changer = new (require('./config').ConfigChanger)(conf);
|
||||
if (!changer[group] || !changer[group].modules) {
|
||||
err = new Error("'"+group+"' is not a valid settings group or has not modules");
|
||||
err.statusCode = 404;
|
||||
throw err;
|
||||
}
|
||||
|
||||
var modList;
|
||||
if (req.params.id) {
|
||||
if (changer[group].domains) {
|
||||
modList = (changer[group].domains.find(req.params.id) || {}).modules;
|
||||
}
|
||||
} else {
|
||||
modList = changer[group].modules;
|
||||
}
|
||||
if (!modList) {
|
||||
err = new Error("'"+group+"' has no domains list or '"+req.params.id+"' does not exist");
|
||||
err.statusCode = 404;
|
||||
throw err;
|
||||
}
|
||||
|
||||
modList.add(req.body);
|
||||
var errors = changer.validate();
|
||||
if (errors.length) {
|
||||
throw Object.assign(new Error(), errors[0], {statusCode: 400});
|
||||
}
|
||||
|
||||
return deps.storage.config.save(changer);
|
||||
}).then(function (config) {
|
||||
var base;
|
||||
if (!req.params.id) {
|
||||
base = config[group];
|
||||
} else {
|
||||
base = config[group].domains.find(function (dom) { return dom.id === req.params.id; });
|
||||
}
|
||||
res.send(deps.recase.snakeCopy(base.modules));
|
||||
}, function (err) {
|
||||
res.statusCode = err.statusCode || 500;
|
||||
err.message = err.message || err.toString();
|
||||
res.end(JSON.stringify({error: {message: err.message, code: err.code}}));
|
||||
});
|
||||
};
|
||||
config.restful.createDomain = function (req, res) {
|
||||
var group = req.params.group;
|
||||
var err;
|
||||
deps.PromiseA.resolve().then(function () {
|
||||
var changer = new (require('./config').ConfigChanger)(conf);
|
||||
if (!changer[group] || !changer[group].domains) {
|
||||
err = new Error("'"+group+"' is not a valid settings group or has no domains list");
|
||||
err.statusCode = 404;
|
||||
throw err;
|
||||
}
|
||||
|
||||
changer[group].domains.add(req.body);
|
||||
var errors = changer.validate();
|
||||
if (errors.length) {
|
||||
throw Object.assign(new Error(), errors[0], {statusCode: 400});
|
||||
}
|
||||
|
||||
return deps.storage.config.save(changer);
|
||||
}).then(function (config) {
|
||||
res.send(deps.recase.snakeCopy(config[group].domains));
|
||||
}, function (err) {
|
||||
res.statusCode = err.statusCode || 500;
|
||||
err.message = err.message || err.toString();
|
||||
res.end(JSON.stringify({error: {message: err.message, code: err.code}}));
|
||||
});
|
||||
};
|
||||
|
||||
var app = require('express')();
|
||||
|
@ -425,6 +520,12 @@ module.exports.create = function (deps, conf) {
|
|||
app.get( '/config/:group', config.restful.readConfig);
|
||||
app.get( '/config/:group/:name(modules|domains)/:id?', config.restful.readConfig);
|
||||
app.get( '/config/:group/:name(domains)/:id/:name2(modules)/:id2?', config.restful.readConfig);
|
||||
app.post( '/config', config.restful.saveConfig);
|
||||
|
||||
app.post( '/config', config.restful.saveBaseConfig);
|
||||
app.post( '/config/:group', config.restful.saveBaseConfig);
|
||||
app.post( '/config/:group/modules', config.restful.createModule);
|
||||
app.post( '/config/:group/domains', config.restful.createDomain);
|
||||
app.post( '/config/:group/domains/:id/modules', config.restful.createModule);
|
||||
|
||||
return app;
|
||||
};
|
||||
|
|
|
@ -3,23 +3,6 @@
|
|||
var validator = new (require('jsonschema').Validator)();
|
||||
var recase = require('recase').create({});
|
||||
|
||||
function deepCopy(obj) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
var result;
|
||||
if (Array.isArray(obj)) {
|
||||
result = [];
|
||||
} else {
|
||||
result = {};
|
||||
}
|
||||
Object.keys(obj).forEach(function (key) {
|
||||
result[key] = deepCopy(obj[key]);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
var portSchema = { type: 'number', minimum: 1, maximum: 65535 };
|
||||
|
||||
var moduleSchemas = {
|
||||
|
@ -67,7 +50,7 @@ var moduleSchemas = {
|
|||
}
|
||||
};
|
||||
// forward is basically the name for the TCP proxy
|
||||
moduleSchemas.forward = deepCopy(moduleSchemas.proxy);
|
||||
moduleSchemas.forward = JSON.parse(JSON.stringify(moduleSchemas.proxy));
|
||||
moduleSchemas.forward.required = [ 'ports' ];
|
||||
moduleSchemas.forward.properties.ports = { type: 'array', items: portSchema };
|
||||
|
||||
|
@ -180,6 +163,12 @@ var socks5Schema = {
|
|||
, port: portSchema
|
||||
}
|
||||
};
|
||||
var deviceSchema = {
|
||||
type: 'object'
|
||||
, properties: {
|
||||
hostname: { type: 'string' }
|
||||
}
|
||||
};
|
||||
|
||||
var mainSchema = {
|
||||
type: 'object'
|
||||
|
@ -192,10 +181,143 @@ var mainSchema = {
|
|||
, mdns: mdnsSchema
|
||||
, ddns: ddnsSchema
|
||||
, socks5: socks5Schema
|
||||
, device: deviceSchema
|
||||
}
|
||||
, additionalProperties: false
|
||||
};
|
||||
|
||||
module.exports.validate = function (config) {
|
||||
function validate(config) {
|
||||
return validator.validate(recase.snakeCopy(config), mainSchema).errors;
|
||||
};
|
||||
}
|
||||
module.exports.validate = validate;
|
||||
|
||||
|
||||
class ModuleList extends Array {
|
||||
constructor(rawList) {
|
||||
super();
|
||||
if (Array.isArray(rawList)) {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(rawList)));
|
||||
}
|
||||
}
|
||||
|
||||
find(id) {
|
||||
return Array.prototype.find.call(this, function (mod) {
|
||||
return mod.id === id;
|
||||
});
|
||||
}
|
||||
add(mod) {
|
||||
if (!mod.type) {
|
||||
throw new Error("module must have a 'type' defined");
|
||||
}
|
||||
if (!moduleSchemas[mod.type]) {
|
||||
throw new Error("invalid module type '"+mod.type+"'");
|
||||
}
|
||||
|
||||
mod.id = require('crypto').randomBytes(4).toString('hex');
|
||||
this.push(mod);
|
||||
}
|
||||
}
|
||||
class DomainList extends Array {
|
||||
constructor(rawList) {
|
||||
super();
|
||||
if (Array.isArray(rawList)) {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(rawList)));
|
||||
}
|
||||
this.forEach(function (dom) {
|
||||
dom.modules = new ModuleList(dom.modules);
|
||||
});
|
||||
}
|
||||
|
||||
find(id) {
|
||||
return Array.prototype.find.call(this, function (dom) {
|
||||
return dom.id === id;
|
||||
});
|
||||
}
|
||||
add(dom) {
|
||||
if (!Array.isArray(dom.names) || !dom.names.length) {
|
||||
throw new Error("domains must have a non-empty array for 'names'");
|
||||
}
|
||||
if (dom.names.some(function (name) { return typeof name !== 'string'; })) {
|
||||
throw new Error("all domain names must be strings");
|
||||
}
|
||||
|
||||
var modList = new ModuleList();
|
||||
if (Array.isArray(dom.modules)) {
|
||||
dom.modules.forEach(function (mod) {
|
||||
modList.add(mod);
|
||||
});
|
||||
}
|
||||
|
||||
dom.id = require('crypto').randomBytes(4).toString('hex');
|
||||
dom.modules = modList;
|
||||
this.push(dom);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigChanger {
|
||||
constructor(start) {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(start)));
|
||||
delete this.device;
|
||||
|
||||
this.http.modules = new ModuleList(this.http.modules);
|
||||
this.http.domains = new DomainList(this.http.domains);
|
||||
this.tls.modules = new ModuleList(this.tls.modules);
|
||||
this.tls.domains = new DomainList(this.tls.domains);
|
||||
this.tcp.modules = new ModuleList(this.tcp.modules);
|
||||
this.dns.modules = new ModuleList(this.dns.modules);
|
||||
}
|
||||
|
||||
update(update) {
|
||||
var self = this;
|
||||
|
||||
if (update.http && update.http.modules) {
|
||||
update.http.modules.forEach(self.http.modules.add.bind(self.http.modules));
|
||||
delete update.http.modules;
|
||||
}
|
||||
if (update.http && update.http.domains) {
|
||||
update.http.domains.forEach(self.http.domains.add.bind(self.http.domains));
|
||||
delete update.http.domains;
|
||||
}
|
||||
|
||||
if (update.tls && update.tls.modules) {
|
||||
update.tls.modules.forEach(self.tls.modules.add.bind(self.tls.modules));
|
||||
delete update.tls.modules;
|
||||
}
|
||||
if (update.tls && update.tls.domains) {
|
||||
update.tls.domains.forEach(self.tls.domains.add.bind(self.tls.domains));
|
||||
delete update.tls.domains;
|
||||
}
|
||||
|
||||
if (update.tcp && update.tcp.modules) {
|
||||
update.tcp.modules.forEach(self.tcp.modules.add.bind(self.tcp.modules));
|
||||
delete update.tcp.modules;
|
||||
}
|
||||
if (update.dns && update.dns.modules) {
|
||||
update.dns.modules.forEach(self.dns.modules.add.bind(self.dns.modules));
|
||||
delete update.dns.modules;
|
||||
}
|
||||
|
||||
function mergeSettings(orig, changes) {
|
||||
Object.keys(changes).forEach(function (key) {
|
||||
// TODO: use an API that can properly handle updating arrays.
|
||||
if (!changes[key] || (typeof changes[key] !== 'object') || Array.isArray(changes[key])) {
|
||||
orig[key] = changes[key];
|
||||
}
|
||||
else if (!orig[key] || typeof orig[key] !== 'object') {
|
||||
orig[key] = changes[key];
|
||||
}
|
||||
else {
|
||||
mergeSettings(orig[key], changes[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
mergeSettings(this, update);
|
||||
|
||||
return validate(this);
|
||||
}
|
||||
|
||||
validate() {
|
||||
return validate(this);
|
||||
}
|
||||
}
|
||||
module.exports.ConfigChanger = ConfigChanger;
|
||||
|
|
|
@ -65,14 +65,33 @@ module.exports.create = function (deps, conf) {
|
|||
}
|
||||
};
|
||||
|
||||
var confCb;
|
||||
var config = {
|
||||
save: function (changes) {
|
||||
deps.messenger.send({
|
||||
type: 'com.daplie.goldilocks/config'
|
||||
, changes: changes
|
||||
});
|
||||
|
||||
return new deps.PromiseA(function (resolve, reject) {
|
||||
var timeoutId = setTimeout(function () {
|
||||
reject(new Error('Did not receive config update from main process in a reasonable time'));
|
||||
confCb = null;
|
||||
}, 15*1000);
|
||||
|
||||
confCb = function (config) {
|
||||
confCb = null;
|
||||
clearTimeout(timeoutId);
|
||||
resolve(config);
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
function updateConf(config) {
|
||||
if (confCb) {
|
||||
confCb(config);
|
||||
}
|
||||
}
|
||||
|
||||
var mdnsId = {
|
||||
_filename: 'mdns-id'
|
||||
|
@ -99,6 +118,7 @@ module.exports.create = function (deps, conf) {
|
|||
return {
|
||||
owners: owners
|
||||
, config: config
|
||||
, updateConf: updateConf
|
||||
, mdnsId: mdnsId
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var config;
|
||||
var modules;
|
||||
|
||||
// Everything that uses the config should be reading it when relevant rather than
|
||||
// just at the beginning, so we keep the reference for the main object and just
|
||||
|
@ -15,7 +16,13 @@ function update(conf) {
|
|||
config[key] = conf[key];
|
||||
}
|
||||
});
|
||||
console.log('config', JSON.stringify(config));
|
||||
|
||||
console.log('config update', JSON.stringify(config));
|
||||
Object.values(modules).forEach(function (mod) {
|
||||
if (typeof mod.updateConf === 'function') {
|
||||
mod.updateConf(config);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function create(conf) {
|
||||
|
@ -38,11 +45,15 @@ function create(conf) {
|
|||
// HTTP proxying connection creation is not something we currently control.
|
||||
, net: require('net')
|
||||
};
|
||||
deps.storage = require('./storage').create(deps, conf);
|
||||
deps.proxy = require('./proxy-conn').create(deps, conf);
|
||||
deps.socks5 = require('./socks5-server').create(deps, conf);
|
||||
deps.loopback = require('./loopback').create(deps, conf);
|
||||
deps.ddns = require('./ddns').create(deps, conf);
|
||||
|
||||
modules = {
|
||||
storage: require('./storage').create(deps, conf)
|
||||
, proxy: require('./proxy-conn').create(deps, conf)
|
||||
, socks5: require('./socks5-server').create(deps, conf)
|
||||
, loopback: require('./loopback').create(deps, conf)
|
||||
, ddns: require('./ddns').create(deps, conf)
|
||||
};
|
||||
Object.assign(deps, modules);
|
||||
|
||||
require('./goldilocks.js').create(deps, conf);
|
||||
process.removeListener('message', create);
|
||||
|
|
Loading…
Reference in New Issue