@devisfuture/electron-modular 1.0.11 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -81,7 +81,9 @@ import { UserModule } from "./user/module.js";
81
81
  import { ResourcesModule } from "./resources/module.js";
82
82
 
83
83
  initSettings({
84
- baseRestApi: process.env.BASE_REST_API ?? "",
84
+ cspConnectSources: process.env.BASE_REST_API
85
+ ? [process.env.BASE_REST_API]
86
+ : [],
85
87
  localhostPort: process.env.LOCALHOST_ELECTRON_SERVER_PORT ?? "",
86
88
  folders: {
87
89
  distRenderer: "dist-renderer",
@@ -100,6 +102,10 @@ app.on("ready", async () => {
100
102
 
101
103
  ---
102
104
 
105
+ ## Example App
106
+
107
+ A small example app demonstrating how to use this package is available at [trae-op/quick-start_react_electron-modular](https://github.com/trae-op/quick-start_react_electron-modular). It contains a minimal React + Electron project that shows module registration, IPC handlers and window managers in action — check its README for setup and run instructions.
108
+
103
109
  ## Module Structure
104
110
 
105
111
  An example of each module's structure, but you can use your own:
@@ -266,6 +272,7 @@ export class UserService {
266
272
  Handle communication between main and renderer processes.
267
273
 
268
274
  ```typescript
275
+ import { ipcMain, type IpcMainEvent } from "electron";
269
276
  import {
270
277
  IpcHandler,
271
278
  TIpcHandlerInterface,
@@ -280,7 +287,7 @@ export class UserIpc implements TIpcHandlerInterface {
280
287
  async onInit({ getWindow }: TParamOnInit<TWindows["main"]>) {
281
288
  const mainWindow = getWindow("window:main");
282
289
 
283
- ipcMainOn("user:fetch", async (event, userId: string) => {
290
+ ipcMain.on("user:fetch", (event: IpcMainEvent, userId: string) => {
284
291
  const user = await this.userService.byId(userId);
285
292
  event.reply("user:fetch:response", user);
286
293
  });
@@ -370,6 +377,80 @@ Important implementation notes ⚠️
370
377
  - Handlers are attached per BrowserWindow instance and cleaned up automatically when the window is closed, so you don't have to manually remove listeners.
371
378
  - The same instance and set of handlers are tracked in a WeakMap internally; re-attaching the same `windowInstance` will not duplicate listeners.
372
379
 
380
+ ### Opening windows with URL params (dynamic routes) 🔗
381
+
382
+ If your renderer uses dynamic routes (for example React Router) you can open a window that targets a specific route by passing a `hash` to `create`. The `hash` is merged with the window manager's defaults and can carry route segments or parameters (for example `window:items/<id>`).
383
+
384
+ Renderer process
385
+
386
+ ```tsx
387
+ <Route path="/window:items/:id" element={<ItemWindow />} />
388
+ ```
389
+
390
+ Renderer (inside `ItemWindow`)
391
+
392
+ ```tsx
393
+ import { useParams } from "react-router-dom";
394
+
395
+ const ItemWindow = () => {
396
+ const { id } = useParams<{ id?: string }>();
397
+
398
+ if (!id) return null;
399
+
400
+ return <span>item id: {id}</span>;
401
+ };
402
+
403
+ export default ItemWindow;
404
+ ```
405
+
406
+ Main process
407
+
408
+ ```typescript
409
+ import { ipcMain, type IpcMainEvent } from "electron";
410
+ import { IpcHandler, type TParamOnInit } from "@devisfuture/electron-modular";
411
+
412
+ @IpcHandler()
413
+ export class ItemsIpc {
414
+ constructor() {}
415
+
416
+ onInit({ getWindow }: TParamOnInit<TWindows["items"]>): void {
417
+ const window = getWindow("window:items");
418
+
419
+ ipcMain.on(
420
+ "itemWindow",
421
+ async (_: IpcMainEvent, payload: { id?: string }) => {
422
+ if (payload.id === undefined) {
423
+ return;
424
+ }
425
+
426
+ await window.create({
427
+ hash: `window:items/${payload.id}`,
428
+ });
429
+ },
430
+ );
431
+ }
432
+ }
433
+ ```
434
+
435
+ Default manager example
436
+
437
+ ```ts
438
+ @WindowManager<TWindows['items']>({
439
+ hash: 'window:items',
440
+ isCache: true,
441
+ options: {
442
+ width: 350,
443
+ height: 300,
444
+ },
445
+ })
446
+ ```
447
+
448
+ Notes:
449
+
450
+ - `await window.create({...})` merges the provided options with the manager's default `options`.
451
+ - When `isCache: true`, `getWindow('window:items')` returns the cached manager and `create` will reuse (or re-create) the BrowserWindow as implemented by the manager.
452
+ - Use a unique `hash` per route instance (for example `window:items/<id>`), so the renderer can read the route from the window URL and navigate to the correct route when the window loads.
453
+
373
454
  ---
374
455
 
375
456
  ## TypeScript types — `TWindows["myWindow"]`
@@ -482,13 +563,17 @@ Initializes framework configuration.
482
563
 
483
564
  **Parameters:**
484
565
 
485
- - `baseRestApi: string` - Base REST API URL
566
+ - `cspConnectSources?: string[]` - Optional array of origins to include in the `connect-src` directive of the generated Content-Security-Policy header.
486
567
  - `localhostPort: string` - Development server port
487
568
  - `folders: { distRenderer: string; distMain: string }` - Build output folders
488
569
 
489
570
  ```typescript
490
571
  initSettings({
491
- baseRestApi: process.env.BASE_REST_API ?? "",
572
+ cspConnectSources: [
573
+ "https://api.example.com",
574
+ "https://cdn.example.com",
575
+ "wss://websocket.example.com",
576
+ ],
492
577
  localhostPort: process.env.LOCALHOST_ELECTRON_SERVER_PORT ?? "",
493
578
  folders: {
494
579
  distRenderer: "dist-renderer",
@@ -497,6 +582,12 @@ initSettings({
497
582
  });
498
583
  ```
499
584
 
585
+ > Note: When a cached window is created the framework will set a Content-Security-Policy header for renderer responses. The `connect-src` directive will include `'self'` plus any entries from `cspConnectSources`. For example:
586
+
587
+ ```
588
+ connect-src 'self' https://api.example.com https://cdn.example.com wss://websocket.example.com;
589
+ ```
590
+
500
591
  #### `bootstrapModules(modules[])`
501
592
 
502
593
  Bootstraps all modules and initializes the DI container.
@@ -707,7 +798,9 @@ export class MyWindow implements TWindowManager {}
707
798
 
708
799
  ```typescript
709
800
  initSettings({
710
- baseRestApi: process.env.BASE_REST_API ?? "",
801
+ cspConnectSources: process.env.BASE_REST_API
802
+ ? [process.env.BASE_REST_API]
803
+ : [],
711
804
  localhostPort: process.env.LOCALHOST_ELECTRON_SERVER_PORT ?? "",
712
805
  folders: { distRenderer: "dist-renderer", distMain: "dist-main" },
713
806
  });
@@ -3,7 +3,7 @@ export type TFolderSettings = {
3
3
  distMain: string;
4
4
  };
5
5
  export type TSettings = {
6
- baseRestApi: string;
6
+ cspConnectSources?: string[];
7
7
  localhostPort: string;
8
8
  folders: TFolderSettings;
9
9
  };
@@ -3,8 +3,9 @@ import path from "node:path";
3
3
  import { cacheWindows } from "./cache.js";
4
4
  import { getWindow } from "./receive.js";
5
5
  import { getSettings } from "../bootstrap/settings.js";
6
- const setupCSP = (base, dev) => {
7
- const csp = `default-src 'self'; connect-src 'self' ${base}; img-src * data:; style-src 'self' 'unsafe-inline'; script-src 'self' ${dev ? "'unsafe-inline'" : ""};`
6
+ const setupCSP = (sources, dev) => {
7
+ const connectSrc = sources.length > 0 ? ` ${sources.join(" ")}` : "";
8
+ const csp = `default-src 'self'; connect-src 'self'${connectSrc}; img-src * data:; style-src 'self' 'unsafe-inline'; script-src 'self' ${dev ? "'unsafe-inline'" : ""};`
8
9
  .replace(/\s{2,}/g, " ")
9
10
  .trim();
10
11
  session.defaultSession.webRequest.onHeadersReceived((d, cb) => {
@@ -21,8 +22,6 @@ export const createWindow = ({ hash, options, isCache, loadURL, }) => {
21
22
  const isDev = process.env.NODE_ENV === "development";
22
23
  const ui = path.join(app.getAppPath(), `/${settings.folders.distRenderer}/index.html`);
23
24
  const preload = path.join(app.getAppPath(), isDev ? "." : "..", `/${settings.folders.distMain}/preload.cjs`);
24
- if (!settings.baseRestApi)
25
- console.warn('Warning: You have to add an environment variable called "process.env.BASE_REST_API"!');
26
25
  if (!settings.localhostPort)
27
26
  console.warn('Warning: You have to add an environment variable called "process.env.LOCALHOST_ELECTRON_SERVER_PORT"!');
28
27
  if (hash && isCache) {
@@ -41,8 +40,8 @@ export const createWindow = ({ hash, options, isCache, loadURL, }) => {
41
40
  ...options?.webPreferences,
42
41
  },
43
42
  });
44
- if (isCache && !loadURL)
45
- setupCSP(settings.baseRestApi, isDev);
43
+ if (isCache && !loadURL && settings.cspConnectSources)
44
+ setupCSP(settings.cspConnectSources, isDev);
46
45
  if (loadURL) {
47
46
  win.loadURL(loadURL);
48
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devisfuture/electron-modular",
3
- "version": "1.0.11",
3
+ "version": "1.1.3",
4
4
  "description": "Core module system, DI container, IPC handlers, and window utilities for Electron main process.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",