From c2ae24dcbc8403a98e2648e3686ab478acb30767 Mon Sep 17 00:00:00 2001 From: mato533 <35281743+mato533@users.noreply.github.com> Date: Mon, 21 Oct 2024 01:31:14 +0900 Subject: [PATCH 1/5] feat: Make channelmap a singleton --- src/channel.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/channel.ts b/src/channel.ts index dd4274f..688ffd6 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -42,10 +42,16 @@ export type ApiChannelMapGenerator = { invoke: ApiChannelMapItemTypeGenerator } +let channelMap = undefined + function getApiChannelMap( apiHandlers: T ): ApiChannelMapGenerator function getApiChannelMap(apiHandlers: IpcBridgeApiImplementation) { + if (channelMap) { + return channelMap + } + const _getApiChannelMap = (apiHandler: ApiHandler) => { const channelMap: ApiChannelMapItem = {} Object.keys(apiHandler).forEach((key) => { @@ -57,7 +63,8 @@ function getApiChannelMap(apiHandlers: IpcBridgeApiImplementation) { }) return channelMap } - return _getApiChannelMap(apiHandlers) + channelMap = _getApiChannelMap(apiHandlers) + return channelMap } const MODE = { From 9e08173cf69df6bda3c9290b4cdb0f92c6f54421 Mon Sep 17 00:00:00 2001 From: mato533 <35281743+mato533@users.noreply.github.com> Date: Mon, 21 Oct 2024 01:40:11 +0900 Subject: [PATCH 2/5] feat: Split API calls for renderer from main Calls from the renderer to main are still handled by registerIpcHandler. On the other hand, change the call from main to the renderer to be independent as a function that returns the emitter. BREAKING CHANGE: change the call from main to the renderer to be independent as a function that returns the emitter.(getIpcApiEmitter) --- src/main.ts | 34 ++++++++++++++++++++++++---------- src/types/index.d.ts | 11 ++++++----- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/main.ts b/src/main.ts index 4bac53b..c1e2e72 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,12 +82,13 @@ 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 case MODE.on: { const _api = api[key] as ApiOnFunction + console.log(`=====${key}`) sender[senderKey] = (window: BrowserWindow, ...args: Parameters) => { window.webContents.send(apiInfo[key], _api(...args)) } @@ -95,8 +107,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 From 339636df42e3e160c1968ab95339ae0283b71257 Mon Sep 17 00:00:00 2001 From: mato533 <35281743+mato533@users.noreply.github.com> Date: Mon, 21 Oct 2024 01:41:00 +0900 Subject: [PATCH 3/5] test: update tests about getIpcApiEmitter --- src/__tests__/main.spec.ts | 4 ++-- src/__tests__/types.spec.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/__tests__/main.spec.ts b/src/__tests__/main.spec.ts index 8eda021..97f0a8b 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 { getIpcApiEmitter, registerIpcHandler } from '../main' import type { BrowserWindow, IpcMainInvokeEvent } from 'electron' @@ -95,7 +95,7 @@ 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) 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 From 0c9f4ea487a415eaf1f4e3a4a012c645d8bf28f5 Mon Sep 17 00:00:00 2001 From: mato533 <35281743+mato533@users.noreply.github.com> Date: Mon, 21 Oct 2024 01:41:26 +0900 Subject: [PATCH 4/5] docs: update playglound source code about getIpcApiEmitter --- playground/src/main/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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) From 8ece6775c6d60f39f175ba73009756b0e09ffbfb Mon Sep 17 00:00:00 2001 From: mato533 <35281743+mato533@users.noreply.github.com> Date: Mon, 21 Oct 2024 02:44:52 +0900 Subject: [PATCH 5/5] fix: add compare chanelmap structure --- src/__tests__/main.spec.ts | 8 ++------ src/channel.ts | 23 ++++++++++++++++++++++- src/main.ts | 1 - 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/__tests__/main.spec.ts b/src/__tests__/main.spec.ts index 97f0a8b..713c187 100644 --- a/src/__tests__/main.spec.ts +++ b/src/__tests__/main.spec.ts @@ -1,4 +1,4 @@ -import { API_CHANNEL_MAP } from '../channel' +import { API_CHANNEL_MAP, getApiChannelMap } from '../channel' import { getIpcApiEmitter, registerIpcHandler } from '../main' import type { BrowserWindow, IpcMainInvokeEvent } from 'electron' @@ -97,11 +97,7 @@ describe('main', () => { } as unknown as BrowserWindow 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/channel.ts b/src/channel.ts index 688ffd6..7163043 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -44,11 +44,32 @@ export type ApiChannelMapGenerator = { 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) { + if (channelMap && haveSameStructure(apiHandlers, channelMap)) { return channelMap } diff --git a/src/main.ts b/src/main.ts index c1e2e72..7ef5a38 100644 --- a/src/main.ts +++ b/src/main.ts @@ -88,7 +88,6 @@ function createhandler(ipcBridgeApi: IpcBridgeApiImplementation, mode: ApiMode) break case MODE.on: { const _api = api[key] as ApiOnFunction - console.log(`=====${key}`) sender[senderKey] = (window: BrowserWindow, ...args: Parameters) => { window.webContents.send(apiInfo[key], _api(...args)) }