erlc-v2 1.0.0-beta.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,17 @@
1
+ ERLC Wrapper Custom License
2
+ Version 1.0
3
+ Copyright (c) 2026
4
+
5
+ Permission is granted to any person obtaining a copy of this software to:
6
+
7
+ 1. Use the software for any purpose.
8
+ 2. Modify the software for personal, internal, educational, or commercial use.
9
+ 3. Distribute compiled applications or systems that include modified or unmodified portions of this software, provided attribution is kept in documentation or notices.
10
+
11
+ Restrictions:
12
+
13
+ 1. You may not claim authorship or original ownership of this software.
14
+ 2. You may not republish the original source code, in whole or substantial part, as your own package, template, or standalone repository.
15
+ 3. You may not remove this license text from original source files when redistributing source.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,286 @@
1
+ # erlc-v2
2
+
3
+ JavaScript client for the ER:LC API v2.
4
+
5
+ Built for Node 18+ with no runtime dependencies.
6
+
7
+ ## Beta Notice
8
+
9
+ This wrapper is in beta (`1.0.0-beta.x`) and is not guaranteed to work 100% in all environments or API states.
10
+
11
+ ## Responsibility and API Safety
12
+
13
+ You are responsible for how you use this wrapper and API key(s). If you use it recklessly (for example, aggressive request spam, ignoring rate limits, or abusive automation) and get rate-limited, blocked, or banned by PRC/Cloudflare, that is on you.
14
+
15
+ This project is provided as-is. Always monitor your integration and respect PRC API rules and headers.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install erlc-v2
21
+ ```
22
+
23
+ ## Quick Start (CommonJS)
24
+
25
+ ```js
26
+ const { Client } = require("erlc-v2");
27
+
28
+ const client = new Client({
29
+ serverKey: "YOUR_SERVER_KEY",
30
+ // globalKey: "YOUR_GLOBAL_KEY", // optional
31
+ });
32
+
33
+ client.on("disconnect", ({ reason, error }) => {
34
+ console.error("Disconnected:", reason, error?.message);
35
+ });
36
+
37
+ async function main() {
38
+ const snapshot = await client.server.fetch({
39
+ players: true,
40
+ staff: true,
41
+ queue: true,
42
+ });
43
+
44
+ console.log("Server:", snapshot.name);
45
+ console.log("Players:", `${snapshot.currentPlayers}/${snapshot.maxPlayers}`);
46
+ console.log("Players payload:", snapshot.players.length);
47
+ }
48
+
49
+ main()
50
+ .catch(console.error)
51
+ .finally(() => client.destroy());
52
+ ```
53
+
54
+ ## Quick Start (ESM)
55
+
56
+ ```js
57
+ import { Client } from "erlc-v2";
58
+
59
+ const client = new Client({
60
+ serverKey: "YOUR_SERVER_KEY",
61
+ });
62
+ ```
63
+
64
+ ## Local System Test Script
65
+
66
+ This repo includes a local test runner at `scripts/test-system.js`.
67
+
68
+ 1. Create `.env` in the project root:
69
+
70
+ ```env
71
+ ERLC_SERVER_KEY=your_server_key
72
+ # optional:
73
+ # ERLC_GLOBAL_KEY=your_global_key
74
+ ```
75
+
76
+ 2. Run:
77
+
78
+ ```bash
79
+ npm run test:system
80
+ ```
81
+
82
+ What it does:
83
+
84
+ - Fetches a full snapshot (`players`, `staff`, `joinLogs`, `queue`, `killLogs`, `commandLogs`, `modCalls`, `vehicles`)
85
+ - Prints the full raw payload
86
+ - Starts a live event monitor (`ready`, joins/leaves, kills, command logs, mod calls, queue/staff updates, disconnects, errors)
87
+ - Prints internal request/rate-limit debug logs
88
+
89
+ Useful flags:
90
+
91
+ ```bash
92
+ # one snapshot, then exit
93
+ npm run test:system -- --once
94
+
95
+ # live monitor with custom poll interval (ms)
96
+ npm run test:system -- --interval=3000
97
+
98
+ # execute a v1 command, then keep monitoring events
99
+ npm run test:system -- --command=":log recentban He was trolling!"
100
+ ```
101
+
102
+ If your server is empty, seeing `CurrentPlayers: 0` and empty arrays is normal.
103
+
104
+ ## Options
105
+
106
+ ```ts
107
+ new Client({
108
+ serverKey: string, // required
109
+ globalKey?: string, // optional
110
+ logging?: boolean, // default: false
111
+ logger?: { info, warn, error, debug },
112
+ cache?: {
113
+ enabled?: boolean, // default: true
114
+ ttlMs?: number, // default: 1500
115
+ maxSize?: number, // default: 500
116
+ },
117
+ rateLimit?: {
118
+ enabled?: boolean, // default: true
119
+ strictSerial?: boolean, // default: true (global one-at-a-time queue)
120
+ perBucketConcurrency?: number, // default: 1
121
+ globalConcurrency?: number, // default: 1
122
+ unauthorizedThreshold?: number, // default: 3
123
+ },
124
+ polling?: {
125
+ enabled?: boolean, // default: true
126
+ intervalMs?: number, // default: 2500 (min enforced: 250)
127
+ bypassCache?: boolean, // default: true
128
+ },
129
+ });
130
+ ```
131
+
132
+ ## API
133
+
134
+ Core:
135
+
136
+ - `await client.server.fetch(flags, requestOptions?)`
137
+ - `client.destroy()`
138
+ - `client.cache.clear()`
139
+
140
+ Convenience methods:
141
+
142
+ - `await client.players.list()`
143
+ - `await client.staff.list()`
144
+ - `await client.logs.kills()`
145
+ - `await client.logs.joins()`
146
+ - `await client.logs.commands()`
147
+ - `await client.logs.modCalls()`
148
+ - `await client.vehicles.list()`
149
+ - `await client.queue.get()`
150
+ - `await client.commands.execute(":h Hey everyone!")` (v1 endpoint)
151
+
152
+ ### Fetch Flags
153
+
154
+ - `players` -> `Players`
155
+ - `staff` -> `Staff`
156
+ - `joinLogs` -> `JoinLogs`
157
+ - `queue` -> `Queue`
158
+ - `killLogs` -> `KillLogs`
159
+ - `commandLogs` -> `CommandLogs`
160
+ - `modCalls` -> `ModCalls`
161
+ - `vehicles` -> `Vehicles`
162
+
163
+ ### Request Options
164
+
165
+ - `bypassCache?: boolean`
166
+ - `cacheTtlMs?: number`
167
+ - `dedupe?: boolean`
168
+
169
+ ### Command Execution (v1)
170
+
171
+ `client.commands.execute(command)` sends a POST request to `/v1/server/command`.
172
+ Command execution is FIFO-queued client-side, so commands run one-at-a-time in order.
173
+
174
+ Blocked by client policy (request will be rejected before hitting the API):
175
+
176
+ - `:view`
177
+ - `:to`
178
+ - `:tocar`
179
+ - `:toatv`
180
+ - `:logs`
181
+ - `:mods`
182
+ - `:admins`
183
+ - `helpers` / `:helpers`
184
+ - `:administrators`
185
+ - `:moderators`
186
+ - `:killlogs`
187
+ - `:kl`
188
+ - `:cmds`
189
+ - `:commands`
190
+
191
+ Example:
192
+
193
+ ```js
194
+ await client.commands.execute(":h Hey everyone!");
195
+ await client.commands.execute(":log recentban He was trolling!");
196
+ ```
197
+
198
+ ## Events
199
+
200
+ - `ready`
201
+ - `playerJoin`
202
+ - `playerLeave`
203
+ - `kill`
204
+ - `vehicleSpawn`
205
+ - `vehicleDespawn`
206
+ - `queueUpdate`
207
+ - `staffUpdate`
208
+ - `modCall`
209
+ - `commandLog`
210
+ - `logCommand` (only when command starts with `:log`)
211
+ - `serverUpdate`
212
+ - `error`
213
+ - `disconnect` (`{ reason, error }`)
214
+
215
+ Alias event names are also supported with `client.on(...)`:
216
+
217
+ - `onReady`
218
+ - `onJoin`
219
+ - `onLeave`
220
+ - `onKill`
221
+ - `onVehicleSpawn`
222
+ - `onVehicleDespawn`
223
+ - `onQueueUpdate`
224
+ - `onStaffUpdate`
225
+ - `onModCall`
226
+ - `onCommandLog`
227
+ - `onLogCommand`
228
+ - `onServerUpdate`
229
+ - `onError`
230
+ - `onDisconnect`
231
+
232
+ Shortcut methods are available too:
233
+
234
+ ```js
235
+ client.onJoin((payload) => console.log("join", payload));
236
+ client.onLeave((payload) => console.log("leave", payload));
237
+ client.onVehicleSpawn((payload) => console.log("spawn", payload));
238
+ client.onLogCommand(({ command, parsed }) => {
239
+ console.log("raw command:", command.Command);
240
+ console.log("keyword:", parsed.keyword);
241
+ console.log("args:", parsed.args);
242
+ });
243
+ ```
244
+
245
+ `logCommand` / `onLogCommand` fires when a command starts with `:log`.
246
+
247
+ Events are deduped per poll cycle so the same log entry is not emitted repeatedly.
248
+
249
+ ## Rate Limits
250
+
251
+ Requests are automatically bucketed using API response headers:
252
+
253
+ - `X-RateLimit-Bucket`
254
+ - `X-RateLimit-Limit`
255
+ - `X-RateLimit-Remaining`
256
+ - `X-RateLimit-Reset`
257
+
258
+ On `429`, the client blocks the affected bucket until retry time/reset.
259
+
260
+ By default, requests are strictly serialized (`strictSerial: true`) so this client does not send parallel requests.
261
+ This is intentionally conservative for anti-abuse/rate-limit safety.
262
+
263
+ ## Errors
264
+
265
+ The client normalizes errors into classes:
266
+
267
+ - `ERLCError`
268
+ - `ERLCHttpError`
269
+ - `ERLCAPIError`
270
+ - `RateLimitError`
271
+ - `KeyExpiredError` (`2002`)
272
+ - `KeyBannedError` (`2004`)
273
+ - `InvalidGlobalKeyError` (`2003`)
274
+ - `ServerOfflineError` (`3002`)
275
+ - `RestrictedError` (`9998`)
276
+ - `ModuleOutOfDateError` (`9999`)
277
+
278
+ Terminal key errors (`2002`, `2004`) trigger disconnect and stop polling.
279
+
280
+ Repeated `403` responses can also trigger disconnect (reason: `"unauthorized"`).
281
+
282
+ ## Notes
283
+
284
+ - API base URL: `https://api.policeroleplay.community`
285
+ - `Server-Key` is required for requests
286
+ - `Authorization` is optional (`globalKey`)
package/index.d.ts ADDED
@@ -0,0 +1,195 @@
1
+ import { EventEmitter } from "events";
2
+
3
+ export interface LoggerLike {
4
+ info?: (...args: any[]) => void;
5
+ warn?: (...args: any[]) => void;
6
+ error?: (...args: any[]) => void;
7
+ debug?: (...args: any[]) => void;
8
+ }
9
+
10
+ export interface CacheOptions {
11
+ enabled?: boolean;
12
+ ttlMs?: number;
13
+ maxSize?: number;
14
+ }
15
+
16
+ export interface RateLimitOptions {
17
+ enabled?: boolean;
18
+ strictSerial?: boolean;
19
+ perBucketConcurrency?: number;
20
+ globalConcurrency?: number;
21
+ unauthorizedThreshold?: number;
22
+ }
23
+
24
+ export interface PollingOptions {
25
+ enabled?: boolean;
26
+ intervalMs?: number;
27
+ bypassCache?: boolean;
28
+ }
29
+
30
+ export interface ClientOptions {
31
+ serverKey: string;
32
+ globalKey?: string;
33
+ logging?: boolean;
34
+ logger?: LoggerLike | null;
35
+ cache?: CacheOptions;
36
+ rateLimit?: RateLimitOptions;
37
+ polling?: PollingOptions;
38
+ }
39
+
40
+ export interface ServerFetchFlags {
41
+ players?: boolean;
42
+ staff?: boolean;
43
+ joinLogs?: boolean;
44
+ queue?: boolean;
45
+ killLogs?: boolean;
46
+ commandLogs?: boolean;
47
+ modCalls?: boolean;
48
+ vehicles?: boolean;
49
+ }
50
+
51
+ export interface RequestOptions {
52
+ bypassCache?: boolean;
53
+ cacheTtlMs?: number;
54
+ dedupe?: boolean;
55
+ }
56
+
57
+ export interface CommandRequestOptions {
58
+ dedupe?: boolean;
59
+ }
60
+
61
+ export interface CommandExecuteResult {
62
+ ok: boolean;
63
+ status: number;
64
+ endpoint: string;
65
+ bucket: string;
66
+ rateLimit: {
67
+ limit: number;
68
+ remaining: number;
69
+ resetEpochMs: number | null;
70
+ };
71
+ }
72
+
73
+ export interface ServerResponse {
74
+ name: string | null;
75
+ ownerId: number | null;
76
+ coOwnerIds: number[];
77
+ currentPlayers: number;
78
+ maxPlayers: number;
79
+ joinKey: string | null;
80
+ accVerifiedReq: string | null;
81
+ teamBalance: boolean | null;
82
+ players: any[];
83
+ staff: any;
84
+ joinLogs: any[];
85
+ queue: any[];
86
+ killLogs: any[];
87
+ commandLogs: any[];
88
+ modCalls: any[];
89
+ vehicles: any[];
90
+ raw: Record<string, any>;
91
+ meta: {
92
+ status: number;
93
+ endpoint: string;
94
+ bucket: string;
95
+ rateLimit: {
96
+ limit: number;
97
+ remaining: number;
98
+ resetEpochMs: number | null;
99
+ };
100
+ };
101
+ }
102
+
103
+ export class ERLCError extends Error {
104
+ details: Record<string, any>;
105
+ }
106
+
107
+ export class ERLCHttpError extends ERLCError {
108
+ status: number | null;
109
+ endpoint: string | null;
110
+ bucket: string | null;
111
+ body: any;
112
+ }
113
+
114
+ export class ERLCAPIError extends ERLCHttpError {
115
+ errorCode: number;
116
+ apiMessage: string;
117
+ }
118
+
119
+ export class RateLimitError extends ERLCAPIError {
120
+ retryAfterMs: number;
121
+ resetEpochMs: number | null;
122
+ }
123
+
124
+ export class KeyExpiredError extends ERLCAPIError {}
125
+ export class KeyBannedError extends ERLCAPIError {}
126
+ export class InvalidGlobalKeyError extends ERLCAPIError {}
127
+ export class ServerOfflineError extends ERLCAPIError {}
128
+ export class RestrictedError extends ERLCAPIError {}
129
+ export class ModuleOutOfDateError extends ERLCAPIError {}
130
+
131
+ export class Client extends EventEmitter {
132
+ constructor(options: ClientOptions);
133
+ cache: { clear: () => void };
134
+ server: {
135
+ fetch: (
136
+ flags?: ServerFetchFlags,
137
+ requestOptions?: RequestOptions,
138
+ ) => Promise<ServerResponse>;
139
+ };
140
+ players: {
141
+ list: (requestOptions?: RequestOptions) => Promise<any[]>;
142
+ };
143
+ staff: {
144
+ list: (requestOptions?: RequestOptions) => Promise<any>;
145
+ };
146
+ logs: {
147
+ kills: (requestOptions?: RequestOptions) => Promise<any[]>;
148
+ joins: (requestOptions?: RequestOptions) => Promise<any[]>;
149
+ commands: (requestOptions?: RequestOptions) => Promise<any[]>;
150
+ modCalls: (requestOptions?: RequestOptions) => Promise<any[]>;
151
+ };
152
+ commands: {
153
+ execute: (
154
+ command: string,
155
+ requestOptions?: CommandRequestOptions,
156
+ ) => Promise<CommandExecuteResult>;
157
+ };
158
+ vehicles: {
159
+ list: (requestOptions?: RequestOptions) => Promise<any[]>;
160
+ };
161
+ queue: {
162
+ get: (requestOptions?: RequestOptions) => Promise<any[]>;
163
+ };
164
+ onReady(listener: (...args: any[]) => void): this;
165
+ onJoin(listener: (...args: any[]) => void): this;
166
+ onLeave(listener: (...args: any[]) => void): this;
167
+ onKill(listener: (...args: any[]) => void): this;
168
+ onVehicleSpawn(listener: (...args: any[]) => void): this;
169
+ onVehicleDespawn(listener: (...args: any[]) => void): this;
170
+ onQueueUpdate(listener: (...args: any[]) => void): this;
171
+ onStaffUpdate(listener: (...args: any[]) => void): this;
172
+ onModCall(listener: (...args: any[]) => void): this;
173
+ onCommandLog(listener: (...args: any[]) => void): this;
174
+ onLogCommand(listener: (...args: any[]) => void): this;
175
+ onServerUpdate(listener: (...args: any[]) => void): this;
176
+ onError(listener: (...args: any[]) => void): this;
177
+ onDisconnect(listener: (...args: any[]) => void): this;
178
+ destroy(): void;
179
+ }
180
+
181
+ declare const _default: {
182
+ Client: typeof Client;
183
+ ERLCError: typeof ERLCError;
184
+ ERLCHttpError: typeof ERLCHttpError;
185
+ ERLCAPIError: typeof ERLCAPIError;
186
+ RateLimitError: typeof RateLimitError;
187
+ KeyExpiredError: typeof KeyExpiredError;
188
+ KeyBannedError: typeof KeyBannedError;
189
+ InvalidGlobalKeyError: typeof InvalidGlobalKeyError;
190
+ ServerOfflineError: typeof ServerOfflineError;
191
+ RestrictedError: typeof RestrictedError;
192
+ ModuleOutOfDateError: typeof ModuleOutOfDateError;
193
+ };
194
+
195
+ export default _default;
package/index.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require("./src");
package/index.mjs ADDED
@@ -0,0 +1,14 @@
1
+ import cjs from "./index.js";
2
+
3
+ export const Client = cjs.Client;
4
+ export const ERLCError = cjs.ERLCError;
5
+ export const ERLCHttpError = cjs.ERLCHttpError;
6
+ export const ERLCAPIError = cjs.ERLCAPIError;
7
+ export const RateLimitError = cjs.RateLimitError;
8
+ export const KeyExpiredError = cjs.KeyExpiredError;
9
+ export const KeyBannedError = cjs.KeyBannedError;
10
+ export const InvalidGlobalKeyError = cjs.InvalidGlobalKeyError;
11
+ export const ServerOfflineError = cjs.ServerOfflineError;
12
+ export const RestrictedError = cjs.RestrictedError;
13
+ export const ModuleOutOfDateError = cjs.ModuleOutOfDateError;
14
+ export default cjs;
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "erlc-v2",
3
+ "version": "1.0.0-beta.0",
4
+ "description": "Premium, lightweight JavaScript wrapper for the ER:LC API v2.",
5
+ "main": "index.js",
6
+ "module": "index.mjs",
7
+ "types": "index.d.ts",
8
+ "files": [
9
+ "index.js",
10
+ "index.mjs",
11
+ "index.d.ts",
12
+ "src",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "types": "./index.d.ts",
19
+ "require": "./index.js",
20
+ "import": "./index.mjs"
21
+ }
22
+ },
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "keywords": [
27
+ "erlc",
28
+ "police-roleplay-community",
29
+ "api",
30
+ "wrapper",
31
+ "nodejs"
32
+ ],
33
+ "license": "LicenseRef-ERLC-Custom",
34
+ "author": "",
35
+ "type": "commonjs"
36
+ }