import * as DOMPurify from "dompurify"; import * as log from "loglevel"; import { ChatMessageDTO } from "../dto/ChatMessageDTO"; import { MessageCipherDTO } from "../dto/MessageCipherDTO"; import { ChatModel } from "../model/ChatModel"; import { Observer } from "../observe/Observer"; import { EncryptionService } from "../service/EncryptionService"; import { MarkDownService } from "../service/MarkDownService"; import { JsonAPI } from "../singleton/JsonAPI"; import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel"; import { ChatViewDeps } from "./ChatViewDeps"; import { fetchHandler } from "./FetchHandler"; import { ObserverData } from "../observe/ObserverData"; import { NotificationService } from "../service/NotificationService"; import { UserModel } from "../model/UserModel"; export class ChatView implements Observer { private readonly _chatModel: ChatModel; private readonly _messageContainer: HTMLElement; private readonly _messageSendTemplate: Handlebars.TemplateDelegate< ChatMessageViewModel >; private readonly _messageReceiveTemplate: Handlebars.TemplateDelegate< ChatMessageViewModel >; private readonly _markdownService: MarkDownService; private readonly _encryptionService: EncryptionService; private readonly _notificationService: NotificationService; private readonly _userModel: UserModel; constructor(deps: ChatViewDeps) { this._messageContainer = deps.messageContainer; this._chatModel = deps.chatModel; this._messageSendTemplate = deps.messageSendTemplate; this._messageReceiveTemplate = deps.messageReceiveTemplate; this._markdownService = deps.markdownService; this._encryptionService = deps.encryptionService; this._notificationService = deps.notificationService; this._userModel = deps.userModel; this._initEventListeners(); $(document).ready(function () { $("#action_menu_btn").click(function () { $(".action_menu").toggle(); }); }); this._chatMessagePageLoadAjax(); } update(cd: ObserverData): void { log.info("ChatView: updating view"); switch (cd.op) { case "clear": { $(this._messageContainer).html(""); } break; case "new": { cd.data.forEach((vm: ChatMessageViewModel) => { let rendered = this.renderMessage(vm); $(this._messageContainer).append(rendered); }); $(this._messageContainer) .stop() .animate( { scrollTop: $(this._messageContainer)[0].scrollHeight, }, 1500 ); } break; case "update": { cd.data.forEach((vm: ChatMessageViewModel) => { let rendered = this.renderMessage(vm); $(this._messageContainer).append(rendered); }); // if (cd.data.length > 0) { // this._userModel.notify(); // } $(this._messageContainer) .stop() .animate( { scrollTop: $(this._messageContainer)[0].scrollHeight, }, 1500 ); } break; case "page": { { const rev: ChatMessageViewModel[] = Object.create(cd.data); rev.reverse(); rev.forEach((vm: ChatMessageViewModel) => { let rendered = this.renderMessage(vm); $(this._messageContainer).prepend(rendered); }); } } break; default: new Error("Invalid option"); } } private renderMessage(vm: ChatMessageViewModel): string { const vmTemp: ChatMessageViewModel = { ...vm }; vmTemp.message = this._markdownService.render(vm.message); switch (vmTemp.fromUser) { case JsonAPI.principleName: return DOMPurify.sanitize(this._messageSendTemplate(vmTemp)); default: return DOMPurify.sanitize(this._messageReceiveTemplate(vmTemp)); } } private _initEventListeners(): void { this._addChatFormEL(); } private _addChatFormEL() { const chatForm = document.getElementById( "chatMessageForm" ) as HTMLSelectElement; if (chatForm == null) { log.error("Chat form is null"); } else { chatForm.addEventListener("submit", (e) => this._createChatMessageDTO(e, chatForm) ); } } private _createChatMessageDTO(e: Event, chatForm: HTMLSelectElement): void { e.preventDefault(); let contactName = JsonAPI.contactName; if (!chatForm.checkValidity()) { log.error("Form is not valid"); chatForm.classList.add("was-validated"); return; } chatForm.classList.add("was-validated"); const chatInput = document.getElementById("chatInput") as HTMLInputElement; const vm = this._userModel.activeUsersList.find( (u) => u.userName == JsonAPI.contactName ); // new Date(). vm!.lastMessageTime = new Date(); const passphrase = vm?.passphrase; if (chatInput.value == "") { this._notificationService.error("Please enter a message"); return; } // if (passphraseInput.value == '' || passphraseInput.value == null) { // this._notificationService.error("Please enter a passphrase"); // log.error("Passphrase is null."); // return; // } const messageContent = chatInput.value; vm!.lastMessageText = messageContent.slice(0, 15) + "..."; const msgTime = new Date(); const context: ChatMessageViewModel = { fromUser: JsonAPI.principleName, toUser: contactName, message: messageContent, messageTime: msgTime, }; this.update({ data: new Array(context), op: "new" }); this._userModel.updateLastActive(contactName, msgTime) this._userModel.notify(); let messageCipher: MessageCipherDTO = this._encryptionService.encrypt( passphrase!, messageContent ); let chatMessageDTO = { fromUser: JsonAPI.principleName, toUser: contactName, messageCipher: messageCipher, messageTime: msgTime.toISOString(), }; this._sendMessageAJAX(chatMessageDTO); } private _sendMessageAJAX(chatMessageDTO: any): void { let headers = new Headers(); // console.log("Token = " + btoa("hmm" + ":" + "hmm")) // headers.append('Accept','application/json') headers.append("Content-Type", "application/json"); // headers.append('Authorization', basicAuthToken); // @ts-ignore headers.append("X-AUTH-TOKEN", JsonAPI.authToken); fetch(JsonAPI.MESSAGE_POST, { method: "POST", headers: headers, body: JSON.stringify(chatMessageDTO), }) .then((response) => { log.debug(response); return response.clone(); }) .then((response) => fetchHandler(response, this._notificationService)); } private _chatMessagePageLoadAjax() { this._messageContainer.addEventListener("scroll", (e) => { if ( $(this._messageContainer).scrollTop() == 0 && $(this._messageContainer).html() != "" ) { let currentMsg = $(".msg:first"); log.debug("Reached top"); const vm = this._userModel.activeUsersList.find( (u) => u.userName == JsonAPI.contactName ); this._chatModel.getMessages(vm!, "page").then(() => { if (currentMsg != null) { // log.debug(currentMsg.offset()!.top) $(this._messageContainer).scrollTop( currentMsg.position().top - $(".msg").position()!.top ); } }); } }); } }