diff --git a/apps/desktop-wallet/src/electron/autoUpdater.ts b/apps/desktop-wallet/src/electron/autoUpdater.ts
new file mode 100644
index 000000000..10125f19e
--- /dev/null
+++ b/apps/desktop-wallet/src/electron/autoUpdater.ts
@@ -0,0 +1,51 @@
+/*
+Copyright 2018 - 2024 The Alephium Authors
+This file is part of the alephium project.
+
+The library is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+The library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with the library. If not, see .
+*/
+
+import { BrowserWindow, ipcMain } from 'electron'
+import { autoUpdater } from 'electron-updater'
+
+import { IS_RC } from '@/electron/utils'
+
+export const configureAutoUpdater = () => {
+ autoUpdater.autoDownload = false
+ autoUpdater.allowPrerelease = IS_RC
+}
+
+export const setupAutoUpdaterListeners = (mainWindow: BrowserWindow) => {
+ autoUpdater.on('download-progress', (info) => mainWindow.webContents.send('updater:download-progress', info))
+
+ autoUpdater.on('error', (error) => mainWindow.webContents.send('updater:error', error))
+
+ autoUpdater.on('update-downloaded', (event) => mainWindow.webContents.send('updater:updateDownloaded', event))
+}
+
+export const handleAutoUpdaterUserActions = () => {
+ ipcMain.handle('updater:checkForUpdates', async () => {
+ try {
+ const result = await autoUpdater.checkForUpdates()
+
+ return result?.updateInfo?.version
+ } catch (e) {
+ console.error(e)
+ }
+ })
+
+ ipcMain.handle('updater:startUpdateDownload', () => autoUpdater.downloadUpdate())
+
+ ipcMain.handle('updater:quitAndInstallUpdate', () => autoUpdater.quitAndInstall())
+}
diff --git a/apps/desktop-wallet/public/electron.js b/apps/desktop-wallet/src/electron/index.ts
similarity index 58%
rename from apps/desktop-wallet/public/electron.js
rename to apps/desktop-wallet/src/electron/index.ts
index 0f719e82f..5f4be95f3 100644
--- a/apps/desktop-wallet/public/electron.js
+++ b/apps/desktop-wallet/src/electron/index.ts
@@ -16,22 +16,25 @@ You should have received a copy of the GNU Lesser General Public License
along with the library. If not, see .
*/
-// Modules to control application life and create native browser window
-const { app, BrowserWindow, dialog, ipcMain, Menu, nativeTheme, shell, nativeImage, protocol } = require('electron')
-const path = require('path')
-const isDev = require('electron-is-dev')
-const contextMenu = require('electron-context-menu')
-const { autoUpdater } = require('electron-updater')
-const TransportNodeHid = require('@ledgerhq/hw-transport-node-hid').default
-const AlephiumLedgerApp = require('@alephium/ledger-app').AlephiumApp
-const { listen } = require('@ledgerhq/logs')
-const web3 = require('@alephium/web3-wallet')
+import { AlephiumApp as AlephiumLedgerApp } from '@alephium/ledger-app'
+import { getHumanReadableError } from '@alephium/shared'
+import web3 from '@alephium/web3-wallet'
+import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
+import { listen } from '@ledgerhq/logs'
+import { app, BrowserWindow, ipcMain, nativeImage, protocol, shell } from 'electron'
+import contextMenu from 'electron-context-menu'
+import isDev from 'electron-is-dev'
+import path from 'path'
+
+import { configureAutoUpdater, handleAutoUpdaterUserActions, setupAutoUpdaterListeners } from '@/electron/autoUpdater'
+import { setupAppMenu } from '@/electron/menu'
+import { handleNativeThemeUserActions, setupNativeThemeListeners } from '@/electron/nativeTheme'
+import { IS_RC, isMac, isWindows } from '@/electron/utils'
+
+configureAutoUpdater()
let alephiumLedgerApp
-const CURRENT_VERSION = app.getVersion()
-const IS_RC = CURRENT_VERSION.includes('-rc.')
-
// Handle deep linking for alephium://
const ALEPHIUM = 'alephium'
const ALEPHIUM_WALLET_CONNECT_DEEP_LINK_PREFIX = `${ALEPHIUM}://wc`
@@ -55,123 +58,17 @@ if (process.defaultApp) {
contextMenu()
-autoUpdater.autoDownload = false
-autoUpdater.allowPrerelease = IS_RC
-
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
-let mainWindow
+let mainWindow: Electron.BrowserWindow | null
// Build menu
-const isMac = process.platform === 'darwin'
-const isWindows = process.platform === 'win32'
-
-const template = [
- ...(isMac
- ? [
- {
- label: app.name,
- submenu: [
- { role: 'about' },
- { type: 'separator' },
- { role: 'services' },
- { type: 'separator' },
- { role: 'hide' },
- { role: 'hideOthers' },
- { role: 'unhide' },
- { type: 'separator' },
- { role: 'quit' }
- ]
- }
- ]
- : []),
- {
- label: 'Edit',
- submenu: [
- { role: 'undo' },
- { role: 'redo' },
- { type: 'separator' },
- { role: 'cut' },
- { role: 'copy' },
- { role: 'paste' },
- ...(isMac
- ? [
- { role: 'pasteAndMatchStyle' },
- { role: 'delete' },
- { role: 'selectAll' },
- { type: 'separator' },
- {
- label: 'Speech',
- submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }]
- }
- ]
- : [{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }])
- ]
- },
- {
- label: 'View',
- submenu: [
- { role: 'resetZoom' },
- { role: 'zoomIn' },
- { role: 'zoomOut' },
- { type: 'separator' },
- { role: 'togglefullscreen' },
- { type: 'separator' },
- { role: 'reload' },
- { role: 'forceReload' }
- ]
- },
- {
- label: 'Window',
- submenu: [
- { role: 'minimize' },
- ...(isMac ? [{ role: 'zoom' }, { type: 'separator' }, { role: 'front' }] : [{ role: 'close' }])
- ]
- },
- {
- role: 'help',
- submenu: [
- ...(isMac
- ? []
- : isWindows
- ? [{ role: 'about' }, { type: 'separator' }]
- : [
- {
- label: 'About',
- click: async () => {
- dialog.showMessageBox(mainWindow, {
- message: `Version ${CURRENT_VERSION}`,
- title: 'About',
- type: 'info'
- })
- }
- }
- ]),
- {
- label: 'Report an issue',
- click: async () => {
- await shell.openExternal('https://github.com/alephium/alephium-frontend/issues/new')
- }
- },
- {
- label: 'Get some help',
- click: async () => {
- await shell.openExternal('https://discord.gg/JErgRBfRSB')
- }
- }
- ]
- }
-]
-
const appURL = isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`
-let deepLinkUri = null
-
-function createWindow() {
- const menu = Menu.buildFromTemplate(template)
- Menu.setApplicationMenu(menu)
+let deepLinkUri: string | null = null
+const createWindow = () => {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
@@ -186,6 +83,8 @@ function createWindow() {
}
})
+ setupAppMenu(mainWindow)
+
if (!isMac && !isWindows) {
mainWindow.setIcon(
nativeImage.createFromPath(path.join(__dirname, isDev ? 'icons/logo-48.png' : '../build/icons/logo-48.png'))
@@ -205,17 +104,10 @@ function createWindow() {
return { action: 'deny' }
})
- nativeTheme.on('updated', () =>
- mainWindow?.webContents.send('theme:shouldUseDarkColors', nativeTheme.shouldUseDarkColors)
- )
-
mainWindow.on('closed', () => (mainWindow = null))
- autoUpdater.on('download-progress', (info) => mainWindow?.webContents.send('updater:download-progress', info))
-
- autoUpdater.on('error', (error) => mainWindow?.webContents.send('updater:error', error))
-
- autoUpdater.on('update-downloaded', (event) => mainWindow?.webContents.send('updater:updateDownloaded', event))
+ setupNativeThemeListeners(mainWindow)
+ setupAutoUpdaterListeners(mainWindow)
if (!isMac) {
if (process.argv.length > 1) {
@@ -230,7 +122,6 @@ function createWindow() {
if (!app.requestSingleInstanceLock()) {
app.quit()
- return
}
// Activate the window of primary instance when a second instance starts
@@ -258,40 +149,20 @@ app.on('second-instance', (_event, args) => {
app.on('ready', async function () {
if (isDev) {
const {
- default: { default: installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS }
+ default: installExtension,
+ REACT_DEVELOPER_TOOLS,
+ REDUX_DEVTOOLS
} = await import('electron-devtools-installer')
await installExtension(REACT_DEVELOPER_TOOLS)
await installExtension(REDUX_DEVTOOLS)
}
- ipcMain.handle('theme:setNativeTheme', (_, theme) => (nativeTheme.themeSource = theme))
-
- // nativeTheme must be reassigned like this because its properties are all computed, so
- // they can't be serialized to be passed over channels.
- ipcMain.handle('theme:getNativeTheme', ({ sender }) =>
- sender.send('theme:getNativeTheme', {
- shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
- themeSource: nativeTheme.themeSource
- })
- )
-
- ipcMain.handle('updater:checkForUpdates', async () => {
- try {
- const result = await autoUpdater.checkForUpdates()
-
- return result?.updateInfo?.version
- } catch (e) {
- console.error(e)
- }
- })
-
- ipcMain.handle('updater:startUpdateDownload', () => autoUpdater.downloadUpdate())
-
- ipcMain.handle('updater:quitAndInstallUpdate', () => autoUpdater.quitAndInstall())
+ handleNativeThemeUserActions()
+ handleAutoUpdaterUserActions()
ipcMain.handle('app:hide', () => {
if (isWindows) {
- mainWindow.blur()
+ mainWindow?.blur()
} else {
app.hide()
}
@@ -299,10 +170,10 @@ app.on('ready', async function () {
ipcMain.handle('app:show', () => {
if (isWindows) {
- mainWindow.minimize()
- mainWindow.restore()
+ mainWindow?.minimize()
+ mainWindow?.restore()
} else {
- mainWindow.show()
+ mainWindow?.show()
}
})
@@ -312,16 +183,14 @@ app.on('ready', async function () {
if (preferedLanguages.length > 0) return preferedLanguages[0]
})
- ipcMain.handle('app:getSystemRegion', () => {
- return app.getSystemLocale()
- })
+ ipcMain.handle('app:getSystemRegion', () => app.getSystemLocale())
ipcMain.handle('app:setProxySettings', async (_, proxySettings) => {
const { address, port } = proxySettings
const proxyRules = !address && !port ? undefined : `socks5://${address}:${port}`
try {
- await mainWindow.webContents.session.setProxy({ proxyRules })
+ await mainWindow?.webContents.session.setProxy({ proxyRules })
} catch (e) {
console.error(e)
}
@@ -366,7 +235,7 @@ app.on('ready', async function () {
success: true,
version,
initialAddress: { hash: account.address, index: 0, publicKey: account.publicKey },
- deviceModel: alephiumLedgerApp.transport.deviceModel.productName
+ deviceModel: alephiumLedgerApp.transport.deviceModel?.productName
}
transport.close()
@@ -380,7 +249,7 @@ app.on('ready', async function () {
console.error('🔌❌', error)
// Retry one more time if the error is unknown, usually the Ledger app needs a moment
- if (error.message.includes('UNKNOWN_ERROR')) {
+ if (getHumanReadableError(error, '').includes('UNKNOWN_ERROR')) {
return new Promise((s) => setTimeout(s, 1000)).then(connect).catch((error) => ({ success: false, error }))
}
@@ -411,7 +280,7 @@ app.on('open-url', (_, url) => {
}
})
-const extractWalletConnectUri = (url) =>
+const extractWalletConnectUri = (url: string) =>
url.substring(url.indexOf(ALEPHIUM_WALLET_CONNECT_URI_PREFIX) + ALEPHIUM_WALLET_CONNECT_URI_PREFIX.length)
// Handle window controls via IPC
diff --git a/apps/desktop-wallet/src/electron/menu.ts b/apps/desktop-wallet/src/electron/menu.ts
new file mode 100644
index 000000000..358f65f08
--- /dev/null
+++ b/apps/desktop-wallet/src/electron/menu.ts
@@ -0,0 +1,126 @@
+/*
+Copyright 2018 - 2024 The Alephium Authors
+This file is part of the alephium project.
+
+The library is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+The library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with the library. If not, see .
+*/
+
+import { app, BrowserWindow, dialog, Menu, MenuItemConstructorOptions, shell } from 'electron'
+
+import { CURRENT_VERSION, isMac, isWindows } from '@/electron/utils'
+
+export const setupAppMenu = (mainWindow: BrowserWindow) => {
+ const menu = Menu.buildFromTemplate(generateMenuTemplate(mainWindow))
+ Menu.setApplicationMenu(menu)
+}
+
+const generateMenuTemplate = (mainWindow: BrowserWindow): MenuItemConstructorOptions[] => [
+ ...(isMac
+ ? ([
+ {
+ label: app.name,
+ submenu: [
+ { role: 'about' },
+ { type: 'separator' },
+ { role: 'services' },
+ { type: 'separator' },
+ { role: 'hide' },
+ { role: 'hideOthers' },
+ { role: 'unhide' },
+ { type: 'separator' },
+ { role: 'quit' }
+ ]
+ }
+ ] as MenuItemConstructorOptions[])
+ : []),
+ {
+ label: 'Edit',
+ submenu: [
+ { role: 'undo' },
+ { role: 'redo' },
+ { type: 'separator' },
+ { role: 'cut' },
+ { role: 'copy' },
+ { role: 'paste' },
+ ...(isMac
+ ? ([
+ { role: 'pasteAndMatchStyle' },
+ { role: 'delete' },
+ { role: 'selectAll' },
+ { type: 'separator' },
+ {
+ label: 'Speech',
+ submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }]
+ }
+ ] as MenuItemConstructorOptions[])
+ : ([{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }] as MenuItemConstructorOptions[]))
+ ]
+ },
+ {
+ label: 'View',
+ submenu: [
+ { role: 'resetZoom' },
+ { role: 'zoomIn' },
+ { role: 'zoomOut' },
+ { type: 'separator' },
+ { role: 'togglefullscreen' },
+ { type: 'separator' },
+ { role: 'reload' },
+ { role: 'forceReload' }
+ ]
+ },
+ {
+ label: 'Window',
+ submenu: [
+ { role: 'minimize' },
+ ...(isMac
+ ? ([{ role: 'zoom' }, { type: 'separator' }, { role: 'front' }] as MenuItemConstructorOptions[])
+ : ([{ role: 'close' }] as MenuItemConstructorOptions[]))
+ ]
+ },
+ {
+ role: 'help',
+ submenu: [
+ ...(isMac
+ ? ([] as MenuItemConstructorOptions[])
+ : isWindows
+ ? ([{ role: 'about' }, { type: 'separator' }] as MenuItemConstructorOptions[])
+ : ([
+ {
+ label: 'About',
+ click: async () => {
+ mainWindow &&
+ dialog.showMessageBox(mainWindow, {
+ message: `Version ${CURRENT_VERSION}`,
+ title: 'About',
+ type: 'info'
+ })
+ }
+ }
+ ] as MenuItemConstructorOptions[])),
+ {
+ label: 'Report an issue',
+ click: async () => {
+ await shell.openExternal('https://github.com/alephium/alephium-frontend/issues/new')
+ }
+ },
+ {
+ label: 'Get some help',
+ click: async () => {
+ await shell.openExternal('https://discord.gg/JErgRBfRSB')
+ }
+ }
+ ]
+ }
+]
diff --git a/apps/desktop-wallet/src/electron/nativeTheme.ts b/apps/desktop-wallet/src/electron/nativeTheme.ts
new file mode 100644
index 000000000..59ccef50d
--- /dev/null
+++ b/apps/desktop-wallet/src/electron/nativeTheme.ts
@@ -0,0 +1,38 @@
+/*
+Copyright 2018 - 2024 The Alephium Authors
+This file is part of the alephium project.
+
+The library is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+The library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with the library. If not, see .
+*/
+
+import { BrowserWindow, ipcMain, nativeTheme } from 'electron'
+
+export const setupNativeThemeListeners = (mainWindow: BrowserWindow) => {
+ nativeTheme.on('updated', () =>
+ mainWindow.webContents.send('theme:shouldUseDarkColors', nativeTheme.shouldUseDarkColors)
+ )
+}
+
+export const handleNativeThemeUserActions = () => {
+ ipcMain.handle('theme:setNativeTheme', (_, theme) => (nativeTheme.themeSource = theme))
+
+ // nativeTheme must be reassigned like this because its properties are all computed, so
+ // they can't be serialized to be passed over channels.
+ ipcMain.handle('theme:getNativeTheme', ({ sender }) =>
+ sender.send('theme:getNativeTheme', {
+ shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
+ themeSource: nativeTheme.themeSource
+ })
+ )
+}
diff --git a/apps/desktop-wallet/src/electron/utils.ts b/apps/desktop-wallet/src/electron/utils.ts
new file mode 100644
index 000000000..62a0e56bd
--- /dev/null
+++ b/apps/desktop-wallet/src/electron/utils.ts
@@ -0,0 +1,27 @@
+/*
+Copyright 2018 - 2024 The Alephium Authors
+This file is part of the alephium project.
+
+The library is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+The library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with the library. If not, see .
+*/
+
+import { app } from 'electron'
+
+export const isMac = process.platform === 'darwin'
+
+export const isWindows = process.platform === 'win32'
+
+export const CURRENT_VERSION = app.getVersion()
+
+export const IS_RC = CURRENT_VERSION.includes('-rc.')
diff --git a/apps/desktop-wallet/tsconfig.json b/apps/desktop-wallet/tsconfig.json
index 1fc55f341..dac647e7b 100644
--- a/apps/desktop-wallet/tsconfig.json
+++ b/apps/desktop-wallet/tsconfig.json
@@ -4,6 +4,8 @@
"exclude": ["node_modules"],
"compilerOptions": {
"baseUrl": ".",
+ "outDir": "dist",
+ "noEmit": false,
"paths": {
"@/*": ["./src/*"]
}