@noxfly/noxus 1.1.10 → 2.0.0
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/.github/copilot-instructions.md +32 -0
- package/README.md +104 -135
- package/dist/index-CI3OMzNR.d.mts +318 -0
- package/dist/index-CI3OMzNR.d.ts +318 -0
- package/dist/{noxus.d.mts → main.d.mts} +45 -233
- package/dist/{noxus.d.ts → main.d.ts} +45 -233
- package/dist/{noxus.js → main.js} +536 -59
- package/dist/{noxus.mjs → main.mjs} +523 -54
- package/dist/renderer.d.mts +1 -0
- package/dist/renderer.d.ts +1 -0
- package/dist/renderer.js +515 -0
- package/dist/renderer.mjs +485 -0
- package/package.json +73 -48
- package/scripts/postbuild.js +10 -5
- package/src/DI/injector-explorer.ts +1 -1
- package/src/app.ts +51 -46
- package/src/decorators/injectable.decorator.ts +6 -17
- package/src/decorators/injectable.metadata.ts +15 -0
- package/src/index.ts +2 -13
- package/src/main.ts +28 -0
- package/src/preload-bridge.ts +75 -0
- package/src/renderer-client.ts +338 -0
- package/src/renderer-events.ts +110 -0
- package/src/request.ts +27 -0
- package/src/router.ts +1 -1
- package/src/socket.ts +73 -0
- package/tsup.config.ts +2 -1
|
@@ -0,0 +1,110 @@
|
|
|
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 'src/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
|
+
}
|
package/src/request.ts
CHANGED
|
@@ -20,6 +20,7 @@ export class Request {
|
|
|
20
20
|
|
|
21
21
|
constructor(
|
|
22
22
|
public readonly event: Electron.MessageEvent,
|
|
23
|
+
public readonly senderId: number,
|
|
23
24
|
public readonly id: string,
|
|
24
25
|
public readonly method: HttpMethod,
|
|
25
26
|
public readonly path: string,
|
|
@@ -68,3 +69,29 @@ export interface IResponse<TBody = unknown> {
|
|
|
68
69
|
export interface IBatchResponsePayload {
|
|
69
70
|
responses: IResponse[];
|
|
70
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
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -213,7 +213,7 @@ export class Router {
|
|
|
213
213
|
|
|
214
214
|
for(const [index, item] of payload.requests.entries()) {
|
|
215
215
|
const subRequestId = item.requestId ?? `${request.id}:${index}`;
|
|
216
|
-
const atomicRequest = new Request(request.event, subRequestId, item.method, item.path, item.body);
|
|
216
|
+
const atomicRequest = new Request(request.event, request.senderId, subRequestId, item.method, item.path, item.body);
|
|
217
217
|
batchResponses.push(await this.handleAtomic(atomicRequest));
|
|
218
218
|
}
|
|
219
219
|
|
package/src/socket.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2025 NoxFly
|
|
3
|
+
* @license MIT
|
|
4
|
+
* @author NoxFly
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Centralizes MessagePort storage for renderer communication and handles
|
|
9
|
+
* push-event delivery back to renderer processes.
|
|
10
|
+
*/
|
|
11
|
+
import { Injectable } from 'src/decorators/injectable.decorator';
|
|
12
|
+
import { createRendererEventMessage } from 'src/request';
|
|
13
|
+
import { Logger } from 'src/utils/logger';
|
|
14
|
+
|
|
15
|
+
interface RendererChannels {
|
|
16
|
+
request: Electron.MessageChannelMain;
|
|
17
|
+
socket: Electron.MessageChannelMain;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Injectable('singleton')
|
|
21
|
+
export class NoxSocket {
|
|
22
|
+
private readonly channels = new Map<number, RendererChannels>();
|
|
23
|
+
|
|
24
|
+
public register(senderId: number, requestChannel: Electron.MessageChannelMain, socketChannel: Electron.MessageChannelMain): void {
|
|
25
|
+
this.channels.set(senderId, { request: requestChannel, socket: socketChannel });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public get(senderId: number): RendererChannels | undefined {
|
|
29
|
+
return this.channels.get(senderId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public unregister(senderId: number): void {
|
|
33
|
+
this.channels.delete(senderId);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public getSenderIds(): number[] {
|
|
37
|
+
return [...this.channels.keys()];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public emit<TPayload = unknown>(eventName: string, payload?: TPayload, targetSenderIds?: number[]): number {
|
|
41
|
+
const normalizedEvent = eventName.trim();
|
|
42
|
+
|
|
43
|
+
if(normalizedEvent.length === 0) {
|
|
44
|
+
throw new Error('Renderer event name must be a non-empty string.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const recipients = targetSenderIds ?? this.getSenderIds();
|
|
48
|
+
let delivered = 0;
|
|
49
|
+
|
|
50
|
+
for(const senderId of recipients) {
|
|
51
|
+
const channel = this.channels.get(senderId);
|
|
52
|
+
|
|
53
|
+
if(!channel) {
|
|
54
|
+
Logger.warn(`No message channel found for sender ID: ${senderId} while emitting "${normalizedEvent}".`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
channel.socket.port1.postMessage(createRendererEventMessage(normalizedEvent, payload));
|
|
60
|
+
delivered++;
|
|
61
|
+
}
|
|
62
|
+
catch(error) {
|
|
63
|
+
Logger.error(`[Noxus] Failed to emit "${normalizedEvent}" to sender ${senderId}.`, error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return delivered;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public emitToRenderer<TPayload = unknown>(senderId: number, eventName: string, payload?: TPayload): boolean {
|
|
71
|
+
return this.emit(eventName, payload, [senderId]) > 0;
|
|
72
|
+
}
|
|
73
|
+
}
|