Full Code of typestack/socket-controllers for AI

develop efb981d1d9d0 cached
119 files
158.7 KB
40.1k tokens
246 symbols
1 requests
Download .txt
Repository: typestack/socket-controllers
Branch: develop
Commit: efb981d1d9d0
Files: 119
Total size: 158.7 KB

Directory structure:
gitextract_1if_z9rq/

├── .github/
│   ├── dependabot.yml
│   ├── semantic.yml
│   └── workflows/
│       ├── auto-approve-dependabot-workflow.yml
│       ├── continuous-deployment-workflow.yml
│       ├── continuous-integration-workflow.yml
│       └── lock-closed-issues-workflow.yml
├── .gitignore
├── .prettierrc.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── codecov.yml
├── eslint.config.mjs
├── jest.config.js
├── jest.setup.js
├── package.json
├── sample/
│   ├── sample1-simple-controller/
│   │   ├── Message.ts
│   │   ├── MessageController.ts
│   │   ├── app.ts
│   │   └── index.html
│   ├── sample2-use-created-socket-io/
│   │   ├── Message.ts
│   │   ├── MessageController.ts
│   │   ├── app.ts
│   │   └── index.html
│   ├── sample3-namespaces/
│   │   ├── Message.ts
│   │   ├── MessageController.ts
│   │   ├── app.ts
│   │   └── index.html
│   ├── sample4-emitters/
│   │   ├── Message.ts
│   │   ├── MessageController.ts
│   │   ├── app.ts
│   │   └── index.html
│   ├── sample5-middlewares/
│   │   ├── AuthenticationMiddleware.ts
│   │   ├── Message.ts
│   │   ├── MessageController.ts
│   │   ├── app.ts
│   │   └── index.html
│   └── sample6-dynamic-namespaces/
│       ├── Message.ts
│       ├── MessageController.ts
│       ├── app.ts
│       └── index.html
├── src/
│   ├── SocketControllers.ts
│   ├── decorators/
│   │   ├── ConnectedSocket.ts
│   │   ├── EmitOnFail.ts
│   │   ├── EmitOnSuccess.ts
│   │   ├── MessageAck.ts
│   │   ├── MessageBody.ts
│   │   ├── Middleware.ts
│   │   ├── NspParam.ts
│   │   ├── NspParams.ts
│   │   ├── OnConnect.ts
│   │   ├── OnDisconnect.ts
│   │   ├── OnDisconnecting.ts
│   │   ├── OnMessage.ts
│   │   ├── SkipEmitOnEmptyResult.ts
│   │   ├── SocketController.ts
│   │   ├── SocketIO.ts
│   │   ├── SocketId.ts
│   │   ├── SocketQueryParam.ts
│   │   ├── SocketRequest.ts
│   │   ├── SocketRooms.ts
│   │   └── UseInterceptor.ts
│   ├── index.ts
│   ├── types/
│   │   ├── ActionMetadata.ts
│   │   ├── ActionTransformOptions.ts
│   │   ├── ControllerMetadata.ts
│   │   ├── HandlerMetadata.ts
│   │   ├── InterceptorInterface.ts
│   │   ├── MiddlewareInterface.ts
│   │   ├── MiddlewareMetadata.ts
│   │   ├── ParameterMetadata.ts
│   │   ├── ResultMetadata.ts
│   │   ├── SocketControllerMetaKey.ts
│   │   ├── SocketControllersOptions.ts
│   │   ├── SocketEventContext.ts
│   │   ├── TransformOptions.ts
│   │   ├── constants/
│   │   │   └── defaultTransformOptions.ts
│   │   └── enums/
│   │       ├── HandlerType.ts
│   │       ├── ParameterType.ts
│   │       ├── ResultType.ts
│   │       └── SocketEventType.ts
│   └── util/
│       ├── add-action-to-controller-metadata.ts
│       ├── add-controller-metadata.ts
│       ├── add-interceptor-to-action-metadata.ts
│       ├── add-middleware-metadata.ts
│       ├── add-parameter-to-action-metadata.ts
│       ├── add-result-to-action-metadata.ts
│       ├── chain-execute.ts
│       └── get-metadata.ts
├── test/
│   ├── functional/
│   │   ├── connected-socket.spec.ts
│   │   ├── controllers/
│   │   │   ├── test.controller.ts
│   │   │   └── test2.controller.ts
│   │   ├── create-socket-server.spec.ts
│   │   ├── emit-on-fail.spec.ts
│   │   ├── emit-on-success.spec.ts
│   │   ├── load-controllers-from-directory.spec.ts
│   │   ├── middlewares.spec.ts
│   │   ├── multiple-controllers-on-same-namespace.spec.ts
│   │   ├── nsp-param.spec.ts
│   │   ├── nsp-params.spec.ts
│   │   ├── on-disconnect.spec.ts
│   │   ├── on-disconnecting.spec.ts
│   │   ├── parameter-transformation.spec.ts
│   │   ├── scoped-controllers.spec.ts
│   │   ├── skip-emit-on-empty-result.spec.ts
│   │   ├── socket-id.spec.ts
│   │   ├── socket-io.spec.ts
│   │   ├── socket-message-ack.spec.ts
│   │   ├── socket-message-body.spec.ts
│   │   ├── socket-query-param.spec.ts
│   │   ├── socket-request.spec.ts
│   │   ├── socket-rooms.spec.ts
│   │   └── use-interceptor.spec.ts
│   └── utilities/
│       ├── testSocketConnection.ts
│       ├── waitForEvent.ts
│       └── waitForTime.ts
├── tsconfig.json
├── tsconfig.prod.json
└── tsconfig.spec.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
  directory: "/"
  schedule:
    interval: daily
    time: "10:00"
    timezone: Europe/Budapest
  open-pull-requests-limit: 5
  versioning-strategy: increase
  commit-message:
    prefix: build
    include: scope
  ignore:
    - dependency-name: "husky"
    - dependency-name: "socket.io"
    - dependency-name: "socket.io-client"

================================================
FILE: .github/semantic.yml
================================================
titleAndCommits: true
allowMergeCommits: false
scopes:
  - deps
  - deps-dev
types:
  - feat
  - fix
  - docs
  - style
  - refactor
  - perf
  - test
  - build
  - ci
  - chore
  - revert
  - merge


================================================
FILE: .github/workflows/auto-approve-dependabot-workflow.yml
================================================
name: Dependabot auto-merge
on:
  pull_request_target
jobs:
  dependabot:
    runs-on: ubuntu-latest
    if: github.actor == 'dependabot[bot]'
    steps:
      - name: 'Auto approve PR by Dependabot'
        uses: hmarr/auto-approve-action@v2.0.0
        with:
          github-token: "${{ secrets.TYPESTACK_BOT_TOKEN }}"
      - name: 'Comment merge command'
        uses: actions/github-script@v3
        with:
          github-token: ${{secrets.TYPESTACK_BOT_TOKEN }}
          script: |
            await github.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: '@dependabot squash and merge'
            })


================================================
FILE: .github/workflows/continuous-deployment-workflow.yml
================================================
name: CD
on:
  release:
    types: [created]
jobs:
  publish:
    name: Publish to NPM
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 'lts/*'
          registry-url: https://registry.npmjs.org
      - run: npm ci --ignore-scripts
      - run: npm run prettier:check
      - run: npm run lint:check
      - run: npm run test:ci
      - run: npm run build
      - run: cp LICENSE build/LICENSE
      - run: cp README.md build/README.md
      - run: jq 'del(.devDependencies) | del(.scripts)' package.json > build/package.json
      - run: npm publish ./build
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}


================================================
FILE: .github/workflows/continuous-integration-workflow.yml
================================================
name: CI
on: [push, pull_request]
jobs:
  checks:
    name: Linters
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 'lts/*'
      - run: npm ci --ignore-scripts
      - run: npm run prettier:check
      - run: npm run lint:check
  tests:
    name: Tests
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: ['lts/*', 'current']
      fail-fast: false
    steps:
      - uses: actions/checkout@v3
      - name: Setting up Node.js (v${{ matrix.node-version }}.x)
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci --ignore-scripts
      - run: npm run test:ci
      - run: npm install codecov -g
        if: ${{ matrix.node-version == 'current' }}
      - run: codecov -f ./coverage/clover.xml -t ${{ secrets.CODECOV_TOKEN }} --commit=$GITHUB_SHA --branch=${GITHUB_REF##*/}
        if: ${{ matrix.node-version == 'current' }}
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 'lts/*'
      - run: npm ci --ignore-scripts
      - run: npm run build


================================================
FILE: .github/workflows/lock-closed-issues-workflow.yml
================================================
name: 'Lock inactive threads'
on:
  schedule:
    - cron: '0 0 * * *'
jobs:
  lock:
    name: Lock closed issues
    runs-on: ubuntu-latest
    steps:
      - uses: dessant/lock-threads@v2
        with:
          github-token: ${{ github.token }}
          issue-lock-inactive-days: 30
          pr-lock-inactive-days: 30
          issue-lock-comment: >
            This issue has been automatically locked since there
            has not been any recent activity after it was closed.
            Please open a new issue for related bugs.
          pr-lock-comment: >
            This pull request has been automatically locked since there
            has not been any recent activity after it was closed.
            Please open a new issue for related bugs.


================================================
FILE: .gitignore
================================================
# Log files
logs
*.log
*.tmp
*.tmp.*
log.txt
npm-debug.log*

# Testing output
lib-cov/**
coverage/**

# Environment files
.env

# Dependency directories
node_modules

# MacOS related files
*.DS_Store
.AppleDouble
.LSOverride
._*
UserInterfaceState.xcuserstate

# Windows related files
Thumbs.db
Desktop.ini
$RECYCLE.BIN/

# IDE - Sublime
*.sublime-project
*.sublime-workspace

# IDE - VSCode
.vscode/**
!.vscode/tasks.json
!.vscode/launch.json

# IDE - IntelliJ
.idea

# Compilation output folders
dist/
build/
tmp/
out-tsc/
temp

# Files for playing around locally
playground.ts
playground.js

================================================
FILE: .prettierrc.yml
================================================
printWidth: 120
tabWidth: 2
useTabs: false
semi: true
singleQuote: true
trailingComma: es5
bracketSpacing: true
arrowParens: avoid

================================================
FILE: CHANGELOG.md
================================================
# Changelog

_This changelog follows the [keep a changelog][keep-a-changelog]_ format to maintain a human readable changelog.

## [0.3.1](https://github.com/typestack/socket-controllers/compare/v0.3.0...v0.3.1) (2024-04-21)

### Changed

- Added missing `@OnDisconnecting()` export to index

## [0.3.0](https://github.com/typestack/socket-controllers/compare/v0.2.0...v0.3.0) (2024-04-21)

### Breaking Changes

- Middlewares without a defined namespace are registered on all namespaces

### Changed

- `glob` package updated from `10.0.0` to `10.3.12`
- `path-to-regexp` package updated from `6.2.1` to `6.2.2`
- `reflect-metadata` package updated from `0.1.13` to `0.2.2`
- `socket.io` package updated from `4.5.4` to `4.7.5`

## [0.2.0](https://github.com/typestack/socket-controllers/compare/v0.1.2...v0.2.0) (2023-04-10)

### Breaking Changes

- Replaced `ScopedContainerGetterParams` with `SocketEventContext`

  BEFORE:

  ```ts
  scopedContainerGetter: (args: ScopedContainerGetterParams) => {
    // ...
  }
  ```

  AFTER:

  ```ts
  scopedContainerGetter: (args: SocketEventContext) => {
    // ...
  }
  ```
  Note: The new interface contains all properties of the previous


### Added

- Added scoped container dispose support  
- Added interceptor support
- Added ack support

### Changed

- `glob` package updated from `8.1.0` to `10.0.0`
  
## [0.1.2](https://github.com/typestack/socket-controllers/compare/v0.1.1...v0.1.2) (2023-01-30)

### Added

- Added scoped controller support

  ```typescript
  // create and run socket server
  const server = new SocketControllers({
    ...
    scopedContainerGetter: (args: ScopedContainerGetterParams) => {
      // Return a container instance to be used to instantiate 
      // the controllers and their dependencies on each event
    }
  });
  ```

## [0.1.1](https://github.com/typestack/socket-controllers/compare/v0.1.0...v0.1.1) (2023-01-27)

### Added

- Added `@OnDisconnecting()` decorator
- Added error type filter option to `@EmitOnFail()` decorator
  
  Example: `@EmitOnFail('message', {errorType: TypeError})`

- Added `index` option to `@MessageBody()` decorator to be able to get multiple event arguments

  Note: If you don't specify the index it will return the first

- Added support to use the same namespace for multiple controllers

  Note: The namespaces must match exactly, providing a differnet pattern will not work due to a socket.io limitation

### Changed

- `glob` package updated from `8.0.3` to `8.1.0`

## [0.1.0](https://github.com/typestack/socket-controllers/compare/v0.0.5...v0.1.0) (2023-01-18)

### Breaking Changes

- Removed `createSocketServer()` in favor of constructor initialization 

  BEFORE:

  ```ts
  import { useSocketServer } from 'socket-controllers';
  import { Server } from 'socket.io';

  const io = new Server(PORT);
  useSocketServer(io);
  ```

  AFTER:

  ```ts
  import { SocketControllers } from "socket-controllers";
  import { Server } from "socket.io";

  const io = new Server(PORT);
  new SocketControllers({io: io, container: YOUR_DI_CONTAINER});
  ```
- Removed `createSocketServer()` in favor of constructor initialization 

  BEFORE:

  ```ts
  import { createSocketServer } from 'socket-controllers';

  const io = createSocketServer(PORT);
  ```

  AFTER:

  ```ts
  import { SocketControllers } from "socket-controllers";

  const server = new SocketControllers({port: PORT, container: YOUR_DI_CONTAINER});
  const io = server.io;
  ```

- Removed `useContainer()` in favor of constructor initialization

  BEFORE:

  ```ts
  import { useContainer } from 'socket-controllers';
  import { Container } from 'typedi';

  useContainer(Container);
  ```

  AFTER:

  ```ts
  import { SocketControllers } from "socket-controllers";
  import { Container } from 'typedi';

  const server = new SocketControllers({port: PORT, container: Container});
  ```
  > Note: DI container is not included anymore, you have to provide your own.

- Changed initialization parameters

  Before:
  ```typescript
  interface SocketControllersOptions {
    controllers?: Function[] | string[];
    middlewares?: Function[] | string[];
    useClassTransformer?: boolean;
    classToPlainTransformOptions?: ClassTransformOptions;
    plainToClassTransformOptions?: ClassTransformOptions;
  }
  ```

  After:
  ```typescript
  interface SocketControllersOptions {
    container: { get<T>(someClass: { new (...args: any[]): T } | Function): T };
    io?: Server;
    port?: number;
    controllers?: Function[] | string[];
    middlewares?: Function[] | string[];
    transformOption?: Partial<{
      transform?: boolean;
      parameterTransformOptions?: ClassTransformOptions;
      resultTransformOptions?: ClassTransformOptions;
    }>;
  }
  ```

- Changed class-transformer property name in decorators that support class-transformer

  Before:
  `classTransformOptions?: ClassTransformOptions`

  After:
  `transformOptions?: ClassTransformOptions`

### Added
- Namespace scope support for middlewares
- `transform: boolean` option to decorators that support class-transformer

### Changed

- `class-transformer` package updated from `0.1.6` to `0.5.1`
- `path-to-regexp` package updated from `3.0.0` to `6.2.1`
- `reflect-metadata` package updated from `0.1.10` to `0.1.13`
- `socket.io` package updated from `2.0.1` to `4.5.4`
- updated various dev dependencies


### [0.0.5][v0.0.5] - 2020-02-04

#### Added

- Added support dynamic namespace
- Added `NspParams`, `NspParam` decorators to handle dynamic namespace name params
- Allowed use function array for controllers and middlewares

#### Fixed

- Import middlewares from directory

[v0.0.5]: https://github.com/typestack/socket-controllers/compare/v0.0.4...v0.0.5
[keep-a-changelog]: https://keepachangelog.com/en/1.0.0/


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018-2020 TypeStack

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# socket-controllers

![Build Status](https://github.com/typestack/socket-controllers/workflows/CI/badge.svg)
[![codecov](https://codecov.io/gh/typestack/socket-controllers/branch/develop/graph/badge.svg)](https://codecov.io/gh/typestack/socket-controllers)
[![npm version](https://badge.fury.io/js/socket-controllers.svg)](https://badge.fury.io/js/socket-controllers)

Use class-based controllers to handle websocket events. Helps to organize your code using websockets in classes.

## Installation

1. Install `socket-controllers`:

   ```
   npm install socket-controllers
   ```

2. Install `reflect-metadata` shim:

   ```
   npm install reflect-metadata
   ```

   and make sure to import it in a global place, like app.ts:

   ```typescript
   import 'reflect-metadata';
   ```
   
3. Install a DI container, for example `typedi`;
   
   ```
   npm install typedi
   ```

## Example of usage

1. Create a file `MessageController.ts`

   ```typescript
   import {
     OnConnect,
     SocketController,
     ConnectedSocket,
     OnDisconnect,
     MessageBody,
     OnMessage,
   } from 'socket-controllers';
   import {Service} from 'typedi'; // Only if you are using typedi

   @SocketController()
   @Service() // Only if you are using typedi
   export class MessageController {
     @OnConnect()
     connection(@ConnectedSocket() socket: any) {
       console.log('client connected');
     }

     @OnDisconnect()
     disconnect(@ConnectedSocket() socket: any) {
       console.log('client disconnected');
     }

     @OnMessage('save')
     save(@ConnectedSocket() socket: any, @MessageBody() message: any) {
       console.log('received message:', message);
       console.log('setting id to the message and sending it back to the client');
       message.id = 1;
       socket.emit('message_saved', message);
     }
   }
   ```

2. Create a file `app.ts`

   ```typescript
   import 'es6-shim'; // this shim is optional if you are using old version of node
   import 'reflect-metadata'; // this shim is required
   import { SocketControllers } from 'socket-controllers';
   import { MessageController } from './MessageController'; 
   import {Container} from 'typedi'; // Only if you are using typedi

   new SocketControllers({
     port: 3001,
     container: Container,
     controllers: [MessageController],
   });
   ```

3. Now you can send `save` websocket message using websocket-client.

## More usage examples

#### Run code on socket client connect / disconnect / disconnecting

Controller action marked with `@OnConnect()` decorator is called once new client connected.
Controller action marked with `@OnDisconnect()` decorator is called once client disconnected.
Controller action marked with `@OnDisconnecting()` decorator is called when the client is disconnecting, before the disconnect event.

```typescript
import { SocketController, OnConnect, OnDisconnect, OnDisconnecting } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnConnect()
  save() {
    console.log('client connected');
  }

  @OnDisconnect()
  save() {
    console.log('client disconnected');
  }

  @OnDisconnecting()
  save() {
    console.log('client is disconnecting');
  }
}
```

#### `@ConnectedSocket()` decorator

To get connected socket instance you need to use `@ConnectedSocket()` decorator.

```typescript
import { SocketController, OnMessage, ConnectedSocket } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('save')
  save(@ConnectedSocket() socket: any) {
    socket.emit('save_success');
  }
}
```

#### `@MessageBody()` decorator

To get received message body use `@MessageBody()` decorator:

```typescript
import { SocketController, OnMessage, MessageBody } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('save')
  save(@MessageBody() message: any) {
    console.log('received message: ', message);
  }
}
```

If you specify a class type to parameter that is decorated with `@MessageBody()`,
socket-controllers will use [class-transformer][1] to create instance of the given class type with the data received in the message.
To disable this behaviour you need to specify `{ transformOption: { transform: false ] }` in SocketControllerOptions when creating a server.

You can define an index to get multiple parameters from the socket event.

```typescript
import { SocketController, OnMessage, MessageBody } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('save')
  save(@MessageBody({index: 0}) param1: any, @MessageBody({index: 1}) param2: any) {
    console.log('received message: ', message1);
    console.log('received message: ', message2);
  }
}
```

#### `@MessageAck()` decorator

To get received message ack use `@MessageAck()` decorator:

```typescript
import { SocketController, OnMessage, MessageAck, MessageBody } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('save')
  save(@MessageBody() message: any, @MessageAck() ack: Function) {
    console.log('received message: ', message);
    ack('callback message');
  }
}
```

> note: ack must be the last parameter in `emit`, otherwise it will be `null`

#### `@SocketQueryParam()` decorator

To get received query parameter use `@SocketQueryParam()` decorator.

```typescript
import { SocketController, OnMessage, MessageBody } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('save')
  save(@SocketQueryParam('token') token: string) {
    console.log('authorization token from query parameter: ', token);
  }
}
```

#### Get socket client id using `@SocketId()` decorator

To get connected client id use `@SocketId()` decorator.

```typescript
import { SocketController, OnMessage, MessageBody } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('save')
  save(@SocketId() id: string) {}
}
```

#### Get access to using socket.io instance using `@SocketIO()` decorator

```typescript
import { SocketController, OnMessage, MessageBody } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('save')
  save(@SocketIO() io: any) {
    // now you can broadcast messages to specific rooms or namespaces using io instance
  }
}
```

#### Send message back to client after method execution

You can use `@EmitOnSuccess` decorator:

```typescript
import { SocketController, OnMessage, EmitOnSuccess } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('save')
  @EmitOnSuccess('save_successfully')
  save() {
    // after this controller executed "save_successfully" message will be emitted back to the client
  }
}
```

If you return something, it will be returned in the emitted message data:

```typescript
import { SocketController, OnMessage, EmitOnSuccess } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('save')
  @EmitOnSuccess('save_successfully')
  save() {
    // after this controller executed "save_successfully" message will be emitted back to the client with message object
    return {
      id: 1,
      text: 'new message',
    };
  }
}
```

You can also control what message will be emitted if there is error/exception during execution:

```typescript
import { SocketController, OnMessage, EmitOnSuccess, EmitOnFail } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('save')
  @EmitOnSuccess('save_successfully')
  @EmitOnFail('save_error_range', {errorType: RangeError})
  @EmitOnFail('save_error_type', {errorType: TypeError})
  @EmitOnFail('save_error')
  save() {
    if (1 === 1) {
      throw new Error('One is equal to one! Fatal error!');
    }
    return {
      id: 1,
      text: 'new message',
    };
  }
}
```

In this case `save_error` message will be sent to the client with `One is equal to one! Fatal error!` error message.
The order is important when defining multiple `@EmitOnFail()` decorators, the first matching errorType will be served

Sometimes you may want to not emit success/error message if returned result is null or undefined.
In such cases you can use `@SkipEmitOnEmptyResult()` decorator.

```typescript
import { SocketController, OnMessage, EmitOnSuccess, EmitOnFail, SkipEmitOnEmptyResult } from 'socket-controllers';

@SocketController()
export class MessageController {
  @OnMessage('get')
  @EmitOnSuccess('get_success')
  @SkipEmitOnEmptyResult()
  get(): Promise<Message[]> {
    return this.messageRepository.findAll();
  }
}
```

In this case if findAll will return undefined, `get_success` message will not be emitted.
If findAll will return array of messages, they will be emitted back to the client in the `get_success` message.
This example also demonstrates Promises support.
If promise returned by controller action, message will be emitted only after promise will be resolved.

#### Using exist server instead of creating a new one

If you need to create and configure socket.io server manually,
you can pass it to the `SocketControllers` constructor.
Here is example of creating socket.io server and configuring it with express:

```typescript
import 'reflect-metadata'; // this shim is required
import { SocketControllers } from 'socket-controllers';
import { Server } from 'socket.io';
import { Container } from 'typedi'; // Only if you are using typedi

const app = require('express')();
const server = require('http').Server(app);
const io = new Server(server);

server.listen(3001);

app.get('/', function (req: any, res: any) {
  res.send('hello express');
});

io.use((socket: any, next: Function) => {
  console.log('Custom middleware');
  next();
});
new SocketControllers({io, container: Container});
```

#### Load all controllers from the given directory

You can load all controllers in once from specific directories, by specifying array of directories via options in
`createSocketServer` or `useSocketServer`:

```typescript
import 'reflect-metadata'; // this shim is required
import { SocketControllers } from 'socket-controllers';
import { Container } from 'typedi'; // Only if you are using typedi

new SocketControllers({
  port: 3000,
  container: Container, 
  controllers: [__dirname + '/controllers/*.js'],
}); // registers all given controllers
```

#### Using socket.io namespaces

To listen to messages only of the specific namespace you can mark a controller with namespace:

```typescript
@SocketController('/messages')
export class MessageController {
  // ...
}
```

Also you can use dynamic namespace, like `express router` patterns:

```typescript
@SocketController('/messages/:userId')
export class MessageController {
  // ...
}
```

## Using middlewares

Middlewares are the functions passed to the `socketIo.use` method.
Middlewares allows you to define a logic that will be executed each time client connected to the server.
To create your middlewares use `@Middleware` decorator:

```typescript
import { Middleware, MiddlewareInterface } from 'socket-controllers';

@Middleware()
export class CompressionMiddleware implements MiddlewareInterface {
  use(socket: any, next: (err?: any) => any) {
    console.log('do something, for example get authorization token and check authorization');
    next();
  }
}
```

You can limit middlewares to namespaces providing either a `string`, `RegExp` or `Array<string | RegExp>` to the `namespace` parameter:

```typescript
import { Middleware, MiddlewareInterface } from 'socket-controllers';

@Middleware({namespace: '/test'})
export class CompressionMiddleware implements MiddlewareInterface {
  use(socket: any, next: (err?: any) => any) {
    console.log('do something, for example get authorization token and check authorization');
    next();
  }
}
```

## Don't forget to load your controllers and middlewares

Controllers and middlewares should be loaded:

```typescript
import 'reflect-metadata';
import { SocketControllers } from 'socket-controllers';
import { MessageController } from './MessageController';
import { MyMiddleware } from './MyMiddleware'; // here we import it
import { Container } from 'typedi'; // Only if you are using typedi
const server = new SocketControllers({
  port: 3000,
  container: Container,
  controllers: [MessageController],
  middlewares: [MyMiddleware],
});
```

Also you can load them from directories. Also you can use glob patterns:

```typescript
import 'reflect-metadata';
import { SocketControllers } from 'socket-controllers';
import { Container } from 'typedi'; // Only if you are using typedi
const server = new SocketControllers({
   port: 3000,
   container: Container,
   controllers: [__dirname + '/controllers/**/*.js'],
   middlewares: [__dirname + '/middlewares/**/*.js'],
});
```

## Using DI container

`socket-controllers` supports a DI container out of the box. You can inject your services into your controllers and
middlewares. Container must be setup during application bootstrap.
Here is example how to integrate socket-controllers with [typedi](https://github.com/pleerock/typedi):

```typescript
import 'reflect-metadata';
import { SocketControllers } from 'socket-controllers';
import { Container } from 'typedi';

// create and run socket server
const server = new SocketControllers({
  port: 3000,
  container: Container,
  controllers: [__dirname + '/controllers/*.js'],
  middlewares: [__dirname + '/middlewares/*.js'],
});
```

That's it, now you can inject your services into your controllers:

```typescript
@Service()
@SocketController()
export class MessageController {
  constructor(private messageRepository: MessageRepository) {}

  // ... controller actions
}
```

> Note: TypeDI won't create instances for unknown classes since 0.9.0, you have to decorate your Class as a `Service()` as well.

### Scoped controllers

You can enable scoped controllers by providing a `scopedContainerGetter` function in SocketServerOptions. This function should return a new container that will be used to instantiate the controller and its dependencies.

You will get a new instance for each event in the controller.

The `scopedContainerGetter` function receives the `SocketEventContext`.

The `scopedContainerDisposer` function receives the container instance you created with `scopedContainerGetter` after the socket action is finished. Use this function to dispose the container if needed.

```typescript
import 'reflect-metadata';
import { SocketControllers, SocketEventContext } from 'socket-controllers';
import { Container, ContainerInstance, Token } from "typedi";

const myDiToken = new Token();

// create and run socket server
const server = new SocketControllers({
   port: 3000,
   container: Container,
   scopedContainerGetter: (args: SocketEventContext) => {
      const container = Container.of(YOUR_REQUEST_CONTEXT);
      container.set(myDiToken, 'MY_VALUE');
      return container;
   },
   scopedContainerDisposer: (container: ContainerInstance) => {
     container.dispose();
   },
   controllers: [__dirname + '/controllers/*.js'],
   middlewares: [__dirname + '/middlewares/*.js'],
});
```

## Interceptors

Interceptors allow you to wrap your event handlers in higher order functions.
With interceptors you can add logging or modify the incoming or outgoing data for event handlers.

```typescript
import {
   SocketController,
   OnMessage,
   EmitOnSuccess,
   EmitOnFail,
   SkipEmitOnEmptyResult,
   UseInterceptor,
   MessageBody
} from 'socket-controllers';

const interceptor: InterceptorInterface = {
   use: (ctx: SocketEventContext, next: () => any) => {
     ctx.messageArgs[0] = 'modified message from controller - ' + ctx.messageArgs[0];
     const resp = next();
     return 'modified response from controller - ' + resp; // modified response from controller - modified response from method - reponse
   },
};

@Service()
class Interceptor implements InterceptorInterface {
   async use(ctx: SocketEventContext, next: () => any) {
     ctx.messageArgs[0] = 'modified message from method - ' + ctx.messageArgs[0];
     const resp = await next();
     return 'modified response from method - ' + resp; // modified response from method - reponse
   }
}

@SocketController()
@UseInterceptor(interceptor)
export class MessageController {
   @OnMessage('get')
   @EmitOnSuccess('get_success')
   @SkipEmitOnEmptyResult()
   @UseInterceptor(Interceptor)
   get(@MessageBody() message: string): Promise<Message[]> {
     console.log(message); // modified message from controller - modified message from method - original message
     return 'response';
   }
}
```

Interceptors are executed in order of definition, starting with the controller interceptors.


## Decorators Reference

| Signature                              | Description                                                                                                                                                                                                                                                                |
|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `@SocketController(namespace?: string\ | Regex)`                                                                                                                                                                                                                                                                    | Registers a class to be a socket controller that can listen to websocket events and respond to them.                                                                                                                                                                       |
| `@OnMessage(messageName: string)`      | Registers controller's action to be executed when socket receives message with given name.                                                                                                                                                                                 |
| `@OnConnect()`                         | Registers controller's action to be executed when client connects to the socket.                                                                                                                                                                                           |
| `@OnDisconnect()`                      | Registers controller's action to be executed when client disconnects from the socket.                                                                                                                                                                                      |
| `@OnDisconnecting()`                   | Registers controller's action to be executed when client is disconnecting from the socket.                                                                                                                                                                                 |
| `@ConnectedSocket()`                   | Injects connected client's socket object to the controller action.                                                                                                                                                                                                         |
| `@SocketIO()`                          | Injects socket.io object that initialized a connection.                                                                                                                                                                                                                    |
| `@MessageBody()`                       | Injects received message body.                                                                                                                                                                                                                                             |
| `@SocketQueryParam(paramName: string)` | Injects query parameter from the received socket request.                                                                                                                                                                                                                  |
| `@SocketId()`                          | Injects socket id from the received request.                                                                                                                                                                                                                               |
| `@SocketRequest()`                     | Injects request object received by socket.                                                                                                                                                                                                                                 |
| `@SocketRooms()`                       | Injects rooms of the connected socket client.                                                                                                                                                                                                                              |
| `@NspParams()`                         | Injects dynamic namespace params.                                                                                                                                                                                                                                          |
| `@NspParam(paramName: string)`         | Injects param from the dynamic namespace.                                                                                                                                                                                                                                  |
| `@Middleware()`                        | Registers a new middleware to be registered in the socket.io.                                                                                                                                                                                                              |
| `@EmitOnSuccess(messageName: string)`  | If this decorator is set then after controller action will emit message with the given name after action execution. It will emit message only if controller succeed without errors. If result is a Promise then it will wait until promise is resolved and emit a message. |
| `@EmitOnFail(messageName: string)`     | If this decorator is set then after controller action will emit message with the given name after action execution. It will emit message only if controller throw an exception. If result is a Promise then it will wait until promise throw an error and emit a message.  |
| `@SkipEmitOnEmptyResult()`             | Used in conjunction with @EmitOnSuccess and @EmitOnFail decorators. If result returned by controller action is null or undefined then messages will not be emitted by @EmitOnSuccess or @EmitOnFail decorators.                                                            |     |

## Samples

Take a look on samples in [./sample](https://github.com/pleerock/socket-controllers/tree/master/sample) for more examples
of usage.

## Related projects

- If you are interested to create controller-based express or koa server use [routing-controllers](https://github.com/pleerock/routing-controllers) module.
- If you need to use dependency injection in use [typedi](https://github.com/pleerock/typedi) module.

[1]: https://github.com/pleerock/class-transformer


================================================
FILE: codecov.yml
================================================
coverage:
  range: 70..100
  round: down
  precision: 2
  status:
    project:
      default:
        threshold: 0%
        paths: 
          - src/**/*.ts
comment: off
ignore:
  - testing/**/*.ts
  - src/**/*.interface.ts


================================================
FILE: eslint.config.mjs
================================================
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
  baseDirectory: __dirname,
  recommendedConfig: js.configs.recommended,
  allConfig: js.configs.all,
});

export default [...compat.extends(
  'plugin:@typescript-eslint/recommended',
  'plugin:@typescript-eslint/recommended-requiring-type-checking',
  'plugin:jest/recommended',
  'prettier',
), {
  plugins: {
    '@typescript-eslint': typescriptEslint,
  },
  languageOptions: {
    parser: tsParser,
    ecmaVersion: 2018,
    sourceType: 'module',
    parserOptions: {
      project: ['./tsconfig.json', './tsconfig.spec.json'],
    },
  },
  rules: {
    '@typescript-eslint/explicit-member-accessibility': 'off',
    '@typescript-eslint/no-angle-bracket-type-assertion': 'off',
    '@typescript-eslint/no-parameter-properties': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/member-delimiter-style': 'off',
    '@typescript-eslint/no-inferrable-types': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/member-ordering': 'error',
    '@typescript-eslint/ban-types': 'off',
    '@typescript-eslint/no-unsafe-return': 'off',
    '@typescript-eslint/no-unsafe-assignment': 'off',
    '@typescript-eslint/no-unsafe-call': 'off',
    '@typescript-eslint/no-unsafe-member-access': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-unsafe-function-type': 'off',
    '@typescript-eslint/no-wrapper-object-types': 'off',
  },
}];

================================================
FILE: jest.config.js
================================================
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  collectCoverageFrom: ['src/**/*.ts', '!src/**/index.ts', '!src/**/*.interface.ts'],
  globals: {},
  setupFilesAfterEnv: ["./jest.setup.js"],
  transform: {
    '^.+\\.tsx?$': [
      'ts-jest',
      {tsconfig: './tsconfig.spec.json'},
    ],
  }
};


================================================
FILE: jest.setup.js
================================================
jest.setTimeout(30000);

require("reflect-metadata");

================================================
FILE: package.json
================================================
{
  "name": "socket-controllers",
  "version": "0.3.1",
  "description": "Use class-based controllers to handle websocket events.",
  "license": "MIT",
  "main": "index.js",
  "author": {
    "name": "TypeStack contributors"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/pleerock/socket-controllers.git"
  },
  "keywords": [
    "websocket",
    "typescript",
    "typescript-websocket",
    "socket-controllers",
    "socket.io",
    "socket-server"
  ],
  "scripts": {
    "build": "rimraf build && tsc --project tsconfig.prod.json",
    "prettier:fix": "prettier --write \"**/*.ts\"",
    "prettier:check": "prettier --check \"**/*.ts\"",
    "lint:fix": "eslint --max-warnings 0 --fix src/**/*.ts",
    "lint:check": "eslint --max-warnings 0 src/**/*.ts",
    "test": "jest --coverage --verbose",
    "test:watch": "jest --watch",
    "test:ci": "jest --runInBand --no-cache --coverage --verbose"
  },
  "dependencies": {
    "class-transformer": "^0.5.1",
    "glob": "^11.0.0",
    "path-to-regexp": "^8.1.0",
    "reflect-metadata": "^0.2.2",
    "socket.io": "^4.7.5"
  },
  "devDependencies": {
    "@types/glob": "^8.1.0",
    "@types/jest": "^29.5.13",
    "@types/node": "^20.14.11",
    "@types/path-to-regexp": "^1.7.0",
    "@types/socket.io": "^3.0.2",
    "@typescript-eslint/eslint-plugin": "^8.6.0",
    "@typescript-eslint/parser": "^8.6.0",
    "eslint": "^9.10.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-jest": "^28.8.3",
    "express": "^4.21.0",
    "husky": "^9.1.6",
    "jest": "^29.7.0",
    "lint-staged": "^15.2.10",
    "prettier": "^3.3.3",
    "rimraf": "6.0.1",
    "socket.io-client": "^4.7.5",
    "ts-jest": "^29.2.5",
    "ts-node": "^10.9.2",
    "typedi": "^0.10.0",
    "typescript": "^5.6.2"
  }
}


================================================
FILE: sample/sample1-simple-controller/Message.ts
================================================
export class Message {
  id: number;
  text: string;
}


================================================
FILE: sample/sample1-simple-controller/MessageController.ts
================================================
import { Message } from './Message';
import { ConnectedSocket, MessageBody, OnConnect, OnDisconnect, OnMessage, SocketController } from '../../src';

@SocketController()
export class MessageController {
  @OnConnect()
  connection(@ConnectedSocket() socket: any) {
    console.log('client connected');
  }

  @OnDisconnect()
  disconnect(@ConnectedSocket() socket: any) {
    console.log('client disconnected');
  }

  @OnMessage('save')
  save(@ConnectedSocket() socket: any, @MessageBody() message: Message) {
    console.log('received message:', message);
    console.log('setting id to the message and sending it back to the client');
    message.id = 1;
    socket.emit('message_saved', message);
  }
}


================================================
FILE: sample/sample1-simple-controller/app.ts
================================================
import 'reflect-metadata';
import { SocketControllers } from '../../src/index';
import { MessageController } from './MessageController';
import { Container } from 'typedi';

new SocketControllers({
  port: 3001,
  container: Container,
  controllers: [MessageController],
}); // creates socket.io server and registers all controllers there

console.log('Socket.io is up and running on port 3001. Send messages via socket-io client.');


================================================
FILE: sample/sample1-simple-controller/index.html
================================================
<html>
<script src="../../node_modules/socket.io-client/dist/socket.io.js"></script>
<script>
    var socket = io("http://localhost:3001");
    socket.on("message_saved", function (message) {
        console.log("Saved message received back: ", message);
    });

    function onClick() {
        socket.emit("save", { text: "Hello this is message" });
    }
</script>
<body>

Watch console for events.<br/>
<button onclick="onClick()">Click to send a save event to the socket server.</button>

</body>
</html>

================================================
FILE: sample/sample2-use-created-socket-io/Message.ts
================================================
export class Message {
  id: number;
  text: string;
}


================================================
FILE: sample/sample2-use-created-socket-io/MessageController.ts
================================================
import { Message } from './Message';
import { ConnectedSocket, MessageBody, OnConnect, OnDisconnect, OnMessage, SocketController } from '../../src';

@SocketController()
export class MessageController {
  @OnConnect()
  connection(@ConnectedSocket() socket: any) {
    console.log('client connected');
  }

  @OnDisconnect()
  disconnect(@ConnectedSocket() socket: any) {
    console.log('client disconnected');
  }

  @OnMessage('save')
  save(@ConnectedSocket() socket: any, @MessageBody() message: Message) {
    console.log('received message:', message);
    console.log('setting id to the message and sending it back to the client');
    message.id = 1;
    socket.emit('message_saved', message);
  }
}


================================================
FILE: sample/sample2-use-created-socket-io/app.ts
================================================
import 'reflect-metadata';
import { SocketControllers } from '../../src/index';
import { MessageController } from './MessageController';
import { Server } from 'socket.io';
import { Container } from 'typedi';

const app = require('express')();
const server = require('http').Server(app);
const io = new Server(server);

server.listen(3001);

app.get('/', function (req: any, res: any) {
  res.send('hello express');
});

io.use((socket: any, next: Function) => {
  console.log('Custom middleware');
  next();
});
new SocketControllers({
  io,
  container: Container,
  controllers: [MessageController],
});

console.log('Socket.io is up and running on port 3001. Send messages via socket-io client.');


================================================
FILE: sample/sample2-use-created-socket-io/index.html
================================================
<html>
<script src="../../node_modules/socket.io-client/dist/socket.io.js"></script>
<script>
    var socket = io("http://localhost:3001");
    socket.on("message_saved", function (message) {
        console.log("Saved message received back: ", message);
    });

    function onClick() {
        socket.emit("save", { text: "Hello this is message" });
    }
</script>
<body>

Watch console for events.<br/>
<button onclick="onClick()">Click to send a save event to the socket server.</button>

</body>
</html>

================================================
FILE: sample/sample3-namespaces/Message.ts
================================================
export class Message {
  id: number;
  text: string;
}


================================================
FILE: sample/sample3-namespaces/MessageController.ts
================================================
import { Message } from './Message';
import { ConnectedSocket, MessageBody, OnConnect, OnDisconnect, OnMessage, SocketController } from '../../src';

@SocketController('/messages')
export class MessageController {
  @OnConnect()
  connection(@ConnectedSocket() socket: any) {
    console.log('client connected');
  }

  @OnDisconnect()
  disconnect(@ConnectedSocket() socket: any) {
    console.log('client disconnected');
  }

  @OnMessage('save')
  save(@ConnectedSocket() socket: any, @MessageBody() message: Message) {
    console.log('received message:', message);
    console.log('setting id to the message and sending it back to the client');
    message.id = 1;
    socket.emit('message_saved', message);
  }
}


================================================
FILE: sample/sample3-namespaces/app.ts
================================================
import 'reflect-metadata';
import { SocketControllers } from '../../src/index';
import { MessageController } from './MessageController';
import { Container } from 'typedi';

new SocketControllers({
  port: 3001,
  container: Container,
  controllers: [MessageController],
}); // creates socket.io server and registers all controllers there

console.log('Socket.io is up and running on port 3001. Send messages via socket-io client.');


================================================
FILE: sample/sample3-namespaces/index.html
================================================
<html>
<script src="../../node_modules/socket.io-client/dist/socket.io.js"></script>
<script>
    var socket = io("http://localhost:3001/messages");
    socket.on("message_saved", function (message) {
        console.log("Saved message received back: ", message);
    });

    function onClick() {
        socket.emit("save", { text: "Hello this is message" });
    }
</script>
<body>

Watch console for events.<br/>
<button onclick="onClick()">Click to send a save event to the /messages namespace.</button>

</body>
</html>

================================================
FILE: sample/sample4-emitters/Message.ts
================================================
export class Message {
  id: number;
  text: string;
}


================================================
FILE: sample/sample4-emitters/MessageController.ts
================================================
import { Message } from './Message';
import {
  ConnectedSocket,
  EmitOnFail,
  EmitOnSuccess,
  MessageBody,
  OnConnect,
  OnDisconnect,
  OnMessage,
  SkipEmitOnEmptyResult,
  SocketController,
} from '../../src';

@SocketController()
export class MessageController {
  @OnConnect()
  connection(@ConnectedSocket() socket: any) {
    console.log('client connected');
  }

  @OnDisconnect()
  disconnect(@ConnectedSocket() socket: any) {
    console.log('client disconnected');
  }

  @OnMessage('save')
  @EmitOnSuccess('message_save_success')
  @EmitOnFail('message_save_failed')
  @SkipEmitOnEmptyResult()
  save(@ConnectedSocket() socket: any, @MessageBody() message: Message) {
    console.log('received message:', message);
    console.log('setting id to the message and sending it back to the client');
    message.id = 1;
    return message;
  }

  @OnMessage('try_to_save')
  @EmitOnSuccess('message_save_success')
  @EmitOnFail('message_save_failed')
  @SkipEmitOnEmptyResult()
  trySave(@ConnectedSocket() socket: any, @MessageBody() message: Message) {
    console.log('received message:', message);
    throw new Error('No, cannot save =(');
  }
}


================================================
FILE: sample/sample4-emitters/app.ts
================================================
import 'reflect-metadata';
import { SocketControllers } from '../../src/index';
import { MessageController } from './MessageController';
import { Container } from 'typedi';

new SocketControllers({
  port: 3001,
  container: Container,
  controllers: [MessageController],
}); // creates socket.io server and registers all controllers there

console.log('Socket.io is up and running on port 3001. Send messages via socket-io client.');


================================================
FILE: sample/sample4-emitters/index.html
================================================
<html>
<script src="../../node_modules/socket.io-client/dist/socket.io.js"></script>
<script>
    var socket = io("http://localhost:3001");
    socket.on("message_save_success", function (message) {
        console.log("Saved message received back: ", message);
    });
    socket.on("message_save_failed", function (error) {
        console.log("Error during message save: ", error);
    });

    function onClick() {
        socket.emit("save", { text: "Hello this is message" });
    }
    function tryToSave() {
        socket.emit("try_to_save", { text: "Hello this is message" });
    }
</script>
<body>

Watch console for events.<br/>
<button onclick="onClick()">Click to send a save event to the socket server.</button><br/>
<button onclick="tryToSave()">Try to save and you'll get an error</button>

</body>
</html>

================================================
FILE: sample/sample5-middlewares/AuthenticationMiddleware.ts
================================================
import { Middleware, MiddlewareInterface } from '../../src';

@Middleware()
export class AuthenticationMiddleware implements MiddlewareInterface {
  use(socket: any, next: (err?: any) => any): any {
    console.log('authentication...');
    next();
  }
}


================================================
FILE: sample/sample5-middlewares/Message.ts
================================================
export class Message {
  id: number;
  text: string;
}


================================================
FILE: sample/sample5-middlewares/MessageController.ts
================================================
import { Message } from './Message';
import { ConnectedSocket, MessageBody, OnConnect, OnDisconnect, OnMessage, SocketController } from '../../src';

@SocketController()
export class MessageController {
  @OnConnect()
  connection(@ConnectedSocket() socket: any) {
    console.log('client connected');
  }

  @OnDisconnect()
  disconnect(@ConnectedSocket() socket: any) {
    console.log('client disconnected');
  }

  @OnMessage('save')
  save(@ConnectedSocket() socket: any, @MessageBody() message: Message) {
    console.log('received message:', message);
    console.log('setting id to the message and sending it back to the client');
    message.id = 1;
    socket.emit('message_saved', message);
  }
}


================================================
FILE: sample/sample5-middlewares/app.ts
================================================
import 'reflect-metadata';
import { SocketControllers } from '../../src/index';
import { AuthenticationMiddleware } from './AuthenticationMiddleware';
import { MessageController } from './MessageController';
import { Container } from 'typedi';

new SocketControllers({
  port: 3001,
  container: Container,
  controllers: [MessageController],
  middlewares: [AuthenticationMiddleware],
}); // creates socket.io server and registers all controllers and middlewares there

console.log('Socket.io is up and running on port 3001. Send messages via socket-io client.');


================================================
FILE: sample/sample5-middlewares/index.html
================================================
<html>
<script src="../../node_modules/socket.io-client/dist/socket.io.js"></script>
<script>
    var socket = io("http://localhost:3001");
    socket.on("message_saved", function (message) {
        console.log("Saved message received back: ", message);
    });

    function onClick() {
        socket.emit("save", { text: "Hello this is message" });
    }
</script>
<body>

Watch console for events.<br/>
<button onclick="onClick()">Click to send a save event to the socket server.</button>

</body>
</html>

================================================
FILE: sample/sample6-dynamic-namespaces/Message.ts
================================================
export class Message {
  id: number;
  text: string;
}


================================================
FILE: sample/sample6-dynamic-namespaces/MessageController.ts
================================================
import { Message } from './Message';
import {
  ConnectedSocket,
  MessageBody,
  NspParams,
  OnConnect,
  OnDisconnect,
  OnMessage,
  SocketController,
} from '../../src';

@SocketController('/messages/:id')
export class MessageController {
  @OnConnect()
  connection(@ConnectedSocket() socket: any) {
    console.log('client connected');
  }

  @OnDisconnect()
  disconnect(@ConnectedSocket() socket: any) {
    console.log('client disconnected');
  }

  @OnMessage('save')
  async save(@ConnectedSocket() socket: any, @MessageBody() message: Message, @NspParams() params: any[]) {
    console.log('received message:', message);
    console.log('namespace params:', params);
    console.log('setting id to the message and sending it back to the client');
    message.id = 1;
    socket.emit('message_saved', message);
  }
}


================================================
FILE: sample/sample6-dynamic-namespaces/app.ts
================================================
import 'reflect-metadata';
import { SocketControllers } from '../../src/index';
import { MessageController } from './MessageController';
import { Container } from 'typedi';

new SocketControllers({
  port: 3001,
  container: Container,
  controllers: [MessageController],
}); // creates socket.io server and registers all controllers there

console.log('Socket.io is up and running on port 3001. Send messages via socket-io client.');


================================================
FILE: sample/sample6-dynamic-namespaces/index.html
================================================
<html>
<script src="../../node_modules/socket.io-client/dist/socket.io.js"></script>
<script>
    var socket = io("http://localhost:3001/messages/1");
    socket.on("message_saved", function (message) {
        console.log("Saved message received back: ", message);
    });

    function onClick() {
        socket.emit("save", { text: "Hello this is message" });
    }
</script>
<body>

Watch console for events.<br/>
<button onclick="onClick()">Click to send a save event to the /messages/1 namespace.</button>

</body>
</html>

================================================
FILE: src/SocketControllers.ts
================================================
import { Namespace, Server, Socket } from 'socket.io';
import { sync } from 'glob';
import { normalize } from 'path';
import { SOCKET_CONTROLLER_META_KEY } from './types/SocketControllerMetaKey';
import { pathToRegexp } from 'path-to-regexp';
import { HandlerMetadata } from './types/HandlerMetadata';
import { HandlerType } from './types/enums/HandlerType';
import { SocketControllersOptions } from './types/SocketControllersOptions';
import { ControllerMetadata } from './types/ControllerMetadata';
import { MiddlewareMetadata } from './types/MiddlewareMetadata';
import { SocketEventType } from './types/enums/SocketEventType';
import { ActionMetadata } from './types/ActionMetadata';
import { ParameterMetadata } from './types/ParameterMetadata';
import { ParameterType } from './types/enums/ParameterType';
import { ResultType } from './types/enums/ResultType';
import { getMetadata } from './util/get-metadata';
import { TransformOptions } from './types/TransformOptions';
import { defaultTransformOptions } from './types/constants/defaultTransformOptions';
import { ActionTransformOptions } from './types/ActionTransformOptions';
import { instanceToPlain, plainToInstance } from 'class-transformer';
import { MiddlewareInterface } from './types/MiddlewareInterface';
import { InterceptorInterface } from './types/InterceptorInterface';
import { chainExecute } from './util/chain-execute';
import { SocketEventContext } from './types/SocketEventContext';

export class SocketControllers {
  public container: { get<T>(someClass: { new (...args: any[]): T } | Function): T };
  public controllers: HandlerMetadata<ControllerMetadata>[];
  public middlewares: HandlerMetadata<MiddlewareMetadata>[];
  public io: Server;
  public transformOptions: TransformOptions;

  constructor(private options: SocketControllersOptions) {
    this.container = options.container;
    this.io = options.io || new Server(options.port);
    this.transformOptions = {
      ...defaultTransformOptions,
      ...options.transformOption,
    };
    this.controllers = this.loadHandlers<ControllerMetadata>(options.controllers || [], HandlerType.CONTROLLER);
    this.middlewares = this.loadHandlers<MiddlewareMetadata>(options.middlewares || [], HandlerType.MIDDLEWARE);

    this.registerMiddlewares();
    this.registerControllers();
  }

  private loadHandlers<T extends Object>(handlers: Array<Function | string>, type: HandlerType): HandlerMetadata<T>[] {
    const loadedHandlers: Function[] = [];

    for (const handler of handlers) {
      if (typeof handler === 'string') {
        loadedHandlers.push(...this.loadHandlersFromPath(handler, type));
      } else {
        loadedHandlers.push(handler);
      }
    }

    return loadedHandlers.map(handler => {
      return {
        metadata: getMetadata(handler),
        target: handler,
      };
    });
  }

  private loadHandlersFromPath(path: string, handlerType: HandlerType): Function[] {
    const files = sync(normalize(path).replace(/\\/g, '/'));

    return files
      .map(file => require(file))
      .reduce((loadedFiles: Function[], loadedFile: Record<string, any>) => {
        const handlersInFile = Object.values(loadedFile).filter(fileEntry => {
          if (typeof fileEntry !== 'function') {
            return false;
          }

          if (!(Reflect as any).hasMetadata(SOCKET_CONTROLLER_META_KEY, fileEntry as Function)) {
            return false;
          }

          return (Reflect as any).getMetadata(SOCKET_CONTROLLER_META_KEY, fileEntry as Function).type === handlerType;
        });
        loadedFiles.push(...(handlersInFile as Function[]));

        return loadedFiles;
      }, []);
  }

  private registerMiddlewares() {
    const middlewares = this.middlewares.slice().sort((middleware1, middleware2) => {
      return (middleware1.metadata.priority || 0) - (middleware2.metadata.priority || 0);
    });

    const middlewaresWithoutNamespace = middlewares.filter(middleware => !middleware.metadata.namespace);

    for (const middleware of middlewaresWithoutNamespace) {
      this.registerMiddleware(this.io as unknown as Namespace, middleware);
    }

    this.io.on('new_namespace', (namespace: Namespace) => {
      for (const middleware of middlewares) {
        const middlewareNamespaces = Array.isArray(middleware.metadata.namespace)
          ? middleware.metadata.namespace
          : [middleware.metadata.namespace];

        const shouldApply = middlewareNamespaces.some(nsp => {
          // Register middlewares without namespace too
          if (nsp == null) {
            return true;
          }

          const nspRegexp = nsp instanceof RegExp ? nsp : pathToRegexp(nsp).regexp;
          return nspRegexp.test(namespace.name);
        });

        if (shouldApply) {
          this.registerMiddleware(namespace, middleware);
        }
      }
    });
  }

  private registerControllers() {
    const controllersWithoutNamespace = this.controllers.filter(controller => !controller.metadata.namespace);
    const controllersWithNamespace = this.controllers.filter(controller => !!controller.metadata.namespace);

    this.io.on('connection', (socket: Socket) => {
      for (const controller of controllersWithoutNamespace) {
        this.registerController(socket, controller);
      }
    });

    const controllerNamespaceMap: Record<string, HandlerMetadata<ControllerMetadata>[]> = {};
    const controllerNamespaceRegExpMap: Record<string, string | RegExp> = {};

    for (const controller of controllersWithNamespace) {
      const nsp = controller.metadata.namespace as string;
      if (!controllerNamespaceMap[nsp]) {
        controllerNamespaceMap[nsp] = [];
      }
      controllerNamespaceMap[nsp].push(controller);
      controllerNamespaceRegExpMap[nsp] = nsp;
    }

    for (const [nsp, controllers] of Object.entries(controllerNamespaceMap)) {
      const namespace = controllerNamespaceRegExpMap[nsp];
      this.io
        .of(namespace instanceof RegExp ? namespace : pathToRegexp(namespace).regexp)
        .on('connection', (socket: Socket) => {
          for (const controller of controllers) {
            this.registerController(socket, controller);
          }
        });
    }
  }

  private registerController(socket: Socket, controller: HandlerMetadata<ControllerMetadata>) {
    const connectedAction = Object.values(controller.metadata.actions || {}).find(
      action => action.type === SocketEventType.CONNECT
    );
    const disconnectedAction = Object.values(controller.metadata.actions || {}).find(
      action => action.type === SocketEventType.DISCONNECT
    );
    const disconnectingAction = Object.values(controller.metadata.actions || {}).find(
      action => action.type === SocketEventType.DISCONNECTING
    );
    const messageActions = Object.values(controller.metadata.actions || {}).filter(
      action => action.type === SocketEventType.MESSAGE
    );

    if (connectedAction) {
      void this.executeAction(socket, controller, connectedAction);
    }

    if (disconnectedAction) {
      socket.on('disconnect', () => {
        void this.executeAction(socket, controller, disconnectedAction);
      });
    }

    if (disconnectingAction) {
      socket.on('disconnecting', () => {
        void this.executeAction(socket, controller, disconnectingAction);
      });
    }

    for (const messageAction of messageActions) {
      socket.on(messageAction.options.name, (...args: any[]) => {
        const messages: any[] = args.slice(0, -1);
        let ack: Function | null = args[args.length - 1];

        if (!(ack instanceof Function)) {
          messages.push(ack);
          ack = null;
        }

        void this.executeAction(socket, controller, messageAction, messageAction.options.name as string, messages, ack);
      });
    }
  }

  private async executeAction(
    socket: Socket,
    controller: HandlerMetadata<ControllerMetadata>,
    action: ActionMetadata,
    eventName?: string,
    data?: any[],
    ack?: Function | null
  ) {
    const eventContext = this.resolveEventContext(
      socket,
      action.type,
      eventName,
      data,
      controller.metadata.namespace,
      ack
    );

    let container = this.container;
    if (this.options.scopedContainerGetter) {
      container = this.options.scopedContainerGetter(eventContext);
    }

    try {
      const controllerInstance: any = container.get(controller.target);

      const actions = [
        ...(action.interceptors || []).map(interceptor => {
          return (
            ((interceptor as any) instanceof Function
              ? container.get(interceptor)
              : interceptor) as InterceptorInterface
          ).use.bind(interceptor);
        }),
        (context: SocketEventContext) => {
          const parameters = this.resolveParameters(
            socket,
            controller.metadata,
            action.parameters || [],
            context.messageArgs,
            ack
          );
          return controllerInstance[action.methodName](...parameters);
        },
      ];

      const actionResult = chainExecute(eventContext, actions);
      const result = await Promise.resolve(actionResult);
      this.handleActionResult(socket, action, result, ResultType.EMIT_ON_SUCCESS);
    } catch (error: any) {
      this.handleActionResult(socket, action, error, ResultType.EMIT_ON_FAIL);
    }

    if (this.options.scopedContainerDisposer) {
      this.options.scopedContainerDisposer(container);
    }
  }

  private handleActionResult(socket: Socket, action: ActionMetadata, result: any, resultType: ResultType) {
    const allOnResultActions = action.results?.filter(result => result.type === resultType) || [];
    const skipOnEmpty = action.results?.some(result => result.type === ResultType.SKIP_EMIT_ON_EMPTY_RESULT);

    if (result == null && skipOnEmpty) {
      return;
    }

    let onResultActions = allOnResultActions;
    if (onResultActions.some(action => action.options.errorType)) {
      const firstFittingAction = allOnResultActions.find(
        action => action.options.errorType && result instanceof (action.options.errorType as Function)
      );

      if (!firstFittingAction) {
        onResultActions = allOnResultActions.filter(action => !action.options.errorType);
      } else {
        onResultActions = [firstFittingAction];
      }
    }

    for (const onResultAction of onResultActions) {
      const transformedValue =
        result instanceof Error
          ? result.message
          : this.transformActionValue(result as never, null, onResultAction.options, 'result');
      socket.emit(onResultAction.options.messageName as never, transformedValue);
    }
  }

  private registerMiddleware(namespace: Namespace, middleware: HandlerMetadata<MiddlewareMetadata>) {
    namespace.use((socket: Socket, next: (err?: any) => void) => {
      const instance: MiddlewareInterface = this.container.get(middleware.target);
      instance.use(socket, next);
    });
  }

  private resolveParameters(
    socket: Socket,
    controllerMetadata: ControllerMetadata,
    parameterMetadatas: ParameterMetadata[],
    data?: any[],
    ack?: Function | null
  ) {
    const parameters = [];

    for (const metadata of parameterMetadatas) {
      const parameterValue = this.resolveParameter(socket, controllerMetadata, metadata, data, ack) as never;
      parameters[metadata.index] = this.transformActionValue(
        parameterValue,
        metadata.reflectedType as never,
        metadata.options,
        'parameter'
      );
    }

    return parameters;
  }

  private resolveParameter(
    socket: Socket,
    controller: ControllerMetadata,
    parameter: ParameterMetadata,
    data?: any[],
    ack?: Function | null
  ) {
    switch (parameter.type) {
      case ParameterType.CONNECTED_SOCKET:
        return socket;
      case ParameterType.SOCKET_ID:
        return socket.id;
      case ParameterType.SOCKET_IO:
        return this.io;
      case ParameterType.SOCKET_ROOMS:
        return socket.rooms;
      case ParameterType.MESSAGE_BODY:
        return data?.[(parameter.options.index as number) || 0];
      case ParameterType.MESSAGE_ACK:
        return ack;
      case ParameterType.SOCKET_QUERY_PARAM:
        return socket.handshake.query[parameter.options.name as string];
      case ParameterType.SOCKET_REQUEST:
        return socket.request;
      case ParameterType.NAMESPACE_PARAMS:
        return this.extractNamespaceParameters(socket, controller.namespace, parameter);
      case ParameterType.NAMESPACE_PARAM:
        return this.extractNamespaceParameters(socket, controller.namespace, parameter)?.[
          parameter.options.name as string
        ];
    }
  }

  private transformActionValue(
    value: never,
    reflectedType: unknown,
    options: ActionTransformOptions,
    transformType: 'parameter' | 'result'
  ) {
    const transformOptions: TransformOptions = {
      transform: options.transform ?? this.transformOptions.transform,
      parameterTransformOptions: options.transformOptions ?? this.transformOptions.parameterTransformOptions,
      resultTransformOptions: options.transformOptions ?? this.transformOptions.resultTransformOptions,
    };

    if (!transformOptions.transform) {
      return value;
    }

    if (typeof value !== 'object' || Array.isArray(value) || value == null) {
      return value;
    }

    if (transformType === 'parameter') {
      return plainToInstance(reflectedType as never, value, transformOptions.parameterTransformOptions);
    }

    if (transformType === 'result') {
      return instanceToPlain(value, transformOptions.resultTransformOptions);
    }

    return value;
  }

  private resolveEventContext(
    socket: Socket,
    eventType: SocketEventType,
    eventName?: string,
    messageBody?: any[],
    namespace?: string | RegExp,
    ack?: Function | null
  ): SocketEventContext {
    return {
      eventType,
      eventName,
      socket,
      socketIo: this.io,
      nspParams: this.extractNamespaceParameters(socket, namespace),
      messageArgs: messageBody,
      ack,
    };
  }

  private extractNamespaceParameters(
    socket: Socket,
    namespace: string | RegExp | undefined,
    parameterMetadata?: ParameterMetadata
  ) {
    let keys: any[];
    let regexp: RegExp;

    if (namespace instanceof RegExp) {
      regexp = namespace;
      keys = [];
    } else {
      const pathToRegexpResult = pathToRegexp(namespace || '/');
      regexp = pathToRegexpResult.regexp;
      keys = pathToRegexpResult.keys;
    }

    const parts: any[] = regexp.exec(socket.nsp.name) || [];
    const params: Record<string, string> = {};
    keys.forEach((key: any, index: number) => {
      params[key.name as string] = parameterMetadata?.options?.transform
        ? this.transformActionValue(
            parts[index + 1] as never,
            parameterMetadata.reflectedType,
            parameterMetadata.options,
            'parameter'
          )
        : parts[index + 1];
    });
    return params;
  }
}


================================================
FILE: src/decorators/ConnectedSocket.ts
================================================
import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';
import { ParameterType } from '../types/enums/ParameterType';

export function ConnectedSocket() {
  return function (object: Object, methodName: string, index: number) {
    const format = (Reflect as any).getMetadata('design:paramtypes', object, methodName)[index];

    addParameterToActionMetadata(object.constructor, methodName, {
      index,
      reflectedType: format,
      type: ParameterType.CONNECTED_SOCKET,
      options: {
        transform: false,
      },
    });
  };
}


================================================
FILE: src/decorators/EmitOnFail.ts
================================================
import { addResultToActionMetadata } from '../util/add-result-to-action-metadata';
import { ResultType } from '../types/enums/ResultType';
import { ActionTransformOptions } from '../types/ActionTransformOptions';

export function EmitOnFail(messageName: string, options?: ActionTransformOptions & { errorType: unknown }): Function {
  return function (object: Object, methodName: string) {
    addResultToActionMetadata(object.constructor, methodName, {
      type: ResultType.EMIT_ON_FAIL,
      options: {
        messageName,
        ...options,
      },
    });
  };
}


================================================
FILE: src/decorators/EmitOnSuccess.ts
================================================
import { addResultToActionMetadata } from '../util/add-result-to-action-metadata';
import { ResultType } from '../types/enums/ResultType';
import { ActionTransformOptions } from '../types/ActionTransformOptions';

export function EmitOnSuccess(messageName: string, options?: ActionTransformOptions): Function {
  return function (object: Object, methodName: string) {
    addResultToActionMetadata(object.constructor, methodName, {
      type: ResultType.EMIT_ON_SUCCESS,
      options: {
        messageName,
        ...options,
      },
    });
  };
}


================================================
FILE: src/decorators/MessageAck.ts
================================================
import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';
import { ParameterType } from '../types/enums/ParameterType';

export function MessageAck() {
  return function (object: Object, methodName: string, index: number) {
    const format = (Reflect as any).getMetadata('design:paramtypes', object, methodName)[index];
    addParameterToActionMetadata(object.constructor, methodName, {
      index,
      reflectedType: format,
      type: ParameterType.MESSAGE_ACK,
      options: {},
    });
  };
}


================================================
FILE: src/decorators/MessageBody.ts
================================================
import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';
import { ParameterType } from '../types/enums/ParameterType';
import { ActionTransformOptions } from '../types/ActionTransformOptions';

export function MessageBody(options?: ActionTransformOptions & { index?: number }) {
  return function (object: Object, methodName: string, index: number) {
    const format = (Reflect as any).getMetadata('design:paramtypes', object, methodName)[index];
    addParameterToActionMetadata(object.constructor, methodName, {
      index,
      reflectedType: format,
      type: ParameterType.MESSAGE_BODY,
      options: {
        ...options,
      },
    });
  };
}


================================================
FILE: src/decorators/Middleware.ts
================================================
import { HandlerType } from '../types/enums/HandlerType';
import { addMiddlewareMetadata } from '../util/add-middleware-metadata';

export function Middleware(options?: {
  priority?: number;
  namespace?: string | RegExp | Array<RegExp | string>;
}): Function {
  return function (object: Function) {
    addMiddlewareMetadata(object, {
      type: HandlerType.MIDDLEWARE,
      namespace: options?.namespace,
      priority: options?.priority,
    });
  };
}


================================================
FILE: src/decorators/NspParam.ts
================================================
import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';
import { ParameterType } from '../types/enums/ParameterType';

export function NspParam(name: string) {
  return function (object: Object, methodName: string, index: number) {
    const format = (Reflect as any).getMetadata('design:paramtypes', object, methodName)[index];

    addParameterToActionMetadata(object.constructor, methodName, {
      index,
      reflectedType: format,
      type: ParameterType.NAMESPACE_PARAM,
      options: { name, transform: false },
    });
  };
}


================================================
FILE: src/decorators/NspParams.ts
================================================
import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';
import { ParameterType } from '../types/enums/ParameterType';

export function NspParams() {
  return function (object: Object, methodName: string, index: number) {
    const format = (Reflect as any).getMetadata('design:paramtypes', object, methodName)[index];

    addParameterToActionMetadata(object.constructor, methodName, {
      index,
      reflectedType: format,
      type: ParameterType.NAMESPACE_PARAMS,
      options: { transform: false },
    });
  };
}


================================================
FILE: src/decorators/OnConnect.ts
================================================
import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';
import { SocketEventType } from '../types/enums/SocketEventType';

export function OnConnect(): Function {
  return function (object: Object, methodName: string) {
    addActionToControllerMetadata(object.constructor, {
      methodName,
      type: SocketEventType.CONNECT,
      options: {},
    });
  };
}


================================================
FILE: src/decorators/OnDisconnect.ts
================================================
import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';
import { SocketEventType } from '../types/enums/SocketEventType';

export function OnDisconnect(): Function {
  return function (object: Object, methodName: string) {
    addActionToControllerMetadata(object.constructor, {
      methodName,
      type: SocketEventType.DISCONNECT,
      options: {},
    });
  };
}


================================================
FILE: src/decorators/OnDisconnecting.ts
================================================
import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';
import { SocketEventType } from '../types/enums/SocketEventType';

export function OnDisconnecting(): Function {
  return function (object: Object, methodName: string) {
    addActionToControllerMetadata(object.constructor, {
      methodName,
      type: SocketEventType.DISCONNECTING,
      options: {},
    });
  };
}


================================================
FILE: src/decorators/OnMessage.ts
================================================
import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';
import { SocketEventType } from '../types/enums/SocketEventType';

export function OnMessage(name?: string): Function {
  return function (object: Object, methodName: string) {
    addActionToControllerMetadata(object.constructor, {
      methodName,
      type: SocketEventType.MESSAGE,
      options: { name },
    });
  };
}


================================================
FILE: src/decorators/SkipEmitOnEmptyResult.ts
================================================
import { addResultToActionMetadata } from '../util/add-result-to-action-metadata';
import { ResultType } from '../types/enums/ResultType';

export function SkipEmitOnEmptyResult(): Function {
  return function (object: Object, methodName: string) {
    addResultToActionMetadata(object.constructor, methodName, {
      type: ResultType.SKIP_EMIT_ON_EMPTY_RESULT,
      options: {},
    });
  };
}


================================================
FILE: src/decorators/SocketController.ts
================================================
import { HandlerType } from '../types/enums/HandlerType';
import { addControllerMetadata } from '../util/add-controller-metadata';

export function SocketController(namespace?: string | RegExp) {
  return function (object: Function) {
    addControllerMetadata(object, { namespace, type: HandlerType.CONTROLLER });
  };
}


================================================
FILE: src/decorators/SocketIO.ts
================================================
import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';
import { ParameterType } from '../types/enums/ParameterType';

export function SocketIO() {
  return function (object: Object, methodName: string, index: number) {
    const format = (Reflect as any).getMetadata('design:paramtypes', object, methodName)[index];

    addParameterToActionMetadata(object.constructor, methodName, {
      index,
      reflectedType: format,
      type: ParameterType.SOCKET_IO,
      options: { transform: false },
    });
  };
}


================================================
FILE: src/decorators/SocketId.ts
================================================
import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';
import { ParameterType } from '../types/enums/ParameterType';

export function SocketId() {
  return function (object: Object, methodName: string, index: number) {
    const format = (Reflect as any).getMetadata('design:paramtypes', object, methodName)[index];

    addParameterToActionMetadata(object.constructor, methodName, {
      index,
      reflectedType: format,
      type: ParameterType.SOCKET_ID,
      options: { transform: false },
    });
  };
}


================================================
FILE: src/decorators/SocketQueryParam.ts
================================================
import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';
import { ParameterType } from '../types/enums/ParameterType';

export function SocketQueryParam(name?: string) {
  return function (object: Object, methodName: string, index: number) {
    const format = (Reflect as any).getMetadata('design:paramtypes', object, methodName)[index];

    addParameterToActionMetadata(object.constructor, methodName, {
      index,
      reflectedType: format,
      type: ParameterType.SOCKET_QUERY_PARAM,
      options: { name, transform: false },
    });
  };
}


================================================
FILE: src/decorators/SocketRequest.ts
================================================
import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';
import { ParameterType } from '../types/enums/ParameterType';

export function SocketRequest() {
  return function (object: Object, methodName: string, index: number) {
    const format = (Reflect as any).getMetadata('design:paramtypes', object, methodName)[index];

    addParameterToActionMetadata(object.constructor, methodName, {
      index,
      reflectedType: format,
      type: ParameterType.SOCKET_REQUEST,
      options: { transform: false },
    });
  };
}


================================================
FILE: src/decorators/SocketRooms.ts
================================================
import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';
import { ParameterType } from '../types/enums/ParameterType';

export function SocketRooms() {
  return function (object: Object, methodName: string, index: number) {
    const format = (Reflect as any).getMetadata('design:paramtypes', object, methodName)[index];

    addParameterToActionMetadata(object.constructor, methodName, {
      index,
      reflectedType: format,
      type: ParameterType.SOCKET_ROOMS,
      options: { transform: false },
    });
  };
}


================================================
FILE: src/decorators/UseInterceptor.ts
================================================
import { addInterceptorToActionMetadata } from '../util/add-interceptor-to-action-metadata';
import { getMetadata } from '../util/get-metadata';
import { ControllerMetadata } from '../types/ControllerMetadata';

export function UseInterceptor(...interceptors: any[]): Function {
  return function (object: Function | Object, methodName?: string) {
    for (const interceptor of interceptors) {
      if (object instanceof Function) {
        // Class interceptor
        const existingMetadata: ControllerMetadata = getMetadata(object);
        for (const key of Object.keys(existingMetadata?.actions || {})) {
          addInterceptorToActionMetadata(object, key, interceptor as Function);
        }
      } else {
        // Method interceptor
        addInterceptorToActionMetadata(object.constructor, methodName as string, interceptor as Function);
      }
    }
  };
}


================================================
FILE: src/index.ts
================================================
export * from './decorators/ConnectedSocket';
export * from './decorators/EmitOnFail';
export * from './decorators/EmitOnSuccess';
export * from './decorators/MessageBody';
export * from './decorators/MessageAck';
export * from './decorators/Middleware';
export * from './decorators/NspParam';
export * from './decorators/NspParams';
export * from './decorators/OnConnect';
export * from './decorators/OnDisconnect';
export * from './decorators/OnDisconnecting';
export * from './decorators/OnMessage';
export * from './decorators/SkipEmitOnEmptyResult';
export * from './decorators/SocketController';
export * from './decorators/SocketId';
export * from './decorators/SocketIO';
export * from './decorators/SocketQueryParam';
export * from './decorators/SocketRequest';
export * from './decorators/SocketRooms';

export * from './types/MiddlewareInterface';
export * from './types/InterceptorInterface';
export * from './types/TransformOptions';
export * from './types/SocketControllersOptions';
export * from './types/enums/SocketEventType';
export * from './types/SocketEventContext';

export * from './SocketControllers';


================================================
FILE: src/types/ActionMetadata.ts
================================================
import { ParameterMetadata } from './ParameterMetadata';
import { ResultMetadata } from './ResultMetadata';
import { SocketEventType } from './enums/SocketEventType';

export interface ActionMetadata {
  type: SocketEventType;
  methodName: string;
  options: any;
  parameters: ParameterMetadata[];
  results: ResultMetadata[];
  interceptors: Function[];
}


================================================
FILE: src/types/ActionTransformOptions.ts
================================================
import { ClassTransformOptions } from 'class-transformer';

export interface ActionTransformOptions {
  transform?: boolean;
  transformOptions?: ClassTransformOptions;
}


================================================
FILE: src/types/ControllerMetadata.ts
================================================
import { HandlerType } from './enums/HandlerType';
import { ActionMetadata } from './ActionMetadata';

export interface ControllerMetadata {
  namespace?: string | RegExp;
  type: HandlerType.CONTROLLER;
  actions: {
    [methodName: string]: ActionMetadata;
  };
}


================================================
FILE: src/types/HandlerMetadata.ts
================================================
export interface HandlerMetadata<T> {
  metadata: T;
  target: Function;
}


================================================
FILE: src/types/InterceptorInterface.ts
================================================
import { SocketEventContext } from './SocketEventContext';

export interface InterceptorInterface {
  use(context: SocketEventContext, next: () => any): any;
}


================================================
FILE: src/types/MiddlewareInterface.ts
================================================
import { Socket } from 'socket.io';

export interface MiddlewareInterface {
  use(socket: Socket, next: (err?: any) => any): any;
}


================================================
FILE: src/types/MiddlewareMetadata.ts
================================================
import { HandlerType } from './enums/HandlerType';

export interface MiddlewareMetadata {
  namespace?: string | RegExp | Array<RegExp | string>;
  priority?: number;
  type: HandlerType.MIDDLEWARE;
}


================================================
FILE: src/types/ParameterMetadata.ts
================================================
import { ParameterType } from './enums/ParameterType';
import { ActionTransformOptions } from './ActionTransformOptions';

export interface ParameterMetadata {
  type: ParameterType;
  index: number;
  reflectedType: any;
  options: ActionTransformOptions & Record<string, unknown>;
}


================================================
FILE: src/types/ResultMetadata.ts
================================================
import { ResultType } from './enums/ResultType';
import { ActionTransformOptions } from './ActionTransformOptions';

export interface ResultMetadata {
  type: ResultType;
  options: ActionTransformOptions & Record<string, unknown>;
}


================================================
FILE: src/types/SocketControllerMetaKey.ts
================================================
export const SOCKET_CONTROLLER_META_KEY = Symbol('SocketControllerMetaKey');


================================================
FILE: src/types/SocketControllersOptions.ts
================================================
import { Server } from 'socket.io';
import { TransformOptions } from './TransformOptions';
import { SocketEventContext } from './SocketEventContext';

export interface SocketControllersOptions {
  container: { get<T>(someClass: { new (...args: any[]): T } | Function): T };

  scopedContainerGetter?: (context: SocketEventContext) => {
    get<T>(someClass: { new (...args: any[]): T } | Function): T;
  };

  scopedContainerDisposer?: (container: { get<T>(someClass: { new (...args: any[]): T } | Function): T }) => void;

  io?: Server;

  port?: number;

  controllers?: Function[] | string[];

  middlewares?: Function[] | string[];

  transformOption?: Partial<TransformOptions>;
}


================================================
FILE: src/types/SocketEventContext.ts
================================================
import { SocketEventType } from './enums/SocketEventType';
import { Server, Socket } from 'socket.io';

export interface SocketEventContext {
  socketIo: Server;
  socket: Socket;
  eventType: SocketEventType;
  eventName?: string;
  messageArgs?: any[];
  nspParams?: Record<string, string>;
  ack?: Function | null;
}


================================================
FILE: src/types/TransformOptions.ts
================================================
import { ClassTransformOptions } from 'class-transformer';

export interface TransformOptions {
  transform?: boolean;
  parameterTransformOptions?: ClassTransformOptions;
  resultTransformOptions?: ClassTransformOptions;
}


================================================
FILE: src/types/constants/defaultTransformOptions.ts
================================================
import { TransformOptions } from '../TransformOptions';

export const defaultTransformOptions: TransformOptions = {
  transform: true,
};


================================================
FILE: src/types/enums/HandlerType.ts
================================================
export enum HandlerType {
  CONTROLLER,
  MIDDLEWARE,
}


================================================
FILE: src/types/enums/ParameterType.ts
================================================
export enum ParameterType {
  CUSTOM,
  CONNECTED_SOCKET,
  MESSAGE_BODY,
  MESSAGE_ACK,
  SOCKET_QUERY_PARAM,
  SOCKET_IO,
  SOCKET_ID,
  SOCKET_REQUEST,
  SOCKET_ROOMS,
  NAMESPACE_PARAMS,
  NAMESPACE_PARAM,
}


================================================
FILE: src/types/enums/ResultType.ts
================================================
export enum ResultType {
  EMIT_ON_SUCCESS,
  EMIT_ON_FAIL,
  SKIP_EMIT_ON_EMPTY_RESULT,
}


================================================
FILE: src/types/enums/SocketEventType.ts
================================================
export enum SocketEventType {
  MESSAGE,
  CONNECT,
  DISCONNECT,
  DISCONNECTING,
}


================================================
FILE: src/util/add-action-to-controller-metadata.ts
================================================
import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';
import { ActionMetadata } from '../types/ActionMetadata';
import { getMetadata } from './get-metadata';
import { ControllerMetadata } from '../types/ControllerMetadata';

export const addActionToControllerMetadata = (
  target: Function,
  actionMetadata: Pick<ActionMetadata, 'type' | 'methodName' | 'options'>
) => {
  const existingMetadata = getMetadata<any, ControllerMetadata>(target);
  (Reflect as any).defineMetadata(
    SOCKET_CONTROLLER_META_KEY,
    {
      ...existingMetadata,
      actions: {
        ...existingMetadata?.actions,
        [actionMetadata.methodName]: {
          ...existingMetadata?.actions?.[actionMetadata.methodName],
          ...actionMetadata,
        },
      },
    },
    target
  );
};


================================================
FILE: src/util/add-controller-metadata.ts
================================================
import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';
import { ControllerMetadata } from '../types/ControllerMetadata';
import { getMetadata } from './get-metadata';

export const addControllerMetadata = (target: Function, metadata: Pick<ControllerMetadata, 'namespace' | 'type'>) => {
  const existingMetadata = getMetadata<any, ControllerMetadata>(target);
  (Reflect as any).defineMetadata(
    SOCKET_CONTROLLER_META_KEY,
    {
      ...existingMetadata,
      ...metadata,
    },
    target
  );
};


================================================
FILE: src/util/add-interceptor-to-action-metadata.ts
================================================
import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';
import { getMetadata } from './get-metadata';
import { ControllerMetadata } from '../types/ControllerMetadata';

export const addInterceptorToActionMetadata = (target: Function, methodName: string, interceptor: Function) => {
  const existingMetadata = getMetadata<any, ControllerMetadata>(target);
  (Reflect as any).defineMetadata(
    SOCKET_CONTROLLER_META_KEY,
    {
      ...existingMetadata,
      actions: {
        ...existingMetadata?.actions,
        [methodName]: {
          ...existingMetadata?.actions?.[methodName],
          interceptors: [interceptor, ...(existingMetadata?.actions?.[methodName]?.interceptors || [])],
        },
      },
    },
    target
  );
};


================================================
FILE: src/util/add-middleware-metadata.ts
================================================
import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';
import { MiddlewareMetadata } from '../types/MiddlewareMetadata';

export const addMiddlewareMetadata = (target: Function, metadata: MiddlewareMetadata) => {
  (Reflect as any).defineMetadata(SOCKET_CONTROLLER_META_KEY, metadata, target);
};


================================================
FILE: src/util/add-parameter-to-action-metadata.ts
================================================
import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';
import { ParameterMetadata } from '../types/ParameterMetadata';
import { getMetadata } from './get-metadata';
import { ControllerMetadata } from '../types/ControllerMetadata';

export const addParameterToActionMetadata = (target: Function, methodName: string, args: ParameterMetadata) => {
  const existingMetadata = getMetadata<any, ControllerMetadata>(target);
  (Reflect as any).defineMetadata(
    SOCKET_CONTROLLER_META_KEY,
    {
      ...existingMetadata,
      actions: {
        ...existingMetadata?.actions,
        [methodName]: {
          ...existingMetadata?.actions?.[methodName],
          parameters: [args, ...(existingMetadata?.actions?.[methodName]?.parameters || [])],
        },
      },
    },
    target
  );
};


================================================
FILE: src/util/add-result-to-action-metadata.ts
================================================
import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';
import { ResultMetadata } from '../types/ResultMetadata';
import { getMetadata } from './get-metadata';
import { ControllerMetadata } from '../types/ControllerMetadata';

export const addResultToActionMetadata = (target: Function, methodName: string, args: ResultMetadata) => {
  const existingMetadata = getMetadata<any, ControllerMetadata>(target);
  (Reflect as any).defineMetadata(
    SOCKET_CONTROLLER_META_KEY,
    {
      ...existingMetadata,
      actions: {
        ...existingMetadata?.actions,
        [methodName]: {
          ...existingMetadata?.actions?.[methodName],
          results: [args, ...(existingMetadata?.actions?.[methodName]?.results || [])],
        },
      },
    },
    target
  );
};


================================================
FILE: src/util/chain-execute.ts
================================================
export function chainExecute(context: any, chain: Function[]) {
  function next() {
    const middleware: Function = chain.shift() as Function;

    if (middleware && typeof middleware === 'function') {
      return middleware(context, next);
    }
  }

  return next();
}


================================================
FILE: src/util/get-metadata.ts
================================================
import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';

export const getMetadata = <T extends Object, U>(target: T): U => {
  return (Reflect as any).getMetadata(SOCKET_CONTROLLER_META_KEY, target);
};


================================================
FILE: test/functional/connected-socket.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';

describe('ConnectedSocket', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Connected socket is retrieved correctly', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        testResult = socket.id;
        socket.emit('connected');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    expect(wsClient.id).toEqual(testResult);
  });
});


================================================
FILE: test/functional/controllers/test.controller.ts
================================================
import { ConnectedSocket, OnConnect, SocketController } from '../../../src';
import { Service } from 'typedi';

@SocketController()
@Service()
export class TestController {
  @OnConnect()
  connected(@ConnectedSocket() socket: any) {
    socket.emit('connected');
  }
}


================================================
FILE: test/functional/controllers/test2.controller.ts
================================================
import { ConnectedSocket, OnMessage, SocketController } from '../../../src';
import { Service } from 'typedi';

@SocketController()
@Service()
export class Test2Controller {
  @OnMessage('test')
  connected(@ConnectedSocket() socket: any) {
    socket.emit('response');
  }
}


================================================
FILE: test/functional/create-socket-server.spec.ts
================================================
import { SocketController, SocketControllers } from '../../src';
import { testConnection } from '../utilities/testSocketConnection';
import { Container, Service } from 'typedi';

describe('Create socket server', () => {
  let wsApp: SocketControllers;
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  afterEach(() => {
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.io.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('should create socket server without options', async () => {
    expect.assertions(1);
    wsApp = new SocketControllers({ port: PORT, container: Container });
    expect(await testConnection(PATH_FOR_CLIENT)).toEqual(0);
  });

  it('should create socket server with empty controllers array in options', async () => {
    expect.assertions(1);
    wsApp = new SocketControllers({ port: PORT, controllers: [], container: Container });
    expect(await testConnection(PATH_FOR_CLIENT)).toEqual(0);
  });

  it('should create socket server with controllers array in options', async () => {
    expect.assertions(1);

    @SocketController()
    @Service()
    class TestController {}

    wsApp = new SocketControllers({ port: PORT, container: Container, controllers: [TestController] });
    expect(await testConnection(PATH_FOR_CLIENT)).toEqual(0);
  });
});


================================================
FILE: test/functional/emit-on-fail.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { EmitOnFail, OnMessage } from '../../src';
import { waitForTime } from '../utilities/waitForTime';

describe('EmitOnFail', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Emit defined event on failing sync execution', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      @EmitOnFail('fail')
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('request')
      @EmitOnFail('fail')
      testEvent() {
        throw new Error('error string');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    const errors = [];

    wsClient.on('fail', data => {
      errors.push(data);
    });

    await waitForEvent(wsClient, 'connected');
    expect(errors.length).toEqual(0);

    wsClient.emit('request');
    await waitForEvent(wsClient, 'fail');
    expect(errors[0]).toEqual('error string');
    expect(errors.length).toEqual(1);
  });

  it('Emit defined event on failing async execution', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      @EmitOnFail('fail')
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('request')
      @EmitOnFail('fail')
      async testEvent() {
        throw new Error('error string');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    const errors = [];

    wsClient.on('fail', data => {
      errors.push(data);
    });

    await waitForEvent(wsClient, 'connected');
    expect(errors.length).toEqual(0);

    wsClient.emit('request');
    await waitForEvent(wsClient, 'fail');
    expect(errors[0]).toEqual('error string');
    expect(errors.length).toEqual(1);
  });

  it('Emit defined event on failing with specific error type', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('request')
      @EmitOnFail('fail1', { errorType: RangeError })
      @EmitOnFail('fail2', { errorType: TypeError })
      @EmitOnFail('fail3')
      async testEvent() {
        throw new RangeError('range error');
      }

      @OnMessage('request2')
      @EmitOnFail('fail1', { errorType: RangeError })
      @EmitOnFail('fail2', { errorType: TypeError })
      @EmitOnFail('fail3')
      async testEvent2() {
        throw new TypeError('type error');
      }

      @OnMessage('request3')
      @EmitOnFail('fail1', { errorType: RangeError })
      @EmitOnFail('fail2', { errorType: TypeError })
      @EmitOnFail('fail3')
      async testEvent3() {
        throw new Error('test error');
      }

      @OnMessage('request4')
      @EmitOnFail('fail1', { errorType: Error })
      @EmitOnFail('fail2', { errorType: TypeError })
      @EmitOnFail('fail3')
      async testEvent4() {
        throw new TypeError('type error 2');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    const errors = { fail1: [], fail2: [], fail3: [] };

    wsClient.on('fail1', data => {
      errors.fail1.push(data);
    });

    wsClient.on('fail2', data => {
      errors.fail2.push(data);
    });

    wsClient.on('fail3', data => {
      errors.fail3.push(data);
    });

    await waitForEvent(wsClient, 'connected');

    wsClient.emit('request');
    wsClient.emit('request2');
    wsClient.emit('request3');
    wsClient.emit('request4');

    await waitForTime(1000);

    expect(errors).toEqual({
      fail1: ['range error', 'type error 2'],
      fail2: ['type error'],
      fail3: ['test error'],
    });
  });
});


================================================
FILE: test/functional/emit-on-success.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { EmitOnSuccess, OnMessage } from '../../src';

describe('EmitOnSuccess', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Emit defined event on successful sync execution', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('request')
      @EmitOnSuccess('response')
      testEvent() {
        throw new Error('error string');
      }

      @OnMessage('request2')
      @EmitOnSuccess('response')
      testEvent2() {
        return 'data';
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    const responses = [];

    wsClient.on('response', data => {
      responses.push(data);
    });

    await waitForEvent(wsClient, 'connected');
    expect(responses.length).toEqual(0);

    wsClient.emit('request');
    wsClient.emit('request2');
    await waitForEvent(wsClient, 'response');
    expect(responses[0]).toEqual('data');
    expect(responses.length).toEqual(1);
  });

  it('Emit defined event on successful async execution', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('request')
      @EmitOnSuccess('response')
      async testEvent() {
        throw new Error('error string');
      }

      @OnMessage('request2')
      @EmitOnSuccess('response')
      async testEvent2() {
        return 'data';
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    const responses = [];

    wsClient.on('response', data => {
      responses.push(data);
    });

    await waitForEvent(wsClient, 'connected');
    expect(responses.length).toEqual(0);

    wsClient.emit('request');
    wsClient.emit('request2');
    await waitForEvent(wsClient, 'response');
    expect(responses[0]).toEqual('data');
    expect(responses.length).toEqual(1);
  });
});


================================================
FILE: test/functional/load-controllers-from-directory.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container } from 'typedi';
import { waitForEvent } from '../utilities/waitForEvent';
import path from 'path';

describe('Load controllers from directory', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Load controllers from directory', async () => {
    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [path.join(__dirname, './controllers/**.*')],
    });
    wsClient = io(PATH_FOR_CLIENT, { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.emit('test');
    await waitForEvent(wsClient, 'response');
    expect(true).toEqual(true);
  });
});


================================================
FILE: test/functional/middlewares.spec.ts
================================================
import { Server } from 'socket.io';
import { Socket, io } from 'socket.io-client';
import { Container, Service } from 'typedi';
import { waitForEvent } from '../utilities/waitForEvent';
import { createServer, Server as HttpServer } from 'http';
import { Middleware } from '../../src/decorators/Middleware';
import { MiddlewareInterface } from '../../src/types/MiddlewareInterface';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { SocketControllers } from '../../src/SocketControllers';

describe('Middlewares', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult: string[] = [];
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = [];

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('no namespace', async () => {
    @Middleware()
    @Service()
    class GlobalMiddleware implements MiddlewareInterface {
      use(socket: any, next: (err?: any) => any): any {
        testResult.push('global middleware');
        next();
      }
    }

    @SocketController()
    @Service()
    class Controller {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      middlewares: [GlobalMiddleware],
      controllers: [Controller],
    });
    wsClient = io(PATH_FOR_CLIENT, { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    expect(testResult).toEqual(['global middleware']);
  });

  describe('string namespace', () => {
    it('correct namespace', async () => {
      @Middleware({ namespace: '/string' })
      @Service()
      class StringNamespaceMiddleware implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push('string middleware');
          next();
        }
      }

      @Middleware()
      @Service()
      class MiddlewareWithoutNamespace implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push('middleware without namespace');
          next();
        }
      }

      @SocketController('/string')
      @Service()
      class StringNamespaceController {
        @OnConnect()
        connected(@ConnectedSocket() socket: Socket) {
          socket.emit('connected');
        }
      }

      socketControllers = new SocketControllers({
        io: wsApp,
        container: Container,
        middlewares: [StringNamespaceMiddleware, MiddlewareWithoutNamespace],
        controllers: [StringNamespaceController],
      });
      wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

      await waitForEvent(wsClient, 'connected');
      expect(testResult).toEqual(['string middleware', 'middleware without namespace']);
    });

    it('incorrect namespace', async () => {
      @Middleware({ namespace: '/string' })
      @Service()
      class StringNamespaceMiddleware implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push('string middleware');
          next();
        }
      }

      @Middleware()
      @Service()
      class MiddlewareWithoutNamespace implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push('middleware without namespace');
          next();
        }
      }

      @SocketController('/string2')
      @Service()
      class String2NamespaceController {
        @OnConnect()
        connected(@ConnectedSocket() socket: Socket) {
          socket.emit('connected');
        }
      }

      socketControllers = new SocketControllers({
        io: wsApp,
        container: Container,
        middlewares: [StringNamespaceMiddleware, MiddlewareWithoutNamespace],
        controllers: [String2NamespaceController],
      });
      wsClient = io(PATH_FOR_CLIENT + '/string2', { reconnection: false, timeout: 5000, forceNew: true });

      await waitForEvent(wsClient, 'connected');
      expect(testResult).toEqual(['middleware without namespace']);
    });
  });

  describe('regexp namespace', () => {
    it('correct namespace', async () => {
      @Middleware({ namespace: /^\/dynamic-\d+$/ })
      @Service()
      class RegexpNamespaceMiddleware implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push(socket.nsp.name as string);
          next();
        }
      }

      @Middleware()
      @Service()
      class MiddlewareWithoutNamespace implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push('middleware without namespace');
          next();
        }
      }

      @SocketController(/^\/dynamic-\d+$/)
      @Service()
      class RegexpNamespaceController {
        @OnConnect()
        connected(@ConnectedSocket() socket: Socket) {
          socket.emit('connected');
        }
      }

      socketControllers = new SocketControllers({
        io: wsApp,
        container: Container,
        middlewares: [RegexpNamespaceMiddleware, MiddlewareWithoutNamespace],
        controllers: [RegexpNamespaceController],
      });
      wsClient = io(PATH_FOR_CLIENT + '/dynamic-1', { reconnection: false, timeout: 5000, forceNew: true });

      await waitForEvent(wsClient, 'connected');
      expect(testResult).toEqual(['/dynamic-1', 'middleware without namespace']);
    });

    it('incorrect namespace', async () => {
      @Middleware({ namespace: /^\/dynamic-\s+$/ })
      @Service()
      class RegexpNamespaceMiddleware implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push(socket.nsp.name as string);
          next();
        }
      }

      @Middleware()
      @Service()
      class MiddlewareWithoutNamespace implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push('middleware without namespace');
          next();
        }
      }

      @SocketController(/^\/dynamic-\d+$/)
      @Service()
      class RegexpNamespaceController {
        @OnConnect()
        connected(@ConnectedSocket() socket: Socket) {
          socket.emit('connected');
        }
      }

      socketControllers = new SocketControllers({
        io: wsApp,
        container: Container,
        middlewares: [RegexpNamespaceMiddleware, MiddlewareWithoutNamespace],
        controllers: [RegexpNamespaceController],
      });
      wsClient = io(PATH_FOR_CLIENT + '/dynamic-1', { reconnection: false, timeout: 5000, forceNew: true });

      await waitForEvent(wsClient, 'connected');
      expect(testResult).toEqual(['middleware without namespace']);
    });
  });

  describe('array namespace', () => {
    it('correct namespace', async () => {
      @Middleware({ namespace: [/^\/dynamic-\d+$/] })
      @Service()
      class RegexpArrayNamespaceMiddleware implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push(socket.nsp.name as string);
          next();
        }
      }

      @Middleware()
      @Service()
      class MiddlewareWithoutNamespace implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push('middleware without namespace');
          next();
        }
      }

      @SocketController(/^\/dynamic-\d+$/)
      @Service()
      class RegexpNamespaceController {
        @OnConnect()
        connected(@ConnectedSocket() socket: Socket) {
          socket.emit('connected');
        }
      }

      socketControllers = new SocketControllers({
        io: wsApp,
        container: Container,
        middlewares: [RegexpArrayNamespaceMiddleware, MiddlewareWithoutNamespace],
        controllers: [RegexpNamespaceController],
      });
      wsClient = io(PATH_FOR_CLIENT + '/dynamic-1', { reconnection: false, timeout: 5000, forceNew: true });

      await waitForEvent(wsClient, 'connected');
      expect(testResult).toEqual(['/dynamic-1', 'middleware without namespace']);
    });

    it('incorrect namespace', async () => {
      @Middleware({ namespace: [/^\/dynamic-\s+$/] })
      @Service()
      class RegexpArrayNamespaceMiddleware implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push(socket.nsp.name as string);
          next();
        }
      }

      @Middleware()
      @Service()
      class MiddlewareWithoutNamespace implements MiddlewareInterface {
        use(socket: any, next: (err?: any) => any): any {
          testResult.push('middleware without namespace');
          next();
        }
      }

      @SocketController(/^\/dynamic-\d+$/)
      @Service()
      class RegexpNamespaceController {
        @OnConnect()
        connected(@ConnectedSocket() socket: Socket) {
          socket.emit('connected');
        }
      }

      socketControllers = new SocketControllers({
        io: wsApp,
        container: Container,
        middlewares: [RegexpArrayNamespaceMiddleware, MiddlewareWithoutNamespace],
        controllers: [RegexpNamespaceController],
      });
      wsClient = io(PATH_FOR_CLIENT + '/dynamic-1', { reconnection: false, timeout: 5000, forceNew: true });

      await waitForEvent(wsClient, 'connected');
      expect(testResult).toEqual(['middleware without namespace']);
    });
  });
});


================================================
FILE: test/functional/multiple-controllers-on-same-namespace.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { ConnectedSocket, OnConnect, SocketController, SocketControllers } from '../../src';
import { Container, Service } from 'typedi';
import { waitForTime } from '../utilities/waitForTime';

describe('Multiple controllers with same namespace', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('using string namespace', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        testResult = [...(testResult || []), '1'];
        socket.emit('connected');
      }
    }

    @SocketController('/string')
    @Service()
    class TestController2 {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        testResult = [...(testResult || []), '2'];
        socket.emit('connected2');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController, TestController2],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForTime(100);
    expect(testResult).toContain('1');
    expect(testResult).toContain('2');
  });
});


================================================
FILE: test/functional/nsp-param.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { NspParam } from '../../src';

describe('NspParam', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Namespace param is retrieved correctly', async () => {
    @SocketController('/:first/:second')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket, @NspParam('second') parameter: string) {
        testResult = parameter;
        socket.emit('connected');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/test1/test2', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    expect(testResult).toEqual('test2');
  });
});


================================================
FILE: test/functional/nsp-params.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { NspParams } from '../../src';

describe('NspParams', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Namespace params are retrieved correctly', async () => {
    @SocketController('/:first/:second')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket, @NspParams() parameters: { first: string; second: string }) {
        testResult = parameters;
        socket.emit('connected');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/test1/test2', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    expect(testResult).toEqual({ first: 'test1', second: 'test2' });
  });
});


================================================
FILE: test/functional/on-disconnect.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { OnDisconnect } from '../../src';
import { waitForTime } from '../utilities/waitForTime';

describe('OnDisconnect', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('OnDisconnect is called correctly', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnDisconnect()
      disconnected() {
        testResult = 'disconnected';
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.disconnect();
    await waitForTime(1000);
    expect(testResult).toEqual('disconnected');
  });
});


================================================
FILE: test/functional/on-disconnecting.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { OnDisconnect } from '../../src';
import { waitForTime } from '../utilities/waitForTime';
import { OnDisconnecting } from '../../src/decorators/OnDisconnecting';

describe('OnDisconnecting', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult = [];
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = [];

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('OnDisconnect is called correctly', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnDisconnect()
      disconnected() {
        testResult.push('disconnected');
      }

      @OnDisconnecting()
      disconnecting() {
        testResult.push('disconnecting');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.disconnect();
    await waitForTime(1000);
    expect(testResult).toEqual(['disconnecting', 'disconnected']);
  });
});


================================================
FILE: test/functional/parameter-transformation.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { MessageBody, OnMessage } from '../../src';
import { Expose } from 'class-transformer';

describe('Parameter transformation', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Parameters are converted correctly with the given options', async () => {
    class Body {
      @Expose() prop1: string;
      @Expose() prop2: number;
    }

    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        testResult = socket.id;
        socket.emit('connected');
      }

      @OnMessage('test')
      test(
        @ConnectedSocket() socket: Socket,
        @MessageBody({
          transform: true,
          transformOptions: { excludeExtraneousValues: true, enableImplicitConversion: true },
        })
        body: Body
      ) {
        testResult = body;
        socket.emit('result');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');

    wsClient.emit('test', { prop1: 'test', prop2: '2', prop3: 10 });
    await waitForEvent(wsClient, 'result');
    expect(testResult).toBeInstanceOf(Body);
    expect(testResult.prop1).toEqual('test');
    expect(testResult.prop2).toEqual(2);
    expect(testResult.prop3).toEqual(undefined);
  });
});


================================================
FILE: test/functional/scoped-controllers.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, ContainerInstance, Inject, Service, Token } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { EmitOnSuccess, OnMessage, SocketEventContext } from '../../src';

describe('Scoped controllers', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult = [];
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = [];

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('two instances should be different', async () => {
    @Service()
    class TestService {}

    @SocketController('/string')
    @Service()
    class TestController {
      constructor(private testService: TestService) {}

      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('test')
      @EmitOnSuccess('done')
      test() {
        testResult.push(this.testService);
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
      scopedContainerGetter: (args: SocketEventContext) => {
        return Container.of(Math.random().toString());
      },
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.emit('test');
    await waitForEvent(wsClient, 'done');
    wsClient.emit('test');
    await waitForEvent(wsClient, 'done');
    expect(testResult[0]).not.toBe(testResult[1]);
  });

  it('two global instances should be the same', async () => {
    @Service({ global: true })
    class TestService {}

    @SocketController('/string')
    @Service()
    class TestController {
      constructor(private testService: TestService) {}

      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('test')
      @EmitOnSuccess('done')
      test() {
        testResult.push(this.testService);
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
      scopedContainerGetter: (args: SocketEventContext) => {
        return Container.of(Math.random().toString());
      },
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.emit('test');
    await waitForEvent(wsClient, 'done');
    wsClient.emit('test');
    await waitForEvent(wsClient, 'done');
    expect(testResult[0]).toBe(testResult[1]);
  });

  it('additional injectables should be retrievable', async () => {
    const token = new Token('ADDITIONAL');
    let counter = 0;

    @Service({ global: true })
    class TestService {}

    @SocketController('/string')
    @Service()
    class TestController {
      constructor(
        private testService: TestService,
        @Inject(token) public myAdditional: number
      ) {}

      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('test')
      @EmitOnSuccess('done')
      test() {
        testResult.push(this.myAdditional);
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
      scopedContainerGetter: (args: SocketEventContext) => {
        const container = Container.of(counter.toString());
        container.set(token, counter);
        counter++;
        return container;
      },
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.emit('test');
    await waitForEvent(wsClient, 'done');
    wsClient.emit('test');
    await waitForEvent(wsClient, 'done');
    expect(testResult[0]).toBe(1);
    expect(testResult[1]).toBe(2);
  });

  it('arguments should be provided correctly to getter', async () => {
    @SocketController('/:test')
    @Service()
    class TestController {
      @OnConnect()
      @EmitOnSuccess('connected')
      connected(@ConnectedSocket() socket: Socket) {}

      @OnMessage('test')
      @EmitOnSuccess('done')
      test() {}
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
      scopedContainerGetter: (args: SocketEventContext) => {
        testResult.push(args);
        return Container.of('');
      },
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.emit('test', 'args1');
    await waitForEvent(wsClient, 'done');

    expect(testResult[1].socket.id).toEqual(wsClient.id);
    expect(testResult[1].socketIo).toBe(socketControllers.io);
    expect(testResult[1].eventName).toBe('test');
    expect(testResult[1].messageArgs).toEqual(['args1']);
    expect(testResult[1].nspParams).toEqual({ test: 'string' });
  });

  it('container should be disposed', async () => {
    const token = new Token('ADDITIONAL');

    @Service({ global: true })
    class TestService {}

    @SocketController('/string')
    @Service()
    class TestController {
      constructor(
        private testService: TestService,
        @Inject(token) public myAdditional: number
      ) {}

      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('test')
      @EmitOnSuccess('done')
      test() {
        testResult.push(this.myAdditional);
      }
    }

    let container;
    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
      scopedContainerGetter: () => {
        container = Container.of('test');
        container.set(token, 'test');
        return container;
      },
      scopedContainerDisposer: (scopedContainer: ContainerInstance) => {
        scopedContainer.reset({ strategy: 'resetServices' });
      },
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.emit('test');
    await waitForEvent(wsClient, 'done');

    expect(Container.has(TestService)).toBe(true);
    expect(container.has(token)).toBe(false);
    expect(container.has(TestController)).toBe(false);
  });
});


================================================
FILE: test/functional/skip-emit-on-empty-result.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { EmitOnFail, EmitOnSuccess, OnMessage, SkipEmitOnEmptyResult } from '../../src';

describe('SkipEmitOnEmptyResult', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Skip emit of defined event on successful sync execution', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('request')
      @EmitOnSuccess('response')
      @SkipEmitOnEmptyResult()
      testEvent() {
        return { data: true };
      }

      @OnMessage('request2')
      @EmitOnSuccess('response')
      @SkipEmitOnEmptyResult()
      testEvent2() {
        return null;
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    const responses = [];

    wsClient.on('response', data => {
      responses.push(data);
    });

    await waitForEvent(wsClient, 'connected');
    expect(responses.length).toEqual(0);

    wsClient.emit('request2');
    wsClient.emit('request');

    await waitForEvent(wsClient, 'response');
    expect(responses.length).toEqual(1);
    expect(responses[0]).toEqual({ data: true });
  });

  it('Skip emit of defined event on successful async execution', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      async connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('request')
      @EmitOnSuccess('response')
      @SkipEmitOnEmptyResult()
      async testEvent() {
        return { data: true };
      }

      @OnMessage('request2')
      @EmitOnSuccess('response')
      @SkipEmitOnEmptyResult()
      async testEvent2() {
        return null;
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    const responses = [];

    wsClient.on('response', data => {
      responses.push(data);
    });

    await waitForEvent(wsClient, 'connected');
    expect(responses.length).toEqual(0);

    wsClient.emit('request2');
    wsClient.emit('request');

    await waitForEvent(wsClient, 'response');
    expect(responses.length).toEqual(1);
    expect(responses[0]).toEqual({ data: true });
  });

  it('Skip emit of defined event on failing sync execution', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('request')
      @EmitOnFail('response')
      @SkipEmitOnEmptyResult()
      testEvent() {
        throw new Error('error string');
      }

      @OnMessage('request2')
      @EmitOnFail('response')
      @SkipEmitOnEmptyResult()
      testEvent2() {
        return Promise.reject(null);
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    const responses = [];

    wsClient.on('response', data => {
      responses.push(data);
    });

    await waitForEvent(wsClient, 'connected');
    expect(responses.length).toEqual(0);

    wsClient.emit('request2');
    wsClient.emit('request');

    await waitForEvent(wsClient, 'response');
    expect(responses.length).toEqual(1);
    expect(responses[0]).toEqual('error string');
  });

  it('Skip emit of defined event on failing async execution', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      async connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('request')
      @EmitOnFail('response')
      @SkipEmitOnEmptyResult()
      async testEvent() {
        throw new Error('error string');
      }

      @OnMessage('request2')
      @EmitOnFail('response2')
      @SkipEmitOnEmptyResult()
      async testEvent2(@ConnectedSocket() socket: Socket) {
        return Promise.reject(null);
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    const responses = [];

    wsClient.on('response', data => {
      responses.push(data);
    });

    await waitForEvent(wsClient, 'connected');
    expect(responses.length).toEqual(0);

    wsClient.emit('request2');
    wsClient.emit('request');

    await waitForEvent(wsClient, 'response');
    expect(responses.length).toEqual(1);
    expect(responses[0]).toEqual('error string');
  });
});


================================================
FILE: test/functional/socket-id.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { SocketId } from '../../src';

describe('SocketId', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Connected socket id is retrieved correctly', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket, @SocketId() socketId: string) {
        testResult = socketId;
        socket.emit('connected');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    expect(wsClient.id).toEqual(testResult);
  });
});


================================================
FILE: test/functional/socket-io.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { SocketIO } from '../../src';

describe('SocketIo', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Socket.io instance is retrieved correctly', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket, @SocketIO() socketIO: Server) {
        testResult = socketIO;
        socket.emit('connected');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    expect(socketControllers.io).toEqual(testResult);
  });
});


================================================
FILE: test/functional/socket-message-ack.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { MessageAck, MessageBody, OnMessage, SocketId } from '../../src';

describe('MessageAck', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let testAckResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;
    testAckResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Event ack is retrieved correctly', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket, @SocketId() socketId: string) {
        testResult = socketId;
        socket.emit('connected');
      }

      @OnMessage('test')
      test(@MessageBody() data: any, @ConnectedSocket() socket: Socket, @MessageAck() ack: Function) {
        testResult = data;
        testAckResult = ack;
        socket.emit('return');
      }

      @OnMessage('test2')
      test2(
        @MessageAck() ack: Function,
        @MessageBody({ index: 1 }) data1: any,
        @MessageBody({ index: 0 }) data0: any,
        @ConnectedSocket() socket: Socket
      ) {
        testResult = { data1, data0 };
        ack?.('test ack2');
        socket.emit('return2');
      }

      @OnMessage('test3')
      test3(
        @ConnectedSocket() socket: Socket,
        @MessageAck() ack: Function,
        @MessageBody({ index: 1 }) data1: any,
        @MessageBody({ index: 0 }) data0: any
      ) {
        testResult = { data1, data0 };
        testAckResult = ack;
        socket.emit('return3');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');

    const ack = (ack: any) => (testAckResult = ack);

    wsClient.emit('test', 'test data');
    await waitForEvent(wsClient, 'return');
    expect(testResult).toEqual('test data');
    expect(testAckResult).toBeNull();

    wsClient.emit('test2', 'test data 0', 'test data 1', 'test data 2', ack);
    await waitForEvent(wsClient, 'return2');
    expect(testResult).toEqual({ data0: 'test data 0', data1: 'test data 1' });
    expect(testAckResult).toEqual('test ack2');

    // ack should be the last parameter
    wsClient.emit('test3', 'test data 0', 'test data 1', ack, 'test data 2');
    await waitForEvent(wsClient, 'return3');
    expect(testResult).toEqual({ data0: 'test data 0', data1: 'test data 1' });
    expect(testAckResult).toBeNull();
  });
});


================================================
FILE: test/functional/socket-message-body.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { MessageBody, OnMessage, SocketId } from '../../src';

describe('MessageBody', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Event body is retrieved correctly', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket, @SocketId() socketId: string) {
        testResult = socketId;
        socket.emit('connected');
      }

      @OnMessage('test')
      test(@MessageBody() data: any, @ConnectedSocket() socket: Socket) {
        testResult = data;
        socket.emit('return');
      }

      @OnMessage('test2')
      test2(
        @MessageBody({ index: 1 }) data1: any,
        @MessageBody({ index: 0 }) data0: any,
        @ConnectedSocket() socket: Socket
      ) {
        testResult = { data1, data0 };
        socket.emit('return2');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');

    wsClient.emit('test', 'test data');
    await waitForEvent(wsClient, 'return');
    expect(testResult).toEqual('test data');

    wsClient.emit('test2', 'test data 0', 'test data 1', 'test data 2', ack => {
      console.log(ack);
    });
    await waitForEvent(wsClient, 'return2');
    expect(testResult).toEqual({ data0: 'test data 0', data1: 'test data 1' });
  });
});


================================================
FILE: test/functional/socket-query-param.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { ConnectedSocket, OnConnect, SocketController, SocketControllers, SocketQueryParam } from '../../src';
import { Container, Service } from 'typedi';
import { waitForEvent } from '../utilities/waitForEvent';

describe('SocketQueryParam', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Socket query param is retrieved correctly', async () => {
    @SocketController()
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket, @SocketQueryParam('testParam') parameter: string) {
        testResult = parameter;
        socket.emit('connected');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '?testParam=testValue', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    expect(testResult).toEqual('testValue');
  });
});


================================================
FILE: test/functional/socket-request.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import {
  ConnectedSocket,
  OnConnect,
  SocketController,
  SocketControllers,
  SocketQueryParam,
  SocketRequest,
} from '../../src';
import { Container, Service } from 'typedi';
import { waitForEvent } from '../utilities/waitForEvent';

describe('SocketRequest', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Socket request is retrieved correctly', async () => {
    @SocketController()
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket, @SocketRequest() request: any) {
        testResult = request;
        socket.emit('connected');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '?testParam=testValue', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    expect(testResult.url).toContain('/socket.io/?testParam=testValue&EIO=');
  });
});


================================================
FILE: test/functional/socket-rooms.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { SocketRooms } from '../../src';

describe('SocketRooms', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult;
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = undefined;

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('Socket rooms set is retrieved correctly', async () => {
    @SocketController('/string')
    @Service()
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket, @SocketRooms() rooms: any) {
        testResult = rooms;
        socket.emit('connected');
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    expect([...(testResult as Set<string>).keys()][0]).toEqual(wsClient.id);
  });
});


================================================
FILE: test/functional/use-interceptor.spec.ts
================================================
import { createServer, Server as HttpServer } from 'http';
import { Server } from 'socket.io';
import { io, Socket } from 'socket.io-client';
import { SocketControllers } from '../../src/SocketControllers';
import { Container, Service } from 'typedi';
import { SocketController } from '../../src/decorators/SocketController';
import { OnConnect } from '../../src/decorators/OnConnect';
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
import { waitForEvent } from '../utilities/waitForEvent';
import { EmitOnSuccess, MessageBody, OnMessage, SocketEventType } from '../../src';
import { UseInterceptor } from '../../src/decorators/UseInterceptor';
import { InterceptorInterface } from '../../src/types/InterceptorInterface';
import { SocketEventContext } from '../../src/types/SocketEventContext';

describe('UseInterceptor', () => {
  const PORT = 8080;
  const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;

  let httpServer: HttpServer;
  let wsApp: Server;
  let wsClient: Socket;
  let testResult = [];
  let socketControllers: SocketControllers;

  beforeEach(done => {
    httpServer = createServer();
    wsApp = new Server(httpServer, {
      cors: {
        origin: '*',
      },
    });
    httpServer.listen(PORT, () => {
      done();
    });
  });

  afterEach(() => {
    testResult = [];

    Container.reset();
    wsClient.close();
    wsClient = null;
    socketControllers = null;
    return new Promise(resolve => {
      if (wsApp)
        return wsApp.close(() => {
          resolve(null);
        });
      resolve(null);
    });
  });

  it('interceptor should be executed in the correct order', async () => {
    @Service()
    class testInterceptor implements InterceptorInterface {
      use(ctx: SocketEventContext, next: any) {
        testResult.push('testInterceptor start');
        const resp = next();
        testResult.push('testInterceptor end');
        return resp;
      }
    }

    const plain: InterceptorInterface = {
      use: (ctx: SocketEventContext, next: () => any) => {
        testResult.push('plain start');
        const resp = next();
        testResult.push('plain end');
        return resp;
      },
    };

    @SocketController('/string')
    @Service()
    @UseInterceptor(plain)
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('test')
      @UseInterceptor(testInterceptor)
      @EmitOnSuccess('finished')
      test() {
        testResult.push('action');
        return 'test';
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.emit('test');
    const response = await waitForEvent(wsClient, 'finished');
    expect(testResult).toEqual([
      'plain start',
      'plain end',
      'plain start',
      'testInterceptor start',
      'action',
      'testInterceptor end',
      'plain end',
    ]);
    expect(response).toEqual('test');
  });

  it('interceptor should be able to skip further actions', async () => {
    @Service()
    class testInterceptor implements InterceptorInterface {
      use(ctx: SocketEventContext, next: any) {
        testResult.push('testInterceptor start');
        const response = next();
        testResult.push('testInterceptor end');
        return response;
      }
    }

    const plain: InterceptorInterface = {
      use: (ctx: SocketEventContext, next: () => any) => {
        testResult.push('plain start');

        if (ctx.eventType === SocketEventType.CONNECT) {
          next();
        }

        testResult.push('plain end');
        return 'plain response';
      },
    };

    @SocketController('/string')
    @Service()
    @UseInterceptor(plain)
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket) {
        socket.emit('connected');
      }

      @OnMessage('test')
      @UseInterceptor(testInterceptor)
      @EmitOnSuccess('finished')
      test() {
        testResult.push('action');
        return 'test';
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.emit('test');
    const response = await waitForEvent(wsClient, 'finished');
    expect(testResult).toEqual(['plain start', 'plain end', 'plain start', 'plain end']);
    expect(response).toEqual('plain response');
  });

  it('interceptor should be able to mutate the context', async () => {
    @Service()
    class testInterceptor implements InterceptorInterface {
      use(ctx: SocketEventContext, next: any) {
        testResult.push(ctx.messageArgs?.[0]);
        ctx.messageArgs = ['testInterceptor'];
        return next();
      }
    }

    const plain: InterceptorInterface = {
      use: (ctx: SocketEventContext, next: () => any) => {
        testResult.push(ctx.messageArgs?.[0]);
        ctx.messageArgs = ['plain'];
        return next();
      },
    };

    @SocketController('/string')
    @Service()
    @UseInterceptor(plain)
    class TestController {
      @OnConnect()
      connected(@ConnectedSocket() socket: Socket, @MessageBody() message: any) {
        testResult.push(message);
        socket.emit('connected');
      }

      @OnMessage('test')
      @UseInterceptor(testInterceptor)
      @EmitOnSuccess('finished')
      test(@MessageBody() message: any) {
        testResult.push(message);
        return 'test';
      }
    }

    socketControllers = new SocketControllers({
      io: wsApp,
      container: Container,
      controllers: [TestController],
    });
    wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });

    await waitForEvent(wsClient, 'connected');
    wsClient.emit('test', 'my body');
    const response = await waitForEvent(wsClient, 'finished');
    expect(testResult).toEqual([undefined, 'plain', 'my body', 'plain', 'testInterceptor']);
    expect(response).toEqual('test');
  });
});


================================================
FILE: test/utilities/testSocketConnection.ts
================================================
import socketio from 'socket.io-client';

export async function testConnection(path: string) {
  return await new Promise<number>((resolve, reject) => {
    const socket = socketio(path, { reconnection: false, timeout: 5000 });
    socket.on('connect', () => socket.disconnect());
    socket.on('connect_error', reject);
    socket.on('disconnect', () => {
      resolve(0);
    });
  });
}


================================================
FILE: test/utilities/waitForEvent.ts
================================================
import { Socket } from 'socket.io-client';
import { Server } from 'socket.io';

export const waitForEvent = (socket: Socket | Server, event: string): Promise<unknown> => {
  return new Promise(resolve => {
    socket.on(event, data => {
      resolve(data);
    });
  });
};


================================================
FILE: test/utilities/waitForTime.ts
================================================
export const waitForTime = (time: number): Promise<unknown> => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(null);
    }, time);
  });
};


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2018",
    "lib": ["es2018"],
    "outDir": "build",
    "rootDir": "./src",
    "strict": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "forceConsistentCasingInFileNames": true
  },
  "exclude": ["node_modules", "sample", "**/*.spec.ts", "testing/**", "test/**"],
}


================================================
FILE: tsconfig.prod.json
================================================
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "strict": false,
    "sourceMap": true,
    "removeComments": false,
    "declaration": true,
  },
}


================================================
FILE: tsconfig.spec.json
================================================
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "strict": false,
    "strictPropertyInitialization": false,
    "sourceMap": true,
    "inlineSourceMap": true,
    "removeComments": true,
    "noImplicitAny": false,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "rootDir": "./"
  },
  "exclude": ["node_modules"]
}
Download .txt
gitextract_1if_z9rq/

├── .github/
│   ├── dependabot.yml
│   ├── semantic.yml
│   └── workflows/
│       ├── auto-approve-dependabot-workflow.yml
│       ├── continuous-deployment-workflow.yml
│       ├── continuous-integration-workflow.yml
│       └── lock-closed-issues-workflow.yml
├── .gitignore
├── .prettierrc.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── codecov.yml
├── eslint.config.mjs
├── jest.config.js
├── jest.setup.js
├── package.json
├── sample/
│   ├── sample1-simple-controller/
│   │   ├── Message.ts
│   │   ├── MessageController.ts
│   │   ├── app.ts
│   │   └── index.html
│   ├── sample2-use-created-socket-io/
│   │   ├── Message.ts
│   │   ├── MessageController.ts
│   │   ├── app.ts
│   │   └── index.html
│   ├── sample3-namespaces/
│   │   ├── Message.ts
│   │   ├── MessageController.ts
│   │   ├── app.ts
│   │   └── index.html
│   ├── sample4-emitters/
│   │   ├── Message.ts
│   │   ├── MessageController.ts
│   │   ├── app.ts
│   │   └── index.html
│   ├── sample5-middlewares/
│   │   ├── AuthenticationMiddleware.ts
│   │   ├── Message.ts
│   │   ├── MessageController.ts
│   │   ├── app.ts
│   │   └── index.html
│   └── sample6-dynamic-namespaces/
│       ├── Message.ts
│       ├── MessageController.ts
│       ├── app.ts
│       └── index.html
├── src/
│   ├── SocketControllers.ts
│   ├── decorators/
│   │   ├── ConnectedSocket.ts
│   │   ├── EmitOnFail.ts
│   │   ├── EmitOnSuccess.ts
│   │   ├── MessageAck.ts
│   │   ├── MessageBody.ts
│   │   ├── Middleware.ts
│   │   ├── NspParam.ts
│   │   ├── NspParams.ts
│   │   ├── OnConnect.ts
│   │   ├── OnDisconnect.ts
│   │   ├── OnDisconnecting.ts
│   │   ├── OnMessage.ts
│   │   ├── SkipEmitOnEmptyResult.ts
│   │   ├── SocketController.ts
│   │   ├── SocketIO.ts
│   │   ├── SocketId.ts
│   │   ├── SocketQueryParam.ts
│   │   ├── SocketRequest.ts
│   │   ├── SocketRooms.ts
│   │   └── UseInterceptor.ts
│   ├── index.ts
│   ├── types/
│   │   ├── ActionMetadata.ts
│   │   ├── ActionTransformOptions.ts
│   │   ├── ControllerMetadata.ts
│   │   ├── HandlerMetadata.ts
│   │   ├── InterceptorInterface.ts
│   │   ├── MiddlewareInterface.ts
│   │   ├── MiddlewareMetadata.ts
│   │   ├── ParameterMetadata.ts
│   │   ├── ResultMetadata.ts
│   │   ├── SocketControllerMetaKey.ts
│   │   ├── SocketControllersOptions.ts
│   │   ├── SocketEventContext.ts
│   │   ├── TransformOptions.ts
│   │   ├── constants/
│   │   │   └── defaultTransformOptions.ts
│   │   └── enums/
│   │       ├── HandlerType.ts
│   │       ├── ParameterType.ts
│   │       ├── ResultType.ts
│   │       └── SocketEventType.ts
│   └── util/
│       ├── add-action-to-controller-metadata.ts
│       ├── add-controller-metadata.ts
│       ├── add-interceptor-to-action-metadata.ts
│       ├── add-middleware-metadata.ts
│       ├── add-parameter-to-action-metadata.ts
│       ├── add-result-to-action-metadata.ts
│       ├── chain-execute.ts
│       └── get-metadata.ts
├── test/
│   ├── functional/
│   │   ├── connected-socket.spec.ts
│   │   ├── controllers/
│   │   │   ├── test.controller.ts
│   │   │   └── test2.controller.ts
│   │   ├── create-socket-server.spec.ts
│   │   ├── emit-on-fail.spec.ts
│   │   ├── emit-on-success.spec.ts
│   │   ├── load-controllers-from-directory.spec.ts
│   │   ├── middlewares.spec.ts
│   │   ├── multiple-controllers-on-same-namespace.spec.ts
│   │   ├── nsp-param.spec.ts
│   │   ├── nsp-params.spec.ts
│   │   ├── on-disconnect.spec.ts
│   │   ├── on-disconnecting.spec.ts
│   │   ├── parameter-transformation.spec.ts
│   │   ├── scoped-controllers.spec.ts
│   │   ├── skip-emit-on-empty-result.spec.ts
│   │   ├── socket-id.spec.ts
│   │   ├── socket-io.spec.ts
│   │   ├── socket-message-ack.spec.ts
│   │   ├── socket-message-body.spec.ts
│   │   ├── socket-query-param.spec.ts
│   │   ├── socket-request.spec.ts
│   │   ├── socket-rooms.spec.ts
│   │   └── use-interceptor.spec.ts
│   └── utilities/
│       ├── testSocketConnection.ts
│       ├── waitForEvent.ts
│       └── waitForTime.ts
├── tsconfig.json
├── tsconfig.prod.json
└── tsconfig.spec.json
Download .txt
SYMBOL INDEX (246 symbols across 76 files)

FILE: sample/sample1-simple-controller/Message.ts
  class Message (line 1) | class Message {

FILE: sample/sample1-simple-controller/MessageController.ts
  class MessageController (line 5) | class MessageController {
    method connection (line 7) | connection(@ConnectedSocket() socket: any) {
    method disconnect (line 12) | disconnect(@ConnectedSocket() socket: any) {
    method save (line 17) | save(@ConnectedSocket() socket: any, @MessageBody() message: Message) {

FILE: sample/sample2-use-created-socket-io/Message.ts
  class Message (line 1) | class Message {

FILE: sample/sample2-use-created-socket-io/MessageController.ts
  class MessageController (line 5) | class MessageController {
    method connection (line 7) | connection(@ConnectedSocket() socket: any) {
    method disconnect (line 12) | disconnect(@ConnectedSocket() socket: any) {
    method save (line 17) | save(@ConnectedSocket() socket: any, @MessageBody() message: Message) {

FILE: sample/sample3-namespaces/Message.ts
  class Message (line 1) | class Message {

FILE: sample/sample3-namespaces/MessageController.ts
  class MessageController (line 5) | class MessageController {
    method connection (line 7) | connection(@ConnectedSocket() socket: any) {
    method disconnect (line 12) | disconnect(@ConnectedSocket() socket: any) {
    method save (line 17) | save(@ConnectedSocket() socket: any, @MessageBody() message: Message) {

FILE: sample/sample4-emitters/Message.ts
  class Message (line 1) | class Message {

FILE: sample/sample4-emitters/MessageController.ts
  class MessageController (line 15) | class MessageController {
    method connection (line 17) | connection(@ConnectedSocket() socket: any) {
    method disconnect (line 22) | disconnect(@ConnectedSocket() socket: any) {
    method save (line 30) | save(@ConnectedSocket() socket: any, @MessageBody() message: Message) {
    method trySave (line 41) | trySave(@ConnectedSocket() socket: any, @MessageBody() message: Messag...

FILE: sample/sample5-middlewares/AuthenticationMiddleware.ts
  class AuthenticationMiddleware (line 4) | class AuthenticationMiddleware implements MiddlewareInterface {
    method use (line 5) | use(socket: any, next: (err?: any) => any): any {

FILE: sample/sample5-middlewares/Message.ts
  class Message (line 1) | class Message {

FILE: sample/sample5-middlewares/MessageController.ts
  class MessageController (line 5) | class MessageController {
    method connection (line 7) | connection(@ConnectedSocket() socket: any) {
    method disconnect (line 12) | disconnect(@ConnectedSocket() socket: any) {
    method save (line 17) | save(@ConnectedSocket() socket: any, @MessageBody() message: Message) {

FILE: sample/sample6-dynamic-namespaces/Message.ts
  class Message (line 1) | class Message {

FILE: sample/sample6-dynamic-namespaces/MessageController.ts
  class MessageController (line 13) | class MessageController {
    method connection (line 15) | connection(@ConnectedSocket() socket: any) {
    method disconnect (line 20) | disconnect(@ConnectedSocket() socket: any) {
    method save (line 25) | async save(@ConnectedSocket() socket: any, @MessageBody() message: Mes...

FILE: src/SocketControllers.ts
  class SocketControllers (line 26) | class SocketControllers {
    method constructor (line 33) | constructor(private options: SocketControllersOptions) {
    method loadHandlers (line 47) | private loadHandlers<T extends Object>(handlers: Array<Function | stri...
    method loadHandlersFromPath (line 66) | private loadHandlersFromPath(path: string, handlerType: HandlerType): ...
    method registerMiddlewares (line 89) | private registerMiddlewares() {
    method registerControllers (line 123) | private registerControllers() {
    method registerController (line 157) | private registerController(socket: Socket, controller: HandlerMetadata...
    method executeAction (line 202) | private async executeAction(
    method handleActionResult (line 259) | private handleActionResult(socket: Socket, action: ActionMetadata, res...
    method registerMiddleware (line 289) | private registerMiddleware(namespace: Namespace, middleware: HandlerMe...
    method resolveParameters (line 296) | private resolveParameters(
    method resolveParameter (line 318) | private resolveParameter(
    method transformActionValue (line 351) | private transformActionValue(
    method resolveEventContext (line 382) | private resolveEventContext(
    method extractNamespaceParameters (line 401) | private extractNamespaceParameters(

FILE: src/decorators/ConnectedSocket.ts
  function ConnectedSocket (line 4) | function ConnectedSocket() {

FILE: src/decorators/EmitOnFail.ts
  function EmitOnFail (line 5) | function EmitOnFail(messageName: string, options?: ActionTransformOption...

FILE: src/decorators/EmitOnSuccess.ts
  function EmitOnSuccess (line 5) | function EmitOnSuccess(messageName: string, options?: ActionTransformOpt...

FILE: src/decorators/MessageAck.ts
  function MessageAck (line 4) | function MessageAck() {

FILE: src/decorators/MessageBody.ts
  function MessageBody (line 5) | function MessageBody(options?: ActionTransformOptions & { index?: number...

FILE: src/decorators/Middleware.ts
  function Middleware (line 4) | function Middleware(options?: {

FILE: src/decorators/NspParam.ts
  function NspParam (line 4) | function NspParam(name: string) {

FILE: src/decorators/NspParams.ts
  function NspParams (line 4) | function NspParams() {

FILE: src/decorators/OnConnect.ts
  function OnConnect (line 4) | function OnConnect(): Function {

FILE: src/decorators/OnDisconnect.ts
  function OnDisconnect (line 4) | function OnDisconnect(): Function {

FILE: src/decorators/OnDisconnecting.ts
  function OnDisconnecting (line 4) | function OnDisconnecting(): Function {

FILE: src/decorators/OnMessage.ts
  function OnMessage (line 4) | function OnMessage(name?: string): Function {

FILE: src/decorators/SkipEmitOnEmptyResult.ts
  function SkipEmitOnEmptyResult (line 4) | function SkipEmitOnEmptyResult(): Function {

FILE: src/decorators/SocketController.ts
  function SocketController (line 4) | function SocketController(namespace?: string | RegExp) {

FILE: src/decorators/SocketIO.ts
  function SocketIO (line 4) | function SocketIO() {

FILE: src/decorators/SocketId.ts
  function SocketId (line 4) | function SocketId() {

FILE: src/decorators/SocketQueryParam.ts
  function SocketQueryParam (line 4) | function SocketQueryParam(name?: string) {

FILE: src/decorators/SocketRequest.ts
  function SocketRequest (line 4) | function SocketRequest() {

FILE: src/decorators/SocketRooms.ts
  function SocketRooms (line 4) | function SocketRooms() {

FILE: src/decorators/UseInterceptor.ts
  function UseInterceptor (line 5) | function UseInterceptor(...interceptors: any[]): Function {

FILE: src/types/ActionMetadata.ts
  type ActionMetadata (line 5) | interface ActionMetadata {

FILE: src/types/ActionTransformOptions.ts
  type ActionTransformOptions (line 3) | interface ActionTransformOptions {

FILE: src/types/ControllerMetadata.ts
  type ControllerMetadata (line 4) | interface ControllerMetadata {

FILE: src/types/HandlerMetadata.ts
  type HandlerMetadata (line 1) | interface HandlerMetadata<T> {

FILE: src/types/InterceptorInterface.ts
  type InterceptorInterface (line 3) | interface InterceptorInterface {

FILE: src/types/MiddlewareInterface.ts
  type MiddlewareInterface (line 3) | interface MiddlewareInterface {

FILE: src/types/MiddlewareMetadata.ts
  type MiddlewareMetadata (line 3) | interface MiddlewareMetadata {

FILE: src/types/ParameterMetadata.ts
  type ParameterMetadata (line 4) | interface ParameterMetadata {

FILE: src/types/ResultMetadata.ts
  type ResultMetadata (line 4) | interface ResultMetadata {

FILE: src/types/SocketControllerMetaKey.ts
  constant SOCKET_CONTROLLER_META_KEY (line 1) | const SOCKET_CONTROLLER_META_KEY = Symbol('SocketControllerMetaKey');

FILE: src/types/SocketControllersOptions.ts
  type SocketControllersOptions (line 5) | interface SocketControllersOptions {

FILE: src/types/SocketEventContext.ts
  type SocketEventContext (line 4) | interface SocketEventContext {

FILE: src/types/TransformOptions.ts
  type TransformOptions (line 3) | interface TransformOptions {

FILE: src/types/enums/HandlerType.ts
  type HandlerType (line 1) | enum HandlerType {

FILE: src/types/enums/ParameterType.ts
  type ParameterType (line 1) | enum ParameterType {

FILE: src/types/enums/ResultType.ts
  type ResultType (line 1) | enum ResultType {

FILE: src/types/enums/SocketEventType.ts
  type SocketEventType (line 1) | enum SocketEventType {

FILE: src/util/chain-execute.ts
  function chainExecute (line 1) | function chainExecute(context: any, chain: Function[]) {

FILE: test/functional/connected-socket.spec.ts
  class TestController (line 50) | @SocketController('/string')
    method connected (line 54) | connected(@ConnectedSocket() socket: Socket) {

FILE: test/functional/controllers/test.controller.ts
  class TestController (line 6) | class TestController {
    method connected (line 8) | connected(@ConnectedSocket() socket: any) {

FILE: test/functional/controllers/test2.controller.ts
  class Test2Controller (line 6) | class Test2Controller {
    method connected (line 8) | connected(@ConnectedSocket() socket: any) {

FILE: test/functional/create-socket-server.spec.ts
  class TestController (line 35) | @SocketController()

FILE: test/functional/emit-on-fail.spec.ts
  class TestController (line 52) | @SocketController('/string')
    method connected (line 57) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 63) | testEvent() {
    method connected (line 96) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 102) | async testEvent() {
    method connected (line 134) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 142) | async testEvent() {
    method testEvent2 (line 150) | async testEvent2() {
    method testEvent3 (line 158) | async testEvent3() {
    method testEvent4 (line 166) | async testEvent4() {
  class TestController (line 91) | @SocketController('/string')
    method connected (line 57) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 63) | testEvent() {
    method connected (line 96) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 102) | async testEvent() {
    method connected (line 134) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 142) | async testEvent() {
    method testEvent2 (line 150) | async testEvent2() {
    method testEvent3 (line 158) | async testEvent3() {
    method testEvent4 (line 166) | async testEvent4() {
  class TestController (line 130) | @SocketController('/string')
    method connected (line 57) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 63) | testEvent() {
    method connected (line 96) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 102) | async testEvent() {
    method connected (line 134) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 142) | async testEvent() {
    method testEvent2 (line 150) | async testEvent2() {
    method testEvent3 (line 158) | async testEvent3() {
    method testEvent4 (line 166) | async testEvent4() {

FILE: test/functional/emit-on-success.spec.ts
  class TestController (line 51) | @SocketController('/string')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 61) | testEvent() {
    method testEvent2 (line 67) | testEvent2() {
    method connected (line 100) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 106) | async testEvent() {
    method testEvent2 (line 112) | async testEvent2() {
  class TestController (line 96) | @SocketController('/string')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 61) | testEvent() {
    method testEvent2 (line 67) | testEvent2() {
    method connected (line 100) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 106) | async testEvent() {
    method testEvent2 (line 112) | async testEvent2() {

FILE: test/functional/middlewares.spec.ts
  class GlobalMiddleware (line 52) | @Middleware()
    method use (line 55) | use(socket: any, next: (err?: any) => any): any {
  class Controller (line 61) | @SocketController()
    method connected (line 65) | connected(@ConnectedSocket() socket: Socket) {
  class StringNamespaceMiddleware (line 84) | @Middleware({ namespace: '/string' })
    method use (line 87) | use(socket: any, next: (err?: any) => any): any {
    method use (line 127) | use(socket: any, next: (err?: any) => any): any {
  class MiddlewareWithoutNamespace (line 93) | @Middleware()
    method use (line 96) | use(socket: any, next: (err?: any) => any): any {
    method use (line 136) | use(socket: any, next: (err?: any) => any): any {
    method use (line 178) | use(socket: any, next: (err?: any) => any): any {
    method use (line 218) | use(socket: any, next: (err?: any) => any): any {
    method use (line 260) | use(socket: any, next: (err?: any) => any): any {
    method use (line 300) | use(socket: any, next: (err?: any) => any): any {
  class StringNamespaceController (line 102) | @SocketController('/string')
    method connected (line 106) | connected(@ConnectedSocket() socket: Socket) {
  class StringNamespaceMiddleware (line 124) | @Middleware({ namespace: '/string' })
    method use (line 87) | use(socket: any, next: (err?: any) => any): any {
    method use (line 127) | use(socket: any, next: (err?: any) => any): any {
  class MiddlewareWithoutNamespace (line 133) | @Middleware()
    method use (line 96) | use(socket: any, next: (err?: any) => any): any {
    method use (line 136) | use(socket: any, next: (err?: any) => any): any {
    method use (line 178) | use(socket: any, next: (err?: any) => any): any {
    method use (line 218) | use(socket: any, next: (err?: any) => any): any {
    method use (line 260) | use(socket: any, next: (err?: any) => any): any {
    method use (line 300) | use(socket: any, next: (err?: any) => any): any {
  class String2NamespaceController (line 142) | @SocketController('/string2')
    method connected (line 146) | connected(@ConnectedSocket() socket: Socket) {
  class RegexpNamespaceMiddleware (line 166) | @Middleware({ namespace: /^\/dynamic-\d+$/ })
    method use (line 169) | use(socket: any, next: (err?: any) => any): any {
    method use (line 209) | use(socket: any, next: (err?: any) => any): any {
  class MiddlewareWithoutNamespace (line 175) | @Middleware()
    method use (line 96) | use(socket: any, next: (err?: any) => any): any {
    method use (line 136) | use(socket: any, next: (err?: any) => any): any {
    method use (line 178) | use(socket: any, next: (err?: any) => any): any {
    method use (line 218) | use(socket: any, next: (err?: any) => any): any {
    method use (line 260) | use(socket: any, next: (err?: any) => any): any {
    method use (line 300) | use(socket: any, next: (err?: any) => any): any {
  class RegexpNamespaceController (line 184) | @SocketController(/^\/dynamic-\d+$/)
    method connected (line 188) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 228) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 270) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 310) | connected(@ConnectedSocket() socket: Socket) {
  class RegexpNamespaceMiddleware (line 206) | @Middleware({ namespace: /^\/dynamic-\s+$/ })
    method use (line 169) | use(socket: any, next: (err?: any) => any): any {
    method use (line 209) | use(socket: any, next: (err?: any) => any): any {
  class MiddlewareWithoutNamespace (line 215) | @Middleware()
    method use (line 96) | use(socket: any, next: (err?: any) => any): any {
    method use (line 136) | use(socket: any, next: (err?: any) => any): any {
    method use (line 178) | use(socket: any, next: (err?: any) => any): any {
    method use (line 218) | use(socket: any, next: (err?: any) => any): any {
    method use (line 260) | use(socket: any, next: (err?: any) => any): any {
    method use (line 300) | use(socket: any, next: (err?: any) => any): any {
  class RegexpNamespaceController (line 224) | @SocketController(/^\/dynamic-\d+$/)
    method connected (line 188) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 228) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 270) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 310) | connected(@ConnectedSocket() socket: Socket) {
  class RegexpArrayNamespaceMiddleware (line 248) | @Middleware({ namespace: [/^\/dynamic-\d+$/] })
    method use (line 251) | use(socket: any, next: (err?: any) => any): any {
    method use (line 291) | use(socket: any, next: (err?: any) => any): any {
  class MiddlewareWithoutNamespace (line 257) | @Middleware()
    method use (line 96) | use(socket: any, next: (err?: any) => any): any {
    method use (line 136) | use(socket: any, next: (err?: any) => any): any {
    method use (line 178) | use(socket: any, next: (err?: any) => any): any {
    method use (line 218) | use(socket: any, next: (err?: any) => any): any {
    method use (line 260) | use(socket: any, next: (err?: any) => any): any {
    method use (line 300) | use(socket: any, next: (err?: any) => any): any {
  class RegexpNamespaceController (line 266) | @SocketController(/^\/dynamic-\d+$/)
    method connected (line 188) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 228) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 270) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 310) | connected(@ConnectedSocket() socket: Socket) {
  class RegexpArrayNamespaceMiddleware (line 288) | @Middleware({ namespace: [/^\/dynamic-\s+$/] })
    method use (line 251) | use(socket: any, next: (err?: any) => any): any {
    method use (line 291) | use(socket: any, next: (err?: any) => any): any {
  class MiddlewareWithoutNamespace (line 297) | @Middleware()
    method use (line 96) | use(socket: any, next: (err?: any) => any): any {
    method use (line 136) | use(socket: any, next: (err?: any) => any): any {
    method use (line 178) | use(socket: any, next: (err?: any) => any): any {
    method use (line 218) | use(socket: any, next: (err?: any) => any): any {
    method use (line 260) | use(socket: any, next: (err?: any) => any): any {
    method use (line 300) | use(socket: any, next: (err?: any) => any): any {
  class RegexpNamespaceController (line 306) | @SocketController(/^\/dynamic-\d+$/)
    method connected (line 188) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 228) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 270) | connected(@ConnectedSocket() socket: Socket) {
    method connected (line 310) | connected(@ConnectedSocket() socket: Socket) {

FILE: test/functional/multiple-controllers-on-same-namespace.spec.ts
  class TestController (line 47) | @SocketController('/string')
    method connected (line 51) | connected(@ConnectedSocket() socket: Socket) {
  class TestController2 (line 57) | @SocketController('/string')
    method connected (line 61) | connected(@ConnectedSocket() socket: Socket) {

FILE: test/functional/nsp-param.spec.ts
  class TestController (line 51) | @SocketController('/:first/:second')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket, @NspParam('second') param...

FILE: test/functional/nsp-params.spec.ts
  class TestController (line 51) | @SocketController('/:first/:second')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket, @NspParams() parameters: ...

FILE: test/functional/on-disconnect.spec.ts
  class TestController (line 52) | @SocketController('/string')
    method connected (line 56) | connected(@ConnectedSocket() socket: Socket) {
    method disconnected (line 61) | disconnected() {

FILE: test/functional/on-disconnecting.spec.ts
  class TestController (line 53) | @SocketController('/string')
    method connected (line 57) | connected(@ConnectedSocket() socket: Socket) {
    method disconnected (line 62) | disconnected() {
    method disconnecting (line 67) | disconnecting() {

FILE: test/functional/parameter-transformation.spec.ts
  class Body (line 52) | class Body {
  class TestController (line 57) | @SocketController('/string')
    method connected (line 61) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 67) | test(

FILE: test/functional/scoped-controllers.spec.ts
  class TestService (line 51) | @Service()
  class TestController (line 54) | @SocketController('/string')
    method constructor (line 57) | constructor(private testService: TestService) {}
    method connected (line 60) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 66) | test() {
    method constructor (line 96) | constructor(private testService: TestService) {}
    method connected (line 99) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 105) | test() {
    method constructor (line 138) | constructor(
    method connected (line 144) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 150) | test() {
    method connected (line 183) | connected(@ConnectedSocket() socket: Socket) {}
    method test (line 187) | test() {}
    method constructor (line 221) | constructor(
    method connected (line 227) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 233) | test() {
  class TestService (line 90) | @Service({ global: true })
  class TestController (line 93) | @SocketController('/string')
    method constructor (line 57) | constructor(private testService: TestService) {}
    method connected (line 60) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 66) | test() {
    method constructor (line 96) | constructor(private testService: TestService) {}
    method connected (line 99) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 105) | test() {
    method constructor (line 138) | constructor(
    method connected (line 144) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 150) | test() {
    method connected (line 183) | connected(@ConnectedSocket() socket: Socket) {}
    method test (line 187) | test() {}
    method constructor (line 221) | constructor(
    method connected (line 227) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 233) | test() {
  class TestService (line 132) | @Service({ global: true })
  class TestController (line 135) | @SocketController('/string')
    method constructor (line 57) | constructor(private testService: TestService) {}
    method connected (line 60) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 66) | test() {
    method constructor (line 96) | constructor(private testService: TestService) {}
    method connected (line 99) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 105) | test() {
    method constructor (line 138) | constructor(
    method connected (line 144) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 150) | test() {
    method connected (line 183) | connected(@ConnectedSocket() socket: Socket) {}
    method test (line 187) | test() {}
    method constructor (line 221) | constructor(
    method connected (line 227) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 233) | test() {
  class TestController (line 178) | @SocketController('/:test')
    method constructor (line 57) | constructor(private testService: TestService) {}
    method connected (line 60) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 66) | test() {
    method constructor (line 96) | constructor(private testService: TestService) {}
    method connected (line 99) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 105) | test() {
    method constructor (line 138) | constructor(
    method connected (line 144) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 150) | test() {
    method connected (line 183) | connected(@ConnectedSocket() socket: Socket) {}
    method test (line 187) | test() {}
    method constructor (line 221) | constructor(
    method connected (line 227) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 233) | test() {
  class TestService (line 215) | @Service({ global: true })
  class TestController (line 218) | @SocketController('/string')
    method constructor (line 57) | constructor(private testService: TestService) {}
    method connected (line 60) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 66) | test() {
    method constructor (line 96) | constructor(private testService: TestService) {}
    method connected (line 99) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 105) | test() {
    method constructor (line 138) | constructor(
    method connected (line 144) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 150) | test() {
    method connected (line 183) | connected(@ConnectedSocket() socket: Socket) {}
    method test (line 187) | test() {}
    method constructor (line 221) | constructor(
    method connected (line 227) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 233) | test() {

FILE: test/functional/skip-emit-on-empty-result.spec.ts
  class TestController (line 51) | @SocketController('/string')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 62) | testEvent() {
    method testEvent2 (line 69) | testEvent2() {
    method connected (line 103) | async connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 110) | async testEvent() {
    method testEvent2 (line 117) | async testEvent2() {
    method connected (line 151) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 158) | testEvent() {
    method testEvent2 (line 165) | testEvent2() {
    method connected (line 199) | async connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 206) | async testEvent() {
    method testEvent2 (line 213) | async testEvent2(@ConnectedSocket() socket: Socket) {
  class TestController (line 99) | @SocketController('/string')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 62) | testEvent() {
    method testEvent2 (line 69) | testEvent2() {
    method connected (line 103) | async connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 110) | async testEvent() {
    method testEvent2 (line 117) | async testEvent2() {
    method connected (line 151) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 158) | testEvent() {
    method testEvent2 (line 165) | testEvent2() {
    method connected (line 199) | async connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 206) | async testEvent() {
    method testEvent2 (line 213) | async testEvent2(@ConnectedSocket() socket: Socket) {
  class TestController (line 147) | @SocketController('/string')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 62) | testEvent() {
    method testEvent2 (line 69) | testEvent2() {
    method connected (line 103) | async connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 110) | async testEvent() {
    method testEvent2 (line 117) | async testEvent2() {
    method connected (line 151) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 158) | testEvent() {
    method testEvent2 (line 165) | testEvent2() {
    method connected (line 199) | async connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 206) | async testEvent() {
    method testEvent2 (line 213) | async testEvent2(@ConnectedSocket() socket: Socket) {
  class TestController (line 195) | @SocketController('/string')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 62) | testEvent() {
    method testEvent2 (line 69) | testEvent2() {
    method connected (line 103) | async connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 110) | async testEvent() {
    method testEvent2 (line 117) | async testEvent2() {
    method connected (line 151) | connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 158) | testEvent() {
    method testEvent2 (line 165) | testEvent2() {
    method connected (line 199) | async connected(@ConnectedSocket() socket: Socket) {
    method testEvent (line 206) | async testEvent() {
    method testEvent2 (line 213) | async testEvent2(@ConnectedSocket() socket: Socket) {

FILE: test/functional/socket-id.spec.ts
  class TestController (line 51) | @SocketController('/string')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket, @SocketId() socketId: str...

FILE: test/functional/socket-io.spec.ts
  class TestController (line 51) | @SocketController('/string')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket, @SocketIO() socketIO: Ser...

FILE: test/functional/socket-message-ack.spec.ts
  class TestController (line 53) | @SocketController('/string')
    method connected (line 57) | connected(@ConnectedSocket() socket: Socket, @SocketId() socketId: str...
    method test (line 63) | test(@MessageBody() data: any, @ConnectedSocket() socket: Socket, @Mes...
    method test2 (line 70) | test2(
    method test3 (line 82) | test3(

FILE: test/functional/socket-message-body.spec.ts
  class TestController (line 51) | @SocketController('/string')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket, @SocketId() socketId: str...
    method test (line 61) | test(@MessageBody() data: any, @ConnectedSocket() socket: Socket) {
    method test2 (line 67) | test2(

FILE: test/functional/socket-query-param.spec.ts
  class TestController (line 47) | @SocketController()
    method connected (line 51) | connected(@ConnectedSocket() socket: Socket, @SocketQueryParam('testPa...

FILE: test/functional/socket-request.spec.ts
  class TestController (line 54) | @SocketController()
    method connected (line 58) | connected(@ConnectedSocket() socket: Socket, @SocketRequest() request:...

FILE: test/functional/socket-rooms.spec.ts
  class TestController (line 51) | @SocketController('/string')
    method connected (line 55) | connected(@ConnectedSocket() socket: Socket, @SocketRooms() rooms: any) {

FILE: test/functional/use-interceptor.spec.ts
  class testInterceptor (line 54) | @Service()
    method use (line 56) | use(ctx: SocketEventContext, next: any) {
    method use (line 116) | use(ctx: SocketEventContext, next: any) {
    method use (line 172) | use(ctx: SocketEventContext, next: any) {
  class TestController (line 73) | @SocketController('/string')
    method connected (line 78) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 85) | test() {
    method connected (line 142) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 149) | test() {
    method connected (line 192) | connected(@ConnectedSocket() socket: Socket, @MessageBody() message: a...
    method test (line 200) | test(@MessageBody() message: any) {
  class testInterceptor (line 114) | @Service()
    method use (line 56) | use(ctx: SocketEventContext, next: any) {
    method use (line 116) | use(ctx: SocketEventContext, next: any) {
    method use (line 172) | use(ctx: SocketEventContext, next: any) {
  class TestController (line 137) | @SocketController('/string')
    method connected (line 78) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 85) | test() {
    method connected (line 142) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 149) | test() {
    method connected (line 192) | connected(@ConnectedSocket() socket: Socket, @MessageBody() message: a...
    method test (line 200) | test(@MessageBody() message: any) {
  class testInterceptor (line 170) | @Service()
    method use (line 56) | use(ctx: SocketEventContext, next: any) {
    method use (line 116) | use(ctx: SocketEventContext, next: any) {
    method use (line 172) | use(ctx: SocketEventContext, next: any) {
  class TestController (line 187) | @SocketController('/string')
    method connected (line 78) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 85) | test() {
    method connected (line 142) | connected(@ConnectedSocket() socket: Socket) {
    method test (line 149) | test() {
    method connected (line 192) | connected(@ConnectedSocket() socket: Socket, @MessageBody() message: a...
    method test (line 200) | test(@MessageBody() message: any) {

FILE: test/utilities/testSocketConnection.ts
  function testConnection (line 3) | async function testConnection(path: string) {
Condensed preview — 119 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (176K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 376,
    "preview": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: \"10:00\"\n    time"
  },
  {
    "path": ".github/semantic.yml",
    "chars": 199,
    "preview": "titleAndCommits: true\nallowMergeCommits: false\nscopes:\n  - deps\n  - deps-dev\ntypes:\n  - feat\n  - fix\n  - docs\n  - style\n"
  },
  {
    "path": ".github/workflows/auto-approve-dependabot-workflow.yml",
    "chars": 735,
    "preview": "name: Dependabot auto-merge\non:\n  pull_request_target\njobs:\n  dependabot:\n    runs-on: ubuntu-latest\n    if: github.acto"
  },
  {
    "path": ".github/workflows/continuous-deployment-workflow.yml",
    "chars": 729,
    "preview": "name: CD\non:\n  release:\n    types: [created]\njobs:\n  publish:\n    name: Publish to NPM\n    runs-on: ubuntu-latest\n    st"
  },
  {
    "path": ".github/workflows/continuous-integration-workflow.yml",
    "chars": 1259,
    "preview": "name: CI\non: [push, pull_request]\njobs:\n  checks:\n    name: Linters\n    runs-on: ubuntu-latest\n    steps:\n      - uses: "
  },
  {
    "path": ".github/workflows/lock-closed-issues-workflow.yml",
    "chars": 760,
    "preview": "name: 'Lock inactive threads'\non:\n  schedule:\n    - cron: '0 0 * * *'\njobs:\n  lock:\n    name: Lock closed issues\n    run"
  },
  {
    "path": ".gitignore",
    "chars": 593,
    "preview": "# Log files\nlogs\n*.log\n*.tmp\n*.tmp.*\nlog.txt\nnpm-debug.log*\n\n# Testing output\nlib-cov/**\ncoverage/**\n\n# Environment file"
  },
  {
    "path": ".prettierrc.yml",
    "chars": 130,
    "preview": "printWidth: 120\ntabWidth: 2\nuseTabs: false\nsemi: true\nsingleQuote: true\ntrailingComma: es5\nbracketSpacing: true\narrowPar"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 5794,
    "preview": "# Changelog\n\n_This changelog follows the [keep a changelog][keep-a-changelog]_ format to maintain a human readable chang"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2018-2020 TypeStack\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 23788,
    "preview": "# socket-controllers\n\n![Build Status](https://github.com/typestack/socket-controllers/workflows/CI/badge.svg)\n[![codecov"
  },
  {
    "path": "codecov.yml",
    "chars": 223,
    "preview": "coverage:\n  range: 70..100\n  round: down\n  precision: 2\n  status:\n    project:\n      default:\n        threshold: 0%\n    "
  },
  {
    "path": "eslint.config.mjs",
    "chars": 1844,
    "preview": "import typescriptEslint from '@typescript-eslint/eslint-plugin';\nimport tsParser from '@typescript-eslint/parser';\nimpor"
  },
  {
    "path": "jest.config.js",
    "chars": 320,
    "preview": "module.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'node',\n  collectCoverageFrom: ['src/**/*.ts', '!src/**/index"
  },
  {
    "path": "jest.setup.js",
    "chars": 53,
    "preview": "jest.setTimeout(30000);\n\nrequire(\"reflect-metadata\");"
  },
  {
    "path": "package.json",
    "chars": 1787,
    "preview": "{\n  \"name\": \"socket-controllers\",\n  \"version\": \"0.3.1\",\n  \"description\": \"Use class-based controllers to handle websocke"
  },
  {
    "path": "sample/sample1-simple-controller/Message.ts",
    "chars": 55,
    "preview": "export class Message {\n  id: number;\n  text: string;\n}\n"
  },
  {
    "path": "sample/sample1-simple-controller/MessageController.ts",
    "chars": 708,
    "preview": "import { Message } from './Message';\nimport { ConnectedSocket, MessageBody, OnConnect, OnDisconnect, OnMessage, SocketCo"
  },
  {
    "path": "sample/sample1-simple-controller/app.ts",
    "chars": 435,
    "preview": "import 'reflect-metadata';\nimport { SocketControllers } from '../../src/index';\nimport { MessageController } from './Mes"
  },
  {
    "path": "sample/sample1-simple-controller/index.html",
    "chars": 510,
    "preview": "<html>\n<script src=\"../../node_modules/socket.io-client/dist/socket.io.js\"></script>\n<script>\n    var socket = io(\"http:"
  },
  {
    "path": "sample/sample2-use-created-socket-io/Message.ts",
    "chars": 55,
    "preview": "export class Message {\n  id: number;\n  text: string;\n}\n"
  },
  {
    "path": "sample/sample2-use-created-socket-io/MessageController.ts",
    "chars": 708,
    "preview": "import { Message } from './Message';\nimport { ConnectedSocket, MessageBody, OnConnect, OnDisconnect, OnMessage, SocketCo"
  },
  {
    "path": "sample/sample2-use-created-socket-io/app.ts",
    "chars": 702,
    "preview": "import 'reflect-metadata';\nimport { SocketControllers } from '../../src/index';\nimport { MessageController } from './Mes"
  },
  {
    "path": "sample/sample2-use-created-socket-io/index.html",
    "chars": 510,
    "preview": "<html>\n<script src=\"../../node_modules/socket.io-client/dist/socket.io.js\"></script>\n<script>\n    var socket = io(\"http:"
  },
  {
    "path": "sample/sample3-namespaces/Message.ts",
    "chars": 55,
    "preview": "export class Message {\n  id: number;\n  text: string;\n}\n"
  },
  {
    "path": "sample/sample3-namespaces/MessageController.ts",
    "chars": 719,
    "preview": "import { Message } from './Message';\nimport { ConnectedSocket, MessageBody, OnConnect, OnDisconnect, OnMessage, SocketCo"
  },
  {
    "path": "sample/sample3-namespaces/app.ts",
    "chars": 435,
    "preview": "import 'reflect-metadata';\nimport { SocketControllers } from '../../src/index';\nimport { MessageController } from './Mes"
  },
  {
    "path": "sample/sample3-namespaces/index.html",
    "chars": 525,
    "preview": "<html>\n<script src=\"../../node_modules/socket.io-client/dist/socket.io.js\"></script>\n<script>\n    var socket = io(\"http:"
  },
  {
    "path": "sample/sample4-emitters/Message.ts",
    "chars": 55,
    "preview": "export class Message {\n  id: number;\n  text: string;\n}\n"
  },
  {
    "path": "sample/sample4-emitters/MessageController.ts",
    "chars": 1164,
    "preview": "import { Message } from './Message';\nimport {\n  ConnectedSocket,\n  EmitOnFail,\n  EmitOnSuccess,\n  MessageBody,\n  OnConne"
  },
  {
    "path": "sample/sample4-emitters/app.ts",
    "chars": 435,
    "preview": "import 'reflect-metadata';\nimport { SocketControllers } from '../../src/index';\nimport { MessageController } from './Mes"
  },
  {
    "path": "sample/sample4-emitters/index.html",
    "chars": 824,
    "preview": "<html>\n<script src=\"../../node_modules/socket.io-client/dist/socket.io.js\"></script>\n<script>\n    var socket = io(\"http:"
  },
  {
    "path": "sample/sample5-middlewares/AuthenticationMiddleware.ts",
    "chars": 255,
    "preview": "import { Middleware, MiddlewareInterface } from '../../src';\n\n@Middleware()\nexport class AuthenticationMiddleware implem"
  },
  {
    "path": "sample/sample5-middlewares/Message.ts",
    "chars": 55,
    "preview": "export class Message {\n  id: number;\n  text: string;\n}\n"
  },
  {
    "path": "sample/sample5-middlewares/MessageController.ts",
    "chars": 708,
    "preview": "import { Message } from './Message';\nimport { ConnectedSocket, MessageBody, OnConnect, OnDisconnect, OnMessage, SocketCo"
  },
  {
    "path": "sample/sample5-middlewares/app.ts",
    "chars": 565,
    "preview": "import 'reflect-metadata';\nimport { SocketControllers } from '../../src/index';\nimport { AuthenticationMiddleware } from"
  },
  {
    "path": "sample/sample5-middlewares/index.html",
    "chars": 510,
    "preview": "<html>\n<script src=\"../../node_modules/socket.io-client/dist/socket.io.js\"></script>\n<script>\n    var socket = io(\"http:"
  },
  {
    "path": "sample/sample6-dynamic-namespaces/Message.ts",
    "chars": 55,
    "preview": "export class Message {\n  id: number;\n  text: string;\n}\n"
  },
  {
    "path": "sample/sample6-dynamic-namespaces/MessageController.ts",
    "chars": 829,
    "preview": "import { Message } from './Message';\nimport {\n  ConnectedSocket,\n  MessageBody,\n  NspParams,\n  OnConnect,\n  OnDisconnect"
  },
  {
    "path": "sample/sample6-dynamic-namespaces/app.ts",
    "chars": 435,
    "preview": "import 'reflect-metadata';\nimport { SocketControllers } from '../../src/index';\nimport { MessageController } from './Mes"
  },
  {
    "path": "sample/sample6-dynamic-namespaces/index.html",
    "chars": 529,
    "preview": "<html>\n<script src=\"../../node_modules/socket.io-client/dist/socket.io.js\"></script>\n<script>\n    var socket = io(\"http:"
  },
  {
    "path": "src/SocketControllers.ts",
    "chars": 15055,
    "preview": "import { Namespace, Server, Socket } from 'socket.io';\nimport { sync } from 'glob';\nimport { normalize } from 'path';\nim"
  },
  {
    "path": "src/decorators/ConnectedSocket.ts",
    "chars": 578,
    "preview": "import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';\nimport { ParameterType } from '"
  },
  {
    "path": "src/decorators/EmitOnFail.ts",
    "chars": 573,
    "preview": "import { addResultToActionMetadata } from '../util/add-result-to-action-metadata';\nimport { ResultType } from '../types/"
  },
  {
    "path": "src/decorators/EmitOnSuccess.ts",
    "chars": 554,
    "preview": "import { addResultToActionMetadata } from '../util/add-result-to-action-metadata';\nimport { ResultType } from '../types/"
  },
  {
    "path": "src/decorators/MessageAck.ts",
    "chars": 534,
    "preview": "import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';\nimport { ParameterType } from '"
  },
  {
    "path": "src/decorators/MessageBody.ts",
    "chars": 690,
    "preview": "import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';\nimport { ParameterType } from '"
  },
  {
    "path": "src/decorators/Middleware.ts",
    "chars": 461,
    "preview": "import { HandlerType } from '../types/enums/HandlerType';\nimport { addMiddlewareMetadata } from '../util/add-middleware-"
  },
  {
    "path": "src/decorators/NspParam.ts",
    "chars": 573,
    "preview": "import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';\nimport { ParameterType } from '"
  },
  {
    "path": "src/decorators/NspParams.ts",
    "chars": 557,
    "preview": "import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';\nimport { ParameterType } from '"
  },
  {
    "path": "src/decorators/OnConnect.ts",
    "chars": 400,
    "preview": "import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';\nimport { SocketEventType } fr"
  },
  {
    "path": "src/decorators/OnDisconnect.ts",
    "chars": 406,
    "preview": "import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';\nimport { SocketEventType } fr"
  },
  {
    "path": "src/decorators/OnDisconnecting.ts",
    "chars": 412,
    "preview": "import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';\nimport { SocketEventType } fr"
  },
  {
    "path": "src/decorators/OnMessage.ts",
    "chars": 419,
    "preview": "import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';\nimport { SocketEventType } fr"
  },
  {
    "path": "src/decorators/SkipEmitOnEmptyResult.ts",
    "chars": 397,
    "preview": "import { addResultToActionMetadata } from '../util/add-result-to-action-metadata';\nimport { ResultType } from '../types/"
  },
  {
    "path": "src/decorators/SocketController.ts",
    "chars": 322,
    "preview": "import { HandlerType } from '../types/enums/HandlerType';\nimport { addControllerMetadata } from '../util/add-controller-"
  },
  {
    "path": "src/decorators/SocketIO.ts",
    "chars": 549,
    "preview": "import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';\nimport { ParameterType } from '"
  },
  {
    "path": "src/decorators/SocketId.ts",
    "chars": 549,
    "preview": "import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';\nimport { ParameterType } from '"
  },
  {
    "path": "src/decorators/SocketQueryParam.ts",
    "chars": 585,
    "preview": "import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';\nimport { ParameterType } from '"
  },
  {
    "path": "src/decorators/SocketRequest.ts",
    "chars": 559,
    "preview": "import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';\nimport { ParameterType } from '"
  },
  {
    "path": "src/decorators/SocketRooms.ts",
    "chars": 555,
    "preview": "import { addParameterToActionMetadata } from '../util/add-parameter-to-action-metadata';\nimport { ParameterType } from '"
  },
  {
    "path": "src/decorators/UseInterceptor.ts",
    "chars": 874,
    "preview": "import { addInterceptorToActionMetadata } from '../util/add-interceptor-to-action-metadata';\nimport { getMetadata } from"
  },
  {
    "path": "src/index.ts",
    "chars": 1126,
    "preview": "export * from './decorators/ConnectedSocket';\nexport * from './decorators/EmitOnFail';\nexport * from './decorators/EmitO"
  },
  {
    "path": "src/types/ActionMetadata.ts",
    "chars": 359,
    "preview": "import { ParameterMetadata } from './ParameterMetadata';\nimport { ResultMetadata } from './ResultMetadata';\nimport { Soc"
  },
  {
    "path": "src/types/ActionTransformOptions.ts",
    "chars": 171,
    "preview": "import { ClassTransformOptions } from 'class-transformer';\n\nexport interface ActionTransformOptions {\n  transform?: bool"
  },
  {
    "path": "src/types/ControllerMetadata.ts",
    "chars": 266,
    "preview": "import { HandlerType } from './enums/HandlerType';\nimport { ActionMetadata } from './ActionMetadata';\n\nexport interface "
  },
  {
    "path": "src/types/HandlerMetadata.ts",
    "chars": 75,
    "preview": "export interface HandlerMetadata<T> {\n  metadata: T;\n  target: Function;\n}\n"
  },
  {
    "path": "src/types/InterceptorInterface.ts",
    "chars": 160,
    "preview": "import { SocketEventContext } from './SocketEventContext';\n\nexport interface InterceptorInterface {\n  use(context: Socke"
  },
  {
    "path": "src/types/MiddlewareInterface.ts",
    "chars": 132,
    "preview": "import { Socket } from 'socket.io';\n\nexport interface MiddlewareInterface {\n  use(socket: Socket, next: (err?: any) => a"
  },
  {
    "path": "src/types/MiddlewareMetadata.ts",
    "chars": 201,
    "preview": "import { HandlerType } from './enums/HandlerType';\n\nexport interface MiddlewareMetadata {\n  namespace?: string | RegExp "
  },
  {
    "path": "src/types/ParameterMetadata.ts",
    "chars": 285,
    "preview": "import { ParameterType } from './enums/ParameterType';\nimport { ActionTransformOptions } from './ActionTransformOptions'"
  },
  {
    "path": "src/types/ResultMetadata.ts",
    "chars": 234,
    "preview": "import { ResultType } from './enums/ResultType';\nimport { ActionTransformOptions } from './ActionTransformOptions';\n\nexp"
  },
  {
    "path": "src/types/SocketControllerMetaKey.ts",
    "chars": 77,
    "preview": "export const SOCKET_CONTROLLER_META_KEY = Symbol('SocketControllerMetaKey');\n"
  },
  {
    "path": "src/types/SocketControllersOptions.ts",
    "chars": 687,
    "preview": "import { Server } from 'socket.io';\nimport { TransformOptions } from './TransformOptions';\nimport { SocketEventContext }"
  },
  {
    "path": "src/types/SocketEventContext.ts",
    "chars": 320,
    "preview": "import { SocketEventType } from './enums/SocketEventType';\nimport { Server, Socket } from 'socket.io';\n\nexport interface"
  },
  {
    "path": "src/types/TransformOptions.ts",
    "chars": 224,
    "preview": "import { ClassTransformOptions } from 'class-transformer';\n\nexport interface TransformOptions {\n  transform?: boolean;\n "
  },
  {
    "path": "src/types/constants/defaultTransformOptions.ts",
    "chars": 138,
    "preview": "import { TransformOptions } from '../TransformOptions';\n\nexport const defaultTransformOptions: TransformOptions = {\n  tr"
  },
  {
    "path": "src/types/enums/HandlerType.ts",
    "chars": 56,
    "preview": "export enum HandlerType {\n  CONTROLLER,\n  MIDDLEWARE,\n}\n"
  },
  {
    "path": "src/types/enums/ParameterType.ts",
    "chars": 212,
    "preview": "export enum ParameterType {\n  CUSTOM,\n  CONNECTED_SOCKET,\n  MESSAGE_BODY,\n  MESSAGE_ACK,\n  SOCKET_QUERY_PARAM,\n  SOCKET_"
  },
  {
    "path": "src/types/enums/ResultType.ts",
    "chars": 91,
    "preview": "export enum ResultType {\n  EMIT_ON_SUCCESS,\n  EMIT_ON_FAIL,\n  SKIP_EMIT_ON_EMPTY_RESULT,\n}\n"
  },
  {
    "path": "src/types/enums/SocketEventType.ts",
    "chars": 85,
    "preview": "export enum SocketEventType {\n  MESSAGE,\n  CONNECT,\n  DISCONNECT,\n  DISCONNECTING,\n}\n"
  },
  {
    "path": "src/util/add-action-to-controller-metadata.ts",
    "chars": 809,
    "preview": "import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';\nimport { ActionMetadata } from '../types/"
  },
  {
    "path": "src/util/add-controller-metadata.ts",
    "chars": 529,
    "preview": "import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';\nimport { ControllerMetadata } from '../ty"
  },
  {
    "path": "src/util/add-interceptor-to-action-metadata.ts",
    "chars": 762,
    "preview": "import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';\nimport { getMetadata } from './get-metada"
  },
  {
    "path": "src/util/add-middleware-metadata.ts",
    "chars": 321,
    "preview": "import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';\nimport { MiddlewareMetadata } from '../ty"
  },
  {
    "path": "src/util/add-parameter-to-action-metadata.ts",
    "chars": 815,
    "preview": "import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';\nimport { ParameterMetadata } from '../typ"
  },
  {
    "path": "src/util/add-result-to-action-metadata.ts",
    "chars": 797,
    "preview": "import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';\nimport { ResultMetadata } from '../types/"
  },
  {
    "path": "src/util/chain-execute.ts",
    "chars": 273,
    "preview": "export function chainExecute(context: any, chain: Function[]) {\n  function next() {\n    const middleware: Function = cha"
  },
  {
    "path": "src/util/get-metadata.ts",
    "chars": 226,
    "preview": "import { SOCKET_CONTROLLER_META_KEY } from '../types/SocketControllerMetaKey';\n\nexport const getMetadata = <T extends Ob"
  },
  {
    "path": "test/functional/connected-socket.spec.ts",
    "chars": 1916,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/controllers/test.controller.ts",
    "chars": 270,
    "preview": "import { ConnectedSocket, OnConnect, SocketController } from '../../../src';\nimport { Service } from 'typedi';\n\n@SocketC"
  },
  {
    "path": "test/functional/controllers/test2.controller.ts",
    "chars": 276,
    "preview": "import { ConnectedSocket, OnMessage, SocketController } from '../../../src';\nimport { Service } from 'typedi';\n\n@SocketC"
  },
  {
    "path": "test/functional/create-socket-server.spec.ts",
    "chars": 1374,
    "preview": "import { SocketController, SocketControllers } from '../../src';\nimport { testConnection } from '../utilities/testSocket"
  },
  {
    "path": "test/functional/emit-on-fail.spec.ts",
    "chars": 5498,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/emit-on-success.spec.ts",
    "chars": 3635,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/load-controllers-from-directory.spec.ts",
    "chars": 1582,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/middlewares.spec.ts",
    "chars": 10199,
    "preview": "import { Server } from 'socket.io';\nimport { Socket, io } from 'socket.io-client';\nimport { Container, Service } from 't"
  },
  {
    "path": "test/functional/multiple-controllers-on-same-namespace.spec.ts",
    "chars": 2043,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/nsp-param.spec.ts",
    "chars": 1994,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/nsp-params.spec.ts",
    "chars": 2048,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/on-disconnect.spec.ts",
    "chars": 2122,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/on-disconnecting.spec.ts",
    "chars": 2318,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/parameter-transformation.spec.ts",
    "chars": 2707,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/scoped-controllers.spec.ts",
    "chars": 7485,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/skip-emit-on-empty-result.spec.ts",
    "chars": 6325,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/socket-id.spec.ts",
    "chars": 1979,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/socket-io.spec.ts",
    "chars": 1987,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/socket-message-ack.spec.ts",
    "chars": 3655,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/socket-message-body.spec.ts",
    "chars": 2752,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/socket-query-param.spec.ts",
    "chars": 1813,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/socket-request.spec.ts",
    "chars": 1846,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/socket-rooms.spec.ts",
    "chars": 2008,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/functional/use-interceptor.spec.ts",
    "chars": 6396,
    "preview": "import { createServer, Server as HttpServer } from 'http';\nimport { Server } from 'socket.io';\nimport { io, Socket } fro"
  },
  {
    "path": "test/utilities/testSocketConnection.ts",
    "chars": 391,
    "preview": "import socketio from 'socket.io-client';\n\nexport async function testConnection(path: string) {\n  return await new Promis"
  },
  {
    "path": "test/utilities/waitForEvent.ts",
    "chars": 275,
    "preview": "import { Socket } from 'socket.io-client';\nimport { Server } from 'socket.io';\n\nexport const waitForEvent = (socket: Soc"
  },
  {
    "path": "test/utilities/waitForTime.ts",
    "chars": 166,
    "preview": "export const waitForTime = (time: number): Promise<unknown> => {\n  return new Promise(resolve => {\n    setTimeout(() => "
  },
  {
    "path": "tsconfig.json",
    "chars": 422,
    "preview": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2018\",\n    \"lib\": [\"es2018\"],\n    \"outDir\": \"build\","
  },
  {
    "path": "tsconfig.prod.json",
    "chars": 162,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"strict\": false,\n    \"sourceMap\": true,\n    \"removeComments"
  },
  {
    "path": "tsconfig.spec.json",
    "chars": 357,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"strict\": false,\n    \"strictPropertyInitialization\": false,"
  }
]

About this extraction

This page contains the full source code of the typestack/socket-controllers GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 119 files (158.7 KB), approximately 40.1k tokens, and a symbol index with 246 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!