@noxfly/noxus 3.0.0-dev.3 → 3.0.0-dev.5

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.
Files changed (57) hide show
  1. package/README.md +114 -7
  2. package/dist/child.d.mts +7 -1
  3. package/dist/child.d.ts +7 -1
  4. package/dist/child.js +402 -862
  5. package/dist/child.js.map +1 -0
  6. package/dist/child.mjs +389 -850
  7. package/dist/child.mjs.map +1 -0
  8. package/dist/main.d.mts +171 -125
  9. package/dist/main.d.ts +171 -125
  10. package/dist/main.js +967 -886
  11. package/dist/main.js.map +1 -0
  12. package/dist/main.mjs +914 -834
  13. package/dist/main.mjs.map +1 -0
  14. package/dist/preload.js.map +1 -0
  15. package/dist/preload.mjs.map +1 -0
  16. package/dist/renderer.d.mts +17 -2
  17. package/dist/renderer.d.ts +17 -2
  18. package/dist/renderer.js +161 -118
  19. package/dist/renderer.js.map +1 -0
  20. package/dist/renderer.mjs +150 -106
  21. package/dist/renderer.mjs.map +1 -0
  22. package/package.json +10 -9
  23. package/.editorconfig +0 -16
  24. package/.github/copilot-instructions.md +0 -32
  25. package/.vscode/settings.json +0 -3
  26. package/eslint.config.js +0 -109
  27. package/scripts/postbuild.js +0 -31
  28. package/src/DI/app-injector.ts +0 -160
  29. package/src/DI/injector-explorer.ts +0 -143
  30. package/src/DI/token.ts +0 -53
  31. package/src/decorators/controller.decorator.ts +0 -58
  32. package/src/decorators/guards.decorator.ts +0 -15
  33. package/src/decorators/injectable.decorator.ts +0 -81
  34. package/src/decorators/method.decorator.ts +0 -66
  35. package/src/decorators/middleware.decorator.ts +0 -15
  36. package/src/index.ts +0 -10
  37. package/src/internal/app.ts +0 -217
  38. package/src/internal/bootstrap.ts +0 -109
  39. package/src/internal/exceptions.ts +0 -57
  40. package/src/internal/preload-bridge.ts +0 -75
  41. package/src/internal/renderer-client.ts +0 -338
  42. package/src/internal/renderer-events.ts +0 -110
  43. package/src/internal/request.ts +0 -97
  44. package/src/internal/router.ts +0 -353
  45. package/src/internal/routes.ts +0 -78
  46. package/src/internal/socket.ts +0 -73
  47. package/src/main.ts +0 -26
  48. package/src/non-electron-process.ts +0 -22
  49. package/src/preload.ts +0 -10
  50. package/src/renderer.ts +0 -13
  51. package/src/utils/forward-ref.ts +0 -31
  52. package/src/utils/logger.ts +0 -430
  53. package/src/utils/radix-tree.ts +0 -210
  54. package/src/utils/types.ts +0 -21
  55. package/src/window/window-manager.ts +0 -268
  56. package/tsconfig.json +0 -29
  57. package/tsup.config.ts +0 -50
@@ -1,338 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { IBatchRequestItem, IBatchResponsePayload, IRequest, IResponse } from './request';
8
- import { RendererEventRegistry } from './renderer-events';
9
-
10
- export interface IPortRequester {
11
- requestPort(): void;
12
- }
13
-
14
- export interface RendererClientOptions {
15
- bridge?: IPortRequester | null;
16
- bridgeName?: string | string[];
17
- initMessageType?: string;
18
- windowRef?: Window;
19
- generateRequestId?: () => string;
20
- }
21
-
22
- interface PendingRequest<T = unknown> {
23
- resolve: (value: T) => void;
24
- reject: (reason: IResponse<T>) => void;
25
- request: IRequest;
26
- submittedAt: number;
27
- }
28
-
29
- const DEFAULT_INIT_EVENT = 'init-port';
30
- const DEFAULT_BRIDGE_NAMES = ['noxus', 'ipcRenderer'];
31
-
32
- function defaultRequestId(): string {
33
- if(typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
34
- return crypto.randomUUID();
35
- }
36
-
37
- return `${Date.now().toString(16)}-${Math.floor(Math.random() * 1e8).toString(16)}`;
38
- }
39
-
40
- function normalizeBridgeNames(preferred?: string | string[]): string[] {
41
- const names: string[] = [];
42
-
43
- const add = (name: string | undefined): void => {
44
- if(!name)
45
- return;
46
-
47
- if(!names.includes(name)) {
48
- names.push(name);
49
- }
50
- };
51
-
52
- if(Array.isArray(preferred)) {
53
- for(const name of preferred) {
54
- add(name);
55
- }
56
- }
57
- else {
58
- add(preferred);
59
- }
60
-
61
- for(const fallback of DEFAULT_BRIDGE_NAMES) {
62
- add(fallback);
63
- }
64
-
65
- return names;
66
- }
67
-
68
- function resolveBridgeFromWindow(windowRef: Window, preferred?: string | string[]): IPortRequester | null {
69
- const names = normalizeBridgeNames(preferred);
70
- const globalRef = windowRef as unknown as Record<string, unknown> | null | undefined;
71
-
72
- if(!globalRef) {
73
- return null;
74
- }
75
-
76
- for(const name of names) {
77
- const candidate = globalRef[name];
78
-
79
- if(candidate && typeof (candidate as IPortRequester).requestPort === 'function') {
80
- return candidate as IPortRequester;
81
- }
82
- }
83
-
84
- return null;
85
- }
86
-
87
- export class NoxRendererClient {
88
- public readonly events = new RendererEventRegistry();
89
-
90
- protected readonly pendingRequests = new Map<string, PendingRequest>();
91
-
92
- protected requestPort: MessagePort | undefined;
93
- protected socketPort: MessagePort | undefined;
94
- protected senderId: number | undefined;
95
-
96
- private readonly bridge: IPortRequester | null;
97
- private readonly initMessageType: string;
98
- private readonly windowRef: Window;
99
- private readonly generateRequestId: () => string;
100
-
101
- private isReady = false;
102
- private setupPromise: Promise<void> | undefined;
103
- private setupResolve: (() => void) | undefined;
104
- private setupReject: ((reason: Error) => void) | undefined;
105
-
106
- constructor(options: RendererClientOptions = {}) {
107
- this.windowRef = options.windowRef ?? window;
108
- const resolvedBridge = options.bridge ?? resolveBridgeFromWindow(this.windowRef, options.bridgeName);
109
- this.bridge = resolvedBridge ?? null;
110
- this.initMessageType = options.initMessageType ?? DEFAULT_INIT_EVENT;
111
- this.generateRequestId = options.generateRequestId ?? defaultRequestId;
112
- }
113
-
114
- public async setup(): Promise<void> {
115
- if(this.isReady) {
116
- return Promise.resolve();
117
- }
118
-
119
- if(this.setupPromise) {
120
- return this.setupPromise;
121
- }
122
-
123
- if(!this.bridge || typeof this.bridge.requestPort !== 'function') {
124
- throw new Error('[Noxus] Renderer bridge is missing requestPort().');
125
- }
126
-
127
- this.setupPromise = new Promise<void>((resolve, reject) => {
128
- this.setupResolve = resolve;
129
- this.setupReject = reject;
130
- });
131
-
132
- this.windowRef.addEventListener('message', this.onWindowMessage);
133
- this.bridge.requestPort();
134
-
135
- return this.setupPromise;
136
- }
137
-
138
- public dispose(): void {
139
- this.windowRef.removeEventListener('message', this.onWindowMessage);
140
-
141
- this.requestPort?.close();
142
- this.socketPort?.close();
143
-
144
- this.requestPort = undefined;
145
- this.socketPort = undefined;
146
- this.senderId = undefined;
147
- this.isReady = false;
148
-
149
- this.pendingRequests.clear();
150
- }
151
-
152
- public async request<TResponse, TBody = unknown>(request: Omit<IRequest<TBody>, 'requestId' | 'senderId'>): Promise<TResponse> {
153
- const senderId = this.senderId;
154
- const requestId = this.generateRequestId();
155
-
156
- if(senderId === undefined) {
157
- return Promise.reject(this.createErrorResponse(requestId, 'MessagePort is not available'));
158
- }
159
-
160
- const readinessError = this.validateReady(requestId);
161
-
162
- if(readinessError) {
163
- return Promise.reject(readinessError as IResponse<TResponse>);
164
- }
165
-
166
- const message: IRequest<TBody> = {
167
- requestId,
168
- senderId,
169
- ...request,
170
- };
171
-
172
- return new Promise<TResponse>((resolve, reject) => {
173
- const pending: PendingRequest<TResponse> = {
174
- resolve,
175
- reject: (response: IResponse<TResponse>) => reject(response),
176
- request: message,
177
- submittedAt: Date.now(),
178
- };
179
-
180
- this.pendingRequests.set(message.requestId, pending as PendingRequest);
181
-
182
- this.requestPort!.postMessage(message);
183
- });
184
- }
185
-
186
- public async batch(requests: Omit<IBatchRequestItem<unknown>, 'requestId'>[]): Promise<IBatchResponsePayload> {
187
- return this.request<IBatchResponsePayload>({
188
- method: 'BATCH',
189
- path: '',
190
- body: {
191
- requests,
192
- },
193
- });
194
- }
195
-
196
- public getSenderId(): number | undefined {
197
- return this.senderId;
198
- }
199
-
200
- private readonly onWindowMessage = (event: MessageEvent): void => {
201
- if(event.data?.type !== this.initMessageType) {
202
- return;
203
- }
204
-
205
- if(!Array.isArray(event.ports) || event.ports.length < 2) {
206
- const error = new Error('[Noxus] Renderer expected two MessagePorts (request + socket).');
207
-
208
- console.error(error);
209
- this.setupReject?.(error);
210
- this.resetSetupState();
211
- return;
212
- }
213
-
214
- this.windowRef.removeEventListener('message', this.onWindowMessage);
215
-
216
- this.requestPort = event.ports[0];
217
- this.socketPort = event.ports[1];
218
- this.senderId = event.data.senderId;
219
-
220
- if(this.requestPort === undefined || this.socketPort === undefined) {
221
- const error = new Error('[Noxus] Renderer failed to receive valid MessagePorts.');
222
- console.error(error);
223
- this.setupReject?.(error);
224
- this.resetSetupState();
225
- return;
226
- }
227
-
228
- this.attachRequestPort(this.requestPort);
229
- this.attachSocketPort(this.socketPort);
230
-
231
- this.isReady = true;
232
- this.setupResolve?.();
233
- this.resetSetupState(true);
234
- };
235
-
236
- private readonly onSocketMessage = (event: MessageEvent): void => {
237
- if(this.events.tryDispatchFromMessageEvent(event)) {
238
- return;
239
- }
240
-
241
- console.warn('[Noxus] Received a socket message that is not a renderer event payload.', event.data);
242
- };
243
-
244
- private readonly onRequestMessage = (event: MessageEvent): void => {
245
- if(this.events.tryDispatchFromMessageEvent(event)) {
246
- return;
247
- }
248
-
249
- const response: IResponse = event.data;
250
-
251
- if(!response || typeof response.requestId !== 'string') {
252
- console.error('[Noxus] Renderer received an invalid response payload.', response);
253
- return;
254
- }
255
-
256
- const pending = this.pendingRequests.get(response.requestId);
257
-
258
- if(!pending) {
259
- console.error(`[Noxus] No pending handler found for request ${response.requestId}.`);
260
- return;
261
- }
262
-
263
- this.pendingRequests.delete(response.requestId);
264
-
265
- this.onRequestCompleted(pending, response);
266
-
267
- if(response.status >= 400) {
268
- pending.reject(response as IResponse<any>);
269
- return;
270
- }
271
-
272
- pending.resolve(response.body as unknown);
273
- };
274
-
275
- protected onRequestCompleted(pending: PendingRequest, response: IResponse): void {
276
- if(typeof console.groupCollapsed === 'function') {
277
- console.groupCollapsed(`${response.status} ${pending.request.method} /${pending.request.path}`);
278
- }
279
-
280
- if(response.error) {
281
- console.error('error message:', response.error);
282
- }
283
-
284
- if(response.body !== undefined) {
285
- console.info('response:', response.body);
286
- }
287
-
288
- console.info('request:', pending.request);
289
- console.info(`Request duration: ${Date.now() - pending.submittedAt} ms`);
290
-
291
- if(typeof console.groupCollapsed === 'function') {
292
- console.groupEnd();
293
- }
294
- }
295
-
296
- private attachRequestPort(port: MessagePort): void {
297
- port.onmessage = this.onRequestMessage;
298
- port.start();
299
- }
300
-
301
- private attachSocketPort(port: MessagePort): void {
302
- port.onmessage = this.onSocketMessage;
303
- port.start();
304
- }
305
-
306
- private validateReady(requestId: string): IResponse | undefined {
307
- if(!this.isElectronEnvironment()) {
308
- return this.createErrorResponse(requestId, 'Not running in Electron environment');
309
- }
310
-
311
- if(!this.requestPort) {
312
- return this.createErrorResponse(requestId, 'MessagePort is not available');
313
- }
314
-
315
- return undefined;
316
- }
317
-
318
- private createErrorResponse<T>(requestId: string, message: string): IResponse<T> {
319
- return {
320
- status: 500,
321
- requestId,
322
- error: message,
323
- };
324
- }
325
-
326
- private resetSetupState(success = false): void {
327
- if(!success) {
328
- this.setupPromise = undefined;
329
- }
330
-
331
- this.setupResolve = undefined;
332
- this.setupReject = undefined;
333
- }
334
-
335
- public isElectronEnvironment(): boolean {
336
- return typeof window !== 'undefined' && /Electron/.test(window.navigator.userAgent);
337
- }
338
- }
@@ -1,110 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- /**
8
- * Lightweight event registry to help renderer processes subscribe to
9
- * push messages sent by the main process through Noxus.
10
- */
11
- import { IRendererEventMessage, isRendererEventMessage } from './request';
12
-
13
- export type RendererEventHandler<TPayload = unknown> = (payload: TPayload) => void;
14
-
15
- export interface RendererEventSubscription {
16
- unsubscribe(): void;
17
- }
18
-
19
- export class RendererEventRegistry {
20
- private readonly listeners = new Map<string, Set<RendererEventHandler>>();
21
-
22
- /**
23
- *
24
- */
25
- public subscribe<TPayload>(eventName: string, handler: RendererEventHandler<TPayload>): RendererEventSubscription {
26
- const normalizedEventName = eventName.trim();
27
-
28
- if(normalizedEventName.length === 0) {
29
- throw new Error('Renderer event name must be a non-empty string.');
30
- }
31
-
32
- const handlers = this.listeners.get(normalizedEventName) ?? new Set<RendererEventHandler>();
33
-
34
- handlers.add(handler as RendererEventHandler);
35
- this.listeners.set(normalizedEventName, handlers);
36
-
37
- return {
38
- unsubscribe: () => this.unsubscribe(normalizedEventName, handler as RendererEventHandler),
39
- };
40
- }
41
-
42
- /**
43
- *
44
- */
45
- public unsubscribe<TPayload>(eventName: string, handler: RendererEventHandler<TPayload>): void {
46
- const handlers = this.listeners.get(eventName);
47
-
48
- if(!handlers) {
49
- return;
50
- }
51
-
52
- handlers.delete(handler as RendererEventHandler);
53
-
54
- if(handlers.size === 0) {
55
- this.listeners.delete(eventName);
56
- }
57
- }
58
-
59
- /**
60
- *
61
- */
62
- public clear(eventName?: string): void {
63
- if(eventName) {
64
- this.listeners.delete(eventName);
65
- return;
66
- }
67
-
68
- this.listeners.clear();
69
- }
70
-
71
- /**
72
- *
73
- */
74
- public dispatch<TPayload>(message: IRendererEventMessage<TPayload>): void {
75
- const handlers = this.listeners.get(message.event);
76
-
77
- if(!handlers || handlers.size === 0) {
78
- return;
79
- }
80
-
81
- handlers.forEach((handler) => {
82
- try {
83
- handler(message.payload as TPayload);
84
- }
85
- catch(error) {
86
- console.error(`[Noxus] Renderer event handler for "${message.event}" threw an error.`, error);
87
- }
88
- });
89
- }
90
-
91
- /**
92
- *
93
- */
94
- public tryDispatchFromMessageEvent(event: MessageEvent): boolean {
95
- if(!isRendererEventMessage(event.data)) {
96
- return false;
97
- }
98
-
99
- this.dispatch(event.data);
100
- return true;
101
- }
102
-
103
- /**
104
- *
105
- */
106
- public hasHandlers(eventName: string): boolean {
107
- const handlers = this.listeners.get(eventName);
108
- return !!handlers && handlers.size > 0;
109
- }
110
- }
@@ -1,97 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
-
8
- import { AtomicHttpMethod, HttpMethod } from '../decorators/method.decorator';
9
- import { AppInjector, RootInjector } from '../DI/app-injector';
10
-
11
- /**
12
- * The Request class represents an HTTP request in the Noxus framework.
13
- * It encapsulates the request data, including the event, ID, method, path, and body.
14
- * It also provides a context for dependency injection through the AppInjector.
15
- */
16
- export class Request {
17
- public readonly context: AppInjector = RootInjector.createScope();
18
-
19
- public readonly params: Record<string, string> = {};
20
-
21
- constructor(
22
- public readonly event: Electron.MessageEvent,
23
- public readonly senderId: number,
24
- public readonly id: string,
25
- public readonly method: HttpMethod,
26
- public readonly path: string,
27
- public readonly body: any,
28
- ) {
29
- this.path = path.replace(/^\/|\/$/g, '');
30
- }
31
- }
32
-
33
- /**
34
- * The IRequest interface defines the structure of a request object.
35
- * It includes properties for the sender ID, request ID, path, method, and an optional body.
36
- * This interface is used to standardize the request data across the application.
37
- */
38
- export interface IRequest<TBody = unknown> {
39
- senderId: number;
40
- requestId: string;
41
- path: string;
42
- method: HttpMethod;
43
- body?: TBody;
44
- }
45
-
46
- export interface IBatchRequestItem<TBody = unknown> {
47
- requestId?: string;
48
- path: string;
49
- method: AtomicHttpMethod;
50
- body?: TBody;
51
- }
52
-
53
- export interface IBatchRequestPayload {
54
- requests: IBatchRequestItem[];
55
- }
56
-
57
- /**
58
- * Creates a Request object from the IPC event data.
59
- * This function extracts the necessary information from the IPC event and constructs a Request instance.
60
- */
61
- export interface IResponse<TBody = unknown> {
62
- requestId: string;
63
- status: number;
64
- body?: TBody;
65
- error?: string;
66
- stack?: string;
67
- }
68
-
69
- export interface IBatchResponsePayload {
70
- responses: IResponse[];
71
- }
72
-
73
- export const RENDERER_EVENT_TYPE = 'noxus:event';
74
-
75
- export interface IRendererEventMessage<TPayload = unknown> {
76
- type: typeof RENDERER_EVENT_TYPE;
77
- event: string;
78
- payload?: TPayload;
79
- }
80
-
81
- export function createRendererEventMessage<TPayload = unknown>(event: string, payload?: TPayload): IRendererEventMessage<TPayload> {
82
- return {
83
- type: RENDERER_EVENT_TYPE,
84
- event,
85
- payload,
86
- };
87
- }
88
-
89
- export function isRendererEventMessage(value: unknown): value is IRendererEventMessage {
90
- if(value === null || typeof value !== 'object') {
91
- return false;
92
- }
93
-
94
- const possibleMessage = value as Partial<IRendererEventMessage>;
95
-
96
- return possibleMessage.type === RENDERER_EVENT_TYPE && typeof possibleMessage.event === 'string';
97
- }