π A powerful framework for building real-time applications on top of Socket.IO using class-based approach with decorators.
- Installation
- Example usage
- More examples
- Loading all portals by suffix
- Loading all middlewares by suffix
- Handling connection event
- Handling disconnecting event
- Handling disconnected event
- @ConnectedSocket() decorator
- @Message() decorator
- @MessageAck() decorator
- @SocketId() decorator
- @SocketIO() decorator
- @SocketQueryParam() decorator
- @SocketRequest() decorator
- @SocketRooms() decorator
- @EmitOnSuccess() decorator
- @EmitOnFail() decorator
- Using socket.io namespaces
- Using middlewares
- Creating instance of class from message
- Using dependency injection
- Contributing
- License
-
Install socketwise:
Using npm:
npm install socketwise
Using yarn:
yarn add socketwise
Using pnpm:
pnpm add socketwise
-
Install typings for socket.io:
npm install --save-dev @types/socket.io
-
Set this options in
tsconfig.json
file of your project:{ "emitDecoratorMetadata": true, "experimentalDecorators": true }
- Create a file called
message.portal.ts
import { Socket } from 'socket.io'
import {
ConnectedSocket,
Message,
OnConnect,
OnDisconnect,
OnDisconnecting,
Portal,
SubscribeMessage
} from 'socketwise'
@Portal()
export class MessagePortal {
@OnConnect()
public onConnect(): void {
console.log('Connected.')
}
@OnDisconnecting()
public onDisconnecting(): void {
console.log('Disconnecting...')
}
@OnDisconnect()
public onDisconnect(): void {
console.log('Disconnected.')
}
@SubscribeMessage('save')
public onSave(@ConnectedSocket() socket: Socket, @Message() message: string): void {
console.log('Received message:', message)
socket.emit('message_saved')
}
}
- Create a file called
index.ts
import { Socketwise } from 'socketwise'
import { MessagePortal } from './message.portal'
new Socketwise({
port: 3000,
portals: [MessagePortal]
})
- Now you can send
save
message from your client application using socket.io-client package.
By specifying the suffix of your portal files, you can load all portals from anywhere in your project.
import { Socketwise } from 'socketwise'
new Socketwise({
port: 3000,
portals: '*.portal.ts'
})
By specifying the suffix of your middleware files, you can load all middlewares from anywhere in your project.
import { Socketwise } from 'socketwise'
new Socketwise({
port: 3000,
portals: '*.portal.ts',
middlewares: '*.middleware.ts'
})
You can handle connection event with @OnConnect()
decorator. In this example onConnect
method will be called once new client connected.
import { Portal, OnConnect } from 'socketwise'
@Portal()
export class MessagePortal {
@OnConnect()
public onConnect(): void {
console.log('Connected.')
}
}
You can handle disconnecting event with @OnDisconnecting()
decorator. In this example onDisconnecting
method will be called when the client is disconnecting.
import { Portal, OnDisconnecting } from 'socketwise'
@Portal()
export class MessagePortal {
@OnDisconnecting()
public onDisconnecting(): void {
console.log('Disconnecting...')
}
}
You can handle disconnected event with @OnDisconnect()
decorator. In this example onDisconnect
method will be called when the client is disconnected.
import { Portal, OnDisconnect } from 'socketwise'
@Portal()
export class MessagePortal {
@OnDisconnect()
public onDisconnect(): void {
console.log('Disconnected.')
}
}
You can get connected socket instance with using @ConnectedSocket()
decorator.
import { Socket } from 'socket.io'
import { Portal, SubscribeMessage } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
public onSave(@ConnectedSocket() socket: Socket): void {
socket.emit('message_saved')
}
}
You can get received message body with using @Message()
decorator.
import { Portal, SubscribeMessage, Message } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
public onSave(@Message() message: unknown): void {
console.log('Received message:', message)
}
}
You can get received message ack with using @MessageAck()
decorator.
import { Portal, SubscribeMessage, Message, MessageAck } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
public onSave(@Message() message: unknown, @MessageAck() ack: Function): void {
console.log('Received message:', message)
ack('callback value')
}
}
Note: ack should be the last parameter in the emit function; otherwise, it will be null.
You can get connected client id with using @SocketId()
decorator.
import { Portal, SubscribeMessage, SocketId } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
public onSave(@SocketId() socketId: string): void {
console.log('Socket id:', socketId)
}
}
You can get connected socket.io instance with using @SocketIO()
decorator.
import { Server } from 'socket.io'
import { Portal, SubscribeMessage, SocketIO } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
public onSave(@SocketIO() io: Server): void {}
}
You can get received query parameter with using @SocketQueryParam()
decorator.
import { Portal, SubscribeMessage, SocketQueryParam } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
public onSave(@SocketQueryParam('token') token: string): void {
console.log('authorization token from query parameter:', token)
}
}
You can get request object of socket with using @SocketRequest()
decorator.
import { IncomingMessage } from 'http'
import { Portal, SubscribeMessage, SocketRequest } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
public onSave(@SocketRequest() request: IncomingMessage): void {
console.log('request object:', request)
}
}
You can get rooms object of socket with using @SocketRooms()
decorator.
import { Portal, SubscribeMessage, SocketRooms } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
public onSave(@SocketRooms() rooms: Set<Object>): void {
console.log('socket rooms:', rooms)
}
}
You can send message back to client after method execution with using @EmitOnSuccess()
decorator.
import { Portal, SubscribeMessage, EmitOnSuccess } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
@EmitOnSuccess('message_saved')
public onSave(): void {
// after executing this method, the 'message_saved' message will be sent back to the client
}
}
If you return something, it will be returned in the emitted message data:
import { Portal, SubscribeMessage, EmitOnSuccess } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
@EmitOnSuccess('message_saved')
public onSave(): Record<string, unknown> {
// after executing this method, the 'message_saved' message will be sent back to the client with message object
return {
id: 10,
content: 'message saved successfully'
}
}
}
You have the ability to manage which message will be sent if an error or exception occurs during execution with using @EmitOnFail()
decorator.
import { Portal, SubscribeMessage, EmitOnSuccess, EmitOnFail } from 'socketwise'
@Portal()
export class MessagePortal {
@SubscribeMessage('save')
@EmitOnSuccess('message_saved')
@EmitOnFail('custom_save_error', { message: 'custom error message' })
@EmitOnFail('save_error')
public onSave(): Record<string, unknown> {
if (true === true) {
throw new Error('Error! True is equal to true')
}
return {
id: 10,
content: 'message saved successfully'
}
}
}
After execution of this method save_error
message will be sent to the client with Error! True is equal to true
error message and custom_save_error
message also will be sent to the client with { message: 'custom error message' }
message object.
To exclusively listen to messages within a spesific namespace, you can mark a portal with namespace:
import { Portal, SubscribeMessage } from 'socketwise'
@Portal('/users')
export class UserPortal {
@SubscribeMessage('create')
public onCreate(): void {}
}
Also you can use dynamic namespace:
import { Portal, SubscribeMessage } from 'socketwise'
@Portal('/users/:userId')
export class UserPortal {
@SubscribeMessage('create')
public onCreate(): void {}
}
You can get dynamic namespace params with using @NamespaceParams()
decorator.
import { Portal, SubscribeMessage, NamespaceParams } from 'socketwise'
@Portal('/users/:userId')
export class UserPortal {
@SubscribeMessage('create')
public onCreate(@NamespaceParams() params: Record<string, string>): void {
console.log('Namespace params:', params)
// for instance, if the userId is 12, the params object will be this: { userId: '12' }
}
}
You can get spesific dynamic namespace param with using @NamespaceParam()
decorator.
import { Portal, SubscribeMessage, NamespaceParam } from 'socketwise'
@Portal('/users/:userId')
export class UserPortal {
@SubscribeMessage('create')
public onCreate(@NamespaceParam('userId') userId: string): void {
console.log('User id:', userId)
}
}
Middlewares refer to functions that are provided to the socketIo.use
method. They enable you to establish a set of instructions that will run every time a client connects to the server. You can create your own middlewares using the @Middleware()
decorator like below:
import { Socket } from 'socket.io'
import { Middleware, SocketIONextFunc, SocketwiseMiddleware } from 'socketwise'
@Middleware()
export class AuthorizationMiddleware implements SocketwiseMiddleware {
public use(socket: Socket, next: SocketIONextFunc): void {
console.log('check authorization...')
next()
}
}
You have the option to restrict middlewares to namespaces by specifying a string
, RegExp
or Array<string | RegExp>
for parameter of the @Middleware()
decorator.
import { Socket } from 'socket.io'
import { Middleware, SocketIONextFunc, SocketwiseMiddleware } from 'socketwise'
@Middleware('/users')
export class AuthorizationMiddleware implements SocketwiseMiddleware {
public use(socket: Socket, next: SocketIONextFunc): void {
console.log('check authorization...')
next()
}
}
If you provide a class type for a parameter decorated with @Message()
, socketwise will use the class-transformer to construct an instance of the designated class type using the data received within the message. To deactivate this functionality, you should indicate { useClassTransformer: false }
within the SocketwiseOptions when creating a server.
import { Portal, SubscribeMessage, Message } from 'socketwise'
class User {
private name!: string
private surname!: string
public getFullName(): string {
return this.name + ' ' + this.surname
}
}
@Portal()
export class UserPortal {
@SubscribeMessage('save')
public onSave(@Message() user: User): void {
console.log('Full name:', user.getFullName())
}
}
Socketwise uses MagnoDI as its built-in DI container and exposes it to users. You can use this container to inject your services into your portals and middlewares.
Let's take a look at how to use it by creating a service:
import { Injectable } from 'socketwise'
@Injectable()
export class LoggerService {
public log(message: string): void {
console.log(message)
}
}
Note: To register our service with the container, we need to mark it with the
@Injectable()
decorator.
Afterwards, let's inject this service into our portal:
@Portal()
export class MessagePortal {
constructor(private readonly loggerService: LoggerService) {}
// portal events...
}
- Fork this repository.
- Create a new branch with feature name.
- Create your feature.
- Commit and set commit message with feature name.
- Push your code to your fork repository.
- Create pull request.
Socketwise is MIT licensed.