@devisfuture/electron-modular 1.0.11 → 1.1.2

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",
@@ -266,6 +268,7 @@ export class UserService {
266
268
  Handle communication between main and renderer processes.
267
269
 
268
270
  ```typescript
271
+ import { ipcMain, type IpcMainEvent } from "electron";
269
272
  import {
270
273
  IpcHandler,
271
274
  TIpcHandlerInterface,
@@ -280,7 +283,7 @@ export class UserIpc implements TIpcHandlerInterface {
280
283
  async onInit({ getWindow }: TParamOnInit<TWindows["main"]>) {
281
284
  const mainWindow = getWindow("window:main");
282
285
 
283
- ipcMainOn("user:fetch", async (event, userId: string) => {
286
+ ipcMain.on("user:fetch", (event: IpcMainEvent, userId: string) => {
284
287
  const user = await this.userService.byId(userId);
285
288
  event.reply("user:fetch:response", user);
286
289
  });
@@ -370,6 +373,80 @@ Important implementation notes ⚠️
370
373
  - 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
374
  - The same instance and set of handlers are tracked in a WeakMap internally; re-attaching the same `windowInstance` will not duplicate listeners.
372
375
 
376
+ ### Opening windows with URL params (dynamic routes) 🔗
377
+
378
+ 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>`).
379
+
380
+ Renderer process
381
+
382
+ ```tsx
383
+ <Route path="/window:items/:id" element={<ItemWindow />} />
384
+ ```
385
+
386
+ Renderer (inside `ItemWindow`)
387
+
388
+ ```tsx
389
+ import { useParams } from "react-router-dom";
390
+
391
+ const ItemWindow = () => {
392
+ const { id } = useParams<{ id?: string }>();
393
+
394
+ if (!id) return null;
395
+
396
+ return <span>item id: {id}</span>;
397
+ };
398
+
399
+ export default ItemWindow;
400
+ ```
401
+
402
+ Main process
403
+
404
+ ```typescript
405
+ import { ipcMain, type IpcMainEvent } from "electron";
406
+ import { IpcHandler, type TParamOnInit } from "@devisfuture/electron-modular";
407
+
408
+ @IpcHandler()
409
+ export class ItemsIpc {
410
+ constructor() {}
411
+
412
+ onInit({ getWindow }: TParamOnInit<TWindows["items"]>): void {
413
+ const window = getWindow("window:items");
414
+
415
+ ipcMain.on(
416
+ "itemWindow",
417
+ async (_: IpcMainEvent, payload: { id?: string }) => {
418
+ if (payload.id === undefined) {
419
+ return;
420
+ }
421
+
422
+ await window.create({
423
+ hash: `window:items/${payload.id}`,
424
+ });
425
+ },
426
+ );
427
+ }
428
+ }
429
+ ```
430
+
431
+ Default manager example
432
+
433
+ ```ts
434
+ @WindowManager<TWindows['items']>({
435
+ hash: 'window:items',
436
+ isCache: true,
437
+ options: {
438
+ width: 350,
439
+ height: 300,
440
+ },
441
+ })
442
+ ```
443
+
444
+ Notes:
445
+
446
+ - `await window.create({...})` merges the provided options with the manager's default `options`.
447
+ - When `isCache: true`, `getWindow('window:items')` returns the cached manager and `create` will reuse (or re-create) the BrowserWindow as implemented by the manager.
448
+ - 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.
449
+
373
450
  ---
374
451
 
375
452
  ## TypeScript types — `TWindows["myWindow"]`
@@ -482,13 +559,17 @@ Initializes framework configuration.
482
559
 
483
560
  **Parameters:**
484
561
 
485
- - `baseRestApi: string` - Base REST API URL
562
+ - `cspConnectSources?: string[]` - Optional array of origins to include in the `connect-src` directive of the generated Content-Security-Policy header.
486
563
  - `localhostPort: string` - Development server port
487
564
  - `folders: { distRenderer: string; distMain: string }` - Build output folders
488
565
 
489
566
  ```typescript
490
567
  initSettings({
491
- baseRestApi: process.env.BASE_REST_API ?? "",
568
+ cspConnectSources: [
569
+ "https://api.example.com",
570
+ "https://cdn.example.com",
571
+ "wss://websocket.example.com",
572
+ ],
492
573
  localhostPort: process.env.LOCALHOST_ELECTRON_SERVER_PORT ?? "",
493
574
  folders: {
494
575
  distRenderer: "dist-renderer",
@@ -497,6 +578,12 @@ initSettings({
497
578
  });
498
579
  ```
499
580
 
581
+ > 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:
582
+
583
+ ```
584
+ connect-src 'self' https://api.example.com https://cdn.example.com wss://websocket.example.com;
585
+ ```
586
+
500
587
  #### `bootstrapModules(modules[])`
501
588
 
502
589
  Bootstraps all modules and initializes the DI container.
@@ -707,7 +794,9 @@ export class MyWindow implements TWindowManager {}
707
794
 
708
795
  ```typescript
709
796
  initSettings({
710
- baseRestApi: process.env.BASE_REST_API ?? "",
797
+ cspConnectSources: process.env.BASE_REST_API
798
+ ? [process.env.BASE_REST_API]
799
+ : [],
711
800
  localhostPort: process.env.LOCALHOST_ELECTRON_SERVER_PORT ?? "",
712
801
  folders: { distRenderer: "dist-renderer", distMain: "dist-main" },
713
802
  });
@@ -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.2",
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",