diff --git a/client/webapp/client-controller.ts b/client/webapp/client-controller.ts index 9f3421f..9fcb9db 100644 --- a/client/webapp/client-controller.ts +++ b/client/webapp/client-controller.ts @@ -16,8 +16,6 @@ import { Message, Member, Channel, Changes, ConnectionInfo, CacheServerData, Ser import DBCache from './db-cache'; import ResourceRAMCache from './resource-ram-cache'; import RecentMessageRAMCache from './message-ram-cache'; -import { channel } from 'diagnostics_channel'; -import Q from './q-module'; // Events: // 'connected' function() called when connected to the server @@ -73,6 +71,8 @@ interface FetchCachedAndVerifyProps { } export default class ClientController extends EventEmitter { + private dbCache: DBCache; + public id: string; public memberId: string; public url: string; @@ -96,9 +96,11 @@ export default class ClientController extends EventEmitter { public resourceCallbacks: Map Promise | void)[]>; public dedupedCallbacks: Map Promise | void)[]>; - constructor(config: ServerConfig) { + 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; @@ -157,7 +159,7 @@ export default class ClientController extends EventEmitter { await this.ensureMembers(); await this.ensureChannels(); let message = Message.fromDBData(dataMessage, this.members, this.channels); - await DBCache.upsertServerMessages(this.id, message.channel.id, [ message ]); + 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); @@ -185,7 +187,7 @@ export default class ClientController extends EventEmitter { this.emit('added-channels', [ channel ]); }); this.socket.on('update-server', async (serverMeta) => { - await DBCache.updateServer(this.id, serverMeta); + await this.dbCache.updateServer(this.id, serverMeta); this.emit('update-server', serverMeta); }); } @@ -195,38 +197,38 @@ export default class ClientController extends EventEmitter { for (let member of members) { this.members.set(member.id, member); } - await DBCache.updateServerMembers(this.id, Array.from(this.members.values())); + 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 DBCache.updateServerMembers(this.id, Array.from(this.members.values())); + 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 DBCache.updateServerMembers(this.id, Array.from(this.members.values())); + 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 DBCache.updateServerChannels(this.id, Array.from(this.channels.values())); + 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 DBCache.updateServerChannels(this.id, Array.from(this.channels.values())); + 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 DBCache.updateServerChannels(this.id, Array.from(this.channels.values())); + await this.dbCache.updateServerChannels(this.id, Array.from(this.channels.values())); }); this.on('added-messages', async (channel: Channel, addedAfter: Map, addedBefore: Map) => { @@ -239,19 +241,19 @@ export default class ClientController extends EventEmitter { // 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 DBCache.upsertServerMessages(this.id, channel.id, Array.from(addedAfter.values())); + 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 DBCache.upsertServerMessages(this.id, channel.id, data.map(change => change.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 DBCache.deleteServerMessages(this.id, messages.map(message => message.id)); + await this.dbCache.deleteServerMessages(this.id, messages.map(message => message.id)); }); } @@ -427,7 +429,7 @@ export default class ClientController extends EventEmitter { return; } this.memberId = memberId; - DBCache.updateServerMemberId(this.id, this.memberId); + this.dbCache.updateServerMemberId(this.id, this.memberId); this.isVerified = true; LOG.info(`s#${this.id} client verified as u#${this.memberId}`); resolve(); @@ -457,7 +459,7 @@ export default class ClientController extends EventEmitter { return; } this.memberId = memberId; - DBCache.updateServerMemberId(this.id, this.memberId); + this.dbCache.updateServerMemberId(this.id, this.memberId); this.isVerified = true; LOG.info(`verified at server#${this.id} as u#${this.memberId}`); resolve(); @@ -534,10 +536,10 @@ export default class ClientController extends EventEmitter { } let metadata = await this._fetchCachedAndVerify({ serverFunc: async () => { return ServerMetaData.fromServerDBData(await this._queryServer('fetch-server')); }, - cacheFunc: async () => { return await DBCache.getServer(this.id); }, + cacheFunc: async () => { return await this.dbCache.getServer(this.id); }, cacheUpdateFunc: async (cacheData: CacheServerData | null, serverData: ServerMetaData) => { if (!isDifferent(cacheData, serverData)) return false; - await DBCache.updateServer(this.id, serverData); + await this.dbCache.updateServer(this.id, serverData); return true; }, updateEventName: 'update-server', @@ -575,7 +577,7 @@ export default class ClientController extends EventEmitter { LOG.warn('Unable to find self in members', this.members); } } else { - let cacheMembers = await DBCache.getMembers(this.id); + let cacheMembers = await this.dbCache.getMembers(this.id); if (cacheMembers) { let member = cacheMembers.find(m => m.id == this.memberId); if (member) { @@ -599,7 +601,7 @@ export default class ClientController extends EventEmitter { let dataMembers = (await this._queryServer('fetch-members')) as any[]; return dataMembers.map((dataMember: any) => Member.fromDBData(dataMember)); }, - cacheFunc: async () => { return await DBCache.getMembers(this.id); }, + cacheFunc: async () => { return await this.dbCache.getMembers(this.id); }, cacheUpdateFunc: async (cacheData: Member[] | null, serverData: Member[]) => { function equal(cacheMember: Member, serverMember: Member) { return ( @@ -619,7 +621,7 @@ export default class ClientController extends EventEmitter { return false; } - await DBCache.updateServerMembers(this.id, serverData); + await this.dbCache.updateServerMembers(this.id, serverData); this._updateCachedMembers(serverData); if (changes.deleted.length > 0) { @@ -648,7 +650,7 @@ export default class ClientController extends EventEmitter { let dataChannels = (await this._queryServer('fetch-channels')) as any[]; return dataChannels.map((dataChannel: any) => Channel.fromDBData(dataChannel)); }, - cacheFunc: async () => { return await DBCache.getChannels(this.id); }, + 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 && @@ -749,7 +751,7 @@ export default class ClientController extends EventEmitter { // 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 DBCache.clearServerMessages(this.id, deletedMessages[0].channel.id); + 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 @@ -773,7 +775,7 @@ export default class ClientController extends EventEmitter { } //LOG.debug('skipping ' + i + ' deleted messages on the cache side -> deleting ' + cacheDeletedMessages.length + ' cache messages instead of ' + deletedMessages.length); if (cacheDeletedMessages.length > 0) { - await DBCache.deleteServerMessages(this.id, cacheDeletedMessages.map(m => m.id)); + await this.dbCache.deleteServerMessages(this.id, cacheDeletedMessages.map(m => m.id)); } } } @@ -800,7 +802,7 @@ export default class ClientController extends EventEmitter { return dataMessages.map((dataMessage: any) => Message.fromDBData(dataMessage, this.members, this.channels)); }, cacheFunc: async () => { - return await DBCache.getMessagesRecent(this.id, channelId, number, this.members, this.channels); + 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); @@ -820,7 +822,7 @@ export default class ClientController extends EventEmitter { return dataMessages.map((dataMessage: any) => Message.fromDBData(dataMessage, this.members, this.channels)); }, cacheFunc: async () => { - return await DBCache.getMessagesBefore(this.id, channelId, messageId, number, this.members, this.channels); + 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); @@ -838,7 +840,7 @@ export default class ClientController extends EventEmitter { 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 DBCache.getMessagesAfter(this.id, channelId, messageId, number, 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); }, @@ -858,7 +860,7 @@ export default class ClientController extends EventEmitter { return resourceCacheDataBuff; } - let cacheData = await DBCache.getResource(this.id, resourceId); + let cacheData = await this.dbCache.getResource(this.id, resourceId); if (cacheData !== null) { ResourceRAMCache.putResource(this.id, resourceId, cacheData.data); return cacheData.data; @@ -868,7 +870,7 @@ export default class ClientController extends EventEmitter { let serverData = await this._queryServer('fetch-resource', resourceId); ResourceRAMCache.putResource(this.id, resourceId, serverData.data); - await DBCache.upsertServerResources(this.id, [ serverData ]); + await this.dbCache.upsertServerResources(this.id, [ serverData ]); return serverData.data; } diff --git a/client/webapp/controller.ts b/client/webapp/controller.ts index c48b333..4aef5e4 100644 --- a/client/webapp/controller.ts +++ b/client/webapp/controller.ts @@ -20,16 +20,18 @@ import { IAddServerData } from './elements/overlay-add-server'; export default class Controller extends EventEmitter { public servers: ClientController[] = []; - constructor() { + constructor( + private dbCache: DBCache + ) { super(); } async _connectFromConfig(serverConfig: ServerConfig): Promise { LOG.debug(`connecting to server#${serverConfig.serverId} at ${serverConfig.url}`); - let server = new ClientController(serverConfig); + let server = new ClientController(this.dbCache, serverConfig); - await DBCache.clearAllMemberStatus(server.id); + await this.dbCache.clearAllMemberStatus(server.id); this.servers.push(server); @@ -61,7 +63,7 @@ export default class Controller extends EventEmitter { async init(): Promise { this.servers = []; - let serverConfigs = await DBCache.getServerConfigs(); + let serverConfigs = await this.dbCache.getServerConfigs(); // TODO: HTML prompt if no server configs if (serverConfigs.length == 0) { @@ -132,13 +134,13 @@ export default class Controller extends EventEmitter { } else { try { let serverConfig: ServerConfig | null = null; - await DBCache.queueTransaction(async () => { + await this.dbCache.queueTransaction(async () => { let publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' }); let privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' }); - let identityId = await DBCache.addIdentity(publicKeyPem, privateKeyPem); - let serverId = await DBCache.addServer(url, cert, name); - await DBCache.addServerIdentity(serverId, identityId); - serverConfig = await DBCache.getServerConfig(serverId, identityId); + let identityId = await this.dbCache.addIdentity(publicKeyPem, privateKeyPem); + let serverId = await this.dbCache.addServer(url, cert, name); + await this.dbCache.addServerIdentity(serverId, identityId); + serverConfig = await this.dbCache.getServerConfig(serverId, identityId); }); if (serverConfig == null) { throw new Error('unable to get server config'); @@ -158,7 +160,7 @@ export default class Controller extends EventEmitter { } async removeServer(server: ClientController): Promise { - await DBCache.removeServer(server.id); + await this.dbCache.removeServer(server.id); this.servers = this.servers.filter(s => s != server); } } diff --git a/client/webapp/db-cache.ts b/client/webapp/db-cache.ts index e1ac31a..12c5a8b 100644 --- a/client/webapp/db-cache.ts +++ b/client/webapp/db-cache.ts @@ -12,30 +12,29 @@ import * as sqlite from 'sqlite'; import * as sqlite3 from 'sqlite3'; import { Message, Member, Channel, Resource, NotInitializedError, ServerMetaData, ServerConfig, CacheServerData } from './data-types'; -let db: sqlite.Database | null = null; - // A cache implemented using an sqlite database // Also stores configuration for server connections export default class DBCache { - static TRANSACTION_QUEUE = new ConcurrentQueue(1); + private TRANSACTION_QUEUE = new ConcurrentQueue(1); - static async beginTransaction(): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - await db.run('BEGIN TRANSACTION'); + private constructor( + private readonly db: sqlite.Database + ) {} + + async beginTransaction(): Promise { + await this.db.run('BEGIN TRANSACTION'); } - static async rollbackTransaction(): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - await db.run('ROLLBACK'); + async rollbackTransaction(): Promise { + await this.db.run('ROLLBACK'); } - static async commitTransaction(): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - await db.run('COMMIT'); + async commitTransaction(): Promise { + await this.db.run('COMMIT'); } - static async queueTransaction(func: (() => Promise)): Promise { - await DBCache.TRANSACTION_QUEUE.push(async () => { + async queueTransaction(func: (() => Promise)): Promise { + await this.TRANSACTION_QUEUE.push(async () => { try { await this.beginTransaction(); await func(); @@ -47,22 +46,21 @@ export default class DBCache { }); } - static async connect(): Promise { + static async connect(): Promise { try { await fs.access('./db'); } catch (e) { await fs.mkdir('./db'); } - db = await sqlite.open({ + return new DBCache(await sqlite.open({ driver: sqlite3.Database, filename: './db/cache.db' - }); + })); } - static async init(): Promise { + async init(): Promise { await this.queueTransaction(async () => { - if (db === null) throw new NotInitializedError('db not initialized'); - await db.run(` + await this.db.run(` CREATE TABLE IF NOT EXISTS identities ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , public_key TEXT NOT NULL @@ -70,7 +68,7 @@ export default class DBCache { ) `); - await db.run(` + await this.db.run(` CREATE TABLE IF NOT EXISTS servers ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , url TEXT NOT NULL @@ -81,7 +79,7 @@ export default class DBCache { ) `); - await db.run(` + await this.db.run(` CREATE TABLE IF NOT EXISTS server_identities ( server_id INTEGER NOT NULL , identity_id INTEGER NOT NULL @@ -90,7 +88,7 @@ export default class DBCache { ) `); - await db.run(` + await this.db.run(` CREATE TABLE IF NOT EXISTS members ( id TEXT NOT NULL , server_id INTEGER NOT NULL REFERENCES servers(id) @@ -105,7 +103,7 @@ export default class DBCache { ) `); - await db.run(` + await this.db.run(` CREATE TABLE IF NOT EXISTS channels ( id TEXT NOT NULL , server_id INTEGER NOT NULL REFERENCES servers(id) @@ -116,7 +114,7 @@ export default class DBCache { ) `); - await db.run(` + await this.db.run(` CREATE TABLE IF NOT EXISTS resources ( id TEXT NOT NULL , server_id INTEGER NOT NULL REFERENCES servers(id) @@ -127,10 +125,10 @@ export default class DBCache { , CONSTRAINT resources_id_server_id_con UNIQUE (id, server_id) ) `); - await db.run('CREATE INDEX IF NOT EXISTS resources_data_size_idx ON resources (data_size)'); + 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 db.run(` + await this.db.run(` CREATE TABLE IF NOT EXISTS messages ( id TEXT NOT NULL , server_id INTEGER NOT NULL REFERENCES servers(id) @@ -146,34 +144,31 @@ export default class DBCache { , CONSTRAINT messages_id_server_id_con UNIQUE (id, server_id) ) `); - await db.run('CREATE INDEX IF NOT EXISTS messages_id_idx ON messages (id)'); - await db.run('CREATE INDEX IF NOT EXISTS messages_sent_dtg_idx ON messages (sent_dtg)'); + 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)'); }); } - static async close(): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - await db.close(); + async close(): Promise { + await this.db.close(); } // dangerous! - static async reset(): Promise { + async reset(): Promise { await this.queueTransaction(async () => { - if (db === null) throw new NotInitializedError('db not initialized'); - await db.run('DROP TABLE IF EXISTS identities'); - await db.run('DROP TABLE IF EXISTS servers'); - await db.run('DROP TABLE IF EXISTS server_identities'); - await db.run('DROP TABLE IF EXISTS members'); - await db.run('DROP TABLE IF EXISTS channels'); - await db.run('DROP TABLE IF EXISTS resources'); - await db.run('DROP TABLE IF EXISTS messages'); + 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 - static async addIdentity(publicKeyPem: string, privateKeyPem: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let result = await db.run(` + 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) { @@ -183,9 +178,8 @@ export default class DBCache { } // returns the id (client-side) of the server inserted - static async addServer(url: string, cert?: string, name?: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let result = await db.run(` + 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) { @@ -194,17 +188,15 @@ export default class DBCache { return result.lastID as number; } - static async removeServer(serverId: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let result = await db.run('DELETE FROM servers WHERE id=?', [ serverId ]); + 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'); } } - static async addServerIdentity(serverId: number, identityId: number): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let result = await db.run(` + 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) { @@ -212,27 +204,24 @@ export default class DBCache { } } - static async updateServer(serverId: string, serverMeta: ServerMetaData): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let result = await db.run('UPDATE servers SET name=?, icon_resource_id=? WHERE id=?', [ serverMeta.name, serverMeta.iconResourceId, serverId ]); + 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'); } } - static async updateServerMemberId(serverId: string, memberId: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let result = await db.run('UPDATE servers SET member_id=? WHERE id=?', [ memberId, serverId ]); + 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}`); } } - static async updateServerMembers(serverId: string, members: Member[]): Promise { + async updateServerMembers(serverId: string, members: Member[]): Promise { await this.queueTransaction(async () => { - if (db === null) throw new NotInitializedError('db not initialized'); - await db.run('DELETE FROM members WHERE server_id=?', [ serverId ]); - let stmt = await db.prepare('INSERT INTO members (id, server_id, display_name, status, avatar_resource_id, role_name, role_color, role_priority, privileges) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'); + 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) { @@ -244,17 +233,15 @@ export default class DBCache { }); } - static async clearAllMemberStatus(serverId: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - await db.run(`UPDATE members SET status='unknown' WHERE server_id=?`, [ serverId ]); + async clearAllMemberStatus(serverId: string): Promise { + await this.db.run(`UPDATE members SET status='unknown' WHERE server_id=?`, [ serverId ]); } - static async updateServerChannels(serverId: string, channels: Channel[]): Promise { + async updateServerChannels(serverId: string, channels: Channel[]): Promise { console.log('setting to ' + channels.length + ' channels'); await this.queueTransaction(async () => { - if (db === null) throw new NotInitializedError('db not initialized'); - await db.run('DELETE FROM channels WHERE server_id=?', [ serverId ]); - let stmt = await db.prepare('INSERT INTO channels (id, server_id, "index", name, flavor_text) VALUES (?, ?, ?, ?, ?)'); + 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) { @@ -267,12 +254,11 @@ export default class DBCache { } // TODO: make this singular and a non-transaction based function? - static async upsertServerResources(serverId: string, resources: Resource[]): Promise { + async upsertServerResources(serverId: string, resources: Resource[]): Promise { await this.queueTransaction(async () => { - if (db === null) throw new NotInitializedError('db not initialized'); - let currentSizeResult = await db.get('SELECT SUM(data_size) AS current_size FROM resources WHERE server_id=?', [ serverId ]); + 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 db.prepare(` + 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 `); @@ -281,8 +267,8 @@ export default class DBCache { continue; } while (resource.data.length + currentSize > Globals.MAX_SERVER_RESOURCE_CACHE_SIZE) { - let targetResult = await db.get('SELECT id, data_size FROM resources ORDER BY last_used ASC LIMIT 1'); - let deleteResult = await db.run('DELETE FROM resources WHERE id=?', [ targetResult.id ]); + 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'); } @@ -298,10 +284,9 @@ export default class DBCache { } // Note: messages are directly from the server response, not parsed - static async upsertServerMessages(serverId: string, channelId: string, messages: Message[]): Promise { + async upsertServerMessages(serverId: string, channelId: string, messages: Message[]): Promise { await this.queueTransaction(async () => { - if (db === null) throw new NotInitializedError('db not initialized'); - let stmt = await db.prepare(` + 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 @@ -326,7 +311,7 @@ export default class DBCache { } await stmt.finalize(); // delete the oldest messages if the cache is too big - await db.run(` + 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) @@ -336,15 +321,13 @@ export default class DBCache { } - static async clearServerMessages(serverId: string, channelId: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - await db.run('DELETE FROM messages WHERE server_id=? AND channel_id=?', [ serverId, channelId ]); + async clearServerMessages(serverId: string, channelId: string): Promise { + await this.db.run('DELETE FROM messages WHERE server_id=? AND channel_id=?', [ serverId, channelId ]); } - static async deleteServerMessages(serverId: string, messageIds: string[]): Promise { + async deleteServerMessages(serverId: string, messageIds: string[]): Promise { await this.queueTransaction(async () => { - if (db === null) throw new NotInitializedError('db not initialized'); - let stmt = await db.prepare('DELETE FROM messages WHERE id=? AND server_id=?'); // include server_id for security purposes + 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) { @@ -355,9 +338,8 @@ export default class DBCache { }); } - static async getServerConfigs(): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let result = await db.all(` + async getServerConfigs(): Promise { + let result = await this.db.all(` SELECT servers.id AS server_id , servers.url AS url @@ -376,9 +358,8 @@ export default class DBCache { return result.map((dataServerConfig: any) => ServerConfig.fromDBData(dataServerConfig)); } - static async getServerConfig(serverId: number, identityId: number): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let result = await db.get(` + async getServerConfig(serverId: number, identityId: number): Promise { + let result = await this.db.get(` SELECT servers.id AS server_id , servers.url AS url @@ -410,9 +391,8 @@ CREATE TABLE IF NOT EXISTS servers ( ) */ - static async getServer(serverId: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let result = await db.get(` + async getServer(serverId: string): Promise { + let result = await this.db.get(` SELECT id, url, cert, name, icon_resource_id, member_id @@ -426,16 +406,14 @@ CREATE TABLE IF NOT EXISTS servers ( return CacheServerData.fromDBData(result); } - static async getServerMemberId(serverId: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let server = await db.get('SELECT member_id FROM servers WHERE id=?', [ serverId ]); + 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 - static async getMembers(serverId: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let members = await db.all('SELECT * FROM members WHERE server_id=?', [ serverId ]); + async getMembers(serverId: string): Promise { + let members = await this.db.all('SELECT * FROM members WHERE server_id=?', [ serverId ]); if (members.length === 0) { return null; } @@ -443,9 +421,8 @@ CREATE TABLE IF NOT EXISTS servers ( } // returns null if no channels - static async getChannels(serverId: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let channels = await db.all('SELECT * FROM channels WHERE server_id=?', [ serverId ]); + async getChannels(serverId: string): Promise { + let channels = await this.db.all('SELECT * FROM channels WHERE server_id=?', [ serverId ]); if (channels.length === 0) { return null; } @@ -453,12 +430,11 @@ CREATE TABLE IF NOT EXISTS servers ( } // returns [] if no messages found - static async getMessagesRecent( + async getMessagesRecent( serverId: string, channelId: string, number: number, members: Map, channels: Map ): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let messages = await db.all(` + let messages = await this.db.all(` SELECT * FROM ( SELECT "id", "channel_id", "member_id" @@ -478,13 +454,12 @@ CREATE TABLE IF NOT EXISTS servers ( } // returns null if no messages found - static async getMessagesBefore( + async getMessagesBefore( serverId: string, channelId: string, messageId: string, number: number, members: Map, channels: Map ): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); // Note: this query succeeds returning no results if the message with specified id is not found - let messages = await db.all(` + let messages = await this.db.all(` SELECT * FROM ( SELECT "id", "channel_id", "member_id" @@ -510,13 +485,12 @@ CREATE TABLE IF NOT EXISTS servers ( } // returns null if no messages found - static async getMessagesAfter( + async getMessagesAfter( serverId: string, channelId: string, messageId: string, number: number, members: Map, channels: Map ): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); // Note: this query succeeds returning no results if the message with specified id is not found - let messages = await db.all(` + let messages = await this.db.all(` SELECT "id", "channel_id", "member_id" , "sent_dtg", "text" @@ -539,10 +513,9 @@ CREATE TABLE IF NOT EXISTS servers ( return messages.map((messageData: any) => Message.fromDBData(messageData, members, channels)); } - static async getResource(serverId: string, resourceId: string): Promise { - if (db === null) throw new NotInitializedError('db not initialized'); - let row = await db.get('SELECT id, data, hash FROM resources WHERE server_id=? AND id=?', [ serverId, resourceId ]); - await db.run('UPDATE resources SET last_used=?1 WHERE server_id=?2 AND id=?3', [ new Date().getTime(), serverId, resourceId ]); + 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; } diff --git a/client/webapp/script.ts b/client/webapp/script.ts index e71cf41..cfe1b76 100644 --- a/client/webapp/script.ts +++ b/client/webapp/script.ts @@ -41,12 +41,12 @@ window.addEventListener('DOMContentLoaded', () => { await LOG.ensureSourceMaps(); LOG.silly('web client log source maps loaded'); - await DBCache.connect(); - await DBCache.init(); + const dbCache = await DBCache.connect(); + await dbCache.init(); LOG.silly('cache initialized'); - const controller = new Controller(); + const controller = new Controller(dbCache); await controller.init(); LOG.silly('controller initialized');