orbit-db/OrbitClient.js

214 lines
6.3 KiB
JavaScript
Raw Normal View History

2015-12-26 19:05:51 +02:00
'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;