/
createIPCHandler.ts
81 lines (70 loc) · 2.32 KB
/
createIPCHandler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import type { AnyRouter, inferRouterContext } from '@trpc/server';
import { ipcMain } from 'electron';
import type { BrowserWindow, IpcMainEvent } from 'electron';
import { handleIPCMessage } from './handleIPCMessage';
import { CreateContextOptions } from './types';
import { ELECTRON_TRPC_CHANNEL } from '../constants';
import { ETRPCRequest } from '../types';
import { Unsubscribable } from '@trpc/server/observable';
type Awaitable<T> = T | Promise<T>;
const getInternalId = (event: IpcMainEvent, request: ETRPCRequest) => {
const messageId = request.method === 'request' ? request.operation.id : request.id;
return `${event.sender.id}-${event.senderFrame.routingId}:${messageId}`;
};
class IPCHandler<TRouter extends AnyRouter> {
#windows: BrowserWindow[] = [];
#subscriptions: Map<string, Unsubscribable> = new Map();
constructor({
createContext,
router,
windows = [],
}: {
createContext?: (opts: CreateContextOptions) => Awaitable<inferRouterContext<TRouter>>;
router: TRouter;
windows?: BrowserWindow[];
}) {
windows.forEach((win) => this.attachWindow(win));
ipcMain.on(ELECTRON_TRPC_CHANNEL, (event: IpcMainEvent, request: ETRPCRequest) => {
handleIPCMessage({
router,
createContext,
internalId: getInternalId(event, request),
event,
message: request,
subscriptions: this.#subscriptions,
});
});
}
attachWindow(win: BrowserWindow) {
if (this.#windows.includes(win)) {
return;
}
this.#windows.push(win);
this.#attachSubscriptionCleanupHandler(win);
}
detachWindow(win: BrowserWindow) {
this.#windows = this.#windows.filter((w) => w !== win);
for (const [key, sub] of this.#subscriptions.entries()) {
if (key.startsWith(`${win.webContents.id}-`)) {
sub.unsubscribe();
this.#subscriptions.delete(key);
}
}
}
#attachSubscriptionCleanupHandler(win: BrowserWindow) {
win.webContents.on('destroyed', () => {
this.detachWindow(win);
});
}
}
export const createIPCHandler = <TRouter extends AnyRouter>({
createContext,
router,
windows = [],
}: {
createContext?: (opts: CreateContextOptions) => Promise<inferRouterContext<TRouter>>;
router: TRouter;
windows?: Electron.BrowserWindow[];
}) => {
return new IPCHandler({ createContext, router, windows });
};