From 696ed67f884a8baa123d0d7944e972975e643912 Mon Sep 17 00:00:00 2001 From: Alexander Rondon Date: Tue, 28 Jul 2020 16:14:33 -0500 Subject: [PATCH] feat: WebSocketMetadata has attribute name feat: bind of io instance and namespace to inject in other controllers. --- public/chats.html | 123 +++++++++++++----- src/application.ts | 4 +- src/controllers/chat.controller.ws.ts | 4 +- src/controllers/todo.controller.ts | 56 ++++---- .../decorators/websocket.decorator.ts | 9 ++ src/websockets/websocket.server.ts | 23 ++-- 6 files changed, 146 insertions(+), 73 deletions(-) diff --git a/public/chats.html b/public/chats.html index 3cce29d..02770de 100644 --- a/public/chats.html +++ b/public/chats.html @@ -1,39 +1,90 @@ - - Socket.IO chat - - - - -
- -
- - - - + + Socket.IO chat + + + + +
+ + +
+ + + + + diff --git a/src/application.ts b/src/application.ts index 1073195..c0e9ce3 100644 --- a/src/application.ts +++ b/src/application.ts @@ -31,6 +31,8 @@ export class TodoListApplication extends BootMixin( this.component(RestExplorerComponent); + this.booters(WebsocketControllerBooter); + this.projectRoot = __dirname; // Customize @loopback/boot Booter Conventions here this.bootOptions = { @@ -47,8 +49,6 @@ export class TodoListApplication extends BootMixin( }, }; - this.booters(WebsocketControllerBooter); - this.setupLogging(); } diff --git a/src/controllers/chat.controller.ws.ts b/src/controllers/chat.controller.ws.ts index 86e106b..3c3b7fd 100644 --- a/src/controllers/chat.controller.ws.ts +++ b/src/controllers/chat.controller.ws.ts @@ -4,7 +4,7 @@ import {ws} from '../websockets/decorators/websocket.decorator'; /** * A demo controller for websocket */ -@ws(/^\/chats\/\d+$/) +@ws({ name: 'chatNsp', namespace: /^\/chats\/\d+$/ }) export class ChatControllerWs { constructor( @ws.socket() // Equivalent to `@inject('ws.socket')` @@ -19,6 +19,8 @@ export class ChatControllerWs { connect(socket: Socket) { console.log('Client connected: %s', this.socket.id); socket.join('room 1'); + // Room notification of request /todos/room/example/emit (TodoController) + socket.join('some room'); } /** diff --git a/src/controllers/todo.controller.ts b/src/controllers/todo.controller.ts index 29ead67..a1f50fd 100644 --- a/src/controllers/todo.controller.ts +++ b/src/controllers/todo.controller.ts @@ -3,34 +3,27 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {inject} from '@loopback/core'; -import {Filter, repository} from '@loopback/repository'; -import { - del, - get, - getModelSchemaRef, - HttpErrors, - param, - patch, - post, - put, - requestBody, -} from '@loopback/rest'; -import {Todo} from '../models'; -import {TodoRepository} from '../repositories'; -import {Geocoder} from '../services'; +import { inject } from '@loopback/core'; +import { Filter, repository } from '@loopback/repository'; +import { Server } from 'socket.io'; +import { del, get, getModelSchemaRef, HttpErrors, param, patch, post, put, requestBody, } from '@loopback/rest'; +import { Todo } from '../models'; +import { TodoRepository } from '../repositories'; +import { Geocoder } from '../services'; +import { ws } from "../websockets/decorators/websocket.decorator"; export class TodoController { constructor( @repository(TodoRepository) protected todoRepository: TodoRepository, @inject('services.Geocoder') protected geoService: Geocoder, - ) {} + ) { + } @post('/todos', { responses: { '200': { description: 'Todo model instance', - content: {'application/json': {schema: getModelSchemaRef(Todo)}}, + content: { 'application/json': { schema: getModelSchemaRef(Todo) } }, }, }, }) @@ -38,11 +31,11 @@ export class TodoController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Todo, {title: 'NewTodo', exclude: ['id']}), + schema: getModelSchemaRef(Todo, { title: 'NewTodo', exclude: ['id'] }), }, }, }) - todo: Omit, + todo: Omit, ): Promise { if (todo.remindAtAddress) { const geo = await this.geoService.geocode(todo.remindAtAddress); @@ -65,7 +58,7 @@ export class TodoController { responses: { '200': { description: 'Todo model instance', - content: {'application/json': {schema: getModelSchemaRef(Todo)}}, + content: { 'application/json': { schema: getModelSchemaRef(Todo) } }, }, }, }) @@ -82,7 +75,7 @@ export class TodoController { description: 'Array of Todo model instances', content: { 'application/json': { - schema: {type: 'array', items: getModelSchemaRef(Todo)}, + schema: { type: 'array', items: getModelSchemaRef(Todo) }, }, }, }, @@ -90,7 +83,7 @@ export class TodoController { }) async findTodos( @param.filter(Todo) - filter?: Filter, + filter?: Filter, ): Promise { return this.todoRepository.find(filter); } @@ -121,11 +114,11 @@ export class TodoController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Todo, {partial: true}), + schema: getModelSchemaRef(Todo, { partial: true }), }, }, }) - todo: Partial, + todo: Partial, ): Promise { await this.todoRepository.updateById(id, todo); } @@ -137,7 +130,18 @@ export class TodoController { }, }, }) - async deleteTodo(@param.path.number('id') id: number): Promise { + async deleteTodo( + @param.path.number('id') id: number + ): Promise { await this.todoRepository.deleteById(id); } + + @post('/todos/room/example/emit') + async exampleRoomEmmit( + @ws.namespace('chatNsp') nsp: Server + ): Promise { + nsp.to('some room').emit('some room event', `time: ${new Date().getTime()}`); + console.log('exampleRoomEmmit'); + return 'room event emitted'; + } } diff --git a/src/websockets/decorators/websocket.decorator.ts b/src/websockets/decorators/websocket.decorator.ts index 9e4fd75..7e2a278 100644 --- a/src/websockets/decorators/websocket.decorator.ts +++ b/src/websockets/decorators/websocket.decorator.ts @@ -8,6 +8,7 @@ import { } from '@loopback/context'; export interface WebSocketMetadata { + name?: string, namespace?: string | RegExp; } @@ -42,6 +43,14 @@ export namespace ws { return inject('ws.socket'); } + export function server() { + return inject('ws.server'); + } + + export function namespace(name: string) { + return inject(`ws.namespace.${name}`); + } + /** * Decorate a method to subscribe to websocket events. * For example, diff --git a/src/websockets/websocket.server.ts b/src/websockets/websocket.server.ts index f49eadd..57d7a3e 100644 --- a/src/websockets/websocket.server.ts +++ b/src/websockets/websocket.server.ts @@ -2,7 +2,7 @@ import { Constructor, Context } from '@loopback/context'; import { HttpServer } from '@loopback/http-server'; import { Server, ServerOptions, Socket } from 'socket.io'; import { WebSocketControllerFactory } from './websocket-controller-factory'; -import { getWebSocketMetadata } from "./decorators/websocket.decorator"; +import { getWebSocketMetadata, WebSocketMetadata } from "./decorators/websocket.decorator"; import SocketIOServer = require("socket.io"); const debug = require('debug')('loopback:websocket'); @@ -20,12 +20,13 @@ export class WebSocketServer extends Context { private io: Server; constructor( - ctx: Context, + public ctx: Context, public readonly httpServer: HttpServer, private options: ServerOptions = {}, ) { super(ctx); this.io = SocketIOServer(options); + ctx.bind('ws.server').to(this.io); } /** @@ -39,14 +40,20 @@ export class WebSocketServer extends Context { /** * Register a websocket controller * @param ControllerClass - * @param namespace + * @param meta */ - route(ControllerClass: Constructor, namespace?: string | RegExp) { - if (namespace == null) { - const meta = getWebSocketMetadata(ControllerClass); - namespace = meta && meta.namespace; + route(ControllerClass: Constructor, meta?: WebSocketMetadata | string | RegExp) { + if(meta instanceof RegExp || typeof meta === 'string'){ + meta = { namespace: meta } as WebSocketMetadata; } - const nsp = namespace ? this.io.of(namespace) : this.io; + if (meta == null) { + meta = getWebSocketMetadata(ControllerClass) as WebSocketMetadata; + } + const nsp = (meta && meta.namespace) ? this.io.of(meta.namespace) : this.io; + if (meta && meta.name) { + this.ctx.bind(`ws.namespace.${meta.name}`).to(nsp); + } + /* eslint-disable @typescript-eslint/no-misused-promises */ nsp.on('connection', async socket => { console.log('connection', 'connection');