can't connect to Socket.IO server on Heroku - node.js

I want to host my frontend on Vercel (I'm using Nextjs) and since it doesn't support socket connections in it's API routes I decided to move this part of my app to Heroku. My problem is that when I use the server from my frontend in dev environment it works just fine, but when I deploy it to Heroku I get this error:
Access to XMLHttpRequest at 'https://my-socket-server.herokuapp.com/socket.io/?EIO=3&transport=polling&t=NMPkkyL' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
my server code looks like this:
import express from 'express';
import io, { Namespace } from 'socket.io';
import { PORT, menus, socketServerConfig, adminRoom } from './util/config';
const server = express().listen(PORT, () =>
console.log(`server running on port ${PORT}`)
);
const socketServer = io(server, socketServerConfig);
const attachSocketHandlers = (server: Namespace) => {
server.on('connection', socket => {
const handlers = {
'admin-log-in': () => {
socket.join(adminRoom);
},
'need-waiter': (table: Table) => {
server.to(adminRoom).emit('need-waiter', table);
},
'need-receipt': (table: Table) => {
server.to(adminRoom).emit('need-receipt', table);
},
order: (order: Order) => {
server.to(adminRoom).emit('order', order);
},
disconnect: () => {
socket.leaveAll();
},
};
Object.entries(handlers).map(([event, handler]) => {
socket.on(event, handler);
});
});
};
menus.forEach(menu => {
const namespacedServer = socketServer.of(`/${menu}`);
attachSocketHandlers(namespacedServer);
});
What I understood from the socket.io docs is that if you list no origins in the config it allows all origins to access the socket server.This is my socketServerConfig:
import { ServerOptions } from 'socket.io';
const defaultPort = 4000;
export const PORT = process.env?.PORT ?? defaultPort;
export const menus = ['more'];
export const adminRoom = 'admin';
export const socketServerConfig: ServerOptions = {
serveClient: false, // i don't serve any static files
};
this is how I connect from my frontend:
const url = 'https://my-socket-server.herokuapp.com/';
const io = connect(`${url}${menu}`);
I tried various solutions from SOF but I just can't get it to work, any help would be appreciated. Thanks in advance.

Try doing npm install cors and adding this to your server:
const cors = require('cors');
const whitelist = [
'http://localhost:3000',
YOUR FRONTEND URL
];
const corsOptions = {
origin: function (origin, callback) {
console.log('** Origin of request ' + origin);
if (whitelist.indexOf(origin) !== -1 || !origin) {
console.log('Origin acceptable');
callback(null, true);
} else {
console.log('Origin rejected');
callback(new Error('Not allowed by CORS'));
}
},
};
express().use(cors(corsOptions));
Hope this helped!

Related

WebSocket connection to 'ws://localhost:8080/socket.io/?EIO=4&transport=websocket' failed. Apply socket.io in ApolloServer nodejs/React is not working

I change from "const socketio = require("socket.io-client");" to "const socketio = require("socket.io");" but it is not working.
the third pic is Back-end and fourth is Front-end. it doesn't say the exact error is but "this.ws =" and "websocket.js:50 WebSocket connection to 'ws://localhost:8080/socket.io/?EIO=4&transport=websocket' failed: " is all.
you can check in https://socket.io/docs/v4/server-initialization/ i think it's:
io.on("connection",()=>{}) // on server side
if that doesn't work you can try to move all socket code before server.listen it would be something like this:
const http = require('http')
const { Server } = require('socket.io')
const app = require('./app')
const server = http.createServer(app)
const io = new Server(server, {
cors: {
origin: 'http://localhost:8080',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
},
})
io.on('connection', (socket) => {
console.log("socket",socket);
}
server.listen(process.env.PORT || 5000, () => {
console.log(`Listening on port ${process.env.PORT || 5000}`)
})

Difficulty setting Cookie when using an ngrok tunnel (Express server on Node.js, React app frontend)

As outlined in the title, I am having difficulty setting a http cookie to be used for auth purposes when tunnelling using ngrok.
The following code works fine (obviously with the relevant endpoints specified) when i am running a query from from localhost to a localhost endpoint in my dev environment but breaks down as soon as i start to query the ngrok tunnel endpoint.
Frontend api query (simplified as part of larger application)
function fetchRequest (path, options) {
const endpoint = 'http://xxx.ngrok.io'; // the ngrok tunnel endpoint
return fetch(endpoint + path, options)
.then(res => {
return res.json();
})
.catch((err) => {
console.log('Error:', err);
});
}
function postRequest (url, body, credentials='include') {
return fetchRequest(`${url}`, {
method: 'POST',
withCredentials: true,
credentials: credentials,
headers: {'Content-Type': 'application/json', Accept: 'application.json'},
body: JSON.stringify(body)
});
}
// data to be passed to backend for authentication
let data = {pin: pin, username : username};
postRequest('/',data)
Express server on Node.js with ngrok tunnel (app.js)
const express = require('express')
const session = require('express-session')
const cors = require('cors')
const router = require('./router');
const tunnel = require('./ngrok')
const app = express()
const port = process.env.PORT || 4001;
app.use(cors({
origin: 'http://localhost:3000'
credentials: true,
}))
app.use(express.json());
const expiryDate = new Date(Date.now() + 60 * 60 * 1000) // 1 hour
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
expires: expiryDate
// sameSite: 'none'
// secure: true
}
}))
app.use(router)
let useNGROK = true;
if (useNGROK) {
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
tunnel.createHTTPtunnel().then((url) => {
console.log(`New tunnel created with endpoint: ${url}`)
});
} else {
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
}
Ngrok configuration (ngrok.js)
const ngrok = require('ngrok');
const find = require('find-process');
const port = process.env.PORT || '3000';
const tunnel = {
createHTTPtunnel: async function () {
const list = await find('name', 'ngrok');
if (list.length > 0) {
let api = ngrok.getApi();
if (api == null) {
this.kill_existing_tunnel();
} else {
let open_tunnels = await ngrok.getApi().listTunnels();
return open_tunnels.tunnels[0].public_url;
}
}
let ngrok_config = {
proto: 'http',
bind_tls: false,
name: process.env.NGROK_NAME,
hostname: process.env.NGROK_CUSTOM_DOMAIN,
// host_header: 'rewrite',
authtoken: '',
region: 'eu',
};
return ngrok.connect({ ...ngrok_config, addr: port });
},
kill_existing_tunnel: async () => {
const list = await find('name', 'ngrok');
list.forEach((p) => {
try {
process.kill(p.pid);
console.log(`Killed process: ${p.name} before creating ngrok tunnel`);
} catch (e) {
console.log(e);
}
});
}
}
module.exports = tunnel;
** router & controller (router.js & controller.js respectively) **
*router.js*
const router = require('express').Router();
const example = require('./controller')
router.post('/', example.authenticate);
module.exports = router;
*controller.js*
async function authenticate (req, res) {
try {
res.send(JSON.stringify('trying to send cookie'))
} catch (e) {
console.log('Error', e)
res.sendStatus(500)
}
}
module.exports = {
authenticate
};
The following information is provided when inspecting the Set-Cookie response header in the network requests:
This Set-Cookie header didn’t specify a “SameSite” attribute and was defaulted to “SameSite=Lax” and was blocked because it came from a cross-site response which was not the response to a top-level navigation. The Set-Cookie had to have been set with “SameSite=None” to enable cross site usage.
Attempted fix 1//
If I add the following options to the cookie {sameSite: ‘none’, secure:true}, amend the ngrok config to set {bind_tls: true} and run https on my front end (using a custom SSL certificate as per the create react app documentation), and query the https tunnel, then no cookie is received in the response from the server at all (request is sent and response 200 is received but with no cookie).
Attempted fix 2//
I also tried to change the host_header option to rewrite in the ngrok config (to mirror a response from localhost rather than from ngrok) and this did not work.
Any help would be much appreciated as I have little experience and I am stuck!

CORS error with socket-io connections on Chrome v88+ and Nestjs server

I have an application connecting to a Nestjs server to establish a WS connection (server is on a different URL, so it is a CORS request).
The WebsocketGateway is defined as such.
#WebSocketGateway(port, {
handlePreflightRequest: (req, res) => {
const headers = {
'Access-Control-Allow-Headers': 'Authorization',
'Access-Control-Allow-Origin': 'the page origin',
'Access-Control-Allow-Credentials': true,
};
res.writeHead(200, headers);
res.end();
}
})
Works like a charm on Chrome v87 and down and on Firefox. Since upgrading my browser to Chrome 88, the front-end socket-io connection goes on a connect-reconnect loop, as:
The preflight request passes and gets a 200 response, with the headers set above;
The actual connection fails with CORS error as the only message in the browser console
Just incase someone else needs this, in your decorator there is a cors property
#WebSocketGateway({ cors: true })
This is how i fixed
import { IoAdapter } from '#nestjs/platform-socket.io';
import { ServerOptions } from 'socket.io';
export class SocketAdapter extends IoAdapter {
createIOServer(
port: number,
options?: ServerOptions & {
namespace?: string;
server?: any;
},
) {
const server = super.createIOServer(port, { ...options, cors: true });
return server;
}
}
main.ts
const app = await NestFactory.create(AppModule, { cors: true });
app.useWebSocketAdapter(new SocketAdapter(app));

Socket io communication blocked by CORS policy in a Node js + Angular application

So I use a simple node.js server with express and socket.io
When I try to communicate with a simple client written in javascript, it works perfectly, but when I try to create the communication with an Angular app (using ngx-socket-io), I get the following error message :
Access to XMLHttpRequest at 'http://localhost:5000/socket.io/?EIO=3&transport=polling&t=NQFEnVV' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
This is the nodejs server :
var express = require('express');
var app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 5000;
app.set(port, process.env.PORT);
app.use(express.static('./client/'));
io.on('connection', socket => {
socket.emit('connection', 'data from server!');
});
http.listen(port, () => {
console.log('App listening on port ' + port);
});
And this is how I implement socket.io on Angular client :
app.module.ts :
import { SocketIoModule, SocketIoConfig } from 'ngx-socket-io';
const hostname = window.location.hostname;
const url = (hostname === 'localhost') ? `${window.location.protocol}//${hostname}:5000` : undefined;
const config: SocketIoConfig = { url, options: {} };
#NgModule({
declarations: [...],
imports: [
...
SocketIoModule.forRoot(config)
class that use socket io :
constructor(private socket: Socket) { }
this.socket.on('connection', (data) => {
console.log('Working ' + data);
});
I tried to fix the CORS error with headers in server side such as :
const io = require('socket.io')(server, {
cors: {
origin: '*',
}
});
or
app.use(function(request, response, next) {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
but it still display the same error.
I use of course the command ng serve to run my application (running on port 4200) and it was perfectly working 3 months ago. (Working with the ng serve, and the ng build prod as well).
Verify socket.io-client version on the angular packaje.json file. If it's lower than 3.1.2 you could try to upgrade it by
npm install socket.io-client#3.1.2 --save
Also you could verify the serverside once again looking at this
const app = require('express')();
const http = require('http').createServer(app);
const io = require('socket.io')(http, {
cors: {
origins: ['http://localhost:4200']
}
});
Replace http://localhost:4200 by your current environmment on the Angular App
try this
const io = require('socket.io')(server, {
cors: {
origin: "http://localhost:4200",
methods: ["GET", "POST"]
}
});
you can add or remove mothods
also try adding this
var cors = require('cors');
app.use(cors());
Adding fix from angular side (no changes in Node server required).
That cors: { origins: ['http://localhost:4200']} didn't work for me so I sat up a proxy file in angular project to tackle CORS.
add file in src/proxy.config.js file
{
"/api": { // this attaches /api/ in api url
"target": "http://localhost:3000",
"secure": false,
"logLevel": "debug"
},
"/": { // use this as second entry
"target": "http://localhost:3000",
"secure": false,
"logLevel": "debug"
}
}
in angular.json, add this;
"serve": {
"options": {
"browserTarget": "move-safe:build",
"proxyConfig": "src/proxy.config.json"
},
and re-build the angular app
It worked on the local machine but didn't on production.
I was confused why is that happening but suddenly i noticed that the HEROKU deployed it on something like
https://dummydomain.herokkuapp.com
but i was trying to connect it from
http://dummydomain.herokkuapp.com
that may be the reason for your app as well.
:D

How to create an HTTPS server in Node.js using docker?

I'm trying to create an HTTPS server using node.js and and docker. I have already created the certificates and configured the app. But I don't know how to run the image docker. I am trying this, but it is not working.
Does anyone know how to do this?
sudo docker run --name cont_docker -p 443:3333 --link my-mongo:my-mongo user/docker_app
I am not using docker-compose
---> EDIT <---
The container is not crashing. It is not responding. In the app I get 'couold not connect to server'. I think it is not binding the port
const express = require('express');
const getRootPath = require('../helpers/get-root-path.helper');
var fs = require('fs');
const https = require('https'); // REQUIRE HTTPS
const privateKey = fs.readFileSync('private/key/path', 'utf8');
const certificate = fs.readFileSync('cert/key/path', 'utf8');
let _express = null;
let _config = null;
let _router = null;
let _credentials = null;
class Server {
constructor({ config, router }) {
_router = router;
_config = config;
_credentials = { privateKey: privateKey, certificate: certificate } // ADD CREDENTIALS
_express = express();
}
/*
This methods returns a promisse that will be in charge of initate the server.
*/
start() {
_express.use(express.static(`${this.getRootPath(__dirname)}/public`))
_express.use(_router)
_express.engine('html', require('ejs').renderFile)
_express.set('view engine', 'html')
return new Promise(resolve => {
const server = https.createServer(this._credentials, _express)
server.listen(_config.port, () => {
console.log(`App running on port ${_config.port}`)
resolve()
})
})
}
getRootPath(path) {
return getRootPath(path)
}
}
module.exports = Server
---> APP.JS <---
const container = require('./src/startup/container')
const server = container.resolve("app")
const { db_host, db_database } = container.resolve("config")
const mongoose = require('mongoose')
mongoose.set("useCreateIndex", true)
mongoose
.connect(`mongodb://${db_host}/${db_database}`, { useUnifiedTopology: true, useNewUrlParser: true, useCreateIndex: true, useFindAndModify: false })
.then(response => {
console.log(`Connected to Mongo! Database name: "${response.connections[0].name}" on "${response.connections[0].host}"`)
server.start()
})
.catch(console.log)

Resources