Compare commits
No commits in common. "fa389e3eb71aa5782fc04fafeb1dd4dcf113a5cb" and "01b3cc1aa1259a426bd733eb96b32299572bea72" have entirely different histories.
fa389e3eb7
...
01b3cc1aa1
6
.gitignore
vendored
@ -141,8 +141,4 @@ data/*
|
|||||||
|
|
||||||
dist/*
|
dist/*
|
||||||
|
|
||||||
src/buildInfo.json
|
src/buildInfo.json
|
||||||
.electrobun-cache
|
|
||||||
artifacts
|
|
||||||
app_dist
|
|
||||||
build
|
|
||||||
6
.npmrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
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
@ -3,14 +3,16 @@ properties([
|
|||||||
])
|
])
|
||||||
|
|
||||||
def writeBuildMetadata() {
|
def writeBuildMetadata() {
|
||||||
if (isUnix()) {
|
nodejs(nodeJSInstallationName: 'Node23') {
|
||||||
sh '''
|
if (isUnix()) {
|
||||||
bun --eval "await Bun.write('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n')"
|
sh '''
|
||||||
'''
|
node -e "const fs = require('fs'); fs.writeFileSync('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n');"
|
||||||
} else {
|
'''
|
||||||
bat '''
|
} else {
|
||||||
bun --eval "await Bun.write('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n')"
|
bat '''
|
||||||
'''
|
node -e "const fs = require('fs'); fs.writeFileSync('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n');"
|
||||||
|
'''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,12 +23,17 @@ def buildLinux() {
|
|||||||
checkout scm
|
checkout scm
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Setup Bun (Ubuntu)') {
|
stage('Setup Node.js (Ubuntu)') {
|
||||||
sh 'bun -v'
|
nodejs(nodeJSInstallationName: 'Node23') {
|
||||||
|
sh 'node -v'
|
||||||
|
sh 'pnpm -v'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Install Dependencies (Ubuntu)') {
|
stage('Install Dependencies (Ubuntu)') {
|
||||||
sh 'bun install --frozen-lockfile'
|
nodejs(nodeJSInstallationName: 'Node23') {
|
||||||
|
sh 'pnpm install --frozen-lockfile --production=false'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Write Build Metadata (Ubuntu)') {
|
stage('Write Build Metadata (Ubuntu)') {
|
||||||
@ -34,7 +41,9 @@ def buildLinux() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage('Build (Ubuntu)') {
|
stage('Build (Ubuntu)') {
|
||||||
sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production bun run build:linux"
|
nodejs(nodeJSInstallationName: 'Node23') {
|
||||||
|
sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production pnpm build:linux"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Archive Artifacts (Ubuntu)') {
|
stage('Archive Artifacts (Ubuntu)') {
|
||||||
@ -53,19 +62,25 @@ def buildOnLabel(label, buildCommand) {
|
|||||||
checkout scm
|
checkout scm
|
||||||
}
|
}
|
||||||
|
|
||||||
stage("Setup Bun (${label})") {
|
stage("Setup Node.js (${label})") {
|
||||||
if (isUnix()) {
|
nodejs(nodeJSInstallationName: 'Node23') {
|
||||||
sh 'bun -v'
|
if (isUnix()) {
|
||||||
} else {
|
sh 'node -v'
|
||||||
bat 'bun -v'
|
sh 'pnpm -v'
|
||||||
|
} else {
|
||||||
|
bat 'node -v'
|
||||||
|
bat 'pnpm -v'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage("Install Dependencies (${label})") {
|
stage("Install Dependencies (${label})") {
|
||||||
if (isUnix()) {
|
nodejs(nodeJSInstallationName: 'Node23') {
|
||||||
sh 'bun install --frozen-lockfile'
|
if (isUnix()) {
|
||||||
} else {
|
sh 'pnpm install --frozen-lockfile --production=false'
|
||||||
bat 'bun install --frozen-lockfile'
|
} else {
|
||||||
|
bat 'pnpm install --frozen-lockfile --production=false'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,15 +89,17 @@ def buildOnLabel(label, buildCommand) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage("Build (${label})") {
|
stage("Build (${label})") {
|
||||||
if (isUnix()) {
|
nodejs(nodeJSInstallationName: 'Node23') {
|
||||||
sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production ${buildCommand}"
|
if (isUnix()) {
|
||||||
} else {
|
sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production ${buildCommand}"
|
||||||
bat "set VITE_BUILD_NUMBER=${env.BUILD_NUMBER} && set NODE_ENV=production && ${buildCommand}"
|
} else {
|
||||||
|
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-*', fingerprint: true
|
archiveArtifacts artifacts: 'app_dist/**/farmcontrol-server-*.dmg, app_dist/**/farmcontrol-server-*.exe, app_dist/**/farmcontrol-server-*.pkg, app_dist/**/farmcontrol-server-*.msi', fingerprint: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,10 +109,13 @@ def setBuildNameFromPackageVersion() {
|
|||||||
node('ubuntu') {
|
node('ubuntu') {
|
||||||
stage('Set Build Name') {
|
stage('Set Build Name') {
|
||||||
checkout scm
|
checkout scm
|
||||||
def version = sh(
|
def version
|
||||||
script: "bun --eval \"console.log(JSON.parse(await Bun.file('package.json').text()).version)\"",
|
nodejs(nodeJSInstallationName: 'Node23') {
|
||||||
returnStdout: true
|
version = sh(
|
||||||
).trim()
|
script: "node -p \"require('./package.json').version\"",
|
||||||
|
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}"
|
||||||
@ -107,8 +127,8 @@ try {
|
|||||||
setBuildNameFromPackageVersion()
|
setBuildNameFromPackageVersion()
|
||||||
|
|
||||||
parallel(
|
parallel(
|
||||||
'Windows Build': buildOnLabel('windows', 'bun run build:app'),
|
'Windows Build': buildOnLabel('windows', 'pnpm build:electron'),
|
||||||
'MacOS Build': buildOnLabel('macos', 'bun run build:app:mac'),
|
'MacOS Build': buildOnLabel('macos', 'pnpm build:electron:mac'),
|
||||||
'Ubuntu Build': { buildLinux() }
|
'Ubuntu Build': { buildLinux() }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -4,10 +4,9 @@
|
|||||||
"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": "691a1db49ce913faf0e51284",
|
"id": "6a6654425330570c496f12ee",
|
||||||
"authCode": "6wdCQPKMr_zuFaJe3-AY99uRuAEZRgU2lLNTuAFPW5hPvzzTrSra3rh3woZLj-eE"
|
"authCode": "VCXo44VxFhJb2pvZDQ0b3QPX7lYMg7SGZ6s4HBU72dJb-PBNk81UfwYhqqYKn1Ez"
|
||||||
},
|
}
|
||||||
"dataDir": "/Users/tombutcher/Projects/farmcontrol-server/data"
|
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"logLevel": "info",
|
"logLevel": "info",
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
// 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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
3
nodemon.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"ignore": ["node_modules/*", "*.log", "public/*", "config.json"]
|
||||||
|
}
|
||||||
92
package.json
@ -6,19 +6,20 @@
|
|||||||
"bin": "build/index.js",
|
"bin": "build/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "bun run build/server && bun build/index.js",
|
"start": "node build/index.js",
|
||||||
"dev": "cross-env NODE_ENV=development bun --watch src/index.js",
|
"dev": "cross-env NODE_ENV=development nodemon src/index.js",
|
||||||
"dev:app": "concurrently \"cross-env NODE_ENV=development bun run dev:renderer\" \"cross-env NODE_ENV=development electrobun dev\"",
|
"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:renderer": "vite src/app --port 5287 --no-open",
|
"build": "pnpm run cleanBuild && pnpm build:server && pnpm build:renderer",
|
||||||
"build": "bun run cleanBuild && bun run build:server && bun run build:renderer",
|
"build:server": "shx mkdir -p build && shx cp -r src/. build/ && shx cp package.json config.json build/",
|
||||||
"build:server": "bun run scripts/build-server.mjs",
|
"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:renderer": "vite build src/app",
|
"build:electron-renderer": "vite build src/electron --outDir build/electron",
|
||||||
"build:app": "bun run build && electrobun build --env=stable",
|
"build:electron": "pnpm build && electron-builder",
|
||||||
"build:app:mac": "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:linux": "bun run cleanBuild && bun run build:server && bun run build:linux-binary && bun run build:linux-packages",
|
"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-binary": "bun scripts/build-linux-binary.mjs",
|
"build:linux-bundle": "node scripts/build-linux-bundle.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": "rm -rf build dist"
|
"cleanBuild": "rimraf build"
|
||||||
},
|
},
|
||||||
"author": "Tom Butcher",
|
"author": "Tom Butcher",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@ -46,18 +47,83 @@
|
|||||||
},
|
},
|
||||||
"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",
|
||||||
"electrobun": "^1.18.1",
|
"electron": "^38.7.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
Normal file
9
pnpm-workspace.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
allowBuilds:
|
||||||
|
canvas: true
|
||||||
|
chromedriver: true
|
||||||
|
electron-winstaller: true
|
||||||
|
electron: true
|
||||||
|
esbuild: true
|
||||||
|
protobufjs: true
|
||||||
|
sharp: true
|
||||||
|
unrs-resolver: true
|
||||||
92
readme.md
@ -2,55 +2,83 @@
|
|||||||
|
|
||||||
[](https://ci.tombutcher.work/job/farmcontrol/job/farmcontrol-server/job/main/)
|
[](https://ci.tombutcher.work/job/farmcontrol/job/farmcontrol-server/job/main/)
|
||||||
|
|
||||||
A Bun application that bridges communication between Farm Control cloud services, local websocket clients, and connected printers.
|
A Node.js application that bridges communication between external websocket clients and a Moonraker-controlled 3D printer.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Connects to the Farm Control websocket API
|
- Connects to Moonraker API via websocket
|
||||||
- Provides a local HTTP server for CLI and integrations
|
- Provides a websocket server for external clients
|
||||||
- Desktop UI built with React, Ant Design, and Electrobun
|
- Relays JSON-RPC commands between clients and the printer
|
||||||
- Headless Linux service packaging for servers and Raspberry Pi hosts
|
- Broadcasts printer status updates to all connected clients
|
||||||
- Relays printer and document printer state to the desktop UI
|
- Handles reconnection if the Moonraker connection is lost
|
||||||
|
|
||||||
## 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:
|
||||||
```
|
```
|
||||||
bun install
|
npm install
|
||||||
```
|
```
|
||||||
3. Edit `config.json` for your environment
|
3. Edit the `config.json` file to match your Moonraker setup
|
||||||
4. Start the server:
|
4. Start the server:
|
||||||
```
|
```
|
||||||
bun run dev
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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
|
||||||
|
|
||||||
`config.json` contains `development` and `production` sections. Each section supports:
|
The `config.json` file contains the following options:
|
||||||
|
|
||||||
- `logLevel`
|
```json
|
||||||
- `url` (websocket server)
|
{
|
||||||
- `apiUrl`
|
"moonraker": {
|
||||||
- `host.id` and `host.authCode`
|
"host": "localhost",
|
||||||
- `dataDir`
|
"port": 7125,
|
||||||
|
"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
|
||||||
|
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
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}`);
|
|
||||||
41
scripts/build-linux-bundle.mjs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
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'
|
||||||
|
})
|
||||||
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
version="$(bun --eval "console.log(JSON.parse(await Bun.file('package.json').text()).version)")"
|
version="$(node -p "require('./package.json').version")"
|
||||||
output_dir="app_dist/linux"
|
output_dir="app_dist/linux"
|
||||||
binary="${output_dir}/farmcontrol-server"
|
binary="${output_dir}/farmcontrol-server"
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
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}`);
|
|
||||||
3
scripts/import-meta-url.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
|
|
||||||
|
export const import_meta_url = pathToFileURL(__filename)
|
||||||
@ -1,75 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
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 });
|
|
||||||
@ -8,9 +8,7 @@ import { startWaiting, stopWaiting } from "./spinner.js";
|
|||||||
// Determine environment
|
// Determine environment
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const isPackaged =
|
const isPackaged = __dirname.includes("app.asar");
|
||||||
__dirname.includes(path.sep + "build" + path.sep) ||
|
|
||||||
__dirname.includes(path.sep + "Resources" + path.sep);
|
|
||||||
|
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
process.env.NODE_ENV = "production";
|
process.env.NODE_ENV = "production";
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
const listeners = new Map();
|
|
||||||
|
|
||||||
let sendMessage = () => {};
|
|
||||||
|
|
||||||
export function setMessageSender(sender) {
|
|
||||||
sendMessage = sender;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendIPC(channel, data) {
|
|
||||||
sendMessage(channel, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function onIPCData(channel, callback) {
|
|
||||||
if (!listeners.has(channel)) {
|
|
||||||
listeners.set(channel, new Set());
|
|
||||||
}
|
|
||||||
listeners.get(channel).add(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeAllListeners(channel) {
|
|
||||||
listeners.delete(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dispatchIPC(channel, data) {
|
|
||||||
const channelListeners = listeners.get(channel);
|
|
||||||
if (!channelListeners) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const callback of channelListeners) {
|
|
||||||
callback(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
import log4js from "log4js";
|
|
||||||
import { BrowserView } from "electrobun/bun";
|
|
||||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
|
||||||
import { setMessageSender, sendIPC } from "./notify.js";
|
|
||||||
|
|
||||||
const logger = log4js.getLogger("RPC");
|
|
||||||
let mainWindow = null;
|
|
||||||
|
|
||||||
export function createAppRpc() {
|
|
||||||
return BrowserView.defineRPC({
|
|
||||||
maxRequestTime: 30000,
|
|
||||||
handlers: {
|
|
||||||
requests: {
|
|
||||||
getData: async () => {
|
|
||||||
startWaiting("Getting data...", logger);
|
|
||||||
try {
|
|
||||||
const socketClient = global.socketClient;
|
|
||||||
|
|
||||||
if (!socketClient) {
|
|
||||||
pushState({
|
|
||||||
authenticated: false,
|
|
||||||
connected: false,
|
|
||||||
loading: false,
|
|
||||||
host: {},
|
|
||||||
printers: [],
|
|
||||||
documentPrinters: [],
|
|
||||||
});
|
|
||||||
await stopWaiting();
|
|
||||||
return { ok: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
pushState({
|
|
||||||
authenticated: socketClient.authenticated,
|
|
||||||
connected: socketClient.connected,
|
|
||||||
loading: socketClient.loading,
|
|
||||||
host: socketClient.host || {},
|
|
||||||
printers: socketClient.printerManager.printers || [],
|
|
||||||
documentPrinters:
|
|
||||||
socketClient.documentPrinterManager.documentPrinters || [],
|
|
||||||
});
|
|
||||||
await stopWaiting();
|
|
||||||
return { ok: true };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error getting printer data:", error);
|
|
||||||
pushState({
|
|
||||||
authenticated: false,
|
|
||||||
connected: false,
|
|
||||||
loading: false,
|
|
||||||
host: {},
|
|
||||||
printers: [],
|
|
||||||
documentPrinters: [],
|
|
||||||
});
|
|
||||||
await stopWaiting();
|
|
||||||
return { ok: false };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
authenticateOTP: async ({ otp }) => {
|
|
||||||
startWaiting("Authenticating with OTP...", logger);
|
|
||||||
try {
|
|
||||||
const socketClient = global.socketClient;
|
|
||||||
if (socketClient) {
|
|
||||||
await socketClient.authenticateWithOtp(otp);
|
|
||||||
} else {
|
|
||||||
logger.error("Socket client not available for OTP authentication");
|
|
||||||
}
|
|
||||||
await stopWaiting();
|
|
||||||
return { ok: true };
|
|
||||||
} catch (error) {
|
|
||||||
await stopWaiting();
|
|
||||||
logger.error("Error during OTP authentication:", error);
|
|
||||||
return { ok: false };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
minimizeWindow: async () => {
|
|
||||||
if (mainWindow && !mainWindow.isDestroyed?.()) {
|
|
||||||
mainWindow.minimize();
|
|
||||||
}
|
|
||||||
return { ok: true };
|
|
||||||
},
|
|
||||||
maximizeWindow: async () => {
|
|
||||||
if (mainWindow && !mainWindow.isDestroyed?.()) {
|
|
||||||
if (mainWindow.isMaximized?.()) {
|
|
||||||
mainWindow.unmaximize();
|
|
||||||
} else {
|
|
||||||
mainWindow.maximize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { ok: true };
|
|
||||||
},
|
|
||||||
closeWindow: async () => {
|
|
||||||
if (mainWindow && !mainWindow.isDestroyed?.()) {
|
|
||||||
mainWindow.close();
|
|
||||||
}
|
|
||||||
return { ok: true };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
messages: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function pushState({
|
|
||||||
authenticated,
|
|
||||||
connected,
|
|
||||||
loading,
|
|
||||||
host,
|
|
||||||
printers,
|
|
||||||
documentPrinters,
|
|
||||||
}) {
|
|
||||||
sendIPC("setAuthenticated", authenticated);
|
|
||||||
sendIPC("setConnected", connected);
|
|
||||||
sendIPC("setLoading", loading);
|
|
||||||
sendIPC("setHost", host);
|
|
||||||
sendIPC("setPrinters", printers);
|
|
||||||
sendIPC("setDocumentPrinters", documentPrinters);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerMainWindow(window) {
|
|
||||||
mainWindow = window;
|
|
||||||
global.mainWindow = window;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setupIPC(window = mainWindow) {
|
|
||||||
if (window) {
|
|
||||||
registerMainWindow(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mainWindow) {
|
|
||||||
logger.warn("No main window available, skipping RPC setup");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMessageSender((channel, data) => {
|
|
||||||
try {
|
|
||||||
const send = mainWindow?.webview?.rpc?.send;
|
|
||||||
|
|
||||||
if (send) {
|
|
||||||
send[channel](data);
|
|
||||||
logger.info(`Message sent to main window on channel: ${channel}`, data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.warn(
|
|
||||||
`No RPC sender available for channel: ${channel}. Is the window ready?`,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Error sending message on channel ${channel}:`, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info("RPC handlers ready");
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
import log4js from "log4js";
|
|
||||||
import { BrowserWindow, Updater } from "electrobun/bun";
|
|
||||||
import { loadConfig } from "../config.js";
|
|
||||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
|
||||||
import { createAppRpc, registerMainWindow } from "./rpc.js";
|
|
||||||
|
|
||||||
const config = loadConfig();
|
|
||||||
const logger = log4js.getLogger("Desktop");
|
|
||||||
logger.level = config.logLevel;
|
|
||||||
|
|
||||||
const DEV_SERVER_PORT = 5287;
|
|
||||||
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`;
|
|
||||||
|
|
||||||
export async function getMainViewUrl() {
|
|
||||||
const channel = await Updater.localInfo.channel();
|
|
||||||
if (channel === "dev" || process.env.NODE_ENV === "development") {
|
|
||||||
try {
|
|
||||||
await fetch(DEV_SERVER_URL, { method: "HEAD" });
|
|
||||||
logger.info(`Using Vite dev server at ${DEV_SERVER_URL}`);
|
|
||||||
return DEV_SERVER_URL;
|
|
||||||
} catch {
|
|
||||||
logger.warn(
|
|
||||||
"Vite dev server not running. Start it with `bun run dev:renderer`.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "views://mainview/index.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createDesktopWindow() {
|
|
||||||
const url = await getMainViewUrl();
|
|
||||||
const rpc = createAppRpc();
|
|
||||||
|
|
||||||
startWaiting("Creating desktop window...", logger);
|
|
||||||
|
|
||||||
const mainWindow = new BrowserWindow({
|
|
||||||
title: "Farm Control Server",
|
|
||||||
url,
|
|
||||||
rpc,
|
|
||||||
titleBarStyle: "hiddenInset",
|
|
||||||
trafficLightOffset: { x: 14, y: 12 },
|
|
||||||
frame: {
|
|
||||||
width: 900,
|
|
||||||
height: 600,
|
|
||||||
x: 100,
|
|
||||||
y: 100,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
registerMainWindow(mainWindow);
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
mainWindow.webview.on("dom-ready", async () => {
|
|
||||||
await stopWaiting();
|
|
||||||
resolve(mainWindow);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -4,7 +4,7 @@ import log4js from "log4js";
|
|||||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||||
import CupsPrinterInterface from "./interfaces/cupsinterface.js";
|
import CupsPrinterInterface from "./interfaces/cupsinterface.js";
|
||||||
import ReceiptInterface from "./interfaces/receiptinterface.js";
|
import ReceiptInterface from "./interfaces/receiptinterface.js";
|
||||||
import { sendIPC } from "../desktop/notify.js";
|
import { sendIPC } from "../electron/ipc.js";
|
||||||
// Load configuration
|
// Load configuration
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { loadConfig } from "../config.js";
|
import { loadConfig } from "../config.js";
|
||||||
import log4js from "log4js";
|
import log4js from "log4js";
|
||||||
import { sendIPC } from "../desktop/notify.js";
|
import { sendIPC } from "../electron/ipc.js";
|
||||||
import { DocumentPrinterClient } from "./documentprinterclient.js";
|
import { DocumentPrinterClient } from "./documentprinterclient.js";
|
||||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 934 B After Width: | Height: | Size: 934 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1000 B After Width: | Height: | Size: 1000 B |
122
src/electron/ipc.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import log4js from "log4js";
|
||||||
|
import { notPrompting } from "../utils.js";
|
||||||
|
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||||
|
|
||||||
|
const logger = log4js.getLogger("IPC");
|
||||||
|
let mainWindow = null;
|
||||||
|
|
||||||
|
export async function setupIPC() {
|
||||||
|
// Only import Electron if we're in an Electron environment
|
||||||
|
let ipcMain;
|
||||||
|
try {
|
||||||
|
const electron = await import("electron");
|
||||||
|
ipcMain = electron.ipcMain;
|
||||||
|
mainWindow = global.mainWindow;
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn("Electron not available, skipping IPC setup");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only proceed if we have ipcMain
|
||||||
|
if (!ipcMain) {
|
||||||
|
logger.warn("ipcMain not available, skipping IPC setup");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.on("getData", async () => {
|
||||||
|
startWaiting("Getting data...", logger);
|
||||||
|
try {
|
||||||
|
// Get the global socket client instance
|
||||||
|
const socketClient = global.socketClient;
|
||||||
|
|
||||||
|
if (!socketClient) {
|
||||||
|
sendIPC("setAuthenticated", false);
|
||||||
|
sendIPC("setConnected", false);
|
||||||
|
sendIPC("setLoading", false);
|
||||||
|
sendIPC("setHost", {});
|
||||||
|
sendIPC("setPrinters", []);
|
||||||
|
sendIPC("setDocumentPrinters", []);
|
||||||
|
await stopWaiting();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send individual data pieces to match renderer expectations
|
||||||
|
sendIPC("setAuthenticated", socketClient.authenticated);
|
||||||
|
sendIPC("setConnected", socketClient.connected);
|
||||||
|
sendIPC("setLoading", socketClient.loading);
|
||||||
|
sendIPC("setHost", socketClient.host || {});
|
||||||
|
sendIPC("setPrinters", socketClient.printerManager.printers || []);
|
||||||
|
sendIPC(
|
||||||
|
"setDocumentPrinters",
|
||||||
|
socketClient.documentPrinterManager.documentPrinters || [],
|
||||||
|
);
|
||||||
|
await stopWaiting();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error getting printer data:", error);
|
||||||
|
sendIPC("setAuthenticated", false);
|
||||||
|
sendIPC("setConnected", false);
|
||||||
|
sendIPC("setLoading", false);
|
||||||
|
sendIPC("setHost", {});
|
||||||
|
sendIPC("setPrinters", []);
|
||||||
|
await stopWaiting();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Window management IPC handlers
|
||||||
|
ipcMain.on("window-minimize", (event) => {
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.minimize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on("window-maximize", (event) => {
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
if (mainWindow.isMaximized()) {
|
||||||
|
mainWindow.unmaximize();
|
||||||
|
} else {
|
||||||
|
mainWindow.maximize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on("window-close", (event) => {
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// OTP authentication handler
|
||||||
|
ipcMain.on("authenticateOTP", async (event, otp) => {
|
||||||
|
startWaiting("Authenticating with OTP...", logger);
|
||||||
|
try {
|
||||||
|
const socketClient = global.socketClient;
|
||||||
|
if (socketClient) {
|
||||||
|
notPrompting();
|
||||||
|
await socketClient.authenticateWithOtp(otp);
|
||||||
|
} else {
|
||||||
|
logger.error("Socket client not available for OTP authentication");
|
||||||
|
}
|
||||||
|
await stopWaiting();
|
||||||
|
} catch (error) {
|
||||||
|
await stopWaiting();
|
||||||
|
logger.error("Error during OTP authentication:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("IPC handlers setup complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendIPC(channel, data) {
|
||||||
|
try {
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.webContents.send(channel, data);
|
||||||
|
logger.info(`Message sent to main window on channel: ${channel}`, data);
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
`No main window available, cannot send message on channel: ${channel}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error sending message on channel ${channel}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
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";
|
||||||
21
src/electron/preload.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { contextBridge, ipcRenderer } from "electron";
|
||||||
|
|
||||||
|
// Expose protected methods that allow the renderer process to use
|
||||||
|
// the ipcRenderer without exposing the entire object
|
||||||
|
contextBridge.exposeInMainWorld("electronAPI", {
|
||||||
|
onIPCData: (channel, callback) => {
|
||||||
|
ipcRenderer.on(channel, (event, data) => callback(data));
|
||||||
|
},
|
||||||
|
// Send messages to main process
|
||||||
|
sendIPC: (channel, data) => {
|
||||||
|
console.log("SEND IPC", channel);
|
||||||
|
ipcRenderer.send(channel, data);
|
||||||
|
},
|
||||||
|
// Window management
|
||||||
|
minimize: () => ipcRenderer.send("window-minimize"),
|
||||||
|
maximize: () => ipcRenderer.send("window-maximize"),
|
||||||
|
close: () => ipcRenderer.send("window-close"),
|
||||||
|
removeAllListeners: (channel) => {
|
||||||
|
ipcRenderer.removeAllListeners(channel);
|
||||||
|
},
|
||||||
|
});
|
||||||