Initial version

This commit is contained in:
haad 2015-12-26 19:05:51 +02:00
commit f8e363eedd
19 changed files with 1318 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*sublime*
node_modules/
debug.log
WIP/

65
BetterRequest.js Normal file
View File

@ -0,0 +1,65 @@
'use strict'
var unirest = require('unirest')
class Request {
constructor() {
this.url = '';
this.method = 'GET';
this.headers = {};
this.body = {};
}
get(url) {
return this._init('GET', url);
}
post(url) {
return this._init('POST', url);
}
put(url) {
return this._init('PUT', url);
}
delete(url) {
return this._init('DELETE', url);
}
_init(method, url) {
this.url = url;
this.method = method;
this.body = {};
this.headers = {};
return this;
}
set(key, value) {
this.headers[key] = value;
return this;
}
send(body) {
this.body = body;
return this;
}
end(callback) {
if(!this.url.startsWith("http"))
this.url = "http://" + this.url
unirest(this.method, this.url)
.headers(this.headers)
.type('application/json')
.send(this.body)
.end((res) => {
if(res.error)
callback(res.body ? res.body.message : "Connection refused", null);
else
callback(null, res.body);
});
}
}
module.exports = new Request();

85
Encryption.js Normal file
View File

@ -0,0 +1,85 @@
'use strict';
var fs = require('fs');
var crypto = require('crypto');
var Base58 = require('bs58');
var algorithm = 'aes-256-ecb';
var useEncryption = true;
/* ENCRYPTION / DECRYPTION */
function encrypt(text, privkey, pubkey) {
if(!useEncryption)
return text;
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;
}
function decrypt(text, privkey, pubkey) {
if(!useEncryption)
return text;
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;
}
/* SIGNING FUNCTIONS */
function hashWithSHA512(data, salt) {
if(!salt) salt = "null";
var hash = crypto.createHmac('sha512', salt);
hash.update(data);
var value = hash.digest('hex');
return value;
}
function sign(data, privkey, seq, salt) {
if(!salt) salt = "null";
var sign = crypto.createSign('RSA-SHA256');
var hash = hashWithSHA512(data, "" + salt)
sign.update("" + seq + hash);
var sig = sign.sign(privkey, 'hex');
return sig;
}
function verify(data, pubkey, sig, seq, salt) {
if(!salt) salt = "null";
var verify = crypto.createVerify('RSA-SHA256');
var hash = hashWithSHA512(data, salt);
verify.update("" + seq + hash);
var verified = verify.verify(pubkey, sig, 'hex');
return verified;
}
/* PUBLIC */
module.exports = {
encrypt: function(payload, privkey, pubkey) {
return encrypt(payload, privkey, pubkey);
},
decrypt: function(payload, privkey, pubkey) {
return decrypt(payload, privkey, pubkey);
},
sign: (data, privateKey, sequenceNumber, salt) => {
return sign(data, privateKey, sequenceNumber, salt);
},
verify: (data, publicKey, signature, sequenceNumber, salt) => {
return verify(data, publicKey, signature, sequenceNumber, salt);
},
hash: (data, salt) => {
return hashWithSHA512(data, salt);
}
}

83
HashCacheClient.js Normal file
View File

@ -0,0 +1,83 @@
'use strict'
var request = require('./BetterRequest');
class HashCacheClient {
constructor(host, credentials) {
this.host = host
this.credentials = credentials
this.linkedList = this.linkedList.bind(this)
}
linkedList(hash, password) {
return {
head: () => this._get(hash, password),
add: (head) => this._add(hash, password, head),
setMode: (mode) => this._setModes(hash, password, mode),
delete: () => this._delete(hash, password)
}
}
_get(hash, password) {
return new Promise((resolve, reject) => {
request
.get(this.host + '/channel/' + hash)
.set('Authorization', this.credentials)
.send({ password: password })
.end((err, res) => { this._resolveRequest(err, res, resolve, reject) });
})
}
_add(hash, password, head) {
return new Promise((resolve, reject) => {
request
.put(this.host + '/channel/' + hash + '/add')
.set('Authorization', this.credentials)
.send({ head: head, password: password })
.end((err, res) => { this._resolveRequest(err, res, resolve, reject) });
})
}
_setModes(hash, password, modes) {
return new Promise((resolve, reject) => {
request
.post(this.host + '/channel/' + hash)
.set('Authorization', this.credentials)
.send({ modes: modes, password: password })
.end((err, res) => { this._resolveRequest(err, res, resolve, reject) });
})
}
_delete(hash, password) {
return new Promise((resolve, reject) => {
request
.delete(this.host + '/channel/' + hash)
.set('Authorization', this.credentials)
.end((err, res) => { this._resolveRequest(err, res, resolve, reject) });
})
}
_resolveRequest(err, res, resolve, reject) {
if(err)
reject(res ? res : err.toString());
else
resolve(res ? res : {});
}
}
module.exports = {
connect: (host, username, password) => {
var credentials = `Basic ${username}=${password}`;
return new Promise((resolve, reject) => {
request
.post(host + '/register')
.set('Authorization', credentials)
.end((err, res) => {
if(err)
reject(res ? res.body.message : err.toString())
else
resolve(new HashCacheClient(host, credentials, res));
})
})
}
}

31
HashCacheItem.js Normal file
View File

@ -0,0 +1,31 @@
'use strict';
var encryption = require('./Encryption');
class HashCacheItem {
constructor(sequenceNumber, targetHash, metaInfo) {
this.seq = sequenceNumber;
this.target = targetHash;
this.meta = metaInfo;
}
}
class EncryptedHashCacheItem extends HashCacheItem {
constructor(sequenceNumber, targetHash, metaInfo, publicKey, privateKey, salt) {
super(sequenceNumber, targetHash, metaInfo, publicKey, privateKey, salt);
this.pubkey = publicKey;
this.target = encryption.encrypt(targetHash, privateKey, publicKey);
this.payload = this.target; // old hash-cache api compatibility
this.meta = encryption.encrypt(JSON.stringify(metaInfo), privateKey, publicKey);
try {
this.sig = encryption.sign(this.target, privateKey, this.seq, salt || "");
} catch(e) {
console.log("Signing HashCacheItem failed:", e);
}
}
}
module.exports = {
HashCacheItem: HashCacheItem,
EncryptedHashCacheItem: EncryptedHashCacheItem
};

11
ItemTypes.js Normal file
View File

@ -0,0 +1,11 @@
'use strict';
let ItemTypes = {
Message: "text",
Snippet: "snippet",
File: "file",
List: "list",
Link: "link"
};
module.exports = ItemTypes;

20
Keystore.js Normal file
View File

@ -0,0 +1,20 @@
'use strict';
var fs = require('fs');
var path = require('path');
var getAppPath = () => {
return process.type && process.env.ENV !== "dev" ? process.resourcesPath + "/app/" : process.cwd();
}
var privkey = fs.readFileSync(path.resolve(getAppPath(), 'keys/private.pem')).toString('ascii');
var pubkey = fs.readFileSync(path.resolve(getAppPath(), 'keys/public.pem')).toString('ascii');
module.exports = {
getKeys: (hash) => {
return {
publicKey: pubkey,
privateKey: privkey
};
}
}

49
MessageFactory.js Normal file
View File

@ -0,0 +1,49 @@
// 'use strict';
// var async = require('asyncawait/async');
// var await = require('asyncawait/await');
// var ipfsAPI = require('./ipfs-api-promised');
// var HashCacheItem = require('./HashCacheItem').EncryptedHashCacheItem;
// var MetaInfo = require('./MetaInfo');
// var ItemTypes = require('./ItemTypes');
// var Keystore = require('./Keystore');
// var Post = require('./Post');
// var encryption = require('./Encryption');
// var pubkey = Keystore.getKeys().publicKey;
// var privkey = Keystore.getKeys().privateKey;
// class MessageFactory {
// static create(text, seq) {
// // var seq = this.channels[hash].seq;
// var size = -1;
// var metaInfo = new MetaInfo(ItemTypes.Message, size, new Date().getTime());
// var hcItem = new HashCacheItem(seq, post.Hash, metaInfo, pubkey, privkey, password);
// var item = await (ipfsAPI.putObject(this.ipfs, JSON.stringify(hcItem)));
// var newHead = { Hash: item.Hash };
// if(seq > 0) {
// var iter = await (this._iterator(hash, password))
// var prevHead = iter.next();
// var headItem = await (ipfsAPI.getObject(this.ipfs, prevHead.hash));
// seq = JSON.parse(headItem.Data)["seq"] + 1;
// newHead = await (ipfsAPI.patchObject(this.ipfs, item.Hash, prevHead.hash))
// }
// return newHead;
// }
// static _publishContent(ipfs, text) {
// var post = new Post(text);
// var enc = MessageFactory._encryptPost(post);
// var res = await (ipfsAPI.putObject(ipfs, JSON.stringify(enc)));
// return res.Hash;
// }
// static _encryptPost(post) {
// return new Post(encryption.encrypt(post.content, privkey, pubkey));
// }
// }
// module.exports = MessageFactory;

13
MetaInfo.js Normal file
View File

@ -0,0 +1,13 @@
'use strict';
var encryption = require('./Encryption');
class MetaInfo {
constructor(type, size, ts) {
this.type = type;
this.size = size;
this.ts = ts;
}
}
module.exports = MetaInfo;

213
OrbitClient.js Normal file
View File

@ -0,0 +1,213 @@
'use strict';
var async = require('asyncawait/async');
var await = require('asyncawait/await');
var ipfsDaemon = require('./ipfs-daemon');
var ipfsAPI = require('./ipfs-api-promised');
var HashCache = require('./HashCacheClient');
var HashCacheItem = require('./HashCacheItem').EncryptedHashCacheItem;
var MetaInfo = require('./MetaInfo');
var ItemTypes = require('./ItemTypes');
var Keystore = require('./Keystore');
var Post = require('./Post');
var encryption = require('./Encryption');
var pubkey = Keystore.getKeys().publicKey;
var privkey = Keystore.getKeys().privateKey;
class OrbitClientFactory {
static connect(host, username, password, ipfs) {
if(!ipfs) {
var ipfsd = await(ipfsDaemon());
ipfs = ipfsd.daemon;
}
var client = new OrbitClient(host, username, password, ipfs);
await(client._connect())
return client;
}
}
class OrbitClient {
constructor(host, username, password, ipfs) {
this.host = host;
this.credentials = { username: username, password: password };
this.channels = {};
this.ipfs = ipfs;
}
channel(hash, password) {
return {
iterator: (options) => this._iterator(hash, password, options),
send: (text, options) => {
if(!this.channels[hash]) {
this.channels[hash] = { seq: 0 }
var item = await(this.client.linkedList(hash, password).head())
if(item.head) {
var headItem = await (ipfsAPI.getObject(this.ipfs, item.head));
this.channels[hash] = { seq: JSON.parse(headItem.Data)["seq"] + 1 }
}
} else {
this.channels[hash] = { seq: this.channels[hash].seq + 1 }
}
return this._send(hash, password, text, options);
},
delete: () => this._delete(hash, password),
setMode: (mode) => { /* TODO */ }
}
}
_iterator(channel, password, options) {
var currentIndex = 0;
var messages = [];
if(!options) options = {};
// Options
var limit = options.limit ? options.limit : 1;
var gt = options.gt ? options.gt : null;
var gte = options.gte ? options.gte : null;
var lt = options.lt ? options.lt : null;
var lte = options.lte ? options.lte : null;
var reverse = options.reverse ? options.reverse : false;
var startFromHash;
if(lt || lte) {
startFromHash = lte ? lte : lt;
} else {
var channel = await (this.client.linkedList(channel, password).head())
startFromHash = channel.head ? channel.head : null;
}
if((gt || lt) && limit > -1) limit += 1;
if(startFromHash) {
// Get messages
messages = this._fetchRecursive(startFromHash, password, limit, gte ? gte : gt, 0);
// Slice the array
var startIndex = 0;
var endIndex = messages.length;
if(limit > -1) {
startIndex = Math.max(messages.length - limit, 0);
endIndex = messages.length - ((gt || lt) ? 1 : 0);
} else if(limit === -1) {
endIndex = messages.length - (gt ? 1 : 0);
}
messages = messages.slice(startIndex, endIndex)
}
if(reverse) messages.reverse();
// Iterator interface implementation
let iterator = {
[Symbol.iterator]() {
return this;
},
next: () => {
var item = { value: messages[currentIndex], done: false };
if(currentIndex < messages.length)
currentIndex ++;
else
item = { value: null, done: true };
return item;
},
collect: () => {
return messages;
}
}
// return Promise.resolve(iterator);
return iterator;
}
_fetchOne(hash, password) {
var item = null;
if(hash) {
item = await (ipfsAPI.getObject(this.ipfs, hash));
var data = JSON.parse(item.Data);
// verify
var verified = encryption.verify(data.target, data.pubkey, data.sig, data.seq, password);
if(!verified) throw "Item '" + hash + "' has the wrong signature"
// decrypt
var targetDec = encryption.decrypt(data.target, privkey, 'TODO: pubkey');
var metaDec = encryption.decrypt(data.meta, privkey, 'TODO: pubkey');
data.target = targetDec;
data.meta = JSON.parse(metaDec);
item.Data = data;
}
return item;
}
_fetchRecursive(hash, password, amount, last, currentDepth) {
var res = [];
if(!last && amount > -1 && currentDepth >= amount)
return res;
var message = await (this._fetchOne(hash, password));
res.push({ hash: hash, item: message });
currentDepth ++;
if((last && hash === last))
return res;
if(message && message.Links[0]) {
var next = this._fetchRecursive(message.Links[0].Hash, password, amount, last, currentDepth);
res = res.concat(next);
}
return res;
}
_publish(text) {
var post = new Post(text);
post.encrypt(privkey, pubkey);
return await (ipfsAPI.putObject(this.ipfs, JSON.stringify(post)));
}
_createMessage(channel, password, post) {
var seq = this.channels[channel].seq;
var size = -1;
var metaInfo = new MetaInfo(ItemTypes.Message, size, new Date().getTime());
var hcItem = new HashCacheItem(seq, post.Hash, metaInfo, pubkey, privkey, password);
var item = await (ipfsAPI.putObject(this.ipfs, JSON.stringify(hcItem)));
var newHead = { Hash: item.Hash };
if(seq > 0) {
var iter = this._iterator(channel, password);
var prevHead = iter.next().value;
var headItem = await (ipfsAPI.getObject(this.ipfs, prevHead.hash));
seq = JSON.parse(headItem.Data)["seq"] + 1;
newHead = await (ipfsAPI.patchObject(this.ipfs, item.Hash, prevHead.hash))
}
return newHead;
}
_send(channel, password, text, options) {
// TODO: check options for what type to publish as (text, snippet, file, etc.)
var post = this._publish(text);
var message = this._createMessage(channel, password, post);
await(this.client.linkedList(channel, password).add(message.Hash))
return message.Hash;
}
_delete(channel, password) {
await(this.client.linkedList(channel, password).delete())
delete this.channels[channel];
return true;
}
_connect() {
this.client = await(HashCache.connect(this.host, this.credentials.username, this.credentials.password));
return;
}
}
module.exports = OrbitClientFactory;

15
Post.js Normal file
View File

@ -0,0 +1,15 @@
'use strict';
var encryption = require('./Encryption');
class Post {
constructor(content) {
this.content = content;
}
encrypt(privkey, pubkey) {
this.content = encryption.encrypt(this.content, privkey, pubkey);
}
}
module.exports = Post;

65
README.md Normal file
View File

@ -0,0 +1,65 @@
# orbit-client
## Introduction
***VERY MUCH WIP! WILL NOT WORK WHEN CLONED, orbit-server REQUIRED!***
Client library to interact with orbit-server. Implements the levelDOWN API without get(key, cb).
orbit-server uses linked lists on top of IPFS.
### TODO
- local caching of messages
- channel.send()
- check options for what type to publish as (text, snippet, file, etc.)
- channel.setMode()
## API
connect(host, username, password)
channel(name, password)
.send(text)
.iterator([options])
.delete()
## Usage
```javascript
var OrbitClient = require('./OrbitClient');
var host = 'localhost:3006'; // orbit-server address
// Connect
var orbit = OrbitClient.connect(host, username, password); // OrbitClient
var channelName = 'hello-world';
var channelPwd = '';
// Send a message
var head = orbit.channel(channelName, channelPwd).send('hello'); // <ipfs-hash>
// Iterator options
var options = { limit: -1 }; // fetch all messages
// {
// gt: <hash>,
// gte: <hash>,
// lt: <hash>,
// lte: <hash>,
// limit: 10,
// reverse: true
// }
// Get messages
var iter = orbit.channel(channelName, channelPwd).iterator(options); // Symbol.iterator
var next = iter.next(); // { value: <item>, done: false|true}
// OR:
// for(let i of iter)
// console.log(i.hash, i.item.Data.seq);
// delete channel
var result = orbit.channel(channelName, channelPwd).delete(); // true | false
```

21
TODO.md.js Normal file
View File

@ -0,0 +1,21 @@
var orbit = await (OrbitClient.connect(host, username, password));
var head = await (orbit.channel(channelName, channelPwd).send('hello world'));
var iter = await (orbit.channel(channelName, channelPwd).iterator());
var next = iter.next();
var deleted = await (orbit.channel(channelName, channelPwd).delete());
var modes = await (orbit.channel(channelName, channelPwd).setMode({mode: '+r', params: 'password': '123' }));
// Pubsub
await (orbit.channel(channelName, channelPwd).subscribe(onMessage));
var head = await (orbit.channel(channelName, channelPwd).publish('hello world'));
orbit.connect(host, username, password)
orbit.channels.list() // { 'ch1':{...}, 'ch2': {...} }
orbit.channels.join('ch3', password)
orbit.channels.leave('ch3')
orbit.channels.onMessage(callback)
orbit.publish('ch3', 'hello!')

81
ipfs-api-promised.js Normal file
View File

@ -0,0 +1,81 @@
'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;

35
ipfs-daemon.js Normal file
View File

@ -0,0 +1,35 @@
'use strict';
var path = require('path');
var async = require('asyncawait/async');
var await = require('asyncawait/await');
var Promise = require('bluebird');
var ipfsdCtl = require('ipfsd-ctl');
let getUserHome = () => {
return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
};
let ipfsPath = path.resolve(getUserHome() + '/.ipfs');
var startIpfs = async (() => {
let ipfs, nodeInfo;
try {
var ipfsNode = Promise.promisify(ipfsdCtl.local.bind(ipfsPath))
var ipfsd = await (ipfsNode());
var start = Promise.promisify(ipfsd.startDaemon.bind(ipfsd));
ipfs = await (start());
var 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());
});

27
keys/private.pem Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAnVo7VWOTSXV9Jril0lBB0G0b2Smoj5TozbK3fZ86rkgJSdiE
sIbD/dpl6oQSraHXX8Cpu9AdqvzDd3Mf/8SsKfHmobNRUKUPZkgHWbK1eMfIq+kh
0ggE4wXYTKAA7kGIzCUWGkSG2tqo7R5FDHdbpQtevh+qqg1I8cYccS1WVXFNy9Em
vdLPXH7a8ZsWzM9XrW7LX5Bpo1z2+LYVHcNYG04zVA8Ulrv3bG24ATCRKQdxzVpQ
mNx8r9W47ESo9krLoNbwrY4A7rURQjBOBS5tUq52Ui2YImA3BG57O8jY/fBT0u9O
5D8BsmjBM+4/0t7I2r1OcAc9ERNy89iBsjSLawIDAQABAoIBAFn8aygm/OkqpnrJ
aBNBUJIIg7+KYlpSV1yjUuolcGKQ0bcIesGVIGqBRn16MFBZ2JhqzuaYWw8Y5BPg
o7EaYTbPnwxkRyFuc8MTeBik6cD7x9gASBYb/edyxUL00fNvSPMuVliJTfHE0Ixi
HizF6YQWX4UtCOWdGVUi7csPmWrcrnYbALHPtb7Z4u/hSa1N4ZuaHd8ljWwDgLgB
P8B/5beeZVtpfYgrEorqRwIXxVR/vo1fafV/DUtctEPg7wEIsPAyd1Gt9WVmv1Ij
RyxXyhicGJWQnVlj6Iraco8OqSGjRspsMxnS+ccic+1U2ImDBpTHmoSj4ncyGNMP
WPodN2kCgYEAzdC9KGge/1Vm8IGGfiqm1Uu8GKhAJOi4t3i1p9f3jH5OyEM8PRHv
6hYqtoa+bTBfA5ErQSBI7Yn7qSHrCV1cV9MWD5wnxAKzRHwMAM81HbH2P6D2l/YH
QVUmdgKy7zIWUDHxaeFUdbM55Z3NQIrEmyLnv4pzQivT9tBDhHUip78CgYEAw7hg
C6mOvV6rb3MXVplZlIUmccNcEVNkc/0mcLCJ33BMV+hR4DBf+abNIeutKq8Q0hbZ
PWgbprzQAL7mnMbQXv3yVdIQrcCZKJ4Y7Tz5sofVtNVQCPbLcG/k+beM96q/jodr
IYzExub3pX9LzgZHoMdwzLDKDKy6iT0iORkLZ1UCgYBtVNuHIXiF7toWHNhTIVkq
qWasOoSIls+5NvhYxxMLoPU80OqpDmRCF7Bs8bsqya1PecheekYDWW9Ec24ltWCe
jtWDSVKef0i3sqW1sFzo/2ZuepVEOD+2ZwNdYSWohxTvWPiDZF23gJPa9C7agFzc
hHhTebqshhe9Xvje3lKghQKBgFB2eU3yHFOwGuzLtRmOG+zaPK7icPRSg2bH+Ui0
20m3wjsVsKtpV9ur/oNmf5+fr1uftxnutgd7ckwML4Tem4WiA0HTBbZZCt5O+NJ3
kfQasfoRBEFbLNIBvnIHSvX7BZuUArAdTjyzcbZhOgnuPP5VYANW3woM7PdE4c/1
guGtAoGAKhL5UTD2aQDYFWt4fQvoHTrGIT5LcifegYRd+jQRiW7DRg4opknPWdj8
xvBExU1ZXtvxpT+KuvAhAqjc8usC+5VhoEYbX2F06ubqhy9aQbCtL5sIhZPVIUIq
UVYu1uWj9FqTJWgVhdMQ3P6PrIb9UaWRwkBYsGJulbT88YPet6I=
-----END RSA PRIVATE KEY-----

19
keys/public.pem Normal file
View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDBjCCAe4CCQDai2CYe+oyADANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE1MTAyNDE4MzQwNloXDTE1MTEyMzE4MzQwNlowRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AJ1aO1Vjk0l1fSa4pdJQQdBtG9kpqI+U6M2yt32fOq5ICUnYhLCGw/3aZeqEEq2h
11/AqbvQHar8w3dzH//ErCnx5qGzUVClD2ZIB1mytXjHyKvpIdIIBOMF2EygAO5B
iMwlFhpEhtraqO0eRQx3W6ULXr4fqqoNSPHGHHEtVlVxTcvRJr3Sz1x+2vGbFszP
V61uy1+QaaNc9vi2FR3DWBtOM1QPFJa792xtuAEwkSkHcc1aUJjcfK/VuOxEqPZK
y6DW8K2OAO61EUIwTgUubVKudlItmCJgNwRuezvI2P3wU9LvTuQ/AbJowTPuP9Le
yNq9TnAHPRETcvPYgbI0i2sCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAjYne/qu7
7KylXi39x72cY/sEpTErKMhCJNm4DL3Mb4YfkDiOZFbNGO+1NKNKHB1DTbu5ECtT
vYrBLGwLICklVpVP65podAHweDGGEMW6cp4ypWOpJM4NqyNtL8HKpi2PB/n3SkYi
z7aCuagHbU+5YZhm4kj8KDjmbCyn7hU1vQVKf+fw+dLXNUAElI1GLv2tENEEevtl
Tj7DurHCrxPcdrbgxQMIbDcpPMXVX4OlS51EsT5mVgGIoUFKZN8u9+AwzU+INKZp
6SjbrK3y/HqI4jgeDQPY9GQcGooL1ro9OHfSz61m6WiYh9V6vjq2e8bmc00U/yTA
lSErmB6aqZ2+0w==
-----END CERTIFICATE-----

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "orbit-client",
"version": "1.0.0",
"description": "A client module to communicate with an Orbit network",
"main": "OrbitClient.js",
"scripts": {
"test": "mocha"
},
"author": "Haad",
"license": "ISC",
"dependencies": {
"asyncawait": "^1.0.1",
"bluebird": "^3.1.1",
"bs58": "^3.0.0",
"ipfsd-ctl": "^0.7.1",
"unirest": "^0.4.2"
},
"devDependencies": {
"mocha": "^2.3.4"
}
}

460
test/orbit-client-tests.js Normal file
View File

@ -0,0 +1,460 @@
'use strict';
var assert = require('assert');
var async = require('asyncawait/async');
var await = require('asyncawait/await');
var encryption = require('../Encryption');
var OrbitClient = require('../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);
// Orbit
var host = 'localhost:3006';
var username = 'testrunner';
var password = '';
describe('Orbit Client', () => {
// let ipfs, server, orbit, httpServer;
let orbit;
let head = '';
let second = '';
let items = [];
let channel = 'abc';
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);
resolve();
}));
start().then(done);
});
after(function(done) {
// server.shutdown();
// httpServer.close();
// rmDir(serverConfig.userDataPath);
done();
});
/* TESTS */
describe('Connect', function() {
it('connects to hash-cache-server', async((done) => {
assert.notEqual(orbit, null);
assert.notEqual(orbit.client, null);
assert.equal(orbit.host, host);
assert.equal(orbit.credentials.username, username);
assert.equal(orbit.credentials.password, password);
done();
}));
});
describe('Delete channel', function() {
it('deletes a channel', async((done) => {
var result = orbit.channel(channel, '').delete();
assert.equal(result, true);
var iter = orbit.channel(channel, '').iterator();
assert.equal(iter.next().value, null);
done();
}));
it('deletes a channel with a password', async((done) => {
done();
}));
it('doesn\'t delete a channel when password is wrong', async((done) => {
done();
}));
it('doesn\'t delete a channel when user is not an op', async((done) => {
done();
}));
});
describe('Insert', function() {
it('adds an item to an empty channel', async((done) => {
try {
head = orbit.channel(channel, '').send('hello');
assert.notEqual(head, null);
assert.equal(head.startsWith('Qm'), true);
assert.equal(head.length, 46);
} catch(e) {
assert.equal(e, null);
}
done();
}));
it('adds a new item to a channel with one item', async((done) => {
try {
second = orbit.channel(channel, '').send('hello');
assert.notEqual(second, null);
assert.notEqual(second, head);
assert.equal(second.startsWith('Qm'), true);
assert.equal(second.length, 46);
} catch(e) {
assert.equal(e, null);
}
done();
}));
it('adds five items', async((done) => {
for(var i = 0; i < 5; i ++) {
try {
var s = orbit.channel(channel, '').send('hello');
assert.notEqual(s, null);
assert.equal(s.startsWith('Qm'), true);
assert.equal(s.length, 46);
} catch(e) {
assert.equal(e, null);
}
}
done();
}));
it('adds an item that is > 256 bytes', async((done) => {
try {
var msg = new Buffer(512);
msg.fill('a')
var s = orbit.channel(channel, '').send(msg.toString());
assert.notEqual(s, null);
assert.equal(s.startsWith('Qm'), true);
assert.equal(s.length, 46);
} catch(e) {
assert.equal(e, null);
}
done();
}));
});
describe('Iterator', function() {
var items = [];
var itemCount = 5;
before(function(done) {
var addMessages = () => new Promise(async((resolve, reject) => {
var result = orbit.channel(channel, '').delete();
var iter = orbit.channel(channel, '').iterator();
for(var i = 0; i < itemCount; i ++) {
var s = orbit.channel(channel, '').send('hello');
items.push(s);
}
resolve();
}));
addMessages().then(done);
});
describe('Defaults', function() {
it('returns an iterator', async((done) => {
var iter = orbit.channel(channel, '').iterator();
assert.notEqual(iter, null);
assert.notEqual(iter.next, null);
done();
}));
it('implements Iterator interface', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: -1 });
var messages = [];
for(let i of iter)
messages.push(i.hash);
assert.equal(messages.length, items.length);
done();
}));
it('returns 1 item as default', async((done) => {
var iter = orbit.channel(channel, '').iterator();
var first = iter.next().value;
var second = iter.next().value;
assert.equal(first.hash, items[items.length - 1]);
assert.equal(second, null);
done();
}));
});
describe('Collect', function() {
it('returns all items', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: -1 });
var messages = iter.collect();
assert.equal(messages.length, items.length);
done();
}));
it('returns 1 item', async((done) => {
var iter = orbit.channel(channel, '').iterator();
var messages = iter.collect();
assert.equal(messages.length, 1);
done();
}));
it('returns 3 items', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: 3 });
var messages = iter.collect();
assert.equal(messages.length, 3);
done();
}));
});
describe('Options: limit', function() {
it('returns 1 item when limit is 0', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: 0 });
var first = iter.next().value;
var second = iter.next().value;
assert.equal(first.hash, items[items.length - 1]);
assert.equal(second, null);
done();
}));
it('returns 1 item when limit is 1', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: 1 });
var first = iter.next().value;
var second = iter.next().value;
assert.equal(first.hash, items[items.length - 1]);
assert.equal(second, null);
done();
}));
it('returns 3 items', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: 3 });
var first = iter.next().value;
var second = iter.next().value;
var third = iter.next().value;
var fourth = iter.next().value;
assert.equal(first.hash, items[items.length - 1]);
assert.equal(second.hash, items[items.length - 2]);
assert.equal(third.hash, items[items.length - 3]);
assert.equal(fourth, null);
done();
}));
it('returns all items', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: -1 });
var messages = iter.collect().map((e) => e.hash);
messages.reverse();
assert.equal(messages.length, items.length);
assert.equal(messages[0], items[0]);
done();
}));
it('returns all items when limit is bigger than -1', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: -300 });
var messages = iter.collect().map((e) => e.hash);
assert.equal(messages.length, items.length);
done();
}));
it('returns all items when limit is bigger than number of items', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: 300 });
var messages = iter.collect().map((e) => e.hash);
assert.equal(messages.length, items.length);
done();
}));
});
describe('Options: reverse', function() {
it('returns all items reversed', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: -1, reverse: true });
var messages = iter.collect().map((e) => e.hash);
assert.equal(messages.length, items.length);
assert.equal(messages[0], items[0]);
done();
}));
});
describe('Options: ranges', function() {
describe('gt & gte', function() {
it('returns 0 items when gt is the head', async((done) => {
var iter = orbit.channel(channel, '').iterator();
var head = iter.next().value;
var iter2 = orbit.channel(channel, '').iterator({ gt: head.hash });
var messages = iter2.collect().map((e) => e.hash);
assert.equal(messages.length, 0);
done();
}));
it('returns 1 item when gte is the head', async((done) => {
var iter = orbit.channel(channel, '').iterator({ gte: items[items.length -1], limit: -1 });
var messages = iter.collect().map((e) => e.hash);
assert.equal(messages.length, 1);
assert.equal(messages[0], items[items.length -1]);
done();
}));
it('returns 2 item when gte is defined', async((done) => {
var iter = orbit.channel(channel, '').iterator({ gte: items[items.length - 2], limit: -1 });
var messages = iter.collect().map((e) => e.hash);
assert.equal(messages.length, 2);
assert.equal(messages[0], items[items.length - 1]);
assert.equal(messages[1], items[items.length - 2]);
done();
}));
it('returns all items when gte is the root item', async((done) => {
var iter = orbit.channel(channel, '').iterator({ gte: items[0], limit: -1 });
var messages = iter.collect().map((e) => e.hash);
assert.equal(messages.length, itemCount);
done();
}));
it('returns items when gt is the root item', async((done) => {
var iter = orbit.channel(channel, '').iterator({ gt: items[0], limit: -1 });
var messages = iter.collect().map((e) => e.hash);
assert.equal(messages.length, itemCount - 1);
assert.equal(messages[0], items[items.length - 1]);
assert.equal(messages[3], items[1]);
done();
}));
it('returns items when gt is defined', async((done) => {
var iter = orbit.channel(channel, '').iterator({ limit: -1});
var messages = iter.collect().map((e) => e.hash);
var gt = messages[2];
var iter2 = orbit.channel(channel, '').iterator({ gt: gt, limit: 100 });
var messages2 = iter2.collect().map((e) => e.hash);
assert.equal(messages2.length, 2);
assert.equal(messages2[0], messages[0]);
assert.equal(messages2[1], messages[1]);
done();
}));
});
describe('lt & lte', function() {
it('returns one item when lt is the head', async((done) => {
var iter = orbit.channel(channel, '').iterator();
var head = iter.next().value;
var iter2 = orbit.channel(channel, '').iterator({ lt: head.hash });
var messages = iter2.collect().map((e) => e.hash);
assert.equal(messages.length, 1);
assert.equal(messages[0], head.hash);
done();
}));
it('returns all items when lt is head and limit is -1', async((done) => {
var iter = orbit.channel(channel, '').iterator();
var head = iter.next().value;
var iter2 = orbit.channel(channel, '').iterator({ lt: head.hash, limit: -1 });
var messages = iter2.collect().map((e) => e.hash);
assert.equal(messages.length, itemCount);
assert.equal(messages[0], head.hash);
assert.equal(messages[4], items[0]);
done();
}));
it('returns 3 items when lt is head and limit is 3', async((done) => {
var iter = orbit.channel(channel, '').iterator();
var head = iter.next().value;
var iter2 = orbit.channel(channel, '').iterator({ lt: head.hash, limit: 3 });
var messages = iter2.collect().map((e) => e.hash);
assert.equal(messages.length, 3);
assert.equal(messages[0], head.hash);
assert.equal(messages[2], items[2]);
done();
}));
it('returns null when lt is the root item', async((done) => {
var iter = orbit.channel(channel, '').iterator({ lt: items[0] });
var messages = iter.collect();
assert.equal(messages.length, 0);
done();
}));
it('returns one item when lte is the root item', async((done) => {
var iter = orbit.channel(channel, '').iterator({ lte: items[0] });
var messages = iter.collect().map((e) => e.hash);
assert.equal(messages.length, 1);
assert.equal(messages[0], items[0]);
done();
}));
it('returns all items when lte is the head', async((done) => {
var iter = orbit.channel(channel, '').iterator();
var head = iter.next().value;
var iter2 = orbit.channel(channel, '').iterator({ lte: head.hash, limit: -1 });
var messages = iter2.collect().map((e) => e.hash);
assert.equal(messages.length, itemCount);
assert.equal(messages[0], items[items.length - 1]);
assert.equal(messages[4], items[0]);
done();
}));
it('returns 3 items when lte is the head', async((done) => {
var iter = orbit.channel(channel, '').iterator();
var head = iter.next().value;
var iter2 = orbit.channel(channel, '').iterator({ lte: items[items.length - 2], limit: 3 });
var messages = iter2.collect().map((e) => e.hash);
assert.equal(messages.length, 3);
assert.equal(messages[0], items[items.length - 2]);
assert.equal(messages[2], items[1]);
done();
}));
});
});
});
});
// 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);
// };