npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

socketwise

v1.0.1

Published

🔌 A powerful framework for building real-time applications on top of Socket.IO using class-based approach with decorators.

Downloads

107

Readme

Table of Contents

Installation

  1. Install socketwise:

    Using npm:

    npm install socketwise

    Using yarn:

    yarn add socketwise

    Using pnpm:

    pnpm add socketwise
  2. Install typings for socket.io:

    npm install --save-dev @types/socket.io
  3. Set this options in tsconfig.json file of your project:

    {
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true
    }

Example usage

  1. 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')
  }
}
  1. Create a file called index.ts
import { Socketwise } from 'socketwise'
import { MessagePortal } from './message.portal'

new Socketwise({
  port: 3000,
  portals: [MessagePortal]
})
  1. Now you can send save message from your client application using socket.io-client package.

More examples

Loading all portals by suffix

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'
})

Loading all middlewares by suffix

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'
})

Handling connection event

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.')
  }
}

Handling disconnecting event

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...')
  }
}

Handling disconnected event

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.')
  }
}

@ConnectedSocket() decorator

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')
  }
}

@Message() decorator

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)
  }
}

@MessageAck() decorator

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.

@SocketId() decorator

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)
  }
}

@SocketIO() decorator

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 {}
}

@SocketQueryParam() decorator

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)
  }
}

@SocketRequest() decorator

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)
  }
}

@SocketRooms() decorator

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)
  }
}

@EmitOnSuccess() decorator

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'
    }
  }
}

@EmitOnFail() decorator

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.

Using socket.io namespaces

Using namespaces

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 {}
}

@NamespaceParams() decorator

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' }
  }
}

@NamespaceParam() decorator

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)
  }
}

Using middlewares

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()
  }
}

Creating instance of class from message

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())
  }
}

Using dependency injection

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...
}

Contributing

  1. Fork this repository.
  2. Create a new branch with feature name.
  3. Create your feature.
  4. Commit and set commit message with feature name.
  5. Push your code to your fork repository.
  6. Create pull request.

License

Socketwise is MIT licensed.