(async () => {
try {
const {ipcRenderer, shell} = require('electron');
const http = require('node:http');
const StoreModule = require('electron-store');
const Store = StoreModule.default || StoreModule;
const uiStateStore = new Store();
let domReady = false
let loadingOverlay
let hasSuccessfulUiLoad = false
const UI_STORAGE_KEY = 'ui-storage'
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.iframe) {
return
}
if (loading) {
global.p3xre.iframe.classList.add('p3xre-webview-loading')
} else {
global.p3xre.iframe.classList.remove('p3xre-webview-loading')
}
}
const getStoredUiState = () => {
const stored = uiStateStore.get(UI_STORAGE_KEY)
if (!stored || typeof stored !== 'object' || Array.isArray(stored)) {
return {}
}
return stored
}
const getUiStorageBootstrapValue = () => {
try {
return Buffer.from(JSON.stringify(getStoredUiState()), 'utf8').toString('base64url')
} catch (e) {
console.warn('p3xre: could not encode iframe UI storage bootstrap', e)
return ''
}
}
const getIframeUrlWithUiState = (baseUrl) => {
try {
const url = new URL(baseUrl)
const bootstrap = getUiStorageBootstrapValue()
if (bootstrap) {
url.searchParams.set('p3xreUiStorage', bootstrap)
}
return url.toString()
} catch (e) {
console.warn('p3xre: could not append iframe UI storage bootstrap to url', e)
return baseUrl
}
}
const syncIframeUiState = () => {
if (!global.p3xre || !global.p3xre.iframe) {
return
}
try {
global.p3xre.iframe.name = JSON.stringify({
p3xreUiStorage: getStoredUiState(),
})
} catch (e) {
console.warn('p3xre: could not sync iframe UI storage bootstrap', e)
}
}
let p3xSetLanguageWaiter
ipcRenderer.on('p3x-set-language', (event, data) => {
const callMe = async () => {
if (domReady === false) {
clearTimeout(p3xSetLanguageWaiter)
p3xSetLanguageWaiter = setTimeout(callMe, 250)
return;
}
const translation = data.translation
const stringsModule = await import(`../../../strings/${translation}/index.mjs`)
global.p3xre.strings = stringsModule.default
global.p3xre.iframe.contentWindow.postMessage({ type: 'p3x-set-language', translation: translation }, '*')
}
callMe()
})
ipcRenderer.on('p3x-menu', function (event, data) {
global.p3xre.iframe.contentWindow.postMessage({ type: 'p3x-menu', action: data.action }, '*')
})
ipcRenderer.on('p3x-new-window', function (event, data) {
shell.openExternal(data.url)
})
// Listen for theme changes from the Angular app (iframe) to sync dark mode on the shell
window.addEventListener('message', (event) => {
const fromIframe = Boolean(global.p3xre?.iframe?.contentWindow) && event.source === global.p3xre.iframe.contentWindow
if (event.data?.type === 'p3x-theme-change') {
if (!fromIframe) {
return
}
document.body.classList.remove('p3xr-theme-dark', 'p3xr-theme-light')
document.body.classList.add(event.data.dark ? 'p3xr-theme-dark' : 'p3xr-theme-light')
return
}
if (event.data?.type === 'p3x-ui-storage-set') {
if (!fromIframe || typeof event.data.key !== 'string' || typeof event.data.value !== 'string') {
return
}
const uiState = getStoredUiState()
uiState[event.data.key] = event.data.value
uiStateStore.set(UI_STORAGE_KEY, uiState)
syncIframeUiState()
// If language changed from the web UI, tell main process to update menus
if (event.data.key === 'p3xr-language') {
ipcRenderer.send('p3x-set-language-from-web', { key: event.data.value })
}
}
})
const pkg = require('../../../../package.json')
const enStrings = await import('../../../strings/en/index.mjs')
global.p3xre = {
iframe: undefined,
pkg: pkg,
strings: enStrings.default
}
const { p3xToast } = await import('./ui.mjs')
global.p3xre.toast = p3xToast
ipcRenderer.on('p3x-action', function (event, data) {
switch (data.action) {
case 'toast':
p3xre.toast.action(data.message)
break;
}
})
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.iframe = document.getElementById("p3xre-redis-ui-electron");
loadingOverlay = document.getElementById('p3xre-loading-overlay')
setLoadingState(true)
syncIframeUiState()
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}`
let currentLocalServerHostIndex = 0
const getCurrentLocalServerUrl = () => getLocalServerUrl(localServerHosts[currentLocalServerHostIndex])
const devServerUrl = 'http://localhost:8080'
const isDev = process.env.hasOwnProperty('NODE_ENV') && process.env.NODE_ENV === 'development'
const maxWaitRetries = 120
const waitRetryDelayMs = 500
// Wait for the local HTTP server to become available before loading the iframe.
const waitForServer = async (targetUrl) => {
for (let i = 0; i < maxWaitRetries; i++) {
for (const host of localServerHosts) {
const available = await isLocalHttpAvailable(port, 800, host)
if (available) {
currentLocalServerHostIndex = localServerHosts.indexOf(host)
return getCurrentLocalServerUrl()
}
}
await new Promise((r) => setTimeout(r, waitRetryDelayMs))
}
// Fall back to the target even if not confirmed available
return targetUrl
}
global.p3xre.iframe.addEventListener('load', function () {
domReady = true
const iframeSrc = global.p3xre.iframe.src || ''
if (iframeSrc === 'about:blank') {
return
}
hasSuccessfulUiLoad = true
setLoadingState(false)
ipcRenderer.send('p3x-iframe-ready')
})
let serverUrl
if (isDev) {
console.log('development mode')
const devServerAvailable = await isLocalHttpAvailable(8080)
if (devServerAvailable) {
serverUrl = devServerUrl
} else {
console.warn('Dev server http://localhost:8080 is not running, waiting for local server...')
serverUrl = await waitForServer(getCurrentLocalServerUrl())
}
} else {
serverUrl = await waitForServer(getCurrentLocalServerUrl())
}
global.p3xre.iframe.src = getIframeUrlWithUiState(serverUrl)
} catch (e) {
console.error(e);
alert(e.message);
}
}
if (document.readyState === 'complete') {
window.p3xreRun()
} else {
window.addEventListener('load', () => {
window.p3xreRun()
})
}
} catch(e) {
console.error('p3xre: fatal error in onload.mjs', e)
}
})();