@alca/rocket-league-stats-api 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jacob Foster
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @alca/rocket-league-stats-api
2
+
3
+ Connect to the Rocket League's Stats API over WebSocket.
4
+
5
+ # Install
6
+
7
+ ```bash
8
+ npm i @alca/rocket-league-stats-api
9
+ ```
10
+
11
+ # Usage
12
+
13
+ ```ts
14
+ import RocketLeagueStatsApiClient from '@alca/rocket-league-stats-api';
15
+
16
+ const client = new RocketLeagueStatsApiClient();
17
+
18
+ client.connect();
19
+
20
+ client.onSocketOpen = () => console.log('Connected!');
21
+
22
+ client.onGoalScored = data => {
23
+ console.log(`${data.Scorer.Name} scored! ${data.Assister ? `(${data.Assister.Name} assisted)` : ''}`);
24
+ };
25
+ ```
26
+
27
+ ## Configuration
28
+
29
+ See the [configuration section](https://www.rocketleague.com/developer/stats-api#configuration) of the official documentation. The "WebPort" port value (`49124` by default) is what is used for the WebSocket connection. The port can be changed but will need to be passed to the client constructor or changed in the options property before connecting.
30
+
31
+ # Notes
32
+
33
+ ## Duplicate StatfeedEvent for local bots and splitscreen
34
+
35
+ If you play an offline Exhibition or play with a splitscreen player, you will receive multiple events to `onStatfeedEvent`. Some care should be taken to throttle duplicate data.
@@ -0,0 +1,550 @@
1
+ export declare namespace Commands {
2
+ namespace ChangePov {
3
+ type Focus = "Ball" | `${number}`;
4
+ type Perspective = "Fly" | "SoftAttach" | "HardAttach" | "PlayerView" | "AutoCam" | "Camera_Director";
5
+ }
6
+ }
7
+ export interface BaseMessage {
8
+ Event: Events.AllEventNames;
9
+ Data: string;
10
+ }
11
+ export interface Vector {
12
+ X: number;
13
+ Y: number;
14
+ Z: number;
15
+ }
16
+ export interface Player {
17
+ /** Display name. */
18
+ Name: string;
19
+ /** Platform identifier in the format Platform|Uid|Splitscreen (e.g. "Steam|123|0", "Epic|456|0"). */
20
+ PrimaryId: string;
21
+ /** Spectator shortcut number. */
22
+ Shortcut: number;
23
+ /** Team index (0 = Blue, 1 = Orange). */
24
+ TeamNum: number;
25
+ /** Total match score. */
26
+ Score: number;
27
+ /** Goals scored this match. */
28
+ Goals: number;
29
+ /** Shot attempts this match. */
30
+ Shots: number;
31
+ /** Assists earned this match. */
32
+ Assists: number;
33
+ /** Saves made this match. */
34
+ Saves: number;
35
+ /** Total ball touches. */
36
+ Touches: number;
37
+ /** Touches by the car body (not ball). */
38
+ CarTouches: number;
39
+ /** Demolitions inflicted. */
40
+ Demos: number;
41
+ /** Asset names of the equipped loadout for the player's current team sorted by product slot. */
42
+ Loadout: string[];
43
+ /**
44
+ * True if the player currently has a vehicle.
45
+ * @SPECTATOR
46
+ */
47
+ bHasCar: boolean;
48
+ /**
49
+ * Vehicle speed in Unreal Units/second.
50
+ * @SPECTATOR
51
+ */
52
+ Speed: number;
53
+ /**
54
+ * Boost amount 0–100.
55
+ * @SPECTATOR
56
+ */
57
+ Boost: number;
58
+ /**
59
+ * True if the player is currently boosting.
60
+ * @SPECTATOR
61
+ */
62
+ bBoosting?: boolean;
63
+ /**
64
+ * True if at least 3 wheels are touching the world.
65
+ * @SPECTATOR
66
+ */
67
+ bOnGround?: boolean;
68
+ /**
69
+ * True if the vehicle is on a wall.
70
+ * @SPECTATOR
71
+ */
72
+ bOnWall?: boolean;
73
+ /**
74
+ * True if the player is holding handbrake.
75
+ * @SPECTATOR
76
+ */
77
+ bPowersliding?: boolean;
78
+ /**
79
+ * True if the vehicle is currently destroyed.
80
+ * @SPECTATOR
81
+ */
82
+ bDemolished?: boolean;
83
+ /**
84
+ * True if the vehicle is at supersonic speed.
85
+ * @SPECTATOR
86
+ */
87
+ bSupersonic?: boolean;
88
+ /**
89
+ * Class name of the player's currently available Rumble pickup. Omitted when not playing with rumble powerups.
90
+ * @SPECTATOR
91
+ */
92
+ PickupClass?: string;
93
+ /**
94
+ * The player who demolished this player. Present only when demolished.
95
+ */
96
+ Attacker?: PlayerRef;
97
+ }
98
+ export type PlayerRef = Pick<Player, "Name" | "Shortcut" | "TeamNum">;
99
+ export interface BallLastTouch {
100
+ /** The player who made the last touch. */
101
+ Player: PlayerRef;
102
+ /** Speed of the ball resulting from this hit. */
103
+ Speed: number;
104
+ }
105
+ export declare namespace Events {
106
+ export type AllEventNames = "UpdateState" | "BallHit" | "BoostPickup" | "ClockUpdatedSeconds" | "CountdownBegin" | "CrossbarHit" | "GoalReplayStart" | "GoalReplayWillEnd" | "GoalReplayEnd" | "GoalScored" | "MatchCreated" | "MatchInitialized" | "MatchPaused" | "MatchUnpaused" | "MatchEnded" | "MatchDestroyed" | "PlayerJoined" | "PlayerLeft" | "PodiumStart" | "ReplayCreated" | "RoundStarted" | "StatfeedEvent";
107
+ interface EventBase {
108
+ /** Only set for online or LAN matches. */
109
+ MatchGuid: string;
110
+ }
111
+ export namespace Partials_UpdateState {
112
+ interface GameTeam {
113
+ /** Team name. */
114
+ Name: string;
115
+ /** Team index. */
116
+ TeamNum: number;
117
+ /** Team goal count. */
118
+ Score: number;
119
+ /**
120
+ * Hex color code (no #) for the team’s primary color.
121
+ * @example "FF9F00"
122
+ */
123
+ ColorPrimary: string;
124
+ /** Hex color code for the team’s secondary color.
125
+ * @example "E5E5E5"
126
+ */
127
+ ColorSecondary: string;
128
+ }
129
+ interface GameBall {
130
+ /** Current ball speed in Unreal Units/second. */
131
+ Speed: number;
132
+ /** Index of the last team to touch the ball. `255` if the ball has not been touched. */
133
+ TeamNum: number;
134
+ }
135
+ /**
136
+ * Match metadata.
137
+ */
138
+ interface Game {
139
+ /** One entry per team, ordered by TeamNum. */
140
+ Teams: GameTeam[];
141
+ /** Playlist Id of the current match. */
142
+ PlaylistId: number;
143
+ /** Seconds remaining in the match. */
144
+ TimeSeconds: number;
145
+ /** True if the match is in overtime. */
146
+ bOvertime: boolean;
147
+ /** Current ball state. */
148
+ Ball: GameBall;
149
+ /** True if a goal replay or history replay is active. */
150
+ bReplay: boolean;
151
+ /** True if a team has won. */
152
+ bHasWinner: boolean;
153
+ /** Name of the winning team. Empty string if no winner yet. */
154
+ Winner: string;
155
+ /** Asset name of the current map (e.g. "Stadium_P").
156
+ * @example "Wasteland_GRS_P"
157
+ */
158
+ Arena: string;
159
+ /** True if the client is currently viewing a specific vehicle. */
160
+ bHasTarget: boolean;
161
+ /** Player currently being viewed. Members are an empty string or 0 if the player does not have a spectator target. */
162
+ Target?: PlayerRef;
163
+ /** Current frame number if a replay is active. */
164
+ Frame?: number;
165
+ /** Seconds elapsed since game start if a replay is active. */
166
+ Elapsed?: number;
167
+ }
168
+ }
169
+ /**
170
+ * Sent X amount of times per second based on the player's PacketSendRate preference.
171
+ */
172
+ export interface Event_UpdateState extends EventBase {
173
+ /** One entry per player in the match. */
174
+ Players: Player[];
175
+ /** Match metadata. */
176
+ Game: Partials_UpdateState.Game;
177
+ }
178
+ export namespace Partials_BallHit {
179
+ interface Ball {
180
+ /** Ball speed before the hit (Unreal Units/second). */
181
+ PreHitSpeed: number;
182
+ /** Ball speed after the hit (Unreal Units/second). */
183
+ PostHitSpeed: number;
184
+ /** World position of the ball at impact. */
185
+ Location: Vector;
186
+ }
187
+ }
188
+ export interface Event_BallHit extends EventBase {
189
+ /** Players that hit the ball that frame. */
190
+ Players: PlayerRef[];
191
+ /** Ball state at the moment of the hit. */
192
+ Ball: Partials_BallHit.Ball;
193
+ }
194
+ export interface Event_BoostPickup extends EventBase {
195
+ /** The player who collected the boost. */
196
+ Player: PlayerRef;
197
+ /** World location of the pickup. */
198
+ Location: Vector;
199
+ /** Amount of boost granted by the pickup. */
200
+ BoostAmount: number;
201
+ /** Pickup class: BoostType_Pad (small pad), BoostType_Pill (full/big boost). */
202
+ BoostType: "BoostType_Pad" | "BoostType_Pill";
203
+ /** True if the pickup occurred during replay playback. */
204
+ bReplay: boolean;
205
+ }
206
+ export interface Event_ClockUpdatedSeconds extends EventBase {
207
+ /** Seconds remaining in the match. */
208
+ TimeSeconds: number;
209
+ /** True if the game is in overtime. */
210
+ bOvertime: boolean;
211
+ }
212
+ export interface Event_CountdownBegin extends EventBase {
213
+ }
214
+ export interface Event_CrossbarHit extends EventBase {
215
+ /** Ball speed on impact. */
216
+ BallSpeed: number;
217
+ /** Impact force of the ball relative to the crossbar normal. */
218
+ ImpactForce: number;
219
+ /** The last touch of the ball before the crossbar hit. */
220
+ BallLastTouch: BallLastTouch;
221
+ /** World position of the ball when the impact occurred. */
222
+ BallLocation: Vector;
223
+ }
224
+ export interface Event_GoalReplayStart extends EventBase {
225
+ }
226
+ export interface Event_GoalReplayWillEnd extends EventBase {
227
+ }
228
+ export interface Event_GoalReplayEnd extends EventBase {
229
+ }
230
+ export interface Event_GoalScored extends EventBase {
231
+ /** Speed of the ball (Unreal Units/second) when it crossed the goal line. */
232
+ GoalSpeed: number;
233
+ /** Length of the previous round in seconds. */
234
+ GoalTime: number;
235
+ /** World position of the ball when the goal was scored. */
236
+ ImpactLocation: Vector;
237
+ /** The player who scored the goal. */
238
+ Scorer: PlayerRef;
239
+ /** The last touch of the ball before the goal. */
240
+ BallLastTouch: BallLastTouch;
241
+ /** Present only when an assist was recorded. */
242
+ Assister?: PlayerRef;
243
+ }
244
+ export interface Event_MatchCreated extends EventBase {
245
+ }
246
+ export interface Event_MatchInitialized extends EventBase {
247
+ }
248
+ export interface Event_MatchPaused extends EventBase {
249
+ }
250
+ export interface Event_MatchUnpaused extends EventBase {
251
+ }
252
+ export interface Event_MatchEnded extends EventBase {
253
+ /** Team index of the winning team. */
254
+ WinnerTeamNum: number;
255
+ }
256
+ export interface Event_MatchDestroyed extends EventBase {
257
+ }
258
+ export interface Event_PlayerJoined extends EventBase {
259
+ /** Display name of the player who joined. */
260
+ PlayerName: string;
261
+ /** Platform identifier in the format Platform|Uid|Splitscreen (e.g. "Steam|123|0", "Epic|456|0"). */
262
+ PrimaryId: string;
263
+ }
264
+ export interface Event_PlayerLeft extends EventBase {
265
+ /** Display name of the player who left. */
266
+ PlayerName: string;
267
+ /** Platform identifier in the format Platform|Uid|Splitscreen (e.g. "Steam|123|0", "Epic|456|0"). */
268
+ PrimaryId: string;
269
+ }
270
+ export interface Event_PodiumStart extends EventBase {
271
+ }
272
+ export interface Event_ReplayCreated extends EventBase {
273
+ /** File name of the replay. */
274
+ FileName: string;
275
+ /** Timestamp the replay was created. */
276
+ Date: string;
277
+ }
278
+ export interface Event_RoundStarted extends EventBase {
279
+ }
280
+ export interface Event_StatfeedEvent extends EventBase {
281
+ /**
282
+ * Asset name of the StatEvent (e.g. "Demolish", "Save").
283
+ */
284
+ EventName: "Demolish" | "Demolition" | "Goal" | "Win" | "MVP" | "AerialGoal" | "BackwardsGoal" | "BicycleGoal" | "LongGoal" | "TurtleGoal" | "PoolShot" | "OvertimeGoal" | "HatTrick" | "Assist" | "Playmaker" | "Save" | "EpicSave" | "Savior" | "Shot" | "Center" | "Clear" | "FirstTouch" | "BreakoutDamage" | "BreakoutDamageLarge" | "LowFive" | "HighFive" | "HoopsSwishGoal" | "BicycleHit" | "OwnGoal" | "FlipReset";
285
+ /**
286
+ * Localized display label for the stat (e.g. "Demolition").
287
+ */
288
+ Type: string;
289
+ /** Player who earned the stat. */
290
+ MainTarget: PlayerRef;
291
+ /** Player involved in the stat (e.g. the demolished player). */
292
+ SecondaryTarget?: PlayerRef;
293
+ }
294
+ export {};
295
+ }
296
+ export declare class ConnectionError extends Error {
297
+ }
298
+ export declare class Connection {
299
+ ws: WebSocket;
300
+ onOpen?: (e: Event) => void;
301
+ onMessage?: (event: Events.AllEventNames, dataStr: string) => void;
302
+ onClose?: (e: CloseEvent) => void;
303
+ onError?: (err: ConnectionError) => void;
304
+ constructor(host: string, port: number, secure: boolean);
305
+ handleMessage(e: MessageEvent): void;
306
+ protected handleError(error: Error): void;
307
+ protected handleError(message: string, errorOptions?: ErrorOptions): void;
308
+ removeListeners(): void;
309
+ close(): void;
310
+ isConnected(): boolean;
311
+ send(message: object): void;
312
+ }
313
+ export declare class ClientError extends Error {
314
+ }
315
+ export interface ClientOptions {
316
+ /**
317
+ * Hostname to connect to the Rocket League client.
318
+ * @default '127.0.0.1'
319
+ */
320
+ host: string;
321
+ /**
322
+ * Port to connect to the Rocket League client.
323
+ * @default 49124
324
+ */
325
+ port: number;
326
+ /**
327
+ * Whether to use the wss: (true) or ws: (false) protocol.
328
+ * @default false
329
+ */
330
+ secure: boolean;
331
+ }
332
+ /**
333
+ * @see https://www.rocketleague.com/developer/stats-api
334
+ */
335
+ declare class Client {
336
+ /**
337
+ * Client options to use when connecting, etc. Recommended to configure with the constructor initially.
338
+ */
339
+ options: ClientOptions;
340
+ socket?: Connection;
341
+ protected reconnectAttempts: number;
342
+ protected reconnectTimeout: Parameters<typeof clearTimeout>[0];
343
+ protected wasCloseCalled: boolean;
344
+ /**
345
+ * A lifecycle event for when the socket is successfully connected.
346
+ */
347
+ onSocketConnected?: () => void;
348
+ /**
349
+ * A lifecycle event for when the socket is closed.
350
+ */
351
+ onSocketClosed?: (event: {
352
+ wasCloseCalled: boolean;
353
+ }) => void;
354
+ /** Emitted for any internal errors. */
355
+ onError?: (err: Error) => void;
356
+ /** All events received on the socket except for UpdateState (tick event). */
357
+ onEvent?: (event: Events.AllEventNames, data: object) => void;
358
+ /**
359
+ * Sent X amount of times per second based on the player's PacketSendRate preference.
360
+ * @see https://www.rocketleague.com/developer/stats-api#UpdateState
361
+ */
362
+ onUpdateState?: (data: Events.Event_UpdateState) => void;
363
+ /**
364
+ * Sent one frame after the ball is hit.
365
+ * @see https://www.rocketleague.com/developer/stats-api#BallHit
366
+ */
367
+ onBallHit?: (data: Events.Event_BallHit) => void;
368
+ /**
369
+ * Sent when a vehicle collects a boost pad or pill.
370
+ * @SPECTATOR
371
+ * @see https://www.rocketleague.com/developer/stats-api#BoostPickup
372
+ */
373
+ onBoostPickup?: (data: Events.Event_BoostPickup) => void;
374
+ /**
375
+ * Sent when the in-game clock has changed.
376
+ * @SPECTATOR
377
+ * @see https://www.rocketleague.com/developer/stats-api#ClockUpdatedSeconds
378
+ */
379
+ onClockUpdatedSeconds?: (data: Events.Event_ClockUpdatedSeconds) => void;
380
+ /**
381
+ * Sent at the start of each round when the countdown starts.
382
+ * @see https://www.rocketleague.com/developer/stats-api#CountdownBegin
383
+ */
384
+ onCountdownBegin?: (data: Events.Event_CountdownBegin) => void;
385
+ /**
386
+ * Sent when the ball hits a crossbar.
387
+ * @see https://www.rocketleague.com/developer/stats-api#CrossbarHit
388
+ */
389
+ onCrossbarHit?: (data: Events.Event_CrossbarHit) => void;
390
+ /**
391
+ * Sent when a goal replay starts.
392
+ * @see https://www.rocketleague.com/developer/stats-api#GoalReplayStart
393
+ */
394
+ onGoalReplayStart?: (data: Events.Event_GoalReplayStart) => void;
395
+ /**
396
+ * Sent when a goal replay ends.
397
+ * @see https://www.rocketleague.com/developer/stats-api#GoalReplayWillEnd
398
+ */
399
+ onGoalReplayWillEnd?: (data: Events.Event_GoalReplayWillEnd) => void;
400
+ /**
401
+ * Sent when the ball explodes during a goal replay. If the replay is skipped this event will not fire.
402
+ * @see https://www.rocketleague.com/developer/stats-api#GoalReplayEnd
403
+ */
404
+ onGoalReplayEnd?: (data: Events.Event_GoalReplayEnd) => void;
405
+ /**
406
+ * Sent when a goal is scored.
407
+ * @see https://www.rocketleague.com/developer/stats-api#GoalScored
408
+ */
409
+ onGoalScored?: (data: Events.Event_GoalScored) => void;
410
+ /**
411
+ * Sent when all teams are created and replicated.
412
+ * @see https://www.rocketleague.com/developer/stats-api#MatchCreated
413
+ */
414
+ onMatchCreated?: (data: Events.Event_MatchCreated) => void;
415
+ /**
416
+ * Sent when the first countdown starts.
417
+ * @see https://www.rocketleague.com/developer/stats-api#MatchInitialized
418
+ */
419
+ onMatchInitialized?: (data: Events.Event_MatchInitialized) => void;
420
+ /**
421
+ * Sent when the game is paused by a match admin.
422
+ * @see https://www.rocketleague.com/developer/stats-api#MatchPaused
423
+ */
424
+ onMatchPaused?: (data: Events.Event_MatchPaused) => void;
425
+ /**
426
+ * Sent when the game is unpaused by a match admin.
427
+ * @see https://www.rocketleague.com/developer/stats-api#MatchUnpaused
428
+ */
429
+ onMatchUnpaused?: (data: Events.Event_MatchUnpaused) => void;
430
+ /**
431
+ * Sent when the match ends and a winner is chosen.
432
+ * @see https://www.rocketleague.com/developer/stats-api#MatchUnpaused
433
+ */
434
+ onMatchEnded?: (data: Events.Event_MatchEnded) => void;
435
+ /**
436
+ * Sent when leaving the game.
437
+ * @see https://www.rocketleague.com/developer/stats-api#MatchDestroyed
438
+ */
439
+ onMatchDestroyed?: (data: Events.Event_MatchDestroyed) => void;
440
+ /**
441
+ * Sent when a player is added to the current match.
442
+ * @see https://www.rocketleague.com/developer/stats-api#PlayerJoined
443
+ */
444
+ onPlayerJoined?: (data: Events.Event_PlayerJoined) => void;
445
+ /**
446
+ * Sent when a player is removed from the current match.
447
+ * @see https://www.rocketleague.com/developer/stats-api#PlayerLeft
448
+ */
449
+ onPlayerLeft?: (data: Events.Event_PlayerLeft) => void;
450
+ /**
451
+ * Sent when the game enters the podium state after the match ends.
452
+ * @see https://www.rocketleague.com/developer/stats-api#PodiumStart
453
+ */
454
+ onPodiumStart?: (data: Events.Event_PodiumStart) => void;
455
+ /**
456
+ * Sent when a replay is initialized. Does not pertain to goal replays,
457
+ * only replays you load via the Match History menu.
458
+ * @see https://www.rocketleague.com/developer/stats-api#ReplayCreated
459
+ */
460
+ onReplayCreated?: (data: Events.Event_ReplayCreated) => void;
461
+ /**
462
+ * Sent when the game enters the active state (after the countdown finishes).
463
+ * @see https://www.rocketleague.com/developer/stats-api#RoundStarted
464
+ */
465
+ onRoundStarted?: (data: Events.Event_RoundStarted) => void;
466
+ /**
467
+ * Sent when someone earns a stat.
468
+ * @see https://www.rocketleague.com/developer/stats-api#StatfeedEvent
469
+ */
470
+ onStatfeedEvent?: (data: Events.Event_StatfeedEvent) => void;
471
+ constructor(opts?: Partial<ClientOptions>);
472
+ protected handleOpen(): void;
473
+ protected handleClose(): void;
474
+ protected handleError(error: Error): void;
475
+ protected handleError(message: string, errorOptions?: ErrorOptions): void;
476
+ protected handleMessage(event: Events.AllEventNames, dataStr: string): void;
477
+ protected sendCommand(command: string, data: object): void;
478
+ /**
479
+ * Connect to the Rocket League client. Will ignore subsequent calls to connect if already connected.
480
+ */
481
+ connect(): void;
482
+ /**
483
+ * Close to connection to the Rocket League client. Will ignore subsequent calls to close.
484
+ */
485
+ close(): void;
486
+ /**
487
+ * Tests if the client has a socket that is open.
488
+ */
489
+ isConnected(): this is {
490
+ socket: Connection;
491
+ };
492
+ /**
493
+ * Changes the spectator/replay viewpoint. At least one of Focus or Perspective must be supplied; both may be combined.
494
+ * @SPECTATOR
495
+ * @REPLAY
496
+ * @param opts.focus What to look at -- the ball or a specific player.
497
+ * @param opts.perspective Camera mode to switch to.
498
+ * @see https://www.rocketleague.com/developer/stats-api#ChangePOV
499
+ */
500
+ changePov(opts: Partial<{
501
+ focus: Commands.ChangePov.Focus;
502
+ perspective: Commands.ChangePov.Perspective;
503
+ }>): void;
504
+ /**
505
+ * Loads and begins playback of a replay by file name or by file path.
506
+ * @param opts.fileName Name of the replay file to play. Takes precedence over Path when both are set.
507
+ * @param opts.path File path of a replay to play. Used when FileName is empty.
508
+ * @see https://www.rocketleague.com/developer/stats-api#LoadReplay
509
+ */
510
+ loadReplay(opts: Partial<{
511
+ fileName: string;
512
+ path: string;
513
+ }>): void;
514
+ /**
515
+ * Jumps replay playback to a target frame or time.
516
+ * @REPLAY
517
+ * @param opts.frame Target frame number. Takes precedence over timeSeconds when set.
518
+ * @param opts.timeSeconds Target time in seconds.
519
+ * @see https://www.rocketleague.com/developer/stats-api#SeekReplay
520
+ */
521
+ seekReplay(opts: Partial<{
522
+ frame: number;
523
+ timeSeconds: number;
524
+ }>): void;
525
+ /**
526
+ * Sets replay playback speed.
527
+ * @REPLAY
528
+ * @param speed Playback multiplier. 1.0 = normal, 0.5 = half speed, 2.0 = double. Must be ≥ 0.
529
+ * @see https://www.rocketleague.com/developer/stats-api#SetGameSpeed
530
+ */
531
+ setGameSpeed(speed: number): void;
532
+ /**
533
+ * Shows or hides the in-game HUD.
534
+ * @param visible true shows the HUD; false hides it.
535
+ * @see https://www.rocketleague.com/developer/stats-api#SetHUDVisibility
536
+ */
537
+ setHudVisibility(visible: boolean): void;
538
+ /**
539
+ * Pauses or unpauses the current match or replay.
540
+ * @param paused true pauses; false resumes.
541
+ * @see https://www.rocketleague.com/developer/stats-api#SetMatchPaused
542
+ */
543
+ setMatchPaused(paused: boolean): void;
544
+ }
545
+
546
+ export {
547
+ Client as default,
548
+ };
549
+
550
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var ConnectionError=class extends Error{},Connection=class{ws;onOpen;onMessage;onClose;onError;constructor(host,port,secure){let url=new URL(`ws://${host}`);url.port=port.toString(),url.protocol=secure?"wss:":"ws:",this.ws=new WebSocket(url),this.ws.onopen=e=>this.onOpen?.(e),this.ws.onclose=e=>this.onClose?.(e),this.ws.onmessage=e=>this.handleMessage(e)}handleMessage(e){try{let{Event:event,Data:dataStr}=JSON.parse(e.data);this.onMessage?.(event,dataStr)}catch(err){this.handleError("Failed to parse WebSocket message data",{cause:err});return}}handleError(msg,errorOptions){let err;if(typeof msg=="string")err=new ConnectionError(msg,errorOptions);else if(msg instanceof Error)msg instanceof ConnectionError?err=msg:err=new ConnectionError("Unhandled error",{cause:msg});else throw new TypeError("Error is not a string or Error instance");if(!this.onError)throw err;this.onError(err)}removeListeners(){this.onOpen=void 0,this.onMessage=void 0,this.onClose=void 0,this.onError=void 0}close(){this.ws.close()}isConnected(){return this.ws.readyState===WebSocket.OPEN}send(message){this.ws.send(JSON.stringify(message))}};var ClientError=class extends Error{},Client=class{options;socket;reconnectAttempts=0;reconnectTimeout=void 0;wasCloseCalled=!1;onSocketConnected;onSocketClosed;onError;onEvent;onUpdateState;onBallHit;onBoostPickup;onClockUpdatedSeconds;onCountdownBegin;onCrossbarHit;onGoalReplayStart;onGoalReplayWillEnd;onGoalReplayEnd;onGoalScored;onMatchCreated;onMatchInitialized;onMatchPaused;onMatchUnpaused;onMatchEnded;onMatchDestroyed;onPlayerJoined;onPlayerLeft;onPodiumStart;onReplayCreated;onRoundStarted;onStatfeedEvent;constructor(opts={}){this.options={host:opts.host??"127.0.0.1",port:opts.port??49124,secure:opts.secure??!1}}handleOpen(){this.reconnectAttempts=0,clearTimeout(this.reconnectTimeout),this.onSocketConnected?.()}handleClose(){if(this.socket?.removeListeners(),this.socket=void 0,this.onSocketClosed?.({wasCloseCalled:this.wasCloseCalled}),this.wasCloseCalled)return;this.reconnectAttempts++;let jitter=Math.random()*.8+.2,minDelay=128,maxDelay=8192,t=Math.min(2**this.reconnectAttempts,maxDelay)/maxDelay,range=maxDelay-minDelay,delay=Math.floor(minDelay+jitter*t*range);this.reconnectTimeout=setTimeout(()=>this.connect(),delay)}handleError(msg,errorOptions){let err;if(typeof msg=="string")err=new ClientError(msg,errorOptions);else if(msg instanceof Error)msg instanceof ConnectionError?err=new ClientError(`Connection error: ${msg.message}`,{cause:msg}):msg instanceof ClientError?err=msg:err=new ClientError(`Unhandled error: [${msg.name}] ${msg.message}`,{cause:msg});else throw new TypeError("Error is not a string or Error instance",{cause:msg});if(!this.onError)throw err;this.onError(err)}handleMessage(event,dataStr){let data;try{data=JSON.parse(dataStr)}catch(err){this.handleError("Failed to parse event data",{cause:err});return}if(!data||typeof data!="object"){this.handleError("Data did not parse as an object",{cause:{data}});return}switch(event!=="UpdateState"&&this.onEvent?.(event,data),event){case"UpdateState":{this.onUpdateState?.(data);break}case"ClockUpdatedSeconds":{this.onClockUpdatedSeconds?.(data);break}case"BallHit":{this.onBallHit?.(data);break}case"StatfeedEvent":{this.onStatfeedEvent?.(data);break}case"CrossbarHit":{this.onCrossbarHit?.(data);break}case"GoalScored":{this.onGoalScored?.(data);break}case"BoostPickup":{this.onBoostPickup?.(data);break}case"GoalReplayStart":{this.onGoalReplayStart?.(data);break}case"GoalReplayWillEnd":{this.onGoalReplayWillEnd?.(data);break}case"GoalReplayEnd":{this.onGoalReplayEnd?.(data);break}case"PlayerJoined":{this.onPlayerJoined?.(data);break}case"PlayerLeft":{this.onPlayerLeft?.(data);break}case"RoundStarted":{this.onRoundStarted?.(data);break}case"CountdownBegin":{this.onCountdownBegin?.(data);break}case"PodiumStart":{this.onPodiumStart?.(data);break}case"MatchCreated":{this.onMatchCreated?.(data);break}case"MatchInitialized":{this.onMatchInitialized?.(data);break}case"MatchPaused":{this.onMatchPaused?.(data);break}case"MatchUnpaused":{this.onMatchUnpaused?.(data);break}case"MatchEnded":{this.onMatchEnded?.(data);break}case"MatchDestroyed":{this.onMatchDestroyed?.(data);break}case"ReplayCreated":{this.onReplayCreated?.(data);break}default:throw new Error(`Unhandled event "${event}"`)}}sendCommand(command,data){if(!this.isConnected())throw new ClientError("Not connected");return this.socket.send({Command:command,Data:data})}connect(){this.socket||(clearTimeout(this.reconnectTimeout),this.wasCloseCalled=!1,this.socket=new Connection(this.options.host,this.options.port,this.options.secure),this.socket.onOpen=()=>this.handleOpen(),this.socket.onClose=_e=>this.handleClose(),this.socket.onError=err=>this.handleError(err),this.socket.onMessage=(event,dataStr)=>this.handleMessage(event,dataStr))}close(){clearTimeout(this.reconnectTimeout),this.wasCloseCalled=!0,this.socket?.close(),this.socket=void 0}isConnected(){return this.socket?.isConnected()??!1}changePov(opts){return this.sendCommand("ChangePOV",{Focus:opts.focus,Perspective:opts.perspective})}loadReplay(opts){return this.sendCommand("LoadReplay",{FileName:opts.fileName,Path:opts.path})}seekReplay(opts){return this.sendCommand("SeekReplay",{Frame:opts.frame,TimeSeconds:opts.timeSeconds})}setGameSpeed(speed){return this.sendCommand("SetGameSpeed",{Speed:speed})}setHudVisibility(visible){return this.sendCommand("SetHUDVisibility",{bVisible:visible})}setMatchPaused(paused){return this.sendCommand("SetMatchPaused",{bPaused:paused})}};export{ClientError,Connection,ConnectionError,Client as default};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/Connection.ts", "../src/Client.ts"],
4
+ "mappings": "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",
5
+ "names": []
6
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@alca/rocket-league-stats-api",
3
+ "version": "0.1.0",
4
+ "description": "Connect to the Rocket League's Stats API over WebSocket",
5
+ "keywords": [
6
+ "rocket-league",
7
+ "stats-api",
8
+ "typescript",
9
+ "websocket"
10
+ ],
11
+ "license": "MIT",
12
+ "author": "Alca <jacob@alca.tv>",
13
+ "homepage": "https://github.com/alcadesign/rocket-league-stats-api#readme",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/alcadesign/rocket-league-stats-api.git"
17
+ },
18
+ "type": "module",
19
+ "main": "./dist/index.js",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "import": "./dist/index.js",
25
+ "types": "./dist/index.d.ts"
26
+ }
27
+ },
28
+ "scripts": {
29
+ "test": "echo \"Error: no test specified\" && exit 1",
30
+ "build": "node scripts/build.ts && dts-bundle-generator -o dist/index.d.ts src/index.ts --no-banner --silent"
31
+ },
32
+ "devDependencies": {
33
+ "dts-bundle-generator": "^9.5.1",
34
+ "esbuild": "^0.28.2",
35
+ "typescript": "^6.0.3"
36
+ },
37
+ "allowScripts": {
38
+ "esbuild@0.28.2": true
39
+ },
40
+ "files": [
41
+ "dist"
42
+ ]
43
+ }