@noxfly/noxus 1.2.0 → 2.1.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.
@@ -6,7 +6,9 @@
6
6
 
7
7
  import { Lifetime } from "src/DI/app-injector";
8
8
  import { InjectorExplorer } from "src/DI/injector-explorer";
9
- import { Type } from "src/utils/types";
9
+ import { defineInjectableMetadata } from "src/decorators/injectable.metadata";
10
+ import { Type } from "src/main";
11
+ export { getInjectableMetadata, hasInjectableMetadata, INJECTABLE_METADATA_KEY } from "src/decorators/injectable.metadata";
10
12
 
11
13
  /**
12
14
  * The Injectable decorator marks a class as injectable.
@@ -15,25 +17,12 @@ import { Type } from "src/utils/types";
15
17
  * either from the constructor of the class that needs it of from the `inject` function.
16
18
  * @param lifetime - The lifetime of the injectable. Can be 'singleton', 'scope', or 'transient'.
17
19
  */
18
- export function Injectable(lifetime: Lifetime = 'scope'): ClassDecorator {
20
+ export function Injectable(lifetime: Lifetime = "scope"): ClassDecorator {
19
21
  return (target) => {
20
- if(typeof target !== 'function' || !target.prototype) {
22
+ if (typeof target !== "function" || !target.prototype) {
21
23
  throw new Error(`@Injectable can only be used on classes, not on ${typeof target}`);
22
24
  }
23
-
24
- Reflect.defineMetadata(INJECTABLE_METADATA_KEY, lifetime, target);
25
+ defineInjectableMetadata(target, lifetime);
25
26
  InjectorExplorer.register(target as unknown as Type<any>, lifetime);
26
27
  };
27
28
  }
28
-
29
- /**
30
- * Gets the injectable metadata for a given target class.
31
- * This metadata includes the lifetime of the injectable defined by the @Injectable decorator.
32
- * @param target - The target class to get the injectable metadata from.
33
- * @returns The lifetime of the injectable if it exists, otherwise undefined.
34
- */
35
- export function getInjectableMetadata(target: Type<unknown>): Lifetime | undefined {
36
- return Reflect.getMetadata(INJECTABLE_METADATA_KEY, target);
37
- }
38
-
39
- export const INJECTABLE_METADATA_KEY = Symbol('INJECTABLE_METADATA_KEY');
@@ -0,0 +1,15 @@
1
+ import type { Lifetime } from "src/DI/app-injector";
2
+
3
+ export const INJECTABLE_METADATA_KEY = Symbol("INJECTABLE_METADATA_KEY");
4
+
5
+ export function defineInjectableMetadata(target: Function, lifetime: Lifetime): void {
6
+ Reflect.defineMetadata(INJECTABLE_METADATA_KEY, lifetime, target);
7
+ }
8
+
9
+ export function getInjectableMetadata(target: Function): Lifetime | undefined {
10
+ return Reflect.getMetadata(INJECTABLE_METADATA_KEY, target) as Lifetime | undefined;
11
+ }
12
+
13
+ export function hasInjectableMetadata(target: Function): boolean {
14
+ return Reflect.hasMetadata(INJECTABLE_METADATA_KEY, target);
15
+ }
package/src/index.ts CHANGED
@@ -6,3 +6,4 @@
6
6
 
7
7
  export * from './request';
8
8
  export * from './renderer-events';
9
+ export * from './renderer-client';
package/src/main.ts CHANGED
@@ -6,7 +6,10 @@
6
6
 
7
7
  /**
8
8
  * Entry point for Electron main-process consumers.
9
+ * order of exports here matters and can affect module resolution.
10
+ * Please be cautious when modifying.
9
11
  */
12
+
10
13
  export * from './DI/app-injector';
11
14
  export * from './router';
12
15
  export * from './app';
@@ -18,8 +21,11 @@ export * from './decorators/controller.decorator';
18
21
  export * from './decorators/injectable.decorator';
19
22
  export * from './decorators/method.decorator';
20
23
  export * from './decorators/module.decorator';
24
+ export * from './preload-bridge';
21
25
  export * from './utils/logger';
22
26
  export * from './utils/types';
23
27
  export * from './request';
24
28
  export * from './renderer-events';
29
+ export * from './renderer-client';
25
30
  export * from './socket';
31
+
@@ -0,0 +1,75 @@
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 'src/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
+ }
@@ -0,0 +1,338 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
7
+ import { IBatchRequestItem, IBatchResponsePayload, IRequest, IResponse } from 'src/request';
8
+ import { RendererEventRegistry } from 'src/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
+ }
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,
package/src/router.ts CHANGED
@@ -145,6 +145,8 @@ export class Router {
145
145
  body: null,
146
146
  };
147
147
 
148
+ let isCritical: boolean = false;
149
+
148
150
  try {
149
151
  const routeDef = this.findRoute(request);
150
152
  await this.resolveController(request, response, routeDef);
@@ -162,11 +164,13 @@ export class Router {
162
164
  response.stack = error.stack;
163
165
  }
164
166
  else if(error instanceof Error) {
167
+ isCritical = true;
165
168
  response.status = 500;
166
169
  response.error = error.message || 'Internal Server Error';
167
170
  response.stack = error.stack || 'No stack trace available';
168
171
  }
169
172
  else {
173
+ isCritical = true;
170
174
  response.status = 500;
171
175
  response.error = 'Unknown error occurred';
172
176
  response.stack = 'No stack trace available';
@@ -177,15 +181,28 @@ export class Router {
177
181
 
178
182
  const message = `< ${response.status} ${request.method} /${request.path} ${Logger.colors.yellow}${Math.round(t1 - t0)}ms${Logger.colors.initial}`;
179
183
 
180
- if(response.status < 400)
184
+ if(response.status < 400) {
181
185
  Logger.log(message);
182
- else if(response.status < 500)
186
+ }
187
+ else if(response.status < 500) {
183
188
  Logger.warn(message);
184
- else
185
- Logger.error(message);
189
+ }
190
+ else {
191
+ if(isCritical) {
192
+ Logger.critical(message);
193
+ }
194
+ else {
195
+ Logger.error(message);
196
+ }
197
+ }
186
198
 
187
199
  if(response.error !== undefined) {
188
- Logger.error(response.error);
200
+ if(isCritical) {
201
+ Logger.critical(response.error);
202
+ }
203
+ else {
204
+ Logger.error(response.error);
205
+ }
189
206
 
190
207
  if(response.stack !== undefined) {
191
208
  Logger.errorStack(response.stack);
@@ -207,13 +224,15 @@ export class Router {
207
224
  body: { responses: [] },
208
225
  };
209
226
 
227
+ let isCritical: boolean = false;
228
+
210
229
  try {
211
230
  const payload = this.normalizeBatchPayload(request.body);
212
231
  const batchResponses: IResponse[] = [];
213
232
 
214
233
  for(const [index, item] of payload.requests.entries()) {
215
234
  const subRequestId = item.requestId ?? `${request.id}:${index}`;
216
- const atomicRequest = new Request(request.event, subRequestId, item.method, item.path, item.body);
235
+ const atomicRequest = new Request(request.event, request.senderId, subRequestId, item.method, item.path, item.body);
217
236
  batchResponses.push(await this.handleAtomic(atomicRequest));
218
237
  }
219
238
 
@@ -228,11 +247,13 @@ export class Router {
228
247
  response.stack = error.stack;
229
248
  }
230
249
  else if(error instanceof Error) {
250
+ isCritical = true;
231
251
  response.status = 500;
232
252
  response.error = error.message || 'Internal Server Error';
233
253
  response.stack = error.stack || 'No stack trace available';
234
254
  }
235
255
  else {
256
+ isCritical = true;
236
257
  response.status = 500;
237
258
  response.error = 'Unknown error occurred';
238
259
  response.stack = 'No stack trace available';
@@ -243,15 +264,28 @@ export class Router {
243
264
 
244
265
  const message = `< ${response.status} ${request.method} /${request.path} ${Logger.colors.yellow}${Math.round(t1 - t0)}ms${Logger.colors.initial}`;
245
266
 
246
- if(response.status < 400)
267
+ if(response.status < 400) {
247
268
  Logger.log(message);
248
- else if(response.status < 500)
269
+ }
270
+ else if(response.status < 500) {
249
271
  Logger.warn(message);
250
- else
251
- Logger.error(message);
272
+ }
273
+ else {
274
+ if(isCritical) {
275
+ Logger.critical(message);
276
+ }
277
+ else {
278
+ Logger.error(message);
279
+ }
280
+ }
252
281
 
253
282
  if(response.error !== undefined) {
254
- Logger.error(response.error);
283
+ if(isCritical) {
284
+ Logger.critical(response.error);
285
+ }
286
+ else {
287
+ Logger.error(response.error);
288
+ }
255
289
 
256
290
  if(response.stack !== undefined) {
257
291
  Logger.errorStack(response.stack);
package/src/socket.ts CHANGED
@@ -12,24 +12,29 @@ import { Injectable } from 'src/decorators/injectable.decorator';
12
12
  import { createRendererEventMessage } from 'src/request';
13
13
  import { Logger } from 'src/utils/logger';
14
14
 
15
+ interface RendererChannels {
16
+ request: Electron.MessageChannelMain;
17
+ socket: Electron.MessageChannelMain;
18
+ }
19
+
15
20
  @Injectable('singleton')
16
21
  export class NoxSocket {
17
- private readonly messagePorts = new Map<number, Electron.MessageChannelMain>();
22
+ private readonly channels = new Map<number, RendererChannels>();
18
23
 
19
- public register(senderId: number, channel: Electron.MessageChannelMain): void {
20
- this.messagePorts.set(senderId, channel);
24
+ public register(senderId: number, requestChannel: Electron.MessageChannelMain, socketChannel: Electron.MessageChannelMain): void {
25
+ this.channels.set(senderId, { request: requestChannel, socket: socketChannel });
21
26
  }
22
27
 
23
- public get(senderId: number): Electron.MessageChannelMain | undefined {
24
- return this.messagePorts.get(senderId);
28
+ public get(senderId: number): RendererChannels | undefined {
29
+ return this.channels.get(senderId);
25
30
  }
26
31
 
27
32
  public unregister(senderId: number): void {
28
- this.messagePorts.delete(senderId);
33
+ this.channels.delete(senderId);
29
34
  }
30
35
 
31
36
  public getSenderIds(): number[] {
32
- return [...this.messagePorts.keys()];
37
+ return [...this.channels.keys()];
33
38
  }
34
39
 
35
40
  public emit<TPayload = unknown>(eventName: string, payload?: TPayload, targetSenderIds?: number[]): number {
@@ -43,7 +48,7 @@ export class NoxSocket {
43
48
  let delivered = 0;
44
49
 
45
50
  for(const senderId of recipients) {
46
- const channel = this.messagePorts.get(senderId);
51
+ const channel = this.channels.get(senderId);
47
52
 
48
53
  if(!channel) {
49
54
  Logger.warn(`No message channel found for sender ID: ${senderId} while emitting "${normalizedEvent}".`);
@@ -51,7 +56,7 @@ export class NoxSocket {
51
56
  }
52
57
 
53
58
  try {
54
- channel.port1.postMessage(createRendererEventMessage(normalizedEvent, payload));
59
+ channel.socket.port1.postMessage(createRendererEventMessage(normalizedEvent, payload));
55
60
  delivered++;
56
61
  }
57
62
  catch(error) {