Skip to content

herenickname/timesync

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@herenickname/timesync

npm version License: MIT

πŸ”„ Modern time synchronization library for JavaScript/TypeScript

This is an updated fork of the original enmasseio/timesync library with the following improvements:

  • βœ… TypeScript rewrite - Full type definitions included
  • βœ… Modern ES2020+ - No polyfills needed, uses native fetch and Promise
  • βœ… Vite bundler - Modern build system with tree-shaking support
  • βœ… ESM first - Proper ES module support with CommonJS fallback
  • βœ… Updated dependencies - All dependencies updated to latest versions
  • βœ… Vitest testing - Modern test framework

Time synchronization between peers.

v2.0 - Completely rewritten in TypeScript for modern JavaScript environments (ES2020+). No polyfills needed.

Usage scenarios:

  • master/slave: Clients synchronize their time to that of a single server, via either HTTP requests or WebSockets.
  • peer-to-peer: Clients are connected in a (dynamic) peer-to-peer network using WebRTC or WebSockets and must converge to a single, common time in the network.

Install

Install via npm:

npm install @herenickname/timesync

Usage

A timesync client can basically connect to one server or multiple peers, and will synchronize it's time. The synchronized time can be retrieved via the method now(), and the client can subscribe to events like 'change' and 'sync'.

import { Timesync } from '@herenickname/timesync';

// create a timesync instance
const ts = new Timesync({
  server: '...',  // either a single server,
  peers: [...]    // or multiple peers
});

// get notified on changes in the offset
ts.on('change', (offset) => {
  console.log('offset from system time:', offset, 'ms');
});

// wait for synchronization to complete before using the time
await ts.waitUntilSynced(); // wait for at least 1 sync (default)
// await ts.waitUntilSynced(3); // or wait for 3 syncs for better accuracy

// get the synchronized time
console.log('now:', new Date(ts.now()));

Example

Here a full usage example with express.js, showing both server and client side. timesync has build-in support for requests over http and can be used with express, a default http server, or other solutions. timesync can also be used over other transports than http, for example using websockets or webrtc. This is demonstrated in the advanced examples.

More examples are available in the /examples folder. Some of the examples use libraries like express or socket.io. Before you can run these examples you will have to install these dependencies.

server.js

import express from "express";
import { expressHandler } from "@herenickname/timesync/server";
import bodyParser from "body-parser";

// create an express app
const port = 8081;
const app = express();
app.listen(port);
console.log("Server listening at http://localhost:" + port);

// parse JSON bodies
app.use(bodyParser.json());

// serve static index.html
app.get("/", express.static(__dirname));

// handle timesync requests
app.post("/timesync", expressHandler);

index.html

<!DOCTYPE html>
<html>
  <head>
    <script type="module">
      import { Timesync } from "/node_modules/timesync/dist/timesync.js";

      // create a timesync instance
      const ts = new Timesync({
        server: "/timesync",
        interval: 10000,
      });

      // get notified on changes in the offset
      ts.on("change", (offset) => {
        document.body.innerHTML += "changed offset: " + offset + " ms<br>";
      });

      // get synchronized time
      setInterval(() => {
        const now = new Date(ts.now());
        document.body.innerHTML += "now: " + now.toISOString() + "<br>";
      }, 1000);
    </script>
  </head>
  <body></body>
</html>

API

Client

Construction

An instance of timesync is created as:

import { Timesync } from "@herenickname/timesync";

const ts = new Timesync(options);

Options

The following options are available:

Name Type Default Description
delay number 1000 Delay in milliseconds between every request sent.
interval number or null 3600000 Interval in milliseconds for running a synchronization. Defaults to 1 hour. Set to null to disable automatically running synchronizations (synchronize by calling sync()).
now function Date.now Function returning the local system time.
peers string[] or string [] Array or comma separated string with uri's or id's of the peers to synchronize with. Cannot be used in conjunction with option server.
repeat number 5 Number of times to do a request to every peer.
server string none Url of a single server in case of a master/slave configuration. Cannot be used in conjunction with option peers.
timeout number 10000 Timeout in milliseconds for requests to fail.

Methods

Name Return type Description
destroy() none Destroy the timesync instance. Stops automatic synchronization. If timesync is currently executing a synchronization, this synchronization will be finished first.
now() number Get the synchronized time. Returns a timestamp. To create a Date, call new Date(ts.now()).
on(event, callback) this Register a callback handler for an event. Returns the timesync instance. See section Events for more information.
off(event [, callback]) this Unregister a callback handler for an event. If no callback is provided, all callbacks of this event will be removed. Returns the timesync instance. See section Events for more information.
sync() Promise Do a synchronization with all peers now. Returns a Promise that resolves when sync is complete.
waitUntilSynced(minSyncCount = 1) Promise<void> Wait until a minimum number of synchronizations have completed. Returns immediately if the required number of syncs has already occurred. Useful for ensuring multiple syncs before relying on synchronized time (default: 1).

To be able to send and receive messages from peers, timesync needs a transport. To hook up a transport like a websocket or http requests, one has to override the send(to, data, timeout) method of the timesync instance, and has to call ts.receive(from, data) on incoming messages.

Name Return type Description
send(to, data, timeout) : Promise none Send a message to a peer. to is the id of the peer, and data a JSON object containing the message. Must return a Promise which resolves when the message has been sent, or rejects when sending failed or a timeout occurred.
receive(from, data) none Receive a message from a peer. from is the id of the sender, and data a JSON object containing the message.

timesync sends messages using the JSON-RPC protocol, as described in the section Protocol.

Events

timesync emits events when starting and finishing a synchronization, and when the time offset changes. To listen for events:

ts.on("change", (offset) => {
  console.log("offset changed:", offset);
});

Available events:

Name Description
change Emitted when the offset is changed. This can only happen during a synchronization. Callbacks are called with the new offset (a number) as argument.
error Emitted when an error occurred. Callbacks are called with the error as argument.
sync Emitted when a synchronization is started or finished. Callback are called with a value 'start' or 'end' as argument.

Properties

Name Type Description
offset number The offset from system time in milliseconds.
options Object An object holding all options of the timesync instance. One can safely adjust options like peers at any time. Not all options can be changed after construction, for example a changed interval value will not be applied.

Server

timesync comes with a build in server to serve as a master for time synchronization. Clients can adjust their time to that of the server. The server basically just implements a POST request responding with its current time, and serves the static files. It's quite easy to implement this request handler yourself, as is demonstrated in the advanced examples.

The protocol used by the server is described in the section Protocol.

Load

The server can be loaded in node.js as:

import {
  createServer,
  attachServer,
  expressHandler,
} from "@herenickname/timesync/server";

Methods

Name Return type Description
createServer() http.Server Create a new, dedicated http Server.
attachServer(server, [path]) void Attach a request handler for time synchronization requests to an existing http Server. Argument server must be an instance of http.Server. Argument path is optional, and is /timesync by default.
expressHandler function Express-compatible middleware for handling timesync requests.
requestHandler function A default request handler for Node.js http server. Signature is requestHandler(request, response).

Protocol

timesync sends messages using the JSON-RPC protocol. A peer sends a message:

{ "jsonrpc": "2.0", "id": "12345", "method": "timesync" }

The receiving peer replies with the same id and its current time:

{ "jsonrpc": "2.0", "id": "12345", "result": 1423151204595 }

The sending peer matches the returned message by id and uses the result to adjust it's offset.

Algorithm

timesync uses a simple synchronization protocol aimed at the gaming industry, and extends this for peer-to-peer networks. The algorithm is described here:

A simple algorithm with these properties is as follows:

  1. Client stamps current local time on a "time request" packet and sends to server
  2. Upon receipt by server, server stamps server-time and returns
  3. Upon receipt by client, client subtracts current time from sent time and divides by two to compute latency. It subtracts current time from server time to determine client-server time delta and adds in the half-latency to get the correct clock delta. (So far this algorithm is very similar to SNTP)
  4. The first result should immediately be used to update the clock since it will get the local clock into at least the right ballpark (at least the right timezone!)
  5. The client repeats steps 1 through 3 five or more times, pausing a few seconds each time. Other traffic may be allowed in the interim, but should be minimized for best results
  6. The results of the packet receipts are accumulated and sorted in lowest-latency to highest-latency order. The median latency is determined by picking the mid-point sample from this ordered list.
  7. All samples above approximately 1 standard-deviation from the median are discarded and the remaining samples are averaged using an arithmetic mean.

This algorithm assumes multiple clients synchronizing with a single server. In case of multiple peers, timesync will take the average offset of all peers (excluding itself) as offset.

Tutorials

Resources

Build

To build the library:

npm install
npm run build

This will generate the following files in the /dist folder:

  • timesync.js - ES module
  • timesync.cjs - CommonJS module
  • timesync.umd.js - UMD bundle (for browsers)
  • timesync.d.ts - TypeScript declarations
  • server.js - ES module for server
  • server.cjs - CommonJS module for server

For development with hot reload:

npm run dev

Migration from v1.x

Version 2.0 is a complete rewrite in TypeScript. Key changes:

  • No polyfills needed - Uses native ES2020+ features (Promise, fetch, etc.)
  • TypeScript first - Full type definitions included
  • Class-based API - Use new Timesync(options) instead of timesync.create(options)
  • Native fetch - Uses native fetch API instead of XMLHttpRequest
  • Vite bundler - Modern build system with tree-shaking support
  • ESM exports - Proper ES module support with CommonJS fallback

The create() factory function is still available for backwards compatibility but is deprecated.

Credits

This library is a fork of the original timesync by Jos de Jong. Thanks to the original author for creating this excellent time synchronization solution.

License

MIT Β© herenickname

Original work Β© Jos de Jong

About

πŸ”„ Modern time synchronization library for JavaScript/TypeScript

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors