Skip to content

Commit

Permalink
feat: WebSocketMetadata has attribute name
Browse files Browse the repository at this point in the history
feat: bind of io instance and namespace to inject in other controllers.
  • Loading branch information
alexkander committed Jul 28, 2020
1 parent d853e5d commit 696ed67
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 73 deletions.
123 changes: 87 additions & 36 deletions public/chats.html
Original file line number Diff line number Diff line change
@@ -1,39 +1,90 @@
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
#messages { margin-bottom: 40px }
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
$(function () {
var socket = io('/chats/1');
$('form').submit(function(){
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
window.scrollTo(0, document.body.scrollHeight);
});
});
</script>
</body>
<head>
<title>Socket.IO chat</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font: 13px Helvetica, Arial;
}

form {
background: #000;
padding: 3px;
position: fixed;
bottom: 0;
width: 100%;
}

form input {
border: 0;
padding: 10px;
width: 90%;
margin-right: .5%;
}

form button {
width: 9%;
background: rgb(130, 224, 255);
border: none;
padding: 10px;
}

#messages {
list-style-type: none;
margin: 0;
padding: 0;
}

#messages li {
padding: 5px 10px;
}

#messages li:nth-child(odd) {
background: #eee;
}

#messages {
margin-bottom: 40px
}
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off"/>
<button>Send</button>
</form>
<button id="emitRoomEvent">Emit room event</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
$(function () {
var socket = io('/chats/1');
$('form').submit(function () {
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function (msg) {
$('#messages').append($('<li>').text(msg));
window.scrollTo(0, document.body.scrollHeight);
});
socket.on('some room event', function (msg) {
console.log('msg', msg)
$('#messages').append($('<li>').text(`some room event: ${msg}`));
window.scrollTo(0, document.body.scrollHeight);
});
$('#emitRoomEvent').on('click', function () {
$.post('/todos/room/example/emit')
.then(console.log);
});
});
</script>
</body>
</html>
4 changes: 2 additions & 2 deletions src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -47,8 +49,6 @@ export class TodoListApplication extends BootMixin(
},
};

this.booters(WebsocketControllerBooter);

this.setupLogging();
}

Expand Down
4 changes: 3 additions & 1 deletion src/controllers/chat.controller.ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')`
Expand All @@ -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');
}

/**
Expand Down
56 changes: 30 additions & 26 deletions src/controllers/todo.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,39 @@
// 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) } },
},
},
})
async createTodo(
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Todo, {title: 'NewTodo', exclude: ['id']}),
schema: getModelSchemaRef(Todo, { title: 'NewTodo', exclude: ['id'] }),
},
},
})
todo: Omit<Todo, 'id'>,
todo: Omit<Todo, 'id'>,
): Promise<Todo> {
if (todo.remindAtAddress) {
const geo = await this.geoService.geocode(todo.remindAtAddress);
Expand All @@ -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) } },
},
},
})
Expand All @@ -82,15 +75,15 @@ export class TodoController {
description: 'Array of Todo model instances',
content: {
'application/json': {
schema: {type: 'array', items: getModelSchemaRef(Todo)},
schema: { type: 'array', items: getModelSchemaRef(Todo) },
},
},
},
},
})
async findTodos(
@param.filter(Todo)
filter?: Filter<Todo>,
filter?: Filter<Todo>,
): Promise<Todo[]> {
return this.todoRepository.find(filter);
}
Expand Down Expand Up @@ -121,11 +114,11 @@ export class TodoController {
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Todo, {partial: true}),
schema: getModelSchemaRef(Todo, { partial: true }),
},
},
})
todo: Partial<Todo>,
todo: Partial<Todo>,
): Promise<void> {
await this.todoRepository.updateById(id, todo);
}
Expand All @@ -137,7 +130,18 @@ export class TodoController {
},
},
})
async deleteTodo(@param.path.number('id') id: number): Promise<void> {
async deleteTodo(
@param.path.number('id') id: number
): Promise<void> {
await this.todoRepository.deleteById(id);
}

@post('/todos/room/example/emit')
async exampleRoomEmmit(
@ws.namespace('chatNsp') nsp: Server
): Promise<any> {
nsp.to('some room').emit('some room event', `time: ${new Date().getTime()}`);
console.log('exampleRoomEmmit');
return 'room event emitted';
}
}
9 changes: 9 additions & 0 deletions src/websockets/decorators/websocket.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@loopback/context';

export interface WebSocketMetadata {
name?: string,
namespace?: string | RegExp;
}

Expand Down Expand Up @@ -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,
Expand Down
23 changes: 15 additions & 8 deletions src/websockets/websocket.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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);
}

/**
Expand All @@ -39,14 +40,20 @@ export class WebSocketServer extends Context {
/**
* Register a websocket controller
* @param ControllerClass
* @param namespace
* @param meta
*/
route(ControllerClass: Constructor<any>, namespace?: string | RegExp) {
if (namespace == null) {
const meta = getWebSocketMetadata(ControllerClass);
namespace = meta && meta.namespace;
route(ControllerClass: Constructor<any>, 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');
Expand Down

0 comments on commit 696ed67

Please sign in to comment.