mirror of
https://github.com/orbitdb/orbitdb.git
synced 2025-03-30 15:08:28 +00:00
312 lines
10 KiB
JavaScript
312 lines
10 KiB
JavaScript
'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 KeyedHashCacheItem = require('./HashCacheItem').KeyedEncryptedHashCacheItem;
|
|
var HashCacheOps = require('./HashCacheItem').HashCacheOps;
|
|
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 OrbitClient {
|
|
constructor(ipfs) {
|
|
this.sequences = {};
|
|
this.ipfs = ipfs;
|
|
this.network = {};
|
|
this.user = null;
|
|
}
|
|
|
|
channel(hash, password) {
|
|
if(password === undefined) password = '';
|
|
return {
|
|
info: (options) => this._info(hash, password),
|
|
iterator: (options) => this._iterator(hash, password, options),
|
|
add: (text, options) => {
|
|
// TODO: create updateChannelSequence(), move the update to send() and remove()
|
|
this.sequences[hash] = !this.sequences[hash] ? this._getChannelSequence(hash, password) : this.sequences[hash] + 1;
|
|
return this._send(hash, password, text, options);
|
|
},
|
|
put: (key, data, options) => {
|
|
this.sequences[hash] = !this.sequences[hash] ? this._getChannelSequence(hash, password) : this.sequences[hash] + 1;
|
|
return this._put(hash, password, key, data, options);
|
|
},
|
|
get: (key, options) => {
|
|
options = options ? Object.assign(options, { key: key }) : { key: key }
|
|
// console.log(JSON.stringify(this._iterator(hash, password, options).collect()));
|
|
const items = this._iterator(hash, password, options).collect();
|
|
return items[0] ? items[0].item.Payload : null;
|
|
},
|
|
remove: (options) => {
|
|
this.sequences[hash] = !this.sequences[hash] ? this._getChannelSequence(hash, password) : this.sequences[hash] + 1;
|
|
return this._remove(hash, password, options);
|
|
},
|
|
delete: () => this._delete(hash, password),
|
|
setMode: (mode) => this._setMode(hash, password, mode)
|
|
}
|
|
}
|
|
|
|
_getChannelSequence(channel, password) {
|
|
var seq = 0
|
|
var item = await(this.client.linkedList(channel, password).head())
|
|
if(item.head) {
|
|
var headItem = await (ipfsAPI.getObject(this.ipfs, item.head));
|
|
seq = JSON.parse(headItem.Data)["seq"] + 1;
|
|
}
|
|
return seq;
|
|
}
|
|
|
|
_iterator(channel, password, options) {
|
|
const messages = this._getMessages(channel, password, options);
|
|
|
|
// Iterator interface implementation
|
|
let currentIndex = 0;
|
|
let iterator = {
|
|
[Symbol.iterator]() {
|
|
return this;
|
|
},
|
|
next: () => {
|
|
let item = { value: null, done: true };
|
|
if(currentIndex < messages.length) {
|
|
item = { value: messages[currentIndex], done: false };
|
|
currentIndex ++;
|
|
}
|
|
return item;
|
|
},
|
|
collect: () => messages
|
|
}
|
|
|
|
return iterator;
|
|
}
|
|
|
|
_getMessages(channel, password, options) {
|
|
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 key = options.key ? options.key : null;
|
|
|
|
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, [], key);
|
|
|
|
// Slice the array
|
|
var startIndex = 0;
|
|
var endIndex = messages.length;
|
|
if(limit < 0) {
|
|
endIndex = messages.length - (gt ? 1 : 0);
|
|
} else {
|
|
startIndex = Math.max(0, messages.length - limit);
|
|
endIndex = messages.length - ((gt || lt) ? 1 : 0);
|
|
}
|
|
|
|
messages = messages.slice(startIndex, endIndex)
|
|
}
|
|
|
|
if(reverse) messages.reverse();
|
|
|
|
return messages;
|
|
}
|
|
|
|
_fetchOne(hash, password) {
|
|
let item = null;
|
|
if(hash) {
|
|
item = await (ipfsAPI.getObject(this.ipfs, hash));
|
|
let data = JSON.parse(item.Data);
|
|
|
|
// verify signature
|
|
const verified = Encryption.verify(data.target, data.pubkey, data.sig, data.seq, password);
|
|
if(!verified) throw "Item '" + hash + "' has the wrong signature"
|
|
|
|
// decrypt data structure
|
|
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);
|
|
|
|
// fetch and decrypt content
|
|
if(data.op === HashCacheOps.Add || data.op === HashCacheOps.Put) {
|
|
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;
|
|
}
|
|
|
|
// 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, key) {
|
|
var res = [];
|
|
var deletedItems = deleted ? deleted : [];
|
|
|
|
if(!currentDepth) currentDepth = 0;
|
|
|
|
var message = await (this._fetchOne(hash, password));
|
|
|
|
// console.log(message);
|
|
|
|
if(message.Data.op === HashCacheOps.Add && !this._contains(deletedItems, hash)) {
|
|
res.push({ hash: hash, item: message });
|
|
currentDepth ++;
|
|
} else if(message.Data.op === HashCacheOps.Put && !this._contains(deletedItems, message.Data.key)) {
|
|
if(!key || key && key === message.Data.key) {
|
|
res.push({ hash: hash, item: message });
|
|
currentDepth ++;
|
|
deletedItems.push(message.Data.key);
|
|
}
|
|
} else if(message.Data.op === HashCacheOps.Delete) {
|
|
deletedItems.push(message.Data.target);
|
|
}
|
|
|
|
if(key && message.Data.key === key)
|
|
return res;
|
|
|
|
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, deletedItems, key);
|
|
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, key, target, operation, options) {
|
|
var seq = this.sequences[channel];
|
|
var size = -1;
|
|
var metaInfo = new MetaInfo(ItemTypes.Message, size, new Date().getTime());
|
|
var hcItem;
|
|
if(operation === HashCacheOps.Put)
|
|
hcItem = new KeyedHashCacheItem(operation, key, seq, target, metaInfo, pubkey, privkey, password);
|
|
else
|
|
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 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.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, null, post.Hash, HashCacheOps.Add);
|
|
await(this.client.linkedList(channel, password).add(message.Hash));
|
|
return message.Hash;
|
|
}
|
|
|
|
// WIP
|
|
_put(channel, password, key, data, options) {
|
|
// TODO: options
|
|
var post = this._publish(data);
|
|
var message = this._createMessage(channel, password, key, post.Hash, HashCacheOps.Put);
|
|
await(this.client.linkedList(channel, password).add(message.Hash));
|
|
return message.Hash;
|
|
}
|
|
|
|
_remove(channel, password, options) {
|
|
const target = options.key ? options.key : (options.hash ? options.hash : null);
|
|
const message = this._createMessage(channel, password, null, target, HashCacheOps.Delete);
|
|
await(this.client.linkedList(channel, password).add(message.Hash))
|
|
return message.Hash;
|
|
}
|
|
|
|
_delete(channel, password) {
|
|
await(this.client.linkedList(channel, password).delete())
|
|
delete this.sequences[channel];
|
|
return true;
|
|
}
|
|
|
|
_setMode(channel, password, modes) {
|
|
var m = []
|
|
if(typeof modes !== 'Array')
|
|
m.push(modes);
|
|
else
|
|
m = modes;
|
|
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) {
|
|
this.client = await(HashCache.connect(host, username, password));
|
|
this.user = this.client.info.user;
|
|
this.network = {
|
|
id: this.client.info.networkId,
|
|
name: this.client.info.name,
|
|
config: this.client.info.config
|
|
};
|
|
}
|
|
}
|
|
|
|
class OrbitClientFactory {
|
|
static connect(host, username, password, ipfs) {
|
|
if(!ipfs) {
|
|
var ipfsd = await(ipfsDaemon());
|
|
ipfs = ipfsd.daemon;
|
|
}
|
|
|
|
var client = new OrbitClient(ipfs);
|
|
await(client._connect(host, username, password))
|
|
return client;
|
|
}
|
|
}
|
|
|
|
module.exports = OrbitClientFactory;
|