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');
});
Related
I have built an app with tRPCv10 and NextAuth. As my app requires realtime updates, I have followed tRPC's docs on implementing subscriptions with websockets. tRPC docs on subscription tRPC example app.
From what I understand, to use websockets in tRPC, I need to create a standalone http server and run it alongside my Nextjs app. When I emit data through EventEmitter, the data is proxied through this http server and sent to all other subscribers. Thus, I have deployed my standalone http server on Railway with port 6957, and my Nextjs app on Vercel
Everything is working well when I am developing, through localhost. However, when I'm trying to deploy it, there is an error trying to connect to the websocket server and I'm receiving a NextAuth error when logging in too.
For example, my server name is "https://xxx-server.up.railway.app/" and my Nextjs app is "https://xxx-client.vercel.app/".
On the client side, I'm receiving an error: WebSocket connection to 'wss://xxx-server.up.railway.app:6957/' failed. When I hit the login button which runs the authorize function in NextAuth, the console returns the error: POST https://xxx-client.vercel.app/api/auth/calback/credentials? 401.
For reference, here are the file for _app.tsx and my websocket server:
// _app.tsx
const MyApp: AppType = ({
Component,
pageProps: { session, ...pageProps },
}) => {
return (
<SessionProvider session={session} refetchOnWindowFocus={false}>
<Component {...pageProps} />
</SessionProvider>
);
};
const getBaseUrl = () => {
if (typeof window !== "undefined") {
return "";
}
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
};
function getEndingLink() {
if (typeof window === "undefined") {
return httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
});
}
const client = createWSClient({
url: "wss://xxx-server.up.railway.app:6957"
});
return wsLink<AppRouter>({
client,
});
}
export default withTRPC<AppRouter>({
config({ ctx }) {
/**
* If you want to use SSR, you need to use the server's full URL
* #link https://trpc.io/docs/ssr
*/
const url = `${getBaseUrl()}/api/trpc`;
return {
url,
transformer: superjson,
links: [getEndingLink()],
/**
* #link https://react-query.tanstack.com/reference/QueryClient
*/
// queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
};
},
/**
* #link https://trpc.io/docs/ssr
*/
ssr: true,
})(MyApp);
// prodServer.ts
const port = parseInt(process.env.PORT || "3000", 10);
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = http.createServer((req, res) => {
const proto = req.headers["x-forwarded-proto"];
if (proto && proto === "http") {
// redirect to ssl
res.writeHead(303, {
location: `https://` + req.headers.host + (req.headers.url ?? ""),
});
res.end();
return;
}
const parsedUrl = parse(req.url!, true);
handle(req, res, parsedUrl);
});
const wss = new ws.Server({ server });
const handler = applyWSSHandler({ wss, router: appRouter, createContext });
process.on("SIGTERM", () => {
console.log("SIGTERM");
handler.broadcastReconnectNotification();
});
server.listen(port);
// tslint:disable-next-line:no-console
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? "development" : process.env.NODE_ENV
}`
);
});
I am using mqttjs and socketio on my nodejs backend.
I am using angular as my frontend framework.
On my frontend there are 3 routes.
All requires socket connection for real time data.
So on ngOnInit i run client side socket io connection code and on ngOnDestroy I will run socket disconnect as well.
And in my server side code (index.js) there are mainly 3 actions that is happening.
const io = require('socket.io')(server)
mqtt.createConnection();
mqtt.mqttSubscriptions(io);
mqtt.mqttMessages(io);
These are the mqtt methods:
const createConnection = () => {
let options = {
protocol: 'mqtt',
clientId: process.env.MQTT_CLIENT_ID,
username: process.env.MQTT_USERNAME,
password: process.env.MQTT_PASSWORD,
};
client = mqtt.connect(process.env.MQTT_HOST, options);
client.on('connect', function() {
winston.info('MQTT connected');
});
client.on('error', function(err) {
winston.error(err);
});
};
const mqttSubscriptions = io => {
winston.info(`Socket connected.`);
client.subscribe([TOPICS.DATA], function(error, granted) {
if (error) {
winston.error(error);
}
winston.info('Topics: ', granted);
});
};
const mqttMessages = io => {
io.sockets.on('connection', socket => {
winston.info(`Socket connected.`);
client.on('message', function(topic, message) {
let payload = JSON.parse(message.toString());
winston.info(topic);
winston.info(payload.id);
switch (topic) {
case TOPICS.DATA:
dataController.storeData(payload, io);
break;
default:
winston.error('Wrong topic');
break;
}
});
});
};
And on the datacontroller I am running
socket.emit()
My problem is everytime I navigate to a route and come back the dataController.storeData is called multiple times.
That is when I am at route A, and then navigate to route B and then back to A and then to C, the data is multiplied that many times of my route navigation. (In this case 4 times.)
I found that it is socket io and mqtt connection problem, but I don't know how to solve, since I am new to both of these.
Any help?
I have an express 4 server I'm trying to integrate with Socket.IO.
If I do io.attach(serverInstance) on server and io.connect("http://localhost:3002") on a react client using socket.io-client connection is established and the server logs something like socket connection active, id: 3IjhKYkpY19D6nWHAAAB, client ip: ::ffff:127.0.0.1.
But when I pass {path: "/socket/socket.io"} as io.attach(serverInstance, {path: "/socket/socket.io"}) and io.connect("http://localhost:3002", {path: "/socket/socket.io"}) nothing is logged, i.e., connection is not being made. I recently started learning socket.io so i don't know much.
I want that incoming socket connection should be requested at localhost:3002/socket and not localhost:3002/.
server side:
class SocketInterface {
constructor() {
this.server = undefined; // will hold the server instance once defined.
this.io = socket(); // referencing socket interface.
this.getServerReference = this.getServerReference.bind(this);
this.ioDotOn = this.ioDotOn.bind(this);
}
/**
* this method defines the sever reference once the server starts.
* #param {http.Server} server: reference to the http server.
*/
getServerReference(server) {
this.server = server;
this.io.attach(server, {path: "/socket/socket.io"});
}
/**
* this method executes io.on()
*/
ioDotOn() {
if (this.server) {
this.io.on("connection", socket => {
// when connection is established
const ip =
socket.handshake.headers["x-forwarded-for"] ||
socket.conn.remoteAddress;
console.log(
`socket connection active, id: ${socket.id}, client ip: ${ip}`
);
// when connection breaks
socket.on("disconnect", () => {
console.log("user gone");
});
});
} else throw new Error("Server is undefined");
}
}
I require this class in server.js as
const socketInterface = require("./socket/socket-io");
let server = undefined;
// first connect to local db.
MongoInterface.mongoConnect(err => {
if (err) {
// if error, occurs terminate.
console.log(err);
process.exit(1);
} else {
// if connected to MongoDB, get a reference to the taskDbOps singleton object.
console.log("Connected to MongoDB server");
// start the server and store a reference to the server.
server = app.listen(config.PORT, err => {
if (err) console.log(err);
else console.log("Application Server Running on Port: ", config.PORT);
});
// starting the socket interface.
if (server) {
socketInterface.getServerReference(server);
socketInterface.ioDotOn(); // socket service.
}
}
});
react client
It is a mock client that has an api.js for socket connection like so:
api.js
import io from "socket.io-client";
function x() {
const socketConn = io.connect("http://localhost:3002", {
path: "/socket/socket.io"
});
}
export { x };
this is imported in app.js like so:
import React from "react";
import { x } from "./api";
import logo from "./logo.svg";
import "./App.css";
function App() {
x(); //connecting to socket
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
I'm using socket.io 2.3
Thanks
const socketInterface = require("./socket/socket-io");
// first connect to local db.
MongoInterface.mongoConnect(err => {
if (err) {
// if error, occurs terminate.
console.log(err);
process.exit(1);
} else {
// if connected to MongoDB, get a reference to the taskDbOps singleton object.
console.log("Connected to MongoDB server");
// start the server and store a reference to the server.
// binding the server variable here resolves the problem
let server = app.listen(config.PORT, err => {
if (err) console.log(err);
else console.log("Application Server Running on Port: ", config.PORT);
});
// starting the socket interface.
if (server) {
socketInterface.getServerReference(server);
socketInterface.ioDotOn(); // socket service.
}
}
});
Following is my POST function in Node js. I want to call a funtion in my client side HTML to display an error message on the page.
router.post('/',(req,res)=>{
const data = JSON.stringify({
institute_name: req.body.institute_name,
email : req.body.email,
password : req.body.password
})
const options = {
host:'localhost',
port:'8888',
path:'/registerInstitute',
method:'POST',
headers: {
'Content-Type':'application/json'
}
}
const req1 = http.request(options, (res1)=>
{
const status = res1.statusCode
if (status == 201)
{
//admin created
res.redirect('/?account_created=true')
}
else if ( status == 409)
{
//CALL AJAX FUNCTION TO DISPLAY ERROR MSG
}
})
req1.write(data)
req1.end()
})
The simple answer is NO. But there are workarounds to do so and I've demonstrated the one that will suite your scenario
Sockets
You can use web sockets to trigger event to notify client to run specific function
On service side you can do something like this:
var io = require('socket.io').listen(80); // initiate socket.io server
if (
io.sockets.on('connection', function (socket) {
if ( error == 403 ) {
socket.emit('runErrorFunction', { error: "error data" });
}
});
You can do something like this on client side:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost'); // connect to server
socket.on('runErrorFunction', function (data) { // listen to runErrorFunction event raised by the server
// Here goes your error showing logic
});
</script>
Here is the link for more info Socket.io
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');
}
}