container.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. /**
  2. * This file is part of Threema Web.
  3. *
  4. * Threema Web is free software: you can redistribute it and/or modify it
  5. * under the terms of the GNU Affero General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or (at
  7. * your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Affero General Public License
  15. * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. import {ReceiverService} from '../services/receiver';
  18. /**
  19. * A simple HashSet implementation based on a JavaScript object.
  20. *
  21. * Only strings can be stored in this set.
  22. */
  23. class StringHashSet {
  24. private setObj = {};
  25. private val = {};
  26. public add(str: string): void {
  27. this.setObj[str] = this.val;
  28. }
  29. public contains(str: string): boolean {
  30. return this.setObj[str] === this.val;
  31. }
  32. public remove(str: string): void {
  33. delete this.setObj[str];
  34. }
  35. public values() {
  36. let values = [];
  37. for (let i in this.setObj) {
  38. if (this.setObj[i] === this.val) {
  39. values.push(i);
  40. }
  41. }
  42. return values;
  43. }
  44. }
  45. angular.module('3ema.container', [])
  46. .factory('Container', ['$filter', '$log', 'ReceiverService',
  47. function($filter, $log, receiverService: ReceiverService) {
  48. type ContactMap = Map<string, threema.ContactReceiver>;
  49. type GroupMap = Map<string, threema.GroupReceiver>;
  50. type DistributionListMap = Map<string, threema.DistributionListReceiver>;
  51. type MessageMap = Map<threema.ReceiverType, Map<string, ReceiverMessages>>;
  52. type ConversationFilter = (data: threema.Conversation[]) => threema.Conversation[];
  53. type ConversationConverter = (data: threema.Conversation) => threema.Conversation;
  54. type MessageConverter = (data: threema.Message) => threema.Message;
  55. /**
  56. * Collection that manages receivers like contacts, groups or distribution lists.
  57. *
  58. * Think of it like the "address book".
  59. */
  60. class Receivers implements threema.Container.Receivers {
  61. public me: threema.MeReceiver = null;
  62. public contacts: ContactMap = new Map();
  63. public groups: GroupMap = new Map();
  64. public distributionLists: DistributionListMap = new Map();
  65. /**
  66. * Get the receiver map for the specified type.
  67. */
  68. public get(receiverType: threema.ReceiverType): threema.Receiver | Map<string, threema.Receiver> {
  69. switch (receiverType) {
  70. case 'me':
  71. return this.me;
  72. case 'contact':
  73. return this.contacts;
  74. case 'group':
  75. return this.groups;
  76. case 'distributionList':
  77. return this.distributionLists;
  78. default:
  79. throw new Error('Unknown or invalid receiver type: ' + receiverType);
  80. }
  81. }
  82. /**
  83. * Get the receiver matching a certain template.
  84. */
  85. public getData(receiver: threema.BaseReceiver): threema.Receiver | null {
  86. if (receiver.type === 'me') {
  87. return this.me.id === receiver.id ? this.me : undefined;
  88. } else {
  89. const receivers = this.get(receiver.type) as Map<string, threema.Receiver>;
  90. return receivers.get(receiver.id);
  91. }
  92. }
  93. /**
  94. * Set receiver data.
  95. */
  96. public set(data: threema.Container.ReceiverData) {
  97. this.setMe(data['me' as threema.ReceiverType]);
  98. this.setContacts(data['contact' as threema.ReceiverType]);
  99. this.setGroups(data['group' as threema.ReceiverType]);
  100. this.setDistributionLists(data['distributionList' as threema.ReceiverType]);
  101. }
  102. /**
  103. * Set own contact.
  104. */
  105. public setMe(data: threema.MeReceiver): void {
  106. data.type = 'me';
  107. this.me = data;
  108. }
  109. /**
  110. * Set contacts.
  111. */
  112. public setContacts(data: threema.ContactReceiver[]): void {
  113. this.contacts = new Map(data.map((c) => {
  114. c.type = 'contact';
  115. return [c.id, c];
  116. }) as any) as ContactMap;
  117. if (this.me !== undefined) {
  118. this.contacts.set(this.me.id, this.me);
  119. }
  120. }
  121. /**
  122. * Set groups.
  123. */
  124. public setGroups(data: threema.GroupReceiver[]): void {
  125. this.groups = new Map(data.map((g) => {
  126. g.type = 'group';
  127. return [g.id, g];
  128. }) as any) as GroupMap;
  129. }
  130. /**
  131. * Set distribution lists.
  132. */
  133. public setDistributionLists(data: threema.DistributionListReceiver[]): void {
  134. this.distributionLists = new Map(data.map((d) => {
  135. d.type = 'distributionList';
  136. return [d.id, d];
  137. }) as any) as DistributionListMap;
  138. }
  139. public extend(receiverType: threema.ReceiverType, data: threema.Receiver): threema.Receiver {
  140. switch (receiverType) {
  141. case 'me':
  142. return this.extendMe(data as threema.MeReceiver);
  143. case 'contact':
  144. return this.extendContact(data as threema.ContactReceiver);
  145. case 'group':
  146. return this.extendGroup(data as threema.GroupReceiver);
  147. case 'distributionList':
  148. return this.extendDistributionList(data as threema.DistributionListReceiver);
  149. default:
  150. throw new Error('Unknown or invalid receiver type: ' + receiverType);
  151. }
  152. }
  153. public extendDistributionList(data: threema.DistributionListReceiver): threema.DistributionListReceiver {
  154. let distributionListReceiver = this.distributionLists.get(data.id);
  155. if (distributionListReceiver === undefined) {
  156. data.type = 'distributionList';
  157. this.distributionLists.set(data.id, data);
  158. return data;
  159. }
  160. // update existing object
  161. distributionListReceiver = angular.extend(distributionListReceiver, data);
  162. return distributionListReceiver;
  163. }
  164. public extendGroup(data: threema.GroupReceiver): threema.GroupReceiver {
  165. let groupReceiver = this.groups.get(data.id);
  166. if (groupReceiver === undefined) {
  167. data.type = 'group';
  168. this.groups.set(data.id, data);
  169. return data;
  170. }
  171. // update existing object
  172. groupReceiver = angular.extend(groupReceiver, data);
  173. return groupReceiver;
  174. }
  175. public extendMe(data: threema.MeReceiver): threema.MeReceiver {
  176. if (this.me === undefined) {
  177. data.type = 'me';
  178. this.me = data;
  179. return data;
  180. }
  181. // update existing object
  182. this.me = angular.extend(this.me, data);
  183. return this.me;
  184. }
  185. public extendContact(data: threema.ContactReceiver): threema.ContactReceiver {
  186. let contactReceiver = this.contacts.get(data.id);
  187. if (contactReceiver === undefined) {
  188. data.type = 'contact';
  189. this.contacts.set(data.id, data);
  190. return data;
  191. }
  192. // update existing object
  193. contactReceiver = angular.extend(contactReceiver, data);
  194. return contactReceiver;
  195. }
  196. }
  197. class Conversations implements threema.Container.Conversations {
  198. private conversations: threema.Conversation[] = [];
  199. public filter: ConversationFilter = null;
  200. private converter: ConversationConverter = null;
  201. /**
  202. * Get conversations.
  203. */
  204. public get(): threema.Conversation[] {
  205. let conversations = this.conversations;
  206. if (this.filter != null) {
  207. conversations = this.filter(conversations);
  208. }
  209. if (this.converter != null) {
  210. conversations = conversations.map(this.converter);
  211. }
  212. return conversations;
  213. }
  214. /**
  215. * Set conversations.
  216. */
  217. public set(data: threema.Conversation[]): void {
  218. data.forEach((existingConversation: threema.Conversation) => {
  219. this.updateOrAdd(existingConversation);
  220. });
  221. }
  222. public add(conversation: threema.Conversation): void {
  223. this.conversations.splice(conversation.position, 0, conversation);
  224. }
  225. public updateOrAdd(conversation: threema.Conversation): void {
  226. let moveDirection = 0;
  227. let updated = false;
  228. for (let p of this.conversations.keys()) {
  229. if (receiverService.compare(this.conversations[p], conversation)) {
  230. // ok, replace me and break
  231. let old = this.conversations[p];
  232. if (old.position !== conversation.position) {
  233. // position also changed...
  234. moveDirection = old.position > conversation.position ? -1 : 1;
  235. }
  236. this.conversations[p] = conversation;
  237. updated = true;
  238. }
  239. }
  240. // reset the position field to correct the sorting
  241. if (moveDirection !== 0) {
  242. // reindex
  243. let before = true;
  244. for (let p in this.conversations) {
  245. if (receiverService.compare(this.conversations[p], conversation)) {
  246. before = false;
  247. } else if (before && moveDirection < 0) {
  248. this.conversations[p].position++;
  249. } else if (!before && moveDirection > 0) {
  250. this.conversations[p].position++;
  251. }
  252. }
  253. // sort by position field
  254. this.conversations.sort(function (convA, convB) {
  255. return convA.position - convB.position;
  256. });
  257. } else if (!updated) {
  258. this.add(conversation);
  259. }
  260. }
  261. public remove(conversation: threema.Conversation): void {
  262. for (let p of this.conversations.keys()) {
  263. if (receiverService.compare(this.conversations[p], conversation)) {
  264. // remove conversation from array
  265. this.conversations.splice(p, 1);
  266. return;
  267. }
  268. }
  269. }
  270. /**
  271. * Set a filter.
  272. */
  273. public setFilter(filter: ConversationFilter): void {
  274. this.filter = filter;
  275. }
  276. /**
  277. * Set a converter.
  278. */
  279. public setConverter(converter: ConversationConverter): void {
  280. this.converter = converter;
  281. }
  282. }
  283. /**
  284. * Messages between local user and a receiver.
  285. */
  286. class ReceiverMessages {
  287. // The message id used as reference when paging.
  288. public referenceMsgId: number = null;
  289. // Whether a message request has been sent yet.
  290. public requested = false;
  291. // This flag indicates that more (older) messages are available.
  292. public more = true;
  293. // List of messages.
  294. public list: threema.Message[] = [];
  295. }
  296. /**
  297. * This class manages all messages for the current user.
  298. */
  299. class Messages implements threema.Container.Messages {
  300. // The messages are stored in date-ascending order,
  301. // newest messages are appended, older messages are prepended.
  302. private messages: MessageMap = new Map();
  303. // Message converter
  304. public converter: MessageConverter = null;
  305. /**
  306. * Ensure that the receiver exists in the receiver map.
  307. */
  308. private lazyCreate(receiver: threema.Receiver): void {
  309. // If the type is not yet known, create a new type map.
  310. if (!this.messages.has(receiver.type)) {
  311. this.messages.set(receiver.type, new Map());
  312. }
  313. // If the receiver is not yet known, initialize it.
  314. const typeMap = this.messages.get(receiver.type);
  315. if (!typeMap.has(receiver.id)) {
  316. typeMap.set(receiver.id, new ReceiverMessages());
  317. }
  318. }
  319. /**
  320. * Return the `ReceiverMessages` instance for the specified receiver.
  321. *
  322. * If the receiver is not known yet, it is initialized.
  323. */
  324. private getReceiverMessages(receiver: threema.Receiver): ReceiverMessages {
  325. this.lazyCreate(receiver);
  326. return this.messages.get(receiver.type).get(receiver.id);
  327. }
  328. /**
  329. * Return the list of messages for the specified receiver.
  330. *
  331. * If the receiver is not known yet, it is initialized with an empty
  332. * message list.
  333. */
  334. public getList(receiver: threema.Receiver): threema.Message[] {
  335. return this.getReceiverMessages(receiver).list;
  336. }
  337. /**
  338. * Clear and reset all loaded messages but do not remove objects
  339. * @param $scope
  340. */
  341. public clear($scope: ng.IScope): void {
  342. this.messages.forEach ((messageMap: Map<string, ReceiverMessages>,
  343. receiverType: threema.ReceiverType) => {
  344. messageMap.forEach ((messages: ReceiverMessages, id: string) => {
  345. messages.requested = false;
  346. messages.referenceMsgId = null;
  347. messages.more = true;
  348. messages.list = [];
  349. this.notify({
  350. id: id,
  351. type: receiverType,
  352. } as threema.Receiver, $scope);
  353. });
  354. });
  355. }
  356. /**
  357. * Reset the cached messages of a receiver (e.g. the receiver was locked by the mobile)
  358. */
  359. public clearReceiverMessages(receiver: threema.Receiver): Number {
  360. let cachedMessageCount = 0;
  361. if (this.messages.has(receiver.type)) {
  362. let typeMessages = this.messages.get(receiver.type);
  363. if (typeMessages.has(receiver.id)) {
  364. cachedMessageCount = typeMessages.get(receiver.id).list.length;
  365. typeMessages.delete(receiver.id);
  366. }
  367. }
  368. return cachedMessageCount;
  369. }
  370. /**
  371. * Return whether messages from/for the specified receiver are available.
  372. */
  373. public contains(receiver: threema.Receiver): boolean {
  374. return this.messages.has(receiver.type) &&
  375. this.messages.get(receiver.type).has(receiver.id);
  376. }
  377. /**
  378. * Return whether there are more (older) messages available to fetch
  379. * for the specified receiver.
  380. */
  381. public hasMore(receiver: threema.Receiver): boolean {
  382. return this.getReceiverMessages(receiver).more;
  383. }
  384. /**
  385. * Set the "more" flag for the specified receiver.
  386. *
  387. * The flag indicates that more (older) messages are available.
  388. */
  389. public setMore(receiver: threema.Receiver, more: boolean): void {
  390. this.getReceiverMessages(receiver).more = more;
  391. }
  392. /**
  393. * Return the reference msg id for the specified receiver.
  394. */
  395. public getReferenceMsgId(receiver: threema.Receiver): number {
  396. return this.getReceiverMessages(receiver).referenceMsgId;
  397. }
  398. /**
  399. * Return whether the messages for the specified receiver are already
  400. * requested.
  401. */
  402. public isRequested(receiver: threema.Receiver): boolean {
  403. return this.getReceiverMessages(receiver).requested;
  404. }
  405. /**
  406. * Set the requested flag for the specified receiver.
  407. */
  408. public setRequested(receiver: threema.Receiver): void {
  409. const messages = this.getReceiverMessages(receiver);
  410. // If a request was already pending, this must be a bug.
  411. if (messages.requested) {
  412. throw new Error('Message request for receiver ' + receiver.id + ' still pending');
  413. }
  414. // Set requested
  415. messages.requested = true;
  416. }
  417. /**
  418. * Clear the "requested" flag for the specified receiver messages.
  419. */
  420. public clearRequested(receiver): void {
  421. const messages = this.getReceiverMessages(receiver);
  422. messages.requested = false;
  423. }
  424. /**
  425. * Append newer messages.
  426. *
  427. * Messages must be sorted ascending by date.
  428. */
  429. public addNewer(receiver: threema.Receiver, messages: threema.Message[]): void {
  430. if (messages.length === 0) {
  431. // do nothing
  432. return;
  433. }
  434. const receiverMessages = this.getReceiverMessages(receiver);
  435. // if the list is empty, add the current message as ref
  436. if (receiverMessages.list.length === 0) {
  437. receiverMessages.referenceMsgId = messages[0].id;
  438. }
  439. receiverMessages.list.push.apply(receiverMessages.list, messages);
  440. }
  441. /**
  442. * Prepend older messages.
  443. *
  444. * Messages must be sorted ascending by date (oldest first).
  445. */
  446. public addOlder(receiver: threema.Receiver, messages: threema.Message[]): void {
  447. if (messages.length === 0) {
  448. // do nothing
  449. return;
  450. }
  451. // Get reference to message list for the specified receiver
  452. const receiverMessages = this.getReceiverMessages(receiver);
  453. // If the first or last message is already contained in the list,
  454. // do nothing.
  455. const firstId = messages[0].id;
  456. const lastId = messages[messages.length - 1].id;
  457. const predicate = (msg: threema.Message) => msg.id === firstId || msg.id === lastId;
  458. if (receiverMessages.list.findIndex(predicate, receiverMessages.list) !== -1) {
  459. $log.warn('Messages to be prepended intersect with existing messages:', messages);
  460. return;
  461. }
  462. // Add the oldest message as ref
  463. receiverMessages.referenceMsgId = messages[0].id;
  464. receiverMessages.list.unshift.apply(receiverMessages.list, messages);
  465. }
  466. /**
  467. * Update/replace a message with a newer version.
  468. *
  469. * Return a boolean indicating whether the message was found and
  470. * replaced, or not.
  471. */
  472. public update(receiver: threema.Receiver, message: threema.Message): boolean {
  473. const list = this.getList(receiver);
  474. for (let i = 0; i < list.length; i++) {
  475. if (list[i].id === message.id) {
  476. if (message.thumbnail === undefined) {
  477. // do not reset the thumbnail
  478. message.thumbnail = list[i].thumbnail;
  479. }
  480. list[i] = message;
  481. return true;
  482. }
  483. }
  484. return false;
  485. }
  486. /**
  487. * Update a thumbnail of a message, if a message was found the method will return true
  488. *
  489. * @param receiver
  490. * @param messageId
  491. * @param thumbnailImage
  492. * @returns {boolean}
  493. */
  494. public setThumbnail(receiver: threema.Receiver, messageId: number, thumbnailImage: string): boolean {
  495. const list = this.getList(receiver);
  496. for (let message of list) {
  497. if (message.id === messageId) {
  498. if (message.thumbnail === undefined) {
  499. message.thumbnail = {img: thumbnailImage} as threema.Thumbnail;
  500. } else {
  501. message.thumbnail.img = thumbnailImage;
  502. }
  503. return true;
  504. }
  505. }
  506. return false;
  507. }
  508. /**
  509. * Remove a message.
  510. *
  511. * Return a boolean indicating whether the message was found and
  512. * removed, or not.
  513. */
  514. public remove(receiver: threema.Receiver, messageId: number): boolean {
  515. const list = this.getList(receiver);
  516. for (let i = 0; i < list.length; i++) {
  517. if (list[i].id === messageId) {
  518. list.splice(i, 1);
  519. return true;
  520. }
  521. }
  522. return false;
  523. }
  524. /**
  525. * Remove a message.
  526. *
  527. * Return a boolean indicating whether the message was found and
  528. * removed, or not.
  529. */
  530. public removeTemporary(receiver: threema.Receiver, temporaryMessageId: string): boolean {
  531. const list = this.getList(receiver);
  532. for (let i = 0; i < list.length; i++) {
  533. if (list[i].temporaryId === temporaryMessageId) {
  534. list.splice(i, 1);
  535. return true;
  536. }
  537. }
  538. return false;
  539. }
  540. public bindTemporaryToMessageId(receiver: threema.Receiver, temporaryId: string, messageId: number): boolean {
  541. const list = this.getList(receiver);
  542. for (let item of list) {
  543. if (item.temporaryId === temporaryId) {
  544. if (item.id !== undefined) {
  545. // do not bind to a new message id
  546. return false;
  547. }
  548. // reset temporary id
  549. item.temporaryId = null;
  550. // assign to "real" message id
  551. item.id = messageId;
  552. return true;
  553. }
  554. }
  555. return false;
  556. }
  557. public notify(receiver: threema.Receiver, $scope: ng.IScope) {
  558. $scope.$broadcast('threema.receiver.' + receiver.type + '.' + receiver.id + '.messages',
  559. this.getList(receiver), this.hasMore(receiver));
  560. }
  561. /**
  562. * register a message change notify on the given scope
  563. * return the CURRENT list of loaded messages
  564. */
  565. public register(receiver: threema.Receiver, $scope: ng.IScope, callback: any): threema.Message[] {
  566. $scope.$on('threema.receiver.' + receiver.type + '.' + receiver.id + '.messages', callback);
  567. return this.getList(receiver);
  568. }
  569. public updateFirstUnreadMessage(receiver: threema.Receiver): void {
  570. const receiverMessages = this.getReceiverMessages(receiver);
  571. if (receiverMessages !== undefined && receiverMessages.list.length > 0) {
  572. // remove unread
  573. let removedElements = 0;
  574. let firstUnreadMessageIndex;
  575. receiverMessages.list = receiverMessages.list.filter((message: threema.Message, index: number) => {
  576. if (message.type === 'status'
  577. && message.statusType === 'firstUnreadMessage') {
  578. removedElements++;
  579. return false;
  580. } else if (firstUnreadMessageIndex === undefined
  581. && !message.isOutbox
  582. && message.unread) {
  583. firstUnreadMessageIndex = index;
  584. }
  585. return true;
  586. });
  587. if (firstUnreadMessageIndex !== undefined) {
  588. firstUnreadMessageIndex -= removedElements;
  589. receiverMessages.list.splice(firstUnreadMessageIndex, 0 , {
  590. type: 'status',
  591. isStatus: true,
  592. statusType: 'firstUnreadMessage',
  593. } as threema.Message);
  594. }
  595. }
  596. }
  597. }
  598. /**
  599. * Converters transform a message or conversation.
  600. */
  601. class Converter {
  602. public static unicodeToEmoji(message: threema.Message) {
  603. if (message.type === 'text') {
  604. message.body = emojione.toShort(message.body);
  605. }
  606. return message;
  607. }
  608. /**
  609. * Retrieve the receiver corresponding to this conversation and set the
  610. * `receiver` attribute.
  611. */
  612. public static addReceiverToConversation(receivers: Receivers) {
  613. return (conversation: threema.Conversation): threema.Conversation => {
  614. conversation.receiver = receivers.getData({
  615. type: conversation.type,
  616. id: conversation.id,
  617. } as threema.Receiver);
  618. return conversation;
  619. };
  620. }
  621. }
  622. class Filters {
  623. public static hasData(receivers) {
  624. return (obj) => $filter('hasData')(obj, receivers);
  625. }
  626. public static hasContact(contacts) {
  627. return (obj) => $filter('hasContact')(obj, contacts);
  628. }
  629. public static isValidMessage(contacts) {
  630. return (obj) => $filter('isValidMessage')(obj, contacts);
  631. }
  632. }
  633. /**
  634. * This class manages the typing flags for receivers.
  635. *
  636. * Internally values are stored in a hash set for efficient lookup.
  637. */
  638. class Typing implements threema.Container.Typing {
  639. private set = new StringHashSet();
  640. private getReceiverUid(receiver: threema.ContactReceiver): string {
  641. return receiver.type + '-' + receiver.id;
  642. }
  643. public setTyping(receiver: threema.ContactReceiver): void {
  644. this.set.add(this.getReceiverUid(receiver));
  645. }
  646. public unsetTyping(receiver: threema.ContactReceiver): void {
  647. this.set.remove(this.getReceiverUid(receiver));
  648. }
  649. public isTyping(receiver: threema.ContactReceiver): boolean {
  650. return this.set.contains(this.getReceiverUid(receiver));
  651. }
  652. }
  653. /**
  654. * Holds message drafts and quotes
  655. */
  656. class Drafts implements threema.Container.Drafts {
  657. private quotes = new Map<String, threema.Quote>();
  658. // Use to implement draft texts!
  659. private texts = new Map<string, string>();
  660. private getReceiverUid(receiver: threema.Receiver): string {
  661. // do not use receiver.type => can be null
  662. return receiver.id;
  663. }
  664. public setQuote(receiver: threema.Receiver, quote: threema.Quote): void {
  665. this.quotes.set(this.getReceiverUid(receiver), quote);
  666. }
  667. public removeQuote(receiver: threema.Receiver): void {
  668. this.quotes.delete(this.getReceiverUid(receiver));
  669. }
  670. public getQuote(receiver: threema.Receiver): threema.Quote {
  671. return this.quotes.get(this.getReceiverUid(receiver));
  672. }
  673. public setText(receiver: threema.Receiver, draftMessage: string): void {
  674. this.texts.set(this.getReceiverUid(receiver), draftMessage);
  675. }
  676. public removeText(receiver: threema.Receiver): void {
  677. this.texts.delete(this.getReceiverUid(receiver));
  678. }
  679. public getText(receiver: threema.Receiver): string {
  680. return this.texts.get(this.getReceiverUid(receiver));
  681. }
  682. }
  683. return {
  684. Converter: Converter as threema.Container.Converter,
  685. Filters: Filters as threema.Container.Filters,
  686. createReceivers: () => new Receivers(),
  687. createConversations: () => new Conversations(),
  688. createMessages: () => new Messages(),
  689. createTyping: () => new Typing(),
  690. createDrafts: () => new Drafts(),
  691. } as threema.Container.Factory;
  692. }]);