mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
Kn/macos signing (#2432)
* chore: migrate from altool to notarytool * fix litte things in build-common * add pyenv install in build-osx * add pyinstaller/electron/signing_logs to gitignore * chore: migrate from altool to notarytool * fix little things in build.common * add pyenv install in build osx * add pyinatsller electron signing logs to gitignore * updated build-osx.sh * fix entitlement * heavily refactoring the electron app * polish and improve * further bugfixing and polishing * tiny change to improve support of MacOS * Fix dependency issues --------- Co-authored-by: Manolis <moneymanolis@protonmail.com>
This commit is contained in:
parent
7970c3d8ab
commit
e9dc494d22
22 changed files with 932 additions and 803 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -25,6 +25,7 @@ pyinstaller/electron/downloadloc.js
|
||||||
pyinstaller/electron/node_modules
|
pyinstaller/electron/node_modules
|
||||||
pyinstaller/electron/package-lock.json
|
pyinstaller/electron/package-lock.json
|
||||||
pyinstaller/electron/dist
|
pyinstaller/electron/dist
|
||||||
|
pyinstaller/electron/signing_logs
|
||||||
pyinstaller/electron/fonts
|
pyinstaller/electron/fonts
|
||||||
pyinstaller/electron/typography.css
|
pyinstaller/electron/typography.css
|
||||||
pyinstaller/electron/output.css
|
pyinstaller/electron/output.css
|
||||||
|
|
|
||||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
|
|
@ -1,4 +1,9 @@
|
||||||
{
|
{
|
||||||
"python.testing.unittestEnabled": false,
|
"python.testing.unittestEnabled": false,
|
||||||
"python.testing.pytestEnabled": true
|
"python.testing.pytestEnabled": true,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.device.camera</key>
|
<key>com.apple.security.device.camera</key>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,20 @@
|
||||||
<html>
|
<html>
|
||||||
<link rel="stylesheet" type="text/css" href="./styles.css">
|
<link rel="stylesheet" type="text/css" href="./output.css" />
|
||||||
<body style="overflow: auto; height: 100%;">
|
<body style="overflow: auto; height: 100%">
|
||||||
<div class="card" style="width:90%">
|
<div class="card" style="width: 90%">
|
||||||
This window shows you the last 700 lines Logs of the specterApp. This also includes
|
This window shows you the last 700 lines Logs of the specterApp. This also includes the Logs of specterd which are
|
||||||
the Logs of specterd which are marked as such. It might give you hints on why specter is not coming up properly.
|
marked as such. It might give you hints on why specter is not coming up properly. The best approach is to scroll to the
|
||||||
The best approach is to scroll to the bottom and then search upwards for errors.
|
bottom and then search upwards for errors. You can find the logfile in [yourHomedirectory]/.specter/specterApp.log.
|
||||||
You can find the logfile in [yourHomedirectory]/.specter/specterApp.log.
|
<pre><code id="specterapp-logs"></code></pre>
|
||||||
<pre><code id="specterapp-logs"></code></pre>
|
<br />
|
||||||
<br>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const helpers = require('./helpers')
|
const helpers = require('./src/helpers')
|
||||||
helpers.getSpecterAppLogs( (lines) => {
|
helpers.getSpecterAppLogs((lines) => {
|
||||||
document.getElementById('specterapp-logs').innerText = lines
|
document.getElementById('specterapp-logs').innerText = lines
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
const fs = require('fs')
|
|
||||||
const os = require('os')
|
|
||||||
const path = require('path')
|
|
||||||
const crypto = require('crypto')
|
|
||||||
const readLastLines = require('read-last-lines');
|
|
||||||
const downloadloc = require('./downloadloc');
|
|
||||||
const appName = downloadloc.appName()
|
|
||||||
const appNameLower = appName.toLowerCase()
|
|
||||||
const isDev = process.env.NODE_ENV === "development"
|
|
||||||
const unresolvedDevFolder = process.env.SPECTER_DATA_FOLDER || "~/.specter_dev"
|
|
||||||
const devFolder = unresolvedDevFolder.replace(/^~/, os.homedir());
|
|
||||||
const prodFolder = path.resolve(os.homedir(), `.${appNameLower}`)
|
|
||||||
const isMac = process.platform === 'darwin'
|
|
||||||
|
|
||||||
let appSettingsPath
|
|
||||||
let specterdDirPath
|
|
||||||
let specterAppLogPath
|
|
||||||
let versionData
|
|
||||||
|
|
||||||
// Use different version-data.jsons
|
|
||||||
|
|
||||||
// Should look like this:
|
|
||||||
// {
|
|
||||||
// "version": "v2.0.0-pre32",
|
|
||||||
// "sha256": "aa049abf3e75199bad26fbded08ee5911ad48e325b42c43ec195136bd0736785"
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
let versionDataPath = `${devFolder}/version-data.json`
|
|
||||||
try {
|
|
||||||
versionData = require(versionDataPath)
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
versionData = require('./version-data.json')
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log('Could not find default version data configurations: '+e)
|
|
||||||
versionData = {
|
|
||||||
"version": "",
|
|
||||||
"sha256": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
appSettingsPath = `${devFolder}/app_settings.json`
|
|
||||||
specterdDirPath = `${devFolder}/specterd-binaries`
|
|
||||||
specterAppLogPath = `${devFolder}/specterApp.log`
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appSettingsPath = `${prodFolder}/app_settings.json`
|
|
||||||
specterdDirPath = `${prodFolder}/specterd-binaries`
|
|
||||||
specterAppLogPath = `${prodFolder}/specterApp.log`
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFileHash(filename, callback) {
|
|
||||||
let shasum = crypto.createHash('sha256')
|
|
||||||
// Updating shasum with file content
|
|
||||||
, s = fs.ReadStream(filename)
|
|
||||||
s.on('data', function(data) {
|
|
||||||
shasum.update(data)
|
|
||||||
})
|
|
||||||
// making digest
|
|
||||||
s.on('end', function() {
|
|
||||||
var hash = shasum.digest('hex')
|
|
||||||
callback(hash)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAppSettings() {
|
|
||||||
let defaultSettings = {
|
|
||||||
mode: 'specterd',
|
|
||||||
specterURL: 'http://localhost:25441',
|
|
||||||
basicAuth: false,
|
|
||||||
basicAuthUser: '',
|
|
||||||
basicAuthPass: '',
|
|
||||||
tor: false,
|
|
||||||
proxyURL: "socks5://127.0.0.1:9050",
|
|
||||||
specterdVersion: (versionData && versionData.version !== undefined) ? versionData.version : 'unknown',
|
|
||||||
specterdHash: (versionData && versionData.sha256 !== undefined) ? versionData.sha256 : 'unknown',
|
|
||||||
specterdCLIArgs: "",
|
|
||||||
versionInitialized: false
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!fs.existsSync(appSettingsPath)){
|
|
||||||
fs.mkdirSync(path.resolve(appSettingsPath, '..'), { recursive: true });
|
|
||||||
}
|
|
||||||
fs.writeFileSync(appSettingsPath, JSON.stringify(defaultSettings), { flag: 'wx' });
|
|
||||||
} catch {
|
|
||||||
// settings file already exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure to add missing settings in case the format changed or new settings were added
|
|
||||||
let appSettings = require(appSettingsPath)
|
|
||||||
for (let key of Object.keys(defaultSettings)) {
|
|
||||||
if (!appSettings.hasOwnProperty(key)) {
|
|
||||||
appSettings[key] = defaultSettings[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return appSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSpecterAppLogs(callback) {
|
|
||||||
readLastLines.read(specterAppLogPath, 700)
|
|
||||||
.then(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getFileHash: getFileHash,
|
|
||||||
appSettingsPath: appSettingsPath,
|
|
||||||
getAppSettings: getAppSettings,
|
|
||||||
specterdDirPath: specterdDirPath,
|
|
||||||
getSpecterAppLogs: getSpecterAppLogs,
|
|
||||||
specterAppLogPath: specterAppLogPath,
|
|
||||||
versionData,
|
|
||||||
isDev: isDev,
|
|
||||||
devFolder,
|
|
||||||
isMac, isMac
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +1,18 @@
|
||||||
// Modules to control application life and create native browser window
|
// Modules to control application life and create native browser window
|
||||||
const { app, nativeTheme, nativeImage, BrowserWindow, Menu, Tray, screen, shell, dialog, ipcMain } = require('electron')
|
|
||||||
|
|
||||||
const path = require('path')
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const { URL } = require('node:url')
|
|
||||||
const request = require('request')
|
|
||||||
const https = require('https')
|
|
||||||
const extract = require('extract-zip')
|
|
||||||
const defaultMenu = require('electron-default-menu')
|
|
||||||
const ProgressBar = require('electron-progressbar')
|
|
||||||
const { spawn, exec } = require('child_process')
|
const { spawn, exec } = require('child_process')
|
||||||
const {
|
const { app, nativeTheme, nativeImage, BrowserWindow, Menu, Tray, screen, shell, dialog, ipcMain } = require('electron')
|
||||||
getFileHash,
|
const defaultMenu = require('electron-default-menu')
|
||||||
getAppSettings,
|
const contextMenu = require('electron-context-menu')
|
||||||
appSettingsPath,
|
|
||||||
specterdDirPath,
|
const { appSettingsPath, specterdDirPath, appSettings, platformName, appNameLower } = require('./src/config.js')
|
||||||
specterAppLogPath,
|
const { logger } = require('./src/logging.js')
|
||||||
versionData,
|
|
||||||
isDev,
|
|
||||||
devFolder,
|
|
||||||
isMac,
|
|
||||||
} = require('./helpers')
|
|
||||||
const downloadloc = require('./downloadloc')
|
const downloadloc = require('./downloadloc')
|
||||||
|
const { downloadSpecterd } = require('./src/download.js')
|
||||||
const getDownloadLocation = downloadloc.getDownloadLocation
|
const getDownloadLocation = downloadloc.getDownloadLocation
|
||||||
const appName = downloadloc.appName()
|
const { startSpecterd, quitSpecterd } = require('./src/specterd.js')
|
||||||
const appNameLower = appName.toLowerCase()
|
const { getFileHash, getAppSettings, versionData, isDev, devFolder, isMac } = require('./src/helpers.js')
|
||||||
|
const { showError, updatingLoaderMsg, initMainWindow, loadUrl, initTray } = require('./src/uiHelpers.js')
|
||||||
|
|
||||||
// Quit again if there is no version-data in dev
|
// Quit again if there is no version-data in dev
|
||||||
if (isDev && versionData === undefined) {
|
if (isDev && versionData === undefined) {
|
||||||
|
|
@ -39,23 +27,6 @@ ipcMain.handle('showMessageBoxSync', (e, message, buttons) => {
|
||||||
dialog.showMessageBoxSync(mainWindow, { message, buttons })
|
dialog.showMessageBoxSync(mainWindow, { message, buttons })
|
||||||
})
|
})
|
||||||
|
|
||||||
// Logging
|
|
||||||
const { transports, format, createLogger } = require('winston')
|
|
||||||
const combinedLog = new transports.File({ filename: specterAppLogPath })
|
|
||||||
const winstonOptions = {
|
|
||||||
exitOnError: false,
|
|
||||||
format: format.combine(
|
|
||||||
format.timestamp(),
|
|
||||||
format.json(),
|
|
||||||
format.printf((info) => {
|
|
||||||
return `${info.timestamp} [${info.level}] : ${info.message}`
|
|
||||||
})
|
|
||||||
),
|
|
||||||
transports: [new transports.Console({ json: false }), combinedLog],
|
|
||||||
exceptionHandlers: [combinedLog],
|
|
||||||
}
|
|
||||||
const logger = createLogger(winstonOptions)
|
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
logger.info('Running the Electron app in dev mode.')
|
logger.info('Running the Electron app in dev mode.')
|
||||||
}
|
}
|
||||||
|
|
@ -75,11 +46,10 @@ if (isMac && isDev) {
|
||||||
app.dock.setIcon(dockIcon)
|
app.dock.setIcon(dockIcon)
|
||||||
}
|
}
|
||||||
|
|
||||||
let appSettings = getAppSettings()
|
|
||||||
let dimensions = { width: 1500, height: 1000 }
|
let dimensions = { width: 1500, height: 1000 }
|
||||||
|
|
||||||
// Modify the context menu
|
// Modify the context menu
|
||||||
const contextMenu = require('electron-context-menu')
|
|
||||||
contextMenu({
|
contextMenu({
|
||||||
menu: (actions) => [
|
menu: (actions) => [
|
||||||
{
|
{
|
||||||
|
|
@ -101,136 +71,16 @@ contextMenu({
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
// The standard quit item cannot be replaced / modified and it is not triggering the
|
|
||||||
// before-quit event on MacOS if a child window is open
|
|
||||||
const dockMenuWithforceQuit = Menu.buildFromTemplate([
|
|
||||||
{
|
|
||||||
label: 'Force Quit during download',
|
|
||||||
click: () => {
|
|
||||||
// If the progress bar exists, close it
|
|
||||||
if (progressBar) {
|
|
||||||
progressBar.close()
|
|
||||||
}
|
|
||||||
// Quit the app
|
|
||||||
app.quit()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
// Download function with progress bar
|
|
||||||
let progressBar
|
|
||||||
const download = (uri, filename, callback) => {
|
|
||||||
// HEAD request first
|
|
||||||
request.head(uri, (err, res, body) => {
|
|
||||||
if (res.statusCode != 404) {
|
|
||||||
let receivedBytes = 0
|
|
||||||
const totalBytes = res.headers['content-length']
|
|
||||||
logger.info(`Total size to download: ${totalBytes}`)
|
|
||||||
progressBar = new ProgressBar({
|
|
||||||
indeterminate: false,
|
|
||||||
abortOnError: true,
|
|
||||||
text: 'Downloading the Specter binary from GitHub',
|
|
||||||
detail:
|
|
||||||
'This can take several minutes depending on your Internet connection. Specter will start once the download is finished.',
|
|
||||||
maxValue: totalBytes,
|
|
||||||
browserWindow: {
|
|
||||||
parent: mainWindow,
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
detail: {
|
|
||||||
'margin-bottom': '12px',
|
|
||||||
},
|
|
||||||
bar: {
|
|
||||||
'background-color': '#fff',
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
'background-color': '#000',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add Force Quit item during download for MacOS dock
|
|
||||||
if (isMac) {
|
|
||||||
app.dock.setMenu(dockMenuWithforceQuit)
|
|
||||||
}
|
|
||||||
|
|
||||||
progressBar.on('completed', () => {
|
|
||||||
progressBar.close()
|
|
||||||
// Remove the Force Quit dock item again for Mac
|
|
||||||
if (isMac) {
|
|
||||||
const updatedDockMenu = Menu.buildFromTemplate(
|
|
||||||
dockMenuWithforceQuit.items.filter((item) => item.label !== 'Force Quit during download')
|
|
||||||
)
|
|
||||||
app.dock.setMenu(updatedDockMenu)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
progressBar.on('aborted', () => {
|
|
||||||
logger.info('Download was aborted before it could finish.')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Loggin the download progress
|
|
||||||
let lastLogTime = 0
|
|
||||||
const logInterval = 5000 // log every 5 seconds
|
|
||||||
progressBar.on('progress', () => {
|
|
||||||
const currentTime = Date.now()
|
|
||||||
if (currentTime - lastLogTime >= logInterval) {
|
|
||||||
lastLogTime = currentTime
|
|
||||||
logger.info(`Download status: ${((receivedBytes / totalBytes) * 100).toFixed(0)}%`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// GET request
|
|
||||||
request(uri)
|
|
||||||
.on('data', (chunk) => {
|
|
||||||
receivedBytes += chunk.length
|
|
||||||
if (progressBar) {
|
|
||||||
progressBar.value = receivedBytes
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.pipe(fs.createWriteStream(filename))
|
|
||||||
.on('close', callback)
|
|
||||||
}
|
|
||||||
// If the download link was not found, call callback (updatingLoaderMsg with error feedback)
|
|
||||||
else {
|
|
||||||
logger.info(`Error while trying to download specterd: ${err}`)
|
|
||||||
callback(true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let specterdProcess
|
let specterdProcess
|
||||||
let automaticWalletImport = false
|
let automaticWalletImport = false
|
||||||
let mainWindow
|
let mainWindow
|
||||||
let prefWindow
|
let prefWindow
|
||||||
let tray
|
|
||||||
let trayMenu
|
|
||||||
|
|
||||||
// Flag the app was quitted
|
// Flag the app was quitted
|
||||||
let quitted = false
|
let quitted = false
|
||||||
|
|
||||||
let webPreferences = {
|
|
||||||
worldSafeExecuteJavaScript: true,
|
|
||||||
contextIsolation: true,
|
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
|
||||||
}
|
|
||||||
|
|
||||||
app.commandLine.appendSwitch('ignore-certificate-errors')
|
app.commandLine.appendSwitch('ignore-certificate-errors')
|
||||||
|
|
||||||
let platformName = ''
|
|
||||||
switch (process.platform) {
|
|
||||||
case 'darwin':
|
|
||||||
platformName = 'osx'
|
|
||||||
break
|
|
||||||
case 'win32':
|
|
||||||
platformName = 'win64'
|
|
||||||
break
|
|
||||||
case 'linux':
|
|
||||||
platformName = 'x86_64-linux-gnu'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
throw `Unknown platformName ${platformName}`
|
|
||||||
}
|
|
||||||
logger.info('Using version ' + appSettings.specterdVersion)
|
logger.info('Using version ' + appSettings.specterdVersion)
|
||||||
logger.info('Using platformName ' + platformName)
|
logger.info('Using platformName ' + platformName)
|
||||||
|
|
||||||
|
|
@ -261,85 +111,35 @@ app.on('login', function (event, webContents, request, authInfo, callback) {
|
||||||
trySavedAuth = false
|
trySavedAuth = false
|
||||||
})
|
})
|
||||||
|
|
||||||
function createWindow(specterURL) {
|
|
||||||
if (!mainWindow) {
|
|
||||||
initMainWindow()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the browser window.
|
|
||||||
if (appSettings.tor) {
|
|
||||||
mainWindow.webContents.session.setProxy({ proxyRules: appSettings.proxyURL })
|
|
||||||
}
|
|
||||||
mainWindow.loadURL(specterURL + '?mode=remote')
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
// Create the tray icon
|
// Create the tray icon
|
||||||
logger.info('Framework ready! Starting tray icon ...')
|
logger.info('Framework ready! Starting tray icon ...')
|
||||||
if (isMac) {
|
initTray(openPreferences, quitSpecterd)
|
||||||
const trayIconPath = nativeTheme.shouldUseDarkColors ? '/assets/menu_icon_dark.png' : '/assets/menu_icon_light.png'
|
|
||||||
const createTrayIcon = (trayIconPath) => {
|
|
||||||
let trayIcon = nativeImage.createFromPath(app.getAppPath() + trayIconPath)
|
|
||||||
// Resize
|
|
||||||
trayIcon = trayIcon.resize({ width: 22, height: 22 })
|
|
||||||
return trayIcon
|
|
||||||
}
|
|
||||||
const trayIcon = createTrayIcon(trayIconPath)
|
|
||||||
tray = new Tray(trayIcon)
|
|
||||||
|
|
||||||
// Change the tray icon if appearance is changed in Mac settings
|
|
||||||
const updateTrayIcon = () => {
|
|
||||||
logger.info('Updating tray icon ...')
|
|
||||||
const trayIconPath = nativeTheme.shouldUseDarkColors ? '/assets/menu_icon_dark.png' : '/assets/menu_icon_light.png'
|
|
||||||
const newTrayIcon = createTrayIcon(trayIconPath)
|
|
||||||
tray.setImage(newTrayIcon)
|
|
||||||
}
|
|
||||||
nativeTheme.on('updated', updateTrayIcon)
|
|
||||||
} else {
|
|
||||||
const trayIcon = nativeImage.createFromPath(app.getAppPath() + '/assets/menu_icon.png')
|
|
||||||
tray = new Tray(trayIcon)
|
|
||||||
}
|
|
||||||
|
|
||||||
trayMenu = [
|
|
||||||
{ label: 'Launching Specter ...', enabled: false },
|
|
||||||
{
|
|
||||||
label: 'Show Specter',
|
|
||||||
click() {
|
|
||||||
mainWindow.show()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Settings',
|
|
||||||
click() {
|
|
||||||
openPreferences()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Quit',
|
|
||||||
click() {
|
|
||||||
quitSpecterd()
|
|
||||||
app.quit()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
tray.setToolTip('Specter')
|
|
||||||
tray.setContextMenu(Menu.buildFromTemplate(trayMenu))
|
|
||||||
|
|
||||||
dimensions = screen.getPrimaryDisplay().size
|
dimensions = screen.getPrimaryDisplay().size
|
||||||
|
|
||||||
// create a new `splash`-Window
|
// create a new `splash`-Window
|
||||||
logger.info('Framework Ready! Initializing Main-Window, populating Menu ...')
|
logger.info('Framework Ready! Initializing Main-Window, populating Menu ...')
|
||||||
initMainWindow()
|
mainWindow = initMainWindow(dimensions)
|
||||||
|
mainWindow.on('close', function (event) {
|
||||||
|
if (platformName == 'win64') {
|
||||||
|
quitSpecterd()
|
||||||
|
app.quit()
|
||||||
|
} else {
|
||||||
|
event.preventDefault()
|
||||||
|
mainWindow.hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
setMainMenu()
|
setMainMenu()
|
||||||
|
|
||||||
mainWindow.loadURL(`file://${__dirname}/splash.html`)
|
loadUrl(`file://${__dirname}/splash.html`)
|
||||||
|
|
||||||
if (!fs.existsSync(specterdDirPath)) {
|
if (!fs.existsSync(specterdDirPath)) {
|
||||||
logger.info('Creating specterd-binaries folder')
|
logger.info('Creating specterd-binaries folder:' + specterdDirPath)
|
||||||
fs.mkdirSync(specterdDirPath, { recursive: true })
|
fs.mkdirSync(specterdDirPath, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,294 +177,6 @@ app.whenReady().then(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function initMainWindow() {
|
|
||||||
// In production we use the icons from the build folder
|
|
||||||
// Note: On MacOS setting an icon here as no effect
|
|
||||||
const iconPath = isDev ? path.join(__dirname, 'assets-dev/app_icon.png') : ''
|
|
||||||
mainWindow = new BrowserWindow({
|
|
||||||
width: parseInt(dimensions.width * 0.8),
|
|
||||||
minWidth: 1120,
|
|
||||||
height: parseInt(dimensions.height * 0.8),
|
|
||||||
icon: iconPath,
|
|
||||||
webPreferences,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Ensures that any links with target="_blank" or window.open() will be opened in the user's default browser instead of within the app
|
|
||||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
||||||
shell.openExternal(url)
|
|
||||||
return { action: 'deny' }
|
|
||||||
})
|
|
||||||
|
|
||||||
mainWindow.on('close', function (event) {
|
|
||||||
if (platformName == 'win64') {
|
|
||||||
quitSpecterd()
|
|
||||||
app.quit()
|
|
||||||
} else {
|
|
||||||
event.preventDefault()
|
|
||||||
mainWindow.hide()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
mainWindow.webContents.on('did-fail-load', function () {
|
|
||||||
mainWindow.loadURL(`file://${__dirname}/splash.html`)
|
|
||||||
updatingLoaderMsg(
|
|
||||||
`Failed to load: ${appSettings.specterURL}<br>Please make sure the URL is entered correctly in the settings and try again...`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadSpecterd(specterdPath) {
|
|
||||||
updatingLoaderMsg(`Starting download`)
|
|
||||||
updateSpecterdStatus(`Downloading the ${appName} binary...`)
|
|
||||||
// Some logging
|
|
||||||
logger.info('Using version ' + appSettings.specterdVersion)
|
|
||||||
logger.info('Using platformName ' + platformName)
|
|
||||||
download_location = getDownloadLocation(appSettings.specterdVersion, platformName)
|
|
||||||
logger.info('Downloading from ' + download_location)
|
|
||||||
download(download_location, specterdPath + '.zip', function (errored) {
|
|
||||||
if (errored == true) {
|
|
||||||
updatingLoaderMsg(
|
|
||||||
`Downloading the ${appNameLower} binary from GitHub failed, could not reach the server or the file wasn't found.`
|
|
||||||
)
|
|
||||||
updateSpecterdStatus(`Downloading ${appNameLower}d failed...`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
updatingLoaderMsg('Download completed. Unpacking files...')
|
|
||||||
logger.info('Extracting ' + specterdPath)
|
|
||||||
|
|
||||||
extract(specterdPath + '.zip', { dir: specterdPath + '-dir' }).then(function () {
|
|
||||||
let extraPath = ''
|
|
||||||
switch (process.platform) {
|
|
||||||
case 'darwin':
|
|
||||||
extraPath = appNameLower + 'd'
|
|
||||||
break
|
|
||||||
case 'win32':
|
|
||||||
extraPath = appNameLower + 'd.exe'
|
|
||||||
break
|
|
||||||
case 'linux':
|
|
||||||
extraPath = appNameLower + 'd'
|
|
||||||
}
|
|
||||||
var oldPath = specterdPath + `-dir/${extraPath}`
|
|
||||||
var newPath = specterdPath + (platformName == 'win64' ? '.exe' : '')
|
|
||||||
|
|
||||||
fs.renameSync(oldPath, newPath)
|
|
||||||
fs.unlinkSync(specterdPath + '.zip')
|
|
||||||
fs.rmdirSync(specterdPath + '-dir', { recursive: true })
|
|
||||||
getFileHash(specterdPath + (platformName == 'win64' ? '.exe' : ''), function (specterdHash) {
|
|
||||||
if (appSettings.specterdHash.toLowerCase() === specterdHash || appSettings.specterdHash == '') {
|
|
||||||
startSpecterd(specterdPath)
|
|
||||||
} else {
|
|
||||||
updatingLoaderMsg('Specterd version could not be validated.')
|
|
||||||
logger.error(`hash of downloaded file: ${specterdHash}`)
|
|
||||||
logger.error(`Expected hash: ${appSettings.specterdHash}`)
|
|
||||||
updateSpecterdStatus('Failed to launch specterd...')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSpecterdStatus(status) {
|
|
||||||
trayMenu[0] = { label: status, enabled: false }
|
|
||||||
tray.setContextMenu(Menu.buildFromTemplate(trayMenu))
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatingLoaderMsg(msg, showSpinner = false) {
|
|
||||||
if (mainWindow) {
|
|
||||||
let code = `
|
|
||||||
var launchText = document.getElementById('launch-text');
|
|
||||||
if (launchText) {
|
|
||||||
launchText.innerHTML = '${msg}';
|
|
||||||
}
|
|
||||||
var spinnerElement = document.getElementById('spinner');
|
|
||||||
if (spinnerElement) {
|
|
||||||
if (${showSpinner} === true) {
|
|
||||||
spinnerElement.classList.remove('hidden')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
spinnerElement.classList.add('hidden')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
mainWindow.webContents.executeJavaScript(code)
|
|
||||||
}
|
|
||||||
logger.info('Updated LoaderMsg: ' + msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkSpecterd(logs, specterdStarted) {
|
|
||||||
// There doesn't seem to be another more straightforward way to check whether specterd is running: https://github.com/nodejs/help/issues/1191
|
|
||||||
// Setting a timeout to avoid waiting for specterd endlessly
|
|
||||||
const timeout = 180000 // 3 minutes
|
|
||||||
const now = Date.now()
|
|
||||||
const timeElapsed = now - specterdStarted
|
|
||||||
if (timeElapsed > timeout) {
|
|
||||||
return 'timeout'
|
|
||||||
}
|
|
||||||
if (logs.toString().includes('Serving Flask app')) {
|
|
||||||
return 'running'
|
|
||||||
} else {
|
|
||||||
return 'not running'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let specterIsRunning = false
|
|
||||||
function startSpecterd(specterdPath) {
|
|
||||||
if (platformName == 'win64') {
|
|
||||||
specterdPath += '.exe'
|
|
||||||
}
|
|
||||||
let appSettings = getAppSettings()
|
|
||||||
let hwiBridgeMode = appSettings.mode == 'hwibridge'
|
|
||||||
updatingLoaderMsg('Launching Specter ...', (showSpinner = true))
|
|
||||||
updateSpecterdStatus('Launching Specter ...')
|
|
||||||
let specterdArgs = ['server']
|
|
||||||
specterdArgs.push('--no-filelog')
|
|
||||||
if (hwiBridgeMode) specterdArgs.push('--hwibridge')
|
|
||||||
if (appSettings.specterdCLIArgs != '') {
|
|
||||||
// User has inputed cli arguments in the UI
|
|
||||||
let specterdExtraArgs = appSettings.specterdCLIArgs.split(' ')
|
|
||||||
specterdExtraArgs.forEach((arg) => {
|
|
||||||
// Ensures that whitespaces are not used as cli arguments
|
|
||||||
if (arg != '') {
|
|
||||||
specterdArgs.push(arg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// locale fix (copying from nodejs-env + adding locales)
|
|
||||||
const options = {
|
|
||||||
env: { ...process.env },
|
|
||||||
}
|
|
||||||
options.env['LC_ALL'] = 'en_US.utf-8'
|
|
||||||
options.env['LANG'] = 'en_US.utf-8'
|
|
||||||
options.env['SPECTER_LOGFORMAT'] = 'SPECTERD: %(levelname)s in %(module)s: %(message)s'
|
|
||||||
specterdProcess = spawn(specterdPath, specterdArgs, options)
|
|
||||||
const specterdStarted = Date.now()
|
|
||||||
|
|
||||||
// We are checking for both, stdout and stderr, to be on the save side.
|
|
||||||
specterdProcess.stdout.on('data', (data) => {
|
|
||||||
logger.info('stdout-' + data.toString())
|
|
||||||
let serverdStatus = checkSpecterd(data, specterdStarted)
|
|
||||||
// We don't want to check the logs forever, just until specterd is up and running
|
|
||||||
if (!specterIsRunning) {
|
|
||||||
if (serverdStatus === 'running') {
|
|
||||||
logger.info(`Specter server seems to run ...`)
|
|
||||||
updateSpecterdStatus('Specter is running')
|
|
||||||
specterIsRunning = true
|
|
||||||
if (mainWindow) {
|
|
||||||
if (automaticWalletImport === true) {
|
|
||||||
logger.info('Performing automatic wallet import ...')
|
|
||||||
updatingLoaderMsg('Launching wallet importer. This will only work with a node connection.', (showSpinner = true))
|
|
||||||
setTimeout(() => {
|
|
||||||
importWallet(walletDataFromUrl)
|
|
||||||
}, 3000)
|
|
||||||
} else {
|
|
||||||
logger.info('Normal startup of Specter.')
|
|
||||||
createWindow(appSettings.specterURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (serverdStatus === 'timeout') {
|
|
||||||
showError('Specter does not seem to start. Check the logs in the menu for more details.')
|
|
||||||
updateSpecterdStatus('Specter does not start')
|
|
||||||
logger.error('Startup timeout for specterd exceeded')
|
|
||||||
} else {
|
|
||||||
updatingLoaderMsg('Still waiting for Specter to start ...')
|
|
||||||
updateSpecterdStatus('Specter is starting')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
specterdProcess.stderr.on('data', (data) => {
|
|
||||||
logger.info('stderr-' + data.toString())
|
|
||||||
let serverdStatus = checkSpecterd(data, specterdStarted)
|
|
||||||
if (!specterIsRunning) {
|
|
||||||
if (serverdStatus === 'running') {
|
|
||||||
logger.info(`Specter server seems to run ...`)
|
|
||||||
updateSpecterdStatus('Specter is running')
|
|
||||||
specterIsRunning = true
|
|
||||||
if (mainWindow) {
|
|
||||||
logger.info('... creating Electron window for it.')
|
|
||||||
createWindow(appSettings.specterURL)
|
|
||||||
}
|
|
||||||
} else if (serverdStatus === 'timeout') {
|
|
||||||
showError('Specter does not seem to start. Check the logs in the menu for more details.')
|
|
||||||
updateSpecterdStatus('Specter does not start')
|
|
||||||
logger.error('Startup timeout for specterd exceeded')
|
|
||||||
} else {
|
|
||||||
updatingLoaderMsg('Still waiting for Specter to start ...')
|
|
||||||
updateSpecterdStatus('Specter is starting')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
specterdProcess.on('exit', (code) => {
|
|
||||||
logger.error(`specterd exited with code ${code}`)
|
|
||||||
showError(`Specter exited with exit code ${code}. Check the logs in the menu for more details.`)
|
|
||||||
})
|
|
||||||
|
|
||||||
specterdProcess.on('error', (err) => {
|
|
||||||
logger.error(`Error starting Specter server: ${err}`)
|
|
||||||
showError(`Specter failed to start, due to ${err.message}. Check the logs in the menu for more details.`)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('activate', function () {
|
|
||||||
// On macOS it's common to re-create a window in the app when the
|
|
||||||
// dock icon is clicked and there are no other windows open.
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) createWindow(appSettings.specterURL)
|
|
||||||
})
|
|
||||||
// since these are streams, you can pipe them elsewhere
|
|
||||||
specterdProcess.on('close', (code) => {
|
|
||||||
updateSpecterdStatus('Specter stopped...')
|
|
||||||
logger.info(`child process exited with code ${code}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let walletDataFromUrl
|
|
||||||
// Checking whether the app was opened via a Specter URL and determine whether to perform a specific startup action
|
|
||||||
app.on('open-url', (_, url) => {
|
|
||||||
logger.info('The app was opened via URL, checking the URL to decide whether to do any automatic actions ...')
|
|
||||||
// Parse the URL to extract the query parameters
|
|
||||||
const specterUrl = new URL(url)
|
|
||||||
const searchParams = specterUrl.searchParams
|
|
||||||
// Get the query parameter values
|
|
||||||
const action = searchParams.get('action')
|
|
||||||
const data = searchParams.get('data')
|
|
||||||
if (action === 'importWallet' && data !== '') {
|
|
||||||
logger.info('Automatic wallet import identified in the URL, setting automaticWalletImport to true.')
|
|
||||||
automaticWalletImport = true
|
|
||||||
walletDataFromUrl = data
|
|
||||||
// Directly import if the app and specterd is already running
|
|
||||||
if (specterIsRunning) {
|
|
||||||
logger.info('Performing automatic wallet import ...')
|
|
||||||
mainWindow.loadURL(`file://${__dirname}/splash.html`)
|
|
||||||
updatingLoaderMsg('Launching wallet importer. This will only work with a node connection.', (showSpinner = true))
|
|
||||||
setTimeout(() => {
|
|
||||||
importWallet(walletDataFromUrl)
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Automatically import the wallet json string, bring user to the final import wallet screen.
|
|
||||||
// Only proceed with the import if the importFromWalletSoftwareBtn can be found.
|
|
||||||
// If it is not, users are redirected by specterd to the configure connection screen.
|
|
||||||
function importWallet(walletData) {
|
|
||||||
mainWindow.loadURL(appSettings.specterURL + '/wallets/new_wallet/')
|
|
||||||
if (mainWindow) {
|
|
||||||
let code = `
|
|
||||||
const importFromWalletSoftwareBtn = document.getElementById('import-from-wallet-software-btn')
|
|
||||||
if (importFromWalletSoftwareBtn) {
|
|
||||||
importFromWalletSoftwareBtn.click()
|
|
||||||
const walletDataTextArea = document.getElementById('txt')
|
|
||||||
if (walletDataTextArea) {
|
|
||||||
walletDataTextArea.value = \`${walletData}\`
|
|
||||||
}
|
|
||||||
const continueBtn = document.getElementById('continue-with-wallet-import-btn');
|
|
||||||
continueBtn.click()
|
|
||||||
}
|
|
||||||
`
|
|
||||||
mainWindow.webContents.executeJavaScript(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on('window-all-closed', function () {
|
app.on('window-all-closed', function () {
|
||||||
if (platformName == 'win64') {
|
if (platformName == 'win64') {
|
||||||
quitSpecterd()
|
quitSpecterd()
|
||||||
|
|
@ -715,21 +227,6 @@ ipcMain.on('request-mainprocess-action', (event, arg) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function quitSpecterd() {
|
|
||||||
if (specterdProcess) {
|
|
||||||
try {
|
|
||||||
if (platformName == 'win64') {
|
|
||||||
exec('taskkill /F /T /PID ' + specterdProcess.pid)
|
|
||||||
exec('taskkill /IM specterd.exe ')
|
|
||||||
process.kill(-specterdProcess.pid)
|
|
||||||
}
|
|
||||||
specterdProcess.kill('SIGINT')
|
|
||||||
} catch (e) {
|
|
||||||
logger.info('Specterd quit warning: ' + e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMainMenu() {
|
function setMainMenu() {
|
||||||
const menu = defaultMenu(app, shell)
|
const menu = defaultMenu(app, shell)
|
||||||
|
|
||||||
|
|
@ -808,21 +305,17 @@ function openErrorLog() {
|
||||||
createNewWindow('error_logs.html', width, height).show()
|
createNewWindow('error_logs.html', width, height).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
function showError(error) {
|
|
||||||
updatingLoaderMsg('Specter encountered an error:<br>' + error.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
process.on('unhandledRejection', (error) => {
|
process.on('unhandledRejection', (error) => {
|
||||||
showError(error)
|
showError(error)
|
||||||
logger.error(error.toString(), error.name)
|
logger.error(error.stack)
|
||||||
})
|
})
|
||||||
|
|
||||||
process.on('uncaughtException', (error) => {
|
process.on('uncaughtException', (error) => {
|
||||||
showError(error)
|
showError(error)
|
||||||
// I would love to rethrow the error here as this would create a stacktrace in the logs
|
// I would love to rethrow the error here as this would create a stacktrace in the logs
|
||||||
// but this will terminate the whole process even though i've set
|
// but this will terminate the whole process even though i've set
|
||||||
// exitOnError: false in the wistonOptions above.
|
// exitOnError: false in the winstonOptions above.
|
||||||
// Unacceptable for the folks which can't use a commandline, clicking an icon
|
// Unacceptable for the folks which can't use a commandline, clicking an icon
|
||||||
//throw(error)
|
//throw(error)
|
||||||
logger.error(error.toString())
|
logger.error(error.stack)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
109
pyinstaller/electron/package-lock.json
generated
109
pyinstaller/electron/package-lock.json
generated
|
|
@ -1,26 +1,26 @@
|
||||||
{
|
{
|
||||||
"name": "Specter",
|
"name": "Specter",
|
||||||
"version": "v0.0.0",
|
"version": "v2.0.3-pre1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "Specter",
|
"name": "Specter",
|
||||||
"version": "v0.0.0",
|
"version": "v2.0.3-pre1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-context-menu": "^2.3.0",
|
"electron-context-menu": "^2.5.2",
|
||||||
"electron-default-menu": "^1.0.2",
|
"electron-default-menu": "^1.0.2",
|
||||||
"electron-progressbar": "^2.0.1",
|
"electron-progressbar": "^2.1.0",
|
||||||
"extract-zip": "^2.0.1",
|
"extract-zip": "^2.0.1",
|
||||||
"read-last-lines": "^1.8.0",
|
"read-last-lines": "^1.8.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"winston": "^3.3.3"
|
"winston": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^22.1.0",
|
"electron": "^22.3.12",
|
||||||
"electron-builder": "^23.3.1"
|
"electron-builder": "^23.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@dabh/diagnostics": {
|
"node_modules/@dabh/diagnostics": {
|
||||||
|
|
@ -1421,9 +1421,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron": {
|
"node_modules/electron": {
|
||||||
"version": "22.1.0",
|
"version": "22.3.27",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-22.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-22.3.27.tgz",
|
||||||
"integrity": "sha512-wz5s4N6V7zyKm4YQmXJImFoxO1Doai32ShYm0FzOLPBMwLMdQBV+REY+j1opRx0KJ9xJEIdjYgcA8OSw6vx3pA==",
|
"integrity": "sha512-7Rht21vHqj4ZFRnKuZdFqZFsvMBCmDqmjetiMqPtF+TmTBiGne1mnstVXOA/SRGhN2Qy5gY5bznJKpiqogjM8A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1502,13 +1502,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-context-menu": {
|
"node_modules/electron-context-menu": {
|
||||||
"version": "2.3.0",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.5.2.tgz",
|
||||||
"integrity": "sha512-XYsYkNY+jvX4C5o09qMuZoKL6e9frnQzBFehZSIiKp6zK0u3XYowJYDyK3vDKKZxYsOIGiE/Gbx40jERC03Ctw==",
|
"integrity": "sha512-1cEQR6fA9ktFsRBc+eXPwvrOgAPytUD7rUV4iBAA5zTrLAPKokJ23xeMjcK2fjrDPrlFRBxcLz0KP+GUhMrSCQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cli-truncate": "^2.0.0",
|
"cli-truncate": "^2.1.0",
|
||||||
"electron-dl": "^3.0.0",
|
"electron-dl": "^3.1.0",
|
||||||
"electron-is-dev": "^1.0.1"
|
"electron-is-dev": "^1.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-default-menu": {
|
"node_modules/electron-default-menu": {
|
||||||
|
|
@ -1517,13 +1520,19 @@
|
||||||
"integrity": "sha512-YAL/UNR3kPG58wOOlmDpTG3i6+bzwhHx6NllIOaLuVrU7uYifeYGGdk5IH2Hap4wVEx2YTA8cqQ2PGSplYwDWQ=="
|
"integrity": "sha512-YAL/UNR3kPG58wOOlmDpTG3i6+bzwhHx6NllIOaLuVrU7uYifeYGGdk5IH2Hap4wVEx2YTA8cqQ2PGSplYwDWQ=="
|
||||||
},
|
},
|
||||||
"node_modules/electron-dl": {
|
"node_modules/electron-dl": {
|
||||||
"version": "3.0.2",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.5.2.tgz",
|
||||||
"integrity": "sha512-pRgE9Jbhoo5z6Vk3qi+vIrfpMDlCp2oB1UeR96SMnsfz073jj0AZGQwp69EdIcEvlUlwBSGyJK8Jt6OB6JLn+g==",
|
"integrity": "sha512-i104cl+u8yJ0lhpRAtUWfeGuWuL1PL6TBiw2gLf0MMIBjfgE485Ags2mcySx4uWU9P9uj/vsD3jd7X+w1lzZxw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ext-name": "^5.0.0",
|
"ext-name": "^5.0.0",
|
||||||
"pupa": "^2.0.1",
|
"pupa": "^2.0.1",
|
||||||
"unused-filename": "^2.1.0"
|
"unused-filename": "^2.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-is-dev": {
|
"node_modules/electron-is-dev": {
|
||||||
|
|
@ -1581,9 +1590,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/electron-progressbar": {
|
"node_modules/electron-progressbar": {
|
||||||
"version": "2.0.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-2.2.1.tgz",
|
||||||
"integrity": "sha512-+N60GX2q+KH5OvZXxwtjMTZB/1AyxriFd95vOnR3sOfNpvz+30LMsM0a9SnEivZE6N8Djy7F3z4TY8pLs8aopw==",
|
"integrity": "sha512-LQ9bxM3Tf5PG/1QngY8ywvht7IKvQ8tEIra8uh3RkLASqN/GYvr4r0uU9qz38r0Zn72UcouYzumKDfLwoI/rsw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"extend": "^3.0.1"
|
"extend": "^3.0.1"
|
||||||
}
|
}
|
||||||
|
|
@ -2216,7 +2225,7 @@
|
||||||
"node_modules/is-plain-obj": {
|
"node_modules/is-plain-obj": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
||||||
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
|
"integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -2514,7 +2523,7 @@
|
||||||
"node_modules/modify-filename": {
|
"node_modules/modify-filename": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz",
|
||||||
"integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=",
|
"integrity": "sha512-EickqnKq3kVVaZisYuCxhtKbZjInCuwgwZWyAmRIp1NTMhri7r3380/uqwrUHfaDiPzLVTuoNy4whX66bxPVog==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -2669,9 +2678,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pupa": {
|
"node_modules/pupa": {
|
||||||
"version": "2.0.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz",
|
||||||
"integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==",
|
"integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"escape-goat": "^2.0.0"
|
"escape-goat": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -2968,7 +2977,7 @@
|
||||||
"node_modules/sort-keys": {
|
"node_modules/sort-keys": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
||||||
"integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
|
"integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-plain-obj": "^1.0.0"
|
"is-plain-obj": "^1.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -2979,7 +2988,7 @@
|
||||||
"node_modules/sort-keys-length": {
|
"node_modules/sort-keys-length": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz",
|
||||||
"integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=",
|
"integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sort-keys": "^1.0.0"
|
"sort-keys": "^1.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -4592,9 +4601,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron": {
|
"electron": {
|
||||||
"version": "22.1.0",
|
"version": "22.3.27",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-22.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-22.3.27.tgz",
|
||||||
"integrity": "sha512-wz5s4N6V7zyKm4YQmXJImFoxO1Doai32ShYm0FzOLPBMwLMdQBV+REY+j1opRx0KJ9xJEIdjYgcA8OSw6vx3pA==",
|
"integrity": "sha512-7Rht21vHqj4ZFRnKuZdFqZFsvMBCmDqmjetiMqPtF+TmTBiGne1mnstVXOA/SRGhN2Qy5gY5bznJKpiqogjM8A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@electron/get": "^2.0.0",
|
"@electron/get": "^2.0.0",
|
||||||
|
|
@ -4652,13 +4661,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron-context-menu": {
|
"electron-context-menu": {
|
||||||
"version": "2.3.0",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.5.2.tgz",
|
||||||
"integrity": "sha512-XYsYkNY+jvX4C5o09qMuZoKL6e9frnQzBFehZSIiKp6zK0u3XYowJYDyK3vDKKZxYsOIGiE/Gbx40jERC03Ctw==",
|
"integrity": "sha512-1cEQR6fA9ktFsRBc+eXPwvrOgAPytUD7rUV4iBAA5zTrLAPKokJ23xeMjcK2fjrDPrlFRBxcLz0KP+GUhMrSCQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"cli-truncate": "^2.0.0",
|
"cli-truncate": "^2.1.0",
|
||||||
"electron-dl": "^3.0.0",
|
"electron-dl": "^3.1.0",
|
||||||
"electron-is-dev": "^1.0.1"
|
"electron-is-dev": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron-default-menu": {
|
"electron-default-menu": {
|
||||||
|
|
@ -4667,9 +4676,9 @@
|
||||||
"integrity": "sha512-YAL/UNR3kPG58wOOlmDpTG3i6+bzwhHx6NllIOaLuVrU7uYifeYGGdk5IH2Hap4wVEx2YTA8cqQ2PGSplYwDWQ=="
|
"integrity": "sha512-YAL/UNR3kPG58wOOlmDpTG3i6+bzwhHx6NllIOaLuVrU7uYifeYGGdk5IH2Hap4wVEx2YTA8cqQ2PGSplYwDWQ=="
|
||||||
},
|
},
|
||||||
"electron-dl": {
|
"electron-dl": {
|
||||||
"version": "3.0.2",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.5.2.tgz",
|
||||||
"integrity": "sha512-pRgE9Jbhoo5z6Vk3qi+vIrfpMDlCp2oB1UeR96SMnsfz073jj0AZGQwp69EdIcEvlUlwBSGyJK8Jt6OB6JLn+g==",
|
"integrity": "sha512-i104cl+u8yJ0lhpRAtUWfeGuWuL1PL6TBiw2gLf0MMIBjfgE485Ags2mcySx4uWU9P9uj/vsD3jd7X+w1lzZxw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ext-name": "^5.0.0",
|
"ext-name": "^5.0.0",
|
||||||
"pupa": "^2.0.1",
|
"pupa": "^2.0.1",
|
||||||
|
|
@ -4722,9 +4731,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron-progressbar": {
|
"electron-progressbar": {
|
||||||
"version": "2.0.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-2.2.1.tgz",
|
||||||
"integrity": "sha512-+N60GX2q+KH5OvZXxwtjMTZB/1AyxriFd95vOnR3sOfNpvz+30LMsM0a9SnEivZE6N8Djy7F3z4TY8pLs8aopw==",
|
"integrity": "sha512-LQ9bxM3Tf5PG/1QngY8ywvht7IKvQ8tEIra8uh3RkLASqN/GYvr4r0uU9qz38r0Zn72UcouYzumKDfLwoI/rsw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"extend": "^3.0.1"
|
"extend": "^3.0.1"
|
||||||
}
|
}
|
||||||
|
|
@ -5220,7 +5229,7 @@
|
||||||
"is-plain-obj": {
|
"is-plain-obj": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
||||||
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
|
"integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="
|
||||||
},
|
},
|
||||||
"is-stream": {
|
"is-stream": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
|
@ -5445,7 +5454,7 @@
|
||||||
"modify-filename": {
|
"modify-filename": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz",
|
||||||
"integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE="
|
"integrity": "sha512-EickqnKq3kVVaZisYuCxhtKbZjInCuwgwZWyAmRIp1NTMhri7r3380/uqwrUHfaDiPzLVTuoNy4whX66bxPVog=="
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
|
|
@ -5570,9 +5579,9 @@
|
||||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||||
},
|
},
|
||||||
"pupa": {
|
"pupa": {
|
||||||
"version": "2.0.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz",
|
||||||
"integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==",
|
"integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"escape-goat": "^2.0.0"
|
"escape-goat": "^2.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -5809,7 +5818,7 @@
|
||||||
"sort-keys": {
|
"sort-keys": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
||||||
"integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
|
"integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-plain-obj": "^1.0.0"
|
"is-plain-obj": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -5817,7 +5826,7 @@
|
||||||
"sort-keys-length": {
|
"sort-keys-length": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz",
|
||||||
"integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=",
|
"integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"sort-keys": "^1.0.0"
|
"sort-keys": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
"author": "Specter",
|
"author": "Specter",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^22.1.0",
|
"electron": "^22.3.12",
|
||||||
"electron-builder": "^23.3.1"
|
"electron-builder": "^23.6.0"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"productName": "Specter",
|
"productName": "Specter",
|
||||||
|
|
@ -30,9 +30,9 @@
|
||||||
"category": "public.app-category.utilities",
|
"category": "public.app-category.utilities",
|
||||||
"identity": "Kim Neunert (FWV59JHV83)",
|
"identity": "Kim Neunert (FWV59JHV83)",
|
||||||
"entitlements": "./build/entitlements.mac.plist",
|
"entitlements": "./build/entitlements.mac.plist",
|
||||||
"entitlementsInherit": "./build/entitlements.mac.plist",
|
|
||||||
"hardenedRuntime": true,
|
"hardenedRuntime": true,
|
||||||
"icon": "./icons/icon.icns"
|
"icon": "./icons/icon.icns",
|
||||||
|
"provisioningProfile": "/Users/kim/Specter_Desktop_vanilla.provisionprofile"
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"icon": "./icons/icon.ico"
|
"icon": "./icons/icon.ico"
|
||||||
|
|
@ -45,12 +45,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-context-menu": "^2.3.0",
|
"electron-context-menu": "^2.5.2",
|
||||||
"electron-default-menu": "^1.0.2",
|
"electron-default-menu": "^1.0.2",
|
||||||
"electron-progressbar": "^2.0.1",
|
"electron-progressbar": "^2.1.0",
|
||||||
"extract-zip": "^2.0.1",
|
"extract-zip": "^2.0.1",
|
||||||
"read-last-lines": "^1.8.0",
|
"read-last-lines": "^1.8.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"winston": "^3.3.3"
|
"winston": "^3.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
// All of the Node.js APIs are available in the preload process.
|
// All of the Node.js APIs are available in the preload process.
|
||||||
// It has the same sandbox as a Chrome extension.
|
// It has the same sandbox as a Chrome extension.
|
||||||
|
|
||||||
|
const { contextBridge, ipcRenderer } = require('electron')
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('api', {
|
||||||
|
send: (channel, data) => ipcRenderer.send(channel, data),
|
||||||
|
receive: (channel, func) => ipcRenderer.on(channel, (event, ...args) => func(...args)),
|
||||||
|
})
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// No idea why this is here
|
||||||
const replaceText = (selector, text) => {
|
const replaceText = (selector, text) => {
|
||||||
const element = document.getElementById(selector)
|
const element = document.getElementById(selector)
|
||||||
if (element) element.innerText = text
|
if (element) element.innerText = text
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const type of ['chrome', 'node', 'electron']) {
|
for (const type of ['chrome', 'node', 'electron']) {
|
||||||
replaceText(`${type}-version`, process.versions[type])
|
replaceText(`${type}-version`, process.versions[type])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,25 @@
|
||||||
// `nodeIntegration` is turned off. Use `preload.js` to
|
// `nodeIntegration` is turned off. Use `preload.js` to
|
||||||
// selectively enable features needed in the rendering
|
// selectively enable features needed in the rendering
|
||||||
// process.
|
// process.
|
||||||
|
|
||||||
|
// All of the Node.js APIs are available in the preload process.
|
||||||
|
// It has the same sandbox as a Chrome extension.
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
|
||||||
|
const updateSpinner = (show) => {
|
||||||
|
const spinnerElement = document.getElementById('spinner');
|
||||||
|
if (spinnerElement) {
|
||||||
|
spinnerElement.classList.toggle('hidden', !show);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.api.receive('update-loader-message', (data) => {
|
||||||
|
const launchTextElement = document.getElementById('launch-text');
|
||||||
|
if (launchTextElement) {
|
||||||
|
launchTextElement.textContent = data.msg;
|
||||||
|
updateSpinner(data.showSpinner);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
@ -6,4 +6,5 @@
|
||||||
<svg id="spinner" class="text-white rounded-full bg-dark-700 w-4 h-4 animate-spin hidden" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M52.8359,43.6067l1.30463e-08,-1.88843e-08c-0.312506,0.452349 -0.932543,0.565714 -1.38489,0.253208c-0.0990138,-0.0684038 -0.184804,-0.154194 -0.253208,-0.253208l-6.9888,-9.9742l-5.91546e-08,-8.31834e-08c-0.328807,-0.46237 -0.220533,-1.10375 0.241837,-1.43255c0.168889,-0.120103 0.370155,-0.186318 0.577363,-0.189948h4.9835l3.54852e-07,0.000503677c-0.00686545,-9.94223 -8.0722,-17.9964 -18.0144,-17.9896c-4.13508,0.00285541 -8.14312,1.42919 -11.3504,4.03924l4.60883e-08,-3.91242e-08c-0.842285,0.715013 -2.10472,0.611839 -2.81974,-0.230446c-0.715013,-0.842285 -0.611839,-2.10472 0.230446,-2.81974l3.46098e-07,-2.83814e-07c9.39634,-7.70537 23.26,-6.33457 30.9654,3.06177c3.22304,3.93035 4.98589,8.85536 4.98903,13.9382h4.9935l2.44822e-09,4.33431e-11c0.567328,0.0100438 1.0191,0.478096 1.00905,1.04542c-0.0036664,0.207098 -0.0698445,0.408252 -0.189853,0.577075Zm-40.0129,-23.1717l6.9888,9.9742l-2.71216e-09,-3.81325e-09c0.32881,0.4623 0.220594,1.10362 -0.241706,1.43243c-0.168809,0.120065 -0.369975,0.186285 -0.577094,0.189969h-4.983l2.60913e-07,0.000383721c0.0068661,9.94206 8.07207,17.9961 18.0141,17.9893c4.1354,-0.00285595 8.14372,-1.42943 11.3511,-4.03989l-7.47965e-08,6.34738e-08c0.8424,-0.714877 2.10482,-0.6115 2.8197,0.2309c0.714877,0.8424 0.6115,2.10482 -0.2309,2.8197l4.04125e-07,-3.31403e-07c-9.39617,7.70531 -23.2597,6.33461 -30.965,-3.06156c-3.22311,-3.93039 -4.98597,-8.85549 -4.98903,-13.9384h-4.9947l5.45126e-08,9.68797e-10c-0.567328,-0.0100825 -1.01906,-0.478166 -1.00898,-1.04549c0.00367938,-0.207033 0.0698308,-0.408123 0.189782,-0.576906l6.9888,-9.9742l-3.21808e-08,4.66031e-08c0.312407,-0.452417 0.93242,-0.565917 1.38484,-0.25351c0.0990287,0.0683822 0.184838,0.154154 0.253263,0.253153Z" fill="currentColor" fill-rule="evenodd"></path></svg>
|
<svg id="spinner" class="text-white rounded-full bg-dark-700 w-4 h-4 animate-spin hidden" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M52.8359,43.6067l1.30463e-08,-1.88843e-08c-0.312506,0.452349 -0.932543,0.565714 -1.38489,0.253208c-0.0990138,-0.0684038 -0.184804,-0.154194 -0.253208,-0.253208l-6.9888,-9.9742l-5.91546e-08,-8.31834e-08c-0.328807,-0.46237 -0.220533,-1.10375 0.241837,-1.43255c0.168889,-0.120103 0.370155,-0.186318 0.577363,-0.189948h4.9835l3.54852e-07,0.000503677c-0.00686545,-9.94223 -8.0722,-17.9964 -18.0144,-17.9896c-4.13508,0.00285541 -8.14312,1.42919 -11.3504,4.03924l4.60883e-08,-3.91242e-08c-0.842285,0.715013 -2.10472,0.611839 -2.81974,-0.230446c-0.715013,-0.842285 -0.611839,-2.10472 0.230446,-2.81974l3.46098e-07,-2.83814e-07c9.39634,-7.70537 23.26,-6.33457 30.9654,3.06177c3.22304,3.93035 4.98589,8.85536 4.98903,13.9382h4.9935l2.44822e-09,4.33431e-11c0.567328,0.0100438 1.0191,0.478096 1.00905,1.04542c-0.0036664,0.207098 -0.0698445,0.408252 -0.189853,0.577075Zm-40.0129,-23.1717l6.9888,9.9742l-2.71216e-09,-3.81325e-09c0.32881,0.4623 0.220594,1.10362 -0.241706,1.43243c-0.168809,0.120065 -0.369975,0.186285 -0.577094,0.189969h-4.983l2.60913e-07,0.000383721c0.0068661,9.94206 8.07207,17.9961 18.0141,17.9893c4.1354,-0.00285595 8.14372,-1.42943 11.3511,-4.03989l-7.47965e-08,6.34738e-08c0.8424,-0.714877 2.10482,-0.6115 2.8197,0.2309c0.714877,0.8424 0.6115,2.10482 -0.2309,2.8197l4.04125e-07,-3.31403e-07c-9.39617,7.70531 -23.2597,6.33461 -30.965,-3.06156c-3.22311,-3.93039 -4.98597,-8.85549 -4.98903,-13.9384h-4.9947l5.45126e-08,9.68797e-10c-0.567328,-0.0100825 -1.01906,-0.478166 -1.00898,-1.04549c0.00367938,-0.207033 0.0698308,-0.408123 0.189782,-0.576906l6.9888,-9.9742l-3.21808e-08,4.66031e-08c0.312407,-0.452417 0.93242,-0.565917 1.38484,-0.25351c0.0990287,0.0683822 0.184838,0.154154 0.253263,0.253153Z" fill="currentColor" fill-rule="evenodd"></path></svg>
|
||||||
<p id="launch-text" class="text-lg">Launching Specter Desktop...</p>
|
<p id="launch-text" class="text-lg">Launching Specter Desktop...</p>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="renderer.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
109
pyinstaller/electron/src/config.js
Normal file
109
pyinstaller/electron/src/config.js
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
const os = require('os')
|
||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
|
||||||
|
const downloadloc = require('../downloadloc');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let platformName = ''
|
||||||
|
switch (process.platform) {
|
||||||
|
case 'darwin':
|
||||||
|
platformName = 'osx'
|
||||||
|
break
|
||||||
|
case 'win32':
|
||||||
|
platformName = 'win64'
|
||||||
|
break
|
||||||
|
case 'linux':
|
||||||
|
platformName = 'x86_64-linux-gnu'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw `Unknown platformName ${platformName}`
|
||||||
|
}
|
||||||
|
const appName = downloadloc.appName()
|
||||||
|
const appNameLower = appName.toLowerCase()
|
||||||
|
const isDev = process.env.NODE_ENV === "development"
|
||||||
|
const unresolvedDevFolder = process.env.SPECTER_DATA_FOLDER || "~/.specter_dev"
|
||||||
|
const devFolder = unresolvedDevFolder.replace(/^~/, os.homedir());
|
||||||
|
const prodFolder = path.resolve(os.homedir(), `.${appNameLower}`)
|
||||||
|
let appSettingsPath
|
||||||
|
let appSettings
|
||||||
|
let specterdDirPath
|
||||||
|
let specterAppLogPath
|
||||||
|
let versionDataPath
|
||||||
|
let versionData
|
||||||
|
|
||||||
|
function getAppSettings() {
|
||||||
|
let defaultSettings = {
|
||||||
|
mode: 'specterd',
|
||||||
|
specterURL: 'http://localhost:25441',
|
||||||
|
basicAuth: false,
|
||||||
|
basicAuthUser: '',
|
||||||
|
basicAuthPass: '',
|
||||||
|
tor: false,
|
||||||
|
proxyURL: "socks5://127.0.0.1:9050",
|
||||||
|
specterdVersion: (versionData && versionData.version !== undefined) ? versionData.version : 'unknown',
|
||||||
|
specterdHash: (versionData && versionData.sha256 !== undefined) ? versionData.sha256 : 'unknown',
|
||||||
|
specterdCLIArgs: "",
|
||||||
|
versionInitialized: false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(appSettingsPath)){
|
||||||
|
fs.mkdirSync(path.resolve(appSettingsPath, '..'), { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(appSettingsPath, JSON.stringify(defaultSettings), { flag: 'wx' });
|
||||||
|
} catch (error) {
|
||||||
|
if (error.toString().startsWith("Error: EEXIST: file already exists,")) {
|
||||||
|
//ignore
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to add missing settings in case the format changed or new settings were added
|
||||||
|
let appSettings = require(appSettingsPath)
|
||||||
|
for (let key of Object.keys(defaultSettings)) {
|
||||||
|
if (!appSettings.hasOwnProperty(key)) {
|
||||||
|
appSettings[key] = defaultSettings[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return appSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDev) {
|
||||||
|
versionDataPath = `${devFolder}/version-data.json`
|
||||||
|
|
||||||
|
} else {
|
||||||
|
versionDataPath = `../version-data.json`
|
||||||
|
}
|
||||||
|
versionData = require(versionDataPath)
|
||||||
|
|
||||||
|
|
||||||
|
if (isDev) {
|
||||||
|
appSettingsPath = `${devFolder}/app_settings.json`
|
||||||
|
specterdDirPath = `${devFolder}/specterd-binaries`
|
||||||
|
specterAppLogPath = `${devFolder}/specterApp.log`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
appSettingsPath = `${prodFolder}/app_settings.json`
|
||||||
|
specterdDirPath = `${prodFolder}/specterd-binaries`
|
||||||
|
specterAppLogPath = `${prodFolder}/specterApp.log`
|
||||||
|
}
|
||||||
|
appSettings = getAppSettings()
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
platformName,
|
||||||
|
appSettings,
|
||||||
|
appName,
|
||||||
|
appNameLower,
|
||||||
|
appSettingsPath,
|
||||||
|
specterdDirPath,
|
||||||
|
specterAppLogPath,
|
||||||
|
versionDataPath,
|
||||||
|
versionData,
|
||||||
|
isDev: isDev,
|
||||||
|
devFolder,
|
||||||
|
}
|
||||||
145
pyinstaller/electron/src/download.js
Normal file
145
pyinstaller/electron/src/download.js
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
const request = require('request')
|
||||||
|
const fs = require('fs')
|
||||||
|
const { app, Menu } = require('electron')
|
||||||
|
const extract = require('extract-zip')
|
||||||
|
const { getDownloadLocation } = require('../downloadloc.js')
|
||||||
|
const { appName, appSettings, platformName, appNameLower, versionData, versionDataPath } = require('./config.js')
|
||||||
|
const { isMac, getFileHash } = require('./helpers.js')
|
||||||
|
const { logger } = require('./logging.js')
|
||||||
|
const ProgressBar = require('electron-progressbar')
|
||||||
|
const { updateSpecterdStatus, updatingLoaderMsg, createProgressBar } = require('./uiHelpers.js')
|
||||||
|
const { startSpecterd } = require('./specterd.js')
|
||||||
|
|
||||||
|
// The standard quit item cannot be replaced / modified and it is not triggering the
|
||||||
|
// before-quit event on MacOS if a child window is open
|
||||||
|
const dockMenuWithforceQuit = Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: 'Force Quit during download',
|
||||||
|
click: () => {
|
||||||
|
// If the progress bar exists, close it
|
||||||
|
if (progressBar) {
|
||||||
|
progressBar.close()
|
||||||
|
}
|
||||||
|
// Quit the app
|
||||||
|
app.quit()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
function downloadSpecterd(specterdPath) {
|
||||||
|
updatingLoaderMsg(`Starting download`)
|
||||||
|
updateSpecterdStatus(`Downloading the ${appName} binary...`)
|
||||||
|
// Some logging
|
||||||
|
logger.info('Using version ' + appSettings.specterdVersion)
|
||||||
|
logger.info('Using platformName ' + platformName)
|
||||||
|
download_location = getDownloadLocation(appSettings.specterdVersion, platformName)
|
||||||
|
logger.info('Downloading from ' + download_location)
|
||||||
|
download(download_location, specterdPath + '.zip', function (errored) {
|
||||||
|
if (errored == true) {
|
||||||
|
updatingLoaderMsg(
|
||||||
|
`Downloading the ${appNameLower} binary from GitHub failed, could not reach the server or the file wasn't found.`
|
||||||
|
)
|
||||||
|
updateSpecterdStatus(`Downloading ${appNameLower}d failed...`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updatingLoaderMsg('Download completed. Unpacking files...')
|
||||||
|
logger.info('Extracting ' + specterdPath)
|
||||||
|
|
||||||
|
extract(specterdPath + '.zip', { dir: specterdPath + '-dir' }).then(function () {
|
||||||
|
let extraPath = ''
|
||||||
|
switch (process.platform) {
|
||||||
|
case 'darwin':
|
||||||
|
extraPath = appNameLower + 'd'
|
||||||
|
break
|
||||||
|
case 'win32':
|
||||||
|
extraPath = appNameLower + 'd.exe'
|
||||||
|
break
|
||||||
|
case 'linux':
|
||||||
|
extraPath = appNameLower + 'd'
|
||||||
|
}
|
||||||
|
var oldPath = specterdPath + `-dir/${extraPath}`
|
||||||
|
var newPath = specterdPath + (platformName == 'win64' ? '.exe' : '')
|
||||||
|
|
||||||
|
fs.renameSync(oldPath, newPath)
|
||||||
|
fs.unlinkSync(specterdPath + '.zip')
|
||||||
|
fs.rmdirSync(specterdPath + '-dir', { recursive: true })
|
||||||
|
getFileHash(specterdPath + (platformName == 'win64' ? '.exe' : ''), function (specterdHash) {
|
||||||
|
if (appSettings.specterdHash.toLowerCase() === specterdHash || appSettings.specterdHash == '') {
|
||||||
|
startSpecterd(specterdPath)
|
||||||
|
} else {
|
||||||
|
updatingLoaderMsg('Specterd version could not be validated.')
|
||||||
|
logger.error(`hash of downloaded file: ${specterdHash}`)
|
||||||
|
logger.error(`Expected hash: ${appSettings.specterdHash} from ${versionDataPath}`)
|
||||||
|
updateSpecterdStatus('Failed to launch specterd...')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download function with progress bar
|
||||||
|
const download = (uri, filename, callback) => {
|
||||||
|
// HEAD request first
|
||||||
|
request.head(uri, (err, res, body) => {
|
||||||
|
if (res.statusCode != 404) {
|
||||||
|
let receivedBytes = 0
|
||||||
|
const totalBytes = res.headers['content-length']
|
||||||
|
logger.info(`Total size to download: ${totalBytes}`)
|
||||||
|
const progressBar = createProgressBar(totalBytes)
|
||||||
|
// Add Force Quit item during download for MacOS dock
|
||||||
|
if (isMac) {
|
||||||
|
app.dock.setMenu(dockMenuWithforceQuit)
|
||||||
|
}
|
||||||
|
|
||||||
|
progressBar.on('completed', () => {
|
||||||
|
progressBar.close()
|
||||||
|
// Remove the Force Quit dock item again for Mac
|
||||||
|
if (isMac) {
|
||||||
|
const updatedDockMenu = Menu.buildFromTemplate(
|
||||||
|
dockMenuWithforceQuit.items.filter((item) => item.label !== 'Force Quit during download')
|
||||||
|
)
|
||||||
|
app.dock.setMenu(updatedDockMenu)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
progressBar.on('aborted', () => {
|
||||||
|
logger.warn('Download was aborted before it could finish.')
|
||||||
|
})
|
||||||
|
// Loggin the download progress
|
||||||
|
let lastLogTime = 0
|
||||||
|
const logInterval = 5000 // log every 5 seconds
|
||||||
|
progressBar.on('progress', () => {
|
||||||
|
const currentTime = Date.now()
|
||||||
|
if (currentTime - lastLogTime >= logInterval) {
|
||||||
|
lastLogTime = currentTime
|
||||||
|
logger.info(`Download status: ${((receivedBytes / totalBytes) * 100).toFixed(0)}%`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// GET request
|
||||||
|
request(uri)
|
||||||
|
.on('data', (chunk) => {
|
||||||
|
receivedBytes += chunk.length
|
||||||
|
if (progressBar) {
|
||||||
|
progressBar.value = receivedBytes
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.pipe(fs.createWriteStream(filename))
|
||||||
|
.on('close', callback)
|
||||||
|
}
|
||||||
|
// If the download link was not found, call callback (updatingLoaderMsg with error feedback)
|
||||||
|
else {
|
||||||
|
logger.error(`Error while trying to download specterd: ${err}`)
|
||||||
|
try {
|
||||||
|
callback(true)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
downloadSpecterd: downloadSpecterd,
|
||||||
|
download: download,
|
||||||
|
}
|
||||||
43
pyinstaller/electron/src/helpers.js
Normal file
43
pyinstaller/electron/src/helpers.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
const { Menu } = require('electron')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
const readLastLines = require('read-last-lines')
|
||||||
|
const isMac = process.platform === 'darwin'
|
||||||
|
|
||||||
|
const { versionData, appSettingsPath, specterAppLogPath } = require('./config.js')
|
||||||
|
const { logger } = require('./logging.js')
|
||||||
|
|
||||||
|
// Use different version-data.jsons
|
||||||
|
|
||||||
|
// Should look like this:
|
||||||
|
// {
|
||||||
|
// "version": "v2.0.0-pre32",
|
||||||
|
// "sha256": "aa049abf3e75199bad26fbded08ee5911ad48e325b42c43ec195136bd0736785"
|
||||||
|
// }
|
||||||
|
|
||||||
|
function getFileHash(filename, callback) {
|
||||||
|
let shasum = crypto.createHash('sha256'),
|
||||||
|
// Updating shasum with file content
|
||||||
|
s = fs.ReadStream(filename)
|
||||||
|
s.on('data', function (data) {
|
||||||
|
shasum.update(data)
|
||||||
|
})
|
||||||
|
// making digest
|
||||||
|
s.on('end', function () {
|
||||||
|
var hash = shasum.digest('hex')
|
||||||
|
callback(hash)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSpecterAppLogs(callback) {
|
||||||
|
readLastLines.read(specterAppLogPath, 700).then(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getFileHash,
|
||||||
|
getSpecterAppLogs,
|
||||||
|
versionData,
|
||||||
|
isMac,
|
||||||
|
isMac,
|
||||||
|
}
|
||||||
22
pyinstaller/electron/src/logging.js
Normal file
22
pyinstaller/electron/src/logging.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
const { specterAppLogPath } = require('./config.js')
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
const { transports, format, createLogger } = require('winston')
|
||||||
|
const combinedLog = new transports.File({ filename: specterAppLogPath })
|
||||||
|
const winstonOptions = {
|
||||||
|
exitOnError: false,
|
||||||
|
format: format.combine(
|
||||||
|
format.timestamp(),
|
||||||
|
format.json(),
|
||||||
|
format.printf((info) => {
|
||||||
|
return `${info.timestamp} [${info.level}] : ${info.message}`
|
||||||
|
})
|
||||||
|
),
|
||||||
|
transports: [new transports.Console({ json: false }), combinedLog],
|
||||||
|
exceptionHandlers: [combinedLog],
|
||||||
|
}
|
||||||
|
const logger = createLogger(winstonOptions)
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
189
pyinstaller/electron/src/specterd.js
Normal file
189
pyinstaller/electron/src/specterd.js
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
const { app, BrowserWindow } = require('electron')
|
||||||
|
const { appSettings, platformName } = require('./config')
|
||||||
|
const {
|
||||||
|
showError,
|
||||||
|
updateSpecterdStatus,
|
||||||
|
updatingLoaderMsg,
|
||||||
|
createWindow,
|
||||||
|
loadUrl,
|
||||||
|
executeJavaScript,
|
||||||
|
} = require('./uiHelpers')
|
||||||
|
const { logger } = require('./logging')
|
||||||
|
const { spawn } = require('child_process')
|
||||||
|
const { URL } = require('url')
|
||||||
|
|
||||||
|
function checkSpecterd(logs, specterdStarted) {
|
||||||
|
// There doesn't seem to be another more straightforward way to check whether specterd is running: https://github.com/nodejs/help/issues/1191
|
||||||
|
// Setting a timeout to avoid waiting for specterd endlessly
|
||||||
|
const timeout = 180000 // 3 minutes
|
||||||
|
const now = Date.now()
|
||||||
|
const timeElapsed = now - specterdStarted
|
||||||
|
if (timeElapsed > timeout) {
|
||||||
|
return 'timeout'
|
||||||
|
}
|
||||||
|
if (logs.toString().includes('Serving Flask app')) {
|
||||||
|
return 'running'
|
||||||
|
} else {
|
||||||
|
return 'not running'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let specterIsRunning = false
|
||||||
|
function startSpecterd(specterdPath, automaticWalletImport = false) {
|
||||||
|
if (platformName == 'win64') {
|
||||||
|
specterdPath += '.exe'
|
||||||
|
}
|
||||||
|
let hwiBridgeMode = appSettings.mode == 'hwibridge'
|
||||||
|
updatingLoaderMsg('Launching Specter ...', (showSpinner = 'true'))
|
||||||
|
updateSpecterdStatus('Launching Specter ...')
|
||||||
|
let specterdArgs = ['server']
|
||||||
|
specterdArgs.push('--no-filelog')
|
||||||
|
if (hwiBridgeMode) specterdArgs.push('--hwibridge')
|
||||||
|
if (appSettings.specterdCLIArgs != '') {
|
||||||
|
// User has inputed cli arguments in the UI
|
||||||
|
let specterdExtraArgs = appSettings.specterdCLIArgs.split(' ')
|
||||||
|
specterdExtraArgs.forEach((arg) => {
|
||||||
|
// Ensures that whitespaces are not used as cli arguments
|
||||||
|
if (arg != '') {
|
||||||
|
specterdArgs.push(arg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// locale fix (copying from nodejs-env + adding locales)
|
||||||
|
const options = {
|
||||||
|
env: { ...process.env },
|
||||||
|
}
|
||||||
|
options.env['LC_ALL'] = 'en_US.utf-8'
|
||||||
|
options.env['LANG'] = 'en_US.utf-8'
|
||||||
|
options.env['SPECTER_LOGFORMAT'] = 'SPECTERD: %(levelname)s in %(module)s: %(message)s'
|
||||||
|
logger.info(`Starting specter: ${specterdPath} ${specterdArgs.join(' ')}`)
|
||||||
|
specterdProcess = spawn(specterdPath, specterdArgs, options)
|
||||||
|
const specterdStarted = Date.now()
|
||||||
|
|
||||||
|
// We are checking for both, stdout and stderr, to be on the save side.
|
||||||
|
specterdProcess.stdout.on('data', (data) => {
|
||||||
|
actOnNewLogLine(data.toString(), 'stdout')
|
||||||
|
})
|
||||||
|
|
||||||
|
specterdProcess.stderr.on('data', (data) => {
|
||||||
|
actOnNewLogLine(data.toString(), 'stderr')
|
||||||
|
})
|
||||||
|
|
||||||
|
const actOnNewLogLine = (logLine, origin) => {
|
||||||
|
logger.info(`${origin}: ${logLine.replace(/(\r\n|\n|\r)/gm, '')}`)
|
||||||
|
const serverdStatus = checkSpecterd(logLine, specterdStarted)
|
||||||
|
if (!specterIsRunning) {
|
||||||
|
if (serverdStatus === 'running') {
|
||||||
|
logger.info(`Specter server seems to run ...`)
|
||||||
|
updateSpecterdStatus('Specter is running')
|
||||||
|
specterIsRunning = true
|
||||||
|
if (automaticWalletImport === true) {
|
||||||
|
logger.info('Performing automatic wallet import ...')
|
||||||
|
updatingLoaderMsg('Launching wallet importer. This will only work with a node connection.', (showSpinner = true))
|
||||||
|
setTimeout(() => {
|
||||||
|
importWallet(walletDataFromUrl)
|
||||||
|
}, 3000)
|
||||||
|
} else {
|
||||||
|
logger.info('Normal startup of Specter.')
|
||||||
|
createWindow(appSettings.specterURL)
|
||||||
|
}
|
||||||
|
} else if (serverdStatus === 'timeout') {
|
||||||
|
showError('Specter does not seem to start. Check the logs in the menu for more details.')
|
||||||
|
updateSpecterdStatus('Specter does not start')
|
||||||
|
logger.error('Startup timeout for specterd exceeded')
|
||||||
|
} else {
|
||||||
|
updatingLoaderMsg('Still waiting for Specter to start ...')
|
||||||
|
updateSpecterdStatus('Specter is starting')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
specterdProcess.on('exit', (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
logger.error(`specterd exited with code ${code}`)
|
||||||
|
showError(`Specter exited with exit code ${code}. Check the logs in the menu for more details.`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
specterdProcess.on('error', (err) => {
|
||||||
|
logger.error(`Error starting Specter server: ${err}`)
|
||||||
|
showError(`Specter failed to start, due to ${err.message}. Check the logs in the menu for more details.`)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', function () {
|
||||||
|
// On macOS it's common to re-create a window in the app when the
|
||||||
|
// dock icon is clicked and there are no other windows open.
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) createWindow(appSettings.specterURL)
|
||||||
|
})
|
||||||
|
// since these are streams, you can pipe them elsewhere
|
||||||
|
specterdProcess.on('close', (code) => {
|
||||||
|
updateSpecterdStatus('Specter stopped...')
|
||||||
|
logger.info(`child process exited with code ${code}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function quitSpecterd() {
|
||||||
|
if (specterdProcess) {
|
||||||
|
try {
|
||||||
|
if (platformName == 'win64') {
|
||||||
|
exec('taskkill /F /T /PID ' + specterdProcess.pid)
|
||||||
|
exec('taskkill /IM specterd.exe ')
|
||||||
|
process.kill(-specterdProcess.pid)
|
||||||
|
}
|
||||||
|
specterdProcess.kill('SIGINT')
|
||||||
|
} catch (e) {
|
||||||
|
logger.info('Specterd quit warning: ' + e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let walletDataFromUrl
|
||||||
|
// Checking whether the app was opened via a Specter URL and determine whether to perform a specific startup action
|
||||||
|
app.on('open-url', (_, url) => {
|
||||||
|
logger.info('The app was opened via URL, checking the URL to decide whether to do any automatic actions ...')
|
||||||
|
// Parse the URL to extract the query parameters
|
||||||
|
const specterUrl = new URL(url)
|
||||||
|
const searchParams = specterUrl.searchParams
|
||||||
|
// Get the query parameter values
|
||||||
|
const action = searchParams.get('action')
|
||||||
|
const data = searchParams.get('data')
|
||||||
|
if (action === 'importWallet' && data !== '') {
|
||||||
|
logger.info('Automatic wallet import identified in the URL, setting automaticWalletImport to true.')
|
||||||
|
automaticWalletImport = true
|
||||||
|
walletDataFromUrl = data
|
||||||
|
// Directly import if the app and specterd is already running
|
||||||
|
if (specterIsRunning) {
|
||||||
|
logger.info('Performing automatic wallet import ...')
|
||||||
|
loadURL(`file://${__dirname}/splash.html`)
|
||||||
|
updatingLoaderMsg('Launching wallet importer. This will only work with a node connection.', (showSpinner = true))
|
||||||
|
setTimeout(() => {
|
||||||
|
importWallet(walletDataFromUrl)
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Automatically import the wallet json string, bring user to the final import wallet screen.
|
||||||
|
// Only proceed with the import if the importFromWalletSoftwareBtn can be found.
|
||||||
|
// If it is not, users are redirected by specterd to the configure connection screen.
|
||||||
|
function importWallet(walletData) {
|
||||||
|
loadUrl(appSettings.specterURL + '/wallets/new_wallet/')
|
||||||
|
let code = `
|
||||||
|
const importFromWalletSoftwareBtn = document.getElementById('import-from-wallet-software-btn')
|
||||||
|
if (importFromWalletSoftwareBtn) {
|
||||||
|
importFromWalletSoftwareBtn.click()
|
||||||
|
const walletDataTextArea = document.getElementById('txt')
|
||||||
|
if (walletDataTextArea) {
|
||||||
|
walletDataTextArea.value = \`${walletData}\`
|
||||||
|
}
|
||||||
|
const continueBtn = document.getElementById('continue-with-wallet-import-btn');
|
||||||
|
continueBtn.click()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
executeJavaScript(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
startSpecterd,
|
||||||
|
quitSpecterd,
|
||||||
|
}
|
||||||
177
pyinstaller/electron/src/uiHelpers.js
Normal file
177
pyinstaller/electron/src/uiHelpers.js
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
const path = require('path')
|
||||||
|
const { Menu, BrowserWindow, nativeTheme, app, Tray, nativeImage } = require('electron')
|
||||||
|
const { logger } = require('./logging')
|
||||||
|
const { appSettings, isDev, platformName } = require('./config')
|
||||||
|
const ProgressBar = require('electron-progressbar')
|
||||||
|
const { isMac } = require('./helpers')
|
||||||
|
|
||||||
|
// Initialized with initMainWindow
|
||||||
|
let mainWindow
|
||||||
|
// Initialized with InitTray
|
||||||
|
let tray
|
||||||
|
let trayMenu
|
||||||
|
|
||||||
|
const loadUrl = (url) => {
|
||||||
|
mainWindow.loadURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const executeJavaScript = (code) => {
|
||||||
|
mainWindow.webContents.executeJavaScript(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(error) {
|
||||||
|
updatingLoaderMsg('Specter encountered an error:' + error.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatingLoaderMsg(msg, showSpinner = false) {
|
||||||
|
if (mainWindow) {
|
||||||
|
// see preload.js where this is setup
|
||||||
|
mainWindow.webContents.send('update-loader-message', {
|
||||||
|
msg,
|
||||||
|
showSpinner,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
logger.error('mainWindow not initialized in updatingLoaderMsg')
|
||||||
|
}
|
||||||
|
logger.info('Updated LoaderMsg: ' + msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSpecterdStatus(status) {
|
||||||
|
trayMenu[0] = { label: status, enabled: false }
|
||||||
|
tray.setContextMenu(Menu.buildFromTemplate(trayMenu))
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWindow(specterURL) {
|
||||||
|
if (!mainWindow) {
|
||||||
|
initMainWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the browser window.
|
||||||
|
if (appSettings.tor) {
|
||||||
|
mainWindow.webContents.session.setProxy({ proxyRules: appSettings.proxyURL })
|
||||||
|
}
|
||||||
|
mainWindow.loadURL(specterURL + '?mode=remote')
|
||||||
|
}
|
||||||
|
|
||||||
|
let webPreferences = {
|
||||||
|
worldSafeExecuteJavaScript: true,
|
||||||
|
contextIsolation: true,
|
||||||
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
|
}
|
||||||
|
|
||||||
|
function initMainWindow(dimensions) {
|
||||||
|
// In production we use the icons from the build folder
|
||||||
|
// Note: On MacOS setting an icon here as no effect
|
||||||
|
const iconPath = isDev ? path.join(__dirname, 'assets-dev/app_icon.png') : ''
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: parseInt(dimensions.width * 0.8),
|
||||||
|
minWidth: 1120,
|
||||||
|
height: parseInt(dimensions.height * 0.8),
|
||||||
|
icon: iconPath,
|
||||||
|
webPreferences,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Ensures that any links with target="_blank" or window.open() will be opened in the user's default browser instead of within the app
|
||||||
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
|
shell.openExternal(url)
|
||||||
|
return { action: 'deny' }
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.webContents.on('did-fail-load', function () {
|
||||||
|
mainWindow.loadURL(`file://${__dirname}/splash.html`)
|
||||||
|
updatingLoaderMsg(
|
||||||
|
`Failed to load: ${appSettings.specterURL}<br>Please make sure the URL is entered correctly in the settings and try again...</b>`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return mainWindow
|
||||||
|
}
|
||||||
|
|
||||||
|
const initTray = (openPreferences, quitSpecterd) => {
|
||||||
|
if (isMac) {
|
||||||
|
const trayIconPath = nativeTheme.shouldUseDarkColors ? '/assets/menu_icon_dark.png' : '/assets/menu_icon_light.png'
|
||||||
|
const createTrayIcon = (trayIconPath) => {
|
||||||
|
let trayIcon = nativeImage.createFromPath(app.getAppPath() + trayIconPath)
|
||||||
|
// Resize
|
||||||
|
trayIcon = trayIcon.resize({ width: 22, height: 22 })
|
||||||
|
return trayIcon
|
||||||
|
}
|
||||||
|
const trayIcon = createTrayIcon(trayIconPath)
|
||||||
|
tray = new Tray(trayIcon)
|
||||||
|
|
||||||
|
// Change the tray icon if appearance is changed in Mac settings
|
||||||
|
const updateTrayIcon = () => {
|
||||||
|
logger.info('Updating tray icon ...')
|
||||||
|
const trayIconPath = nativeTheme.shouldUseDarkColors ? '/assets/menu_icon_dark.png' : '/assets/menu_icon_light.png'
|
||||||
|
const newTrayIcon = createTrayIcon(trayIconPath)
|
||||||
|
tray.setImage(newTrayIcon)
|
||||||
|
}
|
||||||
|
nativeTheme.on('updated', updateTrayIcon)
|
||||||
|
} else {
|
||||||
|
const trayIcon = nativeImage.createFromPath(app.getAppPath() + '/assets/menu_icon.png')
|
||||||
|
tray = new Tray(trayIcon)
|
||||||
|
}
|
||||||
|
|
||||||
|
trayMenu = [
|
||||||
|
{ label: 'Launching Specter ...', enabled: false },
|
||||||
|
{
|
||||||
|
label: 'Show Specter',
|
||||||
|
click() {
|
||||||
|
mainWindow.show()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
click() {
|
||||||
|
openPreferences()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
click() {
|
||||||
|
quitSpecterd()
|
||||||
|
app.quit()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
tray.setToolTip('Specter')
|
||||||
|
tray.setContextMenu(Menu.buildFromTemplate(trayMenu))
|
||||||
|
}
|
||||||
|
|
||||||
|
const createProgressBar = (totalBytes) => {
|
||||||
|
progressBar = new ProgressBar({
|
||||||
|
indeterminate: false,
|
||||||
|
abortOnError: true,
|
||||||
|
text: 'Downloading the Specter binary from GitHub',
|
||||||
|
detail:
|
||||||
|
'This can take several minutes depending on your Internet connection. Specter will start once the download is finished.',
|
||||||
|
maxValue: totalBytes,
|
||||||
|
browserWindow: {
|
||||||
|
parent: mainWindow,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
detail: {
|
||||||
|
'margin-bottom': '12px',
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
'background-color': '#fff',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
'background-color': '#000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return progressBar
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
loadUrl,
|
||||||
|
executeJavaScript,
|
||||||
|
showError,
|
||||||
|
updatingLoaderMsg,
|
||||||
|
updateSpecterdStatus,
|
||||||
|
createWindow,
|
||||||
|
initMainWindow,
|
||||||
|
mainWindow,
|
||||||
|
initTray,
|
||||||
|
createProgressBar,
|
||||||
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ dependencies = {file = ["requirements.in"]}
|
||||||
test = [
|
test = [
|
||||||
"black==22.3.0",
|
"black==22.3.0",
|
||||||
"pre-commit==2.13.0",
|
"pre-commit==2.13.0",
|
||||||
"pip-tools==6.12.2",
|
"pip-tools==6.13",
|
||||||
"pytest==7.1.2",
|
"pytest==7.1.2",
|
||||||
"PySocks==1.7.1",
|
"PySocks==1.7.1",
|
||||||
"pytest-cov==2.10.1",
|
"pytest-cov==2.10.1",
|
||||||
|
|
|
||||||
|
|
@ -816,9 +816,9 @@ urllib3==1.26.14 \
|
||||||
--hash=sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72 \
|
--hash=sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72 \
|
||||||
--hash=sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1
|
--hash=sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1
|
||||||
# via requests
|
# via requests
|
||||||
werkzeug==2.2.3 \
|
werkzeug==2.2.2 \
|
||||||
--hash=sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe \
|
--hash=sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f \
|
||||||
--hash=sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612
|
--hash=sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5
|
||||||
# via
|
# via
|
||||||
# flask
|
# flask
|
||||||
# flask-login
|
# flask-login
|
||||||
|
|
@ -832,6 +832,5 @@ wtforms==3.0.1 \
|
||||||
# via flask-wtf
|
# via flask-wtf
|
||||||
|
|
||||||
# WARNING: The following packages were not pinned, but pip requires them to be
|
# WARNING: The following packages were not pinned, but pip requires them to be
|
||||||
# pinned when the requirements file includes hashes and the requirement is not
|
# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
|
||||||
# satisfied by a package already installed. Consider using the --allow-unsafe flag.
|
|
||||||
# setuptools
|
# setuptools
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ function calc_pytestinit_nodeimpl_version {
|
||||||
if cat ../pyproject.toml | grep -q "${node_impl}d-version" ; then
|
if cat ../pyproject.toml | grep -q "${node_impl}d-version" ; then
|
||||||
# in this case, we use the expected version from the test also as the tag to be checked out
|
# in this case, we use the expected version from the test also as the tag to be checked out
|
||||||
# i admit that this is REALLY ugly. Happy for any recommendations to do that more easy
|
# i admit that this is REALLY ugly. Happy for any recommendations to do that more easy
|
||||||
PINNED=$(cat ../pyproject.toml | grep "addopts = " | grep -oP "${node_impl}d-version \K\S+" | cut -d'"' -f1)
|
PINNED=$(cat ../pyproject.toml | grep "addopts = " | ${grep} -oP "${node_impl}d-version \K\S+" | cut -d'"' -f1)
|
||||||
|
|
||||||
if [ "$node_impl" = "elements" ]; then
|
if [ "$node_impl" = "elements" ]; then
|
||||||
# in the case of elements, the tags have a "elements-" prefix
|
# in the case of elements, the tags have a "elements-" prefix
|
||||||
|
|
@ -182,7 +182,14 @@ function check_compile_prerequisites {
|
||||||
|
|
||||||
function check_binary_prerequisites {
|
function check_binary_prerequisites {
|
||||||
if [ $(uname) = "Darwin" ]; then
|
if [ $(uname) = "Darwin" ]; then
|
||||||
echo " --> No binary prerequisites checking for MacOS, GOOD LUCK!"
|
if brew list --versions grep &>/dev/null; then
|
||||||
|
grep=ggrep
|
||||||
|
else
|
||||||
|
echo "install grep via brew install grep"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " --> No FURTHER binary prerequisites checking for MacOS, GOOD LUCK!"
|
||||||
|
|
||||||
else
|
else
|
||||||
REQUIRED_PKGS="wget"
|
REQUIRED_PKGS="wget"
|
||||||
for REQUIRED_PKG in $REQUIRED_PKGS; do
|
for REQUIRED_PKG in $REQUIRED_PKGS; do
|
||||||
|
|
@ -194,6 +201,7 @@ function check_binary_prerequisites {
|
||||||
apt-get --yes install $REQUIRED_PKG
|
apt-get --yes install $REQUIRED_PKG
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
grep=grep
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,7 +220,7 @@ function sub_compile {
|
||||||
if [ $(uname) = "Darwin" ]; then
|
if [ $(uname) = "Darwin" ]; then
|
||||||
find tests/${node_impl}/src -maxdepth 1 -type f -perm +111 -exec ls -ld {} \;
|
find tests/${node_impl}/src -maxdepth 1 -type f -perm +111 -exec ls -ld {} \;
|
||||||
else
|
else
|
||||||
find tests/${node_impl}/src -maxdepth 1 -type f -executable -exec ls -ld {} \;
|
find tests/${node_impl}/src -maxdepth 1 -type f -executable -exec ls -ld {} \;
|
||||||
fi
|
fi
|
||||||
END=$(date +%s.%N)
|
END=$(date +%s.%N)
|
||||||
DIFF=$(echo "$END - $START" | bc)
|
DIFF=$(echo "$END - $START" | bc)
|
||||||
|
|
@ -251,6 +259,7 @@ function sub_binary {
|
||||||
rm -rf ./"$node_impl"
|
rm -rf ./"$node_impl"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
rm "$node_impl"
|
||||||
ln -s ./"$node_impl"-${version} "$node_impl"
|
ln -s ./"$node_impl"-${version} "$node_impl"
|
||||||
echo " --> Listing binaries"
|
echo " --> Listing binaries"
|
||||||
if [ $(uname) = "Darwin" ]; then
|
if [ $(uname) = "Darwin" ]; then
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ function create_virtualenv_for_pyinstaller {
|
||||||
echo " But first Delete it ..."
|
echo " But first Delete it ..."
|
||||||
rm -rf .buildenv
|
rm -rf .buildenv
|
||||||
fi
|
fi
|
||||||
virtualenv --python=python3 .buildenv
|
virtualenv --python=python3.10 .buildenv
|
||||||
source .buildenv/bin/activate
|
source .buildenv/bin/activate
|
||||||
pip3 install -e ".[test]"
|
pip3 install -e ".[test]"
|
||||||
}
|
}
|
||||||
|
|
@ -102,6 +102,13 @@ function make_hash_if_necessary {
|
||||||
}
|
}
|
||||||
|
|
||||||
function building_electron_app {
|
function building_electron_app {
|
||||||
|
# https://www.electron.build/
|
||||||
|
# Prerequisites:
|
||||||
|
# * A developer Certificate (in the System keychain)
|
||||||
|
# * private and public key in the login-keychain
|
||||||
|
# * The cert needs to be referenced in pyinstaller/electron/package.json -> build.mac.identity
|
||||||
|
|
||||||
|
|
||||||
platform="-- --${1}" # either linux or win (maxOS is empty)
|
platform="-- --${1}" # either linux or win (maxOS is empty)
|
||||||
cd pyinstaller/electron
|
cd pyinstaller/electron
|
||||||
echo " --> building electron-app"
|
echo " --> building electron-app"
|
||||||
|
|
@ -113,8 +120,18 @@ function building_electron_app {
|
||||||
}
|
}
|
||||||
|
|
||||||
function macos_code_sign {
|
function macos_code_sign {
|
||||||
|
# prerequisites for this:
|
||||||
|
# in short:
|
||||||
|
# * make sure you have a proper app-specific password on https://appleid.apple.com/account/manage
|
||||||
|
# * collect some information via scrun altool --list-providers -u "<yourAppleID>"
|
||||||
|
# * create profile via xcrun notarytool store-credentials --apple-id "<YourAppleID>" --password "app-specific-pw" --team-id "seeFromAbove"
|
||||||
|
# * Call the profile: SpecterProfile
|
||||||
|
# For details see:
|
||||||
|
# * https://www.youtube.com/watch?v=2xJcMzoi0EI
|
||||||
|
# * https://blog.dgunia.de/2022/09/01/switching-from-altool-to-notarytool/
|
||||||
|
# * https://scriptingosx.com/2021/07/notarize-a-command-line-tool-with-notarytool/
|
||||||
# This creates a ZIP archive from the app package (using the ditto command).
|
# This creates a ZIP archive from the app package (using the ditto command).
|
||||||
# This ZIP archive is then used to upload the app to the Apple notarization service.
|
# This ZIP archive is then used to upload the app to the Apple notarization service via xcrun notarytool (formerly xcrun altool)
|
||||||
# After the app has been uploaded to the Apple servers and notarized, the ZIP archive is not used again.
|
# After the app has been uploaded to the Apple servers and notarized, the ZIP archive is not used again.
|
||||||
# The function uses the xcrun stapler command to attach the notarization result to the app, and then exits.
|
# The function uses the xcrun stapler command to attach the notarization result to the app, and then exits.
|
||||||
|
|
||||||
|
|
@ -124,46 +141,27 @@ function macos_code_sign {
|
||||||
cd pyinstaller/electron
|
cd pyinstaller/electron
|
||||||
echo ' --> Attempting to code sign...'
|
echo ' --> Attempting to code sign...'
|
||||||
echo ' executing: ditto -c -k --keepParent "dist/mac/${specterimg_filename}.app" dist/${specterimg_filename}.zip'
|
echo ' executing: ditto -c -k --keepParent "dist/mac/${specterimg_filename}.app" dist/${specterimg_filename}.zip'
|
||||||
ditto -c -k --keepParent "dist/mac/${specterimg_filename}.app" dist/${specterimg_filename}.zip
|
|
||||||
|
ditto -c -k --keepParent "dist/${dist_mac_folder_name}/${specterimg_filename}.app" dist/${specterimg_filename}.zip
|
||||||
# upload
|
# upload
|
||||||
echo ' uploading ... '
|
echo ' uploading ... '
|
||||||
echo ' executing: xcrun altool --notarize-app -t osx -f dist/${specterimg_filename}.zip --primary-bundle-id "solutions.specter.desktop" -u "${mail}" --password "@keychain:AC_PASSWORD" --output-format json'
|
|
||||||
output_json=$(xcrun altool --notarize-app -t osx -f dist/${specterimg_filename}.zip --primary-bundle-id "solutions.specter.desktop" -u "${mail}" --password "@keychain:AC_PASSWORD" --output-format json)
|
output_json=$(xcrun notarytool submit dist/${specterimg_filename}.zip --apple-id "kneunert@gmail.com" --keychain-profile "SpecterProfile" --output-format json --wait )
|
||||||
echo "JSON-Output:"
|
|
||||||
|
echo "Request ID: "
|
||||||
# parsing the requestuuid which we'll need to track progress
|
# parsing the requestuuid which we'll need to track progress
|
||||||
requestuuid=$(echo $output_json | jq -r '."notarization-upload".RequestUUID')
|
requestuuid=$(echo $output_json | jq -r '.id')
|
||||||
mkdir -p signing_logs
|
status=$(echo $output_json | jq -r '.status')
|
||||||
i=1
|
if [ "$status" = "Invalid" ]; then
|
||||||
while [ $i -le 6 ] ; do
|
mkdir -p signing_logs
|
||||||
echo " check result in minute $i ..."
|
echo "issues with notarisation"
|
||||||
sign_result_json=$(xcrun altool --notarization-info $requestuuid -u "${mail}" --password "@keychain:AC_PASSWORD" --output-format json)
|
xcrun notarytool log ${requestuuid} --keychain-profile SpecterProfile | tee ./signing_logs/${app_name}_${timestamp}_${requestuuid}.log
|
||||||
timestamp=$(date +"%Y%m%d-%H%M")
|
|
||||||
# If it's not json-parseable
|
|
||||||
if ! echo "$sign_result_json" | jq .; then
|
|
||||||
echo $sign_result_json > ./signing_logs/${app_name}_${timestamp}_${requestuuid}.log
|
|
||||||
echo "ERROR: track-json not parseable."
|
|
||||||
echo "$sign_result_json"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# if it's no longer in progress
|
|
||||||
status=$(echo "$sign_result_json" | jq -e -r '.["notarization-info"].Status')
|
|
||||||
if [ "$status" != "in progress" ]; then
|
|
||||||
echo " Finished code sign with status $status"
|
|
||||||
echo $sign_result_json | jq . > ./signing_logs/${app_name}_${timestamp}_${requestuuid}.log
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
i=$(( $i + 1 ))
|
|
||||||
sleep 60
|
|
||||||
done
|
|
||||||
if [ "$status" != "success" ]; then
|
|
||||||
echo "ERROR: status $status"
|
|
||||||
echo $(echo $sign_result_json | jq .)
|
|
||||||
echo
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The stapler somehow "staples" the result of the notarisation in to your app
|
# The stapler somehow "staples" the result of the notarisation in to your app
|
||||||
# see e.g. https://stackoverflow.com/questions/58817903/how-to-download-notarized-files-from-apple
|
# see e.g. https://stackoverflow.com/questions/58817903/how-to-download-notarized-files-from-apple
|
||||||
xcrun stapler staple "dist/mac/${specterimg_filename}.app"
|
xcrun stapler staple "dist/${dist_mac_folder_name}/${specterimg_filename}.app"
|
||||||
cd ../..
|
cd ../..
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,37 @@ cd ..
|
||||||
|
|
||||||
# Overriding this function
|
# Overriding this function
|
||||||
function create_virtualenv_for_pyinstaller {
|
function create_virtualenv_for_pyinstaller {
|
||||||
if [ -d .buildenv ]; then
|
# This currently assumes to be run with: Python 3.10.11
|
||||||
echo " --> Deleting .buildenv"
|
|
||||||
rm -rf .buildenv
|
|
||||||
fi
|
|
||||||
# This currently assumes to be run with: Python 3.10.4
|
|
||||||
# Important: pyinstaller needs a Python binary with shared library files
|
# Important: pyinstaller needs a Python binary with shared library files
|
||||||
# With pyenv, for example, you get this like so: env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.10.4
|
# With pyenv, for example, you get this like so: env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.10.4
|
||||||
virtualenv .buildenv
|
# Use pyenv if available
|
||||||
source .buildenv/bin/activate
|
if command -v pyenv >/dev/null 2>&1; then
|
||||||
|
### This is usually in .zshrc, putting it in .bashrc didn't work ###
|
||||||
|
###
|
||||||
|
export PYENV_ROOT="$HOME/.pyenv"
|
||||||
|
command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
|
||||||
|
eval "$(pyenv init -)"
|
||||||
|
### this needs the pyenv-virtualenv plugin. If you don't have it:
|
||||||
|
### git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv
|
||||||
|
eval "$(pyenv virtualenv-init -)"
|
||||||
|
### ------------------------------------------------------------ ###
|
||||||
|
PYTHON_VERSION=3.10.11
|
||||||
|
export PYENV_VERSION=$PYTHON_VERSION
|
||||||
|
echo "pyenv is available. Setting PYENV_VERSION to 3.10.4, using pyenv-virtualenv to create the buildenv..."
|
||||||
|
# echo " --> Deleting .buildenv"
|
||||||
|
# pyenv uninstall -f .buildenv
|
||||||
|
# rm -rf "$HOME/.pyenv/versions/$PYTHON_VERSION/envs/.buildenv"
|
||||||
|
# pyenv virtualenv 3.10.4 .buildenv
|
||||||
|
pyenv activate .buildenv
|
||||||
|
else
|
||||||
|
echo "pyenv is not available. Using system Python version."
|
||||||
|
if [ -d .buildenv ]; then
|
||||||
|
echo " --> Deleting .buildenv"
|
||||||
|
rm -rf .buildenv
|
||||||
|
fi
|
||||||
|
virtualenv .buildenv
|
||||||
|
source .buildenv/bin/activate
|
||||||
|
fi
|
||||||
pip3 install -e ".[test]"
|
pip3 install -e ".[test]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,17 +63,10 @@ END_COMMENT
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
# brew install gmp # to prevent module 'embit.util' has no attribute 'ctypes_secp256k1'
|
# brew install gmp # to prevent module 'embit.util' has no attribute 'ctypes_secp256k1'
|
||||||
|
# brew install jq
|
||||||
# npm install --global create-dmg
|
# npm install --global create-dmg
|
||||||
|
|
||||||
### Trouble shooting
|
### Trouble shooting
|
||||||
# Currently, only MacOS Catalina is supported to build the dmg-file
|
|
||||||
# Therefore we expect xcode 12.1 (according to google)
|
|
||||||
# After installation of xcode: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
|
||||||
# otherwise you get xcrun: error: unable to find utility "altool", not a developer tool or in PATH
|
|
||||||
# catalina might have a a too old version of bash. You need at least 4.0 or so
|
|
||||||
# 3.2 is too low definitely
|
|
||||||
# brew install bash
|
|
||||||
|
|
||||||
# If you have the common issue "errSecInternalComponent" while signing the code:
|
# If you have the common issue "errSecInternalComponent" while signing the code:
|
||||||
# https://medium.com/@ceyhunkeklik/how-to-fix-ios-application-code-signing-error-4818bd331327
|
# https://medium.com/@ceyhunkeklik/how-to-fix-ios-application-code-signing-error-4818bd331327
|
||||||
|
|
||||||
|
|
@ -67,6 +82,15 @@ We have:
|
||||||
* upload will upload all the binary artifacts to the github-release-page. This includes the creation of the hash-files
|
* upload will upload all the binary artifacts to the github-release-page. This includes the creation of the hash-files
|
||||||
and the gnupg signing
|
and the gnupg signing
|
||||||
|
|
||||||
|
### Trouble shooting (Legacy)
|
||||||
|
# Currently, only MacOS Catalina is supported to build the dmg-file
|
||||||
|
# Therefore we expect xcode 12.1 (according to google)
|
||||||
|
# After installation of xcode: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||||
|
# otherwise you get xcrun: error: unable to find utility "altool", not a developer tool or in PATH
|
||||||
|
# catalina might have a a too old version of bash. You need at least 4.0 or so
|
||||||
|
# 3.2 is too low definitely
|
||||||
|
# brew install bash
|
||||||
|
|
||||||
# Example-call:
|
# Example-call:
|
||||||
./utils/build-osx.sh --debug --version v1.10.0-pre23 --appleid "Kim Neunert (FWV59JHV83)" --mail "kim@specter.solutions" make-hash specterd electron sign upload
|
./utils/build-osx.sh --debug --version v1.10.0-pre23 --appleid "Kim Neunert (FWV59JHV83)" --mail "kim@specter.solutions" make-hash specterd electron sign upload
|
||||||
EOF
|
EOF
|
||||||
|
|
@ -158,9 +182,6 @@ fi
|
||||||
if [[ "$build_electron" = "True" ]]; then
|
if [[ "$build_electron" = "True" ]]; then
|
||||||
prepare_npm
|
prepare_npm
|
||||||
make_hash_if_necessary
|
make_hash_if_necessary
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
npm i
|
npm i
|
||||||
if [[ "${appleid}" == '' ]]
|
if [[ "${appleid}" == '' ]]
|
||||||
then
|
then
|
||||||
|
|
@ -174,6 +195,10 @@ if [[ "$build_electron" = "True" ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$build_sign" = "True" ]]; then
|
if [[ "$build_sign" = "True" ]]; then
|
||||||
|
dist_mac_folder_name=mac
|
||||||
|
if [ "$(uname -m)" = "arm64" ]; then
|
||||||
|
dist_mac_folder_name=${dist_mac_folder_name}-arm64
|
||||||
|
fi
|
||||||
if [[ "$appleid" != '' ]]
|
if [[ "$appleid" != '' ]]
|
||||||
then
|
then
|
||||||
macos_code_sign
|
macos_code_sign
|
||||||
|
|
@ -183,7 +208,7 @@ if [[ "$build_sign" = "True" ]]; then
|
||||||
mkdir -p release
|
mkdir -p release
|
||||||
rm -rf release/*
|
rm -rf release/*
|
||||||
|
|
||||||
create-dmg pyinstaller/electron/dist/mac/${specterimg_filename}.app --identity="Developer ID Application: ${appleid}" dist
|
create-dmg pyinstaller/electron/dist/${dist_mac_folder_name}/${specterimg_filename}.app --identity="Developer ID Application: ${appleid}" dist
|
||||||
# create-dmg doesn't create the prepending "v" to the version
|
# create-dmg doesn't create the prepending "v" to the version
|
||||||
node_comp_version=$(python3 -c "print('$version'[1:])")
|
node_comp_version=$(python3 -c "print('$version'[1:])")
|
||||||
mv "dist/${specterimg_filename} ${node_comp_version}.dmg" release/${specterimg_filename}-${version}.dmg
|
mv "dist/${specterimg_filename} ${node_comp_version}.dmg" release/${specterimg_filename}-${version}.dmg
|
||||||
|
|
@ -211,12 +236,12 @@ if [ "$app_name" == "specter" ]; then
|
||||||
echo "sha256sum * > SHA256SUMS-macos"
|
echo "sha256sum * > SHA256SUMS-macos"
|
||||||
echo "python3 ../../utils/github.py upload SHA256SUMS-macos"
|
echo "python3 ../../utils/github.py upload SHA256SUMS-macos"
|
||||||
echo "gpg --detach-sign --armor SHA256SUMS-macos"
|
echo "gpg --detach-sign --armor SHA256SUMS-macos"
|
||||||
echo "python3 ../../utils/github.py upload SHA256SUMS-macos.asc"
|
echo "python3 ../utils/github.py upload SHA256SUMS-macos.asc"
|
||||||
|
|
||||||
|
|
||||||
if [[ "$upload" = "True" ]]; then
|
if [[ "$upload" = "True" ]]; then
|
||||||
echo " --> This build got triggered for version $version"
|
echo " --> This build got triggered for version $version"
|
||||||
. ../../specter_gh_upload.sh
|
. ../../specter_gh_upload.sh # A simple file looks like: export GH_BIN_UPLOAD_PW=...(GH token)
|
||||||
export CI_COMMIT_TAG=$version
|
export CI_COMMIT_TAG=$version
|
||||||
if [[ -z "$CI_PROJECT_ROOT_NAMESPACE" ]]; then
|
if [[ -z "$CI_PROJECT_ROOT_NAMESPACE" ]]; then
|
||||||
export CI_PROJECT_ROOT_NAMESPACE=cryptoadvance
|
export CI_PROJECT_ROOT_NAMESPACE=cryptoadvance
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue