Skip to content

Commit 0171470

Browse files
committed
refactor(auth, user): move cacheUser function to main scope
Relocate the cacheUser function to the main cache-responses module to improve code organization and accessibility for both auth and user IPC handlers. This change enhances the modular structure of the application.
1 parent f74fd36 commit 0171470

File tree

5 files changed

+6
-278
lines changed

5 files changed

+6
-278
lines changed

docs/main-process-architecture.md

Lines changed: 0 additions & 260 deletions
Original file line numberDiff line numberDiff line change
@@ -1,260 +0,0 @@
1-
# Main Process Architecture
2-
3-
This document describes the architecture and maintenance guidelines for the Electron main process located in `src/main`.
4-
5-
It is intentionally written as a **practical guide** for how this repository structures main-process code (startup, windows, IPC, storage, services, and feature modules).
6-
7-
## Architecture Goals
8-
9-
- **Feature modularity**: each feature lives in its own folder (`auth/`, `user/`, `updater/`, etc.) and exposes a small surface area to the app entrypoint.
10-
- **Single IPC gateway**: renderer talks to main through a single exposed API (`window.electron`) and two IPC channels (`send`, `invoke`) plus one receive channel (`receive`).
11-
- **Window lifecycle control**: a shared window controller (`@shared/control-window/*`) creates, caches, reuses, and hides windows consistently.
12-
- **Security by default**: IPC handlers validate the sender frame (`@shared/utils.ts`) to reduce the chance of unauthorized IPC calls.
13-
- **Shared infrastructure**: storage, REST API wrapper, menu, tray, notifications, and logging are centralized under `@shared/`.
14-
15-
## Directory Structure
16-
17-
The main process code is organized by feature, with a shared directory for common utilities.
18-
19-
```architecture
20-
src/main/
21-
├── @shared/ # Shared utilities, types, and helpers
22-
│ ├── control-window/ # Window creation/destruction logic
23-
│ ├── ipc/ # IPC gateway registration + helpers
24-
│ ├── menu/ # Application menu configuration
25-
│ ├── services/ # Shared services (e.g., REST API, error handling)
26-
│ ├── tray/ # System tray logic
27-
│ ├── logger.ts # Electron-log wrapper
28-
│ ├── notification.ts # Shared Notification instance
29-
│ ├── path-resolver.ts # Resolve preload/UI/assets paths
30-
│ ├── store.ts # Electron store wrapper
31-
│ └── utils.ts # General utilities (IPC wrappers, env checks)
32-
├── <module>/ # Modules (e.g., auth, user, updater)
33-
│ ├── ipc.ts # IPC event handlers registration
34-
│ ├── service.ts # Business logic and external API calls
35-
│ ├── utils.ts # Helper functions of module
36-
│ ├── types.ts # Feature-specific types
37-
│ └── window.ts # Window configuration (if applicable)
38-
├── app.ts # Application entry point
39-
├── config.ts # Global configuration (URLs, window names)
40-
└── preload.cts # Context bridge and preload script
41-
```
42-
43-
## Startup Flow (What happens on app launch)
44-
45-
The main process entrypoint is `src/main/app.ts`.
46-
47-
High-level flow:
48-
49-
1. Load environment variables (special handling in production builds).
50-
2. Apply global Electron settings (e.g., hardware acceleration disabled).
51-
3. Configure updater feed (Windows), crash handlers, and global UI infra (tray/menu/notifications).
52-
4. Create the main window using the shared window factory.
53-
5. Register IPC once and dispatch events to feature modules.
54-
55-
### Environment loading
56-
57-
Both `src/main/app.ts` and `src/main/config.ts` load `.env` in production from `process.resourcesPath/.env`. This keeps config available when packaged.
58-
59-
### Global configuration (`config.ts`)
60-
61-
`src/main/config.ts` centralizes:
62-
63-
- window hashes (`windows`)
64-
- folders used by packaging/runtime (`folders`)
65-
- menu labels and icon names
66-
- copy for notifications/errors (`messages`)
67-
- publish metadata and REST API URLs (`publishOptions`, `restApi`)
68-
69-
Feature modules should import constants from `config.ts` instead of hardcoding strings.
70-
71-
## IPC Architecture
72-
73-
### Renderer API surface (`preload.cts`)
74-
75-
The preload script exposes a strict API under `window.electron`:
76-
77-
- `send(payload)` → fire-and-forget events to main (IPC channel: `send`)
78-
- `invoke(payload)` → request/response to main (IPC channel: `invoke`)
79-
- `receive(callback)` → subscribe to main → renderer push messages (IPC channel: `receive`)
80-
81-
This repo uses typed envelopes (see `@shared/ipc/types.ts`) where every message has a `type` and optional `data`.
82-
83-
### Main IPC gateway (`@shared/ipc/ipc.ts`)
84-
85-
Main process listens on exactly two inbound channels:
86-
87-
- `ipcMain.on("send", ...)`
88-
- `ipcMain.handle("invoke", ...)`
89-
90-
And it emits to renderer using a single outbound channel:
91-
92-
- `webContents.send("receive", payload)`
93-
94-
Helpers:
95-
96-
- `sendToRenderer(webContents, payload)`
97-
- `replyToRenderer(event, payload)`
98-
99-
### IPC security: sender validation (`@shared/utils.ts`)
100-
101-
Before dispatching, every inbound IPC message validates the sender frame via `validateEventFrame(event.senderFrame)`.
102-
103-
Rules (simplified):
104-
105-
- In dev, allow `localhost:<LOCALHOST_PORT|LOCALHOST_ELECTRON_SERVER_PORT>`.
106-
- In prod, require `file:` URLs and only accept known window hashes (from `config.ts -> windows`).
107-
108-
When adding new windows/routes, ensure their hash exists in `config.ts -> windows`, otherwise IPC will reject events.
109-
110-
### IPC dispatch pattern in `app.ts`
111-
112-
`src/main/app.ts` registers IPC once and delegates per feature:
113-
114-
- `onSend`: fan-out to feature `handleSend` functions (`auth`, `user`, `app-preload`, `updater`)
115-
- `onInvoke`: delegate to the single invoke handler currently used (`app-version`)
116-
117-
If you add a new feature with IPC, follow the same pattern: export `handleSend` and/or `handleInvoke` from the feature module and register it in `app.ts`.
118-
119-
## Window Architecture
120-
121-
### Window creation (`@shared/control-window/create.ts`)
122-
123-
All windows should be created through `createWindow(...)`. It standardizes:
124-
125-
- preload script location (points to built `dist-main/preload.cjs`)
126-
- dev vs prod URL loading:
127-
- dev → `http://localhost:<LOCALHOST_PORT>/#<hash>`
128-
- prod → `loadFile(dist-renderer/index.html, { hash })`
129-
- optional direct `loadURL` (used for OAuth and the preload spinner window)
130-
- basic security defaults:
131-
- `contextIsolation: true`
132-
- `nodeIntegration: false`
133-
134-
### Window caching and reuse (`@shared/control-window/cache.ts`, `receive.ts`)
135-
136-
Windows can be cached by `hash`:
137-
138-
- If `isCache: true` and a cached instance exists, `createWindow` reuses it and calls `show()`.
139-
- Cached windows are hidden on close (`event.preventDefault(); window.hide()`), which enables “re-open without re-create”.
140-
141-
Use `getWindow(hash)` to fetch cached windows safely (returns `undefined` if destroyed).
142-
143-
### Global window cleanup (`@shared/control-window/destroy.ts`)
144-
145-
`destroyWindows()` destroys all windows on `before-quit`.
146-
147-
### Content Security Policy
148-
149-
`createWindow` attaches a CSP header (when `isCache` is enabled and not using `loadURL`). It optionally allows connecting to `BASE_REST_API` and allows `unsafe-inline` scripts only in dev.
150-
151-
If you add a window that needs network access, prefer routing those requests through the shared REST API service and keep CSP consistent.
152-
153-
## Storage and Caching
154-
155-
### In-memory store vs persistent storage (`@shared/store.ts`)
156-
157-
This repo uses two storage layers:
158-
159-
- `store` (in-memory `Map`) for runtime references/flags (e.g., update process flag, window references)
160-
- `electron-store` for persistence (auth token, user id, cached API responses)
161-
162-
Key patterns:
163-
164-
- `setStore("updateWindow", window)` stores an in-memory reference.
165-
- `setElectronStorage("authToken", token)` persists auth session.
166-
167-
### REST API wrapper (`@shared/services/rest-api/service.ts`)
168-
169-
The REST API service:
170-
171-
- uses Axios with a request interceptor that injects `Authorization: Bearer <token>` from persistent storage
172-
- returns a consistent `ApiResponse<T>` shape: `{ status, data?, error? }`
173-
- logs out automatically on `401`
174-
- supports response caching into electron-store when `options.isCache` is enabled
175-
176-
### Cache lookup helpers (`@shared/cache-responses.ts`)
177-
178-
`cacheUser(userId)` fetches a cached user response by reconstructing the request URL key.
179-
180-
## Shared UI Infrastructure
181-
182-
- `@shared/menu/*`: defines the default application menu and allows `app.ts` to patch menu entries (e.g., dev tools).
183-
- `@shared/tray/*`: defines the tray menu and tray icon setup; `app.ts` wires click handlers.
184-
- `@shared/notification.ts`: initializes a reusable `Notification` instance.
185-
- `@shared/logger.ts`: configures `electron-log` formatting and levels.
186-
- `@shared/path-resolver.ts`: canonical paths to preload/UI/assets (use when you need to resolve locations reliably across dev/prod).
187-
188-
## Feature Modules (How features are structured)
189-
190-
Each feature typically follows this shape:
191-
192-
- `ipc.ts`: exports `handleSend` and/or `handleInvoke` to integrate with the global IPC gateway
193-
- `service.ts`: business logic and side effects (REST calls, filesystem, OS APIs)
194-
- `window.ts`: window creation + show behavior (optional)
195-
- `types.ts`: feature-specific types (optional)
196-
197-
### app-version
198-
199-
- `app-version/ipc.ts`: exposes `invoke` handler for retrieving the current app version.
200-
201-
### app-preload
202-
203-
- `app-preload/window.ts`: shows a frameless always-on-top spinner window (loads a local `spinner.html`).
204-
- `app-preload/ipc.ts`: initializes the spinner on startup and listens for a `windowClosePreload` event to hide the spinner and show the main window.
205-
206-
### auth
207-
208-
- `auth/window.ts`: opens an auth/OAuth BrowserWindow pointing at the backend auth route.
209-
- uses a dedicated persistent session partition (`persist:auth`)
210-
- `auth/ipc.ts`:
211-
- handles `logout` and `checkAuth`
212-
- opens auth window when renderer requests `windowAuth`
213-
- listens to `will-redirect` to detect:
214-
- verify callback → extracts `token` and `userId` → persists them → notifies renderer (`receive` channel)
215-
- user-exists error → shows dialog
216-
217-
### user
218-
219-
- `user/ipc.ts`: on `user` request, replies with cached user immediately (if present) and then fetches fresh user data.
220-
- `user/service.ts`: fetches the user via the shared REST API wrapper and caches responses.
221-
222-
### updater
223-
224-
Updater has two implementations:
225-
226-
- **Windows** (`updater/services/win/*`) uses `electron-updater`.
227-
- `setFeedURL.ts` configures GitHub provider and optional token.
228-
- `controlUpdater.ts` listens for updater events and pushes status to renderer (`sendUpdateInfo`).
229-
- **macOS** (`updater/services/mac/*`) uses GitHub Releases API + manual download.
230-
- checks latest version, compares versions, downloads `.dmg` to `~/Downloads/app-update/`, reports progress.
231-
232-
Main entry points:
233-
234-
- `updater/window.ts`: creates/shows the update window and triggers update checks.
235-
- `updater/services/checkForUpdates.ts`: orchestrates platform-specific logic.
236-
- `updater/ipc.ts`: handles renderer events (`checkForUpdates`, `restart`, `openLatestVersion`).
237-
238-
### crash
239-
240-
- `crash/service.ts`: sets process and app-level crash handlers:
241-
- `uncaughtException`
242-
- `unhandledRejection`
243-
- `render-process-gone`
244-
It tears down tray/preload UI and shows an error dialog.
245-
246-
## How to Add a New Main-Process Feature Module
247-
248-
1. Create `src/main/<feature>/`.
249-
2. Add `ipc.ts` exporting `handleSend` and/or `handleInvoke`.
250-
3. Put business logic in `service.ts` (use shared services where possible).
251-
4. If a window is needed, create `window.ts` that calls `createWindow({ hash, isCache: true, options })`.
252-
5. Add any new window hash to `src/main/config.ts -> windows`.
253-
6. Wire the feature into `src/main/app.ts`:
254-
- add imports
255-
- include your handler in the `registerIpc({ onSend, onInvoke })` fan-out.
256-
7. If you introduce new IPC message types, update the shared type declarations under the repository `types/` folder so `preload.cts` and renderer stay type-safe.
257-
258-
## Testing Notes
259-
260-
Main-process unit tests are co-located next to implementation files (e.g., `create.test.ts`). When writing new tests, mock Electron APIs (`ipcMain`, `BrowserWindow`, etc.) and focus on pure logic + IPC handler behavior.

src/main/@shared/notification.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/main/auth/ipc.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { cacheUser } from "#shared/cache-responses.js";
21
import { ipcMainOn, ipcWebContentsSend } from "#shared/ipc/ipc.js";
32
import { getElectronStorage } from "#shared/store.js";
43
import {
@@ -9,6 +8,8 @@ import {
98

109
import { restApi } from "../config.js";
1110

11+
import { cacheUser } from "#main/cache-responses.js";
12+
1213
import { AuthService } from "./service.js";
1314

1415
@IpcHandler()
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { restApi } from "../config.js";
1+
import { getElectronStorage } from "#shared/store.js";
22

3-
import { getElectronStorage } from "./store.js";
3+
import { restApi } from "./config.js";
44

55
export function cacheUser(userId: string | undefined): TUser | undefined {
66
let user: TUser | undefined = undefined;

src/main/user/ipc.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { cacheUser } from "#shared/cache-responses.js";
21
import { ipcMainOn } from "#shared/ipc/ipc.js";
32
import { getElectronStorage } from "#shared/store.js";
43
import {
54
IpcHandler,
65
getWindow as getWindows,
76
} from "@devisfuture/electron-modular";
87

8+
import { cacheUser } from "#main/cache-responses.js";
9+
910
import { UserService } from "./service.js";
1011

1112
@IpcHandler()

0 commit comments

Comments
 (0)