@sonoransoftware/sonoran.js 1.0.34 → 1.0.36
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/.github/workflows/auto-pr-on-branch-push.yml +89 -0
- package/.github/workflows/codex_instructions.md +24 -0
- package/.github/workflows/push-pr-nudge-codex.yml +50 -0
- package/dist/constants.d.ts +242 -1
- package/dist/constants.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/instance/Instance.d.ts +6 -0
- package/dist/instance/Instance.js +27 -0
- package/dist/instance/instance.types.d.ts +3 -0
- package/dist/libs/rest/src/lib/REST.d.ts +2 -1
- package/dist/libs/rest/src/lib/REST.js +118 -0
- package/dist/libs/rest/src/lib/RequestManager.d.ts +2 -0
- package/dist/libs/rest/src/lib/RequestManager.js +209 -0
- package/dist/libs/rest/src/lib/errors/RateLimitError.js +19 -1
- package/dist/libs/rest/src/lib/utils/constants.d.ts +108 -24
- package/dist/libs/rest/src/lib/utils/constants.js +118 -2
- package/dist/managers/CADActiveUnitsManager.d.ts +3 -0
- package/dist/managers/CADActiveUnitsManager.js +16 -6
- package/dist/managers/CADManager.d.ts +223 -0
- package/dist/managers/CADManager.js +513 -4
- package/dist/managers/CADServerManager.d.ts +11 -0
- package/dist/managers/CADServerManager.js +56 -13
- package/dist/managers/CMSManager.d.ts +78 -0
- package/dist/managers/CMSManager.js +213 -3
- package/dist/managers/CMSServerManager.d.ts +8 -0
- package/dist/managers/CMSServerManager.js +61 -18
- package/dist/managers/RadioManager.d.ts +55 -0
- package/dist/managers/RadioManager.js +224 -0
- package/package.json +1 -1
- package/readme.md +294 -12
- package/src/constants.ts +281 -1
- package/src/index.ts +42 -1
- package/src/instance/Instance.ts +30 -1
- package/src/instance/instance.types.ts +4 -1
- package/src/libs/rest/src/lib/REST.ts +117 -1
- package/src/libs/rest/src/lib/RequestManager.ts +229 -10
- package/src/libs/rest/src/lib/errors/RateLimitError.ts +20 -2
- package/src/libs/rest/src/lib/utils/constants.ts +223 -26
- package/src/managers/CADActiveUnitsManager.ts +19 -6
- package/src/managers/CADManager.ts +574 -4
- package/src/managers/CADServerManager.ts +59 -15
- package/src/managers/CMSManager.ts +196 -2
- package/src/managers/CMSServerManager.ts +65 -21
- package/src/managers/RadioManager.ts +187 -0
|
@@ -5,7 +5,8 @@ import { EventEmitter } from 'events';
|
|
|
5
5
|
|
|
6
6
|
import type { Instance } from '../../../../instance/Instance';
|
|
7
7
|
import { RESTOptions, RateLimitData, RestEvents } from './REST';
|
|
8
|
-
import { DefaultCADRestOptions, DefaultCMSRestOptions, AllAPITypes/**, RESTTypedAPIDataStructs, PossibleRequestData*/ } from './utils/constants';
|
|
8
|
+
import { DefaultCADRestOptions, DefaultCMSRestOptions, DefaultRadioRestOptions, AllAPITypes/**, RESTTypedAPIDataStructs, PossibleRequestData*/ } from './utils/constants';
|
|
9
|
+
import type { AllAPITypeData } from './utils/constants';
|
|
9
10
|
import { productEnums } from '../../../../constants';
|
|
10
11
|
// import { APIError, HTTPError } from './errors';
|
|
11
12
|
import { IHandler } from './handlers/IHandler';
|
|
@@ -29,6 +30,7 @@ export interface RequestData {
|
|
|
29
30
|
key: string;
|
|
30
31
|
type: string;
|
|
31
32
|
data: any;
|
|
33
|
+
internalKey?: string;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export interface InternalRequestData extends RequestData {
|
|
@@ -87,6 +89,10 @@ export class RequestManager extends EventEmitter {
|
|
|
87
89
|
this.options = { ...DefaultCMSRestOptions, ...options };
|
|
88
90
|
break;
|
|
89
91
|
}
|
|
92
|
+
case productEnums.RADIO: {
|
|
93
|
+
this.options = { ...DefaultRadioRestOptions, ...options };
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
90
96
|
default: {
|
|
91
97
|
throw new Error('No Product provided for RequestManager initialization');
|
|
92
98
|
}
|
|
@@ -134,10 +140,16 @@ export class RequestManager extends EventEmitter {
|
|
|
134
140
|
case productEnums.CMS:
|
|
135
141
|
apiURL = instance.cmsApiUrl;
|
|
136
142
|
break;
|
|
143
|
+
case productEnums.RADIO:
|
|
144
|
+
apiURL = instance.radioApiUrl;
|
|
145
|
+
break;
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
const findType = AllAPITypes.find((_type) => _type.type === type);
|
|
140
149
|
if (findType) {
|
|
150
|
+
if (product === productEnums.RADIO) {
|
|
151
|
+
return RequestManager.resolveRadioRequest(instance, apiURL, findType, data, apiData);
|
|
152
|
+
}
|
|
141
153
|
apiData.fullUrl = `${apiURL}/${findType.path}`;
|
|
142
154
|
apiData.method = findType.method;
|
|
143
155
|
apiData.fetchOptions.method = findType.method;
|
|
@@ -150,6 +162,18 @@ export class RequestManager extends EventEmitter {
|
|
|
150
162
|
apiData.data.data = clonedData;
|
|
151
163
|
break;
|
|
152
164
|
}
|
|
165
|
+
case 'SET_GAME_SERVERS': {
|
|
166
|
+
apiData.data.data = clonedData;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case 'TRIGGER_PROMOTION_FLOWS': {
|
|
170
|
+
apiData.data.data = clonedData;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case 'GET_PROMOTION_FLOWS': {
|
|
174
|
+
apiData.data.data = [];
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
153
177
|
case 'SET_PENAL_CODES': {
|
|
154
178
|
apiData.data.data = [clonedData[0]];
|
|
155
179
|
break;
|
|
@@ -186,14 +210,37 @@ export class RequestManager extends EventEmitter {
|
|
|
186
210
|
apiData.data.data = clonedData;
|
|
187
211
|
break;
|
|
188
212
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
213
|
+
case 'SET_POSTALS': {
|
|
214
|
+
apiData.data.data = [clonedData[0]];
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case 'SET_CLOCK': {
|
|
218
|
+
apiData.data.data = Array.isArray(clonedData) ? clonedData : [clonedData];
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case 'JOIN_COMMUNITY':
|
|
222
|
+
case 'LEAVE_COMMUNITY': {
|
|
223
|
+
const internalKey = clonedData?.internalKey;
|
|
224
|
+
if (internalKey !== undefined) {
|
|
225
|
+
apiData.data.internalKey = internalKey;
|
|
226
|
+
}
|
|
227
|
+
const accountsSource = clonedData?.accounts;
|
|
228
|
+
const accountsArray = Array.isArray(accountsSource) ? accountsSource : accountsSource ? [accountsSource] : [];
|
|
229
|
+
apiData.data.data = accountsArray.map((entry: any) => {
|
|
230
|
+
if (typeof entry === 'string') {
|
|
231
|
+
return { account: entry };
|
|
232
|
+
}
|
|
233
|
+
if (entry && typeof entry === 'object' && 'account' in entry) {
|
|
234
|
+
return entry;
|
|
235
|
+
}
|
|
236
|
+
return { account: String(entry) };
|
|
237
|
+
});
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
case 'NEW_CHARACTER': {
|
|
241
|
+
apiData.data.data = [clonedData[0]];
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
197
244
|
case 'EDIT_CHARACTER': {
|
|
198
245
|
apiData.data.data = [clonedData[0]];
|
|
199
246
|
break;
|
|
@@ -255,7 +302,179 @@ export class RequestManager extends EventEmitter {
|
|
|
255
302
|
return apiData;
|
|
256
303
|
}
|
|
257
304
|
|
|
305
|
+
private static resolveRadioRequest(instance: Instance, apiURL: string | boolean, apiType: AllAPITypeData, request: RequestData, apiData: APIData): APIData {
|
|
306
|
+
if (!apiURL || typeof apiURL !== 'string') {
|
|
307
|
+
throw new Error('Radio API URL could not be resolved for request.');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const rawData = request.data;
|
|
311
|
+
const payload: any =
|
|
312
|
+
rawData == null ? {} : (typeof rawData === 'object' ? cloneObject(rawData) : rawData);
|
|
313
|
+
const headers: Record<string, string> = {
|
|
314
|
+
Accept: 'application/json'
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const applyHeaders = (source: unknown) => {
|
|
318
|
+
if (!source) return;
|
|
319
|
+
if (Array.isArray(source)) {
|
|
320
|
+
for (const [key, value] of source) {
|
|
321
|
+
headers[key] = value;
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (typeof source === 'object' && source !== null && 'forEach' in source && typeof (source as any).forEach === 'function') {
|
|
326
|
+
(source as any).forEach((value: string, key: string) => {
|
|
327
|
+
headers[key] = value;
|
|
328
|
+
});
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
Object.assign(headers, source as Record<string, string>);
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
applyHeaders(instance.apiHeaders);
|
|
335
|
+
|
|
336
|
+
let method = apiType.method;
|
|
337
|
+
let path = apiType.path;
|
|
338
|
+
let body: unknown;
|
|
339
|
+
|
|
340
|
+
const ensureAuth = () => {
|
|
341
|
+
if (!request.id || !request.key) {
|
|
342
|
+
throw new Error('Community ID or API Key could not be found for request.');
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
id: request.id,
|
|
346
|
+
key: request.key,
|
|
347
|
+
encodedId: encodeURIComponent(request.id),
|
|
348
|
+
encodedKey: encodeURIComponent(request.key)
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const encodeSegment = (value: string | number) => encodeURIComponent(String(value));
|
|
353
|
+
|
|
354
|
+
switch (apiType.type) {
|
|
355
|
+
case 'RADIO_GET_COMMUNITY_CHANNELS': {
|
|
356
|
+
const auth = ensureAuth();
|
|
357
|
+
path = `${apiType.path}/${auth.encodedId}/${auth.encodedKey}`;
|
|
358
|
+
method = 'GET';
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case 'RADIO_GET_CONNECTED_USERS': {
|
|
362
|
+
const auth = ensureAuth();
|
|
363
|
+
path = `${apiType.path}/${auth.encodedId}/${auth.encodedKey}`;
|
|
364
|
+
method = 'GET';
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
case 'RADIO_GET_CONNECTED_USER': {
|
|
368
|
+
const auth = ensureAuth();
|
|
369
|
+
const roomIdRaw = payload?.roomId ?? payload?.roomID;
|
|
370
|
+
if (roomIdRaw === undefined) {
|
|
371
|
+
throw new Error('roomId is required for RADIO_GET_CONNECTED_USER requests.');
|
|
372
|
+
}
|
|
373
|
+
const roomIdNumeric = typeof roomIdRaw === 'number' ? roomIdRaw : Number(roomIdRaw);
|
|
374
|
+
if (Number.isNaN(roomIdNumeric)) {
|
|
375
|
+
throw new Error('roomId must be a number for RADIO_GET_CONNECTED_USER requests.');
|
|
376
|
+
}
|
|
377
|
+
const identity = payload?.identity;
|
|
378
|
+
if (!identity) {
|
|
379
|
+
throw new Error('identity is required for RADIO_GET_CONNECTED_USER requests.');
|
|
380
|
+
}
|
|
381
|
+
path = `${apiType.path}/${auth.encodedId}/${auth.encodedKey}/${encodeSegment(roomIdNumeric)}/${encodeSegment(identity)}`;
|
|
382
|
+
method = 'GET';
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
case 'RADIO_SET_USER_CHANNELS': {
|
|
386
|
+
const auth = ensureAuth();
|
|
387
|
+
const identity = payload?.identity;
|
|
388
|
+
if (!identity) {
|
|
389
|
+
throw new Error('identity is required for RADIO_SET_USER_CHANNELS requests.');
|
|
390
|
+
}
|
|
391
|
+
const options = payload?.options ?? {};
|
|
392
|
+
path = `${apiType.path}/${auth.encodedId}/${auth.encodedKey}/${encodeSegment(identity)}`;
|
|
393
|
+
method = 'POST';
|
|
394
|
+
const requestBody: Record<string, unknown> = {};
|
|
395
|
+
if (options?.transmit !== undefined) {
|
|
396
|
+
requestBody.transmit = options.transmit;
|
|
397
|
+
}
|
|
398
|
+
if (options?.scan !== undefined) {
|
|
399
|
+
requestBody.scan = options.scan;
|
|
400
|
+
}
|
|
401
|
+
body = requestBody;
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case 'RADIO_SET_USER_DISPLAY_NAME': {
|
|
405
|
+
const auth = ensureAuth();
|
|
406
|
+
const accId = payload?.accId;
|
|
407
|
+
const displayName = payload?.displayName;
|
|
408
|
+
if (!accId || !displayName) {
|
|
409
|
+
throw new Error('accId and displayName are required for RADIO_SET_USER_DISPLAY_NAME requests.');
|
|
410
|
+
}
|
|
411
|
+
method = 'POST';
|
|
412
|
+
body = {
|
|
413
|
+
id: auth.id,
|
|
414
|
+
key: auth.key,
|
|
415
|
+
accId,
|
|
416
|
+
displayName
|
|
417
|
+
};
|
|
418
|
+
path = apiType.path;
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
case 'RADIO_GET_SERVER_SUBSCRIPTION_FROM_IP': {
|
|
422
|
+
method = 'GET';
|
|
423
|
+
path = apiType.path;
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case 'RADIO_SET_SERVER_IP': {
|
|
427
|
+
const auth = ensureAuth();
|
|
428
|
+
const pushUrl = payload?.pushUrl;
|
|
429
|
+
if (!pushUrl) {
|
|
430
|
+
throw new Error('pushUrl is required for RADIO_SET_SERVER_IP requests.');
|
|
431
|
+
}
|
|
432
|
+
method = 'POST';
|
|
433
|
+
body = {
|
|
434
|
+
id: auth.id,
|
|
435
|
+
key: auth.key,
|
|
436
|
+
pushUrl
|
|
437
|
+
};
|
|
438
|
+
path = apiType.path;
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
case 'RADIO_SET_IN_GAME_SPEAKER_LOCATIONS': {
|
|
442
|
+
const auth = ensureAuth();
|
|
443
|
+
const locations = payload?.locations;
|
|
444
|
+
if (!Array.isArray(locations)) {
|
|
445
|
+
throw new Error('locations array is required for RADIO_SET_IN_GAME_SPEAKER_LOCATIONS requests.');
|
|
446
|
+
}
|
|
447
|
+
method = 'POST';
|
|
448
|
+
body = {
|
|
449
|
+
id: auth.id,
|
|
450
|
+
key: auth.key,
|
|
451
|
+
locations
|
|
452
|
+
};
|
|
453
|
+
const token = payload?.token ?? auth.key;
|
|
454
|
+
if (token) {
|
|
455
|
+
headers.Authorization = `Bearer ${token}`;
|
|
456
|
+
}
|
|
457
|
+
path = apiType.path;
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
default: {
|
|
461
|
+
throw new Error(`Unsupported radio API type received: ${apiType.type}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
apiData.typePath = path;
|
|
466
|
+
apiData.fullUrl = `${apiURL}/${path}`;
|
|
467
|
+
apiData.method = method;
|
|
468
|
+
apiData.fetchOptions.method = method;
|
|
469
|
+
if (body !== undefined) {
|
|
470
|
+
headers['Content-Type'] = 'application/json';
|
|
471
|
+
apiData.fetchOptions.body = JSON.stringify(body);
|
|
472
|
+
}
|
|
473
|
+
apiData.fetchOptions.headers = headers;
|
|
474
|
+
return apiData;
|
|
475
|
+
}
|
|
476
|
+
|
|
258
477
|
debug(log: string) {
|
|
259
478
|
return this.instance._debugLog(log);
|
|
260
479
|
}
|
|
261
|
-
}
|
|
480
|
+
}
|
|
@@ -16,6 +16,24 @@ export class RateLimitError extends Error implements RateLimitData {
|
|
|
16
16
|
* The name of the error
|
|
17
17
|
*/
|
|
18
18
|
public override get name(): string {
|
|
19
|
-
|
|
19
|
+
let productName: string;
|
|
20
|
+
switch (this.product) {
|
|
21
|
+
case productEnums.CAD: {
|
|
22
|
+
productName = 'Sonoran CAD';
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
case productEnums.CMS: {
|
|
26
|
+
productName = 'Sonoran CMS';
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case productEnums.RADIO: {
|
|
30
|
+
productName = 'Sonoran Radio';
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
default: {
|
|
34
|
+
productName = 'Invalid Product';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return `Ratelimit Hit - [${productName} '${this.type}']`;
|
|
20
38
|
}
|
|
21
|
-
}
|
|
39
|
+
}
|