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 🙏

© 2026 – Pkg Stats / Ryan Hefner

ntpfence

v0.1.0

Published

sntp time client with clock offset, replacing the deprecated sntp package

Readme

ntpfence

ask an ntp server what time it is. zero deps.

npm i ntpfence

why

the package npm still reaches for, sntp, does 1.4M downloads a week, is formally deprecated, and its last release was november 2018. it pulls four dependencies. nothing maintained replaced it at any scale.

an sntp request is a 48 byte udp packet. it did not need a successor with dependencies, it just needed one.

use

import { query, time, offset } from 'ntpfence'

const r = await query()          // pool.ntp.org by default
r.offset        // how far this machine's clock is off, in ms
r.delay         // round trip time
r.time          // the server's idea of now, unix ms
r.stratum       // 1 is a primary reference, 2+ is downstream
r.referenceId   // 'GOOG', or an address

await time()    // a corrected Date, without touching the system clock
await offset()  // just the milliseconds

against real servers:

time.cloudflare.com    offset  270ms | delay 192ms | stratum 3 | 10.136.8.6
time.google.com        offset  454ms | delay 656ms | stratum 1 | GOOG
pool.ntp.org           offset  258ms | delay 491ms | stratum 2 | 216.239.35.0

more than one server

a single server can be unreachable, or wrong. best asks several at once and keeps the reading with the shortest round trip, which is the least distorted sample:

import { best } from 'ntpfence'

const r = await best()   // pool.ntp.org, cloudflare, google
const r = await best(['time.cloudflare.com', 'ntp.example.internal'])

it resolves as long as one server answers.

it does not believe just any packet

udp has no connection, so anything that reaches the socket could try to move your clock. a reply is checked before it counts:

| the reply | result | |---|---| | does not echo the exact timestamp we sent | BAD_REPLY — the important one | | is not in server mode | BAD_REPLY | | is stratum 0, a kiss of death | KISS_OF_DEATH | | is stratum 16 or higher | NOT_SYNCED | | says its own clock is unsynchronised | NOT_SYNCED | | has an empty transmit timestamp | BAD_REPLY | | is shorter than 48 bytes | BAD_REPLY |

the origin check is what matters most: the request's transmit timestamp acts as a nonce, and a reply that cannot echo it never saw the request.

errors are all TimeError with a code, so you can branch without matching on messages. the socket is closed on every path, including timeouts.

clock, not system clock

nothing here sets your system time. that needs privileges and is the job of ntpd or chronyd. this tells you the offset so you can apply it yourself — which is usually what you want for signing deadlines, token expiry, or refusing to run when a container's clock has drifted:

const { offset } = await query()
if (Math.abs(offset) > 30_000) throw new Error('clock drift over 30s, refusing to start')

low level

import { encode, decode, offsets } from 'ntpfence'

decode(bytes)                 // every rfc 5905 header field
offsets(t1, t2, t3, t4)       // the rfc 4330 offset and delay maths

notes

era 0 of the ntp epoch rolls over in 2036. timestamps past it are handled, so this keeps working.

node:dgram is the only import.

correctness

27 tests. the wire format is checked field by field against handcrafted packets, the offset maths against the worked example in rfc 4330, and the client against a real udp server — including a forged reply carrying someone else's origin timestamp, which must be ignored rather than trusted.

live tests against cloudflare, google and pool.ntp.org run with NTPFENCE_LIVE=1, and assert the three independently agree with each other.

license

MIT