import * as electronRemote from '@electron/remote'; const electronConsole = electronRemote.getGlobal('console') as Console; import Logger from '../../logger/logger'; const LOG = new Logger(__filename, electronConsole); import { $, $$, $$$, $$$$ } from './elements/require/q-module'; $.setDocument(document); LOG.silly('script.js begins'); import Controller from './controller'; import DBCache from './db-cache'; import Globals from './globals'; import Elements from './elements'; import UI from './ui'; import Actions from './actions'; import { Channel, ConnectionInfo, Message } from './data-types'; LOG.silly('modules loaded'); $.assert(Globals.MESSAGES_PER_REQUEST < Globals.MAX_CURRENT_MESSAGES, 'messages per request must be less than max current messages'); window.addEventListener('unhandledrejection', (e) => { LOG.error('Unhandled Promise Rejection', e.reason); }); window.addEventListener('error', (e) => { LOG.error('Uncaught Error', e.error); }); window.addEventListener('DOMContentLoaded', () => { $('body').classList.remove('preload'); (async () => { await DBCache.connect(); await DBCache.init(); LOG.silly('cache initialized'); const controller = new Controller(); await controller.init(); LOG.silly('controller initialized'); const ui = new UI(document); const actions = new Actions(document, ui); LOG.silly('actions initialized'); Elements.init({ window, document, controller, ui, actions }); Elements.bindBaseEvents(); LOG.silly('elements initialized'); // Add server icons await ui.setServers(controller.servers); if (controller.servers.length > 0) { // Click on the first server in the list $('#server-list .server').click(); } // Receive Current Channel Messages controller.on('new-message', async (server, message) => { if (ui.isActiveServer(server) && ui.isActiveChannel(message.channel)) { if (ui.messagesAtBottom) { // add the message to the bottom of the message feed await ui.addMessagesAfter(server, message.channel, [ message ], null); ui.jumpMessagesToBottom(); } else if (message.member.id == server.memberId) { // this set of messages will include the new messageserverId LOG.debug('not at bottom, jumping down since message was sent by the current user'); await actions.fetchAndUpdateMessagesRecent(server, message.channel); } } }); controller.on('verified', async (server) => { (async () => { // update connection info await actions.fetchAndUpdateConnection(server); })(); (async () => { // refresh members cache if (server.members) { await server.fetchMembers(); } else { await actions.fetchAndUpdateMembers(server); } })(); (async () => { // refresh channels cache if (server.channels) { await server.fetchChannels(); } else { await actions.fetchAndUpdateChannels(server); } })(); (async () => { // refresh current channel messages if (ui.hasActiveChannel()) { if (ui.messagePairs.size == 0) { // fetch messages again since there are no messages yet await actions.fetchAndUpdateMessagesRecent(server, ui.activeChannel as Channel); } else { // Just update the infinite scroll. NOTE: this will not remove deleted messages ui.messagesAtTop = false; ui.messagesAtBottom = false; (await $('#channel-feed-content-wrapper') as any).updateInfiniteScroll(); } } })(); }); controller.on('disconnected', (server) => { (async () => { await actions.fetchAndUpdateConnection(server); })(); (async () => { await actions.fetchAndUpdateMembers(server); })(); }); controller.on('update-server', async (server, serverData) => { LOG.debug(`s#${server.id} metadata updated`) await ui.updateServerName(server, serverData.name); // Not using withPotentialError since keeping the old icon is a fine fallback try { let iconBuff = await server.fetchResource(serverData.icon_resource_id); await ui.updateServerIcon(server, iconBuff); } catch (e) { LOG.error('Error fetching new server icon', e); // Keep the old server icon, just log an error. // Should go through another try after a restart } }); controller.on('deleted-members', async (server, members) => { LOG.debug(members.length + ' deleted members'); await ui.deleteMembers(server, members); }); controller.on('updated-members', async (server, data) => { LOG.debug(data.length + ' updated members s#' + server.id); await ui.updateMembers(server, data); if (ui.activeConnection && data.find((c: any) => c.newDataPoint.id === (ui.activeConnection as ConnectionInfo).id)) { await actions.fetchAndUpdateConnection(server); } }); controller.on('added-members', async (server, members) => { LOG.debug(members.length + ' added members'); await ui.addMembers(server, members); }); controller.on('deleted-channels', async (server, channels) => { LOG.debug(channels.length + ' deleted channels'); await ui.deleteChannels(server, channels); }); controller.on('updated-channels', async (server, data) => { LOG.debug(data.length + ' updated channels'); await ui.updateChannels(server, data); }); controller.on('added-channels', async (server, channels) => { LOG.debug(channels.length + ' added channels'); await ui.addChannels(server, channels); }); controller.on('deleted-messages', async (server, channel, messages) => { LOG.debug(messages.length + ' deleted messages'); //LOG.debug('deleted messages:', { messages: deletedMessages.map(message => message.text) }); // messages were deleted but the cache still had them await ui.deleteMessages(server, channel, messages); }); controller.on('updated-messages', async (server, channel, data) => { LOG.debug(data.length + ' updated messages'); // messages were updated on the server-side await ui.updateMessages(server, channel, data); }); controller.on('added-messages', async (server, channel, addedAfter, addedBefore) => { LOG.debug(addedAfter.size + ' added messages'); // addedBefore.size should equal addedAfter.size //LOG.debug('added messages', { messages: Array.from(addedAfter.values()).map(message => message.text) }); // messages were added in a place that the cache did not have them if (!ui.isMessagePairsServer(server)) return; // these messages are not from the ones in the feed if (!ui.isMessagePairsChannel(channel)) return; // these messages are not from the ones in the feed let currentMessagesSorted = Array.from(ui.messagePairs.values()).sort((a, b) => { return a.message.sent.getTime() - b.message.sent.getTime(); }); // length could be 0 if all previous messages were 'deleted' if (currentMessagesSorted.length == 0) { // Simply set the text channel messages rather than calculating where to add them ui.setMessages(server, channel, Array.from(addedAfter.values()), { atTop: false, atBottom: true }); return; } let firstMessage = currentMessagesSorted[0].message; let lastMessage = currentMessagesSorted[currentMessagesSorted.length - 1].message; // messages that were updated (currently extraneous) // Note: this should never happen since these should be handled by updated-messages // Note: this may be used to replace dummy pre-sent messages let toUpdate: { newDataPoint: Message, oldDataPoint: Message }[] = []; for (let addedMessage of addedBefore.values()) { if (ui.messagePairs.has(addedMessage.id)) { toUpdate.push({ newDataPoint: addedMessage, oldDataPoint: (ui.messagePairs.get(addedMessage.id) as { message: Message, element: HTMLElement }).message }); } } if (toUpdate.length > 0) { LOG.warn('updating messages in added-messages... this was intended to be extraneous...', { toUpdate: toUpdate, toUpdateLength: toUpdate.length }); await ui.updateMessages(server, channel, toUpdate); } // messages before the first message let toAddBefore: Message[] = []; let nextFirstMessage = firstMessage; while (addedBefore.has(nextFirstMessage.id)) { nextFirstMessage = addedBefore.get(nextFirstMessage.id); toAddBefore.unshift(nextFirstMessage); } if (toAddBefore.length > 0) { LOG.debug('adding ' + toAddBefore.length + ' before'); await ui.addMessagesBefore(server, channel, toAddBefore, firstMessage); } // messages after the last message let toAddAfter: Message[] = []; let nextLastMessage = lastMessage; while (addedAfter.has(nextLastMessage.id)) { nextLastMessage = addedAfter.get(nextLastMessage.id); toAddAfter.push(nextLastMessage); } if (toAddAfter.length > 0) { LOG.debug('adding ' + toAddAfter.length + ' after'); await ui.addMessagesAfter(server, channel, toAddAfter, lastMessage); } // messages added between messages already in the feed let toAddBetween: { messageTop: HTMLElement, messageBottom: HTMLElement, betweenMessages: Message[] }[] = []; for (let i = 1; i < currentMessagesSorted.length; ++i) { let messageTop = currentMessagesSorted[i - 1].message; let messageBottom = currentMessagesSorted[i].message; let betweenMessages: Message[] = []; let followMessage = messageTop; // this should never be null as long as there is an addedAfter (will throw error if this is not the case) let lastFollowMessage = addedBefore.get(messageBottom.id); while (addedAfter.has(followMessage.id) && followMessage.id != lastFollowMessage.id) { followMessage = addedAfter.get(followMessage.id); betweenMessages.push(followMessage); } if (betweenMessages.length > 0) { toAddBetween.push({ messageTop: (ui.messagePairs.get(messageTop.id) as { message: Message, element: HTMLElement }).element, messageBottom: (ui.messagePairs.get(messageBottom.id) as { message: Message, element: HTMLElement }).element, betweenMessages: betweenMessages }); } } // add messages in between if (toAddBetween.length > 0) { LOG.debug('adding ' + toAddBetween.length + ' between sets'); } for (let messageSet of toAddBetween) { await ui.addMessagesBetween(server, channel, messageSet.betweenMessages, messageSet.messageTop, messageSet.messageBottom); } }); })(); })