Add .remove(<hash>) functionality, add more test (channel info)

This commit is contained in:
haad
2016-01-17 18:10:16 +08:00
parent 60ea31f1ff
commit c5fe0ff17e
6 changed files with 197 additions and 76 deletions

View File

@@ -1,9 +1,15 @@
'use strict';
var encryption = require('./Encryption');
let encryption = require('./Encryption');
let HashCacheOps = {
Add: "ADD",
Delete: "DELETE"
}
class HashCacheItem {
constructor(sequenceNumber, targetHash, metaInfo) {
constructor(operation, sequenceNumber, targetHash, metaInfo) {
this.op = operation;
this.seq = sequenceNumber;
this.target = targetHash;
this.meta = metaInfo;
@@ -11,8 +17,8 @@ class HashCacheItem {
}
class EncryptedHashCacheItem extends HashCacheItem {
constructor(sequenceNumber, targetHash, metaInfo, publicKey, privateKey, salt) {
super(sequenceNumber, targetHash, metaInfo);
constructor(operation, sequenceNumber, targetHash, metaInfo, publicKey, privateKey, salt) {
super(operation, sequenceNumber, targetHash, metaInfo);
this.pubkey = publicKey;
try {
this.target = encryption.encrypt(targetHash, privateKey, publicKey);
@@ -26,6 +32,7 @@ class EncryptedHashCacheItem extends HashCacheItem {
}
module.exports = {
HashCacheOps: HashCacheOps,
HashCacheItem: HashCacheItem,
EncryptedHashCacheItem: EncryptedHashCacheItem
};

View File

@@ -6,6 +6,7 @@ var ipfsDaemon = require('./ipfs-daemon');
var ipfsAPI = require('./ipfs-api-promised');
var HashCache = require('./HashCacheClient');
var HashCacheItem = require('./HashCacheItem').EncryptedHashCacheItem;
var HashCacheOps = require('./HashCacheItem').HashCacheOps;
var MetaInfo = require('./MetaInfo');
var ItemTypes = require('./ItemTypes');
var Keystore = require('./Keystore');
@@ -24,12 +25,18 @@ class OrbitClient {
}
channel(hash, password) {
if(password === undefined) password = '';
return {
info: (options) => this._info(hash, password),
iterator: (options) => this._iterator(hash, password, options),
send: (text, options) => {
put: (text, options) => {
this.sequences[hash] = !this.sequences[hash] ? this._getChannelSequence(hash, password) : this.sequences[hash] + 1;
return this._send(hash, password, text, options);
},
remove: (targetHash) => {
this.sequences[hash] = !this.sequences[hash] ? this._getChannelSequence(hash, password) : this.sequences[hash] + 1;
return this._remove(hash, password, targetHash);
},
delete: () => this._delete(hash, password),
setMode: (mode) => this._setMode(hash, password, mode)
}
@@ -75,11 +82,11 @@ class OrbitClient {
// Slice the array
var startIndex = 0;
var endIndex = messages.length;
if(limit > -1) {
if(limit < 0) {
endIndex = messages.length - (gt ? 1 : 0);
} else {
startIndex = Math.max(0, messages.length - limit);
endIndex = messages.length - ((gt || lt) ? 1 : 0);
} else if(limit === -1) {
endIndex = messages.length - (gt ? 1 : 0);
}
messages = messages.slice(startIndex, endIndex)
@@ -100,16 +107,14 @@ class OrbitClient {
return this;
},
next: () => {
var item = { value: messages[currentIndex], done: false };
if(currentIndex < messages.length)
var item = { value: null, done: true };
if(currentIndex < messages.length) {
item = { value: messages[currentIndex], done: false };
currentIndex ++;
else
item = { value: null, done: true };
}
return item;
},
collect: () => {
return messages;
}
collect: () => messages
}
return iterator;
@@ -119,40 +124,62 @@ class OrbitClient {
var item = null;
if(hash) {
item = await (ipfsAPI.getObject(this.ipfs, hash));
var data = JSON.parse(item.Data);
let data = JSON.parse(item.Data);
// verify
var verified = Encryption.verify(data.target, data.pubkey, data.sig, data.seq, password);
const 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');
const targetDec = Encryption.decrypt(data.target, privkey, 'TODO: pubkey');
const metaDec = Encryption.decrypt(data.meta, privkey, 'TODO: pubkey');
data.target = targetDec;
data.meta = JSON.parse(metaDec);
item.Data = data;
if(data.op === HashCacheOps.Add) {
const payload = await (ipfsAPI.getObject(this.ipfs, data.target));
const contentEnc = JSON.parse(payload.Data)["content"];
const contentDec = Encryption.decrypt(contentEnc, privkey, 'TODO: pubkey');
item.Payload = contentDec;
}
item.Data = data;
}
return item;
}
_fetchRecursive(hash, password, amount, last, currentDepth) {
// TEMP
_contains(src, e) {
let contains = false;
src.forEach((a) => {
if(a.toString() === e.toString()) contains = true;
});
return contains;
}
_fetchRecursive(hash, password, amount, last, currentDepth, deleted) {
var res = [];
var deletedItems = deleted ? deleted : [];
if(!currentDepth) currentDepth = 0;
if(!last && amount > -1 && currentDepth >= amount)
return res;
var message = await (this._fetchOne(hash, password));
res.push({ hash: hash, item: message });
currentDepth ++;
if(message.Data.op === HashCacheOps.Add && !this._contains(deletedItems, hash)) {
res.push({ hash: hash, item: message });
currentDepth ++;
} else if(message.Data.op === HashCacheOps.Delete) {
deletedItems.push(message.Data.target);
}
if((last && hash === last))
return res;
if(!last && amount > -1 && currentDepth >= amount)
return res;
if(message && message.Links[0]) {
var next = this._fetchRecursive(message.Links[0].Hash, password, amount, last, currentDepth);
var next = this._fetchRecursive(message.Links[0].Hash, password, amount, last, currentDepth, deletedItems);
res = res.concat(next);
}
@@ -165,29 +192,34 @@ class OrbitClient {
return await (ipfsAPI.putObject(this.ipfs, JSON.stringify(post)));
}
_createMessage(channel, password, post) {
_createMessage(channel, password, target, operation) {
var seq = this.sequences[channel];
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 hcItem = new HashCacheItem(operation, seq, target, 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));
var prevHead = await(this.client.linkedList(channel, password).head());
var headItem = await (ipfsAPI.getObject(this.ipfs, prevHead.head));
seq = JSON.parse(headItem.Data)["seq"] + 1;
newHead = await (ipfsAPI.patchObject(this.ipfs, item.Hash, prevHead.hash))
newHead = await (ipfsAPI.patchObject(this.ipfs, item.Hash, prevHead.head))
this.sequences[channel] = seq;
}
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);
var message = this._createMessage(channel, password, post.Hash, HashCacheOps.Add);
await(this.client.linkedList(channel, password).add(message.Hash));
return message.Hash;
}
_remove(channel, password, target) {
var message = this._createMessage(channel, password, target, HashCacheOps.Delete);
await(this.client.linkedList(channel, password).add(message.Hash))
return message.Hash;
}
@@ -204,7 +236,12 @@ class OrbitClient {
m.push(modes);
else
m = modes;
return await(this.client.linkedList(channel, password).setMode(m))
var res = await(this.client.linkedList(channel, password).setMode(m));
return res.modes;
}
_info(channel, password) {
return await(this.client.linkedList(channel, password).head())
}
_connect(host, username, password) {
@@ -215,9 +252,7 @@ class OrbitClient {
name: this.client.info.name,
config: this.client.info.config
};
return;
}
}
class OrbitClientFactory {

View File

@@ -9,11 +9,9 @@ Client library to interact with orbit-server. Implements the levelDOWN API witho
orbit-server uses linked lists on top of IPFS. orbit-server not *yet* released, working on it.
### TODO
- local caching of messages
- channel.send()
- API for sending a file
- use HTTPS instead of HTTP (channel password are sent in plaintext atm)
- API for fetching message content
- Tests for .remove(...)
- Local caching of messages
- Use HTTPS instead of HTTP (channel password are sent in plaintext atm)
- API for fetching user info
- OrbitNetwork
+ channel system (join, part, pub/sub)
@@ -23,14 +21,16 @@ orbit-server uses linked lists on top of IPFS. orbit-server not *yet* released,
channel(name, password)
.send(text)
.put(text)
.remove(hash)
.iterator([options])
.delete()
.setMode(modes)
.delete()
## Usage
```javascript
var async = require('asyncawait/async');
@@ -43,13 +43,15 @@ async(() => {
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>
var head = orbit.channel(channelName).send('hello'); // <ipfs-hash>
// Delete a message
orbit.channel(channelName).remove(head);
// Iterator options
var options = { limit: -1 }; // fetch all messages
var options = { limit: -1 }; // fetch all messages, default is 1
// {
// gt: <hash>,
// gte: <hash>,
@@ -60,7 +62,7 @@ async(() => {
// }
// Get messages
var iter = orbit.channel(channelName, channelPwd).iterator(options); // Symbol.iterator
var iter = orbit.channel(channelName).iterator(options); // Symbol.iterator
var next = iter.next(); // { value: <item>, done: false|true}
// OR:
@@ -70,7 +72,7 @@ async(() => {
// Set modes
var password = 'hello';
var channelModes;
channelModes = orbit.channel(channel, channelPwd).setMode({ mode: "+r", params: { password: password } }); // { modes: { r: { password: 'hello' } } }
channelModes = orbit.channel(channel).setMode({ mode: "+r", params: { password: password } }); // { modes: { r: { password: 'hello' } } }
channelModes = orbit.channel(channel, password).setMode({ mode: "+w", params: { ops: [orbit.user.id] } }); // { modes: { ... } }
channelModes = orbit.channel(channel, password).setMode({ mode: "-r" }); // { modes: { ... } }
channelModes = orbit.channel(channel, '').setMode({ mode: "-w" }); // { modes: {} }

View File

@@ -29,7 +29,7 @@ let run = (async(() => {
var iter = orbit.channel(channel, '').iterator(options);
for(let i of iter) {
console.log(i.item.Data.seq, i.hash, "ts: " + i.item.Data.meta.ts);
console.log(i.item.Data.seq, i.item.Data.op, i.hash, "ts: " + i.item.Data.meta.ts, i.item.Payload);
}
console.log("Fetch messages took " + timer.stop() + "ms");

View File

@@ -18,16 +18,36 @@ let run = (async(() => {
// Delete channel and its data
var result = orbit.channel(channel, '').delete();
// Add the first message and delete it immediately
orbit.channel(channel, '').put("hello world!");
var e = orbit.channel(channel, '').iterator({ limit: -1 }).collect()[0].hash;
orbit.channel(channel, '').remove(e);
var messages = 100;
var i = 0;
while(i < messages) {
var i = 1;
while(i <= messages) {
var timer = new Timer(true);
// Send a message
var head = orbit.channel(channel, '').send(JSON.stringify({ omg: "hello" }));
// var head = orbit.channel(channel, '').send(JSON.stringify({ omg: "hello" }));
var head = orbit.channel(channel, '').put("hello world " + i);
console.log(i, head, timer.stop() + "ms");
if(i === 4) {
console.log("remove", head);
orbit.channel(channel, '').remove(head);
}
i ++;
}
var items = orbit.channel(channel, '').iterator({ limit: -1 }).collect();
orbit.channel(channel, '').remove(items[2].hash); // 97
orbit.channel(channel, '').remove(items[3].hash); // 96
orbit.channel(channel, '').remove(items[66].hash); // 34
orbit.channel(channel, '').remove(items[items.length - 10].hash); // 11
orbit.channel(channel, '').remove(items[items.length - 9].hash); // 10
orbit.channel(channel, '').remove(items[items.length - 8].hash); // 9
} catch(e) {
console.error("error:", e);
console.error(e.stack);

View File

@@ -35,7 +35,7 @@ describe('Orbit Client', () => {
let head = '';
let second = '';
let items = [];
let channel = 'abc1';
let channel = 'abcde';
before(function(done) {
// logger.setLevel('ERROR');
@@ -58,17 +58,21 @@ describe('Orbit Client', () => {
// }
var start = () => new Promise(async((resolve, reject) => {
orbit = OrbitClient.connect(host, username, password);
orbit.channel(channel, 'hello').setMode({ mode: "-r" })
// orbit.channel(channel, 'hello').setMode({ mode: "-r" })
resolve();
}));
start().then(done);
});
after(function(done) {
var deleteChannel = () => new Promise(async((resolve, reject) => {
orbit.channel(channel, '').delete();
resolve();
}));
deleteChannel().then(done);
// server.shutdown();
// httpServer.close();
// rmDir(serverConfig.userDataPath);
done();
});
/* TESTS */
@@ -80,11 +84,54 @@ describe('Orbit Client', () => {
assert.equal(orbit.network.id, 'anon-test');
assert.equal(orbit.network.name, 'Anonymous Networks TEST');
assert.notEqual(orbit.network.config.SupernodeRouting, null);
assert.equal(orbit.network.config.Bootstrap.length, 3);
assert.notEqual(orbit.network.config.Bootstrap.length, 0);
done();
}));
});
describe('Info', function() {
it('gets channel info on empty channel', async((done) => {
var info = orbit.channel(channel, '').info();
assert.notEqual(info, null);
assert.equal(info.head, null);
assert.notEqual(info.modes, null);
done();
}));
it('gets channel info on an existing channel', async((done) => {
var msg = orbit.channel(channel, '').put('hello');
var info = orbit.channel(channel, '').info();
assert.notEqual(info, null);
assert.equal(info.head, msg);
assert.notEqual(info.modes, null);
assert.equal(info.modes.r, null);
done();
}));
it('gets channel info when channel has modes set', async((done) => {
try {
orbit.channel(channel).delete();
var mode = {
mode: "+r",
params: {
password: 'password'
}
};
var res = orbit.channel(channel, '').setMode(mode)
var info = orbit.channel(channel, 'password').info();
assert.notEqual(info, null);
assert.equal(info.head, null);
assert.equal(JSON.stringify(info.modes), JSON.stringify(res));
orbit.channel(channel, 'password').delete();
} catch(e) {
orbit.channel(channel, 'password').delete();
assert.equal(e, null);
}
done();
}));
});
describe('Delete', function() {
it('deletes a channel from the database', async((done) => {
var result = orbit.channel(channel, '').delete();
@@ -110,7 +157,7 @@ describe('Orbit Client', () => {
describe('Insert', function() {
it('adds an item to an empty channel', async((done) => {
try {
head = orbit.channel(channel, '').send('hello');
head = orbit.channel(channel, '').put('hello');
assert.notEqual(head, null);
assert.equal(head.startsWith('Qm'), true);
assert.equal(head.length, 46);
@@ -122,7 +169,7 @@ describe('Orbit Client', () => {
it('adds a new item to a channel with one item', async((done) => {
try {
second = orbit.channel(channel, '').send('hello');
second = orbit.channel(channel, '').put('hello');
assert.notEqual(second, null);
assert.notEqual(second, head);
assert.equal(second.startsWith('Qm'), true);
@@ -136,7 +183,7 @@ describe('Orbit Client', () => {
it('adds five items', async((done) => {
for(var i = 0; i < 5; i ++) {
try {
var s = orbit.channel(channel, '').send('hello');
var s = orbit.channel(channel, '').put('hello');
assert.notEqual(s, null);
assert.equal(s.startsWith('Qm'), true);
assert.equal(s.length, 46);
@@ -151,7 +198,7 @@ describe('Orbit Client', () => {
try {
var msg = new Buffer(512);
msg.fill('a')
var s = orbit.channel(channel, '').send(msg.toString());
var s = orbit.channel(channel, '').put(msg.toString());
assert.notEqual(s, null);
assert.equal(s.startsWith('Qm'), true);
assert.equal(s.length, 46);
@@ -172,7 +219,7 @@ describe('Orbit Client', () => {
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');
var s = orbit.channel(channel, '').put('hello' + i);
items.push(s);
}
resolve();
@@ -183,8 +230,15 @@ describe('Orbit Client', () => {
describe('Defaults', function() {
it('returns an iterator', async((done) => {
var iter = orbit.channel(channel, '').iterator();
var next = iter.next().value;
assert.notEqual(iter, null);
assert.notEqual(iter.next, null);
assert.notEqual(next, null);
assert.notEqual(next.item, null);
assert.notEqual(next.item.Links, null);
assert.notEqual(next.item.Data, null);
assert.equal(next.item.Data.seq, 4);
assert.notEqual(next.item.Payload, null);
assert.equal(next.item.Payload, 'hello4');
done();
}));
@@ -205,6 +259,7 @@ describe('Orbit Client', () => {
var second = iter.next().value;
assert.equal(first.hash, items[items.length - 1]);
assert.equal(second, null);
assert.equal(first.item.Payload, 'hello4');
done();
}));
});
@@ -214,6 +269,8 @@ describe('Orbit Client', () => {
var iter = orbit.channel(channel, '').iterator({ limit: -1 });
var messages = iter.collect();
assert.equal(messages.length, items.length);
assert.equal(messages[messages.length - 1].item.Payload, 'hello0');
assert.equal(messages[0].item.Payload, 'hello4');
done();
}));
@@ -458,9 +515,9 @@ describe('Orbit Client', () => {
password: password
}
};
var res = orbit.channel(channel, '').setMode(mode)
assert.notEqual(res.modes.r, null);
assert.equal(res.modes.r.password, password);
var modes = orbit.channel(channel, '').setMode(mode)
assert.notEqual(modes.r, null);
assert.equal(modes.r.password, password);
} catch(e) {
assert.equal(e, null);
}
@@ -469,7 +526,7 @@ describe('Orbit Client', () => {
it('can\'t read with wrong password', async((done) => {
try {
var res = orbit.channel(channel, '').iterator();
var modes = orbit.channel(channel, '').iterator();
assert.equal(true, false);
} catch(e) {
assert.equal(e, 'Unauthorized');
@@ -485,9 +542,9 @@ describe('Orbit Client', () => {
ops: [orbit.user.id]
}
};
var res = orbit.channel(channel, password).setMode(mode);
assert.notEqual(res.modes.w, null);
assert.equal(res.modes.w.ops[0], orbit.user.id);
var modes = orbit.channel(channel, password).setMode(mode);
assert.notEqual(modes.w, null);
assert.equal(modes.w.ops[0], orbit.user.id);
} catch(e) {
assert.equal(e, null);
}
@@ -501,8 +558,8 @@ describe('Orbit Client', () => {
it('removes write mode', async((done) => {
try {
var res = orbit.channel(channel, password).setMode({ mode: "-w" });
assert.equal(res.modes.w, null);
var modes = orbit.channel(channel, password).setMode({ mode: "-w" });
assert.equal(modes.w, null);
} catch(e) {
assert.equal(e, null);
}
@@ -511,8 +568,8 @@ describe('Orbit Client', () => {
it('removes read mode', async((done) => {
try {
var res = orbit.channel(channel, password).setMode({ mode: "-r" });
assert.equal(res.modes.r, null);
var modes = orbit.channel(channel, password).setMode({ mode: "-r" });
assert.equal(modes.r, null);
} catch(e) {
assert.equal(e, null);
}