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'; export class ChatView implements Observer { private readonly _chatModel: ChatModel; private readonly _messageContainer: HTMLElement; private readonly _messageSendTemplate: Handlebars.TemplateDelegate; private readonly _messageReceiveTemplate: Handlebars.TemplateDelegate; private readonly _markdownService: MarkDownService; private readonly _encryptionService: EncryptionService; private readonly _notificationService: NotificationService; 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._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": { const rev: ChatMessageViewModel[] = Object.create(cd.data) // rev.reverse(); let arr: string[] = []; rev.forEach((vm: ChatMessageViewModel) => { const vmTemp: ChatMessageViewModel = { ...vm }; vmTemp.message = this._markdownService.render(vm.message); /** Very Important!!! * Sanitizing HTML before displaying on webpage to prevent XSS attacks!! */ let rendered; if (vmTemp.fromUser == JsonAPI.principleName) { rendered = DOMPurify.sanitize(this._messageSendTemplate(vmTemp)); } else { rendered = DOMPurify.sanitize(this._messageReceiveTemplate(vmTemp)); } $(this._messageContainer).append(rendered); }); $(this._messageContainer).stop().animate({ scrollTop: $(this._messageContainer)[0].scrollHeight }, 1500); } break; default: { const rev: ChatMessageViewModel[] = Object.create(cd.data) rev.reverse(); let arr: string[] = []; rev.forEach((vm: ChatMessageViewModel) => { const vmTemp: ChatMessageViewModel = { ...vm }; vmTemp.message = this._markdownService.render(vm.message); /** Very Important!!! * Sanitizing HTML before displaying on webpage to prevent XSS attacks!! */ let rendered; if (vmTemp.fromUser == JsonAPI.principleName) { rendered = DOMPurify.sanitize(this._messageSendTemplate(vmTemp)); } else { rendered = DOMPurify.sanitize(this._messageReceiveTemplate(vmTemp)); } $(this._messageContainer).prepend(rendered); }); } } } 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 (contactName == null) { log.error("Contact name is null"); return; } 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 passphraseInput = document.getElementById('passphrase') as HTMLInputElement; if (chatInput.value == '' || chatInput.value == null) { this._notificationService.error("Please enter a message"); log.error("Chat input is null."); return; } if (passphraseInput.value == '' || passphraseInput.value == null) { this._notificationService.error("Please enter a passphrase"); log.error("Passphrase is null."); return; } const messageContent = chatInput.value; const msgTime = new Date(); const context: ChatMessageViewModel = { fromUser: JsonAPI.principleName || "error", toUser: "", message: messageContent, messageTime: msgTime }; this.update({data: new Array(context), op: "new"}) let messageCipher: MessageCipherDTO = this._encryptionService.encrypt(passphraseInput.value, messageContent) let chatMessageDTO = { "fromUser": JsonAPI.principleName || "", "toUser": contactName, "messageCipher": messageCipher, messageTime: msgTime } this._sendMessageAJAX(chatMessageDTO); } private _sendMessageAJAX(chatMessageDTO: ChatMessageDTO): 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') let passphrase: string; let passphraseInput = document.getElementById('passphrase') as HTMLInputElement; if (passphraseInput == null) { log.error('passphraseInput element reference is null'); return; } passphrase = passphraseInput.value if (passphrase == '' || passphrase == null) { // alert('Please input passphrase') // alertify.error('Please enter a passphrase'); log.error('passphrase is empty or null'); return; } if (JsonAPI.contactName != null) this._chatModel.getMessages(JsonAPI.contactName, passphrase, null, "page").then(() => { if (currentMsg != null) { // log.debug(currentMsg.offset()!.top) $(this._messageContainer).scrollTop(currentMsg.position().top - $('.msg').position()!.top) } }); } }) } }