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.
 
 
 
 
 
 

243 lines
7.5 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";
import { UserModel } from "../model/UserModel";
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;
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<ChatMessageViewModel>): 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
);
}
});
}
});
}
}