UNPKG

3box

Version:
913 lines (753 loc) 28.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } var isIPFS = require('is-ipfs'); var API = require('./api'); var config = require('./config'); var _require = require('./3id/utils'), symEncryptBase = _require.symEncryptBase, symDecryptBase = _require.symDecryptBase, newSymKey = _require.newSymKey; var utils = require('./utils/index'); var orbitAddress = require('orbit-db/src/orbit-db-address'); var ORBITDB_OPTS = config.orbitdb_options; var MODERATOR = 'MODERATOR'; var MEMBER = 'MEMBER'; var isValid3ID = function isValid3ID(did) { var parts = did.split(':'); if (!parts[0] === 'did' || !parts[1] === '3') return false; return isIPFS.cid(parts[2]); }; var Thread = /*#__PURE__*/function () { /** * Please use **space.joinThread** to get the instance of this class */ function Thread(name, replicator, members, firstModerator, confidential, user, subscribe) { (0, _classCallCheck2["default"])(this, Thread); this._name = name; this._replicator = replicator; this._spaceName = name ? name.split('.')[2] : undefined; this._subscribe = subscribe; this._queuedNewPosts = []; this._members = Boolean(members); this._firstModerator = firstModerator; this._user = user; if (confidential) { this._confidential = true; this._members = true; if (typeof confidential === 'string') { this._encKeyId = confidential; } else { this._symKey = newSymKey(); this._encKeyId = utils.sha256(this._symKey); } } } /** * Post a message to the thread * * @param {Object} message The message * @return {String} The postId of the new post */ (0, _createClass2["default"])(Thread, [{ key: "post", value: function () { var _post = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(message) { var timestamp; return _regenerator["default"].wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: this._requireLoad(); this._requireAuth(); this._subscribe(this._address, { firstModerator: this._firstModerator, members: this._members, name: this._name }); this._replicator.ensureConnected(this._address, true); timestamp = Math.floor(new Date().getTime() / 1000); // seconds if (this._confidential) message = this._symEncrypt(message); return _context.abrupt("return", this._db.add({ message: message, timestamp: timestamp })); case 7: case "end": return _context.stop(); } } }, _callee, this); })); function post(_x) { return _post.apply(this, arguments); } return post; }() }, { key: "_getThreadAddress", value: function () { var _getThreadAddress2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2() { var address; return _regenerator["default"].wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: if (!this._address) { _context2.next = 2; break; } return _context2.abrupt("return", this._address); case 2: _context2.next = 4; return this._initAcConfigs(); case 4: _context2.next = 6; return this._replicator._orbitdb._determineAddress(this._name, 'feed', { accessController: this._accessController }, false); case 6: address = _context2.sent.toString(); this._address = address; return _context2.abrupt("return", this._address); case 9: case "end": return _context2.stop(); } } }, _callee2, this); })); function _getThreadAddress() { return _getThreadAddress2.apply(this, arguments); } return _getThreadAddress; }() /** * Add a moderator to this thread, throws error is user can not add a moderator * * @param {String} id Moderator Id */ }, { key: "addModerator", value: function () { var _addModerator = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3(id) { return _regenerator["default"].wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: this._requireLoad(); this._requireAuth(); if (!id.startsWith('0x')) { _context3.next = 6; break; } _context3.next = 5; return API.getSpaceDID(id, this._spaceName); case 5: id = _context3.sent; case 6: if (isValid3ID(id)) { _context3.next = 8; break; } throw new Error('addModerator: must provide valid 3ID'); case 8: _context3.t0 = this._db.access; _context3.t1 = MODERATOR; _context3.t2 = id; _context3.next = 13; return this._encryptSymKey(id); case 13: _context3.t3 = _context3.sent; return _context3.abrupt("return", _context3.t0.grant.call(_context3.t0, _context3.t1, _context3.t2, _context3.t3)); case 15: case "end": return _context3.stop(); } } }, _callee3, this); })); function addModerator(_x2) { return _addModerator.apply(this, arguments); } return addModerator; }() /** * List moderators * * @return {Array<String>} Array of moderator DIDs */ }, { key: "listModerators", value: function () { var _listModerators = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4() { return _regenerator["default"].wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: this._requireLoad(); return _context4.abrupt("return", this._db.access.capabilities.moderators); case 2: case "end": return _context4.stop(); } } }, _callee4, this); })); function listModerators() { return _listModerators.apply(this, arguments); } return listModerators; }() /** * Add a member to this thread, throws if user can not add member, throw is not member thread * * @param {String} id Member Id */ }, { key: "addMember", value: function () { var _addMember = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee5(id) { return _regenerator["default"].wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: this._requireLoad(); this._requireAuth(); this._throwIfNotMembers(); if (!id.startsWith('0x')) { _context5.next = 7; break; } _context5.next = 6; return API.getSpaceDID(id, this._spaceName); case 6: id = _context5.sent; case 7: if (isValid3ID(id)) { _context5.next = 9; break; } throw new Error('addMember: must provide valid 3ID'); case 9: _context5.t0 = this._db.access; _context5.t1 = MEMBER; _context5.t2 = id; _context5.next = 14; return this._encryptSymKey(id); case 14: _context5.t3 = _context5.sent; return _context5.abrupt("return", _context5.t0.grant.call(_context5.t0, _context5.t1, _context5.t2, _context5.t3)); case 16: case "end": return _context5.stop(); } } }, _callee5, this); })); function addMember(_x3) { return _addMember.apply(this, arguments); } return addMember; }() /** * List members, throws if not member thread * * @return {Array<String>} Array of member DIDs */ }, { key: "listMembers", value: function () { var _listMembers = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee6() { return _regenerator["default"].wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: this._throwIfNotMembers(); this._requireLoad(); return _context6.abrupt("return", this._db.access.capabilities.members); case 3: case "end": return _context6.stop(); } } }, _callee6, this); })); function listMembers() { return _listMembers.apply(this, arguments); } return listMembers; }() }, { key: "_throwIfNotMembers", value: function _throwIfNotMembers() { if (!this._members) throw new Error('Thread: Not a members only thread, function not available'); } /** * Delete post * * @param {String} id Moderator Id */ }, { key: "deletePost", value: function () { var _deletePost = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee7(hash) { return _regenerator["default"].wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: this._requireLoad(); this._requireAuth(); return _context7.abrupt("return", this._db.remove(hash)); case 3: case "end": return _context7.stop(); } } }, _callee7, this); })); function deletePost(_x4) { return _deletePost.apply(this, arguments); } return deletePost; }() /** * Returns an array of posts, based on the options. * If hash not found when passing gt, gte, lt, or lte, * the iterator will return all items (respecting limit and reverse). * * @param {Object} opts Optional parameters * @param {String} opts.gt Greater than, takes an postId * @param {String} opts.gte Greater than or equal to, takes an postId * @param {String} opts.lt Less than, takes an postId * @param {String} opts.lte Less than or equal to, takes an postId * @param {Integer} opts.limit Limiting the number of entries in result, defaults to -1 (no limit) * @param {Boolean} opts.reverse If set to true will result in reversing the result * * @return {Array<Object>} true if successful */ }, { key: "getPosts", value: function () { var _getPosts = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee8() { var _this = this; var opts, decrypt, _args8 = arguments; return _regenerator["default"].wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: opts = _args8.length > 0 && _args8[0] !== undefined ? _args8[0] : {}; decrypt = function decrypt(entry) { if (!_this._confidential) return entry; var message = _this._symDecrypt(entry.message); return { message: message, timestamp: entry.timestamp }; }; this._requireLoad(); if (!opts.limit) opts.limit = -1; return _context8.abrupt("return", this._db.iterator(opts).collect().map(function (entry) { var post = decrypt(entry.payload.value); var metaData = { postId: entry.hash, author: entry.identity.id }; return Object.assign(metaData, post); })); case 5: case "end": return _context8.stop(); } } }, _callee8, this); })); function getPosts() { return _getPosts.apply(this, arguments); } return getPosts; }() /** * Register a function to be called after new updates * have been received from the network or locally. * * @param {Function} updateFn The function that will get called */ }, { key: "onUpdate", value: function () { var _onUpdate = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee9(updateFn) { return _regenerator["default"].wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { case 0: this._requireLoad(); this._db.events.on('replicated', function (address, hash, entry, prog, tot) { updateFn(); }); this._db.events.on('write', function (dbname, entry) { updateFn(); }); case 3: case "end": return _context9.stop(); } } }, _callee9, this); })); function onUpdate(_x5) { return _onUpdate.apply(this, arguments); } return onUpdate; }() /** * Register a function to be called for every new * capability that is added to the thread access controller. * This inlcudes when a moderator or member is added. * The function takes one parameter, which is the capabilities obj, or * you can call listModerator / listMembers again instead. * * @param {Function} updateFn The function that will get called */ }, { key: "onNewCapabilities", value: function () { var _onNewCapabilities = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee10(updateFn) { var _this2 = this; return _regenerator["default"].wrap(function _callee10$(_context10) { while (1) { switch (_context10.prev = _context10.next) { case 0: this._db.access.on('updated', function (event) { updateFn(_this2._db.access.capabilities); }); case 1: case "end": return _context10.stop(); } } }, _callee10, this); })); function onNewCapabilities(_x6) { return _onNewCapabilities.apply(this, arguments); } return onNewCapabilities; }() // Loads by orbitdb address or db name }, { key: "_load", value: function () { var _load2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee11(dbString) { var loadByAddress; return _regenerator["default"].wrap(function _callee11$(_context11) { while (1) { switch (_context11.prev = _context11.next) { case 0: loadByAddress = dbString && orbitAddress.isValid(dbString); if (loadByAddress) { _context11.next = 4; break; } _context11.next = 4; return this._initAcConfigs(); case 4: _context11.next = 6; return this._replicator._orbitdb.feed(dbString || this._name, _objectSpread(_objectSpread({}, ORBITDB_OPTS), {}, { accessController: this._accessController })); case 6: this._db = _context11.sent; _context11.next = 9; return this._db.load(); case 9: if (loadByAddress) { this._firstModerator = this._db.access._firstModerator; this._members = this._db.access._members; this._encKeyId = this._db.access._encKeyId; this._confidential = Boolean(this._db.access._encKeyId); this._name = this._db.address.path; this._spaceName = this._name.split('.')[2]; } this._address = this._db.address.toString(); this._replicator.ensureConnected(this._address, true); return _context11.abrupt("return", this._address); case 13: case "end": return _context11.stop(); } } }, _callee11, this); })); function _load(_x7) { return _load2.apply(this, arguments); } return _load; }() }, { key: "_initConfidential", value: function () { var _initConfidential2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee12() { var _this3 = this; var encryptedKey; return _regenerator["default"].wrap(function _callee12$(_context12) { while (1) { switch (_context12.prev = _context12.next) { case 0: if (!this._symKey) { _context12.next = 13; break; } if (!(this._user.DID !== this._firstModerator)) { _context12.next = 3; break; } throw new Error('_initConfidential: firstModerator must initialize a confidential thread'); case 3: _context12.t0 = this._db.access; _context12.t1 = MODERATOR; _context12.t2 = this._firstModerator; _context12.next = 8; return this._encryptSymKey(); case 8: _context12.t3 = _context12.sent; _context12.next = 11; return _context12.t0.grant.call(_context12.t0, _context12.t1, _context12.t2, _context12.t3); case 11: _context12.next = 28; break; case 13: encryptedKey = null; _context12.prev = 14; encryptedKey = this._db.access.getEncryptedKey(this._user.DID); _context12.next = 23; break; case 18: _context12.prev = 18; _context12.t4 = _context12["catch"](14); _context12.next = 22; return new Promise(function (resolve, reject) { _this3.onNewCapabilities(function (val) { var key = null; try { key = _this3._db.access.getEncryptedKey(_this3._user.DID); } catch (e) {} if (key !== null) resolve(key); }); setTimeout(function () { return resolve(null); }, 10000); }); case 22: encryptedKey = _context12.sent; case 23: if (encryptedKey) { _context12.next = 25; break; } throw new Error("_initConfidential: no access for ".concat(this._user.DID)); case 25: _context12.next = 27; return this._decryptSymKey(encryptedKey); case 27: this._symKey = _context12.sent; case 28: case "end": return _context12.stop(); } } }, _callee12, this, [[14, 18]]); })); function _initConfidential() { return _initConfidential2.apply(this, arguments); } return _initConfidential; }() }, { key: "_requireLoad", value: function _requireLoad() { if (!this._db) throw new Error('_load must be called before interacting with the store'); } }, { key: "_requireAuth", value: function _requireAuth() { if (!this._authenticated) throw new Error('You must authenticate before performing this action'); } }, { key: "close", value: function () { var _close = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee13() { return _regenerator["default"].wrap(function _callee13$(_context13) { while (1) { switch (_context13.prev = _context13.next) { case 0: this._requireLoad(); _context13.next = 3; return this._db.close(); case 3: case "end": return _context13.stop(); } } }, _callee13, this); })); function close() { return _close.apply(this, arguments); } return close; }() }, { key: "_setIdentity", value: function () { var _setIdentity2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee14(odbId) { return _regenerator["default"].wrap(function _callee14$(_context14) { while (1) { switch (_context14.prev = _context14.next) { case 0: this._db.setIdentity(odbId); this._db.access._db.setIdentity(odbId); this._authenticated = true; // TODO not too clear hear, but does require auth, and to be after load if (!this._confidential) { _context14.next = 6; break; } _context14.next = 6; return this._initConfidential(); case 6: case "end": return _context14.stop(); } } }, _callee14, this); })); function _setIdentity(_x8) { return _setIdentity2.apply(this, arguments); } return _setIdentity; }() }, { key: "_initAcConfigs", value: function () { var _initAcConfigs2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee15() { return _regenerator["default"].wrap(function _callee15$(_context15) { while (1) { switch (_context15.prev = _context15.next) { case 0: if (!this._accessController) { _context15.next = 2; break; } return _context15.abrupt("return"); case 2: if (!this._firstModerator.startsWith('0x')) { _context15.next = 6; break; } _context15.next = 5; return API.getSpaceDID(this._firstModerator, this._spaceName); case 5: this._firstModerator = _context15.sent; case 6: this._accessController = { type: 'thread-access', threadName: this._name, members: this._members, firstModerator: this._firstModerator }; if (this._encKeyId) { this._accessController.encKeyId = this._encKeyId; } case 8: case "end": return _context15.stop(); } } }, _callee15, this); })); function _initAcConfigs() { return _initAcConfigs2.apply(this, arguments); } return _initAcConfigs; }() }, { key: "_symEncrypt", value: function _symEncrypt(message) { var msg = utils.pad(JSON.stringify(message)); return symEncryptBase(msg, this._symKey); } }, { key: "_symDecrypt", value: function _symDecrypt(payload) { var paddedMsg = symDecryptBase(payload.ciphertext, this._symKey, payload.nonce); return JSON.parse(utils.unpad(paddedMsg)); } }, { key: "_encryptSymKey", value: function () { var _encryptSymKey2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee16(to) { return _regenerator["default"].wrap(function _callee16$(_context16) { while (1) { switch (_context16.prev = _context16.next) { case 0: if (this._confidential) { _context16.next = 2; break; } return _context16.abrupt("return", null); case 2: return _context16.abrupt("return", this._user.encrypt(this._symKey, { to: to })); case 3: case "end": return _context16.stop(); } } }, _callee16, this); })); function _encryptSymKey(_x9) { return _encryptSymKey2.apply(this, arguments); } return _encryptSymKey; }() }, { key: "_decryptSymKey", value: function () { var _decryptSymKey2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee17(encKey) { var key; return _regenerator["default"].wrap(function _callee17$(_context17) { while (1) { switch (_context17.prev = _context17.next) { case 0: _context17.next = 2; return this._user.decrypt(encKey, true); case 2: key = _context17.sent; return _context17.abrupt("return", new Uint8Array(key)); case 4: case "end": return _context17.stop(); } } }, _callee17, this); })); function _decryptSymKey(_x10) { return _decryptSymKey2.apply(this, arguments); } return _decryptSymKey; }() }, { key: "address", get: function get() { return this._db ? this._address : null; } }]); return Thread; }(); module.exports = Thread;