From ad346fc37d80450265c7751a062405254615052c Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Sun, 21 Nov 2021 21:01:40 -0600 Subject: [PATCH] replaced improper references to 'server' with 'guild' in client --- client/webapp/_client-controller.ts | 944 ------------------ client/webapp/_db-cache.ts | 522 ---------- client/webapp/_recent-message-ram-cache.ts | 136 --- client/webapp/actions.ts | 2 +- client/webapp/data-types.ts | 66 -- client/webapp/elements/channel.ts | 2 +- client/webapp/elements/context-menu-guild.ts | 18 +- client/webapp/elements/context-menu-img.ts | 2 +- client/webapp/elements/events-guild-title.ts | 10 +- client/webapp/elements/events-text-input.ts | 8 +- .../webapp/elements/overlay-guild-settings.ts | 4 +- client/webapp/globals.ts | 2 +- client/webapp/guild-combined.ts | 10 +- client/webapp/guild-socket.ts | 10 +- client/webapp/guild-types.ts | 4 +- client/webapp/guilds-manager.ts | 14 +- client/webapp/index.html | 16 +- client/webapp/resource-ram-cache.ts | 4 +- client/webapp/styles/channel-list.scss | 4 +- client/webapp/styles/contexts.scss | 6 +- client/webapp/styles/overlays.scss | 8 +- client/webapp/styles/server-list.scss | 32 +- client/webapp/styles/server-members.scss | 10 +- client/webapp/styles/server.scss | 12 +- client/webapp/styles/styles.scss | 6 +- 25 files changed, 92 insertions(+), 1760 deletions(-) delete mode 100644 client/webapp/_client-controller.ts delete mode 100644 client/webapp/_db-cache.ts delete mode 100644 client/webapp/_recent-message-ram-cache.ts diff --git a/client/webapp/_client-controller.ts b/client/webapp/_client-controller.ts deleted file mode 100644 index e44e03a..0000000 --- a/client/webapp/_client-controller.ts +++ /dev/null @@ -1,944 +0,0 @@ -import * as electronRemote from '@electron/remote'; -const electronConsole = electronRemote.getGlobal('console') as Console; -import Logger from '../../logger/logger'; -const LOG = Logger.create(__filename, electronConsole); - -import * as StackTrace from '../../stack-trace/stack-trace'; - -import * as crypto from 'crypto'; -import { EventEmitter } from 'events'; - -import * as socketio from 'socket.io-client'; - -import ConcurrentQueue from '../../concurrent-queue/concurrent-queue'; - -import { Message, Member, Channel, Changes, ConnectionInfo, CacheServerData, ServerMetaData, ServerConfig, ShouldNeverHappenError, Token } from './data-types'; -import DBCache from './db-cache'; -import ResourceRAMCache from './resource-ram-cache'; -import RecentMessageRAMCache from './message-ram-cache'; - -// Events: -// 'connected' function() called when connected to the guild -// 'disconnected' function() called when connection to the guild is lost -// 'verified' function() called when verification handshake is completed - -// 'update-guild' function(guildMetadata) called when the guild metadata updates - -// 'deleted-members' function(members) called when members were deleted on the server-side -// 'updated-members' function(data: Array [ { oldMember, newMember } ]) called when a member was updated on the server-side -// 'added-members' function(members) called when members were added on the server-side - -// 'deleted-channels' function(channels) called when channels were deleted on the server-side -// 'updated-channels' function(data: Array [ { oldChannel, newChannel } ]) called when a channel was updated on the server-side -// 'added-channels' function(channels) called when channels are added on the server-side - -// 'new-message' function(message) called when a message is received in a channel - -// 'deleted-messages' function(messages) called when messages were deleted on the server-side -// 'updated-messages' function(data: Array [ { oldMessage, newMessage } ]) called when messages were updated on the server-side -// 'added-messages' function(addedAfter : Map addedMessage>, addedBefore : addedMessage>) called when messages were added on the server-side (that were not in our client-side db). - -// Functions: -// these three have grab counterparts -// async fetchMetadata() Fetches the guild information as { name, icon_resource_id } -// async fetchMembers() Fetches the guild members as [ { id, display_name, avatar_resource_id }, ... ] -// async fetchChannels() Fetches the available channels as [ { id, name }, ... ] - -// async fetchMessagesRecent(channelId, number) Fetches the most recent number messages in a channel [ Message, ... ] -// async fetchMessagesBefore(channelId, messageId, number) Fetches number messages before a specified message [ Message, ... ] -// async fetchMessagesAfter(channelId, messageId, number) Fetches number messages after a specified message [ Message, ... ] - -// TODO: change this to grab -// async fetchResource(resourceId) Fetches the resource associated with a specified resrouceId - -// async sendMessage(channelId, text) Sends a text message to a channel, returning the sent Message -// async sendMessageWithResource(channelId, text, resource, resourceName) Sends a text message with a resource to a channel, returning the sent Message. text is optional - -// async setStatus(status) Set the current logged in user's status -// async setDisplayName(displayName) Sets the current logged in user's display name -// async setAvatar(avatarBuff) -// async updateChannel(channelId, name, flavorText) Updates a channel's name and flavor text - -// async queryTokens() Queries for the login tokens as [ { token, member_id, created, expires }, ... ] -// async revokeToken(token) Revokes an outstanding token - -interface FetchCachedAndVerifyProps { - lock?: ConcurrentQueue, - serverFunc: (() => Promise) - cacheFunc: (() => Promise), - cacheUpdateFunc: ((cacheData: ClientType | null, serverData: ServerType) => Promise), // returns true if changes were made to the cache - updateEventName?: string | null, -} - -export default class ClientController extends EventEmitter { - private dbCache: DBCache; - - public id: string; - public memberId: string; - public url: string; - public publicKey: crypto.KeyObject; - public privateKey: crypto.KeyObject; - public serverCert: string - - public socket: socketio.Socket; - - private _metadata: ServerMetaData | CacheServerData | null; - private _recentMessages: RecentMessageRAMCache; - - public channels: Map; - public members: Map; - - public channelsLock: ConcurrentQueue; - public membersLock: ConcurrentQueue; - - public isVerified: boolean; - - public resourceCallbacks: Map Promise | void)[]>; - public dedupedCallbacks: Map Promise | void)[]>; - - constructor(dbCache: DBCache, config: ServerConfig) { - super(); - - this.dbCache = dbCache; - - // TODO: fetch these from the cache when they are needed rather than storing them in memory (especially private key) - let publicKey = typeof config.publicKey === 'string' ? crypto.createPublicKey(config.publicKey) : config.publicKey; - let privateKey = typeof config.privateKey === 'string' ? crypto.createPrivateKey(config.privateKey) : config.privateKey; - - this.id = config.guildId + ''; // this is the client-side server id (from Cache) - this.memberId = config.memberId; - this.url = config.url; - this.publicKey = publicKey; - this.privateKey = privateKey; - this.serverCert = config.serverCert; - - this.socket = socketio.connect(this.url, { - forceNew: true, - ca: this.serverCert, // this provides identity verification for the server - }); - - this._metadata = null; // use grabMetadata(); - this._recentMessages = new RecentMessageRAMCache(); // use grabRecentMessages(); - - // These are used to make message objects more useful - // TODO: make these private - this.channels = new Map(); // use grabChannels(); - this.members = new Map(); // use grabMembers(); - - this.channelsLock = new ConcurrentQueue(1); - this.membersLock = new ConcurrentQueue(1); - - this.isVerified = false; - - this.resourceCallbacks = new Map Promise | void)[]>(); // resourceId -> [ callbackFunc, ... ]; - this.dedupedCallbacks = new Map Promise | void)[]>(); // dedupeId -> [ callbackFunc, ... ]; - - this._bindSocket(); - this._bindInternalEvents(); - } - - _bindSocket(): void { - this.socket.on('connect', async () => { - LOG.info('connected to server#' + this.id); - this.emit('connected'); - // TODO: re-verify on verification failure? - await this.verify(); - }); - this.socket.on('disconnect', () => { - LOG.info('disconnected from server#' + this.id); - this.isVerified = false; - - this._metadata = null; - this._recentMessages.clear(); - this.channels.clear(); - this.members.clear(); - - this.emit('disconnected'); - }); - this.socket.on('new-message', async (dataMessage) => { - await this.ensureMembers(); - await this.ensureChannels(); - let message = Message.fromDBData(dataMessage, this.members, this.channels); - await this.dbCache.upsertServerMessages(this.id, message.channel.id, [ message ]); - LOG.info(message.toString()); - this._recentMessages.addNewMessage(this.id, message); - this.emit('new-message', message); - }); - this.socket.on('update-member', async (member) => { - await this.ensureMembers(); - let oldMember = this.members.get(member.id); - if (oldMember) { - this.emit('updated-members', [ { oldMember: oldMember, newMember: member } ]); - } else { - this.emit('added-members', [ member ]); - } - }); - this.socket.on('update-channel', async (channel) => { - await this.ensureChannels(); - let oldChannel = this.channels.get(channel.id); - if (oldChannel) { - this.emit('updated-channels', [ { oldChannel: oldChannel, newChannel: channel } ]); - } else { - this.emit('added-channels', [ channel ]); - } - }); - this.socket.on('new-channel', async (channel) => { - await this.ensureChannels(); - this.emit('added-channels', [ channel ]); - }); - this.socket.on('update-server', async (serverMeta) => { - await this.dbCache.updateServer(this.id, serverMeta); - this.emit('update-server', serverMeta); - }); - } - - _bindInternalEvents(): void { - this.on('added-members', async (members: Member[]) => { - for (let member of members) { - this.members.set(member.id, member); - } - await this.dbCache.updateServerMembers(this.id, Array.from(this.members.values())); - }); - this.on('updated-members', async (data: { oldMember: Member, newMember: Member }[]) => { - for (const { oldMember, newMember } of data) { - this.members.set(newMember.id, newMember); - } - await this.dbCache.updateServerMembers(this.id, Array.from(this.members.values())); - }); - this.on('deleted-members', async (members: Member[]) => { - for (let member of members) { - this.members.delete(member.id); - } - await this.dbCache.updateServerMembers(this.id, Array.from(this.members.values())); - }); - - this.on('added-channels', async (channels: Channel[]) => { - for (let channel of channels) { - this.channels.set(channel.id, channel); - } - await this.dbCache.updateServerChannels(this.id, Array.from(this.channels.values())); - }); - this.on('updated-channels', async (data: { oldChannel: Channel, newChannel: Channel }[]) => { - for (const { oldChannel, newChannel } of data) { - this.channels.set(newChannel.id, newChannel); - } - await this.dbCache.updateServerChannels(this.id, Array.from(this.channels.values())); - }); - this.on('deleted-channels', async (channels: Channel[]) => { - for (let channel of channels) { - this.channels.delete(channel.id); - } - await this.dbCache.updateServerChannels(this.id, Array.from(this.channels.values())); - }); - - this.on('added-messages', async (channel: Channel, addedAfter: Map, addedBefore: Map) => { - // Adding messages is surprisingly complicated (see script.js's added-messages function) - // so we can just drop the channel and refresh it once if any messages got added while - // we were gone. Further, it is probably best to make 100% sure that the script.js - // implementation is correct before copying it elsewhere. (I'm 90% sure it is correct) - // Getting this correct and actually implementing the added-messages feature in the - // RAM cache would be useful for first-time joiners to a server, getting the channel - // messages for the first time - // Alternatively, just store the date in the message and use order-by - this._recentMessages.dropChannel(this.id, channel.id); - await this.dbCache.upsertServerMessages(this.id, channel.id, Array.from(addedAfter.values())); - }); - this.on('updated-messages', async (channel: Channel, data: { oldMessage: Message, newMessage: Message }[]) => { - for (let { oldMessage, newMessage } of data) { - this._recentMessages.updateMessage(this.id, oldMessage, newMessage); - } - await this.dbCache.upsertServerMessages(this.id, channel.id, data.map(change => change.newMessage)); - }); - this.on('deleted-messages', async (_channel: Channel, messages: Message[]) => { - for (let message of messages) { - this._recentMessages.deleteMessage(this.id, message); - } - await this.dbCache.deleteServerMessages(this.id, messages.map(message => message.id)); - }); - } - - _updateCachedMembers(members: Member[]): void { - this.members.clear(); - for (let member of members) { - this.members.set(member.id, member); - } - } - - _updateCachedChannels(channels: Channel[]): void { - this.channels.clear(); - for (let channel of channels) { - this.channels.set(channel.id, channel); - } - } - - /** - * Fetches data from the server but returns cached data if it already exists. If the server data comes back different - * from the cached data, emits an event with the server data - * @param lock A locking enqueue function to make sure that the cache data does not change while waiting for the server data to be requested. - * @param serverFunc A function() that returns the data from the server - * @param cacheFunc A function() that returns the data from the cache (returns null if no data) - * @param cacheUpdateFunc A function(cacheData, serverData) is called after the server data is fetched. It should be used to update the data in the cache - * @param updateEventName The name of the event to emit when the server data is different from the cache data (and the cache data is not null), null for no event - */ - async _fetchCachedAndVerify(props: FetchCachedAndVerifyProps): Promise { - const { lock, serverFunc, cacheFunc, cacheUpdateFunc, updateEventName } = props; - return await new Promise(async (resolve, reject) => { - let func = async () => { - try { - let serverPromise = serverFunc(); - - let cacheData = await cacheFunc(); - if (cacheData !== null) { - resolve(cacheData); - } - - let serverData: U; - try { - serverData = await serverPromise; - } catch (e) { - if (cacheData !== null) { - // Print an error if this was already resolved - LOG.warn('Error fetching server data:', e); - return; - } else { - throw e; - } - } - - // make sure the 'added/deleted/updated' events get run before returning the server data - try { - let changesMade = await cacheUpdateFunc(cacheData, serverData); - - if (updateEventName && cacheData != null && changesMade) { - this.emit(updateEventName, serverData); - } - } catch (e) { - LOG.error('error handling cache update', e); - } - - if (cacheData == null) { - resolve(serverData); - } - } catch (e) { - reject(e); - } - }; - if (lock) { - await lock.push(func); - } else { - await func(); - } - }); - } - - // This function is a promise for error stack tracking purposes. - async _socketEmitTimeout(ms: number, name: string, ...args: any[]): Promise { - return new Promise((resolve) => { - let socketArgs = args.slice(0, args.length - 1); - let respond = args[args.length - 1]; - - let cutoff = false; - let timeout = setTimeout(() => { - cutoff = true; - respond('emit timeout'); - resolve(); - }, ms); - - this.socket.emit(name, ...socketArgs, (...respondArgs: any[]) => { - if (cutoff) { - return; - } - clearTimeout(timeout); - respond(...respondArgs); - resolve(); - }); - }); - } - - /** - * Queries data from the server - * @param endpoint The server-side socket endpoint - * @param args The server socket arguments - */ - async _queryServer(endpoint: string, ...args: any[]): Promise { - // NOTE: socket.io may cause client-side memory leaks if the ack function is never called - await this.ensureVerified(5000); - let message = `querying s#${this.id} @${endpoint}(${args.map(arg => LOG.inspect(arg)).join(', ')})`; - LOG.silly(message); - //if (endpoint === 'fetch-messages-recent') LOG.silly(null, new Error('call stack')); - return await new Promise((resolve, reject) => { - this._socketEmitTimeout(5000, endpoint, ...args, async (errMsg: string, serverData: any) => { - if (errMsg) { - reject(new Error('error fetching server data @' + endpoint + ' / [' + args.map(arg => LOG.inspect(arg)).join(', ') + ']: ' + errMsg)); - } else { - resolve(serverData); - } - }); - }); - } - - // TODO: Make this "T extends something with an id" - static _getChanges(cacheData: T[] | null, serverData: T[], equal: ((a: T, b: T) => boolean)): Changes { - if (cacheData === null) { - return { updated: [], added: serverData, deleted: [] }; - } - let updated: { oldDataPoint: T, newDataPoint: T }[] = []; - let added: T[] = []; - let deleted: T[] = []; - for (let serverDataPoint of serverData) { - let cacheDataPoint = cacheData.find((m: T) => (m as any).id == (serverDataPoint as any).id); - if (cacheDataPoint) { - if (!equal(cacheDataPoint, serverDataPoint)) { - updated.push({ oldDataPoint: cacheDataPoint, newDataPoint: serverDataPoint }); - } - } else { - added.push(serverDataPoint); - } - } - for (let cacheDataPoint of cacheData) { - let serverDataPoint = serverData.find((s: T) => (s as any).id == (cacheDataPoint as any).id); - if (serverDataPoint == null) { - deleted.push(cacheDataPoint); - } - } - - return { updated, added, deleted }; - } - - disconnect(): void { - this.socket.disconnect(); - } - - async verify(): Promise { - await new Promise(async (resolve, reject) => { - // Solve the server's challenge - let publicKeyBuff = this.publicKey.export({ type: 'spki', format: 'der' }); - this._socketEmitTimeout(5000, 'challenge', publicKeyBuff, (errMsg, algo, type, challenge) => { - if (errMsg) { - reject(new Error('challenge request failed: ' + errMsg)); - return; - } - const sign = crypto.createSign(algo); - sign.write(challenge); - sign.end(); - - let signature = sign.sign(this.privateKey, type); - this._socketEmitTimeout(5000, 'verify', signature, (errMsg, memberId) => { - if (errMsg) { - reject(new Error('verification request failed: ' + errMsg)); - return; - } - this.memberId = memberId; - this.dbCache.updateServerMemberId(this.id, this.memberId); - this.isVerified = true; - LOG.info(`verified at server#${this.id} as u#${this.memberId}`); - resolve(); - this.emit('verified'); - }); - }); - }); - } - - // timeout is in ms - async ensureVerified(timeout: number): Promise { - if (this.isVerified) { - return; - } - await new Promise((resolve, reject) => { - let timeoutId: any = null; - let listener = () => { - clearTimeout(timeoutId); - resolve(); - } - this.once('verified', listener); - timeoutId = setTimeout(() => { - this.off('verified', listener); - reject(new Error('verification timeout')); - }, timeout); - }); - } - - async ensureMetadata(): Promise { - if (this._metadata !== null) return; - await this.fetchMetadata(); - } - - async ensureMembers(): Promise { - if (this.members.size > 0) return; - await this.fetchMembers(); - } - - async ensureChannels(): Promise { - if (this.channels.size > 0) return; - await this.fetchChannels(); - } - - async getMyMember(): Promise { - await this.ensureMembers(); - return this.members.get(this.memberId) as Member; - } - - async grabMetadata(): Promise { - await this.ensureMetadata(); - return this._metadata as ServerMetaData | CacheServerData; - } - - async grabMembers(): Promise { - await this.ensureMembers(); - return Array.from(this.members.values()); - } - - async grabChannels(): Promise { - await this.ensureChannels(); - return Array.from(this.channels.values()); - } - - async grabRecentMessages(channelId: string, number: number): Promise { - let cached = this._recentMessages.getRecentMessages(this.id, channelId, number); - if (cached !== null) return cached; - return await this.fetchMessagesRecent(channelId, number); - } - - async fetchMetadata(): Promise { - function isDifferent(cacheData: CacheServerData | null, serverData: ServerMetaData) { - if (cacheData === null) return true; - return !!(cacheData.name != serverData.name || cacheData.iconResourceId != serverData.iconResourceId) - } - let metadata = await this._fetchCachedAndVerify({ - serverFunc: async () => { return ServerMetaData.fromServerDBData(await this._queryServer('fetch-server')); }, - cacheFunc: async () => { return await this.dbCache.getServer(this.id); }, - cacheUpdateFunc: async (cacheData: CacheServerData | null, serverData: ServerMetaData) => { - if (!isDifferent(cacheData, serverData)) return false; - await this.dbCache.updateServer(this.id, serverData); - return true; - }, - updateEventName: 'update-server', - }); - this._metadata = metadata as ServerMetaData | CacheServerData; - return metadata; - } - - // if not verified, will attempt to load from cache rather than waiting for verification - // returns { avatar_resource_id, display_name, status } - async fetchConnectionInfo(): Promise { - let connection: ConnectionInfo = { - id: null, - avatarResourceId: null, - displayName: 'Connecting...', - status: '', - privileges: [], - roleName: null, - roleColor: null, - rolePriority: null - } - if (this.isVerified) { - await this.ensureMembers(); - let member = this.members.get(this.memberId); - if (member) { - connection.id = member.id; - connection.avatarResourceId = member.avatarResourceId; - connection.displayName = member.displayName; - connection.status = member.status; - connection.roleName = member.roleName; - connection.roleColor = member.roleColor; - connection.rolePriority = member.rolePriority; - connection.privileges = member.privileges; - } else { - LOG.warn('Unable to find self in members', this.members); - } - } else { - let cacheMembers = await this.dbCache.getMembers(this.id); - if (cacheMembers) { - let member = cacheMembers.find(m => m.id == this.memberId); - if (member) { - connection.id = member.id; - connection.avatarResourceId = member.avatarResourceId; - connection.displayName = member.displayName; - connection.status = 'connecting'; - connection.privileges = []; - } else { - LOG.warn('Unable to find self in cached members'); - } - } - } - return connection; - } - - async fetchMembers(): Promise { - let members = await this._fetchCachedAndVerify({ - lock: this.membersLock, - serverFunc: async () => { - let dataMembers = (await this._queryServer('fetch-members')) as any[]; - return dataMembers.map((dataMember: any) => Member.fromDBData(dataMember)); - }, - cacheFunc: async () => { return await this.dbCache.getMembers(this.id); }, - cacheUpdateFunc: async (cacheData: Member[] | null, serverData: Member[]) => { - function equal(cacheMember: Member, serverMember: Member) { - return ( - cacheMember.id === serverMember.id && - cacheMember.displayName === serverMember.displayName && - cacheMember.status === serverMember.status && - cacheMember.avatarResourceId === serverMember.avatarResourceId && - cacheMember.roleName === serverMember.roleName && - cacheMember.roleColor === serverMember.roleColor && - cacheMember.rolePriority === serverMember.rolePriority && - cacheMember.privileges.join(',') === serverMember.privileges.join(',') - ); - } - let changes = ClientController._getChanges(cacheData, serverData, equal); - - if (changes.updated.length == 0 && changes.added.length == 0 && changes.deleted.length == 0) { - return false; - } - - await this.dbCache.updateServerMembers(this.id, serverData); - this._updateCachedMembers(serverData); - - if (changes.deleted.length > 0) { - this.emit('deleted-members', changes.deleted); - } - if (changes.added.length > 0) { - this.emit('added-members', changes.added); - } - if (changes.updated.length > 0) { - this.emit('updated-members', changes.updated.map(change => ({ oldMember: change.oldDataPoint, newMember: change.newDataPoint }))); - } - - return true; - }, - updateEventName: null, - }); - this._updateCachedMembers(members); - return members; - } - - async fetchChannels(): Promise { - let changes: Changes | null = null; - let channels = await this._fetchCachedAndVerify({ - lock: this.channelsLock, - serverFunc: async () => { - let dataChannels = (await this._queryServer('fetch-channels')) as any[]; - return dataChannels.map((dataChannel: any) => Channel.fromDBData(dataChannel)); - }, - cacheFunc: async () => { return await this.dbCache.getChannels(this.id); }, - cacheUpdateFunc: async (cacheData: Channel[] | null, serverData: Channel[]) => { - function equal(cacheChannel: Channel, serverChannel: Channel) { - return cacheChannel.id == serverChannel.id && - cacheChannel.index == serverChannel.index && - cacheChannel.name == serverChannel.name && - cacheChannel.flavorText == serverChannel.flavorText; - } - - let changes = ClientController._getChanges(cacheData, serverData, equal); - if (changes.updated.length == 0 && changes.added.length == 0 && changes.deleted.length == 0) { - return false; - } - - // All cache updates are handled by internal event handlers - if (changes.deleted.length > 0) { - this.emit('deleted-channels', changes.deleted); - } - if (changes.added.length > 0) { - this.emit('added-channels', changes.added); - } - if (changes.updated.length > 0) { - this.emit('updated-channels', changes.updated.map(change => ({ oldChannel: change.oldDataPoint, newChannel: change.newDataPoint }))); - } - - return true; - }, - updateEventName: null, - }); - this._updateCachedChannels(channels); - return channels; - } - - // channelId: the id of the channel the messages were fetched from (used in the events) - // firstMessageId: the first message in the current list of messages - // lastMessageId: the last element in the current list of messages - private async updateMessageCache( - channelId: string, - firstMessageId: string | null, - lastMessageId: string | null, - cacheMessages: Message[] | null, - serverMessages: Message[] - ): Promise { - function equal(cacheMessage: Message, serverMessage: Message) { - return cacheMessage.id == serverMessage.id && - cacheMessage.channel.id == serverMessage.channel.id && - cacheMessage.member.id == serverMessage.member.id && - cacheMessage.sent.getTime() == serverMessage.sent.getTime() && - cacheMessage.text == serverMessage.text && - cacheMessage.resourceId == serverMessage.resourceId && - cacheMessage.resourceName == serverMessage.resourceName && - cacheMessage.resourceWidth == serverMessage.resourceWidth && - cacheMessage.resourceHeight == serverMessage.resourceHeight && - cacheMessage.resourcePreviewId == serverMessage.resourcePreviewId - } - - let diffFound = false; - - let updatedMessages: { oldMessage: Message, newMessage: Message }[] = []; - let addedAfter = new Map(); // messageId -> message added after this message - let addedBefore = new Map(); // messageId -> message added before this message - for (let i = 0; i < serverMessages.length; ++i) { - let serverMessage = serverMessages[i]; - let cacheMessage = cacheMessages?.find(m => serverMessage.id == m.id); - if (cacheMessage) { - if (!equal(cacheMessage, serverMessage)) { - diffFound = true; - updatedMessages.push({ - oldMessage: cacheMessage, - newMessage: serverMessage, - }); - } - } else { - // items in server not in cache are added - diffFound = true; - let comesAfter = serverMessages[i - 1] || { id: lastMessageId }; // this message comesAfter comesAfter - let comesBefore = serverMessages[i + 1] || { id: firstMessageId }; // this message comesBefore comesBefore - addedAfter.set(comesAfter.id, serverMessage); - addedBefore.set(comesBefore.id, serverMessage); - } - } - - let deletedMessages: Message[] = []; - if (cacheMessages !== null) { - for (let cacheMessage of cacheMessages) { - let serverMessage = serverMessages.find(m => cacheMessage.id == m.id); - if (serverMessage == null) { - // items in cache not in server are deleted - diffFound = true; - deletedMessages.push(cacheMessage); - } - } - } - - // Send out the events and update the cache - - if (cacheMessages !== null) { - if (cacheMessages.length > 0 && deletedMessages.length === cacheMessages.length) { - // if all of the cache data was invalid, it is likely that it needs to be cleared - // this typically happens when the server got a lot of new messages since the cache - // was last updated - await this.dbCache.clearServerMessages(this.id, deletedMessages[0].channel.id); - } else if (deletedMessages.length > 0) { - // Messages from the cache that come on the far side of the request are marked as deleted - // so they are deleted from the UI. However, they should not be removed from the cache - // yet since they most likely have not been pulled from the server yet. (and will be - // very likely pulled from the server on the next fetch). - let cacheDeletedMessages = deletedMessages.slice(); - let i: number | null = null; - if (firstMessageId || (firstMessageId == null && lastMessageId == null)) { // before & recent - i = 0; - while (cacheDeletedMessages.length > 0 && cacheDeletedMessages[0].id == cacheMessages[i].id) { - cacheDeletedMessages.shift(); - i++; - } - } - if (lastMessageId) { // after - i = 0; - while (cacheDeletedMessages.length > 0 && cacheDeletedMessages[cacheDeletedMessages.length - 1].id == cacheMessages[cacheMessages.length - 1 - i].id) { - cacheDeletedMessages.pop(); - i++; - } - } - //LOG.debug('skipping ' + i + ' deleted messages on the cache side -> deleting ' + cacheDeletedMessages.length + ' cache messages instead of ' + deletedMessages.length); - if (cacheDeletedMessages.length > 0) { - await this.dbCache.deleteServerMessages(this.id, cacheDeletedMessages.map(m => m.id)); - } - } - } - - if (deletedMessages.length > 0) { // make sure to do deleted before added - this.emit('deleted-messages', this.channels.get(channelId), deletedMessages); - } - if (updatedMessages.length > 0) { - this.emit('updated-messages', this.channels.get(channelId), updatedMessages); - } - if (addedAfter.size > 0 || addedBefore.size > 0) { - this.emit('added-messages', this.channels.get(channelId), addedAfter, addedBefore); - } - - return diffFound; - } - - async fetchMessagesRecent(channelId: string, number: number): Promise { - await this.ensureMembers(); - await this.ensureChannels(); - let messages = await this._fetchCachedAndVerify({ - serverFunc: async () => { - let dataMessages = await this._queryServer('fetch-messages-recent', channelId, number) as any[]; - return dataMessages.map((dataMessage: any) => Message.fromDBData(dataMessage, this.members, this.channels)); - }, - cacheFunc: async () => { - return await this.dbCache.getMessagesRecent(this.id, channelId, number, this.members, this.channels); - }, - cacheUpdateFunc: async (cacheData: Message[] | null, serverData: Message[]) => { - return await this.updateMessageCache(channelId, null, null, cacheData, serverData); - }, - updateEventName: null // all events are handled in diffFunc - }); - this._recentMessages.putRecentMessages(this.id, channelId, messages); - return messages; - } - - async fetchMessagesBefore(channelId: string, messageId: string, number: number): Promise { - await this.ensureMembers(); - await this.ensureChannels(); - let messages = await this._fetchCachedAndVerify({ - serverFunc: async () => { - let dataMessages = await this._queryServer('fetch-messages-before', channelId, messageId, number) as any[]; - return dataMessages.map((dataMessage: any) => Message.fromDBData(dataMessage, this.members, this.channels)); - }, - cacheFunc: async () => { - return await this.dbCache.getMessagesBefore(this.id, channelId, messageId, number, this.members, this.channels); - }, - cacheUpdateFunc: async (cacheData: Message[] | null, serverData: Message[]) => { - return await this.updateMessageCache(channelId, messageId, null, cacheData, serverData); - }, - updateEventName: null // all events are handled in diffFunc - }); - return messages; - } - - async fetchMessagesAfter(channelId: string, messageId: string, number: number): Promise { - await this.ensureMembers(); - await this.ensureChannels(); - let messages = await this._fetchCachedAndVerify({ - serverFunc: async () => { - let dataMessages = await this._queryServer('fetch-messages-after', channelId, messageId, number) as any[]; - return dataMessages.map((dataMessage: any) => Message.fromDBData(dataMessage, this.members, this.channels)); - }, - cacheFunc: async () => { return await this.dbCache.getMessagesAfter(this.id, channelId, messageId, number, this.members, this.channels); }, - cacheUpdateFunc: async (cacheData: Message[] | null, serverData: Message[]) => { - return await this.updateMessageCache(channelId, null, messageId, cacheData, serverData); - }, - updateEventName: null // all events are handled in diffFunc - }); - return messages; - } - - async _fetchResourceInternal(resourceId: string): Promise { - // not using standard _fetchCached here because server-side resources never change. - // rather, the resource_id would be updated if it changes for a message, server icon, avatar, etc. - // this provides for a much simpler cache system (3 stages, client-memory, client-db, server-side) - // since all guildId / resourceId pairs will never update their resource buffers, this cache becomes exceedingly simple - - let resourceCacheDataBuff = ResourceRAMCache.getResource(this.id, resourceId); - if (resourceCacheDataBuff != null) { - return resourceCacheDataBuff; - } - - let cacheData = await this.dbCache.getResource(this.id, resourceId); - if (cacheData !== null) { - ResourceRAMCache.putResource(this.id, resourceId, cacheData.data); - return cacheData.data; - } - - // Note: Not pre-requesting from server asynchronously to reduce fetch-resource requests - let serverData = await this._queryServer('fetch-resource', resourceId); - - ResourceRAMCache.putResource(this.id, resourceId, serverData.data); - await this.dbCache.upsertServerResources(this.id, [ serverData ]); - - return serverData.data; - } - - /** - * Deduplicates client-side resource requests. Useful for when the client wants to fetch an avatar - * multiple times for the channel feed. Or if there is a duplicate image in the feed. - * @param resourceId - */ - async fetchResource(resourceId: string): Promise { - return await new Promise(async (resolve, reject) => { - let resultFunc = (err: any, resourceBuff: Buffer | null) => { - if (err) { - reject(err); - } else if (resourceBuff) { - resolve(resourceBuff); - } else { - reject(new ShouldNeverHappenError('no buffer or error!')); - } - } - if (this.resourceCallbacks.has(resourceId)) { - this.resourceCallbacks.get(resourceId)?.push(resultFunc); - } else { - this.resourceCallbacks.set(resourceId, [ resultFunc ]); - let result: Buffer | null = null; - let err: any = null; - try { - result = await this._fetchResourceInternal(resourceId); - } catch (e) { - err = e; - } - for (let callbackFunc of this.resourceCallbacks.get(resourceId) ?? []) { - callbackFunc(err, result); - } - this.resourceCallbacks.delete(resourceId); - } - }); - } - - async sendMessage(channelId: string, text: string) { - let dataMessage = await this._queryServer('send-message', channelId, text); - await this.ensureMembers(); - await this.ensureChannels(); - return Message.fromDBData(dataMessage, this.members, this.channels); - } - - async sendMessageWithResource(channelId: string, text: string | null, resourceBuff: Buffer, resourceName: string) { - let dataMessage = await this._queryServer('send-message-with-resource', channelId, text, resourceBuff, resourceName); - await this.ensureMembers(); - await this.ensureChannels(); - return Message.fromDBData(dataMessage, this.members, this.channels); - } - - async setStatus(status: string): Promise { - // wow, that's simple for a change - await this._queryServer('set-status', status); - } - - async setDisplayName(displayName: string): Promise { - await this._queryServer('set-display-name', displayName); - } - - async setAvatar(avatarBuff: Buffer): Promise { - await this._queryServer('set-avatar', avatarBuff); - } - - async setName(name: string): Promise { - return ServerMetaData.fromServerDBData(await this._queryServer('set-name', name)); - } - - async setIcon(iconBuff: Buffer): Promise { - return ServerMetaData.fromServerDBData(await this._queryServer('set-icon', iconBuff)); - } - - async updateChannel(channelId: string, name: string, flavorText: string | null): Promise { - return Channel.fromDBData(await this._queryServer('update-channel', channelId, name, flavorText)); - } - - async createChannel(name: string, flavorText: string | null): Promise { - return Channel.fromDBData(await this._queryServer('create-text-channel', name, flavorText)); - } - - async queryTokens(): Promise { - // No cacheing for now, this is a relatively small request and comes - // after a context-menu click so it's not as important to cache as the - // channels, members, and messages - let dataTokens = await this._queryServer('fetch-tokens'); - await this.ensureMembers(); - return dataTokens.map(dataToken => { - return Token.fromDBData(dataToken, this.members); - }); - } - - async revokeToken(token: string): Promise { - return await this._queryServer('revoke-token', token); - } - - close(): void { - this.socket.close(); - } -} diff --git a/client/webapp/_db-cache.ts b/client/webapp/_db-cache.ts deleted file mode 100644 index 6f841f2..0000000 --- a/client/webapp/_db-cache.ts +++ /dev/null @@ -1,522 +0,0 @@ -import * as electronRemote from '@electron/remote'; -const electronConsole = electronRemote.getGlobal('console') as Console; -import Logger from '../../logger/logger'; -const LOG = Logger.create(__filename, electronConsole); - -import * as fs from 'fs/promises'; -import ConcurrentQueue from '../../concurrent-queue/concurrent-queue'; - -import Globals from './globals'; - -import * as sqlite from 'sqlite'; -import * as sqlite3 from 'sqlite3'; -import { Message, Member, Channel, Resource, ServerMetaData, ServerConfig, CacheServerData } from './data-types'; - -// A cache implemented using an sqlite database -// Also stores configuration for server connections -export default class DBCache { - private TRANSACTION_QUEUE = new ConcurrentQueue(1); - - private constructor( - private readonly db: sqlite.Database - ) {} - - async beginTransaction(): Promise { - await this.db.run('BEGIN TRANSACTION'); - } - - async rollbackTransaction(): Promise { - await this.db.run('ROLLBACK'); - } - - async commitTransaction(): Promise { - await this.db.run('COMMIT'); - } - - async queueTransaction(func: (() => Promise)): Promise { - await this.TRANSACTION_QUEUE.push(async () => { - try { - await this.beginTransaction(); - await func(); - await this.commitTransaction(); - } catch (e) { - await this.rollbackTransaction(); - throw e; - } - }); - } - - static async connect(): Promise { - try { - await fs.access('./db'); - } catch (e) { - await fs.mkdir('./db'); - } - return new DBCache(await sqlite.open({ - driver: sqlite3.Database, - filename: './db/cache.db' - })); - } - - async init(): Promise { - await this.queueTransaction(async () => { - await this.db.run(` - CREATE TABLE IF NOT EXISTS identities ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT - , public_key TEXT NOT NULL - , private_key TEXT NOT NULL - ) - `); - - await this.db.run(` - CREATE TABLE IF NOT EXISTS servers ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT - , url TEXT NOT NULL - , cert TEXT NOT NULL - , name TEXT - , icon_resource_id TEXT - , member_id TEXT - ) - `); - - await this.db.run(` - CREATE TABLE IF NOT EXISTS server_identities ( - server_id INTEGER NOT NULL - , identity_id INTEGER NOT NULL - , FOREIGN KEY (server_id) REFERENCES servers(id) - , FOREIGN KEY (identity_id) REFERENCES identities(id) - ) - `); - - await this.db.run(` - CREATE TABLE IF NOT EXISTS members ( - id TEXT NOT NULL - , server_id INTEGER NOT NULL REFERENCES servers(id) - , display_name TEXT NOT NULL - , status TEXT NOT NULL - , avatar_resource_id TEXT NOT NULL - , role_name TEXT - , role_color TEXT - , role_priority INTEGER - , privileges TEXT - , CONSTRAINT members_id_server_id_con UNIQUE (id, server_id) - ) - `); - - await this.db.run(` - CREATE TABLE IF NOT EXISTS channels ( - id TEXT NOT NULL - , server_id INTEGER NOT NULL REFERENCES servers(id) - , "index" INTEGER NOT NULL - , name TEXT NOT NULL - , flavor_text TEXT - , CONSTRAINT channels_id_server_id_con UNIQUE (id, server_id) - ) - `); - - await this.db.run(` - CREATE TABLE IF NOT EXISTS resources ( - id TEXT NOT NULL - , server_id INTEGER NOT NULL REFERENCES servers(id) - , hash BLOB NOT NULL - , data BLOB NOT NULL - , data_size INTEGER NOT NULL - , last_used INTEGER NOT NULL - , CONSTRAINT resources_id_server_id_con UNIQUE (id, server_id) - ) - `); - await this.db.run('CREATE INDEX IF NOT EXISTS resources_data_size_idx ON resources (data_size)'); - - // note: no foreign key on resource_id since we may not have cached the resource yet - await this.db.run(` - CREATE TABLE IF NOT EXISTS messages ( - id TEXT NOT NULL - , server_id INTEGER NOT NULL REFERENCES servers(id) - , channel_id TEXT NOT NULL REFERENCES channels(id) - , member_id TEXT NOT NULL REFERENCES members(id) - , sent_dtg INTEGER NOT NULL - , text TEXT - , resource_id TEXT - , resource_name TEXT - , resource_width INTEGER - , resource_height INTEGER - , resource_preview_id TEXT - , CONSTRAINT messages_id_server_id_con UNIQUE (id, server_id) - ) - `); - await this.db.run('CREATE INDEX IF NOT EXISTS messages_id_idx ON messages (id)'); - await this.db.run('CREATE INDEX IF NOT EXISTS messages_sent_dtg_idx ON messages (sent_dtg)'); - }); - } - - async close(): Promise { - await this.db.close(); - } - - // dangerous! - async reset(): Promise { - await this.queueTransaction(async () => { - await this.db.run('DROP TABLE IF EXISTS identities'); - await this.db.run('DROP TABLE IF EXISTS servers'); - await this.db.run('DROP TABLE IF EXISTS server_identities'); - await this.db.run('DROP TABLE IF EXISTS members'); - await this.db.run('DROP TABLE IF EXISTS channels'); - await this.db.run('DROP TABLE IF EXISTS resources'); - await this.db.run('DROP TABLE IF EXISTS messages'); - }); - } - - // returns the id of the identity inserted - async addIdentity(publicKeyPem: string, privateKeyPem: string): Promise { - let result = await this.db.run(` - INSERT INTO identities (public_key, private_key) VALUES (?, ?) - `, [ publicKeyPem, privateKeyPem ]); - if (!result || result.changes !== 1) { - throw new Error('unable to insert identity'); - } - return result.lastID as number; - } - - // returns the id (client-side) of the server inserted - async addServer(url: string, cert?: string, name?: string): Promise { - let result = await this.db.run(` - INSERT INTO servers (url, cert, name, icon_resource_id) VALUES (?, ?, ?, NULL) - `, [ url, cert, name ]); - if (!result || result.changes !== 1) { - throw new Error('unable to insert server'); - } - return result.lastID as number; - } - - async removeServer(serverId: string): Promise { - let result = await this.db.run('DELETE FROM servers WHERE id=?', [ serverId ]); - if (result.changes != 1) { - throw new Error('unable to remove server'); - } - } - - async addServerIdentity(serverId: number, identityId: number): Promise { - let result = await this.db.run(` - INSERT INTO server_identities (server_id, identity_id) VALUES (?, ?) - `, [ serverId, identityId ]); - if (result.changes != 1) { - throw new Error('unable to insert server identity'); - } - } - - async updateServer(serverId: string, serverMeta: ServerMetaData): Promise { - let result = await this.db.run('UPDATE servers SET name=?, icon_resource_id=? WHERE id=?', [ serverMeta.name, serverMeta.iconResourceId, serverId ]); - if (result.changes != 1) { - throw new Error('unable to update server'); - } - } - - async updateServerMemberId(serverId: string, memberId: string): Promise { - let result = await this.db.run('UPDATE servers SET member_id=? WHERE id=?', [ memberId, serverId ]); - if (result.changes != 1) { - throw new Error(`unable to update member id, s#${serverId}, mem#${memberId}`); - } - } - - async updateServerMembers(serverId: string, members: Member[]): Promise { - await this.queueTransaction(async () => { - await this.db.run('DELETE FROM members WHERE server_id=?', [ serverId ]); - let stmt = await this.db.prepare('INSERT INTO members (id, server_id, display_name, status, avatar_resource_id, role_name, role_color, role_priority, privileges) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'); - for (let member of members) { - let result = await stmt.run([ member.id, serverId, member.displayName, member.status, member.avatarResourceId, member.roleName, member.roleColor, member.rolePriority, member.privileges?.join(',') ]); - if (result.changes != 1) { - // note: probably want to warn and continue - throw new Error('failed to insert member'); - } - } - await stmt.finalize(); - }); - } - - async clearAllMemberStatus(serverId: string): Promise { - await this.db.run(`UPDATE members SET status='unknown' WHERE server_id=?`, [ serverId ]); - } - - async updateServerChannels(serverId: string, channels: Channel[]): Promise { - console.log('setting to ' + channels.length + ' channels'); - await this.queueTransaction(async () => { - await this.db.run('DELETE FROM channels WHERE server_id=?', [ serverId ]); - let stmt = await this.db.prepare('INSERT INTO channels (id, server_id, "index", name, flavor_text) VALUES (?, ?, ?, ?, ?)'); - for (let channel of channels) { - let result = await stmt.run([ channel.id, serverId, channel.index, channel.name, channel.flavorText ]); - if (result.changes != 1) { - // note: probably want to warn and continue - throw new Error('failed to insert channel'); - } - } - await stmt.finalize(); - }); - } - - // TODO: make this singular and a non-transaction based function? - async upsertServerResources(serverId: string, resources: Resource[]): Promise { - await this.queueTransaction(async () => { - let currentSizeResult = await this.db.get('SELECT SUM(data_size) AS current_size FROM resources WHERE server_id=?', [ serverId ]); - let currentSize = parseInt(currentSizeResult.current_size || 0); - let stmt = await this.db.prepare(` - INSERT INTO resources (id, server_id, hash, data, data_size, last_used) VALUES (?1, ?2, ?3, ?4, ?5, ?6) - ON CONFLICT (id, server_id) DO UPDATE SET hash=?3, data=?4, last_used=?6 - `); - for (let resource of resources) { - if (resource.data.length > Globals.MAX_CACHED_RESOURCE_SIZE) { - continue; - } - while (resource.data.length + currentSize > Globals.MAX_SERVER_RESOURCE_CACHE_SIZE) { - let targetResult = await this.db.get('SELECT id, data_size FROM resources ORDER BY last_used ASC LIMIT 1'); - let deleteResult = await this.db.run('DELETE FROM resources WHERE id=?', [ targetResult.id ]); - if (deleteResult.changes != 1) { - throw new Error('failed to delete excess resource'); - } - currentSize -= targetResult.data_size; - } - let result = await stmt.run([ resource.id, serverId, resource.hash, resource.data, resource.data.length, new Date().getTime() ]); - if (result.changes != 1) { - throw new Error('failed to insert resource'); - } - } - await stmt.finalize(); - }); - } - - async upsertServerMessages(serverId: string, channelId: string, messages: Message[]): Promise { - await this.queueTransaction(async () => { - let stmt = await this.db.prepare(` - INSERT INTO messages ( - id, server_id, channel_id, member_id, sent_dtg, text - , resource_id, resource_name, resource_width, resource_height, resource_preview_id - ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11) - ON CONFLICT (id, server_id) - DO UPDATE SET - server_id=?2, channel_id=?3, member_id=?4, sent_dtg=?5, text=?6 - , resource_id=?7, resource_name=?8, resource_width=?9, resource_height=?10, resource_preview_id=?11 - `); - for (let message of messages) { - let result = await stmt.run([ - message.id, serverId, message.channel.id, - message.member.id, message.sent.getTime(), message.text, - message.resourceId, message.resourceName, - message.resourceWidth, message.resourceHeight, - message.resourcePreviewId - ]); - if (result.changes != 1) { - // note: probably want to warn and continue - throw new Error('failed to insert message'); - } - } - await stmt.finalize(); - // delete the oldest messages if the cache is too big - await this.db.run(` - DELETE FROM messages WHERE id IN ( - SELECT id FROM messages WHERE server_id=?1 AND channel_id=?2 ORDER BY sent_dtg - LIMIT max(0, (SELECT COUNT(*) FROM messages WHERE server_id=?1 AND channel_id=?2) - ?3) - ) - `, [ serverId, channelId, Globals.MAX_CACHED_CHANNEL_MESSAGES ]); - }); - } - - async clearServerMessages(serverId: string, channelId: string): Promise { - await this.db.run('DELETE FROM messages WHERE server_id=? AND channel_id=?', [ serverId, channelId ]); - } - - async deleteServerMessages(serverId: string, messageIds: string[]): Promise { - await this.queueTransaction(async () => { - let stmt = await this.db.prepare('DELETE FROM messages WHERE id=? AND server_id=?'); // include server_id for security purposes - for (let messageId of messageIds) { - let result = await stmt.run([ messageId, serverId ]); - if (result.changes != 1) { - // note: probably want to warn and continue - throw new Error('failed to delete message'); - } - } - }); - } - - async getServerConfigs(): Promise { - let result = await this.db.all(` - SELECT - servers.id AS server_id - , servers.url AS url - , servers.cert AS server_cert - , servers.member_id AS member_id - , identities.public_key AS public_key - , identities.private_key AS private_key - FROM - server_identities - , servers - , identities - WHERE - server_identities.identity_id = identities.id - AND server_identities.server_id = servers.id - `); - return result.map((dataServerConfig: any) => ServerConfig.fromDBData(dataServerConfig)); - } - - async getServerConfig(serverId: number, identityId: number): Promise { - let result = await this.db.get(` - SELECT - servers.id AS server_id - , servers.url AS url - , servers.cert AS server_cert - , servers.member_id AS member_id - , identities.public_key AS public_key - , identities.private_key AS private_key - FROM - server_identities - , servers - , identities - WHERE - server_identities.identity_id = identities.id - AND server_identities.server_id = servers.id - AND servers.id=? - AND identities.id=? - `, [ serverId, identityId ]); - return ServerConfig.fromDBData(result); - } - -/* -CREATE TABLE IF NOT EXISTS servers ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT - , url TEXT NOT NULL - , cert TEXT NOT NULL - , name TEXT - , icon_resource_id TEXT - , member_id TEXT -) -*/ - - async getServer(serverId: string): Promise { - let result = await this.db.get(` - SELECT - id, url, cert, name, - icon_resource_id, member_id - FROM servers - WHERE id=? - `, [ serverId ]); - if (result.name == null || result.icon_resource_id == null) { - // server is not set up yet. - return null; - } - return CacheServerData.fromDBData(result); - } - - async getServerMemberId(serverId: string): Promise { - let server = await this.db.get('SELECT member_id FROM servers WHERE id=?', [ serverId ]); - return server.member_id; - } - - // returns null if no members - async getMembers(serverId: string): Promise { - let members = await this.db.all('SELECT * FROM members WHERE server_id=?', [ serverId ]); - if (members.length === 0) { - return null; - } - return members.map((dataMember: any) => Member.fromDBData(dataMember)); - } - - // returns null if no channels - async getChannels(serverId: string): Promise { - let channels = await this.db.all('SELECT * FROM channels WHERE server_id=?', [ serverId ]); - if (channels.length === 0) { - return null; - } - return channels.map((dataChannel: any) => Channel.fromDBData(dataChannel)); - } - - // returns [] if no messages found - async getMessagesRecent( - serverId: string, channelId: string, number: number, - members: Map, channels: Map - ): Promise { - let messages = await this.db.all(` - SELECT * FROM ( - SELECT - "id", "channel_id", "member_id" - ,"sent_dtg", "text" - , "resource_id" - , "resource_name" - , "resource_width" - , "resource_height" - , "resource_preview_id" - FROM "messages" - WHERE "server_id"=? AND "channel_id"=? - ORDER BY "sent_dtg" DESC - LIMIT ? - ) AS "r" ORDER BY "r"."sent_dtg" ASC - `, [ serverId, channelId, number ]); - return messages.map((dataMessage: any) => Message.fromDBData(dataMessage, members, channels)); - } - - // returns null if no messages found - async getMessagesBefore( - serverId: string, channelId: string, messageId: string, number: number, - members: Map, channels: Map - ): Promise { - // Note: this query succeeds returning no results if the message with specified id is not found - let messages = await this.db.all(` - SELECT * FROM ( - SELECT - "id", "channel_id", "member_id" - , "sent_dtg", "text" - , "resource_id" - , "resource_name" - , "resource_width" - , "resource_height" - , "resource_preview_id" - FROM "messages" - WHERE - "server_id"=? - AND "channel_id"=? - AND "sent_dtg" < (SELECT "sent_dtg" FROM "messages" WHERE "id"=?) - ORDER BY "sent_dtg" DESC - LIMIT ? - ) AS "r" ORDER BY "r"."sent_dtg" ASC - `, [ serverId, channelId, messageId, number ]); - if (messages.length == 0) { - return null; - } - return messages.map((messageData: any) => Message.fromDBData(messageData, members, channels)); - } - - // returns null if no messages found - async getMessagesAfter( - serverId: string, channelId: string, messageId: string, number: number, - members: Map, channels: Map - ): Promise { - // Note: this query succeeds returning no results if the message with specified id is not found - let messages = await this.db.all(` - SELECT - "id", "channel_id", "member_id" - , "sent_dtg", "text" - , "resource_id" - , "resource_name" - , "resource_width" - , "resource_height" - , "resource_preview_id" - FROM "messages" - WHERE - "server_id"=? - AND "channel_id"=? - AND "sent_dtg" > (SELECT "sent_dtg" FROM "messages" WHERE "id"=?) - ORDER BY "sent_dtg" ASC - LIMIT ? - `, [ serverId, channelId, messageId, number ]); - if (messages.length == 0) { - return null; - } - return messages.map((messageData: any) => Message.fromDBData(messageData, members, channels)); - } - - async getResource(serverId: string, resourceId: string): Promise { - let row = await this.db.get('SELECT id, data, hash FROM resources WHERE server_id=? AND id=?', [ serverId, resourceId ]); - await this.db.run('UPDATE resources SET last_used=?1 WHERE server_id=?2 AND id=?3', [ new Date().getTime(), serverId, resourceId ]); - if (!row) { - return null; - } - return Resource.fromDBData(row); - } -} diff --git a/client/webapp/_recent-message-ram-cache.ts b/client/webapp/_recent-message-ram-cache.ts deleted file mode 100644 index 30c18e5..0000000 --- a/client/webapp/_recent-message-ram-cache.ts +++ /dev/null @@ -1,136 +0,0 @@ -import Globals from './globals'; - -import { Message, ShouldNeverHappenError } from './data-types'; - -// TODO: this class is junk now :( - -// TODO: make this non-static -export default class RecentMessageRAMCache { - private data = new Map(); // (guildId, channelId) -> { messages, size, hasFirstMessage, lastUsed } - private size = 0; - - clear(): void { - this.data.clear(); - this.size = 0; - } - - _cullIfNeeded(): void { // TODO: Test this - if (this.size > Globals.MAX_RAM_CACHED_MESSAGES_TOTAL_SIZE) { - let entries = Array.from(this.data.entries()) - .map(([ key, value ]) => { return { id: key, value: value }}) - .sort((a, b) => b.value.lastUsed.getTime() - a.value.lastUsed.getTime()); - while (this.size > Globals.MAX_RAM_CACHED_MESSAGES_TOTAL_SIZE) { - let entry = entries.pop(); - if (entry === undefined) throw new ShouldNeverHappenError('No entry in the array but the message cache still has a size...'); - this.data.delete(entry.id); - this.size -= entry.value.size; - } - } - } - - _cullChannelIfNeeded(guildId: string, channelId: string): void { // TODO: test this - let id = `s#${guildId}/c#${channelId}`; - let value = this.data.get(id); - if (!value) return; - - while (value.size > Globals.MAX_RAM_CACHED_MESSAGES_CHANNEL_SIZE) { - value.hasFirstMessage = false; - let message = value.messages.shift(); - if (!message) return; - value.size -= message.text?.length ?? 0; - this.size -= message.text?.length ?? 0; - } - } - - // @param messages may be modified in addNewMessage due to pass-by-reference fun - putRecentMessages(guildId: string, channelId: string, messages: Message[]): void { - let size = 0; - for (let message of messages) { - size += message.text?.length ?? 0; - } - if (size > Globals.MAX_RAM_CACHED_MESSAGES_CHANNEL_SIZE) return; - let id = `s#${guildId}/c#${channelId}`; - this.data.set(id, { - messages: messages, - size: size, - hasFirstMessage: true, // will be false if this was ever channel-culled - lastUsed: new Date() - }); - this.size += size; - this._cullIfNeeded(); - } - - addNewMessage(guildId: string, message: Message): void { - let channelId = message.channel.id; - let id = `s#${guildId}/c#${channelId}`; - - let value = this.data.get(id); - if (!value) return; - - value.messages.push(message); - value.size += message.text?.length ?? 0; - this.size += message.text?.length ?? 0; - - this._cullChannelIfNeeded(guildId, channelId); - this._cullIfNeeded(); - } - - updateMessage(guildId: string, oldMessage: Message, newMessage: Message): void { - let channelId = oldMessage.channel.id; - let id = `s#${guildId}/c#${channelId}`; - - let value = this.data.get(id); - if (!value) return; - - let taggedIndex = value.messages.findIndex(cachedMessage => cachedMessage.id == oldMessage.id); - - let [ oldCachedMessage ] = value.messages.splice(taggedIndex, 1, newMessage); - value.size -= oldCachedMessage.text?.length ?? 0; - value.size += newMessage.text?.length ?? 0; - this.size -= oldCachedMessage.text?.length ?? 0; - this.size += newMessage.text?.length ?? 0; - - this._cullChannelIfNeeded(guildId, channelId); - this._cullIfNeeded(); - } - - deleteMessage(guildId: string, message: Message): void { - let channelId = message.channel.id; - let id = `s#${guildId}/c#${channelId}`; - - let value = this.data.get(id); - if (!value) return; - - let taggedIndex = value.messages.findIndex(cachedMessage => cachedMessage.id == message.id); - - let [ oldCachedMessage ] = value.messages.splice(taggedIndex, 1); - value.size -= oldCachedMessage.text?.length ?? 0; - this.size -= oldCachedMessage.text?.length ?? 0; - - // No need to cull since we are always shrinking - } - - dropChannel(guildId: string, channelId: string): void { - let id = `s#${guildId}/c#${channelId}`; - let value = this.data.get(id); - if (!value) return; - this.size -= value.size; - this.data.delete(id); - } - - getRecentMessages(guildId: string, channelId: string, number: number): Message[] | null { - let id = `s#${guildId}/c#${channelId}`; - if (!this.data.has(id)) { - //LOG.silly(`recent message cache miss on ${id} requesting ${number}`); - return null; // not in the cache - } - let value = this.data.get(id); - if (!value) throw new ShouldNeverHappenError('javascript is not on a single thread >:|'); - if (!value.hasFirstMessage && value.messages.length < number) { - //LOG.silly(`recent message cache incomplete on ${id} requesting ${number}`, { value }); - return null; // requesting older messages than we have - } - value.lastUsed = new Date(); - return value.messages.slice(-number); - } -} diff --git a/client/webapp/actions.ts b/client/webapp/actions.ts index 6053a2d..40c1016 100644 --- a/client/webapp/actions.ts +++ b/client/webapp/actions.ts @@ -33,7 +33,7 @@ export default class Actions { errorIndicatorAddFunc: async (errorIndicatorElement) => { await ui.setMembersErrorIndicator(guild, errorIndicatorElement); }, - errorContainer: q.$('#server-members'), + errorContainer: q.$('#guild-members'), errorMessage: 'Error loading members' }); } diff --git a/client/webapp/data-types.ts b/client/webapp/data-types.ts index e1fca06..8e3ff44 100644 --- a/client/webapp/data-types.ts +++ b/client/webapp/data-types.ts @@ -373,72 +373,6 @@ export interface Changes { deleted: T[] } - -// Old Garbage -export class ServerConfig { - private constructor( - public readonly guildId: string, - public readonly url: string, - public readonly serverCert: string, - public readonly memberId: string, - public readonly publicKey: string, - public readonly privateKey: string, - public readonly source?: any - ) {} - - public static fromDBData(dataServerConfig: any): ServerConfig { - return new ServerConfig( - dataServerConfig.server_id, - dataServerConfig.url, - dataServerConfig.server_cert, - dataServerConfig.member_id, - dataServerConfig.public_key, - dataServerConfig.private_key, - dataServerConfig - ); - } -} - -export class ServerMetaData { - private constructor( - public readonly name: string, - public readonly iconResourceId: string, - public readonly source?: any - ) {} - - public static fromServerDBData(data: any): ServerMetaData { - return new ServerMetaData( - data.name, - data.icon_resource_id, - data - ); - } -} - -export class CacheServerData { - constructor( - public readonly id: string, - public readonly url: string, - public readonly cert: string, - public readonly name: string, - public readonly iconResourceId: string | null, - public readonly memberId: string, - public readonly source?: any - ) {} - - public static fromDBData(data: any): CacheServerData { - return new CacheServerData( - data.id, - data.url, - data.cert, - data.name, - data.icon_resource_id, - data.member_id, - data - ); - } -} - export interface ConnectionInfo { id: string | null; avatarResourceId: string | null; diff --git a/client/webapp/elements/channel.ts b/client/webapp/elements/channel.ts index d0deaff..5471f43 100644 --- a/client/webapp/elements/channel.ts +++ b/client/webapp/elements/channel.ts @@ -8,7 +8,7 @@ import Q from '../q-module'; import CombinedGuild from '../guild-combined'; export default function createChannel(document: Document, q: Q, ui: UI, guild: CombinedGuild, channel: Channel) { - let element = q.create({ class: 'channel text', 'meta-id': channel.id, 'meta-server-id': guild.id, content: [ + let element = q.create({ class: 'channel text', 'meta-id': channel.id, 'meta-guild-id': guild.id, content: [ // Scraped directly from discord (#) { class: 'icon', content: BaseElements.TEXT_CHANNEL_ICON }, { class: 'name', content: channel.name }, diff --git a/client/webapp/elements/context-menu-guild.ts b/client/webapp/elements/context-menu-guild.ts index 767d5f9..b504ddd 100644 --- a/client/webapp/elements/context-menu-guild.ts +++ b/client/webapp/elements/context-menu-guild.ts @@ -10,23 +10,23 @@ import UI from '../ui'; import GuildsManager from '../guilds-manager'; import CombinedGuild from '../guild-combined'; -export default function createServerContextMenu(document: Document, q: Q, ui: UI, guildsManager: GuildsManager, guild: CombinedGuild) { +export default function createGuildContextMenu(document: Document, q: Q, ui: UI, guildsManager: GuildsManager, guild: CombinedGuild) { let element = BaseElements.createContextMenu(document, { - class: 'server-context', content: [ - { class: 'item red leave-server', content: 'Leave Server' } + class: 'guild-context', content: [ + { class: 'item red leave-guild', content: 'Leave Guild' } ] }); - q.$$$(element, '.leave-server').addEventListener('click', async () => { + q.$$$(element, '.leave-guild').addEventListener('click', async () => { element.removeSelf(); guild.disconnect(); - await guildsManager.removeServer(guild); + await guildsManager.removeGuild(guild); await ui.removeGuild(guild); - let firstServerElement = q.$_('#server-list .server'); - if (firstServerElement) { - firstServerElement.click(); + let firstGuildElement = q.$_('#guild-list .guild'); + if (firstGuildElement) { + firstGuildElement.click(); } else { - LOG.warn('no first server element to click on'); + LOG.warn('no first guild element to click on'); } }); diff --git a/client/webapp/elements/context-menu-img.ts b/client/webapp/elements/context-menu-img.ts index b3c3f74..3851c5f 100644 --- a/client/webapp/elements/context-menu-img.ts +++ b/client/webapp/elements/context-menu-img.ts @@ -28,7 +28,7 @@ export default function createImageContextMenu( q.$$$(contextMenu, '.copy-image').innerText = 'Copying...'; let nativeImage: electron.NativeImage; if (mime != 'image/png' && mime != 'image/jpeg' && mime != 'image/jpg') { - // use sharp to convserver: serverrt to png since nativeImage only supports jpeg/png + // use sharp to convert to png since nativeImage only supports jpeg/png nativeImage = electron.nativeImage.createFromBuffer(await sharp(buffer).png().toBuffer()); } else { nativeImage = electron.nativeImage.createFromBuffer(buffer); diff --git a/client/webapp/elements/events-guild-title.ts b/client/webapp/elements/events-guild-title.ts index 18325e6..ce8d60d 100644 --- a/client/webapp/elements/events-guild-title.ts +++ b/client/webapp/elements/events-guild-title.ts @@ -1,10 +1,10 @@ import Q from '../q-module'; import UI from '../ui'; -import createServerTitleContextMenu from './context-menu-guild-title'; +import createGuildTitleContextMenu from './context-menu-guild-title'; import ElementsUtil from './require/elements-util'; -export default function bindAddServerTitleEvents(document: Document, q: Q, ui: UI) { - q.$('#server-name-container').addEventListener('click', () => { +export default function bindAddGuildTitleEvents(document: Document, q: Q, ui: UI) { + q.$('#guild-name-container').addEventListener('click', () => { if (ui.activeConnection === null) return; if (ui.activeGuild === null) return; if (!ui.activeGuild.isSocketVerified()) return; @@ -13,8 +13,8 @@ export default function bindAddServerTitleEvents(document: Document, q: Q, ui: U !ui.activeConnection.privileges.includes('modify_members') ) return; - let contextMenu = createServerTitleContextMenu(document, q, ui, ui.activeGuild); + let contextMenu = createGuildTitleContextMenu(document, q, ui, ui.activeGuild); document.body.appendChild(contextMenu); - ElementsUtil.alignContextElement(contextMenu, q.$('#server-name-container'), { top: 'bottom', centerX: 'centerX' }); + ElementsUtil.alignContextElement(contextMenu, q.$('#guild-name-container'), { top: 'bottom', centerX: 'centerX' }); }); } diff --git a/client/webapp/elements/events-text-input.ts b/client/webapp/elements/events-text-input.ts index 912c2e5..fd1fdb4 100644 --- a/client/webapp/elements/events-text-input.ts +++ b/client/webapp/elements/events-text-input.ts @@ -27,10 +27,10 @@ export default function bindTextInputEvents(document: Document, q: Q, ui: UI): v sendingMessage = true; - let server = ui.activeGuild as CombinedGuild; + let guild = ui.activeGuild as CombinedGuild; let channel = ui.activeChannel as Channel; - if (!server.isSocketVerified()) { + if (!guild.isSocketVerified()) { LOG.warn('client attempted to send message while not verified'); q.$('#send-error').innerText = 'Not Connected to Server'; await ElementsUtil.shakeElement(q.$('#send-error'), 400); @@ -46,11 +46,11 @@ export default function bindTextInputEvents(document: Document, q: Q, ui: UI): v return; } - await ui.lockMessages(server, channel, async () => { + await ui.lockMessages(guild, channel, async () => { q.$('#text-input').removeAttribute('contenteditable'); q.$('#text-input').classList.add('sending'); try { - await server.requestSendMessage(channel.id, text); + await guild.requestSendMessage(channel.id, text); q.$('#send-error').innerText = ''; q.$('#text-input').innerText = ''; diff --git a/client/webapp/elements/overlay-guild-settings.ts b/client/webapp/elements/overlay-guild-settings.ts index 988da8d..8740157 100644 --- a/client/webapp/elements/overlay-guild-settings.ts +++ b/client/webapp/elements/overlay-guild-settings.ts @@ -98,7 +98,7 @@ export default function createGuildSettingsOverlay(document: Document, q: Q, gui // Set Name if (newName != guildMeta.name) { try { - await guild.requestSetServerName(newName); + await guild.requestSetGuildName(newName); guildMeta = await guild.fetchMetadata(); } catch (e) { LOG.error('error setting new guild name', e); @@ -112,7 +112,7 @@ export default function createGuildSettingsOverlay(document: Document, q: Q, gui // Set Icon if (!failed && newIconBuff != null) { try { - await guild.requestSetServerIcon(newIconBuff); + await guild.requestSetGuildIcon(newIconBuff); newIconBuff = null; // prevent resubmit } catch (e) { LOG.error('error setting new guild icon', e); diff --git a/client/webapp/globals.ts b/client/webapp/globals.ts index e1e5dc5..a50d833 100644 --- a/client/webapp/globals.ts +++ b/client/webapp/globals.ts @@ -21,7 +21,7 @@ export default class Globals { static MAX_CACHED_CHANNEL_MESSAGES = 1000; // the 1000 most recent messages in each text channel are cached (in the sqlite db) - static MAX_SERVER_RESOURCE_CACHE_SIZE = 1024 * 1024 * 1024; // 1 GB max resource cache per guild + static MAX_GUILD_RESOURCE_CACHE_SIZE = 1024 * 1024 * 1024; // 1 GB max resource cache per guild static MAX_CACHED_RESOURCE_SIZE = 1024 * 1024 * 4; // 4 MB is the biggest resource that will be cached static MAX_RAM_CACHED_MESSAGES_CHANNEL_CHARACTERS = 1024 * 1024 * 64; // at most, 64 MB of channel diff --git a/client/webapp/guild-combined.ts b/client/webapp/guild-combined.ts index 0281090..6ac400c 100644 --- a/client/webapp/guild-combined.ts +++ b/client/webapp/guild-combined.ts @@ -7,7 +7,7 @@ import * as socketio from 'socket.io-client'; import PersonalDBGuild from './guild-personal-db'; import RAMGuild from './guild-ram'; import SocketGuild from './guild-socket'; -import { Changes, Channel, ConnectionInfo, GuildMetadata, GuildMetadataWithIds, Member, Message, Resource, ServerMetaData, SocketConfig, Token } from './data-types'; +import { Changes, Channel, ConnectionInfo, GuildMetadata, GuildMetadataWithIds, Member, Message, Resource, SocketConfig, Token } from './data-types'; import MessageRAMCache from "./message-ram-cache"; import PersonalDB from "./personal-db"; @@ -299,11 +299,11 @@ export default class CombinedGuild extends EventEmitter Guild - async requestSetServerName(serverName: string): Promise { - await this.socketGuild.requestSetServerName(serverName); + async requestSetGuildName(guildName: string): Promise { + await this.socketGuild.requestSetGuildName(guildName); } - async requestSetServerIcon(serverIcon: Buffer): Promise { - await this.socketGuild.requestSetServerIcon(serverIcon); + async requestSetGuildIcon(guildIcon: Buffer): Promise { + await this.socketGuild.requestSetGuildIcon(guildIcon); } async requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise { await this.socketGuild.requestDoUpdateChannel(channelId, name, flavorText); diff --git a/client/webapp/guild-socket.ts b/client/webapp/guild-socket.ts index 36e81d3..d3cb249 100644 --- a/client/webapp/guild-socket.ts +++ b/client/webapp/guild-socket.ts @@ -4,7 +4,7 @@ import Logger from '../../logger/logger'; const LOG = Logger.create(__filename, electronConsole); import * as socketio from "socket.io-client"; -import { Channel, GuildMetadata, Member, Message, Resource, ServerMetaData, Token } from "./data-types"; +import { Channel, GuildMetadata, Member, Message, Resource, Token } from "./data-types"; import Globals from './globals'; import { Connectable, AsyncRequestable, AsyncGuaranteedFetchable } from './guild-types'; import DedupAwaiter from './dedup-awaiter'; @@ -145,11 +145,11 @@ export default class SocketGuild extends EventEmitter implements As async requestSetAvatar(avatar: Buffer): Promise { await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'set-avatar', avatar); } - async requestSetServerName(serverName: string): Promise { - await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'set-name', serverName); + async requestSetGuildName(guildName: string): Promise { + await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'set-name', guildName); } - async requestSetServerIcon(serverIcon: Buffer): Promise { - await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'set-icon', serverIcon); + async requestSetGuildIcon(guildIcon: Buffer): Promise { + await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'set-icon', guildIcon); } async requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise { let _changedChannel = await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'update-channel', channelId, name, flavorText); diff --git a/client/webapp/guild-types.ts b/client/webapp/guild-types.ts index 261185b..d1fb667 100644 --- a/client/webapp/guild-types.ts +++ b/client/webapp/guild-types.ts @@ -45,8 +45,8 @@ export interface AsyncRequestable { requestSetStatus(status: string): Promise; requestSetDisplayName(displayName: string): Promise; requestSetAvatar(avatar: Buffer): Promise; - requestSetServerName(serverName: string): Promise; - requestSetServerIcon(serverIcon: Buffer): Promise; + requestSetGuildName(guildName: string): Promise; + requestSetGuildIcon(guildIcon: Buffer): Promise; requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise; requestDoCreateChannel(name: string, flavorText: string | null): Promise; requestDoRevokeToken(token: string): Promise; diff --git a/client/webapp/guilds-manager.ts b/client/webapp/guilds-manager.ts index dab42c1..ce9cb5f 100644 --- a/client/webapp/guilds-manager.ts +++ b/client/webapp/guilds-manager.ts @@ -9,8 +9,8 @@ import * as socketio from 'socket.io-client'; import * as crypto from 'crypto'; -import { Changes, Channel, GuildMetadata, GuildMetadataWithIds, Member, Message, Resource, ServerConfig, SocketConfig, Token } from './data-types'; -import { IAddServerData } from './elements/overlay-add-server'; +import { Changes, Channel, GuildMetadata, GuildMetadataWithIds, Member, Message, Resource, SocketConfig, Token } from './data-types'; +import { IAddGuildData } from './elements/overlay-add-guild'; import { EventEmitter } from 'tsee'; import CombinedGuild from './guild-combined'; import PersonalDB from './personal-db'; @@ -54,7 +54,7 @@ export default class GuildsManager extends EventEmitter<{ } async _connectFromConfig(guildMetadata: GuildMetadataWithIds, socketConfig: SocketConfig): Promise { - LOG.debug(`connecting to server#${guildMetadata.id} at ${socketConfig.url}`); + LOG.debug(`connecting to g#${guildMetadata.id} at ${socketConfig.url}`); let guild = await CombinedGuild.create( guildMetadata, @@ -112,8 +112,8 @@ export default class GuildsManager extends EventEmitter<{ }); } - async addNewGuild(serverConfig: IAddServerData, displayName: string, avatarBuff: Buffer): Promise { - const { name, url, cert, token } = serverConfig; + async addNewGuild(guildConfig: IAddGuildData, displayName: string, avatarBuff: Buffer): Promise { + const { name, url, cert, token } = guildConfig; LOG.debug('Adding new server', { name, url, cert, token, displayName, avatarBuff }); @@ -132,7 +132,7 @@ export default class GuildsManager extends EventEmitter<{ }); }); try { - // Create a new Public/Private key pair to identify ourselves with this server + // Create a new Public/Private key pair to identify ourselves with this guild let { publicKey, privateKey } = await new Promise((resolve, reject) => { crypto.generateKeyPair('rsa', { modulusLength: 4096 }, (err, publicKey, privateKey) => { if (err) { @@ -178,7 +178,7 @@ export default class GuildsManager extends EventEmitter<{ } } - async removeServer(guild: CombinedGuild): Promise { + async removeGuild(guild: CombinedGuild): Promise { await this.personalDB.queueTransaction(async () => { await this.personalDB.removeGuildSockets(guild.id); await this.personalDB.removeGuild(guild.id); diff --git a/client/webapp/index.html b/client/webapp/index.html index 599fad4..39afd4a 100644 --- a/client/webapp/index.html +++ b/client/webapp/index.html @@ -35,16 +35,16 @@
-
-
-
+
+
+
- +
-
-
-
+
+
+
-
+
diff --git a/client/webapp/resource-ram-cache.ts b/client/webapp/resource-ram-cache.ts index 889b3ee..8cf1731 100644 --- a/client/webapp/resource-ram-cache.ts +++ b/client/webapp/resource-ram-cache.ts @@ -13,11 +13,11 @@ export default class ResourceRAMCache { let id = `s#${guildId}/r#${resource.id}`; this.data.set(id, { resource: resource, lastUsed: new Date() }); this.size += resource.data.length; - if (this.size > Globals.MAX_SERVER_RESOURCE_CACHE_SIZE) { // TODO: this feature needs to be tested + if (this.size > Globals.MAX_GUILD_RESOURCE_CACHE_SIZE) { // TODO: this feature needs to be tested let entries = Array.from(this.data.entries()) .map(([ key, value ]) => { return { id: key, value: value }; }) .sort((a, b) => b.value.lastUsed.getTime() - a.value.lastUsed.getTime()); // oldest last (for pop) - while (this.size > Globals.MAX_SERVER_RESOURCE_CACHE_SIZE) { + while (this.size > Globals.MAX_GUILD_RESOURCE_CACHE_SIZE) { let entry = entries.pop(); if (entry === undefined) throw new ShouldNeverHappenError('No entry in the array but the ram cache still has a size...'); this.data.delete(entry.id); diff --git a/client/webapp/styles/channel-list.scss b/client/webapp/styles/channel-list.scss index 943fc18..5c5d2e2 100644 --- a/client/webapp/styles/channel-list.scss +++ b/client/webapp/styles/channel-list.scss @@ -50,7 +50,7 @@ } } -#server.privilege-modify_channels .channel:hover .modify, -#server.privilege-modify_channels .channel.active .modify { +#guild.privilege-modify_channels .channel:hover .modify, +#guild.privilege-modify_channels .channel.active .modify { display: unset; } diff --git a/client/webapp/styles/contexts.scss b/client/webapp/styles/contexts.scss index 5d36003..a29527d 100644 --- a/client/webapp/styles/contexts.scss +++ b/client/webapp/styles/contexts.scss @@ -47,8 +47,8 @@ margin-right: 8px; } - .server-title-context .item .icon img, - .server-title-context .item .icon svg { + .guild-title-context .item .icon img, + .guild-title-context .item .icon svg { width: 16px; height: 16px; } @@ -105,7 +105,7 @@ border-radius: 4px; } - .content.server { + .content.guild { line-height: 1; .name:not(:last-child) { diff --git a/client/webapp/styles/overlays.scss b/client/webapp/styles/overlays.scss index 7f83b32..5658c9d 100644 --- a/client/webapp/styles/overlays.scss +++ b/client/webapp/styles/overlays.scss @@ -201,9 +201,9 @@ body > .overlay { } } - /* Server Settings Overlay */ + /* guild Settings Overlay */ - .content.server-settings { + .content.guild-settings { min-width: 350px; .preview { @@ -228,9 +228,9 @@ body > .overlay { } } - /* Add Server Overlay */ + /* Add guild Overlay */ - .content.add-server { + .content.add-guild { min-width: 350px; background-color: $background-secondary; border-radius: 12px; diff --git a/client/webapp/styles/server-list.scss b/client/webapp/styles/server-list.scss index bf21ffb..7715aa9 100644 --- a/client/webapp/styles/server-list.scss +++ b/client/webapp/styles/server-list.scss @@ -1,6 +1,6 @@ @import "theme.scss"; -#server-list-container { +#guild-list-container { display: flex; flex-flow: column; overflow-y: scroll; @@ -12,25 +12,25 @@ overflow-y: scroll; } -#server-list { +#guild-list { display: flex; flex-flow: column; } -#server-list::-webkit-scrollbar { +#guild-list::-webkit-scrollbar { display: none; } -#add-server, -#server-list .server { +#add-guild, +#guild-list .guild { cursor: pointer; margin-bottom: 8px; display: flex; align-items: center; } -#add-server .pill, -#server-list .server .pill { +#add-guild .pill, +#guild-list .guild .pill { background-color: $header-primary; width: 8px; height: 0; @@ -40,29 +40,29 @@ transition: height .1s ease-in-out; } -#server-list .server.active .pill { +#guild-list .guild.active .pill { height: 40px; } -#server-list .server.unread:not(.active) .pill { +#guild-list .guild.unread:not(.active) .pill { height: 8px; } -#add-server:hover .pill, -#server-list .server:not(.active):hover .pill { +#add-guild:hover .pill, +#guild-list .guild:not(.active):hover .pill { height: 20px; } -#add-server img, -#server-list .server img { +#add-guild img, +#guild-list .guild img { width: 48px; height: 48px; border-radius: 24px; transition: border-radius .1s ease-in-out; } -#add-server:hover img, -#server-list .server:hover img, -#server-list .server.active img { +#add-guild:hover img, +#guild-list .guild:hover img, +#guild-list .guild.active img { border-radius: 16px; } diff --git a/client/webapp/styles/server-members.scss b/client/webapp/styles/server-members.scss index 36ed823..0797d8e 100644 --- a/client/webapp/styles/server-members.scss +++ b/client/webapp/styles/server-members.scss @@ -1,6 +1,6 @@ @import "theme.scss"; -#server-members { +#guild-members { box-sizing: border-box; flex: none; /* >:| NOT GONNA SHINK BOI */ background-color: $background-secondary; @@ -10,22 +10,22 @@ padding: 8px 0 8px 8px; } -#server-members .member { +#guild-members .member { background-color: $background-secondary; padding: 4px 8px; margin-bottom: 4px; border-radius: 4px; } -#server-members .member .name { +#guild-members .member .name { width: calc(208px - 40px); } -#server-members .member .status-circle { +#guild-members .member .status-circle { border-color: $background-secondary; } -#server-members .member:hover { +#guild-members .member:hover { background-color: $background-modifier-hover; cursor: pointer; } diff --git a/client/webapp/styles/server.scss b/client/webapp/styles/server.scss index 553fe83..699ec9e 100644 --- a/client/webapp/styles/server.scss +++ b/client/webapp/styles/server.scss @@ -1,11 +1,11 @@ @import "theme.scss"; -#server { +#guild { flex: 1; display: flex; } -#server-sidebar { +#guild-sidebar { flex: none; /* >:| NOT GONNA SHINK BOI */ width: 240px; display: flex; @@ -14,7 +14,7 @@ border-top-left-radius: 8px; } -#server-name-container { +#guild-name-container { padding: 0 16px; height: 48px; font-weight: 600; @@ -25,14 +25,14 @@ border-bottom: 1px solid $background-secondary-alt; } -#server-name { +#guild-name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -#server.privilege-modify_profile #server-name, -#server.privilege-modify_members #server-name { +#guild.privilege-modify_profile #guild-name, +#guild.privilege-modify_members #guild-name { cursor: pointer; } diff --git a/client/webapp/styles/styles.scss b/client/webapp/styles/styles.scss index 8e28af7..c2fcab4 100644 --- a/client/webapp/styles/styles.scss +++ b/client/webapp/styles/styles.scss @@ -15,9 +15,9 @@ @import "overlays.scss"; @import "scrollbars.scss"; @import "scrollbars.scss"; -@import "server-list.scss"; -@import "server-members.scss"; -@import "server.scss"; +@import "guild-list.scss"; +@import "guild-members.scss"; +@import "guild.scss"; @import "shake.scss"; @import "status-circles.scss"; @import "title-bar.scss";