@devisfuture/electron-modular 1.0.10 → 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
@@ -1,5 +1,7 @@
1
1
  # Package Documentation
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@devisfuture/electron-modular.svg)](https://www.npmjs.com/package/@devisfuture/electron-modular) [![Downloads](https://img.shields.io/npm/dm/@devisfuture/electron-modular.svg)](https://www.npmjs.com/package/@devisfuture/electron-modular) [![Build](https://img.shields.io/github/actions/workflow/status/trae-op/electron-modular/ci.yml?branch=main)](https://github.com/trae-op/electron-modular/actions/workflows/ci.yml) [![Coverage](https://img.shields.io/codecov/c/github/trae-op/electron-modular/main)](https://codecov.io/gh/trae-op/electron-modular) [![Bundle size](https://img.shields.io/bundlephobia/minzip/@devisfuture/electron-modular)](https://bundlephobia.com/package/@devisfuture/electron-modular) [![License](https://img.shields.io/npm/l/@devisfuture/electron-modular.svg)](https://github.com/trae-op/electron-modular/blob/main/LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-%233178C6.svg?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
4
+
3
5
  ## Overview
4
6
 
5
7
  A lightweight dependency injection container for Electron's main process that brings modular architecture and clean code organization to your desktop applications.
@@ -79,7 +81,9 @@ import { UserModule } from "./user/module.js";
79
81
  import { ResourcesModule } from "./resources/module.js";
80
82
 
81
83
  initSettings({
82
- baseRestApi: process.env.BASE_REST_API ?? "",
84
+ cspConnectSources: process.env.BASE_REST_API
85
+ ? [process.env.BASE_REST_API]
86
+ : [],
83
87
  localhostPort: process.env.LOCALHOST_ELECTRON_SERVER_PORT ?? "",
84
88
  folders: {
85
89
  distRenderer: "dist-renderer",
@@ -264,6 +268,7 @@ export class UserService {
264
268
  Handle communication between main and renderer processes.
265
269
 
266
270
  ```typescript
271
+ import { ipcMain, type IpcMainEvent } from "electron";
267
272
  import {
268
273
  IpcHandler,
269
274
  TIpcHandlerInterface,
@@ -278,7 +283,7 @@ export class UserIpc implements TIpcHandlerInterface {
278
283
  async onInit({ getWindow }: TParamOnInit<TWindows["main"]>) {
279
284
  const mainWindow = getWindow("window:main");
280
285
 
281
- ipcMainOn("user:fetch", async (event, userId: string) => {
286
+ ipcMain.on("user:fetch", (event: IpcMainEvent, userId: string) => {
282
287
  const user = await this.userService.byId(userId);
283
288
  event.reply("user:fetch:response", user);
284
289
  });
@@ -368,6 +373,80 @@ Important implementation notes ⚠️
368
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.
369
374
  - The same instance and set of handlers are tracked in a WeakMap internally; re-attaching the same `windowInstance` will not duplicate listeners.
370
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
+
371
450
  ---
372
451
 
373
452
  ## TypeScript types — `TWindows["myWindow"]`
@@ -480,13 +559,17 @@ Initializes framework configuration.
480
559
 
481
560
  **Parameters:**
482
561
 
483
- - `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.
484
563
  - `localhostPort: string` - Development server port
485
564
  - `folders: { distRenderer: string; distMain: string }` - Build output folders
486
565
 
487
566
  ```typescript
488
567
  initSettings({
489
- baseRestApi: process.env.BASE_REST_API ?? "",
568
+ cspConnectSources: [
569
+ "https://api.example.com",
570
+ "https://cdn.example.com",
571
+ "wss://websocket.example.com",
572
+ ],
490
573
  localhostPort: process.env.LOCALHOST_ELECTRON_SERVER_PORT ?? "",
491
574
  folders: {
492
575
  distRenderer: "dist-renderer",
@@ -495,6 +578,12 @@ initSettings({
495
578
  });
496
579
  ```
497
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
+
498
587
  #### `bootstrapModules(modules[])`
499
588
 
500
589
  Bootstraps all modules and initializes the DI container.
@@ -705,7 +794,9 @@ export class MyWindow implements TWindowManager {}
705
794
 
706
795
  ```typescript
707
796
  initSettings({
708
- baseRestApi: process.env.BASE_REST_API ?? "",
797
+ cspConnectSources: process.env.BASE_REST_API
798
+ ? [process.env.BASE_REST_API]
799
+ : [],
709
800
  localhostPort: process.env.LOCALHOST_ELECTRON_SERVER_PORT ?? "",
710
801
  folders: { distRenderer: "dist-renderer", distMain: "dist-main" },
711
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.10",
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",
@@ -25,7 +25,7 @@
25
25
  "sideEffects": false,
26
26
  "scripts": {
27
27
  "build": "tsc -p tsconfig.json",
28
- "test": "vitest run",
28
+ "test": "vitest run --coverage",
29
29
  "prepublishOnly": "npm run build"
30
30
  },
31
31
  "peerDependencies": {