I have an app made with React, Node.js and Socket.io
I deployed Node backend to heroku , frontend to Netlify
And here is my server.js
file;
const mongoose = require('mongoose');
const Document = require('./Document');
const dotenv = require('dotenv');
const path = require('path');
const express = require('express');
const http = require('http');
const socketio = require('socket.io');
dotenv.config();
const app = express();
app.use(cors());
const server = http.createServer(app);
const io = socketio(server, {
cors: {
origin: 'https://googledocs-clone-sbayrak.netlify.app/',
methods: ['GET', 'POST'],
},
});
app.get('/', (req, res) => {
res.status(200).send('hello!!');
});
const connectDB = async () => {
try {
const connect = await mongoose.connect(process.env.MONGODB_URI, {
useUnifiedTopology: true,
useNewUrlParser: true,
});
console.log('MongoDB Connected...');
} catch (error) {
console.error(`Error : ${error.message}`);
process.exit(1);
}
};
connectDB();
let defaultValue = '';
const findOrCreateDocument = async (id) => {
if (id === null) return;
const document = await Document.findById({ _id: id });
if (document) return document;
const result = await Document.create({ _id: id, data: defaultValue });
return result;
};
io.on('connection', (socket) => {
socket.on('get-document', async (documentId) => {
const document = await findOrCreateDocument(documentId);
socket.join(documentId);
socket.emit('load-document', document.data);
socket.on('send-changes', (delta) => {
socket.broadcast.to(documentId).emit('receive-changes', delta);
});
socket.on('save-document', async (data) => {
await Document.findByIdAndUpdate(documentId, { data });
});
});
console.log('connected');
});
server.listen(process.env.PORT || 5000, () =>
console.log(`Server has started.`)
);
and this is where I make request from frontend;
import Quill from 'quill';
import 'quill/dist/quill.snow.css';
import { useParams } from 'react-router-dom';
import { io } from 'socket.io-client';
const SAVE_INTERVAL_MS = 2000;
const TextEditor = () => {
const [socket, setSocket] = useState();
const [quill, setQuill] = useState();
const { id: documentId } = useParams();
useEffect(() => {
const s = io('https://googledocs-clone-sbayrak.herokuapp.com/');
setSocket(s);
return () => {
s.disconnect();
};
}, []);
/* below other functions */
/* below other functions */
/* below other functions */
}
Web origins do not contain a path.
Accordingly, https://googledocs-clone-sbayrak.netlify.app/
is not a valid Web origin. Drop that trailing slash.
Origin
headerAccording to the CORS protocol (specified in the Fetch standard), browsers never set the Origin
request header to a value with a trailing slash. Therefore, if a page at https://googledocs-clone-sbayrak.netlify.app/whatever
issues a cross-origin request, that request's Origin
header will contain
https://googledocs-clone-sbayrak.netlify.app
without any trailing slash.
You're using Socket.IO, which relies on the Node.js cors
package. That package won't set any Access-Control-Allow-Origin
in the response if the request's origin doesn't exactly match your CORS configuration's origin
value (https://googledocs-clone-sbayrak.netlify.app/
).
Obviously,
'https://googledocs-clone-sbayrak.netlify.app' ===
'https://googledocs-clone-sbayrak.netlify.app/'
evaluates to false
, which causes the cors
package not to set any Access-Control-Allow-Origin
header in the response, which causes the CORS check to fail in your browser, hence the CORS error you observed.
Section 3.2.5 of the Fetch Standard even provides an enlightening example of this mistake,
Access-Control-Allow-Origin: https://rabbit.invalid/
and explains why it causes the CORS check to fail:
A serialized origin has no trailing slash.