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

@sa.games/analytics-sdk

v0.1.0

Published

Browser / game analytics client SDK — canonical event emission, offline queue, sessions (spec 009-client-sdk). MIT.

Readme

@sa.games/analytics-sdk

The npm package is @sa.games/analytics-sdk. The wire identifier sdk.name = "analytics-sdk" is stable regardless of the npm scope, so changing the publish scope never changes the on-the-wire protocol.

The browser / game client analytics SDK for the self-hostable analytics platform (spec 009-client-sdk). It turns game-code calls into canonical envelopes, survives flaky networks and killed tabs, and implements the platform session definition — the shipped half of the "live counts in minutes" promise.

  • Trust posture. This SDK is the public, embeddable, spoofable path. It authenticates with a public sdk_key (pk_…); every event is server-stamped provenance = client. It can never mint revenue — money is the server SDK's job (@tusi-game/analytics-sdk-server).
  • Zero runtime dependencies. ESM + CJS + a browser-global bundle + .d.ts.

Install

npm install @sa.games/analytics-sdk

Quickstart (the US1 promise: live counts in minutes)

import { AnalyticsClient } from '@sa.games/analytics-sdk';

const sdk = await AnalyticsClient.init({
  sdkKey: 'pk_live_xxx',                 // PUBLIC key — safe to ship in a build
  endpoint: 'https://analytics.example.com',
});

await sdk.track('level_start', { level: 1 });

Script tag / Phaser embed:

<script src="https://unpkg.com/@sa.games/analytics-sdk/dist/index.global.js"></script>
<script>
  AnalyticsSDK.AnalyticsClient.init({ sdkKey: 'pk_...', endpoint: '...' })
    .then((sdk) => sdk.track('level_start'));
</script>

API

| Call | Effect | |---|---| | AnalyticsClient.init({ sdkKey, endpoint, ...config }) | Fails fast if sdkKey is not a pk_ key; loads state, reconciles a killed session, starts the flush loop + lifecycle listeners. | | track(name, props?) | One generic event; bumps session activity. | | economy(flowType, currencyType, amount, reason, context?) | One client-provenance economy flow (amount > 0, magnitude only). | | newPurchaseAttempt() | Mints + returns a purchase_attempt_id (see below). | | purchaseContext(purchaseAttemptId, context?) | The zero-money purchase companion, keyed by purchase_attempt_id. | | identify(userId) | Persists user_id; emits a one-off identify alias edge on the first anon→user transition. | | appClose() | Explicit close for runtimes without reliable lifecycle events. | | flush() | Forces an immediate transmit attempt. |

Purchases: thread purchase_attempt_id through the store call

The client never sends money. To join client-side purchase context (in_game_state, player level, session counter) to the authoritative revenue row emitted by the server SDK, mint a purchase_attempt_id and thread it through both the store purchase and the companion:

const attemptId = sdk.newPurchaseAttempt();

// Thread it into the store purchase so your backend can read it back:
//   StoreKit 2 → Product.PurchaseOption.appAccountToken(UUID(uuidString: attemptId))
//   Google Play → setObfuscatedAccountId(attemptId)
await store.buy(product, { appAccountToken: attemptId });

// Emit the zero-money companion in the SAME session:
await sdk.purchaseContext(attemptId, { player_level: 12, in_game_state: 'boss_fight' });

Your backend then relays the SAME purchase_attempt_id onto the verified revenue row via @tusi-game/analytics-sdk-server's verifiedPurchase. If you cannot thread it, the companion still ships and the purchase still counts — only the segmented dimensions are lost (graceful degradation, spec §3.2). The store transaction_id is the server's money-dedup key; purchase_attempt_id is the context-join key — two keys, two jobs.

Configuration (init options)

| Knob | Default | Notes | |---|---|---| | session_inactivity_timeout_min | 30 | Mirrors the server knob; no remote-config in v1 — keep the two in agreement by hand. Drift changes session boundaries only. | | batch_max_events / flush_interval_ms | 50 / 10000 | Flush on size or interval, whichever first. | | offline_queue_max_events | 10000 | Drop-oldest at the cap (a queue_overflow count is visible in debug). | | retry_backoff_base_ms / retry_backoff_max_ms | 2000 / 300000 | Exponential + full jitter. | | storage | auto | auto prefers IndexedDB → localStorage → memory. | | batch_max_bytes | 60000 | Per-POST byte cap; the unload flush stays under the ~64 KB beacon cap. | | compress | auto | gzip via CompressionStream where available. | | event_ttl_ms | 82800000 (23 h) | Non-money events older than this are dropped before send (honors the 24 h server dedup window). | | debug | false | Local rejections, drop counters, transport errors → console. |

Reliability contract

  • At-least-once. A crash between send and ack re-sends events with their original event_id (never re-minted); the server's 24 h windowed dedup absorbs the redelivery. Retries re-stamp client_sent_time but never client_event_time.
  • Never blocks gameplay. A capture returns after a durable enqueue; transport and retry run off the hot path.
  • Never throws into game code. Storage quota/write failures evict-oldest and count; they never surface as an exception.

Versioning

Package semver moves independently of the wire version (which stays v:1 additive-only forever). The sdk.version on every batch is provenance, not protocol. Releases are cut via changesets; CI publishes on tag via npm trusted publishing (OIDC) with provenance — no NPM_TOKEN.

License

MIT — the embeddable SDK is MIT (GPL-2.0-compatible) even though the platform is Apache-2.0, so it can ship inside third-party game builds.