Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions gcs/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,54 +129,55 @@ function getUserConfiguration() {
return userSettings
}

ipcMain.handle("getSettings", () => {
ipcMain.handle("settings:fetch-settings", () => {
return getUserConfiguration()
})
ipcMain.handle("setSettings", (_, settings) => {
ipcMain.handle("settings:save-settings", (_, settings) => {
saveUserConfiguration(settings)
})

ipcMain.handle("isMac", () => {
ipcMain.handle("app:is-mac", () => {
return process.platform == "darwin"
})
ipcMain.on("close", () => {
ipcMain.on("window:close", () => {
closeWithBackend()
})
ipcMain.on("minimise", () => {
ipcMain.on("window:minimise", () => {
getWindow()?.minimize()
})
ipcMain.on("maximise", () => {
ipcMain.on("window:maximise", () => {
getWindow()?.isMaximized()
? getWindow()?.unmaximize()
: getWindow()?.maximize()
})

ipcMain.on("reload", () => {
ipcMain.on("window:reload", () => {
getWindow()?.reload()
})
ipcMain.on("force_reload", () => {
ipcMain.on("window:force-reload", () => {
getWindow()?.webContents.reloadIgnoringCache()
})
ipcMain.on("toggle_developer_tools", () => {
ipcMain.on("window:toggle-developer-tools", () => {
getWindow()?.webContents.toggleDevTools()
})
ipcMain.on("actual_size", () => {
ipcMain.on("window:actual-size", () => {
getWindow()?.webContents.setZoomFactor(1)
})
ipcMain.on("toggle_fullscreen", () => {
ipcMain.on("window:toggle-fullscreen", () => {
getWindow()?.isFullScreen()
? getWindow()?.setFullScreen(false)
: getWindow()?.setFullScreen(true)
})
ipcMain.on("zoom_in", () => {
ipcMain.on("window:zoom-in", () => {
const window = getWindow()?.webContents
window?.setZoomFactor(window?.getZoomFactor() + 0.1)
})
ipcMain.on("zoom_out", () => {
ipcMain.on("window:zoom-out", () => {
const window = getWindow()?.webContents
window?.setZoomFactor(window?.getZoomFactor() - 0.1)
})
ipcMain.on("openFileInExplorer", (_event, filePath) => {

ipcMain.on("window:open-file-in-explorer", (_event, filePath) => {
shell.showItemInFolder(filePath)
})

Expand Down
12 changes: 4 additions & 8 deletions gcs/electron/modules/aboutWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ export function openAboutPopout() {
return { action: "deny" }
})

// Windows doesn't consider maximising to be fullscreening so we must prevent default
aboutPopoutWin.on("maximize", (e: Event) => {
e.preventDefault()
})
aboutPopoutWin.on("close", () => {
aboutPopoutWin = null
})
Expand All @@ -58,11 +54,11 @@ export function destroyAboutWindow() {
}

export default function registerAboutIPC() {
ipcMain.removeHandler("openAboutWindow")
ipcMain.removeHandler("closeAboutWindow")
ipcMain.removeHandler("app:open-about-window")
ipcMain.removeHandler("app:close-about-window")

ipcMain.handle("openAboutWindow", () => {
ipcMain.handle("app:open-about-window", () => {
openAboutPopout()
})
ipcMain.handle("closeAboutWindow", () => closeAboutPopout())
ipcMain.handle("app:close-about-window", () => closeAboutPopout())
}
18 changes: 7 additions & 11 deletions gcs/electron/modules/linkStatsWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ export function openLinkStatsWindow() {
linkStatsWin?.loadFile(path.join(process.env.DIST, "linkStats.html"))
}

// Windows doesn't consider maximising to be fullscreening so we must prevent default
linkStatsWin.on("maximize", (e: Event) => {
e.preventDefault()
})
linkStatsWin.on("close", () => {
linkStatsWin = null
})
Expand All @@ -50,16 +46,16 @@ export function destroyLinkStatsWindow() {
}

export default function registerLinkStatsIPC() {
ipcMain.removeHandler("openLinkStatsWindow")
ipcMain.removeHandler("closeLinkStatsWindow")
ipcMain.removeHandler("update-link-stats")
ipcMain.removeHandler("app:open-link-stats-window")
ipcMain.removeHandler("app:close-link-stats-window")
ipcMain.removeHandler("app:update-link-stats")

ipcMain.handle("openLinkStatsWindow", () => {
ipcMain.handle("app:open-link-stats-window", () => {
openLinkStatsWindow()
})
ipcMain.handle("closeLinkStatsWindow", () => closeLinkStatsWindow())
ipcMain.handle("update-link-stats", (_, linkStats) => {
linkStatsWin?.webContents.send("send-link-stats", linkStats)
ipcMain.handle("app:close-link-stats-window", () => closeLinkStatsWindow())
ipcMain.handle("app:update-link-stats", (_, linkStats) => {
linkStatsWin?.webContents.send("app:send-link-stats", linkStats)
const uptimeFormatted = new Date(Math.round(linkStats.uptime) * 1000)
.toISOString()
.substring(11, 19)
Expand Down
15 changes: 5 additions & 10 deletions gcs/electron/modules/webcam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ export function openWebcamPopout(
})
})

// Windows doesn't consider maximising to be fullscreening so we must prevent default
webcamPopoutWin.on("maximize", (e: Event) => {
e.preventDefault()
})

// Ensure initial size fits the aspect ratio ()
webcamPopoutWin.setSize(
webcamPopoutWin.getBounds().width,
Expand All @@ -92,7 +87,7 @@ export function openWebcamPopout(
export function closeWebcamPopout(mainWindow: BrowserWindow | null) {
console.log("Destroying webcam window")
destroyWebcamWindow()
mainWindow?.webContents.send("webcam-closed")
mainWindow?.webContents.send("app:webcam-closed")
}

export function destroyWebcamWindow() {
Expand All @@ -101,11 +96,11 @@ export function destroyWebcamWindow() {
}

export default function registerWebcamIPC(mainWindow: BrowserWindow) {
ipcMain.removeHandler("openWebcamWindow")
ipcMain.removeHandler("closeWebcamWindow")
ipcMain.removeHandler("app:open-webcam-window")
ipcMain.removeHandler("app:close-webcam-window")

ipcMain.handle("openWebcamWindow", (_, videoStreamId, name, aspect) => {
ipcMain.handle("app:open-webcam-window", (_, videoStreamId, name, aspect) => {
openWebcamPopout(videoStreamId, name, aspect)
})
ipcMain.handle("closeWebcamWindow", () => closeWebcamPopout(mainWindow))
ipcMain.handle("app:close-webcam-window", () => closeWebcamPopout(mainWindow))
}
110 changes: 71 additions & 39 deletions gcs/electron/preload.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,82 @@
import { contextBridge, ipcRenderer } from "electron"

// --------- Expose some API to the Renderer process ---------
// Whitelist of allowed IPC channels for security
const ALLOWED_INVOKE_CHANNELS = [
"fla:open-file",
"fla:get-recent-logs",
"fla:clear-recent-logs",
Comment thread
1Blademaster marked this conversation as resolved.
"missions:get-save-mission-file-path",
"app:get-node-env",
"app:get-version",
"app:is-mac",
"settings:fetch-settings",
"settings:save-settings",
"app:open-webcam-window",
"app:close-webcam-window",
"app:open-about-window",
"app:close-about-window",
"app:open-link-stats-window",
"app:close-link-stats-window",
"app:update-link-stats",
]

const ALLOWED_SEND_CHANNELS = [
"window:close",
"window:minimise",
"window:maximise",
"window:reload",
"window:force-reload",
"window:toggle-developer-tools",
"window:actual-size",
"window:toggle-fullscreen",
"window:zoom-in",
"window:zoom-out",
"window:open-file-in-explorer",
]

const ALLOWED_ON_CHANNELS = [
"main-process-message",
"app:webcam-closed",
"app:send-link-stats",
"fla:log-parse-progress",
]

contextBridge.exposeInMainWorld("ipcRenderer", {
...withPrototype(ipcRenderer),
loadFile: (data) => ipcRenderer.invoke("fla:open-file", data),
getRecentLogs: () => ipcRenderer.invoke("fla:get-recent-logs"),
clearRecentLogs: () => ipcRenderer.invoke("fla:clear-recent-logs"),
getSaveMissionFilePath: (options) =>
ipcRenderer.invoke("missions:get-save-mission-file-path", options),
getNodeEnv: () => ipcRenderer.invoke("app:get-node-env"),
getVersion: () => ipcRenderer.invoke("app:get-version"),
getSettings: () => ipcRenderer.invoke("getSettings"),
saveSettings: (settings) => ipcRenderer.invoke("setSettings", settings),
openWebcamWindow: (id, name, aspect) =>
ipcRenderer.invoke("openWebcamWindow", id, name, aspect),
closeWebcamWindow: () => ipcRenderer.invoke("closeWebcamWindow"),
onCameraWindowClose: (callback) =>
ipcRenderer.on("webcam-closed", () => callback()),
openAboutWindow: () => ipcRenderer.invoke("openAboutWindow"),
closeAboutWindow: () => ipcRenderer.invoke("closeAboutWindow"),
openLinkStatsWindow: () => ipcRenderer.invoke("openLinkStatsWindow"),
closeLinkStatsWindow: () => ipcRenderer.invoke("closeLinkStatsWindow"),
updateLinkStats: (linkStats) =>
ipcRenderer.invoke("update-link-stats", linkStats),
onGetLinkStats: (callback) =>
ipcRenderer.on("send-link-stats", (_, stats) => callback(stats)),
})
// Secure invoke method - only allows whitelisted channels
invoke: (channel, ...args) => {
if (ALLOWED_INVOKE_CHANNELS.includes(channel)) {
return ipcRenderer.invoke(channel, ...args)
}
throw new Error(`IPC invoke channel '${channel}' is not allowed`)
},

// `exposeInMainWorld` can't detect attributes and methods of `prototype`, manually patching it.
function withPrototype(obj) {
const protos = Object.getPrototypeOf(obj)
// Secure send method - only allows whitelisted channels
send: (channel, ...args) => {
if (ALLOWED_SEND_CHANNELS.includes(channel)) {
return ipcRenderer.send(channel, ...args)
}
throw new Error(`IPC send channel '${channel}' is not allowed`)
},

for (const [key, value] of Object.entries(protos)) {
if (Object.prototype.hasOwnProperty.call(obj, key)) continue
// Secure on method - only allows whitelisted channels
on: (channel, callback) => {
if (ALLOWED_ON_CHANNELS.includes(channel)) {
return ipcRenderer.on(channel, callback)
}
throw new Error(`IPC on channel '${channel}' is not allowed`)
},

if (typeof value === "function") {
// Some native APIs, like `NodeJS.EventEmitter['on']`, don't work in the Renderer process. Wrapping them into a function.
obj[key] = function (...args) {
return value.call(obj, ...args)
}
} else {
obj[key] = value
// Secure removeAllListeners - only for whitelisted channels
removeAllListeners: (channel) => {
if (ALLOWED_ON_CHANNELS.includes(channel)) {
return ipcRenderer.removeAllListeners(channel)
}
}
return obj
}
throw new Error(
`IPC removeAllListeners channel '${channel}' is not allowed`,
)
},
})

// --------- Preload scripts loading ---------
function domReady(condition = ["complete", "interactive"]) {
Expand Down
4 changes: 2 additions & 2 deletions gcs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"@vitejs/plugin-react": "^4.0.4",
"@vitest/browser": "^2.0.5",
"autoprefixer": "^10.4.16",
"electron": "^26.1.0",
"electron": "^38.0.0",
"electron-builder": "^24.6.4",
"eslint": "^8.48.0",
"eslint-plugin-react-hooks": "^4.6.0",
Expand All @@ -96,7 +96,7 @@
"postcss-preset-mantine": "^1.12.0",
"postcss-simple-vars": "^7.0.1",
"typescript": "^5.2.2",
"vite": "^4.5.3",
"vite": "^7.1.5",
"vite-plugin-electron": "^0.14.0",
"vite-plugin-electron-renderer": "^0.14.5"
},
Expand Down
4 changes: 2 additions & 2 deletions gcs/src/components/SingleRunWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ export default function SingleRunWrapper({ children }) {
// this is done by getting the latest release from the GitHub API and comparing
// it to the current version.

const nodeEnv = await window.ipcRenderer.getNodeEnv()
const nodeEnv = await window.ipcRenderer.invoke("app:get-node-env")
if (nodeEnv !== "production") return

const currentVersion = await window.ipcRenderer.getVersion()
const currentVersion = await window.ipcRenderer.invoke("app:get-version")

// https://docs.github.com/en/rest/releases/releases#get-the-latest-release
const latestGithubRelease = await octokit.request(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
*/

// Native
import { useCallback, useState, useEffect, useRef } from "react"
import { useCallback, useEffect, useRef, useState } from "react"

// Mantine
import { Select, Tabs } from "@mantine/core"
import { useSessionStorage } from "@mantine/hooks"
import { Tabs, Select } from "@mantine/core"

// Helper
import Webcam from "react-webcam"
import { IconExternalLink, IconVideoOff } from "@tabler/icons-react"
import Webcam from "react-webcam"

export default function CameraTabsSection({ tabPadding }) {
// Camera devices
Expand All @@ -21,7 +21,7 @@ export default function CameraTabsSection({ tabPadding }) {
defaultValue: null,
})

window.ipcRenderer.onCameraWindowClose(() => setPictureInPicture(false))
window.ipcRenderer.on("app:webcam-closed", () => setPictureInPicture(false))

// Ref used to get video capture stream to send to new electron window
const videoRef = useRef(null)
Expand All @@ -47,8 +47,9 @@ export default function CameraTabsSection({ tabPadding }) {
streamTrack.getSettings().width / streamTrack.getSettings().height

pictureInPicture
? window.ipcRenderer.closeWebcamWindow()
: window.ipcRenderer.openWebcamWindow(
? window.ipcRenderer.invoke("app:close-webcam-window")
: window.ipcRenderer.invoke(
"app:open-webcam-window",
deviceId,
streamTrack.label,
streamAspect,
Expand Down
4 changes: 2 additions & 2 deletions gcs/src/components/dashboard/webcam/webcam.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client"

import Webcam from "react-webcam"
import { IconX } from "@tabler/icons-react"
import { useRef } from "react"
import Webcam from "react-webcam"

export default function CameraWindow() {
const searchParams = new URLSearchParams(window.location.search)
Expand All @@ -22,7 +22,7 @@ export default function CameraWindow() {
</div>
<button
className="group px-2 no-drag hover:bg-red-500 h-[100%]"
onClick={() => window.ipcRenderer.closeWebcamWindow()}
onClick={() => window.ipcRenderer.invoke("app:close-webcam-window")}
>
<IconX
stroke={2}
Expand Down
2 changes: 1 addition & 1 deletion gcs/src/components/error/errorBoundary.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default function ErrorBoundaryFallback({ error }) {
size="lg"
variant="light"
color="blue"
onClick={() => window.ipcRenderer.send("force_reload")}
onClick={() => window.ipcRenderer.send("window:force-reload")}
className="mt-2"
>
Go Back!
Expand Down
Loading
Loading