@bluebottle_gg/league-broadcast-client 0.1.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.
- package/LICENSE +674 -0
- package/README.md +530 -0
- package/dist/index.d.ts +1052 -0
- package/dist/index.js +670 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# League Broadcast Client
|
|
2
|
+
|
|
3
|
+
A TypeScript/JavaScript client library for connecting to the League Broadcast backend and receiving real-time game data.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔌 Automatic WebSocket connection management with reconnection
|
|
8
|
+
- 📊 Real-time game state and data updates
|
|
9
|
+
- 🎮 Event-driven architecture for game events (kills, objectives, etc.)
|
|
10
|
+
- ⚡ **Framework-agnostic reactive store** with fine-grained subscriptions
|
|
11
|
+
- 🔄 Works seamlessly with React, Vue, Svelte, Solid, Angular, and vanilla JS
|
|
12
|
+
- 📦 TypeScript support with full type definitions
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @bluebottle_gg/league-broadcast-client
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { LeagueBroadcastClient, GameState } from "@bluebottle_gg/league-broadcast-client";
|
|
24
|
+
|
|
25
|
+
// Create client instance
|
|
26
|
+
const client = new LeagueBroadcastClient({
|
|
27
|
+
host: "localhost",
|
|
28
|
+
port: 58869,
|
|
29
|
+
autoConnect: true,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Listen for state updates
|
|
33
|
+
client.onStateUpdate((gameData) => {
|
|
34
|
+
console.log("Game time:", gameData.gameTime);
|
|
35
|
+
console.log("Scoreboard:", gameData.scoreboard);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Listen for game status changes
|
|
39
|
+
client.onGameStatusChange((status, isTestingEnv) => {
|
|
40
|
+
if (status === GameState.InGame) {
|
|
41
|
+
console.log("Game started!");
|
|
42
|
+
} else if (status === GameState.OutOfGame) {
|
|
43
|
+
console.log("Game ended!");
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Listen for game events
|
|
48
|
+
client.onGameEvents({
|
|
49
|
+
onKillFeedEvent: (event) => {
|
|
50
|
+
console.log("Kill event:", event);
|
|
51
|
+
},
|
|
52
|
+
onObjectiveEvent: (event) => {
|
|
53
|
+
console.log("Objective taken:", event);
|
|
54
|
+
},
|
|
55
|
+
onPlayerEvent: (event) => {
|
|
56
|
+
console.log("Player event:", event);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## API Reference
|
|
62
|
+
|
|
63
|
+
### LeagueBroadcastClient
|
|
64
|
+
|
|
65
|
+
#### Constructor
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
new LeagueBroadcastClient(config: LeagueBroadcastClientConfig)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Config Options:**
|
|
72
|
+
|
|
73
|
+
| Option | Type | Default | Description |
|
|
74
|
+
| ------------- | --------- | ------------ | -------------------------------------- |
|
|
75
|
+
| `host` | `string` | **required** | Backend server hostname or IP |
|
|
76
|
+
| `port` | `number` | `58869` | Backend server port |
|
|
77
|
+
| `wsRoute` | `string` | `/ws/in` | WebSocket endpoint route |
|
|
78
|
+
| `apiRoute` | `string` | `/api` | API endpoint route |
|
|
79
|
+
| `cacheRoute` | `string` | `/cache` | Cache endpoint route |
|
|
80
|
+
| `useHttps` | `boolean` | `false` | Use WSS/HTTPS instead of WS/HTTP |
|
|
81
|
+
| `autoConnect` | `boolean` | `true` | Automatically connect on instantiation |
|
|
82
|
+
|
|
83
|
+
#### Methods
|
|
84
|
+
|
|
85
|
+
##### Connection Management
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Connect to backend
|
|
89
|
+
await client.connect(): Promise<void>
|
|
90
|
+
|
|
91
|
+
// Disconnect from backend
|
|
92
|
+
client.disconnect(): void
|
|
93
|
+
|
|
94
|
+
// Check connection status
|
|
95
|
+
client.isConnected(): boolean
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
##### Data Access
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Get current game data
|
|
102
|
+
client.getGameData(): ingameFrontendData
|
|
103
|
+
|
|
104
|
+
// Get current game state
|
|
105
|
+
client.getGameState(): GameState
|
|
106
|
+
|
|
107
|
+
// Check if in testing environment
|
|
108
|
+
client.isInTestingEnvironment(): boolean
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
##### URLs
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Get API base URL
|
|
115
|
+
client.getApiUrl(): string
|
|
116
|
+
|
|
117
|
+
// Get cache URL (optionally with path)
|
|
118
|
+
client.getCacheUrl(path?: string): string
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
##### Event Handlers
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// State updates - called when game data changes
|
|
125
|
+
client.onStateUpdate(handler: (state: ingameFrontendData) => void): () => void
|
|
126
|
+
|
|
127
|
+
// Game status changes - called when game starts/ends
|
|
128
|
+
client.onGameStatusChange(handler: (status: GameState, isTestingEnv: boolean) => void): () => void
|
|
129
|
+
|
|
130
|
+
// Game events - register handlers for various game events
|
|
131
|
+
client.onGameEvents(handlers: GameDataEventHandlers): void
|
|
132
|
+
|
|
133
|
+
// Connection events
|
|
134
|
+
client.onConnect(handler: () => void): () => void
|
|
135
|
+
client.onDisconnect(handler: () => void): () => void
|
|
136
|
+
client.onError(handler: (error: Event) => void): () => void
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
All event handler registration methods return an unsubscribe function.
|
|
140
|
+
|
|
141
|
+
### GameDataEventHandlers
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
interface GameDataEventHandlers {
|
|
145
|
+
onPlayerEvent?: (event: playerEvent) => void;
|
|
146
|
+
onTeamEvent?: (event: teamEvent) => void;
|
|
147
|
+
onObjectiveEvent?: (event: objectiveEvent) => void;
|
|
148
|
+
onFirstTowerEvent?: (event: firstTowerEvent) => void;
|
|
149
|
+
onAnnouncementEvent?: (event: announcerEvent) => void;
|
|
150
|
+
onKillFeedEvent?: (event: killFeedEvent) => void;
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Reactive API
|
|
155
|
+
|
|
156
|
+
The client includes a **framework-agnostic reactive store** that enables fine-grained subscriptions to specific parts of the game state. The store is compatible with React, Vue, Svelte, Solid, Angular, and vanilla JavaScript.
|
|
157
|
+
|
|
158
|
+
### Core Concepts
|
|
159
|
+
|
|
160
|
+
The reactive API is built around **selectors** — functions that extract a slice of state. The backend sends a full snapshot each update, and `null` values explicitly mean “not available / don’t show”. The client uses **structural sharing** to preserve nested object references when values are unchanged, so selectors only re-fire when their content actually changes (with configurable equality comparison for derived values).
|
|
161
|
+
|
|
162
|
+
### Quick Examples
|
|
163
|
+
|
|
164
|
+
#### Vanilla JavaScript
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Watch a specific value
|
|
168
|
+
const unsubscribe = client.watch(
|
|
169
|
+
(s) => s.gameData.scoreboard?.teams[0]?.kills,
|
|
170
|
+
(kills, prevKills) => {
|
|
171
|
+
console.log(`Blue team kills: ${prevKills} → ${kills}`);
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Clean up when done
|
|
176
|
+
unsubscribe();
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### React 18+
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { useSyncExternalStore } from 'react';
|
|
183
|
+
|
|
184
|
+
function BlueTeamKills() {
|
|
185
|
+
const slice = client.select(s => s.gameData.scoreboard?.teams[0]?.kills);
|
|
186
|
+
const kills = useSyncExternalStore(slice.subscribe, slice.getSnapshot);
|
|
187
|
+
|
|
188
|
+
return <div>Blue Kills: {kills ?? 0}</div>;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Vue 3
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { ref, onUnmounted } from "vue";
|
|
196
|
+
|
|
197
|
+
export default {
|
|
198
|
+
setup() {
|
|
199
|
+
const kills = ref(0);
|
|
200
|
+
|
|
201
|
+
const unsubscribe = client.watch(
|
|
202
|
+
(s) => s.gameData.scoreboard?.teams[0]?.kills,
|
|
203
|
+
(value) => {
|
|
204
|
+
kills.value = value ?? 0;
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
onUnmounted(() => unsubscribe());
|
|
209
|
+
|
|
210
|
+
return { kills };
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Svelte
|
|
216
|
+
|
|
217
|
+
The `Subscribable` returned by `select()` matches Svelte's store contract:
|
|
218
|
+
|
|
219
|
+
```svelte
|
|
220
|
+
<script lang="ts">
|
|
221
|
+
const kills = client.select(s => s.gameData.scoreboard?.teams[0]?.kills);
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<div>Blue Kills: {$kills ?? 0}</div>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Reactive Methods
|
|
228
|
+
|
|
229
|
+
#### `client.select(selector, equalityFn?)`
|
|
230
|
+
|
|
231
|
+
Create a subscribable slice that only notifies listeners when the selected value changes.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const slice = client.select(
|
|
235
|
+
(s) => s.gameData.gameTime,
|
|
236
|
+
Object.is, // Default equality function (can use shallowEqual for objects)
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Get current value
|
|
240
|
+
const currentTime = slice.getSnapshot();
|
|
241
|
+
|
|
242
|
+
// Subscribe to changes
|
|
243
|
+
const unsubscribe = slice.subscribe(() => {
|
|
244
|
+
console.log("Game time changed:", slice.getSnapshot());
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Parameters:**
|
|
249
|
+
|
|
250
|
+
- `selector: (snapshot: GameStateSnapshot) => T` — Function to extract value from state
|
|
251
|
+
- `equalityFn?: (a: T, b: T) => boolean` — Optional equality comparison (defaults to `Object.is`)
|
|
252
|
+
|
|
253
|
+
**Returns:** `Subscribable<T>` with:
|
|
254
|
+
|
|
255
|
+
- `subscribe(listener: () => void): () => void` — Subscribe to changes
|
|
256
|
+
- `getSnapshot(): T` — Get current value
|
|
257
|
+
|
|
258
|
+
#### `client.watch(selector, callback, equalityFn?)`
|
|
259
|
+
|
|
260
|
+
Imperative API for watching a derived value. Callback is invoked whenever the selected value changes.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
const unsubscribe = client.watch(
|
|
264
|
+
(s) => s.gameData.scoreboard?.teams[0]?.kills,
|
|
265
|
+
(kills, prevKills) => {
|
|
266
|
+
console.log(`Kills changed: ${prevKills} → ${kills}`);
|
|
267
|
+
},
|
|
268
|
+
);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Parameters:**
|
|
272
|
+
|
|
273
|
+
- `selector: (snapshot: GameStateSnapshot) => T` — Function to extract value
|
|
274
|
+
- `callback: (value: T, prev: T) => void` — Called when value changes
|
|
275
|
+
- `equalityFn?: (a: T, b: T) => boolean` — Optional equality comparison
|
|
276
|
+
|
|
277
|
+
**Returns:** `() => void` — Unsubscribe function
|
|
278
|
+
|
|
279
|
+
#### `client.store`
|
|
280
|
+
|
|
281
|
+
Direct access to the `GameStateStore` instance for advanced use cases:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// Get full snapshot
|
|
285
|
+
const snapshot = client.store.getSnapshot();
|
|
286
|
+
console.log("Game time:", snapshot.gameData.gameTime);
|
|
287
|
+
console.log("Game state:", snapshot.gameState);
|
|
288
|
+
console.log("Version:", snapshot.version);
|
|
289
|
+
|
|
290
|
+
// Subscribe to all changes (unfiltered)
|
|
291
|
+
const unsubscribe = client.store.subscribe(() => {
|
|
292
|
+
console.log("Store updated");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Watch with immediate callback
|
|
296
|
+
client.store.watchImmediate(
|
|
297
|
+
(s) => s.gameData.gameTime,
|
|
298
|
+
(time, prevTime) => {
|
|
299
|
+
console.log(`Time: ${prevTime} → ${time}`);
|
|
300
|
+
},
|
|
301
|
+
);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Equality Functions
|
|
305
|
+
|
|
306
|
+
#### `Object.is` (default)
|
|
307
|
+
|
|
308
|
+
Uses strict equality (`===`). Best for primitives and object references:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
client.select((s) => s.gameData.gameTime); // Uses Object.is by default
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### `shallowEqual`
|
|
315
|
+
|
|
316
|
+
Compares objects by their enumerable properties. Use when selecting objects whose identity changes but contents may not:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { shallowEqual } from "@bluebottle_gg/league-broadcast-client";
|
|
320
|
+
|
|
321
|
+
const stats = client.select(
|
|
322
|
+
(s) => ({
|
|
323
|
+
kills: s.gameData.scoreboard?.teams[0]?.kills ?? 0,
|
|
324
|
+
deaths: s.gameData.scoreboardBottom?.teams[0]?.players[0]?.deaths ?? 0,
|
|
325
|
+
}),
|
|
326
|
+
shallowEqual, // Only trigger when values change, not object identity
|
|
327
|
+
);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### GameStateSnapshot
|
|
331
|
+
|
|
332
|
+
The snapshot passed to selectors contains:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
interface GameStateSnapshot {
|
|
336
|
+
gameData: ingameFrontendData; // Full game data
|
|
337
|
+
gameState: GameState; // Current game state enum
|
|
338
|
+
version: number; // Monotonic version counter
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Performance Tips
|
|
343
|
+
|
|
344
|
+
**✅ DO:** Use selectors to watch specific values
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
client.watch((s) => s.gameData.scoreboard?.teams[0]?.kills, handleKills);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**❌ DON'T:** Subscribe to entire store and manually filter
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// This triggers on EVERY change, even unrelated ones
|
|
354
|
+
client.store.subscribe(() => {
|
|
355
|
+
const kills = client.store.getSnapshot().gameData.scoreboard?.teams[0]?.kills;
|
|
356
|
+
handleKills(kills);
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**✅ DO:** Use `shallowEqual` for derived objects
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
client.watch(
|
|
364
|
+
(s) => ({ kills: s.gameData.scoreboard?.teams[0]?.kills ?? 0 }),
|
|
365
|
+
handleStats,
|
|
366
|
+
shallowEqual,
|
|
367
|
+
);
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**✅ DO:** Select nested objects by reference (structural sharing keeps stable references when unchanged)
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
// Only triggers when team object reference changes
|
|
374
|
+
client.watch((s) => s.gameData.scoreboard?.teams[0], handleTeam);
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**✅ DO:** Handle `null` as “not available”
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
client.watch((s) => s.gameData.scoreboard, (scoreboard) => {
|
|
381
|
+
if (!scoreboard) {
|
|
382
|
+
hideScoreboard();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
renderScoreboard(scoreboard);
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Framework Integration Examples
|
|
390
|
+
|
|
391
|
+
See [examples/reactivity.ts](examples/reactivity.ts) for complete examples with:
|
|
392
|
+
|
|
393
|
+
- React (with `useSyncExternalStore` and custom hooks)
|
|
394
|
+
- Vue 3 (Composition API)
|
|
395
|
+
- Svelte (native store contract)
|
|
396
|
+
- Solid.js
|
|
397
|
+
- Angular (with Signals)
|
|
398
|
+
- Vanilla JavaScript
|
|
399
|
+
|
|
400
|
+
## Examples
|
|
401
|
+
|
|
402
|
+
### Basic Usage
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import { LeagueBroadcastClient } from "@bluebottle_gg/league-broadcast-client";
|
|
406
|
+
|
|
407
|
+
const client = new LeagueBroadcastClient({
|
|
408
|
+
host: "192.168.1.100",
|
|
409
|
+
port: 58869,
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Game data is automatically updated in real-time
|
|
413
|
+
setInterval(() => {
|
|
414
|
+
const data = client.getGameData();
|
|
415
|
+
console.log("Current game time:", data.gameTime);
|
|
416
|
+
}, 1000);
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### React Integration
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
import { useEffect, useState } from 'react';
|
|
423
|
+
import { LeagueBroadcastClient, ingameFrontendData, GameState } from '@bluebottle_gg/league-broadcast-client';
|
|
424
|
+
|
|
425
|
+
function useLeagueBroadcast(host: string, port: number) {
|
|
426
|
+
const [client] = useState(() => new LeagueBroadcastClient({ host, port }));
|
|
427
|
+
const [gameData, setGameData] = useState<ingameFrontendData | null>(null);
|
|
428
|
+
const [gameState, setGameState] = useState<GameState>(GameState.OutOfGame);
|
|
429
|
+
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
const unsubscribeState = client.onStateUpdate((data) => {
|
|
432
|
+
setGameData(data);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const unsubscribeStatus = client.onGameStatusChange((status) => {
|
|
436
|
+
setGameState(status);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
return () => {
|
|
440
|
+
unsubscribeState();
|
|
441
|
+
unsubscribeStatus();
|
|
442
|
+
client.disconnect();
|
|
443
|
+
};
|
|
444
|
+
}, [client]);
|
|
445
|
+
|
|
446
|
+
return { gameData, gameState, client };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Usage in component
|
|
450
|
+
function GameDisplay() {
|
|
451
|
+
const { gameData, gameState } = useLeagueBroadcast('localhost', 58869);
|
|
452
|
+
|
|
453
|
+
if (gameState === GameState.OutOfGame) {
|
|
454
|
+
return <div>No game in progress</div>;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return (
|
|
458
|
+
<div>
|
|
459
|
+
<h1>Game Time: {gameData?.gameTime}</h1>
|
|
460
|
+
<div>Blue Team Score: {gameData?.scoreboard?.teams[0]?.totalKills}</div>
|
|
461
|
+
<div>Red Team Score: {gameData?.scoreboard?.teams[1]?.totalKills}</div>
|
|
462
|
+
</div>
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Event Handling
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
import { LeagueBroadcastClient } from "@bluebottle_gg/league-broadcast-client";
|
|
471
|
+
|
|
472
|
+
const client = new LeagueBroadcastClient({
|
|
473
|
+
host: "localhost",
|
|
474
|
+
port: 58869,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// Handle all game events
|
|
478
|
+
client.onGameEvents({
|
|
479
|
+
onKillFeedEvent: (event) => {
|
|
480
|
+
console.log(`${event.killerName} killed ${event.victimName}`);
|
|
481
|
+
},
|
|
482
|
+
onObjectiveEvent: (event) => {
|
|
483
|
+
console.log(`Objective secured: ${event.objectiveType}`);
|
|
484
|
+
},
|
|
485
|
+
onFirstTowerEvent: (event) => {
|
|
486
|
+
console.log(`First tower taken by team ${event.teamId}`);
|
|
487
|
+
},
|
|
488
|
+
onAnnouncementEvent: (event) => {
|
|
489
|
+
console.log(`Announcement: ${event.announcementType}`);
|
|
490
|
+
},
|
|
491
|
+
onPlayerEvent: (event) => {
|
|
492
|
+
console.log(`Player event: ${event.eventType}`);
|
|
493
|
+
},
|
|
494
|
+
onTeamEvent: (event) => {
|
|
495
|
+
console.log(`Team event: ${event.eventType}`);
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Manual Connection Control
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
import { LeagueBroadcastClient } from "@bluebottle_gg/league-broadcast-client";
|
|
504
|
+
|
|
505
|
+
const client = new LeagueBroadcastClient({
|
|
506
|
+
host: "localhost",
|
|
507
|
+
port: 58869,
|
|
508
|
+
autoConnect: false, // Don't connect automatically
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// Connect manually when ready
|
|
512
|
+
async function connectToGame() {
|
|
513
|
+
try {
|
|
514
|
+
await client.connect();
|
|
515
|
+
console.log("Connected successfully!");
|
|
516
|
+
} catch (error) {
|
|
517
|
+
console.error("Connection failed:", error);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Disconnect when done
|
|
522
|
+
function disconnectFromGame() {
|
|
523
|
+
client.disconnect();
|
|
524
|
+
console.log("Disconnected");
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## License
|
|
529
|
+
|
|
530
|
+
GPLv3
|