erlc-v2 1.0.1 → 1.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/index.d.ts CHANGED
@@ -49,6 +49,17 @@ export interface PollingOptions {
49
49
  bypassCache?: boolean;
50
50
  }
51
51
 
52
+ export interface ApiServerOptions {
53
+ enabled?: boolean;
54
+ host?: string;
55
+ port?: number;
56
+ path?: string;
57
+ webhookPath?: string;
58
+ publicUrl?: string;
59
+ token?: string;
60
+ logRequests?: boolean;
61
+ }
62
+
52
63
  export interface ClientOptions {
53
64
  serverKey: string;
54
65
  globalKey?: string;
@@ -57,6 +68,7 @@ export interface ClientOptions {
57
68
  cache?: CacheOptions;
58
69
  rateLimit?: RateLimitOptions;
59
70
  polling?: PollingOptions;
71
+ api?: ApiServerOptions;
60
72
  }
61
73
 
62
74
  export interface ServerFetchFlags {
@@ -67,6 +79,7 @@ export interface ServerFetchFlags {
67
79
  killLogs?: boolean;
68
80
  commandLogs?: boolean;
69
81
  modCalls?: boolean;
82
+ emergencyCalls?: boolean;
70
83
  vehicles?: boolean;
71
84
  }
72
85
 
@@ -85,6 +98,8 @@ export interface CommandExecuteResult {
85
98
  status: number;
86
99
  endpoint: string;
87
100
  bucket: string;
101
+ message: string | null;
102
+ commandId: string | null;
88
103
  rateLimit: {
89
104
  limit: number;
90
105
  remaining: number;
@@ -92,6 +107,102 @@ export interface CommandExecuteResult {
92
107
  };
93
108
  }
94
109
 
110
+ export interface VehicleData {
111
+ Name?: string;
112
+ Owner?: string;
113
+ Plate?: string;
114
+ Texture?: string | null;
115
+ ColorHex?: string;
116
+ ColorName?: string;
117
+ [key: string]: any;
118
+ }
119
+
120
+ export interface EmergencyCallData {
121
+ Team?: string;
122
+ Caller?: number;
123
+ Players?: number[];
124
+ Position?: number[];
125
+ StartedAt?: number;
126
+ CallNumber?: number;
127
+ Description?: string;
128
+ PositionDescriptor?: string;
129
+ [key: string]: any;
130
+ }
131
+
132
+ export interface VehicleSearchFilters {
133
+ query?: string;
134
+ search?: string;
135
+ plate?: string;
136
+ owner?: string;
137
+ name?: string;
138
+ color?: string;
139
+ colorName?: string;
140
+ texture?: string;
141
+ exact?: boolean;
142
+ limit?: number;
143
+ }
144
+
145
+ export interface ApiServerInfo {
146
+ running: boolean;
147
+ host: string;
148
+ port: number | null;
149
+ path: string;
150
+ webhookPath: string;
151
+ localUrl: string | null;
152
+ webhookUrl: string | null;
153
+ publicUrl: string | null;
154
+ tokenEnabled: boolean;
155
+ }
156
+
157
+ export interface WebhookPayload {
158
+ type: "command" | "emergencyCall" | "verification" | "unknown";
159
+ body: Record<string, any>;
160
+ timestamp: string | null;
161
+ signature: string | null;
162
+ rawBody: Buffer | null;
163
+ headers: Record<string, any>;
164
+ server: string | null;
165
+ events: Array<{
166
+ event: string | null;
167
+ origin: string | null;
168
+ eventTimestamp: number | null;
169
+ data: Record<string, any>;
170
+ command: string | null;
171
+ argument: string;
172
+ args: string;
173
+ }>;
174
+ entry: {
175
+ event: string | null;
176
+ origin: string | null;
177
+ eventTimestamp: number | null;
178
+ data: Record<string, any>;
179
+ command: string | null;
180
+ argument: string;
181
+ args: string;
182
+ } | null;
183
+ event: string | null;
184
+ origin: string | null;
185
+ eventTimestamp: number | null;
186
+ data: Record<string, any> | null;
187
+ command: string | null;
188
+ argument: string;
189
+ args: string;
190
+ }
191
+
192
+ export interface ApiRequestPayload {
193
+ method: string;
194
+ path: string;
195
+ query: Record<string, string>;
196
+ kind: "route" | "webhook";
197
+ status: number;
198
+ type: string | null;
199
+ body: any;
200
+ note: string;
201
+ tookMs: number;
202
+ ip: string | string[] | null;
203
+ at: string;
204
+ }
205
+
95
206
  export interface MapCoordinateBounds {
96
207
  minX: number;
97
208
  maxX: number;
@@ -173,7 +284,8 @@ export interface ServerResponse {
173
284
  killLogs: any[];
174
285
  commandLogs: any[];
175
286
  modCalls: any[];
176
- vehicles: any[];
287
+ emergencyCalls: EmergencyCallData[];
288
+ vehicles: VehicleData[];
177
289
  raw: Record<string, any>;
178
290
  meta: {
179
291
  status: number;
@@ -249,6 +361,9 @@ export class Client extends EventEmitter {
249
361
  joins: (requestOptions?: RequestOptions) => Promise<any[]>;
250
362
  commands: (requestOptions?: RequestOptions) => Promise<any[]>;
251
363
  modCalls: (requestOptions?: RequestOptions) => Promise<any[]>;
364
+ emergencyCalls: (
365
+ requestOptions?: RequestOptions,
366
+ ) => Promise<EmergencyCallData[]>;
252
367
  };
253
368
  commands: {
254
369
  execute: (
@@ -257,11 +372,32 @@ export class Client extends EventEmitter {
257
372
  ) => Promise<CommandExecuteResult>;
258
373
  };
259
374
  vehicles: {
260
- list: (requestOptions?: RequestOptions) => Promise<any[]>;
375
+ list: (requestOptions?: RequestOptions) => Promise<VehicleData[]>;
376
+ search: (
377
+ filters?: string | VehicleSearchFilters,
378
+ requestOptions?: RequestOptions,
379
+ ) => Promise<VehicleData[]>;
380
+ findByPlate: (
381
+ plate: string,
382
+ requestOptions?: RequestOptions,
383
+ ) => Promise<VehicleData | null>;
384
+ findByOwner: (
385
+ owner: string,
386
+ requestOptions?: RequestOptions,
387
+ ) => Promise<VehicleData[]>;
388
+ findOne: (
389
+ filters?: string | VehicleSearchFilters,
390
+ requestOptions?: RequestOptions,
391
+ ) => Promise<VehicleData | null>;
261
392
  };
262
393
  queue: {
263
394
  get: (requestOptions?: RequestOptions) => Promise<any[]>;
264
395
  };
396
+ api: {
397
+ start: () => Promise<ApiServerInfo>;
398
+ stop: () => void;
399
+ info: () => ApiServerInfo;
400
+ };
265
401
  onReady(listener: (...args: any[]) => void): this;
266
402
  onJoin(listener: (...args: any[]) => void): this;
267
403
  onLeave(listener: (...args: any[]) => void): this;
@@ -271,9 +407,14 @@ export class Client extends EventEmitter {
271
407
  onQueueUpdate(listener: (...args: any[]) => void): this;
272
408
  onStaffUpdate(listener: (...args: any[]) => void): this;
273
409
  onModCall(listener: (...args: any[]) => void): this;
410
+ onEmergencyCall(listener: (...args: any[]) => void): this;
274
411
  onCommandLog(listener: (...args: any[]) => void): this;
275
412
  onLogCommand(listener: (...args: any[]) => void): this;
276
413
  onServerUpdate(listener: (...args: any[]) => void): this;
414
+ onApiRequest(listener: (payload: ApiRequestPayload) => void): this;
415
+ onWebhook(listener: (payload: WebhookPayload) => void): this;
416
+ onWebhookCommand(listener: (payload: WebhookPayload) => void): this;
417
+ onWebhookEmergencyCall(listener: (payload: WebhookPayload) => void): this;
277
418
  onError(listener: (...args: any[]) => void): this;
278
419
  onDisconnect(listener: (...args: any[]) => void): this;
279
420
  destroy(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "erlc-v2",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Premium, lightweight JavaScript wrapper for the ER:LC API v2.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -23,9 +23,12 @@
23
23
  "engines": {
24
24
  "node": ">=18"
25
25
  },
26
- "scripts": {
27
- "test:map": "node test-map-render.js"
28
- },
26
+ "scripts": {
27
+ "test:map": "node test-map-render.js",
28
+ "dev:api": "node scripts/start-api.js",
29
+ "test:api": "node scripts/check-api.js",
30
+ "test:features": "node scripts/test-features.js"
31
+ },
29
32
  "dependencies": {
30
33
  "@napi-rs/canvas": "^0.1.74"
31
34
  },
package/src/Client.js CHANGED
@@ -2,11 +2,13 @@ const { EventEmitter } = require("events");
2
2
  const Cache = require("./cache/Cache");
3
3
  const RequestManager = require("./rest/RequestManager");
4
4
  const Poller = require("./events/Poller");
5
+ const LocalApiServer = require("./api/LocalApiServer");
5
6
  const { DEFAULT_OPTIONS, QUERY_FLAG_MAP } = require("./util/constants");
6
7
  const { mergeOptions } = require("./util/options");
7
8
  const { createLogger } = require("./util/logger");
8
9
  const { normalizeServerResponse } = require("./util/normalize");
9
10
  const { renderPlayerMap } = require("./map/renderPlayerMap");
11
+ const { searchVehicles, findVehicleByPlate } = require("./util/vehicleSearch");
10
12
  const { ERLCError } = require("./errors");
11
13
 
12
14
  const BASE_URL = "https://api.policeroleplay.community";
@@ -37,9 +39,14 @@ const EVENT_ALIASES = {
37
39
  onQueueUpdate: "queueUpdate",
38
40
  onStaffUpdate: "staffUpdate",
39
41
  onModCall: "modCall",
42
+ onEmergencyCall: "emergencyCall",
40
43
  onCommandLog: "commandLog",
41
44
  onLogCommand: "logCommand",
42
45
  onServerUpdate: "serverUpdate",
46
+ onApiRequest: "apiRequest",
47
+ onWebhook: "webhook",
48
+ onWebhookCommand: "webhookCommand",
49
+ onWebhookEmergencyCall: "webhookEmergencyCall",
43
50
  onError: "error",
44
51
  onDisconnect: "disconnect",
45
52
  };
@@ -134,6 +141,10 @@ class Client extends EventEmitter {
134
141
  this.server
135
142
  .fetch({ modCalls: true }, requestOptions)
136
143
  .then((d) => d.modCalls),
144
+ emergencyCalls: (requestOptions = {}) =>
145
+ this.server
146
+ .fetch({ emergencyCalls: true }, requestOptions)
147
+ .then((d) => d.emergencyCalls),
137
148
  };
138
149
 
139
150
  this.commands = {
@@ -146,6 +157,29 @@ class Client extends EventEmitter {
146
157
  this.server
147
158
  .fetch({ vehicles: true }, requestOptions)
148
159
  .then((d) => d.vehicles),
160
+ search: (filters = {}, requestOptions = {}) =>
161
+ this.server
162
+ .fetch({ vehicles: true }, requestOptions)
163
+ .then((d) => searchVehicles(d.vehicles, filters)),
164
+ findByPlate: (plate, requestOptions = {}) =>
165
+ this.server
166
+ .fetch({ vehicles: true }, requestOptions)
167
+ .then((d) => findVehicleByPlate(d.vehicles, plate)),
168
+ findByOwner: (owner, requestOptions = {}) =>
169
+ this.server
170
+ .fetch({ vehicles: true }, requestOptions)
171
+ .then((d) => searchVehicles(d.vehicles, { owner })),
172
+ findOne: (filters = {}, requestOptions = {}) =>
173
+ this.server
174
+ .fetch({ vehicles: true }, requestOptions)
175
+ .then((d) =>
176
+ searchVehicles(
177
+ d.vehicles,
178
+ typeof filters === "string"
179
+ ? { query: filters, limit: 1 }
180
+ : { ...filters, limit: 1 },
181
+ )[0] ?? null,
182
+ ),
149
183
  };
150
184
 
151
185
  this.queue = {
@@ -153,10 +187,23 @@ class Client extends EventEmitter {
153
187
  this.server.fetch({ queue: true }, requestOptions).then((d) => d.queue),
154
188
  };
155
189
 
190
+ this.api = new LocalApiServer(this, this.options.api);
191
+
156
192
  this.poller = new Poller(this, this.options.polling);
157
193
  if (this.options.polling?.enabled !== false) {
158
194
  this.poller.start();
159
195
  }
196
+ if (this.options.api?.enabled || this.options.api?.port) {
197
+ this.api.start().catch((error) => {
198
+ this.logger.error({
199
+ msg: "local_api_start_failed",
200
+ error: error?.message || String(error),
201
+ });
202
+ if (this.listenerCount("error") > 0) {
203
+ this.emit("error", error);
204
+ }
205
+ });
206
+ }
160
207
  }
161
208
 
162
209
  on(eventName, listener) {
@@ -227,6 +274,10 @@ class Client extends EventEmitter {
227
274
  return this.on("commandLog", listener);
228
275
  }
229
276
 
277
+ onEmergencyCall(listener) {
278
+ return this.on("emergencyCall", listener);
279
+ }
280
+
230
281
  onLogCommand(listener) {
231
282
  return this.on("logCommand", listener);
232
283
  }
@@ -235,6 +286,22 @@ class Client extends EventEmitter {
235
286
  return this.on("serverUpdate", listener);
236
287
  }
237
288
 
289
+ onApiRequest(listener) {
290
+ return this.on("apiRequest", listener);
291
+ }
292
+
293
+ onWebhook(listener) {
294
+ return this.on("webhook", listener);
295
+ }
296
+
297
+ onWebhookCommand(listener) {
298
+ return this.on("webhookCommand", listener);
299
+ }
300
+
301
+ onWebhookEmergencyCall(listener) {
302
+ return this.on("webhookEmergencyCall", listener);
303
+ }
304
+
238
305
  onError(listener) {
239
306
  return this.on("error", listener);
240
307
  }
@@ -333,7 +400,7 @@ class Client extends EventEmitter {
333
400
 
334
401
  const response = await this.requestManager.request({
335
402
  method: "POST",
336
- path: "/v1/server/command",
403
+ path: "/v2/server/command",
337
404
  body: { command: normalizedCommand },
338
405
  useCache: false,
339
406
  dedupe: requestOptions.dedupe === true,
@@ -345,6 +412,11 @@ class Client extends EventEmitter {
345
412
  endpoint: response.endpoint,
346
413
  bucket: response.bucket,
347
414
  rateLimit: response.rateLimit,
415
+ message: response.data?.message ?? null,
416
+ commandId:
417
+ response.data?.commandId ??
418
+ response.data?.CommandId ??
419
+ null,
348
420
  };
349
421
  });
350
422
  }
@@ -370,6 +442,7 @@ class Client extends EventEmitter {
370
442
  if (this.state.destroyed) return;
371
443
  this.state.destroyed = true;
372
444
  this.poller.destroy();
445
+ this.api.stop();
373
446
  this.requestManager.destroy(
374
447
  this.state.disconnectError || new ERLCError("Client destroyed"),
375
448
  );