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 socketio from "socket.io-client"; import { Channel, GuildMetadata, Member, Message, Resource, ServerMetaData, Token } from "./data-types"; import Globals from './globals'; import { Connectable, AsyncRequestable, AsyncGuaranteedFetchable } from './guild-types'; import DedupAwaiter from './dedup-awaiter'; import Util from './util'; import SocketVerifier from './socket-verifier'; // Note: you should not be calling the eventemitter functions on outside classes export default class SocketGuild extends Connectable implements AsyncGuaranteedFetchable, AsyncRequestable { private queryDedups = new Map>(); constructor( private socket: socketio.Socket, private verifier: SocketVerifier ) { super(); // TODO: These requests should be 'bindable' through a connectable structure this.socket.on('connect', async () => { this.emit('connect'); }); this.socket.on('disconnect', () => { this.emit('disconnect'); }); // TODO: Make sure the UI updates when we get an added/updated member // TODO: The server does not emit update-message this.socket.on('update-message', async (updatedDataMessage: any) => { let updatedMessage = Message.fromDBData(updatedDataMessage); this.emit('update-messages', [ updatedMessage ]); }); this.socket.on('new-message', async (dataMessage: any) => { let message = Message.fromDBData(dataMessage); this.emit('new-messages', [ message ]); }); this.socket.on('update-member', async (updatedDataMember: any) => { let updatedMember = Member.fromDBData(updatedDataMember); this.emit('update-members', [ updatedMember ]); }); // TODO: The server does not emit new-member this.socket.on('new-member', async (dataMember: any) => { let member = Member.fromDBData(dataMember); this.emit('new-members', [ member ]); }); this.socket.on('update-channel', async (updatedDataChannel: any) => { let updatedChannel = Channel.fromDBData(updatedDataChannel); this.emit('update-channels', [ updatedChannel ]); }); this.socket.on('new-channel', async (dataChannel: any) => { let channel = Channel.fromDBData(dataChannel); this.emit('new-channels', [ channel ]); }); this.socket.on('update-metadata', async (dataMeta: any) => { let guildMeta = GuildMetadata.fromDBData(dataMeta); this.emit('update-metadata', guildMeta); }); } // server helper functions private async query(timeout: number, endpoint: string, ...args: any[]): Promise { await this.verifier.ensureVerified(); return await new Promise((resolve, reject) => { Util.socketEmitTimeout(this.socket, timeout, endpoint, ...args, (errMsg: string | null, serverData: any) => { if (errMsg) { reject(new Error(`error fetching server data @${endpoint} / [${args.map(arg => LOG.inspect(arg)).join(', ')}]: ${errMsg}`)); } else { resolve(serverData); } }); }); } // Note: timeout is only respected for the first deduped request private async queryDedup(timeout: number, endpoint: string, ...args: (string | number)[]): Promise { let id = `query-@${endpoint}(${args.join(',')})`; let dedup = this.queryDedups.get(id); if (!dedup) { dedup = new DedupAwaiter(async () => { let result = await this.query(timeout, endpoint, ...args); this.queryDedups.delete(id); return result; }); } return await dedup.call(); } // connectable functions async fetchMetadata(): Promise { let data = await this.queryDedup(Globals.DEFAULT_SOCKET_TIMEOUT, 'fetch-server'); return GuildMetadata.fromDBData(data); } async fetchMembers(): Promise { let data = await this.queryDedup(Globals.DEFAULT_SOCKET_TIMEOUT, 'fetch-members'); return data.map((dataMember: any) => Member.fromDBData(dataMember)); } async fetchChannels(): Promise { let data = await this.queryDedup(Globals.DEFAULT_SOCKET_TIMEOUT, 'fetch-channels'); return data.map((dataChannel: any) => Channel.fromDBData(dataChannel)); } async fetchMessagesRecent(channelId: string, number: number): Promise { let data = await this.queryDedup(Globals.DEFAULT_SOCKET_TIMEOUT, 'fetch-messages-recent', channelId, number); return data.map((dataMessage: any) => Message.fromDBData(dataMessage)); } async fetchMessagesBefore(channelId: string, messageId: string, number: number): Promise { let data = await this.queryDedup(Globals.DEFAULT_SOCKET_TIMEOUT, 'fetch-messages-before', channelId, messageId, number); return data.map((dataMessage: any) => Message.fromDBData(dataMessage)); } async fetchMessagesAfter(channelId: string, messageId: string, number: number): Promise { let data = await this.queryDedup(Globals.DEFAULT_SOCKET_TIMEOUT, 'fetch-messages-after', channelId, messageId, number); return data.map((dataMessage: any) => Message.fromDBData(dataMessage)); } async fetchResource(resourceId: string): Promise { let data = await this.queryDedup(Globals.DEFAULT_SOCKET_TIMEOUT, 'fetch-resource', resourceId); return Resource.fromDBData(data); } async fetchTokens(): Promise { let data = await this.queryDedup(Globals.DEFAULT_SOCKET_TIMEOUT, 'fetch-tokens'); return data.map((dataToken: any) => Token.fromDBData(dataToken)); } // We don't want to dedup these async requestSendMessage(channelId: string, text: string): Promise { let _dataMessage = await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'send-message', channelId, text); } async requestSendMessageWithResource(channelId: string, text: string | null, resource: Buffer, resourceName: string): Promise { let _dataMessage = await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'send-message-with-resource', channelId, text, resource, resourceName); } async requestSetStatus(status: string): Promise { await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'set-status', status); } async requestSetDisplayName(displayName: string): Promise { await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'set-display-name', displayName); } 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 requestSetServerIcon(serverIcon: Buffer): Promise { await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'set-icon', serverIcon); } async requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise { let _changedChannel = await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'update-channel', channelId, name, flavorText); } async requestDoCreateChannel(name: string, flavorText: string | null): Promise { let _newChannel = await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'create-text-channel', name, flavorText); } async requestDoRevokeToken(token: string): Promise { await this.query(Globals.DEFAULT_SOCKET_TIMEOUT, 'revoke-token', token); } }