@central-ticket/queue-sdk
v0.6.0
Published
SDK de cola virtual de CentralQ. Por defecto monta la UI de CentralQ (`<central-q>`) con la experiencia publicada en el dashboard, sin bloquear la admisión ni el polling.
Maintainers
Readme
CentralQ Queue SDK
SDK de cola virtual de CentralQ. Por defecto monta la UI de CentralQ
(<central-q>) con la experiencia publicada en el dashboard, sin bloquear la
admisión ni el polling.
UI de CentralQ o UI propia
La UI incluida se monta siempre, salvo que pases headless: true. La misma
opción existe en CentralQ.init, createQueue, las sesiones por recurso
(useQueue) y el runtime por URL (createCentralQRouteRuntime,
useCentralQRuntime de React y Vue, y createCentralQRuntime de Svelte).
import { createCentralQRouteRuntime } from "@central-ticket/queue-sdk";
// UI de CentralQ (default)
const runtime = createCentralQRouteRuntime({ publishableKey: "ctq_pub_..." });
// UI propia: el SDK gestiona la cola y vos dibujás la espera
const headlessRuntime = createCentralQRouteRuntime({
publishableKey: "ctq_pub_...",
headless: true,
});
headlessRuntime.subscribe((state) => {
if (state.shouldBlockNavigation) renderWaiting(state);
});headless sólo cambia quién dibuja la espera: la admisión, la sesión y la
verificación en el backend son las mismas.
Preview del builder
El subpath @central-ticket/queue-sdk/preview reutiliza exactamente la misma
UI y los mismos estilos del runtime, pero no crea sesiones, códigos QR,
listeners remotos ni solicitudes de engagement.
import {
mountCentralQPreview,
type CentralQPreviewModel,
} from "@central-ticket/queue-sdk/preview";
const model: CentralQPreviewModel = {
config: {
schemaVersion: 1,
layout: "EDITORIAL",
title: "Tu lugar está reservado",
palette: {
primary: "#173D35",
background: "#F8F5EF",
text: "#17231F",
accent: "#E6B98A",
},
},
scenario: "WAITING",
viewport: "desktop",
};
const preview = mountCentralQPreview(container, model);
preview.update({ ...model, scenario: "QUEUE_PAUSED" });
preview.destroy();Escenarios disponibles: LOADING, WAITING, QUEUE_PAUSED, PREOPENING,
EXPIRED y ERROR. Los fixtures son deterministas: posición 128, 127 personas
delante y ETA de 12–18 minutos.
Extensiones de experiencia
WaitingRoomExtensions admite snake y promoBanner. El banner conserva
schemaVersion: 1, exige URLs HTTPS en payloads públicos y abre enlaces con
noopener noreferrer. Los SDK anteriores ignoran esta extensión.
Secuencia de entrega
Publicar primero el SDK, fijar el dashboard a esa versión exacta y desplegar
después el worker/dashboard compatible. Para esta entrega la secuencia es
@central-ticket/[email protected] → dashboard 0.3.0 → worker/dashboard.
Admisión agnóstica y acceso conservado (0.5.0)
CentralQ administra presencia, admisión, posición, permisos y vencimientos. La aplicación conserva sus pantallas, formularios, operaciones y reglas para iniciar o suspender la admisión. El SDK no recibe estados de modal, checkout, carrito u orden. La confirmación autoritativa del consumo sigue siendo server-side.
Svelte
useQueue conserva su nombre y sus stores (status, token, position, ahead,
expiresAt, waitingCount, pauseReason, error) y agrega configured e
isProtected. A partir de 0.5 requiere Svelte 5. Un getter se observa mediante
toStore, por lo que los cambios en evento, cliente, identidad y enabled se
aplican durante la vida del componente. También admite un Readable de opciones
o un objeto estático. Se destruye la sesión anterior al cambiar de recurso y se
cancelan sus suscripciones al desmontar.
<script lang="ts">
import { useQueue } from '@central-ticket/queue-sdk/svelte';
import { get } from 'svelte/store';
let { client, resourceId, enabled = true } = $props();
const queue = useQueue(() => ({
client,
eventId: resourceId,
enabled,
headless: true
}));
const { status, token, position, error } = queue;
async function runProtectedAction() {
if (!(await queue.refresh())) return;
const result = await callApplicationBackend(get(token));
if (result.accessConsumed) queue.consume();
}
</script>El consumidor decide qué significa enabled: puede depender de la ruta, una
operación en curso o cualquier condición propia. headless: true conserva la UI de la
aplicación; al omitirlo, CentralQ monta su UI con la experiencia publicada.
| Operación | Efecto |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| enabled: false | Detiene solicitudes y polling sin consumir, liberar ni extender un permiso válido. Su vencimiento local permanece activo. |
| enabled: true / resume() | Reanuda admisión y vuelve a comprobar el acceso; conserva un permiso todavía válido. |
| refresh() | Revalida protección y acceso; devuelve false si la admisión está deshabilitada. |
| consume() | Invalida el acceso local y avisa a las otras instancias del mismo scope. Detiene admisión hasta una reanudación explícita. No confirma consumo por HTTP. |
| restart() / reset() | Abandona el intento anterior y solicita una nueva admisión normal. |
| leave() | Abandona explícitamente el intento y libera una reserva preservada no utilizada. |
Motor independiente del framework
Para JavaScript u otros adaptadores, client.createQueueSession({ eventId })
expone start, subscribe, refresh, setEnabled, consume, restart, leave
y destroy. client.notifyAccessConsumed(eventId) notifica consumo local desde
otra parte de la aplicación. Estas operaciones no conocen el negocio del
consumidor ni ejecutan su acción protegida.
Sin userId, el worker determina la identidad de navegador y el SDK registra
presencia también mientras la protección está inactiva. Al activarse, obtiene un
init mediante CAPTCHA/PoW cuando corresponde y reclama el acceso preservado.
Con userId explícito, conserva esa identidad y la admisión ordinaria sin crear
reservas para una identidad de navegador distinta. queueInitToken junto con
autoInitToken: false permite credenciales emitidas por el backend integrador.
El polling de presencia usa intervalos de 9–11 segundos sin solapar solicitudes.
La vuelta a la pestaña y refresh() revalidan el estado. El worker siempre emite
los permisos y controla su vencimiento; el SDK no concede ni extiende acceso.
Las credenciales y notificaciones se aíslan por URL, publishable key y recurso.
Una aplicación puede proporcionar presenceToken para adoptar una credencial
anterior: la lectura de sus claves de almacenamiento pertenece a su adaptador.
Bypass y recuperación
bypassCode se canjea después de la admisión habitual y adopta el token y
vencimiento devueltos por el worker. No reclama un segundo turno ordinario. La
limpieza de la reserva y el init no utilizados se reintenta sin volver a gastar
el código. El permiso confirmado se guarda best-effort en sessionStorage,
asociado al scope y a la identidad, para recuperarlo tras desmontar y volver a
montar. Consumo, reinicio y vencimiento lo invalidan. Pausar y reanudar conserva
el mismo permiso y su fecha de vencimiento.
Una respuesta perdida del canje después de gastar el código en el servidor sigue
requiriendo idempotencia del endpoint. Un token local tampoco reemplaza la
verificación server-side. El cliente HTTP avanzado conserva registerPresence,
claimPreservedSession y releasePreservedSession, con QueueRequestError y
status/reason tipados.
Migración desde 0.4
0.5 retira createEventSession, preparePurchase, setCheckoutOpen,
markOrderCreated y notifyOrderCreated. Los consumidores Svelte conservan
useQueue; las decisiones de negocio quedan en la aplicación. Los consumidores
avanzados usan el motor genérico y las operaciones anteriores. Las versiones
0.4 publicadas siguen disponibles para aplicaciones todavía no migradas.
Las dependencias de producción deben apuntar a la versión publicada en npm, sin tarballs ni overrides locales. La adopción de datos de una integración previa se implementa en esa aplicación, no en el SDK.
Tiempo de espera
WAITING conserva los campos de ETA y agrega estimateSource?: "CONFIGURED" | "OBSERVED" | null.
La UI de CentralQ usa Tiempo de espera estimado, respeta etaLabel personalizado y muestra
rangos redondeados a minutos (o En breve para rangos de hasta 30 segundos).
Cuando el worker no dispone de una referencia fiable, muestra Te avisaremos acá cuando sea tu turno.
La UI se actualiza con el polling del worker y entre pestañas, sin un contador local independiente.
Las estimaciones iniciales de configuración tienen confianza LOW; no representan flujo observado.
Validación de navegador de la espera, sus actualizaciones y el ingreso:
pnpm test:visual e2e/eta.spec.tsURL rules and application resource IDs
Supply urls alongside eventId to resolve dashboard URL rules before admission
or access verification. The application supplies equivalent URLs; the SDK does
not infer routes or business identifiers. Matching URL rules take precedence;
with no match, protection by eventId remains available. Conflicting waiting
rooms, unavailable lookups and malformed responses fail closed. Inactive rooms
still resolve so browser presence can be preserved.
Browser: useQueue(() => ({ client, eventId: resourceId, urls, enabled })) keeps
the same lifecycle. Its eventId store contains the resolved queue alias; use
that alias for local consumption outside the mounted session.
Backend: acquireAccess({ eventId: resourceId, urls: trustedUrls, token }) resolves
with the secret key and returns a lease scoped to the queue alias. Construct URLs
from trusted application configuration and database metadata, never request
headers, a browser-provided queue alias or decoded unverified token claims. Keep
resource authorization and transaction checks tied to the business resource ID.
Liberación por falta de conexión (0.5.2)
La sala configura activeInactivitySeconds desde el dashboard: 0 conserva el
acceso hasta su vencimiento; 30..86400 habilita un plazo sin heartbeat. El SDK
continúa comunicándose al cambiar de pestaña o minimizar; no envía leave al
ocultarse o desmontarse. Cerrar o perder red interrumpe la comunicación y el
worker recupera el lugar al cumplirse el plazo y ejecutar su ciclo de limpieza.
useQueue y createQueueSession revalidan el permiso activo al sincronizar,
incluidos los accesos por código. La revalidación no extiende expiresAt. Si el
worker ya lo revocó, el SDK descarta el token y obtiene admisión nueva mediante
el flujo normal. Un fallo temporal de red no se interpreta como una revocación
autoritativa. Los adaptadores basados en CentralQ también procesan el rechazo
terminal del heartbeat. Las operaciones ya autorizadas conservan su lease.
Actualizar consumidores a 0.5.2 antes de habilitar esta política en salas que utilicen códigos bypass. No cambia el contrato HTTP del backend de operaciones.
Inicio de admisión (0.5.3)
La sesión reutiliza challengeRequired de presencia y la respuesta del primer
join. Con un worker compatible, la admisión pública inicial hace cuatro
solicitudes: presencia, init, claim y join. El controlador adopta el resultado
sin repetir join ni abrir un stream de comunicación para un acceso ya admitido.
Las posiciones en espera o pausa conservan polling, promoción y comunicación;
los accesos admitidos conservan heartbeat y vencimiento.
CAPTCHA/PoW y recuperación de turnos preservados siguen vigentes. Si un worker
anterior omite challengeRequired, se conserva la consulta de protección.
La respuesta de admisión sólo se reutiliza al arrancar el controlador: una
pestaña seguidora que toma liderazgo más tarde obtiene estado actualizado.
Núcleo server independiente del framework
Importá la entrada @central-ticket/queue-sdk/server en Hono, Express, SvelteKit
u otro backend. No requiere Nest, DOM ni navegador.
import { createCentralQServer } from "@central-ticket/queue-sdk/server";
const centralq = createCentralQServer({ secretKey }); // reutilizar por servidor
const lease = await centralq.acquireAccess({ eventId: trustedResourceId, token });
try {
// La aplicación confirma su operación durable e idempotente.
const operation = await commitApplicationOperation();
const completion = await lease.complete(operation.id);
// Inspeccionar completion; si falla, conservar el recibo y reconciliar.
} finally {
lease.dispose(); // sólo detiene renovación; no completa ni libera
}validateQueueTokenServer sólo comprueba vigencia del turno. acquireAccess
adquiere el lease de procesamiento y renueva mientras se ejecuta la operación;
complete(stableId) consume el turno. release() sólo libera ese lease después
de rollback/no operación confirmado, sin abandonar ni consumir el turno.
retryCompletion({ eventId, token, leaseId, completionId }) y
retryRelease({ eventId, token, leaseId }) recuperan credenciales persistidas sin
adquirir otro lease. Los retries en memoria no son un outbox ni una transacción
entre CentralQ y la DB comercial. Ante resultado incierto, no liberar ni repetir
la operación: reconciliar mediante su ID durable. Mantener las credenciales fuera
del navegador y los logs. Los recursos no protegidos tienen finalización no-op.
La adquisición rechaza con CentralQAccessError: kind distingue denegación,
red, timeout, configuración, protocolo y fallo upstream; upstreamStatus conserva
el código HTTP cuando la respuesta de acceso está disponible. No existe fail-open
implícito. onEvent permite observabilidad opcional sin credenciales; no hay logger
por defecto. El adaptador Nest delega en este mismo núcleo y conserva su traducción
HTTP. Ver el ejemplo Hono.
Ejemplos completos por framework
Next.js,
Nuxt/Vue,
SvelteKit y
Hono distinguen validación simple y operación
simulada con adquisición/finalización. Las interfaces browser gestionan el init y
la espera mediante useQueue; después del consumo confirmado notifican el acceso
consumido y detienen el flujo, sin utilizar leave como completion.
Son demos sin DB comercial: una respuesta incierta bloquea el reenvío en la página, pero no reemplaza idempotencia ni recibos durables. La app real conserva scope, payload y un ID comercial estable, confirma su transacción y reconcilia los resultados de completion. Las credenciales de recuperación quedan server-side.
Ambientes y hosts fijos
Todas las entradas browser/server y los adaptadores apuntan a uno de dos hosts
fijos, elegido con environment ("production" por defecto):
production:https://queue.centralticket.com.arsandbox:https://sandbox-queue.centralticket.com.ar
No hay opción apiUrl, baseUrl, variable de entorno ni excepción de
desarrollo para cambiar ese destino. Un valor inválido de environment lanza un
TypeError antes de la red. Las URLs de recursos, redirects y orígenes
autorizados siguen siendo datos de la integración.
CENTRALQ_API_URLS expone el mapa fijo, resolveCentralQApiUrl(environment)
resuelve el host y CENTRALQ_DEFAULT_API_URL conserva su nombre histórico como
alias de producción.
Migración del cliente HTTP avanzado:
new QueueHttpClient(apiKey?, initToken?, environment?). Se retira el argumento
antiguo de URL; API key e init token conservan sus headers. withInitToken(token)
mantiene la API key y el ambiente. La propiedad apiUrl es de sólo lectura para
identificar el scope de cache/presencia; no configura transporte.
