@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
|
+
[](https://www.npmjs.com/package/@devisfuture/electron-modular) [](https://www.npmjs.com/package/@devisfuture/electron-modular) [](https://github.com/trae-op/electron-modular/actions/workflows/ci.yml) [](https://codecov.io/gh/trae-op/electron-modular) [](https://bundlephobia.com/package/@devisfuture/electron-modular) [](https://github.com/trae-op/electron-modular/blob/main/LICENSE) [](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
|
-
|
|
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
|
-
|
|
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
|
-
- `
|
|
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
|
-
|
|
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
|
-
|
|
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,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 = (
|
|
7
|
-
const
|
|
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.
|
|
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.
|
|
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": {
|