cordis/client/webapp/preload.ts
Michael Peters f00127f486 fixes
2021-12-03 18:47:33 -06:00

263 lines
10 KiB
TypeScript

import * as electronRemote from '@electron/remote';
const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
LOG.silly('preload begins');
import * as path from 'path';
import * as fs from 'fs/promises';
import GuildsManager from './guilds-manager';
import Globals from './globals';
import UI from './ui';
import Actions from './actions';
import { Changes, Channel, ConnectionInfo, GuildMetadata, Member, Message, Resource, Token } from './data-types';
import Q from './q-module';
import bindWindowButtonEvents from './elements/events-window-buttons';
import bindTextInputEvents from './elements/events-text-input';
import bindInfiniteScrollEvents from './elements/events-infinite-scroll';
import bindConnectionEvents from './elements/events-connection';
import bindAddGuildTitleEvents from './elements/events-guild-title';
import bindAddGuildEvents from './elements/events-add-guild';
import PersonalDB from './personal-db';
import MessageRAMCache from './message-ram-cache';
import ResourceRAMCache from './resource-ram-cache';
import CombinedGuild from './guild-combined';
import { AutoVerifierChangesType } from './auto-verifier';
LOG.silly('modules loaded');
if (Globals.MESSAGES_PER_REQUEST >= Globals.MAX_CURRENT_MESSAGES) throw new Error('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', () => {
document.body.classList.remove('preload');
(async () => {
// Wait for the log to load the typescript source maps so that
// logs will include typescript files+line numbers instead of
// compiled javascript ones.
await LOG.ensureSourceMaps();
LOG.silly('web client log source maps loaded');
// make sure the personaldb directory exists
await fs.mkdir(path.dirname(Globals.PERSONALDB_FILE), { recursive: true });
const personalDB = await PersonalDB.create(Globals.PERSONALDB_FILE);
await personalDB.init();
LOG.silly('personal db initialized');
let messageRAMCache = new MessageRAMCache();
let resourceRAMCache = new ResourceRAMCache();
LOG.silly('ram caches initialized');
const guildsManager = new GuildsManager(messageRAMCache, resourceRAMCache, personalDB);
await guildsManager.init();
LOG.silly('controller initialized');
const q = new Q(document);
const ui = new UI(document, q);
LOG.silly('action classes initialized');
bindWindowButtonEvents(q);
bindTextInputEvents(document, q, ui);
bindInfiniteScrollEvents(q, ui);
bindConnectionEvents(document, q, ui);
bindAddGuildTitleEvents(document, q, ui);
bindAddGuildEvents(document, q, ui, guildsManager);
LOG.silly('events bound');
// Add guild icons
await ui.setGuilds(guildsManager, guildsManager.guilds);
if (guildsManager.guilds.length > 0) {
// Click on the first guild in the list
q.$('#guild-list .guild').click();
}
// Connection Events
guildsManager.on('verified', async (guild: CombinedGuild) => {
(async () => { // update connection info
await Actions.fetchAndUpdateConnection(ui, guild);
})();
(async () => { // refresh members list
await Actions.fetchAndUpdateMembers(q, ui, guild);
})();
(async () => { // refresh channels list
await Actions.fetchAndUpdateChannels(q, ui, guild);
})();
(async () => { // refresh current channel messages
if (ui.activeChannel === null) return;
if (ui.messagePairs.size == 0) {
// fetch messages again since there are no messages yet
await Actions.fetchAndUpdateMessagesRecent(q, ui, guild, ui.activeChannel);
} else {
// If we already have messages, just update the infinite scroll.
// NOTE: this will not add/remove new/deleted messages
ui.messagesAtTop = false;
ui.messagesAtBottom = false;
(q.$('#channel-feed-content-wrapper') as any).updateInfiniteScroll();
}
})();
});
guildsManager.on('disconnect', (guild: CombinedGuild) => {
// Update everyone with the 'unknown' status
(async () => {
await Actions.fetchAndUpdateConnection(ui, guild);
})();
(async () => {
await Actions.fetchAndUpdateMembers(q, ui, guild);
})();
});
// Change Events
guildsManager.on('update-metadata', async (guild: CombinedGuild, guildMeta: GuildMetadata) => {
LOG.debug(`g#${guild.id} metadata updated`)
await ui.updateGuildName(guild, guildMeta.name);
// Not using withPotentialError since keeping the old icon is a fine fallback
if (guildMeta.iconResourceId) {
try {
let icon = await guild.fetchResource(guildMeta.iconResourceId);
await ui.updateGuildIcon(guild, icon.data);
} catch (e) {
LOG.error('Error fetching new guild icon', e);
// Keep the old guild icon, just log an error.
// Should go through another try after a restart
}
}
});
guildsManager.on('remove-members', async (guild: CombinedGuild, members: Member[]) => {
LOG.debug(members.length + ' removed members');
await ui.deleteMembers(guild, members);
});
guildsManager.on('update-members', async (guild: CombinedGuild, updatedMembers: Member[]) => {
LOG.debug(updatedMembers.length + ' updated members g#' + guild.id);
await ui.updateMembers(guild, updatedMembers);
if (
ui.activeConnection !== null &&
updatedMembers.find(member => member.id === (ui.activeConnection as ConnectionInfo).id)
) {
await Actions.fetchAndUpdateConnection(ui, guild);
}
});
guildsManager.on('new-members', async (guild: CombinedGuild, members: Member[]) => {
LOG.debug(members.length + ' new members');
await ui.addMembers(guild, members);
});
guildsManager.on('remove-channels', async (guild: CombinedGuild, channels: Channel[]) => {
LOG.debug(channels.length + ' removed channels');
await ui.deleteChannels(guild, channels);
});
guildsManager.on('update-channels', async (guild: CombinedGuild, updatedChannels: Channel[]) => {
LOG.debug(updatedChannels.length + ' updated channels');
await ui.updateChannels(guild, updatedChannels);
});
guildsManager.on('new-channels', async (guild: CombinedGuild, channels: Channel[]) => {
LOG.debug(channels.length + ' added channels');
await ui.addChannels(guild, channels);
});
guildsManager.on('remove-messages', async (guild: CombinedGuild, messages: Message[]) => {
LOG.debug(messages.length + ' deleted messages');
await ui.deleteMessages(guild, messages);
});
guildsManager.on('update-messages', async (guild: CombinedGuild, updatedMessages: Message[]) => {
LOG.debug(updatedMessages.length + ' updated messages');
await ui.updateMessages(guild, updatedMessages);
});
guildsManager.on('new-messages', async (guild: CombinedGuild, messages: Message[]) => {
if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
for (let message of messages) {
if (ui.activeChannel === null || ui.activeChannel.id !== message.channel.id) return;
if (ui.messagesAtBottom) {
// add the message to the bottom of the message feed
await ui.addMessages(guild, [ message ]);
ui.jumpMessagesToBottom();
} else if (message.member.id == guild.memberId) {
// this set of messages will include the new messageguildId
LOG.debug('not at bottom, jumping down since message was sent by the current user');
await Actions.fetchAndUpdateMessagesRecent(q, ui, guild, message.channel);
}
}
});
// Conflict Events
guildsManager.on('conflict-metadata', async (guild: CombinedGuild, changesType: AutoVerifierChangesType, oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => {
LOG.debug('metadata conflict', { newGuildMeta: newGuildMeta });
(async () => { await ui.updateGuildName(guild, newGuildMeta.name); })();
(async () => {
let icon = await guild.fetchResource(newGuildMeta.iconResourceId);
await ui.updateGuildIcon(guild, icon.data);
})();
});
guildsManager.on('conflict-channels', async (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Channel>) => {
LOG.debug('channels conflict', { changes });
if (changes.deleted.length > 0) await ui.deleteChannels(guild, changes.deleted);
if (changes.added.length > 0) await ui.addChannels(guild, changes.added);
if (changes.updated.length > 0) await ui.updateChannels(guild, changes.updated.map(pair => pair.newDataPoint));
});
guildsManager.on('conflict-members', async (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Member>) => {
//LOG.debug('members conflict', { changes });
if (changes.deleted.length > 0) await ui.deleteMembers(guild, changes.deleted);
if (changes.added.length > 0) await ui.addMembers(guild, changes.added);
if (changes.updated.length > 0) {
(async () => {
await ui.updateMembers(guild, changes.updated.map(pair => pair.newDataPoint));
})();
(async () => {
LOG.debug('updating conflict members connection...');
// Likely getting called before the ram is updated
await Actions.fetchAndUpdateConnection(ui, guild);
})();
}
});
guildsManager.on('conflict-messages', async (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Message>) => {
//LOG.debug('messages conflict', { changes });
if (changes.deleted.length > 0) await ui.deleteMessages(guild, changes.deleted);
if (changes.added.length > 0) await ui.addMessages(guild, changes.added);
if (changes.updated.length > 0) await ui.updateMessages(guild, changes.updated.map(pair => pair.newDataPoint));
});
guildsManager.on('conflict-tokens', async (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Token>) => {
LOG.debug('tokens conflict', { changes });
// TODO
});
guildsManager.on('conflict-resource', async (guild: CombinedGuild, changesType: AutoVerifierChangesType, oldResource: Resource, newResource: Resource) => {
LOG.debug('resource conflict', { oldResource, newResource });
// TODO (these changes should not happen often if at all)
});
})();
});