Skip to content

Commit

Permalink
feat: ipc controller to register handlers and dispatch events
Browse files Browse the repository at this point in the history
  • Loading branch information
KatoakDR committed Nov 19, 2023
1 parent b6da0df commit 91cec60
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 82 deletions.
2 changes: 1 addition & 1 deletion electron/main/ipc/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './ipc';
export * from './ipc.controller';
export * from './ipc.types';
144 changes: 144 additions & 0 deletions electron/main/ipc/ipc.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { ipcMain } from 'electron';
import { createLogger } from '../logger';
import type { Dispatcher } from '../types';
import type {
IpcHandlerRegistry,
IpcInvokableEvent,
IpcInvokeHandler,
} from './ipc.types';

const logger = createLogger('ipc');

export class IpcController {
private dispatch: Dispatcher;
private ipcHandlerRegistry: IpcHandlerRegistry;

constructor(options: { dispatch: Dispatcher }) {
this.dispatch = options.dispatch;
this.ipcHandlerRegistry = this.createIpcHandlerRegistry();
}

private createIpcHandlerRegistry(): IpcHandlerRegistry {
return {
ping: this.pingHandler,
sgeAddAccount: this.sgeAddAccountHandler,
sgeRemoveAccount: this.sgeRemoveAccountHandler,
sgeListAccounts: this.sgeListAccountsHandler,
sgeListCharacters: this.sgeListCharactersHandler,
gamePlayCharacter: this.gamePlayCharacterHandler,
gameSendCommand: this.gameSendCommandHandler,
};
}

public registerHandlers(): void {
Object.keys(this.ipcHandlerRegistry).forEach((channel) => {
const handler = this.ipcHandlerRegistry[channel as IpcInvokableEvent];

if (!handler) {
logger.error('no handler registered for channel', { channel });
throw new Error(`[IPC:CHANNEL:INVALID] ${channel}`);
}

ipcMain.handle(channel, async (_event, ...params) => {
try {
logger.debug('handling channel request', { channel });
const result = await handler(params as any);
logger.debug('handled channel request', { channel });
return result;
} catch (error) {
logger.error('error handling channel request', { channel, error });
throw new Error(`[IPC:CHANNEL:ERROR] ${channel}: ${error?.message}`);
}
});
});
}

public deregisterHandlers(): void {
Object.keys(this.ipcHandlerRegistry).forEach((channel) => {
ipcMain.removeHandler(channel);
ipcMain.removeAllListeners(channel);
});
}

private pingHandler: IpcInvokeHandler<'ping'> = async (): Promise<string> => {
this.dispatch('window:pong', 'pong2');
return 'pong';
};

private sgeAddAccountHandler: IpcInvokeHandler<'sgeAddAccount'> = async (
args
): Promise<void> => {
const { gameCode, username, password } = args[0];

// TODO
logger.info('sgeAddAccountHandler', { gameCode, username });
};

private sgeRemoveAccountHandler: IpcInvokeHandler<'sgeRemoveAccount'> =
async (args): Promise<void> => {
const { gameCode, username } = args[0];

// TODO
logger.info('sgeRemoveAccountHandler', { gameCode, username });
};

private sgeListAccountsHandler: IpcInvokeHandler<'sgeListAccounts'> = async (
args
): Promise<
Array<{
gameCode: string;
username: string;
}>
> => {
const { gameCode } = args[0];

// TODO
logger.info('sgeListAccountsHandler', { gameCode });

return [];
};

private sgeListCharactersHandler: IpcInvokeHandler<'sgeListCharacters'> =
async (
args
): Promise<
Array<{
id: string;
name: string;
}>
> => {
const { gameCode, username } = args[0];

// TODO
logger.info('sgeListCharactersHandler', { gameCode, username });

return [];
};

private gamePlayCharacterHandler: IpcInvokeHandler<'gamePlayCharacter'> =
async (args): Promise<void> => {
const { gameCode, username, characterName } = args[0];

logger.info('gamePlayCharacterHandler', {
gameCode,
username,
characterName,
});

// TODO look up sge credentials for { gameCode, username }
// TODO use sge service to get character game play credentials
// TODO Game.initInstance({ credentials, dispatch });
// TODO game instance emit data via dispatch function
// TODO renderer listens for game data and updates ui accordingly
};

private gameSendCommandHandler: IpcInvokeHandler<'gameSendCommand'> = async (
args
): Promise<void> => {
const command = args[0];

logger.info('gameSendCommandHandler', { command });

// TODO Game.getInstance().sendCommand(command);
};
}
79 changes: 0 additions & 79 deletions electron/main/ipc/ipc.ts

This file was deleted.

11 changes: 9 additions & 2 deletions electron/main/ipc/ipc.types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
export interface IpcInvokeHandler<K extends keyof AppAPI> {
/**
* Defines the IPC API exposed to the renderer process.
* The main process must provide call-response handlers for this API.
* Excludes the `onMessage` push-style API from main to renderer.
*/
export type IpcInvokableEvent = keyof Omit<AppAPI, 'onMessage'>;

export interface IpcInvokeHandler<K extends IpcInvokableEvent> {
(params: Parameters<AppAPI[K]>): ReturnType<AppAPI[K]>;
}

export type IpcHandlerRegistry = {
[channel in keyof AppAPI]: IpcInvokeHandler<channel>;
[channel in IpcInvokableEvent]: IpcInvokeHandler<channel>;
};
4 changes: 4 additions & 0 deletions electron/main/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Emit a message on a channel and pass arbitrary data.
*/
export type Dispatcher = (channel: string, ...args: Array<unknown>) => void;

0 comments on commit 91cec60

Please sign in to comment.