u2a 3.5.3 → 3.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/configure.js +30 -0
- package/src/commands/remove.js +18 -14
- package/src/utils/app.js +244 -0
- package/src/utils/appGenerator.js +16 -241
- package/src/utils/builder.js +21 -21
- package/src/utils/favicon.js +2 -2
- package/src/utils/logger.js +5 -5
- package/src/utils/postinstall.js +42 -5
- package/src/utils/securexec.js +6 -6
- package/src/utils/settings.js +2 -0
- package/src/utils/upgradeLA.js +141 -0
package/package.json
CHANGED
|
@@ -83,6 +83,32 @@ function configureDebug(action) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
function configureAutoUpgrade(action) {
|
|
87
|
+
try {
|
|
88
|
+
if (action === 'status') {
|
|
89
|
+
const status = getSetting('autoupgrade_localapps');
|
|
90
|
+
logger.info(`Debug logs are currently ${status ? chalk.green('enabled') : chalk.yellow('disabled')}`);
|
|
91
|
+
logger.info(`Default setting is: ${DEFAULT_SETTINGS.autoupgrade_localapps ? chalk.green('enabled') : chalk.yellow('disabled')}`);
|
|
92
|
+
return;
|
|
93
|
+
} else if (action === 'enable') {
|
|
94
|
+
setSetting('autoupgrade_localapps', true);
|
|
95
|
+
logger.info(chalk.green('Automatic upgrades have been enabled'));
|
|
96
|
+
} else if (action === 'disable') {
|
|
97
|
+
setSetting('autoupgrade_localapps', false);
|
|
98
|
+
logger.info(chalk.yellow('Automatic upgrades have been disabled'));
|
|
99
|
+
} else if (action === 'reset') {
|
|
100
|
+
resetSetting('autoupgrade_localapps');
|
|
101
|
+
logger.info(`Automatic upgrades have been resetted to: ${DEFAULT_SETTINGS.autoupgrade_localapps ? chalk.green('enabled') : chalk.yellow('disabled')}`);
|
|
102
|
+
} else {
|
|
103
|
+
logger.error(`Invalid action: ${action}`);
|
|
104
|
+
logger.info('Available actions: status, enable, disable, reset');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
logger.error(`Error configuring auto upgrade`, err.message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
86
112
|
async function resetSettings(action) {
|
|
87
113
|
try {
|
|
88
114
|
if (action === 'reset') {
|
|
@@ -118,6 +144,7 @@ async function configure(category, action) {
|
|
|
118
144
|
logger.error('Missing category or action');
|
|
119
145
|
logger.info('Usage: u2a configure [category] [action]');
|
|
120
146
|
logger.info('Available categories:');
|
|
147
|
+
logger.info(' autoupgrade - Configure automatic upgrade on installed localapps on update');
|
|
121
148
|
logger.info(' debug - Configure always showing debug logs');
|
|
122
149
|
logger.info(' reports - Configure anonymous usage reports');
|
|
123
150
|
logger.info(' settings - Resets settings (only reset action)');
|
|
@@ -143,6 +170,9 @@ async function configure(category, action) {
|
|
|
143
170
|
case 'settings':
|
|
144
171
|
await resetSettings(action);
|
|
145
172
|
break;
|
|
173
|
+
case 'autoupgrade':
|
|
174
|
+
await configureAutoUpgrade(action);
|
|
175
|
+
break;
|
|
146
176
|
default:
|
|
147
177
|
logger.error(`Unknown configuration category: ${category}`);
|
|
148
178
|
logger.info('Available categories: debug, reports, vcheck, settings');
|
package/src/commands/remove.js
CHANGED
|
@@ -9,7 +9,7 @@ const { getDomainName } = require('../utils/url');
|
|
|
9
9
|
|
|
10
10
|
const logger = new Logger('remove');
|
|
11
11
|
|
|
12
|
-
async function processRemoval(appName) {
|
|
12
|
+
async function processRemoval(appName, useInquirer) {
|
|
13
13
|
try {
|
|
14
14
|
const db = readDB();
|
|
15
15
|
|
|
@@ -18,18 +18,22 @@ async function processRemoval(appName) {
|
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
if (useInquirer) {
|
|
22
|
+
|
|
23
|
+
const { confirm } = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: 'confirm',
|
|
26
|
+
name: 'confirm',
|
|
27
|
+
message: `Are you sure you want to remove the application for ${appName}?`,
|
|
28
|
+
default: false
|
|
29
|
+
}
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
if (!confirm) {
|
|
33
|
+
logger.info('Operation canceled');
|
|
34
|
+
return;
|
|
27
35
|
}
|
|
28
|
-
]);
|
|
29
36
|
|
|
30
|
-
if (!confirm) {
|
|
31
|
-
logger.info('Operation canceled');
|
|
32
|
-
return;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
const appInfo = db[appName];
|
|
@@ -47,7 +51,7 @@ async function processRemoval(appName) {
|
|
|
47
51
|
|
|
48
52
|
const packageName = `u2a-${appName.replace(/\s+/g, '-')}`;
|
|
49
53
|
let appDataPath;
|
|
50
|
-
|
|
54
|
+
|
|
51
55
|
if (process.platform === 'win32') {
|
|
52
56
|
appDataPath = path.join(process.env.APPDATA, packageName);
|
|
53
57
|
} else if (process.platform === 'darwin') {
|
|
@@ -73,9 +77,9 @@ async function processRemoval(appName) {
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
async function removeApp(appName) {
|
|
80
|
+
async function removeApp(appName, useInquirer = true) {
|
|
77
81
|
const sAppName = sanitizeInput(appName);
|
|
78
|
-
await processRemoval(sAppName);
|
|
82
|
+
await processRemoval(sAppName, useInquirer);
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
module.exports = {
|
package/src/utils/app.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
const { app, BrowserWindow, Menu, shell, nativeTheme } = require('electron');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const APP_NAME = "{APP_NAME}";
|
|
6
|
+
const APP_URL = "{APP_URL}";
|
|
7
|
+
const APP_ICON_PATH = "{APP_ICON_PATH}";
|
|
8
|
+
const WINDOW_WIDTH = {WINDOW_WIDTH};
|
|
9
|
+
const WINDOW_HEIGHT = {WINDOW_HEIGHT};
|
|
10
|
+
|
|
11
|
+
let mainWindow;
|
|
12
|
+
let splashWindow;
|
|
13
|
+
|
|
14
|
+
function logAppInfo() {
|
|
15
|
+
const packageJsonPath = path.join(__dirname, 'package.json');
|
|
16
|
+
let packageInfo = {};
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
20
|
+
packageInfo = JSON.parse(packageJsonContent);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Error reading package.json:', error.message);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log('\n--------------------------------');
|
|
26
|
+
console.log(' APPLICATION INFORMATION');
|
|
27
|
+
console.log('--------------------------------');
|
|
28
|
+
console.log(`Application: ${APP_NAME}`);
|
|
29
|
+
console.log(`URL: ${APP_URL}`);
|
|
30
|
+
console.log(`Started at: ${new Date().toLocaleString()}`);
|
|
31
|
+
console.log(`App directory: ${__dirname}`);
|
|
32
|
+
console.log(`Icon path: ${APP_ICON_PATH}`);
|
|
33
|
+
|
|
34
|
+
console.log('\n PACKAGE INFO:');
|
|
35
|
+
console.log(` - Name: ${packageInfo.name || 'N/A'}`);
|
|
36
|
+
console.log(` - Version: ${packageInfo.version || 'N/A'}`);
|
|
37
|
+
console.log(` - Description: ${packageInfo.description || 'N/A'}`);
|
|
38
|
+
console.log(` - Electron version: ${packageInfo.dependencies?.electron || 'N/A'}`);
|
|
39
|
+
|
|
40
|
+
if (packageInfo.build) {
|
|
41
|
+
console.log('\n BUILD CONFIG:');
|
|
42
|
+
console.log(` - App ID: ${packageInfo.build.appId || 'N/A'}`);
|
|
43
|
+
console.log(` - Product Name: ${packageInfo.build.productName || 'N/A'}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log('--------------------------------\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createSplashScreen() {
|
|
50
|
+
splashWindow = new BrowserWindow({
|
|
51
|
+
width: 200,
|
|
52
|
+
height: 40,
|
|
53
|
+
transparent: true,
|
|
54
|
+
frame: false,
|
|
55
|
+
alwaysOnTop: true,
|
|
56
|
+
skipTaskbar: true,
|
|
57
|
+
resizable: false,
|
|
58
|
+
icon: APP_ICON_PATH,
|
|
59
|
+
webPreferences: {
|
|
60
|
+
nodeIntegration: false,
|
|
61
|
+
contextIsolation: true
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const isDarkMode = nativeTheme.shouldUseDarkColors;
|
|
66
|
+
const bgColor = isDarkMode ? '#333333' : '#f5f5f5';
|
|
67
|
+
const loaderBgColor = isDarkMode ? '#555555' : '#e0e0e0';
|
|
68
|
+
const loaderColor = isDarkMode ? '#ffffff' : '#2563eb';
|
|
69
|
+
const shadowColor = isDarkMode ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.2)';
|
|
70
|
+
|
|
71
|
+
const splashHtml = `
|
|
72
|
+
<!DOCTYPE html>
|
|
73
|
+
<html>
|
|
74
|
+
<head>
|
|
75
|
+
<meta charset="UTF-8">
|
|
76
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none';">
|
|
77
|
+
<title>Loading</title>
|
|
78
|
+
<style>
|
|
79
|
+
html, body {
|
|
80
|
+
margin: 0;
|
|
81
|
+
padding: 0;
|
|
82
|
+
width: 100%;
|
|
83
|
+
height: 100%;
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
background-color: transparent;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.container {
|
|
89
|
+
width: 100%;
|
|
90
|
+
height: 100%;
|
|
91
|
+
display: flex;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
align-items: center;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.loader-container {
|
|
97
|
+
width: 180px;
|
|
98
|
+
height: 12px;
|
|
99
|
+
background-color: ${bgColor};
|
|
100
|
+
border-radius: 6px;
|
|
101
|
+
overflow: hidden;
|
|
102
|
+
box-shadow: 0 2px 8px ${shadowColor};
|
|
103
|
+
padding: 3px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.loader-bg {
|
|
107
|
+
width: 100%;
|
|
108
|
+
height: 100%;
|
|
109
|
+
background-color: ${loaderBgColor};
|
|
110
|
+
border-radius: 4px;
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
position: relative;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.loader {
|
|
116
|
+
position: absolute;
|
|
117
|
+
top: 0;
|
|
118
|
+
left: 0;
|
|
119
|
+
height: 100%;
|
|
120
|
+
width: 30%;
|
|
121
|
+
background-color: ${loaderColor};
|
|
122
|
+
border-radius: 4px;
|
|
123
|
+
animation: loading 1.5s infinite ease-in-out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@keyframes loading {
|
|
127
|
+
0% {
|
|
128
|
+
left: -30%;
|
|
129
|
+
}
|
|
130
|
+
100% {
|
|
131
|
+
left: 100%;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
</style>
|
|
135
|
+
</head>
|
|
136
|
+
<body>
|
|
137
|
+
<div class="container">
|
|
138
|
+
<div class="loader-container">
|
|
139
|
+
<div class="loader-bg">
|
|
140
|
+
<div class="loader"></div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</body>
|
|
145
|
+
</html>
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
const splashPath = path.join(app.getPath('temp'), `${APP_NAME}-splash.html`);
|
|
149
|
+
fs.writeFileSync(splashPath, splashHtml);
|
|
150
|
+
|
|
151
|
+
splashWindow.loadFile(splashPath);
|
|
152
|
+
|
|
153
|
+
const { screen } = require('electron');
|
|
154
|
+
const primaryDisplay = screen.getPrimaryDisplay();
|
|
155
|
+
const { width, height } = primaryDisplay.workAreaSize;
|
|
156
|
+
|
|
157
|
+
splashWindow.setPosition(
|
|
158
|
+
Math.floor(width / 2 - 100),
|
|
159
|
+
Math.floor(height / 2 - 20)
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function createWindow() {
|
|
164
|
+
logAppInfo();
|
|
165
|
+
|
|
166
|
+
app.setAppUserModelId(APP_NAME);
|
|
167
|
+
|
|
168
|
+
mainWindow = new BrowserWindow({
|
|
169
|
+
width: WINDOW_WIDTH,
|
|
170
|
+
height: WINDOW_HEIGHT,
|
|
171
|
+
title: APP_NAME,
|
|
172
|
+
icon: APP_ICON_PATH,
|
|
173
|
+
show: false,
|
|
174
|
+
webPreferences: {
|
|
175
|
+
nodeIntegration: false,
|
|
176
|
+
contextIsolation: true
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
mainWindow.webContents.on('did-finish-load', () => {
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
if (splashWindow && !splashWindow.isDestroyed()) {
|
|
183
|
+
splashWindow.close();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!mainWindow.isVisible()) {
|
|
187
|
+
mainWindow.show();
|
|
188
|
+
mainWindow.focus();
|
|
189
|
+
}
|
|
190
|
+
}, 500);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
mainWindow.loadURL(APP_URL);
|
|
194
|
+
|
|
195
|
+
mainWindow.webContents.on('context-menu', (e, params) => {
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
mainWindow.webContents.executeJavaScript(`
|
|
198
|
+
window.addEventListener('contextmenu', (e) => {
|
|
199
|
+
e.stopPropagation();
|
|
200
|
+
}, true);
|
|
201
|
+
`);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
mainWindow.webContents.on('new-window', (event, url) => {
|
|
205
|
+
event.preventDefault();
|
|
206
|
+
shell.openExternal(url);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const template = [];
|
|
210
|
+
|
|
211
|
+
const menu = Menu.buildFromTemplate(template);
|
|
212
|
+
Menu.setApplicationMenu(menu);
|
|
213
|
+
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
if (splashWindow && !splashWindow.isDestroyed()) {
|
|
216
|
+
splashWindow.close();
|
|
217
|
+
if (!mainWindow.isVisible()) {
|
|
218
|
+
mainWindow.show();
|
|
219
|
+
mainWindow.focus();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}, 10000);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (process.platform === 'win32') {
|
|
226
|
+
app.setAppUserModelId(app.name);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
app.whenReady().then(() => {
|
|
230
|
+
createSplashScreen();
|
|
231
|
+
createWindow();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
app.on('window-all-closed', () => {
|
|
235
|
+
if (process.platform !== 'darwin') {
|
|
236
|
+
app.quit();
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
app.on('activate', () => {
|
|
241
|
+
if (BrowserWindow.getAllWindows().length === 0) {
|
|
242
|
+
createWindow();
|
|
243
|
+
}
|
|
244
|
+
});
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
//Todo: replace electron logo with the one of the app in popup windows
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
const fs = require('fs');
|
|
2
5
|
const path = require('path');
|
|
3
6
|
const Logger = require('./logger');
|
|
@@ -8,254 +11,26 @@ function generateMainJs(appName, url, iconPath, options = {}) {
|
|
|
8
11
|
const width = options.width || 1200;
|
|
9
12
|
const height = options.height || 800;
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const path = require('path');
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
|
|
16
|
-
const APP_NAME = "${appName}";
|
|
17
|
-
const APP_URL = "${url}";
|
|
18
|
-
const APP_ICON_PATH = "${iconPath.replace(/\\/g, '\\\\')}";
|
|
19
|
-
const WINDOW_WIDTH = ${width};
|
|
20
|
-
const WINDOW_HEIGHT = ${height};
|
|
21
|
-
|
|
22
|
-
let mainWindow;
|
|
23
|
-
let splashWindow;
|
|
24
|
-
|
|
25
|
-
function logAppInfo() {
|
|
26
|
-
const packageJsonPath = path.join(__dirname, 'package.json');
|
|
27
|
-
let packageInfo = {};
|
|
14
|
+
const templatePath = path.join(__dirname, 'app.js');
|
|
15
|
+
let template;
|
|
28
16
|
|
|
29
17
|
try {
|
|
30
|
-
|
|
31
|
-
packageInfo = JSON.parse(packageJsonContent);
|
|
18
|
+
template = fs.readFileSync(templatePath, 'utf8');
|
|
32
19
|
} catch (error) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
console.log('\\n--------------------------------');
|
|
37
|
-
console.log(' APPLICATION INFORMATION');
|
|
38
|
-
console.log('--------------------------------');
|
|
39
|
-
console.log(\`Application: \${APP_NAME}\`);
|
|
40
|
-
console.log(\`URL: \${APP_URL}\`);
|
|
41
|
-
console.log(\`Started at: \${new Date().toLocaleString()}\`);
|
|
42
|
-
console.log(\`App directory: \${__dirname}\`);
|
|
43
|
-
console.log(\`Icon path: \${APP_ICON_PATH}\`);
|
|
44
|
-
|
|
45
|
-
console.log('\\n PACKAGE INFO:');
|
|
46
|
-
console.log(\` - Name: \${packageInfo.name || 'N/A'}\`);
|
|
47
|
-
console.log(\` - Version: \${packageInfo.version || 'N/A'}\`);
|
|
48
|
-
console.log(\` - Description: \${packageInfo.description || 'N/A'}\`);
|
|
49
|
-
console.log(\` - Electron version: \${packageInfo.dependencies?.electron || 'N/A'}\`);
|
|
50
|
-
|
|
51
|
-
if (packageInfo.build) {
|
|
52
|
-
console.log('\\n BUILD CONFIG:');
|
|
53
|
-
console.log(\` - App ID: \${packageInfo.build.appId || 'N/A'}\`);
|
|
54
|
-
console.log(\` - Product Name: \${packageInfo.build.productName || 'N/A'}\`);
|
|
20
|
+
logger.error('Error reading electronApp.js template', error);
|
|
21
|
+
throw new Error('Failed to read Electron application template');
|
|
55
22
|
}
|
|
56
23
|
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
height: 40,
|
|
64
|
-
transparent: true,
|
|
65
|
-
frame: false,
|
|
66
|
-
alwaysOnTop: true,
|
|
67
|
-
skipTaskbar: true,
|
|
68
|
-
resizable: false,
|
|
69
|
-
icon: APP_ICON_PATH,
|
|
70
|
-
webPreferences: {
|
|
71
|
-
nodeIntegration: false,
|
|
72
|
-
contextIsolation: true
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const isDarkMode = nativeTheme.shouldUseDarkColors;
|
|
77
|
-
const bgColor = isDarkMode ? '#333333' : '#f5f5f5';
|
|
78
|
-
const loaderBgColor = isDarkMode ? '#555555' : '#e0e0e0';
|
|
79
|
-
const loaderColor = isDarkMode ? '#ffffff' : '#2563eb';
|
|
80
|
-
const shadowColor = isDarkMode ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.2)';
|
|
81
|
-
|
|
82
|
-
const splashHtml = \`
|
|
83
|
-
<!DOCTYPE html>
|
|
84
|
-
<html>
|
|
85
|
-
<head>
|
|
86
|
-
<meta charset="UTF-8">
|
|
87
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none';">
|
|
88
|
-
<title>Loading</title>
|
|
89
|
-
<style>
|
|
90
|
-
html, body {
|
|
91
|
-
margin: 0;
|
|
92
|
-
padding: 0;
|
|
93
|
-
width: 100%;
|
|
94
|
-
height: 100%;
|
|
95
|
-
overflow: hidden;
|
|
96
|
-
background-color: transparent;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.container {
|
|
100
|
-
width: 100%;
|
|
101
|
-
height: 100%;
|
|
102
|
-
display: flex;
|
|
103
|
-
justify-content: center;
|
|
104
|
-
align-items: center;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.loader-container {
|
|
108
|
-
width: 180px;
|
|
109
|
-
height: 12px;
|
|
110
|
-
background-color: \${bgColor};
|
|
111
|
-
border-radius: 6px;
|
|
112
|
-
overflow: hidden;
|
|
113
|
-
box-shadow: 0 2px 8px \${shadowColor};
|
|
114
|
-
padding: 3px;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.loader-bg {
|
|
118
|
-
width: 100%;
|
|
119
|
-
height: 100%;
|
|
120
|
-
background-color: \${loaderBgColor};
|
|
121
|
-
border-radius: 4px;
|
|
122
|
-
overflow: hidden;
|
|
123
|
-
position: relative;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.loader {
|
|
127
|
-
position: absolute;
|
|
128
|
-
top: 0;
|
|
129
|
-
left: 0;
|
|
130
|
-
height: 100%;
|
|
131
|
-
width: 30%;
|
|
132
|
-
background-color: \${loaderColor};
|
|
133
|
-
border-radius: 4px;
|
|
134
|
-
animation: loading 1.5s infinite ease-in-out;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
@keyframes loading {
|
|
138
|
-
0% {
|
|
139
|
-
left: -30%;
|
|
140
|
-
}
|
|
141
|
-
100% {
|
|
142
|
-
left: 100%;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
</style>
|
|
146
|
-
</head>
|
|
147
|
-
<body>
|
|
148
|
-
<div class="container">
|
|
149
|
-
<div class="loader-container">
|
|
150
|
-
<div class="loader-bg">
|
|
151
|
-
<div class="loader"></div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
</div>
|
|
155
|
-
</body>
|
|
156
|
-
</html>
|
|
157
|
-
\`;
|
|
158
|
-
|
|
159
|
-
const splashPath = path.join(app.getPath('temp'), \`\${APP_NAME}-splash.html\`);
|
|
160
|
-
fs.writeFileSync(splashPath, splashHtml);
|
|
24
|
+
const mainJs = template
|
|
25
|
+
.replace('{APP_NAME}', appName)
|
|
26
|
+
.replace('{APP_URL}', url)
|
|
27
|
+
.replace('{APP_ICON_PATH}', iconPath.replace(/\\/g, '\\\\'))
|
|
28
|
+
.replace('{WINDOW_WIDTH}', width)
|
|
29
|
+
.replace('{WINDOW_HEIGHT}', height);
|
|
161
30
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const { screen } = require('electron');
|
|
165
|
-
const primaryDisplay = screen.getPrimaryDisplay();
|
|
166
|
-
const { width, height } = primaryDisplay.workAreaSize;
|
|
167
|
-
|
|
168
|
-
splashWindow.setPosition(
|
|
169
|
-
Math.floor(width / 2 - 100),
|
|
170
|
-
Math.floor(height / 2 - 20)
|
|
171
|
-
);
|
|
31
|
+
return mainJs;
|
|
172
32
|
}
|
|
173
33
|
|
|
174
|
-
function createWindow() {
|
|
175
|
-
logAppInfo();
|
|
176
|
-
|
|
177
|
-
app.setAppUserModelId(APP_NAME);
|
|
178
|
-
|
|
179
|
-
mainWindow = new BrowserWindow({
|
|
180
|
-
width: WINDOW_WIDTH,
|
|
181
|
-
height: WINDOW_HEIGHT,
|
|
182
|
-
title: APP_NAME,
|
|
183
|
-
icon: APP_ICON_PATH,
|
|
184
|
-
show: false,
|
|
185
|
-
webPreferences: {
|
|
186
|
-
nodeIntegration: false,
|
|
187
|
-
contextIsolation: true
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
mainWindow.webContents.on('did-finish-load', () => {
|
|
192
|
-
setTimeout(() => {
|
|
193
|
-
if (splashWindow && !splashWindow.isDestroyed()) {
|
|
194
|
-
splashWindow.close();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (!mainWindow.isVisible()) {
|
|
198
|
-
mainWindow.show();
|
|
199
|
-
mainWindow.focus();
|
|
200
|
-
}
|
|
201
|
-
}, 500);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
mainWindow.loadURL(APP_URL);
|
|
205
|
-
|
|
206
|
-
mainWindow.webContents.on('context-menu', (e, params) => {
|
|
207
|
-
e.preventDefault();
|
|
208
|
-
mainWindow.webContents.executeJavaScript(\`
|
|
209
|
-
window.addEventListener('contextmenu', (e) => {
|
|
210
|
-
e.stopPropagation();
|
|
211
|
-
}, true);
|
|
212
|
-
\`);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
mainWindow.webContents.on('new-window', (event, url) => {
|
|
216
|
-
event.preventDefault();
|
|
217
|
-
shell.openExternal(url);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
const template = [];
|
|
221
|
-
|
|
222
|
-
const menu = Menu.buildFromTemplate(template);
|
|
223
|
-
Menu.setApplicationMenu(menu);
|
|
224
|
-
|
|
225
|
-
setTimeout(() => {
|
|
226
|
-
if (splashWindow && !splashWindow.isDestroyed()) {
|
|
227
|
-
splashWindow.close();
|
|
228
|
-
if (!mainWindow.isVisible()) {
|
|
229
|
-
mainWindow.show();
|
|
230
|
-
mainWindow.focus();
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}, 10000);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (process.platform === 'win32') {
|
|
237
|
-
app.setAppUserModelId(app.name);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
app.whenReady().then(() => {
|
|
241
|
-
createSplashScreen();
|
|
242
|
-
createWindow();
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
app.on('window-all-closed', () => {
|
|
246
|
-
if (process.platform !== 'darwin') {
|
|
247
|
-
app.quit();
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
app.on('activate', () => {
|
|
252
|
-
if (BrowserWindow.getAllWindows().length === 0) {
|
|
253
|
-
createWindow();
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
`;
|
|
257
|
-
} // note for one day: put this in a different file
|
|
258
|
-
|
|
259
34
|
async function createPackageJson(appName, iconPath, isExecutable = false, createSetup = false) {
|
|
260
35
|
const u2aPackagePath = path.resolve(__dirname, '../../package.json');
|
|
261
36
|
|
|
@@ -300,7 +75,7 @@ async function createPackageJson(appName, iconPath, isExecutable = false, create
|
|
|
300
75
|
};
|
|
301
76
|
|
|
302
77
|
packageJson.dependencies = {};
|
|
303
|
-
|
|
78
|
+
|
|
304
79
|
packageJson.scripts.package = "electron-packager . --overwrite --asar";
|
|
305
80
|
packageJson.scripts.setup = "electron-builder";
|
|
306
81
|
}
|
package/src/utils/builder.js
CHANGED
|
@@ -7,21 +7,21 @@ const logger = new Logger('builder');
|
|
|
7
7
|
|
|
8
8
|
async function buildExecutable(appDir, appName, platform, iconPath, options) {
|
|
9
9
|
logger.info(`Building executable for ${platform}...`);
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
try {
|
|
12
12
|
const installOptions = {
|
|
13
13
|
cwd: appDir,
|
|
14
14
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
15
15
|
windowsHide: true
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
secureExec('npm install --save-dev electron-packager electron', installOptions);
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
let platformFlag = '';
|
|
21
21
|
let archFlag = `--arch=${options.arch || 'x64'}`;
|
|
22
22
|
let iconOption = '';
|
|
23
|
-
|
|
24
|
-
switch(platform) {
|
|
23
|
+
|
|
24
|
+
switch (platform) {
|
|
25
25
|
case 'windows':
|
|
26
26
|
platformFlag = '--platform=win32';
|
|
27
27
|
iconOption = iconPath ? `--icon="${iconPath}"` : '';
|
|
@@ -40,23 +40,23 @@ async function buildExecutable(appDir, appName, platform, iconPath, options) {
|
|
|
40
40
|
default:
|
|
41
41
|
platformFlag = `--platform=${process.platform}`;
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
const packageCommand = `npx electron-packager . "${appName}" ${platformFlag} ${archFlag} --out=dist --overwrite --asar ${iconOption}`;
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
logger.debug(`Executing: ${packageCommand}`);
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
secureExec(packageCommand, installOptions);
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
let distPlatform = '';
|
|
51
|
-
switch(platform) {
|
|
51
|
+
switch (platform) {
|
|
52
52
|
case 'windows': distPlatform = 'win32'; break;
|
|
53
53
|
case 'darwin': distPlatform = 'darwin'; break;
|
|
54
54
|
case 'linux': distPlatform = 'linux'; break;
|
|
55
55
|
default: distPlatform = process.platform;
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
const outputPath = path.join(appDir, 'dist', `${appName}-${distPlatform}-x64`);
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
if (fs.existsSync(outputPath)) {
|
|
61
61
|
logger.debug(`Executable built successfully at: ${outputPath}`);
|
|
62
62
|
return outputPath;
|
|
@@ -72,18 +72,18 @@ async function buildExecutable(appDir, appName, platform, iconPath, options) {
|
|
|
72
72
|
|
|
73
73
|
async function buildSetup(appDir, platform, arch) {
|
|
74
74
|
logger.info(`Building setup for ${platform}${arch ? ` (${arch})` : ''}...`);
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
try {
|
|
77
77
|
const installOptions = {
|
|
78
78
|
cwd: appDir,
|
|
79
79
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
80
80
|
windowsHide: true
|
|
81
81
|
};
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
secureExec('npm install --save-dev electron-builder', installOptions);
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
let builderArgs = '';
|
|
86
|
-
switch(platform) {
|
|
86
|
+
switch (platform) {
|
|
87
87
|
case 'windows':
|
|
88
88
|
builderArgs = '--win';
|
|
89
89
|
break;
|
|
@@ -96,15 +96,15 @@ async function buildSetup(appDir, platform, arch) {
|
|
|
96
96
|
default:
|
|
97
97
|
builderArgs = '';
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
if (arch) {
|
|
101
101
|
builderArgs += ` --${arch}`;
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
const builderCommand = `npx electron-builder ${builderArgs}`;
|
|
105
105
|
logger.debug(`Executing: ${builderCommand}`);
|
|
106
106
|
secureExec(builderCommand, installOptions);
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
const installerPath = path.join(appDir, 'installer');
|
|
109
109
|
if (fs.existsSync(installerPath)) {
|
|
110
110
|
logger.debug(`Setup created at: ${installerPath}`);
|
|
@@ -121,6 +121,6 @@ async function buildSetup(appDir, platform, arch) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
module.exports = {
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
buildExecutable,
|
|
125
|
+
buildSetup
|
|
126
126
|
}
|
package/src/utils/favicon.js
CHANGED
|
@@ -4,7 +4,7 @@ const axios = require('axios');
|
|
|
4
4
|
const { APPS_DIR } = require('./config');
|
|
5
5
|
const { normalizeUrl, getDomainName } = require('./url');
|
|
6
6
|
const Logger = require('./logger');
|
|
7
|
-
const { parseICO} = require('icojs');
|
|
7
|
+
const { parseICO } = require('icojs');
|
|
8
8
|
const sharp = require('sharp');
|
|
9
9
|
const pngToIco = require('png-to-ico');
|
|
10
10
|
|
|
@@ -18,7 +18,7 @@ async function getFavicon(url) {
|
|
|
18
18
|
try {
|
|
19
19
|
const domain = getDomainName(url);
|
|
20
20
|
const normalizedUrl = await normalizeUrl(url);
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
const faviconUrl = `${normalizedUrl}/favicon.ico`;
|
|
23
23
|
const iconResponse = await axios.get(faviconUrl, { responseType: 'arraybuffer' });
|
|
24
24
|
|
package/src/utils/logger.js
CHANGED
|
@@ -22,10 +22,10 @@ class Logger {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
_format(status, message) {
|
|
25
|
-
const timestamp = new Date().toLocaleTimeString('en-US', {
|
|
26
|
-
hour: '2-digit',
|
|
27
|
-
minute: '2-digit',
|
|
28
|
-
second: '2-digit'
|
|
25
|
+
const timestamp = new Date().toLocaleTimeString('en-US', {
|
|
26
|
+
hour: '2-digit',
|
|
27
|
+
minute: '2-digit',
|
|
28
|
+
second: '2-digit'
|
|
29
29
|
});
|
|
30
30
|
return `[${timestamp}] - ${status} | ${message}`;
|
|
31
31
|
}
|
|
@@ -56,7 +56,7 @@ class Logger {
|
|
|
56
56
|
const formattedMessage = this._format('ERROR', `${message}${error ? ' ' + error : ''}`);
|
|
57
57
|
console.log(chalk.red(formattedMessage));
|
|
58
58
|
this._writeToFile(formattedMessage);
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
if (error && error.stack) {
|
|
61
61
|
this._writeToFile(`Stack trace: ${error.stack}`);
|
|
62
62
|
}
|
package/src/utils/postinstall.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
// Todo: autoupgrade for installed localapps, will be useful if we change file structure
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
const fs = require('fs');
|
|
2
5
|
const path = require('path');
|
|
3
6
|
const axios = require('axios');
|
|
4
7
|
const Logger = require('./logger');
|
|
5
8
|
const { setupConfig, CONFIG_DIR } = require('./config');
|
|
6
9
|
const { initSettings, getSetting } = require('./settings');
|
|
10
|
+
const { upgrade } = require('./upgradeLA');
|
|
7
11
|
|
|
8
12
|
setupConfig(); // builds ~/.u2a/*
|
|
9
13
|
|
|
@@ -18,7 +22,7 @@ const formatVersionLine = (label, version) => {
|
|
|
18
22
|
const baseLength = `; ${label}: `.length + version.length;
|
|
19
23
|
const spaceCount = Math.max(0, 30 - baseLength);
|
|
20
24
|
const spaces = ' '.repeat(spaceCount);
|
|
21
|
-
|
|
25
|
+
|
|
22
26
|
return `; ${label}: ${version}${spaces};`;
|
|
23
27
|
};
|
|
24
28
|
|
|
@@ -29,7 +33,7 @@ async function run() {
|
|
|
29
33
|
const postinstallData = JSON.parse(fs.readFileSync(postinstallJsonPath, 'utf8'));
|
|
30
34
|
currentVersion = postinstallData.version || '';
|
|
31
35
|
isUpgrade = true;
|
|
32
|
-
|
|
36
|
+
|
|
33
37
|
let newVersion = '';
|
|
34
38
|
try {
|
|
35
39
|
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
|
|
@@ -39,7 +43,7 @@ async function run() {
|
|
|
39
43
|
logger.error(`Error reading package.json for display`, err.message);
|
|
40
44
|
newVersion = 'unknown';
|
|
41
45
|
}
|
|
42
|
-
|
|
46
|
+
|
|
43
47
|
logger.info(';=============================;');
|
|
44
48
|
logger.info('; u2a has been updated! ;');
|
|
45
49
|
logger.info('; Successfully migrated from ;');
|
|
@@ -48,6 +52,7 @@ async function run() {
|
|
|
48
52
|
logger.info(';=============================;');
|
|
49
53
|
|
|
50
54
|
initSettings();
|
|
55
|
+
upgrade();
|
|
51
56
|
} catch (err) {
|
|
52
57
|
logger.error(`Error reading postinstall.json`, err.message);
|
|
53
58
|
isUpgrade = false;
|
|
@@ -75,13 +80,14 @@ async function run() {
|
|
|
75
80
|
logger.info('; \'u2a configure reports ;');
|
|
76
81
|
logger.info('; disable\' ;');
|
|
77
82
|
logger.info(';=============================;');
|
|
78
|
-
|
|
83
|
+
|
|
79
84
|
initSettings(true);
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
|
|
82
88
|
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
|
|
83
89
|
let newVersion = '';
|
|
84
|
-
|
|
90
|
+
|
|
85
91
|
try {
|
|
86
92
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
87
93
|
newVersion = packageJson.version || '';
|
|
@@ -113,6 +119,37 @@ async function run() {
|
|
|
113
119
|
} catch (err) {
|
|
114
120
|
logger.error(`Error updating postinstall.json`, err.message);
|
|
115
121
|
}
|
|
122
|
+
|
|
123
|
+
if (isUpgrade) {
|
|
124
|
+
try {
|
|
125
|
+
logger.info('Checking for local apps that need upgrading...');
|
|
126
|
+
const upgradeStats = await upgrade(currentVersion, newVersion);
|
|
127
|
+
|
|
128
|
+
if (upgradeStats.skipped) {
|
|
129
|
+
if (upgradeStats.reason === 'disabled') {
|
|
130
|
+
logger.warn('Automatic upgrade for local apps is disabled in settings, skipped.');
|
|
131
|
+
} else if (upgradeStats.reason === 'not-core-update') {
|
|
132
|
+
logger.warn('This is not a core update, skipping local apps upgrade');
|
|
133
|
+
} else if (upgradeStats.reason === 'new-install') {
|
|
134
|
+
logger.warn('New installation, no local apps to upgrade');
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
logger.warn(`Local apps upgrade results: ${upgradeStats.upgraded} upgraded, ${upgradeStats.failed} failed`);
|
|
138
|
+
|
|
139
|
+
if (upgradeStats.upgraded > 0) {
|
|
140
|
+
logger.info(';=============================;');
|
|
141
|
+
logger.info('; Local apps have been updated;');
|
|
142
|
+
logger.info(formatVersionLine("Updated", upgradeStats.upgraded.toString())); //not a version but who cares anyway
|
|
143
|
+
if (upgradeStats.failed > 0) {
|
|
144
|
+
logger.info(formatVersionLine("Skipped", upgradeStats.failed.toString()));
|
|
145
|
+
}
|
|
146
|
+
logger.info(';=============================;');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
logger.error('Error while upgrading local apps:', err.message);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
116
153
|
}
|
|
117
154
|
|
|
118
155
|
run().catch(err => {
|
package/src/utils/securexec.js
CHANGED
|
@@ -2,12 +2,12 @@ const { execSync } = require('child_process');
|
|
|
2
2
|
const { sanitizeCommand } = require('./sanitize');
|
|
3
3
|
|
|
4
4
|
function secureExec(command, options = {}) {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const sanitizedCommand = sanitizeCommand(command);
|
|
6
|
+
const result = execSync(sanitizedCommand, options);
|
|
7
|
+
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
7
10
|
|
|
8
|
-
return result;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
11
|
module.exports = {
|
|
12
|
-
|
|
12
|
+
secureExec
|
|
13
13
|
}
|
package/src/utils/settings.js
CHANGED
|
@@ -8,6 +8,7 @@ const DEFAULT_SETTINGS = {
|
|
|
8
8
|
send_anon_reports: true,
|
|
9
9
|
version_check: true,
|
|
10
10
|
always_show_debug: false,
|
|
11
|
+
autoupgrade_localapps: false,
|
|
11
12
|
|
|
12
13
|
};
|
|
13
14
|
|
|
@@ -104,6 +105,7 @@ function resetSetting(key) {
|
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
module.exports = {
|
|
108
|
+
DEFAULT_SETTINGS,
|
|
107
109
|
initSettings,
|
|
108
110
|
getSettings,
|
|
109
111
|
saveSettings,
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
//seems still a bit unstable, thats why this feature is disabled by default
|
|
2
|
+
//enable with 'u2a configure autoupgrade enable'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { readDB } = require('./config');
|
|
9
|
+
const Logger = require('./logger');
|
|
10
|
+
const { createApp } = require('../commands/create');
|
|
11
|
+
const { removeApp } = require('../commands/remove');
|
|
12
|
+
const { getSetting } = require('./settings');
|
|
13
|
+
|
|
14
|
+
const logger = new Logger('upgradeLA');
|
|
15
|
+
|
|
16
|
+
function isCoreUpdate(currentVersion, newVersion) {
|
|
17
|
+
try {
|
|
18
|
+
const [currentSecurity, currentCore, currentFeature] = currentVersion.split('.').map(Number);
|
|
19
|
+
const [newSecurity, newCore, newFeature] = newVersion.split('.').map(Number);
|
|
20
|
+
|
|
21
|
+
return (currentSecurity !== newSecurity || currentCore !== newCore);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
logger.debug(`Error comparing versions: ${currentVersion} vs ${newVersion}`, err.message);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractDimensionsFromMainJs(mainJsPath) {
|
|
29
|
+
try {
|
|
30
|
+
if (!fs.existsSync(mainJsPath)) {
|
|
31
|
+
logger.warn(`main.js file not found at ${mainJsPath}`);
|
|
32
|
+
return { width: 1200, height: 800 };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const mainJsContent = fs.readFileSync(mainJsPath, 'utf8');
|
|
36
|
+
|
|
37
|
+
const widthMatch = mainJsContent.match(/const\s+WINDOW_WIDTH\s*=\s*(\d+)/);
|
|
38
|
+
const width = widthMatch ? parseInt(widthMatch[1], 10) : 1200;
|
|
39
|
+
|
|
40
|
+
const heightMatch = mainJsContent.match(/const\s+WINDOW_HEIGHT\s*=\s*(\d+)/);
|
|
41
|
+
const height = heightMatch ? parseInt(heightMatch[1], 10) : 800;
|
|
42
|
+
|
|
43
|
+
logger.debug(`Extracted dimensions from main.js: ${width}x${height}`);
|
|
44
|
+
return { width, height };
|
|
45
|
+
} catch (err) {
|
|
46
|
+
logger.debug(`Error extracting dimensions from main.js: ${mainJsPath}`, err.message);
|
|
47
|
+
return { width: 1200, height: 800 };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function upgradeLocalApp(appName, appData) {
|
|
52
|
+
logger.debug(`Upgrading local app: ${appName}`);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const { url, path: appPath, icon } = appData;
|
|
56
|
+
|
|
57
|
+
const mainJsPath = path.join(appPath, 'main.js');
|
|
58
|
+
const { width, height } = extractDimensionsFromMainJs(mainJsPath);
|
|
59
|
+
|
|
60
|
+
const options = {
|
|
61
|
+
name: appName,
|
|
62
|
+
width: width,
|
|
63
|
+
height: height,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (icon)
|
|
67
|
+
options.icon = icon;
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
await removeApp(appName, false);
|
|
71
|
+
logger.debug(`Removed existing app: ${appName}`);
|
|
72
|
+
|
|
73
|
+
await createApp(url, options);
|
|
74
|
+
logger.debug(`Successfully upgraded: ${appName}`);
|
|
75
|
+
|
|
76
|
+
return true;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
logger.debug(`Failed to upgrade ${appName}`, err.message);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function upgradeLocalApps(currentVersion, newVersion) {
|
|
84
|
+
logger.debug(`Checking for local apps that need upgrading`);
|
|
85
|
+
|
|
86
|
+
const autoUpgradeEnabled = getSetting('autoupgrade_localapps');
|
|
87
|
+
if (!autoUpgradeEnabled) {
|
|
88
|
+
logger.debug(`Automatic upgrade for local apps is disabled. Skipping upgrades.`);
|
|
89
|
+
return { skipped: true, reason: 'disabled', upgraded: 0, failed: 0, total: 0 };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!isCoreUpdate(currentVersion, newVersion)) {
|
|
93
|
+
logger.debug(`Not a core update (${currentVersion} → ${newVersion}). Skipping local app upgrades.`);
|
|
94
|
+
return { skipped: true, reason: 'not-core-update', upgraded: 0, failed: 0, total: 0 };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const db = readDB();
|
|
99
|
+
const apps = Object.keys(db);
|
|
100
|
+
const stats = { skipped: false, upgraded: 0, failed: 0, total: apps.length };
|
|
101
|
+
|
|
102
|
+
logger.debug(`Found ${apps.length} local apps to check for upgrade`);
|
|
103
|
+
|
|
104
|
+
for (const appName of apps) {
|
|
105
|
+
const appData = db[appName];
|
|
106
|
+
|
|
107
|
+
if (!appData.path || !fs.existsSync(appData.path)) { //skips if no path but shouldnt happen
|
|
108
|
+
logger.debug(`Skipping ${appName}: Not a local app or path doesn't exist`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const success = await upgradeLocalApp(appName, appData);
|
|
113
|
+
if (success) {
|
|
114
|
+
stats.upgraded++;
|
|
115
|
+
} else {
|
|
116
|
+
stats.failed++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
logger.debug(`Local apps upgrade completed: ${stats.upgraded} upgraded, ${stats.failed} failed, ${apps.length - stats.upgraded - stats.failed} skipped`);
|
|
121
|
+
return stats;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
logger.debug(`Error upgrading local apps`, err.message);
|
|
124
|
+
return { skipped: false, upgraded: 0, failed: 0, total: 0, error: err.message };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function upgrade(currentVersion, newVersion) {
|
|
129
|
+
logger.debug(`Starting local apps upgrade check: ${currentVersion} → ${newVersion}`);
|
|
130
|
+
|
|
131
|
+
if (currentVersion === '0.0.0') {
|
|
132
|
+
logger.debug('New installation detected, no apps to upgrade');
|
|
133
|
+
return { skipped: true, reason: 'new-install' };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return await upgradeLocalApps(currentVersion, newVersion);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
upgrade
|
|
141
|
+
};
|