timesync

repository·master·Indexed 43 days ago

https://github.com/enmasseio/timesync

A library for time synchronization between peers supporting master/slave (client-server) and peer-to-peer architectures via HTTP, WebSockets, or WebRTC. Version 1.0.11 uses the JSON-RPC 2.0 protocol to calculate clock deltas and latency, providing a synchronized timestamp via the `now()` method. It includes a Node.js server implementation that can be used standalone or attached to existing servers like Express.

Tokens
5K
Snippets
21
Records
24
Agent score
55%

What's inside timesync

  1. Understand the timesync protocol and algorithm

    master

    Protocol

    timesync uses the JSON-RPC 2.0 protocol.

    • Request: {"jsonrpc": "2.0", "id": "12345", "method": "timesync"}
    • Response: {"jsonrpc": "2.0", "id": "12345", "result": 1423151204595}

    Algorithm

    The synchronization algorithm follows these steps:

    1. Client sends a timestamped request.
    2. Server responds with its current time.
    3. Client calculates latency (half of the round-trip time) and the clock delta.
    4. The first result is used immediately to get into the correct 'ballpark'.
    5. The client repeats this multiple times (default 5) to collect samples.
    6. Samples are sorted by latency; outliers (above ~1 standard deviation from the median) are discarded.
    7. The final offset is the arithmetic mean of the remaining samples.
  2. Run the basic Express example

    master

    To run the basic Express implementation of timesync, follow these steps:

    1. Install the required express dependency:
      npm install express
    2. Start the server using Node.js:
      node server.js
      Alternatively, you can start the server with debugging enabled:
      DEBUG=* node server.js
    3. Access the example in your browser at: http://localhost:8081
    npm install express
    node server.js
  3. Retrieve server time and offset from Android Activity

    master

    To pull the synchronized time or offset from the WebView into your Java code, use mWebView.loadUrl() to execute JavaScript commands that trigger your CustomJavaScriptInterface methods. These methods interact with the ts and lastOffset variables defined in your server-side HTML.

    • getServerTime(): Executes javascript:Android.getServerTime(ts.now()); to fetch the current synchronized time.
    • getTimeOffset(): Executes javascript:Android.getTimeOffset(lastOffset); to fetch the current offset.
    private long getServerTime() {
        mWebView.loadUrl("javascript:Android.getServerTime(ts.now());");
        return serverTime;
    }
    
    private long getTimeOffset() {
        mWebView.loadUrl("javascript:Android.getTimeOffset(lastOffset);");
        return timeOffset;
    }
  4. Configure WebView for time synchronization in Android

    master

    To use timesync in an Android app, you must use a WebView to bridge the JavaScript-based time synchronization with your Java code.

    1. Layout: Add an invisible WebView to your layout.xml.
    2. Settings: In your Activity's onCreate, enable JavaScript and allow file access from URLs.
    3. Interface: Attach a JavascriptInterface to the WebView to allow the JavaScript environment to call back into your Java code.
    // layout.xml
    <WebView
        android:layout_width="1dp"
        android:layout_height="1dp"
        android:visibility="invisible"
        android:id="@+id/webview"/>
    
    // Activity.java
    mWebView = (WebView) findViewById(R.id.webview);
    WebSettings settings = mWebView.getSettings();
    settings.setJavaScriptEnabled(true);
    settings.setAllowFileAccessFromFileURLs(true);
    settings.setAllowUniversalAccessFromFileURLs(true);
    mWebView.loadUrl("http://" + serverIPPort);
    mWebView.addJavascriptInterface(new CustomJavaScriptInterface(mWebView.getContext()), "Android");
  5. Set up the timesync server-side HTML for Android integration

    master

    To synchronize time with an Android application, you must host an HTML file on your Node.js server that initializes a timesync client. This client will manage the connection and offset calculations. The HTML file should include the timesync.js library and expose the ts (client instance) and lastOffset variables to the global scope so the Android WebView can access them via a JavaScript interface.

    Note: If supporting older browsers, include es5-shim and es6-shim.

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.0.5/es5-shim.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.23.0/es6-shim.min.js"></script>
      <script src="/timesync/timesync.js"></script>
    </head>
    <body>
      <script>
        var lastOffset = 0;
    
        // create a timesync client
        var ts = timesync.create({
          server: '/timesync',
          interval: 10000
        });
    
        // get notified on changes in the offset
        ts.on('change', function (offset) {
            lastOffset = offset;
        });
      </script>
    </body>
    </html>
  6. Run the basic HTTP example

    master

    To run the basic HTTP demonstration, you must first install the promise dependency. Once installed, you can start the server using node server.js. If you need to debug the process, you can enable debug logging by prefixing the command with DEBUG=*.

    # Install dependencies
    npm install promise
    
    # Start the server
    node server.js
    
    # Or start with debugging enabled
    DEBUG=* node server.js
  7. Basic usage of the timesync client

    master

    A timesync client can connect to either a single server (master/slave) or multiple peers (peer-to-peer) to synchronize time. You can retrieve the synchronized time using now() and listen for synchronization events like 'change' and 'sync'.

    Note: In the example below, timesync() is used as a constructor. In some environments (like browsers), you may need to use timesync.create() depending on how the library is loaded.

    // create a timesync instance
    var ts = timesync({
      server: '...',  // either a single server,
      peers: [...]    // or multiple peers
    });
    
    // get notified on changes in the offset
    ts.on('change', function (offset) {
      console.log('offset from system time:', offset, 'ms');
    });
    
    // get the synchronized time
    console.log('now:', new Date(ts.now()));
  8. Understand the `requestHandler` behavior

    master

    The requestHandler manages two types of incoming requests:

    1. POST requests: Used for time synchronization. The handler reads the JSON body and returns a JSON response containing the current server timestamp. It supports both raw Node.js req objects and Express-like objects (where req.body is pre-populated).

      • Response Format: {"id": <id_from_request_or_null>, "result": <timestamp_ms>}
      • Error Handling: Returns a 400 status if the JSON is invalid or if the POST body exceeds 1MB.
    2. GET requests: Used to serve the client-side library files. It specifically looks for requests ending in timesync.js or timesync.min.js and serves them with the application/javascript Content-Type.

    If a request does not match these patterns, it returns a 404 Not Found.

  9. Configure the timesync client options

    master

    When constructing a timesync instance, you can provide an options object to customize behavior.

    Note: You cannot use server and peers at the same time.

    var ts = timesync({
      delay: 1000,           // Delay in ms between requests (default: 1000)
      interval: 3600000,      // Sync interval in ms (default: 1 hour). Set to null to disable auto-sync.
      now: Date.now,         // Function returning local system time
      peers: ['uri1', 'uri2'], // Array or comma-separated string of peer URIs/IDs
      repeat: 5,             // Number of requests to every peer (default: 5)
      server: '/timesync',   // URL of a single server
      timeout: 10000         // Request timeout in ms (default: 10000)
    });
  10. Handle timesync client events

    master

    The client emits the following events:

    • 'change': Emitted when the time offset changes during synchronization. The callback receives the new offset (number).
    • 'error': Emitted when an error occurs. The callback receives the error object.
    • 'sync': Emitted when a synchronization starts or finishes. The callback receives 'start' or 'end'.
    ts.on('change', function (offset) {
      console.log('offset changed:', offset);
    });
    
    ts.on('sync', function (status) {
      console.log('sync status:', status); // 'start' or 'end'
    });