node socket app new instance scope - node.js

it is a simple socket app using event base pattern
const invitation = require('./invitation');
module.exports = function(io){
io.on('connection', (socket)=>{
var prepareGame = new PrepareGame(socket)
socket.on("sendInvitation",(data, ack)=>{
prepareGame.sendInvitation(data,ack)
});
});
}
and in prepareGame.js
const events = require('events');
const util = require('util');
class PrepareGame extends events {
constructor(socket) {
super();
this.user = socket.user
var self = this
draftInvitation(data){
this.newInvitation = {
from_user: self.user.id,
to_user: data.to_user,
message:data.message,
created_at:moment().unix(),
}
return this
};
self.on("toSocket", (eventName, clientId, data) => {
console.log(` ===>>>> sending to listener ${eventName}`, clientId);
var client = users[clientId]
if(client)
client.emit(eventName, data)
});
}
// public function
sendInvitation(data, ack) {
// console.log(this);
var self = this
data.message = 'New Invitation'
draftInvitation(data)
.emit("toSocket", "getInvitation", data.to_user, self.newInvitation)
setTimeout(()=>{
data.message = 'Invitation timeout'
draftInvitation(data)
.emit("toSocket", "getInvitation", self.user.id, self.newInvitation)
}, 15000)
if(typeof ack == 'function')
ack({
status:200,
message: "invitation sent",
})
}
}
util.inherits(PrepareGame, events.EventEmitter )
module.exports = PrepareGame
code is sum of different design pattern. it's working fine but I've some queries
io.connection called once to connect to socket and prepareGame
instance created. considering two instance for two user then
how sendInvitation automatically bound correct instance when calling
what happen with new prepareGame instance when socket disconnect ?
i want to remove (data, ack)=>{ } encloser from socket.on mean it
should socket.on ("sendInvitation",prepareGame.sendInvitation) then
how to manage this reference in sendInvitation function

Related

Nodejs - Express - Socket.io How to send a message to a specific socket.id from an outside function?

I'm having problem finding the right answer for this. I'm trying to send a message to a specific socket.id given I'm handling multiple users but I need to do it from another function which does not have access to socket.io.
I need to send the message to the specific socket.id inside the function:
var authorizePublish = function(client, topic, payload, callback) {
//here
}
socketLib.js
/// Import Modules ///
const mosca = require('mosca');
const DeviceService = require('../services/device.service');
const config = require('../config');
const util = require('../modules/util.js');
module.exports = async function(httpServer, sessionParser) {
var io = require("socket.io")(httpServer); // For Sockets
io.use(function(socket, next) {
sessionParser(socket.request, socket.request.res, next);
});
io.sockets.on('connection', function(socket) {
const userSessionId = socket.request.session.userId;
const userId = socket.request.session.passport.user;
const socketId = socket.request.session.id;
if (userSessionId == '')
console.log('client connected');
console.log(`Client connected: ${userSessionId}, with userid: ${userId}, with socket: ${socketId} conectado`);
socket.on('msg:message', async function (data) {
socket.emit('message', data);
});
socket.on('disconnect', function(data) {
console.log('user disconnected');
});
});
/// Mosca Settings ///
var moscaServer = null;
var moscaSettings = {
interfaces: [ { type: "mqtt", port: 1884 }, { type: "http", port: 5000, bundle: true, static: './' }
],};
var debug = util.isDebug(),
isAuth = util.isAuth()
//// Mosca Server ////
var dbHost = config.dbHost;
moscaServer = new mosca.Server(moscaSettings);
moscaServer.on('ready', setup);
var authenticate = function(client, username, callback) {
console.log('-------- Authenticating MQTT user... --------');
callback(null, flag);
if(authenticate) client.user = username;
}
/// Mosca Events ///
moscaServer.on('published', function (packet, client) {
var arr = packet.topic.split('/');
if (arr.length !== 3) { return; }
});
/// Mosca Functions ///
var authorizePublish = function(client, topic, payload, callback) {
if (client.user == topic.split('/')[1]) {
// socket.emit('message', payload.toString()); (Here is where I need to get access to the client socket.id in order to send him a message with the payload.
callback(null, true);
}
else {
callback(null, false);
}
}
function setup() {
if (isAuth) {
moscaServer.authenticate = authenticate;
}
if (config.authPub === true) {
moscaServer.authorizePublish = authorizePublish;
}
if(config.authSubs == true) {
moscaServer.authorizeSubscribe = authorizeSubscribe;
}
console.log('Mosca server is up and running')
}
}
First off, the way you send to a specific socket id is with this:
io.to(id).emit(msg, data);
where id is the socket.id of the socket.io connection that you want to send to.
Or, you could put that into a function:
function sendToId(id, msg, data) {
io.to(id).emit(msg, data);
}
So, secondly to be able to do that from anywhere you have a couple options:
You can import the io instance and use it with the above line of code.
You can export a function called something like sendToId() from a module that does have access to the io instance and then you can import that function in order to be able to use it.
You can assign the io instance to a known object so that anyone with access to that more popularly known object can then retrieve it from there. An example of that is to set it as a properly on the Express app object as in app.set("io", io) and then anyone with access to the app object can you let io = app.get("io"); to get it and use it.

NodeJs can't listen to socket.io private channel when active sockets are only one

I am broadcasting connection between nodejs, and flutter...I am emitting to private channel, and listening to the same channel.
I am defining socket.io config in server.js, then, I am using an instance of it # authcontroller.js.
The issue I am having is that when I try to listen to the private channel from auth.controller, I am receiving the response only when active sockets are more than one socket. However, from server.js, I am receiving the response instantaneously, even with only one socket.
Below is the code:
Server.js
//general dependencies
const port = process.env.PORT || 3000;
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
const fileUpload = require('express-fileupload');
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}))
const api = require('../api/AuthController')
var server = http.listen(3000, () => {
console.log('server is running on port', server.address().port);
});
//routes
const routes = require('../api/routes');
routes(app,io);
//socket config
global.io = io; //added
io.on('connection', function(socket){
socket.on('privateChannelID', function (message) {
console.log('message received from server.js')
});
});
AuthController.js:
global.io.on('connection', function (socket) {
socket.on('privateChannelID', function (message) {
console.log('message received from authcontoller')
});
});
Any idea, what I am missing?
Thanks in advance
Update
Frontend code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:adhara_socket_io/adhara_socket_io.dart';
import 'package:geocoder/geocoder.dart';
const String URI = "http://10.0.2.2:3000/";
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
_MyHomePageState();
List<String> toPrint = ["trying to connect"];
SocketIOManager manager;
Map<String, SocketIO> sockets = {};
Map<String, bool> _isProbablyConnected = {};
bool newtripRequest = false;
var pickupController;
var dropoffController;
SocketIO socketController;
var driver = 'driver';
String socketIdentifier;
#override
void initState() {
super.initState();
manager = SocketIOManager();
initSocket("default");
}
#override
void dispose() {
super.dispose();
}
initSocket(String identifier) async {
setState(() => _isProbablyConnected[identifier] = true);
SocketIO socket = await manager.createInstance(SocketOptions(
//Socket IO server URI
URI,
nameSpace: (identifier == "namespaced") ? "/adhara" : "/",
//Query params - can be used for authentication
query: {
"auth": "--SOME AUTH STRING---",
"info": "new connection from adhara-socketio",
"timestamp": DateTime.now().toString()
},
//Enable or disable platform channel logging
enableLogging: false,
transports: [
Transports.WEB_SOCKET /*, Transports.POLLING*/
] //Enable required transport
));
setState(() {
socketIdentifier = identifier;
});
socket.onConnect((data) {
pprint("connected...");
pprint(data);
sendMessage('news', 'yes', socketIdentifier);
});
socket.onConnectError(pprint);
socket.onConnectTimeout(pprint);
socket.onError(pprint);
socket.onDisconnect(pprint);
socket.on("news", (data) => newTripRquest(data));
socket.connect();
sockets[identifier] = socket;
}
bool isProbablyConnected(String identifier) {
return _isProbablyConnected[identifier] ?? false;
}
disconnect(String identifier) async {
await manager.clearInstance(sockets[identifier]);
setState(() => _isProbablyConnected[identifier] = false);
}
sendMessage(privateChannel, messageBody, identifier) {
//pprint("sending message from '$identifier'...");
sockets[identifier].emit(driverChannel, [
{'response' : messageBody}]);
//pprint("Message emitted from '$identifier'...");
}
pprint(data) {
setState(() {
if (data is Map) {
data = json.encode(data);
}
print(data);
toPrint.add(data);
});
}
Update:
AuthConroller.js
'use strict'
const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
var authController = {
findNearestDriver: function (req, res) {
var query = Driver.find({
'geo': {
$near: [
req.body.lat,
req.body.lng
],
// $maxDistance: distance
}
});
query.exec(async function (err, driver) {
if (err) {
console.log(err);
throw err;
}
if (!driver) {
res.json({});
} else {
for (let i = 0; i < driver.length; i++) {
global.io.emit(`news${driver[i]._id}`, {
pickupLat: req.body.lat, pickupLng: req.body.lng, dropOffLat:
req.body.dropLat, dropOffLng: req.body.dropLng });
global.io.on('connection', function (socket) {
socket.on(`news${driver[i]._id}`, function (message) {
if (message.message === 'Accept') {
resultRecieved = true
console.log('message received')
}
});
});
}
res.json(driver);
}
});
}
}
module.exports = authController;
Inside of authController.js, you have a:
global.io.on('connection', ...)
that is embedded inside some other function findNearestDriver(). So, that connection event handler won't be active until after findNearestDriver() function is called and executes and until after the query inside of it finishes. This means you will miss any connection events that occur before that function is called.
It's also inside a for loop which is almost never what you want as that just creates identical, but duplicate event handlers.
Event handlers like global.io.on('connection', ...) should generally be near the top level of your module and initialized when the module is initialized, not in a place where they can be run multiple times.
Note, you are also loading authController.js before global.io has a value. That means you can't just move the global.io.on('connection', ...) to the top level of the module unless you also move the require('authController.js') to be AFTER global.io is set.
But, I think you need a bit of a redesign for how this part of authController.js works because connection event handlers need to be at the top level of the module, not buried deep inside a for loop inside a function.
This nicely illustrates one of the problems with using global variables to communicate things when there are asynchronous operations involved. It creates frustrating timing or load order problems. IMO, it would be better to explicitly pass io to this module in a module constructor function and use it from there, not from a global. This would make it a lot less likely that you would code it in a way that tries to use io before it is initialized.

reconnecting to nodejs websocket on failure

This is my first practice after reading some tutorials and videos. Basically, I need message to be sent from the server (nodejs) to the client (Angular 6). At first tho, when client app is booted, it sends user id to the server for authentication. The server then will send data based on that user.
Now my problem is on first load and a few calls, the connection does work. But then on refresh or so, the connection drops. My client does console out "retrying" but it never succeeds. It works only if I manually restart the server and reload the client so a new connection could be established.
How can I maintain a fairly stable connection throughout the lifetime of a client? At times the readyState stays at 3 on the server i.e. connecting, which I am confused with because the client does try to reconnect...just fails.
My Server is simple. index.js (Tried to put it up on stackblitz but failed...would appreciate if someone can figure out the dependency file: nodejs websocket server)
const express = require('express');
const bodyParser = require('body-parser');
const pg = require ('pg');
var ws = require('./ws')
var app = express()
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.listen(3000, function () {
console.log('We are running on port 3000!')
})
ws.js:
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'error.log'})
]
});
var WebSocketServer = require("ws").Server,
wss = new WebSocketServer({
port: 40511
});
let data = {
'packet': ['amy1', 'amy2', 'amy3']
}
const mx = 2;
const mn = 0;
wss.on("connection", function(ws) {
ws.on("message", function(user) {
// client has called now. If the connection
// fails, the client does try to connection again and again -- no limit but it simply doesn't seem to have effect. When connecting, it simply sends user name
console.log("received: %s", user);
setInterval(function(){
var random = Math.floor(Math.random() * (mx - mn + 1) + mn);
if (ws.readyState == ws.OPEN){
ws.send(data['packet'][random]);
}
}, 3000);
});
});
My front end is: service.ts
import { Observable} from 'rxjs';
export class WebSocketService {
socket: WebSocket;
constructor() { }
initConnection(): void {
if(!this.socket){
this.socket = new WebSocket('ws://localhost:40511');
// when connection is open, send user id
this.socket.onopen = () => this.socket.send(2);
}
}
manageState() : Observable<any>{
const vm = this;
return new Observable(observer => {
this.socket.onerror = (e) => {
// close it
this.socket.close();
observer.next('web socket communication closed due to error')
};
this.socket.onclose = (e) => {
//socket closed for any reason
setTimeout(function() {
console.log('try to connect')
vm.initConnection();
observer.next('still trying')
}, 1000);
}
});
}
onMessage(): Observable<any> {
// when message arrives:
return new Observable(observer => {
this.socket.onmessage = (e) => {
console.log(e.data);
observer.next(e.data)
};
});
}
}
component.ts:
// initialize the connection
this.service.initConnection();
this.service.onMessage().subscribe(
data => {
// we have got data
console.log('data came ', data)
},
err => {
console.log("error websocking service ", err);
}
);
// track state of the communication, letting the service to reconnect if connection is dropped
this.service.manageState().subscribe(data => {
console.log(data);
});

How to stub a nodejs "required" constructor using sinon?

I'm writing unit tests for a method that uses the email-templates module like this:
var EmailTemplate = require('email-templates').EmailTemplate;
module.exports = {
sendTemplateEmail: function (emailName, data, subject, to, from) {
var template = new EmailTemplate(__dirname + "/../emails/" + emailName);
data.from = FROM;
data.host = config.host;
return template.render(data)
.then(function (result) {
return mailer.sendEmail(subject, to, from, result.html, result.text);
})
.then(function () {
log.info(util.format("Sent %s email to %s. data=%s", emailName, to, JSON.stringify(data)));
return Promise.resolve();
})
.catch(function (err) {
return Promise.reject(new InternalError(err, "Error sending %s email to %s. data=%s", emailName, to, JSON.stringify(data)));
});
}
};
The unit test looks like this:
var assert = require("assert"),
sinon = require("sinon"),
Promise = require("bluebird"),
proxyquire = require("proxyquire");
describe('mailer#sendTemplateEmail', function () {
var templates,
template;
beforeEach(function() {
templates = {
EmailTemplate: function(path) {}
};
template = {
render: function(data) {}
};
sinon.stub(templates, "EmailTemplate").returns(template);
});
it("should reject immediately if template.render fails", function () {
const TO = {email: "user1#example.com", first: "User"};
const FROM = {email: "user2#example.com", first: "User"};
const EMAIL_NAME = "results";
const SUBJECT = "Results are in!";
const DATA = {
week: 10,
season: "2015"
};
var err = new Error("error");
var mailer = proxyquire("../src/mailer", {
"email-templates": templates
});
sinon.stub(template, "render").returns(Promise.reject(err));
return mailer.sendTemplateEmail(EMAIL_NAME, DATA, SUBJECT, TO, FROM)
.then(function () {
assert.fail("Expected a rejected promise.");
})
.catch(function (err) {
assert(err.message === "error");
assert(mailer.sendEmail.notCalled);
});
});
};
The problem I'm encountering is on the first line of the sendTemplateEmail function which instantiates a new EmailTemplate object. The EmailTemplate constructor being called points to the non-stub EmailTemplate function defined in the beforeEach, rather than the sinon stub created on the last line of the beforeEach. If I evaluate the require('email-templates').EmailTemplate statement, however, it correctly points to the sinon stub. I'd prefer not to have to change my code to call the require statement inline like:
var template = new require('email-templates').EmailTemplate(__dirname + "/../emails/" + emailName);
Is there any way to accomplish the stub the way I'm intending?
You can inject your dependency when you construct your mailer - exp:
function mailer(options) {
options = options || {};
this.email_template = options.email_template;
}
Then in the sendTemplateEmail function - use the email_template member.
Also - not sure about your mailer code - but if you need your mailer to act as a singleton in your code (and it isn't already) - you can add this to your mailer:
module.exports = {
getInstance: function(emailTemplate) {
if(this.instance === null){
this.instance = new mailer(emailTemplate);
}
return this.instance;
}
}
Then when you require your mailer you can use the syntax:
var template = new require('email-templates').EmailTemplate(__dirname + "/../emails/" + emailName);
var mail = mailer.getInstance(template);
This way your application (unit test framework or your actual/real-world application) will determine the type of mailer that will be used for the lifetime of the process.

WebRTC Video sharing app doesn't work

I have been trying to share a video stream between two clients over WebRTC for about a week now and I have no idea how to proceed any further. I'm frustrated and could really use some help from the more experienced. Please help me get this running.
I am using Websockets and NodeJS. I will post all of my code below:
Server Code ( on NodeJS )
"use strict";
/** Requires **/
var webSocketServer = require('websocket').server,
expr = require("express"),
xpress = expr(),
server = require('http').createServer(xpress);
// Configure express
xpress.configure(function() {
xpress.use(expr.static(__dirname + "/public"));
xpress.set("view options", {layout: false});
});
// Handle GET requests to root directory
xpress.get('/', function(req, res) {
res.sendfile(__dirname + '/public/index.html');
});
// WebSocket Server
var wsServer = new webSocketServer({
httpServer: server
});
// Set up the http server
server.listen(8000, function(err) {
if(!err) { console.log("Listening on port 8000"); }
});
var clients = [ ];
/** On connection established */
wsServer.on('request', function(request) {
// Accept connection - you should check 'request.origin' to make sure that client is connecting from your website
var connection = request.accept(null, request.origin);
var self = this;
// We need to know client index to remove them on 'close' event
var index = clients.push(connection) - 1;
// Event Listener for when Clients send a message to the Server
connection.on('message', function(message) {
var parsedMessage = JSON.parse(message.utf8Data);
if ( parsedMessage.kind == 'senderDescription' ) {
wsServer.broadcastUTF(JSON.stringify({ kind:'callersDescription', callerData: parsedMessage }));
}
});
});
Index.html loads and immediately runs VideoChatApp.js
function VideoChatApp() {
this.connection = null;
this.runConnection();
}
_p = VideoChatApp.prototype;
/** Initialize the connection and sets up the event listeners **/
_p.runConnection = function(){
// To allow event listeners to have access to the correct scope
var self = this;
// if user is running mozilla then use it's built-in WebSocket
window.WebSocket = window.WebSocket || window.MozWebSocket;
// if browser doesn't support WebSocket, just show some notification and exit
if (!window.WebSocket) { return; }
/** Where to make the connection **/
var host = location.origin.replace(/^http/, 'ws');
console.log(host);
this.connection = new WebSocket(host);
/** Once the connection is established **/
this.connection.onopen = function () {
console.log("Web Socket Connection Established");
self.onConnectionEstablished();
};
/** If there was a problem with the connection */
this.connection.onerror = function (error) {
console.log("ERROR with the connection *sadface*");
};
}; // end runConnection
_p.onConnectionEstablished = function() {
// My connection to the nodejs server
var websocketConnection = this.connection;
// Some local variables for use later
var mediaConstraints = {
optional: [],
mandatory: {
OfferToReceiveVideo: true
}
};
var offerer, answerer;
this.theLocalStream = null;
var amITheCaller = false;
var localVideoTag = document.getElementById('localVideoTag');
var remoteVideoTag = document.getElementById('remoteVideoTag');
window.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCSessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
window.RTCIceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
window.URL = window.webkitURL || window.URL;
window.iceServers = {
iceServers: [{
url: 'stun:23.21.150.121'
}]
};
var callButton = document.getElementById("callButton");
callButton.onclick = callClicked;
function callClicked() {
amITheCaller = true;
setUpOffer();
}
offerer = new RTCPeerConnection(window.iceServers);
answerer = new RTCPeerConnection(window.iceServers);
/** Start Here - Set up my local stream **/
getUserMedia(function (stream) {
hookUpLocalStream(stream);
});
function getUserMedia(callback) {
navigator.getUserMedia({
video: true
}, callback, onerror);
function onerror(e) {
console.error(e);
}
}
function hookUpLocalStream(localStream) {
this.theLocalStream = localStream;
callButton.disabled = false;
localVideoTag.src = URL.createObjectURL(localStream);
localVideoTag.play();
};
/* When you click call, then we come here. Here I want to set up the offer and send it. */
function setUpOffer() {
var stream = theLocalStream;
offerer.addStream(stream);
offerer.onaddstream = function (event) {
console.log("onaddstream callback was called");
};
offerer.onicecandidate = function (event) {
if (!event || !event.candidate) return;
answerer.addIceCandidate(event.candidate);
};
offerer.createOffer(function (offer) {
offerer.setLocalDescription(offer);
console.log("------------------- What I am sending: -------------------------");
console.log(offer);
console.log(stream);
console.log("-----------------------------------------------------------------\n");
var jsonMsg = JSON.stringify( {kind:'senderDescription', streamInfo: offer, theStream: stream} );
websocketConnection.send( jsonMsg );
//answererPeer(offer, stream);
}, onSdpError, mediaConstraints);
}
/* Respond to a call */
function answererPeer(offer, stream) {
answerer.addStream(stream);
answerer.onaddstream = function (event) {
remoteVideoTag.src = URL.createObjectURL(event.stream);
remoteVideoTag.play();
};
answerer.onicecandidate = function (event) {
if (!event || !event.candidate) return;
offerer.addIceCandidate(event.candidate);
};
answerer.setRemoteDescription(offer, onSdpSucces, onSdpError);
answerer.createAnswer(function (answer) {
answerer.setLocalDescription(answer);
offerer.setRemoteDescription(answer, onSdpSucces, onSdpError);
}, onSdpError, mediaConstraints);
}
function onSdpError(e) {
console.error('onSdpError', e);
}
function onSdpSucces() {
console.log('onSdpSucces');
}
websocketConnection.onmessage = function (messageFromServer) {
console.log(" ------------------------ Message from server: -------------------- ");
var parsedMessage = JSON.parse(messageFromServer.data);
if(parsedMessage.callerData.kind = "senderDescription") {
console.log("Received a senderDescription");
console.log(parsedMessage.callerData.streamInfo);
console.log(parsedMessage.callerData.theStream);
console.log("-------------------------------------------------------------------\n");
answererPeer(parsedMessage.callerData.streamInfo, parsedMessage.callerData.theStream);
}
};
};// end onConnectionEstablished()
Finally, here are my errors:
I am not sure if this is still interesting for you, but I have some very good experience with WebRTC using PeerJS as a wrapper around it. It takes care of all the stuff you don't want to do (http://peerjs.com/). There is the client library as well as a very nice signaling server for nodejs (https://github.com/peers/peerjs-server). You can easy extend this server in your own node server.
This may not explain why your approach failed, but gets WebRTC running easily.
You can start with code that is already working and completely open. Check out easyrtc.com we have a client api, signalling server and working code. And if you have problems with that code ask us for help on Google Groups for easyrtc.

Resources