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
6 changes: 4 additions & 2 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"semi": false,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2,
"endOfLine": "lf"
"endOfLine": "lf",
"objectWrap": "preserve",
"bracketSameLine": false
}
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion postcss.config.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} } }
module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} } };
64 changes: 32 additions & 32 deletions src/main/shared/IPCController.ts → src/main/IPCController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,35 @@
* main pushes via webContents.send(channel, ...args)
*/

import { ipcMain, ipcRenderer, IpcMainInvokeEvent, IpcMainEvent, IpcRendererEvent } from 'electron'
import { ipcMain, ipcRenderer, IpcMainInvokeEvent, IpcMainEvent, IpcRendererEvent } from 'electron';

// ─── Route descriptors ────────────────────────────────────────────────────────

type InvokeRoute = {
type: 'invoke'
channel: string
handler: (event: IpcMainInvokeEvent, ...args: any[]) => any
}
type: 'invoke';
channel: string;
handler: (event: IpcMainInvokeEvent, ...args: any[]) => any;
};

type SendRoute = {
type: 'send'
channel: string
handler: (event: IpcMainEvent, ...args: any[]) => void
}
type: 'send';
channel: string;
handler: (event: IpcMainEvent, ...args: any[]) => void;
};

/** No handler — main pushes via webContents.send(channel, ...) */
type OnRoute = {
type: 'on'
channel: string
type: 'on';
channel: string;
/** Cast a function signature here to type the callback args on window.api.onFoo.
* e.g. `args: {} as (profileId: string, line: ConsoleLine) => void`
* Never called at runtime — purely a compile-time phantom. */
args?: (...args: any[]) => void
}
args?: (...args: any[]) => void;
};

type Route = InvokeRoute | SendRoute | OnRoute
type Route = InvokeRoute | SendRoute | OnRoute;

export type RouteMap = Record<string, Route>
export type RouteMap = Record<string, Route>;

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

Expand All @@ -50,40 +50,40 @@ type InvokeAPI<R extends InvokeRoute> = R['handler'] extends (
...args: infer A
) => infer Ret
? (...args: A) => Promise<Awaited<Ret>>
: never
: never;

type SendAPI<R extends SendRoute> = R['handler'] extends (_e: any, ...args: infer A) => any
? (...args: A) => void
: never
: never;

type OnAPI<K extends string, R extends OnRoute> = R extends { args: (...args: infer A) => void }
? { [key in `on${Capitalize<K>}`]: (cb: (...args: A) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void };

/** Derives the full window.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]>
: M[K] extends SendRoute
? SendAPI<M[K]>
: never
: never;
} & {
[K in keyof M as M[K]['type'] extends 'on'
? `on${Capitalize<string & K>}`
: never]: M[K] extends OnRoute
? M[K] extends { args: (...args: infer A) => void }
? (cb: (...args: A) => void) => () => void
: (cb: (...args: any[]) => void) => () => void
: never
}
: never;
};

// ─── Main-process: register all routes onto ipcMain ──────────────────────────

export function registerIPC(routes: RouteMap[]): void {
for (const map of routes) {
for (const route of Object.values(map)) {
if (route.type === 'invoke') ipcMain.handle(route.channel, route.handler)
if (route.type === 'send') ipcMain.on(route.channel, route.handler)
if (route.type === 'invoke') ipcMain.handle(route.channel, route.handler);
if (route.type === 'send') ipcMain.on(route.channel, route.handler);
// 'on' routes are push-only from main — no listener to register
}
}
Expand All @@ -92,26 +92,26 @@ export function registerIPC(routes: RouteMap[]): void {
// ─── Preload: build the window.api object ────────────────────────────────────

export function buildPreloadAPI(routes: RouteMap[]): Record<string, unknown> {
const api: Record<string, unknown> = {}
const api: Record<string, unknown> = {};

for (const map of routes) {
for (const [key, route] of Object.entries(map)) {
if (route.type === 'invoke') {
api[key] = (...args: unknown[]) => ipcRenderer.invoke(route.channel, ...args)
api[key] = (...args: unknown[]) => ipcRenderer.invoke(route.channel, ...args);
}
if (route.type === 'send') {
api[key] = (...args: unknown[]) => ipcRenderer.send(route.channel, ...args)
api[key] = (...args: unknown[]) => ipcRenderer.send(route.channel, ...args);
}
if (route.type === 'on') {
const cbKey = `on${key[0].toUpperCase()}${key.slice(1)}`
const cbKey = `on${key[0].toUpperCase()}${key.slice(1)}`;
api[cbKey] = (cb: (...args: unknown[]) => void) => {
const handler = (_e: IpcRendererEvent, ...args: unknown[]) => cb(...args)
ipcRenderer.on(route.channel, handler)
return () => ipcRenderer.off(route.channel, handler)
}
const handler = (_e: IpcRendererEvent, ...args: unknown[]) => cb(...args);
ipcRenderer.on(route.channel, handler);
return () => ipcRenderer.off(route.channel, handler);
};
}
}
}

return api
return api;
}
44 changes: 44 additions & 0 deletions src/main/JRCEnvironment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { app, BrowserWindow } from 'electron';
import { EnvironmentIPC } from './ipc/Environment.ipc';
import { JRCEnvironment } from './shared/types/App.types';
import { getSettings } from './Store';

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'],
};

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

broadcast();
}

export function getEnvironment() {
return env;
}

export function shouldStartMinimized(): boolean {
return process.argv.includes('--minimized');
}

function broadcast(channel: string = EnvironmentIPC.change.channel) {
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send(channel, env));
}

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

const login = app.getLoginItemSettings();

if (login.wasOpenedAtLogin || process.argv.includes('--autostart')) return 'withSystem';

return 'userRequest';
}
Loading
Loading