Search code examples
timesynchronizationnetwork-programminglow-latency

How to detect which client pressed a button first on a webpage?


Context

I'm trying to create a trivia website that can detect which user pressed a button first; my idea is that the first user who clicks the button gets to answer the trivia question. I don't really mind which backend framework I end up having to use, but I need upmost precision.

Stay with me - I've got a lot of questions.

Questions

  1. If I were to send a timestamp along with a request when a user presses a button, how can I make sure this timestamp isn't spoofed? Or more likely, how can I make sure that all clients' clocks are synced as precisely as possible? (Side question, how do websites like time.is detect the precision of your clock?) For example, if client A's clock was 250ms faster than client B, then wouldn't that mean client A has an advantage?

  2. If I were to just simply use the first request that the server receives, wouldn't this be susceptible to latency? And if so, is there a way to compensate for this by say, getting the average latency of the connection, then subtracting it from the timestamp from when the server received the request? (Sidenote: latency on the connection(s) I intend to host this website on may not be stable/consistent.)

  3. Would a router impact the latency between client and server? For example, in the first point, if every client were to sync their clocks, if the router were to delay the connection of one client (for example, due to performance limitations), wouldn't one client's clock be more delayed? And in the second point, if the router were to delay a connection, wouldn't a client be disadvantaged? Any way to circumvent or mitigate this?

  4. I know this kind of ruins the entire point of the question, but is there a way to connect a USB cable from a client device (e.g. phone) to the server (e.g. laptop)? Would this be more precise? (This one's more just for my curiosity, I don't actually plan to do this.)

  5. Would the peformance of a client's device have any noticeable impact? Usually, I'd assume this is negligible, but I imagine that it could be more concerning when 10 people are mashing a button at the same time. Again, any way to circumvent/mitigate this?

Requirements

Ideally, I would like a client to just open their web browser and go to a URL or IP for simplicity (also because I really do not want to learn React or Flutter or something else). The server is likely just going to be a mid-range laptop, and the clients are probably going to be something like a mobile device (e.g., phone).

I don't need to deliver a question to the clients at the same time, like Kahoot. I only need to find which user pressed a button first. I also don't really mind waiting for, say half a second, before the person who pressed the button first to be revealed. Any ideas?

Research

Research: Had a look here, it was kind of useful, but it didn't really touch too much on time precision. Other results were all regarding wired technology (I was seriously considering using an Arduino for a moment), but my use case requires a wireless and deployable-basically-everywhere solution.

Please note I haven't really tried anything, just looking for directions and a guide on where I should start and consider while programming.

Thanks in advance!


Solution

  • That's a very interesting question you have. I can see multiple scenarios where something like that would be useful/required (gambling, banking, military, home automation...)

    I had to deal with something similar with the IoT device cloud I run although in my case it was the matter of knowing the exact time something happens on a remote device (that doesn't even have a real time clock of any kind)

    I'll give you some pointers that may be helpful and give you some ideas

    • spoofing - that's unfortunately impossible to prevent if you are using plain setup like a web browser where the javascript code logic is exposed. You could maybe make it a bit more difficult by using WebAssembly or as you've pointed out, bundling it into an Arduino, something like an ESP8266 that the client connects to over WiFi and the ESP acts as a middleware between the client and your server.

    • clock sync - that's actually quite simple. You don't rely at all on the client clock, only trust your own server! The way to do it is to send the server's timestamp with the request and on the client code when you receive the server response you start a timer to measure elapsed time, when the user presses a button you send back the "received timestamp" + "the elapsed time" it took the user to click a button. That effectively translates to the server time when the button was pressed so you don't care what the client clock is actually set or even if the client has a clock.

    In javascript it would be something like this and would have a millisecond precision. If you need microseconds than it's probably WebAssembly or Arduino you would need.

    var startTime
    var serverTime
    // happens when you receive the first response from the server
    function start(request_timestamp){
      serverTime = request_timestamp //save the server timestamp
      startTime = Date.now() //grab the current timestamp
    } 
    
    //happens when you are sending your reponse
    function end(){
      let endTime = serverTime + (Date.now() - startTime)
      ///...send this to the server
    
    }
    
    • latency - if you want to reduce the latency to a minimum than you should be using WebSockets as you would only have one round of http negotiations when the connection is established. For simplicity you could try first with AJAX. Here's a simple logic:

    You want to first try and measure as best as you can the latency between the client and your server. Effectively you send few "pings" to the server and use the above code (but in reverse) to try to measure the latency. Technically that would also account for the client router and satellites, swamps, deserts and whatnot it takes for the data packets to travel.

    So, you would send a test request to the server with the current client timestamp, and the server simply echoes back that request (like a web socket ping) and you than take the difference between the current timestamp and the returned echo timestamp. That would be your round trip latency or half that for a one way.

    Here's a very rough abstract code:

    function testLatency(){
      
       let curr_timestamp = Date.now()
       let measured_latency
    
       ajax.post(server_address,{command:"testPing",timestamp: curr_timestamp},function(response){
          measured_latency = (Date.now() - response["timestamp"]) / 2 // divide by 2 to get a one way latency if that's what you need
       })
    
    }
    

    on the server side it simply returns back the timestamp variable it has received (not the server timestamp)

    You repeat that few times and then average it and you will have an estimated latency that you can subtract from the "endTime" that you send back (the first example code above).

    Substitute AJAX with WebSockets and you will get pretty reasonable accuracy in milliseconds.

    If you want to play with Arduino, you are welcome to ping me a message privately on chipchop.io, I've just setup a forum there (it's empty, you can be the first victim to post:-), I love messing with stuff like that and I am currently playing with a concept that could be interesting for an application like you've described.