lightstream-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,86 @@
1
+ import type { LightStreamErrorCode } from './errors.js';
2
+ export type Platform = 'tiktok' | 'kick' | 'twitch';
3
+ export type Timeframe = 'hourly' | 'daily' | 'weekly' | 'all';
4
+ export type ConnectionStatus = 'idle' | 'connecting' | 'connected' | 'offline' | 'reconnecting' | 'disconnected' | 'error';
5
+ export interface ClientOptions {
6
+ baseUrl?: string;
7
+ wsUrl?: string;
8
+ apiKey?: string;
9
+ token?: string;
10
+ jwtKey?: string;
11
+ autoReconnect?: boolean;
12
+ maxReconnectAttempts?: number;
13
+ reconnectIntervalMs?: number;
14
+ pingIntervalMs?: number;
15
+ }
16
+ export interface StreamConnectedEvent {
17
+ channel: string;
18
+ platform: Platform;
19
+ isLive: true;
20
+ title?: string;
21
+ viewerCount?: number;
22
+ startedAt?: string;
23
+ }
24
+ export interface StreamOfflineEvent {
25
+ channel: string;
26
+ platform: Platform;
27
+ isLive: false;
28
+ code: LightStreamErrorCode;
29
+ message: string;
30
+ }
31
+ export interface ConnectionErrorEvent {
32
+ channel?: string;
33
+ platform?: Platform;
34
+ code: LightStreamErrorCode;
35
+ message: string;
36
+ details?: unknown;
37
+ retryable: boolean;
38
+ }
39
+ export interface ChatMessageEvent {
40
+ id: string;
41
+ platform: Platform;
42
+ channel: string;
43
+ user: {
44
+ id: string;
45
+ username: string;
46
+ displayName: string;
47
+ avatar?: string;
48
+ badges?: string[];
49
+ };
50
+ content: string;
51
+ timestamp: number;
52
+ emotes?: Array<{
53
+ name: string;
54
+ url: string;
55
+ }>;
56
+ }
57
+ export interface GiftEvent {
58
+ id: string;
59
+ platform: Platform;
60
+ channel: string;
61
+ user: {
62
+ id: string;
63
+ username: string;
64
+ displayName: string;
65
+ avatar?: string;
66
+ };
67
+ gift: {
68
+ id: string;
69
+ name: string;
70
+ value: number;
71
+ repeatCount: number;
72
+ repeatEnd: boolean;
73
+ iconUrl?: string;
74
+ };
75
+ timestamp: number;
76
+ }
77
+ export interface LightStreamEventMap {
78
+ status: (status: ConnectionStatus) => void;
79
+ connected: (event: StreamConnectedEvent) => void;
80
+ 'stream:offline': (event: StreamOfflineEvent) => void;
81
+ error: (event: ConnectionErrorEvent) => void;
82
+ disconnected: (reason: string) => void;
83
+ chat: (event: ChatMessageEvent) => void;
84
+ gift: (event: GiftEvent) => void;
85
+ raw: (data: unknown) => void;
86
+ }
@@ -0,0 +1,15 @@
1
+ // examples/basic-connection.ts
2
+ import { createLightStreamClient } from '../src/index.js'
3
+
4
+ const client = createLightStreamClient({
5
+ baseUrl: 'https://gateway.lightstream.lat',
6
+ wsUrl: 'wss://gateway.lightstream.lat/ws',
7
+ apiKey: process.env.LIGHTSTREAM_API_KEY || 'YOUR_API_KEY',
8
+ })
9
+
10
+ client.stream.on('status', (s) => console.log('Status:', s))
11
+ client.stream.on('connected', (e) => console.log('Stream live:', e))
12
+ client.stream.on('stream:offline', (e) => console.log('Stream offline:', e))
13
+ client.stream.on('chat', (c) => console.log(`[${c.platform}] ${c.user.displayName}: ${c.content}`))
14
+
15
+ await client.stream.connect('twitch', 'shroud')
@@ -0,0 +1,27 @@
1
+ // examples/bot-moderation.ts
2
+ import { createLightStreamClient, type ChatMessageEvent } from '../src/index.js'
3
+
4
+ const BANNED_WORDS = ['spamword1', 'badlink.com', 'scam']
5
+
6
+ const client = createLightStreamClient({
7
+ baseUrl: 'https://gateway.lightstream.lat',
8
+ wsUrl: 'wss://gateway.lightstream.lat/ws',
9
+ apiKey: process.env.LIGHTSTREAM_API_KEY!,
10
+ })
11
+
12
+ client.stream.on('chat', async (msg: ChatMessageEvent) => {
13
+ const hasBadWord = BANNED_WORDS.some((word) => msg.content.toLowerCase().includes(word))
14
+
15
+ if (hasBadWord) {
16
+ console.log(`🚨 Moderating message from ${msg.user.username} in ${msg.channel}...`)
17
+
18
+ // 1. Delete message
19
+ await client.rest.moderation.deleteMessage(msg.platform, msg.channel, msg.id)
20
+
21
+ // 2. Mute user for 5 minutes
22
+ await client.rest.moderation.mute(msg.platform, msg.channel, msg.user.username, 300)
23
+ console.log(`User ${msg.user.username} muted for 300s.`)
24
+ }
25
+ })
26
+
27
+ await client.stream.connect('kick', 'my_kick_channel')
@@ -0,0 +1,19 @@
1
+ // examples/earnings-tracker.ts
2
+ import { createLightStreamClient } from '../src/index.js'
3
+
4
+ const client = createLightStreamClient({
5
+ baseUrl: 'https://gateway.lightstream.lat',
6
+ apiKey: process.env.LIGHTSTREAM_API_KEY!,
7
+ })
8
+
9
+ async function checkEarnings(platform: 'tiktok' | 'kick' | 'twitch', channel: string) {
10
+ const [streamInfo, earnings] = await Promise.all([
11
+ client.rest.getRoomStreamInfo(platform, channel),
12
+ client.rest.getUserEarnings(platform, channel),
13
+ ])
14
+
15
+ console.log('Stream Info:', streamInfo)
16
+ console.log('Earnings & Diamonds:', earnings)
17
+ }
18
+
19
+ await checkEarnings('tiktok', 'jesusquesillo')
@@ -0,0 +1,48 @@
1
+ // examples/react-use-stream.tsx
2
+ import { useEffect, useState, useRef } from 'react'
3
+ import {
4
+ createLightStreamClient,
5
+ type ConnectionStatus,
6
+ type ChatMessageEvent,
7
+ type GiftEvent,
8
+ type Platform
9
+ } from '../src/index.js'
10
+
11
+ export interface UseLightStreamOptions {
12
+ platform: Platform
13
+ channel: string
14
+ token?: string
15
+ apiKey?: string
16
+ }
17
+
18
+ export function useLightStream({ platform, channel, token, apiKey }: UseLightStreamOptions) {
19
+ const [status, setStatus] = useState<ConnectionStatus>('idle')
20
+ const [isLive, setIsLive] = useState<boolean>(false)
21
+ const [chatMessages, setChatMessages] = useState<ChatMessageEvent[]>([])
22
+ const [gifts, setGifts] = useState<GiftEvent[]>([])
23
+ const clientRef = useRef<any>(null)
24
+
25
+ useEffect(() => {
26
+ const client = createLightStreamClient({
27
+ baseUrl: 'https://gateway.lightstream.lat',
28
+ wsUrl: 'wss://gateway.lightstream.lat/ws',
29
+ token,
30
+ apiKey,
31
+ })
32
+ clientRef.current = client
33
+
34
+ client.stream.on('status', setStatus)
35
+ client.stream.on('connected', () => setIsLive(true))
36
+ client.stream.on('stream:offline', () => setIsLive(false))
37
+ client.stream.on('chat', (msg) => setChatMessages((prev) => [...prev.slice(-99), msg]))
38
+ client.stream.on('gift', (gift) => setGifts((prev) => [...prev.slice(-49), gift]))
39
+
40
+ client.stream.connect(platform, channel)
41
+
42
+ return () => {
43
+ client.stream.disconnect()
44
+ }
45
+ }, [platform, channel, token, apiKey])
46
+
47
+ return { status, isLive, chatMessages, gifts, client: clientRef.current }
48
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "lightstream-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Official TypeScript/JavaScript SDK for LightStream Gateway (Unified TikTok, Kick, and Twitch Real-time Streaming, REST APIs, and Moderation)",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src",
18
+ "README.md",
19
+ "QUICKSTART.md",
20
+ "LICENSE",
21
+ "examples"
22
+ ],
23
+ "scripts": {
24
+ "build": "bun build ./src/index.ts --outfile ./dist/index.mjs --target node --external ws && bun build ./src/index.ts --outfile ./dist/index.cjs --target node --format cjs --external ws && bun x tsc --emitDeclarationOnly",
25
+ "typecheck": "tsc --noEmit",
26
+ "test": "bun test"
27
+ },
28
+ "dependencies": {
29
+ "ws": "^8.18.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "@types/ws": "^8.5.10",
34
+ "typescript": "^5.0.0"
35
+ },
36
+ "keywords": [
37
+ "lightstream",
38
+ "tiktok",
39
+ "kick",
40
+ "twitch",
41
+ "livestream",
42
+ "realtime",
43
+ "websocket",
44
+ "moderation",
45
+ "sdk"
46
+ ],
47
+ "author": "LightStream Team",
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/LightCode-ctrl/gateway-lightstream.git"
52
+ }
53
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,49 @@
1
+ // src/errors.ts
2
+ // Canonical LightStream Domain Error Taxonomy and helpers.
3
+
4
+ export const LIGHTSTREAM_ERROR_CODES = {
5
+ STREAMER_OFFLINE: 'STREAMER_OFFLINE',
6
+ ROOM_NOT_FOUND: 'ROOM_NOT_FOUND',
7
+ AUTHENTICATION_FAILED: 'AUTHENTICATION_FAILED',
8
+ FORBIDDEN_CHANNEL: 'FORBIDDEN_CHANNEL',
9
+ RATE_LIMITED: 'RATE_LIMITED',
10
+ UPSTREAM_ERROR: 'UPSTREAM_ERROR',
11
+ TIMEOUT: 'TIMEOUT',
12
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
13
+ } as const
14
+
15
+ export type LightStreamErrorCode = keyof typeof LIGHTSTREAM_ERROR_CODES
16
+
17
+ export interface LightStreamErrorPayload {
18
+ code: LightStreamErrorCode
19
+ message: string
20
+ details?: unknown
21
+ retryable?: boolean
22
+ timestamp: string
23
+ }
24
+
25
+ export class LightStreamError extends Error {
26
+ readonly code: LightStreamErrorCode
27
+ readonly details?: unknown
28
+ readonly retryable: boolean
29
+ readonly timestamp: string
30
+
31
+ constructor(payload: LightStreamErrorPayload) {
32
+ super(payload.message)
33
+ this.name = 'LightStreamError'
34
+ this.code = payload.code
35
+ this.details = payload.details
36
+ this.retryable = payload.retryable ?? false
37
+ this.timestamp = payload.timestamp
38
+ }
39
+
40
+ toJSON(): LightStreamErrorPayload {
41
+ return {
42
+ code: this.code,
43
+ message: this.message,
44
+ details: this.details,
45
+ retryable: this.retryable,
46
+ timestamp: this.timestamp,
47
+ }
48
+ }
49
+ }
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ // src/index.ts
2
+ // Entrypoint for the official LightStream TypeScript SDK.
3
+
4
+ import { LightStreamRestClient } from './rest-client.js'
5
+ import { LightStreamStreamClient } from './stream-client.js'
6
+ import type { ClientOptions } from './types.js'
7
+
8
+ export class LightStreamClient {
9
+ readonly rest: LightStreamRestClient
10
+ readonly stream: LightStreamStreamClient
11
+
12
+ constructor(options: ClientOptions) {
13
+ this.rest = new LightStreamRestClient({
14
+ baseUrl: options.baseUrl || 'https://gateway.lightstream.lat',
15
+ apiKey: options.apiKey,
16
+ jwtKey: options.jwtKey || options.token,
17
+ })
18
+ this.stream = new LightStreamStreamClient(options)
19
+ }
20
+ }
21
+
22
+ export function createLightStreamClient(options: ClientOptions): LightStreamClient {
23
+ return new LightStreamClient(options)
24
+ }
25
+
26
+ export * from './types.js'
27
+ export * from './errors.js'
28
+ export * from './rest-client.js'
29
+ export * from './stream-client.js'
@@ -0,0 +1,143 @@
1
+ // src/rest-client.ts
2
+ // Typed HTTP REST client for LightStream Gateway Developer APIs.
3
+
4
+ import type { Platform, Timeframe } from './types.js'
5
+
6
+ export interface RestClientConfig {
7
+ baseUrl: string
8
+ apiKey?: string
9
+ jwtKey?: string
10
+ }
11
+
12
+ export class LightStreamRestClient {
13
+ constructor(private config: RestClientConfig) {}
14
+
15
+ private async fetchJson<T>(path: string, options: RequestInit = {}): Promise<T> {
16
+ const url = `${this.config.baseUrl.replace(/\/+$/, '')}${path}`
17
+ const headers = new Headers(options.headers || {})
18
+ if (!headers.has('Accept')) headers.set('Accept', 'application/json')
19
+ if (this.config.apiKey) {
20
+ headers.set('x-api-key', this.config.apiKey)
21
+ } else if (this.config.jwtKey) {
22
+ headers.set('Authorization', `Bearer ${this.config.jwtKey}`)
23
+ }
24
+
25
+ const res = await fetch(url, { ...options, headers })
26
+ const data = await res.json()
27
+ if (!res.ok) {
28
+ throw new Error((data as { error?: string })?.error || `HTTP ${res.status}`)
29
+ }
30
+ return data as T
31
+ }
32
+
33
+ // Auth & Scoped Ephemeral Tokens
34
+ async issueToken(options: {
35
+ expireAfterSeconds?: number
36
+ allowedPlatforms?: Platform[]
37
+ allowedChannels?: string[]
38
+ maxWebSockets?: number
39
+ }): Promise<{ token: string; expiresAt: number; maxWebSockets: number }> {
40
+ const res = await this.fetchJson<{ ok: boolean; data: { token: string; expiresAt: number; maxWebSockets: number } }>(
41
+ '/api/v1/auth/tokens',
42
+ {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': 'application/json' },
45
+ body: JSON.stringify(options),
46
+ },
47
+ )
48
+ return res.data
49
+ }
50
+
51
+ // Streams & Rooms
52
+ async bulkCheckStreams(requests: Array<{ platform: Platform; channel: string }>): Promise<Array<{
53
+ platform: Platform
54
+ channel: string
55
+ isLive: boolean
56
+ viewerCount: number
57
+ title?: string
58
+ cached?: boolean
59
+ }>> {
60
+ const res = await this.fetchJson<{ ok: boolean; results: any[] }>('/api/v1/streams/bulk-check', {
61
+ method: 'POST',
62
+ headers: { 'Content-Type': 'application/json' },
63
+ body: JSON.stringify({ requests }),
64
+ })
65
+ return res.results
66
+ }
67
+
68
+ async getRoomStreamInfo(platform: Platform, channel: string): Promise<Record<string, unknown>> {
69
+ const res = await this.fetchJson<{ ok: boolean; data: Record<string, unknown> }>(
70
+ `/api/v1/rooms/${encodeURIComponent(platform)}/${encodeURIComponent(channel)}/stream-info`,
71
+ )
72
+ return res.data
73
+ }
74
+
75
+ async getUserEarnings(platform: Platform, channel: string): Promise<Record<string, unknown>> {
76
+ const res = await this.fetchJson<{ ok: boolean; data: Record<string, unknown> }>(
77
+ `/api/v1/users/${encodeURIComponent(platform)}/${encodeURIComponent(channel)}/earnings`,
78
+ )
79
+ return res.data
80
+ }
81
+
82
+ async getGiftsCatalog(platform: Platform = 'tiktok'): Promise<any[]> {
83
+ const res = await this.fetchJson<{ ok: boolean; data: any[] }>(
84
+ `/api/v1/gifts/catalog?platform=${encodeURIComponent(platform)}`,
85
+ )
86
+ return res.data
87
+ }
88
+
89
+ // Rankings
90
+ async getLeaderboard(platform: Platform, timeframe: Timeframe = 'daily', limit = 50): Promise<any[]> {
91
+ const res = await this.fetchJson<{ ok: boolean; entries: any[] }>(
92
+ `/api/v1/rankings/leaderboard?platform=${encodeURIComponent(platform)}&timeframe=${encodeURIComponent(timeframe)}&limit=${limit}`,
93
+ )
94
+ return res.entries
95
+ }
96
+
97
+ async searchRankings(channel: string, platform: Platform = 'tiktok', timeframe: Timeframe = 'daily'): Promise<any> {
98
+ const res = await this.fetchJson<{ ok: boolean; data: any }>(
99
+ `/api/v1/rankings/search?channel=${encodeURIComponent(channel)}&platform=${encodeURIComponent(platform)}&timeframe=${encodeURIComponent(timeframe)}`,
100
+ )
101
+ return res.data
102
+ }
103
+
104
+ // Moderation
105
+ readonly moderation = {
106
+ mute: (platform: Platform, channel: string, targetUser: string, duration?: number) =>
107
+ this.fetchJson('/api/v1/moderation/mutes', {
108
+ method: 'POST',
109
+ headers: { 'Content-Type': 'application/json' },
110
+ body: JSON.stringify({ platform, channel, targetUser, duration }),
111
+ }),
112
+ unmute: (platform: Platform, channel: string, targetUser: string) =>
113
+ this.fetchJson('/api/v1/moderation/mutes', {
114
+ method: 'DELETE',
115
+ headers: { 'Content-Type': 'application/json' },
116
+ body: JSON.stringify({ platform, channel, targetUser }),
117
+ }),
118
+ ban: (platform: Platform, channel: string, targetUser: string, reason?: string) =>
119
+ this.fetchJson('/api/v1/moderation/bans', {
120
+ method: 'POST',
121
+ headers: { 'Content-Type': 'application/json' },
122
+ body: JSON.stringify({ platform, channel, targetUser, reason }),
123
+ }),
124
+ unban: (platform: Platform, channel: string, targetUser: string) =>
125
+ this.fetchJson('/api/v1/moderation/bans', {
126
+ method: 'DELETE',
127
+ headers: { 'Content-Type': 'application/json' },
128
+ body: JSON.stringify({ platform, channel, targetUser }),
129
+ }),
130
+ deleteMessage: (platform: Platform, channel: string, messageId: string) =>
131
+ this.fetchJson('/api/v1/moderation/messages', {
132
+ method: 'DELETE',
133
+ headers: { 'Content-Type': 'application/json' },
134
+ body: JSON.stringify({ platform, channel, messageId }),
135
+ }),
136
+ toggleComments: (platform: Platform, channel: string, enabled: boolean) =>
137
+ this.fetchJson('/api/v1/moderation/comments/toggle', {
138
+ method: 'POST',
139
+ headers: { 'Content-Type': 'application/json' },
140
+ body: JSON.stringify({ platform, channel, enabled }),
141
+ }),
142
+ }
143
+ }