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

reservaqui

v0.3.2

Published

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

Readme

reservaqui

SDK para embedar o mapa de assentos Reserva Aqui em qualquer página web.

npm install reservaqui

Via CDN:

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

Guia de integração

Pré-requisitos — o que você precisa ter em mãos

| Dado | O que é | Exemplo | |---|---|---| | workspaceKey | Chave pública do workspace | pub_abc123 | | event | ID numérico do evento na tabela events | "42" | | baseUrl | URL base do seu servidor Reserva Aqui | https://tickets.myapp.com |

Atenção: event deve ser o ID numérico (42), não um slug ("meu-show") nem um UUID. Se passar errado, o backend rejeita todas as requisições com 4xx.


Passo 1 — Renderizar o mapa

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

<script type="module">
import { SeatingChart } from 'reservaqui';

const chart = new SeatingChart({
  divId:        'seat-map',
  baseUrl:      'https://tickets.myapp.com',
  workspaceKey: 'pub_abc123',
  event:        '42',           // ID numérico do evento
  mode:         'simplified',   // ou 'manager'

  onReady(eventId, objectKeys) {
    console.log('Mapa pronto', eventId);
  },

  onSelectionChanged(seatIds, ticketTypes, objectKeys, items, pricingSelection) {
    console.log('Seleção atual:', seatIds);
  },

  onError(action, message) {
    console.error('Erro Reserva Aqui:', action, message);
  },
}).render();
</script>

Passo 2 — Criar session token (obrigatório antes de qualquer hold)

O SDK cria e gerencia o token automaticamente. Basta chamar antes de fazer o hold:

// O SDK verifica se o token ainda é válido; recria se estiver perto de expirar.
const { session_token, expires_at } = await chart.ensureValidSession();

Ou se quiser criar explicitamente na primeira vez:

const { session_token, expires_at } = await chart.createSessionToken();

O SDK salva o token internamente e o renova automaticamente antes de expirar. Você não precisa gerenciar o timer manualmente.


Passo 3 — Fazer hold por label

// label é o campo "label" do inventário (ex: "A-1", "Mesa 3")
// NÃO é o objectId do builder
const result = await chart.holdByLabel('A-1');
// result: { label: 'A-1', status: 'held', created: true }

Para liberar:

await chart.releaseByLabel('A-1');

Como obter o label correto? Chame getInventory() e use o campo label de cada item:

const inventory = await chart.getInventory();
// inventory.items[n].label  ← use este valor no holdByLabel()

Passo 4 — Receber o evento de hold criado

Quando o mapa interno cria um hold, o SDK dispara onHoldCreated:

const chart = new SeatingChart({
  // ...
  onHoldCreated(holdId, holdToken, expiresAt, seatIds, ticketTypes, objectKeys, items) {
    // holdId === holdToken === sessionToken (após o refactor de session-token)
    // Envie para o seu backend finalizar o pedido
    fetch('/api/orders', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionToken: holdId, seatIds, ticketTypes }),
    });
  },
}).render();

Fluxo completo em código

import { SeatingChart } from 'reservaqui';

const chart = new SeatingChart({
  divId:        'seat-map',
  baseUrl:      'https://tickets.myapp.com',
  workspaceKey: 'pub_abc123',
  event:        '42',

  onReady: () => console.log('Mapa pronto'),
  onError: (action, msg) => console.error(action, msg),
}).render();

// Quando o usuário clicar em "Reservar":
async function reservar(label) {
  try {
    await chart.ensureValidSession();          // 1. garante token válido
    const result = await chart.holdByLabel(label); // 2. faz o hold
    console.log('Hold criado:', result);
  } catch (err) {
    console.error('Falha no hold:', err.status, err.code, err.message);
  }
}

Headers enviados automaticamente

O SDK injeta esses headers em todas as requisições protegidas:

| Header | Valor | |---|---| | X-SeatHold-Event-Id | config.event (ID numérico) | | X-SeatHold-Public-Key | config.workspaceKey | | X-SeatHold-Session-Token | token criado em /api/session-tokens |

Todas as requisições também enviam credentials: 'include' para suportar cookies CSRF em ambientes browser.


Erros comuns e o que fazer

| Status | Código | Causa | Solução | |---|---|---|---| | 4xx | — | event não é numérico | Usar o ID numérico do evento, não slug | | 401/403 | invalid_or_expired_session_token | Token expirou | Chamar ensureValidSession() antes do hold — o SDK renova automaticamente | | 404 | — | Label errado no path | Confirmar que o label vem de getInventory(), não do objectId do builder | | 419 | — | Cookie CSRF ausente | SDK já envia credentials: 'include' desde a v0.1.17 | | 4xx | workspace_key_required | Header público ausente | Verificar que workspaceKey está correto |


Multiprice (opcional)

As categorias/setores são cadastrados e enviados exclusivamente pelo Reserva Aqui no evento seathold:ready. O app que incorpora o mapa fornece apenas os tipos de ingresso e seus preços em centavos. Configure o pricing depois que o mapa estiver pronto, usando o id recebido nas categorias:

const chart = new SeatingChart({
  divId: 'seat-map',
  baseUrl: 'https://tickets.myapp.com',
  workspaceKey: 'pub_abc123',
  event: '42',
  onReady(eventId, objectKeys, categories) {
    // categories vem do evento seathold:ready
    // [{ id: 'pista-1', label: 'Pista', key: 'pista' }]
    chart.setPricing([
      {
        categoryId: categories[0].id,
        ticketTypes: [
          { id: 'inteira', label: 'Inteira', price: 10000, currency: 'BRL', color: '#2563eb' },
          { id: 'meia', label: 'Meia', price: 5000, currency: 'BRL', color: '#16a34a' },
        ],
      },
    ]);
  },
}).render();

Também é possível passar pricing na configuração inicial. O SDK aguarda o seathold:ready, valida cada categoryId contra categories[].id e então envia automaticamente a configuração ao iframe.

Se setPricing() for chamado antes do iframe estar pronto, o SDK guarda a configuração e envia automaticamente após seathold:ready. As categorias também podem ser consultadas com chart.getCategories().

O preço é sempre enviado em centavos: 10000 = R$ 100,00 e 5000 = R$ 50,00. Um categoryId que não exista nas categorias recebidas é rejeitado com um erro claro via onError e exceção do método setPricing.


Captura de session token do iframe

Se o próprio embed criar a sessão internamente (modo padrão sem createSessionToken() explícito):

const chart = new SeatingChart({
  // ...
  onSessionCreated(sessionToken, expiresAt) {
    // primeira sessão criada pelo iframe
    salvarNoBackend(sessionToken, expiresAt);
  },
  onSessionUpdated(sessionToken, expiresAt) {
    // sessão renovada pelo iframe
    salvarNoBackend(sessionToken, expiresAt);
  },
}).render();

Referência rápida de métodos

chart.render()                         // monta o iframe
chart.destroy()                        // remove o iframe e listeners

await chart.createSessionToken()       // POST /api/session-tokens (primeira vez)
await chart.refreshSessionToken()      // força renovação imediata
await chart.ensureValidSession()       // reusa ou renova se próximo de expirar

await chart.getBuilder()               // GET /api/render-map/builder
await chart.getInventory()             // GET /api/render-map/inventory
await chart.holdByLabel('A-1')         // POST /api/inventory/A-1/hold
await chart.releaseByLabel('A-1')      // POST /api/inventory/A-1/release

chart.setSelectedSeats([1, 2, 3])      // seleciona assentos programaticamente
chart.holdCreated(token, expiresAt)    // notifica o iframe de um hold externo
chart.releaseHold()                    // libera o hold atual no iframe
chart.updateSession(token, expiresAt)  // atualiza token no iframe
chart.requestState()                   // solicita snapshot do estado atual
chart.setPricing(rules)                // atualiza pricing em tempo real (categoryId)

Referência de tipos

type SeatingChartConfig = {
  divId: string;
  baseUrl: string;
  workspaceKey: string;
  event: string;                        // ID numérico como string
  mode?: 'manager' | 'simplified';
  environment?: 'production' | 'sandbox';
  sessionToken?: string;
  sessionExpiresAt?: string | null;
  sessionRefreshBufferMs?: number;      // padrão: 30000ms
  pricing?: PricingRule[];
  height?: number | string;             // padrão: 600px
  width?: number | string;              // padrão: 100%
  onReady?: (eventId: string, objectKeys?: string[], categories?: SeatingCategory[]) => void;
  onSelectionChanged?: (seatIds, ticketTypes, objectKeys, items, pricingSelection) => void;
  onObjectClicked?: (objectId, objectType, objectKey?, categoryKey?) => void;
  onCategoryChanged?: (categoryKey: string | null) => void;
  onViewChanged?: (zoom: number, position: { x: number; y: number }) => void;
  onHoldCreated?: (holdId, holdToken, expiresAt, seatIds, ticketTypes, objectKeys, items) => void;
  onHoldReleased?: () => void;
  onState?: (state: SessionState) => void;
  onSessionCreated?: (sessionToken: string, expiresAt: string) => void;
  onSessionUpdated?: (sessionToken: string | null, expiresAt: number | null) => void;
  onError?: (action: string, message: string) => void;
};

type PricingRule = {
  categoryId: string | number; // deve existir em seathold:ready.categories[].id
  ticketTypes: TicketType[];
};

type SeatingCategory = {
  id: string | number;
  label: string;
  key?: string | null;
};

type TicketType = {
  id: string;
  label: string;
  color?: string | null;
  price: number;               // centavos: 10000 = R$ 100,00
  currency: string;
};

type InventoryStatusResponse = {
  label: string;
  status: 'available' | 'held' | string;
  created?: boolean;
};

type SessionTokenResponse = {
  session_token: string;
  expires_at: string;
};