@noxfly/noxus 3.0.0-dev.4 → 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.
- package/dist/child.js.map +1 -0
- package/dist/child.mjs.map +1 -0
- package/dist/main.js.map +1 -0
- package/dist/main.mjs.map +1 -0
- package/dist/preload.js.map +1 -0
- package/dist/preload.mjs.map +1 -0
- package/dist/renderer.js.map +1 -0
- package/dist/renderer.mjs.map +1 -0
- package/package.json +10 -9
- package/.editorconfig +0 -16
- package/.github/copilot-instructions.md +0 -128
- package/.vscode/settings.json +0 -3
- package/AGENTS.md +0 -5
- package/eslint.config.js +0 -109
- package/scripts/postbuild.js +0 -31
- package/src/DI/app-injector.ts +0 -173
- package/src/DI/injector-explorer.ts +0 -201
- package/src/DI/token.ts +0 -53
- package/src/decorators/controller.decorator.ts +0 -58
- package/src/decorators/guards.decorator.ts +0 -15
- package/src/decorators/injectable.decorator.ts +0 -81
- package/src/decorators/method.decorator.ts +0 -66
- package/src/decorators/middleware.decorator.ts +0 -15
- package/src/index.ts +0 -10
- package/src/internal/app.ts +0 -219
- package/src/internal/bootstrap.ts +0 -141
- package/src/internal/exceptions.ts +0 -57
- package/src/internal/preload-bridge.ts +0 -75
- package/src/internal/renderer-client.ts +0 -374
- package/src/internal/renderer-events.ts +0 -110
- package/src/internal/request.ts +0 -102
- package/src/internal/router.ts +0 -365
- package/src/internal/routes.ts +0 -142
- package/src/internal/socket.ts +0 -75
- package/src/main.ts +0 -26
- package/src/non-electron-process.ts +0 -22
- package/src/preload.ts +0 -10
- package/src/renderer.ts +0 -13
- package/src/utils/forward-ref.ts +0 -31
- package/src/utils/logger.ts +0 -430
- package/src/utils/radix-tree.ts +0 -243
- package/src/utils/types.ts +0 -21
- package/src/window/window-manager.ts +0 -302
- package/tsconfig.json +0 -29
- package/tsup.config.ts +0 -50
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @copyright 2025 NoxFly
|
|
3
|
-
* @license MIT
|
|
4
|
-
* @author NoxFly
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export class ResponseException extends Error {
|
|
8
|
-
public readonly status: number = 0;
|
|
9
|
-
|
|
10
|
-
constructor(message?: string);
|
|
11
|
-
constructor(statusCode?: number, message?: string);
|
|
12
|
-
constructor(statusOrMessage?: number | string, message?: string) {
|
|
13
|
-
let statusCode: number | undefined;
|
|
14
|
-
|
|
15
|
-
if(typeof statusOrMessage === 'number') {
|
|
16
|
-
statusCode = statusOrMessage;
|
|
17
|
-
}
|
|
18
|
-
else if(typeof statusOrMessage === 'string') {
|
|
19
|
-
message = statusOrMessage;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
super(message ?? "");
|
|
23
|
-
|
|
24
|
-
if(statusCode !== undefined) {
|
|
25
|
-
this.status = statusCode;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
this.name = this.constructor.name
|
|
29
|
-
.replace(/([A-Z])/g, ' $1');
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 4XX
|
|
34
|
-
export class BadRequestException extends ResponseException { public override readonly status = 400; }
|
|
35
|
-
export class UnauthorizedException extends ResponseException { public override readonly status = 401; }
|
|
36
|
-
export class PaymentRequiredException extends ResponseException { public override readonly status = 402; }
|
|
37
|
-
export class ForbiddenException extends ResponseException { public override readonly status = 403; }
|
|
38
|
-
export class NotFoundException extends ResponseException { public override readonly status = 404; }
|
|
39
|
-
export class MethodNotAllowedException extends ResponseException { public override readonly status = 405; }
|
|
40
|
-
export class NotAcceptableException extends ResponseException { public override readonly status = 406; }
|
|
41
|
-
export class RequestTimeoutException extends ResponseException { public override readonly status = 408; }
|
|
42
|
-
export class ConflictException extends ResponseException { public override readonly status = 409; }
|
|
43
|
-
export class UpgradeRequiredException extends ResponseException { public override readonly status = 426; }
|
|
44
|
-
export class TooManyRequestsException extends ResponseException { public override readonly status = 429; }
|
|
45
|
-
// 5XX
|
|
46
|
-
export class InternalServerException extends ResponseException { public override readonly status = 500; }
|
|
47
|
-
export class NotImplementedException extends ResponseException { public override readonly status = 501; }
|
|
48
|
-
export class BadGatewayException extends ResponseException { public override readonly status = 502; }
|
|
49
|
-
export class ServiceUnavailableException extends ResponseException { public override readonly status = 503; }
|
|
50
|
-
export class GatewayTimeoutException extends ResponseException { public override readonly status = 504; }
|
|
51
|
-
export class HttpVersionNotSupportedException extends ResponseException { public override readonly status = 505; }
|
|
52
|
-
export class VariantAlsoNegotiatesException extends ResponseException { public override readonly status = 506; }
|
|
53
|
-
export class InsufficientStorageException extends ResponseException { public override readonly status = 507; }
|
|
54
|
-
export class LoopDetectedException extends ResponseException { public override readonly status = 508; }
|
|
55
|
-
export class NotExtendedException extends ResponseException { public override readonly status = 510; }
|
|
56
|
-
export class NetworkAuthenticationRequiredException extends ResponseException { public override readonly status = 511; }
|
|
57
|
-
export class NetworkConnectTimeoutException extends ResponseException { public override readonly status = 599; }
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @copyright 2025 NoxFly
|
|
3
|
-
* @license MIT
|
|
4
|
-
* @author NoxFly
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { contextBridge, ipcRenderer } from 'electron/renderer';
|
|
8
|
-
import type { IPortRequester } from './renderer-client';
|
|
9
|
-
|
|
10
|
-
export interface NoxusPreloadAPI extends IPortRequester {}
|
|
11
|
-
|
|
12
|
-
export interface NoxusPreloadOptions {
|
|
13
|
-
exposeAs?: string;
|
|
14
|
-
initMessageType?: string;
|
|
15
|
-
requestChannel?: string;
|
|
16
|
-
responseChannel?: string;
|
|
17
|
-
targetWindow?: Window;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const DEFAULT_EXPOSE_NAME = 'noxus';
|
|
21
|
-
const DEFAULT_INIT_EVENT = 'init-port';
|
|
22
|
-
const DEFAULT_REQUEST_CHANNEL = 'gimme-my-port';
|
|
23
|
-
const DEFAULT_RESPONSE_CHANNEL = 'port';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Exposes a minimal bridge in the isolated preload context so renderer processes
|
|
27
|
-
* can request the two MessagePorts required by Noxus. The bridge forwards both
|
|
28
|
-
* request/response and socket ports to the renderer via window.postMessage.
|
|
29
|
-
*/
|
|
30
|
-
export function exposeNoxusBridge(options: NoxusPreloadOptions = {}): NoxusPreloadAPI {
|
|
31
|
-
const {
|
|
32
|
-
exposeAs = DEFAULT_EXPOSE_NAME,
|
|
33
|
-
initMessageType = DEFAULT_INIT_EVENT,
|
|
34
|
-
requestChannel = DEFAULT_REQUEST_CHANNEL,
|
|
35
|
-
responseChannel = DEFAULT_RESPONSE_CHANNEL,
|
|
36
|
-
targetWindow = window,
|
|
37
|
-
} = options;
|
|
38
|
-
|
|
39
|
-
const api: NoxusPreloadAPI = {
|
|
40
|
-
requestPort: () => {
|
|
41
|
-
ipcRenderer.send(requestChannel);
|
|
42
|
-
|
|
43
|
-
ipcRenderer.once(responseChannel, (event, message: { senderId: number }) => {
|
|
44
|
-
const ports = (event.ports ?? []).filter((port): port is MessagePort => port !== undefined);
|
|
45
|
-
|
|
46
|
-
if(ports.length === 0) {
|
|
47
|
-
console.error('[Noxus] No MessagePort received from main process.');
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
for(const port of ports) {
|
|
52
|
-
try {
|
|
53
|
-
port.start();
|
|
54
|
-
}
|
|
55
|
-
catch(error) {
|
|
56
|
-
console.error('[Noxus] Failed to start MessagePort.', error);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
targetWindow.postMessage(
|
|
61
|
-
{
|
|
62
|
-
type: initMessageType,
|
|
63
|
-
senderId: message?.senderId,
|
|
64
|
-
},
|
|
65
|
-
'*',
|
|
66
|
-
ports,
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
contextBridge.exposeInMainWorld(exposeAs, api);
|
|
73
|
-
|
|
74
|
-
return api;
|
|
75
|
-
}
|
|
@@ -1,374 +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
|
-
* Timeout in milliseconds for IPC requests.
|
|
22
|
-
* If the main process does not respond within this duration,
|
|
23
|
-
* the request Promise is rejected and the pending entry cleaned up.
|
|
24
|
-
* Defaults to 10 000 ms. Set to 0 to disable.
|
|
25
|
-
*/
|
|
26
|
-
requestTimeout?: number;
|
|
27
|
-
/** @default true */
|
|
28
|
-
enableLogging?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface PendingRequest<T = unknown> {
|
|
32
|
-
resolve: (value: T) => void;
|
|
33
|
-
reject: (reason: IResponse<T>) => void;
|
|
34
|
-
request: IRequest;
|
|
35
|
-
submittedAt: number;
|
|
36
|
-
timer?: ReturnType<typeof setTimeout>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const DEFAULT_INIT_EVENT = 'init-port';
|
|
40
|
-
const DEFAULT_BRIDGE_NAMES = ['noxus', 'ipcRenderer'];
|
|
41
|
-
|
|
42
|
-
function defaultRequestId(): string {
|
|
43
|
-
if(typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
44
|
-
return crypto.randomUUID();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return `${Date.now().toString(16)}-${Math.floor(Math.random() * 1e8).toString(16)}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function normalizeBridgeNames(preferred?: string | string[]): string[] {
|
|
51
|
-
const names: string[] = [];
|
|
52
|
-
|
|
53
|
-
const add = (name: string | undefined): void => {
|
|
54
|
-
if(!name)
|
|
55
|
-
return;
|
|
56
|
-
|
|
57
|
-
if(!names.includes(name)) {
|
|
58
|
-
names.push(name);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
if(Array.isArray(preferred)) {
|
|
63
|
-
for(const name of preferred) {
|
|
64
|
-
add(name);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
add(preferred);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
for(const fallback of DEFAULT_BRIDGE_NAMES) {
|
|
72
|
-
add(fallback);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return names;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function resolveBridgeFromWindow(windowRef: Window, preferred?: string | string[]): IPortRequester | null {
|
|
79
|
-
const names = normalizeBridgeNames(preferred);
|
|
80
|
-
const globalRef = windowRef as unknown as Record<string, unknown> | null | undefined;
|
|
81
|
-
|
|
82
|
-
if(!globalRef) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
for(const name of names) {
|
|
87
|
-
const candidate = globalRef[name];
|
|
88
|
-
|
|
89
|
-
if(candidate && typeof (candidate as IPortRequester).requestPort === 'function') {
|
|
90
|
-
return candidate as IPortRequester;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export class NoxRendererClient {
|
|
98
|
-
public readonly events = new RendererEventRegistry();
|
|
99
|
-
|
|
100
|
-
protected readonly pendingRequests = new Map<string, PendingRequest>();
|
|
101
|
-
|
|
102
|
-
protected requestPort: MessagePort | undefined;
|
|
103
|
-
protected socketPort: MessagePort | undefined;
|
|
104
|
-
protected senderId: number | undefined;
|
|
105
|
-
|
|
106
|
-
private readonly bridge: IPortRequester | null;
|
|
107
|
-
private readonly initMessageType: string;
|
|
108
|
-
private readonly windowRef: Window;
|
|
109
|
-
private readonly generateRequestId: () => string;
|
|
110
|
-
private readonly requestTimeout: number;
|
|
111
|
-
|
|
112
|
-
private isReady = false;
|
|
113
|
-
private setupPromise: Promise<void> | undefined;
|
|
114
|
-
private setupResolve: (() => void) | undefined;
|
|
115
|
-
private setupReject: ((reason: Error) => void) | undefined;
|
|
116
|
-
|
|
117
|
-
private enableLogging: boolean;
|
|
118
|
-
|
|
119
|
-
constructor(options: RendererClientOptions = {}) {
|
|
120
|
-
this.windowRef = options.windowRef ?? window;
|
|
121
|
-
const resolvedBridge = options.bridge ?? resolveBridgeFromWindow(this.windowRef, options.bridgeName);
|
|
122
|
-
this.bridge = resolvedBridge ?? null;
|
|
123
|
-
this.initMessageType = options.initMessageType ?? DEFAULT_INIT_EVENT;
|
|
124
|
-
this.generateRequestId = options.generateRequestId ?? defaultRequestId;
|
|
125
|
-
this.requestTimeout = options.requestTimeout ?? 10_000;
|
|
126
|
-
this.enableLogging = options.enableLogging ?? true;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
public async setup(): Promise<void> {
|
|
130
|
-
if(this.isReady) {
|
|
131
|
-
return Promise.resolve();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if(this.setupPromise) {
|
|
135
|
-
return this.setupPromise;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if(!this.bridge || typeof this.bridge.requestPort !== 'function') {
|
|
139
|
-
throw new Error('[Noxus] Renderer bridge is missing requestPort().');
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
this.setupPromise = new Promise<void>((resolve, reject) => {
|
|
143
|
-
this.setupResolve = resolve;
|
|
144
|
-
this.setupReject = reject;
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
this.windowRef.addEventListener('message', this.onWindowMessage);
|
|
148
|
-
this.bridge.requestPort();
|
|
149
|
-
|
|
150
|
-
return this.setupPromise;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
public dispose(): void {
|
|
154
|
-
this.windowRef.removeEventListener('message', this.onWindowMessage);
|
|
155
|
-
|
|
156
|
-
this.requestPort?.close();
|
|
157
|
-
this.socketPort?.close();
|
|
158
|
-
|
|
159
|
-
this.requestPort = undefined;
|
|
160
|
-
this.socketPort = undefined;
|
|
161
|
-
this.senderId = undefined;
|
|
162
|
-
this.isReady = false;
|
|
163
|
-
|
|
164
|
-
for(const pending of this.pendingRequests.values()) {
|
|
165
|
-
if(pending.timer !== undefined) {
|
|
166
|
-
clearTimeout(pending.timer);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
this.pendingRequests.clear();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
public async request<TResponse, TBody = unknown>(request: Omit<IRequest<TBody>, 'requestId' | 'senderId'>): Promise<TResponse> {
|
|
174
|
-
const senderId = this.senderId;
|
|
175
|
-
const requestId = this.generateRequestId();
|
|
176
|
-
|
|
177
|
-
if(senderId === undefined) {
|
|
178
|
-
return Promise.reject(this.createErrorResponse(requestId, 'MessagePort is not available'));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const readinessError = this.validateReady(requestId);
|
|
182
|
-
|
|
183
|
-
if(readinessError) {
|
|
184
|
-
return Promise.reject(readinessError as IResponse<TResponse>);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const message: IRequest<TBody> = {
|
|
188
|
-
requestId,
|
|
189
|
-
senderId,
|
|
190
|
-
...request,
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
return new Promise<TResponse>((resolve, reject) => {
|
|
194
|
-
const pending: PendingRequest<TResponse> = {
|
|
195
|
-
resolve,
|
|
196
|
-
reject: (response: IResponse<TResponse>) => reject(response),
|
|
197
|
-
request: message,
|
|
198
|
-
submittedAt: Date.now(),
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
if(this.requestTimeout > 0) {
|
|
202
|
-
pending.timer = setTimeout(() => {
|
|
203
|
-
this.pendingRequests.delete(message.requestId);
|
|
204
|
-
reject(this.createErrorResponse<TResponse>(message.requestId, `Request timed out after ${this.requestTimeout}ms`) as IResponse<TResponse>);
|
|
205
|
-
}, this.requestTimeout);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
this.pendingRequests.set(message.requestId, pending as PendingRequest);
|
|
209
|
-
|
|
210
|
-
this.requestPort!.postMessage(message);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
public async batch(requests: Omit<IBatchRequestItem<unknown>, 'requestId'>[]): Promise<IBatchResponsePayload> {
|
|
215
|
-
return this.request<IBatchResponsePayload>({
|
|
216
|
-
method: 'BATCH',
|
|
217
|
-
path: '',
|
|
218
|
-
body: {
|
|
219
|
-
requests,
|
|
220
|
-
},
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
public getSenderId(): number | undefined {
|
|
225
|
-
return this.senderId;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private readonly onWindowMessage = (event: MessageEvent): void => {
|
|
229
|
-
if(event.data?.type !== this.initMessageType) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if(!Array.isArray(event.ports) || event.ports.length < 2) {
|
|
234
|
-
const error = new Error('[Noxus] Renderer expected two MessagePorts (request + socket).');
|
|
235
|
-
|
|
236
|
-
console.error(error);
|
|
237
|
-
this.setupReject?.(error);
|
|
238
|
-
this.resetSetupState();
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
this.windowRef.removeEventListener('message', this.onWindowMessage);
|
|
243
|
-
|
|
244
|
-
this.requestPort = event.ports[0];
|
|
245
|
-
this.socketPort = event.ports[1];
|
|
246
|
-
this.senderId = event.data.senderId;
|
|
247
|
-
|
|
248
|
-
if(this.requestPort === undefined || this.socketPort === undefined) {
|
|
249
|
-
const error = new Error('[Noxus] Renderer failed to receive valid MessagePorts.');
|
|
250
|
-
console.error(error);
|
|
251
|
-
this.setupReject?.(error);
|
|
252
|
-
this.resetSetupState();
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
this.attachRequestPort(this.requestPort);
|
|
257
|
-
this.attachSocketPort(this.socketPort);
|
|
258
|
-
|
|
259
|
-
this.isReady = true;
|
|
260
|
-
this.setupResolve?.();
|
|
261
|
-
this.resetSetupState(true);
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
private readonly onSocketMessage = (event: MessageEvent): void => {
|
|
265
|
-
if(this.events.tryDispatchFromMessageEvent(event)) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
console.warn('[Noxus] Received a socket message that is not a renderer event payload.', event.data);
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
private readonly onRequestMessage = (event: MessageEvent): void => {
|
|
273
|
-
if(this.events.tryDispatchFromMessageEvent(event)) {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const response: IResponse = event.data;
|
|
278
|
-
|
|
279
|
-
if(!response || typeof response.requestId !== 'string') {
|
|
280
|
-
console.error('[Noxus] Renderer received an invalid response payload.', response);
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const pending = this.pendingRequests.get(response.requestId);
|
|
285
|
-
|
|
286
|
-
if(!pending) {
|
|
287
|
-
console.error(`[Noxus] No pending handler found for request ${response.requestId}.`);
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if(pending.timer !== undefined) {
|
|
292
|
-
clearTimeout(pending.timer);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
this.pendingRequests.delete(response.requestId);
|
|
296
|
-
|
|
297
|
-
this.onRequestCompleted(pending, response);
|
|
298
|
-
|
|
299
|
-
if(response.status >= 400) {
|
|
300
|
-
pending.reject(response as IResponse<any>);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
pending.resolve(response.body as unknown);
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
protected onRequestCompleted(pending: PendingRequest, response: IResponse): void {
|
|
308
|
-
if(!this.enableLogging) {
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if(typeof console.groupCollapsed === 'function') {
|
|
313
|
-
console.groupCollapsed(`${response.status} ${pending.request.method} /${pending.request.path}`);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if(response.error) {
|
|
317
|
-
console.error('error message:', response.error);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if(response.body !== undefined) {
|
|
321
|
-
console.info('response:', response.body);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
console.info('request:', pending.request);
|
|
325
|
-
console.info(`Request duration: ${Date.now() - pending.submittedAt} ms`);
|
|
326
|
-
|
|
327
|
-
if(typeof console.groupCollapsed === 'function') {
|
|
328
|
-
console.groupEnd();
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
private attachRequestPort(port: MessagePort): void {
|
|
333
|
-
port.onmessage = this.onRequestMessage;
|
|
334
|
-
port.start();
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
private attachSocketPort(port: MessagePort): void {
|
|
338
|
-
port.onmessage = this.onSocketMessage;
|
|
339
|
-
port.start();
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
private validateReady(requestId: string): IResponse | undefined {
|
|
343
|
-
if(!this.isElectronEnvironment()) {
|
|
344
|
-
return this.createErrorResponse(requestId, 'Not running in Electron environment');
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if(!this.requestPort) {
|
|
348
|
-
return this.createErrorResponse(requestId, 'MessagePort is not available');
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return undefined;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
private createErrorResponse<T>(requestId: string, message: string): IResponse<T> {
|
|
355
|
-
return {
|
|
356
|
-
status: 500,
|
|
357
|
-
requestId,
|
|
358
|
-
error: message,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
private resetSetupState(success = false): void {
|
|
363
|
-
if(!success) {
|
|
364
|
-
this.setupPromise = undefined;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
this.setupResolve = undefined;
|
|
368
|
-
this.setupReject = undefined;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
public isElectronEnvironment(): boolean {
|
|
372
|
-
return typeof window !== 'undefined' && /Electron/.test(window.navigator.userAgent);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
@@ -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
|
-
}
|