mirror of
https://github.com/Pandipipas/scoreko-electron-dev.git
synced 2026-06-05 21:22:07 +00:00
refactor: update security policies in window creation and enhance loading page CSP
This commit is contained in:
@@ -1,47 +1,41 @@
|
|||||||
# scoreko-electron
|
# Scoreko Desktop
|
||||||
|
|
||||||
Windows desktop installer for Scoreko. The packaged app includes Electron, NodeCG, the compiled `scoreko-dev` bundle, and the production modules needed to run it, so end users do not need Node.js, pnpm, or a cloned repository.
|
This is the Windows desktop wrapper for Scoreko. It bundles Electron, NodeCG, and our custom `scoreko-dev` bundle into a single standalone executable. Users just double-click the installer and everything works—no Node.js, pnpm, or command line required.
|
||||||
|
|
||||||
## Build on a development machine
|
## Local Development
|
||||||
|
|
||||||
From the repository root:
|
If you're working on the app locally, start by installing dependencies at the repository root:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Then from `scoreko-electron-dev`:
|
Then, move into the wrapper folder:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
cd scoreko-electron-dev
|
||||||
npm install
|
npm install
|
||||||
npm run dist:win
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer is written to `scoreko-electron-dev/release/Scoreko-setup-0.1.0.exe`.
|
### Useful Commands
|
||||||
|
|
||||||
## What the build does
|
- `npm run start`: Builds the bundle and launches Electron locally for testing.
|
||||||
|
- `npm run dist:win`: Packages everything and creates the `.exe` Windows installer in the `release/` folder.
|
||||||
|
- `npm run prepare:runtime`: Extracts a fresh NodeCG runtime from the parent bundle (useful if you changed dependencies).
|
||||||
|
- `npm run rebuild:native`: Rebuilds native Node modules (like SQLite) specifically for Electron's V8 engine.
|
||||||
|
- `npm run doctor`: Runs a quick sanity check to verify your local configuration and port availability.
|
||||||
|
|
||||||
- Builds the parent `scoreko-dev` bundle with `pnpm build`.
|
## How it works under the hood
|
||||||
- Creates `scoreko-electron-dev/lib/nodecg` with a small NodeCG runtime.
|
|
||||||
- Installs production runtime modules into that runtime.
|
|
||||||
- Rebuilds `better-sqlite3` for Electron before creating the installer.
|
|
||||||
- Packages the runtime as an Electron extra resource outside the app archive.
|
|
||||||
|
|
||||||
## Runtime behavior
|
When you build the installer, the script automatically compiles the main `scoreko-dev` bundle, provisions a lightweight NodeCG runtime in `lib/nodecg`, and packages it as an external asset alongside the Electron app.
|
||||||
|
|
||||||
On first launch, Scoreko copies the packaged NodeCG runtime to the user's app data folder and then relaunches itself before starting NodeCG. This keeps `cfg`, `db`, and `logs` writable on Windows even when the app is installed under `Program Files`, and avoids transient startup failures caused by freshly copied runtime files.
|
When a user runs Scoreko for the first time, the app copies this NodeCG runtime directly into their local AppData folder. This is a deliberate choice: it ensures that databases, configs, and logs remain fully writable, even if the user installed the app in restricted directories like `Program Files`.
|
||||||
|
|
||||||
## Useful scripts
|
## Auto-Updates via Gitea
|
||||||
|
|
||||||
- `npm run start`: build everything and run Electron locally.
|
Scoreko supports seamless, opt-in updates through your Gitea instance.
|
||||||
- `npm run prepare:runtime`: recreate `lib/nodecg` from the parent bundle.
|
|
||||||
- `npm run rebuild:native`: rebuild NodeCG native modules for Electron.
|
|
||||||
- `npm run dist:win`: create the Windows installer.
|
|
||||||
- `npm run doctor`: check the prepared runtime and the configured port.
|
|
||||||
|
|
||||||
## Updates from Gitea
|
Before building your production installer, check `static/updates.json`:
|
||||||
|
|
||||||
Scoreko can check a Gitea release feed without forcing the user to update. Edit `static/updates.json` before building:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -52,11 +46,15 @@ Scoreko can check a Gitea release feed without forcing the user to update. Edit
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For each release, bump `package.json` version, build with `npm run dist:win`, create a Gitea release tagged like `v0.2.0`, and attach `release/Scoreko-setup-0.2.0.exe`. When Scoreko sees a newer tag, it asks whether to download and install it.
|
**To ship an update:**
|
||||||
|
1. Bump the version in `package.json`.
|
||||||
|
2. Run `npm run dist:win` to generate the new installer.
|
||||||
|
3. Create a new release tag in Gitea (e.g., `v0.2.0`) and attach the `.exe`.
|
||||||
|
4. The app will detect the new version, notify the user, and handle the installation safely.
|
||||||
|
|
||||||
## Configuration
|
## Environment Configuration
|
||||||
|
|
||||||
The defaults match the parent bundle:
|
The app ships with sensible defaults that match our development bundle:
|
||||||
|
|
||||||
- `NODECG_BUNDLE_NAME=scoreko-dev`
|
- `NODECG_BUNDLE_NAME=scoreko-dev`
|
||||||
- `NODECG_PORT=9090`
|
- `NODECG_PORT=9090`
|
||||||
@@ -65,4 +63,4 @@ The defaults match the parent bundle:
|
|||||||
- `SCOREKO_UPDATES_ENABLED=true`
|
- `SCOREKO_UPDATES_ENABLED=true`
|
||||||
- `SCOREKO_UPDATE_ASSET_PATTERN=Scoreko-setup-.*\.exe$`
|
- `SCOREKO_UPDATE_ASSET_PATTERN=Scoreko-setup-.*\.exe$`
|
||||||
|
|
||||||
Copy `.env.example` only if you need local overrides while developing.
|
You only need to mess with `.env.example` if you want to override these values locally while testing.
|
||||||
|
|||||||
+21
-23
@@ -1,29 +1,27 @@
|
|||||||
# Main process architecture
|
# Main Process Architecture
|
||||||
|
|
||||||
## Startup flow
|
This document breaks down how the Electron main process is structured and what happens when the app launches.
|
||||||
|
|
||||||
1. `src/main/main.ts` loads `appConfig` from `config/runtime-config.ts`.
|
## Startup Flow
|
||||||
2. Installs or refreshes the packaged NodeCG runtime in user data when needed (`nodecg/runtime-provisioner.ts`).
|
|
||||||
3. Creates windows (`windows/window-factory.ts`).
|
|
||||||
4. Starts NodeCG with `nodecg/process-manager.ts`.
|
|
||||||
5. Waits for HTTP readiness and shows loading -> main dashboard.
|
|
||||||
6. Checks the configured Gitea latest-release endpoint for optional updates.
|
|
||||||
7. On shutdown, runs a single graceful-stop flow to avoid orphan processes.
|
|
||||||
|
|
||||||
## Main modules
|
When a user opens Scoreko, the app goes through a precise sequence to ensure NodeCG starts reliably:
|
||||||
|
|
||||||
- `config/runtime-config.ts`: read/validate env vars.
|
1. **Configuration:** `src/main/main.ts` kicks things off by loading `appConfig` via `config/runtime-config.ts`.
|
||||||
- `nodecg/runtime-provisioner.ts`: install/refresh the managed runtime in the writable user data folder and report whether it changed.
|
2. **Runtime Provisioning:** The app checks the user's AppData directory. If the packaged NodeCG runtime is missing or outdated, it extracts a fresh copy (`nodecg/runtime-provisioner.ts`).
|
||||||
- `nodecg/process-manager.ts`: start, readiness, and stop for NodeCG; install/permission/port validation.
|
3. **Window Creation:** The initial windows (like the loading screen) are instantiated via `windows/window-factory.ts`.
|
||||||
- `updates/update-manager.ts`: optional Gitea release checks, installer download, and user-controlled install.
|
4. **NodeCG Boot:** `nodecg/process-manager.ts` spawns the NodeCG process in the background.
|
||||||
- `updates/update-utils.ts`: release version comparison and installer asset selection.
|
5. **Readiness Check:** The app continuously polls NodeCG until the HTTP server responds. Once ready, it transitions the UI from the loading screen to the main dashboard.
|
||||||
- `windows/window-factory.ts`: window creation and navigation policy.
|
6. **Update Check:** If updates are enabled, the app checks the configured Gitea endpoint in the background to see if a newer version is available.
|
||||||
- `windows/navigation-security.ts`: internal navigation allowlist and safe external schemes.
|
7. **Graceful Shutdown:** When the user closes the app, it triggers a unified teardown sequence to cleanly kill the NodeCG child process, preventing zombie processes from lingering in the background.
|
||||||
- `errors/error-presenter.ts`: fatal error presentation.
|
|
||||||
- `errors/logger.ts`: structured logging (`info/warn/error/debug`).
|
|
||||||
|
|
||||||
## Principles
|
## Core Modules
|
||||||
|
|
||||||
- Mechanical refactors first.
|
Here is where the heavy lifting happens:
|
||||||
- Incremental hardening with conservative fallback.
|
|
||||||
- Automated validation via `typecheck`, `build`, `test`, `doctor`, `lint`.
|
- **`config/runtime-config.ts`**: Handles environment variables and defaults.
|
||||||
|
- **`nodecg/runtime-provisioner.ts`**: Manages copying the NodeCG runtime out of the read-only Electron package into the writable user data folder.
|
||||||
|
- **`nodecg/process-manager.ts`**: Handles starting, polling, and killing the NodeCG server. It also validates ports and permissions before launching.
|
||||||
|
- **`updates/update-manager.ts`**: Coordinates the Gitea update flow (checking versions, downloading installers, prompting the user).
|
||||||
|
- **`windows/window-factory.ts`**: Centralizes window configuration and security defaults.
|
||||||
|
- **`windows/navigation-security.ts`**: Intercepts navigation events to block unauthorized domains and safely hand off external links (like docs or emails) to the user's default browser.
|
||||||
|
- **`errors/error-presenter.ts` & `errors/logger.ts`**: Manages structured logging (`electron-log`) and displaying the fallback error screen if boot fails.
|
||||||
|
|||||||
+34
-36
@@ -1,45 +1,43 @@
|
|||||||
# Troubleshooting
|
# Troubleshooting Guide
|
||||||
|
|
||||||
## `The packaged NodeCG runtime is incomplete`
|
Here are some common issues you might run into while developing or using the Scoreko desktop app, along with quick fixes.
|
||||||
|
|
||||||
- Run `npm run prepare:runtime` from `scoreko-electron-dev`.
|
## The app says the NodeCG runtime is incomplete
|
||||||
- If the parent bundle is not installed yet, run `pnpm install` from the repository root first.
|
This usually means you haven't bundled the runtime yet.
|
||||||
|
- Run `npm run prepare:runtime` in the `scoreko-electron-dev` folder.
|
||||||
|
- If you haven't even installed the parent bundle, go up to the repository root and run `pnpm install` first.
|
||||||
|
|
||||||
## `NodeCG is present but internal dependencies are missing`
|
## NodeCG is present but internal dependencies are missing
|
||||||
|
This happens if dependencies changed or the initial copy was interrupted.
|
||||||
|
- Re-run `npm run prepare:runtime` to get a fresh copy.
|
||||||
|
- If you're seeing SQLite errors when the app launches, you probably need to run `npm run rebuild:native` to compile it for Electron's V8 engine.
|
||||||
|
|
||||||
- Recreate the runtime with `npm run prepare:runtime`.
|
## "No read/write permissions on NodeCG"
|
||||||
- If native SQLite errors appear during launch, run `npm run rebuild:native` before packaging.
|
In production, Scoreko runs NodeCG out of your AppData folder to ensure it has write access. During local development, it runs directly from the repo.
|
||||||
|
If you see this permission error locally, another process probably has a file locked. Close any zombie Scoreko or Node processes and try `npm run start` again.
|
||||||
|
|
||||||
## `No read/write permissions on NodeCG`
|
## Port 9090 is already in use
|
||||||
|
You have another instance of NodeCG (or another web server) running on port 9090.
|
||||||
|
- Find and kill the process using the port, or change `NODECG_PORT` in your `.env` file to something else (like 9091).
|
||||||
|
- You can use `npm run doctor` to quickly test port availability.
|
||||||
|
|
||||||
- Installed builds run NodeCG from the user's app data folder, so this usually means the local development copy is locked.
|
## Timeout while waiting for NodeCG
|
||||||
- Close any running Scoreko/NodeCG process and run `npm run start` again.
|
The app waits for the NodeCG HTTP server to respond. If it times out:
|
||||||
|
- Check your terminal output. NodeCG might be crashing or hanging on startup due to a bundle error.
|
||||||
|
- If your machine is just slow, you can increase `NODECG_STARTUP_TIMEOUT_MS` in the `.env` file.
|
||||||
|
|
||||||
## `Port <PORT> is already in use`
|
## The app crashes immediately on a fresh install
|
||||||
|
Scoreko copies the runtime to `%AppData%\scoreko\nodecg` and relaunches itself on the very first run.
|
||||||
|
If it gets stuck in a loop or fails immediately:
|
||||||
|
- Check if your antivirus or Windows Search Indexer is aggressively locking the files in AppData as they are being copied.
|
||||||
|
- Try running `npm run rebuild:native` and then repackaging the app with `npm run dist:win`.
|
||||||
|
|
||||||
- Free the port or set `NODECG_PORT` in `.env`.
|
## macOS builds are failing complaining about an icon
|
||||||
- Use `npm run doctor` to validate availability before startup.
|
The `electron-builder` config explicitly looks for a Mac icon at `static/icons/icon.icns`. If you don't have one, generate it and place it there before running the macOS build.
|
||||||
|
|
||||||
## `Timeout while waiting for NodeCG`
|
## Auto-updates aren't triggering
|
||||||
|
If you published a new release on Gitea but the app ignores it:
|
||||||
- Check the Electron/NodeCG output in the terminal.
|
- Double check that `static/updates.json` has `"enabled": true` before you build the installer.
|
||||||
- Increase `NODECG_STARTUP_TIMEOUT_MS` if the environment is slow.
|
- Ensure your `apiUrl` points exactly to the Gitea API: `http://gitea.../api/v1/repos/<owner>/<repo>/releases/latest`.
|
||||||
- Recreate the runtime with `npm run prepare:runtime` if the bundle changed.
|
- The git tag you created (e.g., `v0.2.0`) must be semantically higher than the version currently in your `package.json`.
|
||||||
|
- Make sure the installer `.exe` you uploaded to Gitea actually matches the regex in `assetPattern` (default is `Scoreko-setup-.*\.exe$`).
|
||||||
## First launch after install fails
|
|
||||||
|
|
||||||
- Scoreko relaunches itself automatically after a fresh runtime install.
|
|
||||||
- If it still fails, check whether antivirus or file indexing is locking `%AppData%\scoreko\nodecg`.
|
|
||||||
- Rebuild the installer with `npm run dist:win` after running `npm run rebuild:native`.
|
|
||||||
|
|
||||||
## macOS build fails because of icon
|
|
||||||
|
|
||||||
- The configuration expects `static/icons/icon.icns`.
|
|
||||||
- Create that file before running macOS packaging.
|
|
||||||
|
|
||||||
## Updates do not appear
|
|
||||||
|
|
||||||
- Check that `static/updates.json` has `"enabled": true` before building the installer.
|
|
||||||
- The `apiUrl` must point to Gitea's latest release API: `/api/v1/repos/<owner>/<repo>/releases/latest`.
|
|
||||||
- The release tag must be newer than the installed `package.json` version, for example `v0.2.0`.
|
|
||||||
- The release must include an installer asset matching `assetPattern`, by default `Scoreko-setup-.*\.exe$`.
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function createMainWindow({
|
|||||||
const windowOptions = createWindowOptions({ allowDevTools, appConfig, rootPath, isLoadingWindow: false });
|
const windowOptions = createWindowOptions({ allowDevTools, appConfig, rootPath, isLoadingWindow: false });
|
||||||
const window = new BrowserWindow(windowOptions);
|
const window = new BrowserWindow(windowOptions);
|
||||||
|
|
||||||
denyPermissionsByDefault(window);
|
applySecurityPolicies(window, allowDevTools);
|
||||||
window.setMenuBarVisibility(false);
|
window.setMenuBarVisibility(false);
|
||||||
|
|
||||||
window.webContents.setWindowOpenHandler(({ url }) => {
|
window.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
@@ -65,7 +65,7 @@ export function createLoadingWindow({
|
|||||||
}: Omit<WindowServiceDependencies, "mainDashboardUrl">): BrowserWindow {
|
}: Omit<WindowServiceDependencies, "mainDashboardUrl">): BrowserWindow {
|
||||||
const window = new BrowserWindow(createWindowOptions({ allowDevTools, appConfig, rootPath, isLoadingWindow: true }));
|
const window = new BrowserWindow(createWindowOptions({ allowDevTools, appConfig, rootPath, isLoadingWindow: true }));
|
||||||
|
|
||||||
denyPermissionsByDefault(window);
|
applySecurityPolicies(window, allowDevTools);
|
||||||
|
|
||||||
window.on("page-title-updated", (event) => {
|
window.on("page-title-updated", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -123,8 +123,25 @@ function createWindowOptions({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function denyPermissionsByDefault(window: BrowserWindow): void {
|
function applySecurityPolicies(window: BrowserWindow, allowDevTools: boolean): void {
|
||||||
window.webContents.session.setPermissionRequestHandler((_webContents, _permission, callback) => {
|
window.webContents.session.setPermissionRequestHandler((_webContents, _permission, callback) => {
|
||||||
callback(false);
|
callback(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||||
|
callback({
|
||||||
|
responseHeaders: {
|
||||||
|
...details.responseHeaders,
|
||||||
|
"Content-Security-Policy": [
|
||||||
|
"default-src 'self' 'unsafe-inline' 'unsafe-eval' data: http://localhost:* http://127.0.0.1:*; connect-src * ws: wss:; img-src * data: blob:; media-src * data: blob:; font-src * data:;"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!allowDevTools) {
|
||||||
|
window.webContents.on("devtools-opened", () => {
|
||||||
|
window.webContents.closeDevTools();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline';">
|
||||||
<title>Scoreko</title>
|
<title>Scoreko</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|||||||
Reference in New Issue
Block a user