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

@loglab/telemetry

v0.2.1

Published

Lightweight, dependency-free browser telemetry SDK with console, network and error capture plus an embeddable feedback widget.

Readme

@loglab/telemetry

Browser telemetry SDK with a built-in feedback widget. Captures console output, fetch/XMLHttpRequest activity and unhandled errors in the background, keeps a rolling ten-minute window of them in memory, and attaches that window to every bug report the widget sends.

No dependencies. React bindings ship in the same package and are optional.

The published runtime bundles are minified, mangled and shipped without source maps. Type declarations remain available so the public API stays type-safe. This is packaging hardening, not encryption: browser code can always be inspected by a determined user.

Install

pnpm add @loglab/telemetry

Quick start

import { Telemetry } from "@loglab/telemetry";

Telemetry.init({
  apiKey: "demo",
  endpoint: "/api/events",
  widget: true,
});

That is the whole integration. From this point on:

  • console calls, network requests and unhandled errors are recorded and batched to endpoint;
  • a discreet launcher is pinned to the side of the viewport;
  • a bug report sent through the widget carries the diagnostics window with it.

Custom events and programmatic opening:

Telemetry.capture("checkout_started", { plan: "pro" });
Telemetry.feedback.open(); // type picker
Telemetry.feedback.open("bug"); // straight to the bug form

Telemetry is a shared per-page client. For isolated instances (tests, multiple roots) use createTelemetry().

Configuration

| Option | Default | Purpose | | -------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------- | | apiKey | — | Sent in the batch envelope and as x-api-key. | | endpoint | — | Where batches are posted. Never instrumented itself. | | environment, release | — | Passed through in context. | | widget | false | true for defaults, or WidgetOptions. | | console | true | false to disable, or { levels, maxArgs, maxArgLength }. Default levels: log, info, warn, error. | | network | true | false to disable, or { fetch, xhr, ignoreUrls, captureHeaders }. | | errors | true | error and unhandledrejection listeners. | | privacy | see below | { redactKeys, redactPatterns, stripQuery, maxDepth } (maxDepth defaults to 4). | | limits | 10 min window | { maxLogs, maxErrors, maxRequests, maxBytesPerBuffer, windowMs }. | | sampleRate | 1 | Ratio of non-feedback events forwarded. Feedback and errors are never sampled. | | flushInterval | 5000 | Idle delay before an accumulated batch is sent (minimum 250). | | maxBatchSize | 25 | Reaching it flushes immediately. | | maxQueuedBatches, maxRetries | 10, 3 | Backpressure and retry bounds. | | flushOnHidden | true | Flush on pagehide/visibilitychange, via sendBeacon when possible. | | user | — | Arbitrary identity object added to context. | | beforeSend | — | Last chance to mutate or drop a batch (null drops it). | | transport | HTTP | Replace delivery entirely. | | debug | false | Log SDK internals through the pristine console. |

Widget appearance

The popup and launcher can be branded without replacing the feedback flow:

Telemetry.init({
  apiKey: "demo",
  endpoint: "/api/events",
  widget: {
    side: "right",
    theme: {
      accentColor: "#6d5dfc",
      surfaceColor: "#18181b",
      cornerStyle: "round",
      panelWidth: "comfortable",
      shadow: "soft",
    },
    copy: {
      launcher: "Tell us what you think",
      title: "How can we improve?",
    },
  },
});

Colors use six-digit hex values. cornerStyle accepts square, soft or round; panelWidth accepts compact, comfortable or wide; shadow accepts none, soft or strong.

Public API

Telemetry.init(config)                      // idempotent, no-op without a DOM
Telemetry.capture(name, properties?)        // application event
Telemetry.captureException(error, options?) // handled error
Telemetry.getDiagnostics()                  // { logs, errors, requests }
Telemetry.bufferUsage()                     // counts + approximate bytes
Telemetry.subscribe(observer)               // returns an unsubscribe function
Telemetry.buildBugReport(message, email?)   // payload without sending it
Telemetry.feedback.open(type?) / close() / isOpen() / submit(submission)
Telemetry.flush()                           // resolves once queued batches drain
Telemetry.shutdown()                        // restores every patched global

React

The React entry point has no 'use client' directive of its own — put it on your own component so the rest of the package stays importable from server code.

"use client";

import {
  TelemetryProvider,
  FeedbackPanel,
  useDiagnostics,
  useTelemetry,
} from "@loglab/telemetry/react";

export function Providers({ children }) {
  return (
    <TelemetryProvider
      config={{ apiKey: "demo", endpoint: "/api/events", widget: true }}
    >
      {children}
    </TelemetryProvider>
  );
}
  • useTelemetry() — the client from context, or the shared one.
  • useDiagnostics(throttleMs?) — live view of the buffers, coalesced renders.
  • FeedbackPanel — the widget's own card rendered inline. It mounts the same factory the modal uses, so an embedded panel and the launcher can never drift.

What reaches your endpoint

One POST per batch, content-type: application/json:

{
  "sdk": { "name": "@loglab/telemetry", "version": "0.2.1" },
  "apiKey": "demo",
  "sentAt": 1756000512345,
  "session": { "id": "…", "startedAt": 1756000500000 },
  "context": {
    "page": { "url": "…", "path": "/schedule" },
    "browser": { "…": "…" },
  },
  "events": [
    {
      "type": "console",
      "level": "warn",
      "args": ["slot 09:30 is full"],
      "timestamp": 1756000512000,
    },
    {
      "type": "request",
      "method": "DELETE",
      "url": "…",
      "status": 500,
      "duration": 412,
      "success": false,
      "kind": "xmlhttprequest",
      "timestamp": 1756000512100,
    },
    {
      "type": "error",
      "message": "…",
      "kind": "exception",
      "handled": false,
      "timestamp": 1756000512200,
    },
  ],
}

A bug report is a single self-contained event:

{
  "type": "bug_report",
  "message": "Saving the appointment does nothing",
  "timestamp": 1756000512345,
  "page": { "url": "…", "path": "/schedule" },
  "browser": {
    "userAgent": "…",
    "language": "en-US",
    "viewport": { "width": 1512, "height": 860 },
  },
  "logs": [],
  "errors": [],
  "requests": [],
}

Ideas and comments send { "type": "feedback", "feedbackType": "idea" | "comment", … } without the diagnostics window.

Feedback bypasses the queue so the form learns whether delivery succeeded and can keep the user's text on failure.

Privacy

Defaults, enforced at capture time — before anything is buffered, let alone sent:

  • object keys, query parameters and header names are normalized (lowercased, separators stripped) and redacted when they contain any fragment from DEFAULT_SENSITIVE_KEYS — authorization, cookie, password, passwd, passphrase, secret, token, apikey, accesskey, privatekey, credential, sessionid, creditcard, cardnumber, cvv, ssn, otp, jwt. So X-API-Key, api_key, accessToken and refresh_token are all replaced with [REDACTED];
  • token-shaped values (bearer tokens, JWTs, long hex/base64 blobs) are scrubbed out of strings and stack traces;
  • document.cookie is never read, input values are never captured, request and response bodies are never captured;
  • headers are only captured with network.captureHeaders: true, and still pass through redaction.

Add your own rules with privacy.redactKeys and privacy.redactPatterns, or drop query strings entirely with privacy.stripQuery: true.

Behaviour guarantees

  • Patched globals delegate to the originals: return values, thrown errors, rejected promises, headers and bodies are untouched, and DevTools output is unchanged. Capture failures are swallowed, never surfaced to your code.
  • Instrumentation is installed once per page and is reversible via shutdown().
  • Buffers are bounded by record count and by approximate byte size, then pruned lazily to windowMs; nothing polls while the page is idle.
  • Circular structures, getters that throw and huge payloads are all safe to log.

Development

pnpm build      # tsup: ESM + CJS + .d.ts
pnpm test       # vitest (jsdom)
pnpm typecheck