From 97974ada120b92fd7fd4230269d7b7f41fc236b0 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Tue, 28 Jul 2026 22:03:06 +0100 Subject: [PATCH] Add Electrobun configuration and renderer bridge for desktop app. Co-authored-by: Cursor --- electrobun.config.ts | 34 +++++++++++++ src/app/electrobun-bridge.js | 75 ++++++++++++++++++++++++++++ src/{electron => app}/main.jsx | 1 + src/{electron => app}/vite.config.js | 3 +- 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 electrobun.config.ts create mode 100644 src/app/electrobun-bridge.js rename src/{electron => app}/main.jsx (88%) rename src/{electron => app}/vite.config.js (82%) diff --git a/electrobun.config.ts b/electrobun.config.ts new file mode 100644 index 0000000..27b5b27 --- /dev/null +++ b/electrobun.config.ts @@ -0,0 +1,34 @@ +// Electrobun requires this filename; application code remains JavaScript. +export default { + app: { + name: "Farm Control Server", + identifier: "com.tombutcher.farmcontrolserver", + version: "1.0.0", + description: "Farm Control Server desktop host application", + }, + build: { + buildFolder: "build", + artifactFolder: "app_dist", + bun: { + entrypoint: "src/bun/index.js", + }, + copy: { + "dist/app/index.html": "views/mainview/index.html", + "dist/app/assets": "views/mainview/assets", + "config.json": "config.json", + }, + watchIgnore: ["dist/**"], + mac: { + bundleCEF: false, + }, + linux: { + bundleCEF: false, + }, + win: { + bundleCEF: false, + }, + }, + runtime: { + exitOnLastWindowClosed: false, + }, +}; diff --git a/src/app/electrobun-bridge.js b/src/app/electrobun-bridge.js new file mode 100644 index 0000000..d7a5249 --- /dev/null +++ b/src/app/electrobun-bridge.js @@ -0,0 +1,75 @@ +import Electrobun, { Electroview } from "electrobun/view"; + +const listeners = new Map(); + +const rpc = Electroview.defineRPC({ + maxRequestTime: 30000, + handlers: { + requests: {}, + messages: { + "*": (channel, data) => { + const channelListeners = listeners.get(channel); + if (!channelListeners) { + return; + } + + for (const callback of channelListeners) { + callback(data); + } + }, + }, + }, +}); + +const electroview = new Electrobun.Electroview({ rpc }); + +function onIPCData(channel, callback) { + if (!listeners.has(channel)) { + listeners.set(channel, new Set()); + } + listeners.get(channel).add(callback); +} + +function removeAllListeners(channel) { + listeners.delete(channel); +} + +function sendIPC(channel, data) { + if (channel === "getData") { + electroview.rpc.request.getData({}); + return; + } + + if (channel === "authenticateOTP") { + electroview.rpc.request.authenticateOTP({ otp: data }); + return; + } + + if (channel === "window-minimize") { + electroview.rpc.request.minimizeWindow({}); + return; + } + + if (channel === "window-maximize") { + electroview.rpc.request.maximizeWindow({}); + return; + } + + if (channel === "window-close") { + electroview.rpc.request.closeWindow({}); + return; + } + + console.warn(`Unhandled IPC channel: ${channel}`); +} + +window.electronAPI = { + onIPCData, + sendIPC, + minimize: () => sendIPC("window-minimize"), + maximize: () => sendIPC("window-maximize"), + close: () => sendIPC("window-close"), + removeAllListeners, +}; + +export default window.electronAPI; diff --git a/src/electron/main.jsx b/src/app/main.jsx similarity index 88% rename from src/electron/main.jsx rename to src/app/main.jsx index 8ecffbd..263752c 100644 --- a/src/electron/main.jsx +++ b/src/app/main.jsx @@ -1,5 +1,6 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import "./electrobun-bridge.js"; import "./index.css"; import App from "./App.jsx"; import "antd/dist/reset.css"; diff --git a/src/electron/vite.config.js b/src/app/vite.config.js similarity index 82% rename from src/electron/vite.config.js rename to src/app/vite.config.js index ada16e9..bd6a530 100644 --- a/src/electron/vite.config.js +++ b/src/app/vite.config.js @@ -6,10 +6,11 @@ import svgo from "vite-plugin-svgo"; export default defineConfig({ base: "./", + publicDir: path.resolve(__dirname, "public"), plugins: [react(), svgr(), svgo()], root: path.resolve(__dirname), build: { - outDir: path.resolve(__dirname, "../../build/electron"), + outDir: path.resolve(__dirname, "../../dist/app"), emptyOutDir: true, rollupOptions: { input: {