npm package: @herenickname/timesync
Description: Modern time synchronization library for JavaScript/TypeScript. Sync time between clients and servers via HTTP, WebSockets, or WebRTC. Updated fork of enmasseio/timesync with TypeScript support and modern ES2020+ features.
Author: herenickname
Latest version: 2.2.1

# @herenickname/timesync

[![npm version](https://img.shields.io/npm/v/@herenickname/timesync.svg)](https://www.npmjs.com/package/@herenickname/timesync)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

> 🔄 Modern time synchronization library for JavaScript/TypeScript

This is an **updated fork** of the original [enmasseio/timesync](https://github.com/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:

```bash
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'`.

```ts
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](/examples/advanced).

More examples are available in the [/examples](/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**

```ts
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**

```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:

```ts
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](#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](#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](#protocol).

### Events

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

```ts
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](/examples/advanced).

The protocol used by the server is described in the section [Protocol](#protocol).

### Load

The server can be loaded in node.js as:

```ts
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:

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

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

```json
{ "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](https://web.archive.org/web/20160310125700/http://mine-control.com/zack/timesync/timesync.html):

> 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

- [Using the timesync library in Android applications](https://github.com/enmasseio/timesync/blob/master/docs/android-tutorial.md)

# Resources

- [A Stream-based Time Synchronization Technique For Networked Computer Games](https://web.archive.org/web/20160310125700/http://mine-control.com/zack/timesync/timesync.html)
- [Network Time Protocol](http://www.wikiwand.com/en/Network_Time_Protocol)

# 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](https://github.com/enmasseio/timesync) by [Jos de Jong](https://github.com/josdejong). Thanks to the original author for creating this excellent time synchronization solution.

# License

MIT © [herenickname](https://github.com/herenickname)

Original work © [Jos de Jong](https://github.com/josdejong)