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

@ticketlayer/live

v5.9.0

Published

Ticketlayer Live SDK - buyer-facing client (browser + React Native)

Readme

@ticketlayer/live

The buyer-facing Ticketlayer SDK. It runs in the browser (@ticketlayer/live), in React Native (@ticketlayer/live/native) and, from a separate entry, on the server (@ticketlayer/live/server).

npm install @ticketlayer/live

Third-party pages load the same client from the CDN build instead. The vault page architecture/sdk-architecture.md lists what is on npm.

Browser

import { createLiveClient } from '@ticketlayer/live';

const tl = await createLiveClient({
  baseUrl: 'https://live-staging.t9r.dev',
  publishableKey: 'tlpk_...',   // the channel's publishable key (embed); omit on a hosted storefront
  checkout: { mode: 'modal', embedBaseUrl: 'https://hbo.staging.t9r.dev' },
});

The checkout modal hands the page's theme to the hosted checkout, so the frame wears the brand the Elements around it wear: every token a brand may set (the ones with a theme key in @ticketlayer/theme) that this page resolves to something other than its default, read from document.documentElement and document.body, travels in the iframe URL's theme parameter. A page at the defaults sends nothing. checkout: { mode: 'modal', inheritTheme: false } keeps the checkout on the channel theme alone. readHostTheme and encodeHostTheme are exported for a host that opens /embed/checkout itself.

The CDN build (dist/ticketlayer.js, served at /v1/ticketlayer.js) boots itself from data-* attributes on its script tag; see src/auto.ts and cdn/README.md at the repository root. CLAUDE.md is the working agreement.

Analytics

@ticketlayer/live/analytics is Ticketlayer's first-party analytics beacon, standalone so a page that only counts visits (Manage, say) loads it alone: about 1.5 KB gzipped, and pnpm --filter @ticketlayer/live size fails above 2 KB.

import { createAnalytics } from '@ticketlayer/live/analytics';

const analytics = createAnalytics({
  endpoint: 'https://la-staging.t9r.dev/e', // production's https://la.ticketlayer.com/e when left out
  key: 'tlas_...',                          // a channel's tlpk_ or a staff site's tlas_; omit to resolve from the Origin
  surface: 'manage',
});
analytics.track('x.report_opened', { report: 'sales' });

It counts a page view on start and on every History API navigation that changes the path (autoPageviews: false turns that off), sends the time the page was visible when it is hidden, batches up to 20 events and sends them with sendBeacon (falling back to fetch with keepalive). It stores nothing on the device: no cookie, no localStorage, no sessionStorage. It sends nothing while Global Privacy Control or Do Not Track is on, or after optOut(), which lasts for the page. URLs leave the page as origin, path and the campaign parameters only (utm_*, ref, tl_c). A <meta name="tl-page-token"> on the page is sent with every batch.

The Live client carries one too, keyed with its channel key: tl.analytics and window.Ticketlayer.analytics, loaded on first use. createLiveClient's analytics option sets endpoint, enabled, surface (element unless the host says otherwise; the hosted box office passes hbo), hostPageTracking (false counts the elements' events but not the page's own views) and autoPageviews; the CDN build reads data-analytics-url, data-analytics="false" and data-host-page-tracking="false". The Elements record their own events through the connector's analytics (element.view, element.open, seat.select, cart.add, checkout.step, concierge.open), and the cart's id rides on every event while there is a cart.

Landing context

createLiveClient reads where the visit came from once, at boot: the page (origin, path and the campaign parameters), a referrer from another host (origin and path), utm_*, ref and the campaign link tl_c. Only tl_c is taken off the address; the UTM parameters stay. It is tl.landing, and every cart the client makes is created with it, so the order can say where the sale came from. Live keeps it on the session for the session's life.

React Native

@ticketlayer/live/native is the same stateful client (events, cart, auth, orders, the event bus) built for an app instead of a page: nothing defaults from window.location, nothing is published on window, no theme is injected and no iframe modal is mounted. The Live session id travels in the X-Live-Session header (as the browser embed's does) and is kept in an async token storage you inject, so a cart or a magic-link sign-in survives an app restart. Card details never enter the app: the hosted checkout opens in a WebView.

import * as SecureStore from 'expo-secure-store';
import { createNativeLiveClient, secureStoreStorage } from '@ticketlayer/live/native';

const tl = await createNativeLiveClient({
  apiUrl: 'https://api.live.ticketlayer.com',      // required: no page to default from
  publishableKey: 'tlpk_...',
  checkoutBaseUrl: 'https://checkout.ticketlayer.com', // the host serving /embed/checkout
  tokenStorage: secureStoreStorage(SecureStore),   // or asyncStorageStorage(AsyncStorage)
});

const listings = await tl.events.list();
await tl.cart.add({ eventOccurrenceId, categoryId, ticketTypeId, quantity: 1 });

Token storage is { get, set, remove } (all async). Two adapters ship with the package, each taking the platform module as a parameter so the SDK depends on neither: secureStoreStorage(SecureStore, { key?, options? }) for expo-secure-store (the keychain: preferred) and asyncStorageStorage(AsyncStorage, { key? }) for @react-native-async-storage/async-storage. memoryStorage() is the default and forgets the session on restart. On start the client resumes a stored session (GET /session); a token the API rejects is removed and the client starts lazily, creating a session on the first cart mutation or sign-in, as the browser client does.

Checkout, following live-hostedboxoffice/docs/embed-checkout-contract.md:

import { WebView } from 'react-native-webview';
import { Linking } from 'react-native';
import { parseCheckoutMessage } from '@ticketlayer/live/native';

const RETURN_URL = 'myapp://tickets/complete'; // on the checkout host's RETURN_URL_ALLOWLIST

// 1. The URL hands the session (and so the cart) to the hosted checkout.
const url = await tl.buildCheckoutUrl({ returnUrl: RETURN_URL });

// 2. Success arrives as a tl:complete message ...
<WebView
  source={{ uri: url }}
  onMessage={(e) => {
    const m = parseCheckoutMessage(e.nativeEvent.data);
    if (m?.type === 'tl:complete') tl.completeCheckout(m); // emits checkout:completed, drops the cart
    if (m?.type === 'tl:close') close();
  }}
  onShouldStartLoadWithRequest={(req) => {
    if (req.url.startsWith(RETURN_URL)) { Linking.openURL(req.url); return false; }
    return req.url.startsWith('https://');
  }}
/>

// 3. ... and as the return_url deep link (also on a cold start).
Linking.addEventListener('url', ({ url }) => {
  const done = tl.parseCheckoutReturn(url, { returnUrl: RETURN_URL });
  if (done) tl.completeCheckout(done); // { orderId, orderReference }
});

tl.signOut() ends the session, removes the stored token and clears the local cart; the next mutation mints a fresh session. The pure helpers (buildCheckoutUrl, parseCheckoutReturn, parseCheckoutMessage) are exported too for hosts that manage the session themselves.

A complete illustrative app is in examples/expo/ (type-checked against the built package with npx tsc -p examples/expo/tsconfig.json).

Server-side rendering

@ticketlayer/live/server is a stateless catalogue client for Node and edge runtimes: plain fetch, no DOM, no window guards, no Elements. It reads the channel's listings, event detail and theme, keeps a small in-memory TTL cache (60s by default, cache: false to disable), and never pulls the browser client into your server bundle. Nothing from it is included in the browser build.

import { createServerClient } from '@ticketlayer/live/server';

export const live = createServerClient({
  apiUrl: process.env.LIVE_API_INTERNAL_URL!,
  // One of: a channel publishable key, or the storefront domain (resolved
  // once via GET /channel and cached).
  publishableKey: process.env.LIVE_PUBLISHABLE_KEY,
  // domain: 'tickets.example.com',
  cache: { ttlMs: 60_000 }, // or false when the framework caches for you
});

A Next.js server component:

// app/events/[id]/page.tsx
import { notFound } from 'next/navigation';
import { createServerClient, LiveAPIError } from '@ticketlayer/live/server';

const live = createServerClient({
  apiUrl: process.env.LIVE_API_INTERNAL_URL!,
  publishableKey: process.env.LIVE_PUBLISHABLE_KEY!,
  // Let Next's fetch cache own revalidation instead of the SDK's TTL cache.
  cache: false,
});

export default async function EventPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  try {
    const [event, theme] = await Promise.all([
      live.events.get(id, { fetchInit: { next: { revalidate: 300, tags: ['events', `event:${id}`] } } }),
      live.theme.get({ fetchInit: { next: { revalidate: 300, tags: ['theme'] } } }),
    ]);
    return (
      <main style={{ fontFamily: theme.typography?.fontFamily }}>
        <h1>{event.name}</h1>
      </main>
    );
  } catch (err) {
    if (err instanceof LiveAPIError && err.status === 404) notFound();
    throw err;
  }
}

Surface:

| Call | Returns | |------|---------| | events.list(opts?) | Listing[] - the channel's active listings | | events.get(eventId, opts?) | EventDetail - throws LiveAPIError (404) when not listed | | theme.get(opts?) | Theme - throws LiveAPIError (404) when none is assigned | | session.create() | { sessionToken, channel, customer } - a Live session id, e.g. to hand to a WebView checkout | | get(path, opts?) | Unwrapped JSend data for any other Live GET | | clearCache() | Drops cached reads and the domain-resolved key |

opts.fetchInit is merged into that request (headers merge, everything else overrides), so framework-specific fetch options pass straight through. opts.noCache bypasses the in-memory cache for one call.

Every request carries TL-Version, TL-Client and User-Agent. TL-Client is the header every Ticketlayer SDK sets, on every runtime, because a browser cannot set User-Agent: the fetch spec makes it a forbidden header name and the browser drops it without saying so. This entry only ever runs in Node or on an edge runtime, so it sets both and sends the same identity in each (ticketlayer-live/<version> (node/<v>)). Pass userAgent to name your own program instead; TL-Client still identifies the SDK build either way.

const live = createServerClient({
  apiUrl: process.env.LIVE_API_INTERNAL_URL!,
  publishableKey: process.env.LIVE_PUBLISHABLE_KEY!,
  userAgent: 'acme-box-office/2.1',
});

Scripts

From the repository root, after pnpm install:

pnpm --filter @ticketlayer/live build      # tsc -> dist/ (index.js, server.js, native/, types)
pnpm --filter @ticketlayer/live build:cdn  # + esbuild IIFE -> dist/ticketlayer.js
pnpm --filter @ticketlayer/live test       # builds, then node --test test/*.test.mjs and the analytics size check
pnpm --filter @ticketlayer/live size       # the analytics size check alone (after a build)
pnpm --filter @ticketlayer/live docs:gen   # typedoc -> docs-output/

Releasing

Versioning and publishing are driven by changesets for the whole workspace: add a changeset with your change, and on merge to main the release workflow keeps a "Release Ticketlayer Live" pull request up to date; merging that publishes to npm with provenance. src/client.ts and src/generated/ are generated by backstage/tools/sdk-tooling (backstage's sdk-regenerate.yml opens the regeneration pull requests when the contract changes) and are shipped exactly as committed. The ticketlayer block in package.json records the spec version, dated API version and generator each release was built from.