A self hosted chat application with end-to-end encrypted messaging.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
6.2 KiB

  1. import { Subject } from "../observe/Observable";
  2. import { Observer } from "../observe/Observer";
  3. import { JsonAPI } from "../singleton/JsonAPI";
  4. import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
  5. import { ChatModelHelper } from "./ChatModelHelper";
  6. import log = require("loglevel");
  7. import { ObserverData } from "../observe/ObserverData";
  8. import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
  9. import moment = require("moment");
  10. interface Params {
  11. userName: string;
  12. data: ChatMessageViewModel[];
  13. op: string;
  14. }
  15. export class ChatModel implements Subject<ChatMessageViewModel> {
  16. /**
  17. * @type {Observer[]} List of subscribers. In real life, the list of
  18. * subscribers can be stored more comprehensively (categorized by event
  19. * type, etc.).
  20. */
  21. private readonly _observers: Observer<ChatMessageViewModel>[] = [];
  22. private readonly _messagePageMap: Map<string, number>;
  23. private readonly _messagesMap: Map<string, ChatMessageViewModel[]>;
  24. private readonly _chatModelHelper: ChatModelHelper;
  25. constructor(chatModelHelper: ChatModelHelper) {
  26. this._messagePageMap = new Map();
  27. this._messagesMap = new Map();
  28. this._chatModelHelper = chatModelHelper;
  29. }
  30. /**
  31. * The subscription management methods.
  32. */
  33. public attach(observer: Observer<ChatMessageViewModel>): void {
  34. log.info("Subject: Attached an observer.");
  35. this._observers.push(observer);
  36. }
  37. public detach(observer: Observer<ChatMessageViewModel>): void {
  38. const observerIndex = this._observers.indexOf(observer);
  39. this._observers.splice(observerIndex, 1);
  40. log.info("Subject: Detached an observer.");
  41. }
  42. private storeUserMessages(
  43. username: string,
  44. messages: ChatMessageViewModel[],
  45. op: string
  46. ) {
  47. switch (op) {
  48. case "clear":
  49. this._messagesMap.set(username, []);
  50. break;
  51. case "page":
  52. this._messagesMap.set(
  53. username,
  54. messages.concat(this.getStoredUserMessages(username))
  55. );
  56. break;
  57. // case "page": this._messagesMap.set(username, messages);
  58. case "new":
  59. this._messagesMap.set(
  60. username,
  61. this.getStoredUserMessages(username).concat(messages)
  62. );
  63. break;
  64. case "update":
  65. this._messagesMap.set(
  66. username,
  67. this.getStoredUserMessages(username).concat(messages)
  68. );
  69. break;
  70. default:
  71. new Error("Invalid option");
  72. }
  73. }
  74. private getStoredUserMessages(username: string): ChatMessageViewModel[] {
  75. let temp = this._messagesMap.get(username);
  76. if (temp == null) return [];
  77. else {
  78. return temp;
  79. }
  80. }
  81. /**
  82. * Trigger an update in each subscriber.
  83. */
  84. public notify(p: Params): void {
  85. log.info("Subject: Notifying observers...");
  86. switch (p.op) {
  87. case "clear":
  88. {
  89. const od: ObserverData<ChatMessageViewModel> = {
  90. data: [],
  91. op: "clear",
  92. };
  93. for (const observer of this._observers) {
  94. observer.update(od);
  95. }
  96. }
  97. break;
  98. case "new":
  99. {
  100. const od: ObserverData<ChatMessageViewModel> = {
  101. data: p.data,
  102. op: p.op,
  103. };
  104. for (const observer of this._observers) {
  105. observer.update(od);
  106. }
  107. }
  108. break;
  109. case "page":
  110. {
  111. const od: ObserverData<ChatMessageViewModel> = {
  112. data: p.data,
  113. op: p.op,
  114. };
  115. for (const observer of this._observers) {
  116. observer.update(od);
  117. }
  118. }
  119. break;
  120. case "update":
  121. {
  122. const od: ObserverData<ChatMessageViewModel> = {
  123. data: p.data,
  124. op: p.op,
  125. };
  126. for (const observer of this._observers) {
  127. observer.update(od);
  128. }
  129. }
  130. break;
  131. default: {
  132. log.error("error");
  133. }
  134. }
  135. }
  136. public someBusinessMethod(chatMessageList: ChatMessageViewModel[]): void {}
  137. public clear(): void {
  138. log.info("Clearing model");
  139. this._messagePageMap.set(JsonAPI.contactName!, 0);
  140. this.storeUserMessages(JsonAPI.contactName!, Array(), "clear");
  141. this.notify({ userName: "", data: [], op: "clear" });
  142. }
  143. public async getMessages(
  144. aVm: ActiveUserViewModel,
  145. op: string
  146. ): Promise<ChatMessageViewModel[]> {
  147. if (this._messagePageMap.get(aVm.userName!) == null)
  148. this._messagePageMap.set(aVm.userName!, 0);
  149. const pageNumber = this._messagePageMap.get(aVm.userName!);
  150. const cVMs = await this._chatModelHelper.getMessages(
  151. aVm.userName!,
  152. aVm.passphrase,
  153. pageNumber!,
  154. aVm.lastMessageTime!,
  155. op
  156. );
  157. let cVMsFiltered = Array<ChatMessageViewModel>();
  158. if (cVMs != null && cVMs.length != 0) {
  159. log.info("Subject: My state has just changed");
  160. const existingMessages = this.getStoredUserMessages(aVm.userName!);
  161. log.debug("existing message:");
  162. log.debug(existingMessages);
  163. log.debug("new messages:");
  164. log.debug(cVMs);
  165. // filter duplicates
  166. cVMsFiltered = cVMs.filter((c) => {
  167. const res = existingMessages.filter((m) => {
  168. if (moment(c.messageTime).isSame(moment(m.messageTime))) return true;
  169. });
  170. if (res.length > 0) return false;
  171. return true;
  172. });
  173. if (op == "update") {
  174. // update the active user last message text and times for each user
  175. const lastMessage = cVMsFiltered[cVMsFiltered.length - 1];
  176. cVMsFiltered.forEach((v) => {
  177. if (v.fromUser == aVm.userName) {
  178. aVm.lastMessageTime = lastMessage.messageTime;
  179. aVm.lastMessageText = lastMessage.message.slice(0, 15) + "...";
  180. }
  181. });
  182. } else {
  183. this._messagePageMap.set(
  184. aVm.userName!,
  185. this._messagePageMap.get(aVm.userName!)! + 1
  186. );
  187. }
  188. this.storeUserMessages(aVm.userName!, cVMsFiltered, op);
  189. this.notify({ userName: aVm.userName!, data: cVMsFiltered, op: op });
  190. }
  191. return cVMsFiltered;
  192. }
  193. public async isPassphraseValid(
  194. passphrase: string,
  195. userName: string
  196. ): Promise<boolean> {
  197. let valid = await this._chatModelHelper.isPassphraseValid(
  198. passphrase,
  199. userName
  200. );
  201. return valid;
  202. }
  203. }