I am creating a node.js app with a react client and an express/socketio backend and passport authentication. I'm trying to emit a socketio event from the client and then have the backend respond but it seems that the request is getting lost somewhere between when it's emitted to when the backend processes it.
Below are all the files I thought relevant to the problem at hand. Backend is on localhost:5000 and client is on localhost:3000 As I said, everything goes well until the line marked in Organization.js. But at/after that line, the event is presumably sent but none of the corresponding routes in socket.js are hit.
Backend start file - app.js
'use strict';
import express from 'express'
import { createServer } from 'http'
import path from 'path'
import { fileURLToPath } from 'url';
import logger from 'morgan'
import cookieParser from 'cookie-parser'
import index from './routes/index.js'
import session from 'express-session'
import passport from './src/auth.js'
import { Server } from 'socket.io'
import { CrashCollector } from 'crashcollector'
new CrashCollector('crash_logs', (err, exit) => {
console.log('Cleaning up...')
console.log('Saving some stuff...')
// Calls callback funtion executing process.exit()
// You can also just write process.exit() here
exit()
})
const app = express();
const server = createServer(app)
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const io = new Server(server, {
cookie: { maxAge: 24 * 60 * 60 * 1000 },
cors: {
origin: 'http://localhost:3000',
credentials: true
}
})
const sessionMiddleware = session({
secret: "changeit", resave: false,
saveUninitialized: false
});
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname '/public/favicon.ico'));
app.use(sessionMiddleware)
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(passport.initialize());
app.use(passport.session());
app.use('/', index);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
let err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
if (app.get('env') === 'development') {
app.use(function (err, req, res, next) {
console.log('dev error handler')
res.status(err.status || 500);
res.end()
});
}
// production error handler
// no stacktraces leaked to user
app.use(function (err, req, res, next) {
console.log('prod error handler')
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
app.set('port', process.env.PORT || 3000); //process.env.PORT = 5000
server.listen(app.get('port'), _ => {
console.log(`Server listening on port ${app.get('port')}`)
})
export {
io,
sessionMiddleware
}
Backend - socket.js
import { io, sessionMiddleware } from '../app.js'
import passport from '../src/auth.js'
import { getSequel } from '../src/database.js'
const wrap = middleware => (socket, next) =>
middleware(socket.request, {}, next);
io.use((_, next) => {
console.log('connection attempt')
next()
})
io.use(wrap(sessionMiddleware));
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
io.use((con, next) => {
console.log('socket check', !!con.request.user)
if (con.request.user) {
next()
} else {
next(new Error('unauthorized'))
}
});
io.on('connection', (con) => {
console.log(`new connection ${con.id}`);
con.on('confirm', cb => {
cb(con.request.user.name);
});
con.on('eventTable', _ => {
console.log('eventTable')
con.emit(con.request?.user?.getEvents()?.map(e => e.toJSON()))
})
});
Client start file - index.js
import React from 'react'
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App/App';
import { io } from 'socket.io-client'
import configs from '../package.json'
const container = document.getElementById('root');
const root = createRoot(container);
export const socket = io.connect(configs.proxy, { //configs.proxy = http://localhost:5000
withCredentials: true
})
socket.on('connect', _ => {
console.log('socket connect', socket.id)
})
socket.on('connect_error', _ => {
console.log('socket connect_error')
})
socket.on('disconnect', _ => {
console.log('socket disconnect')
})
socket.prependAny((eventName, ...args) => {
console.log(eventName, args)
})
root.render((
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
));
Client - App.js
import React from 'react'
import { Route, Routes } from 'react-router-dom'
import './App.css'
import Login from './pages/Login'
import Home from './pages/Home'
import LoginCheck from './helper/LoginCheck'
const App = () => {
console.log('app')
return (
<Routes>
<Route exact path="/" element={<LoginCheck />} >
<Route index element={<Home />} />
</Route>
<Route path="/login" element={<Login />} />
</Routes>
)
}
export default App;
Client - Home.js
import React, { useState } from 'react';
import Org from './Organization'
const Home = () => {
console.log('home')
return localStorage.getItem('isOrg') ? (
<Org />
) : (
'Employee'
)
}
export default Home;
Client - Organization.js
import React, { createElement, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'
import Countdown from 'ds-countdown'
import { socket } from '../../index'
const Org = () => {
console.log('org')
const setTable = (raffles) => {
console.log('cb eventTable')
JSON.parse(raffles).forEach(raffle => {
const row = createElement('tr')
const name = createElement('td')
name.append(raffle.name)
const num = createElement('td')
num.append(raffle.num)
const started = Date.parse(raffle.start) >= Date.now()
const countdown =
<><td>
<p style={{ color: started ? 'red' : 'green' }}>
{started ? 'Starts' : 'Ends'} in:
<div id={`timer${raffle.id}`} />
</p>
</td></>
const action = createElement('td')
const edit = createElement('button')
edit.addEventListener('click', e => {
//goto edit at raffle.id
alert('edit')
})
const end = createElement('button')
end.addEventListener('click', e => {
//set timer to 0
alert('start/end')
})
action.append(edit)
action.append(end)
row.append(name)
row.append(num)
row.append(countdown)
row.append(action)
document.getElementById('eventTable').append(row)
new Countdown({
id: `timer${raffle.id}`,
targetTime: started ?
raffle.end : raffle.start
})
})
}
useEffect(() => {
console.log('useEffect')
**socket.emit('eventTable')** //THIS IS WHERE IT BREAKS
socket.on('eventTable', setTable)
return () => {
socket.off('eventTable', setTable)
}
})
return (<>
<h1 style={{ textAlign: 'center' }}>
{localStorage.getItem('name')}
</h1>
<table style={{ marginLeft: 'auto', marginRight: 'auto' }}
border="1"><caption>
<h3>Scheduled Raffles</h3>
</caption>
<tbody id="eventTable">
<tr>
<th>Name</th>
<th>Num Participants</th>
<th>Countdown</th>
<th>Action</th>
</tr>
<tr>
<td>Test</td>
<td>10</td>
<td>10:10:04</td>
<td><button> Edit </button> <button> End Now </button> <button> Stop </button></td>
</tr>
</tbody>
</table>
</>);
}
export default Org;
I get no errors on the front end. In fact, the client says that a connection has been established, thanks to the socket.on('connect') in index.js. None of the console.logs in the socket.js file are getting activated, so I have reason to believe that the route isn't getting hit. I also activated Debugging on socket but the logs there don't help me very much:
Server listening on port 5000
socket.io:server incoming connection with id 1wpATq-xiQ1jHttwAAAA 2s
socket.io-parser decoded 0 as {"type":0,"nsp":"/"} 0ms
socket.io:client connecting to namespace / 0ms
socket.io:namespace adding socket to nsp / 0ms
socket.io:socket socket connected - writing packet 0ms
socket.io:socket join room f6lna-kbntjuwffpAAAB 3ms
socket.io-parser encoding packet {"type":0,"data":{"sid":"f6lna-kbntjuwffpAAAB"},"nsp":"/"} 7ms
socket.io-parser encoded {"type":0,"data":{"sid":"f6lna-kbntjuwffpAAAB"},"nsp":"/"} as 0{"sid":"f6lna-kbntjuwffpAAAB"} 1ms
POST /login 200 653.887 ms - 37
socket.io-parser decoded 2["eventTable"] as {"type":2,"nsp":"/","data":["eventTable"]} 3s
socket.io:socket got packet {"type":2,"nsp":"/","data":["eventTable"]} 3s
socket.io:socket emitting event ["eventTable"] 0ms
socket.io:socket dispatching an event ["eventTable"] 1ms
socket.io-parser decoded 2["eventTable"] as {"type":2,"nsp":"/","data":["eventTable"]} 15ms
socket.io:socket got packet {"type":2,"nsp":"/","data":["eventTable"]} 2ms
socket.io:socket emitting event ["eventTable"] 0ms
socket.io:socket dispatching an event ["eventTable"] 1ms
CodePudding user response:
The server side socket.emit
call should have an event name as the first argument.
con.emit('eventTable', con.request?.user?.getEvents()?.map(e => e.toJSON()))
CodePudding user response:
The problem was I was starting the server before initializing the sockets.js routes. I had to do some reconfiguring on the backend, but basically the way I fixed it was making an express router in sockets.js, importing said router to app.js, then doing app.use('/socket', sockets)
, BEFORE calling server.listen(PORT)
. Below is the final state of my backend:
new Backend file - init.js:
import express from 'express'
import { createServer } from 'http'
import session from 'express-session'
import { Server } from 'socket.io'
import { CrashCollector } from 'crashcollector'
new CrashCollector('crash_logs', (err, exit) => {
console.log('Cleaning up...')
console.log('Saving some stuff...')
// Calls callback funtion executing process.exit()
// You can also just write process.exit() here
exit()
})
const app = express();
const server = createServer(app)
const io = new Server(server, {
cookie: { maxAge: 24 * 60 * 60 * 1000 },
cors: {
origin: 'http://localhost:3000',
credentials: true
}
})
const sessionMiddleware = session({
secret: "changeit", resave: false,
saveUninitialized: false
});
export {
server,
app,
io,
sessionMiddleware
}
app.js:
'use strict';
import express from 'express'
import path from 'path'
import { fileURLToPath } from 'url';
import logger from 'morgan'
import cookieParser from 'cookie-parser'
import index from './routes/index.js'
import passport from './src/auth.js'
import { server, app, sessionMiddleware } from './init.js'
import sockets from './routes/sockets.js'
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname '/public/favicon.ico'));
app.use(sessionMiddleware)
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(passport.initialize());
app.use(passport.session());
app.use('/', index);
*app.use('/sockets', sockets)* //This is the bit I was missing
// catch 404 and forward to error handler
app.use(function (req, res, next) {
let err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
if (app.get('env') === 'development') {
app.use(function (err, req, res, next) {
console.log('dev error handler')
res.status(err.status || 500);
res.end()
});
}
// production error handler
// no stacktraces leaked to user
app.use(function (err, req, res, next) {
console.log('prod error handler')
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
app.set('port', process.env.PORT || 3000);
server.listen(app.get('port'), _ => {
console.log(`Server listening on port ${app.get('port')}`)
})
sockets.js:
import { io, sessionMiddleware } from '../init.js'
import passport from '../src/auth.js'
import { getSequel } from '../src/database.js'
import { Router } from 'express'
const router = Router()
const wrap = middleware => (socket, next) =>
middleware(socket.request, {}, next);
io.use((_, next) => {
console.log('connection attempt')
next()
})
io.use(wrap(sessionMiddleware));
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
io.use((con, next) => {
console.log('socket check', !!con.request.user)
if (con.request.user) {
next()
} else {
next(new Error('unauthorized'))
}
});
io.on('connection', (con) => {
console.log(`new connection ${con.id}`);
con.on('confirm', cb => {
cb(con.request.user.name);
});
con.on('eventTable', cb => {
con.request.user.getEvents()
.then(events => events.map(e => e.JSON()))
//I switched to a callback here, rather than emitting a new event
.then(mappedArray => cb(mappedArray))
})
});
export default router