@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
|
-
|
|
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
|
-
|
|
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
|
-
- `
|
|
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
|
-
|
|
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
|
-
|
|
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,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