Skip to content

Commit 3116225

Browse files
authored
Update main.js
1 parent 29c4b2d commit 3116225

1 file changed

Lines changed: 222 additions & 39 deletions

File tree

main.js

Lines changed: 222 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,202 @@
1-
1+
// main.js
22
import { app, BrowserWindow, BrowserView } from 'electron';
3-
import path from 'node:path';
43
import fs from 'node:fs';
4+
import path from 'node:path';
55

6-
let mainWindow, topView, bottomView, config;
7-
8-
// Mitigate blank window on some GPUs / VMs
6+
// ---- Runtime safety: reduce chances of "blank" windows on some GPUs/VMs
97
app.disableHardwareAcceleration();
108

11-
function loadConfig() {
12-
const exeDir = path.dirname(app.getPath('exe'));
9+
// -------- Globals
10+
let mainWindow;
11+
let topView;
12+
let bottomView;
13+
let config;
14+
let cfgPathInUse = null;
15+
let defaultUA = { top: null, bottom: null };
16+
17+
// ---- Helpers
18+
19+
function getExeDir() {
20+
try {
21+
return path.dirname(app.getPath('exe'));
22+
} catch {
23+
return process.cwd();
24+
}
25+
}
26+
27+
function findConfigPath() {
28+
const exeDir = getExeDir();
1329
const candidates = [
14-
path.join(exeDir, 'config.json'),
15-
path.join(process.cwd(), 'config.json')
30+
path.join(exeDir, 'config.json'), // portable / next to EXE (preferred)
31+
path.join(process.cwd(), 'config.json'), // dev fallback
1632
];
1733
for (const p of candidates) {
18-
try { if (fs.existsSync(p)) return normalize(JSON.parse(fs.readFileSync(p,'utf-8'))); } catch {}
34+
try {
35+
if (fs.existsSync(p)) return p;
36+
} catch {}
1937
}
20-
return normalize({"topUrl": "https://awacs-portal2.icloud-prd.eu-west-1.aws.pmicloud.biz/speed/ro70-09-10/operators?plant=RO70&area=Secondary", "bottomUrl": "https://awacs-portal2.icloud-prd.eu-west-1.aws.pmicloud.biz/speed/ro70-11-12/operators?plant=RO70&area=Secondary", "dividerRatio": 0.55, "minWidth": 1200, "minHeight": 800, "userAgent": "", "lockToInitialOrigin": false});
38+
// If none exist, default to exeDir for future writes/expectations
39+
return path.join(exeDir, 'config.json');
2140
}
2241

23-
function normalize(cfg) {
42+
function normalizeConfig(cfg) {
2443
return {
25-
topUrl: cfg.topUrl || 'https://example.com',
26-
bottomUrl: cfg.bottomUrl || 'https://example.com',
27-
dividerRatio: Math.min(0.9, Math.max(0.1, cfg.dividerRatio ?? 0.55)),
28-
minWidth: cfg.minWidth ?? 1000,
29-
minHeight: cfg.minHeight ?? 700,
30-
userAgent: cfg.userAgent || '',
31-
lockToInitialOrigin: cfg.lockToInitialOrigin ?? false
44+
topUrl: cfg?.topUrl || 'https://example.com',
45+
bottomUrl: cfg?.bottomUrl || 'https://example.com',
46+
dividerRatio: Math.min(0.9, Math.max(0.1, cfg?.dividerRatio ?? 0.55)),
47+
minWidth: cfg?.minWidth ?? 1000,
48+
minHeight: cfg?.minHeight ?? 700,
49+
userAgent: typeof cfg?.userAgent === 'string' ? cfg.userAgent.trim() : '',
50+
lockToInitialOrigin: !!cfg?.lockToInitialOrigin,
3251
};
3352
}
3453

35-
function getOrigin(u) { try { const x = new URL(u); return `${x.protocol}//${x.host}`; } catch { return ''; } }
54+
function loadConfig() {
55+
const p = cfgPathInUse || findConfigPath();
56+
cfgPathInUse = p;
57+
try {
58+
if (fs.existsSync(p)) {
59+
const raw = fs.readFileSync(p, 'utf-8');
60+
const parsed = JSON.parse(raw);
61+
return normalizeConfig(parsed);
62+
}
63+
} catch {}
64+
// Fallback defaults (you can inline your own baked defaults here if desired)
65+
return normalizeConfig({});
66+
}
67+
68+
function getOrigin(u) {
69+
try {
70+
const x = new URL(u);
71+
return `${x.protocol}//${x.host}`;
72+
} catch {
73+
return '';
74+
}
75+
}
3676

3777
function guardNavigation(view, initialUrl) {
3878
if (!config.lockToInitialOrigin) return;
3979
const origin = getOrigin(initialUrl);
40-
view.webContents.on('will-navigate', (e, url) => { if (getOrigin(url) !== origin) e.preventDefault(); });
41-
view.webContents.setWindowOpenHandler((d) => { return (getOrigin(d.url) !== origin) ? {action:'deny'} : {action:'allow'}; });
80+
81+
view.webContents.on('will-navigate', (e, url) => {
82+
if (getOrigin(url) !== origin) e.preventDefault();
83+
});
84+
view.webContents.setWindowOpenHandler((d) => {
85+
if (getOrigin(d.url) !== origin) return { action: 'deny' };
86+
return { action: 'allow' };
87+
});
88+
}
89+
90+
// Simple debounce for file watcher
91+
function debounce(fn, ms = 250) {
92+
let t;
93+
return (...args) => {
94+
clearTimeout(t);
95+
t = setTimeout(() => fn(...args), ms);
96+
};
4297
}
4398

99+
// ---- Layout
100+
44101
function layout() {
45102
if (!mainWindow || !topView || !bottomView) return;
46-
const [w,h] = mainWindow.getContentSize();
103+
const [w, h] = mainWindow.getContentSize();
47104
const split = Math.round(h * config.dividerRatio);
48-
topView.setBounds({x:0,y:0,width:w,height:split});
49-
topView.setAutoResize({width:true,height:true});
50-
bottomView.setBounds({x:0,y:split,width:w,height:h-split});
51-
bottomView.setAutoResize({width:true,height:true});
105+
topView.setBounds({ x: 0, y: 0, width: w, height: split });
106+
topView.setAutoResize({ width: true, height: true });
107+
bottomView.setBounds({ x: 0, y: split, width: w, height: h - split });
108+
bottomView.setAutoResize({ width: true, height: true });
52109
}
53110

111+
// ---- Apply config changes live
112+
113+
function applyConfigChanges(next) {
114+
// Determine what changed
115+
const urlsChanged =
116+
next.topUrl !== config.topUrl || next.bottomUrl !== config.bottomUrl;
117+
const ratioChanged = next.dividerRatio !== config.dividerRatio;
118+
const uaChanged = next.userAgent !== config.userAgent;
119+
const lockChanged = next.lockToInitialOrigin !== config.lockToInitialOrigin;
120+
121+
config = next;
122+
123+
// Update user agent on each view if changed
124+
if (uaChanged && topView && bottomView) {
125+
try {
126+
if (config.userAgent) {
127+
topView.webContents.setUserAgent(config.userAgent);
128+
bottomView.webContents.setUserAgent(config.userAgent);
129+
} else {
130+
// Restore defaults if we captured them
131+
if (defaultUA.top) topView.webContents.setUserAgent(defaultUA.top);
132+
if (defaultUA.bottom) bottomView.webContents.setUserAgent(defaultUA.bottom);
133+
}
134+
} catch {}
135+
}
136+
137+
// Re-guard navigation if lock setting changed
138+
if (lockChanged) {
139+
// Remove old handlers by recreating views is heavy; instead, rely on handler logic
140+
// Since we only ever *tighten* rules, we can add guards now if they weren't present
141+
guardNavigation(topView, config.topUrl);
142+
guardNavigation(bottomView, config.bottomUrl);
143+
}
144+
145+
// Reload URLs if changed or UA changed (UA affects next navigation)
146+
if (urlsChanged || uaChanged) {
147+
const loadOpts = config.userAgent ? { userAgent: config.userAgent } : undefined;
148+
if (topView && topView.webContents.getURL() !== config.topUrl) {
149+
topView.webContents.loadURL(config.topUrl, loadOpts).catch(() => {});
150+
} else if (uaChanged && topView) {
151+
topView.webContents.reload();
152+
}
153+
if (bottomView && bottomView.webContents.getURL() !== config.bottomUrl) {
154+
bottomView.webContents.loadURL(config.bottomUrl, loadOpts).catch(() => {});
155+
} else if (uaChanged && bottomView) {
156+
bottomView.webContents.reload();
157+
}
158+
}
159+
160+
// Re-apply layout if divider changed
161+
if (ratioChanged) layout();
162+
}
163+
164+
function watchConfigFile() {
165+
if (!cfgPathInUse) return;
166+
try {
167+
const handler = debounce(() => {
168+
try {
169+
const next = loadConfig();
170+
applyConfigChanges(next);
171+
} catch {
172+
// Ignore transient parse errors while file is being saved
173+
}
174+
}, 300);
175+
176+
// Prefer fs.watch; if it fails on network shares, fall back to watchFile
177+
const watcher = fs.watch(cfgPathInUse, { persistent: false }, handler);
178+
// On some filesystems, rename events may fire; also handle errors silently
179+
watcher.on('error', () => {
180+
try {
181+
fs.unwatchFile(cfgPathInUse);
182+
} catch {}
183+
try {
184+
fs.watchFile(cfgPathInUse, { interval: 500 }, handler);
185+
} catch {}
186+
});
187+
} catch {
188+
// Fallback: polling
189+
try {
190+
fs.watchFile(cfgPathInUse, { interval: 500 }, debounce(() => {
191+
const next = loadConfig();
192+
applyConfigChanges(next);
193+
}, 300));
194+
} catch {}
195+
}
196+
}
197+
198+
// ---- Create window and views
199+
54200
function createWindow() {
55201
config = loadConfig();
56202

@@ -61,41 +207,78 @@ function createWindow() {
61207
minWidth: config.minWidth,
62208
minHeight: config.minHeight,
63209
backgroundColor: '#111111',
64-
show: true,
65-
autoHideMenuBar: true,
66-
webPreferences: { nodeIntegration:false, contextIsolation:true, sandbox:true, backgroundThrottling:false }
210+
show: true, // show immediately
211+
autoHideMenuBar: true, // no menu (no address bar)
212+
webPreferences: {
213+
nodeIntegration: false,
214+
contextIsolation: true,
215+
sandbox: true,
216+
backgroundThrottling: false,
217+
},
67218
});
68219

69-
topView = new BrowserView({ webPreferences: { contextIsolation:true, sandbox:true } });
70-
bottomView = new BrowserView({ webPreferences: { contextIsolation:true, sandbox:true } });
220+
// Safety net: ensure it shows even if pages hang
221+
setTimeout(() => { try { mainWindow.show(); } catch {} }, 1500);
222+
223+
// Create the two panes
224+
topView = new BrowserView({
225+
webPreferences: { contextIsolation: true, sandbox: true },
226+
});
227+
bottomView = new BrowserView({
228+
webPreferences: { contextIsolation: true, sandbox: true },
229+
});
71230

72231
mainWindow.setBrowserView(topView);
73232
mainWindow.addBrowserView(bottomView);
74233

75-
const loadOpts = config.userAgent ? { userAgent: config.userAgent } : undefined;
234+
// Capture default UA strings before any overrides
235+
try {
236+
defaultUA.top = topView.webContents.getUserAgent?.() || null;
237+
defaultUA.bottom = bottomView.webContents.getUserAgent?.() || null;
238+
} catch {}
76239

77-
setTimeout(() => { try { mainWindow.show(); } catch {} }, 2000);
240+
// Optional custom UA
241+
if (config.userAgent) {
242+
try {
243+
topView.webContents.setUserAgent(config.userAgent);
244+
bottomView.webContents.setUserAgent(config.userAgent);
245+
} catch {}
246+
}
78247

79-
topView.webContents.loadURL(config.topUrl, loadOpts).catch(()=>{});
80-
bottomView.webContents.loadURL(config.bottomUrl, loadOpts).catch(()=>{});
248+
// Load URLs
249+
const loadOpts = config.userAgent ? { userAgent: config.userAgent } : undefined;
250+
topView.webContents.loadURL(config.topUrl, loadOpts).catch(() => {});
251+
bottomView.webContents.loadURL(config.bottomUrl, loadOpts).catch(() => {});
81252

253+
// Navigation guard (optional, per config)
82254
guardNavigation(topView, config.topUrl);
83255
guardNavigation(bottomView, config.bottomUrl);
84256

257+
// Layout now and on resize
85258
mainWindow.on('resize', layout);
86-
mainWindow.on('ready-to-show', () => { layout(); try { mainWindow.show(); } catch {} });
259+
mainWindow.once('ready-to-show', () => { layout(); try { mainWindow.show(); } catch {} });
87260

261+
// Useful diagnostics if something fails to load
88262
for (const wc of [topView.webContents, bottomView.webContents]) {
89263
wc.on('did-fail-load', (_e, code, desc, validatedURL) => {
90264
console.error('did-fail-load', code, desc, validatedURL);
91265
});
92266
wc.on('crashed', () => console.error('webContents crashed'));
93267
}
268+
269+
// Start watching config.json for live updates
270+
watchConfigFile();
94271
}
95272

273+
// ---- App lifecycle
274+
96275
app.whenReady().then(() => {
97276
createWindow();
98-
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
277+
app.on('activate', () => {
278+
if (BrowserWindow.getAllWindows().length === 0) createWindow();
279+
});
99280
});
100281

101-
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });
282+
app.on('window-all-closed', () => {
283+
if (process.platform !== 'darwin') app.quit();
284+
});

0 commit comments

Comments
 (0)