Unable to create cross-domain websocket connection to Node.js socket.io server

cross-domain, node.js, ruby-on-rails-4, socket.io, websocket

Solution

I have a similar setup running and had to configure the following headers on the node express web server.

app = require('express')();
...
app.use(function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  return next();
});

Problem

According to my searches this shouldn't be that hard, and I have tried many things with no luck so far. I have a Node.js server running on Heroku that creates a socket.io server (I also have tried hosting it on Nodejitsu, which didn't help). The socket.io socket connection works with no problems if I serve the page from the actual Node.js server that I want to create a websocket connection to, but I can't make any of my cross-domain socket connection attempts work. I have tried to do a cross-domain connect to this socket.io server several ways: (1) A simple local HTML file with some script that fetches client side socket.io library successfully from node server (using $.getScript), and then tries to create a socket connection (var socket = io.connect('nodejs_server_hostname:port_num');), which fails. (2) Running rails app server locally (development mode) that serves up a response to clients containing javascript that tries to form a websocket connection in the same fashion as method (1), and fails (3) Hosting rails app in Heroku, and doing the same thing as method (2). This fails as well. I made sure to enable websockets as specified in the Heroku documentation (https://devcenter.heroku.com/articles/heroku-labs-websockets). Once again, everything works if the request is served from the actual Node.js server that the client attempts to form a socket.io connection with. The socket.socket.on('error') event is trigged in all three attempts. I have tried modifying the http response headers in the rails app using the following code: ``` response.headers['Access-Control-Allow-Origin'] = '*' ``` This sets the headers correctly (I checked after requesting page from the rails server), but it doesn't fix the socket connection problem. I created an socket-io authorization callback on the Node.js server that performs logging, and it seems this callback is never even hit at all for the cross-domain socket connection attempts. So it seems like the Node.js server never even sees the websocket connection attempt, which is quite confusing to me. Below I will paste all the relevant code files. Node.js server file: ``` var http = require('http'), express = require('express'), whiskers = require('whiskers'), redis = require('redis'), port = process.env.PORT || 5000, app = express(), server = http.createServer(app).listen(port), io = require('socket.io').listen(server); console.log('---- ' + port); console.log(port); console.log('---- ' + port); io.configure(function () { io.set('authorization', function (handshakeData, callback) { console.log("-- inside io.set('authorization, cb)") console.log('-- handshakeData:'); console.log(handshakeData); console.log('-- end of handshakeData logging --'); callback(null, true); // error first callback style }); }); app.configure(function() { app.use(express.static(__dirname + '/public')); app.set('views', __dirname + '/views'); app.set('view options', { layout: false }); app.engine('.html', whiskers.__express); }); app.get('/', function(req, res) { res.render('index.html'); }); io.sockets.on('connection', function(socket) { console.log('---- new client connected ----'); console.log(socket); console.log('---- end of socket details logging for new client connection ----'); socket.emit('news', { hello: 'world' }); socket.on('my other event', function(data) { console.log("-- server -- inside socket.on 'my other event' callback"); console.log(data); }); socket.on('button clicked', function(data) { console.log("-- server -- inside socket.on 'button clicked' callback"); console.log(data); io.sockets.emit('news', { info: 'the button was clicked' }); }); }); ``` Here is the client-side javascript file from Rails app: ``` $(document).ready(function() { console.log('-- inside chat.js -- document is ready'); $('#join-chat-container').hide(); $('#new-message-container').hide(); //$.getScript('http://node-server-for-rails-chat.herokuapp.com/socket.io/socket.io.js') $.getScript('http://node-server-for-rails-chat.jit.su/socket.io/socket.io.js') .done(function( script, textStatus ) { console.log("-- socket.io.js loaded and executed -- textStatus: " + textStatus); setup_socket(); }) .fail(function( jqxhr, settings, exception ) { console.log("-- socket.io.js load error -- exception:"); console.log(exception); console.log("-- jqxhr: " + jqxhr); }); }); function setup_socket() { //var socket = io.connect('node-server-for-rails-chat.herokuapp.com', { port: 19364 }); //var socket = io.connect('node-server-for-rails-chat.jit.su', { port: 5000 }); var socket = io.connect('node-server-for-rails-chat.herokuapp.com:27305'); socket.socket.on('error', function (reason) { console.log('--- cant connect'); console.log(reason); console.error('Unable to connect Socket.IO', reason); }); socket.on('connect_failed', function() { console.log('connect failed'); }); socket.on('connect', function() { console.log('successfully connected!'); }); socket.on('message', function (message_html) { console.log("-- client -- inside socket.on('message', cb) callback"); console.log("-- client -- message_html: " + message_html); add_message(message_html); }); if (!socket.socket.connected) { console.log(socket); console.log("-- Error connecting with socket"); } socket_loaded(socket); }; function socket_loaded(socket) { // this part contains irrelevant (not socket related) code } //more irrelevant code excluded for brevity (add_message function, plus other stuff) ``` This is the stand-alone HTML file I opened locally in browser: ``` <!DOCTYPE html> <html> <head> <script type="text/javascript" src="http://code.jquery.com/jquery-git.js"></script> <script> $(document).ready(function() { if (typeof jQuery !== 'undefined') { console.log('-- client -- jQuery library loaded successfully'); $('#myButton').on('click', function() { console.log("-- client -- inside jquery '#myButton' click callback"); socket.emit('button clicked', { button: '#myButton' }); }); } $.getScript('http://node-server-for-rails-chat.herokuapp.com/socket.io/socket.io.js') .done(function( script, textStatus ) { console.log("-- socket.io.js loaded and executed -- textStatus: " + textStatus); socket_is_loaded(); }) .fail(function( jqxhr, settings, exception ) { console.log("-- socket.io.js load error -- exception:"); console.log(exception); console.log("-- jqxhr: " + jqxhr); }); }); function socket_is_loaded() { //var socket = io.connect('node-server-for-rails-chat.jit.su:5000'); var socket = io.connect('node-server-for-rails-chat.herokuapp.com:27305'); socket.socket.on('error', function (reason) { console.log('--- cant connect'); console.log(reason); console.error('Unable to connect Socket.IO', reason); }); socket.on('connect_failed', function() { console.log('connect failed'); }); socket.on('connect', function() { console.log('successfully connected!'); }); socket.on('news', function(data) { console.log("-- client -- inside socket.on 'news' callback"); console.log(data); //socket.emit('my other event', { my: 'data' }); }); } </script> </head> <body> <h1>Socket.io How-to-use Example Script</h1> <input type='button' id='myButton' value='click me!'/> </body> </html> ``` Does anyone have ideas on how I can solve this problem? Thanks a bunch.

Original source