From a3de71d939ccc3e9252499f97c0a529f5560315b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Dec 2025 16:57:08 +0100 Subject: [PATCH] Fix RPC import issue preventing Host input field from appearing in Electron mode (#991) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: thomasnordquist <7721625+thomasnordquist@users.noreply.github.com> Co-authored-by: Thomas Nordquist --- app/src/actions/Connection.ts | 2 +- app/src/actions/ConnectionManager.ts | 2 +- app/src/actions/Publish.ts | 2 +- app/src/actions/clearTopic.ts | 2 +- .../BrowserCertificateFileSelection.tsx | 2 +- .../ConnectionSetup/ConnectionSettings.tsx | 1 + app/src/components/UpdateNotifier.tsx | 2 +- app/src/components/helper/Save.tsx | 2 +- app/src/eventBus.ts | 134 ++++++++++++++++++ app/src/utils/PersistentStorage.ts | 2 +- events/index.ts | 5 +- 11 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 app/src/eventBus.ts diff --git a/app/src/actions/Connection.ts b/app/src/actions/Connection.ts index 904540b..3ce67b8 100644 --- a/app/src/actions/Connection.ts +++ b/app/src/actions/Connection.ts @@ -9,7 +9,7 @@ import { globalActions } from '.' import { resetStore as resetTreeStore, showTree } from './Tree' import { showError } from './Global' import { TopicViewModel } from '../model/TopicViewModel' -import { addMqttConnectionEvent, makeConnectionStateEvent, removeConnection, rendererEvents } from '../../../events' +import { addMqttConnectionEvent, makeConnectionStateEvent, removeConnection, rendererEvents } from '../eventBus' export const connect = (options: MqttOptions, connectionId: string) => (dispatch: Dispatch, getState: () => AppState) => { diff --git a/app/src/actions/ConnectionManager.ts b/app/src/actions/ConnectionManager.ts index f9924b3..151f1d9 100644 --- a/app/src/actions/ConnectionManager.ts +++ b/app/src/actions/ConnectionManager.ts @@ -13,7 +13,7 @@ import * as path from 'path' import { ActionTypes, Action } from '../reducers/ConnectionManager' import { Subscription } from '../../../backend/src/DataSource/MqttSource' import { connectionsMigrator } from './migrations/Connection' -import { rendererRpc, readFromFile } from '../../../events' +import { rendererRpc, readFromFile } from '../eventBus' import { makeOpenDialogRpc } from '../../../events/OpenDialogRequest' export interface ConnectionDictionary { diff --git a/app/src/actions/Publish.ts b/app/src/actions/Publish.ts index 3a28c9a..cd18fa5 100644 --- a/app/src/actions/Publish.ts +++ b/app/src/actions/Publish.ts @@ -2,7 +2,7 @@ import { Action, ActionTypes } from '../reducers/Publish' import { AppState } from '../reducers' import { Base64Message } from '../../../backend/src/Model/Base64Message' import { Dispatch } from 'redux' -import { MqttMessage, makePublishEvent, rendererEvents, rendererRpc, readFromFile } from '../../../events' +import { MqttMessage, makePublishEvent, rendererEvents, rendererRpc, readFromFile } from '../eventBus' import { makeOpenDialogRpc } from '../../../events/OpenDialogRequest' import { showError } from './Global' import { Base64 } from 'js-base64' diff --git a/app/src/actions/clearTopic.ts b/app/src/actions/clearTopic.ts index 14eef38..a61d0f5 100644 --- a/app/src/actions/clearTopic.ts +++ b/app/src/actions/clearTopic.ts @@ -1,7 +1,7 @@ import * as q from '../../../backend/src/Model' import { AppState } from '../reducers' import { Dispatch } from 'redux' -import { makePublishEvent, rendererEvents } from '../../../events' +import { makePublishEvent, rendererEvents } from '../eventBus' import { moveSelectionUpOrDownwards } from './visibleTreeTraversal' import { globalActions } from '.' diff --git a/app/src/components/ConnectionSetup/BrowserCertificateFileSelection.tsx b/app/src/components/ConnectionSetup/BrowserCertificateFileSelection.tsx index 89a2556..2faede3 100644 --- a/app/src/components/ConnectionSetup/BrowserCertificateFileSelection.tsx +++ b/app/src/components/ConnectionSetup/BrowserCertificateFileSelection.tsx @@ -8,7 +8,7 @@ import { CertificateTypes } from '../../actions/ConnectionManager' import { connect } from 'react-redux' import { connectionManagerActions } from '../../actions' import { withStyles } from '@mui/styles' -import { rendererRpc } from '../../../../events' +import { rendererRpc } from '../../eventBus' import { RpcEvents } from '../../../../events/EventsV2' function BrowserCertificateFileSelection(props: { diff --git a/app/src/components/ConnectionSetup/ConnectionSettings.tsx b/app/src/components/ConnectionSetup/ConnectionSettings.tsx index 73bf4b4..4dcf661 100644 --- a/app/src/components/ConnectionSetup/ConnectionSettings.tsx +++ b/app/src/components/ConnectionSetup/ConnectionSettings.tsx @@ -188,6 +188,7 @@ function ConnectionSettings(props: Props) { value={connection.host} onChange={handleChange('host')} margin="normal" + inputProps={{ 'data-testid': 'host-input' }} /> diff --git a/app/src/components/UpdateNotifier.tsx b/app/src/components/UpdateNotifier.tsx index 526679d..39bf50c 100644 --- a/app/src/components/UpdateNotifier.tsx +++ b/app/src/components/UpdateNotifier.tsx @@ -13,7 +13,7 @@ import { withStyles } from '@mui/styles' import { updateNotifierActions } from '../actions' import { Button, IconButton, Modal, Paper, Snackbar, SnackbarContent, Typography } from '@mui/material' -import { rendererRpc, getAppVersion } from '../../../events' +import { rendererRpc, getAppVersion } from '../eventBus' interface Props { showUpdateNotification: boolean diff --git a/app/src/components/helper/Save.tsx b/app/src/components/helper/Save.tsx index 2b4f938..920eec7 100644 --- a/app/src/components/helper/Save.tsx +++ b/app/src/components/helper/Save.tsx @@ -5,7 +5,7 @@ import CustomIconButton from './CustomIconButton' import { SaveAlt } from '@mui/icons-material' import { bindActionCreators } from 'redux' -import { rendererRpc, writeToFile } from '../../../../events' +import { rendererRpc, writeToFile } from '../../eventBus' import { makeSaveDialogRpc } from '../../../../events/OpenDialogRequest' import { globalActions } from '../../actions' diff --git a/app/src/eventBus.ts b/app/src/eventBus.ts new file mode 100644 index 0000000..2f9342b --- /dev/null +++ b/app/src/eventBus.ts @@ -0,0 +1,134 @@ +/** + * Event bus abstraction layer + * Provides the correct rendererRpc and rendererEvents implementation based on runtime environment + * - In browser mode: uses Socket.IO-based event bus + * - In Electron mode: uses IPC-based event bus + * + * This module uses dynamic imports to avoid bundling unused dependencies. + */ + +import { isBrowserMode } from './utils/browserMode' +import type { Rpc } from '../../events/EventSystem/Rpc' +import type { EventBusInterface } from '../../events/EventSystem/EventBusInterface' + +let rendererRpcInstance: Rpc | null = null +let rendererEventsInstance: EventBusInterface | null = null +let backendRpcInstance: Rpc | null = null +let backendEventsInstance: EventBusInterface | null = null + +/** + * Get the renderer RPC instance + * Lazy-loads the appropriate implementation based on environment + */ +export function getRendererRpc(): Rpc { + if (rendererRpcInstance) { + return rendererRpcInstance + } + + if (isBrowserMode) { + // Dynamic import for browser mode + const browserEventBus = require('./browserEventBus') + rendererRpcInstance = browserEventBus.rendererRpc + } else { + // Dynamic import for Electron mode + const electronEventBus = require('../../events/EventSystem/EventBus') + rendererRpcInstance = electronEventBus.rendererRpc + } + + return rendererRpcInstance +} + +/** + * Get the renderer events instance + * Lazy-loads the appropriate implementation based on environment + */ +export function getRendererEvents(): EventBusInterface { + if (rendererEventsInstance) { + return rendererEventsInstance + } + + if (isBrowserMode) { + // Dynamic import for browser mode + const browserEventBus = require('./browserEventBus') + rendererEventsInstance = browserEventBus.rendererEvents + } else { + // Dynamic import for Electron mode + const electronEventBus = require('../../events/EventSystem/EventBus') + rendererEventsInstance = electronEventBus.rendererEvents + } + + return rendererEventsInstance +} + +/** + * Get the backend RPC instance (for compatibility) + */ +export function getBackendRpc(): Rpc { + if (backendRpcInstance) { + return backendRpcInstance + } + + if (isBrowserMode) { + // In browser mode, backend is accessed via socket.io + const browserEventBus = require('./browserEventBus') + backendRpcInstance = browserEventBus.backendRpc + } else { + // In Electron mode, backend RPC uses IPC + const electronEventBus = require('../../events/EventSystem/EventBus') + backendRpcInstance = electronEventBus.backendRpc + } + + return backendRpcInstance +} + +/** + * Get the backend events instance (for compatibility) + */ +export function getBackendEvents(): EventBusInterface { + if (backendEventsInstance) { + return backendEventsInstance + } + + if (isBrowserMode) { + // In browser mode, backend is accessed via socket.io + const browserEventBus = require('./browserEventBus') + backendEventsInstance = browserEventBus.backendEvents + } else { + // In Electron mode, backend events use IPC + const electronEventBus = require('../../events/EventSystem/EventBus') + backendEventsInstance = electronEventBus.backendEvents + } + + return backendEventsInstance +} + +// Export as named constants for convenience (lazy-loaded on first access) +export const rendererRpc = new Proxy({} as Rpc, { + get(target, prop) { + return getRendererRpc()[prop as keyof Rpc] + } +}) + +export const rendererEvents = new Proxy({} as EventBusInterface, { + get(target, prop) { + return getRendererEvents()[prop as keyof EventBusInterface] + } +}) + +export const backendRpc = new Proxy({} as Rpc, { + get(target, prop) { + return getBackendRpc()[prop as keyof Rpc] + } +}) + +export const backendEvents = new Proxy({} as EventBusInterface, { + get(target, prop) { + return getBackendEvents()[prop as keyof EventBusInterface] + } +}) + +// Re-export all event definitions that are shared +export * from '../../events/Events' +export * from '../../events/EventsV2' +export * from '../../events/EventSystem/EventDispatcher' +export * from '../../events/EventSystem/EventBusInterface' diff --git a/app/src/utils/PersistentStorage.ts b/app/src/utils/PersistentStorage.ts index 0c91bb5..7116719 100644 --- a/app/src/utils/PersistentStorage.ts +++ b/app/src/utils/PersistentStorage.ts @@ -1,4 +1,4 @@ -import { rendererRpc } from '../../../events' +import { rendererRpc } from '../eventBus' import { storageStoreEvent, storageLoadEvent, storageClearEvent } from '../../../events/StorageEvents' diff --git a/events/index.ts b/events/index.ts index c78b36f..615ccf9 100644 --- a/events/index.ts +++ b/events/index.ts @@ -2,7 +2,6 @@ export * from './Events' export * from './EventsV2' export * from './EventSystem/EventDispatcher' // EventBus exports removed - this file contains Electron-specific imports -// which should not be loaded in server/browser mode -// Electron code should import directly from './EventSystem/EventBus' -// export * from './EventSystem/EventBus' +// In Electron mode, webpack replaces '../../../events' to use './EventSystem/EventBus' +// In browser mode, webpack replaces '../../../events' to use browserEventBus.ts export * from './EventSystem/EventBusInterface'