Passing socket.io instance to JS object constructor - javascript

I read this question and attempted to do the solution, however I am trying to pass my instance of io to an object constructor instead of a class. I originally attempted to do something like...
//index.js
const {CONNECTION, CREATE_ROOM} = require('./SignalTypes')
const app = require('express')()
const server = require('http').Server(app)
const io = require('socket.io')(server)
const Lobbies = require('./lobby')
let lobbies = new Lobbies(io)
io.of('/menu').on(CONNECTION, (socket) => {
console.log(`User connected to main menu`)
socket.on(CREATE_ROOM, () => {
const roomKey = lobbies.createLobby()
socket.emit(ROOM_CREATED, roomKey)
})
...
})
And my Lobbies file looks like...
//lobby.js
const shortid = require('shortid')
function Lobbies(io) {
this.io = io;
this.lobbies = {}
}
Lobbies.prototype.createLobby = () => {
let roomKey = shortid.generate()
//create namespace for new lobby
const lobbyNamespace = this.io.of(`/${roomKey}`) // issue
this.lobbies[roomKey] = new Lobby(roomKey, lobbyNamespace)
return roomKey
}
//Lobby object constructor defined later
...
module.exports = Lobbies
However I keep running into errors in which it says io is undefined at the line
//lobby.js
const lobbyNamespace = this.io.of(`/${roomKey}`)
//TypeError: Cannot read property 'of' of undefined
I was wondering if there's a way to pass my io object to my object constructor without having to change it into an ES6 class or something. Any suggestions?

You are losing this reference when using arrow function syntax. I don't know why do you want to use old, hard-to-read syntax, but if you want that instead of class you should do:
Lobbies.prototype.createLobby = function() {
let roomKey = shortid.generate()
//create namespace for new lobby
const lobbyNamespace = this.io.of(`/${roomKey}`) // issue
this.lobbies[roomKey] = new Lobby(roomKey, lobbyNamespace)
return roomKey
}

Related

Websockets in Sapper

I have a readable store in Svelte that looks like this:
const state = {};
export const channels = readable(state, set => {
let st = state;
let socket = new WebSocket("ws://127.0.0.1:5999");
socket.onmessage = function (event) {
var datastr = event.data.split(':');
st[datastr[0]].value = datastr[1];
st[datastr[0]].timestamp = Date.now();
set(st)
};
return () => {
socket.close()
}
});
When I import it to my Svelte App works. But if I put that App.svelte as my index.svelte running on Sapper, it doesnt work at first. It says error 500 websocket is not defined. Once I reload the page in the browser start to work...
I have try to parse a function that creates the store instead:
export const getChannel = () => {
// here my store
return {...store}
}
and then creating the store inside a onMount() like this:
onMount( ()=> {
const channel = getChannel();
});
But doesnt seem to do the trick... What do I miss?
Note: If a just replace the store by a simple writable, and create the websocket onMount(), it works without any problem. I just only wanted to put all the communication inside the store as a readable...
In Sapper, code in components (or imported into components) is executed in Node during server-side rendering unless it's put inside onMount (which doesn't run on the server, because there's no 'mounting' happening) or an if (process.browser) {...} block, or something equivalent.
That includes things like references to $channels causing channels.subscribe(...) to be called during initialisation.
Since there's no WebSocket global in Node, creating that subscription will fail. The simplest workaround is probably a simple feature check:
const state = {};
export const channels = readable(state, (set) => {
if (typeof WebSocket === 'undefined') return;
let st = state;
let socket = new WebSocket("ws://127.0.0.1:5999");
socket.onmessage = function (event) {
var datastr = event.data.split(":");
st[datastr[0]].value = datastr[1];
st[datastr[0]].timestamp = Date.now();
set(st);
};
return () => {
socket.close();
};
});

Socket.io emitting values inside ES6 class

I wonder if any smart individuals could show me how to implement Socket.IO in an OOP environment with ES6 classes. The main problem I keep running into with Socket.io is passing around the server object, in my case called 'io'. Almost every example I've seen of socket.io has been pure spaghetti code, one file with many socket related events and logic. First I tried to pass the server object, io, to new class's constructor, but for some reason you end up with a nasty "RangeError: Maximum call stack size exceeded" error message. Then I've tried to wrap my classes in module.exports function which parameter should contain the io object. Which is fine for the first class. Let's say I pass the io object into my Game, great works as expected. But when I try to reference the io object down to the Round class(Game holds an array of Rounds) I can't. Because that is one hell of a bad practice in NodeJS, require should be global and not inside the modules/functions. So I'm once again back with the same issue.
app.js(where I require the main sockets file)
const io = socketio(server, { origins: '*:*' });
...
require('./sockets')(io);
sockets/index.js(where I initialize my game server, and handle incoming messages from client sockets)
const actions = require('../actions.js');
const chatSockets = require('./chat-sockets');
const climbServer = require('./climb-server');
const authFunctions = require('../auth-functions');
module.exports = (io) => {
io.on('connection', (client) => {
console.log('client connected...');
// Standard join, verify the requested room; if it exists let the client join it.
client.on('join', (data) => {
console.log(data);
console.log(`User ${data.username} tries to join ${data.room}`);
console.log(`Client joined ${data.room}`);
client.join(data.room);
});
client.on('disconnect', () => {
console.log('Client disconnected');
});
client.on(actions.CREATE_GAME, (hostParticipant) => {
console.log('CREATE_GAME', hostParticipant);
// Authorize socket sender by token?
// Create a new game, and set the host to the host participant
climbServer.createGame(io, hostParticipant);
});
client.on(actions.JOIN_GAME, (tokenizedGameId) => {
console.log('JOIN_GAME');
const user = authFunctions.getPayload(tokenizedGameId.token);
// Authorize socket sender by token?
// Create a new game, and set the host to the host participant
const game = climbServer.findGame(tokenizedGameId.content);
game.joinGame(user);
});
});
};
climbServer.js(My game server that keeps track of active games)
const actions = require('../actions.js');
const Game = require('../models/game');
const climbServer = { games: { }, gameCount: 0 };
climbServer.createGame = (io, hostParticipant) => {
// Create a new game instance
const newGame = new Game(hostParticipant);
console.log('New game object created', newGame);
// Store it in the list of game
climbServer.games[newGame.id] = newGame;
// Keep track
climbServer.gameCount += 1;
// Notify clients that a new game was created
io.sockets.in('climb').emit(actions.CLIMB_GAME_CREATED, newGame);
};
climbServer.findGame = gameId => climbServer.games[gameId];
module.exports = climbServer;
Game.js(ES6 class that SHOULD be able to emit to all connected sockets)
const UUID = require('uuid');
const Round = require('./round');
class Game {
// Constructor
constructor(hostParticipant) {
this.id = UUID();
this.playerHost = hostParticipant;
this.playerClient = null;
this.playerCount = 1;
this.rounds = [];
this.timestamp = Date.now();
}
joinGame(clientParticipant) {
console.log('Joining game', clientParticipant);
this.playerClient = clientParticipant;
this.playerCount += 1;
// Start the game by creating the first round
return this.createRound();
}
createRound() {
console.log('Creating new round at Game: ', this.id);
const newRound = new Round(this.id);
return this.rounds.push(newRound);
}
}
module.exports = Game;
Round.js(ES6 class that is used by the Game class(stored in a rounds array))
const actions = require('../actions.js');
class Round {
constructor(gameId) {
console.log('Initializing round of gameId', gameId);
this.timeLeft = 60;
this.gameId = gameId;
this.winner = null;
this.timestamp = Date.now();
// Start countdown when class is instantiated
this.startCountdown();
}
startCountdown() {
const countdown = setInterval(() => {
// broadcast to every client
io.sockets.in(this.gameId).emit(actions.ROUND_TIMER, { gameId: this.gameId, timeLeft: this.timeLeft });
if (this.timeLeft === 0) {
// when no time left, stop counting down
clearInterval(countdown);
this.onRoundEnd();
} else {
// Countdown
this.timeLeft -= 1;
console.log('Countdown', this.timeLeft);
}
}, 1000);
}
onRoundEnd() {
// Evaluate who won
console.log('onRoundEnd: ', this.gameId);
}
}
module.exports = Round;
TO SUMMARIZE with a question: How can I pass a reference of io to my classes so that I'm able to emit to connected sockets within these classes?
This doesn't necessarily have to be ES6 classes, it can be NodeJS objects using the .prototype property. I just want a mainatainable way to handle my game server with sockets... ANY HELP IS APPRECIATED!
After hours upon hours I figured out a solution. If anyone runs into the same thing check my solution out below. Not the best, but much better than putting all socket related code in one file...
Game.js(ES6 Class). Focus on the first line containing 'module.exports'.
const GameFactory = require('../models/game');
const climbServer = { games: { }, gameCount: 0 };
climbServer.createGame = (io, hostParticipant) => {
// Create a new game instance
const Game = GameFactory(io);
const newGame = new Game(hostParticipant);
console.log('New game object created', newGame);
// Store it in the list of game
climbServer.games[newGame.id] = newGame;
// Keep track
climbServer.gameCount += 1;
return newGame;
};
climbServer.findGame = gameId => climbServer.games[gameId];
module.exports = climbServer;
The trick is to use this factory pattern where you first declare:
const GameFactory = require('../models/game');
Then initialize the factory with passing in the Socket.io server object, in my case 'io'. IF YOU pass it in via the constructor you end up with a RangeError, therefore this is the only way. Once again not certain how this code performs in comparison to spaghetti code.
const Game = GameFactory(io);
Finally, you can now instantiate instances of your class:
const newGame = new Game(hostParticipant);
If anyone have improvements or thoughts, please leave me a comment. Still uncertain about the quality of this code.

Nodejs reference to class object from other file

I'm learning nodejs now and Im wondering how can I reference to the object created in a other file
for example:
I have a file with my class user.js which I'm exporting
module.exports = class Username {
constructor(name, lastProjects) {
this.current.name = name;
this.current.lastProjects = lastProjects;
}
};
name.handler.js I can not export it this to oder files
const Alexa = require('alexa-sdk');
const User = require('../models/user.model');
module.exports = Alexa.CreateStateHandler(StatesConst.NAME, {
'NewSession': function () {
this.emit('NewSession'); // Uses the handler in newSessionHandlers
},
'MyNameIsIntent': function() {
var user = new User.Username("Anna", ["project1", "project2"]);
this.emit(':ask', "Hi "+User.Username.name);
}
}
user.handler.js I tottaly dont know how can I write a reference to my new created object Username
const Alexa = require('alexa-sdk');
const User = require('../models/user.model');
module.exports = Alexa.CreateStateHandler(StatesConst.NEWSTATE, {
'NewSession': function () {
this.emit('NewSession'); // Uses the handler in newSessionHandlers
},
'MyUserIntent': function() {
this.emit(':ask', "My username is "+User.Username.name);
}
}
How can I reference to new user object in other files in my programm ? I want that everytime my user starts a program I will get a new user object and I could reference and change attributes in every other file. I would be very appreciate for a help :)
module.exports = Username;
const Alexa = require('alexa-sdk'); // ok?
const User = // in linux, to go 1 dir up is just '.'
console.log(require('../models/user.model'))
see if you get a message = '[Function: Username]'".
If not you will see a message telling you something. Maybe no found file.
Since you are exporting Username by assigning it to module.exports, you need to use it as new User in your handlers. For example, name.handler.js would become:
const Alexa = require('alexa-sdk');
const User = require('../models/user.model');
module.exports = Alexa.CreateStateHandler(StatesConst.NAME, {
'NewSession': function () {
this.emit('NewSession'); // Uses the handler in newSessionHandlers
},
'MyNameIsIntent': function() {
var user = new User("Anna", ["project1", "project2"]);
this.emit(':ask', "Hi "+user.name);
}
}
Note that in the this.emit line, you should use the instance of User, not the class itself. (so user instead of User).
Moreover, in your user.handler.js file, you don't instantiate any User. You will get a Cannot read property 'name' of undefined
Finally if your Username class is in user.js, your require statement should look like require('../models/user'), not require('../models/user-model')

How to include one .js file into another in Node.js? [duplicate]

I'm writing a simple server for Node.js and I'm using my own class called User which looks like:
function User(socket) {
this.socket = socket;
this.nickname = null;
/* ... just the typical source code like functions, variables and bugs ... */
this.write = function(object) {
this.socket.write(JSON.stringify(object));
}
};
and then later in the process I'm instantiating it a lot:
var server = net.createServer(function (socket) {
/* other bugs */
var user = new User(socket);
/* more bugs and bad practise */
});
Can I move my User class definition to another javascript file and "include" it somehow?
You can simply do this:
user.js
class User {
//...
}
module.exports = User // 👈 Export class
server.js
const User = require('./user.js')
let user = new User()
This is called CommonJS module.
ES Modules
Since Node.js version 14 it's possible to use ES Modules with CommonJS. Read more about it in the ESM documentation.
user.mjs (👈 extension is important)
export default class User {}
server.mjs
import User from './user.mjs'
let user = new User()
Using ES6, you can have user.js:
export default class User {
constructor() {
...
}
}
And then use it in server.js
const User = require('./user.js').default;
const user = new User();
Modify your class definition to read like this:
exports.User = function (socket) {
...
};
Then rename the file to user.js. Assuming it's in the root directory of your main script, you can include it like this:
var user = require('./user');
var someUser = new user.User();
That's the quick and dirty version. Read about CommonJS Modules if you'd like to learn more.
Another way in addition to the ones provided here for ES6
module.exports = class TEST{
constructor(size) {
this.map = new MAp();
this.size = size;
}
get(key) {
return this.map.get(key);
}
length() {
return this.map.size;
}
}
and include the same as
var TEST= require('./TEST');
var test = new TEST(1);
If you append this to user.js:
exports.User = User;
then in server.js you can do:
var userFile = require('./user.js');
var User = userFile.User;
http://nodejs.org/docs/v0.4.10/api/globals.html#require
Another way is:
global.User = User;
then this would be enough in server.js:
require('./user.js');

Javascript: inheriting encapsulated variables

Edit: Removed higher-level ideas, included problem-specific and less-transferable code.
I implemented my DAL using DAO's. My application hooks in to various databases (mostly for legacy reasons). In order to facilitate efficient and intelligent usage of connections, I use a ConnectionBroker singleton to manage the various connections that may (or may not be) open. This ConnectionBroker is then injected into the DAO's where they can request control of a particular connection object, request new connections, ect.
From an inheritence POV, I'd like something like:
AbstractDbConnection
|-- MongoDbConnection
|-- MsSqlConnection
|-- CouchDbConnection
|-- ...
Where AbstractDbConnection defines an interface, and implements some shared event-based logic.
var EventEmitter = require('events').EventEmitter;
module.exports = function AbstractDbConnection(host, port, database, login, ...) {
// private
var state = StatesEnum.Closed; // StatesEnum = {Open: 0, Closed: 1, ..}; Object.freeze(StatesEnum);
// api that must be overwritten
this.connect = function connect() {throw new ...}
this.disconnect = function disconnect() {throw new ...}
... <more>
this.getState = function() { return state; }
}
AbstractDbConnection.prototype.__proto__ = EventEmitter.prototype;
And then I implement the interface using driver-specific code:
var mssqldriver = require('mssqldriver'), //fictitious driver
AbstractDbConnection = require(__dirname + '/blah/AbstractDbConnection');
module.exports = function MsSqlConnection(host, port, database, login, ...) {
var me = this;
// implement using driver
this.connect = function connect() {...}
this.disconnect = function disconnect() {...}
... <more>
driverSpecificConnection.on('driverSpecificOpenEvent', function() {
me.emit('open'); // relay driver-specific events into common events
state = StatesEnum.Open; // how ??
}
...
}
MsSqlConnection.prototype.__proto__ = new AbstractDbConnection();
But clearly I want to protect the state property from changing inadvertently.
Just listen for the open event in the "abstract" constructor!
var EventEmitter = require('events').EventEmitter;
module.exports = AbstractDbConnection;
var StatesEnum = module.exports.StatesEnum = Object.freeze({
Open: 0, Closed: 1, …
});
function AbstractDbConnection(host, port, database, login, …) {
// private
var state = StatesEnum.Closed;
EventEmitter.call(this);
this.getState = function() { return state; }
this.on('open', function(e) {
state = StatesEnum.Open;
});
}
AbstractDbConnection.prototype = Object.create(EventEmitter.prototype);
// api that must be overwritten
AbstractDbConnection.prototype.connect = function connect() {throw new …};
AbstractDbConnection.prototype.disconnect = function disconnect() {throw new …};
var Mssqldriver = require('mssqldriver'), //fictitious driver
AbstractDbConnection = require(__dirname + '/blah/AbstractDbConnection');
module.exports = MsSqlConnection;
function MsSqlConnection(host, port, database, login, …) {
AbstractDbConnection.call(this);
this.driver = new Mssqldriver(…);
this.driver.on('driverSpecificOpenEvent', this.emit.bind(this, 'open'));
…
}
MsSqlConnection.prototype = Object.create(AbstractDbConnection.prototype);
MsSqlConnection.prototype.connect = function connect() {…};
MsSqlConnection.prototype.disconnect = function disconnect() {…};
You can use the module pattern to do this.
var transport_module = function() {
var mileage = 0; // private
return {
transport : function(distance) {
mileage += distance;
}
};
}
//use it
var car = transport_module(),
boat = transport_module(),
motorcycle = transport_module();
car.transport(10);
boat.transport(5);
motorcycle.transport(20);
The variable mileage is not visible to any other javascript code. Like a private java/C++ class variable. However, I would think about whether you need this kind of protection. I use modules a lot but not for small objects like class instances in java/C++.

Categories

Resources