goldilocks.js/lib/storage.js

125 lines
3.2 KiB
JavaScript
Raw Normal View History

2017-07-06 17:01:29 +00:00
'use strict';
var PromiseA = require('bluebird');
var path = require('path');
var fs = PromiseA.promisifyAll(require('fs'));
module.exports.create = function (deps, conf) {
var hrIds = require('human-readable-ids').humanReadableIds;
2017-07-06 17:01:29 +00:00
var scmp = require('scmp');
var storageDir = path.join(__dirname, '..', 'var');
function read(fileName) {
return fs.readFileAsync(path.join(storageDir, fileName))
.then(JSON.parse, function (err) {
2017-07-07 19:48:40 +00:00
if (err.code === 'ENOENT') {
2017-07-06 17:01:29 +00:00
return {};
}
throw err;
});
}
function write(fileName, obj) {
return fs.mkdirAsync(storageDir).catch(function (err) {
if (err.code !== 'EEXIST') {
console.error('failed to mkdir', storageDir, err.toString());
}
}).then(function () {
return fs.writeFileAsync(path.join(storageDir, fileName), JSON.stringify(obj), 'utf8');
});
}
var owners = {
_filename: 'owners.json'
, all: function () {
return read(this._filename).then(function (owners) {
return Object.keys(owners).map(function (id) {
var owner = owners[id];
owner.id = id;
return owner;
});
});
}
, get: function (id) {
// While we could directly read the owners file and access the id directly from
// the resulting object I'm not sure of the details of how the object key lookup
// works or whether that would expose us to timing attacks.
// See https://codahale.com/a-lesson-in-timing-attacks/
return this.all().then(function (owners) {
return owners.filter(function (owner) {
return scmp(id, owner.id);
})[0];
});
}
, exists: function (id) {
return this.get(id).then(function (owner) {
return !!owner;
});
}
, set: function (id, obj) {
var self = this;
return read(self._filename).then(function (owners) {
obj.id = id;
owners[id] = obj;
return write(self._filename, owners);
});
}
};
var confCb;
2017-07-06 17:01:29 +00:00
var config = {
save: function (changes) {
deps.messenger.send({
type: 'com.daplie.goldilocks/config'
2017-07-06 17:01:29 +00:00
, 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);
};
});
2017-07-06 17:01:29 +00:00
}
};
function updateConf(config) {
if (confCb) {
confCb(config);
}
}
2017-07-06 17:01:29 +00:00
var mdnsId = {
_filename: 'mdns-id'
, get: function () {
var self = this;
return read("mdns-id").then(function (result) {
if (typeof result !== 'string') {
throw new Error('mDNS ID not present');
}
return result;
}).catch(function () {
return self.set(hrIds.random());
});
}
, set: function (value) {
var self = this;
return write(self._filename, value).then(function () {
return self.get();
});
}
};
2017-07-06 17:01:29 +00:00
return {
owners: owners
, config: config
, updateConf: updateConf
, mdnsId: mdnsId
2017-07-06 17:01:29 +00:00
};
};