Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

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

## [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
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,35 @@ export class MessageController {

> 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 a parameter which contains the socket, socket.io instance, event type, event name, namespace parameters and the message arguments if they are applicable.

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

const myDiToken = new Token();

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

## Decorators Reference

| Signature | Description |
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "socket-controllers",
"version": "0.1.1",
"version": "0.1.2",
"description": "Use class-based controllers to handle websocket events.",
"license": "MIT",
"main": "index.js",
Expand Down Expand Up @@ -44,7 +44,7 @@
"@types/socket.io": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"eslint": "^8.32.0",
"eslint": "^8.33.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"express": "^4.18.2",
Expand Down
79 changes: 49 additions & 30 deletions src/SocketControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { HandlerType } from './types/enums/HandlerType';
import { SocketControllersOptions } from './types/SocketControllersOptions';
import { ControllerMetadata } from './types/ControllerMetadata';
import { MiddlewareMetadata } from './types/MiddlewareMetadata';
import { ActionType } from './types/enums/ActionType';
import { SocketEventType } from './types/enums/SocketEventType';
import { ActionMetadata } from './types/ActionMetadata';
import { ParameterMetadata } from './types/ParameterMetadata';
import { ParameterType } from './types/enums/ParameterType';
Expand All @@ -18,12 +18,13 @@ import { TransformOptions } from './types/TransformOptions';
import { defaultTransformOptions } from './types/constants/defaultTransformOptions';
import { ActionTransformOptions } from './types/ActionTransformOptions';
import { instanceToPlain, plainToInstance } from 'class-transformer';
import { ScopedContainerGetterParams } from './types/ScopedContainerGetterParams';
import { MiddlewareInterface } from './types/MiddlewareInterface';

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

Expand All @@ -34,23 +35,14 @@ export class SocketControllers {
...defaultTransformOptions,
...options.transformOption,
};
this.controllers = this.loadHandlers<Function, ControllerMetadata>(
options.controllers || [],
HandlerType.CONTROLLER
);
this.middlewares = this.loadHandlers<MiddlewareInterface, MiddlewareMetadata>(
options.middlewares || [],
HandlerType.MIDDLEWARE
);
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, U>(
handlers: Array<Function | string>,
type: HandlerType
): HandlerMetadata<T, U>[] {
private loadHandlers<T extends Object>(handlers: Array<Function | string>, type: HandlerType): HandlerMetadata<T>[] {
const loadedHandlers: Function[] = [];

for (const handler of handlers) {
Expand All @@ -64,7 +56,7 @@ export class SocketControllers {
return loadedHandlers.map(handler => {
return {
metadata: getMetadata(handler),
instance: this.container.get(handler),
target: handler,
};
});
}
Expand Down Expand Up @@ -101,7 +93,7 @@ export class SocketControllers {
const middlewaresWithNamespace = middlewares.filter(middleware => !!middleware.metadata.namespace);

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

this.io.on('new_namespace', (namespace: Namespace) => {
Expand All @@ -116,7 +108,7 @@ export class SocketControllers {
});

if (shouldApply) {
this.registerMiddleware(namespace, middleware.instance);
this.registerMiddleware(namespace, middleware);
}
}
});
Expand All @@ -132,7 +124,7 @@ export class SocketControllers {
}
});

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

for (const controller of controllersWithNamespace) {
Expand All @@ -156,18 +148,18 @@ export class SocketControllers {
}
}

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

if (connectedAction) {
Expand Down Expand Up @@ -195,20 +187,29 @@ export class SocketControllers {
messages.push(ack);
}

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

private executeAction(
socket: Socket,
controller: HandlerMetadata<any, ControllerMetadata>,
controller: HandlerMetadata<ControllerMetadata>,
action: ActionMetadata,
eventName?: string,
data?: any[]
) {
const parameters = this.resolveParameters(socket, controller.metadata, action.parameters || [], data);
try {
const actionResult = controller.instance[action.methodName](...parameters);
let container = this.container;
if (this.options.scopedContainerGetter) {
container = this.options.scopedContainerGetter(
this.collectScopedContainerParams(socket, action.type, eventName, data, controller.metadata.namespace)
);
}

const controllerInstance: any = container.get(controller.target);
const actionResult = controllerInstance[action.methodName](...parameters);
Promise.resolve(actionResult)
.then(result => {
this.handleActionResult(socket, action, result, ResultType.EMIT_ON_SUCCESS);
Expand Down Expand Up @@ -251,9 +252,10 @@ export class SocketControllers {
}
}

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

Expand Down Expand Up @@ -334,10 +336,27 @@ export class SocketControllers {
return value;
}

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

private extractNamespaceParameters(
socket: Socket,
namespace: string | RegExp | undefined,
parameterMetadata: ParameterMetadata
parameterMetadata?: ParameterMetadata
) {
const keys: any[] = [];
const regexp = namespace instanceof RegExp ? namespace : pathToRegexp(namespace || '/', keys);
Expand Down
4 changes: 2 additions & 2 deletions src/decorators/OnConnect.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';
import { ActionType } from '../types/enums/ActionType';
import { SocketEventType } from '../types/enums/SocketEventType';

export function OnConnect(): Function {
return function (object: Object, methodName: string) {
addActionToControllerMetadata(object.constructor, {
methodName,
type: ActionType.CONNECT,
type: SocketEventType.CONNECT,
options: {},
});
};
Expand Down
4 changes: 2 additions & 2 deletions src/decorators/OnDisconnect.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';
import { ActionType } from '../types/enums/ActionType';
import { SocketEventType } from '../types/enums/SocketEventType';

export function OnDisconnect(): Function {
return function (object: Object, methodName: string) {
addActionToControllerMetadata(object.constructor, {
methodName,
type: ActionType.DISCONNECT,
type: SocketEventType.DISCONNECT,
options: {},
});
};
Expand Down
4 changes: 2 additions & 2 deletions src/decorators/OnDisconnecting.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';
import { ActionType } from '../types/enums/ActionType';
import { SocketEventType } from '../types/enums/SocketEventType';

export function OnDisconnecting(): Function {
return function (object: Object, methodName: string) {
addActionToControllerMetadata(object.constructor, {
methodName,
type: ActionType.DISCONNECTING,
type: SocketEventType.DISCONNECTING,
options: {},
});
};
Expand Down
4 changes: 2 additions & 2 deletions src/decorators/OnMessage.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';
import { ActionType } from '../types/enums/ActionType';
import { SocketEventType } from '../types/enums/SocketEventType';

export function OnMessage(name?: string): Function {
return function (object: Object, methodName: string) {
addActionToControllerMetadata(object.constructor, {
methodName,
type: ActionType.MESSAGE,
type: SocketEventType.MESSAGE,
options: { name },
});
};
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ export * from './decorators/SocketRooms';
export * from './types/MiddlewareInterface';
export * from './types/TransformOptions';
export * from './types/SocketControllersOptions';
export * from './types/enums/SocketEventType';

export * from './SocketControllers';
4 changes: 2 additions & 2 deletions src/types/ActionMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ParameterMetadata } from './ParameterMetadata';
import { ResultMetadata } from './ResultMetadata';
import { ActionType } from './enums/ActionType';
import { SocketEventType } from './enums/SocketEventType';

export interface ActionMetadata {
type: ActionType;
type: SocketEventType;
methodName: string;
options: any;
parameters: ParameterMetadata[];
Expand Down
6 changes: 3 additions & 3 deletions src/types/HandlerMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface HandlerMetadata<T, U> {
instance: T;
metadata: U;
export interface HandlerMetadata<T> {
metadata: T;
target: Function;
}
Loading