I am currently developing a small app that basically copies whatsapp's functionality, beeing a simple chat.
For that I use websockets and I've stumbled upon several tutorials and stackoverflow posts that use this kind of code for the websocket, which I then used for my project.
private create(url) {
const websocket = new WebSocket(url);
const observable: Observable<MessageEvent> = Observable.create(
(observerInLambda: Observer<MessageEvent>) => {
websocket.onmessage = observerInLambda.next.bind(observerInLambda);
websocket.onerror = observerInLambda.error.bind(observerInLambda);
websocket.onclose = observerInLambda.complete.bind(observerInLambda);
return websocket.close.bind(websocket); // When unsubbing from the observable the websocket is closed
}
);
const observer = {
next: (data: Object) => {
if (websocket.readyState === WebSocket.OPEN) {
websocket.send(JSON.stringify(data));
}
},
error: (err: any) => {
console.log(err);
},
complete: () => {
console.log("complete");
}
};
return Subject.create(observer, observable);
}
This works mostly fine, appart from the disconnecting on unsubscribe, but thats not my question. I get that i have to use some sort of callback/event style code like websocket.onmessage = (data:Object) => doStuff(data); to read from the websocket, but what confuses me why I would have to use a an observer, whose .next function i call, which in turn calls websocket.send. Why not simply call websocket.send instead, which isn't a asynchronous function anyway...
To me it seems that the whole pattern including the subject is a bit overkill for what I am doing? Am I getting something wrong, or is simply not useful in my case?
Related
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)
})
I have an app that uses graphql subscriptions for chat functionality. I have managed to successfully get the subscription working however after introducing the withFilter function in order to filter which clients the messages get sent to I am getting the following error on the frontend:
Subscription field must return Async Iterable. Received: undefined
Here is my subscription resolver:
const { PubSub, withFilter } = require('graphql-yoga');
const pubsub = new PubSub();
pubsub.ee.setMaxListeners(30);
const Subscription = {
detailedConversation: withFilter(
() => pubsub.asyncIterator('detailedConversation'),
(payload, args) => {
return true;
}
)
};
module.exports = {
Subscription,
pubsub
};
As the second parameter of withFilter has to be a function that returns a boolean, I have just set this to return true for the time being.
Graphql-yoga uses graphql-subscriptions under the hood and after reading the documentation on implementation here I can't see what i'm doing wrong?
FYI the error occurs when attempting to subscribe for the first time to a conversation, not whilst sending a message or anything
I know this question is old but gonna give my solution, to others that might come looking for exactly the same solution...
First thing to note is that I'm using the graphql-redis-subscriptions implementation instead of the default implementation.
userUpdated: {
subscribe: withFilter((_, args, { pubsub }) => pubsub.asyncIterator('userUpdated'), (payload, vars) => vars.usersId.includes(payload.userUpdated.id))
}
documentation link
you just check the two arguments are the same then return your actions.
UserUpdated:{
withFilter(
() => pubsub.asyncIterator('UserUpdated'),
(payload, variables) => {
return (payload.UserUpdated.id === variables.channelid);
},
),
}
I want to split my rabbitMQ connection code and call it across different components, so that it (the connection and channel) only initializes ONCE and I can use it whenever instead of having to open the connection again when I want to use it.
What happens right now is, I call the below's code function over and over again everytime I want to pass something to my exchange and queue. (so if I want to pass 20 individual data to rabbitMQ, I ended up opening and closing both the connection and channel 20 times)
Any solutions?
const exchange = "Exchange";
const queue = "Queue";
const passSomeData= async payload => {
amqp = require("amqplib").connect("amqp://localhost");
let ch;
let connection;
let publish = amqp
.then(function(conn) {
connection = conn;
return conn.createConfirmChannel();
})
.then(function(chn) {
ch = chn;
ch.assertQueue(queue, { durable: true });
return ch.assertExchange(exchange, "topic", { durable: true });
})
.then(function() {
const data = {
content: "x",
title: "y",
};
ch.bindQueue(queue, exchange, "routingKey");
return ch.publish(exchange, "routingKey", Buffer.from(JSON.stringify(data)), {
persistent: true
});
})
.then(() => {
setTimeout(function() {
connection.close();
}, 250);
});
};
module.exports = passSomeData;
Answer copied from here
This is a general Javascript question and not one specific to RabbitMQ or the amqplib library.
I believe you can open a connection at the module level and use that within your passSomeData method. Or, passSomeData can lazily open a connection if the module-level "connection" variable is null, and then re-use that connection.
At some point you may need to use a connection pool, but that depends on your use-case and workload.
NOTE: the RabbitMQ team monitors the rabbitmq-users mailing list and only sometimes answers questions on StackOverflow.
I am building an app with socket.io and typescript. As always I have created server and client but now I'm facing weird issue with my server code. My server is listening on 'connection' event and as callback creates new class instance and invokes his onConnect method. In this function it invokes another method - 'bindHandlers'. In this function socket listens to his events.
And this is my problem: if i pass callback to 'draw' event as an anonymous function it works as expected, but if i use my class method it sends events back to to the client instead of broadcasting it. I want to make my code more modular and this issue is blocking me for now.
main file:
io.on("connection", SocketService.createInstance(db).onConnect);
simplified socket file:
export class SocketService {
private socket: Socket | null = null;
constructor(private db: DB) {}
static createInstance = (db: DB) => {
return new SocketService(db);
};
onConnect = (socket: Socket) => {
this.socket = socket;
const username = socket.handshake.query.user;
console.log(`${username} connected ${socket.id}`);
this.bindHandlers(socket);
};
private bindHandlers = (socket: Socket) => {
if (!this.socket) return console.log("socket is undefined");
socket.on("draw", this.onDraw);
// if I swap with code below it works properly
// socket.on("draw", data => {
// socket.broadcast.emit("draw", data);
// });
};
private onDraw = (data: DrawingPoint) => {
const username = this.socket!.handshake.query.user;
const { group } = data;
this.socket!.broadcast.emit("draw", data);
};
The reason why it is working with:
socket.on("draw", data => {
socket.broadcast.emit("draw", data);
});
is because you are using arrow function which for context (this) will have surrounding context, but when defining an event handler such as: socket.on("draw", this.onDraw); context will not be anymore instance of SocketService. Play with it a little bit and debug it to see what will be the context in a case when you are calling it with that.
One solution would be to set the context explicitly such as:
socket.on("draw", this.onDraw.bind(this));
Keep in mind that context of the method/function in JS depends on how method was called.
I'm using Flux and WebSocket in my Reactjs application and during implementation I've encountered some problems.
Questions:
Assuming I have a set of a set of actioncreators and a store for managing the WebSocket connection, and that the connection is started in a actioncreator (open(token)), where should I put my conn.emit's and how do I get other actions access to my connection object so that they can send data to the backend?
Do I have to pass it as an argument to the actions that are called in the views (eg. TodoActions.create(conn, todo)) or is there a smarter way?
Current code is here
I'm using ES6 classes.
If I have omitted anything necessary in the gist, please let me know.
EDIT:
This is what I have concocted so far based on glortho's answer:
import { WS_URL } from "./constants/ws";
import WSActions from "./actions/ws";
class WSClient {
constructor() {
this.conn = null;
}
open(token) {
this.conn = new WebSocket(WS_URL + "?access_token=" + token);
this.conn.onopen = WSActions.onOpen;
this.conn.onclose = WSActions.onClose;
this.conn.onerror = WSActions.onError;
this.conn.addEventListener("action", (payload) => {
WSActions.onAction(payload);
});
}
close() {
this.conn.close();
}
send(msg) {
return this.conn.send(msg);
}
}
export default new WSClient();
You should have a singleton module (not a store or an action creator) that handles opening the socket and directing traffic through. Then any action creator that needs to send/receive data via the socket just requires the module and makes use of its generic methods.
Here's a quick and dirty untested example (assuming you're using CommonJS):
SocketUtils.js:
var SocketActions = require('../actions/SocketActions.js');
var socket = new WebSocket(...);
// your stores will be listening for these dispatches
socket.onmessage = SocketActions.onMessage;
socket.onerror = SocketActions.onError;
module.exports = {
send: function(msg) {
return socket.send(msg);
}
};
MyActionCreator.js
var SocketUtils = require('../lib/SocketUtils.js');
var MyActionCreator = {
onSendStuff: function(msg) {
SocketUtils.send(msg);
// maybe dispatch something here, though the incoming data dispatch will come via SocketActions.onMessage
}
};
Of course, in reality you'll be doing better and different things, but this gives you a sense of how you might structure it.