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

@seathold/sdk

v0.1.17

Published

SeatHold embed SDK — renders an interactive seat map inside an iframe and communicates via postMessage

Readme

SeatHold SDK

Embed an interactive seat map in any web page.

The SDK now also includes helpers for the public SeatHold backend flow:

  • create temporary session_token
  • read builder and inventory with the required headers
  • create and release hold by inventory.label
  • recreate the session before expiration

Installation

npm install @seathold/sdk

Or via CDN:

<script src="https://cdn.jsdelivr.net/npm/@seathold/sdk/dist/seathold.umd.js"></script>

Basic Usage

<div id="seat-map"></div>

<script type="module">
  import { SeatingChart } from '@seathold/sdk';

  const chart = new SeatingChart({
    divId: 'seat-map',
    baseUrl: 'https://tickets.myapp.com',
    workspaceKey: 'pub_xxxxxxxxxxxx',
    event: '019f117c-5954-7312-8e35-f8d29bc57c8e',
    mode: 'simplified',
    onReady: (eventId, objectKeys) => {
      console.log('Map ready for event:', eventId, objectKeys);
    },
    onSelectionChanged: (seatIds, ticketTypes, objectKeys, items, pricingSelection) => {
      console.log('Selected seats:', seatIds);
      console.log('Ticket types chosen:', ticketTypes);
      console.log('Pricing selection:', pricingSelection);
      console.log('Selected items:', items);
    },
    onSessionCreated: (sessionToken, expiresAt) => {
      console.log('Embed session created:', sessionToken, expiresAt);
    },
    onSessionUpdated: (sessionToken, expiresAt) => {
      console.log('Embed session token:', sessionToken, 'expires at:', expiresAt);
    },
    onHoldCreated: (holdId, holdToken, expiresAt, seatIds, ticketTypes) => {
      console.log('Hold created:', holdId);
    },
  }).render();
</script>

Multiprice

Pass pricing to define ticket types per section key. pricing[].category must match the key exposed by the embed payload, for example sections[].key. When the user clicks a seat in that section, a popover shows the available ticket types to choose from.

const chart = new SeatingChart({
  divId: 'seat-map',
  baseUrl: 'https://tickets.myapp.com',
  workspaceKey: 'pub_xxxxxxxxxxxx',
  event: '019f117c-5954-7312-8e35-f8d29bc57c8e',

  pricing: [
    {
      category: '1b',
      ticketTypes: [
        { id: 'pista-inteira', label: 'Inteira',  price: 120, currency: 'BRL' },
        { id: 'pista-meia',    label: 'Meia',     price: 60,  currency: 'BRL', color: '#22c55e' },
      ],
    },
    {
      category: 'vip-a',
      ticketTypes: [
        { id: 'vip-inteira', label: 'VIP Inteira', price: 350, currency: 'BRL' },
      ],
    },
  ],

  onSelectionChanged: (seatIds, ticketTypes, objectKeys, items, pricingSelection) => {
    // ticketTypes: { "seat-42": "pista-inteira", "seat-43": "pista-meia" }
    console.log(seatIds, ticketTypes, objectKeys, items, pricingSelection);
  },

  onSessionCreated: (sessionToken, expiresAt) => {
    // Persist the first session token created inside the iframe
    fetch('/api/embed-session-created', {
      method: 'POST',
      body: JSON.stringify({ sessionToken, expiresAt }),
    });
  },

  onSessionUpdated: (sessionToken, expiresAt) => {
    // Persist the new embed session token created inside the iframe
    fetch('/api/embed-session', {
      method: 'POST',
      body: JSON.stringify({ sessionToken, expiresAt }),
    });
  },

  onHoldCreated: (holdId, holdToken, expiresAt, seatIds, ticketTypes) => {
    // Since the session-token refactor, holdId === holdToken === sessionToken
    // Send the session token to your backend to finalize the order
    fetch('/api/orders', {
      method: 'POST',
      body: JSON.stringify({ holdId, holdToken, ticketTypes }),
    });
  },
}).render();

API

new SeatingChart(config)

| Option | Type | Required | Description | |---|---|---|---| | divId | string | yes | ID of the container element | | baseUrl | string | yes | Base URL of your SeatHold server | | workspaceKey | string | yes | Public workspace key | | event | string | yes | Public event.id used in X-SeatHold-Event-Id | | mode | 'manager' \| 'simplified' | | Embed mode applied on iframe mount | | environment | 'production' \| 'sandbox' | | Optional environment header for POST /api/session-tokens | | sessionToken | string | | Session token for authenticated holds | | sessionExpiresAt | string | | Current session expiration timestamp | | sessionRefreshBufferMs | number | | Recreate the session this many ms before expiration. Default: 30000 | | pricing | PricingRule[] | | Multiprice rules per section key | | height | number or string | | iframe height (default: 600px) | | width | number or string | | iframe width (default: 100%) | | onReady | (eventId, objectKeys?) => void | | Fired when the map finishes loading | | onSelectionChanged | (seatIds, ticketTypes, objectKeys, items, pricingSelection) => void | | Fired on every selection change | | onObjectClicked | (objectId, objectType, objectKey?, categoryKey?) => void | | Fired when any object is clicked | | onCategoryChanged | (categoryId) => void | | Fired when active category changes | | onViewChanged | (zoom, position) => void | | Fired on pan/zoom | | onHoldCreated | (holdId, holdToken, expiresAt, seatIds, ticketTypes, objectKeys, items) => void | | Fired after a hold is created | | onHoldReleased | () => void | | Fired after a hold is released | | onState | (state) => void | | Fired when the iframe responds to requestState() | | onSessionCreated | (sessionToken, expiresAt) => void | | Fired when the iframe creates a new session | | onSessionUpdated | (sessionToken, expiresAt) => void | | Fired when the iframe creates or refreshes the embed session token | | onError | (action, message) => void | | Fired on errors |

Instance methods

chart.render()                        // Inject the iframe and start listening
chart.destroy()                       // Remove the iframe and all listeners
chart.setSelectedSeats([id1, id2])    // Programmatically select seats
await chart.createSessionToken()      // Create a public API session token
await chart.refreshSessionToken()     // Force a new session token and sync the iframe
await chart.ensureValidSession()      // Reuse or recreate the session if close to expiring
await chart.getBuilder()              // GET /api/render-map/builder
await chart.getInventory()            // GET /api/render-map/inventory
await chart.holdByLabel('A-10')       // POST /api/inventory/{label}/hold
await chart.releaseByLabel('A-10')    // POST /api/inventory/{label}/release
chart.holdCreated(sessionToken, expiresAt) // Notify the iframe about an external hold/session
chart.releaseHold()                   // Release the current hold
chart.updateSession(token, expiresAt) // Refresh the session token
chart.requestState()                  // Ask for current state snapshot
chart.setPricing(rules)               // Update pricing rules at runtime

Public API Flow

The public backend uses a temporary session_token before any hold exists. The SDK methods above follow this contract:

  1. createSessionToken() calls POST /api/session-tokens
  2. getBuilder() calls GET /api/render-map/builder
  3. getInventory() calls GET /api/render-map/inventory
  4. holdByLabel(label) calls POST /api/inventory/{label}/hold
  5. releaseByLabel(label) calls POST /api/inventory/{label}/release

Protected routes automatically send:

  • X-SeatHold-Event-Id
  • X-SeatHold-Public-Key
  • X-SeatHold-Session-Token

Important:

  • the SDK sends event.id in X-SeatHold-Event-Id
  • do not use event.uid
  • hold/release is done by inventory.label, not by seat_id

createSessionToken() optionally sends X-SeatHold-Environment: production|sandbox when config.environment is provided.

Embed mode

Set mode only when creating the chart. The SDK sends it as a query param in the iframe URL, for example ?mode=simplified or ?mode=manager.

If mode is omitted, the embed uses the default purchase flow. To change mode after load, destroy and render a new iframe with a different mode.

Session token capture

If the embed creates a new authenticated session from inside the iframe, listen to onSessionCreated and onSessionUpdated to capture the sessionToken in the host app:

const chart = new SeatingChart({
  // ...
  onSessionCreated: (sessionToken, expiresAt) => {
    console.log('New sessionToken from iframe:', sessionToken, expiresAt);
  },
  onSessionUpdated: (sessionToken, expiresAt) => {
    console.log('Updated sessionToken from iframe:', sessionToken, expiresAt);
  },
  onState: (state) => {
    console.log('Current embed state:', state);
  },
}).render();

chart.requestState();

For this to work, the embed must post either:

  • seathold:session_created with { sessionToken, expiresAt }
  • seathold:session_updated with { sessionToken, expiresAt }
  • seathold:state with { sessionToken, sessionExpiresAt, ... }

When the SDK itself creates or refreshes the session through createSessionToken(), refreshSessionToken() or ensureValidSession(), it also:

  • updates the internal token used by the public API helpers
  • syncs the iframe with seathold:update_session
  • recreates the session automatically before expires_at

Hold token compatibility

Since the session-token refactor, holdId === holdToken === sessionToken.

If your booking API previously received holdId, update that integration to use the sessionToken semantics instead. The TypeScript types still allow the legacy names for backward compatibility.

When calling chart.holdCreated(sessionToken, expiresAt), the SDK sends:

{
  type: 'seathold:hold_created',
  holdId: sessionToken,
  holdToken: sessionToken,
  sessionToken,
  expiresAt,
}

Selection payload

The embed can now expose stable commercial keys directly in the selection payload.

type SelectedItem = {
  objectKey: string;
  objectId: string | number;
  sectionKey?: string | null;
  categoryKey?: string | null;
  ticketType: string | null;
};

items in onSelectionChanged and onHoldCreated may include sectionKey and categoryKey when the map or inventory payload provides them.

onSelectionChanged now also receives:

pricingSelection: Record<string, string | null>;

This maps each selected object to the current pricing choice coming from the embed.

Public API Errors

The public API can return codes such as:

  • workspace_key_required
  • invalid_workspace_key
  • session_token_required
  • invalid_or_expired_session_token
  • session_token_event_mismatch
  • session_token_workspace_mismatch

The SDK surfaces these through thrown errors and onError.

TicketType

type TicketType = {
  id: string;
  label: string;
  color?: string | null;
  price?: number | null;
  currency?: string | null;
};