How to use feathersjs-primus to setup websocket connection - node.js

I am trying to setup websocket by feathers js + primus following from this link: https://docs.feathersjs.com/real-time/primus.html.
But I don't know how to get spark instance from primus in server side. Below is my server code:
class SocketService {
create(data, params, callback){
...
}
}
module.exports = function(){
const app = this
let ss = new SocketService()
app.use('socket-shell', ss);
}
In above code, server can get the message from client in create() method. But how can I get spark instance from primus in that method? I want to use spark.write method to send message back to the client.
Below is the server code for configuring feathers services:
app
.use(compress())
.options('*', cors())
.use(cors())
// .use(favicon(path.join(app.get('public'), 'favicon.ico')))
.use('/', serveStatic(app.get('public')))
.use(bodyParser.json())
.use(bodyParser.urlencoded({extended: true}))
.configure(hooks())
.configure(rest())
.configure(primus({
transformer: 'websockets',
timeout: false,
}, (primus) => {
app.use('socket-shell', function(socket, done){
// Exposing a request property to services and hooks
socket.request.feathers.referrer = socket.request.referrer;
done();
});
}))
.configure(services)
.configure(middleware);
Below code is used to registering a event listener on server side but it can't receive event from client:
class SocketService {
constructor(options) {
this.events = [ 'someevent','serverevent' ]
}
setup(app) {
this.app = app;
let socketService = app.service('socket-shell')
socketService.addListener('serverevent', this.serverevent)
}
serverevent(msg){
console.log('serverevent', msg)
}
In client code, I use below code to emit message to server:
var primus = new Primus('http://localhost:3030');
var app = feathers()
.configure(feathers.hooks())
.configure(feathers.primus(primus));
var messageService = app.service('/socket-shell');
messageService.emit('serverevent', {msg:'this is client'})
what wrong with above code?

Ideally you wouldn't use the socket connection directly in your service. A service shouldn't know about how it is being accessed. There are two options:
Set up and send your own events in app.configure(primus(
Have the service emit its own custom events
--
class SocketService {
constructor() {
this.events = [ 'someevent' ];
}
create(data, params, callback){
this.emit('someevent', 'data');
}
}

Related

How to properly handle errors on subscriptions with Apollo Server?

I have an Express + Apollo Server backend. I enabled subscriptions on it using ws and graphql-ws. Everything is working fine.
Now, I would like to handle resolvers errors properly: hide backend details in production, change message based on error type, add a unique ID, etc. On regular mutations, I'm able to do so using the formatResponse function.
On subscriptions, I can't find where I could do it. All I need is a function called before sending data to the client where I have access to data and errors.
How can I do that?
Here's how the WS Server is created:
// Create Web Socket Server
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql'
});
const serverCleanup = graphqlWS.useServer(
{
schema: graphqlApp.schema,
context: async (ctx: any) => {
try {
// ...Some auth checking...
return context;
} catch (e) {
throw new ApolloAuthenticationError('you must be logged in');
}
}
},
wsServer
);
And an example of event sending:
import {PubSub} from 'graphql-subscriptions';
// ...
Subscription: {
tree: {
subscribe: withFilter(
() => pubsub.asyncIterator('some_id'),
(payload, variables) => {
const canReturn = true;
//...Some filtering logic...
return canReturn;
}
)
}
},

Two different websockets working together

I'm trying to figure out how to implement a two separate websockets together. Not sure if this possible or not, but I have a websocket that works on it's own in from node.js file to angular another node.js file that uses Kraken (crypto exchange) websocket that also works in it's own file. I'm trying to consolidate them both together so that whenever a event onChange comes from Kraken websocket, I can relay that data to angular with angular socket.io websocket. Trying to do something like this
const webSocketClient = new WebSocket(connectionURL);
webSocketClient.on("open", function open() {
webSocketClient.send(webSocketSubscription);
});
webSocketClient.on("message", function incoming(wsMsg) {
const data = JSON.parse(wsMsg);
let io = require("socket.io")(server, {
cors: {
origin: "http://localhost:4200",
methods: ["GET", "POST"],
allowedHeaders: ["*"],
credentials: true,
},
});
io.on("connection", (socket) => {
const changes = parseTrades(data);
socketIo.sockets.emit(connection.change, changes);
Log whenever a user connects
console.log("user connected");
socket.emit("test event", JSON.stringify(changes));
});
console.log("DATA HERE", data[0]);
});
webSocketClient.on("close", function close() {
console.log("kraken websocket closed");
});
Although doing this doesnt relay the data to frontend and gives me a memory leak. Is there some way I can accomplish this?
I would probably split up the task a little bit. So have a service for the kraken websocket and maybe a service for your own socket, then have them communicate via observables, that you can also tap into from the front end to display data you want.
#Injectable()
export class KrakenService{
private webSocketClient : WebSocket | null;
private messages$ = new BehaviorSubject<any>(null); // give it a type
openConnection(){
// is connectionUrl from environment ??
this.webSocketClient = new WebSocket(connectionURL);
webSocketClient.on("open", function open() {
/// is webSocketSubscription from environment ??
webSocketClient.send(webSocketSubscription);
});
webSocketClient.on("message", function incoming(wsMsg) {
const data = JSON.parse(wsMsg);
this.messages$.next(data);
console.log("DATA HERE", data[0]);
});
webSocketClient.on("close", function close() {
console.log("kraken websocket closed");
this.webSocketClient = null;
});
}
getKrakenMessages(){
if(webSocketClient == null) this.openConnection();
return messages$.asObserbable();
}
}
So now when you want to either read the web socket messages or use them with the other socket you just subscribe to the krakenService.getKrakenMessages();
Then you can do something similar with you local service as well. Have something that opens connections, and one that emits messages. The example you showed it would open up a connection every time you got a message, so keep those logics separated. And reuse existing connection.

Node.js - Socket.io-client does not emit data

I'm trying to build a simple socket.io-client using nodejs, but I'm facing a trouble...
I'm connecting with the socket.io (server), but I can't emit any data. Follow bellow my simple code:
Client Side:
var socketIO = require('socket.io-client')('http://serverdns:3000');
socketIO.on("dashboard", (data) => {
console.log(data);
});
socketIO.on('connect', function(){
console.log("Connected with the translator service.");
socketIO.emit('dashboard', 'teste');
});
socketIO.on('disconnect', function(){
console.log("Disconnected from the translator service");
});
socketIO.on('error', function(err){
console.log(err);
});
Socket.io version: 2.1.1 (I've tried to use old versions but the same problem happens).
The connect event works, the log "Connected with the translator service." is generated, but emit does not work.
Server side:
var server = require('http').createServer();
var ioServer = require('socket.io')(server, { pingInterval: 2000, pingTimeout: 60000, cookie: false });
class SocketServer {
constructor() {
var self = this;
ioServer.on('connection', function (client) {
console.log('[SOCKETIO] AVAILABLE');
client.on('main', self.main);
client.on('disconnect', self.disconnect);
});
server.listen(3000);
}
getSocket(){
return ioServer;
}
main(data) {
console.log(data);
}
disconnect() {
console.log("[SOCKETIO] DISCONNECTED");
}
}
module.exports = new SocketServer();
Anyone can help me?
Are there anything I'm not seeing?
Thanks a lot.
Right now you are emitting to the event dashboard from client. But on the server side you have no code that is handling that event. You are currently logging the event main which does not match with what you're emitting. Try client.on('dashboard', self.dashboard). Make your own dashboard function.

how to link laravel presence channel with nodejs socket.io

I'm trying to make a SPA with vuejs in frontend and laravel in backend and nodejs server to serve socket.io connections.
I can broadcast events from laravel public and private channels to socket.io through redis and emit it to the client side.
Previously When I made an app with laravel and vuejs that comes with laravel I managed to implement a chat room using laravel echo and laravel echo server but in SPA I couldn't find a way to do it. Laravel Echo has methods .here().joining().leaving() that allow you to build a chatroom. but how can I implement this methods with socket.io ?
on client side I'm using vue-socket.io.
This is vue component that listen to events from socket.io server.
<template>
<div class="container products">
<products></products>
</div>
</template>
<script>
import Products from './products/Products.vue'
export default {
created() {
this.$http.post('auth/token', {
xhrFields: {
withCredentials: true
}
})
.then(response => {
this.$socket.emit('authenticate', {
token: response.data.token
});
})
.catch(error => {
console.log(error.response)
});
},
components: {
'products': Products
},
sockets: {
message(msg) {
console.log(msg)
}
}
}
</script>
<style>
.products {margin-top:60px}
</style>
MessageSentEvent.php
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Support\Facades\Auth;
class MessageSentEvent implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($message)
{
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
if(Auth::check())
return new PresenceChannel('Chat');
}
public function broadcastAs()
{
return 'message';
}
}
routes/channels.php
<?php
use Illuminate\Support\Facades\Auth;
Broadcast::channel('Chat', function() {
if(Auth::check())
return Auth::user();
});
This is nodejs server
server.js
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var redis = require('redis');
var socketioJwt = require('socketio-jwt');
require('dotenv').config({
path: '../backend/.env'
});
// Accept connection and authorize token
io.on('connection', socketioJwt.authorize({
secret: process.env.JWT_SECRET,
timeout: 15000
}));
/* When authenticated, listen to events that come from laravel throug redis */
io.on('authenticated', function (socket) {
console.log('a user connected');
socket.emit('id', socket.decoded_token.sub);
var redisClient = redis.createClient();
redisClient.subscribe('presence-Chat');
redisClient.on('message', (channel, message) => {
console.log('channel: ' + channel + '\nmessage: ' + message);
socket.emit('message', JSON.parse(message).data.message);
});
socket.on('logout', () => {
redisClient.quit();
console.log('a user quit');
});
});
io.on('disconnect', function (){
console.log('a user disconnected');
});
server.listen(3000, function() {
console.log('listening on port 3000');
});

Reconnect socket on user login

I work with a setup created by create-react-app and use flux for data management and the application needs to implement socket on the client side (I use socket.io for this purpose).
Currently the socket is initialised in a Socket.js file the following way:
import io from 'socket.io-client';
import { remoteUrl } from './constants/RemoteUrl';
import SocketWorker from './utilities/SocketWorker';
let socket = io.connect(remoteUrl + '?role=user');
socket.on('statusChange', (data) => {
return SocketWorker.receiveOrderStatusChange(data);
})
export { socket };
It does work, however the problem is that it only tries to connect to the server once, when the site is loaded. When the user opens the site unauthenticated it does not connect and misses to reconnect, thus the connection is not established and socket events are not received
I have tried to create a class instead and react an API for reconnect on the object, like:
import io from 'socket.io-client';
import { remoteUrl } from './constants/RemoteUrl';
import SocketWorker from './utilities/SocketWorker';
function Socket() {
this.socket = io.connect(remoteUrl + '?role=user');
this.reconnect = () => {
this.socket = io.connect(remoteUrl + '?role=user');
}
}
let socket = new Socket();
socket.socket.on('statusChange', (data) => {
return SocketWorker.receiveOrderStatusChange(data);
})
export { socket };
I tried to call the Socket.reconnect() method, although it did not work and connection was not established either. Any idea or alternative solution?
The way I managed to solve this if anyone face the same problem with the Socket.io API:
First, you should encapsulate your Socket into an object created by the constructor, but there is no need to create a reconnect method as the connection is present already (and the auth can be handled through emitted events I will describe below) :
import io from 'socket.io-client';
import { remoteUrl } from './constants/RemoteUrl';
import SocketWorker from './utilities/SocketWorker';
function Socket() {
this.socket = io.connect(remoteUrl + '?role=user');
this.socket.on('statusChange', (data) => {
return SocketWorker.receiveOrderStatusChange(data);
})
};
const socket = new Socket();
export { socket };
You can import the socket anywhere within your project:
import {socket} from './Socket';
And you can call:
socket.socket.emit('joinRoleRoom','user');
socket.socket.emit('joinIdRoom', _user._id);
On the server side, you just need to handled these events as follow:
socket.on('joinRoleRoom', (role) => {
socket.join(role)
console.log('Client joined to: ' + role);
});
socket.on('joinIdRoom', (id) => {
console.log('Client joined to: ' + id);
socket.join(id)
});
The socket will join the necessary rooms based on their auth info obtained during the auth process.
The original accepted answer from sznrbrt would work, but beware it has a serious security flaw.
If you do the following an attacker could join a room by just passing the desired user_id and start to receive sensitive user information. It could be private messages between two individual.
socket.socket.emit('joinRoleRoom','user');
socket.socket.emit('joinIdRoom', user_id);
Socket.io has an option to pass extraHeaders. One can use that to pass a token from the client. The server would use the desired authentication algorithm to decrypt the token and get the user_id.
Example:
socket.js
import io from 'socket.io-client';
import { remoteUrl } from './constants/RemoteUrl';
import SocketWorker from './utilities/SocketWorker';
const socket = io.connect(remoteUrl + '?role=user');
const socketAuth = () => {
socket.io.opts.extraHeaders = {
'x-auth-token': 'SET_TOKEN',
};
socket.io.opts.transportOptions = {
polling: {
extraHeaders: {
'x-auth-token': 'SET_TOKEN',
},
},
};
socket.io.disconnect();
socket.io.open();
};
export { socket, socketAuth };
client.js
import { socket, socketAuth } from './socket';
//After user logs in
socketAuth();
server.js, using a package socketio-jwt-auth
io.use(jwtAuth.authenticate({
secret: 'SECRET',
succeedWithoutToken: true
}, (payload, done) => {
if (payload && payload.id) {
return done(null, payload.id);
}
return done();
}));

Resources