Bridging a simple Node.js & Socket.io chat app with a Rails app (on Heroku) - ruby-on-rails

I have a basic Node.js & Socket.io chat application running on Heroku that I want to integrate into my main rails website. I understand the way to do this is to have two separate Heroku apps - one for rails, one for Node.js.
It doesn't appear to be as simple as moving the client html from the node app to the rails app (giving it the other app's url in 'io.connect();').
The chat app server seems to automatically call the client index.html its own application, and not allow an external source to connect to it. Removing the code that does this (marked below) does not make it work.
I'm painfully new to Node.js & Socket.io and am hoping that this might be a relatively simple fix for a pro.
I believe the functionality I'm after here works in Liam Kaufman's excellent rails/node.js/socket.io example - his node.js server code is here: https://github.com/liamks/Chatty-Node-Server/blob/master/chat-server.js
I've tried mocking my app's code up to be like his, but haven't yet been able to make it work. He e.g. appears to use an 'http' server, whereas mine uses an 'express' server - I wondered if this might be relevant.
Any help would be greatly appreciated.
UPDATE: Ok, so a bizarre turn of events, thanks to redhotvengeance's reply below I've got this working - server is up on heroku and my client html and javascript connects to it. Great - code below. The problem is, however, that the client html file only connects when it's outside of the Rails app!! i.e. on my desktop!! The moment I put it in the rails application's public/ folder or in a view on my localhost, I get nothing! This makes no sense. I checked it wasn't because of any other random erroneous javascript in my asset pipeline conflicting by just creating a new rails app and dropping the html file in the public/ folder - again nothing - just a dead html page that doesn't connect. Does anyone have any idea what might be going on here? Does Rails have some security feature in place that stops connections to external servers or something??
UPDATE 2: I'm told this has something to do with the 'same origin policy', and I'm in trouble. Is there any way around it? Seems Liam didn't have this problem.
Client:
<script src="http://calm-sands-3826.herokuapp.com/socket.io/socket.io.js" type="text/javascript"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script>
var socket = io.connect('http://calm-sands-3826.herokuapp.com');
// on connection to server, ask for user's name with an anonymous callback
socket.on('connect', function(){
// call the server-side function 'adduser' and send one parameter (value of prompt)
socket.emit('adduser', prompt("What's your name?"));
});
// listener, whenever the server emits 'updatechat', this updates the chat body
socket.on('updatelog', function (username, data) {
$('#log').append('<b>'+username + ':</b> ' + data + '<br>');
});
// listener, whenever the server emits 'updateusers', this updates the username list
socket.on('updateusers', function(data) {
$('#users').empty();
$.each(data, function(key, value) {
$('#users').append('<div>' + key + '</div>');
});
});
</script>
<div style="float:left;width:100px;border-right:1px solid black;height:300px;padding:10px;overflow:scroll-y;">
<b>USERS</b>
<div id="users"></div>
</div>
<div style="float:left;width:300px;height:250px;overflow:scroll-y;padding:10px;">
<div id="log"></div>
</div>
Server:
var port = process.env.PORT || 5001;
var io = require('socket.io').listen(parseInt(port));
io.configure(function(){
io.set("transports", ["xhr-polling"]);
io.set("polling duration", 10);
io.set("close timeout", 10);
io.set("log level", 1);
})
// usernames which are currently connected to the chat
var usernames = {};
io.sockets.on('connection', function (socket) {
// when the client emits 'adduser', this listens and executes
socket.on('adduser', function(username){
// we store the username in the socket session for this client
socket.username = username;
// add the client's username to the global list
usernames[username] = username;
// echo to client they've connected
socket.emit('updatelog', 'SERVER', 'you have connected');
// echo globally (all clients) that a person has connected
socket.broadcast.emit('updatelog', 'SERVER', username + ' has connected');
// update the list of users in chat, client-side
io.sockets.emit('updateusers', usernames);
});
// when the user disconnects.. perform this
socket.on('disconnect', function(){
// remove the username from global usernames list
delete usernames[socket.username];
// update list of users in chat, client-side
io.sockets.emit('updateusers', usernames);
// echo globally that this client has left
socket.broadcast.emit('updatelog', 'SERVER', socket.username + ' has disconnected');
});
});

If what you're trying to do is connect pages in your Rails app to your seperate Node.js app running socket.io, then skip setting up Express entirely. You're not looking to actually serve pages from your Node app, just connect users to the socket.io server.
Let's say your Node.js app on Heroku is called: my-awesome-socket-app.
my-awesome-socket-app:
var io = require('socket.io').listen(parseInt(process.env.PORT));
io.configure(function () {
io.set("transports", ["xhr-polling"]);
io.set("polling duration", 10);
});
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function () {
io.sockets.emit('user disconnected');
});
});
Then, in the Rails pages you want to connect to the socket.io server:
<script src="http://my-awesome-socket-app.herokuapp.com/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://my-awesome-socket-app.herokuapp.com');
socket.on('connect', function (data) {
console.log('connected!');
});
</script>

Related

What to change to prevent double request from service worker?

Please do not mark as duplicate. This is not an exact duplicate of the other similar questions here on SO. It's more specific and fully reproducible.
Clone this repo.
yarn && yarn dev
Go to localhost:3000 and make sure under (F12)->Applications->Service workers, the service worker is installed.
Go to Network tab and refresh a few times(F5)
Observe how the network requests are doubled.
Example of what I see:
Or if you want to do it manually follow the instructions below:
yarn create-next-app app_name
cd app_name && yarn
in public folder, create file called service-worker.js and paste the following code:
addEventListener("install", (event) => {
self.skipWaiting();
console.log("Service worker installed!");
});
self.addEventListener("fetch", (event) => {
event.respondWith(
(async function () {
const promiseChain = fetch(event.request.clone()); // clone makes no difference
event.waitUntil(promiseChain); // makes no difference
return promiseChain;
})()
);
});
open pages/index.js and just below import Head from "next/head"; paste the following code:
if (typeof window !== "undefined" && "serviceWorker" in navigator) {
window.addEventListener("load", function () {
// there probably needs to be some check if sw is already registered
navigator.serviceWorker
.register("/service-worker.js", { scope: "/" })
.then(function (registration) {
console.log("SW registered: ", registration);
})
.catch(function (registrationError) {
console.log("SW registration failed: ", registrationError);
});
});
}
yarn dev
go to localhost:3000 and make sure the service worker has been loaded under (F12)Applications/Service Workers
Go to the Network tab and refresh the page a few times. See how the service worker sends two requests for each one
What do I need to change in the service-worker.js code so that there are no double requests?
This is how Chrome DevTools shows requests and is expected.
There is a request for a resource from the client JavaScript to the Service Worker and a request from the Service Worker to the server. This will always happen unless the service worker has the response cached and does not need to check the server for an update.
Does not seems the right way to initialize service worker in Next.js.You may need to look into next-pwa plugin to do it right.Here is the tutorial PWA with Next.js
If anyone is looking for an answer to the original question 'What to change to prevent double request from service worker?', specifically for network requests.
I've found a way to prevent it. Use the following in the serviceworker.js. (This also works for api calls etc.)
self.addEventListener('fetch', async function(event) {
await new Promise(function(res){setTimeout(function(){res("fetch request allowed")}, 9999)})
return false
});

Best practices for calling intuit.ipp.anywhere.setup()?

This is a question about best practices for making the JavaScript call that generates the standard "Connect to QuickBooks" button (for establishing a connection to QuickBooks Harmony via Intuit's v3 REST API).
If I follow Intuit's example, I would:
Reference https://appcenter.intuit.com/Content/IA/intuit.ipp.anywhere.js in a script tag.
Place the <ipp:connectToIntuit></ipp:connectToIntuit> tagset where I want the "Connect to QuickBooks" button to display
Cross my fingers and hope that intuit.ipp.anywhere.js isn't redirecting to a downtime message, again still exists
Make my call to intuit.ipp.anywhere.setup()
See the "Connect to QuickBooks" button
... which works (for many values of "works"), but feels pretty fragile:
If intuit.ipp.anywhere.js is redirecting to a downtime message (read: not JavaScript) or is otherwise unavailable, I'll get a script error.
If I get a script error (or something else goes wrong with Intuit's copy of the script), there isn't any feedback to the user, just a blank space where the "Connect to QuickBooks" button should be.
To make this all a little more resilient, I'm combining the reference to intuit.ipp.anywhere.js and the call to intuit.ipp.anywhere.setup() into a JQuery .ajax() call:
$.ajax({
url: 'https://appcenter.intuit.com/Content/IA/intuit.ipp.anywhere.js',
type: 'GET',
dataType: 'script',
timeout: 4000,
success: function(response) {
if (typeof intuit !== 'undefined') {
intuit.ipp.anywhere.setup({
menuProxy: 'MYMENUPROXYURL.aspx',
grantUrl: 'MYGRANTURL.aspx'
});
}
},
error: function(x, t, m) {
// show some friendly error message about Intuit downtime
}
});
... which also works (for a few more values of "works"):
My call to setup() is wrapped inside the success handler (and an additional check on the existence of the intuit Object), so I shouldn't get a script error if things go wrong.
If the GET of Intuit's script times out (after 4000ms) or returns something that isn't script, I'll show a friendly error message to the user.
Has anyone else taken a different approach?
And is Intuit back online?
That's similar to how we've handled it. We had wrapped it in jQuery.getScript call, but apparently the .fail handler doesn't work with cross domain script tags. Our solution is as follows:
<script type="text/javascript>
var timeoutID;
timeoutID = window.setTimeout(function () {
$("#ippConnectToIntuit").replaceWith('<p class="error-message">There was a problem communicating with QuickBooks. The service may be down or in heavy use. Try again later.</p>');
}, 5000);
$.getScript("https://appcenter.intuit.com/Content/IA/intuit.ipp.anywhere.js")
.done(function () {
window.clearTimeout(timeoutID);
intuit.ipp.anywhere.setup({
menuProxy: '/path/to/our/menu/proxy',
grantUrl: '/path/to/our/grant/url'
});
});
</script>
<div id="ippConnectToIntuit"><ipp:connecttointuit></ipp:connecttointuit></div>

YUI3 and socket.io

Just a simple question:
I am using YUI3 framework for my website and want to use socket.io framework.
Now challenge is to use socket.io with YUI3.
As of now I am using socket.io logic inside YUI sandbox and its working fine.
BUT can there be any fallback of this approach ? If yes, then how should I integerate both ?
Here is the snippet of code:
<script type="text/javascript">
YUI().use('my-slide' , 'node', 'event','transition', function (Y) {
// connecting to nodejs server running on 7001 port for dynamic updates
var broadcast = io.connect('http://localhost:7001/getlatestbroadcast');
broadcast.on('status',function(data){
// some socket logic here
});
// Setting Listener
broadcast.on('moreData',function(data){
// some socket logic here
});
});
</script>
What you're doing definitely works, and there's no problem in using it that way unless you have a conflict with some other variable named io. A slightly more effective way of using Socket.IO (or any other external module in YUI) is to namespace it on the Y object instead:
YUI({
modules: {
'socket.io': {
fullpath: '/socket.io/socket.io.js'
}
},
onProgress: function (e) {
if (e.data[0].name === 'socket.io') {
YUI.add('socket.io', function (Y) {
Y.Socket = io;
});
}
}
}).use('socket.io', function (Y) {
var socket = Y.Socket.connect('http://localhost');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
});
This takes the example from the socket.io website and lets you namespace it as Y.Socket. That way, only when you specifically do YUI().use('socket.io'), will you actually be able to access Y.Socket, which helps with keeping your code organized and loaded in the correct order, thanks to the YUI Loader.
Also, feel free to check out the Socket Model Sync YUI Gallery module I created, if you're looking for an easier way to integrate your YUI App Framework application with Socket.IO.
Hope this helps, and let me know if you have any more questions about integrating the two!

How to connect multiple socket.io clients to different URLs in Node.js?

I am trying to connect many socket.io clients for different URLs in Node.js like so :
app.get('/:id',function(req,res){
io.of('/'+id).on('connection',function(socket){
socket.emit('hello');
})
});
This works however there is a problem :
When a browser refreshs the page http://localhost:3000/xyz for example, the event socket.emit gets fired two times.
If someone accesses the page http://localhost:3000/xyz 10 times, then the event fires 10 times.
This is not good because everytime the user visits that page, the socket events will be fired n+1 times.
What should be done so that I can register sockets to different URLs and at the same time not have this anomaly .
Another thing :
If I do this :
var sock;
io.of('/'+xyz).on('connection',function(socket){
sock=socket;
})
app.get('/:id',function(req,res){
sock.emit('hello');
})
If I use the above code then the socket doesn't get saved succesfully to the sock variable in time. What that means is , I have to do a setInterval of about 1000 .. so that the
sock=socket
line gets fired.
Please help me.
Because with this, in each request to http://localhost:3000/id, you register a new handler, you should be doing that once, not at every request.
app.get('/:id',function(req,res){
io.of('/'+id).on('connection',function(socket){
socket.emit('hello');
})
});
I use below approach to achieve this goal:
client side:
var socket = io.connect('http://localhost:8183/?clientId='+clientId,{"force new connection":true});
server side:
var io = require('socket.io').listen(server);
io.sockets.on('connection', function(socket) {
console.log("url"+socket.handshake.url);
clientId=socket.handshake.query.clientId;
console.log("connected clientId:"+clientId);
});
reference:https://github.com/LearnBoost/socket.io/wiki/Authorizing#global-authorization

Backbone Model from Hub in SignalR

How can i create/convert this script into model in Backbone that can use SignaR Hubs? For example:
<script type="text/javascript">
$(function () {
// Proxy created on the fly
var chat = $.connection.chat;
// Declare a function on the chat hub so the server can invoke it
chat.addMessage = function (message) {
alert("message");
};
// Start the connection
$.connection.hub.start();
});
</script>
EDIT
I did come up with this:
window.Message = Backbone.Model.extend({
hub: undefined,
initialize: function () {
this.hub = $.connection.message;
},
addMessage: function (message) {
alert(message);
},
connect: function () {
$.connection.hub.start();
var messages = this.hub.getAll();//get messages
}
});
but this is not working due to the following error:
this error: :55885 Unexpected response code: 200
If you use default settings SignalR will first try to send a websockets poll to the server. The :55885 is simply the port number of your server. Websockets protocol expects a response status code of 101 (see http://dev.w3.org/html5/websockets/).
If running IIS, unless you run Windows 8 with ASP.NET 4.5 your webserver, it will not recognize a web sockets request and (begin speculation) treat it as a normal get request and return status code 200 (OK) (end speculation) which is an unexpected response in the eyes of the websockets initiator. When this happens SignalR falls back to longpolling instead.
This might not answer your question but it will help you understand the error you get (which is likely not the reason why your code doesn't work)
Also, check out http://srtsolutions.github.com/backbone.signalr/ which is a Backbone.js/SignalR integration Nuget package.

Resources