2024-08-16 07:15:42 -04:00
|
|
|
import { ipcRenderer, contextBridge } from 'electron'
|
|
|
|
import path from 'path'
|
|
|
|
import fs from 'node:fs/promises'
|
|
|
|
import os from 'node:os'
|
|
|
|
import fsSync from 'node:fs'
|
|
|
|
import packageJson from '../package.json'
|
2024-10-25 19:28:10 -04:00
|
|
|
import { MachinesListing } from 'components/MachineManagerProvider'
|
2024-10-07 23:07:18 -04:00
|
|
|
import chokidar from 'chokidar'
|
2024-08-16 07:15:42 -04:00
|
|
|
|
|
|
|
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
|
|
|
|
const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args)
|
|
|
|
const openExternal = (url: any) => ipcRenderer.invoke('shell.openExternal', url)
|
|
|
|
const showInFolder = (path: string) =>
|
|
|
|
ipcRenderer.invoke('shell.showItemInFolder', path)
|
2024-09-23 10:17:56 -04:00
|
|
|
const startDeviceFlow = (host: string): Promise<string> =>
|
|
|
|
ipcRenderer.invoke('startDeviceFlow', host)
|
|
|
|
const loginWithDeviceFlow = (): Promise<string> =>
|
|
|
|
ipcRenderer.invoke('loginWithDeviceFlow')
|
2024-10-15 07:30:00 -04:00
|
|
|
const onUpdateDownloaded = (
|
|
|
|
callback: (value: { version: string; releaseNotes: string }) => void
|
|
|
|
) => ipcRenderer.on('update-downloaded', (_event, value) => callback(value))
|
2024-10-10 12:16:45 -04:00
|
|
|
const onUpdateDownloadStart = (
|
|
|
|
callback: (value: { version: string }) => void
|
|
|
|
) => ipcRenderer.on('update-download-start', (_event, value) => callback(value))
|
|
|
|
const onUpdateError = (callback: (value: Error) => void) =>
|
|
|
|
ipcRenderer.on('update-error', (_event, value) => callback(value))
|
2024-09-24 13:55:42 -04:00
|
|
|
const appRestart = () => ipcRenderer.invoke('app.restart')
|
2024-08-16 07:15:42 -04:00
|
|
|
|
|
|
|
const isMac = os.platform() === 'darwin'
|
|
|
|
const isWindows = os.platform() === 'win32'
|
|
|
|
const isLinux = os.platform() === 'linux'
|
|
|
|
|
2024-10-17 23:42:24 -04:00
|
|
|
let fsWatchListeners = new Map<
|
|
|
|
string,
|
|
|
|
Map<
|
|
|
|
string,
|
|
|
|
{
|
|
|
|
watcher: ReturnType<typeof chokidar.watch>
|
|
|
|
callback: (eventType: string, path: string) => void
|
|
|
|
}
|
|
|
|
>
|
|
|
|
>()
|
2024-10-03 13:02:57 -04:00
|
|
|
|
2024-10-17 23:42:24 -04:00
|
|
|
const watchFileOn = (
|
|
|
|
path: string,
|
|
|
|
key: string,
|
|
|
|
callback: (eventType: string, path: string) => void
|
|
|
|
) => {
|
|
|
|
let watchers = fsWatchListeners.get(path)
|
|
|
|
if (!watchers) {
|
|
|
|
watchers = new Map()
|
|
|
|
}
|
|
|
|
const watcher = chokidar.watch(path, { depth: 1 })
|
2024-10-07 23:07:18 -04:00
|
|
|
watcher.on('all', callback)
|
2024-10-17 23:42:24 -04:00
|
|
|
watchers.set(key, { watcher, callback })
|
|
|
|
fsWatchListeners.set(path, watchers)
|
2024-10-03 13:02:57 -04:00
|
|
|
}
|
2024-10-17 23:42:24 -04:00
|
|
|
const watchFileOff = (path: string, key: string) => {
|
|
|
|
const watchers = fsWatchListeners.get(path)
|
|
|
|
if (!watchers) return
|
|
|
|
const data = watchers.get(key)
|
|
|
|
if (!data) {
|
|
|
|
console.warn(
|
|
|
|
"Trying to remove a watcher, callback that doesn't exist anymore. Suspicious."
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const { watcher, callback } = data
|
|
|
|
watcher.off('all', callback)
|
|
|
|
watchers.delete(key)
|
|
|
|
if (watchers.size === 0) {
|
|
|
|
fsWatchListeners.delete(path)
|
|
|
|
} else {
|
|
|
|
fsWatchListeners.set(path, watchers)
|
|
|
|
}
|
2024-10-03 13:02:57 -04:00
|
|
|
}
|
2024-10-18 10:43:01 -04:00
|
|
|
const readFile = fs.readFile
|
2024-08-16 07:15:42 -04:00
|
|
|
// It seems like from the node source code this does not actually block but also
|
|
|
|
// don't trust me on that (jess).
|
|
|
|
const exists = (path: string) => fsSync.existsSync(path)
|
|
|
|
const rename = (prev: string, next: string) => fs.rename(prev, next)
|
|
|
|
const writeFile = (path: string, data: string | Uint8Array) =>
|
|
|
|
fs.writeFile(path, data, 'utf-8')
|
|
|
|
const readdir = (path: string) => fs.readdir(path, 'utf-8')
|
|
|
|
const stat = (path: string) =>
|
|
|
|
fs.stat(path).catch((e) => Promise.reject(e.code))
|
|
|
|
// Electron has behavior where it doesn't clone the prototype chain over.
|
|
|
|
// So we need to call stat.isDirectory on this side.
|
|
|
|
const statIsDirectory = (path: string) =>
|
|
|
|
stat(path).then((res) => res.isDirectory())
|
|
|
|
const getPath = async (name: string) => ipcRenderer.invoke('app.getPath', name)
|
|
|
|
|
|
|
|
const exposeProcessEnvs = (varNames: Array<string>) => {
|
|
|
|
const envs: Record<string, string> = {}
|
|
|
|
varNames.forEach((varName) => {
|
|
|
|
const envVar = process.env[varName]
|
|
|
|
if (envVar) {
|
|
|
|
envs[varName] = envVar
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return envs
|
|
|
|
}
|
|
|
|
|
|
|
|
const kittycad = (access: string, args: any) =>
|
|
|
|
ipcRenderer.invoke('kittycad', { access, args })
|
|
|
|
|
|
|
|
// We could probably do this from the renderer side, but I fear CORS will
|
|
|
|
// bite our butts.
|
2024-10-17 15:30:46 -07:00
|
|
|
const listMachines = async (
|
|
|
|
machineApiAddr: string
|
|
|
|
): Promise<MachinesListing> => {
|
|
|
|
return fetch(`http://${machineApiAddr}/machines`).then((resp) => {
|
|
|
|
return resp.json()
|
|
|
|
})
|
2024-08-16 07:15:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
const getMachineApiIp = async (): Promise<String | null> =>
|
|
|
|
ipcRenderer.invoke('find_machine_api')
|
|
|
|
|
2024-11-07 17:23:03 -05:00
|
|
|
const getArgvParsed = () => {
|
|
|
|
return ipcRenderer.invoke('argv.parser')
|
|
|
|
}
|
|
|
|
|
2024-08-16 07:15:42 -04:00
|
|
|
contextBridge.exposeInMainWorld('electron', {
|
2024-09-23 10:17:56 -04:00
|
|
|
startDeviceFlow,
|
|
|
|
loginWithDeviceFlow,
|
2024-08-16 07:15:42 -04:00
|
|
|
// Passing fs directly is not recommended since it gives a lot of power
|
|
|
|
// to the browser side / potential malicious code. We restrict what is
|
|
|
|
// exported.
|
2024-10-03 13:02:57 -04:00
|
|
|
watchFileOn,
|
|
|
|
watchFileOff,
|
2024-08-16 07:15:42 -04:00
|
|
|
readFile,
|
|
|
|
writeFile,
|
|
|
|
exists,
|
|
|
|
readdir,
|
|
|
|
rename,
|
|
|
|
rm: fs.rm,
|
|
|
|
path,
|
|
|
|
stat,
|
|
|
|
statIsDirectory,
|
|
|
|
mkdir: fs.mkdir,
|
|
|
|
// opens a dialog
|
|
|
|
open,
|
|
|
|
save,
|
|
|
|
// opens the URL
|
|
|
|
openExternal,
|
|
|
|
showInFolder,
|
|
|
|
getPath,
|
|
|
|
packageJson,
|
|
|
|
arch: process.arch,
|
|
|
|
platform: process.platform,
|
|
|
|
version: process.version,
|
|
|
|
join: path.join,
|
|
|
|
sep: path.sep,
|
|
|
|
os: {
|
|
|
|
isMac,
|
|
|
|
isWindows,
|
|
|
|
isLinux,
|
|
|
|
},
|
|
|
|
process: {
|
|
|
|
// Setter/getter has to be created because
|
|
|
|
// these are read-only over the boundary.
|
|
|
|
env: Object.assign(
|
|
|
|
{},
|
|
|
|
exposeProcessEnvs([
|
|
|
|
'NODE_ENV',
|
|
|
|
'TEST_SETTINGS_FILE_KEY',
|
|
|
|
'VITE_KC_API_WS_MODELING_URL',
|
|
|
|
'VITE_KC_API_BASE_URL',
|
|
|
|
'VITE_KC_SITE_BASE_URL',
|
|
|
|
'VITE_KC_SKIP_AUTH',
|
|
|
|
'VITE_KC_CONNECTION_TIMEOUT_MS',
|
|
|
|
'VITE_KC_DEV_TOKEN',
|
|
|
|
'IS_PLAYWRIGHT',
|
|
|
|
|
|
|
|
// Really we shouldn't use these and our code should use NODE_ENV
|
|
|
|
'DEV',
|
|
|
|
'PROD',
|
|
|
|
'TEST',
|
|
|
|
'CI',
|
|
|
|
])
|
|
|
|
),
|
|
|
|
},
|
|
|
|
kittycad,
|
|
|
|
listMachines,
|
|
|
|
getMachineApiIp,
|
2024-10-10 12:16:45 -04:00
|
|
|
onUpdateDownloadStart,
|
2024-09-24 13:55:42 -04:00
|
|
|
onUpdateDownloaded,
|
2024-10-10 12:16:45 -04:00
|
|
|
onUpdateError,
|
2024-09-24 13:55:42 -04:00
|
|
|
appRestart,
|
2024-11-07 17:23:03 -05:00
|
|
|
getArgvParsed,
|
2024-08-16 07:15:42 -04:00
|
|
|
})
|