UNPKG

identifi-lib

Version:

Basic tools for reading and writing Identifi messages and identities.

1,064 lines (944 loc) 37 kB
'use strict'; exports.__esModule = true; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _message = require('./message'); var _message2 = _interopRequireDefault(_message); var _key = require('./key'); var _key2 = _interopRequireDefault(_key); var _identity = require('./identity'); var _identity2 = _interopRequireDefault(_identity); var _attribute = require('./attribute'); var _attribute2 = _interopRequireDefault(_attribute); var _util = require('./util'); var _util2 = _interopRequireDefault(_util); var _gun = require('gun'); var _gun2 = _interopRequireDefault(_gun); var _then = require('gun/lib/then'); var _then2 = _interopRequireDefault(_then); var _load = require('gun/lib/load'); var _load2 = _interopRequireDefault(_load); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } // eslint-disable-line no-unused-vars // eslint-disable-line no-unused-vars // eslint-disable-line no-unused-vars var GUN_TIMEOUT = 100; // temp method for GUN search async function searchText(node, callback, query, limit, cursor, desc) { var seen = {}; node.map(function (value, key) { var cursorCheck = !cursor || desc && key < cursor || !desc && key > cursor; if (cursorCheck && key.indexOf(query) === 0) { if (typeof limit === 'number' && Object.keys(seen).length >= limit) { return; } if (seen.hasOwnProperty(key)) { return; } if (value && Object.keys(value).length > 1) { seen[key] = true; callback({ value: value, key: key }); } } }); } // TODO: flush onto IPFS /** * Identifi index root. Contains five indexes: identitiesBySearchKey, identitiesByTrustDistance, * messagesByHash, messagesByTimestamp, messagesByDistance. If you want messages saved to IPFS, pass * options.ipfs = instance. * * When you use someone else's index, initialise it using the Index constructor * @param {Object} gun gun node that contains an Identifi index (e.g. user.get('identifi')) * @param {Object} options see default options in example * @example * Default options: *{ * ipfs: undefined, * indexSync: { * importOnAdd: { * enabled: true, * maxMsgCount: 500, * maxMsgDistance: 2 * }, * subscribe: { * enabled: true, * maxMsgDistance: 1 * }, * query: { * enabled: true * }, * msgTypes: { * all: false, * rating: true, * verification: true, * unverification: true * } * } *} * @returns {Index} Identifi index object */ var Index = function () { function Index(gun, options) { var _this = this; _classCallCheck(this, Index); this.gun = gun || new _gun2.default(); this.options = Object.assign({ indexSync: { importOnAdd: { enabled: true, maxMsgCount: 100, maxMsgDistance: 2 }, subscribe: { enabled: true, maxMsgDistance: 1 }, query: { enabled: true }, msgTypes: { all: false, rating: true, verification: true, unverification: true } } }, options); if (options.viewpoint) { this.viewpoint = options.viewpoint; } else { this.gun.get('viewpoint').on(function (val, key, msg, eve) { if (val) { _this.viewpoint = new _attribute2.default(val); eve.off(); } }); } if (this.options.indexSync.subscribe.enabled) { setTimeout(function () { _this.gun.get('trustedIndexes').map().once(function (val, uri) { if (val) { // TODO: only get new messages? _this.gun.user(uri).get('identifi').get('messagesByDistance').map(function (val, key) { var d = Number.parseInt(key.split(':')[0]); console.log('got msg with d', d, key); if (!isNaN(d) && d <= _this.options.indexSync.subscribe.maxMsgDistance) { _message2.default.fromSig(val).then(function (msg) { console.log('adding msg ' + msg.hash + ' from trusted index'); if (_this.options.indexSync.msgTypes.all || _this.options.indexSync.msgTypes.hasOwnProperty(msg.signedData.type)) { _this.addMessage(msg, { checkIfExists: true }); } }); } }); } }); }, 5000); // TODO: this should be made to work without timeout } } /** * Use this to load an index that you can write to * @param {Object} gun gun instance where the index is stored (e.g. new Gun()) * @param {Object} keypair SEA keypair (can be generated with await identifiLib.Key.generate()) * @param {Object} options see default options in Index constructor's example * @returns {Promise} */ Index.create = async function create(gun, keypair) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; if (!keypair) { keypair = await _key2.default.getDefault(); } var user = gun.user(); user.auth(keypair); options.viewpoint = new _attribute2.default('keyID', _key2.default.getId(keypair)); var i = new Index(user.get('identifi'), options); i.gun.get('viewpoint').put(options.viewpoint); var uri = options.viewpoint.uri(); var g = i.gun.get('identitiesBySearchKey').get(uri); var attrs = {}; attrs[options.viewpoint.uri()] = options.viewpoint; if (options.self) { var keys = Object.keys(options.self); for (var _i = 0; _i < keys.length; _i++) { var a = new _attribute2.default(keys[_i], options.self[keys[_i]]); attrs[a.uri()] = a; } } var id = await _identity2.default.create(g, { trustDistance: 0, linkTo: options.viewpoint, attrs: attrs }); await i._addIdentityToIndexes(id.gun); if (options.self) { var recipient = Object.assign(options.self, { keyID: options.viewpoint.value }); _message2.default.createVerification({ recipient: recipient }, keypair).then(function (msg) { i.addMessage(msg); }); } return i; }; Index.getMsgIndexKey = function getMsgIndexKey(msg) { var distance = parseInt(msg.distance); distance = Number.isNaN(distance) ? 99 : distance; distance = ('00' + distance).substring(distance.toString().length); // pad with zeros var key = distance + ':' + Math.floor(Date.parse(msg.timestamp || msg.signedData.timestamp) / 1000) + ':' + (msg.ipfs_hash || msg.hash).substr(0, 9); return key; }; Index.getMsgIndexKeys = function getMsgIndexKeys(msg) { var keys = {}; var distance = parseInt(msg.distance); distance = Number.isNaN(distance) ? 99 : distance; distance = ('00' + distance).substring(distance.toString().length); // pad with zeros var hashSlice = msg.getHash().substr(0, 9); keys.messagesByHash = [msg.getHash()]; keys.messagesByTimestamp = [Math.floor(Date.parse(msg.timestamp || msg.signedData.timestamp) / 1000) + ':' + hashSlice]; keys.messagesByDistance = [distance + ':' + keys.messagesByTimestamp[0]]; keys.messagesByAuthor = []; var authors = msg.getAuthorArray(); for (var i = 0; i < authors.length; i++) { keys.messagesByAuthor.push(authors[i].uri() + ':' + msg.signedData.timestamp + ':' + hashSlice); } keys.messagesByRecipient = []; var recipients = msg.getRecipientArray(); for (var _i2 = 0; _i2 < recipients.length; _i2++) { keys.messagesByRecipient.push(recipients[_i2].uri() + ':' + msg.signedData.timestamp + ':' + hashSlice); } if (['verification', 'unverification'].indexOf(msg.signedData.type) > -1) { keys.verificationsByRecipientAndAuthor = []; for (var _i3 = 0; _i3 < recipients.length; _i3++) { var r = recipients[_i3]; if (!r.isUniqueType()) { continue; } for (var j = 0; j < authors.length; j++) { var a = authors[j]; if (!a.isUniqueType()) { continue; } keys.verificationsByRecipientAndAuthor.push(r.uri() + ':' + a.uri()); } } } else if (msg.signedData.type === 'rating') { keys.ratingsByRecipientAndAuthor = []; for (var _i4 = 0; _i4 < recipients.length; _i4++) { var _r = recipients[_i4]; if (!_r.isUniqueType()) { continue; } for (var _j = 0; _j < authors.length; _j++) { var _a = authors[_j]; if (!_a.isUniqueType()) { continue; } keys.ratingsByRecipientAndAuthor.push(_r.uri() + ':' + _a.uri()); } } } return keys; }; Index.prototype.getIdentityIndexKeys = async function getIdentityIndexKeys(identity, hash) { var indexKeys = { identitiesByTrustDistance: [], identitiesBySearchKey: [] }; var d = void 0; if (identity.linkTo && this.viewpoint.equals(identity.linkTo)) { d = 0; } else { d = await _util2.default.timeoutPromise(identity.get('trustDistance').then(), GUN_TIMEOUT); } function addIndexKey(a) { if (!(a && a.value && a.type)) { // TODO: this sometimes returns undefined return; } var distance = d !== undefined ? d : parseInt(a.dist); distance = Number.isNaN(distance) ? 99 : distance; distance = ('00' + distance).substring(distance.toString().length); // pad with zeros var v = a.value || a[1]; var n = a.type || a[0]; var value = encodeURIComponent(v); var lowerCaseValue = encodeURIComponent(v.toLowerCase()); var name = encodeURIComponent(n); var key = value + ':' + name; var lowerCaseKey = lowerCaseValue + ':' + name; if (!_attribute2.default.isUniqueType(n)) { // allow for multiple index keys with same non-unique attribute key = key + ':' + hash.substr(0, 9); lowerCaseKey = lowerCaseKey + ':' + hash.substr(0, 9); } indexKeys.identitiesBySearchKey.push(key); indexKeys.identitiesByTrustDistance.push(distance + ':' + key); if (key !== lowerCaseKey) { indexKeys.identitiesBySearchKey.push(lowerCaseKey); indexKeys.identitiesByTrustDistance.push(distance + ':' + lowerCaseKey); } if (v.indexOf(' ') > -1) { var words = v.toLowerCase().split(' '); for (var l = 0; l < words.length; l += 1) { var k = encodeURIComponent(words[l]) + ':' + name; if (!_attribute2.default.isUniqueType(n)) { k = k + ':' + hash.substr(0, 9); } indexKeys.identitiesBySearchKey.push(k); indexKeys.identitiesByTrustDistance.push(distance + ':' + k); } } if (key.match(/^http(s)?:\/\/.+\/[a-zA-Z0-9_]+$/)) { var split = key.split('/'); indexKeys.identitiesBySearchKey.push(split[split.length - 1]); indexKeys.identitiesByTrustDistance.push(distance + ':' + split[split.length - 1]); } } if (this.viewpoint.equals(identity.linkTo)) { addIndexKey(identity.linkTo); } await identity.get('attrs').map().once(addIndexKey).then(); return indexKeys; }; /** * @returns {Identity} viewpoint identity of the index */ Index.prototype.getViewpoint = async function getViewpoint() { var vpAttr = void 0; if (this.viewpoint) { vpAttr = this.viewpoint; } else { vpAttr = new _attribute2.default((await this.gun.get('viewpoint').then())); } return new _identity2.default(this.gun.get('identitiesBySearchKey').get(vpAttr.uri())); }; /** * Get an identity referenced by an identifier. * get(type, value) * get(Attribute) * get(value) - guesses the type or throws an error * @returns {Identity} identity that is connected to the identifier param */ Index.prototype.get = function get(a, b) { if (!a) { throw new Error('get failed: param must be a string, received ' + (typeof a === 'undefined' ? 'undefined' : _typeof(a)) + ' ' + a); } var attr = a; if (a.constructor.name !== 'Attribute') { var type = void 0, value = void 0; if (b) { type = a; value = b; } else { value = a; type = _attribute2.default.guessTypeOf(value); } attr = new _attribute2.default(type, value); } return new _identity2.default(this.gun.get('identitiesBySearchKey').get(attr.uri()), attr); }; Index.prototype._getMsgs = async function _getMsgs(msgIndex, callback, limit, cursor, desc, filter) { var results = 0; async function resultFound(result) { if (results >= limit) { return; } var msg = await _message2.default.fromSig(result.value); if (filter && !filter(msg)) { return; } results++; msg.cursor = result.key; if (result.value && result.value.ipfsUri) { msg.ipfsUri = result.value.ipfsUri; } callback(msg); } searchText(msgIndex, resultFound, '', undefined, cursor, desc); }; Index.prototype._addIdentityToIndexes = async function _addIdentityToIndexes(id) { var hash = _gun2.default.node.soul(id) || 'todo'; var indexKeys = await this.getIdentityIndexKeys(id, hash.substr(0, 6)); var indexes = Object.keys(indexKeys); for (var i = 0; i < indexes.length; i++) { var index = indexes[i]; for (var j = 0; j < indexKeys[index].length; j++) { var key = indexKeys[index][j]; console.log('adding key ' + key); await this.gun.get(index).get(key).put(id); } } }; /** * Get Messages sent by identity * @param {Identity} identity identity whose sent Messages to get * @param {Function} callback callback function that receives the Messages one by one */ Index.prototype.getSentMsgs = async function getSentMsgs(identity, callback, limit) { var cursor = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ''; var filter = arguments[4]; return this._getMsgs(identity.gun.get('sent'), callback, limit, cursor, filter); }; /** * Get Messages received by identity * @param {Identity} identity identity whose received Messages to get * @param {Function} callback callback function that receives the Messages one by one */ Index.prototype.getReceivedMsgs = async function getReceivedMsgs(identity, callback, limit) { var cursor = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ''; var filter = arguments[4]; return this._getMsgs(identity.gun.get('received'), callback, limit, cursor, filter); }; Index.prototype._getAttributeTrustDistance = async function _getAttributeTrustDistance(a) { if (!_attribute2.default.isUniqueType(a.type)) { return; } if (this.viewpoint.equals(a)) { return 0; } var id = this.get(a); var d = await id.gun.get('trustDistance').then(); if (isNaN(d)) { d = Infinity; } return d; }; /** * @param {Message} msg * @returns {number} trust distance to msg author. Returns undefined if msg signer is not trusted. */ Index.prototype.getMsgTrustDistance = async function getMsgTrustDistance(msg) { var shortestDistance = Infinity; var signerAttr = new _attribute2.default('keyID', msg.getSignerKeyID()); if (!signerAttr.equals(this.viewpoint)) { var signer = this.get(signerAttr); var d = await signer.gun.get('trustDistance').then(); if (isNaN(d)) { return; } } for (var _iterator = msg.getAuthorArray(), _isArray = Array.isArray(_iterator), _i5 = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { var _ref; if (_isArray) { if (_i5 >= _iterator.length) break; _ref = _iterator[_i5++]; } else { _i5 = _iterator.next(); if (_i5.done) break; _ref = _i5.value; } var a = _ref; var _d = await this._getAttributeTrustDistance(a); if (_d < shortestDistance) { shortestDistance = _d; } } return shortestDistance < Infinity ? shortestDistance : undefined; }; Index.prototype._updateMsgRecipientIdentity = async function _updateMsgRecipientIdentity(msg, msgIndexKey, recipient) { var hash = 'todo'; var identityIndexKeysBefore = await this.getIdentityIndexKeys(recipient, hash.substr(0, 6)); var attrs = await new Promise(function (resolve) { recipient.get('attrs').load(function (r) { return resolve(r); }); }); if (msg.signedData.type === 'verification') { var _loop = function _loop() { if (_isArray2) { if (_i6 >= _iterator2.length) return 'break'; _ref2 = _iterator2[_i6++]; } else { _i6 = _iterator2.next(); if (_i6.done) return 'break'; _ref2 = _i6.value; } var a = _ref2; var hasAttr = false; Object.keys(attrs).forEach(function (k) { // TODO: if author is self, mark as self verified if (a.equals(attrs[k])) { attrs[k].conf = (attrs[k].conf || 0) + 1; hasAttr = true; } }); if (!hasAttr) { attrs[a.uri()] = { type: a.type, value: a.value, conf: 1, ref: 0 }; } if (msg.goodVerification) { attrs[a.uri()].verified = true; } }; for (var _iterator2 = msg.getRecipientArray(), _isArray2 = Array.isArray(_iterator2), _i6 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { var _ref2; var _ret = _loop(); if (_ret === 'break') break; } recipient.get('mostVerifiedAttributes').put(_identity2.default.getMostVerifiedAttributes(attrs)); // TODO: why this needs to be done twice to register? recipient.get('mostVerifiedAttributes').put(_identity2.default.getMostVerifiedAttributes(attrs)); recipient.get('attrs').put(attrs); recipient.get('attrs').put(attrs); } if (msg.signedData.type === 'rating') { var id = await recipient.then(); id.receivedPositive = id.receivedPositive || 0; id.receivedNegative = id.receivedNegative || 0; id.receivedNeutral = id.receivedNeutral || 0; if (msg.isPositive()) { if (typeof id.trustDistance !== 'number' || msg.distance + 1 < id.trustDistance) { recipient.get('trustDistance').put(msg.distance + 1); } id.receivedPositive++; } else { if (msg.distance < id.trustDistance) { recipient.get('trustDistance').put(false); // TODO: this should take into account the aggregate score of the identity } if (msg.isNegative()) { id.receivedNegative++; } else { id.receivedNeutral++; } } recipient.get('receivedPositive').put(id.receivedPositive); recipient.get('receivedNegative').put(id.receivedNegative); recipient.get('receivedNeutral').put(id.receivedNeutral); if (msg.signedData.context === 'verifier') { if (msg.distance === 0) { if (msg.isPositive) { recipient.get('scores').get(msg.signedData.context).get('score').put(10); } else if (msg.isNegative()) { recipient.get('scores').get(msg.signedData.context).get('score').put(0); } else { recipient.get('scores').get(msg.signedData.context).get('score').put(-10); } } } else { // TODO: generic context-dependent score calculation } } var obj = { sig: msg.sig, pubKey: msg.pubKey }; if (msg.ipfsUri) { obj.ipfsUri = msg.ipfsUri; } recipient.get('received').get(msgIndexKey).put(obj); recipient.get('received').get(msgIndexKey).put(obj); var identityIndexKeysAfter = await this.getIdentityIndexKeys(recipient, hash.substr(0, 6)); var indexesBefore = Object.keys(identityIndexKeysBefore); for (var i = 0; i < indexesBefore.length; i++) { var index = indexesBefore[i]; for (var j = 0; j < identityIndexKeysBefore[index].length; j++) { var key = identityIndexKeysBefore[index][j]; if (!identityIndexKeysAfter[index] || identityIndexKeysAfter[index].indexOf(key) === -1) { console.log('removing stale key ' + key + ' from index ' + index); this.gun.get(index).get(key).put(null); } } } }; Index.prototype._updateMsgAuthorIdentity = async function _updateMsgAuthorIdentity(msg, msgIndexKey, author) { if (msg.signedData.type === 'rating') { var id = await author.then(); id.sentPositive = id.sentPositive || 0; id.sentNegative = id.sentNegative || 0; id.sentNeutral = id.sentNeutral || 0; if (msg.isPositive()) { id.sentPositive++; } else if (msg.isNegative()) { id.sentNegative++; } else { id.sentNeutral++; } author.get('sentPositive').put(id.sentPositive); author.get('sentNegative').put(id.sentNegative); author.get('sentNeutral').put(id.sentNeutral); } var obj = { sig: msg.sig, pubKey: msg.pubKey }; if (msg.ipfsUri) { obj.ipfsUri = msg.ipfsUri; } author.get('sent').get(msgIndexKey).put(obj); // for some reason, doesn't work unless I do it twice author.get('sent').get(msgIndexKey).put(obj); return; }; Index.prototype._updateIdentityProfilesByMsg = async function _updateIdentityProfilesByMsg(msg, authorIdentities, recipientIdentities) { var msgIndexKey = Index.getMsgIndexKey(msg); msgIndexKey = msgIndexKey.substr(msgIndexKey.indexOf(':') + 1); var ids = Object.values(Object.assign({}, authorIdentities, recipientIdentities)); for (var i = 0; i < ids.length; i++) { // add new identifiers to identity var data = await ids[i].gun.then(); // TODO: data is sometimes undefined and new identity is not added! var relocated = data ? this.gun.get('identities').set(data) : ids[i].gun; // this may screw up real time updates? and create unnecessary `identities` entries if (recipientIdentities.hasOwnProperty(ids[i].gun['_'].link)) { await this._updateMsgRecipientIdentity(msg, msgIndexKey, ids[i].gun); } if (authorIdentities.hasOwnProperty(ids[i].gun['_'].link)) { await this._updateMsgAuthorIdentity(msg, msgIndexKey, ids[i].gun); } await this._addIdentityToIndexes(relocated); } }; Index.prototype.removeTrustedIndex = async function removeTrustedIndex(gunUri) { this.gun.get('trustedIndexes').get(gunUri).put(null); }; Index.prototype.addTrustedIndex = async function addTrustedIndex(gunUri) { var _this2 = this; var maxMsgsToCrawl = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.options.indexSync.importOnAdd.maxMsgCount; var maxMsgDistance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.options.indexSync.importOnAdd.maxMsgDistance; if (gunUri === this.viewpoint.value) { return; } console.log('addTrustedIndex', gunUri); var exists = await this.gun.get('trustedIndexes').get(gunUri).then(); if (exists) { return; } this.gun.get('trustedIndexes').get(gunUri).put(true); var msgs = []; if (this.options.indexSync.importOnAdd.enabled) { await _util2.default.timeoutPromise(new Promise(function (resolve) { _this2.gun.user(gunUri).get('identifi').get('messagesByDistance').map(function (val, key) { var d = Number.parseInt(key.split(':')[0]); if (!isNaN(d) && d <= maxMsgDistance) { _message2.default.fromSig(val).then(function (msg) { msgs.push(msg); if (msgs.length >= maxMsgsToCrawl) { resolve(); } }); } }); }), 10000); console.log('adding', msgs.length, 'msgs'); this.addMessages(msgs); } }; Index.prototype._updateIdentityIndexesByMsg = async function _updateIdentityIndexesByMsg(msg) { var recipientIdentities = {}; var authorIdentities = {}; var selfAuthored = false; for (var _iterator3 = msg.getAuthorArray(), _isArray3 = Array.isArray(_iterator3), _i7 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { var _ref3; if (_isArray3) { if (_i7 >= _iterator3.length) break; _ref3 = _iterator3[_i7++]; } else { _i7 = _iterator3.next(); if (_i7.done) break; _ref3 = _i7.value; } var _a3 = _ref3; var _id = this.get(_a3); var td = await _util2.default.timeoutPromise(_id.gun.get('trustDistance').then(), GUN_TIMEOUT); if (!isNaN(td)) { authorIdentities[_id.gun['_'].link] = _id; var scores = await _id.gun.get('scores').then(); if (scores && scores.verifier && msg.signedData.type === 'verification') { msg.goodVerification = true; } if (td === 0) { selfAuthored = true; } } } if (!Object.keys(authorIdentities).length) { return; // unknown author, do nothing } for (var _iterator4 = msg.getRecipientArray(), _isArray4 = Array.isArray(_iterator4), _i8 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { var _ref4; if (_isArray4) { if (_i8 >= _iterator4.length) break; _ref4 = _iterator4[_i8++]; } else { _i8 = _iterator4.next(); if (_i8.done) break; _ref4 = _i8.value; } var _a4 = _ref4; var _id2 = this.get(_a4); var _td = await _util2.default.timeoutPromise(_id2.gun.get('trustDistance').then(), GUN_TIMEOUT); if (!isNaN(_td)) { recipientIdentities[_id2.gun['_'].link] = _id2; } if (selfAuthored && _a4.type === 'keyID' && _a4.value !== this.viewpoint.value) { // TODO: not if already added - causes infinite loop? if (msg.isPositive()) { this.addTrustedIndex(_a4.value); } else { this.removeTrustedIndex(_a4.value); } } } if (!Object.keys(recipientIdentities).length) { // recipient is previously unknown var attrs = {}; for (var _iterator5 = msg.getRecipientArray(), _isArray5 = Array.isArray(_iterator5), _i9 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { var _ref5; if (_isArray5) { if (_i9 >= _iterator5.length) break; _ref5 = _iterator5[_i9++]; } else { _i9 = _iterator5.next(); if (_i9.done) break; _ref5 = _i9.value; } var _a2 = _ref5; attrs[_a2.uri()] = _a2; } var linkTo = _identity2.default.getLinkTo(attrs); var random = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); // TODO: bubblegum fix var trustDistance = msg.isPositive() && typeof msg.distance === 'number' ? msg.distance + 1 : false; var id = await _identity2.default.create(this.gun.get('identities').get(random).put({}), { attrs: attrs, linkTo: linkTo, trustDistance: trustDistance }); // {a:1} because inserting {} causes a "no signature on data" error from gun // TODO: take msg author trust into account recipientIdentities[id.gun['_'].link] = id; } return this._updateIdentityProfilesByMsg(msg, authorIdentities, recipientIdentities); }; /** * Add a list of messages to the index. * Useful for example when adding a new WoT dataset that contains previously * unknown authors. * * Iteratively performs sorted merge joins on [previously known identities] and * [new msgs authors], until all messages from within the WoT have been added. * * @param {Array} msgs an array of messages. * @param {Object} ipfs (optional) ipfs instance where the messages are saved * @returns {boolean} true on success */ Index.prototype.addMessages = async function addMessages(msgs) { var _this3 = this; var msgsByAuthor = {}; if (Array.isArray(msgs)) { console.log('sorting ' + msgs.length + ' messages onto a search tree...'); for (var i = 0; i < msgs.length; i++) { for (var _iterator6 = msgs[i].getAuthorArray(), _isArray6 = Array.isArray(_iterator6), _i10 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { var _ref6; if (_isArray6) { if (_i10 >= _iterator6.length) break; _ref6 = _iterator6[_i10++]; } else { _i10 = _iterator6.next(); if (_i10.done) break; _ref6 = _i10.value; } var _a5 = _ref6; if (_a5.isUniqueType()) { var key = _a5.uri() + ':' + msgs[i].getHash(); msgsByAuthor[key] = msgs[i]; } } } console.log('...done'); } else { throw 'msgs param must be an array'; } var msgAuthors = Object.keys(msgsByAuthor).sort(); if (!msgAuthors.length) { return; } var initialMsgCount = void 0, msgCountAfterwards = void 0; var index = this.gun.get('identitiesBySearchKey'); var _loop2 = async function _loop2() { var knownIdentities = []; var stop = false; searchText(index, function (result) { if (stop) { return; } knownIdentities.push(result); }, ''); await new Promise(function (r) { return setTimeout(r, 2000); }); // wait for results to accumulate stop = true; knownIdentities.sort(function (a, b) { if (a.key === b.key) { return 0; } else if (a.key > b.key) { return 1; } else { return -1; } }); var i = 0; var author = msgAuthors[i]; var knownIdentity = knownIdentities.shift(); initialMsgCount = msgAuthors.length; // sort-merge join identitiesBySearchKey and msgsByAuthor while (author && knownIdentity) { if (author.indexOf(knownIdentity.key) === 0) { try { await _util2.default.timeoutPromise(_this3.addMessage(msgsByAuthor[author], { checkIfExists: true }), 10000); } catch (e) { console.log('adding failed:', e, JSON.stringify(msgsByAuthor[author], null, 2)); } msgAuthors.splice(i, 1); author = i < msgAuthors.length ? msgAuthors[i] : undefined; //knownIdentity = knownIdentities.shift(); } else if (author < knownIdentity.key) { author = i < msgAuthors.length ? msgAuthors[++i] : undefined; } else { knownIdentity = knownIdentities.shift(); } } msgCountAfterwards = msgAuthors.length; }; do { await _loop2(); } while (msgCountAfterwards !== initialMsgCount); return true; }; /** * @param msg Message to add to the index * @param ipfs (optional) ipfs instance where the message is additionally saved */ Index.prototype.addMessage = async function addMessage(msg) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (msg.constructor.name !== 'Message') { throw new Error('addMessage failed: param must be a Message, received ' + msg.constructor.name); } var hash = msg.getHash(); if (true === options.checkIfExists) { var exists = await this.gun.get('messagesByHash').get(hash).once().then(); if (exists) { return; } } msg.distance = await this.getMsgTrustDistance(msg); if (msg.distance === undefined) { return false; // do not save messages from untrusted author } var obj = { sig: msg.sig, pubKey: msg.pubKey }; var indexKeys = Index.getMsgIndexKeys(msg); for (var index in indexKeys) { for (var i = 0; i < indexKeys[index].length; i++) { var key = indexKeys[index][i]; console.log('adding to index ' + index + ' message key ' + key); this.gun.get(index).get(key).put(obj); this.gun.get(index).get(key).put(obj); // umm, what? doesn't work unless I write it twice } } if (this.options.ipfs) { try { var ipfsUri = await msg.saveToIpfs(this.options.ipfs); obj.ipfsUri = ipfsUri; this.gun.get('messagesByHash').get(ipfsUri).put(obj); this.gun.get('messagesByHash').get(ipfsUri).put(obj); } catch (e) { console.error('adding msg ' + msg + ' to ipfs failed: ' + e); } } await this._updateIdentityIndexesByMsg(msg); return true; }; /** * @param {string} value search string * @param {string} type (optional) type of searched value * @returns {Array} list of matching identities */ Index.prototype.search = async function search(value, type, callback, limit) { var _this4 = this; // TODO: param 'exact', type param var seen = {}; function searchTermCheck(key) { var arr = key.split(':'); if (arr.length < 3) { return false; } var keyValue = arr[1]; var keyType = arr[2]; if (keyValue.indexOf(encodeURIComponent(value)) !== 0) { return false; } if (type && keyType !== type) { return false; } return true; } this.gun.get('identitiesByTrustDistance').map().once(function (id, key) { if (Object.keys(seen).length >= limit) { // TODO: turn off .map cb return; } if (!searchTermCheck(key)) { return; } var soul = _gun2.default.node.soul(id); if (soul && !seen.hasOwnProperty(soul)) { seen[soul] = true; var identity = new _identity2.default(_this4.gun.get('identitiesByTrustDistance').get(key)); identity.cursor = key; callback(identity); } }); if (this.options.indexSync.query.enabled) { this.gun.get('trustedIndexes').map().once(function (val, key) { if (val) { _this4.gun.user(key).get('identifi').get('identitiesByTrustDistance').map().once(function (id, k) { if (Object.keys(seen).length >= limit) { // TODO: turn off .map cb return; } if (!searchTermCheck(key)) { return; } var soul = _gun2.default.node.soul(id); if (soul && !seen.hasOwnProperty(soul)) { seen[soul] = true; callback(new _identity2.default(_this4.gun.user(key).get('identifi').get('identitiesByTrustDistance').get(k))); } }); } }); } }; /** * @returns {Array} list of messages */ Index.prototype.getMessagesByTimestamp = function getMessagesByTimestamp(callback, limit) { var cursor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; var _this5 = this; var desc = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; var filter = arguments[4]; var seen = {}; var cb = function cb(msg) { console.log('hash', msg.hash); if ((!limit || Object.keys(seen).length <= limit) && !seen.hasOwnProperty(msg.hash)) { seen[msg.hash] = true; callback(msg); } }; this._getMsgs(this.gun.get('messagesByTimestamp'), cb, limit, cursor, filter); if (this.options.indexSync.query.enabled) { this.gun.get('trustedIndexes').map().once(function (val, key) { if (val) { var n = _this5.gun.user(key).get('identifi').get('messagesByTimestamp'); _this5._getMsgs(n, cb, limit, cursor, desc, filter); } }); } }; /** * @returns {Array} list of messages */ Index.prototype.getMessagesByDistance = function getMessagesByDistance(callback, limit) { var cursor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; var _this6 = this; var desc = arguments[3]; var filter = arguments[4]; var seen = {}; var cb = function cb(msg) { if (!seen.hasOwnProperty(msg.hash)) { if ((!limit || Object.keys(seen).length <= limit) && !seen.hasOwnProperty(msg.hash)) { seen[msg.hash] = true; callback(msg); } } }; this._getMsgs(this.gun.get('messagesByDistance'), cb, limit, cursor, desc, filter); if (this.options.indexSync.query.enabled) { this.gun.get('trustedIndexes').map().once(function (val, key) { if (val) { var n = _this6.gun.user(key).get('identifi').get('messagesByDistance'); _this6._getMsgs(n, cb, limit, cursor, desc, filter); } }); } }; return Index; }(); exports.default = Index; module.exports = exports['default'];