How to use RxJs with Socket.IO on event - javascript

I want to use RxJS inside of my socket.on('sense',function(data){});. I am stuck and confused with very few documentation available and my lack of understanding RxJS. Here is my problem.
I have a distSensor.js that has a function pingEnd()
function pingEnd(x){
socket.emit("sense", dist); //pingEnd is fired when an Interrupt is generated.
}
Inside my App.js I have
io.on('connection', function (socket) {
socket.on('sense', function (data) {
//console.log('sense from App4 was called ' + data);
});
});
The sense function gets lots of sensor data which I want to filter using RxJS and I don't know what should I do next to use RxJs here. Any pointers to right docs or sample would help.

You can use Rx.Observable.fromEvent (https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/fromevent.md).
Here's how I did a similar thing using Bacon.js, which has a very similar API: https://github.com/raimohanska/bacon-minsk-2015/blob/gh-pages/server.js#L13
So in Bacon.js it would go like
io.on('connection', function(socket){
Bacon.fromEvent(socket, "sense")
.filter(function(data) { return true })
.forEach(function(data) { dealWith(data) })
})
And in RxJs you'd replace Bacon.fromEvent with Rx.Observable.fromEvent.

I have experienced some strange issues using the fromEvent method, so I prefer just to create my own Observable:
function RxfromIO (io, eventName) {
return Rx.Observable.create(observer => {
io.on(eventName, (data) => {
observer.onNext(data)
});
return {
dispose : io.close
}
});
I can then use like this:
let $connection = RxfromIO(io, 'connection');

You can create an Observable like so:
var senses = Rx.Observable.fromEventPattern(
function add (h) {
socket.on('sense',h);
}
);
Then use senses like any other Observable.

Simply use fromEvent(). Here is a full example in Node.js but works the same in browser. Note that i use first() and takeUntil() to prevent a memory leak: first() only listens to one event and then completes. Now use takeUntil() on all other socket-events you listen to so the observables complete on disconnect:
const app = require('express')();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
const Rx = require('rxjs/Rx');
connection$ = Rx.Observable.fromEvent(io, 'connection');
connection$.subscribe(socket => {
console.log(`Client connected`);
// Observables
const disconnect$ = Rx.Observable.fromEvent(socket, 'disconnect').first();
const message$ = Rx.Observable.fromEvent(socket, 'message').takeUntil(disconnect$);
// Subscriptions
message$.subscribe(data => {
console.log(`Got message from client with data: ${data}`);
io.emit('message', data); // Emit to all clients
});
disconnect$.subscribe(() => {
console.log(`Client disconnected`);
})
});
server.listen(3000);

ES6 one liner that i use, using ES7 bind syntax:
(read $ as stream)
import { Observable } from 'rxjs'
// create socket
const message$ = Observable.create($ => socket.on('message', ::$.next))
// translates to: Observable.create($ => socket.on('message', $.next.bind(this)))
// filter example
const subscription = message$
.filter(message => message.text !== 'spam')
//or .filter(({ text }) => text !== 'spam')
.subscribe(::console.log)

You can use rxjs-dom,
Rx.DOM.fromWebSocket(url, protocol, [openObserver], [closeObserver])
// an observer for when the socket is open
var openObserver = Rx.Observer.create(function(e) {
console.info('socket open');
// Now it is safe to send a message
socket.onNext('test');
});
// an observer for when the socket is about to close
var closingObserver = Rx.Observer.create(function() {
console.log('socket is about to close');
});
// create a web socket subject
socket = Rx.DOM.fromWebSocket(
'ws://echo.websocket.org',
null, // no protocol
openObserver,
closingObserver);
// subscribing creates the underlying socket and will emit a stream of incoming
// message events
socket.subscribe(
function(e) {
console.log('message: %s', e.data);
},
function(e) {
// errors and "unclean" closes land here
console.error('error: %s', e);
},
function() {
// the socket has been closed
console.info('socket closed');
}
);

Related

socket create multiple connection even when only one user connected

I'm new to Socket i try to receive image from server using socket-client-io . i can able to get image but on server side it shows multiple user created but i'm only seeing it in one tab but it shows like 10 users connected(like that)
Code:
const socket = io('ws://localhost:5000')
socket.on('connnection', () => {
console.log('connected to server');
})
socket.on('disconnect', () => {
console.log('Socket disconnecting');
})
useEffect(() => {
const handler = (message) => {
setImage(message.image);
console.log(message);
};
socket.on("send_image", handler);
return () => socket.off("send_image", handler); // assuming `.off` deregisters a callback
}, []);
it works fine and i can get images but it create multiple connection for single tab kindly guide me how to avoid this

What is the correct approach to modify react state from inside a socket listener?

I am making a real-time chat app with react native and socket.io and trying to update the messages state array with new data whenever the 'chat message' event fires. But inside the callback function, the value of messages is always an empty array, however, the desired behavior is that it contains the list of previous messages and just pushes the new data in the array.
I can't figure out what's going on here. I think maybe it's because of the callback function's closure. I wanted to know what is the correct way to modify state variables from inside the event listeners?
const [messages, setMessages] = useState([]);
const _onMessage = (data) => {
console.log(`${client.id} : ${JSON.stringify(data)}`);
console.log('new message');
};
const connect_socket = () => {
client.connect();
client.on('connect', _onConnect);
client.on('disconnect', _onDisconnect);
client.on('chat message', (data) => {
console.log(`${client.id} : ${JSON.stringify(data)}`);
console.log('new message');
console.log(messages); // this is always empty despite it being the state variable
const temp = messages.slice();
console.log(temp);
temp.push(data);
console.log(temp);
setMessages(temp);
});
};
const remove_socket = () => {
client.disconnect();
// client.removeListener('connect', _onConnect);
// client.removeListener('disconnect', _onDisconnect);
// client.removeListener('chat message', _onMessage);
client.off('connect');
client.off('disconnect');
client.off('chat message');
};

How to get "disconnecting" event Nest SocketIO

I have a problem to get all room that socket client currently in when this client disconnect, by using
async handleDisconnect(client: Socket) {
console.log(client.rooms) // result is {}
}
but Nest-SocketIO only return list rooms as {}.
As I known, in socketIO we can use:
socket.on("disconnecting", ()=>{
console.log(socket.rooms // return all room current
}
How can I use this features in NestJS-SOcketIO ?
Thanks for all.
I got this working by attaching event handler on connection.
handleConnection(client: Socket, ...args: any[]) {
this.logger.log(`Client connected: ${client.id}`);
client.on('disconnecting', (reason) => {
this.logger.log(`DISCONNECTING: ${Array.from(client.rooms)}`); // Set { ... }
});
}

How to detect which message was sent from the Websocket server

I have a small web application listening for incoming messages from a Websocket server. I receive them like so
const webSocket = new WebSocket("wss://echo.websocket.org");
webSocket.onopen = event => webSocket.send("test");
webSocket.onmessage = event => console.log(event.data);
but the sending server is more complex. There are multiple types of messages that could come e.g. "UserConnected", "TaskDeleted", "ChannelMoved"
How to detect which type of message was sent? For now I modified the code to
const webSocket = new WebSocket("wss://echo.websocket.org");
webSocket.onopen = event => {
const objectToSend = JSON.stringify({
message: "test-message",
data: "test"
});
webSocket.send(objectToSend);
};
webSocket.onmessage = event => {
const objectToRead = JSON.parse(event.data);
if (objectToRead.message === "test-message") {
console.log(objectToRead.data);
}
};
So do I have to send an object from the server containing the "method name" / "message type" e.g. "TaskDeleted" to identify the correct method to execute at the client? That would result in a big switch case statement, no?
Are there any better ways?
You can avoid the big switch-case statement by mapping the methods directly:
// List of white-listed methods to avoid any funny business
let allowedMethods = ["test", "taskDeleted"];
function methodHandlers(){
this.test = function(data)
{
console.log('test was called', data);
}
this.taskDeleted = function(data)
{
console.log('taskDeleted was called', data);
}
}
webSocket.onmessage = event => {
const objectToRead = JSON.parse(event.data);
let methodName = objectToRead.message;
if (allowerMethods.indexOf(methodName)>=0)
{
let handler = new methodHandlers();
handler[methodName](data);
}
else
{
console.error("Method not allowed: ", methodName)
}
};
As you have requested in one of your comments to have a fluent interface for the websockets like socket.io.
You can make it fluent by using a simple PubSub (Publish Subscribe) design pattern so you can subscribe to specific message types. Node offers the EventEmitter class so you can inherit the on and emit events, however, in this example is a quick mockup using a similar API.
In a production environment I would suggest using the native EventEmitter in a node.js environment, and a browser compatible npm package in the front end.
Check the comments for a description of each piece.
The subscribers are saved in a simple object with a Set of callbacks, you can add unsubscribe if you need it.
note: if you are using node.js you can just extend EventEmitter
// This uses a similar API to node's EventEmitter, you could get it from a node or a number of browser compatible npm packages.
class EventEmitter {
// { [event: string]: Set<(data: any) => void> }
__subscribers = {}
// subscribe to specific message types
on(type, cb) {
if (!this.__subscribers[type]) {
this.__subscribers[type] = new Set
}
this.__subscribers[type].add(cb)
}
// emit a subscribed callback
emit(type, data) {
if (typeof this.__subscribers[type] !== 'undefined') {
const callbacks = [...this.__subscribers[type]]
callbacks.forEach(cb => cb(data))
}
}
}
class SocketYO extends EventEmitter {
constructor({ host }) {
super()
// initialize the socket
this.webSocket = new WebSocket(host);
this.webSocket.onopen = () => {
this.connected = true
this.emit('connect', this)
}
this.webSocket.onerror = console.error.bind(console, 'SockyError')
this.webSocket.onmessage = this.__onmessage
}
// send a json message to the socket
send(type, data) {
this.webSocket.send(JSON.stringify({
type,
data
}))
}
on(type, cb) {
// if the socket is already connected immediately call the callback
if (type === 'connect' && this.connected) {
return cb(this)
}
// proxy EventEmitters `on` method
return super.on(type, cb)
}
// catch any message from the socket and call the appropriate callback
__onmessage = e => {
const { type, data } = JSON.parse(e.data)
this.emit(type, data)
}
}
// create your SocketYO instance
const socket = new SocketYO({
host: 'wss://echo.websocket.org'
})
socket.on('connect', (socket) => {
// you can only send messages once the socket has been connected
socket.send('myEvent', {
message: 'hello'
})
})
// you can subscribe without the socket being connected
socket.on('myEvent', (data) => {
console.log('myEvent', data)
})

How to use socket io inside class

I'm trying to create game using socket io. I want to implement socket inside server.js but I would like to keep events inside Room.js.:
server.js
const io = require('socket.io').listen(server)
io.sockets.on('connection', socket => {
socket.on('join room', data => {
let room = new Room(data.id, socket)
socket.join(`room${data.id}`)
}
}
Room.js
class Room{
constuctor(id, socket){
this.id = id,
this.socket = socket,
this.handlerOfEvents()
}
handlerOfEvents() {
this.socket.on('new player connected', data => {
console.log('New player connected!')
}
}
}
I tried do as above but it doesn't work:
\node_modules\has-binary2\index.js:30
function hasBinary (obj) {
^
RangeError: Maximum call stack size exceeded
Is there any solution to do sth like this?
Or maybe there is another perfect way to implement events for particular room.
This problem occurs when I try assign socket to this.socket but when I put it directly as argument like below:
class Room{
constuctor(id, socket){
this.id = id,
//this.socket = socket,
this.handlerOfEvents(socket)
}
handlerOfEvents(socket) {
socket.on('new player connected', data => {
console.log('New player connected!')
}
}
}
then just invoke this method in server.js
socket.on('joined new player', data => {
...
room.handlerOfEvents(socket)
}
so, this solution is working for now. But if this is proper way?
Issue from link in comments didn't solve my problem unfortunately.
Thanks for helping!

Categories

Resources