1- // main.js
1+ // main.js (ESM)
22import { app , BrowserWindow , BrowserView } from 'electron' ;
33import fs from 'node:fs' ;
44import path from 'node:path' ;
55
6- // ---- Runtime safety: reduce chances of "blank" windows on some GPUs/VMs
6+ // Improve compatibility with some enterprise GPUs/VMs
77app . disableHardwareAcceleration ( ) ;
88
9- // -------- Globals
109let mainWindow ;
1110let topView ;
1211let bottomView ;
1312let config ;
1413let cfgPathInUse = null ;
1514let defaultUA = { top : null , bottom : null } ;
1615
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 ( ) ;
29- const candidates = [
30- path . join ( exeDir , 'config.json' ) , // portable / next to EXE (preferred)
31- path . join ( process . cwd ( ) , 'config.json' ) , // dev fallback
32- ] ;
33- for ( const p of candidates ) {
34- try {
35- if ( fs . existsSync ( p ) ) return p ;
36- } catch { }
37- }
38- // If none exist, default to exeDir for future writes/expectations
39- return path . join ( exeDir , 'config.json' ) ;
40- }
16+ // ---------- Config loading ----------
4117
4218function normalizeConfig ( cfg ) {
4319 return {
@@ -51,6 +27,35 @@ function normalizeConfig(cfg) {
5127 } ;
5228}
5329
30+ function candidatesForConfig ( ) {
31+ const portableDir = process . env . PORTABLE_EXECUTABLE_DIR ; // electron-builder portable
32+ const exeDir = path . dirname ( app . getPath ( 'exe' ) ) ;
33+ 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+
37+ const list = [ ] ;
38+ if ( portableDir ) list . push ( path . join ( portableDir , 'config.json' ) ) ;
39+ list . push (
40+ path . join ( exeDir , 'config.json' ) ,
41+ path . join ( cwd , 'config.json' ) ,
42+ path . join ( userData , 'config.json' ) ,
43+ path . join ( resources , 'config.json' ) // final fallback if bundled
44+ ) ;
45+ return list ;
46+ }
47+
48+ function findConfigPath ( ) {
49+ for ( const p of candidatesForConfig ( ) ) {
50+ try { if ( fs . existsSync ( p ) ) return p ; } catch { }
51+ }
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' ) ;
57+ }
58+
5459function loadConfig ( ) {
5560 const p = cfgPathInUse || findConfigPath ( ) ;
5661 cfgPathInUse = p ;
@@ -61,10 +66,12 @@ function loadConfig() {
6166 return normalizeConfig ( parsed ) ;
6267 }
6368 } catch { }
64- // Fallback defaults ( you can inline your own baked defaults here if desired)
69+ // Fallback defaults — you can put your AWACS URLs here as a baked default if you want
6570 return normalizeConfig ( { } ) ;
6671}
6772
73+ // ---------- Navigation guard ----------
74+
6875function getOrigin ( u ) {
6976 try {
7077 const x = new URL ( u ) ;
@@ -87,16 +94,7 @@ function guardNavigation(view, initialUrl) {
8794 } ) ;
8895}
8996
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- } ;
97- }
98-
99- // ---- Layout
97+ // ---------- Layout ----------
10098
10199function layout ( ) {
102100 if ( ! mainWindow || ! topView || ! bottomView ) return ;
@@ -108,97 +106,90 @@ function layout() {
108106 bottomView . setAutoResize ( { width : true , height : true } ) ;
109107}
110108
111- // ---- Apply config changes live
109+ // ---------- Live apply changes ----------
112110
113111function applyConfigChanges ( next ) {
114- // Determine what changed
115- const urlsChanged =
116- next . topUrl !== config . topUrl || next . bottomUrl !== config . bottomUrl ;
112+ const urlsChanged = next . topUrl !== config . topUrl || next . bottomUrl !== config . bottomUrl ;
117113 const ratioChanged = next . dividerRatio !== config . dividerRatio ;
118114 const uaChanged = next . userAgent !== config . userAgent ;
119115 const lockChanged = next . lockToInitialOrigin !== config . lockToInitialOrigin ;
120116
121117 config = next ;
122118
123- // Update user agent on each view if changed
124119 if ( uaChanged && topView && bottomView ) {
125120 try {
126121 if ( config . userAgent ) {
127122 topView . webContents . setUserAgent ( config . userAgent ) ;
128123 bottomView . webContents . setUserAgent ( config . userAgent ) ;
129124 } else {
130- // Restore defaults if we captured them
131125 if ( defaultUA . top ) topView . webContents . setUserAgent ( defaultUA . top ) ;
132126 if ( defaultUA . bottom ) bottomView . webContents . setUserAgent ( defaultUA . bottom ) ;
133127 }
134128 } catch { }
135129 }
136130
137- // Re-guard navigation if lock setting changed
138131 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
132+ // Re-apply guards (handlers are additive; we only tighten rules when enabled)
141133 guardNavigation ( topView , config . topUrl ) ;
142134 guardNavigation ( bottomView , config . bottomUrl ) ;
143135 }
144136
145- // Reload URLs if changed or UA changed (UA affects next navigation)
146137 if ( urlsChanged || uaChanged ) {
147138 const loadOpts = config . userAgent ? { userAgent : config . userAgent } : undefined ;
148139 if ( topView && topView . webContents . getURL ( ) !== config . topUrl ) {
149140 topView . webContents . loadURL ( config . topUrl , loadOpts ) . catch ( ( ) => { } ) ;
150141 } else if ( uaChanged && topView ) {
151142 topView . webContents . reload ( ) ;
152143 }
144+
153145 if ( bottomView && bottomView . webContents . getURL ( ) !== config . bottomUrl ) {
154146 bottomView . webContents . loadURL ( config . bottomUrl , loadOpts ) . catch ( ( ) => { } ) ;
155147 } else if ( uaChanged && bottomView ) {
156148 bottomView . webContents . reload ( ) ;
157149 }
158150 }
159151
160- // Re-apply layout if divider changed
161152 if ( ratioChanged ) layout ( ) ;
162153}
163154
164155function watchConfigFile ( ) {
165156 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 ) ;
175157
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
158+ const tryApply = debounce ( ( ) => {
159+ try {
160+ const next = loadConfig ( ) ;
161+ applyConfigChanges ( next ) ;
162+ } catch {
163+ // ignore transient parse errors while saving
164+ }
165+ } , 300 ) ;
166+
167+ try {
168+ const watcher = fs . watch ( cfgPathInUse , { persistent : false } , tryApply ) ;
179169 watcher . on ( 'error' , ( ) => {
180- try {
181- fs . unwatchFile ( cfgPathInUse ) ;
182- } catch { }
183- try {
184- fs . watchFile ( cfgPathInUse , { interval : 500 } , handler ) ;
185- } catch { }
170+ try { fs . unwatchFile ( cfgPathInUse ) ; } catch { }
171+ try { fs . watchFile ( cfgPathInUse , { interval : 500 } , tryApply ) ; } catch { }
186172 } ) ;
187173 } catch {
188- // Fallback: polling
189- try {
190- fs . watchFile ( cfgPathInUse , { interval : 500 } , debounce ( ( ) => {
191- const next = loadConfig ( ) ;
192- applyConfigChanges ( next ) ;
193- } , 300 ) ) ;
194- } catch { }
174+ try { fs . watchFile ( cfgPathInUse , { interval : 500 } , tryApply ) ; } catch { }
195175 }
196176}
197177
198- // ---- Create window and views
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 ----------
199187
200188function createWindow ( ) {
201189 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 ) ;
202193
203194 mainWindow = new BrowserWindow ( {
204195 width : 1200 ,
@@ -207,8 +198,8 @@ function createWindow() {
207198 minWidth : config . minWidth ,
208199 minHeight : config . minHeight ,
209200 backgroundColor : '#111111' ,
210- show : true , // show immediately
211- autoHideMenuBar : true , // no menu (no address bar)
201+ show : true , // show immediately
202+ autoHideMenuBar : true , // no address bar/menu
212203 webPreferences : {
213204 nodeIntegration : false ,
214205 contextIsolation : true ,
@@ -220,57 +211,46 @@ function createWindow() {
220211 // Safety net: ensure it shows even if pages hang
221212 setTimeout ( ( ) => { try { mainWindow . show ( ) ; } catch { } } , 1500 ) ;
222213
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- } ) ;
214+ topView = new BrowserView ( { webPreferences : { contextIsolation : true , sandbox : true } } ) ;
215+ bottomView = new BrowserView ( { webPreferences : { contextIsolation : true , sandbox : true } } ) ;
230216
231217 mainWindow . setBrowserView ( topView ) ;
232218 mainWindow . addBrowserView ( bottomView ) ;
233219
234- // Capture default UA strings before any overrides
220+ // capture default UA
235221 try {
236222 defaultUA . top = topView . webContents . getUserAgent ?. ( ) || null ;
237223 defaultUA . bottom = bottomView . webContents . getUserAgent ?. ( ) || null ;
238224 } catch { }
239225
240- // Optional custom UA
241226 if ( config . userAgent ) {
242227 try {
243228 topView . webContents . setUserAgent ( config . userAgent ) ;
244229 bottomView . webContents . setUserAgent ( config . userAgent ) ;
245230 } catch { }
246231 }
247232
248- // Load URLs
249233 const loadOpts = config . userAgent ? { userAgent : config . userAgent } : undefined ;
250234 topView . webContents . loadURL ( config . topUrl , loadOpts ) . catch ( ( ) => { } ) ;
251235 bottomView . webContents . loadURL ( config . bottomUrl , loadOpts ) . catch ( ( ) => { } ) ;
252-
253- // Navigation guard (optional, per config)
254236 guardNavigation ( topView , config . topUrl ) ;
255237 guardNavigation ( bottomView , config . bottomUrl ) ;
256238
257- // Layout now and on resize
258239 mainWindow . on ( 'resize' , layout ) ;
259240 mainWindow . once ( 'ready-to-show' , ( ) => { layout ( ) ; try { mainWindow . show ( ) ; } catch { } } ) ;
260241
261- // Useful diagnostics if something fails to load
242+ // diagnostics
262243 for ( const wc of [ topView . webContents , bottomView . webContents ] ) {
263244 wc . on ( 'did-fail-load' , ( _e , code , desc , validatedURL ) => {
264245 console . error ( 'did-fail-load' , code , desc , validatedURL ) ;
265246 } ) ;
266247 wc . on ( 'crashed' , ( ) => console . error ( 'webContents crashed' ) ) ;
267248 }
268249
269- // Start watching config.json for live updates
270250 watchConfigFile ( ) ;
271251}
272252
273- // ---- App lifecycle
253+ // ---------- App lifecycle ----------
274254
275255app . whenReady ( ) . then ( ( ) => {
276256 createWindow ( ) ;
0 commit comments