UNPKG

3box

Version:
892 lines (743 loc) 28.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); 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; } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } var EventEmitter = require('events').EventEmitter; var _require = require('did-jwt'), verifyJWT = _require.verifyJWT; var _require2 = require('did-resolver'), Resolver = _require2.Resolver; var get3IdResolver = require('3id-resolver').getResolver; var getMuportResolver = require('muport-did-resolver').getResolver; var Room = require('ipfs-pubsub-room'); var DEFAULT_BACKLOG_LIMIT = 100; var GhostThread = /*#__PURE__*/function (_EventEmitter) { (0, _inherits2["default"])(GhostThread, _EventEmitter); var _super = _createSuper(GhostThread); function GhostThread(name, _ref) { var _this; var ipfs = _ref.ipfs; var opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; (0, _classCallCheck2["default"])(this, GhostThread); _this = _super.call(this); _this._name = name; _this._spaceName = name.split('.')[2]; _this._room = Room(ipfs, name); // instance of ipfs pubsub room _this._ipfs = ipfs; _this._peerId = ipfs._peerInfo.id.toB58String(); _this._members = {}; _this._backlog = new Set(); // set of past messages _this._backlogLimit = opts.ghostBacklogLimit || DEFAULT_BACKLOG_LIMIT; _this._filters = opts.ghostFilters || []; var threeIdResolver = get3IdResolver(ipfs, { pin: true }); var muportResolver = getMuportResolver(ipfs); _this._resolver = new Resolver(_objectSpread(_objectSpread({}, threeIdResolver), muportResolver)); _this._room.on('message', /*#__PURE__*/function () { var _ref3 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(_ref2) { var from, data, payload, issuer, verified, passesFilters; return _regenerator["default"].wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: from = _ref2.from, data = _ref2.data; if (!data.toString().startsWith('{')) { _context.next = 7; break; } // we got a non signed message (can only be backlog request, or response) payload = JSON.parse(data); if (!(payload.type !== 'request_backlog' && payload.type !== 'backlog_response')) { _context.next = 5; break; } return _context.abrupt("return"); case 5: _context.next = 12; break; case 7: _context.next = 9; return _this._verifyData(data); case 9: verified = _context.sent; payload = verified.payload; issuer = verified.issuer; case 12: // we pass the payload, issuer and peerID (from) to each filter in our filters array and reduce the value to a single boolean // this boolean indicates whether the message passed the filters passesFilters = _this._filters.reduce(function (acc, filter) { return acc && filter(payload, issuer, from); }, true); if (!(payload && passesFilters)) { _context.next = 25; break; } _context.t0 = payload.type; _context.next = _context.t0 === 'join' ? 17 : _context.t0 === 'request_backlog' ? 19 : _context.t0 === 'backlog_response' ? 21 : 24; break; case 17: _this._userJoined(issuer, from); return _context.abrupt("break", 25); case 19: _this.getPosts(_this._backlogLimit).then(function (posts) { return _this._sendDirect({ type: 'backlog_response', message: posts }, from, true); }); return _context.abrupt("break", 25); case 21: payload.message.map(function (msg) { _this._backlog.add(JSON.stringify(msg)); }); _this.emit('backlog-received', { type: 'backlog', author: issuer, message: payload.message, timestamp: payload.iat }); return _context.abrupt("break", 25); case 24: _this._messageReceived(payload); case 25: case "end": return _context.stop(); } } }, _callee); })); return function (_x) { return _ref3.apply(this, arguments); }; }()); _this._room.on('peer joined', function (peer) { _this._announce(peer); _this._requestBacklog(peer); }); _this._room.on('peer left', function (peer) { return _this._userLeft(peer); }); return _this; } (0, _createClass2["default"])(GhostThread, [{ key: "_set3id", value: function _set3id(threeId) { var _this2 = this; this._3id = threeId; // announce to other peers that we are online this.listMembers().then(function (members) { _this2._room.getPeers().map(function (id) { _this2._announce(id); }); }); } /** * Get a list of users online * * @return {Array<String>} users online */ }, { key: "listMembers", value: function () { var _listMembers = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2() { return _regenerator["default"].wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: return _context2.abrupt("return", Object.keys(this._members).filter(function (id) { return !id.startsWith('Qm'); })); case 1: case "end": return _context2.stop(); } } }, _callee2, this); })); function listMembers() { return _listMembers.apply(this, arguments); } return listMembers; }() /** * Get a peerId's corresponding 3ID * * @param {String} did The DID of the user * @return {String} ipfs peer id */ }, { key: "_threeIdToPeerId", value: function _threeIdToPeerId(did) { return this._members[did]; } /** * Get backlog of all past messages * * @return {Array<Object>} users online */ }, { key: "getPosts", value: function () { var _getPosts = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3() { var num, posts, _args3 = arguments; return _regenerator["default"].wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: num = _args3.length > 0 && _args3[0] !== undefined ? _args3[0] : 0; posts = (0, _toConsumableArray2["default"])(this._backlog).map(function (msg) { return JSON.parse(msg); }).sort(function (p1, p2) { return p1.timestamp - p2.timestamp; }).slice(-num); return _context3.abrupt("return", posts); case 3: case "end": return _context3.stop(); } } }, _callee3, this); })); function getPosts() { return _getPosts.apply(this, arguments); } return getPosts; }() /** * Announce entry in chat and share our 3id and peerID * * @param {String} to The PeerID of a user (optional) */ }, { key: "_announce", value: function () { var _announce2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4(to) { return _regenerator["default"].wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: if (!this._3id) { _context4.next = 8; break; } if (to) { _context4.next = 6; break; } _context4.next = 4; return this._broadcast({ type: 'join' }); case 4: _context4.next = 8; break; case 6: _context4.next = 8; return this._sendDirect({ type: 'join' }, to); case 8: case "end": return _context4.stop(); } } }, _callee4, this); })); function _announce(_x2) { return _announce2.apply(this, arguments); } return _announce; }() /** * Post a message to the thread * * @param {Object} message The message * @param {String} to PeerID to send the message to (optional) */ }, { key: "post", value: function () { var _post = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee5(message, to) { return _regenerator["default"].wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: if (to) { _context5.next = 5; break; } _context5.next = 3; return this._broadcast({ type: 'chat', message: message }); case 3: _context5.next = 7; break; case 5: _context5.next = 7; return this._sendDirect({ type: 'chat', message: message }, to); case 7: case "end": return _context5.stop(); } } }, _callee5, this); })); function post(_x3, _x4) { return _post.apply(this, arguments); } return post; }() }, { key: "deletePost", value: function () { var _deletePost = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee6(hash) { return _regenerator["default"].wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: throw new Error('Not possible to delete post in Ghost Thread'); case 1: case "end": return _context6.stop(); } } }, _callee6); })); function deletePost(_x5) { return _deletePost.apply(this, arguments); } return deletePost; }() }, { key: "addModerator", value: function () { var _addModerator = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee7(id) { return _regenerator["default"].wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: throw new Error('Not possible to add moderator in Ghost Thread'); case 1: case "end": return _context7.stop(); } } }, _callee7); })); function addModerator(_x6) { return _addModerator.apply(this, arguments); } return addModerator; }() }, { key: "listModerators", value: function () { var _listModerators = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee8() { return _regenerator["default"].wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: throw new Error('Not possible to list moderators in Ghost Thread'); case 1: case "end": return _context8.stop(); } } }, _callee8); })); function listModerators() { return _listModerators.apply(this, arguments); } return listModerators; }() }, { key: "addMember", value: function () { var _addMember = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee9(id) { return _regenerator["default"].wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { case 0: throw new Error('Not possible to add member in Ghost Thread'); case 1: case "end": return _context9.stop(); } } }, _callee9); })); function addMember(_x7) { return _addMember.apply(this, arguments); } return addMember; }() /** * Request a backlog of past messages from peers in the chat * * @param {String} to The PeerID of a user (optional) */ }, { key: "_requestBacklog", value: function () { var _requestBacklog2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee10(to) { return _regenerator["default"].wrap(function _callee10$(_context10) { while (1) { switch (_context10.prev = _context10.next) { case 0: if (to) { _context10.next = 5; break; } _context10.next = 3; return this._broadcast({ type: 'request_backlog' }); case 3: _context10.next = 7; break; case 5: _context10.next = 7; return this._sendDirect({ type: 'request_backlog' }, to, true); case 7: case "end": return _context10.stop(); } } }, _callee10, this); })); function _requestBacklog(_x8) { return _requestBacklog2.apply(this, arguments); } return _requestBacklog; }() /** * Leave the chat * */ }, { key: "close", value: function () { var _close = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee11() { return _regenerator["default"].wrap(function _callee11$(_context11) { while (1) { switch (_context11.prev = _context11.next) { case 0: _context11.next = 2; return this._room.leave(); case 2: case "end": return _context11.stop(); } } }, _callee11, this); })); function close() { return _close.apply(this, arguments); } return close; }() /** * Broadcast a message to peers in the room * * @param {Object} message The message */ }, { key: "_broadcast", value: function () { var _broadcast2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee12(message, noSignature) { var payload; return _regenerator["default"].wrap(function _callee12$(_context12) { while (1) { switch (_context12.prev = _context12.next) { case 0: if (!(!this._3id ? !noSignature : false)) { _context12.next = 2; break; } throw new Error('Can not send message if not authenticated'); case 2: if (!noSignature) { _context12.next = 6; break; } _context12.t0 = JSON.stringify(message); _context12.next = 9; break; case 6: _context12.next = 8; return this._3id.signJWT(message); case 8: _context12.t0 = _context12.sent; case 9: payload = _context12.t0; this._room.broadcast(payload); case 11: case "end": return _context12.stop(); } } }, _callee12, this); })); function _broadcast(_x9, _x10) { return _broadcast2.apply(this, arguments); } return _broadcast; }() /** * Send a direct message to a peer * * @param {Object} message The message * @param {String} to The PeerID or 3ID of the receiver */ }, { key: "_sendDirect", value: function () { var _sendDirect2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee13(message, to, noSignature) { var payload; return _regenerator["default"].wrap(function _callee13$(_context13) { while (1) { switch (_context13.prev = _context13.next) { case 0: if (!(!this._3id ? !noSignature : false)) { _context13.next = 2; break; } throw new Error('Can not send message if not authenticated'); case 2: if (!noSignature) { _context13.next = 6; break; } _context13.t0 = JSON.stringify(message); _context13.next = 9; break; case 6: _context13.next = 8; return this._3id.signJWT(message); case 8: _context13.t0 = _context13.sent; case 9: payload = _context13.t0; to.startsWith('Qm') ? this._room.sendTo(to, payload) : this._room.sendTo(this._threeIdToPeerId(to), payload); case 11: case "end": return _context13.stop(); } } }, _callee13, this); })); function _sendDirect(_x11, _x12, _x13) { return _sendDirect2.apply(this, arguments); } return _sendDirect; }() /** * 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 onUpdate(updateFn) { this.removeAllListeners('message'); this.removeAllListeners('backlog-received'); this.on('message', updateFn); this.on('backlog-received', updateFn); } /** * Register a function to be called after new capabilities * have been received from the network or locally. * * @param {Function} updateFn The function that will get called */ }, { key: "onNewCapabilities", value: function onNewCapabilities(updateFn) { this.removeAllListeners('user-joined'); this.removeAllListeners('user-left'); this.on('user-joined', updateFn); this.on('user-left', updateFn); } /** * Handler function for users joining * * @param {String} did The DID of the user * @param {Object} peerID The peerID of the user */ }, { key: "_userJoined", value: function () { var _userJoined2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee14(did, peerID) { var members; return _regenerator["default"].wrap(function _callee14$(_context14) { while (1) { switch (_context14.prev = _context14.next) { case 0: _context14.next = 2; return this.listMembers(); case 2: members = _context14.sent; if (!members.includes(did) && (!this._3id || this._3id.DID !== did)) { this._members[did] = peerID; this._members[peerID] = did; this.emit('user-joined', 'joined', did, peerID); } case 4: case "end": return _context14.stop(); } } }, _callee14, this); })); function _userJoined(_x14, _x15) { return _userJoined2.apply(this, arguments); } return _userJoined; }() /** * Handler function for users leaving * * @param {String} peerID The peerID of the user */ }, { key: "_userLeft", value: function () { var _userLeft2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee15(peerID) { var did; return _regenerator["default"].wrap(function _callee15$(_context15) { while (1) { switch (_context15.prev = _context15.next) { case 0: did = this._members[peerID]; delete this._members[did]; delete this._members[peerID]; this.emit('user-left', 'left', did, peerID); case 4: case "end": return _context15.stop(); } } }, _callee15, this); })); function _userLeft(_x16) { return _userLeft2.apply(this, arguments); } return _userLeft; }() /** * Handler function for received messages * * @param {String} issuer The issuer of the message * @param {Object} payload The payload of the message */ }, { key: "_messageReceived", value: function () { var _messageReceived2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee16(payload) { var type, message, author, timestamp, postId; return _regenerator["default"].wrap(function _callee16$(_context16) { while (1) { switch (_context16.prev = _context16.next) { case 0: type = payload.type, message = payload.message, author = payload.iss, timestamp = payload.iat, postId = payload.postId; this._backlog.add(JSON.stringify({ type: type, author: author, message: message, timestamp: timestamp, postId: postId })); this.emit('message', { type: type, author: author, message: message, timestamp: timestamp, postId: postId }); case 3: case "end": return _context16.stop(); } } }, _callee16, this); })); function _messageReceived(_x17) { return _messageReceived2.apply(this, arguments); } return _messageReceived; }() /** * Verifies the data received * * @param {Buffer} data A buffer of the jwt * @return {JWT} A verified JWT with our payload and issuer */ }, { key: "_verifyData", value: function () { var _verifyData2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee17(data) { var jwt, cidPromise, verified; return _regenerator["default"].wrap(function _callee17$(_context17) { while (1) { switch (_context17.prev = _context17.next) { case 0: jwt = data.toString(); cidPromise = this._ipfs.dag.put(jwt); _context17.prev = 2; _context17.next = 5; return verifyJWT(jwt, { resolver: this._resolver }); case 5: verified = _context17.sent; _context17.next = 8; return cidPromise; case 8: verified.payload.postId = _context17.sent.toString(); return _context17.abrupt("return", verified); case 12: _context17.prev = 12; _context17.t0 = _context17["catch"](2); console.log(_context17.t0); case 15: case "end": return _context17.stop(); } } }, _callee17, this, [[2, 12]]); })); function _verifyData(_x18) { return _verifyData2.apply(this, arguments); } return _verifyData; }() }, { key: "isGhost", get: function get() { return true; } }]); return GhostThread; }(EventEmitter); module.exports = GhostThread;