**The app is called WarpEngine Client.** "Store" named the thing it opens rather than the
thing you run, and the store is a catalog on a site, not a window on your machine. The
window title, the bundle, the packages and the menu entry follow; the repository already
did. The store being driven is named in the side menu, so the bar stopped repeating it as
a badge — the element stays in the page, hidden, because the window check reads it.
**Every title is listed, including the ones this machine cannot install.** They arrive
from the engine with `installable: false` and a reason, and they are drawn dimmed, with an
*unsupported platform* or *no build for this machine* badge, the engine's own sentence
underneath, and nothing to press: a disabled Install would invite a click that can never
work. They get a category of their own — *Not for this machine* — and they are kept out of
the native/hosted categories and counts, because a title with no build has no mode to be
counted under. An engine older than desktop 1.2.0 is unaffected: a missing `installable`
field reads as installable, which is what those engines mean.
**A build can be pointed at another site's registry:**
make dist STORES_API=https://games.example.org/api/stores
BuildConfiguration reads the packaged package.json, where electron-builder's
extraMetadata writes that address, so a client for somebody else's catalog needs no source
change and nothing set on the user's machine. Precedence is runtime environment, then
build, then ours — three audiences, most specific first.
Also: the scrollbars are the window's own, because the platform's light track down the
side menu of a dark window looked like a mistake.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
import path from 'node:path'
|
|
import { BrowserWindow, shell, type BrowserWindowConstructorOptions } from 'electron'
|
|
|
|
const WINDOW_OPTIONS: BrowserWindowConstructorOptions = {
|
|
width: 1040,
|
|
height: 720,
|
|
minWidth: 760,
|
|
minHeight: 520,
|
|
backgroundColor: '#11151c',
|
|
title: 'WarpEngine Client'
|
|
}
|
|
|
|
/**
|
|
* The one window.
|
|
*
|
|
* Locked down deliberately: context isolation on, node integration off, sandbox on,
|
|
* and the page carries a CSP of its own. Nothing here should ever navigate away or
|
|
* open a second window — a link the user clicks goes to their browser instead.
|
|
*/
|
|
export class MainWindowFactory {
|
|
public constructor (private readonly onRendererMessage: (message: string, level: number) => void) {}
|
|
|
|
public createWindow (): BrowserWindow {
|
|
const window = new BrowserWindow({
|
|
...WINDOW_OPTIONS,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, '..', 'preload', 'preload.js'),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
sandbox: true,
|
|
webSecurity: true
|
|
}
|
|
})
|
|
|
|
void window.loadFile(path.join(__dirname, '..', 'renderer', 'index.html'))
|
|
this.forwardRendererDiagnostics(window)
|
|
this.denyNavigation(window)
|
|
return window
|
|
}
|
|
|
|
/** A renderer error is invisible from the main process otherwise. */
|
|
private forwardRendererDiagnostics (window: BrowserWindow): void {
|
|
window.webContents.on('console-message', (details): void => {
|
|
const level = details.level === 'error' ? 3 : details.level === 'warning' ? 2 : 1
|
|
this.onRendererMessage(details.message, level)
|
|
})
|
|
window.webContents.on('render-process-gone', (_event, details): void => {
|
|
this.onRendererMessage(`gone: ${details.reason}`, 3)
|
|
})
|
|
}
|
|
|
|
private denyNavigation (window: BrowserWindow): void {
|
|
window.webContents.setWindowOpenHandler(({ url }: { url: string }): { action: 'deny' } => {
|
|
if (url.startsWith('https://')) void shell.openExternal(url)
|
|
return { action: 'deny' }
|
|
})
|
|
window.webContents.on('will-navigate', (event, url: string): void => {
|
|
if (url === window.webContents.getURL()) return
|
|
event.preventDefault()
|
|
if (url.startsWith('https://')) void shell.openExternal(url)
|
|
})
|
|
}
|
|
}
|