diff --git a/README.md b/README.md index f94b361..4e8d508 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,24 @@ -# orbit-client +# OrbitDB ## Introduction -Key-Value Store and Event Store on IPFS. +Distributed, peer-to-peer* Key-Value Store and Event Log on IPFS. Requires `orbit-server` to connect to. Get from https://github.com/haadcode/orbit-server. +_* Currently requires a centralized server. This will change in the future as required p2p features land in IPFS_ + ## Features - Distributed kv-store and event log database - Stores all data in IPFS - Data encrypted on the wire and at rest - Per channel access rights -_Channel maps to "table", "keyspace", "topic" or "feed" in similar systems_ +_Channel is similar to "table", "keyspace", "topic", "feed" or "collection" in other systems_ ## API +_See Usage below_ + connect(host, username, password) channel(name, password) @@ -36,7 +40,7 @@ _Channel maps to "table", "keyspace", "topic" or "feed" in similar systems_ .get(key) // Retrieve value - .remove({ key: , hash: }) // Remove entry (use one option) + .remove({ key: }) // Remove entry .setMode(modes) // Set channel modes, can be an object or an array of objects @@ -62,11 +66,9 @@ async(() => { const channelName = 'hello-world'; - // Send an event - const head = orbit.channel(channelName).add('hello'); // - - // Delete an event - orbit.channel(channelName).remove(head); + /* Event Log */ + const hash = orbit.channel(channelName).add('hello'); // + orbit.channel(channelName).remove({ key: hash }); // Iterator options const options = { limit: -1 }; // fetch all messages @@ -82,11 +84,12 @@ async(() => { // for(let i of iter) // console.log(i.hash, i.item); - // KV-store + /* KV Store */ orbit.channel(channelName).put("key1", "hello world"); orbit.channel(channelName).get("key1"); // returns "hello world" + orbit.channel(channelName).remove("key1"); - // Modes + /* Modes */ const password = 'hello'; let channelModes; channelModes = orbit.channel(channel).setMode({ mode: "+r", params: { password: password } }); // { modes: { r: { password: 'hello' } } } @@ -94,7 +97,7 @@ async(() => { channelModes = orbit.channel(channel, password).setMode({ mode: "-r" }); // { modes: { ... } } channelModes = orbit.channel(channel, '').setMode({ mode: "-w" }); // { modes: {} } - // Delete channel + /* Delete channel */ const result = orbit.channel(channelName, channelPwd).delete(); // true | false })(); ``` diff --git a/package.json b/package.json index dea8830..f5e7d85 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,23 @@ { - "name": "orbit-client", + "name": "orbit-db", "version": "0.1.1", - "description": "Event Log and KeyValue Store on IPFS", + "description": "Key-Value Store and Event Log on IPFS", "author": "Haad", "license": "MIT", - "engines" : { - "node" : "^4.x.x" + "engines": { + "node": "^4.x.x" }, "main": "src/OrbitClient.js", "dependencies": { "asyncawait": "^1.0.1", "bluebird": "^3.1.1", "bs58": "^3.0.0", - "ipfsd-ctl": "^0.7.1", + "orbit-common": "^0.1.0", "unirest": "^0.4.2" }, "devDependencies": { - "mocha": "^2.3.4" + "mocha": "^2.3.4", + "orbit-server": "^0.1.2" }, "scripts": { "test": "mocha" diff --git a/src/Aggregator.js b/src/Aggregator.js index 2a6bafb..c4a571c 100644 --- a/src/Aggregator.js +++ b/src/Aggregator.js @@ -2,12 +2,12 @@ var async = require('asyncawait/async'); var await = require('asyncawait/await'); -var ipfsAPI = require('./ipfs-api-promised'); +var ipfsAPI = require('orbit-common/lib/ipfs-api-promised'); +var Keystore = require('orbit-common/lib/Keystore'); +var Encryption = require('orbit-common/lib/Encryption'); var HashCache = require('./HashCacheClient'); var HashCacheItem = require('./HashCacheItem').EncryptedHashCacheItem; var HashCacheOps = require('./HashCacheItem').HashCacheOps; -var Keystore = require('./Keystore'); -var Encryption = require('./Encryption'); var pubkey = Keystore.getKeys().publicKey; var privkey = Keystore.getKeys().privateKey; @@ -29,7 +29,7 @@ class Aggregator { if(item) { if((item.op === HashCacheOps.Put || item.op === HashCacheOps.Add) && !this._contains(handledItems, item.key)) { - if(!opts.key || opts.key && opts.key === item.key) { + if(!opts.key || (opts.key && opts.key === item.key)) { res.push({ hash: hash, item: item }); currentDepth ++; handledItems.push(item.target); diff --git a/src/Encryption.js b/src/Encryption.js deleted file mode 100644 index 67f9a8f..0000000 --- a/src/Encryption.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var crypto = require('crypto'); - -var algorithm = 'aes-256-ecb'; - -class Encryption { - static encrypt(text, privkey, pubkey) { - var encrypted; - try { - var cipher = crypto.createCipher(algorithm, privkey) - encrypted = cipher.update(text, 'utf8', 'hex') - encrypted += cipher.final('hex'); - } catch(e) { - console.log("Error while encrypting:", e, e.stack); - } - return encrypted; - } - - static decrypt(text, privkey, pubkey) { - var decrypted; - try { - var cipher = crypto.createDecipher(algorithm, privkey) - decrypted = cipher.update(text, 'hex', 'utf8') - decrypted += cipher.final('utf8'); - } catch(e) { - console.log("Error while decrypting:", e, e.stack); - } - return decrypted; - } - - static hashWithSHA512(data, salt) { - if(!salt) salt = "null"; - var hash = crypto.createHmac('sha512', salt); - hash.update(data); - var value = hash.digest('hex'); - return value; - } - - static sign(data, privkey, seq, salt) { - if(!salt) salt = "null"; - var sign = crypto.createSign('RSA-SHA256'); - var hash = Encryption.hashWithSHA512(data, "" + salt) - sign.update("" + seq + hash); - var sig = sign.sign(privkey, 'hex'); - return sig; - } - - static verify(data, pubkey, sig, seq, salt) { - if(!salt) salt = "null"; - var verify = crypto.createVerify('RSA-SHA256'); - var hash = Encryption.hashWithSHA512(data, salt); - verify.update("" + seq + hash); - var verified = verify.verify(pubkey, sig, 'hex'); - return verified; - } - -} - -module.exports = Encryption; diff --git a/src/HashCacheItem.js b/src/HashCacheItem.js index dcb767f..84396df 100644 --- a/src/HashCacheItem.js +++ b/src/HashCacheItem.js @@ -1,6 +1,6 @@ 'use strict'; -const Encryption = require('./Encryption'); +const Encryption = require('orbit-common/lib/Encryption'); const HashCacheOps = { Add: "ADD", diff --git a/src/OrbitClient.js b/src/OrbitClient.js index bf9a21e..43866a5 100644 --- a/src/OrbitClient.js +++ b/src/OrbitClient.js @@ -2,8 +2,10 @@ var async = require('asyncawait/async'); var await = require('asyncawait/await'); -var ipfsDaemon = require('./ipfs-daemon'); -var ipfsAPI = require('./ipfs-api-promised'); +var Keystore = require('orbit-common/lib/Keystore'); +var Encryption = require('orbit-common/lib/Encryption'); +var ipfsDaemon = require('orbit-common/lib/ipfs-daemon'); +var ipfsAPI = require('orbit-common/lib/ipfs-api-promised'); var HashCache = require('./HashCacheClient'); var HashCacheItem = require('./HashCacheItem').EncryptedHashCacheItem; var HashCacheOps = require('./HashCacheItem').HashCacheOps; @@ -11,8 +13,6 @@ var ItemTypes = require('./ItemTypes'); var MetaInfo = require('./MetaInfo'); var Post = require('./Post'); var Aggregator = require('./Aggregator'); -var Keystore = require('./Keystore'); -var Encryption = require('./Encryption'); var pubkey = Keystore.getKeys().publicKey; var privkey = Keystore.getKeys().privateKey; diff --git a/src/Post.js b/src/Post.js index 253c8d8..f9419ea 100644 --- a/src/Post.js +++ b/src/Post.js @@ -1,6 +1,6 @@ 'use strict'; -var encryption = require('./Encryption'); +var Encryption = require('orbit-common/lib/Encryption'); class Post { constructor(content) { @@ -9,7 +9,7 @@ class Post { } encrypt(privkey, pubkey) { - this.content = encryption.encrypt(this.content, privkey, pubkey); + this.content = Encryption.encrypt(this.content, privkey, pubkey); } } diff --git a/src/ipfs-api-promised.js b/src/ipfs-api-promised.js deleted file mode 100644 index b642162..0000000 --- a/src/ipfs-api-promised.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict'; - -var async = require('asyncawait/async'); -var await = require('asyncawait/await'); -var Promise = require('bluebird'); - -var ipfsAPI = { - cat: (ipfs, hash, cb) => { - var ipfscat = Promise.promisify(ipfs.cat); - return ipfscat(hash); - }, - ls: async ((ipfs, hash) => { - var ipfsls = Promise.promisify(ipfs.ls); - return ipfsls(hash); - }), - add: async ((ipfs, filePath) => { - var addFiles = Promise.promisify((filePath, cb) => { - ipfs.add(filePath, { recursive: true }, cb); - }); - return addFiles(filePath); - }), - getObject: async ((ipfs, hash) => { - var getObject = Promise.promisify(ipfs.object.get); - return getObject(hash); - }), - putObject: async ((ipfs, payload) => { - var putObject = Promise.promisify((payload, cb) => { - ipfs.object.put(new Buffer(JSON.stringify({ Data: payload })), "json", cb); - }); - return putObject(payload); - }), - patchObject: async ((ipfs, root, target) => { - var patchObject = Promise.promisify((root, target, cb) => { - ipfs.object.patch(root, ["add-link", "next", target], cb); - }); - return patchObject(root, target); - }), - statObject: async ((ipfs, hash) => { - var getObject = Promise.promisify(ipfs.object.stat); - return getObject(hash); - }), - pinObject: async ((ipfs, hash) => { - var pinObject = Promise.promisify(ipfs.pin.add); - return pinObject(hash); - }), - getPinned: async ((ipfs) => { - var getPinned = Promise.promisify(ipfs.pin.list); - var list = await (getPinned()); - return Object.keys(list.Keys); - }), - swarmPeers: async ((ipfs) => { - var getPeers = Promise.promisify(ipfs.swarm.peers); - return getPeers(); - }), - swarmConnect: async ((ipfs, hash) => { - var connect = Promise.promisify(ipfs.swarm.connect); - return await (connect(hash)); - }), - dhtPut: async ((ipfs, key, value) => { - var put = Promise.promisify(ipfs.dht.put); - return put(key, value); - }), - dhtGet: async ((ipfs, key) => { - var get = Promise.promisify(ipfs.dht.get); - return get(key); - }), - dhtQuery: async ((ipfs, peerID) => { - var query = Promise.promisify(ipfs.dht.query); - return query(peerID); - }), - dhtFindProviders: async ((ipfs, hash) => { - var findprov = Promise.promisify(ipfs.dht.findprovs); - return findprov(hash); - }), - dhtFindPeer: async ((ipfs, peerID) => { - var findpeer = Promise.promisify(ipfs.dht.findpeer); - return findpeer(peerID); - }) -} - -module.exports = ipfsAPI; diff --git a/src/ipfs-daemon.js b/src/ipfs-daemon.js deleted file mode 100644 index 1eb20d4..0000000 --- a/src/ipfs-daemon.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var async = require('asyncawait/async'); -var await = require('asyncawait/await'); -var Promise = require('bluebird'); -var ipfsdCtl = require('ipfsd-ctl'); - -const getUserHome = () => { - return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME']; -}; - -const ipfsPath = path.resolve(getUserHome() + '/.ipfs'); - -if(!fs.existsSync(ipfsPath)) - fs.mkdirSync(ipfsPath); - -const startIpfs = async (() => { - let ipfs, nodeInfo; - - try { - const ipfsNode = Promise.promisify(ipfsdCtl.local.bind(ipfsPath)) - const ipfsd = await (ipfsNode()); - const start = Promise.promisify(ipfsd.startDaemon.bind(ipfsd)); - ipfs = await (start()); - const getId = Promise.promisify(ipfs.id); - nodeInfo = await (getId()) - } catch(e) { - console.log("Error initializing ipfs daemon:", e); - return null; - } - - return { daemon: ipfs, nodeInfo: nodeInfo }; -}); - -module.exports = async(() => { - return await(startIpfs()); -}); diff --git a/test/orbit-client-tests.js b/test/orbit-client-tests.js index 862c24e..12f8311 100644 --- a/test/orbit-client-tests.js +++ b/test/orbit-client-tests.js @@ -1,78 +1,69 @@ 'use strict'; +var fs = require('fs'); +var path = require('path'); var assert = require('assert'); var async = require('asyncawait/async'); var await = require('asyncawait/await'); -var encryption = require('../src/Encryption'); +var ipfsDaemon = require('orbit-common/lib/ipfs-daemon'); +var logger = require('orbit-common/lib/logger'); +var Server = require('orbit-server/src/server'); var OrbitClient = require('../src/OrbitClient'); -// var mockServerAddresses = [ -// "localhost", -// "127.0.0.1" -// ]; - -// var serverConfig = { -// networkId: "anon-test", -// networkName: "Anonymous Networks TEST", -// salt: "thisisthenetworksalt", -// userDataPath: path.resolve("/tmp/anon-server-tests"), -// verifyMessages: true -// } - -// Create the userDataPath in case it doesn't exist -// if(!fs.existsSync(serverConfig.userDataPath)) -// fs.mkdirSync(serverConfig.userDataPath); +var serverConfig = { + networkId: "orbitdb-test", + networkName: "OrbitDB Test Network", + salt: "hellothisisdog", + userDataPath: "/tmp/orbitdb-tests", + verifyMessages: true +} // Orbit -var host = 'localhost:3006'; -var username = 'testrunner'; -var password = ''; +const host = 'localhost'; +const port = 3006; +const username = 'testrunner'; +const password = ''; + +const startServer = async (() => { + // TODO: this should be handled by orbit-server + if(!fs.existsSync(serverConfig.userDataPath)) + fs.mkdirSync(serverConfig.userDataPath); + + return new Promise(async((resolve, reject) => { + logger.setLevel('ERROR'); + const ipfsd = await(ipfsDaemon()); + const server = Server(ipfsd.daemon, ipfsd.nodeInfo, serverConfig); + server.app.listen(port, () => { + resolve(server); + }); + })); +}); describe('Orbit Client', () => { - // let ipfs, server, orbit, httpServer; - let orbit; + let server, orbit; - let head = ''; - let second = ''; - let items = []; + let head = ''; + let items = []; let channel = 'abcdefgh'; - before(function(done) { - // logger.setLevel('ERROR'); - // Start ipfs daemon - // if(!ipfs && !server) { - // var startIpfsDaemon = new Promise(async((resolve, reject) => { - // ipfs = await(ipfsd()); - // ipfs.nodeInfo.Addresses = mockServerAddresses; - // // Start hash-cache server - // server = require('../../ipfs-backend/server/server')(ipfs.daemon, ipfs.nodeInfo, serverConfig); - // httpServer = server.app.listen(3006, async(() => { - // logger.info('network server listening at http://localhost:%s', 3006); - // orbit = await(OrbitClient.connect(host, username, password, ipfs.daemon)); - // resolve(); - // })); - // })); - // startIpfsDaemon.then(done); - // } else { - // done(); - // } - var start = () => new Promise(async((resolve, reject) => { - orbit = OrbitClient.connect(host, username, password); + before(async((done) => { + var initialize = () => new Promise(async((resolve, reject) => { + orbit = OrbitClient.connect(`${host}:${port}`, username, password); orbit.channel(channel, '').delete(); resolve(); })); - start().then(done); - }); + server = await(startServer()); + await(initialize()); + done(); + })); after(function(done) { var deleteChannel = () => new Promise(async((resolve, reject) => { - orbit.channel(channel, '').delete(); + if(orbit) orbit.channel(channel, '').delete(); resolve(); })); deleteChannel().then(done); - // server.shutdown(); - // httpServer.close(); - // rmDir(serverConfig.userDataPath); + server.shutdown(); }); /* TESTS */ @@ -80,9 +71,9 @@ describe('Orbit Client', () => { it('connects to hash-cache-server', async((done) => { assert.notEqual(orbit, null); assert.notEqual(orbit.client, null); - assert.equal(orbit.user.id, 'QmcLzfQBKuvBYLsmgt4nkaUM7i7LNL37dPtnBZWgGpjPRW'); - assert.equal(orbit.network.id, 'anon-test'); - assert.equal(orbit.network.name, 'Anonymous Networks TEST'); + assert.equal(orbit.user.id, 'Qmf5A5RSTQmcfvigT3j29Fqh2fAHRANk5ooBYKdWsPtr8U'); + assert.equal(orbit.network.id, serverConfig.networkId); + assert.equal(orbit.network.name, serverConfig.networkName); assert.notEqual(orbit.network.config.SupernodeRouting, null); assert.notEqual(orbit.network.config.Bootstrap.length, 0); done(); @@ -158,7 +149,7 @@ describe('Orbit Client', () => { it('adds an item to an empty channel', async((done) => { try { orbit.channel(channel, '').delete(); - head = orbit.channel(channel, '').add('hello'); + const head = orbit.channel(channel, '').add('hello'); assert.notEqual(head, null); assert.equal(head.startsWith('Qm'), true); assert.equal(head.length, 46); @@ -170,8 +161,8 @@ describe('Orbit Client', () => { it('adds a new item to a channel with one item', async((done) => { try { - var v = orbit.channel(channel, '').iterator().collect(); - second = orbit.channel(channel, '').add('hello'); + const head = orbit.channel(channel, '').iterator().collect()[0]; + const second = orbit.channel(channel, '').add('hello'); assert.notEqual(second, null); assert.notEqual(second, head); assert.equal(second.startsWith('Qm'), true); @@ -585,17 +576,3 @@ describe('Orbit Client', () => { }); }); - -// let rmDir = function(dirPath) { -// try { var files = fs.readdirSync(dirPath); } -// catch(e) { return; } -// if (files.length > 0) -// for (var i = 0; i < files.length; i++) { -// var filePath = dirPath + '/' + files[i]; -// if (fs.statSync(filePath).isFile()) -// fs.unlinkSync(filePath); -// else -// rmDir(filePath); -// } -// fs.rmdirSync(dirPath); -// };