diff --git a/playground/src/main/index.ts b/playground/src/main/index.ts index b3665eb..44563f7 100644 --- a/playground/src/main/index.ts +++ b/playground/src/main/index.ts @@ -2,7 +2,7 @@ import { join } from 'node:path' import { app, shell, BrowserWindow } from 'electron' import { electronApp, optimizer, is } from '@electron-toolkit/utils' -import { registerIpcHandler } from 'electron-typed-ipc-bridge/main' +import { getIpcApiEmitter, registerIpcHandler } from 'electron-typed-ipc-bridge/main' import icon from '../../resources/icon.png?asset' import { api } from './api' @@ -59,7 +59,9 @@ app.whenReady().then(() => { }) // IPC test - const ipcApi = registerIpcHandler(api) + registerIpcHandler(api) + + const ipcApi = getIpcApiEmitter(api) createWindow(ipcApi) diff --git a/src/__tests__/main.spec.ts b/src/__tests__/main.spec.ts index 8eda021..713c187 100644 --- a/src/__tests__/main.spec.ts +++ b/src/__tests__/main.spec.ts @@ -1,5 +1,5 @@ -import { API_CHANNEL_MAP } from '../channel' -import { registerIpcHandler } from '../main' +import { API_CHANNEL_MAP, getApiChannelMap } from '../channel' +import { getIpcApiEmitter, registerIpcHandler } from '../main' import type { BrowserWindow, IpcMainInvokeEvent } from 'electron' @@ -95,13 +95,9 @@ describe('main', () => { send: mockSend, }, } as unknown as BrowserWindow - const api = registerIpcHandler(_apiHandlers) + const api = getIpcApiEmitter(_apiHandlers) - const lastArgs = mocks.ipcMain.handle.mock.calls[0] - expect(lastArgs[0]).toBe(API_CHANNEL_MAP) - - const ipcBridgeApiChannelGetter = lastArgs[1] - const channelMap = ipcBridgeApiChannelGetter() + const channelMap = getApiChannelMap(_apiHandlers) api.send.fn2(browserWindow, 1, 2) expect(mockSend).toBeCalled() diff --git a/src/__tests__/types.spec.ts b/src/__tests__/types.spec.ts index 4c6d146..f8e573d 100644 --- a/src/__tests__/types.spec.ts +++ b/src/__tests__/types.spec.ts @@ -1,7 +1,7 @@ import type { BrowserWindow, IpcMainInvokeEvent, IpcRendererEvent } from 'electron' import type { RemoveEventArg, IpcBridgeApiTypeGenerator, IpcBridgeApiInvoker } from '../preload' import type { ApiChannelMapGenerator } from '../channel' -import type { IpcBridgeApiSenderTypeGenerator } from '../main' +import type { IpcBridgeApiEmitterTypeGenerator } from '../main' describe('Type check', () => { it('RemoveEventArg', () => { @@ -83,7 +83,7 @@ describe('Type check', () => { }, } - type TestTarget = IpcBridgeApiSenderTypeGenerator + type TestTarget = IpcBridgeApiEmitterTypeGenerator type ExpectedType = { send: { fn1: (window: BrowserWindow, arg1: string) => void @@ -105,7 +105,7 @@ describe('Type check', () => { }, } - type TestTarget = IpcBridgeApiSenderTypeGenerator + type TestTarget = IpcBridgeApiEmitterTypeGenerator expectTypeOf().toEqualTypeOf() }) @@ -122,7 +122,7 @@ describe('Type check', () => { }, } - type TestTarget = IpcBridgeApiSenderTypeGenerator + type TestTarget = IpcBridgeApiEmitterTypeGenerator type ExpectedType = { send: { fn1: (window: BrowserWindow, arg1: string) => void diff --git a/src/channel.ts b/src/channel.ts index dd4274f..7163043 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -42,10 +42,37 @@ export type ApiChannelMapGenerator = { invoke: ApiChannelMapItemTypeGenerator } +let channelMap = undefined + +function haveSameStructure(obj1, obj2) { + if (!obj1 || !obj2) { + return false + } + if (typeof obj1 !== 'object' && typeof obj2 !== 'object') { + return true + } + if (typeof obj1 !== 'object' || typeof obj2 !== 'object') { + return false + } + + const keys1 = [...Object.keys(obj1)] + const keys2 = [...Object.keys(obj2)] + + if (keys1.length !== keys2.length) { + return false + } + + return keys1.every((key) => keys2.includes(key) && haveSameStructure(obj1[key], obj2[key])) +} + function getApiChannelMap( apiHandlers: T ): ApiChannelMapGenerator function getApiChannelMap(apiHandlers: IpcBridgeApiImplementation) { + if (channelMap && haveSameStructure(apiHandlers, channelMap)) { + return channelMap + } + const _getApiChannelMap = (apiHandler: ApiHandler) => { const channelMap: ApiChannelMapItem = {} Object.keys(apiHandler).forEach((key) => { @@ -57,7 +84,8 @@ function getApiChannelMap(apiHandlers: IpcBridgeApiImplementation) { }) return channelMap } - return _getApiChannelMap(apiHandlers) + channelMap = _getApiChannelMap(apiHandlers) + return channelMap } const MODE = { diff --git a/src/main.ts b/src/main.ts index 4bac53b..7ef5a38 100644 --- a/src/main.ts +++ b/src/main.ts @@ -27,7 +27,7 @@ type IpcBridgeApiSenderTypeConverter = { : never } -export type IpcBridgeApiSenderTypeGenerator = +export type IpcBridgeApiEmitterTypeGenerator = 'on' extends keyof T ? T['on'] extends undefined ? undefined @@ -38,13 +38,21 @@ export type IpcBridgeApiSenderTypeGenerator( - ipcBridgeApi: T -): IpcBridgeApiSenderTypeGenerator +function registerIpcHandler(ipcBridgeApi: T): void function registerIpcHandler(ipcBridgeApi: IpcBridgeApiImplementation) { + createhandler(ipcBridgeApi, MODE.invoke) +} + +function getIpcApiEmitter( + ipcBridgeApi: T +): IpcBridgeApiEmitterTypeGenerator +function getIpcApiEmitter(ipcBridgeApi: IpcBridgeApiImplementation) { + return createhandler(ipcBridgeApi, MODE.on) +} +function createhandler(ipcBridgeApi: IpcBridgeApiImplementation, mode: ApiMode) { const channelMap = getApiChannelMap(ipcBridgeApi) - let mode: ApiMode + let _mode: ApiMode const _registerIpcHandler = (api: ApiHandler = ipcBridgeApi, apiInfo = channelMap, level = 0) => { const keys = Object.keys(apiInfo) if (level === 0) { @@ -53,13 +61,16 @@ function registerIpcHandler(ipcBridgeApi: IpcBridgeApiImplementation) { const sender = {} keys.forEach((key) => { if (level === 0) { - mode = MODE[key] + _mode = MODE[key] + if (MODE[key] !== mode) { + return + } } const senderKey = level === 0 ? 'send' : key if (typeof apiInfo[key] === 'object' && !isApiFunction(api[key])) { console.debug(`${' '.repeat(level)} - ${key}`) - switch (mode) { + switch (_mode) { case MODE.invoke: _registerIpcHandler(api[key], apiInfo[key], level + 1) break @@ -71,7 +82,7 @@ function registerIpcHandler(ipcBridgeApi: IpcBridgeApiImplementation) { } } else if (typeof apiInfo[key] !== 'object' && isApiFunction(api[key])) { console.debug(`${' '.repeat(level)} - ${key} (chanel: ${apiInfo[key]})`) - switch (mode) { + switch (_mode) { case MODE.invoke: ipcMain.handle(apiInfo[key], api[key]) break @@ -95,8 +106,10 @@ function registerIpcHandler(ipcBridgeApi: IpcBridgeApiImplementation) { } return sender } - ipcMain.handle(API_CHANNEL_MAP, () => channelMap) + if (mode === MODE.invoke) { + ipcMain.handle(API_CHANNEL_MAP, () => channelMap) + } return _registerIpcHandler() } -export { registerIpcHandler } +export { registerIpcHandler, getIpcApiEmitter } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 4bdd286..7652cb4 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,5 +1,5 @@ import type { IpcContextBridgeApi } from '../preload' -import type { IpcBridgeApiSenderTypeGenerator } from '../main' +import type { IpcBridgeApiEmitterTypeGenerator } from '../main' import type { IpcBridgeApiImplementation } from '../channel' /** @@ -12,7 +12,7 @@ export type { IpcBridgeApiTypeGenerator } from '../preload' * Type generator for api to send message from main to renderer * Use at the main process */ -export type { IpcBridgeApiSenderTypeGenerator } from '../main' +export type { IpcBridgeApiEmitterTypeGenerator as IpcBridgeApiSenderTypeGenerator } from '../main' /** * Resister IPC handler(for tow way) @@ -20,10 +20,11 @@ export type { IpcBridgeApiSenderTypeGenerator } from '../main' * Use at the main process * @param ipcBridgeApi Implementation for IPC api handlers */ -export function registerIpcHandler( - ipcBridgeApi: T -): IpcBridgeApiSenderTypeGenerator +export function registerIpcHandler(ipcBridgeApi: T): void +export function getIpcApiEmitter( + ipcBridgeApi: T +): IpcBridgeApiEmitterTypeGenerator /** * Generate IPC api that will be exposed to renderer process. * Use at the preload sctipt