diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..86cfb1c26 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# 2d0e4a3f-ac19-430c-bf1b-46c68651ce21 +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_size = 2 +indent_style = space + +[*.{php,html}] +indent_size = 4 + +[Makefile*] +indent_style = tab diff --git a/lib/rlevel.js b/lib/rlevel.js new file mode 100644 index 000000000..2cf3f9217 --- /dev/null +++ b/lib/rlevel.js @@ -0,0 +1,69 @@ +var Gun = ('undefined' !== typeof window) ? window.Gun : require('../gun'); +var debug = false; + +// Adds opts.level, where the value is an initialized levelup (or compatible) instance +Gun.on('create', function(root) { + this.to.next(root); + var opt = root.opt; + + if (debug) debug.emit('create'); + + // Check if the given 'level' argument implements all the components we need + // Intentionally doesn't check for levelup explicitly, to allow different handlers implementing the same api + if ( + (!opt.level) || + ('object' !== typeof opt.level) || + ('function' !== typeof opt.level.get) || + ('function' !== typeof opt.level.put) + ) { + return; + } + + // Register our store + opt.store = opt.store || factory(opt); +}); + +function factory(opt) { + if (debug) debug.emit('factory'); + + var level = opt.level; + var store = {}; + + store.put = function(key, data, cb) { + if (debug) debug.emit('put', key, data); + level.put(key, data, function(err) { + if (debug && err) debug.emit('error', err); + cb(err, 'level'); + }); + }; + + store.get = function(key, cb) { + if (debug) debug.emit('get', key); + level.get(key, function(err, data) { + if (err && (err.name === 'NotFoundError')) err = undefined; + if (debug && err) debug.emit('error', err); + cb(err, data); + }); + }; + + store.list = function(cb, match, params, cbs){ + if (debug) debug.emit('list'); + level + .createKeyStream() + .on('data', function(key) { + console.log("KEY", key); + cb(key); + }) + .on('end', function() { + cb(); + }); + }; + + return store; +} + +// Export debug interface +if ('undefined' === typeof window) { + var EventEmitter = require('events').EventEmitter; + module.exports = debug = new EventEmitter(); +} diff --git a/package-lock.json b/package-lock.json index 4a7521733..0945584a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,6 +92,19 @@ "@types/node": "*" } }, + "abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, "accepts": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", @@ -259,7 +272,6 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "optional": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" @@ -404,6 +416,16 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "deferred-leveldown": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", + "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", + "dev": true, + "requires": { + "abstract-leveldown": "~6.2.1", + "inherits": "^2.0.3" + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -554,6 +576,15 @@ "wtf-8": "1.0.0" } }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, "es-abstract": { "version": "1.17.5", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", @@ -713,6 +744,12 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, + "immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", + "dev": true + }, "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", @@ -823,6 +860,65 @@ "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", "dev": true }, + "level-concat-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", + "dev": true + }, + "level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "dev": true, + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", + "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.4.0", + "xtend": "^4.0.2" + } + }, + "level-supports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "dev": true, + "requires": { + "xtend": "^4.0.2" + } + }, + "leveldown": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", + "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", + "dev": true, + "requires": { + "abstract-leveldown": "~6.2.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "~4.1.0" + } + }, + "levelup": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", + "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", + "dev": true, + "requires": { + "deferred-leveldown": "~5.3.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~4.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -924,6 +1020,12 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, + "napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "dev": true + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -940,6 +1042,12 @@ "semver": "^5.7.0" } }, + "node-gyp-build": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", + "dev": true + }, "object-assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", @@ -1113,6 +1221,12 @@ "integrity": "sha1-SSIQiSM1vTExwKCN2i2T7DVD5CM=", "dev": true }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", @@ -1146,6 +1260,17 @@ "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", "optional": true }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1158,6 +1283,12 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, "sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", @@ -1376,6 +1507,15 @@ "es-abstract": "^1.17.5" } }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -1443,6 +1583,12 @@ "querystring": "0.2.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -1564,6 +1710,12 @@ "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", "dev": true }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/package.json b/package.json index 27a21a8eb..13ea5d486 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,8 @@ "@types/ws": "^7.2.1", "aws-sdk": "^2.528.0", "ip": "^1.1.5", + "leveldown": "^5.6.0", + "levelup": "^4.4.0", "mocha": "^6.2.0", "panic-manager": "^1.2.0", "panic-server": "^1.1.1", diff --git a/test/panic/rlevel.js b/test/panic/rlevel.js new file mode 100644 index 000000000..6bda52a4f --- /dev/null +++ b/test/panic/rlevel.js @@ -0,0 +1,215 @@ +const panic = require('panic-server'); +const clients = panic.clients; +const manager = require('panic-manager')(); + +require('events').EventEmitter.defaultMaxListeners = Infinity; + +const config = { + ip : require('ip').address(), + port : 8765, + servers : 3, + route : { + '/' : __dirname + '/index.html', + '/gun.js' : __dirname + '/../../gun.js', + '/jquery.js' : __dirname + '/../../examples/jquery.js' + } +}; + +const srv = panic.server(); +srv.on('request', (req, res) => { + config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res); +}).listen(config.port); + +manager.start({ + clients: Array(config.servers).fill().map((u,i) => ({ + type: 'node', + port: config.port + i + 1, + })), + panic: `http://${config.ip}:${config.port}` +}); + +const servers = clients.filter('Node.js'); +const server = servers.pluck(1); +const alice = servers.excluding(server).pluck(1); +const bob = servers.excluding(server).excluding(alice).pluck(1); + +describe('Make sure the leveldb storage engine works', function() { + this.timeout(5 * 60 * 60 * 1000); + + it("servers have joined!", function() { + return servers.atLeast(config.servers); + }); + + it("GUN started!", function() { + return server.run(function(test) { + test.async(); + const {config} = test.props; + + const leveldown = require('leveldown'); + const levelup = require('levelup'); + + if (require('fs').existsSync('./lvldata')) { + console.error('Please delete previous data first!'); + return; + } + + // Initialize gun opts + const opts = {}; + opts.level = global.level = levelup(leveldown('./lvldata')); + + // Load the libraries under test + const Gun = require('../../../index.js'); + const debug = require('../../../lib/rlevel.js'); + + // // Add debug messages + // debug.on('get', key => console.log('LEVEL GET', key)); + // debug.on('put', (key, value) => console.log('LEVEL PUT', key, value)); + // debug.on('list', () => console.log('LEVEL LIST')); + // debug.on('error', err => console.log('LEVEL ERROR', err)); + + // Track state (so we can wait on put, it's called late by radisk) + global.state = 0; + debug.on('put', () => global.state++); + + // Create server + opts.web = require('http').createServer((req, res) => { + res.end("Number five is alive!"); + }); + + // Initialize gun & start server + const gun = global.gun = Gun(opts); + opts.web.listen(config.port + 1, () => { + test.done(); + }); + }, {config}); + }); + + it("Alice saves data", function() { + return alice.run(function(test) { + test.async(); + const {config} = test.props; + const Gun = require('../../../index.js'); + + // Start gun + global.gun = Gun({ + peers: 'http://'+ config.ip + ':' + (config.port + 1) + '/gun', + lack : 1000 * 60 * 60, + }); + + // Save data + const ref = gun.get('asdf'); + ref.put({ hello: 'world' }, ack => { + if (ack.err) { + if (ack.lack) return test.fail('ACK timed out, turn your lack of ack up or throughput down'); + return test.fail(ack.err); + } + ref.off(); + test.done(); + }); + + }, {config}); + }); + + it('Server read data', function() { + return server.run(function(test) { + test.async(); + + // Read data (triggers fetch from alice + write to disk) + const ref = gun.get('asdf'); + ref.on(data => { + if (data.hello !== 'world') { + return test.fail('Invalid data returned'); + } + ref.off(); + test.done(); + }); + + }); + }); + + it('Wait for server to store', function() { + return server.run(function(test) { + test.async(); + setTimeout(function awaitState() { + if (global.state < 2) return setTimeout(awaitState, 50); + test.done(); + }, 50); + }); + }); + + it('Close all original running nodes', function() { + clients.pluck(2).run(function() { + if (global.level) { + global.level.close(function() { + process.exit(); + }); + } else { + process.exit(); + } + }); + }); + + it('Start bob', function() { + return bob.run(function(test) { + test.async(); + const {config} = test.props; + + const leveldown = require('leveldown'); + const levelup = require('levelup'); + + // Initialize gun opts + const opts = {}; + opts.level = global.level = levelup(leveldown('./lvldata')); + + // Load the libraries under test + const Gun = require('../../../index.js'); + const debug = require('../../../lib/rlevel.js'); + + // // Add debug messages + // debug.on('get', key => console.log('LEVEL GET', key)); + // debug.on('put', (key, value) => console.log('LEVEL PUT', key, value)); + // debug.on('list', () => console.log('LEVEL LIST')); + // debug.on('error', err => console.log('LEVEL ERROR', err)); + + // Create server + opts.web = require('http').createServer((req, res) => { + res.end("Number five is alive!"); + }); + + // Initialize gun & start server + const gun = global.gun = Gun(opts); + opts.web.listen(config.port + 1, () => { + test.done(); + }); + }, {config}); + }); + + it('Bob read', function() { + return bob.run(function(test) { + test.async(); + + // Read data + const ref = gun.get('asdf'); + ref.on(data => { + if (data.hello !== 'world') { + return test.fail('Invalid data returned'); + } + ref.off(); + test.done(); + }); + + }, {config}); + }); + + it('Shutdown bob', function() { + return clients.run(function() { + process.exit() + }); + }); + + it("All finished!", function(done) { + srv.close(); + done(); + }); + +});