Usage of web-worker for lobby updating (and maybe chat)? - javascript

I created a web version of a board game and made some kind of lobby that people can join.
Once the player joined the lobby a webworker (https://www.w3schools.com/html/html5_webworkers.asp) will start and check the current players within the lobby to display them.
calling part:
<script>
var w;
function startWorker() {
if (typeof(Worker) !== "undefined") {
if (typeof(w) == "undefined") {
//w = new Worker("demo_workers.js");
w = new Worker("js/lobbyUpdater.js");
w.postMessage(localStorage.groupID);
}
w.onmessage = function(event) {
var response = JSON.parse(event.data);
var player = response.player;
var playerarry = player.split(":");
document.getElementById("playerlist").innerHTML = "";
var i = 0;
for (i; i < response.playerCount; i++) {
var singleplayer = playerarry[i].split(",");
if (singleplayer[1] == localStorage.playerNumber) {
document.getElementById("playerlist").innerHTML += '<li><b>' + playerarry[i] + '</b></li>';
} else {
document.getElementById("playerlist").innerHTML += '<li>' + playerarry[i] + '</li>';
}
}
document.getElementById("currentPlayerAmount").innerHTML = response.playerCount;
if (response.closed == 1) {
window.location.href = "playerpage.html";
}
console.log(event.data);
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support Web Workers...";
}
}
function stopWorker() {
w.terminate();
w = undefined;
}
startWorker();
</script>
webworker:
function updateLobby(groupID) {
var Http = new XMLHttpRequest();
var url = '../php/checkLobby.php';
var preGroupID = '?groupID=';
url = url.concat(preGroupID, groupID);
Http.open("GET", url);
Http.send();
Http.onreadystatechange = (e) => {
//console.log(Http.responseText)
postMessage(Http.responseText);
}
var string = "updateLobby('";
string = string.concat(groupID, "')");
setTimeout(string, 1500);
}
onmessage = function (e) {
var groupID = '';
groupID = e.data;
updateLobby(groupID);
};
The weborker is running every 1.5 seconds but i thought that would be very often (every round will create a query on the backend).
Now i thought about creating a chatroom where player can talk to each other. To receive messages i though about starting an another webworker that will check for the messages.
Is the usage of webworker generally ok or am i using a "unwanted" technology for that purpose.
Is there better solution for this ?
Will the usage of such frequent sql querys in the backend lead to extreme performance peaks ? I am not experience when it comes to "how many querys can my 2c/4GB database server handle".
Thanks in advance!

In my opinion, the best solution would be to use the WebSocket API. It allows you to connect with the server then the server is able to send requests to the client! So the client does not spam the server with checking requests and he always gets fresh information when something changed.
Browser's WebSocket API is supported by each modern browser expect Opera Mini: https://caniuse.com/#feat=websockets
Integration Client-side is pretty easy. About Server-side - I did not try to create WebSocket connection with PHP so I cannot say much about that.
Alternative solution, might be Firebase Realtime database. There you can read tutorial about it: https://css-tricks.com/building-a-real-time-chat-app-with-react-and-firebase/

Related

How can I open multiple WebSocket streams

I am trying to stream data from the Binance WebSocket API, I have it working for one symbol at a time.
if ("WebSocket" in window) {
//open websocket
var symbols = getSymbol();
//console.log(symbols);
symbols.forEach(function(entry) {
console.log(entry);
})
var ws = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt#miniTicker")
ws.onopen = function() {
console.log("Binance connected...");
};
ws.onmessage = function(evt) {
var r_msg = evt.data;
var jr_msg = JSON.parse(r_msg);
}
ws.onclose = function() {
console.log("Binance disconnected");
}
} else {
alert("WebSocket is NOT supported");
}
the line var symbols = getSymbol(); creates an array of 431 symbols, my logic (and what I am trying to achieve) is to add the new websocket() to the forEach and stream price data from all of the currency pairs.
I'm not sure if this is possible at all or what a better solution would be but I wish to stream and display live data from the api.
Your idea about putting the new WebSocket() inside the for-each should work. However,
I'm not sure if you are allowed to opening hundreds of web sockets from the same tab, and there could also be some performance issues related to it.
According to the API documentation, it is possible to open just one web socket which will send you data from a list of streams, or even just all streams. Just construct the URLs like this:
Specific streams: wss://stream.binance.com:9443/ws/stream1/stream2/stream3
All streams: wss://stream.binance.com:9443/ws/!miniTicker#arr
Here is a code sample that takes these things into consideration. By default this code uses the URL for all streams, but it also has the code (commented out) that uses specific streams.
let streams = [
"ethbtc#miniTicker","bnbbtc#miniTicker","wavesbtc#miniTicker","bchabcbtc#miniTicker",
"bchsvbtc#miniTicker","xrpbtc#miniTicker","tusdbtc#miniTicker","eosbtc#miniTicker",
"trxbtc#miniTicker","ltcbtc#miniTicker","xlmbtc#miniTicker","bcptbtc#miniTicker",
"adabtc#miniTicker","zilbtc#miniTicker","xmrbtc#miniTicker","stratbtc#miniTicker",
"zecbtc#miniTicker","qkcbtc#miniTicker","neobtc#miniTicker","dashbtc#miniTicker","zrxbtc#miniTicker"
];
let trackedStreams = [];
//let ws = new WebSocket("wss://stream.binance.com:9443/ws/" + streams.join('/'));
let ws = new WebSocket("wss://stream.binance.com:9443/ws/!miniTicker#arr");
ws.onopen = function() {
console.log("Binance connected...");
};
ws.onmessage = function(evt) {
try {
let msgs = JSON.parse(evt.data);
if (Array.isArray(msgs)) {
for (let msg of msgs) {
handleMessage(msg);
}
} else {
handleMessage(msgs)
}
} catch (e) {
console.log('Unknown message: ' + evt.data, e);
}
}
ws.onclose = function() {
console.log("Binance disconnected");
}
function handleMessage(msg) {
const stream = msg.s;
if (trackedStreams.indexOf(stream) === -1) {
document.getElementById('streams').innerHTML += '<br/>' + stream + ': <span id="stream_' + stream + '"></span>';
trackedStreams.push(stream);
document.getElementById('totalstreams').innerText = trackedStreams.length;
}
document.getElementById('stream_' + stream).innerText = msg.v;
}
<span id="totalstreams"></span> streams tracked<br/>
Total traded base asset volume:<br/>
<div id="streams"></div>

".send is not a function", trying to let player 1 know that player 2 connected. (NodeJS, websockets, JavaScript)

So I have been trying to get this to work all day now.
I am creating a simple 2-player web based game using JS and NodeJS, I want to use websockets with this project.
I have gotten so far to let both players connect to the socket, player one will wait for the second player to connect.
When player 2 connects, there is a message sent to Player 2's client, saying that he can start, at which he sends a message back in which he says that he is starting, hence the 'message == "Started"'.
Then when the message has arrived from player 2 at the server, I would like the server to send a message to player 1, the only problem is that "game.playerOne.send" doesn't work, the log says that it's not a function.
To be clear:
Because game.addPlayer(con.id) was executed, game.playerOne contains the connection ID of player one.
This is the code I'm talking about:
const wss = new websocket.Server({ server });
var websockets = {};
var game = new Game(gameStats.started++);
var connectionID = 0;
wss.on("connection", function(ws) {
let con = ws;
con.id = connectionID++;
console.log(con.id + " has connected");
let player = game.addPlayer(con.id);
websockets[con.id] = game;
console.log("Player %s placed in game %s", ws.id, game.id);
if (game.twoConnected()) {
setTimeout(function() {
ws.send("Player 1 is ready!");
}, 1000);
game = new Game(gameStats.started++);
}
con.on("message", function incoming(message) {
let game = websockets[con.id];
let isPlayerOne = (game.playerOne === con);
if (isPlayerOne) {
} else {
if (message == "Started") {
game.playerOne.send(message);
}
}
});
});
I would really like to know how I would go about sending a message to the other websocket (Player 1's socket) and why "game.playerOne.send" is not a valid function.
Thanks in advance!
You are adding ID's of the websocket objects to your player array, not the websocket itself. Therefore you are calling 'send' on an integer, not a websocket object.
Quoted from my comment.
I wasn't able to test this but I changed the line suffixed by // <--. If this is not the solution you are after, I suggest that you edit your question to contain the functions responsible for storing and retrieving the player and all the games so we can take a better look at how and what objects are being stored.
On a sidenote, I suggest using console.log (docs) on, for example, your game.playerOne. This way you can see all of the properties of the object in your debugging console.
const wss = new websocket.Server({ server });
var websockets = {};
var game = new Game(gameStats.started++);
var connectionID = 0;
wss.on("connection", function(ws) {
let con = ws;
con.id = connectionID++;
console.log(con.id + " has connected");
let player = game.addPlayer(con); // <-- this
websockets[con.id] = game;
console.log("Player %s placed in game %s", ws.id, game.id);
if (game.twoConnected()) {
setTimeout(function() {
ws.send("Player 1 is ready!");
}, 1000);
game = new Game(gameStats.started++);
}
con.on("message", function incoming(message) {
let game = websockets[con.id];
let isPlayerOne = (game.playerOne === con);
if (isPlayerOne) {
} else {
if (message == "Started") {
game.playerOne.send(message);
}
}
});
});

Service Worker Respond To Fetch after getting data from another worker

I am using service workers to intercept requests for me and provide the responses to the fetch requests by communicating with a Web worker (also created from the same parent page).
I have used message channels for direct communication between the worker and service worker. Here is a simple POC I have written:
var otherPort, parentPort;
var dummyObj;
var DummyHandler = function()
{
this.onmessage = null;
var selfRef = this;
this.callHandler = function(arg)
{
if (typeof selfRef.onmessage === "function")
{
selfRef.onmessage(arg);
}
else
{
console.error("Message Handler not set");
}
};
};
function msgFromW(evt)
{
console.log(evt.data);
dummyObj.callHandler(evt);
}
self.addEventListener("message", function(evt) {
var data = evt.data;
if(data.msg === "connect")
{
otherPort = evt.ports[1];
otherPort.onmessage = msgFromW;
parentPort = evt.ports[0];
parentPort.postMessage({"msg": "connect"});
}
});
self.addEventListener("fetch", function(event)
{
var url = event.request.url;
var urlObj = new URL(url);
if(!isToBeIntercepted(url))
{
return fetch(event.request);
}
url = decodeURI(url);
var key = processURL(url).toLowerCase();
console.log("Fetch For: " + key);
event.respondWith(new Promise(function(resolve, reject){
dummyObj = new DummyHandler();
dummyObj.onmessage = function(e)
{
if(e.data.error)
{
reject(e.data.error);
}
else
{
var content = e.data.data;
var blob = new Blob([content]);
resolve(new Response(blob));
}
};
otherPort.postMessage({"msg": "content", param: key});
}));
});
Roles of the ports:
otherPort: Communication with worker
parentPort: Communication with parent page
In the worker, I have a database say this:
var dataBase = {
"file1.txt": "This is File1",
"file2.txt": "This is File2"
};
The worker just serves the correct data according to the key sent by the service worker. In reality these will be very large files.
The problem I am facing with this is the following:
Since I am using a global dummyObj, the older dummyObj and hence the older onmessage is lost and only the latest resource is responded with the received data.
In fact, file2 gets This is File1, because the latest dummyObj is for file2.txt but the worker first sends data for file1.txt.
I tried by creating an iframe directly and all the requests inside it are intercepted:
<html>
<head></head>
<body><iframe src="tointercept/file1.txt" ></iframe><iframe src="tointercept/file2.txt"></iframe>
</body>
</html>
Here is what I get as output:
One approach could be to write all the files that could be fetched into IndexedDB in the worker before creating the iframe. Then in the Service Worker fetch those from indexed DB. But I don't want to save all the resources in IDB. So this approach is not what I want.
Does anybody know a way to accomplish what I am trying to do in some other way? Or is there a fix to what I am doing.
Please Help!
UPDATE
I have got this to work by queuing the dummyObjs in a global queue instead of having a global object. And on receiving the response from the worker in msgFromW I pop an element from the queue and call its callHandler function.
But I am not sure if this is a reliable solution. As it assumes that everything will occur in order. Is this assumption correct?
I'd recommend wrapping your message passing between the service worker and the web worker in promises, and then pass a promise that resolves with the data from the web worker to fetchEvent.respondWith().
The promise-worker library can automate this promise-wrapping for you, or you could do it by hand, using this example as a guide.
If you were using promise-worker, your code would look something like:
var promiseWorker = new PromiseWorker(/* your web worker */);
self.addEventListener('fetch', function(fetchEvent) {
if (/* some optional check to see if you want to handle this event */) {
fetchEvent.respondWith(promiseWorker.postMessage(/* file name */));
}
});

Server-Sent Events with Ruby Grape

I am trying to create Server-Sent events on my Ruby Grape API.
The problem is that the connection seems to be closed really fast all the time, as I get Connection closed event all the time on test webpage.
The client connects to the server as I can see the method being called, but I would like to know why is the connection not constant and why I don't receive the data I send using the Thread.
Here is my Ruby code:
$connections = []
class EventsAPI < Sinantra::Base
def connections
$connections
end
get "/" do
content_type "text/event-stream"
stream(:keep_open) { |out|
puts "New connection"
out << "data: {}\n\n"
connections << out
}
end
post "/" do
data = "data\n\n"
connections.each { |out| out << data }
puts "sent\n"
end
end
Here is my Javascript:
var source = new EventSource('http://localhost:9292/events');
source.onmessage = function(e) {
console.log("New message: ", e.data);
showMessage(e.data);
};
source.onopen = function(e) {
// Connection was opened.
};
source.onerror = function(e) {
console.log("Source Error", e)
if (e.eventPhase == EventSource.CLOSED) {
console.log("Connection was closed");
// Connection was closed.
}
};
var showMessage = function(msg) {
var out = document.getElementById('stream');
var d = document.createElement('div')
var b = document.createElement('strong')
var now = new Date;
b.innerHTML = msg;
d.innerHTML = now.getHours() + ":" + now.getMinutes() + ":" +now.getSeconds() + " ";
d.appendChild(b);
out.appendChild(d);
};
EDIT: I got it working with the GET method (I changed the Grape::API to Sinatra::Base as Grape does not implement stream). I now receive data, but the connection is not kept alive and when I use the post method the data never reaches the browser.
Thank you in advance for your answers.
The JS code looks correct. My guess is that you should not start a new thread for your infinite loop. What will be happening is that the main thread will carry on executing, reach the end of its block, and close the http request. Your detached thread is then left writing to a non-existent out stream.
UPDATE in response to your EDIT: POST is not supported in SSE. Data can only be passed to an SSE process by using GET data or cookies.

Receive non HTTP requests in node.js

I need to receive requests in Node JS that almost identical to HTTP requests, but have a different word to HTTP in the header for example the first line of the request is:
POST / RTSP/1.0
(instead of POST / HTTP/1.0)
The rest of the request format is identical to HTTP in every way.
Is there a way of making the http server parser ignore that the protocol is called HTTP in the first line of the request? So I can use http.createServer etc to receive and respond to these "non-HTTP" requests?
(I know I could use the net module rather than the http module, but then I'd have to implement the header parsing etc, all myself. )
Simplest way would be to implement tcp server in node that acts as a proxy replacing initial POST:
( note: I haven't tested this code, but it should be good enough to illustrate idea)
var net = require('net');
net.createServer(function(s)
{
var sawRequest = false;
var buff = "";
var requestText = "";
var connected = false;
var cli = net.createConnection(yourHttpServerPort);
s.on('data', function(d) {
if (!sawRequest) {
requestText += d.toString();
if (requestText.match(/POST \/ RTSP\/1.0/)) {
requestText.replace('POST / RTSP/1.0', 'POST / HTTP/1.0');
buff = requestText;
sawRequest = true;
}
} else {
buff += d.toString();
}
if (connected)
{
if (buff != '')
cli.write(buff);
cli.write(d);
} else {
buff += d.toString();
}
});
cli.once('connect', function() {
connected = true;
cli.write(buff);
});
cli.pipe(s);
}).listen(publicFacingRTSPport);
You could probably do the same with HAProxy

Categories