import {filter, split, last, lastIndexOf, merge, reject} from "lodash"; import {BaseDemultiplexedHttpResource} from "./baseDemultiplexedHttp.resource"; import {ChatClientInfoAttribute, ChatData, ChatMessageResourceFilters} from "../core/entities/chat"; import {ChatMessage, ChatMessageData, MultichatMessage} from "../core/entities/message"; import {StreamListener} from "../core/websockets/websocketManager"; import {APICursorHttpResponse} from "../helpers/apiCursor.helper"; import {Observable} from "rxjs"; export interface ChatInterface { mergeMessages: Function; } /** * Chats resource, base class for the Chat. */ export abstract class BaseDemultiplexedHttpChatsResource extends BaseDemultiplexedHttpResource implements ChatInterface { baseResource = '/chats/'; uuid: string; id: number; client_attributes: Array; sessionKey: string; messages: ChatMessage[] = []; data: ChatData; messagesObservable: Observable; protected sortKey: string; // The key to use to order messages on resourceIdentifyingMetadata: { chat: string }; constructor(data: ChatData) { super(['messages', 'chat']); this.uuid = data.uuid; this.resourceIdentifier = data.uuid; this.id = data.id; this.sessionKey = data.session; this.client_attributes = data.client_attributes; this.stream_endpoint = '/chats/'; this.resourceIdentifyingMetadata = { chat: this.uuid } } public abstract requestMessages(uuid: string, filters?: ChatMessageResourceFilters): Promise>; abstract mergeMessages(messages: Array): void; public abstract get loadedMessages(): ChatMessage[]; /** * This method should return an instance of a ChatMessage or a subclass. * @param {MultichatMessage | ChatMessageData} data * @returns {ChatMessage} */ public abstract newMessage(data: MultichatMessage | ChatMessageData): ChatMessage; protected sortMessages() { let that = this; this.messages.sort(function (msgA: ChatMessage, msgB: ChatMessage) { let a = msgA[that.sortKey], b = msgB[that.sortKey]; if (a < b) { return -1 } else if (a > b) { return 1; } else { return 0 } }); let tmpMsgs: Array = []; this.messages.forEach(function (msg) { if (tmpMsgs.indexOf(msg) === -1) { tmpMsgs.push(msg); } else { that.messages.splice(that.messages.indexOf(msg), 1) } }); } /** * Get the current statuses of Chats. * @param base */ public currentStatuses = (base: string) => { let url = this._subResources.currentStatuses.url; if (base) { let parts = split(base, '?'); url = parts[0] + this._subResources.currentStatuses.resource + (parts[1] ? '?' + parts[1] : ''); } return this._wrapHttp({ method: 'GET', url: url }); }; getMessages(filters?: ChatMessageResourceFilters): Promise { let that = this; return that.requestMessages(that.uuid, filters).then((response: APICursorHttpResponse) => { const loadedMessages = response.results.map((data) => { return this.newMessage(data); }); this.mergeMessages(loadedMessages); const lastMsg = that.lastMessage; if (lastMsg) { that.callStreamCallbacksForMessage('messages', lastMsg); } return Promise.resolve(loadedMessages); }); } loadPreviousMessages() { if (this.cursors['messages'].next) { return this.getUrl(this.cursors['messages'].next, 'messages').then((response: APICursorHttpResponse) => { const loadedMessages = response.results.map((data) => { return this.newMessage(data); }); this.mergeMessages(loadedMessages); return Promise.resolve(loadedMessages); }); } else { return undefined } } loadNextMessages() { if (this.cursors['messages'].previous) { return this.getUrl(this.cursors['messages'].previous, 'messages').then(function (response: APICursorHttpResponse) { const loadedMessages = response.results.map((data) => { return this.newMessage(data); }); this.mergeMessages(loadedMessages); return Promise.resolve(loadedMessages); }); } else return undefined; } /** * Return the current state of this chat * @returns {{nFromLast: nFromLast}} */ get state() { let that = this; return { nFromLast: function (msg: ChatMessage, origin?: string, customKey?: string) { let msgs: ChatMessage[] = that.messages; if (origin) { msgs = reject(filter(msgs, (m: ChatMessage) => { return m.origin === origin }), (m: ChatMessage) => { return m.msg_type === 'command' }); } if (customKey) { msgs = filter(msgs, customKey); } return ((msgs.length - 1) - lastIndexOf(msgs, msg)); }, } } /** * Send a message on this chat * @param {ChatMessage} message */ sendMessage(message: ChatMessage) { this.ensureListeningForMessages(); // Make sure we're listening. let newMessage = merge({ chat: this.data.id }, message.getContents()); this.sendDataOnStream(newMessage, message.stream); } /** * Listen for messages on this chat. Should be idempotent. * @return {Chat} */ ensureListeningForMessages() { this.ensureListeningOnStream('messages', ['create', 'received']); return this; }; stopListeningForMessages() { this.stopListeningOnStream('messages'); return this; }; get lastMessage(): ChatMessage | undefined { const msgs = this.loadedMessages; if (msgs.length > 0) { return last(msgs) } else return undefined; } _findMessagesListener(listener: StreamListener): boolean { return listener.stream === 'messages'; }; }