Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "java-runner-client",
"version": "2.2.2",
"version": "2.2.3",
"description": "Run and manage Java processes with profiles, console I/O, and system tray support",
"main": "dist/main/main.js",
"scripts": {
Expand DownExpand Up@@ -34,7 +34,7 @@
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.0",
"concurrently": "^8.2.0",
"electron": "^35.7.5",
"electron": "^41.1.1",
"electron-builder": "^26.8.1",
"eslint": "^10.1.0",
"postcss": "^8.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Base.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import { ok } from '../core/RestAPI';
import { getAllProfiles, getSettings, saveSettings } from '../core/Store';
import { processManager } from '../core/process/ProcessManager';
import { AppSettings } from '../shared/config/Settings.config';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const BaseRoutes: RouteMap = {
status: defineRoute('status', ({ res }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Log.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { deleteLogFile, getLogFiles, readLogFile } from '../core/process/FileLogger';
import { err, ok } from '../core/RestAPI';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const LogRoutes: RouteMap = {
logs_list: defineRoute('logs_list', ({ res, params }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Process.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { getAllProfiles } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const ProcessRoutes: RouteMap = {
processes_list: defineRoute('processes_list', ({ res }) => ok(res, processManager.getStates())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Profile.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,8 +2,8 @@ import { v4 as uuidv4 } from 'uuid';
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { deleteProfile, getAllProfiles, saveProfile } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/API.types';
import { Profile } from '../shared/types/Profile.types';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';

export const ProfileRoutes: RouteMap = {
profiles_list: defineRoute('profiles_list', ({ res }) => ok(res, getAllProfiles())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/_index.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { RouteKey } from '../shared/config/API.config';
import { BuiltRoute, RouteMap } from '../shared/types/RestAPI.types';
import { BuiltRoute, RouteMap } from '../shared/types/API.types';

import { BaseRoutes } from './Base.routes';
import { LogRoutes } from './Log.routes';
Expand Down
18 changes: 9 additions & 9 deletions src/main/core/IPCController.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,13 +3,13 @@
*
* Defining a route here automatically:
* 1. Registers it on ipcMain (in main.ts via `registerIPC`)
* 2. Exposes it on window.api (in preload.ts via `buildPreloadAPI`)
* 3. Types window.api (via the InferAPI utility below)
* 2. Exposes it on jrc.api (in preload.ts via `buildPreloadAPI`)
* 3. Types jrc.api (via the InferAPI utility below)
*
* Route shapes:
* invoke → renderer calls window.api.foo(...args) → Promise<T>
* send → renderer calls window.api.foo(...args) → void (fire & forget)
* on → renderer calls window.api.onFoo(cb) → unsubscribe fn
* invoke → renderer calls jrc.api.foo(...args) → Promise<T>
* send → renderer calls jrc.api.foo(...args) → void (fire & forget)
* on → renderer calls jrc.api.onFoo(cb) → unsubscribe fn
* main pushes via webContents.send(channel, ...args)
*/

Expand All@@ -33,7 +33,7 @@ type SendRoute = {
type OnRoute = {
type: 'on';
channel: string;
/** Cast a function signature here to type the callback args on window.api.onFoo.
/** Cast a function signature here to type the callback args on jrc.api.onFoo.
* e.g. `args: {} as (profileId: string, line: ConsoleLine) => void`
* Never called at runtime — purely a compile-time phantom. */
args?: (...args: any[]) => void;
Expand All@@ -43,7 +43,7 @@ type Route = InvokeRoute | SendRoute | OnRoute;

export type RouteMap = Record<string, Route>;

// ─── Type inference: RouteMap → window.api shape ──────────────────────────────
// ─── Type inference: RouteMap → jrc.api shape ─────────────────────────────────

type InvokeAPI<R extends InvokeRoute> = R['handler'] extends (
_e: any,
Expand All@@ -60,7 +60,7 @@ type OnAPI<K extends string, R extends OnRoute> = R extends { args: (...args: in
? { [key in `on${Capitalize<K>}`]: (cb: (...args: A) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void };

/** Derives the full window.api type from a RouteMap. */
/** Derives the full jrc.api type from a RouteMap. */
export type InferAPI<M extends RouteMap> = {
[K in keyof M as M[K]['type'] extends 'on' ? never : K]: M[K] extends InvokeRoute
? InvokeAPI<M[K]>
Expand DownExpand Up@@ -89,7 +89,7 @@ export function registerIPC(routes: RouteMap[]): void {
}
}

// ─── Preload: build the window.api object ────────────────────────────────────
// ─── Preload: build the jrc.api object ───────────────────────────────────────

export function buildPreloadAPI(routes: RouteMap[]): Record<string, unknown> {
const api: Record<string, unknown> = {};
Expand Down
6 changes: 3 additions & 3 deletions src/main/core/JRCEnvironment.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,15 +7,15 @@ let env: JRCEnvironment = {
isReady: false,
devMode: null as unknown as JRCEnvironment['devMode'],
type: null as unknown as JRCEnvironment['type'],
startUpSource: null as unknown as JRCEnvironment['startUpSource'],
launchContext: null as unknown as JRCEnvironment['launchContext'],
};

export function loadEnvironment() {
env = {
isReady: true,
devMode: getSettings().devModeEnabled,
type: app.isPackaged ? 'prod' : 'dev',
startUpSource: detectStartupSource(),
launchContext: detectLaunchContext(),
};

broadcast();
Expand All@@ -33,7 +33,7 @@ function broadcast(channel: string = EnvironmentIPC.change.channel) {
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send(channel, env));
}

function detectStartupSource(): JRCEnvironment['startUpSource'] {
function detectLaunchContext(): JRCEnvironment['launchContext'] {
if (!app.isPackaged) return 'development';

const login = app.getLoginItemSettings();
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/RestAPI.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import http from 'http';
import { routes } from '../api/_index';
import { REST_API_CONFIG } from '../shared/config/API.config';
import { CompiledRoute, Params } from '../shared/types/RestAPI.types';
import { CompiledRoute, Params } from '../shared/types/API.types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

Expand Down
3 changes: 1 addition & 2 deletions src/main/core/Store.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { app } from 'electron';
import Store from 'electron-store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { AppSettings } from '../shared/config/Settings.config';
import { AppSettings, DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { Profile } from '../shared/types/Profile.types';

interface StoreSchema {
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/WindowManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ export function createWindow(onClose: (e: Electron.Event) => void): BrowserWindo
backgroundColor: (ALL_THEMES.find((t) => t.id === getSettings().themeId) ?? BUILTIN_THEME)
.colors['base-950'],
icon: getIconImage(),
show: getEnvironment().startUpSource !== 'withSystem',
show: getEnvironment().launchContext !== 'withSystem',
webPreferences: {
preload: path.join(__dirname, '../preload.js'),
contextIsolation: true,
Expand Down
18 changes: 14 additions & 4 deletions src/main/core/process/ProcessManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ import { BrowserWindow } from 'electron';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ProcessIPC } from '../../ipc/Process.ipc';
import { DEFAULT_JAR_RESOLUTION } from '../../shared/config/JarResolution.config';
import { PROTECTED_PROCESS_NAMES } from '../../shared/config/Scanner.config';
import {
ConsoleLine,
Expand All@@ -17,10 +16,15 @@ import { startLogSession, stopLogSession, writeLogLine } from './FileLogger';
import { gracefulStop } from './GracefulStop';

import fs from 'fs';
import { patternToRegex } from '../../shared/config/JarResolution.config';

const SELF_PROCESS_NAME = 'Java Client Runner';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

type SystemMessageType =
| 'start'
| 'stopping'
Expand DownExpand Up@@ -61,7 +65,13 @@ function compareVersionArrays(a: number[], b: number[]): number {
}

function resolveJarPath(profile: Profile): { jarPath: string; error?: string } {
const res = profile.jarResolution ?? DEFAULT_JAR_RESOLUTION;
const res = profile.jarResolution ?? {
enabled: false,
baseDir: '',
pattern: 'app-{version}.jar',
strategy: 'highest-version' as const,
regexOverride: '',
};

if (!res.enabled) {
return { jarPath: profile.jarPath };
Expand Down
26 changes: 25 additions & 1 deletion src/main/ipc/Dev.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
import { BrowserWindow } from 'electron';
import { app, BrowserWindow, shell } from 'electron';
import { readFileSync } from 'fs';
import { join } from 'path';
import type { RouteMap } from '../core/IPCController';
import { getAllProfiles } from '../core/Store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
Expand DownExpand Up@@ -37,4 +39,26 @@ export const DevIPC = {
store.set('settings', DEFAULT_SETTINGS);
},
},

getStoreJson: {
type: 'invoke',
channel: 'dev:getStoreJson',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
try {
return readFileSync(storePath, 'utf-8');
} catch {
return '{}';
}
},
},

openStoreFile: {
type: 'invoke',
channel: 'dev:openStoreFile',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
shell.showItemInFolder(storePath);
},
},
} satisfies RouteMap;
10 changes: 8 additions & 2 deletions src/main/ipc/JarResolution.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
import fs from 'fs';
import path from 'path';
import type { RouteMap } from '../core/IPCController';
import { patternToRegex } from '../shared/config/JarResolution.config';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/JarResolution.types';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/Profile.types';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

function parseVersion(str: string): number[] {
return str
Expand Down
3 changes: 2 additions & 1 deletion src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,8 @@ const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => {
app.on('second-instance', (_event, argv) => {
if (argv.includes('--autostart')) return;
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
Expand Down
5 changes: 3 additions & 2 deletions src/main/preload.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { buildPreloadAPI } from './core/IPCController';
import { allRoutes } from './ipc/_index';
import { EnvironmentIPC } from './ipc/Environment.ipc';

contextBridge.exposeInMainWorld('api', buildPreloadAPI([...allRoutes]));
const api = buildPreloadAPI([...allRoutes]);
const env = buildPreloadAPI([EnvironmentIPC]);

contextBridge.exposeInMainWorld('env', buildPreloadAPI([EnvironmentIPC]));
contextBridge.exposeInMainWorld('jrc', { api, env });
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "java-runner-client",
"version": "2.2.2",
"version": "2.2.3",
"description": "Run and manage Java processes with profiles, console I/O, and system tray support",
"main": "dist/main/main.js",
"scripts": {
Expand DownExpand Up@@ -34,7 +34,7 @@
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.0",
"concurrently": "^8.2.0",
"electron": "^35.7.5",
"electron": "^41.1.1",
"electron-builder": "^26.8.1",
"eslint": "^10.1.0",
"postcss": "^8.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Base.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import { ok } from '../core/RestAPI';
import { getAllProfiles, getSettings, saveSettings } from '../core/Store';
import { processManager } from '../core/process/ProcessManager';
import { AppSettings } from '../shared/config/Settings.config';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const BaseRoutes: RouteMap = {
status: defineRoute('status', ({ res }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Log.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { deleteLogFile, getLogFiles, readLogFile } from '../core/process/FileLogger';
import { err, ok } from '../core/RestAPI';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const LogRoutes: RouteMap = {
logs_list: defineRoute('logs_list', ({ res, params }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Process.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { getAllProfiles } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const ProcessRoutes: RouteMap = {
processes_list: defineRoute('processes_list', ({ res }) => ok(res, processManager.getStates())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Profile.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,8 +2,8 @@ import { v4 as uuidv4 } from 'uuid';
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { deleteProfile, getAllProfiles, saveProfile } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/API.types';
import { Profile } from '../shared/types/Profile.types';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';

export const ProfileRoutes: RouteMap = {
profiles_list: defineRoute('profiles_list', ({ res }) => ok(res, getAllProfiles())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/_index.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { RouteKey } from '../shared/config/API.config';
import { BuiltRoute, RouteMap } from '../shared/types/RestAPI.types';
import { BuiltRoute, RouteMap } from '../shared/types/API.types';

import { BaseRoutes } from './Base.routes';
import { LogRoutes } from './Log.routes';
Expand Down
18 changes: 9 additions & 9 deletions src/main/core/IPCController.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,13 +3,13 @@
*
* Defining a route here automatically:
* 1. Registers it on ipcMain (in main.ts via `registerIPC`)
* 2. Exposes it on window.api (in preload.ts via `buildPreloadAPI`)
* 3. Types window.api (via the InferAPI utility below)
* 2. Exposes it on jrc.api (in preload.ts via `buildPreloadAPI`)
* 3. Types jrc.api (via the InferAPI utility below)
*
* Route shapes:
* invoke → renderer calls window.api.foo(...args) → Promise<T>
* send → renderer calls window.api.foo(...args) → void (fire & forget)
* on → renderer calls window.api.onFoo(cb) → unsubscribe fn
* invoke → renderer calls jrc.api.foo(...args) → Promise<T>
* send → renderer calls jrc.api.foo(...args) → void (fire & forget)
* on → renderer calls jrc.api.onFoo(cb) → unsubscribe fn
* main pushes via webContents.send(channel, ...args)
*/

Expand All@@ -33,7 +33,7 @@ type SendRoute = {
type OnRoute = {
type: 'on';
channel: string;
/** Cast a function signature here to type the callback args on window.api.onFoo.
/** Cast a function signature here to type the callback args on jrc.api.onFoo.
* e.g. `args: {} as (profileId: string, line: ConsoleLine) => void`
* Never called at runtime — purely a compile-time phantom. */
args?: (...args: any[]) => void;
Expand All@@ -43,7 +43,7 @@ type Route = InvokeRoute | SendRoute | OnRoute;

export type RouteMap = Record<string, Route>;

// ─── Type inference: RouteMap → window.api shape ──────────────────────────────
// ─── Type inference: RouteMap → jrc.api shape ─────────────────────────────────

type InvokeAPI<R extends InvokeRoute> = R['handler'] extends (
_e: any,
Expand All@@ -60,7 +60,7 @@ type OnAPI<K extends string, R extends OnRoute> = R extends { args: (...args: in
? { [key in `on${Capitalize<K>}`]: (cb: (...args: A) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void };

/** Derives the full window.api type from a RouteMap. */
/** Derives the full jrc.api type from a RouteMap. */
export type InferAPI<M extends RouteMap> = {
[K in keyof M as M[K]['type'] extends 'on' ? never : K]: M[K] extends InvokeRoute
? InvokeAPI<M[K]>
Expand DownExpand Up@@ -89,7 +89,7 @@ export function registerIPC(routes: RouteMap[]): void {
}
}

// ─── Preload: build the window.api object ────────────────────────────────────
// ─── Preload: build the jrc.api object ───────────────────────────────────────

export function buildPreloadAPI(routes: RouteMap[]): Record<string, unknown> {
const api: Record<string, unknown> = {};
Expand Down
6 changes: 3 additions & 3 deletions src/main/core/JRCEnvironment.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,15 +7,15 @@ let env: JRCEnvironment = {
isReady: false,
devMode: null as unknown as JRCEnvironment['devMode'],
type: null as unknown as JRCEnvironment['type'],
startUpSource: null as unknown as JRCEnvironment['startUpSource'],
launchContext: null as unknown as JRCEnvironment['launchContext'],
};

export function loadEnvironment() {
env = {
isReady: true,
devMode: getSettings().devModeEnabled,
type: app.isPackaged ? 'prod' : 'dev',
startUpSource: detectStartupSource(),
launchContext: detectLaunchContext(),
};

broadcast();
Expand All@@ -33,7 +33,7 @@ function broadcast(channel: string = EnvironmentIPC.change.channel) {
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send(channel, env));
}

function detectStartupSource(): JRCEnvironment['startUpSource'] {
function detectLaunchContext(): JRCEnvironment['launchContext'] {
if (!app.isPackaged) return 'development';

const login = app.getLoginItemSettings();
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/RestAPI.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import http from 'http';
import { routes } from '../api/_index';
import { REST_API_CONFIG } from '../shared/config/API.config';
import { CompiledRoute, Params } from '../shared/types/RestAPI.types';
import { CompiledRoute, Params } from '../shared/types/API.types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

Expand Down
3 changes: 1 addition & 2 deletions src/main/core/Store.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { app } from 'electron';
import Store from 'electron-store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { AppSettings } from '../shared/config/Settings.config';
import { AppSettings, DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { Profile } from '../shared/types/Profile.types';

interface StoreSchema {
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/WindowManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ export function createWindow(onClose: (e: Electron.Event) => void): BrowserWindo
backgroundColor: (ALL_THEMES.find((t) => t.id === getSettings().themeId) ?? BUILTIN_THEME)
.colors['base-950'],
icon: getIconImage(),
show: getEnvironment().startUpSource !== 'withSystem',
show: getEnvironment().launchContext !== 'withSystem',
webPreferences: {
preload: path.join(__dirname, '../preload.js'),
contextIsolation: true,
Expand Down
18 changes: 14 additions & 4 deletions src/main/core/process/ProcessManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ import { BrowserWindow } from 'electron';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ProcessIPC } from '../../ipc/Process.ipc';
import { DEFAULT_JAR_RESOLUTION } from '../../shared/config/JarResolution.config';
import { PROTECTED_PROCESS_NAMES } from '../../shared/config/Scanner.config';
import {
ConsoleLine,
Expand All@@ -17,10 +16,15 @@ import { startLogSession, stopLogSession, writeLogLine } from './FileLogger';
import { gracefulStop } from './GracefulStop';

import fs from 'fs';
import { patternToRegex } from '../../shared/config/JarResolution.config';

const SELF_PROCESS_NAME = 'Java Client Runner';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

type SystemMessageType =
| 'start'
| 'stopping'
Expand DownExpand Up@@ -61,7 +65,13 @@ function compareVersionArrays(a: number[], b: number[]): number {
}

function resolveJarPath(profile: Profile): { jarPath: string; error?: string } {
const res = profile.jarResolution ?? DEFAULT_JAR_RESOLUTION;
const res = profile.jarResolution ?? {
enabled: false,
baseDir: '',
pattern: 'app-{version}.jar',
strategy: 'highest-version' as const,
regexOverride: '',
};

if (!res.enabled) {
return { jarPath: profile.jarPath };
Expand Down
26 changes: 25 additions & 1 deletion src/main/ipc/Dev.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
import { BrowserWindow } from 'electron';
import { app, BrowserWindow, shell } from 'electron';
import { readFileSync } from 'fs';
import { join } from 'path';
import type { RouteMap } from '../core/IPCController';
import { getAllProfiles } from '../core/Store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
Expand DownExpand Up@@ -37,4 +39,26 @@ export const DevIPC = {
store.set('settings', DEFAULT_SETTINGS);
},
},

getStoreJson: {
type: 'invoke',
channel: 'dev:getStoreJson',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
try {
return readFileSync(storePath, 'utf-8');
} catch {
return '{}';
}
},
},

openStoreFile: {
type: 'invoke',
channel: 'dev:openStoreFile',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
shell.showItemInFolder(storePath);
},
},
} satisfies RouteMap;
10 changes: 8 additions & 2 deletions src/main/ipc/JarResolution.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
import fs from 'fs';
import path from 'path';
import type { RouteMap } from '../core/IPCController';
import { patternToRegex } from '../shared/config/JarResolution.config';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/JarResolution.types';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/Profile.types';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

function parseVersion(str: string): number[] {
return str
Expand Down
3 changes: 2 additions & 1 deletion src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,8 @@ const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => {
app.on('second-instance', (_event, argv) => {
if (argv.includes('--autostart')) return;
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
Expand Down
5 changes: 3 additions & 2 deletions src/main/preload.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { buildPreloadAPI } from './core/IPCController';
import { allRoutes } from './ipc/_index';
import { EnvironmentIPC } from './ipc/Environment.ipc';

contextBridge.exposeInMainWorld('api', buildPreloadAPI([...allRoutes]));
const api = buildPreloadAPI([...allRoutes]);
const env = buildPreloadAPI([EnvironmentIPC]);

contextBridge.exposeInMainWorld('env', buildPreloadAPI([EnvironmentIPC]));
contextBridge.exposeInMainWorld('jrc', { api, env });
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "java-runner-client",
"version": "2.2.2",
"version": "2.2.3",
"description": "Run and manage Java processes with profiles, console I/O, and system tray support",
"main": "dist/main/main.js",
"scripts": {
Expand DownExpand Up@@ -34,7 +34,7 @@
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.0",
"concurrently": "^8.2.0",
"electron": "^35.7.5",
"electron": "^41.1.1",
"electron-builder": "^26.8.1",
"eslint": "^10.1.0",
"postcss": "^8.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Base.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import { ok } from '../core/RestAPI';
import { getAllProfiles, getSettings, saveSettings } from '../core/Store';
import { processManager } from '../core/process/ProcessManager';
import { AppSettings } from '../shared/config/Settings.config';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const BaseRoutes: RouteMap = {
status: defineRoute('status', ({ res }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Log.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { deleteLogFile, getLogFiles, readLogFile } from '../core/process/FileLogger';
import { err, ok } from '../core/RestAPI';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const LogRoutes: RouteMap = {
logs_list: defineRoute('logs_list', ({ res, params }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Process.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { getAllProfiles } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const ProcessRoutes: RouteMap = {
processes_list: defineRoute('processes_list', ({ res }) => ok(res, processManager.getStates())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Profile.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,8 +2,8 @@ import { v4 as uuidv4 } from 'uuid';
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { deleteProfile, getAllProfiles, saveProfile } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/API.types';
import { Profile } from '../shared/types/Profile.types';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';

export const ProfileRoutes: RouteMap = {
profiles_list: defineRoute('profiles_list', ({ res }) => ok(res, getAllProfiles())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/_index.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { RouteKey } from '../shared/config/API.config';
import { BuiltRoute, RouteMap } from '../shared/types/RestAPI.types';
import { BuiltRoute, RouteMap } from '../shared/types/API.types';

import { BaseRoutes } from './Base.routes';
import { LogRoutes } from './Log.routes';
Expand Down
18 changes: 9 additions & 9 deletions src/main/core/IPCController.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,13 +3,13 @@
*
* Defining a route here automatically:
* 1. Registers it on ipcMain (in main.ts via `registerIPC`)
* 2. Exposes it on window.api (in preload.ts via `buildPreloadAPI`)
* 3. Types window.api (via the InferAPI utility below)
* 2. Exposes it on jrc.api (in preload.ts via `buildPreloadAPI`)
* 3. Types jrc.api (via the InferAPI utility below)
*
* Route shapes:
* invoke → renderer calls window.api.foo(...args) → Promise<T>
* send → renderer calls window.api.foo(...args) → void (fire & forget)
* on → renderer calls window.api.onFoo(cb) → unsubscribe fn
* invoke → renderer calls jrc.api.foo(...args) → Promise<T>
* send → renderer calls jrc.api.foo(...args) → void (fire & forget)
* on → renderer calls jrc.api.onFoo(cb) → unsubscribe fn
* main pushes via webContents.send(channel, ...args)
*/

Expand All@@ -33,7 +33,7 @@ type SendRoute = {
type OnRoute = {
type: 'on';
channel: string;
/** Cast a function signature here to type the callback args on window.api.onFoo.
/** Cast a function signature here to type the callback args on jrc.api.onFoo.
* e.g. `args: {} as (profileId: string, line: ConsoleLine) => void`
* Never called at runtime — purely a compile-time phantom. */
args?: (...args: any[]) => void;
Expand All@@ -43,7 +43,7 @@ type Route = InvokeRoute | SendRoute | OnRoute;

export type RouteMap = Record<string, Route>;

// ─── Type inference: RouteMap → window.api shape ──────────────────────────────
// ─── Type inference: RouteMap → jrc.api shape ─────────────────────────────────

type InvokeAPI<R extends InvokeRoute> = R['handler'] extends (
_e: any,
Expand All@@ -60,7 +60,7 @@ type OnAPI<K extends string, R extends OnRoute> = R extends { args: (...args: in
? { [key in `on${Capitalize<K>}`]: (cb: (...args: A) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void };

/** Derives the full window.api type from a RouteMap. */
/** Derives the full jrc.api type from a RouteMap. */
export type InferAPI<M extends RouteMap> = {
[K in keyof M as M[K]['type'] extends 'on' ? never : K]: M[K] extends InvokeRoute
? InvokeAPI<M[K]>
Expand DownExpand Up@@ -89,7 +89,7 @@ export function registerIPC(routes: RouteMap[]): void {
}
}

// ─── Preload: build the window.api object ────────────────────────────────────
// ─── Preload: build the jrc.api object ───────────────────────────────────────

export function buildPreloadAPI(routes: RouteMap[]): Record<string, unknown> {
const api: Record<string, unknown> = {};
Expand Down
6 changes: 3 additions & 3 deletions src/main/core/JRCEnvironment.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,15 +7,15 @@ let env: JRCEnvironment = {
isReady: false,
devMode: null as unknown as JRCEnvironment['devMode'],
type: null as unknown as JRCEnvironment['type'],
startUpSource: null as unknown as JRCEnvironment['startUpSource'],
launchContext: null as unknown as JRCEnvironment['launchContext'],
};

export function loadEnvironment() {
env = {
isReady: true,
devMode: getSettings().devModeEnabled,
type: app.isPackaged ? 'prod' : 'dev',
startUpSource: detectStartupSource(),
launchContext: detectLaunchContext(),
};

broadcast();
Expand All@@ -33,7 +33,7 @@ function broadcast(channel: string = EnvironmentIPC.change.channel) {
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send(channel, env));
}

function detectStartupSource(): JRCEnvironment['startUpSource'] {
function detectLaunchContext(): JRCEnvironment['launchContext'] {
if (!app.isPackaged) return 'development';

const login = app.getLoginItemSettings();
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/RestAPI.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import http from 'http';
import { routes } from '../api/_index';
import { REST_API_CONFIG } from '../shared/config/API.config';
import { CompiledRoute, Params } from '../shared/types/RestAPI.types';
import { CompiledRoute, Params } from '../shared/types/API.types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

Expand Down
3 changes: 1 addition & 2 deletions src/main/core/Store.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { app } from 'electron';
import Store from 'electron-store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { AppSettings } from '../shared/config/Settings.config';
import { AppSettings, DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { Profile } from '../shared/types/Profile.types';

interface StoreSchema {
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/WindowManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ export function createWindow(onClose: (e: Electron.Event) => void): BrowserWindo
backgroundColor: (ALL_THEMES.find((t) => t.id === getSettings().themeId) ?? BUILTIN_THEME)
.colors['base-950'],
icon: getIconImage(),
show: getEnvironment().startUpSource !== 'withSystem',
show: getEnvironment().launchContext !== 'withSystem',
webPreferences: {
preload: path.join(__dirname, '../preload.js'),
contextIsolation: true,
Expand Down
18 changes: 14 additions & 4 deletions src/main/core/process/ProcessManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ import { BrowserWindow } from 'electron';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ProcessIPC } from '../../ipc/Process.ipc';
import { DEFAULT_JAR_RESOLUTION } from '../../shared/config/JarResolution.config';
import { PROTECTED_PROCESS_NAMES } from '../../shared/config/Scanner.config';
import {
ConsoleLine,
Expand All@@ -17,10 +16,15 @@ import { startLogSession, stopLogSession, writeLogLine } from './FileLogger';
import { gracefulStop } from './GracefulStop';

import fs from 'fs';
import { patternToRegex } from '../../shared/config/JarResolution.config';

const SELF_PROCESS_NAME = 'Java Client Runner';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

type SystemMessageType =
| 'start'
| 'stopping'
Expand DownExpand Up@@ -61,7 +65,13 @@ function compareVersionArrays(a: number[], b: number[]): number {
}

function resolveJarPath(profile: Profile): { jarPath: string; error?: string } {
const res = profile.jarResolution ?? DEFAULT_JAR_RESOLUTION;
const res = profile.jarResolution ?? {
enabled: false,
baseDir: '',
pattern: 'app-{version}.jar',
strategy: 'highest-version' as const,
regexOverride: '',
};

if (!res.enabled) {
return { jarPath: profile.jarPath };
Expand Down
26 changes: 25 additions & 1 deletion src/main/ipc/Dev.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
import { BrowserWindow } from 'electron';
import { app, BrowserWindow, shell } from 'electron';
import { readFileSync } from 'fs';
import { join } from 'path';
import type { RouteMap } from '../core/IPCController';
import { getAllProfiles } from '../core/Store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
Expand DownExpand Up@@ -37,4 +39,26 @@ export const DevIPC = {
store.set('settings', DEFAULT_SETTINGS);
},
},

getStoreJson: {
type: 'invoke',
channel: 'dev:getStoreJson',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
try {
return readFileSync(storePath, 'utf-8');
} catch {
return '{}';
}
},
},

openStoreFile: {
type: 'invoke',
channel: 'dev:openStoreFile',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
shell.showItemInFolder(storePath);
},
},
} satisfies RouteMap;
10 changes: 8 additions & 2 deletions src/main/ipc/JarResolution.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
import fs from 'fs';
import path from 'path';
import type { RouteMap } from '../core/IPCController';
import { patternToRegex } from '../shared/config/JarResolution.config';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/JarResolution.types';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/Profile.types';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

function parseVersion(str: string): number[] {
return str
Expand Down
3 changes: 2 additions & 1 deletion src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,8 @@ const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => {
app.on('second-instance', (_event, argv) => {
if (argv.includes('--autostart')) return;
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
Expand Down
5 changes: 3 additions & 2 deletions src/main/preload.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { buildPreloadAPI } from './core/IPCController';
import { allRoutes } from './ipc/_index';
import { EnvironmentIPC } from './ipc/Environment.ipc';

contextBridge.exposeInMainWorld('api', buildPreloadAPI([...allRoutes]));
const api = buildPreloadAPI([...allRoutes]);
const env = buildPreloadAPI([EnvironmentIPC]);

contextBridge.exposeInMainWorld('env', buildPreloadAPI([EnvironmentIPC]));
contextBridge.exposeInMainWorld('jrc', { api, env });
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "java-runner-client",
"version": "2.2.2",
"version": "2.2.3",
"description": "Run and manage Java processes with profiles, console I/O, and system tray support",
"main": "dist/main/main.js",
"scripts": {
Expand DownExpand Up@@ -34,7 +34,7 @@
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.0",
"concurrently": "^8.2.0",
"electron": "^35.7.5",
"electron": "^41.1.1",
"electron-builder": "^26.8.1",
"eslint": "^10.1.0",
"postcss": "^8.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Base.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import { ok } from '../core/RestAPI';
import { getAllProfiles, getSettings, saveSettings } from '../core/Store';
import { processManager } from '../core/process/ProcessManager';
import { AppSettings } from '../shared/config/Settings.config';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const BaseRoutes: RouteMap = {
status: defineRoute('status', ({ res }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Log.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { deleteLogFile, getLogFiles, readLogFile } from '../core/process/FileLogger';
import { err, ok } from '../core/RestAPI';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const LogRoutes: RouteMap = {
logs_list: defineRoute('logs_list', ({ res, params }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Process.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { getAllProfiles } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const ProcessRoutes: RouteMap = {
processes_list: defineRoute('processes_list', ({ res }) => ok(res, processManager.getStates())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Profile.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,8 +2,8 @@ import { v4 as uuidv4 } from 'uuid';
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { deleteProfile, getAllProfiles, saveProfile } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/API.types';
import { Profile } from '../shared/types/Profile.types';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';

export const ProfileRoutes: RouteMap = {
profiles_list: defineRoute('profiles_list', ({ res }) => ok(res, getAllProfiles())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/_index.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { RouteKey } from '../shared/config/API.config';
import { BuiltRoute, RouteMap } from '../shared/types/RestAPI.types';
import { BuiltRoute, RouteMap } from '../shared/types/API.types';

import { BaseRoutes } from './Base.routes';
import { LogRoutes } from './Log.routes';
Expand Down
18 changes: 9 additions & 9 deletions src/main/core/IPCController.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,13 +3,13 @@
*
* Defining a route here automatically:
* 1. Registers it on ipcMain (in main.ts via `registerIPC`)
* 2. Exposes it on window.api (in preload.ts via `buildPreloadAPI`)
* 3. Types window.api (via the InferAPI utility below)
* 2. Exposes it on jrc.api (in preload.ts via `buildPreloadAPI`)
* 3. Types jrc.api (via the InferAPI utility below)
*
* Route shapes:
* invoke → renderer calls window.api.foo(...args) → Promise<T>
* send → renderer calls window.api.foo(...args) → void (fire & forget)
* on → renderer calls window.api.onFoo(cb) → unsubscribe fn
* invoke → renderer calls jrc.api.foo(...args) → Promise<T>
* send → renderer calls jrc.api.foo(...args) → void (fire & forget)
* on → renderer calls jrc.api.onFoo(cb) → unsubscribe fn
* main pushes via webContents.send(channel, ...args)
*/

Expand All@@ -33,7 +33,7 @@ type SendRoute = {
type OnRoute = {
type: 'on';
channel: string;
/** Cast a function signature here to type the callback args on window.api.onFoo.
/** Cast a function signature here to type the callback args on jrc.api.onFoo.
* e.g. `args: {} as (profileId: string, line: ConsoleLine) => void`
* Never called at runtime — purely a compile-time phantom. */
args?: (...args: any[]) => void;
Expand All@@ -43,7 +43,7 @@ type Route = InvokeRoute | SendRoute | OnRoute;

export type RouteMap = Record<string, Route>;

// ─── Type inference: RouteMap → window.api shape ──────────────────────────────
// ─── Type inference: RouteMap → jrc.api shape ─────────────────────────────────

type InvokeAPI<R extends InvokeRoute> = R['handler'] extends (
_e: any,
Expand All@@ -60,7 +60,7 @@ type OnAPI<K extends string, R extends OnRoute> = R extends { args: (...args: in
? { [key in `on${Capitalize<K>}`]: (cb: (...args: A) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void };

/** Derives the full window.api type from a RouteMap. */
/** Derives the full jrc.api type from a RouteMap. */
export type InferAPI<M extends RouteMap> = {
[K in keyof M as M[K]['type'] extends 'on' ? never : K]: M[K] extends InvokeRoute
? InvokeAPI<M[K]>
Expand DownExpand Up@@ -89,7 +89,7 @@ export function registerIPC(routes: RouteMap[]): void {
}
}

// ─── Preload: build the window.api object ────────────────────────────────────
// ─── Preload: build the jrc.api object ───────────────────────────────────────

export function buildPreloadAPI(routes: RouteMap[]): Record<string, unknown> {
const api: Record<string, unknown> = {};
Expand Down
6 changes: 3 additions & 3 deletions src/main/core/JRCEnvironment.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,15 +7,15 @@ let env: JRCEnvironment = {
isReady: false,
devMode: null as unknown as JRCEnvironment['devMode'],
type: null as unknown as JRCEnvironment['type'],
startUpSource: null as unknown as JRCEnvironment['startUpSource'],
launchContext: null as unknown as JRCEnvironment['launchContext'],
};

export function loadEnvironment() {
env = {
isReady: true,
devMode: getSettings().devModeEnabled,
type: app.isPackaged ? 'prod' : 'dev',
startUpSource: detectStartupSource(),
launchContext: detectLaunchContext(),
};

broadcast();
Expand All@@ -33,7 +33,7 @@ function broadcast(channel: string = EnvironmentIPC.change.channel) {
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send(channel, env));
}

function detectStartupSource(): JRCEnvironment['startUpSource'] {
function detectLaunchContext(): JRCEnvironment['launchContext'] {
if (!app.isPackaged) return 'development';

const login = app.getLoginItemSettings();
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/RestAPI.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import http from 'http';
import { routes } from '../api/_index';
import { REST_API_CONFIG } from '../shared/config/API.config';
import { CompiledRoute, Params } from '../shared/types/RestAPI.types';
import { CompiledRoute, Params } from '../shared/types/API.types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

Expand Down
3 changes: 1 addition & 2 deletions src/main/core/Store.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { app } from 'electron';
import Store from 'electron-store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { AppSettings } from '../shared/config/Settings.config';
import { AppSettings, DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { Profile } from '../shared/types/Profile.types';

interface StoreSchema {
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/WindowManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ export function createWindow(onClose: (e: Electron.Event) => void): BrowserWindo
backgroundColor: (ALL_THEMES.find((t) => t.id === getSettings().themeId) ?? BUILTIN_THEME)
.colors['base-950'],
icon: getIconImage(),
show: getEnvironment().startUpSource !== 'withSystem',
show: getEnvironment().launchContext !== 'withSystem',
webPreferences: {
preload: path.join(__dirname, '../preload.js'),
contextIsolation: true,
Expand Down
18 changes: 14 additions & 4 deletions src/main/core/process/ProcessManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ import { BrowserWindow } from 'electron';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ProcessIPC } from '../../ipc/Process.ipc';
import { DEFAULT_JAR_RESOLUTION } from '../../shared/config/JarResolution.config';
import { PROTECTED_PROCESS_NAMES } from '../../shared/config/Scanner.config';
import {
ConsoleLine,
Expand All@@ -17,10 +16,15 @@ import { startLogSession, stopLogSession, writeLogLine } from './FileLogger';
import { gracefulStop } from './GracefulStop';

import fs from 'fs';
import { patternToRegex } from '../../shared/config/JarResolution.config';

const SELF_PROCESS_NAME = 'Java Client Runner';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

type SystemMessageType =
| 'start'
| 'stopping'
Expand DownExpand Up@@ -61,7 +65,13 @@ function compareVersionArrays(a: number[], b: number[]): number {
}

function resolveJarPath(profile: Profile): { jarPath: string; error?: string } {
const res = profile.jarResolution ?? DEFAULT_JAR_RESOLUTION;
const res = profile.jarResolution ?? {
enabled: false,
baseDir: '',
pattern: 'app-{version}.jar',
strategy: 'highest-version' as const,
regexOverride: '',
};

if (!res.enabled) {
return { jarPath: profile.jarPath };
Expand Down
26 changes: 25 additions & 1 deletion src/main/ipc/Dev.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
import { BrowserWindow } from 'electron';
import { app, BrowserWindow, shell } from 'electron';
import { readFileSync } from 'fs';
import { join } from 'path';
import type { RouteMap } from '../core/IPCController';
import { getAllProfiles } from '../core/Store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
Expand DownExpand Up@@ -37,4 +39,26 @@ export const DevIPC = {
store.set('settings', DEFAULT_SETTINGS);
},
},

getStoreJson: {
type: 'invoke',
channel: 'dev:getStoreJson',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
try {
return readFileSync(storePath, 'utf-8');
} catch {
return '{}';
}
},
},

openStoreFile: {
type: 'invoke',
channel: 'dev:openStoreFile',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
shell.showItemInFolder(storePath);
},
},
} satisfies RouteMap;
10 changes: 8 additions & 2 deletions src/main/ipc/JarResolution.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
import fs from 'fs';
import path from 'path';
import type { RouteMap } from '../core/IPCController';
import { patternToRegex } from '../shared/config/JarResolution.config';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/JarResolution.types';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/Profile.types';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

function parseVersion(str: string): number[] {
return str
Expand Down
3 changes: 2 additions & 1 deletion src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,8 @@ const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => {
app.on('second-instance', (_event, argv) => {
if (argv.includes('--autostart')) return;
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
Expand Down
5 changes: 3 additions & 2 deletions src/main/preload.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { buildPreloadAPI } from './core/IPCController';
import { allRoutes } from './ipc/_index';
import { EnvironmentIPC } from './ipc/Environment.ipc';

contextBridge.exposeInMainWorld('api', buildPreloadAPI([...allRoutes]));
const api = buildPreloadAPI([...allRoutes]);
const env = buildPreloadAPI([EnvironmentIPC]);

contextBridge.exposeInMainWorld('env', buildPreloadAPI([EnvironmentIPC]));
contextBridge.exposeInMainWorld('jrc', { api, env });
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "java-runner-client",
"version": "2.2.2",
"version": "2.2.3",
"description": "Run and manage Java processes with profiles, console I/O, and system tray support",
"main": "dist/main/main.js",
"scripts": {
Expand DownExpand Up@@ -34,7 +34,7 @@
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.0",
"concurrently": "^8.2.0",
"electron": "^35.7.5",
"electron": "^41.1.1",
"electron-builder": "^26.8.1",
"eslint": "^10.1.0",
"postcss": "^8.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Base.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import { ok } from '../core/RestAPI';
import { getAllProfiles, getSettings, saveSettings } from '../core/Store';
import { processManager } from '../core/process/ProcessManager';
import { AppSettings } from '../shared/config/Settings.config';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const BaseRoutes: RouteMap = {
status: defineRoute('status', ({ res }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Log.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { deleteLogFile, getLogFiles, readLogFile } from '../core/process/FileLogger';
import { err, ok } from '../core/RestAPI';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const LogRoutes: RouteMap = {
logs_list: defineRoute('logs_list', ({ res, params }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Process.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { getAllProfiles } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const ProcessRoutes: RouteMap = {
processes_list: defineRoute('processes_list', ({ res }) => ok(res, processManager.getStates())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Profile.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,8 +2,8 @@ import { v4 as uuidv4 } from 'uuid';
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { deleteProfile, getAllProfiles, saveProfile } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/API.types';
import { Profile } from '../shared/types/Profile.types';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';

export const ProfileRoutes: RouteMap = {
profiles_list: defineRoute('profiles_list', ({ res }) => ok(res, getAllProfiles())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/_index.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { RouteKey } from '../shared/config/API.config';
import { BuiltRoute, RouteMap } from '../shared/types/RestAPI.types';
import { BuiltRoute, RouteMap } from '../shared/types/API.types';

import { BaseRoutes } from './Base.routes';
import { LogRoutes } from './Log.routes';
Expand Down
18 changes: 9 additions & 9 deletions src/main/core/IPCController.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,13 +3,13 @@
*
* Defining a route here automatically:
* 1. Registers it on ipcMain (in main.ts via `registerIPC`)
* 2. Exposes it on window.api (in preload.ts via `buildPreloadAPI`)
* 3. Types window.api (via the InferAPI utility below)
* 2. Exposes it on jrc.api (in preload.ts via `buildPreloadAPI`)
* 3. Types jrc.api (via the InferAPI utility below)
*
* Route shapes:
* invoke → renderer calls window.api.foo(...args) → Promise<T>
* send → renderer calls window.api.foo(...args) → void (fire & forget)
* on → renderer calls window.api.onFoo(cb) → unsubscribe fn
* invoke → renderer calls jrc.api.foo(...args) → Promise<T>
* send → renderer calls jrc.api.foo(...args) → void (fire & forget)
* on → renderer calls jrc.api.onFoo(cb) → unsubscribe fn
* main pushes via webContents.send(channel, ...args)
*/

Expand All@@ -33,7 +33,7 @@ type SendRoute = {
type OnRoute = {
type: 'on';
channel: string;
/** Cast a function signature here to type the callback args on window.api.onFoo.
/** Cast a function signature here to type the callback args on jrc.api.onFoo.
* e.g. `args: {} as (profileId: string, line: ConsoleLine) => void`
* Never called at runtime — purely a compile-time phantom. */
args?: (...args: any[]) => void;
Expand All@@ -43,7 +43,7 @@ type Route = InvokeRoute | SendRoute | OnRoute;

export type RouteMap = Record<string, Route>;

// ─── Type inference: RouteMap → window.api shape ──────────────────────────────
// ─── Type inference: RouteMap → jrc.api shape ─────────────────────────────────

type InvokeAPI<R extends InvokeRoute> = R['handler'] extends (
_e: any,
Expand All@@ -60,7 +60,7 @@ type OnAPI<K extends string, R extends OnRoute> = R extends { args: (...args: in
? { [key in `on${Capitalize<K>}`]: (cb: (...args: A) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void };

/** Derives the full window.api type from a RouteMap. */
/** Derives the full jrc.api type from a RouteMap. */
export type InferAPI<M extends RouteMap> = {
[K in keyof M as M[K]['type'] extends 'on' ? never : K]: M[K] extends InvokeRoute
? InvokeAPI<M[K]>
Expand DownExpand Up@@ -89,7 +89,7 @@ export function registerIPC(routes: RouteMap[]): void {
}
}

// ─── Preload: build the window.api object ────────────────────────────────────
// ─── Preload: build the jrc.api object ───────────────────────────────────────

export function buildPreloadAPI(routes: RouteMap[]): Record<string, unknown> {
const api: Record<string, unknown> = {};
Expand Down
6 changes: 3 additions & 3 deletions src/main/core/JRCEnvironment.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,15 +7,15 @@ let env: JRCEnvironment = {
isReady: false,
devMode: null as unknown as JRCEnvironment['devMode'],
type: null as unknown as JRCEnvironment['type'],
startUpSource: null as unknown as JRCEnvironment['startUpSource'],
launchContext: null as unknown as JRCEnvironment['launchContext'],
};

export function loadEnvironment() {
env = {
isReady: true,
devMode: getSettings().devModeEnabled,
type: app.isPackaged ? 'prod' : 'dev',
startUpSource: detectStartupSource(),
launchContext: detectLaunchContext(),
};

broadcast();
Expand All@@ -33,7 +33,7 @@ function broadcast(channel: string = EnvironmentIPC.change.channel) {
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send(channel, env));
}

function detectStartupSource(): JRCEnvironment['startUpSource'] {
function detectLaunchContext(): JRCEnvironment['launchContext'] {
if (!app.isPackaged) return 'development';

const login = app.getLoginItemSettings();
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/RestAPI.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import http from 'http';
import { routes } from '../api/_index';
import { REST_API_CONFIG } from '../shared/config/API.config';
import { CompiledRoute, Params } from '../shared/types/RestAPI.types';
import { CompiledRoute, Params } from '../shared/types/API.types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

Expand Down
3 changes: 1 addition & 2 deletions src/main/core/Store.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { app } from 'electron';
import Store from 'electron-store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { AppSettings } from '../shared/config/Settings.config';
import { AppSettings, DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { Profile } from '../shared/types/Profile.types';

interface StoreSchema {
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/WindowManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ export function createWindow(onClose: (e: Electron.Event) => void): BrowserWindo
backgroundColor: (ALL_THEMES.find((t) => t.id === getSettings().themeId) ?? BUILTIN_THEME)
.colors['base-950'],
icon: getIconImage(),
show: getEnvironment().startUpSource !== 'withSystem',
show: getEnvironment().launchContext !== 'withSystem',
webPreferences: {
preload: path.join(__dirname, '../preload.js'),
contextIsolation: true,
Expand Down
18 changes: 14 additions & 4 deletions src/main/core/process/ProcessManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ import { BrowserWindow } from 'electron';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ProcessIPC } from '../../ipc/Process.ipc';
import { DEFAULT_JAR_RESOLUTION } from '../../shared/config/JarResolution.config';
import { PROTECTED_PROCESS_NAMES } from '../../shared/config/Scanner.config';
import {
ConsoleLine,
Expand All@@ -17,10 +16,15 @@ import { startLogSession, stopLogSession, writeLogLine } from './FileLogger';
import { gracefulStop } from './GracefulStop';

import fs from 'fs';
import { patternToRegex } from '../../shared/config/JarResolution.config';

const SELF_PROCESS_NAME = 'Java Client Runner';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

type SystemMessageType =
| 'start'
| 'stopping'
Expand DownExpand Up@@ -61,7 +65,13 @@ function compareVersionArrays(a: number[], b: number[]): number {
}

function resolveJarPath(profile: Profile): { jarPath: string; error?: string } {
const res = profile.jarResolution ?? DEFAULT_JAR_RESOLUTION;
const res = profile.jarResolution ?? {
enabled: false,
baseDir: '',
pattern: 'app-{version}.jar',
strategy: 'highest-version' as const,
regexOverride: '',
};

if (!res.enabled) {
return { jarPath: profile.jarPath };
Expand Down
26 changes: 25 additions & 1 deletion src/main/ipc/Dev.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
import { BrowserWindow } from 'electron';
import { app, BrowserWindow, shell } from 'electron';
import { readFileSync } from 'fs';
import { join } from 'path';
import type { RouteMap } from '../core/IPCController';
import { getAllProfiles } from '../core/Store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
Expand DownExpand Up@@ -37,4 +39,26 @@ export const DevIPC = {
store.set('settings', DEFAULT_SETTINGS);
},
},

getStoreJson: {
type: 'invoke',
channel: 'dev:getStoreJson',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
try {
return readFileSync(storePath, 'utf-8');
} catch {
return '{}';
}
},
},

openStoreFile: {
type: 'invoke',
channel: 'dev:openStoreFile',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
shell.showItemInFolder(storePath);
},
},
} satisfies RouteMap;
10 changes: 8 additions & 2 deletions src/main/ipc/JarResolution.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
import fs from 'fs';
import path from 'path';
import type { RouteMap } from '../core/IPCController';
import { patternToRegex } from '../shared/config/JarResolution.config';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/JarResolution.types';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/Profile.types';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

function parseVersion(str: string): number[] {
return str
Expand Down
3 changes: 2 additions & 1 deletion src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,8 @@ const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => {
app.on('second-instance', (_event, argv) => {
if (argv.includes('--autostart')) return;
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
Expand Down
5 changes: 3 additions & 2 deletions src/main/preload.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { buildPreloadAPI } from './core/IPCController';
import { allRoutes } from './ipc/_index';
import { EnvironmentIPC } from './ipc/Environment.ipc';

contextBridge.exposeInMainWorld('api', buildPreloadAPI([...allRoutes]));
const api = buildPreloadAPI([...allRoutes]);
const env = buildPreloadAPI([EnvironmentIPC]);

contextBridge.exposeInMainWorld('env', buildPreloadAPI([EnvironmentIPC]));
contextBridge.exposeInMainWorld('jrc', { api, env });
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "java-runner-client",
"version": "2.2.2",
"version": "2.2.3",
"description": "Run and manage Java processes with profiles, console I/O, and system tray support",
"main": "dist/main/main.js",
"scripts": {
Expand DownExpand Up@@ -34,7 +34,7 @@
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.0",
"concurrently": "^8.2.0",
"electron": "^35.7.5",
"electron": "^41.1.1",
"electron-builder": "^26.8.1",
"eslint": "^10.1.0",
"postcss": "^8.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Base.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import { ok } from '../core/RestAPI';
import { getAllProfiles, getSettings, saveSettings } from '../core/Store';
import { processManager } from '../core/process/ProcessManager';
import { AppSettings } from '../shared/config/Settings.config';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const BaseRoutes: RouteMap = {
status: defineRoute('status', ({ res }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Log.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { deleteLogFile, getLogFiles, readLogFile } from '../core/process/FileLogger';
import { err, ok } from '../core/RestAPI';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const LogRoutes: RouteMap = {
logs_list: defineRoute('logs_list', ({ res, params }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Process.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { getAllProfiles } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const ProcessRoutes: RouteMap = {
processes_list: defineRoute('processes_list', ({ res }) => ok(res, processManager.getStates())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Profile.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,8 +2,8 @@ import { v4 as uuidv4 } from 'uuid';
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { deleteProfile, getAllProfiles, saveProfile } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/API.types';
import { Profile } from '../shared/types/Profile.types';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';

export const ProfileRoutes: RouteMap = {
profiles_list: defineRoute('profiles_list', ({ res }) => ok(res, getAllProfiles())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/_index.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { RouteKey } from '../shared/config/API.config';
import { BuiltRoute, RouteMap } from '../shared/types/RestAPI.types';
import { BuiltRoute, RouteMap } from '../shared/types/API.types';

import { BaseRoutes } from './Base.routes';
import { LogRoutes } from './Log.routes';
Expand Down
18 changes: 9 additions & 9 deletions src/main/core/IPCController.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,13 +3,13 @@
*
* Defining a route here automatically:
* 1. Registers it on ipcMain (in main.ts via `registerIPC`)
* 2. Exposes it on window.api (in preload.ts via `buildPreloadAPI`)
* 3. Types window.api (via the InferAPI utility below)
* 2. Exposes it on jrc.api (in preload.ts via `buildPreloadAPI`)
* 3. Types jrc.api (via the InferAPI utility below)
*
* Route shapes:
* invoke → renderer calls window.api.foo(...args) → Promise<T>
* send → renderer calls window.api.foo(...args) → void (fire & forget)
* on → renderer calls window.api.onFoo(cb) → unsubscribe fn
* invoke → renderer calls jrc.api.foo(...args) → Promise<T>
* send → renderer calls jrc.api.foo(...args) → void (fire & forget)
* on → renderer calls jrc.api.onFoo(cb) → unsubscribe fn
* main pushes via webContents.send(channel, ...args)
*/

Expand All@@ -33,7 +33,7 @@ type SendRoute = {
type OnRoute = {
type: 'on';
channel: string;
/** Cast a function signature here to type the callback args on window.api.onFoo.
/** Cast a function signature here to type the callback args on jrc.api.onFoo.
* e.g. `args: {} as (profileId: string, line: ConsoleLine) => void`
* Never called at runtime — purely a compile-time phantom. */
args?: (...args: any[]) => void;
Expand All@@ -43,7 +43,7 @@ type Route = InvokeRoute | SendRoute | OnRoute;

export type RouteMap = Record<string, Route>;

// ─── Type inference: RouteMap → window.api shape ──────────────────────────────
// ─── Type inference: RouteMap → jrc.api shape ─────────────────────────────────

type InvokeAPI<R extends InvokeRoute> = R['handler'] extends (
_e: any,
Expand All@@ -60,7 +60,7 @@ type OnAPI<K extends string, R extends OnRoute> = R extends { args: (...args: in
? { [key in `on${Capitalize<K>}`]: (cb: (...args: A) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void };

/** Derives the full window.api type from a RouteMap. */
/** Derives the full jrc.api type from a RouteMap. */
export type InferAPI<M extends RouteMap> = {
[K in keyof M as M[K]['type'] extends 'on' ? never : K]: M[K] extends InvokeRoute
? InvokeAPI<M[K]>
Expand DownExpand Up@@ -89,7 +89,7 @@ export function registerIPC(routes: RouteMap[]): void {
}
}

// ─── Preload: build the window.api object ────────────────────────────────────
// ─── Preload: build the jrc.api object ───────────────────────────────────────

export function buildPreloadAPI(routes: RouteMap[]): Record<string, unknown> {
const api: Record<string, unknown> = {};
Expand Down
6 changes: 3 additions & 3 deletions src/main/core/JRCEnvironment.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,15 +7,15 @@ let env: JRCEnvironment = {
isReady: false,
devMode: null as unknown as JRCEnvironment['devMode'],
type: null as unknown as JRCEnvironment['type'],
startUpSource: null as unknown as JRCEnvironment['startUpSource'],
launchContext: null as unknown as JRCEnvironment['launchContext'],
};

export function loadEnvironment() {
env = {
isReady: true,
devMode: getSettings().devModeEnabled,
type: app.isPackaged ? 'prod' : 'dev',
startUpSource: detectStartupSource(),
launchContext: detectLaunchContext(),
};

broadcast();
Expand All@@ -33,7 +33,7 @@ function broadcast(channel: string = EnvironmentIPC.change.channel) {
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send(channel, env));
}

function detectStartupSource(): JRCEnvironment['startUpSource'] {
function detectLaunchContext(): JRCEnvironment['launchContext'] {
if (!app.isPackaged) return 'development';

const login = app.getLoginItemSettings();
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/RestAPI.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import http from 'http';
import { routes } from '../api/_index';
import { REST_API_CONFIG } from '../shared/config/API.config';
import { CompiledRoute, Params } from '../shared/types/RestAPI.types';
import { CompiledRoute, Params } from '../shared/types/API.types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

Expand Down
3 changes: 1 addition & 2 deletions src/main/core/Store.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { app } from 'electron';
import Store from 'electron-store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { AppSettings } from '../shared/config/Settings.config';
import { AppSettings, DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { Profile } from '../shared/types/Profile.types';

interface StoreSchema {
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/WindowManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ export function createWindow(onClose: (e: Electron.Event) => void): BrowserWindo
backgroundColor: (ALL_THEMES.find((t) => t.id === getSettings().themeId) ?? BUILTIN_THEME)
.colors['base-950'],
icon: getIconImage(),
show: getEnvironment().startUpSource !== 'withSystem',
show: getEnvironment().launchContext !== 'withSystem',
webPreferences: {
preload: path.join(__dirname, '../preload.js'),
contextIsolation: true,
Expand Down
18 changes: 14 additions & 4 deletions src/main/core/process/ProcessManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ import { BrowserWindow } from 'electron';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ProcessIPC } from '../../ipc/Process.ipc';
import { DEFAULT_JAR_RESOLUTION } from '../../shared/config/JarResolution.config';
import { PROTECTED_PROCESS_NAMES } from '../../shared/config/Scanner.config';
import {
ConsoleLine,
Expand All@@ -17,10 +16,15 @@ import { startLogSession, stopLogSession, writeLogLine } from './FileLogger';
import { gracefulStop } from './GracefulStop';

import fs from 'fs';
import { patternToRegex } from '../../shared/config/JarResolution.config';

const SELF_PROCESS_NAME = 'Java Client Runner';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

type SystemMessageType =
| 'start'
| 'stopping'
Expand DownExpand Up@@ -61,7 +65,13 @@ function compareVersionArrays(a: number[], b: number[]): number {
}

function resolveJarPath(profile: Profile): { jarPath: string; error?: string } {
const res = profile.jarResolution ?? DEFAULT_JAR_RESOLUTION;
const res = profile.jarResolution ?? {
enabled: false,
baseDir: '',
pattern: 'app-{version}.jar',
strategy: 'highest-version' as const,
regexOverride: '',
};

if (!res.enabled) {
return { jarPath: profile.jarPath };
Expand Down
26 changes: 25 additions & 1 deletion src/main/ipc/Dev.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
import { BrowserWindow } from 'electron';
import { app, BrowserWindow, shell } from 'electron';
import { readFileSync } from 'fs';
import { join } from 'path';
import type { RouteMap } from '../core/IPCController';
import { getAllProfiles } from '../core/Store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
Expand DownExpand Up@@ -37,4 +39,26 @@ export const DevIPC = {
store.set('settings', DEFAULT_SETTINGS);
},
},

getStoreJson: {
type: 'invoke',
channel: 'dev:getStoreJson',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
try {
return readFileSync(storePath, 'utf-8');
} catch {
return '{}';
}
},
},

openStoreFile: {
type: 'invoke',
channel: 'dev:openStoreFile',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
shell.showItemInFolder(storePath);
},
},
} satisfies RouteMap;
10 changes: 8 additions & 2 deletions src/main/ipc/JarResolution.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
import fs from 'fs';
import path from 'path';
import type { RouteMap } from '../core/IPCController';
import { patternToRegex } from '../shared/config/JarResolution.config';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/JarResolution.types';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/Profile.types';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

function parseVersion(str: string): number[] {
return str
Expand Down
3 changes: 2 additions & 1 deletion src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,8 @@ const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => {
app.on('second-instance', (_event, argv) => {
if (argv.includes('--autostart')) return;
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
Expand Down
5 changes: 3 additions & 2 deletions src/main/preload.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { buildPreloadAPI } from './core/IPCController';
import { allRoutes } from './ipc/_index';
import { EnvironmentIPC } from './ipc/Environment.ipc';

contextBridge.exposeInMainWorld('api', buildPreloadAPI([...allRoutes]));
const api = buildPreloadAPI([...allRoutes]);
const env = buildPreloadAPI([EnvironmentIPC]);

contextBridge.exposeInMainWorld('env', buildPreloadAPI([EnvironmentIPC]));
contextBridge.exposeInMainWorld('jrc', { api, env });
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "java-runner-client",
"version": "2.2.2",
"version": "2.2.3",
"description": "Run and manage Java processes with profiles, console I/O, and system tray support",
"main": "dist/main/main.js",
"scripts": {
Expand DownExpand Up@@ -34,7 +34,7 @@
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.0",
"concurrently": "^8.2.0",
"electron": "^35.7.5",
"electron": "^41.1.1",
"electron-builder": "^26.8.1",
"eslint": "^10.1.0",
"postcss": "^8.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Base.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import { ok } from '../core/RestAPI';
import { getAllProfiles, getSettings, saveSettings } from '../core/Store';
import { processManager } from '../core/process/ProcessManager';
import { AppSettings } from '../shared/config/Settings.config';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const BaseRoutes: RouteMap = {
status: defineRoute('status', ({ res }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Log.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { deleteLogFile, getLogFiles, readLogFile } from '../core/process/FileLogger';
import { err, ok } from '../core/RestAPI';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const LogRoutes: RouteMap = {
logs_list: defineRoute('logs_list', ({ res, params }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Process.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { getAllProfiles } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const ProcessRoutes: RouteMap = {
processes_list: defineRoute('processes_list', ({ res }) => ok(res, processManager.getStates())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Profile.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,8 +2,8 @@ import { v4 as uuidv4 } from 'uuid';
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { deleteProfile, getAllProfiles, saveProfile } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/API.types';
import { Profile } from '../shared/types/Profile.types';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';

export const ProfileRoutes: RouteMap = {
profiles_list: defineRoute('profiles_list', ({ res }) => ok(res, getAllProfiles())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/_index.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { RouteKey } from '../shared/config/API.config';
import { BuiltRoute, RouteMap } from '../shared/types/RestAPI.types';
import { BuiltRoute, RouteMap } from '../shared/types/API.types';

import { BaseRoutes } from './Base.routes';
import { LogRoutes } from './Log.routes';
Expand Down
18 changes: 9 additions & 9 deletions src/main/core/IPCController.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,13 +3,13 @@
*
* Defining a route here automatically:
* 1. Registers it on ipcMain (in main.ts via `registerIPC`)
* 2. Exposes it on window.api (in preload.ts via `buildPreloadAPI`)
* 3. Types window.api (via the InferAPI utility below)
* 2. Exposes it on jrc.api (in preload.ts via `buildPreloadAPI`)
* 3. Types jrc.api (via the InferAPI utility below)
*
* Route shapes:
* invoke → renderer calls window.api.foo(...args) → Promise<T>
* send → renderer calls window.api.foo(...args) → void (fire & forget)
* on → renderer calls window.api.onFoo(cb) → unsubscribe fn
* invoke → renderer calls jrc.api.foo(...args) → Promise<T>
* send → renderer calls jrc.api.foo(...args) → void (fire & forget)
* on → renderer calls jrc.api.onFoo(cb) → unsubscribe fn
* main pushes via webContents.send(channel, ...args)
*/

Expand All@@ -33,7 +33,7 @@ type SendRoute = {
type OnRoute = {
type: 'on';
channel: string;
/** Cast a function signature here to type the callback args on window.api.onFoo.
/** Cast a function signature here to type the callback args on jrc.api.onFoo.
* e.g. `args: {} as (profileId: string, line: ConsoleLine) => void`
* Never called at runtime — purely a compile-time phantom. */
args?: (...args: any[]) => void;
Expand All@@ -43,7 +43,7 @@ type Route = InvokeRoute | SendRoute | OnRoute;

export type RouteMap = Record<string, Route>;

// ─── Type inference: RouteMap → window.api shape ──────────────────────────────
// ─── Type inference: RouteMap → jrc.api shape ─────────────────────────────────

type InvokeAPI<R extends InvokeRoute> = R['handler'] extends (
_e: any,
Expand All@@ -60,7 +60,7 @@ type OnAPI<K extends string, R extends OnRoute> = R extends { args: (...args: in
? { [key in `on${Capitalize<K>}`]: (cb: (...args: A) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void };

/** Derives the full window.api type from a RouteMap. */
/** Derives the full jrc.api type from a RouteMap. */
export type InferAPI<M extends RouteMap> = {
[K in keyof M as M[K]['type'] extends 'on' ? never : K]: M[K] extends InvokeRoute
? InvokeAPI<M[K]>
Expand DownExpand Up@@ -89,7 +89,7 @@ export function registerIPC(routes: RouteMap[]): void {
}
}

// ─── Preload: build the window.api object ────────────────────────────────────
// ─── Preload: build the jrc.api object ───────────────────────────────────────

export function buildPreloadAPI(routes: RouteMap[]): Record<string, unknown> {
const api: Record<string, unknown> = {};
Expand Down
6 changes: 3 additions & 3 deletions src/main/core/JRCEnvironment.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,15 +7,15 @@ let env: JRCEnvironment = {
isReady: false,
devMode: null as unknown as JRCEnvironment['devMode'],
type: null as unknown as JRCEnvironment['type'],
startUpSource: null as unknown as JRCEnvironment['startUpSource'],
launchContext: null as unknown as JRCEnvironment['launchContext'],
};

export function loadEnvironment() {
env = {
isReady: true,
devMode: getSettings().devModeEnabled,
type: app.isPackaged ? 'prod' : 'dev',
startUpSource: detectStartupSource(),
launchContext: detectLaunchContext(),
};

broadcast();
Expand All@@ -33,7 +33,7 @@ function broadcast(channel: string = EnvironmentIPC.change.channel) {
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send(channel, env));
}

function detectStartupSource(): JRCEnvironment['startUpSource'] {
function detectLaunchContext(): JRCEnvironment['launchContext'] {
if (!app.isPackaged) return 'development';

const login = app.getLoginItemSettings();
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/RestAPI.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import http from 'http';
import { routes } from '../api/_index';
import { REST_API_CONFIG } from '../shared/config/API.config';
import { CompiledRoute, Params } from '../shared/types/RestAPI.types';
import { CompiledRoute, Params } from '../shared/types/API.types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

Expand Down
3 changes: 1 addition & 2 deletions src/main/core/Store.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { app } from 'electron';
import Store from 'electron-store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { AppSettings } from '../shared/config/Settings.config';
import { AppSettings, DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { Profile } from '../shared/types/Profile.types';

interface StoreSchema {
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/WindowManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ export function createWindow(onClose: (e: Electron.Event) => void): BrowserWindo
backgroundColor: (ALL_THEMES.find((t) => t.id === getSettings().themeId) ?? BUILTIN_THEME)
.colors['base-950'],
icon: getIconImage(),
show: getEnvironment().startUpSource !== 'withSystem',
show: getEnvironment().launchContext !== 'withSystem',
webPreferences: {
preload: path.join(__dirname, '../preload.js'),
contextIsolation: true,
Expand Down
18 changes: 14 additions & 4 deletions src/main/core/process/ProcessManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ import { BrowserWindow } from 'electron';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ProcessIPC } from '../../ipc/Process.ipc';
import { DEFAULT_JAR_RESOLUTION } from '../../shared/config/JarResolution.config';
import { PROTECTED_PROCESS_NAMES } from '../../shared/config/Scanner.config';
import {
ConsoleLine,
Expand All@@ -17,10 +16,15 @@ import { startLogSession, stopLogSession, writeLogLine } from './FileLogger';
import { gracefulStop } from './GracefulStop';

import fs from 'fs';
import { patternToRegex } from '../../shared/config/JarResolution.config';

const SELF_PROCESS_NAME = 'Java Client Runner';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

type SystemMessageType =
| 'start'
| 'stopping'
Expand DownExpand Up@@ -61,7 +65,13 @@ function compareVersionArrays(a: number[], b: number[]): number {
}

function resolveJarPath(profile: Profile): { jarPath: string; error?: string } {
const res = profile.jarResolution ?? DEFAULT_JAR_RESOLUTION;
const res = profile.jarResolution ?? {
enabled: false,
baseDir: '',
pattern: 'app-{version}.jar',
strategy: 'highest-version' as const,
regexOverride: '',
};

if (!res.enabled) {
return { jarPath: profile.jarPath };
Expand Down
26 changes: 25 additions & 1 deletion src/main/ipc/Dev.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
import { BrowserWindow } from 'electron';
import { app, BrowserWindow, shell } from 'electron';
import { readFileSync } from 'fs';
import { join } from 'path';
import type { RouteMap } from '../core/IPCController';
import { getAllProfiles } from '../core/Store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
Expand DownExpand Up@@ -37,4 +39,26 @@ export const DevIPC = {
store.set('settings', DEFAULT_SETTINGS);
},
},

getStoreJson: {
type: 'invoke',
channel: 'dev:getStoreJson',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
try {
return readFileSync(storePath, 'utf-8');
} catch {
return '{}';
}
},
},

openStoreFile: {
type: 'invoke',
channel: 'dev:openStoreFile',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
shell.showItemInFolder(storePath);
},
},
} satisfies RouteMap;
10 changes: 8 additions & 2 deletions src/main/ipc/JarResolution.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
import fs from 'fs';
import path from 'path';
import type { RouteMap } from '../core/IPCController';
import { patternToRegex } from '../shared/config/JarResolution.config';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/JarResolution.types';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/Profile.types';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

function parseVersion(str: string): number[] {
return str
Expand Down
3 changes: 2 additions & 1 deletion src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,8 @@ const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => {
app.on('second-instance', (_event, argv) => {
if (argv.includes('--autostart')) return;
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
Expand Down
5 changes: 3 additions & 2 deletions src/main/preload.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { buildPreloadAPI } from './core/IPCController';
import { allRoutes } from './ipc/_index';
import { EnvironmentIPC } from './ipc/Environment.ipc';

contextBridge.exposeInMainWorld('api', buildPreloadAPI([...allRoutes]));
const api = buildPreloadAPI([...allRoutes]);
const env = buildPreloadAPI([EnvironmentIPC]);

contextBridge.exposeInMainWorld('env', buildPreloadAPI([EnvironmentIPC]));
contextBridge.exposeInMainWorld('jrc', { api, env });
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "java-runner-client",
"version": "2.2.2",
"version": "2.2.3",
"description": "Run and manage Java processes with profiles, console I/O, and system tray support",
"main": "dist/main/main.js",
"scripts": {
Expand DownExpand Up@@ -34,7 +34,7 @@
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.0",
"concurrently": "^8.2.0",
"electron": "^35.7.5",
"electron": "^41.1.1",
"electron-builder": "^26.8.1",
"eslint": "^10.1.0",
"postcss": "^8.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Base.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import { ok } from '../core/RestAPI';
import { getAllProfiles, getSettings, saveSettings } from '../core/Store';
import { processManager } from '../core/process/ProcessManager';
import { AppSettings } from '../shared/config/Settings.config';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const BaseRoutes: RouteMap = {
status: defineRoute('status', ({ res }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Log.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { deleteLogFile, getLogFiles, readLogFile } from '../core/process/FileLogger';
import { err, ok } from '../core/RestAPI';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const LogRoutes: RouteMap = {
logs_list: defineRoute('logs_list', ({ res, params }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Process.routes.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { getAllProfiles } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const ProcessRoutes: RouteMap = {
processes_list: defineRoute('processes_list', ({ res }) => ok(res, processManager.getStates())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Profile.routes.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,8 +2,8 @@ import { v4 as uuidv4 } from 'uuid';
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { deleteProfile, getAllProfiles, saveProfile } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/API.types';
import { Profile } from '../shared/types/Profile.types';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';

export const ProfileRoutes: RouteMap = {
profiles_list: defineRoute('profiles_list', ({ res }) => ok(res, getAllProfiles())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/_index.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { RouteKey } from '../shared/config/API.config';
import { BuiltRoute, RouteMap } from '../shared/types/RestAPI.types';
import { BuiltRoute, RouteMap } from '../shared/types/API.types';

import { BaseRoutes } from './Base.routes';
import { LogRoutes } from './Log.routes';
Expand Down
18 changes: 9 additions & 9 deletions src/main/core/IPCController.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,13 +3,13 @@
*
* Defining a route here automatically:
* 1. Registers it on ipcMain (in main.ts via `registerIPC`)
* 2. Exposes it on window.api (in preload.ts via `buildPreloadAPI`)
* 3. Types window.api (via the InferAPI utility below)
* 2. Exposes it on jrc.api (in preload.ts via `buildPreloadAPI`)
* 3. Types jrc.api (via the InferAPI utility below)
*
* Route shapes:
* invoke → renderer calls window.api.foo(...args) → Promise<T>
* send → renderer calls window.api.foo(...args) → void (fire & forget)
* on → renderer calls window.api.onFoo(cb) → unsubscribe fn
* invoke → renderer calls jrc.api.foo(...args) → Promise<T>
* send → renderer calls jrc.api.foo(...args) → void (fire & forget)
* on → renderer calls jrc.api.onFoo(cb) → unsubscribe fn
* main pushes via webContents.send(channel, ...args)
*/

Expand All@@ -33,7 +33,7 @@ type SendRoute = {
type OnRoute = {
type: 'on';
channel: string;
/** Cast a function signature here to type the callback args on window.api.onFoo.
/** Cast a function signature here to type the callback args on jrc.api.onFoo.
* e.g. `args: {} as (profileId: string, line: ConsoleLine) => void`
* Never called at runtime — purely a compile-time phantom. */
args?: (...args: any[]) => void;
Expand All@@ -43,7 +43,7 @@ type Route = InvokeRoute | SendRoute | OnRoute;

export type RouteMap = Record<string, Route>;

// ─── Type inference: RouteMap → window.api shape ──────────────────────────────
// ─── Type inference: RouteMap → jrc.api shape ─────────────────────────────────

type InvokeAPI<R extends InvokeRoute> = R['handler'] extends (
_e: any,
Expand All@@ -60,7 +60,7 @@ type OnAPI<K extends string, R extends OnRoute> = R extends { args: (...args: in
? { [key in `on${Capitalize<K>}`]: (cb: (...args: A) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void };

/** Derives the full window.api type from a RouteMap. */
/** Derives the full jrc.api type from a RouteMap. */
export type InferAPI<M extends RouteMap> = {
[K in keyof M as M[K]['type'] extends 'on' ? never : K]: M[K] extends InvokeRoute
? InvokeAPI<M[K]>
Expand DownExpand Up@@ -89,7 +89,7 @@ export function registerIPC(routes: RouteMap[]): void {
}
}

// ─── Preload: build the window.api object ────────────────────────────────────
// ─── Preload: build the jrc.api object ───────────────────────────────────────

export function buildPreloadAPI(routes: RouteMap[]): Record<string, unknown> {
const api: Record<string, unknown> = {};
Expand Down
6 changes: 3 additions & 3 deletions src/main/core/JRCEnvironment.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,15 +7,15 @@ let env: JRCEnvironment = {
isReady: false,
devMode: null as unknown as JRCEnvironment['devMode'],
type: null as unknown as JRCEnvironment['type'],
startUpSource: null as unknown as JRCEnvironment['startUpSource'],
launchContext: null as unknown as JRCEnvironment['launchContext'],
};

export function loadEnvironment() {
env = {
isReady: true,
devMode: getSettings().devModeEnabled,
type: app.isPackaged ? 'prod' : 'dev',
startUpSource: detectStartupSource(),
launchContext: detectLaunchContext(),
};

broadcast();
Expand All@@ -33,7 +33,7 @@ function broadcast(channel: string = EnvironmentIPC.change.channel) {
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send(channel, env));
}

function detectStartupSource(): JRCEnvironment['startUpSource'] {
function detectLaunchContext(): JRCEnvironment['launchContext'] {
if (!app.isPackaged) return 'development';

const login = app.getLoginItemSettings();
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/RestAPI.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import http from 'http';
import { routes } from '../api/_index';
import { REST_API_CONFIG } from '../shared/config/API.config';
import { CompiledRoute, Params } from '../shared/types/RestAPI.types';
import { CompiledRoute, Params } from '../shared/types/API.types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

Expand Down
3 changes: 1 addition & 2 deletions src/main/core/Store.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { app } from 'electron';
import Store from 'electron-store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { AppSettings } from '../shared/config/Settings.config';
import { AppSettings, DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { Profile } from '../shared/types/Profile.types';

interface StoreSchema {
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/WindowManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ export function createWindow(onClose: (e: Electron.Event) => void): BrowserWindo
backgroundColor: (ALL_THEMES.find((t) => t.id === getSettings().themeId) ?? BUILTIN_THEME)
.colors['base-950'],
icon: getIconImage(),
show: getEnvironment().startUpSource !== 'withSystem',
show: getEnvironment().launchContext !== 'withSystem',
webPreferences: {
preload: path.join(__dirname, '../preload.js'),
contextIsolation: true,
Expand Down
18 changes: 14 additions & 4 deletions src/main/core/process/ProcessManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ import { BrowserWindow } from 'electron';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ProcessIPC } from '../../ipc/Process.ipc';
import { DEFAULT_JAR_RESOLUTION } from '../../shared/config/JarResolution.config';
import { PROTECTED_PROCESS_NAMES } from '../../shared/config/Scanner.config';
import {
ConsoleLine,
Expand All@@ -17,10 +16,15 @@ import { startLogSession, stopLogSession, writeLogLine } from './FileLogger';
import { gracefulStop } from './GracefulStop';

import fs from 'fs';
import { patternToRegex } from '../../shared/config/JarResolution.config';

const SELF_PROCESS_NAME = 'Java Client Runner';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

type SystemMessageType =
| 'start'
| 'stopping'
Expand DownExpand Up@@ -61,7 +65,13 @@ function compareVersionArrays(a: number[], b: number[]): number {
}

function resolveJarPath(profile: Profile): { jarPath: string; error?: string } {
const res = profile.jarResolution ?? DEFAULT_JAR_RESOLUTION;
const res = profile.jarResolution ?? {
enabled: false,
baseDir: '',
pattern: 'app-{version}.jar',
strategy: 'highest-version' as const,
regexOverride: '',
};

if (!res.enabled) {
return { jarPath: profile.jarPath };
Expand Down
26 changes: 25 additions & 1 deletion src/main/ipc/Dev.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
import { BrowserWindow } from 'electron';
import { app, BrowserWindow, shell } from 'electron';
import { readFileSync } from 'fs';
import { join } from 'path';
import type { RouteMap } from '../core/IPCController';
import { getAllProfiles } from '../core/Store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
Expand DownExpand Up@@ -37,4 +39,26 @@ export const DevIPC = {
store.set('settings', DEFAULT_SETTINGS);
},
},

getStoreJson: {
type: 'invoke',
channel: 'dev:getStoreJson',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
try {
return readFileSync(storePath, 'utf-8');
} catch {
return '{}';
}
},
},

openStoreFile: {
type: 'invoke',
channel: 'dev:openStoreFile',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
shell.showItemInFolder(storePath);
},
},
} satisfies RouteMap;
10 changes: 8 additions & 2 deletions src/main/ipc/JarResolution.ipc.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
import fs from 'fs';
import path from 'path';
import type { RouteMap } from '../core/IPCController';
import { patternToRegex } from '../shared/config/JarResolution.config';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/JarResolution.types';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/Profile.types';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

function parseVersion(str: string): number[] {
return str
Expand Down
3 changes: 2 additions & 1 deletion src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,8 @@ const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => {
app.on('second-instance', (_event, argv) => {
if (argv.includes('--autostart')) return;
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
Expand Down
5 changes: 3 additions & 2 deletions src/main/preload.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { buildPreloadAPI } from './core/IPCController';
import { allRoutes } from './ipc/_index';
import { EnvironmentIPC } from './ipc/Environment.ipc';

contextBridge.exposeInMainWorld('api', buildPreloadAPI([...allRoutes]));
const api = buildPreloadAPI([...allRoutes]);
const env = buildPreloadAPI([EnvironmentIPC]);

contextBridge.exposeInMainWorld('env', buildPreloadAPI([EnvironmentIPC]));
contextBridge.exposeInMainWorld('jrc', { api, env });
Loading
Loading