@nestjs/websockets 12.0.0-alpha.4 → 12.0.0-alpha.6

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/Readme.md CHANGED
@@ -59,11 +59,11 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
59
59
 
60
60
  <table style="text-align:center;">
61
61
  <tr>
62
- <td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
63
- <td><a href="https://microsoft.com/" target="_blank"><img src="https://nestjs.com/img/logos/microsoft-logo.png" width="180" valign="middle" /></a></td>
64
- <td><a href="https://mojam.co" target="_blank"><img src="https://nestjs.com/img/logos/mojam-logo.png" width="80" valign="middle" /></a></td>
62
+ <td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/github/trilon-logo.svg" width="200" valign="middle" /></a></td>
63
+ <td><a href="https://microsoft.com/" target="_blank"><img src="https://nestjs.com/img/github/microsoft-logo.png" width="180" valign="middle" /></a></td>
64
+ <td><a href="https://mojam.co" target="_blank"><img src="https://images.opencollective.com/mojamco/981e9de/logo/256.png?height=256" width="80" valign="middle" /></a></td>
65
65
  <td><a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td>
66
- <td><a href="https://serpapi.com/" target="_blank"><img src="https://nestjs.com/img/logos/serpapi-logo.png" width="150" valign="middle" /></a></td>
66
+ <td><a href="https://serpapi.com/" target="_blank"><img src="https://nestjs.com/img/github/serpapi-logo.png" width="150" valign="middle" /></a></td>
67
67
  </tr>
68
68
  </table>
69
69
 
@@ -71,15 +71,15 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
71
71
 
72
72
  <table style="text-align:center;">
73
73
  <tr>
74
- <td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/logos/red-hat-logo.svg" width="200" valign="middle" /></a></td>
75
- <td><a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td>
76
- <td><a href="https://nx.dev" target="_blank"><img src="https://nestjs.com/img/logos/nx-logo.png" height="45" valign="middle" /></a></td>
77
- <td><a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/logos/intrinisic-logo.png" width="210" valign="middle" /></a></td>
78
- <td><a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/logos/jetbrains-logo.svg" width="90" valign="middle" /></a></td>
74
+ <td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/github/red-hat-logo.svg" width="150" valign="middle" /></a></td>
75
+ <td><a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="120" valign="middle" /></a></td>
76
+ <td><a href="https://nx.dev" target="_blank"><img src="https://nestjs.com/img/github/nx-logo.png" height="45" valign="middle" /></a></td>
77
+ <td><a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/github/intrinisic-logo.png" width="150" valign="middle" /></a></td>
78
+ <td><a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/github/jetbrains-logo.svg" width="70" valign="middle" /></a></td>
79
79
  </tr>
80
80
  <tr>
81
- <td><a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/logos/snyk-logo-black.png" width="185" valign="middle" /></a></td>
82
- <td><a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/logos/movavi-logo.svg" width="105" valign="middle" /></a></td>
81
+ <td><a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/github/snyk-logo-black.png" width="125" valign="middle" /></a></td>
82
+ <td align="center"><a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://images.opencollective.com/movavi/1fb15e5/avatar/256.png?height=256" width="60" valign="middle" /></a></td>
83
83
  </tr>
84
84
  </table>
85
85
 
@@ -87,15 +87,15 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
87
87
 
88
88
  <table style="text-align:center;">
89
89
  <tr>
90
- <td><a href="https://www.mercedes-benz.com/" target="_blank"><img src="https://nestjs.com/img/logos/mercedes-logo.png" width="100" valign="middle" /></a></td>
91
- <td><a href="https://www.dinii.jp/" target="_blank"><img src="https://nestjs.com/img/logos/dinii-logo.png" width="65" valign="middle" /></a></td>
92
- <td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/logos/handsontable-dark-logo.svg#2" width="150" valign="middle" /></a></td>
93
- <td align="center" valign="middle"><a href="https://www.itflashcards.com/" target="_blank"><img src="https://nestjs.com/img/logos/it_flashcards-logo.png" width="170" valign="middle" /></a></td>
94
- <td align="center" valign="middle"><a href="https://arcjet.com/?ref=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/arcjet-logo.svg" width="170" valign="middle" /></a></td>
95
- <td align="center" valign="middle"><a href="https://crawljobs.com" target="_blank"><img src="https://nestjs.com/img/logos/crawljobs-logo.svg" width="130" valign="middle" /></a></td>
90
+ <td><a href="https://www.mercedes-benz.com/" target="_blank"><img src="https://nestjs.com/img/github/mercedes-logo.png" width="100" valign="middle" /></a></td>
91
+ <td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/github/handsontable-dark-logo.svg#2" width="150" valign="middle" /></a></td>
92
+ <td align="center" valign="middle"><a href="https://www.itflashcards.com/" target="_blank"><img src="https://nestjs.com/img/github/it_flashcards-logo.png" width="170" valign="middle" /></a></td>
93
+ <td align="center" valign="middle"><a href="https://arcjet.com/?ref=nestjs" target="_blank"><img src="https://nestjs.com/img/github/arcjet-logo.svg" width="170" valign="middle" /></a></td>
94
+ <td align="center" valign="middle"><a href="https://pandektes.com" target="_blank"><img src="https://nestjs.com/img/github/pandektes-logo.png" width="65" valign="middle" /></a></td>
96
95
  </tr><tr>
97
- <td align="center" valign="middle"><a href="https://pandektes.com" target="_blank"><img src="https://nestjs.com/img/logos/pandektes-logo.png" width="65" valign="middle" /></a></td>
98
- <td align="center" valign="middle"><a href="https://www.fintechcrafts.com/" target="_blank"><img src="https://nestjs.com/img/logos/fintechcrafts-logo.svg" width="65" valign="middle" /></a></td>
96
+ <td align="center" valign="middle"><a href="https://www.fintechcrafts.com/" target="_blank"><img src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/935813df-48a9-4b41-b923-7d70b5621c6b/2026-05-06%2010%2021%2033.png" width="65" valign="middle" /></a></td>
97
+ <td align="center" valign="middle"><a href="https://talordata.com/?campaignid=Ya5Xpw2gmhyee6Tg&utm_source=nestjs&utm_term=nestjs" target="_blank"><img src="https://images.opencollective.com/elowen/9ccac7f/avatar/512.png" width="65" valign="middle" /></a></td>
98
+ <td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="65" valign="middle" /></a></td>
99
99
  </tr>
100
100
  </table>
101
101
 
@@ -103,32 +103,12 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
103
103
 
104
104
  <table>
105
105
  <tr>
106
- <td align="center" valign="middle"><a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/logos/swingdev-logo.svg#1" width="110" valign="middle" /></a></td>
107
- <td align="center" valign="middle"><a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/novologic.png" width="110" valign="middle" /></a></td>
108
- <td align="center" valign="middle"><a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/logos/mantro-logo.svg" width="95" valign="middle" /></a></td>
109
- <td align="center" valign="middle"><a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/logos/triplebyte.png" width="107" valign="middle" /></a></td>
110
- <td align="center" valign="middle"><a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/logos/nearpod-logo.svg" width="100" valign="middle" /></a></td>
111
- <td align="center" valign="middle"><a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/logos/genuinebee.svg" width="97" valign="middle" /></a></td>
112
- </tr>
113
- <tr>
114
- <td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/logos/vpn-review-logo.png" width="85" valign="middle" /></a></td>
115
- <td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/logos/lambda-it-logo.svg" width="115" valign="middle" /></a></td>
116
- <td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/rocketech-logo.svg" width="110" valign="middle" /></a></td>
117
- <td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/logos/anonymistic-logo.png" width="125" valign="middle" /></a></td>
118
- <td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/naologic-logo.svg" width="125" valign="middle" /></a></td>
119
- <td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/logos/triplecore-logo.svg" width="50" valign="middle" /></a></td>
120
- </tr>
121
- <tr>
122
- <td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/logos/polygon-logo.svg" width="120" valign="middle" /></a></td>
123
- <td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/logos/boringowl-logo.svg" width="120" valign="middle" /></a></td>
124
- <td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/logos/nordbot-logo.png" width="120" valign="middle" /></a></td>
125
- <td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/logos/dopiosh-logo.png" width="50" valign="middle" /></a></td>
126
- <td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/logos/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
127
- <td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/logos/tripoffice-logo.png" width="140" valign="middle" /></a></td>
128
- </tr>
129
- <tr>
130
- <td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
131
- <td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
106
+ <td align="center" valign="middle"><a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/github/mantro-logo.svg" width="95" valign="middle" /></a></td>
107
+ <td align="center" valign="middle"><a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/github/triplebyte-logo.png" width="107" valign="middle" /></a></td>
108
+ <td align="center" valign="middle"><a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/github/nearpod-logo.svg" width="100" valign="middle" /></a></td>
109
+ <td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/github/naologic-logo.svg" width="125" valign="middle" /></a></td>
110
+ <td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/github/triplecore-logo.svg" width="50" valign="middle" /></a>
111
+ <td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/github/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
132
112
  </tr>
133
113
  </table>
134
114
 
@@ -22,6 +22,10 @@ export class AbstractWsAdapter {
22
22
  server.on(CONNECTION_EVENT, callback);
23
23
  }
24
24
  bindClientDisconnect(client, callback) {
25
+ if (typeof client.once === 'function') {
26
+ client.once(DISCONNECT_EVENT, callback);
27
+ return;
28
+ }
25
29
  client.on(DISCONNECT_EVENT, callback);
26
30
  }
27
31
  async close(server) {
@@ -6,6 +6,6 @@ import type { NestContainer } from '@nestjs/core';
6
6
  */
7
7
  export declare class ExceptionFiltersContext extends BaseExceptionFilterContext {
8
8
  constructor(container: NestContainer);
9
- create(instance: object, callback: <TClient>(client: TClient, data: any) => any, moduleKey: string): WsExceptionsHandler;
9
+ create(instance: object, callback: <TClient>(client: TClient, data: any) => any, moduleKey: string, contextId?: import("@nestjs/core").ContextId, inquirerId?: string): WsExceptionsHandler;
10
10
  getGlobalMetadata<T extends any[]>(): T;
11
11
  }
@@ -1,6 +1,6 @@
1
1
  import { WsExceptionsHandler } from '../exceptions/ws-exceptions-handler.js';
2
2
  import { EXCEPTION_FILTERS_METADATA, isEmptyArray, } from '@nestjs/common/internal';
3
- import { BaseExceptionFilterContext } from '@nestjs/core/internal';
3
+ import { BaseExceptionFilterContext, STATIC_CONTEXT, } from '@nestjs/core/internal';
4
4
  /**
5
5
  * @publicApi
6
6
  */
@@ -8,10 +8,10 @@ export class ExceptionFiltersContext extends BaseExceptionFilterContext {
8
8
  constructor(container) {
9
9
  super(container);
10
10
  }
11
- create(instance, callback, moduleKey) {
11
+ create(instance, callback, moduleKey, contextId = STATIC_CONTEXT, inquirerId) {
12
12
  this.moduleContext = moduleKey;
13
13
  const exceptionHandler = new WsExceptionsHandler();
14
- const filters = this.createContext(instance, callback, EXCEPTION_FILTERS_METADATA);
14
+ const filters = this.createContext(instance, callback, EXCEPTION_FILTERS_METADATA, contextId, inquirerId);
15
15
  if (isEmptyArray(filters)) {
16
16
  return exceptionHandler;
17
17
  }
@@ -25,7 +25,7 @@ export declare class WsContextCreator {
25
25
  private readonly wsParamsFactory;
26
26
  private readonly handlerMetadataStorage;
27
27
  constructor(wsProxy: WsProxy, exceptionFiltersContext: ExceptionFiltersContext, pipesContextCreator: PipesContextCreator, pipesConsumer: PipesConsumer, guardsContextCreator: GuardsContextCreator, guardsConsumer: GuardsConsumer, interceptorsContextCreator: InterceptorsContextCreator, interceptorsConsumer: InterceptorsConsumer);
28
- create<T extends ParamsMetadata = ParamsMetadata>(instance: Controller, callback: (...args: unknown[]) => void, moduleKey: string, methodName: string): (...args: any[]) => Promise<void>;
28
+ create<T extends ParamsMetadata = ParamsMetadata>(instance: Controller, callback: (...args: unknown[]) => void, moduleKey: string, methodName: string, contextId?: import("@nestjs/core").ContextId, inquirerId?: string): (...args: any[]) => Promise<void>;
29
29
  reflectCallbackParamtypes(instance: Controller, callback: (...args: any[]) => any): any[];
30
30
  reflectCallbackPattern(callback: (...args: any[]) => any): string;
31
31
  createGuardsFn<TContext extends string = ContextType>(guards: any[], instance: Controller, callback: (...args: unknown[]) => any, contextType?: TContext): Function | null;
@@ -1,5 +1,5 @@
1
1
  import { CUSTOM_ROUTE_ARGS_METADATA, isEmptyArray, PARAMTYPES_METADATA, } from '@nestjs/common/internal';
2
- import { ContextUtils, FORBIDDEN_MESSAGE, HandlerMetadataStorage, } from '@nestjs/core/internal';
2
+ import { ContextUtils, FORBIDDEN_MESSAGE, HandlerMetadataStorage, STATIC_CONTEXT, } from '@nestjs/core/internal';
3
3
  import { MESSAGE_METADATA, PARAM_ARGS_METADATA } from '../constants.js';
4
4
  import { WsException } from '../errors/ws-exception.js';
5
5
  import { WsParamsFactory } from '../factories/ws-params-factory.js';
@@ -26,13 +26,13 @@ export class WsContextCreator {
26
26
  this.interceptorsContextCreator = interceptorsContextCreator;
27
27
  this.interceptorsConsumer = interceptorsConsumer;
28
28
  }
29
- create(instance, callback, moduleKey, methodName) {
29
+ create(instance, callback, moduleKey, methodName, contextId = STATIC_CONTEXT, inquirerId) {
30
30
  const contextType = 'ws';
31
31
  const { argsLength, paramtypes, getParamsMetadata } = this.getMetadata(instance, methodName, contextType);
32
- const exceptionHandler = this.exceptionFiltersContext.create(instance, callback, moduleKey);
33
- const pipes = this.pipesContextCreator.create(instance, callback, moduleKey);
34
- const guards = this.guardsContextCreator.create(instance, callback, moduleKey);
35
- const interceptors = this.interceptorsContextCreator.create(instance, callback, moduleKey);
32
+ const exceptionHandler = this.exceptionFiltersContext.create(instance, callback, moduleKey, contextId, inquirerId);
33
+ const pipes = this.pipesContextCreator.create(instance, callback, moduleKey, contextId, inquirerId);
34
+ const guards = this.guardsContextCreator.create(instance, callback, moduleKey, contextId, inquirerId);
35
+ const interceptors = this.interceptorsContextCreator.create(instance, callback, moduleKey, contextId, inquirerId);
36
36
  const paramsMetadata = getParamsMetadata(moduleKey);
37
37
  const paramsOptions = paramsMetadata
38
38
  ? this.contextUtils.mergeParamsMetatypes(paramsMetadata, paramtypes)
@@ -43,7 +43,7 @@ export class GatewayMetadataExplorer {
43
43
  }
44
44
  *scanForServerHooks(instance) {
45
45
  for (const propertyKey in instance) {
46
- if (isFunction(propertyKey)) {
46
+ if (isFunction(instance[propertyKey])) {
47
47
  continue;
48
48
  }
49
49
  const property = String(propertyKey);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs/websockets",
3
- "version": "12.0.0-alpha.4",
3
+ "version": "12.0.0-alpha.6",
4
4
  "description": "Nest - modern, fast, powerful node.js web framework (@websockets)",
5
5
  "author": "Kamil Mysliwiec",
6
6
  "license": "MIT",
@@ -26,8 +26,8 @@
26
26
  "tslib": "2.8.1"
27
27
  },
28
28
  "devDependencies": {
29
- "@nestjs/common": "^12.0.0-alpha.4",
30
- "@nestjs/core": "^12.0.0-alpha.4"
29
+ "@nestjs/common": "^12.0.0-alpha.6",
30
+ "@nestjs/core": "^12.0.0-alpha.6"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@nestjs/common": "^11.0.0",
@@ -41,5 +41,5 @@
41
41
  "optional": true
42
42
  }
43
43
  },
44
- "gitHead": "415e4d33707e74998d8456be7bd305cedb6508e3"
44
+ "gitHead": "4c38a5ab100f1d2a0d50602998968a25509525c7"
45
45
  }
@@ -7,10 +7,12 @@ export declare class SocketModule<THttpServer = any, TAppOptions extends NestApp
7
7
  private applicationConfig;
8
8
  private webSocketsController;
9
9
  private isAdapterInitialized;
10
+ private adapterInitPromise;
10
11
  private httpServer;
11
12
  private appOptions;
12
- register(container: NestContainer, applicationConfig: ApplicationConfig, graphInspector: GraphInspector, appOptions: TAppOptions, httpServer?: THttpServer): void;
13
- connectAllGateways(providers: Map<InjectionToken, InstanceWrapper<Injectable>>, moduleName: string): void;
13
+ private injector;
14
+ register(container: NestContainer, applicationConfig: ApplicationConfig, graphInspector: GraphInspector, appOptions: TAppOptions, httpServer?: THttpServer): Promise<void[][]>;
15
+ connectAllGateways(providers: Map<InjectionToken, InstanceWrapper<Injectable>>, moduleName: string): Promise<void[]>;
14
16
  connectGatewayToServer(wrapper: InstanceWrapper<Injectable>, moduleName: string): Promise<void>;
15
17
  close(): Promise<any>;
16
18
  private initializeAdapter;
package/socket-module.js CHANGED
@@ -6,28 +6,38 @@ import { WsProxy } from './context/ws-proxy.js';
6
6
  import { SocketServerProvider } from './socket-server-provider.js';
7
7
  import { SocketsContainer } from './sockets-container.js';
8
8
  import { WebSocketsController } from './web-sockets-controller.js';
9
- import { GuardsConsumer, GuardsContextCreator, loadAdapter, InterceptorsConsumer, InterceptorsContextCreator, PipesConsumer, PipesContextCreator, } from '@nestjs/core/internal';
9
+ import { Injector, GuardsConsumer, GuardsContextCreator, loadAdapter, InterceptorsConsumer, InterceptorsContextCreator, PipesConsumer, PipesContextCreator, } from '@nestjs/core/internal';
10
10
  export class SocketModule {
11
11
  socketsContainer = new SocketsContainer();
12
12
  applicationConfig;
13
13
  webSocketsController;
14
14
  isAdapterInitialized;
15
+ adapterInitPromise = null;
15
16
  httpServer;
16
17
  appOptions;
18
+ injector;
17
19
  register(container, applicationConfig, graphInspector, appOptions, httpServer) {
18
20
  this.applicationConfig = applicationConfig;
19
21
  this.appOptions = appOptions;
20
22
  this.httpServer = httpServer;
21
- const contextCreator = this.getContextCreator(container);
23
+ this.injector = new Injector({
24
+ preview: container.contextOptions?.preview,
25
+ instanceDecorator: container.contextOptions?.instrument?.instanceDecorator,
26
+ });
27
+ const exceptionFiltersContext = new ExceptionFiltersContext(container);
28
+ const contextCreator = this.getContextCreator(container, applicationConfig, exceptionFiltersContext);
22
29
  const serverProvider = new SocketServerProvider(this.socketsContainer, applicationConfig);
23
- this.webSocketsController = new WebSocketsController(serverProvider, applicationConfig, contextCreator, graphInspector, this.appOptions);
30
+ this.webSocketsController = new WebSocketsController(serverProvider, applicationConfig, contextCreator, container, this.injector, exceptionFiltersContext, graphInspector, this.appOptions);
24
31
  const modules = container.getModules();
25
- modules.forEach(({ providers }, moduleName) => this.connectAllGateways(providers, moduleName));
32
+ return Promise.all(iterate(modules.entries())
33
+ .map(([moduleName, { providers }]) => this.connectAllGateways(providers, moduleName))
34
+ .toArray());
26
35
  }
27
36
  connectAllGateways(providers, moduleName) {
28
- iterate(providers.values())
37
+ return Promise.all(iterate(providers.values())
29
38
  .filter(wrapper => wrapper && !wrapper.isNotMetatype)
30
- .forEach(wrapper => this.connectGatewayToServer(wrapper, moduleName));
39
+ .map(wrapper => this.connectGatewayToServer(wrapper, moduleName))
40
+ .toArray());
31
41
  }
32
42
  async connectGatewayToServer(wrapper, moduleName) {
33
43
  const { instance, metatype } = wrapper;
@@ -36,9 +46,12 @@ export class SocketModule {
36
46
  return;
37
47
  }
38
48
  if (!this.isAdapterInitialized) {
39
- await this.initializeAdapter();
49
+ // Memoize the initialization promise so concurrent gateway connections
50
+ // share a single adapter instead of racing to create their own.
51
+ this.adapterInitPromise ??= this.initializeAdapter();
52
+ await this.adapterInitPromise;
40
53
  }
41
- this.webSocketsController.connectGatewayToServer(instance, metatype, moduleName, wrapper.id);
54
+ this.webSocketsController.connectGatewayToServer(wrapper, moduleName);
42
55
  }
43
56
  async close() {
44
57
  if (!this.applicationConfig) {
@@ -71,7 +84,7 @@ export class SocketModule {
71
84
  this.applicationConfig.setIoAdapter(ioAdapter);
72
85
  this.isAdapterInitialized = true;
73
86
  }
74
- getContextCreator(container) {
75
- return new WsContextCreator(new WsProxy(), new ExceptionFiltersContext(container), new PipesContextCreator(container), new PipesConsumer(), new GuardsContextCreator(container), new GuardsConsumer(), new InterceptorsContextCreator(container), new InterceptorsConsumer());
87
+ getContextCreator(container, config, exceptionFiltersContext) {
88
+ return new WsContextCreator(new WsProxy(), exceptionFiltersContext, new PipesContextCreator(container, config), new PipesConsumer(), new GuardsContextCreator(container, config), new GuardsConsumer(), new InterceptorsContextCreator(container, config), new InterceptorsConsumer());
76
89
  }
77
90
  }
@@ -1,5 +1,5 @@
1
1
  import { assignMetadata } from '@nestjs/common';
2
- import { isNil, isString } from '@nestjs/common/internal';
2
+ import { isNil, isParameterDecoratorOptions, isString, } from '@nestjs/common/internal';
3
3
  import 'reflect-metadata';
4
4
  import { PARAM_ARGS_METADATA } from '../constants.js';
5
5
  export function createWsParamDecorator(paramtype) {
@@ -11,10 +11,7 @@ export function createWsParamDecorator(paramtype) {
11
11
  }
12
12
  export const createPipesWsParamDecorator = (paramtype) => (data, optionsOrPipe, ...pipes) => (target, key, index) => {
13
13
  const args = Reflect.getMetadata(PARAM_ARGS_METADATA, target.constructor, key) || {};
14
- const isDataOptions = data &&
15
- typeof data === 'object' &&
16
- !('transform' in data) &&
17
- ('schema' in data || 'pipes' in data);
14
+ const isDataOptions = isParameterDecoratorOptions(data);
18
15
  if (isDataOptions) {
19
16
  const opts = data;
20
17
  Reflect.defineMetadata(PARAM_ARGS_METADATA, assignMetadata(args, paramtype, index, {
@@ -25,12 +22,10 @@ export const createPipesWsParamDecorator = (paramtype) => (data, optionsOrPipe,
25
22
  }
26
23
  const hasParamData = isNil(data) || isString(data);
27
24
  const paramData = hasParamData ? data : undefined;
28
- const isOptions = optionsOrPipe &&
29
- typeof optionsOrPipe === 'object' &&
30
- ('schema' in optionsOrPipe || 'pipes' in optionsOrPipe);
25
+ const isOptions = isParameterDecoratorOptions(optionsOrPipe);
31
26
  let paramPipes;
32
27
  if (isOptions) {
33
- paramPipes = optionsOrPipe.pipes ?? [];
28
+ paramPipes = [...(optionsOrPipe.pipes ?? []), ...pipes];
34
29
  }
35
30
  else if (hasParamData) {
36
31
  paramPipes = [optionsOrPipe, ...pipes].filter(Boolean);
@@ -7,26 +7,38 @@ import { ServerAndEventStreamsHost } from './interfaces/server-and-event-streams
7
7
  import { SocketServerProvider } from './socket-server-provider.js';
8
8
  import type { NestApplicationContextOptions } from '@nestjs/common/internal';
9
9
  import { type Type } from '@nestjs/common';
10
- import { type ApplicationConfig, type GraphInspector } from '@nestjs/core';
10
+ import { type ApplicationConfig, type ContextId, type GraphInspector, type NestContainer } from '@nestjs/core';
11
+ import { type Injector, type InstanceWrapper, type Module } from '@nestjs/core/internal';
12
+ import { ExceptionFiltersContext } from './context/exception-filters-context.js';
11
13
  export declare class WebSocketsController {
12
14
  private readonly socketServerProvider;
13
15
  private readonly config;
14
16
  private readonly contextCreator;
17
+ private readonly container;
18
+ private readonly injector;
19
+ private readonly exceptionFiltersContext;
15
20
  private readonly graphInspector;
16
21
  private readonly appOptions;
17
22
  private readonly logger;
18
23
  private readonly metadataExplorer;
19
- constructor(socketServerProvider: SocketServerProvider, config: ApplicationConfig, contextCreator: WsContextCreator, graphInspector: GraphInspector, appOptions?: NestApplicationContextOptions);
24
+ private readonly exceptionFiltersCache;
25
+ constructor(socketServerProvider: SocketServerProvider, config: ApplicationConfig, contextCreator: WsContextCreator, container: NestContainer, injector: Injector, exceptionFiltersContext: ExceptionFiltersContext, graphInspector: GraphInspector, appOptions?: NestApplicationContextOptions);
26
+ connectGatewayToServer(instanceWrapper: InstanceWrapper<NestGateway>, moduleKey: string): void;
20
27
  connectGatewayToServer(instance: NestGateway, metatype: Type<unknown> | Function, moduleKey: string, instanceWrapperId: string): void;
28
+ subscribeToServerEvents<T extends GatewayMetadata>(instanceWrapper: InstanceWrapper<NestGateway>, options: T, port: number, moduleKey: string, instanceWrapperId: string): void;
21
29
  subscribeToServerEvents<T extends GatewayMetadata>(instance: NestGateway, options: T, port: number, moduleKey: string, instanceWrapperId: string): void;
22
- subscribeEvents(instance: NestGateway, subscribersMap: MessageMappingProperties[], observableServer: ServerAndEventStreamsHost): void;
30
+ subscribeEvents(instanceWrapper: InstanceWrapper<NestGateway>, subscribersMap: MessageMappingProperties[], observableServer: ServerAndEventStreamsHost, connectionHandler?: Function, disconnectHandler?: Function): void;
23
31
  getConnectionHandler(context: WebSocketsController, instance: NestGateway, subscribersMap: MessageMappingProperties[], disconnect: Subject<any>, connection: Subject<any>): (...args: unknown[]) => void;
24
32
  subscribeInitEvent(instance: NestGateway, event: Subject<any>): void;
25
- subscribeConnectionEvent(instance: NestGateway, event: Subject<any>): void;
26
- subscribeDisconnectEvent(instance: NestGateway, event: Subject<any>): void;
33
+ subscribeConnectionEvent(handlerOrGateway: Function | NestGateway | undefined, event: Subject<any>): void;
34
+ subscribeDisconnectEvent(handlerOrGateway: Function | NestGateway | undefined, event: Subject<any>): void;
27
35
  subscribeMessages<T = any>(subscribersMap: MessageMappingProperties[], client: T, instance: NestGateway): void;
28
36
  pickResult(deferredResult: Promise<any>): Promise<Observable<any>>;
29
37
  inspectEntrypointDefinitions(instance: NestGateway, port: number, messageHandlers: MessageMappingProperties[], instanceWrapperId: string): void;
38
+ createRequestScopedHandler(instanceWrapper: InstanceWrapper<NestGateway>, moduleRef: Module, moduleKey: string, methodName: string): (...args: unknown[]) => Promise<void>;
39
+ createRequestScopedEventHandler(instanceWrapper: InstanceWrapper<NestGateway>, moduleRef: Module, moduleKey: string, methodName: 'handleConnection' | 'handleDisconnect', server: object): (...args: unknown[]) => Promise<unknown>;
40
+ getContextId<T extends Record<any, unknown> = any>(request: T, isTreeDurable: boolean): ContextId;
41
+ private cleanupRequestScopedContext;
30
42
  private assignServerToProperties;
31
43
  private printSubscriptionLogs;
32
44
  }
@@ -5,38 +5,84 @@ import { InvalidSocketPortException } from './errors/invalid-socket-port.excepti
5
5
  import { GatewayMetadataExplorer, } from './gateway-metadata-explorer.js';
6
6
  import { compareElementAt } from './utils/compare-element.util.js';
7
7
  import { Logger } from '@nestjs/common';
8
- import { MetadataScanner, } from '@nestjs/core';
8
+ import { ContextIdFactory, MetadataScanner, } from '@nestjs/core';
9
+ import { ExecutionContextHost, REQUEST_CONTEXT_ID, STATIC_CONTEXT, } from '@nestjs/core/internal';
9
10
  export class WebSocketsController {
10
11
  socketServerProvider;
11
12
  config;
12
13
  contextCreator;
14
+ container;
15
+ injector;
16
+ exceptionFiltersContext;
13
17
  graphInspector;
14
18
  appOptions;
15
19
  logger = new Logger(WebSocketsController.name, {
16
20
  timestamp: true,
17
21
  });
18
22
  metadataExplorer = new GatewayMetadataExplorer(new MetadataScanner());
19
- constructor(socketServerProvider, config, contextCreator, graphInspector, appOptions = {}) {
23
+ exceptionFiltersCache = new WeakMap();
24
+ constructor(socketServerProvider, config, contextCreator, container, injector, exceptionFiltersContext, graphInspector, appOptions = {}) {
20
25
  this.socketServerProvider = socketServerProvider;
21
26
  this.config = config;
22
27
  this.contextCreator = contextCreator;
28
+ this.container = container;
29
+ this.injector = injector;
30
+ this.exceptionFiltersContext = exceptionFiltersContext;
23
31
  this.graphInspector = graphInspector;
24
32
  this.appOptions = appOptions;
25
33
  }
26
- connectGatewayToServer(instance, metatype, moduleKey, instanceWrapperId) {
27
- const options = Reflect.getMetadata(GATEWAY_OPTIONS, metatype) || {};
28
- const port = Reflect.getMetadata(PORT_METADATA, metatype) || 0;
34
+ connectGatewayToServer(instanceOrWrapper, metatypeOrModuleKey, moduleKey, instanceWrapperId) {
35
+ const isInstanceWrapper = typeof metatypeOrModuleKey === 'string' &&
36
+ 'instance' in instanceOrWrapper;
37
+ const instance = isInstanceWrapper
38
+ ? instanceOrWrapper.instance
39
+ : instanceOrWrapper;
40
+ const metatype = isInstanceWrapper
41
+ ? instanceOrWrapper.metatype
42
+ : metatypeOrModuleKey;
43
+ const targetModuleKey = isInstanceWrapper
44
+ ? metatypeOrModuleKey
45
+ : moduleKey;
46
+ const targetInstanceWrapperId = isInstanceWrapper
47
+ ? instanceOrWrapper.id
48
+ : instanceWrapperId;
49
+ const instanceWrapper = isInstanceWrapper
50
+ ? instanceOrWrapper
51
+ : {
52
+ instance,
53
+ metatype,
54
+ id: targetInstanceWrapperId,
55
+ isDependencyTreeStatic: () => true,
56
+ isDependencyTreeDurable: () => false,
57
+ };
58
+ const gatewayMetatype = metatype ?? instance.constructor;
59
+ const options = Reflect.getMetadata(GATEWAY_OPTIONS, gatewayMetatype) || {};
60
+ const port = Reflect.getMetadata(PORT_METADATA, gatewayMetatype) || 0;
29
61
  if (!Number.isInteger(port)) {
30
- throw new InvalidSocketPortException(port, metatype);
62
+ throw new InvalidSocketPortException(port, gatewayMetatype);
31
63
  }
32
- this.subscribeToServerEvents(instance, options, port, moduleKey, instanceWrapperId);
64
+ this.subscribeToServerEvents(instanceWrapper, options, port, targetModuleKey, targetInstanceWrapperId);
33
65
  }
34
- subscribeToServerEvents(instance, options, port, moduleKey, instanceWrapperId) {
66
+ subscribeToServerEvents(instanceOrWrapper, options, port, moduleKey, instanceWrapperId) {
67
+ const instanceWrapper = 'instance' in instanceOrWrapper
68
+ ? instanceOrWrapper
69
+ : {
70
+ instance: instanceOrWrapper,
71
+ metatype: instanceOrWrapper.constructor,
72
+ id: instanceWrapperId,
73
+ isDependencyTreeStatic: () => true,
74
+ isDependencyTreeDurable: () => false,
75
+ };
76
+ const { instance } = instanceWrapper;
35
77
  const nativeMessageHandlers = this.metadataExplorer.explore(instance);
78
+ const isStatic = instanceWrapper.isDependencyTreeStatic();
79
+ const moduleRef = this.container.getModuleByKey(moduleKey);
36
80
  const messageHandlers = nativeMessageHandlers.map(({ callback, isAckHandledManually, message, methodName }) => ({
37
81
  message,
38
82
  methodName,
39
- callback: this.contextCreator.create(instance, callback, moduleKey, methodName),
83
+ callback: isStatic
84
+ ? this.contextCreator.create(instance, callback, moduleKey, methodName, STATIC_CONTEXT)
85
+ : this.createRequestScopedHandler(instanceWrapper, moduleRef, moduleKey, methodName),
40
86
  isAckHandledManually,
41
87
  }));
42
88
  this.inspectEntrypointDefinitions(instance, port, messageHandlers, instanceWrapperId);
@@ -45,14 +91,19 @@ export class WebSocketsController {
45
91
  }
46
92
  const observableServer = this.socketServerProvider.scanForSocketServer(options, port);
47
93
  this.assignServerToProperties(instance, observableServer.server);
48
- this.subscribeEvents(instance, messageHandlers, observableServer);
94
+ this.subscribeEvents(instanceWrapper, messageHandlers, observableServer, isStatic
95
+ ? instance.handleConnection?.bind(instance)
96
+ : this.createRequestScopedEventHandler(instanceWrapper, moduleRef, moduleKey, 'handleConnection', observableServer.server), isStatic
97
+ ? instance.handleDisconnect?.bind(instance)
98
+ : this.createRequestScopedEventHandler(instanceWrapper, moduleRef, moduleKey, 'handleDisconnect', observableServer.server));
49
99
  }
50
- subscribeEvents(instance, subscribersMap, observableServer) {
100
+ subscribeEvents(instanceWrapper, subscribersMap, observableServer, connectionHandler, disconnectHandler) {
101
+ const { instance } = instanceWrapper;
51
102
  const { init, disconnect, connection, server } = observableServer;
52
103
  const adapter = this.config.getIoAdapter();
53
104
  this.subscribeInitEvent(instance, init);
54
- this.subscribeConnectionEvent(instance, connection);
55
- this.subscribeDisconnectEvent(instance, disconnect);
105
+ this.subscribeConnectionEvent(connectionHandler, connection);
106
+ this.subscribeDisconnectEvent(disconnectHandler, disconnect);
56
107
  const handler = this.getConnectionHandler(this, instance, subscribersMap, disconnect, connection);
57
108
  adapter.bindClientConnect(server, handler);
58
109
  this.printSubscriptionLogs(instance, subscribersMap);
@@ -73,15 +124,21 @@ export class WebSocketsController {
73
124
  event.subscribe(instance.afterInit.bind(instance));
74
125
  }
75
126
  }
76
- subscribeConnectionEvent(instance, event) {
77
- if (instance.handleConnection) {
127
+ subscribeConnectionEvent(handlerOrGateway, event) {
128
+ const handler = typeof handlerOrGateway === 'function'
129
+ ? handlerOrGateway
130
+ : handlerOrGateway?.handleConnection?.bind(handlerOrGateway);
131
+ if (handler) {
78
132
  event
79
133
  .pipe(distinctUntilChanged((prev, curr) => compareElementAt(prev, curr, 0)))
80
- .subscribe((args) => instance.handleConnection(...args));
134
+ .subscribe((args) => handler(...args));
81
135
  }
82
136
  }
83
- subscribeDisconnectEvent(instance, event) {
84
- if (instance.handleDisconnect) {
137
+ subscribeDisconnectEvent(handlerOrGateway, event) {
138
+ const handler = typeof handlerOrGateway === 'function'
139
+ ? handlerOrGateway
140
+ : handlerOrGateway?.handleDisconnect?.bind(handlerOrGateway);
141
+ if (handler) {
85
142
  event
86
143
  .pipe(distinctUntilChanged((prev, curr) => {
87
144
  const prevClient = prev?.client || prev;
@@ -90,11 +147,11 @@ export class WebSocketsController {
90
147
  }))
91
148
  .subscribe((data) => {
92
149
  if (data && typeof data === 'object' && 'client' in data) {
93
- instance.handleDisconnect(data.client, data.reason);
150
+ handler(data.client, data.reason);
94
151
  }
95
152
  else {
96
153
  // Backward compatibility: if it's just the client
97
- instance.handleDisconnect(data);
154
+ handler(data);
98
155
  }
99
156
  });
100
157
  }
@@ -133,6 +190,83 @@ export class WebSocketsController {
133
190
  }, instanceWrapperId);
134
191
  });
135
192
  }
193
+ createRequestScopedHandler(instanceWrapper, moduleRef, moduleKey, methodName) {
194
+ const { instance } = instanceWrapper;
195
+ const collection = moduleRef.providers;
196
+ const isTreeDurable = instanceWrapper.isDependencyTreeDurable();
197
+ return async (...args) => {
198
+ const [client] = args;
199
+ try {
200
+ const contextId = this.getContextId(client, isTreeDurable);
201
+ const contextInstance = await this.injector.loadPerContext(instance, moduleRef, collection, contextId);
202
+ return this.contextCreator.create(contextInstance, contextInstance[methodName], moduleKey, methodName, contextId, instanceWrapper.id)(...args);
203
+ }
204
+ catch (err) {
205
+ let exceptionFilter = this.exceptionFiltersCache.get(instance[methodName]);
206
+ if (!exceptionFilter) {
207
+ exceptionFilter = this.exceptionFiltersContext.create(instance, instance[methodName], moduleKey);
208
+ this.exceptionFiltersCache.set(instance[methodName], exceptionFilter);
209
+ }
210
+ const host = new ExecutionContextHost(args);
211
+ host.setType('ws');
212
+ exceptionFilter.handle(err, host);
213
+ }
214
+ };
215
+ }
216
+ createRequestScopedEventHandler(instanceWrapper, moduleRef, moduleKey, methodName, server) {
217
+ const { instance } = instanceWrapper;
218
+ const collection = moduleRef.providers;
219
+ const isTreeDurable = instanceWrapper.isDependencyTreeDurable();
220
+ const targetCallback = instance[methodName];
221
+ return async (...args) => {
222
+ const [client] = args;
223
+ let contextId;
224
+ try {
225
+ contextId = this.getContextId(client, isTreeDurable);
226
+ const contextInstance = await this.injector.loadPerContext(instance, moduleRef, collection, contextId);
227
+ this.assignServerToProperties(contextInstance, server);
228
+ const scopedMethod = contextInstance[methodName];
229
+ return scopedMethod?.apply(contextInstance, args);
230
+ }
231
+ catch (err) {
232
+ if (!targetCallback) {
233
+ throw err;
234
+ }
235
+ let exceptionFilter = this.exceptionFiltersCache.get(targetCallback);
236
+ if (!exceptionFilter) {
237
+ exceptionFilter = this.exceptionFiltersContext.create(instance, targetCallback, moduleKey);
238
+ this.exceptionFiltersCache.set(targetCallback, exceptionFilter);
239
+ }
240
+ const host = new ExecutionContextHost(args);
241
+ host.setType('ws');
242
+ exceptionFilter.handle(err, host);
243
+ }
244
+ finally {
245
+ if (methodName === 'handleDisconnect' && contextId) {
246
+ this.cleanupRequestScopedContext(instanceWrapper, contextId, client);
247
+ }
248
+ }
249
+ };
250
+ }
251
+ getContextId(request, isTreeDurable) {
252
+ const contextId = ContextIdFactory.getByRequest(request);
253
+ if (!request[REQUEST_CONTEXT_ID]) {
254
+ Object.defineProperty(request, REQUEST_CONTEXT_ID, {
255
+ value: contextId,
256
+ enumerable: false,
257
+ writable: false,
258
+ configurable: true,
259
+ });
260
+ const requestProviderValue = isTreeDurable
261
+ ? contextId.payload
262
+ : Object.assign(request, contextId.payload);
263
+ this.container.registerRequestProvider(requestProviderValue, contextId);
264
+ }
265
+ return contextId;
266
+ }
267
+ cleanupRequestScopedContext(_instanceWrapper, _contextId, request) {
268
+ Reflect.deleteProperty(request, REQUEST_CONTEXT_ID);
269
+ }
136
270
  assignServerToProperties(instance, server) {
137
271
  for (const propertyKey of this.metadataExplorer.scanForServerHooks(instance)) {
138
272
  Reflect.set(instance, propertyKey, server);