npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ircam/sync

v2.1.0

Published

Client / Server time synchronization component

Downloads

34

Readme

@ircam/sync

Module that synchronises all clients to a server master clock.

Each client has access to a logical clock that synchronizes to the server clock. The module also provides helper functions that allows to convert the master clock, to and from, the local clock. Everybody can use the common master clock to schedule synchronized events. A good practice is to convert to local time at the last moment to trigger events, in order to minimize drift.

Table of Contents

Install

npm install [--save] @ircam/sync

Example use

This example show the usage of the library through a simple websocket transport with a naive ad-hoc ping / pong protocol.

Server-side

import { SyncServer } from '@ircam/sync';

const startTime = process.hrtime();
const getTimeFunction = () => {
  const now = process.hrtime(startTime);
  return now[0] + now[1] * 1e-9;
}

// 
const syncServer = new SyncServer(getTimeFunction);

const wss = new ws.Server({ server: httpServer });
wss.on('connection', (socket) => {
  // the `receiveFunction` and `sendFunction` functions aim at abstracting 
  // the transport layer between the SyncServer and the SyncClient
  const receiveFunction = callback => {
    socket.on('message', request => {
      request = JSON.parse(request);

      if (request[0] === 0) { // this is a ping
        // parse request
        const pingId = request[1];
        const clientPingTime = request[2];
        // notify the SyncServer
        callback(pingId, clientPingTime);
      }
    });
  };

  const sendFunction = (pingId, clientPingTime, serverPingTime, serverPongTime) => {
      // create response object
      const response = [
        1, // this is a pong
        pingId,
        clientPingTime,
        serverPingTime,
        serverPongTime,
      ];
      // send formatted response to the client
      socket.send(JSON.stringify(response));
  };

  syncServer.start(sendFunction, receiveFunction);
});

Client-side

import { SyncClient } from '@ircam/sync';

// return the local time in second
const getTimeFunction = () => {
  return performance.now() / 1000;
}

// init sync client
const syncClient = new SyncClient(getTimeFunction);
// init socket client
const socket = new WebSocket(url);

socket.addEventListener('open', () => {
  const sendFunction = (pingId, clientPingTime) => {
    const request = [
      0, // this is a ping
      pingId,
      clientPingTime,
    ];

    socket.send(JSON.stringify(request));
  };

  const receiveFunction = callback => {
    socket.addEventListener('message', e => {
      const response = JSON.parse(e.data);

      if (response[0] === 1) { // this is a pong
        const pingId = response[1];
        const clientPingTime = response[2];
        const serverPingTime = response[3];
        const serverPongTime = response[4];

        callback(pingId, clientPingTime, serverPingTime, serverPongTime);
      }
    });
  }

  // check the synchronization status, when this function is called for the 
  // first time, you can consider the synchronization process properly 
  // initiated.
  const statusFunction = status => console.log(status);
  // start synchronization process
  syncClient.start(sendFunction, receiveFunction, statusFunction);
});

// monitor the synchronized clock
setInterval(() => {
  const syncTime = syncClient.getSyncTime();
  console.log(syncTime);
}, 100);

API

Classes

SyncClient

SyncClient instances synchronize to the clock provided by the SyncServer instance. The default estimation behavior is strictly monotonic and guarantee a unique convertion from server time to local time.

Kind: global class
See: SyncClient~start method to actually start a synchronisation process.

new SyncClient(getTimeFunction, [options])

| Param | Type | Default | Description | | --- | --- | --- | --- | | getTimeFunction | getTimeFunction | | | | [options] | Object | | | | [options.pingTimeOutDelay] | Object | | range of duration (in seconds) to consider a ping was not ponged back | | [options.pingTimeOutDelay.min] | Number | 1 | min and max must be set together | | [options.pingTimeOutDelay.max] | Number | 30 | min and max must be set together | | [options.pingSeriesIterations] | Number | 10 | number of ping-pongs in a series | | [options.pingSeriesPeriod] | Number | 0.250 | interval (in seconds) between pings in a series | | [options.pingSeriesDelay] | Number | | range of interval (in seconds) between ping-pong series | | [options.pingSeriesDelay.min] | Number | 10 | min and max must be set together | | [options.pingSeriesDelay.max] | Number | 20 | min and max must be set together | | [options.longTermDataTrainingDuration] | Number | 120 | duration of training, in seconds, approximately, before using the estimate of clock frequency | | [options.longTermDataDuration] | Number | 900 | estimate synchronisation over this duration, in seconds, approximately | | [options.estimationMonotonicity] | Boolean | true | When true, the estimation of the server time is strictly monotonic, and the maximum instability of the estimated server time is then limited to options.estimationStability. | | [options.estimationStability] | Number | 160e-6 | This option applies only when options.estimationMonotonicity is true. The adaptation to the estimated server time is then limited by this positive value. 80e-6 (80 parts per million, PPM) is quite stable, and corresponds to the stability of a conventional clock. 160e-6 is moderately adaptive, and corresponds to the relative stability of 2 clocks; 500e-6 is quite adaptive, it compensates 5 milliseconds in 1 second. It is the maximum value (estimationStability must be lower than 500e-6). |

syncClient.start(sendFunction, receiveFunction, reportFunction)

Start a synchronisation process by registering the receive function passed as second parameter. Then, send regular messages to the server, using the send function passed as first parameter.

Kind: instance method of SyncClient

| Param | Type | Description | | --- | --- | --- | | sendFunction | sendFunction | | | receiveFunction | receiveFunction | to register | | reportFunction | reportFunction | if defined, is called to report the status, on each status change, and each time the estimation of the synchronised time updates. |

syncClient.stop()

Stop the synchronization process

Kind: instance method of SyncClient

syncClient.getLocalTime([syncTime]) ⇒ Number

Get local time, or convert a synchronised time to a local time.

Kind: instance method of SyncClient
Returns: Number - local time, in seconds

| Param | Type | Description | | --- | --- | --- | | [syncTime] | Number | Get local time according to given given syncTime, if syncTime is not defined returns current local time. |

syncClient.getSyncTime([localTime]) ⇒ Number

Get synchronised time, or convert a local time to a synchronised time.

Kind: instance method of SyncClient
Returns: Number - synchronised time, in seconds.

| Param | Type | Description | | --- | --- | --- | | [localTime] | Number | Get sync time according to given given localTime, if localTime is not defined returns current sync time. |

SyncClient~getTimeFunction ⇒ Number

Kind: inner typedef of SyncClient
Returns: Number - strictly monotonic, ever increasing, time in second. When possible the server code should define its own origin (i.e. time=0) in order to maximize the resolution of the clock for a long period of time. When SyncServer~start is called the clock should already be running (cf. audioContext.currentTime that needs user interaction to start)

SyncClient~sendFunction : function

Kind: inner typedef of SyncClient
See: receiveFunction

| Param | Type | Description | | --- | --- | --- | | pingId | Number | unique identifier | | clientPingTime | Number | time-stamp of ping emission |

SyncClient~receiveFunction : function

Kind: inner typedef of SyncClient
See: sendFunction

| Param | Type | Description | | --- | --- | --- | | receiveCallback | receiveCallback | called on each message matching messageType. |

SyncClient~receiveCallback : function

Kind: inner typedef of SyncClient

| Param | Type | Description | | --- | --- | --- | | pingId | Number | unique identifier | | clientPingTime | Number | time-stamp of ping emission | | serverPingTime | Number | time-stamp of ping reception | | serverPongTime | Number | time-stamp of pong emission |

SyncClient~reportFunction : function

Kind: inner typedef of SyncClient

| Param | Type | Description | | --- | --- | --- | | report | Object | | | report.status | String | new, startup, training (offset adaptation), or sync (offset and speed adaptation). | | report.statusDuration | Number | duration since last status change. | | report.timeOffset | Number | time difference between local time and sync time, in seconds. | | report.frequencyRatio | Number | time ratio between local time and sync time. | | report.connection | String | offline or online | | report.connectionDuration | Number | duration since last connection change. | | report.connectionTimeOut | Number | duration, in seconds, before a time-out occurs. | | report.travelDuration | Number | duration of a ping-pong round-trip, in seconds, mean over the the last ping-pong series. | | report.travelDurationMin | Number | duration of a ping-pong round-trip, in seconds, minimum over the the last ping-pong series. | | report.travelDurationMax | Number | duration of a ping-pong round-trip, in seconds, maximum over the the last ping-pong series. |

SyncServer

The SyncServer instance provides a clock on which SyncClient instances synchronize.

Kind: global class
See: SyncServer~start method to actually start a synchronisation process.

new SyncServer(function)

| Param | Type | Description | | --- | --- | --- | | function | getTimeFunction | called to get the local time. It must return a time in seconds, monotonic, ever increasing. |

syncServer.start(sendFunction, receiveFunction)

Start a synchronisation process with a SyncClient by registering the receive function passed as second parameter. On each received message, send a reply using the function passed as first parameter.

Kind: instance method of SyncServer

| Param | Type | | --- | --- | | sendFunction | sendFunction | | receiveFunction | receiveFunction |

syncServer.getLocalTime([syncTime]) ⇒ Number

Get local time, or convert a synchronised time to a local time.

Kind: instance method of SyncServer
Returns: Number - local time, in seconds
Note: getLocalTime and getSyncTime are basically aliases on the server.

| Param | Type | Description | | --- | --- | --- | | [syncTime] | Number | Get local time according to given given syncTime, if syncTime is not defined returns current local time. |

syncServer.getSyncTime([localTime]) ⇒ Number

Get synchronised time, or convert a local time to a synchronised time.

Kind: instance method of SyncServer
Returns: Number - synchronised time, in seconds.
Note: getLocalTime and getSyncTime are basically aliases on the server.

| Param | Type | Description | | --- | --- | --- | | [localTime] | Number | Get sync time according to given given localTime, if localTime is not defined returns current sync time. |

SyncServer~getTimeFunction ⇒ Number

Kind: inner typedef of SyncServer
Returns: Number - monotonic, ever increasing, time in second. When possible the server code should define its own origin (i.e. time=0) in order to maximize the resolution of the clock for a long period of time. When SyncServer~start is called the clock should be running (cf. audioContext.currentTime that needs user interaction to start)
Example

const startTime = process.hrtime();

const getTimeFunction = () => {
  const now = process.hrtime(startTime);
  return now[0] + now[1] * 1e-9;
};

SyncServer~sendFunction : function

Kind: inner typedef of SyncServer
See: receiveFunction

| Param | Type | Description | | --- | --- | --- | | pingId | Number | unique identifier | | clientPingTime | Number | time-stamp of ping emission | | serverPingTime | Number | time-stamp of ping reception | | serverPongTime | Number | time-stamp of pong emission |

SyncServer~receiveFunction : function

Kind: inner typedef of SyncServer
See: sendFunction

| Param | Type | Description | | --- | --- | --- | | receiveCallback | receiveCallback | called on each message matching messageType. |

SyncServer~receiveCallback : function

Kind: inner typedef of SyncServer

| Param | Type | Description | | --- | --- | --- | | pingId | Number | unique identifier | | clientPingTime | Number | time-stamp of ping emission |

Caveats

The synchronisation process is continuous: after a call to the start method, it runs in the background. It is important to avoid blocking it, on the client side and on the server side.

In many cases, running the sync process in another thread is not an option as the local clock will be different accross threads or processes.

Publication

For more information, you can read this article presented at the Web Audio Conference 2016:

Jean-Philippe Lambert, Sébastien Robaszkiewicz, Norbert Schnell. Synchronisation for Distributed Audio Rendering over Heterogeneous Devices, in HTML5. 2nd Web Audio Conference, Apr 2016, Atlanta, GA, United States. ⟨hal-01304889⟩ - https://hal.archives-ouvertes.fr/hal-01304889v1

Note: the stabilisation of the estimated synchronous time has been added after the publication of this article.

License

BSD-3-Clause. See the LICENSE file.