@atlaskit/mention
Version:
A React component used to display user profiles in a list for 'Mention' functionality
344 lines (334 loc) • 13.7 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.DefaultPresenceParser = exports.DefaultPresenceCache = exports.AbstractPresenceResource = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _logger = _interopRequireDefault(require("../util/logger"));
var _MentionResource = require("./MentionResource");
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
var CacheEntry = /*#__PURE__*/function () {
function CacheEntry(pres, timeout) {
(0, _classCallCheck2.default)(this, CacheEntry);
this.presence = pres;
this.expiry = Date.now() + timeout;
}
return (0, _createClass2.default)(CacheEntry, [{
key: "expired",
value: function expired() {
return Date.now() > this.expiry;
}
}]);
}();
var AbstractPresenceResource = exports.AbstractPresenceResource = /*#__PURE__*/function (_AbstractResource) {
function AbstractPresenceResource() {
(0, _classCallCheck2.default)(this, AbstractPresenceResource);
return _callSuper(this, AbstractPresenceResource, arguments);
}
(0, _inherits2.default)(AbstractPresenceResource, _AbstractResource);
return (0, _createClass2.default)(AbstractPresenceResource, [{
key: "refreshPresence",
value: function refreshPresence(userIds) {
throw new Error("not yet implemented.\nParams: userIds=".concat(userIds));
}
}, {
key: "notifyListeners",
value: function notifyListeners(presences) {
this.changeListeners.forEach(function (listener, key) {
try {
listener(presences);
} catch (e) {
// ignore error from listener
(0, _logger.default)("error from listener '".concat(key, "', ignoring"), e);
}
});
}
}]);
}(_MentionResource.AbstractResource);
var PresenceResource = /*#__PURE__*/function (_AbstractPresenceReso) {
function PresenceResource(config) {
var _this;
(0, _classCallCheck2.default)(this, PresenceResource);
_this = _callSuper(this, PresenceResource);
if (!config.url) {
throw new Error('config.url is a required parameter');
}
if (!config.cloudId) {
throw new Error('config.cloudId is a required parameter');
}
_this.config = config;
_this.config.url = PresenceResource.cleanUrl(config.url);
_this.presenceCache = config.cache || new DefaultPresenceCache(config.cacheExpiry);
_this.presenceParser = config.parser || new DefaultPresenceParser();
return _this;
}
(0, _inherits2.default)(PresenceResource, _AbstractPresenceReso);
return (0, _createClass2.default)(PresenceResource, [{
key: "refreshPresence",
value: function refreshPresence(userIds) {
var cacheHits = this.presenceCache.getBulk(userIds);
this.notifyListeners(cacheHits);
var cacheMisses = this.presenceCache.getMissingUserIds(userIds);
if (cacheMisses.length) {
this.retrievePresence(cacheMisses);
}
}
}, {
key: "retrievePresence",
value: function retrievePresence(userIds) {
var _this2 = this;
this.queryDirectoryForPresences(userIds).then(function (res) {
return _this2.presenceParser.parse(res);
}).then(function (presences) {
_this2.notifyListeners(presences);
_this2.presenceCache.update(presences);
});
}
}, {
key: "queryDirectoryForPresences",
value: function queryDirectoryForPresences(userIds) {
var query = {
query: "query getPresenceForMentions($organizationId: String!, $userIds: [String!], $productId: String) {\n PresenceBulk(organizationId: $organizationId, product: $productId, userIds: $userIds) {\n userId\n state\n stateMetadata\n }\n }",
variables: {
organizationId: this.config.cloudId,
userIds: userIds
}
};
if (this.config.productId) {
query.variables['productId'] = this.config.productId;
}
var options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(query)
};
return fetch(this.config.url, options).then(function (response) {
return response.json();
});
}
}], [{
key: "cleanUrl",
value: function cleanUrl(url) {
if (url.substr(-1) !== '/') {
url += '/';
}
return url;
}
}]);
}(AbstractPresenceResource);
var DefaultPresenceCache = exports.DefaultPresenceCache = /*#__PURE__*/function () {
function DefaultPresenceCache(cacheTimeout, cacheTrigger) {
(0, _classCallCheck2.default)(this, DefaultPresenceCache);
this.expiryInMillis = cacheTimeout ? cacheTimeout : DefaultPresenceCache.defaultTimeout;
this.flushTrigger = cacheTrigger ? cacheTrigger : DefaultPresenceCache.defaultFlushTrigger;
this.cache = {};
this.size = 0;
}
/**
* Precondition: _delete is only called internally if userId exists in cache
* Removes cache entry
* @param userId
*/
return (0, _createClass2.default)(DefaultPresenceCache, [{
key: "_delete",
value: function _delete(userId) {
delete this.cache[userId];
this.size--;
}
/**
* Checks a cache entry and calls delete if the info has expired
* @param userId
*/
}, {
key: "_deleteIfExpired",
value: function _deleteIfExpired(userId) {
if (this.contains(userId) && this.cache[userId].expired()) {
this._delete(userId);
}
}
/**
* Cleans expired entries from cache
*/
}, {
key: "_removeExpired",
value: function _removeExpired() {
var _this3 = this;
Object.keys(this.cache).forEach(function (id) {
_this3._deleteIfExpired(id);
});
}
/**
* Checks if a user exists in the cache
* @param userId
*/
}, {
key: "contains",
value: function contains(userId) {
return this.cache.hasOwnProperty(userId);
}
/**
* Retrieves a presence from the cache after checking for expired entries
* @param userId - to index the cache
* @returns Presence - the presence that matches the userId
*/
}, {
key: "get",
value: function get(userId) {
this._deleteIfExpired(userId);
if (!this.contains(userId)) {
return {};
}
return this.cache[userId].presence;
}
/**
* Retrieve multiple presences at once from the cache
* @param userIds - to index the cache
* @returns PresenceMap - A map of userIds to cached Presences
*/
}, {
key: "getBulk",
value: function getBulk(userIds) {
var presences = {};
var _iterator = _createForOfIteratorHelper(userIds),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _userId = _step.value;
if (this.contains(_userId)) {
presences[_userId] = this.get(_userId);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return presences;
}
/**
* For a given list of ids, returns a subset
* of all the ids with missing cache entries.
* @param userIds - to index the cache
* @returns string[] - ids missing from the cache
*/
}, {
key: "getMissingUserIds",
value: function getMissingUserIds(userIds) {
var _this4 = this;
return userIds.filter(function (id) {
return !_this4.contains(id);
});
}
/**
* Precondition: presMap only contains ids of users not in cache
* expired users must first be removed then reinserted with updated presence
* Updates the cache by adding the new Presence entries and setting the expiry time
* @param presMap
*/
}, {
key: "update",
value: function update(presMap) {
var _this5 = this;
if (this.size >= this.flushTrigger) {
this._removeExpired();
}
Object.keys(presMap).forEach(function (userId) {
_this5.cache[userId] = new CacheEntry(presMap[userId], _this5.expiryInMillis);
_this5.size++;
});
}
}]);
}();
(0, _defineProperty2.default)(DefaultPresenceCache, "defaultTimeout", 20000);
(0, _defineProperty2.default)(DefaultPresenceCache, "defaultFlushTrigger", 50);
var DefaultPresenceParser = exports.DefaultPresenceParser = /*#__PURE__*/function () {
function DefaultPresenceParser() {
(0, _classCallCheck2.default)(this, DefaultPresenceParser);
}
return (0, _createClass2.default)(DefaultPresenceParser, [{
key: "mapState",
value: function mapState(state) {
if (state === 'unavailable') {
return 'offline';
} else if (state === 'available') {
return 'online';
} else {
return state;
}
}
}, {
key: "parse",
value: function parse(response) {
var presences = {};
if (response.hasOwnProperty('data') && response['data'].hasOwnProperty('PresenceBulk')) {
var results = response['data'].PresenceBulk;
// Store map of state and time indexed by userId. Ignore null results.
var _iterator2 = _createForOfIteratorHelper(results),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var user = _step2.value;
if (user.userId && user.state) {
var _state = DefaultPresenceParser.extractState(user) || user.state;
presences[user.userId] = {
status: this.mapState(_state)
};
} else if (!user.hasOwnProperty('userId') || !user.hasOwnProperty('state')) {
// eslint-disable-next-line no-console
console.error('Unexpected response from presence service contains keys: ' + Object.keys(user));
}
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
}
return presences;
}
}], [{
key: "extractState",
value: function extractState(presence) {
if (DefaultPresenceParser.isFocusState(presence)) {
return DefaultPresenceParser.FOCUS_STATE;
}
return presence.state;
}
/*
This is a bit of an odd exception. In the case where a user is in "Focus Mode", their presence state
is returned as 'busy' along with a `stateMetadata` object containing a `focus` field.
In this case we ignore the value of the `state` field and treat the presence as a 'focus' state.
*/
}, {
key: "isFocusState",
value: function isFocusState(presence) {
if (presence.stateMetadata) {
try {
var metadata = JSON.parse(presence.stateMetadata);
return metadata && !!metadata.focus;
} catch (e) {
// eslint-disable-next-line no-console
console.error("Failed to parse presence's stateMetadata for user with id ".concat(presence.userId, ": ").concat(presence.stateMetadata));
// eslint-disable-next-line no-console
console.error(e);
}
}
return false;
}
}]);
}();
(0, _defineProperty2.default)(DefaultPresenceParser, "FOCUS_STATE", 'focus');
var _default = exports.default = PresenceResource;