RSS Git Download  Clone
Raw Blame History
const {ipcRenderer, shell} = require('electron');
const http = require('node:http');


let domReady = false
let loadingOverlay
let hasSuccessfulUiLoad = false
let shellAutoReloadTimer
const shellAutoReloadDelayMs = 3000
const shellAutoReloadMaxAttempts = 1
const shellAutoReloadAttemptKey = 'p3xre-shell-auto-reload-attempt'

const setLoadingOverlayVisible = (visible) => {
    if (!loadingOverlay) {
        return
    }
    if (visible) {
        loadingOverlay.classList.remove('p3xre-hidden')
    } else {
        loadingOverlay.classList.add('p3xre-hidden')
    }
}

const setLoadingState = (loading) => {
    setLoadingOverlayVisible(loading)
    if (!global.p3xre || !global.p3xre.webview) {
        return
    }
    if (loading) {
        global.p3xre.webview.classList.add('p3xre-webview-loading')
    } else {
        global.p3xre.webview.classList.remove('p3xre-webview-loading')
    }
}

const clearShellAutoReloadTimer = () => {
    if (!shellAutoReloadTimer) {
        return
    }
    clearTimeout(shellAutoReloadTimer)
    shellAutoReloadTimer = undefined
}

let p3xSetLanguageWaiter
ipcRenderer.on('p3x-set-language', (event, data) => {
    const callMe = () => {
        if (domReady === false) {
            clearTimeout(p3xSetLanguageWaiter)
            setTimeout(callMe, 250)
            return;
        }
        const translation = data.translation
        //console.warn('p3x-set-language', data)
        global.p3xre.strings = require('../../../strings/' + translation + '/index')
        // global.p3xre.webview.getWebContents().executeJavaScript is different!!! - getWebContents deprecated, removed
        global.p3xre.webview.executeJavaScript(`window.p3xrBooter=(()=>{void 0===window.p3xrSetLanguage?setTimeout(()=>{window.p3xrBooter()},500):window.p3xrSetLanguage("${translation}")}),window.p3xrBooter();`)
    }
    callMe()

    /*
    window.p3xrBooter = () => {
        console.log('p3xr booting ...');
        if (window.p3xrSetLanguage === undefined) {
            setTimeout(() => {
                window.p3xrBooter();
            }, 500);
        } else {
            window.p3xrSetLanguage('translation');
        }
    };
    window.p3xrBooter();
    */
})


ipcRenderer.on('p3x-menu', function (event, data) {
    global.p3xre.webview.executeJavaScript(`window.p3xrMenu=(()=>{console.log("p3xr menu booting ..."),void 0===window.p3xrSetMenu?setTimeout(()=>{window.p3xrMenu()},500):window.p3xrSetMenu("${data.action}")}),window.p3xrMenu();`)
    /*
    window.p3xrMenu = () => {
        console.log('p3xr menu booting ...');
        if (window.p3xrSetMenu === undefined) {
            setTimeout(() => {
                window.p3xrMenu();
            }, 500);
        } else {
            window.p3xrSetMenu('menu');
        }
    };
    window.p3xrMenu();
    */
})

ipcRenderer.on('p3x-new-window', function (event, data) {
    shell.openExternal(data.url)
})


ipcRenderer.on('p3x-action', function (event, data) {
    switch (data.action) {
        case 'toast':
            p3xre.toast.action(data.message)
            break;
    }
})

global.p3xre = {
    webview: undefined,
    pkg: require('../../../../package'),
    strings: require('../../../strings/en/index')
};

require('./angular')

const isLocalHttpAvailable = (port, timeoutMs = 800, host = '127.0.0.1') => {
    return new Promise((resolve) => {
        let settled = false
        const done = (value) => {
            if (settled) {
                return
            }
            settled = true
            resolve(value)
        }

        const request = http.request({
            host: host,
            port: port,
            path: '/',
            method: 'GET',
        }, () => {
            done(true)
            request.destroy()
        })

        request.on('error', () => {
            done(false)
        })

        request.setTimeout(timeoutMs, () => {
            request.destroy()
            done(false)
        })

        request.end()
    })
}

window.p3xreRun = async function () {

    
    document.title = `${p3xre.strings.title} v${p3xre.pkg.version}`
    try {
        global.p3xre.webview = document.getElementById("p3xre-redis-ui-electron");
        loadingOverlay = document.getElementById('p3xre-loading-overlay')
        setLoadingState(true)

        //global.p3xre.webview.src = 'http://localhost:7844';

        global.p3xre.webview.addEventListener("dom-ready", function () {
            domReady = true
            if (process.env.hasOwnProperty('NODE_ENV') && (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test')) {
                global.p3xre.webview.openDevTools();
            }
        })

        global.p3xre.webview.addEventListener('did-start-loading', function () {
            // Keep shell loader for initial boot only; Angular route changes can also emit this.
            if (!hasSuccessfulUiLoad) {
                setLoadingState(true)
            }
        })



        // must to remove this code

        /*
        global.p3xre.webview.addEventListener('new-window', function (event) {

            event.preventDefault()
            shell.openExternal(event.url)

        });
         */

        const urlParams = new URLSearchParams(global.location.search)
        const port = urlParams.get('port')
        const localServerHosts = ['127.0.0.1', 'localhost']
        const getLocalServerUrl = (host) => `http://${host}:${port}`
        const isLocalServerUrl = (url) => localServerHosts.some((host) => url.startsWith(getLocalServerUrl(host)))
        let currentLocalServerHostIndex = 0
        const getCurrentLocalServerUrl = () => getLocalServerUrl(localServerHosts[currentLocalServerHostIndex])
        const reloadWebviewTo = (targetUrl) => {
            const currentSrc = global.p3xre.webview.src || ''
            if (currentSrc.startsWith(targetUrl)) {
                if (typeof global.p3xre.webview.reloadIgnoringCache === 'function') {
                    global.p3xre.webview.reloadIgnoringCache()
                } else {
                    const separator = targetUrl.includes('?') ? '&' : '?'
                    global.p3xre.webview.src = `${targetUrl}${separator}_p3xre_reload=${Date.now()}`
                }
                return
            }
            global.p3xre.webview.src = targetUrl
        }
        const devServerUrl = 'http://localhost:8080'
        const isDev = process.env.hasOwnProperty('NODE_ENV') && process.env.NODE_ENV === 'development'
        const retryableLoadErrors = new Set([-102, -105, -106, -118])
        const maxLocalServerLoadRetries = 60
        const localServerLoadRetryDelayMs = 500
        let localServerLoadRetries = 0
        let localServerLoadRetryTimer
        const scheduleShellAutoReload = () => {
            clearShellAutoReloadTimer()
            shellAutoReloadTimer = setTimeout(() => {
                if (hasSuccessfulUiLoad) {
                    return
                }
                const currentAttempt = Number(sessionStorage.getItem(shellAutoReloadAttemptKey) || '0')
                if (currentAttempt >= shellAutoReloadMaxAttempts) {
                    console.error('webview still not loaded after shell auto reload attempt', {
                        attempts: currentAttempt,
                    })
                    return
                }
                const nextAttempt = currentAttempt + 1
                sessionStorage.setItem(shellAutoReloadAttemptKey, String(nextAttempt))
                console.warn('webview still loading, reloading shell', {
                    attempt: nextAttempt,
                    maxAttempts: shellAutoReloadMaxAttempts,
                    delayMs: shellAutoReloadDelayMs,
                })
                global.location.reload()
            }, shellAutoReloadDelayMs)
        }

        const clearLocalServerLoadRetryTimer = () => {
            if (!localServerLoadRetryTimer) {
                return
            }
            clearTimeout(localServerLoadRetryTimer)
            localServerLoadRetryTimer = undefined
        }

        const scheduleLocalServerRetry = () => {
            if (localServerLoadRetries >= maxLocalServerLoadRetries) {
                console.error('local webview load failed: max retries reached', {
                    localServerUrl: getCurrentLocalServerUrl(),
                    retries: localServerLoadRetries,
                })
                return
            }

            localServerLoadRetries += 1
            clearLocalServerLoadRetryTimer()
            localServerLoadRetryTimer = setTimeout(async() => {
                const preferredHost = localServerHosts[currentLocalServerHostIndex]
                const hostsToTry = [preferredHost, ...localServerHosts.filter((host) => host !== preferredHost)]
                for (const host of hostsToTry) {
                    const localServerAvailable = await isLocalHttpAvailable(port, 800, host)
                    if (!localServerAvailable) {
                        continue
                    }
                    currentLocalServerHostIndex = localServerHosts.indexOf(host)
                    const activeLocalServerUrl = getCurrentLocalServerUrl()
                    console.warn('local webview retry succeeded', {
                        localServerUrl: activeLocalServerUrl,
                        retries: localServerLoadRetries,
                    })
                    if (!hasSuccessfulUiLoad) {
                        reloadWebviewTo(activeLocalServerUrl)
                    }
                    return
                }
                scheduleLocalServerRetry()
            }, localServerLoadRetryDelayMs)
        }

        global.p3xre.webview.addEventListener('console-message', function (event) {
            console.log('webview console', {
                level: event.level,
                message: event.message,
                line: event.line,
                sourceId: event.sourceId,
            })

            // The app emits this when socket init is complete; treat it as a reliable UI-ready signal.
            if (typeof event.message === 'string' && event.message.includes('p3xr-socket (socket) connected')) {
                hasSuccessfulUiLoad = true
                sessionStorage.removeItem(shellAutoReloadAttemptKey)
                setLoadingState(false)
                clearLocalServerLoadRetryTimer()
                clearShellAutoReloadTimer()
            }
        })

        global.p3xre.webview.addEventListener('did-fail-load', function (event) {
            // Subresource failures should not trigger shell fallback/reload logic.
            if (event && event.isMainFrame === false) {
                return
            }

            setLoadingState(true)
            scheduleShellAutoReload()
            const currentSrc = global.p3xre.webview.src || ''
            const isDevSource = currentSrc.startsWith(devServerUrl)
            const isLocalSource = isLocalServerUrl(currentSrc)

            if (!retryableLoadErrors.has(event.errorCode)) {
                console.error('webview load failed (non-retryable)', {
                    errorCode: event.errorCode,
                    errorDescription: event.errorDescription,
                    validatedURL: event.validatedURL,
                    currentSrc: currentSrc,
                })
                return
            }

            if (isDev && isDevSource) {
                const activeLocalServerUrl = getCurrentLocalServerUrl()
                console.warn('Dev server unavailable, switching webview to', activeLocalServerUrl)
                reloadWebviewTo(activeLocalServerUrl)
                scheduleLocalServerRetry()
                return
            }

            if (isLocalSource) {
                console.warn('local webview load failed, retrying', {
                    errorCode: event.errorCode,
                    errorDescription: event.errorDescription,
                    validatedURL: event.validatedURL,
                    retry: localServerLoadRetries + 1,
                    maxRetries: maxLocalServerLoadRetries,
                })
                scheduleLocalServerRetry()
            }
        })

        global.p3xre.webview.addEventListener('did-finish-load', function () {
            const currentSrc = global.p3xre.webview.src || ''
            const actualUrl = typeof global.p3xre.webview.getURL === 'function'
                ? (global.p3xre.webview.getURL() || '')
                : ''
            const loadedUrl = actualUrl || currentSrc
            const isErrorPage = loadedUrl.startsWith('chrome-error://')
            const isUiLoaded = (isLocalServerUrl(loadedUrl) || loadedUrl.startsWith(devServerUrl)) && !isErrorPage

            if (!isUiLoaded) {
                console.warn('webview finished load but UI is not ready', {
                    currentSrc: currentSrc,
                    loadedUrl: loadedUrl,
                    isErrorPage: isErrorPage,
                })
                return
            }

            hasSuccessfulUiLoad = true
            sessionStorage.removeItem(shellAutoReloadAttemptKey)
            setLoadingState(false)
            clearLocalServerLoadRetryTimer()
            clearShellAutoReloadTimer()
        })

        if (isDev) {
            console.log('development mode')
            const devServerAvailable = await isLocalHttpAvailable(8080)
            global.p3xre.webview.src = devServerAvailable ? devServerUrl : getCurrentLocalServerUrl()
            scheduleShellAutoReload()
            if (!devServerAvailable) {
                console.warn('Dev server http://localhost:8080 is not running, using', getCurrentLocalServerUrl())
                scheduleLocalServerRetry()
            }
        } else {
            global.p3xre.webview.src = getCurrentLocalServerUrl()
            scheduleLocalServerRetry()
            scheduleShellAutoReload()
        }


    } catch (e) {
        console.error(e);
        alert(e.message);
    }
    /*
    const events = [
//        'did-finish-load',
        'did-fail-load',
//        'did-frame-finish-load',
        'did-start-loading',
//        'did-stop-loading',
//        'did-get-response-details',
        'did-get-redirect-request',
        'did-get-redirect-request',
//        'dom-ready',
//        'page-favicon-updated',
        'new-window',
        'will-navigate',
        'did-navigate',
        'did-navigate-in-page',
        'will-prevent-unload',
        'crashed',
        'plugin-crashed',
        'destroyed',
        'before-input-event',
        'devtools-opened',
        'devtools-closed',
        'devtools-focused',
        'certificate-error',
        'select-client-certificate',
        'login',
        'found-in-page',
        'media-started-playing',
        'media-paused',
        'did-change-theme-color',
//        'update-target-url',
        'cursor-changed',
        'context-menu',
        'select-bluetooth-device',
        'paint',
        'devtools-reload-page',
        'will-attach-webview',
        'did-attach-webview',
//        'console-message',
    ]

    events.forEach(event => {
        webview.addEventListener(event, function() {
            console.groupCollapsed(`NGIVR-ELECTRON-EVENT: ${event}`)
            console.log(arguments);
            console.groupEnd();
        })
    })
*/

}

window.addEventListener('load', () => {
    window.p3xreRun()
})