Skip to content

Commit a6e9d30

Browse files
authored
Update main.js
1 parent 8d36d68 commit a6e9d30

1 file changed

Lines changed: 108 additions & 102 deletions

File tree

main.js

Lines changed: 108 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
1+
12
// main.js (ESM)
2-
import { app, BrowserWindow, BrowserView } from 'electron';
3+
import { app, BrowserWindow, BrowserView, dialog } from 'electron';
34
import fs from 'node:fs';
45
import path from 'node:path';
56

6-
// Improve compatibility with some enterprise GPUs/VMs
7+
// Disable GPU to avoid blank windows on some enterprise GPUs/VMs
78
app.disableHardwareAcceleration();
89

9-
let mainWindow;
10-
let topView;
11-
let bottomView;
12-
let config;
13-
let cfgPathInUse = null;
10+
let mainWindow; let topView; let bottomView; let config; let cfgPathInUse = null;
1411
let defaultUA = { top: null, bottom: null };
12+
let logFile = null;
13+
14+
function getPortableDir() {
15+
// electron-builder portable sets this to the real folder containing the EXE
16+
return process.env.PORTABLE_EXECUTABLE_DIR || null;
17+
}
18+
19+
function getExeDir() {
20+
try { return path.dirname(app.getPath('exe')); } catch { return process.cwd(); }
21+
}
22+
23+
function getLogPath() {
24+
const base = getPortableDir() || getExeDir() || process.cwd();
25+
return path.join(base, 'two-sites-viewer.log');
26+
}
1527

16-
// ---------- Config loading ----------
28+
function log(...args) {
29+
try {
30+
const line = `[${new Date().toISOString()}] ` + args.map(a => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ') + '
31+
';
32+
if (!logFile) logFile = getLogPath();
33+
fs.appendFileSync(logFile, line);
34+
} catch {}
35+
// Also try console for dev runs
36+
try { console.log(...args); } catch {}
37+
}
1738

1839
function normalizeConfig(cfg) {
1940
return {
@@ -28,74 +49,75 @@ function normalizeConfig(cfg) {
2849
}
2950

3051
function candidatesForConfig() {
31-
const portableDir = process.env.PORTABLE_EXECUTABLE_DIR; // electron-builder portable
32-
const exeDir = path.dirname(app.getPath('exe'));
52+
const portableDir = getPortableDir();
53+
const exeDir = getExeDir();
3354
const cwd = process.cwd();
34-
const userData = app.getPath('userData'); // optional override location
35-
const resources = process.resourcesPath; // baked default (if you ship one inside asar)
36-
55+
const userData = app.getPath('userData');
56+
const resources = process.resourcesPath;
3757
const list = [];
3858
if (portableDir) list.push(path.join(portableDir, 'config.json'));
3959
list.push(
4060
path.join(exeDir, 'config.json'),
4161
path.join(cwd, 'config.json'),
4262
path.join(userData, 'config.json'),
43-
path.join(resources, 'config.json') // final fallback if bundled
63+
path.join(resources, 'config.json')
4464
);
4565
return list;
4666
}
4767

68+
function ensureConfigExistsAt(targetPath) {
69+
try {
70+
if (!fs.existsSync(targetPath)) {
71+
fs.writeFileSync(targetPath, JSON.stringify({
72+
topUrl: 'https://example.com',
73+
bottomUrl: 'https://example.com',
74+
dividerRatio: 0.55,
75+
minWidth: 1200,
76+
minHeight: 800,
77+
userAgent: '',
78+
lockToInitialOrigin: false
79+
}, null, 2));
80+
log('Created default config at', targetPath);
81+
}
82+
} catch (e) { log('Failed to create default config at', targetPath, e.message); }
83+
}
84+
4885
function findConfigPath() {
49-
for (const p of candidatesForConfig()) {
86+
const candidates = candidatesForConfig();
87+
log('Config path candidates:');
88+
for (const p of candidates) { log(' -', p, fs.existsSync(p) ? '[exists]' : '[missing]'); }
89+
for (const p of candidates) {
5090
try { if (fs.existsSync(p)) return p; } catch {}
5191
}
52-
// If nothing exists, prefer PORTABLE_EXECUTABLE_DIR (if present) for future writes
53-
if (process.env.PORTABLE_EXECUTABLE_DIR) {
54-
return path.join(process.env.PORTABLE_EXECUTABLE_DIR, 'config.json');
55-
}
56-
return path.join(path.dirname(app.getPath('exe')), 'config.json');
92+
const preferred = (getPortableDir() || getExeDir() || process.cwd());
93+
const fallback = path.join(preferred, 'config.json');
94+
ensureConfigExistsAt(fallback);
95+
return fallback;
5796
}
5897

5998
function loadConfig() {
6099
const p = cfgPathInUse || findConfigPath();
61100
cfgPathInUse = p;
62101
try {
63-
if (fs.existsSync(p)) {
64-
const raw = fs.readFileSync(p, 'utf-8');
65-
const parsed = JSON.parse(raw);
66-
return normalizeConfig(parsed);
67-
}
68-
} catch {}
69-
// Fallback defaults — you can put your AWACS URLs here as a baked default if you want
70-
return normalizeConfig({});
71-
}
72-
73-
// ---------- Navigation guard ----------
74-
75-
function getOrigin(u) {
76-
try {
77-
const x = new URL(u);
78-
return `${x.protocol}//${x.host}`;
79-
} catch {
80-
return '';
102+
const raw = fs.readFileSync(p, 'utf-8');
103+
const parsed = JSON.parse(raw);
104+
return normalizeConfig(parsed);
105+
} catch (e) {
106+
log('Failed to read/parse config at', p, e.message);
107+
// Keep defaults if parse fails
108+
return normalizeConfig({});
81109
}
82110
}
83111

112+
function getOrigin(u) { try { const x = new URL(u); return `${x.protocol}//${x.host}`; } catch { return ''; } }
113+
84114
function guardNavigation(view, initialUrl) {
85115
if (!config.lockToInitialOrigin) return;
86116
const origin = getOrigin(initialUrl);
87-
88-
view.webContents.on('will-navigate', (e, url) => {
89-
if (getOrigin(url) !== origin) e.preventDefault();
90-
});
91-
view.webContents.setWindowOpenHandler((d) => {
92-
if (getOrigin(d.url) !== origin) return { action: 'deny' };
93-
return { action: 'allow' };
94-
});
117+
view.webContents.on('will-navigate', (e, url) => { if (getOrigin(url) !== origin) e.preventDefault(); });
118+
view.webContents.setWindowOpenHandler((d) => { return (getOrigin(d.url) !== origin) ? { action: 'deny' } : { action: 'allow' }; });
95119
}
96120

97-
// ---------- Layout ----------
98-
99121
function layout() {
100122
if (!mainWindow || !topView || !bottomView) return;
101123
const [w, h] = mainWindow.getContentSize();
@@ -106,8 +128,6 @@ function layout() {
106128
bottomView.setAutoResize({ width: true, height: true });
107129
}
108130

109-
// ---------- Live apply changes ----------
110-
111131
function applyConfigChanges(next) {
112132
const urlsChanged = next.topUrl !== config.topUrl || next.bottomUrl !== config.bottomUrl;
113133
const ratioChanged = next.dividerRatio !== config.dividerRatio;
@@ -129,7 +149,6 @@ function applyConfigChanges(next) {
129149
}
130150

131151
if (lockChanged) {
132-
// Re-apply guards (handlers are additive; we only tighten rules when enabled)
133152
guardNavigation(topView, config.topUrl);
134153
guardNavigation(bottomView, config.bottomUrl);
135154
}
@@ -141,7 +160,6 @@ function applyConfigChanges(next) {
141160
} else if (uaChanged && topView) {
142161
topView.webContents.reload();
143162
}
144-
145163
if (bottomView && bottomView.webContents.getURL() !== config.bottomUrl) {
146164
bottomView.webContents.loadURL(config.bottomUrl, loadOpts).catch(() => {});
147165
} else if (uaChanged && bottomView) {
@@ -154,42 +172,30 @@ function applyConfigChanges(next) {
154172

155173
function watchConfigFile() {
156174
if (!cfgPathInUse) return;
157-
158-
const tryApply = debounce(() => {
159-
try {
160-
const next = loadConfig();
161-
applyConfigChanges(next);
162-
} catch {
163-
// ignore transient parse errors while saving
164-
}
175+
const applyLater = debounce(() => {
176+
const next = loadConfig();
177+
log('Config change detected. Applying...');
178+
applyConfigChanges(next);
165179
}, 300);
166180

167181
try {
168-
const watcher = fs.watch(cfgPathInUse, { persistent: false }, tryApply);
169-
watcher.on('error', () => {
182+
const w = fs.watch(cfgPathInUse, { persistent: false }, applyLater);
183+
w.on('error', () => {
170184
try { fs.unwatchFile(cfgPathInUse); } catch {}
171-
try { fs.watchFile(cfgPathInUse, { interval: 500 }, tryApply); } catch {}
185+
try { fs.watchFile(cfgPathInUse, { interval: 500 }, applyLater); } catch {}
172186
});
173187
} catch {
174-
try { fs.watchFile(cfgPathInUse, { interval: 500 }, tryApply); } catch {}
188+
try { fs.watchFile(cfgPathInUse, { interval: 500 }, applyLater); } catch {}
175189
}
176190
}
177191

178-
function debounce(fn, ms = 250) {
179-
let t;
180-
return (...args) => {
181-
clearTimeout(t);
182-
t = setTimeout(() => fn(...args), ms);
183-
};
184-
}
185-
186-
// ---------- Create window ----------
192+
function debounce(fn, ms = 250) { let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); }; }
187193

188194
function createWindow() {
189195
config = loadConfig();
190-
console.log('[TwoSites] Using config at:', cfgPathInUse);
191-
console.log('[TwoSites] Top URL:', config.topUrl);
192-
console.log('[TwoSites] Bottom URL:', config.bottomUrl);
196+
log('[TwoSites] Using config at:', cfgPathInUse);
197+
log('[TwoSites] Top URL:', config.topUrl);
198+
log('[TwoSites] Bottom URL:', config.bottomUrl);
193199

194200
mainWindow = new BrowserWindow({
195201
width: 1200,
@@ -198,8 +204,8 @@ function createWindow() {
198204
minWidth: config.minWidth,
199205
minHeight: config.minHeight,
200206
backgroundColor: '#111111',
201-
show: true, // show immediately
202-
autoHideMenuBar: true, // no address bar/menu
207+
show: true,
208+
autoHideMenuBar: true,
203209
webPreferences: {
204210
nodeIntegration: false,
205211
contextIsolation: true,
@@ -208,7 +214,7 @@ function createWindow() {
208214
},
209215
});
210216

211-
// Safety net: ensure it shows even if pages hang
217+
// Safety: ensure visible even if loads hang
212218
setTimeout(() => { try { mainWindow.show(); } catch {} }, 1500);
213219

214220
topView = new BrowserView({ webPreferences: { contextIsolation: true, sandbox: true } });
@@ -217,17 +223,10 @@ function createWindow() {
217223
mainWindow.setBrowserView(topView);
218224
mainWindow.addBrowserView(bottomView);
219225

220-
// capture default UA
221-
try {
222-
defaultUA.top = topView.webContents.getUserAgent?.() || null;
223-
defaultUA.bottom = bottomView.webContents.getUserAgent?.() || null;
224-
} catch {}
225-
226+
try { defaultUA.top = topView.webContents.getUserAgent?.() || null; } catch {}
227+
try { defaultUA.bottom = bottomView.webContents.getUserAgent?.() || null; } catch {}
226228
if (config.userAgent) {
227-
try {
228-
topView.webContents.setUserAgent(config.userAgent);
229-
bottomView.webContents.setUserAgent(config.userAgent);
230-
} catch {}
229+
try { topView.webContents.setUserAgent(config.userAgent); bottomView.webContents.setUserAgent(config.userAgent); } catch {}
231230
}
232231

233232
const loadOpts = config.userAgent ? { userAgent: config.userAgent } : undefined;
@@ -239,26 +238,33 @@ function createWindow() {
239238
mainWindow.on('resize', layout);
240239
mainWindow.once('ready-to-show', () => { layout(); try { mainWindow.show(); } catch {} });
241240

242-
// diagnostics
243241
for (const wc of [topView.webContents, bottomView.webContents]) {
244-
wc.on('did-fail-load', (_e, code, desc, validatedURL) => {
245-
console.error('did-fail-load', code, desc, validatedURL);
246-
});
247-
wc.on('crashed', () => console.error('webContents crashed'));
242+
wc.on('did-fail-load', (_e, code, desc, validatedURL) => { log('did-fail-load', code, desc, validatedURL); });
243+
wc.on('crashed', () => log('webContents crashed'));
248244
}
249245

250246
watchConfigFile();
251-
}
252247

253-
// ---------- App lifecycle ----------
248+
// If the app is still showing example.com and not your URLs, show a one-time dialog
249+
if (!config.topUrl || config.topUrl.includes('example.com')) {
250+
setTimeout(() => {
251+
try {
252+
dialog.showMessageBox({
253+
type: 'warning',
254+
title: 'Two Sites Viewer',
255+
message: 'No valid config.json was found. A default config may have been created.',
256+
detail: `Config path used: ${cfgPathInUse}
257+
258+
Edit this file and save; the app will auto-reload.`,
259+
});
260+
} catch {}
261+
}, 800);
262+
}
263+
}
254264

255265
app.whenReady().then(() => {
256266
createWindow();
257-
app.on('activate', () => {
258-
if (BrowserWindow.getAllWindows().length === 0) createWindow();
259-
});
267+
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
260268
});
261269

262-
app.on('window-all-closed', () => {
263-
if (process.platform !== 'darwin') app.quit();
264-
});
270+
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });

0 commit comments

Comments
 (0)