Compare commits

..

7 Commits

Author SHA1 Message Date
fa389e3eb7 Update CI pipeline, documentation, and config for Bun/Electrobun.
Some checks failed
farmcontrol/farmcontrol-server/pipeline/head There was a failure building this commit
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 22:03:23 +01:00
8453e7c7cf Switch package manager and runtime from Node/pnpm to Bun.
Update build scripts, entry points, and packaged-path detection for the new toolchain.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 22:03:20 +01:00
07cbd2e07c Simplify PDF utils dynamic imports for Bun runtime.
Remove pkg bytecode workarounds that are no longer needed outside Node pkg builds.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 22:03:14 +01:00
7d8031e009 Route IPC through desktop notify and remove CLI OTP prompting.
OTP authentication is now handled by the desktop UI instead of stdin prompts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 22:03:11 +01:00
97974ada12 Add Electrobun configuration and renderer bridge for desktop app.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 22:03:06 +01:00
f0b6059e70 Add desktop RPC layer replacing Electron IPC and window management.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 22:03:03 +01:00
1728c08e39 Rename UI source directory from src/electron to src/app.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 22:02:57 +01:00
117 changed files with 2416 additions and 10395 deletions

6
.gitignore vendored
View File

@ -141,4 +141,8 @@ data/*
dist/* dist/*
src/buildInfo.json src/buildInfo.json
.electrobun-cache
artifacts
app_dist
build

6
.npmrc
View File

@ -1,6 +0,0 @@
public-hoist-pattern[]=pdf-to-img
public-hoist-pattern[]=pdfjs-dist
public-hoist-pattern[]=sharp
public-hoist-pattern[]=@img/*
public-hoist-pattern[]=canvas
public-hoist-pattern[]=@napi-rs/*

84
Jenkinsfile vendored
View File

@ -3,16 +3,14 @@ properties([
]) ])
def writeBuildMetadata() { def writeBuildMetadata() {
nodejs(nodeJSInstallationName: 'Node23') { if (isUnix()) {
if (isUnix()) { sh '''
sh ''' bun --eval "await Bun.write('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n')"
node -e "const fs = require('fs'); fs.writeFileSync('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n');" '''
''' } else {
} else { bat '''
bat ''' bun --eval "await Bun.write('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n')"
node -e "const fs = require('fs'); fs.writeFileSync('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n');" '''
'''
}
} }
} }
@ -23,17 +21,12 @@ def buildLinux() {
checkout scm checkout scm
} }
stage('Setup Node.js (Ubuntu)') { stage('Setup Bun (Ubuntu)') {
nodejs(nodeJSInstallationName: 'Node23') { sh 'bun -v'
sh 'node -v'
sh 'pnpm -v'
}
} }
stage('Install Dependencies (Ubuntu)') { stage('Install Dependencies (Ubuntu)') {
nodejs(nodeJSInstallationName: 'Node23') { sh 'bun install --frozen-lockfile'
sh 'pnpm install --frozen-lockfile --production=false'
}
} }
stage('Write Build Metadata (Ubuntu)') { stage('Write Build Metadata (Ubuntu)') {
@ -41,9 +34,7 @@ def buildLinux() {
} }
stage('Build (Ubuntu)') { stage('Build (Ubuntu)') {
nodejs(nodeJSInstallationName: 'Node23') { sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production bun run build:linux"
sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production pnpm build:linux"
}
} }
stage('Archive Artifacts (Ubuntu)') { stage('Archive Artifacts (Ubuntu)') {
@ -62,25 +53,19 @@ def buildOnLabel(label, buildCommand) {
checkout scm checkout scm
} }
stage("Setup Node.js (${label})") { stage("Setup Bun (${label})") {
nodejs(nodeJSInstallationName: 'Node23') { if (isUnix()) {
if (isUnix()) { sh 'bun -v'
sh 'node -v' } else {
sh 'pnpm -v' bat 'bun -v'
} else {
bat 'node -v'
bat 'pnpm -v'
}
} }
} }
stage("Install Dependencies (${label})") { stage("Install Dependencies (${label})") {
nodejs(nodeJSInstallationName: 'Node23') { if (isUnix()) {
if (isUnix()) { sh 'bun install --frozen-lockfile'
sh 'pnpm install --frozen-lockfile --production=false' } else {
} else { bat 'bun install --frozen-lockfile'
bat 'pnpm install --frozen-lockfile --production=false'
}
} }
} }
@ -89,17 +74,15 @@ def buildOnLabel(label, buildCommand) {
} }
stage("Build (${label})") { stage("Build (${label})") {
nodejs(nodeJSInstallationName: 'Node23') { if (isUnix()) {
if (isUnix()) { sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production ${buildCommand}"
sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production ${buildCommand}" } else {
} else { bat "set VITE_BUILD_NUMBER=${env.BUILD_NUMBER} && set NODE_ENV=production && ${buildCommand}"
bat "set VITE_BUILD_NUMBER=${env.BUILD_NUMBER} && set NODE_ENV=production && ${buildCommand}"
}
} }
} }
stage("Archive Artifacts (${label})") { stage("Archive Artifacts (${label})") {
archiveArtifacts artifacts: 'app_dist/**/farmcontrol-server-*.dmg, app_dist/**/farmcontrol-server-*.exe, app_dist/**/farmcontrol-server-*.pkg, app_dist/**/farmcontrol-server-*.msi', fingerprint: true archiveArtifacts artifacts: 'app_dist/**/farmcontrol-server-*', fingerprint: true
} }
} }
} }
@ -109,13 +92,10 @@ def setBuildNameFromPackageVersion() {
node('ubuntu') { node('ubuntu') {
stage('Set Build Name') { stage('Set Build Name') {
checkout scm checkout scm
def version def version = sh(
nodejs(nodeJSInstallationName: 'Node23') { script: "bun --eval \"console.log(JSON.parse(await Bun.file('package.json').text()).version)\"",
version = sh( returnStdout: true
script: "node -p \"require('./package.json').version\"", ).trim()
returnStdout: true
).trim()
}
def buildName = "v${version}-b${env.BUILD_NUMBER}" def buildName = "v${version}-b${env.BUILD_NUMBER}"
currentBuild.displayName = buildName currentBuild.displayName = buildName
echo "Build name set to: ${buildName}" echo "Build name set to: ${buildName}"
@ -127,8 +107,8 @@ try {
setBuildNameFromPackageVersion() setBuildNameFromPackageVersion()
parallel( parallel(
'Windows Build': buildOnLabel('windows', 'pnpm build:electron'), 'Windows Build': buildOnLabel('windows', 'bun run build:app'),
'MacOS Build': buildOnLabel('macos', 'pnpm build:electron:mac'), 'MacOS Build': buildOnLabel('macos', 'bun run build:app:mac'),
'Ubuntu Build': { buildLinux() } 'Ubuntu Build': { buildLinux() }
) )

1804
bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,10 @@
"url": "https://dev-wss.tombutcher.work", "url": "https://dev-wss.tombutcher.work",
"apiUrl": "https://dev.tombutcher.work/api", "apiUrl": "https://dev.tombutcher.work/api",
"host": { "host": {
"id": "6a6654425330570c496f12ee", "id": "691a1db49ce913faf0e51284",
"authCode": "VCXo44VxFhJb2pvZDQ0b3QPX7lYMg7SGZ6s4HBU72dJb-PBNk81UfwYhqqYKn1Ez" "authCode": "6wdCQPKMr_zuFaJe3-AY99uRuAEZRgU2lLNTuAFPW5hPvzzTrSra3rh3woZLj-eE"
} },
"dataDir": "/Users/tombutcher/Projects/farmcontrol-server/data"
}, },
"production": { "production": {
"logLevel": "info", "logLevel": "info",

34
electrobun.config.ts Normal file
View File

@ -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,
},
};

View File

@ -1,3 +0,0 @@
{
"ignore": ["node_modules/*", "*.log", "public/*", "config.json"]
}

View File

@ -6,20 +6,19 @@
"bin": "build/index.js", "bin": "build/index.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "node build/index.js", "start": "bun run build/server && bun build/index.js",
"dev": "cross-env NODE_ENV=development nodemon src/index.js", "dev": "cross-env NODE_ENV=development bun --watch src/index.js",
"dev:electron": "concurrently \"cross-env NODE_ENV=development vite src/electron --port 5287 --no-open\" \"cross-env NODE_ENV=development electron src/index.js\"", "dev:app": "concurrently \"cross-env NODE_ENV=development bun run dev:renderer\" \"cross-env NODE_ENV=development electrobun dev\"",
"build": "pnpm run cleanBuild && pnpm build:server && pnpm build:renderer", "dev:renderer": "vite src/app --port 5287 --no-open",
"build:server": "shx mkdir -p build && shx cp -r src/. build/ && shx cp package.json config.json build/", "build": "bun run cleanBuild && bun run build:server && bun run build:renderer",
"build:renderer": "pnpm build:electron-renderer && shx cp src/electron/preload.js build/electron/ && shx rm -rf build/electron/App.jsx build/electron/main.jsx build/electron/App.css build/electron/index.css build/electron/FarmControlLogo.jsx build/electron/vite.config.js build/electron/public build/electron/build", "build:server": "bun run scripts/build-server.mjs",
"build:electron-renderer": "vite build src/electron --outDir build/electron", "build:renderer": "vite build src/app",
"build:electron": "pnpm build && electron-builder", "build:app": "bun run build && electrobun build --env=stable",
"build:electron:mac": "pnpm build && electron-builder --mac dmg --arm64 --x64 && electron-builder --mac pkg --arm64 && electron-builder --mac pkg --x64", "build:app:mac": "bun run build && electrobun build --env=stable",
"build:linux": "pnpm run cleanBuild && pnpm run build:server && pnpm run build:linux-bundle && pnpm run build:linux-pkg && pnpm run build:linux-packages", "build:linux": "bun run cleanBuild && bun run build:server && bun run build:linux-binary && bun run build:linux-packages",
"build:linux-bundle": "node scripts/build-linux-bundle.mjs", "build:linux-binary": "bun scripts/build-linux-binary.mjs",
"build:linux-pkg": "shx mkdir -p app_dist/linux && pnpm exec pkg build/pkg-entry.cjs --config package.json --targets node18-linux-x64 --no-bytecode --public-packages \"pdf-to-img,pdfjs-dist,sharp,canvas,@img/*,@napi-rs/*\" --output app_dist/linux/farmcontrol-server",
"build:linux-packages": "bash scripts/build-linux-packages.sh", "build:linux-packages": "bash scripts/build-linux-packages.sh",
"cleanBuild": "rimraf build" "cleanBuild": "rm -rf build dist"
}, },
"author": "Tom Butcher", "author": "Tom Butcher",
"license": "ISC", "license": "ISC",
@ -47,83 +46,18 @@
}, },
"devDependencies": { "devDependencies": {
"@ant-design/icons": "^6.2.3", "@ant-design/icons": "^6.2.3",
"@electron/rebuild": "^4.0.4",
"@vitejs/plugin-react": "^6.0.2", "@vitejs/plugin-react": "^6.0.2",
"antd": "^5.29.2", "antd": "^5.29.2",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"electron": "^38.7.1", "electrobun": "^1.18.1",
"electron-builder": "^26.0.12",
"esbuild": "^0.25.12",
"jest": "^30.4.2", "jest": "^30.4.2",
"nodemon": "^3.1.14",
"pkg": "^5.8.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^19.2.6", "react": "^19.2.6",
"react-dom": "^19.2.6", "react-dom": "^19.2.6",
"rimraf": "^6.1.3",
"shx": "^0.4.0",
"supertest": "^7.2.2", "supertest": "^7.2.2",
"vite": "^8.0.13", "vite": "^8.0.13",
"vite-plugin-svgo": "^2.0.0", "vite-plugin-svgo": "^2.0.0",
"vite-plugin-svgr": "^5.2.0" "vite-plugin-svgr": "^5.2.0"
},
"pkg": {
"scripts": [
"build/pkg-entry.cjs"
],
"assets": [
"build/config.json",
"node_modules/pdf-to-img/**/*",
"node_modules/pdfjs-dist/**/*",
"node_modules/sharp/**/*",
"node_modules/@img/**/*",
"node_modules/canvas/**/*",
"node_modules/@napi-rs/**/*"
],
"targets": [
"node18-linux-x64"
],
"outputPath": "app_dist/linux"
},
"build": {
"appId": "com.tombutcher.farmcontrolserver",
"productName": "Farm Control Server",
"executableName": "farmcontrol-server",
"artifactName": "farmcontrol-server-${version}-${arch}.${ext}",
"icon": "assets/farmcontrolhosticon.png",
"directories": {
"output": "app_dist"
},
"files": [
"build/**/*",
"node_modules/**/*"
],
"mac": {
"target": [
"dmg",
"pkg"
],
"mergeASARs": true,
"x64ArchFiles": "**/node_modules/@esbuild/darwin-x64/**",
"singleArchFiles": "**/node_modules/**"
},
"win": {
"target": [
"nsis",
"msiWrapped"
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"perMachine": true
},
"msiWrapped": {
"upgradeCode": "{A4E29F1C-8B3D-5E2A-9C71-1D6F8E4A2B90}",
"perMachine": true,
"impersonate": false,
"wrappedInstallerArgs": "/S"
}
} }
} }

9771
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +0,0 @@
allowBuilds:
canvas: true
chromedriver: true
electron-winstaller: true
electron: true
esbuild: true
protobufjs: true
sharp: true
unrs-resolver: true

View File

@ -2,83 +2,55 @@
[![Build Status](https://ci.tombutcher.work/buildStatus/icon?job=farmcontrol%2Ffarmcontrol-server%2Fmain&style=flat-square)](https://ci.tombutcher.work/job/farmcontrol/job/farmcontrol-server/job/main/) [![Build Status](https://ci.tombutcher.work/buildStatus/icon?job=farmcontrol%2Ffarmcontrol-server%2Fmain&style=flat-square)](https://ci.tombutcher.work/job/farmcontrol/job/farmcontrol-server/job/main/)
A Node.js application that bridges communication between external websocket clients and a Moonraker-controlled 3D printer. A Bun application that bridges communication between Farm Control cloud services, local websocket clients, and connected printers.
## Features ## Features
- Connects to Moonraker API via websocket - Connects to the Farm Control websocket API
- Provides a websocket server for external clients - Provides a local HTTP server for CLI and integrations
- Relays JSON-RPC commands between clients and the printer - Desktop UI built with React, Ant Design, and Electrobun
- Broadcasts printer status updates to all connected clients - Headless Linux service packaging for servers and Raspberry Pi hosts
- Handles reconnection if the Moonraker connection is lost - Relays printer and document printer state to the desktop UI
## Requirements
- [Bun](https://bun.sh) 1.1 or newer
## Installation ## Installation
1. Clone this repository 1. Clone this repository
2. Install dependencies: 2. Install dependencies:
``` ```
npm install bun install
``` ```
3. Edit the `config.json` file to match your Moonraker setup 3. Edit `config.json` for your environment
4. Start the server: 4. Start the server:
``` ```
npm start bun run dev
``` ```
## Development
- Headless server: `bun run dev`
- Desktop app with hot reload: `bun run dev:app`
- Renderer only: `bun run dev:renderer`
## Building
- Desktop app (current platform): `bun run build:app`
- Linux headless packages: `bun run build:linux`
Electrobun handles desktop packaging. Linux headless builds use `bun build --compile`.
## Configuration ## Configuration
The `config.json` file contains the following options: `config.json` contains `development` and `production` sections. Each section supports:
```json - `logLevel`
{ - `url` (websocket server)
"moonraker": { - `apiUrl`
"host": "localhost", - `host.id` and `host.authCode`
"port": 7125, - `dataDir`
"protocol": "ws",
"apiKey": null,
"identity": {
"name": "printer-bridge",
"version": "0.1.0",
"type": "external"
}
},
"server": {
"port": 8080
}
}
```
- `moonraker.host`: The hostname or IP address of your Moonraker instance
- `moonraker.port`: The port number of your Moonraker instance
- `moonraker.protocol`: The protocol to use (`ws` or `wss`)
- `moonraker.apiKey`: Your Moonraker API key (if required)
- `server.port`: The port number for the websocket server
## Usage
Connect to the websocket server at `ws://[host]:[port]` and send JSON-RPC formatted messages to control the printer.
Example client-side code:
```javascript
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('Connected to printer bridge');
// Send a command to get printer info
ws.send(JSON.stringify({
jsonrpc: "2.0",
method: "printer.info",
id: 1
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received:', message);
};
```
## License ## License

View File

@ -0,0 +1,70 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const packageJson = JSON.parse(
readFileSync(path.join(rootDir, "package.json"), "utf8"),
);
let buildNumber =
process.env.BUILD_NUMBER || process.env.VITE_BUILD_NUMBER || "dev";
try {
const buildInfo = JSON.parse(
readFileSync(path.join(rootDir, "src/buildInfo.json"), "utf8"),
);
buildNumber = buildInfo.buildNumber ?? buildNumber;
} catch {}
const outputDir = path.join(rootDir, "app_dist/linux");
const outputFile = path.join(outputDir, "farmcontrol-server");
await Bun.$`mkdir -p ${outputDir}`;
const result = await Bun.build({
entrypoints: [path.join(rootDir, "src/headless.js")],
outdir: path.join(rootDir, "build/linux-bundle"),
target: "bun",
format: "esm",
external: [
"sharp",
"canvas",
"@napi-rs/canvas",
"pdf-to-img",
"pdfjs-dist",
"@img/*",
"@napi-rs/*",
],
define: {
"process.env.FC_PACKAGE_VERSION": JSON.stringify(packageJson.version),
"process.env.FC_BUILD_NUMBER": JSON.stringify(buildNumber),
},
});
if (!result.success) {
console.error("Bun build failed");
for (const log of result.logs) {
console.error(log);
}
process.exit(1);
}
const compileResult = await Bun.spawn([
"bun",
"build",
"--compile",
"--target=bun-linux-x64",
path.join(rootDir, "build/linux-bundle/headless.js"),
"--outfile",
outputFile,
], {
cwd: rootDir,
stdout: "inherit",
stderr: "inherit",
});
if (compileResult.exitCode !== 0) {
process.exit(compileResult.exitCode ?? 1);
}
console.log(`Linux binary written to ${outputFile}`);

View File

@ -1,41 +0,0 @@
import { readFileSync } from 'node:fs'
import * as esbuild from 'esbuild'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const packageJson = JSON.parse(
readFileSync(path.join(rootDir, 'package.json'), 'utf8')
)
let buildNumber = process.env.BUILD_NUMBER || process.env.VITE_BUILD_NUMBER || 'dev'
try {
const buildInfo = JSON.parse(
readFileSync(path.join(rootDir, 'src/buildInfo.json'), 'utf8')
)
buildNumber = buildInfo.buildNumber ?? buildNumber
} catch {}
await esbuild.build({
entryPoints: [path.join(rootDir, 'src/headless.js')],
bundle: true,
platform: 'node',
target: 'node18',
format: 'cjs',
outfile: path.join(rootDir, 'build/pkg-entry.cjs'),
external: [
'electron',
'sharp',
'canvas',
'@napi-rs/canvas',
'pdf-to-img',
'pdfjs-dist'
],
inject: [path.join(rootDir, 'scripts/import-meta-url.js')],
define: {
'import.meta.url': 'import_meta_url',
'process.env.FC_PACKAGE_VERSION': JSON.stringify(packageJson.version),
'process.env.FC_BUILD_NUMBER': JSON.stringify(buildNumber)
},
logLevel: 'info'
})

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
version="$(node -p "require('./package.json').version")" version="$(bun --eval "console.log(JSON.parse(await Bun.file('package.json').text()).version)")"
output_dir="app_dist/linux" output_dir="app_dist/linux"
binary="${output_dir}/farmcontrol-server" binary="${output_dir}/farmcontrol-server"

15
scripts/build-server.mjs Normal file
View File

@ -0,0 +1,15 @@
import { cpSync, mkdirSync, rmSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const buildDir = path.join(rootDir, "build");
rmSync(buildDir, { recursive: true, force: true });
mkdirSync(buildDir, { recursive: true });
cpSync(path.join(rootDir, "src"), buildDir, { recursive: true });
cpSync(path.join(rootDir, "package.json"), path.join(buildDir, "package.json"));
cpSync(path.join(rootDir, "config.json"), path.join(buildDir, "config.json"));
console.log(`Server sources copied to ${buildDir}`);

View File

@ -1,3 +0,0 @@
import { pathToFileURL } from 'node:url'
export const import_meta_url = pathToFileURL(__filename)

View File

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

Before

Width:  |  Height:  |  Size: 934 B

After

Width:  |  Height:  |  Size: 934 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1000 B

After

Width:  |  Height:  |  Size: 1000 B

View File

@ -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;

View File

@ -1,5 +1,6 @@
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import "./electrobun-bridge.js";
import "./index.css"; import "./index.css";
import App from "./App.jsx"; import App from "./App.jsx";
import "antd/dist/reset.css"; import "antd/dist/reset.css";

View File

@ -6,10 +6,11 @@ import svgo from "vite-plugin-svgo";
export default defineConfig({ export default defineConfig({
base: "./", base: "./",
publicDir: path.resolve(__dirname, "public"),
plugins: [react(), svgr(), svgo()], plugins: [react(), svgr(), svgo()],
root: path.resolve(__dirname), root: path.resolve(__dirname),
build: { build: {
outDir: path.resolve(__dirname, "../../build/electron"), outDir: path.resolve(__dirname, "../../dist/app"),
emptyOutDir: true, emptyOutDir: true,
rollupOptions: { rollupOptions: {
input: { input: {

8
src/bun/index.js Normal file
View File

@ -0,0 +1,8 @@
import { createDesktopWindow } from "../desktop/window.js";
import { setupIPC } from "../desktop/rpc.js";
import { init } from "../index.js";
const mainWindow = await createDesktopWindow();
await setupIPC(mainWindow);
await init({ headless: false, skipWindow: true });

Some files were not shown because too many files have changed in this diff Show More