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

@gumlet/insights-js-core

v4.1.0

Published

Gumlet Insights allows you to collect data from Video playback.

Readme

@gumlet/insights-js-core

Video analytics SDK for Gumlet Insights. Collects playback lifecycle events — startup, play, pause, seek, rebuffering, quality change, error, end — and sends them to the Gumlet ingest API.

Browser: HTML5, HLS.js, and Shaka Player via GumletInsights.

React Native: consumed by @gumlet/insights-react-native via the legacy gumlet.insights() entry (host runtime with deviceData / identity overrides).


Requirements

  • Node.js ≥ 20.x (build / dev)
  • Browser: one of the three supported players loaded in the page before this SDK
  • React Native: @gumlet/insights-react-native ≥ 2.x (peer of this package ≥ 3.0.2)

Installation

npm install @gumlet/insights-js-core

Or load the pre-built IIFE bundle directly in a <script> tag (sets window.gumlet.GumletInsights):

<script src="dist/main.iife.js"></script>

If you prefer to use MJS (we recommend), here is a way to do it:

<script type="module" src="dist/main.mjs"></script>

Quick start

ESM / npm

import { GumletInsights } from '@gumlet/insights-js-core';

const insights = new GumletInsights({
  workspace_id: 'YOUR_WORKSPACE_ID',
  property_id: 'YOUR_PROPERTY_ID', // optional
});

// HTML5 native video element — pass the element directly
await insights.attach(document.querySelector('video'));

// HLS.js — pass the Hls instance (media element auto-detected from hls.media)
await insights.attach(hls);

// Shaka Player — mediaElement is required
await insights.attach(player, { mediaElement: videoEl });

CDN / MJS

<script type="module" src="dist/main.mjs"></script>
<script type="module">
  const insights = new gumlet.GumletInsights({
    workspace_id: 'YOUR_WORKSPACE_ID',
    property_id: 'YOUR_PROPERTY_ID', // optional
  });
  await insights.attach(player, { mediaElement: videoEl });
</script>

Full custom metadata (local / ingest testing)

To exercise every optional metadata slot (customData1–10, user, video, player, experiment) with distinct placeholder strings, spread fullCustomAnalyticsConfig, then set workspace_id (and optionally property_id). User and session IDs follow the normal cookie-based flow (omit test: true — that mode forces random UUIDs instead of gm_analytics_user_id / gm_analytics_session_id). Use a dedicated property_id for lab traffic if you need to separate it in dashboards.

ESM

import { GumletInsights, fullCustomAnalyticsConfig } from '@gumlet/insights-js-core';

const insights = new GumletInsights({
  ...fullCustomAnalyticsConfig,
  workspace_id: 'YOUR_WORKSPACE_ID',
  property_id: 'YOUR_PROPERTY_ID', // optional
});

IIFE — after loading main.iife.js, use window.gumlet.insightsFullCustomTestConfig:

const insights = new gumlet.GumletInsights({
  ...gumlet.insightsFullCustomTestConfig,
  workspace_id: 'YOUR_WORKSPACE_ID',
  property_id: 'YOUR_PROPERTY_ID', // optional
});

API reference

new GumletInsights(config)

Creates the analytics instance. Required: workspace_id (or legacy alias customWorkspaceId). property_id is optional. If workspace_id is missing or empty, a console.error is logged and the SDK is disabled — all subsequent calls become no-ops.

On construct, the SDK calls GET /license?workspace_id=… on the ingest host (when license I/O is enabled). Beacons are queued until the response arrives:

| Outcome | Behaviour | |---|---| | Granted | Flush queue; apply server ingest (v1 / v2 / v1,v2). If the body includes ouuid, later /v2 beacons send it as custom_organisation_id | | Denied (404/403) | Drop queue; no analytics | | 503 / network after 3 short retries | Fail-open — grant with ingest v1,v2 |

License I/O runs when:

| Runtime | /license | |---|---| | Browser (window + document, not React Native) | Always | | React Native / host SDK (deviceData, sessionID, or userID) | When fetch exists | | Pure SSR / Node (no host signals) | Skipped — ephemeral IDs only, no session HTTP until client hydrate |

React Native may polyfill window / document; core treats navigator.product === 'ReactNative' as a host client, not a browser (no cookie-based session path).

Set debug: true to log license URL, HTTP status, and grant/deny decisions (console.warn so Metro / LogBox surfaces them).

insights.attach(player?, opts?)

Connects the SDK to a player. Runs all validation checks before attaching.

| player value | Behaviour | |---|---| | Hls instance | HLS.js adapter | | shaka.Player instance | Shaka adapter — opts.mediaElement required | | HTMLVideoElement | HTML5 adapter | | null / undefined | HTML5 adapter — opts.mediaElement required |

opts:

| Option | Type | Description | |--------|------|-------------| | mediaElement | HTMLVideoElement | The <video> element. Required for Shaka; recommended for HLS.js. | | starttime | number | Unix epoch ms marking the session start. Defaults to Date.now(). |

Calling attach() while already attached logs a warning and detaches the previous player first.

insights.detach()

Removes all event listeners and disconnects from the player. Safe to call when nothing is attached.

insights.isAttached

boolean — true after a successful attach(), false after detach() or a failed attach.

insights.getImpressionId()

Returns the current impression ID string, or undefined if the SDK failed to initialize.

insights.updateConfig(partial)

Merges a partial config into the running session — updates custom data slots, video metadata, user metadata, and player metadata simultaneously.

insights.updateConfig({
  custom_data_1: 'experiment-a',
  customVideoTitle: 'Episode 3',
});

Individual update helpers

insights.updateCustomData({ custom_data_1: 'value' });
insights.updateCustomVideoData({ customVideoTitle: 'Episode 2' });
insights.updateCustomUserData({ userName: 'alice' });
insights.updateCustomPlayerData({ customPlayerName: 'shaka-v4' });

Validation

attach() checks the player and options before connecting. On failure it logs a [GumletInsights]-prefixed console.error and returns without sending any events.

| Scenario | Error message | |---|---| | SDK not initialized (workspace_id empty/missing) | attach() called but the SDK is not initialized. Check that you passed a valid workspace_id | | null / undefined with no mediaElement | HTML5 mode requires opts.mediaElement (an HTMLVideoElement) | | opts.mediaElement is not an HTMLVideoElement | opts.mediaElement must be an HTMLVideoElement, got: [object …] | | Unrecognized player type | Unrecognized player type (…). Supported: HLS.js, Shaka Player, HTMLVideoElement… | | HLS.js player missing core API (likely destroyed) | HLS.js player is not in a valid state. The player may have been destroyed. | | Shaka without resolvable video element | Shaka Player requires opts.mediaElement — the HTMLVideoElement Shaka is playing into | | Shaka player with isDestroyed() === true | Shaka Player is not in a valid state. The player may have been destroyed. | | Already attached (not an error — just a warning) | attach() called while a player is already attached. Detaching previous player first. |


Configuration

All options are passed to the GumletInsights constructor:

| Key | Type | Required | Description | |-----|------|----------|-------------| | workspace_id | string | Yes | Gumlet video-source Mongo _id (tenant gate for /license) | | property_id | string | No | Legacy Insights property id (optional in 3.x) | | customWorkspaceId | string | No | Legacy alias for workspace_id | | debug | boolean | No | Enable verbose console logging | | test | boolean | No | Use a test user/session ID (events flagged, not counted in production) | | page_url | string | No | Override the detected page URL (useful in iframes / SSR / RN screen_name) | | sessionID | string | No | Override the auto-generated session ID (host SDKs: AsyncStorage session) | | userID | string | No | Override the auto-generated user ID | | sendSessionRequest | boolean | No | Set to false to skip the session-init HTTP call (default true when host supplies a new session) | | deviceData | object | No | Host device envelope (RN: display, OS, orientation, etc.) merged into session + events | | playerData | object | No | Host player envelope at init (RN: player_software, autoplay, page type) | | disable_analytics_v2 | boolean | No | When true, skips the v2 mirror (…/v2) for session / session_event beacons. By default every instance dual-sends v1 + v2 unless this kill-switch is set. | | getUUID | () => string | No | Custom UUID generator for event and playback IDs | | customData1 … customData10 | string | No | Arbitrary metadata attached to every event | | customVideoId | string | No | Override video ID (auto-detected from video.gumlet.io URLs) | | customVideoTitle | string | No | Video title | | customVideoDurationMillis | string | No | Override video duration | | customVideoSeries | string | No | Series/playlist name | | customVideoProducer | string | No | Producer / uploader name | | customVideoLanguage | string | No | Video language | | customVideoVariantName | string | No | A/B variant label | | customVideoVariant | string | No | A/B variant value | | customContentType | string | No | Content category (e.g. "live", "vod") | | customEncodingVariant | string | No | Encoding profile label | | userId | string | No | Custom user id (custom_user_id). Not the Gumlet anonymous user_id UUID. | | userEMail / userEmail | string | No | Viewer email → user_email / custom_user_email on ingest (either spelling; userEMail wins if both are set). | | userName / userCity / … | string | No | Other user identity metadata | | customPlayerName / customPageType / experimentName | string | No | Player / experiment metadata |


Analytics events emitted

| Wire event | Trigger | |------------|---------| | event_setup | SDK initialised | | event_player_ready | Player reported ready | | event_playback_ready | Source loaded | | event_play | User pressed play (first play or resume) | | event_playing | First frame rendered / resumed after pause or seek | | event_playback_started | Startup latency measurement point | | event_pause | Playback paused | | event_rebuffer_start | Stall / buffering begun | | event_rebuffer_end | Buffering recovered | | event_seeked | Seek completed | | event_playback_update | Heartbeat every ~10 seconds of watch time | | event_error | Fatal or non-fatal player error (always sent) | | event_error_recovered | Player resumed after a fatal error only | | event_ended | Video played to completion | | event_mute / event_unmute | Mute state changed | | event_audio_language_changed | Audio language switched (current audio_language on same beacon) | | event_subtitle_language_changed | Subtitle/caption language switched or off (current subtitle_language) |

Every session_event also carries audio_language (active audio) and subtitle_language when captions are on (player_language_code is still sent as a legacy alias of audio_language).

Error severity (Shaka Player)

event_error fires for all Shaka errors regardless of severity. The state machine handles them differently:

| Severity | Value | State machine effect | |----------|-------|----------------------| | RECOVERABLE | 1 | Self-transition — state unchanged, event_error_recovered never fires | | CRITICAL | 2 | Transitions to ERROR state; event_error_recovered fires if playback resumes |


Development

Install dependencies

npm install

Start dev server

npm start

Runs the TypeScript build in watch mode and serves the demo pages at http://localhost:8080. Opens html/hlsjs.html automatically. Navigate between players using the header on any demo page:

| URL | Player | |-----|--------| | /html/html5.html | Native <video> element | | /html/hlsjs.html | HLS.js — Angel One (multi audio + subtitles), quality + language dropdowns | | /html/shaka-4.html | Shaka 4.15.x — Angel One DASH, quality + language dropdowns, error triggers (isTextTrackVisible) | | /html/shaka-5.html | Shaka 5.1.21 (jsDelivr npm dist) — same demo; captions via selectTextTrack / selectTextTrack(null) | | /html/shaka.html | Redirects to shaka-4.html |

Each page includes a live event log (clock timestamp · event family · event name · previous event · millis from previous event · playback time). Click any row to inspect the full payload.

Build

npm run build:release   # production bundle → dist/
npm run build:debug     # watch mode

Output files:

| File | Format | Use | |------|--------|-----| | dist/main.mjs | ESM | npm / bundlers | | dist/main.iife.js | IIFE | CDN <script> tag | | dist/main.d.mts | Declarations | TypeScript consumers (types export) |

Types

npm run typecheck   # tsc --noEmit
npm run lint        # biome; new `any` fails the build

src/ is fully typed — see src/types/README.md for where each type comes from, and TYPING_FINDINGS.md for runtime bugs found (and deliberately not fixed) during the typing pass.

The published package ships declarations at dist/main.d.mts (generated by tsdown), so TypeScript consumers get types without any @types/… package:

import type { AnalyticsConfig, AttachOptions, SamplePayload } from '@gumlet/insights-js-core';

Everything re-exported from src/types/index.ts is importable this way, alongside GumletInsights itself.

Next.js / SSR

DOM access (window, document, navigator, screen, fetch) is guarded so the package can be imported and constructed during a Next.js / Node SSR build. On the server, construction assigns ephemeral IDs only — no cookies and no session HTTP. Call attach() / register*() only on the client (e.g. inside useEffect, or behind 'use client' + a dynamic import with ssr: false). Pass page_url when constructing if you need a page URL while window is unavailable.

React Native (host client)

Used internally by @gumlet/insights-react-native. The HOC constructs via gumlet.insights(config) and passes:

  • workspace_id (required), sessionID / userID / sendSessionRequest from AsyncStorage
  • deviceData (OS, display, orientation, manufacturer, …)
  • playerData (player_software, integration version, screen metadata)

Host-specific behaviour in core:

| Area | Behaviour | |------|-----------| | isBrowser() | false when navigator.product === 'ReactNative' | | Session HTTP | Honors sendSessionRequest from host; not cookie-based | | Orientation | Reads deviceData.orientation; setOrientation() on rotation | | Device meta | Stamped on every event sample from session envelope | | State machine | Static import (Metro cannot load dynamic ESM chunks) |

See the RN SDK README for integration steps.

Test

npm test

149 tests across 12 test files:

| Suite | Coverage | |-------|----------| | SessionManager | Identity, custom data, session expiry | | SampleBuilder | Payload assembly, playback_time_instant_millis | | EventPublisher | Duration guard, 24-hour live-stream guard | | GumletStateMachine | Full lifecycle, rebuffering, error recovery, non-fatal errors, seek-while-paused, quality-change self-transitions | | HTML5Adapter | DOM event → analytics event mapping, destroy() | | HlsjsAdapter | Quality level extraction, error forwarding | | ShakaAdapter | MIME type detection, variant track parsing, error pipeline | | GumletInsights | All attach() validation paths, double-attach, detach, impression ID | | Heartbeat timer | Regression tests for quality-change and mute resetting the window |

Lint

npm run lint        # check
npm run lint:fix    # auto-fix

Release

Publishing is tag-driven (.github/workflows/main.yml):

  1. Move [Unreleased] items in CHANGELOG.md into a dated version block.
  2. Add RELEASE_NOTES_x.x.x.md (user-facing summary).
  3. Bump package.json version and commit.
  4. Push a git tag (e.g. v3.0.3):
npm version patch   # or minor / major
git push origin main --follow-tags

CI runs lint, typecheck, tests, npm run build:release, then:

Requires GitHub secrets: NPM_TOKEN, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY.

Publish @gumlet/insights-react-native only after the core version it depends on is live on npm.


Architecture

GumletInsights  (public API — attach/detach/validate)
└── Analytics   (orchestrator)
    ├── SessionManager     — identity (userId, sessionId, playbackId), custom metadata
    ├── SampleBuilder      — playback-state fields, payload assembly (getSample)
    ├── EventPublisher     — HTTP dispatch (EventsCall), send-and-clear, unload
    └── GumletStateMachine — jsm v3 FSM: SETUP → READY → STARTUP → PLAYING → … → END/ERROR
        ├── HTML5Adapter   — native HTMLVideoElement events (abstract base)
        ├── HlsjsAdapter   — extends HTML5Adapter; hooks Hls.js engine events
        └── ShakaAdapter   — extends HTML5Adapter; hooks Shaka 'error' event

Key design decisions

  • GumletInsights wraps Analytics — Validation, lifecycle management (attach/detach), and the public API surface live in GumletInsights. Analytics and its adapters are internal implementation detail.
  • Single state machine for all players — GumletStateMachine (jsm v3) is shared across all three adapters. Player-specific behaviour lives entirely in the adapter layer.
  • lastKnownCurrentTime_ — video.currentTime resets to 0 when a player replaces its MediaSource — before error and pause events fire. Adapters cache the last non-zero value from timeupdate events and use that for error timestamps instead.
  • sourceSwitching_ flag — A MediaSource replacement causes the browser to fire emptied → pause → playing — none of which are user actions. The flag (set on emptied, cleared only when timeupdate fires with currentTime > 0) suppresses spurious pause and playing analytics events during the switch.
  • Heartbeat isolation — heartbeatTimestamp is independent of onEnterStateTimestamp. Quality-change and mute self-transitions reset onEnterStateTimestamp (for state-duration maths) but must never reset the 10-second heartbeat window.
  • Non-fatal vs fatal errors — Shaka RECOVERABLE (severity 1) errors use a playerNonFatalError self-transition: event_error is still sent, but the state machine stays in its current state so event_error_recovered can never fire spuriously.

Project structure

src/
  core/            — GumletInsights (public API), Analytics, SessionManager,
                     SampleBuilder, EventPublisher, GumletInsightsExport (CDN entry)
  adapters/        — HTML5Adapter (base), HlsjsAdapter, ShakaAdapter
  stateMachine/    — GumletStateMachine (jsm v3)
  cast/            — CastClient, CastReceiver (implemented; wiring pending — see TODO.md)
  enums/           — Events, GumletEnum (wire names), MIMETypes, Players, StreamTypes
  types/           — AnalyticsConfig, SamplePayload, IAdapter, IStateMachine, ambient .d.ts
  utils/           — Logger, Utils, Settings, PlayerDetector, DetectDevice, …
html/              — manual test pages (html5, hlsjs, shaka) + event-logger.js
tests/             — unit tests (phase1 … phase5 + stage1 regression)
dist/              — built output (main.mjs, main.iife.js, main.d.mts)
CHANGELOG.md       — full technical history for agents and developers
TODO.md            — known issues and pending work
V2_REVAMP_PLAN.md  — 8-phase revamp plan (read-only reference)

Legacy API

The pre-v2 gumlet.insights(config).registerXxxPlayer(...) API is still available for CDN users and will not be removed:

// Still works — backward compatible
const analytics = gumlet.insights({
  workspace_id: 'YOUR_WORKSPACE_ID',
  property_id: 'YOUR_ID', // optional
});
analytics.registerShakaPlayer(player, { mediaElement: video });
analytics.registerHLSJSPlayer(hls);
analytics.registerHTML5Player(video);
await analytics.registerReactNativeVideoPlayer({}); // React Native — no player handle

New browser integrations should use GumletInsights. React Native apps should use @gumlet/insights-react-native.


License

MIT © Gumlet Pte. Ltd.