rollback-netcode 0.0.4
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 +21 -0
- package/README.md +140 -0
- package/dist/debug.d.ts +29 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +56 -0
- package/dist/debug.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/encoding.d.ts +80 -0
- package/dist/protocol/encoding.d.ts.map +1 -0
- package/dist/protocol/encoding.js +992 -0
- package/dist/protocol/encoding.js.map +1 -0
- package/dist/protocol/messages.d.ts +271 -0
- package/dist/protocol/messages.d.ts.map +1 -0
- package/dist/protocol/messages.js +114 -0
- package/dist/protocol/messages.js.map +1 -0
- package/dist/rollback/engine.d.ts +261 -0
- package/dist/rollback/engine.d.ts.map +1 -0
- package/dist/rollback/engine.js +543 -0
- package/dist/rollback/engine.js.map +1 -0
- package/dist/rollback/input-buffer.d.ts +225 -0
- package/dist/rollback/input-buffer.d.ts.map +1 -0
- package/dist/rollback/input-buffer.js +483 -0
- package/dist/rollback/input-buffer.js.map +1 -0
- package/dist/rollback/snapshot-buffer.d.ts +119 -0
- package/dist/rollback/snapshot-buffer.d.ts.map +1 -0
- package/dist/rollback/snapshot-buffer.js +256 -0
- package/dist/rollback/snapshot-buffer.js.map +1 -0
- package/dist/session/desync-manager.d.ts +106 -0
- package/dist/session/desync-manager.d.ts.map +1 -0
- package/dist/session/desync-manager.js +136 -0
- package/dist/session/desync-manager.js.map +1 -0
- package/dist/session/lag-monitor.d.ts +69 -0
- package/dist/session/lag-monitor.d.ts.map +1 -0
- package/dist/session/lag-monitor.js +74 -0
- package/dist/session/lag-monitor.js.map +1 -0
- package/dist/session/message-builders.d.ts +86 -0
- package/dist/session/message-builders.d.ts.map +1 -0
- package/dist/session/message-builders.js +199 -0
- package/dist/session/message-builders.js.map +1 -0
- package/dist/session/message-router.d.ts +61 -0
- package/dist/session/message-router.d.ts.map +1 -0
- package/dist/session/message-router.js +105 -0
- package/dist/session/message-router.js.map +1 -0
- package/dist/session/player-manager.d.ts +100 -0
- package/dist/session/player-manager.d.ts.map +1 -0
- package/dist/session/player-manager.js +160 -0
- package/dist/session/player-manager.js.map +1 -0
- package/dist/session/session.d.ts +379 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +1294 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/topology.d.ts +66 -0
- package/dist/session/topology.d.ts.map +1 -0
- package/dist/session/topology.js +72 -0
- package/dist/session/topology.js.map +1 -0
- package/dist/transport/adapter.d.ts +99 -0
- package/dist/transport/adapter.d.ts.map +1 -0
- package/dist/transport/adapter.js +8 -0
- package/dist/transport/adapter.js.map +1 -0
- package/dist/transport/local.d.ts +192 -0
- package/dist/transport/local.d.ts.map +1 -0
- package/dist/transport/local.js +435 -0
- package/dist/transport/local.js.map +1 -0
- package/dist/transport/transforming.d.ts +177 -0
- package/dist/transport/transforming.d.ts.map +1 -0
- package/dist/transport/transforming.js +407 -0
- package/dist/transport/transforming.js.map +1 -0
- package/dist/transport/webrtc.d.ts +285 -0
- package/dist/transport/webrtc.d.ts.map +1 -0
- package/dist/transport/webrtc.js +734 -0
- package/dist/transport/webrtc.js.map +1 -0
- package/dist/types.d.ts +394 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +256 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +59 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +93 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/package.json +61 -0
package/dist/types.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the rollback netcode library.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Minimum valid tick value (-1 represents initial state before tick 0).
|
|
6
|
+
*/
|
|
7
|
+
export const TICK_MIN = -1;
|
|
8
|
+
/**
|
|
9
|
+
* Helper to create a Tick value.
|
|
10
|
+
* This is a simple cast - use validateTick() for validation.
|
|
11
|
+
*
|
|
12
|
+
* @param n - The tick number
|
|
13
|
+
*/
|
|
14
|
+
export function asTick(n) {
|
|
15
|
+
return n;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validate that a number is a valid tick value.
|
|
19
|
+
*
|
|
20
|
+
* @param n - The number to validate
|
|
21
|
+
* @throws ValidationError if the value is not a valid tick (must be integer >= -1)
|
|
22
|
+
*/
|
|
23
|
+
export function validateTick(n) {
|
|
24
|
+
if (!Number.isInteger(n) || n < TICK_MIN) {
|
|
25
|
+
throw new ValidationError(`Tick must be an integer >= ${TICK_MIN}`, "tick", n);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Helper to create a PlayerId value.
|
|
30
|
+
* This is a simple cast - use validatePlayerId() for validation.
|
|
31
|
+
*
|
|
32
|
+
* @param s - The player ID string
|
|
33
|
+
*/
|
|
34
|
+
export function asPlayerId(s) {
|
|
35
|
+
return s;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate that a string is a valid player ID.
|
|
39
|
+
*
|
|
40
|
+
* @param s - The string to validate
|
|
41
|
+
* @throws ValidationError if the value is not a valid player ID (must be non-empty string)
|
|
42
|
+
*/
|
|
43
|
+
export function validatePlayerId(s) {
|
|
44
|
+
if (typeof s !== "string" || s.length === 0) {
|
|
45
|
+
throw new ValidationError("Player ID must be a non-empty string", "playerId", s);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Convert a PlayerId to a transport peer ID string.
|
|
50
|
+
*
|
|
51
|
+
* In the current implementation, these are the same value,
|
|
52
|
+
* but this function makes the intent explicit in code.
|
|
53
|
+
*/
|
|
54
|
+
export function playerIdToPeerId(playerId) {
|
|
55
|
+
return playerId;
|
|
56
|
+
}
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// Session Configuration
|
|
59
|
+
// =============================================================================
|
|
60
|
+
/**
|
|
61
|
+
* Network topology for the session.
|
|
62
|
+
*/
|
|
63
|
+
export var Topology;
|
|
64
|
+
(function (Topology) {
|
|
65
|
+
/** All players connect to each other directly */
|
|
66
|
+
Topology[Topology["Mesh"] = 0] = "Mesh";
|
|
67
|
+
/** All players connect through a host */
|
|
68
|
+
Topology[Topology["Star"] = 1] = "Star";
|
|
69
|
+
})(Topology || (Topology = {}));
|
|
70
|
+
/**
|
|
71
|
+
* Authority model for desync resolution.
|
|
72
|
+
*/
|
|
73
|
+
export var DesyncAuthority;
|
|
74
|
+
(function (DesyncAuthority) {
|
|
75
|
+
/** Host is authoritative; guests send hashes to host only */
|
|
76
|
+
DesyncAuthority[DesyncAuthority["Host"] = 0] = "Host";
|
|
77
|
+
/** Each peer compares independently */
|
|
78
|
+
DesyncAuthority[DesyncAuthority["Peer"] = 1] = "Peer";
|
|
79
|
+
})(DesyncAuthority || (DesyncAuthority = {}));
|
|
80
|
+
/**
|
|
81
|
+
* Player role in the session.
|
|
82
|
+
*/
|
|
83
|
+
export var PlayerRole;
|
|
84
|
+
(function (PlayerRole) {
|
|
85
|
+
/** Active player who sends inputs */
|
|
86
|
+
PlayerRole[PlayerRole["Player"] = 0] = "Player";
|
|
87
|
+
/** Observer who runs simulation but doesn't send inputs */
|
|
88
|
+
PlayerRole[PlayerRole["Spectator"] = 1] = "Spectator";
|
|
89
|
+
})(PlayerRole || (PlayerRole = {}));
|
|
90
|
+
/**
|
|
91
|
+
* Reason for pausing the game.
|
|
92
|
+
*/
|
|
93
|
+
export var PauseReason;
|
|
94
|
+
(function (PauseReason) {
|
|
95
|
+
PauseReason[PauseReason["PlayerRequest"] = 0] = "PlayerRequest";
|
|
96
|
+
PauseReason[PauseReason["PlayerDisconnect"] = 1] = "PlayerDisconnect";
|
|
97
|
+
PauseReason[PauseReason["ExcessiveLag"] = 2] = "ExcessiveLag";
|
|
98
|
+
})(PauseReason || (PauseReason = {}));
|
|
99
|
+
/**
|
|
100
|
+
* Default session configuration values.
|
|
101
|
+
*/
|
|
102
|
+
export const DEFAULT_SESSION_CONFIG = {
|
|
103
|
+
tickRate: 60,
|
|
104
|
+
maxPlayers: 4,
|
|
105
|
+
topology: Topology.Star,
|
|
106
|
+
snapshotHistorySize: 120,
|
|
107
|
+
maxSpeculationTicks: 60,
|
|
108
|
+
hashInterval: 60,
|
|
109
|
+
disconnectTimeout: 5000,
|
|
110
|
+
debug: false,
|
|
111
|
+
desyncAuthority: DesyncAuthority.Peer,
|
|
112
|
+
lagReportThreshold: 30,
|
|
113
|
+
inputRedundancy: 3,
|
|
114
|
+
joinRateLimitRequests: 3,
|
|
115
|
+
joinRateLimitWindowMs: 10000,
|
|
116
|
+
};
|
|
117
|
+
/** Maximum allowed players in a session */
|
|
118
|
+
export const MAX_PLAYERS_LIMIT = 16;
|
|
119
|
+
/**
|
|
120
|
+
* Validate session configuration values.
|
|
121
|
+
* @throws ValidationError if any values are invalid
|
|
122
|
+
*/
|
|
123
|
+
export function validateSessionConfig(config) {
|
|
124
|
+
if (config.tickRate <= 0) {
|
|
125
|
+
throw new ValidationError("tickRate must be greater than 0", "tickRate", config.tickRate);
|
|
126
|
+
}
|
|
127
|
+
if (config.maxPlayers < 1 || config.maxPlayers > MAX_PLAYERS_LIMIT) {
|
|
128
|
+
throw new ValidationError(`maxPlayers must be between 1 and ${MAX_PLAYERS_LIMIT}`, "maxPlayers", config.maxPlayers);
|
|
129
|
+
}
|
|
130
|
+
if (config.maxSpeculationTicks <= 0) {
|
|
131
|
+
throw new ValidationError("maxSpeculationTicks must be greater than 0", "maxSpeculationTicks", config.maxSpeculationTicks);
|
|
132
|
+
}
|
|
133
|
+
if (config.snapshotHistorySize < config.maxSpeculationTicks) {
|
|
134
|
+
throw new ValidationError("snapshotHistorySize must be >= maxSpeculationTicks to support rollback", "snapshotHistorySize", config.snapshotHistorySize);
|
|
135
|
+
}
|
|
136
|
+
if (config.hashInterval <= 0) {
|
|
137
|
+
throw new ValidationError("hashInterval must be greater than 0", "hashInterval", config.hashInterval);
|
|
138
|
+
}
|
|
139
|
+
if (config.disconnectTimeout <= 0) {
|
|
140
|
+
throw new ValidationError("disconnectTimeout must be greater than 0", "disconnectTimeout", config.disconnectTimeout);
|
|
141
|
+
}
|
|
142
|
+
if (config.topology !== Topology.Mesh && config.topology !== Topology.Star) {
|
|
143
|
+
throw new ValidationError("topology must be Topology.Mesh or Topology.Star", "topology", config.topology);
|
|
144
|
+
}
|
|
145
|
+
if (config.desyncAuthority !== DesyncAuthority.Host &&
|
|
146
|
+
config.desyncAuthority !== DesyncAuthority.Peer) {
|
|
147
|
+
throw new ValidationError("desyncAuthority must be DesyncAuthority.Host or DesyncAuthority.Peer", "desyncAuthority", config.desyncAuthority);
|
|
148
|
+
}
|
|
149
|
+
if (config.lagReportThreshold < 0) {
|
|
150
|
+
throw new ValidationError("lagReportThreshold must be >= 0", "lagReportThreshold", config.lagReportThreshold);
|
|
151
|
+
}
|
|
152
|
+
if (config.inputRedundancy < 1) {
|
|
153
|
+
throw new ValidationError("inputRedundancy must be >= 1", "inputRedundancy", config.inputRedundancy);
|
|
154
|
+
}
|
|
155
|
+
if (config.joinRateLimitRequests < 1) {
|
|
156
|
+
throw new ValidationError("joinRateLimitRequests must be >= 1", "joinRateLimitRequests", config.joinRateLimitRequests);
|
|
157
|
+
}
|
|
158
|
+
if (config.joinRateLimitWindowMs <= 0) {
|
|
159
|
+
throw new ValidationError("joinRateLimitWindowMs must be > 0", "joinRateLimitWindowMs", config.joinRateLimitWindowMs);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// =============================================================================
|
|
163
|
+
// Session State
|
|
164
|
+
// =============================================================================
|
|
165
|
+
/**
|
|
166
|
+
* Possible states of a session.
|
|
167
|
+
*/
|
|
168
|
+
export var SessionState;
|
|
169
|
+
(function (SessionState) {
|
|
170
|
+
SessionState[SessionState["Disconnected"] = 0] = "Disconnected";
|
|
171
|
+
SessionState[SessionState["Connecting"] = 1] = "Connecting";
|
|
172
|
+
SessionState[SessionState["Lobby"] = 2] = "Lobby";
|
|
173
|
+
SessionState[SessionState["Playing"] = 3] = "Playing";
|
|
174
|
+
SessionState[SessionState["Paused"] = 4] = "Paused";
|
|
175
|
+
})(SessionState || (SessionState = {}));
|
|
176
|
+
/**
|
|
177
|
+
* Default input predictor that repeats the last confirmed input,
|
|
178
|
+
* or returns an empty Uint8Array if no input is available.
|
|
179
|
+
*/
|
|
180
|
+
export const DEFAULT_INPUT_PREDICTOR = {
|
|
181
|
+
predict(_playerId, _tick, lastConfirmed) {
|
|
182
|
+
return lastConfirmed ?? new Uint8Array(0);
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
// =============================================================================
|
|
186
|
+
// Player Information
|
|
187
|
+
// =============================================================================
|
|
188
|
+
/**
|
|
189
|
+
* Connection state of a player.
|
|
190
|
+
*/
|
|
191
|
+
export var PlayerConnectionState;
|
|
192
|
+
(function (PlayerConnectionState) {
|
|
193
|
+
PlayerConnectionState[PlayerConnectionState["Connecting"] = 0] = "Connecting";
|
|
194
|
+
PlayerConnectionState[PlayerConnectionState["Connected"] = 1] = "Connected";
|
|
195
|
+
PlayerConnectionState[PlayerConnectionState["Disconnected"] = 2] = "Disconnected";
|
|
196
|
+
})(PlayerConnectionState || (PlayerConnectionState = {}));
|
|
197
|
+
// =============================================================================
|
|
198
|
+
// Errors
|
|
199
|
+
// =============================================================================
|
|
200
|
+
/**
|
|
201
|
+
* Source of an error in the rollback netcode system.
|
|
202
|
+
*/
|
|
203
|
+
export var ErrorSource;
|
|
204
|
+
(function (ErrorSource) {
|
|
205
|
+
ErrorSource[ErrorSource["Engine"] = 0] = "Engine";
|
|
206
|
+
ErrorSource[ErrorSource["Transport"] = 1] = "Transport";
|
|
207
|
+
ErrorSource[ErrorSource["Protocol"] = 2] = "Protocol";
|
|
208
|
+
ErrorSource[ErrorSource["Session"] = 3] = "Session";
|
|
209
|
+
})(ErrorSource || (ErrorSource = {}));
|
|
210
|
+
/**
|
|
211
|
+
* Error thrown when a rollback operation fails.
|
|
212
|
+
*/
|
|
213
|
+
export class RollbackError extends Error {
|
|
214
|
+
tick;
|
|
215
|
+
constructor(message, tick, originalError) {
|
|
216
|
+
super(message, { cause: originalError });
|
|
217
|
+
this.tick = tick;
|
|
218
|
+
this.name = "RollbackError";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Error thrown when session validation fails.
|
|
223
|
+
*/
|
|
224
|
+
export class ValidationError extends Error {
|
|
225
|
+
field;
|
|
226
|
+
value;
|
|
227
|
+
constructor(message, field, value) {
|
|
228
|
+
super(message);
|
|
229
|
+
this.field = field;
|
|
230
|
+
this.value = value;
|
|
231
|
+
this.name = "ValidationError";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Error thrown when a game callback fails.
|
|
236
|
+
* Wraps the original error with context about which operation failed and at which tick.
|
|
237
|
+
*/
|
|
238
|
+
export class GameError extends Error {
|
|
239
|
+
operation;
|
|
240
|
+
tick;
|
|
241
|
+
constructor(
|
|
242
|
+
/** The game operation that failed */
|
|
243
|
+
operation,
|
|
244
|
+
/** The tick at which the error occurred */
|
|
245
|
+
tick,
|
|
246
|
+
/** The original error from the game */
|
|
247
|
+
cause) {
|
|
248
|
+
super(`Game ${operation}() failed at tick ${tick}: ${cause.message}`, {
|
|
249
|
+
cause,
|
|
250
|
+
});
|
|
251
|
+
this.operation = operation;
|
|
252
|
+
this.tick = tick;
|
|
253
|
+
this.name = "GameError";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic rate limiter for request throttling.
|
|
3
|
+
*
|
|
4
|
+
* Uses a sliding window algorithm to track requests per key.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for the rate limiter.
|
|
8
|
+
*/
|
|
9
|
+
export interface RateLimiterConfig {
|
|
10
|
+
/** Maximum number of requests allowed within the window */
|
|
11
|
+
maxRequests: number;
|
|
12
|
+
/** Time window in milliseconds */
|
|
13
|
+
windowMs: number;
|
|
14
|
+
/** Optional custom time source for testing (defaults to Date.now) */
|
|
15
|
+
getNow?: () => number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A generic rate limiter that tracks requests per key.
|
|
19
|
+
*
|
|
20
|
+
* Uses a sliding window approach: requests older than windowMs are discarded.
|
|
21
|
+
*/
|
|
22
|
+
export declare class RateLimiter {
|
|
23
|
+
private readonly maxRequests;
|
|
24
|
+
private readonly windowMs;
|
|
25
|
+
private readonly requests;
|
|
26
|
+
private readonly getNow;
|
|
27
|
+
constructor(config: RateLimiterConfig);
|
|
28
|
+
/**
|
|
29
|
+
* Check if a key is currently rate limited.
|
|
30
|
+
*
|
|
31
|
+
* @param key - The key to check (e.g., peer ID)
|
|
32
|
+
* @returns true if the key has exceeded the rate limit
|
|
33
|
+
*/
|
|
34
|
+
isLimited(key: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Record a request for a key.
|
|
37
|
+
*
|
|
38
|
+
* @param key - The key to record (e.g., peer ID)
|
|
39
|
+
*/
|
|
40
|
+
record(key: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Check if limited and record in one operation.
|
|
43
|
+
* Returns true if the request should be rejected.
|
|
44
|
+
*
|
|
45
|
+
* @param key - The key to check and record
|
|
46
|
+
* @returns true if the key is rate limited (request should be rejected)
|
|
47
|
+
*/
|
|
48
|
+
checkAndRecord(key: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Clean up stale entries to prevent memory growth.
|
|
51
|
+
* Call this periodically (e.g., every minute).
|
|
52
|
+
*/
|
|
53
|
+
cleanup(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Clear all rate limit entries.
|
|
56
|
+
*/
|
|
57
|
+
clear(): void;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,WAAW;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,MAAM,EAAE,iBAAiB;IAYrC;;;;;OAKG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAW/B;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMzB;;;;;;OAMG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAepC;;;OAGG;IACH,OAAO,IAAI,IAAI;IAcf;;OAEG;IACH,KAAK,IAAI,IAAI;CAGb"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic rate limiter for request throttling.
|
|
3
|
+
*
|
|
4
|
+
* Uses a sliding window algorithm to track requests per key.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* A generic rate limiter that tracks requests per key.
|
|
8
|
+
*
|
|
9
|
+
* Uses a sliding window approach: requests older than windowMs are discarded.
|
|
10
|
+
*/
|
|
11
|
+
export class RateLimiter {
|
|
12
|
+
maxRequests;
|
|
13
|
+
windowMs;
|
|
14
|
+
requests = new Map();
|
|
15
|
+
getNow;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
if (config.maxRequests <= 0) {
|
|
18
|
+
throw new Error("maxRequests must be greater than 0");
|
|
19
|
+
}
|
|
20
|
+
if (config.windowMs <= 0) {
|
|
21
|
+
throw new Error("windowMs must be greater than 0");
|
|
22
|
+
}
|
|
23
|
+
this.maxRequests = config.maxRequests;
|
|
24
|
+
this.windowMs = config.windowMs;
|
|
25
|
+
this.getNow = config.getNow ?? (() => Date.now());
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if a key is currently rate limited.
|
|
29
|
+
*
|
|
30
|
+
* @param key - The key to check (e.g., peer ID)
|
|
31
|
+
* @returns true if the key has exceeded the rate limit
|
|
32
|
+
*/
|
|
33
|
+
isLimited(key) {
|
|
34
|
+
const now = this.getNow();
|
|
35
|
+
const timestamps = this.requests.get(key) ?? [];
|
|
36
|
+
// Filter to only recent timestamps within the window
|
|
37
|
+
const recentTimestamps = timestamps.filter((t) => now - t < this.windowMs);
|
|
38
|
+
this.requests.set(key, recentTimestamps);
|
|
39
|
+
return recentTimestamps.length >= this.maxRequests;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Record a request for a key.
|
|
43
|
+
*
|
|
44
|
+
* @param key - The key to record (e.g., peer ID)
|
|
45
|
+
*/
|
|
46
|
+
record(key) {
|
|
47
|
+
const timestamps = this.requests.get(key) ?? [];
|
|
48
|
+
timestamps.push(this.getNow());
|
|
49
|
+
this.requests.set(key, timestamps);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if limited and record in one operation.
|
|
53
|
+
* Returns true if the request should be rejected.
|
|
54
|
+
*
|
|
55
|
+
* @param key - The key to check and record
|
|
56
|
+
* @returns true if the key is rate limited (request should be rejected)
|
|
57
|
+
*/
|
|
58
|
+
checkAndRecord(key) {
|
|
59
|
+
const now = this.getNow();
|
|
60
|
+
const timestamps = this.requests.get(key) ?? [];
|
|
61
|
+
const recentTimestamps = timestamps.filter((t) => now - t < this.windowMs);
|
|
62
|
+
if (recentTimestamps.length >= this.maxRequests) {
|
|
63
|
+
this.requests.set(key, recentTimestamps);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
recentTimestamps.push(now);
|
|
67
|
+
this.requests.set(key, recentTimestamps);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clean up stale entries to prevent memory growth.
|
|
72
|
+
* Call this periodically (e.g., every minute).
|
|
73
|
+
*/
|
|
74
|
+
cleanup() {
|
|
75
|
+
const now = this.getNow();
|
|
76
|
+
for (const [key, timestamps] of this.requests) {
|
|
77
|
+
const recentTimestamps = timestamps.filter((t) => now - t < this.windowMs);
|
|
78
|
+
if (recentTimestamps.length === 0) {
|
|
79
|
+
this.requests.delete(key);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.requests.set(key, recentTimestamps);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Clear all rate limit entries.
|
|
88
|
+
*/
|
|
89
|
+
clear() {
|
|
90
|
+
this.requests.clear();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rollback-netcode",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "P2P rollback netcode library for browser-based multiplayer games",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": null
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/someusername6/rollback-netcode.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://someusername6.github.io/rollback-netcode",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"build:demo": "node esbuild.demo.mjs",
|
|
27
|
+
"serve:demo": "npm run build:demo && npx http-server examples/local-transport -p 3000 -c-1",
|
|
28
|
+
"test": "node --import tsx --test tests/*.test.ts tests/**/*.test.ts",
|
|
29
|
+
"lint": "biome check src/",
|
|
30
|
+
"format": "biome format --write src/",
|
|
31
|
+
"benchmark": "node --import tsx src/benchmark.ts",
|
|
32
|
+
"prepublishOnly": "npm run clean && npm run build && npm run lint && npm test"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"netcode",
|
|
36
|
+
"rollback",
|
|
37
|
+
"multiplayer",
|
|
38
|
+
"webrtc",
|
|
39
|
+
"p2p",
|
|
40
|
+
"game",
|
|
41
|
+
"networking",
|
|
42
|
+
"ggpo"
|
|
43
|
+
],
|
|
44
|
+
"author": "someusername6",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@biomejs/biome": "^1.9.0",
|
|
48
|
+
"@types/node": "^25.0.10",
|
|
49
|
+
"@types/pako": "^2.0.4",
|
|
50
|
+
"esbuild": "^0.27.2",
|
|
51
|
+
"tsx": "^4.21.0",
|
|
52
|
+
"typescript": "^5.6.0"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22.0.0"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"pako": "^2.1.0"
|
|
59
|
+
},
|
|
60
|
+
"packageManager": "npm@11.4.1+sha512.fcee43884166b6f9c5d04535fb95650e9708b6948a1f797eddf40e9778646778a518dfa32651b1c62ff36f4ac42becf177ca46ca27d53f24b539190c8d91802b"
|
|
61
|
+
}
|