message.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. /**
  2. * Abstract class Message
  3. * @constructor
  4. * @param {*} e
  5. * @param {number} ts
  6. **/
  7. function Message(e, ts) {
  8. /** @const @type {string} **/
  9. this.userId = e["user"];
  10. /** @type {string} */
  11. this.username = e["username"];
  12. /** @const @type {string} **/
  13. this.id = e["id"] || e["ts"];
  14. /** @const @type {number} **/
  15. this.ts = parseFloat(e["ts"]);
  16. /** @type {string} */
  17. this.text = "";
  18. /** @type {string|undefined} */
  19. this.pendingId;
  20. /** @type {Array.<*>} */
  21. this.attachments = [];
  22. /** @type {boolean} */
  23. this.starred = e["is_starred"] || false;
  24. /** @type {number|boolean} */
  25. this.edited = false;
  26. /** @type {boolean} */
  27. this.removed = false;
  28. /**
  29. * for each emoji code, an array of user id
  30. * @type {Object.<string, Array.<string>>}
  31. **/
  32. this.reactions = {};
  33. /** @type {number} */
  34. this.version = ts;
  35. this.update(e, ts);
  36. };
  37. /**
  38. * @constructor
  39. * @extends {Message}
  40. **/
  41. function MeMessage(e, ts) {
  42. Message.call(this, e, ts);
  43. };
  44. /**
  45. * @constructor
  46. * @extends {Message}
  47. **/
  48. function NoticeMessage(e, ts) {
  49. Message.call(this, e, ts);
  50. };
  51. /**
  52. * @param {*} e
  53. * @param {number} ts
  54. **/
  55. Message.prototype.update = function(e, ts) {
  56. if (e) {
  57. this.text = e["text"] || "";
  58. if (e["attachments"]) this.attachments = e["attachments"];
  59. this.starred = !!e["is_starred"];
  60. this.edited = e["edited"] === undefined ? false : e["edited"];
  61. this.removed = !!e["removed"];
  62. if (e["reactions"]) {
  63. var reactions = {};
  64. e["reactions"].forEach(function(r) {
  65. reactions[r["name"]] = [];
  66. r["users"].forEach(function(userId) {
  67. reactions[r["name"]].push(userId);
  68. });
  69. });
  70. this.reactions = reactions;
  71. }
  72. if (e["pendingId"])
  73. this.pendingId = e["pendingId"];
  74. } else {
  75. this.removed = true;
  76. }
  77. this.version = ts;
  78. };
  79. /** @return {*} */
  80. Message.prototype.toStatic = function() {
  81. var reactions = [];
  82. for (var reaction in this.reactions) {
  83. reactions.push({
  84. "name": reaction
  85. ,"users": this.reactions[reaction]
  86. });
  87. }
  88. return {
  89. "id": this.id
  90. ,"user": this.userId
  91. ,"username": this.username
  92. ,"ts": this.ts
  93. ,"text": this.text
  94. ,"pendingId": this.pendingId
  95. ,"attachments": this.attachments.length ? this.attachments : undefined
  96. ,"is_starred": this.starred || undefined
  97. ,"edited": this.edited === false ? undefined : this.edited
  98. ,"removed": this.removed || undefined
  99. ,"reactions": reactions
  100. ,"isMeMessage": this instanceof MeMessage || undefined
  101. ,"isNotice": this instanceof NoticeMessage || undefined
  102. };
  103. };
  104. /**
  105. * @param {string} reaction
  106. * @param {string} userId
  107. * @param {number} ts
  108. **/
  109. Message.prototype.addReaction = function(reaction, userId, ts) {
  110. if (!this.reactions[reaction])
  111. this.reactions[reaction] = [];
  112. if (this.reactions[reaction].indexOf(userId) === -1) {
  113. this.reactions[reaction].push(userId);
  114. this.version = ts;
  115. return true;
  116. }
  117. return false;
  118. };
  119. /**
  120. * @param {string} reaction
  121. * @param {string} userId
  122. * @param {number} ts
  123. **/
  124. Message.prototype.removeReaction = function(reaction, userId, ts) {
  125. var updated = false;
  126. if (this.reactions[reaction]) {
  127. if (this.reactions[reaction].length === 1 && this.reactions[reaction][0] === userId) {
  128. delete this.reactions[reaction];
  129. updated = true;
  130. } else {
  131. this.reactions[reaction] = this.reactions[reaction].filter(function(i) {
  132. if (i !== userId)
  133. return true;
  134. updated = true;
  135. return false;
  136. });
  137. }
  138. }
  139. if (updated)
  140. this.version = ts;
  141. return updated;
  142. };
  143. Message.prototype.hasReactionForUser = function(reaction, userId) {
  144. if (this.reactions[reaction])
  145. return this.reactions[reaction].indexOf(userId) !== -1;
  146. return false;
  147. };
  148. /**
  149. * @constructor
  150. * @param {Room|string} room or roomId
  151. * @param {number} keepMessages number of messages to keep in memory
  152. * @param {number} maxAge number of messages to keep in memory
  153. * @param {Array|undefined} evts
  154. * @param {number|undefined} now
  155. **/
  156. function RoomHistory(room, keepMessages, maxAge, evts, now) {
  157. /** @type {string} */
  158. this.id = typeof room === "string" ? room : room.id;
  159. /** @type {Array.<Message>} */
  160. this.messages = [];
  161. /** @type {number} */
  162. this.maxAge = maxAge;
  163. /** @type number */
  164. this.v = 0;
  165. /** @const @type {number} */
  166. this.keepMessages = keepMessages;
  167. if (evts) this.pushAll(evts, now);
  168. };
  169. /**
  170. * @param {Array} evts
  171. * @return {number} biggest ts
  172. **/
  173. RoomHistory.prototype.pushAll = function(evts, t) {
  174. var result = 0;
  175. evts.forEach(function(e) {
  176. result = Math.max(this.push(e, t), result);
  177. }.bind(this));
  178. this.resort();
  179. return result;
  180. };
  181. /**
  182. * @param {*} ev
  183. * @param {number} ts
  184. * @return {Message}
  185. **/
  186. RoomHistory.prototype.messageFactory = function(ev, ts) {
  187. if (ev["isMeMessage"] === true)
  188. return new MeMessage(ev, ts);
  189. if (ev["isNotice"] === true)
  190. return new NoticeMessage(ev, ts);
  191. return new Message(ev, ts);
  192. };
  193. /**
  194. * @param {*} e
  195. * @return {number} t
  196. **/
  197. RoomHistory.prototype.push = function(e, t) {
  198. var exists = false
  199. ,ts;
  200. for (var i =0, nbMsg = this.messages.length; i < nbMsg; i++) {
  201. var msgObj = this.messages[i];
  202. if (msgObj.id === e["id"]) {
  203. ts = msgObj.update(e, t);
  204. exists = true;
  205. break;
  206. }
  207. }
  208. if (!exists) {
  209. var msgObj = this.messageFactory(e, t);
  210. this.messages.push(msgObj);
  211. ts = msgObj.ts;
  212. }
  213. this.cleanOld(t);
  214. return ts || 0;
  215. };
  216. RoomHistory.prototype.cleanOld = function(t) {
  217. while (this.messages.length > this.keepMessages)
  218. this.messages.shift();
  219. if (this.maxAge) {
  220. for (var i =0; i < this.messages.length; i++) {
  221. if (this.messages[i].version < t -this.maxAge) {
  222. this.messages.splice(i--, 1);
  223. }
  224. }
  225. }
  226. };
  227. /**
  228. * @return {Message|undefined}
  229. **/
  230. RoomHistory.prototype.lastMessage = function() {
  231. return this.messages[this.messages.length -1];
  232. };
  233. /**
  234. * @param {string} reaction
  235. * @param {string} userId
  236. * @param {string} msgId
  237. * @param {number} ts
  238. * @return {Message|null} WARNING if null, you *should* fetch new message and append it
  239. **/
  240. RoomHistory.prototype.addReaction = function(reaction, userId, msgId, ts) {
  241. var msg = this.getMessageById(msgId);
  242. if (msg)
  243. msg.addReaction(reaction, userId, ts);
  244. return msg;
  245. };
  246. /**
  247. * @param {string} reaction
  248. * @param {string} userId
  249. * @param {string} msgId
  250. * @param {number} ts
  251. * @return {Message|null} WARNING if null, you *should* fetch new message and append it
  252. **/
  253. RoomHistory.prototype.removeReaction = function(reaction, userId, msgId, ts) {
  254. var msg = this.getMessageById(msgId);
  255. if (msg)
  256. msg.removeReaction(reaction, userId, ts);
  257. return msg;
  258. };
  259. /**
  260. * @param {number} ts
  261. * @return {Message|null}
  262. **/
  263. RoomHistory.prototype.getMessage = function(ts) {
  264. for (var i =0, nbMessages = this.messages.length; i < nbMessages && ts >= this.messages[i].ts; i++) {
  265. if (this.messages[i].ts === ts)
  266. return this.messages[i];
  267. }
  268. return null;
  269. };
  270. /**
  271. * @param {string} id
  272. * @return {Message|null}
  273. **/
  274. RoomHistory.prototype.getMessageById = function(id) {
  275. for (var i =0, nbMessages = this.messages.length; i < nbMessages; i++) {
  276. if (this.messages[i].id == id)
  277. return this.messages[i];
  278. }
  279. return null;
  280. };
  281. /**
  282. * @return {Array.<*>}
  283. **/
  284. RoomHistory.prototype.toStatic = function(knownVersion) {
  285. var result = [];
  286. for (var i = this.messages.length -1; i >= 0; i--)
  287. if (this.messages[i].version > knownVersion)
  288. result.push(this.messages[i].toStatic());
  289. return result;
  290. };
  291. RoomHistory.prototype.resort = function() {
  292. this.messages.sort(function(a, b) {
  293. return a.ts -b.ts;
  294. });
  295. };
  296. MeMessage.prototype = Object.create(Message.prototype);
  297. MeMessage.prototype.constructor = MeMessage;
  298. NoticeMessage.prototype = Object.create(Message.prototype);
  299. NoticeMessage.prototype.constructor = NoticeMessage;
  300. /** @suppress {undefinedVars,checkTypes} */
  301. (function() {
  302. if (typeof module !== "undefined") module.exports = {
  303. Message: Message
  304. ,MeMessage: MeMessage
  305. ,NoticeMessage: NoticeMessage
  306. ,RoomHistory: RoomHistory
  307. }
  308. })();