3box
Version:
Interact with user data
892 lines (743 loc) • 28.2 kB
JavaScript
"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;