Skip to content

Commit 91cec60

Browse files
committed
feat: ipc controller to register handlers and dispatch events
1 parent b6da0df commit 91cec60

File tree

5 files changed

+158
-82
lines changed

5 files changed

+158
-82
lines changed

electron/main/ipc/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export * from './ipc';
1+
export * from './ipc.controller';
22
export * from './ipc.types';

electron/main/ipc/ipc.controller.ts

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { ipcMain } from 'electron';
2+
import { createLogger } from '../logger';
3+
import type { Dispatcher } from '../types';
4+
import type {
5+
IpcHandlerRegistry,
6+
IpcInvokableEvent,
7+
IpcInvokeHandler,
8+
} from './ipc.types';
9+
10+
const logger = createLogger('ipc');
11+
12+
export class IpcController {
13+
private dispatch: Dispatcher;
14+
private ipcHandlerRegistry: IpcHandlerRegistry;
15+
16+
constructor(options: { dispatch: Dispatcher }) {
17+
this.dispatch = options.dispatch;
18+
this.ipcHandlerRegistry = this.createIpcHandlerRegistry();
19+
}
20+
21+
private createIpcHandlerRegistry(): IpcHandlerRegistry {
22+
return {
23+
ping: this.pingHandler,
24+
sgeAddAccount: this.sgeAddAccountHandler,
25+
sgeRemoveAccount: this.sgeRemoveAccountHandler,
26+
sgeListAccounts: this.sgeListAccountsHandler,
27+
sgeListCharacters: this.sgeListCharactersHandler,
28+
gamePlayCharacter: this.gamePlayCharacterHandler,
29+
gameSendCommand: this.gameSendCommandHandler,
30+
};
31+
}
32+
33+
public registerHandlers(): void {
34+
Object.keys(this.ipcHandlerRegistry).forEach((channel) => {
35+
const handler = this.ipcHandlerRegistry[channel as IpcInvokableEvent];
36+
37+
if (!handler) {
38+
logger.error('no handler registered for channel', { channel });
39+
throw new Error(`[IPC:CHANNEL:INVALID] ${channel}`);
40+
}
41+
42+
ipcMain.handle(channel, async (_event, ...params) => {
43+
try {
44+
logger.debug('handling channel request', { channel });
45+
const result = await handler(params as any);
46+
logger.debug('handled channel request', { channel });
47+
return result;
48+
} catch (error) {
49+
logger.error('error handling channel request', { channel, error });
50+
throw new Error(`[IPC:CHANNEL:ERROR] ${channel}: ${error?.message}`);
51+
}
52+
});
53+
});
54+
}
55+
56+
public deregisterHandlers(): void {
57+
Object.keys(this.ipcHandlerRegistry).forEach((channel) => {
58+
ipcMain.removeHandler(channel);
59+
ipcMain.removeAllListeners(channel);
60+
});
61+
}
62+
63+
private pingHandler: IpcInvokeHandler<'ping'> = async (): Promise<string> => {
64+
this.dispatch('window:pong', 'pong2');
65+
return 'pong';
66+
};
67+
68+
private sgeAddAccountHandler: IpcInvokeHandler<'sgeAddAccount'> = async (
69+
args
70+
): Promise<void> => {
71+
const { gameCode, username, password } = args[0];
72+
73+
// TODO
74+
logger.info('sgeAddAccountHandler', { gameCode, username });
75+
};
76+
77+
private sgeRemoveAccountHandler: IpcInvokeHandler<'sgeRemoveAccount'> =
78+
async (args): Promise<void> => {
79+
const { gameCode, username } = args[0];
80+
81+
// TODO
82+
logger.info('sgeRemoveAccountHandler', { gameCode, username });
83+
};
84+
85+
private sgeListAccountsHandler: IpcInvokeHandler<'sgeListAccounts'> = async (
86+
args
87+
): Promise<
88+
Array<{
89+
gameCode: string;
90+
username: string;
91+
}>
92+
> => {
93+
const { gameCode } = args[0];
94+
95+
// TODO
96+
logger.info('sgeListAccountsHandler', { gameCode });
97+
98+
return [];
99+
};
100+
101+
private sgeListCharactersHandler: IpcInvokeHandler<'sgeListCharacters'> =
102+
async (
103+
args
104+
): Promise<
105+
Array<{
106+
id: string;
107+
name: string;
108+
}>
109+
> => {
110+
const { gameCode, username } = args[0];
111+
112+
// TODO
113+
logger.info('sgeListCharactersHandler', { gameCode, username });
114+
115+
return [];
116+
};
117+
118+
private gamePlayCharacterHandler: IpcInvokeHandler<'gamePlayCharacter'> =
119+
async (args): Promise<void> => {
120+
const { gameCode, username, characterName } = args[0];
121+
122+
logger.info('gamePlayCharacterHandler', {
123+
gameCode,
124+
username,
125+
characterName,
126+
});
127+
128+
// TODO look up sge credentials for { gameCode, username }
129+
// TODO use sge service to get character game play credentials
130+
// TODO Game.initInstance({ credentials, dispatch });
131+
// TODO game instance emit data via dispatch function
132+
// TODO renderer listens for game data and updates ui accordingly
133+
};
134+
135+
private gameSendCommandHandler: IpcInvokeHandler<'gameSendCommand'> = async (
136+
args
137+
): Promise<void> => {
138+
const command = args[0];
139+
140+
logger.info('gameSendCommandHandler', { command });
141+
142+
// TODO Game.getInstance().sendCommand(command);
143+
};
144+
}

electron/main/ipc/ipc.ts

-79
This file was deleted.

electron/main/ipc/ipc.types.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
export interface IpcInvokeHandler<K extends keyof AppAPI> {
1+
/**
2+
* Defines the IPC API exposed to the renderer process.
3+
* The main process must provide call-response handlers for this API.
4+
* Excludes the `onMessage` push-style API from main to renderer.
5+
*/
6+
export type IpcInvokableEvent = keyof Omit<AppAPI, 'onMessage'>;
7+
8+
export interface IpcInvokeHandler<K extends IpcInvokableEvent> {
29
(params: Parameters<AppAPI[K]>): ReturnType<AppAPI[K]>;
310
}
411

512
export type IpcHandlerRegistry = {
6-
[channel in keyof AppAPI]: IpcInvokeHandler<channel>;
13+
[channel in IpcInvokableEvent]: IpcInvokeHandler<channel>;
714
};

electron/main/types.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* Emit a message on a channel and pass arbitrary data.
3+
*/
4+
export type Dispatcher = (channel: string, ...args: Array<unknown>) => void;

0 commit comments

Comments
 (0)