WebSocket with Express - Invalid WebSocket frame: invalid UTF-8 sequence - node.js

I'm building a web application using HTTPS, Express, Socket.io, and WebSocket. Currently Express is using port 3000. In order to use the same port of WebSocket as 3000 port, I wrote the code as follows.
app.js
const path = require('path');
const express = require('express');
const app = express();
const mainRouter = require('./routes/main/main');
// Middleware
...
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'res')));
app.use('/', mainRouter);
// 404 error handler
app.use((req, res) => {
logger.error(`404 Page not found`);
res.status(404).send('404 Page not found');
});
module.exports = app;
bin/www
const fs = require('fs');
const path = require('path');
const app = require('../app');
const https = require('https');
const options = {
key: fs.readFileSync(path.resolve(__dirname, '../config/ssl/localhost-key.pem')), // Private key
cert: fs.readFileSync(path.resolve(__dirname, '../config/ssl/localhost.pem')), // Certificate
};
const server = https.createServer(options, app);
const io = require('socket.io')(server);
const WebSocket = require('../public/js/websocket');
const normalizePort = (value) => {
const port = parseInt(value, 10);
if(isNaN(port)) {
return value;
}
if(port >= 0) {
return port;
}
return false;
};
const port = normalizePort(process.env.PORT || 3000);
// Setting port
app.set('port', port);
const onError = (err) => {
if(err.syscall !== 'listen') {
throw err;
}
const bind = typeof(port) === 'string' ? `Pipe ${port}` : `Port ${port}`;
switch(err.code) {
case 'EACCES':
console.error(`${bind} requires elevated privileges`);
process.exit(1);
break;
case 'EADDRINUSE':
console.error(`${bind} is already in use`);
process.exit(1);
break;
default:
throw error;
}
};
const onListening = () => {
const address = server.address();
const bind = typeof(address) === 'string' ? `Pipe ${address}` : `Port ${port}`;
};
...
WebSocket(server);
server.listen(port, '0.0.0.0');
server.on('error', onError);
server.on('listening', onListening);
public/js/websocket.js
const ws = require('ws');
module.exports = function WebSocket(server) {
const wss = new ws.Server({ server : server });
wss.on('connection', (ws, req) => {
ws.on('message', (data) => {
ws.send(data);
console.log(data);
});
});
wss.on('listening', () => {
console.log('listening..');
});
};
routes/main/main.js
const router = require('express').Router();
const { v4 : uuidv4 } = require('uuid');
router.get('/', (req, res) => {
res.redirect(`/${uuidv4()}`);
});
router.get('/:room', (req, res) => {
res.render('main/main', { room: req.params.room });
});
module.exports = router;
After configuring the server as above, if you connect to https://localhost:3000 after npm start, the following error occurs.
Error: Invalid WebSocket frame: invalid UTF-8 sequence ... code: 'WS_ERR_INVALID_UTF8', [Symbol(status-code)]: 1007
I don't understand why this error occurs. Also, how can I fix the code to fix the error?

Related

websocket not connecting to nextjs/express server

running into this error with socket.io + nextjs + custom express server:
WebSocket connection to 'ws://localhost:3000/socket.io/?EIO=4&transport=websocket' failed: WebSocket is closed before the connection is established.
Server:
const { Server, Socket } = require("socket.io");
const express = require("express");
const { createServer } = require("http");
const next = require("next");
const { parse } = require("url");
const dev = process.env.NODE_ENV !== "production";
const PORT = process.env.PORT || 3000;
const user = require("./server/api/userAPI");
const game = require("./server/api/gameAPI");
const app = next({ dev });
const handle = app.getRequestHandler();
console.log("hello");
app
.prepare()
.then(() => {
const expressApp = express();
const httpServer = createServer(expressApp);
const io = new Server(httpServer, { transports: ["websocket"] });
expressApp.use(express.json());
expressApp.use("/user", user);
expressApp.use("/game", game);
expressApp.listen(3000, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
expressApp.get("*", (req, res) => {
return handle(req, res);
});
io.on("connect", () => {
console.log("socket connected");
const count = io.engine.clientsCount;
console.log(count);
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});
here is the client:
useEffect(() => {
const socket = io("http://localhost:3000", {
// reconnectionDelay: 1000,
reconnection: true,
reconnectionAttempts: 10,
transports: ["websocket"],
agent: false,
upgrade: false,
rejectUnauthorized: false,
});
socket.on("connect", () => {
console.log("someone connected: ", socket?.id);
});
}, []);
never worked with websockets or a custom next server before, found this online which is similar to my issue but no luck: https://github.com/vercel/next.js/issues/30491
how can i get the socket to connect?

Trying to reuse socket io instance in a controller but getting an error that socket hasn't been initialized

I'm relatively new to node.js and I'm trying to include socket.io into a controller. The idea is to respond to a client when an order is placed through the response object of express but in addition I'd also like to emit an event so that the restaurant owner sees the orders from all the customers coming in 'live'.
I have an index.js file in an api folder with the following code, where I export api, server and PORT:
`
const express = require('express');
const morgan = require('morgan');
const http = require('http');
const cors = require('cors');
const api = express();
const server = http.createServer(api);
const PORT = process.env.PORT || 3000;
api.use(cors());
api.use(morgan('common'));
api.use(express.urlencoded({ extended: true }));
api.use(express.json({ extended: true }));
api.use('/api/v1', require('../routers'));
api.get('/', (req, res) => {
res.send('Backend running.');
});
module.exports = { api, server, PORT };
In the root of the project I have another index.js file with the following code:
/* eslint-disable no-console */
require('dotenv').config();
const mongoose = require('mongoose');
const { api, server, PORT } = require('./api');
const { MONGO_URI } = require('./config');
mongoose.connect(
MONGO_URI,
{ useNewUrlParser: true, useUnifiedTopology: true },
)
.then(() => console.log('Connected to DB'))
.catch((err) => console.log('Error occured while trying to connect to DB', err));
api.listen(PORT, () => console.log(`Listening on ${PORT}`));
const io = require('./socket').init(server);
io.on('connection', (socket) => {
console.log('Connection success', socket.id);
socket.on('disconnect', () => {
console.log('Connection disconnected', socket.id);
});
});
I've placed the code to initialize socket.io and get an instance of it in a folder named socket with the following code in the index.js file:
/* eslint-disable consistent-return */
/* eslint-disable global-require */
const { Server } = require('socket.io');
let io;
module.exports = {
init: (server) => {
try {
io = new Server(server);
return io;
} catch (err) {
console.log(err);
}
},
get: () => {
if (!io) {
throw new Error('socket is not initialized');
}
return io;
},
};
Then I import the io instance in a controller but when I emit an event I get the error that the socket is not initialized. This is how I import the socket instance and emit an event:
const { OrdersService } = require('../services');
const io = require('../socket/index').get();
module.exports = {
create: async (req, res) => {
const { body } = req;
try {
const order = await OrdersService.create(body);
io.emit('new order', order);
res.status(201).json(order);
} catch (err) {
res.status(400).json(err);
}
},
};
What am I doing wrong? Any help would be greatly appreciated :)
I configured socket.io like I did based on previous questions that were raised on this topic here in stackoverflow.

Router.use() requires a middleware function but got a ' + gettype(fn))

one question. I am making an api with node,express and mysql.
And there seems to be an error when I run nodemon.
If anyone knows anything, it would be appreciated.
Error:
throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
My index.js:
const express = require('express')
const app = express()
const routes = require("./routes/transactions")
//Settings
app.use('port', process.env.PORT || 3000)
//Middlewares
app.use(express.json())
//Routes
app.use("/", routes.transactions)
//Crear servidor con el puerto
app.listen(app.get('port'), () => {
console.log('Hola Mundo', app.get('port'))
})
module.exports = app;
My routes/transactions.js
const express = require('express');
const router = express.Router();
const mysqlConnection = require('../database');
router.get('/transactions', (req, res) => {
mysqlConnection.query('SELECT * FROM transactions', (err, rows, fields) => {
if(!err) {
res.json(rows)
} else {
console.error(err)
}
});
});
exports.transactions = router
My database.js
const mysql = require('mysql');
const mysqlConnection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'operations',
})
mysqlConnection.connect(function(err){
if(err) {
console.log(err);
return
} else {
console.log('Db is connected')
}
})
module.exports = mysqlConnection;
You are mistakenly using app.use() instead of app.set().
Change this:
app.use('port', process.env.PORT || 3000)
to this:
app.set('port', process.env.PORT || 3000)
The error comes because you're passing a string or number to app.use() where it expects a middleware function reference.
It seems like you should have had a stack trace for this error (if you learn how to interpret it) that points to the exact line of code (a few steps up the stack) causing the problem which should simplify debugging next time.
I think the error here is resulting from the overload of app.use():
You can use this function in two ways, to use middleware and routes.
routes/transactions.js should be altered to the following:
const router = express.Router();
const mysqlConnection = require('../database');
router.get('/transactions', (req, res) => {
mysqlConnection.query('SELECT * FROM transactions', (err, rows, fields) => {
if(!err) {
res.json(rows)
} else {
console.error(err)
}
});
});
exports.transactions = router
In index.js:
const express = require('express')
const app = express()
const routes = require("./routes/transactions")
//Settings
app.use('port', process.env.PORT || 3000)
//Middlewares
app.use(express.json())
//Routes
app.use("/", routes.transactions)
//Crear servidor con el puerto
app.listen(app.get('port'), () => {
console.log('Hola Mundo', app.get('port'))
})
module.exports = app;
This would run the code in the 'get' endpoint in 'routes/transactions.js' when you navigate to localhost:<port>/transactions.

Websocket failed Invalid frame header

I'm using socket.io with peerjs to create a video conference app. Everything works fine on localhost. but when i push/host it on heroku it shows me this error in the console of browser :
index.js:83 WebSocket connection to 'wss://vidconsom.herokuapp.com/socket.io/?EIO=3&transport=websocket&sid=zbEGAHBj9w_dpcQfAAAF' failed: Invalid frame header.
Can anyone please help?
UPDATE: CHECK BELOW FOR ANSWER
Here is my server.js code:
const express = require("express");
const app = express();
const path = require("path");
// const { PeerServer } = require("peer");
const { ExpressPeerServer } = require("peer");
const { v4: uuidV4 } = require("uuid");
const server = require("http").Server(app);
const io = require("socket.io")(server);
const PORT = process.env.PORT || 3000;
const expServer = server.listen(PORT, () =>
console.log(`Server started on port ${PORT}`)
);
const peerServer = ExpressPeerServer(expServer, {
path: "/peer",
});
app.set("view engine", "ejs");
app.use(express.static(path.join(__dirname, "/public")));
app.use(peerServer);
app.get("/", (req, res) => {
res.redirect(`/${uuidV4()}`);
});
app.get("/:room", (req, res) => {
res.render("room", {
roomId: req.params.room,
PORT,
host: process.env.host | "/",
});
});
io.on("connection", (socket) => {
socket.on("join-room", (roomId, userId) => {
socket.join(roomId);
socket.to(roomId).broadcast.emit("user-connected", userId);
socket.on("disconnect", () => {
socket.to(roomId).broadcast.emit("user-disconnected", userId);
});
});
});
Here is my frontend script.js code:
const socket = io("/");
const videoGrid = document.getElementById("video-grid");
const myPeer = new Peer(undefined, {
host: "/",
port: PORT,
path: "/peer",
});
const myVideo = document.createElement("video");
myVideo.muted = true;
const peers = {};
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
})
.then((stream) => {
addVideoStream(myVideo, stream);
myPeer.on("call", (call) => {
call.answer(stream);
const video = document.createElement("video");
call.on("stream", (userVideoStream) => {
addVideoStream(video, userVideoStream);
});
});
socket.on("user-connected", (userId) => {
connectToNewUser(userId, stream);
});
});
socket.on("user-disconnected", (userId) => {
if (peers[userId]) {
peers[userId].close();
}
});
myPeer.on("open", (id) => {
socket.emit("join-room", ROOM_ID, id);
});
function connectToNewUser(userId, stream) {
const call = myPeer.call(userId, stream);
const video = document.createElement("video");
call.on("stream", (userVideoStream) => {
addVideoStream(video, userVideoStream);
});
call.on("close", () => {
video.remove();
});
peers[userId] = call;
}
function addVideoStream(video, stream) {
video.srcObject = stream;
video.addEventListener("loadedmetadata", () => {
video.play();
});
videoGrid.append(video);
}
On the frontend script.js use port 443 while deploying to heroku:
const myPeer = new Peer(undefined, {
host: "/",
port: 443,
path: "/peer",
});
server.js file
const express = require("express");
const app = express();
const server = require("http").createServer(app);
const { ExpressPeerServer } = require("peer");
const io = require("socket.io")(server, {
cors: {
origin: "*",
},
});
const peerServer = ExpressPeerServer(server, {
debug: true,
port: 443
});
app.set("view engine", "ejs");
app.use("/peerjs", peerServer);
app.use(express.static(path.join(__dirname, "public")));
app.get("/", (req, res) => {
res.render("home");
});
io.on("connection", (socket) => {
socket.on("event", () => {
// do something
});
socket.on("disconnect", () => {
// do something
});
});
server.listen(process.env.PORT || 3000);
script.js file
const socket = io().connect("/");
let peer = new Peer(undefined, {
path: "/peerjs",
host: "/",
port: 443,
});
You can run express server and ExpressPeerServer on different port. You can find the discussion here https://github.com/peers/peerjs/issues/300.
server.js
//Express Server
const express = require('express');
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);
const { v4 : uuidV4} = require('uuid');
app.set('view engine','ejs');
app.use(express.static(__dirname+'/public'));
app.get('/',(req,res) => {
res.redirect(`/${uuidV4()}`);
});
app.get('/:room',(req,res) => {
res.render('room',{ roomId: req.params.room});
});
io.on('connection',socket => {
socket.on('join-room',(roomId,userId,userName) => {
// Do something
});
});
server.listen(3000);
// Peer Server
var ExpressPeerServer = require('peer').ExpressPeerServer;
var peerExpress = require('express');
var peerApp = peerExpress();
var peerServer = require('http').createServer(peerApp);
var options = { debug: true }
var peerPort = 3001;
peerApp.use('/peerjs', ExpressPeerServer(peerServer, options));
peerServer.listen(peerPort);
client.js
var peer = new Peer(undefined, {
path: "/peerjs",
host: "/",
port: "3001",
config: {'iceServers': [
{ url: 'stun:stun.l.google.com:19302' }
]}
});
maybe you need to add the allowEIO3 option when creating the io server
import { Server } from 'socket.io';
const io = new Server(server, {
allowEIO3: true
});
reference: https://socket.io/docs/v3/troubleshooting-connection-issues/
use either socket io or peer server on different port something like
io.listen(4000);

Nextjs and Express as middle ware. How do I set 'localhost:3000/newpage' and 'localhost:3000/newpage/' as the same routes

I am new to express and next and was trying to set 'localhost:3000/newpage' and 'localhost:3000/newpage/' as the same route however as I add a '/' at the end it shows a 404 error.
I am using "next-routes" for dynamic routing and have created routes.js file that looks like this:
const nextRoutes = require("next-routes");
const routes = (module.exports = nextRoutes());
routes.add("index", "/");
routes.add("newpage", "/newpage/:slug"); //with body parser this doesnt work
and my server.js file looks like this:
const express = require("express");
const next = require("next");
const routes = require("./routes");
const dev = process.env.NODE_ENV !== "production";
const port = process.env.PORT || 3000;
const app = next({ dev });
const handle = app.getRequestHandler();
const bodyParser = require("body-parser");
const handler = routes.getRequestHandler(app);
app
.prepare()
.then(() => {
const server = express();
server.use(bodyParser.json()); //with this dynamic routes dont work
server.use (handler); //with this dynamic routes work but / url show 404
server.get("*", (req, res) => {
server.use(handler);
if (req.url.endsWith("/")) {
req.url = req.url.slice(0, -1); // works only when using body parser
}
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});
You can modify the url that you get before passing it to Next's handling.
const next = require('next');
const express = require('express');
const routes = require('./routes');
const port = process.env.PORT || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({dev});
const handle = app.getRequestHandler();
// const handler = routes.getRequestHandler(app); // redundant line
app.prepare().then(() => {
const server = express();
// server.use(handler); // <-- this line is redundant since you need only one handle!
server.get('*', (req, res) => {
if (req.url.endsWith('/')) {
req.url = req.url.slice(0, -1); // remove the last slash
}
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log('> Ready on http://localhost:3000');
});
});
Working example: https://codesandbox.io/s/express-nextjs-react-c47y8?file=/src/index.js
Navigate to /form or /form/
I had to install the body-parser package then used body-parser. I also changed the folder structure such that I didn't have to import the routes. The final code in server.js looks like this:
const express = require("express");
const next = require("next");
const dev = process.env.NODE_ENV !== "production";
const port = process.env.PORT || 3000;
const app = next({ dev });
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
const server = express();
server.get("*", (req, res) => {
if (req.url.endsWith("/")) {
req.url = req.url.slice(0, -1); // remove the last slash
}
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});

Resources