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

lightstream-sdk

v1.13.1

Published

Official TypeScript/JavaScript SDK for LightStream Gateway (Unified TikTok, Kick, and Twitch Real-time Streaming, REST APIs, and Moderation)

Downloads

3,231

Readme

lightstream-sdk

SDK oficial para consumir el Gateway de LightStream con TikTok LIVE, Kick y Twitch.

En 1.10.2, GiftEvent.giftType admite el tipo numérico de TikTok y el nombre de tipo que publica Kick; no cambia el transporte ni la contabilidad.

En 1.10.1, la identidad de regalos se alinea con el journal del gateway: un eventId reutilizado conserva cambios de msgId, groupId, progreso, cierre y totalValue; una copia con otro cursor no se entrega dos veces. GiftEvent declara también los campos de racha y las imágenes del gateway. Para contar regalos del gateway unificado usa monetization.quantity y monetization.amount, no interpretes comboCount como monedas. Para una proyección incremental, agrupa por sesión, usuario, regalo y groupId solo cuando giftType === 1 && isCombo !== false, acredita diferencias respecto al máximo observado y no vuelvas a sumar el cierre. Sin grupo, espera el final. Los regalos individuales se identifican por mensaje. El SDK entrega payloads y marcas de replay; cada consumidor conserva su propia contabilidad.

En 1.8.0, stream.connect(platform, channel, options) conserva silent, events, enableEnrichedAnalytics, enableGiftRadar y enableBattleAnalytics en cada reconexión. La sesión y el dispositivo viajan en auth, nunca en query strings. stream.disconnect() envía la salida explícita de la plataforma antes de cerrar el transporte. Los cierres por red conservan el replay.

Contrato de autenticación

El Gateway separa dos autoridades:

  1. La API key identifica la integración de la aplicación. Debe permanecer en un servidor o BFF y solo se usa para crear una sesión.
  2. La sesión opaca identifica al usuario y al dispositivo. Se crea con deviceId y, opcionalmente, externalUserId. En navegador viaja como la cookie HttpOnly ls_gateway_session; en Node/Bun puede usarse el sessionToken opaco devuelto con transport: 'token'.

El SDK no acepta JWT de Supabase, tokens de proveedor, tokens efímeros, credenciales en query string ni cookies separadas por plataforma.

Instalación

npm install lightstream-sdk
# o
bun add lightstream-sdk

Sesión y tiempo real en Node/Bun

import { createLightStreamClient } from 'lightstream-sdk'

const client = createLightStreamClient({
  gatewayUrl: 'https://gateway.lightstream.lat',
  apiKey: process.env.LIGHTSTREAM_API_KEY, // solo servidor
  deviceId: 'bot-installation-01',
})

const session = await client.session.create({
  deviceId: 'bot-installation-01',
  externalUserId: 'creator-123',
  platform: 'kick',
  transport: 'token',
})

const authenticatedClient = createLightStreamClient({
  gatewayUrl: 'https://gateway.lightstream.lat',
  sessionToken: session.sessionToken,
  deviceId: 'bot-installation-01',
})

authenticatedClient.stream.on('chat', (message) => {
  console.log(message.text)
})
await authenticatedClient.stream.connect('kick', 'xqc')

Para una app web, crea la sesión desde una ruta server-side con la API key y deja que la respuesta establezca ls_gateway_session. El cliente del navegador se crea solo con gatewayUrl; Socket.IO envía la cookie con withCredentials.

OAuth de Kick y Twitch

await client.session.create({
  deviceId: 'web-installation-01',
  externalUserId: 'creator-123',
  platform: 'twitch',
  transport: 'cookie',
})

const { url } = await client.oauth.authorize('twitch', {
  returnTo: 'https://your-app.example/oauth/complete',
})

El Gateway conserva los tokens de Kick/Twitch y solo devuelve la URL, el estado y los metadatos de cuenta. Para desconectar una cuenta:

await client.oauth.disconnect('twitch')

REST

Los endpoints REST de la integración reciben x-api-key desde un entorno confiable. Las operaciones de sesión/OAuth además requieren la sesión opaca.

const live = await client.rest.bulkCheckStreams([
  { platform: 'tiktok', channel: 'charlidamelio' },
  { platform: 'kick', channel: 'xqc' },
  { platform: 'twitch', channel: 'shroud' },
])

await client.rest.moderation.mute('kick', 'xqc', 'spammer', 60)

getUserEarnings() conserva el nombre histórico, pero devuelve actividad y monetización en unidades nativas (nativeAmount, nativeQuantity, nativeUnit), sin estimaciones ni conversiones de moneda. Los rankings comparan únicamente scores nativos dentro de la misma plataforma. La moderación mute/ban filtra eventos entregados por el gateway; borrar mensajes o cambiar comentarios upstream no está disponible y responde HTTP 501.

getLiveStreams() devuelve un snapshot tipado de las conexiones creadas con la misma API key. Para mantener un BFF sincronizado sin polling:

for await (const snapshot of client.rest.watchLiveStreams({}, { signal })) {
  console.log(snapshot.total, snapshot.totalViewers, snapshot.streams)
}

El feed usa SSE y permanece aislado por integración. Consúmalo solo en servidor; un BFF puede proyectar el resultado al navegador sin exponer la API key.

Eventos y errores

El cliente emite connected únicamente cuando el upstream confirma que el canal está en directo. Cuando no lo está emite stream:offline con STREAMER_OFFLINE. También expone status, error, chat, gift, follow, like, subscribe, viewers y los eventos extendidos del Gateway.

Los errores de autenticación (AUTHENTICATION_FAILED) requieren crear o renovar la sesión del dispositivo. La API key no debe enviarse al cliente del navegador ni por URL.

Desarrollo local

bun install
bun run typecheck
bun test
bun run build

MIT (c) 2026 LightStream

Batallas y Power-Ups (1.13.1)

Todos viajan en el evento público battle, con BattleEvent, BattleCard, BoostCard, participantes, equipos, resultados y tareas tipados. Active enableBattleAnalytics: true al conectar; si filtra eventos incluya battle.

participants[].points es el total acumulado actual de cada participante, no un incremento que deba sumarse. roundKey es la identidad estable de la ronda que calcula el gateway; no use battleId por sí solo porque TikTok puede reutilizarlo o enviar "0" como valor protobuf por defecto. Para consumidores que mantienen su propio estado, el SDK incluye un reductor que conserva los totales, ignora actualizaciones de otra ronda y cierra la batalla una sola vez.

El reductor espera un start; un update o end aislado no crea un marcador. Desde 1.13.1 conserva la identidad de ronda, equipos, duración y cierre cancelado sin atribuir una victoria a una ronda reemplazada. La deduplicación distingue progresión de puntuación y cierre aunque compartan un identificador upstream. El consumidor que no desea restaurar rondas previas debe excluir los eventos _replay antes de llamar al reductor:

import { createBattleRoundState, reduceBattleRoundState } from 'lightstream-sdk'

let battle = createBattleRoundState()
client.stream.on('battle', event => {
  battle = reduceBattleRoundState(battle, event)
  if (battle.phase === 'active') {
    console.log(battle.participants.map(({ id, points }) => ({ id, points })))
  }
})

También están disponibles normalizeBattleId(), normalizeBattleScore() y createBattleRoundKey() para adaptadores propios. Un evento end conserva los últimos totales y sus resultados; no se debe inferir el final únicamente por la ausencia temporal de mensajes.

  • status: card: cardPhase === 'activated', card.action === 'use' y isEffectCard confirman que TikTok lanzó/activó la carta. Los tipos son critical-strike, smoke, extra-time, special-effect, potion, wave, top2, top3 y vault-glove. cardNameKey e imageUrl exponen el identificador visual y el asset HTTPS que acompaña la activación. obtain/award/notice/unknown no son activaciones.
  • status: boost: boostPhase === 'available' y boostCards contienen inventario (cardId/taskId/taskSource); no demuestran activación. El protocolo no incluye tipo de efecto ni battleId propio en este mensaje, aunque el gateway puede correlacionarlo con la batalla actualmente abierta.
  • status: task: started/updated describen la tarea y su ventana prevista (scheduledAt); solo type === 'active' después de TASK_SETTLE exitoso confirma x2/x3. activeAt/activeUntil delimitan la activación real.
  • status: update: triggerCriticalStrike, giftId, giftCount, diamonds y fromUserId permiten correlacionar puntuaciones sin disparar otra alerta de activación.
  • status: punish-end termina el castigo, no inicia ni termina otra ronda.
  • Epochs en milisegundos: timestamp, startedAt, sentAt y activeUntil. duration/extraDuration están en segundos. No inventar duración ni multiplicador si faltan.
  • No ejecutar efectos sobre _replay; descartar cartas expiradas y deduplicar por eventId dentro de la sesión. El SDK conserva la marca de replay y distingue progresión/cierre de regalos que reutilizan eventId.

stream:replay_start contiene afterCursor; stream:replay_complete contiene lastCursor y count, tal como los emite el gateway. La API key sigue exclusivamente en el servidor; Socket.IO requiere la sesión opaca.

El cliente REST incluye getLiveStreams, watchLiveStreams, bulkCheckStreams, getRoomStreamInfo, getUserEarnings, getGiftsCatalog, getLeaderboard, searchRankings y moderation; session y oauth mantienen sus clientes propios.

Regalos y animaciones (1.10.0)

El catálogo crece en el gateway: consultar rest.getGiftsCatalog() para el índice compacto, o rest.getCombinedGiftCatalog() / rest.getRegionalGiftCatalog('US') para conservar regiones. Los métodos regionales aceptan { signal }.

// Solo servidor/BFF, después de comprobar sesión y permisos del usuario.
import { LightStreamAnimationClient } from 'lightstream-sdk/server'
const animations = new LightStreamAnimationClient({
  gatewayUrl: process.env.GATEWAY_INTERNAL_URL!,
  internalToken: process.env.GATEWAY_AUTH_TOKEN!,
})
const catalog = await animations.getCatalog()
const asset = catalog.find(item => item.giftId === requestedGiftId)
if (!asset) throw new Error('Animation unavailable')
const response = await animations.playAsset(authorizedUserId, asset)
// El BFF transmite el cuerpo y X-Animation-Key / X-Animation-Id, con private,no-store.
// Nunca transmite GATEWAY_AUTH_TOKEN ni la clave maestra del gateway.
// Navegador: la respuesta procede del BFF de tu aplicación.
import { decryptGiftAnimation } from 'lightstream-sdk'
const blob = await decryptGiftAnimation(responseFromYourBff, { giftId, signal })
const url = URL.createObjectURL(blob)
// Usar en <video>; revocar url al terminar, reemplazar o desmontar el reproductor.

El subpath /server no está disponible en bundles de navegador. Tickets de 30 s, de un uso y ligados a usuario/recurso; no se reintenta su consumo automáticamente. La app debe validar entitlement y la presencia autorizada de overlays antes de reproducir. El cifrado protege el transporte de aplicación, no impide la captura por un consumidor autorizado. Solo el catálogo animado representa vídeos listos; no todos los regalos ni todos los originales 3D tienen un vídeo reproducible.

Version 1.10.3 preserves typed badge params (level/rank/months) and chat emote id/placeInComment. Render original CDN URLs without rewriting signatures. superFanJoin with kind: entrance means an existing fan entered the live; superFan with kind: upgrade means conversion. Do not label entrances as new fans.

Recuperación y normalización (1.13.0)

Mantén una instancia del cliente por conexión; el SDK reusa la sesión, deviceId, canal y streamSessionId al reconectar. Desde 1.12.1 los errores transitorios tienen un solo reintento automático por defecto. El presupuesto solo se restaura después de 60 segundos conectado (reconnectStabilityMs), evitando bucles de conexiones breves. Puedes ajustar maxReconnectAttempts o usar autoReconnect=false. No recrees la sesión ni llames connect por cada status=reconnecting. Auth inválida, canal inexistente/prohibido, rate limit fuera de drenado y offline confirmado no se reintentan. retryExhausted distingue un fallo transitorio cuyo presupuesto se agotó. standbyWhenOffline: true sigue siendo una excepción explícita para sondeo continuo en directos prolongados.

server:restarting abre una ventana de recuperación independiente de 120 segundos (plannedRestartTimeoutMs). Durante ella, los rechazos transitorios del contenedor entrante no consumen el presupuesto normal. El SDK conserva sesión, deviceId, streamSessionId y el cursor confirmado; al reconectar solicita resumeAfter y no avanza el checkpoint hasta completar el replay. client.stream.resumeAfter permite persistir el checkpoint fuera del proceso. La ventana no convierte errores de auth, offline o canal prohibido en reintentables y no promete conservar el socket físico.

Todo evento de datos cruza normalizeGatewayEvent: valida plataforma, tipo, fechas y cursor, rechaza crosstalk y produce el envelope público 1.0. Los gateways anteriores siguen siendo compatibles porque los campos ausentes se completan en el SDK; message se canoniza como chat y objetivos inválidos no se publican.

Errores iniciales y HTTP (1.13.0)

await client.stream.connect(...) rechaza si falla el handshake inicial de Socket.IO. El evento error se conserva para consumidores reactivos y los reintentos posteriores siguen la política configurada. Los clientes REST, sesión y OAuth comparten el mismo transporte, timeouts y normalización de errores. requestTimeoutMs configura el timeout HTTP común sin alterar el transporte realtime.

Redis conserva hasta 30 minutos / aproximadamente 100000 eventos por ámbito. Deduplicación del SDK acotada a 1000 identidades: efectos durables de negocio requieren idempotencia propia. El checkpoint en memoria no sobrevive a reiniciar el proceso; resumeAfter permite restaurar un cursor persistido por el integrador. No hay confirmación transaccional de efectos de handlers asíncronos. Este contrato recupera eventos confirmados en Redis, no eventos que la plataforma nunca entregó mientras su conexión upstream estuvo cerrada. Ningún orquestador transfiere sockets vivos entre procesos.