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

@hexdn/analytics

v0.3.0

Published

Independent, cumulative browser playback analytics for HexDN

Readme

@hexdn/analytics

Independent playback measurement for an existing HTML media element. No HLS, React, or HexDN storage is required. Private server credentials stay out of the browser.

For HexDN collection, first provision a collection key on your trusted server using @hexdn/sdk and your private environment API key:

import { HexDN } from "@hexdn/sdk";

const hexdn = new HexDN({ apiKey: configuration.hexdnApiKey });
const collectionKey = await hexdn.analytics.collectionKeys.create({
  origins: ["https://app.example"],
});

Use your application's exact origins (scheme, host and port), without wildcards. Provision once per origin configuration, not per view. Publish the returned collectionKey.id in your browser configuration; keep the environment API key private. Replace the key below with that returned ID. The SDK handles the collector URL and reporting. Collection-key origins are independent of playback and upload origin settings.

import { monitorPlayback } from "@hexdn/analytics";

const analytics = monitorPlayback(videoElement, {
  video: { id: "your-stable-video-id" },
  collectionKey: "ack_browser_publishable_key",
});

Production collection needs only the key. To carry the server client's environment choice into the browser, use hexdn.analytics.collectionConfig(collectionKey.id). It returns browser-safe configuration without making a request. Pass it directly:

const analytics = monitorPlayback(videoElement, {
  ...collectionConfig,
  video: { id: "your-stable-video-id" },
});

If play requires asynchronous source acquisition, record intent at the start of the play handler to include startup failures:

analytics.intent();
// Resolve the source, then play. Report exhausted failures with recordError().

When the element is mounted only after acquisition, start the same measurement earlier with beginPlaybackAttempt({ video, collectionKey }) (and the same optional collection configuration). Pass its handle as pendingAttempt to monitorPlayback() (or the React player's analytics prop). This transfers the original attempt and reporter once, including startup time. The monitor inherits pendingAttempt.video, so the video identity does not need to be supplied again. Before attachment, call pendingAttempt.recordError(code, { fatal: true }) if acquisition fails, or destroy() if the viewer leaves. After attachment the media monitor owns cleanup; destroying the pending handle is harmless.

If the tenant explicitly refuses access, call recordAccessDenied() on the pending handle or monitor. This ends the attempt as denied without increasing technical errors. Do not infer a tenant denial from a delivery, proof, or expired-credential HTTP 401/403; those remain under the playback recovery policy.

On a real content change, before replacing the source:

analytics.setVideo({ id: "next-video-id" });

On teardown, remove listeners and make a final best-effort keepalive report:

analytics.destroy();

When analytics is disabled or consent is withdrawn, use destroy({ flush: false }) instead, including on a pending pre-media handle. This discards buffered reports and scheduled retries without a final send. Already-issued requests cannot be recalled. The React player applies this when its analytics prop is removed; ordinary unmount still flushes.

Attach once, before play. Duplicate attachment throws; share subscribe() observations with resume persistence or product rules. Subscriber failures cannot interrupt playback. Use a custom asynchronous transport(report, { keepalive }) to route reports through a first-party backend; resolve only after durable acceptance, and reject on failure. Choose either a collectionKey or a custom transport. The lower-level createFetchTransport({ collectionKey }) helper is also available, but ordinary collection does not need to construct a transport. The default fetch transport sends a bounded { key, report } JSON envelope as text/plain, without cookies or private API credentials, and times out requests after ten seconds.

Measurement and lifecycle

  • Play intent creates an attempt without waiting for the network. playing establishes successful playback. First-frame startup time uses requestVideoFrameCallback where available, or recordFirstFrame() supplied by an integration. Missing first-frame timing, including attaching after playback has already started, remains null.
  • Playing time uses monotonic elapsed time validated by media advancement and playback rate. Pauses, seeks and buffering do not count as playing. Replaying a portion increases playing time; coverage remains its union.
  • Coverage is approximate: 100 media-time bucket midpoints crossed during valid playback. Duration is fixed at the first known finite duration, up to seven days. Unknown or infinite duration leaves duration and coverage null.
  • Playing time is split across UTC days using the attempt's initial wall-clock anchor and monotonic elapsed time. Wall-clock changes within a view do not move previously reported activity to another day.
  • A pause/resume, credential renewal, source URL replacement, or recovered error preserves the attempt. setVideo() changes identity only when the supplied video identifiers change. An ended view followed by play creates a new attempt; seeking backward before ending preserves the current attempt.
  • Attempts expire after 30 minutes without playing activity, or 24 hours total. Further playback begins a fresh attempt. Content changes and teardown mark an active attempt abandoned; page hiding alone does not establish abandonment.
  • Native media errors are fatal by default. Engines that recover them must set nativeErrorsFatal: false, then call recordError(code, { fatal: true }) when recovery is exhausted. Error codes are bounded tokens, never signed URLs.
  • Explicit tenant access denial is a separate terminal outcome. Earlier playing time and recovered errors remain recorded when access is withdrawn mid-view.

Reporting and retention boundaries

Reports contain cumulative summaries, not a raw event log. Each attempt has a stable UUID and increasing revisions; retries reuse the exact immutable payload. Normal reports coalesce to roughly 30 seconds while measurements change, with immediate start, pause, seek, end and error flushes. The interval can be configured between 5 and 60 seconds. Visibility/page lifecycle flushes use keepalive and may overtake an ordinary request; the server must deduplicate and order revisions.

Client memory holds at most four latest pending attempts. An unchanged failed snapshot receives at most five automatic retries with bounded backoff; new measurements can resume sending. A long outage can evict the oldest pending attempt. There is no cross-reload browser persistence, and unload delivery is best-effort. Missing final reports remain unknown outcomes. Playback never waits for collection and reports are not proof of human attention.

The browser-independent @hexdn/analytics/protocol export provides the report types, limits, strict validator, and isAnalyticsReportSuccessor(previous, next). The server derives environment scope from collection authorization, rejects stale or conflicting revisions, checks its own acceptance/clock window, and commits accepted deltas atomically. Terminal snapshots are immutable. The validator does not provide authentication, retention, rate limiting, or a proof of watching.

No viewer identity, resume position persistence, raw URLs, arbitrary dimensions, or tenant-specific public-view rules are stored by this package.