import {AbstractConversationUiEvents, ChatHistoryState} from "./AbstractConversationDefinitions"; import {EventHandler, Registry} from "../../../events"; import {LogCategory, logError} from "../../../log"; import {tr, tra} from "../../../i18n/localize"; import { AbstractChat, AbstractChatManager, AbstractChatManagerEvents, AbstractConversationEvents } from "tc-shared/conversations/AbstractConversion"; export const kMaxChatFrameMessageSize = 50; /* max 100 messages, since the server does not support more than 100 messages queried at once */ export abstract class AbstractConversationController< Events extends AbstractConversationUiEvents, Manager extends AbstractChatManager, ManagerEvents extends AbstractChatManagerEvents, ConversationType extends AbstractChat, ConversationEvents extends AbstractConversationEvents > { protected readonly uiEvents: Registry; protected conversationManager: Manager | undefined; protected listenerManager: (() => void)[]; protected currentSelectedConversation: ConversationType; protected currentSelectedListener: (() => void)[]; protected constructor() { this.uiEvents = new Registry(); this.currentSelectedListener = []; this.listenerManager = []; } destroy() { this.listenerManager.forEach(callback => callback()); this.listenerManager.splice(0, this.listenerManager.length); this.uiEvents.fire("notify_destroy"); this.uiEvents.destroy(); } getUiEvents() : Registry { return this.uiEvents; } protected setConversationManager(manager: Manager | undefined) { if(this.conversationManager === manager) { return; } this.listenerManager.forEach(callback => callback()); this.listenerManager = []; this.conversationManager = manager; if(manager) { this.registerConversationManagerEvents(manager); this.setCurrentlySelected(manager.getSelectedConversation()); } else { this.setCurrentlySelected(undefined); } } protected registerConversationManagerEvents(manager: Manager) { this.listenerManager.push(manager.events.on("notify_selected_changed", event => this.setCurrentlySelected(event.newConversation))); this.listenerManager.push(manager.events.on("notify_cross_conversation_support_changed", () => { const currentConversation = this.getCurrentConversation(); if(currentConversation) { this.reportStateToUI(currentConversation); } })); } protected registerConversationEvents(conversation: ConversationType) { this.currentSelectedListener.push(conversation.events.on("notify_unread_timestamp_changed", event => this.uiEvents.fire_react("notify_unread_timestamp_changed", { chatId: conversation.getChatId(), timestamp: event.timestamp }))); this.currentSelectedListener.push(conversation.events.on("notify_send_toggle", event => this.uiEvents.fire_react("notify_send_enabled", { chatId: conversation.getChatId(), enabled: event.enabled }))); this.currentSelectedListener.push(conversation.events.on("notify_chat_event", event => { this.uiEvents.fire_react("notify_chat_event", { chatId: conversation.getChatId(), event: event.event, triggerUnread: event.triggerUnread }); })); this.currentSelectedListener.push(conversation.events.on("notify_state_changed", () => { this.reportStateToUI(conversation); })); this.currentSelectedListener.push(conversation.events.on("notify_history_state_changed", () => { this.reportStateToUI(conversation); })); this.currentSelectedListener.push(conversation.events.on("notify_read_state_changed", () => { this.reportStateToUI(conversation); })); } protected setCurrentlySelected(conversation: ConversationType | undefined) { if(this.currentSelectedConversation === conversation) { return; } this.currentSelectedListener.forEach(callback => callback()); this.currentSelectedListener = []; this.currentSelectedConversation = conversation; this.uiEvents.fire_react("notify_selected_chat", { chatId: conversation ? conversation.getChatId() : "unselected" }); if(conversation) { this.registerConversationEvents(conversation); } } /* TODO: Is this even a thing? */ handlePanelShow() { this.uiEvents.fire_react("notify_panel_show"); } protected reportStateToUI(conversation: AbstractChat) { let historyState: ChatHistoryState; const localHistoryState = this.conversationManager.historyUiStates[conversation.getChatId()]; if(!localHistoryState) { historyState = conversation.hasHistory() ? "available" : "none"; } else { if(Date.now() < localHistoryState.historyRetryTimestamp && localHistoryState.historyErrorMessage) { historyState = "error"; } else if(localHistoryState.executingUIHistoryQuery) { historyState = "loading"; } else if(conversation.hasHistory()) { historyState = "available"; } else { historyState = "none"; } } switch (conversation.getCurrentMode()) { case "normal": if(conversation.isPrivate() && !conversation.isReadable()) { this.uiEvents.fire_react("notify_conversation_state", { chatId: conversation.getChatId(), state: "private", crossChannelChatSupported: this.conversationManager.hasCrossConversationSupport() }); return; } this.uiEvents.fire_react("notify_conversation_state", { chatId: conversation.getChatId(), state: "normal", historyState: historyState, historyErrorMessage: localHistoryState?.historyErrorMessage, historyRetryTimestamp: localHistoryState ? localHistoryState.historyRetryTimestamp : 0, chatFrameMaxMessageCount: kMaxChatFrameMessageSize, unreadTimestamp: conversation.getUnreadTimestamp(), showUserSwitchEvents: conversation.isPrivate() || !this.conversationManager.hasCrossConversationSupport(), sendEnabled: conversation.isSendEnabled(), events: [...conversation.getPresentEvents(), ...conversation.getPresentMessages()] }); break; case "loading": case "unloaded": this.uiEvents.fire_react("notify_conversation_state", { chatId: conversation.getChatId(), state: "loading" }); break; case "error": this.uiEvents.fire_react("notify_conversation_state", { chatId: conversation.getChatId(), state: "error", errorMessage: conversation.getErrorMessage() }); break; case "no-permissions": this.uiEvents.fire_react("notify_conversation_state", { chatId: conversation.getChatId(), state: "no-permissions", failedPermission: conversation.getFailedPermission() }); break; } } public uiQueryHistory(conversation: AbstractChat, timestamp: number, enforce?: boolean) { const localHistoryState = this.conversationManager.historyUiStates[conversation.getChatId()] || (this.conversationManager.historyUiStates[conversation.getChatId()] = { executingUIHistoryQuery: false, historyErrorMessage: undefined, historyRetryTimestamp: 0 }); if(localHistoryState.executingUIHistoryQuery && !enforce) { return; } localHistoryState.executingUIHistoryQuery = true; conversation.queryHistory({ end: 1, begin: timestamp, limit: kMaxChatFrameMessageSize }).then(result => { localHistoryState.executingUIHistoryQuery = false; localHistoryState.historyErrorMessage = undefined; localHistoryState.historyRetryTimestamp = result.nextAllowedQuery; switch (result.status) { case "success": this.uiEvents.fire_react("notify_conversation_history", { chatId: conversation.getChatId(), state: "success", hasMoreMessages: result.moreEvents, retryTimestamp: localHistoryState.historyRetryTimestamp, events: result.events }); break; case "private": this.uiEvents.fire_react("notify_conversation_history", { chatId: conversation.getChatId(), state: "error", errorMessage: localHistoryState.historyErrorMessage = tr("chat is private"), retryTimestamp: localHistoryState.historyRetryTimestamp }); break; case "no-permission": this.uiEvents.fire_react("notify_conversation_history", { chatId: conversation.getChatId(), state: "error", errorMessage: localHistoryState.historyErrorMessage = tra("failed on {}", result.failedPermission || tr("unknown permission")), retryTimestamp: localHistoryState.historyRetryTimestamp }); break; case "error": this.uiEvents.fire_react("notify_conversation_history", { chatId: conversation.getChatId(), state: "error", errorMessage: localHistoryState.historyErrorMessage = result.errorMessage, retryTimestamp: localHistoryState.historyRetryTimestamp }); break; } }); } protected getCurrentConversation() : ConversationType | undefined { return this.currentSelectedConversation; } @EventHandler("query_conversation_state") protected handleQueryConversationState(event: AbstractConversationUiEvents["query_conversation_state"]) { const conversation = this.conversationManager?.findConversationById(event.chatId); if(!conversation) { this.uiEvents.fire_react("notify_conversation_state", { state: "error", errorMessage: tr("Unknown conversation"), chatId: event.chatId }); return; } if(conversation.getCurrentMode() === "unloaded") { /* will switch the state to "loading" and already reports the state to the ui */ conversation.queryCurrentMessages(); } else { this.reportStateToUI(conversation); } } @EventHandler("query_conversation_history") protected handleQueryHistory(event: AbstractConversationUiEvents["query_conversation_history"]) { const conversation = this.conversationManager?.findConversationById(event.chatId); if(!conversation) { this.uiEvents.fire_react("notify_conversation_history", { state: "error", errorMessage: tr("Unknown conversation"), retryTimestamp: Date.now() + 10 * 1000, chatId: event.chatId }); logError(LogCategory.CLIENT, tr("Tried to query history for an unknown conversation with id %s"), event.chatId); return; } this.uiQueryHistory(conversation, event.timestamp); } @EventHandler(["action_clear_unread_flag", "action_self_typing"]) protected handleClearUnreadFlag(event: AbstractConversationUiEvents["action_clear_unread_flag" | "action_self_typing"]) { const conversation = this.conversationManager?.findConversationById(event.chatId); conversation?.setUnreadTimestamp(Date.now()); } @EventHandler("action_send_message") protected handleSendMessage(event: AbstractConversationUiEvents["action_send_message"]) { const conversation = this.conversationManager?.findConversationById(event.chatId); if(!conversation) { logError(LogCategory.CLIENT, tr("Tried to send a chat message to an unknown conversation with id %s"), event.chatId); return; } conversation.sendMessage(event.text); } @EventHandler("action_jump_to_present") protected handleJumpToPresent(event: AbstractConversationUiEvents["action_jump_to_present"]) { const conversation = this.conversationManager?.findConversationById(event.chatId); if(!conversation) { logError(LogCategory.CLIENT, tr("Tried to jump to present for an unknown conversation with id %s"), event.chatId); return; } this.reportStateToUI(conversation); } @EventHandler("query_selected_chat") private handleQuerySelectedChat() { this.uiEvents.fire_react("notify_selected_chat", { chatId: this.currentSelectedConversation ? this.currentSelectedConversation.getChatId() : "unselected"}); } @EventHandler("action_select_chat") private handleActionSelectChat(event: AbstractConversationUiEvents["action_select_chat"]) { const conversation = this.conversationManager?.findConversationById(event.chatId); this.conversationManager.setSelectedConversation(conversation); } }