2024-08-16 07:15:42 -04:00
|
|
|
// Some of the following was taken from bits and pieces of the vite-typescript
|
|
|
|
// template that ElectronJS provides.
|
|
|
|
import dotenv from 'dotenv'
|
2024-09-05 14:33:41 -04:00
|
|
|
import {
|
|
|
|
app,
|
|
|
|
BrowserWindow,
|
|
|
|
ipcMain,
|
|
|
|
dialog,
|
|
|
|
shell,
|
|
|
|
nativeTheme,
|
|
|
|
} from 'electron'
|
2024-08-16 07:15:42 -04:00
|
|
|
import path from 'path'
|
|
|
|
import { Issuer } from 'openid-client'
|
|
|
|
import { Bonjour, Service } from 'bonjour-service'
|
|
|
|
// @ts-ignore: TS1343
|
|
|
|
import * as kittycad from '@kittycad/lib/import'
|
2024-09-03 12:30:14 -04:00
|
|
|
import electronUpdater, { type AppUpdater } from 'electron-updater'
|
2024-08-24 17:47:09 -07:00
|
|
|
import minimist from 'minimist'
|
|
|
|
import getCurrentProjectFile from 'lib/getCurrentProjectFile'
|
2024-09-03 12:11:29 -07:00
|
|
|
import os from 'node:os'
|
2024-09-09 18:17:45 -04:00
|
|
|
import { reportRejection } from 'lib/trap'
|
2024-11-07 17:23:03 -05:00
|
|
|
import argvFromYargs from './commandLineArgs'
|
2024-08-24 17:47:09 -07:00
|
|
|
|
2024-08-25 12:56:11 -07:00
|
|
|
let mainWindow: BrowserWindow | null = null
|
|
|
|
|
2024-12-10 13:51:24 -05:00
|
|
|
// Supporting multiple instances instead of multiple applications
|
|
|
|
let cmdQPressed = false
|
|
|
|
const instances: BrowserWindow[] = []
|
|
|
|
const gotTheLock = app.requestSingleInstanceLock()
|
|
|
|
if (!gotTheLock) {
|
|
|
|
app.quit()
|
|
|
|
process.exit(0)
|
|
|
|
}
|
|
|
|
|
2024-08-24 17:47:09 -07:00
|
|
|
// Check the command line arguments for a project path
|
|
|
|
const args = parseCLIArgs()
|
2024-08-16 07:15:42 -04:00
|
|
|
|
|
|
|
// If it's not set, scream.
|
2024-08-23 06:02:23 +10:00
|
|
|
const NODE_ENV = process.env.NODE_ENV || 'production'
|
|
|
|
if (!process.env.NODE_ENV)
|
|
|
|
console.warn(
|
|
|
|
'*FOX SCREAM* process.env.NODE_ENV is not explicitly set!, defaulting to production'
|
|
|
|
)
|
2024-09-03 12:30:14 -04:00
|
|
|
// Default prod values
|
|
|
|
|
|
|
|
// dotenv override when present
|
2024-08-16 07:15:42 -04:00
|
|
|
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
|
|
|
|
|
2024-09-03 12:30:14 -04:00
|
|
|
process.env.VITE_KC_API_WS_MODELING_URL ??=
|
|
|
|
'wss://api.zoo.dev/ws/modeling/commands'
|
|
|
|
process.env.VITE_KC_API_BASE_URL ??= 'https://api.zoo.dev'
|
|
|
|
process.env.VITE_KC_SITE_BASE_URL ??= 'https://zoo.dev'
|
|
|
|
process.env.VITE_KC_SKIP_AUTH ??= 'false'
|
|
|
|
process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??= '15000'
|
|
|
|
|
2024-08-25 12:56:11 -07:00
|
|
|
const ZOO_STUDIO_PROTOCOL = 'zoo-studio'
|
|
|
|
|
|
|
|
/// Register our application to handle all "electron-fiddle://" protocols.
|
|
|
|
if (process.defaultApp) {
|
|
|
|
if (process.argv.length >= 2) {
|
|
|
|
app.setAsDefaultProtocolClient(ZOO_STUDIO_PROTOCOL, process.execPath, [
|
|
|
|
path.resolve(process.argv[1]),
|
|
|
|
])
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
app.setAsDefaultProtocolClient(ZOO_STUDIO_PROTOCOL)
|
|
|
|
}
|
|
|
|
|
2024-08-24 17:47:09 -07:00
|
|
|
// Global app listeners
|
2024-08-25 12:56:11 -07:00
|
|
|
// Must be done before ready event.
|
|
|
|
registerStartupListeners()
|
2024-08-24 17:47:09 -07:00
|
|
|
|
2024-09-04 19:18:33 -07:00
|
|
|
const createWindow = (filePath?: string): BrowserWindow => {
|
2024-08-25 12:56:11 -07:00
|
|
|
const newWindow = new BrowserWindow({
|
2024-08-16 07:15:42 -04:00
|
|
|
autoHideMenuBar: true,
|
|
|
|
show: false,
|
|
|
|
width: 1800,
|
|
|
|
height: 1200,
|
|
|
|
webPreferences: {
|
|
|
|
nodeIntegration: false, // do not give the application implicit system access
|
|
|
|
contextIsolation: true, // expose system functions in preload
|
|
|
|
sandbox: false, // expose nodejs in preload
|
|
|
|
preload: path.join(__dirname, './preload.js'),
|
|
|
|
},
|
2024-08-22 17:51:18 +10:00
|
|
|
icon: path.resolve(process.cwd(), 'assets', 'icon.png'),
|
2024-09-03 12:11:29 -07:00
|
|
|
frame: os.platform() !== 'darwin',
|
|
|
|
titleBarStyle: 'hiddenInset',
|
2024-09-05 14:33:41 -04:00
|
|
|
backgroundColor: nativeTheme.shouldUseDarkColors ? '#1C1C1C' : '#FCFCFC',
|
2024-08-16 07:15:42 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
// and load the index.html of the app.
|
|
|
|
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
2024-09-09 18:17:45 -04:00
|
|
|
newWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL).catch(reportRejection)
|
2024-08-16 07:15:42 -04:00
|
|
|
} else {
|
2024-09-09 18:17:45 -04:00
|
|
|
getProjectPathAtStartup(filePath)
|
|
|
|
.then(async (projectPath) => {
|
|
|
|
const startIndex = path.join(
|
|
|
|
__dirname,
|
|
|
|
`../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`
|
|
|
|
)
|
|
|
|
|
|
|
|
if (projectPath === null) {
|
|
|
|
await newWindow.loadFile(startIndex)
|
|
|
|
return
|
|
|
|
}
|
2024-09-04 19:18:33 -07:00
|
|
|
|
2024-09-09 18:17:45 -04:00
|
|
|
console.log('Loading file', projectPath)
|
2024-09-04 19:18:33 -07:00
|
|
|
|
2024-09-09 18:17:45 -04:00
|
|
|
const fullUrl = `/file/${encodeURIComponent(projectPath)}`
|
|
|
|
console.log('Full URL', fullUrl)
|
2024-09-04 19:18:33 -07:00
|
|
|
|
2024-09-09 18:17:45 -04:00
|
|
|
await newWindow.loadFile(startIndex, {
|
|
|
|
hash: fullUrl,
|
|
|
|
})
|
2024-09-04 19:18:33 -07:00
|
|
|
})
|
2024-09-09 18:17:45 -04:00
|
|
|
.catch(reportRejection)
|
2024-08-16 07:15:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Open the DevTools.
|
|
|
|
// mainWindow.webContents.openDevTools()
|
|
|
|
|
2024-08-25 12:56:11 -07:00
|
|
|
newWindow.show()
|
|
|
|
|
2024-12-10 13:51:24 -05:00
|
|
|
instances.push(newWindow)
|
2024-08-25 12:56:11 -07:00
|
|
|
return newWindow
|
2024-08-16 07:15:42 -04:00
|
|
|
}
|
|
|
|
|
2024-12-10 13:51:24 -05:00
|
|
|
// before-quit with multiple instances
|
|
|
|
if (process.platform === 'darwin') {
|
|
|
|
// Quit from the dock context menu should quit the application directly
|
|
|
|
app.on('before-quit', () => {
|
|
|
|
cmdQPressed = true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-09-04 15:47:04 -07:00
|
|
|
// Quit when all windows are closed, even on macOS. There, it's common
|
2024-08-16 07:15:42 -04:00
|
|
|
// for applications and their menu bar to stay active until the user quits
|
2024-09-04 15:47:04 -07:00
|
|
|
// explicitly with Cmd + Q, but it is a really weird behavior with our app.
|
2024-12-10 13:51:24 -05:00
|
|
|
// app.on('window-all-closed', () => {
|
|
|
|
// app.quit()
|
|
|
|
// })
|
2024-08-16 07:15:42 -04:00
|
|
|
app.on('window-all-closed', () => {
|
2024-12-10 13:51:24 -05:00
|
|
|
if (cmdQPressed || process.platform !== 'darwin') {
|
|
|
|
app.quit()
|
|
|
|
}
|
2024-08-16 07:15:42 -04:00
|
|
|
})
|
|
|
|
|
2024-12-10 13:51:24 -05:00
|
|
|
// Various actions can trigger this event, such as launching the application for the first time,
|
|
|
|
// attempting to re-launch the application when it's already running, or clicking on the application's dock or taskbar icon.
|
|
|
|
app.on('activate', () => createWindow())
|
|
|
|
|
2024-08-16 07:15:42 -04:00
|
|
|
// This method will be called when Electron has finished
|
|
|
|
// initialization and is ready to create browser windows.
|
|
|
|
// Some APIs can only be used after this event occurs.
|
2024-08-25 12:56:11 -07:00
|
|
|
app.on('ready', (event, data) => {
|
|
|
|
// Create the mainWindow
|
|
|
|
mainWindow = createWindow()
|
|
|
|
})
|
2024-08-16 07:15:42 -04:00
|
|
|
|
2024-12-10 13:51:24 -05:00
|
|
|
// This event will be emitted inside the primary instance of your application when a second instance
|
|
|
|
// has been executed and calls app.requestSingleInstanceLock().
|
|
|
|
app.on('second-instance', (event, argv, workingDirectory) => createWindow())
|
|
|
|
|
2024-08-16 07:15:42 -04:00
|
|
|
// For now there is no good reason to separate these out to another file(s)
|
|
|
|
// There is just not enough code to warrant it and further abstracts everything
|
|
|
|
// which is already quite abstracted
|
|
|
|
|
|
|
|
ipcMain.handle('app.getPath', (event, data) => {
|
|
|
|
return app.getPath(data)
|
|
|
|
})
|
|
|
|
|
|
|
|
ipcMain.handle('dialog.showOpenDialog', (event, data) => {
|
|
|
|
return dialog.showOpenDialog(data)
|
|
|
|
})
|
|
|
|
ipcMain.handle('dialog.showSaveDialog', (event, data) => {
|
|
|
|
return dialog.showSaveDialog(data)
|
|
|
|
})
|
|
|
|
|
|
|
|
ipcMain.handle('shell.showItemInFolder', (event, data) => {
|
|
|
|
return shell.showItemInFolder(data)
|
|
|
|
})
|
|
|
|
|
|
|
|
ipcMain.handle('shell.openExternal', (event, data) => {
|
|
|
|
return shell.openExternal(data)
|
|
|
|
})
|
|
|
|
|
2024-11-07 17:23:03 -05:00
|
|
|
ipcMain.handle('argv.parser', (event, data) => {
|
|
|
|
return argvFromYargs
|
|
|
|
})
|
|
|
|
|
2024-09-23 10:17:56 -04:00
|
|
|
ipcMain.handle('startDeviceFlow', async (_, host: string) => {
|
2024-08-16 07:15:42 -04:00
|
|
|
// Do an OAuth 2.0 Device Authorization Grant dance to get a token.
|
|
|
|
// We quiet ts because we are not using this in the standard way.
|
|
|
|
// @ts-ignore
|
|
|
|
const issuer = new Issuer({
|
|
|
|
device_authorization_endpoint: `${host}/oauth2/device/auth`,
|
|
|
|
token_endpoint: `${host}/oauth2/device/token`,
|
|
|
|
})
|
|
|
|
const client = new issuer.Client({
|
|
|
|
// We can hardcode the client ID.
|
|
|
|
// This value is safe to be embedded in version control.
|
|
|
|
// This is the client ID of the KittyCAD app.
|
|
|
|
client_id: '2af127fb-e14e-400a-9c57-a9ed08d1a5b7',
|
|
|
|
token_endpoint_auth_method: 'none',
|
|
|
|
})
|
|
|
|
|
|
|
|
const handle = await client.deviceAuthorization()
|
|
|
|
|
2024-09-23 10:17:56 -04:00
|
|
|
// Register this handle to be used later.
|
|
|
|
ipcMain.handleOnce('loginWithDeviceFlow', async () => {
|
|
|
|
if (!handle) {
|
|
|
|
return Promise.reject(
|
|
|
|
new Error(
|
|
|
|
'No handle available. Did you call startDeviceFlow before calling this?'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
shell.openExternal(handle.verification_uri_complete).catch(reportRejection)
|
|
|
|
|
|
|
|
// Wait for the user to login.
|
|
|
|
try {
|
|
|
|
console.log('Polling for token')
|
|
|
|
const tokenSet = await handle.poll()
|
|
|
|
console.log('Received token set')
|
|
|
|
console.log(tokenSet)
|
|
|
|
return tokenSet.access_token
|
|
|
|
} catch (e) {
|
|
|
|
console.log(e)
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.reject(new Error('No access token received'))
|
|
|
|
})
|
2024-08-16 07:15:42 -04:00
|
|
|
|
2024-09-23 10:17:56 -04:00
|
|
|
// Return the user code so the app can display it.
|
|
|
|
return handle.user_code
|
2024-08-16 07:15:42 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
ipcMain.handle('kittycad', (event, data) => {
|
|
|
|
return data.access
|
|
|
|
.split('.')
|
|
|
|
.reduce(
|
|
|
|
(obj: any, prop: any) => obj[prop],
|
|
|
|
kittycad
|
|
|
|
)(data.args)
|
|
|
|
})
|
|
|
|
|
|
|
|
ipcMain.handle('find_machine_api', () => {
|
|
|
|
const timeoutAfterMs = 5000
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
// if it takes too long reject this promise
|
|
|
|
setTimeout(() => resolve(null), timeoutAfterMs)
|
|
|
|
const bonjourEt = new Bonjour({}, (error: Error) => {
|
|
|
|
console.log('An issue with Bonjour services was encountered!')
|
|
|
|
console.error(error)
|
|
|
|
resolve(null)
|
|
|
|
})
|
|
|
|
console.log('Looking for machine API...')
|
2024-08-19 15:57:31 -07:00
|
|
|
bonjourEt.find(
|
|
|
|
{ protocol: 'tcp', type: 'machine-api' },
|
|
|
|
(service: Service) => {
|
|
|
|
console.log('Found machine API!', JSON.stringify(service))
|
|
|
|
if (!service.addresses || service.addresses?.length === 0) {
|
|
|
|
console.log('No addresses found for machine API!')
|
|
|
|
return resolve(null)
|
|
|
|
}
|
|
|
|
const ip = service.addresses[0]
|
|
|
|
const port = service.port
|
|
|
|
// We want to return the ip address of the machine API.
|
2024-10-17 15:30:46 -07:00
|
|
|
console.log(`Machine API found at ${ip}:${port}`)
|
2024-08-19 15:57:31 -07:00
|
|
|
resolve(`${ip}:${port}`)
|
|
|
|
}
|
|
|
|
)
|
2024-08-16 07:15:42 -04:00
|
|
|
})
|
|
|
|
})
|
2024-08-24 17:47:09 -07:00
|
|
|
|
2024-09-03 12:30:14 -04:00
|
|
|
export function getAutoUpdater(): AppUpdater {
|
|
|
|
// Using destructuring to access autoUpdater due to the CommonJS module of 'electron-updater'.
|
|
|
|
// It is a workaround for ESM compatibility issues, see https://github.com/electron-userland/electron-builder/issues/7976.
|
|
|
|
const { autoUpdater } = electronUpdater
|
|
|
|
return autoUpdater
|
|
|
|
}
|
|
|
|
|
2024-09-09 18:17:45 -04:00
|
|
|
app.on('ready', () => {
|
2024-09-03 12:30:14 -04:00
|
|
|
const autoUpdater = getAutoUpdater()
|
2024-09-24 13:55:42 -04:00
|
|
|
setTimeout(() => {
|
|
|
|
autoUpdater.checkForUpdates().catch(reportRejection)
|
|
|
|
}, 1000)
|
2024-09-03 12:30:14 -04:00
|
|
|
const fifteenMinutes = 15 * 60 * 1000
|
|
|
|
setInterval(() => {
|
2024-09-24 13:55:42 -04:00
|
|
|
autoUpdater.checkForUpdates().catch(reportRejection)
|
2024-09-03 12:30:14 -04:00
|
|
|
}, fifteenMinutes)
|
|
|
|
|
2024-10-10 12:16:45 -04:00
|
|
|
autoUpdater.on('error', (error) => {
|
|
|
|
console.error('updater-error', error)
|
|
|
|
mainWindow?.webContents.send('updater-error', error)
|
|
|
|
})
|
|
|
|
|
2024-09-03 12:30:14 -04:00
|
|
|
autoUpdater.on('update-available', (info) => {
|
|
|
|
console.log('update-available', info)
|
|
|
|
})
|
|
|
|
|
2024-10-10 12:16:45 -04:00
|
|
|
autoUpdater.prependOnceListener('download-progress', (progress) => {
|
|
|
|
// For now, we'll send nothing and just start a loading spinner.
|
|
|
|
// See below for a TODO to send progress data to the renderer.
|
|
|
|
console.log('update-download-start', {
|
|
|
|
version: '',
|
|
|
|
})
|
|
|
|
mainWindow?.webContents.send('update-download-start', progress)
|
|
|
|
})
|
|
|
|
|
|
|
|
autoUpdater.on('download-progress', (progress) => {
|
|
|
|
// TODO: in a future PR (https://github.com/KittyCAD/modeling-app/issues/3994)
|
|
|
|
// send this data to mainWindow to show a progress bar for the download.
|
|
|
|
console.log('download-progress', progress)
|
|
|
|
})
|
|
|
|
|
2024-09-03 12:30:14 -04:00
|
|
|
autoUpdater.on('update-downloaded', (info) => {
|
|
|
|
console.log('update-downloaded', info)
|
2024-10-15 07:30:00 -04:00
|
|
|
mainWindow?.webContents.send('update-downloaded', {
|
|
|
|
version: info.version,
|
|
|
|
releaseNotes: info.releaseNotes,
|
|
|
|
})
|
2024-09-24 13:55:42 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
ipcMain.handle('app.restart', () => {
|
|
|
|
autoUpdater.quitAndInstall()
|
2024-09-03 12:30:14 -04:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2024-09-04 19:18:33 -07:00
|
|
|
const getProjectPathAtStartup = async (
|
|
|
|
filePath?: string
|
|
|
|
): Promise<string | null> => {
|
2024-08-24 17:47:09 -07:00
|
|
|
// If we are in development mode, we don't want to load a project at
|
|
|
|
// startup.
|
|
|
|
// Since the args passed are always '.'
|
|
|
|
if (NODE_ENV !== 'production') {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2024-09-04 19:18:33 -07:00
|
|
|
let projectPath: string | null = filePath || null
|
|
|
|
if (projectPath === null) {
|
|
|
|
// macOS: open-file events that were received before the app is ready
|
|
|
|
const macOpenFiles: string[] = (global as any).macOpenFiles
|
|
|
|
if (macOpenFiles && macOpenFiles && macOpenFiles.length > 0) {
|
|
|
|
projectPath = macOpenFiles[0] // We only do one project at a time
|
|
|
|
}
|
|
|
|
// Reset this so we don't accidentally use it again.
|
|
|
|
const macOpenFilesEmpty: string[] = []
|
|
|
|
// @ts-ignore
|
|
|
|
global['macOpenFiles'] = macOpenFilesEmpty
|
|
|
|
|
|
|
|
// macOS: open-url events that were received before the app is ready
|
|
|
|
const getOpenUrls: string[] = (global as any).getOpenUrls
|
|
|
|
if (getOpenUrls && getOpenUrls.length > 0) {
|
|
|
|
projectPath = getOpenUrls[0] // We only do one project at a
|
|
|
|
}
|
|
|
|
// Reset this so we don't accidentally use it again.
|
|
|
|
// @ts-ignore
|
|
|
|
global['getOpenUrls'] = []
|
|
|
|
|
|
|
|
// Check if we have a project path in the command line arguments
|
|
|
|
// If we do, we will load the project at that path
|
|
|
|
if (args._.length > 1) {
|
|
|
|
if (args._[1].length > 0) {
|
|
|
|
projectPath = args._[1]
|
|
|
|
// Reset all this value so we don't accidentally use it again.
|
|
|
|
args._[1] = ''
|
|
|
|
}
|
2024-08-24 17:47:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (projectPath) {
|
|
|
|
// We have a project path, load the project information.
|
|
|
|
console.log(`Loading project at startup: ${projectPath}`)
|
2024-09-04 19:18:33 -07:00
|
|
|
const currentFile = await getCurrentProjectFile(projectPath)
|
|
|
|
|
|
|
|
if (currentFile instanceof Error) {
|
|
|
|
console.error(currentFile)
|
|
|
|
return null
|
2024-08-24 17:47:09 -07:00
|
|
|
}
|
|
|
|
|
2024-09-04 19:18:33 -07:00
|
|
|
console.log(`Project loaded: ${currentFile}`)
|
|
|
|
return currentFile
|
2024-08-24 17:47:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return null
|
2024-09-04 19:18:33 -07:00
|
|
|
}
|
2024-08-24 17:47:09 -07:00
|
|
|
|
|
|
|
function parseCLIArgs(): minimist.ParsedArgs {
|
|
|
|
return minimist(process.argv, {})
|
|
|
|
}
|
|
|
|
|
2024-08-25 12:56:11 -07:00
|
|
|
function registerStartupListeners() {
|
2024-08-24 17:47:09 -07:00
|
|
|
/**
|
|
|
|
* macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before
|
|
|
|
* the app-ready event. We listen very early for open-file and remember this upon startup as path to open.
|
|
|
|
*/
|
|
|
|
const macOpenFiles: string[] = []
|
|
|
|
// @ts-ignore
|
|
|
|
global['macOpenFiles'] = macOpenFiles
|
|
|
|
app.on('open-file', function (event, path) {
|
2024-08-25 12:56:11 -07:00
|
|
|
event.preventDefault()
|
|
|
|
|
|
|
|
// If we have a mainWindow, lets open another window.
|
|
|
|
if (mainWindow) {
|
2024-09-04 19:18:33 -07:00
|
|
|
createWindow(path)
|
|
|
|
} else {
|
|
|
|
macOpenFiles.push(path)
|
2024-08-25 12:56:11 -07:00
|
|
|
}
|
2024-08-24 17:47:09 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
/**
|
|
|
|
* macOS: react to open-url requests.
|
|
|
|
*/
|
|
|
|
const openUrls: string[] = []
|
2024-08-25 12:56:11 -07:00
|
|
|
// @ts-ignore
|
|
|
|
global['openUrls'] = openUrls
|
2024-08-24 17:47:09 -07:00
|
|
|
const onOpenUrl = function (
|
|
|
|
event: { preventDefault: () => void },
|
|
|
|
url: string
|
|
|
|
) {
|
|
|
|
event.preventDefault()
|
|
|
|
|
2024-08-25 12:56:11 -07:00
|
|
|
// If we have a mainWindow, lets open another window.
|
|
|
|
if (mainWindow) {
|
2024-09-04 19:18:33 -07:00
|
|
|
createWindow(url)
|
|
|
|
} else {
|
|
|
|
openUrls.push(url)
|
2024-08-25 12:56:11 -07:00
|
|
|
}
|
2024-08-24 17:47:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
app.on('will-finish-launching', function () {
|
|
|
|
app.on('open-url', onOpenUrl)
|
|
|
|
})
|
|
|
|
}
|