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/package-lock.json
|
||||
pyinstaller/electron/dist
|
||||
pyinstaller/electron/signing_logs
|
||||
pyinstaller/electron/fonts
|
||||
pyinstaller/electron/typography.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.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">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,20 @@
|
|||
<html>
|
||||
<link rel="stylesheet" type="text/css" href="./styles.css">
|
||||
<body style="overflow: auto; height: 100%;">
|
||||
<div class="card" style="width:90%">
|
||||
This window shows you the last 700 lines Logs of the specterApp. This also includes
|
||||
the Logs of specterd which are marked as such. It might give you hints on why specter is not coming up properly.
|
||||
The best approach is to scroll to the bottom and then search upwards for errors.
|
||||
You can find the logfile in [yourHomedirectory]/.specter/specterApp.log.
|
||||
<pre><code id="specterapp-logs"></code></pre>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
<link rel="stylesheet" type="text/css" href="./output.css" />
|
||||
<body style="overflow: auto; height: 100%">
|
||||
<div class="card" style="width: 90%">
|
||||
This window shows you the last 700 lines Logs of the specterApp. This also includes the Logs of specterd which are
|
||||
marked as such. It might give you hints on why specter is not coming up properly. The best approach is to scroll to the
|
||||
bottom and then search upwards for errors. You can find the logfile in [yourHomedirectory]/.specter/specterApp.log.
|
||||
<pre><code id="specterapp-logs"></code></pre>
|
||||
<br />
|
||||
</div>
|
||||
<script>
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const helpers = require('./helpers')
|
||||
helpers.getSpecterAppLogs( (lines) => {
|
||||
document.getElementById('specterapp-logs').innerText = lines
|
||||
})
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const helpers = require('./src/helpers')
|
||||
helpers.getSpecterAppLogs((lines) => {
|
||||
document.getElementById('specterapp-logs').innerText = lines
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</body>
|
||||
</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
|
||||
const { app, nativeTheme, nativeImage, BrowserWindow, Menu, Tray, screen, shell, dialog, ipcMain } = require('electron')
|
||||
|
||||
const path = require('path')
|
||||
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 {
|
||||
getFileHash,
|
||||
getAppSettings,
|
||||
appSettingsPath,
|
||||
specterdDirPath,
|
||||
specterAppLogPath,
|
||||
versionData,
|
||||
isDev,
|
||||
devFolder,
|
||||
isMac,
|
||||
} = require('./helpers')
|
||||
const { app, nativeTheme, nativeImage, BrowserWindow, Menu, Tray, screen, shell, dialog, ipcMain } = require('electron')
|
||||
const defaultMenu = require('electron-default-menu')
|
||||
const contextMenu = require('electron-context-menu')
|
||||
|
||||
const { appSettingsPath, specterdDirPath, appSettings, platformName, appNameLower } = require('./src/config.js')
|
||||
const { logger } = require('./src/logging.js')
|
||||
const downloadloc = require('./downloadloc')
|
||||
const { downloadSpecterd } = require('./src/download.js')
|
||||
const getDownloadLocation = downloadloc.getDownloadLocation
|
||||
const appName = downloadloc.appName()
|
||||
const appNameLower = appName.toLowerCase()
|
||||
const { startSpecterd, quitSpecterd } = require('./src/specterd.js')
|
||||
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
|
||||
if (isDev && versionData === undefined) {
|
||||
|
|
@ -39,23 +27,6 @@ ipcMain.handle('showMessageBoxSync', (e, 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) {
|
||||
logger.info('Running the Electron app in dev mode.')
|
||||
}
|
||||
|
|
@ -75,11 +46,10 @@ if (isMac && isDev) {
|
|||
app.dock.setIcon(dockIcon)
|
||||
}
|
||||
|
||||
let appSettings = getAppSettings()
|
||||
let dimensions = { width: 1500, height: 1000 }
|
||||
|
||||
// Modify the context menu
|
||||
const contextMenu = require('electron-context-menu')
|
||||
|
||||
contextMenu({
|
||||
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 automaticWalletImport = false
|
||||
let mainWindow
|
||||
let prefWindow
|
||||
let tray
|
||||
let trayMenu
|
||||
|
||||
// Flag the app was quitted
|
||||
let quitted = false
|
||||
|
||||
let webPreferences = {
|
||||
worldSafeExecuteJavaScript: true,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
}
|
||||
|
||||
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 platformName ' + platformName)
|
||||
|
||||
|
|
@ -261,85 +111,35 @@ app.on('login', function (event, webContents, request, authInfo, callback) {
|
|||
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
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
// Create the tray icon
|
||||
logger.info('Framework ready! Starting tray icon ...')
|
||||
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))
|
||||
initTray(openPreferences, quitSpecterd)
|
||||
|
||||
dimensions = screen.getPrimaryDisplay().size
|
||||
|
||||
// create a new `splash`-Window
|
||||
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()
|
||||
|
||||
mainWindow.loadURL(`file://${__dirname}/splash.html`)
|
||||
loadUrl(`file://${__dirname}/splash.html`)
|
||||
|
||||
if (!fs.existsSync(specterdDirPath)) {
|
||||
logger.info('Creating specterd-binaries folder')
|
||||
logger.info('Creating specterd-binaries folder:' + specterdDirPath)
|
||||
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 () {
|
||||
if (platformName == 'win64') {
|
||||
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() {
|
||||
const menu = defaultMenu(app, shell)
|
||||
|
||||
|
|
@ -808,21 +305,17 @@ function openErrorLog() {
|
|||
createNewWindow('error_logs.html', width, height).show()
|
||||
}
|
||||
|
||||
function showError(error) {
|
||||
updatingLoaderMsg('Specter encountered an error:<br>' + error.toString())
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (error) => {
|
||||
showError(error)
|
||||
logger.error(error.toString(), error.name)
|
||||
logger.error(error.stack)
|
||||
})
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
showError(error)
|
||||
// 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
|
||||
// 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
|
||||
//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",
|
||||
"version": "v0.0.0",
|
||||
"version": "v2.0.3-pre1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "Specter",
|
||||
"version": "v0.0.0",
|
||||
"version": "v2.0.3-pre1",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"electron-context-menu": "^2.3.0",
|
||||
"electron-context-menu": "^2.5.2",
|
||||
"electron-default-menu": "^1.0.2",
|
||||
"electron-progressbar": "^2.0.1",
|
||||
"electron-progressbar": "^2.1.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"read-last-lines": "^1.8.0",
|
||||
"request": "^2.88.2",
|
||||
"winston": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^22.1.0",
|
||||
"electron-builder": "^23.3.1"
|
||||
"electron": "^22.3.12",
|
||||
"electron-builder": "^23.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dabh/diagnostics": {
|
||||
|
|
@ -1421,9 +1421,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "22.1.0",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-22.1.0.tgz",
|
||||
"integrity": "sha512-wz5s4N6V7zyKm4YQmXJImFoxO1Doai32ShYm0FzOLPBMwLMdQBV+REY+j1opRx0KJ9xJEIdjYgcA8OSw6vx3pA==",
|
||||
"version": "22.3.27",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-22.3.27.tgz",
|
||||
"integrity": "sha512-7Rht21vHqj4ZFRnKuZdFqZFsvMBCmDqmjetiMqPtF+TmTBiGne1mnstVXOA/SRGhN2Qy5gY5bznJKpiqogjM8A==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1502,13 +1502,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-context-menu": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.3.0.tgz",
|
||||
"integrity": "sha512-XYsYkNY+jvX4C5o09qMuZoKL6e9frnQzBFehZSIiKp6zK0u3XYowJYDyK3vDKKZxYsOIGiE/Gbx40jERC03Ctw==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.5.2.tgz",
|
||||
"integrity": "sha512-1cEQR6fA9ktFsRBc+eXPwvrOgAPytUD7rUV4iBAA5zTrLAPKokJ23xeMjcK2fjrDPrlFRBxcLz0KP+GUhMrSCQ==",
|
||||
"dependencies": {
|
||||
"cli-truncate": "^2.0.0",
|
||||
"electron-dl": "^3.0.0",
|
||||
"electron-is-dev": "^1.0.1"
|
||||
"cli-truncate": "^2.1.0",
|
||||
"electron-dl": "^3.1.0",
|
||||
"electron-is-dev": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-default-menu": {
|
||||
|
|
@ -1517,13 +1520,19 @@
|
|||
"integrity": "sha512-YAL/UNR3kPG58wOOlmDpTG3i6+bzwhHx6NllIOaLuVrU7uYifeYGGdk5IH2Hap4wVEx2YTA8cqQ2PGSplYwDWQ=="
|
||||
},
|
||||
"node_modules/electron-dl": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.0.2.tgz",
|
||||
"integrity": "sha512-pRgE9Jbhoo5z6Vk3qi+vIrfpMDlCp2oB1UeR96SMnsfz073jj0AZGQwp69EdIcEvlUlwBSGyJK8Jt6OB6JLn+g==",
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.5.2.tgz",
|
||||
"integrity": "sha512-i104cl+u8yJ0lhpRAtUWfeGuWuL1PL6TBiw2gLf0MMIBjfgE485Ags2mcySx4uWU9P9uj/vsD3jd7X+w1lzZxw==",
|
||||
"dependencies": {
|
||||
"ext-name": "^5.0.0",
|
||||
"pupa": "^2.0.1",
|
||||
"unused-filename": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-is-dev": {
|
||||
|
|
@ -1581,9 +1590,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/electron-progressbar": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-2.0.1.tgz",
|
||||
"integrity": "sha512-+N60GX2q+KH5OvZXxwtjMTZB/1AyxriFd95vOnR3sOfNpvz+30LMsM0a9SnEivZE6N8Djy7F3z4TY8pLs8aopw==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-2.2.1.tgz",
|
||||
"integrity": "sha512-LQ9bxM3Tf5PG/1QngY8ywvht7IKvQ8tEIra8uh3RkLASqN/GYvr4r0uU9qz38r0Zn72UcouYzumKDfLwoI/rsw==",
|
||||
"dependencies": {
|
||||
"extend": "^3.0.1"
|
||||
}
|
||||
|
|
@ -2216,7 +2225,7 @@
|
|||
"node_modules/is-plain-obj": {
|
||||
"version": "1.1.0",
|
||||
"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": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -2514,7 +2523,7 @@
|
|||
"node_modules/modify-filename": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz",
|
||||
"integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=",
|
||||
"integrity": "sha512-EickqnKq3kVVaZisYuCxhtKbZjInCuwgwZWyAmRIp1NTMhri7r3380/uqwrUHfaDiPzLVTuoNy4whX66bxPVog==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -2669,9 +2678,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/pupa": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz",
|
||||
"integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz",
|
||||
"integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==",
|
||||
"dependencies": {
|
||||
"escape-goat": "^2.0.0"
|
||||
},
|
||||
|
|
@ -2968,7 +2977,7 @@
|
|||
"node_modules/sort-keys": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
||||
"integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
|
||||
"integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==",
|
||||
"dependencies": {
|
||||
"is-plain-obj": "^1.0.0"
|
||||
},
|
||||
|
|
@ -2979,7 +2988,7 @@
|
|||
"node_modules/sort-keys-length": {
|
||||
"version": "1.0.1",
|
||||
"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": {
|
||||
"sort-keys": "^1.0.0"
|
||||
},
|
||||
|
|
@ -4592,9 +4601,9 @@
|
|||
}
|
||||
},
|
||||
"electron": {
|
||||
"version": "22.1.0",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-22.1.0.tgz",
|
||||
"integrity": "sha512-wz5s4N6V7zyKm4YQmXJImFoxO1Doai32ShYm0FzOLPBMwLMdQBV+REY+j1opRx0KJ9xJEIdjYgcA8OSw6vx3pA==",
|
||||
"version": "22.3.27",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-22.3.27.tgz",
|
||||
"integrity": "sha512-7Rht21vHqj4ZFRnKuZdFqZFsvMBCmDqmjetiMqPtF+TmTBiGne1mnstVXOA/SRGhN2Qy5gY5bznJKpiqogjM8A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@electron/get": "^2.0.0",
|
||||
|
|
@ -4652,13 +4661,13 @@
|
|||
}
|
||||
},
|
||||
"electron-context-menu": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.3.0.tgz",
|
||||
"integrity": "sha512-XYsYkNY+jvX4C5o09qMuZoKL6e9frnQzBFehZSIiKp6zK0u3XYowJYDyK3vDKKZxYsOIGiE/Gbx40jERC03Ctw==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.5.2.tgz",
|
||||
"integrity": "sha512-1cEQR6fA9ktFsRBc+eXPwvrOgAPytUD7rUV4iBAA5zTrLAPKokJ23xeMjcK2fjrDPrlFRBxcLz0KP+GUhMrSCQ==",
|
||||
"requires": {
|
||||
"cli-truncate": "^2.0.0",
|
||||
"electron-dl": "^3.0.0",
|
||||
"electron-is-dev": "^1.0.1"
|
||||
"cli-truncate": "^2.1.0",
|
||||
"electron-dl": "^3.1.0",
|
||||
"electron-is-dev": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"electron-default-menu": {
|
||||
|
|
@ -4667,9 +4676,9 @@
|
|||
"integrity": "sha512-YAL/UNR3kPG58wOOlmDpTG3i6+bzwhHx6NllIOaLuVrU7uYifeYGGdk5IH2Hap4wVEx2YTA8cqQ2PGSplYwDWQ=="
|
||||
},
|
||||
"electron-dl": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.0.2.tgz",
|
||||
"integrity": "sha512-pRgE9Jbhoo5z6Vk3qi+vIrfpMDlCp2oB1UeR96SMnsfz073jj0AZGQwp69EdIcEvlUlwBSGyJK8Jt6OB6JLn+g==",
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.5.2.tgz",
|
||||
"integrity": "sha512-i104cl+u8yJ0lhpRAtUWfeGuWuL1PL6TBiw2gLf0MMIBjfgE485Ags2mcySx4uWU9P9uj/vsD3jd7X+w1lzZxw==",
|
||||
"requires": {
|
||||
"ext-name": "^5.0.0",
|
||||
"pupa": "^2.0.1",
|
||||
|
|
@ -4722,9 +4731,9 @@
|
|||
}
|
||||
},
|
||||
"electron-progressbar": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-2.0.1.tgz",
|
||||
"integrity": "sha512-+N60GX2q+KH5OvZXxwtjMTZB/1AyxriFd95vOnR3sOfNpvz+30LMsM0a9SnEivZE6N8Djy7F3z4TY8pLs8aopw==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-2.2.1.tgz",
|
||||
"integrity": "sha512-LQ9bxM3Tf5PG/1QngY8ywvht7IKvQ8tEIra8uh3RkLASqN/GYvr4r0uU9qz38r0Zn72UcouYzumKDfLwoI/rsw==",
|
||||
"requires": {
|
||||
"extend": "^3.0.1"
|
||||
}
|
||||
|
|
@ -5220,7 +5229,7 @@
|
|||
"is-plain-obj": {
|
||||
"version": "1.1.0",
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
|
|
@ -5445,7 +5454,7 @@
|
|||
"modify-filename": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz",
|
||||
"integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE="
|
||||
"integrity": "sha512-EickqnKq3kVVaZisYuCxhtKbZjInCuwgwZWyAmRIp1NTMhri7r3380/uqwrUHfaDiPzLVTuoNy4whX66bxPVog=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
|
|
@ -5570,9 +5579,9 @@
|
|||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||
},
|
||||
"pupa": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz",
|
||||
"integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz",
|
||||
"integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==",
|
||||
"requires": {
|
||||
"escape-goat": "^2.0.0"
|
||||
}
|
||||
|
|
@ -5809,7 +5818,7 @@
|
|||
"sort-keys": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
||||
"integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
|
||||
"integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==",
|
||||
"requires": {
|
||||
"is-plain-obj": "^1.0.0"
|
||||
}
|
||||
|
|
@ -5817,7 +5826,7 @@
|
|||
"sort-keys-length": {
|
||||
"version": "1.0.1",
|
||||
"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": {
|
||||
"sort-keys": "^1.0.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@
|
|||
"author": "Specter",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^22.1.0",
|
||||
"electron-builder": "^23.3.1"
|
||||
"electron": "^22.3.12",
|
||||
"electron-builder": "^23.6.0"
|
||||
},
|
||||
"build": {
|
||||
"productName": "Specter",
|
||||
|
|
@ -30,9 +30,9 @@
|
|||
"category": "public.app-category.utilities",
|
||||
"identity": "Kim Neunert (FWV59JHV83)",
|
||||
"entitlements": "./build/entitlements.mac.plist",
|
||||
"entitlementsInherit": "./build/entitlements.mac.plist",
|
||||
"hardenedRuntime": true,
|
||||
"icon": "./icons/icon.icns"
|
||||
"icon": "./icons/icon.icns",
|
||||
"provisioningProfile": "/Users/kim/Specter_Desktop_vanilla.provisionprofile"
|
||||
},
|
||||
"win": {
|
||||
"icon": "./icons/icon.ico"
|
||||
|
|
@ -45,12 +45,12 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-context-menu": "^2.3.0",
|
||||
"electron-context-menu": "^2.5.2",
|
||||
"electron-default-menu": "^1.0.2",
|
||||
"electron-progressbar": "^2.0.1",
|
||||
"electron-progressbar": "^2.1.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"read-last-lines": "^1.8.0",
|
||||
"request": "^2.88.2",
|
||||
"winston": "^3.3.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
// All of the Node.js APIs are available in the preload process.
|
||||
// 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', () => {
|
||||
// No idea why this is here
|
||||
const replaceText = (selector, text) => {
|
||||
const element = document.getElementById(selector)
|
||||
if (element) element.innerText = text
|
||||
}
|
||||
|
||||
for (const type of ['chrome', 'node', 'electron']) {
|
||||
replaceText(`${type}-version`, process.versions[type])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,3 +4,25 @@
|
|||
// `nodeIntegration` is turned off. Use `preload.js` to
|
||||
// selectively enable features needed in the rendering
|
||||
// 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>
|
||||
<p id="launch-text" class="text-lg">Launching Specter Desktop...</p>
|
||||
</div>
|
||||
<script src="renderer.js"></script>
|
||||
</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 = [
|
||||
"black==22.3.0",
|
||||
"pre-commit==2.13.0",
|
||||
"pip-tools==6.12.2",
|
||||
"pip-tools==6.13",
|
||||
"pytest==7.1.2",
|
||||
"PySocks==1.7.1",
|
||||
"pytest-cov==2.10.1",
|
||||
|
|
|
|||
|
|
@ -816,9 +816,9 @@ urllib3==1.26.14 \
|
|||
--hash=sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72 \
|
||||
--hash=sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1
|
||||
# via requests
|
||||
werkzeug==2.2.3 \
|
||||
--hash=sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe \
|
||||
--hash=sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612
|
||||
werkzeug==2.2.2 \
|
||||
--hash=sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f \
|
||||
--hash=sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5
|
||||
# via
|
||||
# flask
|
||||
# flask-login
|
||||
|
|
@ -832,6 +832,5 @@ wtforms==3.0.1 \
|
|||
# via flask-wtf
|
||||
|
||||
# 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
|
||||
# satisfied by a package already installed. Consider using the --allow-unsafe flag.
|
||||
# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
|
||||
# setuptools
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ function calc_pytestinit_nodeimpl_version {
|
|||
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
|
||||
# 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
|
||||
# in the case of elements, the tags have a "elements-" prefix
|
||||
|
|
@ -182,7 +182,14 @@ function check_compile_prerequisites {
|
|||
|
||||
function check_binary_prerequisites {
|
||||
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
|
||||
REQUIRED_PKGS="wget"
|
||||
for REQUIRED_PKG in $REQUIRED_PKGS; do
|
||||
|
|
@ -194,6 +201,7 @@ function check_binary_prerequisites {
|
|||
apt-get --yes install $REQUIRED_PKG
|
||||
fi
|
||||
done
|
||||
grep=grep
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +220,7 @@ function sub_compile {
|
|||
if [ $(uname) = "Darwin" ]; then
|
||||
find tests/${node_impl}/src -maxdepth 1 -type f -perm +111 -exec ls -ld {} \;
|
||||
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
|
||||
END=$(date +%s.%N)
|
||||
DIFF=$(echo "$END - $START" | bc)
|
||||
|
|
@ -251,6 +259,7 @@ function sub_binary {
|
|||
rm -rf ./"$node_impl"
|
||||
fi
|
||||
fi
|
||||
rm "$node_impl"
|
||||
ln -s ./"$node_impl"-${version} "$node_impl"
|
||||
echo " --> Listing binaries"
|
||||
if [ $(uname) = "Darwin" ]; then
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ function create_virtualenv_for_pyinstaller {
|
|||
echo " But first Delete it ..."
|
||||
rm -rf .buildenv
|
||||
fi
|
||||
virtualenv --python=python3 .buildenv
|
||||
virtualenv --python=python3.10 .buildenv
|
||||
source .buildenv/bin/activate
|
||||
pip3 install -e ".[test]"
|
||||
}
|
||||
|
|
@ -102,6 +102,13 @@ function make_hash_if_necessary {
|
|||
}
|
||||
|
||||
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)
|
||||
cd pyinstaller/electron
|
||||
echo " --> building electron-app"
|
||||
|
|
@ -113,8 +120,18 @@ function building_electron_app {
|
|||
}
|
||||
|
||||
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 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.
|
||||
# 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
|
||||
echo ' --> Attempting to code sign...'
|
||||
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
|
||||
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)
|
||||
echo "JSON-Output:"
|
||||
|
||||
output_json=$(xcrun notarytool submit dist/${specterimg_filename}.zip --apple-id "kneunert@gmail.com" --keychain-profile "SpecterProfile" --output-format json --wait )
|
||||
|
||||
echo "Request ID: "
|
||||
# parsing the requestuuid which we'll need to track progress
|
||||
requestuuid=$(echo $output_json | jq -r '."notarization-upload".RequestUUID')
|
||||
mkdir -p signing_logs
|
||||
i=1
|
||||
while [ $i -le 6 ] ; do
|
||||
echo " check result in minute $i ..."
|
||||
sign_result_json=$(xcrun altool --notarization-info $requestuuid -u "${mail}" --password "@keychain:AC_PASSWORD" --output-format json)
|
||||
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
|
||||
requestuuid=$(echo $output_json | jq -r '.id')
|
||||
status=$(echo $output_json | jq -r '.status')
|
||||
if [ "$status" = "Invalid" ]; then
|
||||
mkdir -p signing_logs
|
||||
echo "issues with notarisation"
|
||||
xcrun notarytool log ${requestuuid} --keychain-profile SpecterProfile | tee ./signing_logs/${app_name}_${timestamp}_${requestuuid}.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 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
|
||||
xcrun stapler staple "dist/mac/${specterimg_filename}.app"
|
||||
xcrun stapler staple "dist/${dist_mac_folder_name}/${specterimg_filename}.app"
|
||||
cd ../..
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,15 +9,37 @@ cd ..
|
|||
|
||||
# Overriding this function
|
||||
function create_virtualenv_for_pyinstaller {
|
||||
if [ -d .buildenv ]; then
|
||||
echo " --> Deleting .buildenv"
|
||||
rm -rf .buildenv
|
||||
fi
|
||||
# This currently assumes to be run with: Python 3.10.4
|
||||
# This currently assumes to be run with: Python 3.10.11
|
||||
# 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
|
||||
virtualenv .buildenv
|
||||
source .buildenv/bin/activate
|
||||
# Use pyenv if available
|
||||
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]"
|
||||
}
|
||||
|
||||
|
|
@ -41,17 +63,10 @@ END_COMMENT
|
|||
|
||||
### Prerequisites
|
||||
# brew install gmp # to prevent module 'embit.util' has no attribute 'ctypes_secp256k1'
|
||||
# brew install jq
|
||||
# npm install --global create-dmg
|
||||
|
||||
### 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:
|
||||
# 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
|
||||
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:
|
||||
./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
|
||||
|
|
@ -158,9 +182,6 @@ fi
|
|||
if [[ "$build_electron" = "True" ]]; then
|
||||
prepare_npm
|
||||
make_hash_if_necessary
|
||||
|
||||
|
||||
|
||||
npm i
|
||||
if [[ "${appleid}" == '' ]]
|
||||
then
|
||||
|
|
@ -174,6 +195,10 @@ if [[ "$build_electron" = "True" ]]; then
|
|||
fi
|
||||
|
||||
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" != '' ]]
|
||||
then
|
||||
macos_code_sign
|
||||
|
|
@ -183,7 +208,7 @@ if [[ "$build_sign" = "True" ]]; then
|
|||
mkdir -p 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
|
||||
node_comp_version=$(python3 -c "print('$version'[1:])")
|
||||
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 "python3 ../../utils/github.py upload 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
|
||||
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
|
||||
if [[ -z "$CI_PROJECT_ROOT_NAMESPACE" ]]; then
|
||||
export CI_PROJECT_ROOT_NAMESPACE=cryptoadvance
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue