I'm trying to implement an HTML5 app that will work on desktop, android, and iOS. The app's main function is to wirelessly send commands to the server about what scripts to run and to receive regular messages pushed from that server about the status of those scripts.
The server is running nodejs and I decided to use Server Sent Events to do the push notifications. This requires that I use Firefox on Android since the native Android browser doesn't support SSE.
My implementation all works fine: the node server is publishing the events as it should and my client-side HTML5/javascript is picking it up fine on desktop chrome/firefox/safari, on my iPod iOS6, and my Android 2.2 phone.
However, there are 4 common situations that I need to handle:
the device loses its wi-fi signal
nodejs server crash (hopefully this isn't common!)
put browser into the background on iPod/Android while browsing another app, etc.
lock screen on iPod/Android while browser is running
Chrome/Safari behave perfectly on both desktop and iPod, as follows: if the server crashes, the site automatically reconnects and gets the pushed messages as soon as the server is up again, and if the browser app goes into the background for whatever reason, it is still getting those messages while in the background, or at the very least automatically reconnects as soon as it comes back into the foreground.
Firefox, however, both on desktop and on Android, is all too eager to close down that EventSource connection permanently. As soon as the browser loses connection to the server, either from server crash, from putting the firefox app into the background or from locking the screen, the EventSource connection is killed and does not ever try to reconnect. Of course, you can just reload the page when you come back to it, but this is annoying, especially in the case where you need to lock the screen because you need to put the phone in your pocket (this app needs to be used in some trekking situations).
Can anyone recommend any solution for this aside from just having to reload the page in Android Firefox all the time? Below is my dummy implementation, just sending random numbers every 5 seconds.
Server at /main/src
src : function(req, res) {
req.socket.setTimeout(Infinity);
// send headers for event-stream connection
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
res.write('\n');
var messageCount = 0;
var process;
function printEvent() {
messageCount++;
rand = Math.floor((Math.random()*10000)+1);
res.write('id: ' + messageCount + '\n');
res.write("data: " + rand + '\n\n');
process = setTimeout(printEvent, 5000);
}
printEvent();
res.socket.on('close', function () {
res.end();
clearTimeout(process);
});
}
Client
var source = new EventSource('/main/src');
source.onopen = function(e){
$("#test").html("Connected to server. Waiting for data...");
}
source.onmessage = function(e){
$("#test").html("MSG: " + e.data);
}
source.onerror = function(e){
var txt;
switch(e.target.readyState){
case EventSource.CONNECTING:
txt = 'Reconnecting...';
break;
case EventSource.CLOSED:
txt = 'Connection failed. Will not retry.';
break;
}
$("#test").html("Error: " + txt);
}
Thanks!!
i know only one solution, that is already used in many libs:
"heartbeat" messages - on the client side you may check, if the "random number" is not received from the server in 10 seconds (5000 seconds + some lag) - the connection is seems to be broken and you should do the reconnection
(with native EventSource you may use "close" method, than create new EventSource OR you can try https://github.com/Yaffle/EventSource )
Related
I am building a web app using Vue and Socket.io.
The app is divided into two: A Host (Hosting a party) and the guests.
It's a party game and the people participating will be guidede to do stuff simultaneously so syncing between devices is crucial.
Every body (including the host) will be asked to download an audio file. Once everybody has downloaded the audio file the Host can then start the playback on all devices using socket.io.
The Host emits an event "startAllAudio" to the socket.
socket.emit("startAllAudio");
When the socket gets the event it emits a new event "startAudio" with a timestamp set 3 seconds ahead in time.
const startDelay = 3000;
var now = new Date().getTime();
io.emit("startAudio", {
timeToStart: now + parseInt(startDelay),
});
All this works like a charm on iOs devices. But on android it does not. After debugging a lot I found that the internal clock on the Android devices is out of sync with (Or at least not the same as) the iOs devices. I have two different relatively new Android phones for testing and even they are not in sync with each other. I have 10+ various iOs devices for testing and the all sync perfectly.
I made a javascript clock with milliseconds and put it on the web app for debugging. And you can clearly see that the two Android phones are different from each other and from the iOs devices which are the same (within reason). The difference in time matches the audible difference in playback.
var interval = window.setInterval(() => {
const date = new Date(Date.now());
this.time = date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() + ":" + date.getMilliseconds();
}, 500);
So finally we get to the actual question(s):
How would you go about fixing that?
Can you force the android devices to timesync? Maybe even to a specific time server?
I thought about "probing" the responce time between the web app and the socket.
i.e. sending 3 emits and time the respons time and the take an average of that and use it with the timestamp on the Android phones to adjust the timeToStart value sendt from the socket. But it does not seem like a bulletproof method.
Any suggestions would be greatly appreciated.
I have come across many posts on here and various other sites about how to get socket.io working in an android phonegap app, unfortunately I still seem to be having problems. If you've had any experience of getting this kind of thing set up, any pointers you could give would be greatly appreciated, having now spent several days tearing my hair out :P
So to get started I have a very simple socket.io server setup and running on my local machine:
var server = require('http').createServer();
var io = require('socket.io')(server);
io.sockets.on('connection', function (socket) {
console.log('socket connected');
socket.on('disconnect', function () {
console.log('socket disconnected');
});
socket.emit('text', 'wow. such event. very real time.');
});
server.listen(3000);
which I got from one of the tutorials that I found, and it seems to work ok.
Now in my phonegap app all the code I have is as follows:
var app = angular.module("MyApp", [
"MyCtrl"
]);
app.controller("AppCtrl", ["$scope", function ($scope) {
console.log("About to attempt a connection (" + socket + ")!");
var socket = io("10.0.5.159:3000");
socket.on("connect", function(data) {
console.log("Connected: " + data);
});
socket.on("error", function(err) {
console.log("Error: " + err);
});
}]);
Just a basic angular.js app with a single controller that attempts to connect to the server (the ip address provided is the correct one on my local network). My log gets hit so I know this code is executed, but I never get a connect event or an error event, and no errors come up in the console, it just seems to fail silently. I also get no connection event on the server.
As per another stackoverflow post I have ensured that my config.xml contains the appropriate
<access origin="10.0.5.159*" />
entry. But I still seem to be getting nowhere with it. The same line of code
var socket = io("10.0.5.159:3000");
when executed in a safari browser window works exactly as expected and my server logs a connection event, so I'm confident that the server and the connect code are valid.
Any ideas what I might be doing wrong?
Your code is not working because on the server side u have emit "text" event therefore on client side your are supposed to write socket.on("text")
On server side you haven't emit "connect" and "error" events then how you are supposed to get these events on client side. And I don't think that normal socket.js works with Angular js, You have to add Angular.js socket.io library.
I have an app that works fine on iOS. It uses Cordova and uses remote requests for some of its functions, however, on android, only on a device, it sometimes (pretty frequently) will not connect. It almost always works on the Genymotion emulator, except once, where I saw "NET CACHE ERR" or something similar to that in the Chrome dev tools console. (The default Android emulator cannot connect however.)
The calls are simple jQuery ajax post requests that work fine in the Genymotion emulator (all times except once), and fine in a browser and in the iOS Cordova app. Networking permissions are enabled and asked upon installation. Interestingly, it can still get the device id used for push notifications (which requires a network response, from what I can tell, however it could be misleading I suppose).
Example for the obligatory request:
function connect(){
$.post('https://example.com/stuff.php', {
'var': 'a variable!'
}, function(data) {
//internet request complete, confirm connection successful
window.connected = true;
});
}
window.connected = false;
setInterval(
function(){
if(window.connected==true){
//yeah you connected
}else{
//naw you not connected homie
connect();
}
},
3500
);
Lets say there are two android devices and a webpage.
On each of those devices there is a button being clicked and the webpage shows the accumulated clicks.
What makes this possible?
Are the android devices sending that data to a sql database and that counter is reading what currently is in that database?
Or can the device really send the information into the page?
The device can communicate data to the server or to the client side code. You can then have to decide what to do with the data. If you send it to the client side code then it would only update on one device, whereas sending it to the server would allow one click counter across every device.
Changing the page on the server side would add caching problems. So you probably want a data file instead, just a json or something that the server overwrites every time it receives another click.
This isn't very fast though and causes synchronisation issues between the users. For starters for a standard html page the client code is in charge of the calls to the server, so we would have to check the server periodically to see if the value has changed (say every 5 seconds).
A faster alternative would be to use a web socket. The client would keep a connection open to the server and listen for updates from the server, removing the need for our periodic check. Additionally the socket can be used to send clicks to the server and the server can keep the value in its memory removing the need for file writes.
Node.js can be downloaded from http://nodejs.org and this is the plug in I use for web sockets https://github.com/Worlize/WebSocket-Node.
So here is some server side code for a simple web socket server that parrots messages from 1 user to all users, including the user who sent it.
var connections = [];
var WebSocketServer = require('websocket').server;
var http = require('http');
var server = http.createServer(function(request, response) {
// process HTTP request. Since we're writing just WebSockets server
// we don't have to implement anything.
});
server.listen(1337, function() { });
// create the server
wsServer = new WebSocketServer({
httpServer: server
});
// WebSocket server
wsServer.on('request', function(request) {
//got a new user requesting a connection, so lets accept and store them
var connection = request.accept(null, request.origin);
connections.push(connection);
connection.on('message', function(message) { //inbound message
if (message.type === 'utf8') {
// process WebSocket message
send(message.utf8Data); //bounce to everyone else
console.log(message);
}
});
});
setInterval(function(){console.log(connections.length +" :users");},5000);
//every 5 seconds, tell us how many users we have
function send(message){
var i = connections.length;
while(i--)
connections[i].send(message);
//send the message to all users
}
Example client side
<html><head><script>
var connection, connIsActive = false;
// if user is running mozilla then use it's built-in WebSocket
window.WebSocket = window.WebSocket || window.MozWebSocket;
connection = new WebSocket('ws://127.0.0.1:1337');
connection.onopen = function () {
// connection is opened and ready to use
connIsActive = true;
console.log('Connection loaded');
};
connection.onerror = function (error) {
// an error occurred when sending/receiving data
connIsActive = false
console.log('error');
};
connection.onmessage = function (message) {
// handle incoming message
console.log(message.data);
};
function send(message){
if(connIsActive)connection.send(message);
}
</script></head></html>
Save the first snippet as "myserver.js" (or similar) and start it up in node via command line. Save the second snippet as a "client.html" and open it on 2 devices, or just 2 browser instances. Theres no interface on the client but you can send and receive messages from the debug console using send("message string")
Provided you can wrap your head around my snippets, modifying it to work like your example should prove fairly easy!
To achieve what you are trying to do, you need to learn socket programming for android.
In order to use two device(clients) you also need to learn port forwarding for servers.
To begin try this tutorial.
I set up an openfire server on a server within our network and gave it a domain name. I want to connect to it via my phone in a phonegap app and therefore implemented a strophe.js client within my app.
It works fine on my Nexus 5 (Android 4.4.3), but as soon as I want to run it on my Samsung Galaxy S2 (Android 4.1.2) or the Samsung Galaxy Tab (GT-P7501 - Android 4.0.4) I don't get any response from the server. Here is the code snippet of my connect:
var BOSH_SERVICE = 'http://SERVERNAME:7070/http-bind/';
var connection = null;
$(document).ready(function () {
connection = new Strophe.Connection(BOSH_SERVICE);
connection.rawOutput = log;
connection.rawInput = log;
connection.connect('id#servername/resource', 'test', onConnect);
});
function log(msg) {
console.log(msg);
}
function rawInput(data) {
log('RECV: ' + data);
}
function rawOutput(data) {
log('SENT: ' + data);
}
the console log will be:
SENT: <body rid='367573377' xmlns='http://jabber.org/protocol/httpbind' to='servername' xml:lang='en' wait='60' hold='1' content='text/xml; charset=utf-8' ver='1.6' xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'/>
This will be repeated a few times but I don't get any incoming messages. The servers version number is openfire 3.9.3. As all this code works on my nexus 5 I assume that the code is correct. I Also doubt that my server is configured wrong, nevertheless I included a screenshot of the config settings of the openfire server in the end.
The openfire xmpp server is running on a windows server and I access it via wifi/dyndns.
Do you have any ideas why this does not work on the samsung galaxy? Every help will be appreciated. Thanks in advance !
I could "solve" this by myself. It is really not a big deal, but it took me a really long time (3 days) to realize this, so in case anyone will ever come accross the same phenomena this info might help:
In my case all the configurations of the server and the strophe client above are correct. Indeed the only reason the connect did not work properly on all devices seemed to be that these devices could not even ping the server, even though they are in the same network. In my case I gave the server a static domain name, which was the key issue. Somehow the Google Nexus 5 is able to resolve this name to an ip adress via the dns-server, but both the elder samsung galaxy s2 and the samsung galaxy tab aren't.
Solution: I replaced the static domain name with the corresponding ip-adress in my strophe.js connection.