Websocket detect client disconnect fails - javascript

I am using the following logic to verify whether client is connected to websocket or not: Client keeps sending pings at an interval to the server. Server has a timeout which triggers an event on completing. As soon as Client pings server, the server timeout resets. This works fine when one client is connected. But the logic breaks on multiple clients. How can I fix this? As soon as 2nd client connects, Server says client 1 has disconnected and doesn't print anything when either of the 2 disconnect.
This is my server logic:
const WebSocket = require("ws");
const express = require("express");
const app = express();
const server = require("http").createServer(app);
const url = require('url');
const PORT = process.env.PORT || 3000;
const wss = new WebSocket.Server({ server: server });
app.get("/", (req, res) => res.send("Temst."));
var tm;
function ping(client) {
tm = setTimeout(function () {
console.log(`[-] ${client} Disconnected`);
wss.emit("customClose", client);
}, 5000);
}
function pong(client) {
clearInterval(tm);
// console.log("[!] Cleared timeout");
ping(client);
}
wss.on("connection", function connection(ws, req) {
var queryData = url.parse(req.url,true).query;
ping(queryData.id);
console.log(`[+] ${req.socket.remoteAddress} Connected`);
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
message = {
type: "alert",
msg: `${queryData.id} has Connected.`,
};
client.send(JSON.stringify(message), { binary: false });
}
});
ws.on("message", function incoming(message) {
if (message == "__ping__") {
console.log(`[!] Ping Receieved from ${req.socket.remoteAddress}`);
pong(queryData.id);
} else {
`[!] Message Receieved from ${req.socket.remoteAddress}`;
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(msg, { binary: false });
}
});
}
});
});
wss.addListener("customClose", function (m) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
message = {
type: "alert",
msg: `${m} has Disconnected.`,
};
client.send(JSON.stringify(message), { binary: false });
}
});
});
server.listen(PORT, () => console.log("Listening on port 3000"));

I think I have solved the problem. After looking at the ws package docs, I tried server side pinging instead of client side. It is working for multiple users for now. Will update if any problems occur.
const WebSocket = require("ws");
const express = require("express");
const app = express();
const server = require("http").createServer(app);
const url = require("url");
const PORT = process.env.PORT || 3000;
const wss = new WebSocket.Server({ server: server });
app.get("/", (req, res) => res.send("Temst."));
var myClients = [];
wss.on("connection", function connection(ws, req) {
var queryData = url.parse(req.url, true).query;
myClients.push({
id: queryData.id,
wsoc: ws,
isAlive: true,
});
console.log(`[+] ${req.socket.remoteAddress} Connected`);
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
message = {
type: "alert",
msg: `${queryData.id} has Connected.`,
};
client.send(JSON.stringify(message), { binary: false });
}
});
ws.on("pong", () => {
let x = myClients.find((o) => o.wsoc === ws);
x.isAlive = true;
});
ws.on("message", function incoming(message) {
console.log(`[!] Message Receieved from ${req.socket.remoteAddress}`);
msg = JSON.parse(message);
console.log(queryData);
msg = { ...msg, time: new Date().toISOString() };
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(msg), { binary: false });
}
});
});
});
wss.addListener("customClose", function (m) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
message = {
type: "alert",
msg: `${m} has Disconnected.`,
};
client.send(JSON.stringify(message), { binary: false });
}
});
});
const interval = setInterval(function ping() {
myClients.forEach((clnt, index) => {
if (clnt.isAlive === false) {
console.log("[-]", clnt.id, "has Disconnected.");
wss.emit("customClose", clnt.id);
clnt.wsoc.terminate();
myClients.splice(index, 1);
}
clnt.isAlive = false;
clnt.wsoc.ping();
});
}, 5000);
server.listen(PORT, () => console.log("Listening on port 3000"));

Related

Screen sharing issue in WebRTC/nodejs with socket.io

im working on a vidoe conferencing application and I cant seem to get the screen sharing working getting an error "mediaTypeError: Cannot read properties of undefined (reading 'getSender')". The screensharing is able to start but nothing is being shared
This is my server file.
const express = require("express");
const app = express();
const server = require("http").Server(app);
const { v4: uuidv4 } = require("uuid");
const io = require("socket.io")(server);
// Peer
const { ExpressPeerServer } = require("peer");
const peerServer = ExpressPeerServer(server, {
debug: true,
});
app.set("view engine", "ejs");
app.use(express.static("public"));
app.use("/peerjs", peerServer);
app.get("/", (req, rsp) => {
rsp.redirect(`/${uuidv4()}`);
});
app.get("/:room", (req, res) => {
res.render("room", { roomId: req.params.room });
});
io.on("connection", (socket) => {
socket.on("join-room", (roomId, userId) => {
socket.join(roomId);
socket.to(roomId).emit("user-connected", userId);
socket.on("message", (message) => {
io.to(roomId).emit("createMessage", message);
});
});
});
server.listen(process.env.PORT || 3030);
Codes for screensharing where there seem to be an issue with getSender
share__Btn.addEventListener("click", (e) => {
navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always"
},
audio: {
echoCancellation: true,
noiseSuppression: true
}
}).then((stream) => {
let videoTrack = stream.getVideoTracks()[0];
let sender = currentPeer.getSender().find(function (s) {
return s.track.kind == videoTrack.kind
})
sender.replaceTrack(videoTrack)
}).catch((err) => {
console.log("unable to get display media" + err)
})
})
});
peer.on("call", function (call) {
getUserMedia(
{ video: true, audio: true },
function (stream) {
call.answer(stream); // Answer the call with stream.
const video = document.createElement("video");
call.on("stream", function (remoteStream) {
if (!peerList.includes(call.peer)) {
addVideoStream(video, remoteStream);
currentPeer = call.peerConnection
peerList.push(call.peer);
}
});
},
function (err) {
console.log("Failed to get local stream", err);
}
);
});
Github link for the full codes: https://github.com/sucxh/simLearn
I'm assuming that currentPeer is an RTCPeerConnection as documented here: https://peerjs.com/docs.html#dataconnection-peerconnection. In that case it's a simple typo. The method is called getSenders() and not getSender(). Adding the missing "s" should make the error go away.

multiple sessions established to socket io

I'm new to node and trying to create a server with socketio to send messages to front (react). But when I run the server and many connections begin to be established it shows something like this :
Listening on port 3000
client connectedLv3LqI9EhmYTLBIIAAAA
client connectedrKLlxRPDkRs20L43AAAB
client connectedHAUuOADubCigGNTzAAAC
client connectedFF1dTRFe-HFublfgAAAD
Here is my code :
var express = require("express");
var app = express();
const http = require("http");
const SocketIO = require("socket.io");
//database connections
let user;
const MongoClient = require("mongodb").MongoClient;
const uri = "mongodb://localhost:27017/?replicaSet = rs01";
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
client.connect().then((db) => {
let requests = 0;
let responses = 0;
let transactions = 0;
const changeStream = client
.db("gardesh_pay_silk")
.collection("silk_profile")
.watch();
changeStream.on("change", (next) => {
console.log(next);
});
const silkProfileQueries = client
.db("gardesh_pay_silk")
.collection("silk_profile_queries")
.watch();
silkProfileQueries.on("change", (next) => {
console.log(next);
});
const silkSQLQueries = client
.db("gardesh_pay_silk")
.collection("silk_sqlquery")
.watch();
silkSQLQueries.on("change", (next) => {
console.log(next);
transactions += 1;
});
const silkRequest = client
.db("gardesh_pay_silk")
.collection("silk_request")
.watch();
silkRequest.on("change", (next) => {
console.log(next);
user.broadcast.emit("requested", next);
requests++;
});
const silkResponse = client
.db("gardesh_pay_silk")
.collection("silk_response")
.watch();
silkResponse.on("change", (next) => {
console.log(next);
responses += 1;
});
console.log(requests);
});
const server = http.createServer(app);
const io = SocketIO(server);
io.on("connection", (client) => {
user = client;
console.log("client connected"+client.id);
client.on("disconnect", () => {
console.log("user disconnected");
});
});
const port = process.env.PORT || 3000;
server.listen(port, () => console.log('Listening on port'+port));
I think that the problem is in this part of code:
const server = http.createServer(app);
const io = SocketIO(server);
io.on("connection", (client) => {
user = client;
console.log("client connected"+client.id);
client.on("disconnect", () => {
console.log("user disconnected");
});
});
and here is my connection in client side :
import { socketIOClient, io } from "socket.io-client";
import { type } from "jquery";
class Again extends React.Component {
timing=()=> {
this.props.req_changer(this.requests);
this.props.res_changer(this.responses);
this.props.trans_changer(this.transactions);
this.requests = 0;
this.responses = 0;
this.transactions = 0;
}
constructor(props) {
super(props);
this.state = {
endpoint: "http://127.0.0.1:4000",
requested: "undefined",
};
}
componentDidMount() {
this.count = setInterval(this.timing, 1000);
}
componentWillUnmount() {
clearInterval(this.count);
}
render() {
const socket = socketIOClient(this.state.endpoint);
socket.on("requested", (object) => {
if (object.ns.coll == "silk_request") {
this.requests++;
}
if (object.ns.coll == "silk_response") {
this.responses++;
}
if (object.ns.coll == "silk_sqlquery") {
this.transactions++;
}
});
return <div></div>;
}
}
export default Again;```
There is some issues in your code.
A) Client side:
Don't put the const socket & socket.on into the render method. The render method should contain only the visualizations (UI if you want)...
The creation of the socket constant -can be- in the class's constructor, or, outside of the class:
const socket = socketIOClient(this.state.endpoint);
class Again extends React.Component {
.
.
.
}
The listening of the sockets requests should be under the componentDidMount() function:
componentDidMount() {
socket.on("requested", (object) => {
if (object.ns.coll == "silk_request") {
this.requests++;
}
if (object.ns.coll == "silk_response") {
this.responses++;
}
if (object.ns.coll == "silk_sqlquery") {
this.transactions++;
}
});
}
B) Server side:
this is the important issue, the 'Client' attribute you used is a socket:
io.on("connection", (socket) => {
.
.
});
Every socket has a session. So for the same client we can have multiple sessions at the same time (for example, for every refreshing of the application). So you need to save the client's sessions. To do it, you can use the express session middleware:
io.use((socket, next) => {
app.sessionMiddleware(socket.request, {}, next);
});
io.on("connection", (socket) => {
const session = socket.request.session;
session.save();
});
You need also to save all sessions (if the client use multiple navigators for exemple, the server should send the requests for all of them, bu sockets), and then to delete them when client disconnecting:
var onlineClients = {};
var socketToClient = {};
io.on('connection', (socket) => {
const session = socket.request.session;
session.save();
if(onlineClients[session.clientID] === undefined){
onlineClients[session.clientID] = {
sockets: []
}
}
socketToClient[socket.id] = session.clientID;
onlineClients[session.clientID].sockets.push(socket);
.
.
.
socket.on('disconnect', () => {
let clientId = socketToClient[socket.id]
if(clientId){
let clientSockets = onlineClients[clientId].sockets
clientSockets = clientSockets.filter(s => socket.id !== s.id)
onlineClients[clientId].sockets = clientSockets
if(clientSockets.length === 0){
delete onlineClients[clientId]
}
}
});
Conclusion: your question title should be: multiple sessions established to socket io
Hope it can help you!
here is my node server code :
var app = express();
const http = require("http");
const SocketIO = require("socket.io");
// database connections
let user;
const MongoClient = require("mongodb").MongoClient;
const uri = "mongodb://localhost:27017/?replicaSet = rs01";
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
client.connect().then((db) => {
let requests = 0;
let responses = 0;
let transactions = 0;
const changeStream = client
.db("gardesh_pay_silk")
.collection("silk_profile")
.watch();
changeStream.on("change", (next) => {
console.log(next);
});
const silkProfileQueries = client
.db("gardesh_pay_silk")
.collection("silk_profile_queries")
.watch();
silkProfileQueries.on("change", (next) => {
console.log(next);
});
const silkSQLQueries = client
.db("gardesh_pay_silk")
.collection("silk_sqlquery")
.watch();
silkSQLQueries.on("change", (next) => {
console.log(next);
transactions += 1;
});
const silkRequest = client
.db("gardesh_pay_silk")
.collection("silk_request")
.watch();
silkRequest.on("change", (next) => {
console.log(next);
user.broadcast.emit("requested", next);
requests++;
});
const silkResponse = client
.db("gardesh_pay_silk")
.collection("silk_response")
.watch();
silkResponse.on("change", (next) => {
console.log(next);
responses += 1;
});
console.log(requests);
});
const server = http.createServer(app);
var onlineClients = {};
var socketToClient = {};
const io = SocketIO.listen(4000);
io.use((socket, next) => {
app.sessionMiddleware(socket.request, {}, next);
});
io.on('connection', (socket) => {
const session = socket.request.session;
this.user = socket;
session.save();
if (onlineClients[session.clientID] === undefined) {
onlineClients[session.clientID] = {
sockets: []
}
}
socketToClient[socket.id] = session.clientID;
onlineClients[session.clientID].sockets.push(socket);
socket.on('disconnect', () => {
let clientId = socketToClient[socket.id]
if (clientId) {
let clientSockets = onlineClients[clientId].sockets
clientSockets = clientSockets.filter(s => socket.id !== s.id)
onlineClients[clientId].sockets = clientSockets
if (clientSockets.length === 0) {
delete onlineClients[clientId]
}
}
});
});
const port = process.env.PORT || 3000;
server.listen(port, () => console.log(`Listening on port ${port}`));
it is just a very simple server that watch's for changes in mongodb...

How to debug Node + Socket.io CPU Issues, doing nothing but connections 100% cpu

I'm running a Node Socket.io server with Express with docker compose
and client side is vue.js. The service is running well, but the CPU grows 100
and stays, i'm every time doing restart to my docker compose,still after 5 minutes cpu reaches jumping 100%.. I don't know what to check since there's laterally nothing but the most basic stuff..client and server connection and disconnect works perfect,all events is working good but i don't know which way i ll focus to find my bugs.
Here are my backend code socket code.
import { verifyToken } from './middleware/auth';
import Socket from './components/services/socket';
const app = express();
const socketPort = process.env.SOCKET_PORT || 8001;
const io = require('socket.io')(socketPort);
const port = process.env.PORT || 3000;
app.listen(port, () => {
log.info(`HTTP server is running 🚀 on port ${port}
Socket service running 🚀 on port ${socketPort}
Process Pid: ${process.pid}`);
});
const socket = new Socket(io);
socket.modelLoaded();
socket.js file______________
import { socketTokenVerification } from './../../middleware/auth';
export default class {
constructor(io) {
this.io = io;
this.connections = {};
}
addConnection(socket) {
if (this.connections[socket.user_id]) {
this.socketDisconnect(this.connections[socket.user_id]);
}
this.connections[socket.user_id] = {
id: socket.id,
isAuthenticated: true,
user_id: socket.user_id
};
}
removeConnection(socket) {
delete this.connections[socket.user_id];
}
socketDisconnect(socket) {
if (this.io.sockets.connected[socket.id]) {
this.io.sockets.connected[socket.id].disconnect(true);
}
}
modelLoaded() {
const { io, connections } = this;
io.on('connection', socket => {
const authData = socketTokenVerification(socket);
if (authData.error) {
io.to(socket.id).emit('error', {
message: authData.message
});
this.socketDisconnect(socket);
return;
}
const { user_id } = authData;
socket.user_id = user_id;
this.addConnection(socket);
User.connected(socket.user_id);
socket.on('disconnect', () => {
this.removeConnection(socket);
User.disconnect(user_id);
this.socketDisconnect(socket);
Object.keys(io.sockets.connected).length);
});
Client side code.
import io from 'socket.io-client';
socket = io(ENDPOINT, {
transports: ['websocket'],
});
socket.on('connect', () => {
console.log('conected');
});
socket.on('disconnect', () => {
console.log('disconnected');
});
socket.on('reconnect', () => {
console.log('reconnect');
});
socket.on('receive_message', (receiveMsg) => {
console.log(recMsg, 'mes');
});
socket.on('error', (err) => {
console.log(err, 'err');
});
},
beforeDestroy() {
socket.disconnect(true);
},

socket.io - Client connects to two sockets by same server

I am trying to build socket connections between React client-side and Node.js server-side. But the server will host two sockets. Here is the server-side code
var app = express();
var server = http.createServer(app);
var io = require('socket.io')(2893, {
path: "/ws",
resource: "/ws",
transports: ['websocket'],
pingTimeout: 5000
});
var redis = require('redis');
const subscriber = redis.createClient();
require('./server/route')(app, io);
require('./server/lib/subscriber')(require('socket.io').listen(server), subscriber);
The first socket connection is ok, but I wonder why the second one is not working (which is attached with listen(server). Here is subscriber module I wrote:
module.exports = (io, subscriber) => {
io.sockets.on('connection', (socket) => {
console.log(socket);
socket.on('room', (room) => {
socket.join(room);
});
});
subscriber.on('pmessage', (pattern, channel, message) => {
const msg = JSON.parse(message);
const idCallcenter = msg.idCallcenter;
return io.to(idCallcenter).emit('message', { type: channel, message: msg });
});
subscriber.psubscribe('*');
};
And the client-side React module
var socketOption = { path: "/ws", transports: ['websocket'] };
var socket = io("http://localhost:2893", socketOption);
var socket2 = io.connect("http://localhost:4004");
export default function (user) {
debugger
socket.user = user;
contact(socket);
notify(socket);
socket.on('connect', function () {
debug('socket connect', socket.id);
store.dispatch(connectNetworkSuccess());
socket.emit('user-online', {
idUser: user._id,
idCallcenter: user.idCallcenter,
email: user.email
});
});
socket2.on('connect', () => {
debug('Socket connected');
socket2.emit('room', user.idCallcenter);
});
socket2.on('message', (data) => {
debugger
debug('Socket message');
debug(data);
const type = data.type;
const message = data.message;
if (type === 'recordFetched') {
}
});
socket.emit('user-online', {
idUser: user._id,
idCallcenter: user.idCallcenter,
email: user.email
});
socket.on('disconnect', function (reason) {
debug('socket disconnect', reason);
store.dispatch(connectNetworkFailed());
});
}
The first socket (in port 2893) runs normally. Meanwhile, socket2 (in port 4004) does not connect. It does not jump into connection callback of both server and client sides. What did I do wrong here?
I solved the case myself. The working code on client side is:
export default function (user) {
debugger
var socketOption = { path: "/ws", transports: ['websocket'] };
var socket = env === "local" ? io("http://localhost:2893", socketOption) : io(window.location.origin, socketOption);
var socket2 = io.connect();
socket.user = user;
contact(socket);
notify(socket);
socket.on('connect', function () {
debug('socket connect', socket.id);
store.dispatch(connectNetworkSuccess());
socket.emit('user-online', {
idUser: user._id,
idCallcenter: user.idCallcenter,
email: user.email
});
});
socket2.on('connect', () => {
console.log('Socket connected');
socket2.emit('room', user.idCallcenter);
});
socket2.on('message', (data) => {
debugger
console.log('Socket message', data);
const type = data.type;
const message = data.message;
if (type === 'recordFetched') {
}
});
socket.emit('user-online', {
idUser: user._id,
idCallcenter: user.idCallcenter,
email: user.email
});
socket.on('disconnect', function (reason) {
debug('socket disconnect', reason);
store.dispatch(connectNetworkFailed());
});
}
The server did jump into connection callback, but not room callback. I suppose it is because the connect callback of client side was defined after the connection is made, so that it couldn't jump into it. This is my possibility. Am I right?

Node Bot FrameWork bot getting http 500 error after azure deployment

I've created a bot while using MS Bot Framework and deploy it to Azure.
After the deployment, the bot is returning HTTP 500 error when we're trying the '/api/messages' URL.
Here my app.js :
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const dialog_service_1 = require("./services/dialog-service");
const authentification_service_1 = require("./services/authentification-service");
const restify = require("restify");
const bot_service_1 = require("./services/bot-service");
const utilities_service_1 = require("./services/utilities-service");
require("dotenv").config();
let botService = new bot_service_1.BotService();
// let utilitiesService = new UtilitiesService(__dirname + '/assets/labels.json');
let dialogService = new dialog_service_1.DialogService(bot_service_1.BotService.bot);
let port = process.env.port || process.env.PORT || '3978';
const server = restify.createServer({
formatters: {
'text/html': function (req, res, body) {
return body.toString();
}
}
});
console.log('server created');
// change done for restify 5.X+ (mapParams should be specified # true)
server.use(restify.plugins.bodyParser({
mapParams: true
}));
console.log('trying to listening..');
server.listen(port, () => {
console.log('%s server listening to %s', server.name, server.url);
});
console.log('listening');
console.log('mounting styles folder...');
//add the build/styles folder to the restify server
server.get(/\/styles\/?.*/, restify.plugins.serveStatic({
directory: __dirname + '/assets'
}));
console.log('mounted');
console.log('mounting api/messages endpoint...');
// entry point of your bot
server.post("/api/messages", bot_service_1.BotService.bot.connector("*").listen());
console.log('mounted...');
console.log('mounting api/oauthcallback endpoint...');
//callback handling
server.post("/api/oauthcallback", (req, res, next) => {
let authorizationCode = req.params.code;
if (authorizationCode !== undefined) {
authentification_service_1.AuthentificationService.acquireTokenWithAuthorizationCode(authorizationCode).then((response) => {
let state = req.params.state;
if (state) {
let address = JSON.parse(state);
response.state = state;
bot_service_1.BotService.bot.beginDialog(address, "/oauth-success", response);
}
utilities_service_1.UtilitiesService.readFile(__dirname + '/assets/html/callback.html').then(body => {
res.send(200, body, { "Content-Length": Buffer.byteLength(body).toString(), "Content-Type": "text/html" });
res.end();
});
}).catch((errorMessage) => {
var body = "<html><body>" + errorMessage + "</body></html>";
res.send(200, body, { "Content-Length": Buffer.byteLength(body).toString(), "Content-Type": "text/html" });
res.end();
});
}
else {
var body = "<html><body>" + "unable to retrieve the authentication code" + "</body></html > ";
res.send(200, body, { "Content-Length": Buffer.byteLength(body).toString(), "Content-Type": "text/html" });
res.end();
}
});
console.log('mounted');
//# sourceMappingURL=app.js.map
I've added some logs to help me, all console.log() is reached. so it seems that the server is well started...
Thanks for your help.
I noticed you aren't initiating a bot and starting a dialog. When I try your code as-is I receive a 502 error. When I introduce code for the bot the error disappears and the bot responds, as expected.
Since I don't have access to all of your files, I had to remove the associated code calls. So, I can't say if the error you are receiving is related to any of that code.
I used connector.listen() in the server.post for 'api/messages'. Defining connector, as shown below, follows the basic setup found in the documentation for building a bot using Node.
Hope this helps.
Steve.
'use string';
const builder = require('botbuilder');
const restify = require('restify');
require('dotenv').config();
let port = process.env.port || process.env.PORT || '3978';
let server = restify.createServer({
formatters: {
'text/html': function (req, res, body) {
return body.toString();
}
}
});
// change done for restify 5.X+ (mapParams should be specified # true)
server.use(restify.plugins.bodyParser({
mapParams: true
}));
server.listen(port, () => {
console.log('%s server listening to %s', server.name, server.url);
});
// entry point of your bot
let connector = new builder.ChatConnector({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword,
openIdMetadata: process.env.BotOpenIdMetadata
});
server.post('/api/messages', connector.listen());
//callback handling
server.post('/api/oauthcallback', (req, res, next) => {
var authorizationCode = req.params.code;
if (authorizationCode !== undefined) {
console.log('authorization code provided');
}
else {
console.log('authorization code not provided');
}
});
// inMemoryStorage should only be used for testing. It is not stable for a production environment
let inMemoryStorage = new builder.MemoryBotStorage();
let bot = new builder.UniversalBot(connector).set('storage', inMemoryStorage);
bot.dialog('/', [
function (session) {
session.send('Hi');
}
]);

Categories

Resources