|
|
@ -3,162 +3,217 @@ import { Observer } from "../observe/Observer"; |
|
|
|
import { JsonAPI } from "../singleton/JsonAPI"; |
|
|
|
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel"; |
|
|
|
import { ChatModelHelper } from "./ChatModelHelper"; |
|
|
|
import log = require('loglevel'); |
|
|
|
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 |
|
|
|
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); |
|
|
|
/** |
|
|
|
* @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"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public detach(observer: Observer<ChatMessageViewModel>): void { |
|
|
|
const observerIndex = this._observers.indexOf(observer); |
|
|
|
this._observers.splice(observerIndex, 1); |
|
|
|
log.info('Subject: Detached an observer.'); |
|
|
|
private getStoredUserMessages(username: string): ChatMessageViewModel[] { |
|
|
|
let temp = this._messagesMap.get(username); |
|
|
|
if (temp == null) return []; |
|
|
|
else { |
|
|
|
return temp; |
|
|
|
} |
|
|
|
|
|
|
|
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"); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 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); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
private getStoredUserMessages(username: string): ChatMessageViewModel[] { |
|
|
|
let temp = this._messagesMap.get(username); |
|
|
|
if (temp == null) |
|
|
|
return []; |
|
|
|
else { |
|
|
|
return temp; |
|
|
|
break; |
|
|
|
case "new": |
|
|
|
{ |
|
|
|
const od: ObserverData<ChatMessageViewModel> = { |
|
|
|
data: p.data, |
|
|
|
op: p.op, |
|
|
|
}; |
|
|
|
for (const observer of this._observers) { |
|
|
|
observer.update(od); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 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") } |
|
|
|
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 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 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(vm: ActiveUserViewModel, op: string): Promise<ChatMessageViewModel[]> { |
|
|
|
if (this._messagePageMap.get(vm.userName!) == null) |
|
|
|
this._messagePageMap.set(vm.userName!, 0); |
|
|
|
|
|
|
|
const pageNumber = this._messagePageMap.get(vm.userName!) |
|
|
|
const cVMs = await this._chatModelHelper.getMessages(vm.userName!, vm.passphrase, pageNumber!, vm.lastMessageTime!, op); |
|
|
|
let cVMs2 = Array(); |
|
|
|
if (cVMs != null) { |
|
|
|
log.info('Subject: My state has just changed') |
|
|
|
const existingMessages = this.getStoredUserMessages(vm.userName!); |
|
|
|
log.debug('existing message:') |
|
|
|
log.debug(existingMessages); |
|
|
|
log.debug('new messages:') |
|
|
|
log.debug(cVMs); |
|
|
|
cVMs2 = 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 (cVMs2.length != 0 && op == "update") { |
|
|
|
const lastMessageText = vm.lastMessageText = cVMs2[cVMs2.length - 1].message; |
|
|
|
cVMs2.forEach(v => { |
|
|
|
if (v.userName == vm.userName) { |
|
|
|
v.lastMessageTime = cVMs2[cVMs2.length - 1].messageTime; |
|
|
|
v.lastMessageText = cVMs2[cVMs2.length - 1].message.slice(0, 15) + "..." |
|
|
|
} |
|
|
|
}) |
|
|
|
}; |
|
|
|
|
|
|
|
if (cVMs2.length != 0 && op != "update") { |
|
|
|
|
|
|
|
this._messagePageMap.set(vm.userName!, this._messagePageMap.get(vm.userName!)! + 1); |
|
|
|
} |
|
|
|
|
|
|
|
this.storeUserMessages(vm.userName!, cVMs2, op); |
|
|
|
this.notify({ userName: vm.userName!, data: cVMs2, op: op }); |
|
|
|
} |
|
|
|
else { |
|
|
|
log.error('Messages were null'); |
|
|
|
} |
|
|
|
|
|
|
|
return cVMs2; |
|
|
|
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 }); |
|
|
|
} |
|
|
|
|
|
|
|
public async isPassphraseValid(passphrase: string, userName: string): Promise<boolean> { |
|
|
|
let valid = await this._chatModelHelper.isPassphraseValid(passphrase, userName); |
|
|
|
return valid; |
|
|
|
} |
|
|
|
} |
|
|
|
return cVMsFiltered; |
|
|
|
} |
|
|
|
|
|
|
|
public async isPassphraseValid( |
|
|
|
passphrase: string, |
|
|
|
userName: string |
|
|
|
): Promise<boolean> { |
|
|
|
let valid = await this._chatModelHelper.isPassphraseValid( |
|
|
|
passphrase, |
|
|
|
userName |
|
|
|
); |
|
|
|
return valid; |
|
|
|
} |
|
|
|
} |