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

import { Subject } from "../observe/Observable";
import { Observer } from "../observe/Observer";
import { JsonAPI } from "../singleton/JsonAPI";
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
import { ChatModelHelper } from "./ChatModelHelper";
import log = require("loglevel");
import { ObserverData } from "../observe/ObserverData";
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
import moment = require("moment");
interface Params {
userName: string;
data: ChatMessageViewModel[];
op: string;
}
export class ChatModel implements Subject<ChatMessageViewModel> {
/**
* @type {Observer[]} List of subscribers. In real life, the list of
* subscribers can be stored more comprehensively (categorized by event
* type, etc.).
*/
private readonly _observers: Observer<ChatMessageViewModel>[] = [];
private readonly _messagePageMap: Map<string, number>;
private readonly _messagesMap: Map<string, ChatMessageViewModel[]>;
private readonly _chatModelHelper: ChatModelHelper;
constructor(chatModelHelper: ChatModelHelper) {
this._messagePageMap = new Map();
this._messagesMap = new Map();
this._chatModelHelper = chatModelHelper;
}
/**
* The subscription management methods.
*/
public attach(observer: Observer<ChatMessageViewModel>): void {
log.info("Subject: Attached an observer.");
this._observers.push(observer);
}
public detach(observer: Observer<ChatMessageViewModel>): void {
const observerIndex = this._observers.indexOf(observer);
this._observers.splice(observerIndex, 1);
log.info("Subject: Detached an observer.");
}
private storeUserMessages(
username: string,
messages: ChatMessageViewModel[],
op: string
) {
switch (op) {
case "clear":
this._messagesMap.set(username, []);
break;
case "page":
this._messagesMap.set(
username,
messages.concat(this.getStoredUserMessages(username))
);
break;
// case "page": this._messagesMap.set(username, messages);
case "new":
this._messagesMap.set(
username,
this.getStoredUserMessages(username).concat(messages)
);
break;
case "update":
this._messagesMap.set(
username,
this.getStoredUserMessages(username).concat(messages)
);
break;
default:
new Error("Invalid option");
}
}
private getStoredUserMessages(username: string): ChatMessageViewModel[] {
let temp = this._messagesMap.get(username);
if (temp == null) return [];
else {
return temp;
}
}
/**
* Trigger an update in each subscriber.
*/
public notify(p: Params): void {
log.info("Subject: Notifying observers...");
switch (p.op) {
case "clear":
{
const od: ObserverData<ChatMessageViewModel> = {
data: [],
op: "clear",
};
for (const observer of this._observers) {
observer.update(od);
}
}
break;
case "new":
{
const od: ObserverData<ChatMessageViewModel> = {
data: p.data,
op: p.op,
};
for (const observer of this._observers) {
observer.update(od);
}
}
break;
case "page":
{
const od: ObserverData<ChatMessageViewModel> = {
data: p.data,
op: p.op,
};
for (const observer of this._observers) {
observer.update(od);
}
}
break;
case "update":
{
const od: ObserverData<ChatMessageViewModel> = {
data: p.data,
op: p.op,
};
for (const observer of this._observers) {
observer.update(od);
}
}
break;
default: {
log.error("error");
}
}
}
public someBusinessMethod(chatMessageList: ChatMessageViewModel[]): void {}
public clear(): void {
log.info("Clearing model");
this._messagePageMap.set(JsonAPI.contactName!, 0);
this.storeUserMessages(JsonAPI.contactName!, Array(), "clear");
this.notify({ userName: "", data: [], op: "clear" });
}
public async getMessages(
aVm: ActiveUserViewModel,
op: string
): Promise<ChatMessageViewModel[]> {
if (this._messagePageMap.get(aVm.userName!) == null)
this._messagePageMap.set(aVm.userName!, 0);
const pageNumber = this._messagePageMap.get(aVm.userName!);
const cVMs = await this._chatModelHelper.getMessages(
aVm.userName!,
aVm.passphrase,
pageNumber!,
aVm.lastMessageTime!,
op
);
let cVMsFiltered = Array<ChatMessageViewModel>();
if (cVMs != null && cVMs.length != 0) {
log.info("Subject: My state has just changed");
const existingMessages = this.getStoredUserMessages(aVm.userName!);
log.debug("existing message:");
log.debug(existingMessages);
log.debug("new messages:");
log.debug(cVMs);
// filter duplicates
cVMsFiltered = cVMs.filter((c) => {
const res = existingMessages.filter((m) => {
if (moment(c.messageTime).isSame(moment(m.messageTime))) return true;
});
if (res.length > 0) return false;
return true;
});
if (op == "update") {
// update the active user last message text and times for each user
const lastMessage = cVMsFiltered[cVMsFiltered.length - 1];
cVMsFiltered.forEach((v) => {
if (v.fromUser == aVm.userName) {
aVm.lastMessageTime = lastMessage.messageTime;
aVm.lastMessageText = lastMessage.message.slice(0, 15) + "...";
}
});
} else {
this._messagePageMap.set(
aVm.userName!,
this._messagePageMap.get(aVm.userName!)! + 1
);
}
this.storeUserMessages(aVm.userName!, cVMsFiltered, op);
this.notify({ userName: aVm.userName!, data: cVMsFiltered, op: op });
}
return cVMsFiltered;
}
public async isPassphraseValid(
passphrase: string,
userName: string
): Promise<boolean> {
let valid = await this._chatModelHelper.isPassphraseValid(
passphrase,
userName
);
return valid;
}
}