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.
 
 
 
 
 
 

223 lines
8.7 KiB

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<ChatMessageViewModel> {
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;
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<ChatMessageViewModel>): 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)
}
});
}
})
}
}