@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.
Files changed (44) hide show
  1. package/.github/workflows/auto-pr-on-branch-push.yml +89 -0
  2. package/.github/workflows/codex_instructions.md +24 -0
  3. package/.github/workflows/push-pr-nudge-codex.yml +50 -0
  4. package/dist/constants.d.ts +242 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/index.d.ts +1 -1
  7. package/dist/instance/Instance.d.ts +6 -0
  8. package/dist/instance/Instance.js +27 -0
  9. package/dist/instance/instance.types.d.ts +3 -0
  10. package/dist/libs/rest/src/lib/REST.d.ts +2 -1
  11. package/dist/libs/rest/src/lib/REST.js +118 -0
  12. package/dist/libs/rest/src/lib/RequestManager.d.ts +2 -0
  13. package/dist/libs/rest/src/lib/RequestManager.js +209 -0
  14. package/dist/libs/rest/src/lib/errors/RateLimitError.js +19 -1
  15. package/dist/libs/rest/src/lib/utils/constants.d.ts +108 -24
  16. package/dist/libs/rest/src/lib/utils/constants.js +118 -2
  17. package/dist/managers/CADActiveUnitsManager.d.ts +3 -0
  18. package/dist/managers/CADActiveUnitsManager.js +16 -6
  19. package/dist/managers/CADManager.d.ts +223 -0
  20. package/dist/managers/CADManager.js +513 -4
  21. package/dist/managers/CADServerManager.d.ts +11 -0
  22. package/dist/managers/CADServerManager.js +56 -13
  23. package/dist/managers/CMSManager.d.ts +78 -0
  24. package/dist/managers/CMSManager.js +213 -3
  25. package/dist/managers/CMSServerManager.d.ts +8 -0
  26. package/dist/managers/CMSServerManager.js +61 -18
  27. package/dist/managers/RadioManager.d.ts +55 -0
  28. package/dist/managers/RadioManager.js +224 -0
  29. package/package.json +1 -1
  30. package/readme.md +294 -12
  31. package/src/constants.ts +281 -1
  32. package/src/index.ts +42 -1
  33. package/src/instance/Instance.ts +30 -1
  34. package/src/instance/instance.types.ts +4 -1
  35. package/src/libs/rest/src/lib/REST.ts +117 -1
  36. package/src/libs/rest/src/lib/RequestManager.ts +229 -10
  37. package/src/libs/rest/src/lib/errors/RateLimitError.ts +20 -2
  38. package/src/libs/rest/src/lib/utils/constants.ts +223 -26
  39. package/src/managers/CADActiveUnitsManager.ts +19 -6
  40. package/src/managers/CADManager.ts +574 -4
  41. package/src/managers/CADServerManager.ts +59 -15
  42. package/src/managers/CMSManager.ts +196 -2
  43. package/src/managers/CMSServerManager.ts +65 -21
  44. package/src/managers/RadioManager.ts +187 -0
@@ -25,8 +25,7 @@ export class CMSManager extends BaseManager {
25
25
  protected async buildManager(instance: Instance) {
26
26
  const mutableThis = this as globalTypes.Mutable<CMSManager>;
27
27
  try {
28
- const versionResp: any = await this.rest?.request('GET_SUB_VERSION');
29
- const version = Number.parseInt(versionResp.replace(/(^\d+)(.+$)/i,'$1'));
28
+ const version = await this.getSubscriptionVersion();
30
29
  if (version >= globalTypes.CMSSubscriptionVersionEnum.STANDARD) {
31
30
  this.servers = new CMSServerManager(instance, this);
32
31
  }
@@ -41,6 +40,15 @@ export class CMSManager extends BaseManager {
41
40
  }
42
41
  }
43
42
 
43
+ /**
44
+ * Retrieves the community's CMS subscription version.
45
+ */
46
+ public async getSubscriptionVersion(): Promise<number> {
47
+ const versionResp: any = await this.rest?.request('GET_SUB_VERSION');
48
+ const versionString = String(versionResp);
49
+ return Number.parseInt(versionString.replace(/(^\d+)(.+$)/i, '$1'), 10);
50
+ }
51
+
44
52
  /**
45
53
  * Verifies the whitelist of a given account with the given parameters to search of said account.
46
54
  * @param {Object | string} data The object or [accId | apiId as a string] that contains data to get a community account to verify if it has whitelist to the specified server. *If given as a string it will default to the set or default cms server id (1).
@@ -328,6 +336,46 @@ export class CMSManager extends BaseManager {
328
336
  });
329
337
  }
330
338
 
339
+ /**
340
+ * Triggers promotion flows for the specified users.
341
+ */
342
+ public async triggerPromotionFlows(flows: globalTypes.CMSTriggerPromotionFlowPayload[]): Promise<globalTypes.CMSTriggerPromotionFlowsPromiseResult> {
343
+ if (!Array.isArray(flows) || flows.length === 0) {
344
+ throw new Error('flows array must include at least one promotion flow payload.');
345
+ }
346
+
347
+ return new Promise(async (resolve, reject) => {
348
+ try {
349
+ const response: any = await this.rest?.request('TRIGGER_PROMOTION_FLOWS', flows);
350
+ resolve({ success: true, data: response });
351
+ } catch (err) {
352
+ if (err instanceof APIError) {
353
+ resolve({ success: false, reason: err.response });
354
+ } else {
355
+ reject(err);
356
+ }
357
+ }
358
+ });
359
+ }
360
+
361
+ /**
362
+ * Retrieves the available promotion flows configured in CMS.
363
+ */
364
+ public async getPromotionFlows(): Promise<globalTypes.CMSGetPromotionFlowsPromiseResult> {
365
+ return new Promise(async (resolve, reject) => {
366
+ try {
367
+ const response: any = await this.rest?.request('GET_PROMOTION_FLOWS');
368
+ resolve({ success: true, data: response });
369
+ } catch (err) {
370
+ if (err instanceof APIError) {
371
+ resolve({ success: false, reason: err.response });
372
+ } else {
373
+ reject(err);
374
+ }
375
+ }
376
+ });
377
+ }
378
+
331
379
  /**
332
380
  * Gets a list of online ERLC players for the given roblox join code.
333
381
  * @param {string} robloxJoinCode The roblox join code to get the online players for.
@@ -348,6 +396,152 @@ export class CMSManager extends BaseManager {
348
396
  });
349
397
  }
350
398
 
399
+ /**
400
+ * Toggles RSVP for the provided event and account.
401
+ */
402
+ public async rsvp(eventId: string, params: { apiId?: string, username?: string, accId?: string, discord?: string, uniqueId?: string } = {}): Promise<globalTypes.CMSRsvpPromiseResult> {
403
+ return new Promise(async (resolve, reject) => {
404
+ try {
405
+ const response: any = await this.rest?.request('RSVP', eventId, params.apiId, params.accId, params.discord, params.uniqueId);
406
+ if (typeof response === 'string') {
407
+ resolve({ success: true, status: response });
408
+ } else {
409
+ resolve({ success: true, data: response });
410
+ }
411
+ } catch (err) {
412
+ if (err instanceof APIError) {
413
+ resolve({ success: false, reason: err.response });
414
+ } else {
415
+ reject(err);
416
+ }
417
+ }
418
+ });
419
+ }
420
+
421
+ /**
422
+ * Updates the stage of a form for the specified account.
423
+ */
424
+ public async changeFormStage(params: { accId?: string, formId: number, newStageId: string, apiId?: string, username?: string, discord?: string, uniqueId: number }): Promise<globalTypes.CMSChangeFormStagePromiseResult> {
425
+ if (!params.formId || !params.newStageId) {
426
+ throw new Error('formId and newStageId are required to change a form stage.');
427
+ }
428
+ if (params.uniqueId === undefined || Number.isNaN(Number(params.uniqueId))) {
429
+ throw new Error('uniqueId is required to change a form stage.');
430
+ }
431
+
432
+ return new Promise(async (resolve, reject) => {
433
+ try {
434
+ const response: any = await this.rest?.request('CHANGE_FORM_STAGE', params.accId, params.formId, params.newStageId, params.apiId, params.username, params.discord, params.uniqueId);
435
+ resolve({ success: true, data: response });
436
+ } catch (err) {
437
+ if (err instanceof APIError) {
438
+ resolve({ success: false, reason: err.response });
439
+ } else {
440
+ reject(err);
441
+ }
442
+ }
443
+ });
444
+ }
445
+
446
+ /**
447
+ * Retrieves submissions for a specific form template.
448
+ */
449
+ public async getFormSubmissions<T = unknown>(templateId: number, options: { skip?: number, take?: number } = {}): Promise<globalTypes.CMSGetFormSubmissionsPromiseResult<T>> {
450
+ return new Promise(async (resolve, reject) => {
451
+ try {
452
+ const response: any = await this.rest?.request('GET_FORM_TEMPLATE_SUBMISSIONS', templateId, options.skip, options.take);
453
+ resolve({ success: true, data: response });
454
+ } catch (err) {
455
+ if (err instanceof APIError) {
456
+ resolve({ success: false, reason: err.response });
457
+ } else {
458
+ reject(err);
459
+ }
460
+ }
461
+ });
462
+ }
463
+
464
+ /**
465
+ * Updates profile fields for the specified account.
466
+ */
467
+ public async editAccountProfileFields(params: { apiId?: string, username?: string, accId?: string, discord?: string, uniqueId?: string, profileFields: globalTypes.CMSProfileFieldUpdate[] }): Promise<globalTypes.CMSEditAccountProfileFieldsPromiseResult> {
468
+ if (!params.profileFields || params.profileFields.length === 0) {
469
+ throw new Error('profileFields array must include at least one value.');
470
+ }
471
+
472
+ return new Promise(async (resolve, reject) => {
473
+ try {
474
+ const response: any = await this.rest?.request('EDIT_ACC_PROFLIE_FIELDS', params.apiId, params.username, params.accId, params.discord, params.uniqueId, params.profileFields);
475
+ resolve({ success: true, data: response });
476
+ } catch (err) {
477
+ if (err instanceof APIError) {
478
+ resolve({ success: false, reason: err.response });
479
+ } else {
480
+ reject(err);
481
+ }
482
+ }
483
+ });
484
+ }
485
+
486
+ /**
487
+ * Gets the current clock in entry for a community member.
488
+ * @param {Object} params Identification parameters for the account.
489
+ */
490
+ public async getCurrentClockIn(params: { accId?: string, apiId?: string, username?: string, discord?: string, uniqueId?: string }): Promise<globalTypes.CMSGetCurrentClockInPromiseResult> {
491
+ return new Promise(async (resolve, reject) => {
492
+ try {
493
+ const response: any = await this.rest?.request('GET_CURRENT_CLOCK_IN', params.apiId, params.username, params.accId, params.discord, params.uniqueId);
494
+ if (typeof response === 'string') {
495
+ resolve({ success: true, reason: response, data: null });
496
+ } else {
497
+ resolve({ success: true, data: response });
498
+ }
499
+ } catch (err) {
500
+ if (err instanceof APIError) {
501
+ resolve({ success: false, reason: err.response });
502
+ } else {
503
+ reject(err);
504
+ }
505
+ }
506
+ });
507
+ }
508
+
509
+ /**
510
+ * Retrieves community accounts using optional pagination and status filters.
511
+ */
512
+ public async getAccounts(options: { skip?: number, take?: number, sysStatus?: boolean, comStatus?: boolean, banned?: boolean, archived?: boolean } = {}): Promise<globalTypes.CMSGetAccountsPromiseResult> {
513
+ return new Promise(async (resolve, reject) => {
514
+ try {
515
+ const response: any = await this.rest?.request('GET_ACCOUNTS', options);
516
+ resolve({ success: true, data: response });
517
+ } catch (err) {
518
+ if (err instanceof APIError) {
519
+ resolve({ success: false, reason: err.response });
520
+ } else {
521
+ reject(err);
522
+ }
523
+ }
524
+ });
525
+ }
526
+
527
+ /**
528
+ * Retrieves configured profile fields for the community.
529
+ */
530
+ public async getProfileFields(): Promise<globalTypes.CMSGetProfileFieldsPromiseResult> {
531
+ return new Promise(async (resolve, reject) => {
532
+ try {
533
+ const response: any = await this.rest?.request('GET_PROFILE_FIELDS');
534
+ resolve({ success: true, data: response });
535
+ } catch (err) {
536
+ if (err instanceof APIError) {
537
+ resolve({ success: false, reason: err.response });
538
+ } else {
539
+ reject(err);
540
+ }
541
+ }
542
+ });
543
+ }
544
+
351
545
  /**
352
546
  * Gets the current ERLC player queue count for the provided roblox join code.
353
547
  * @param {string} robloxJoinCode The roblox join code to get the player queue size for.
@@ -1,33 +1,77 @@
1
1
  import { Instance } from '../instance/Instance';
2
- import { CMSServerAPIStruct } from '../libs/rest/src';
2
+ import { APIError, CMSServerAPIStruct } from '../libs/rest/src';
3
+ import * as globalTypes from '../constants';
3
4
  import { CMSServer } from '../structures/CMSServer';
4
5
  import { CacheManager } from './CacheManager';
5
6
  import { CMSManager } from './CMSManager';
6
7
 
7
8
  export class CMSServerManager extends CacheManager<number, CMSServer, CMSServerAPIStruct> {
8
- constructor(instance: Instance, manager: CMSManager) {
9
+ constructor(instance: Instance, private readonly manager: CMSManager) {
9
10
  super(instance, CMSServer, []);
11
+ void this.initialize();
12
+ }
13
+
14
+ /**
15
+ * Retrieves the CMS game servers belonging to the community.
16
+ */
17
+ public async getGameServers(): Promise<CMSServerAPIStruct[]> {
18
+ const serversRes: any = await this.manager.rest?.request('GET_GAME_SERVERS');
19
+ const parsed = typeof serversRes === 'string' ? JSON.parse(serversRes) : serversRes;
20
+ const servers = Array.isArray(parsed?.servers) ? parsed.servers : [];
21
+ return servers;
22
+ }
23
+
24
+ private async initialize(): Promise<void> {
25
+ const managerRef = this.manager;
26
+ while(!managerRef.ready) {
27
+ await new Promise((resolve) => {
28
+ setTimeout(resolve, 100);
29
+ });
30
+ }
31
+ try {
32
+ const servers = await this.getGameServers();
33
+ servers.forEach((server: CMSServerAPIStruct) => {
34
+ const serverStruct = {
35
+ id: server.id,
36
+ config: server
37
+ };
38
+ this._add(serverStruct, true, server.id);
39
+ });
40
+ console.log(`Found ${servers.length} servers`);
41
+ } catch (err) {
42
+ throw new Error(String(err));
43
+ }
44
+ }
45
+
46
+ public async setGameServers(servers: globalTypes.CMSSetGameServerStruct[]): Promise<globalTypes.CMSSetGameServersPromiseResult> {
47
+ if (!Array.isArray(servers) || servers.length === 0) {
48
+ throw new Error('servers array must include at least one server configuration.');
49
+ }
10
50
 
11
- (async () => {
12
- while(!manager.ready) {
13
- await new Promise((resolve) => {
14
- setTimeout(resolve, 100);
15
- });
16
- }
51
+ return new Promise(async (resolve, reject) => {
17
52
  try {
18
- const serversRes: any = await manager.rest?.request('GET_GAME_SERVERS');
19
- const servers = serversRes.servers;
20
- servers.forEach((server: CMSServerAPIStruct) => {
21
- const serverStruct = {
22
- id: server.id,
23
- config: server
24
- };
25
- this._add(serverStruct, true, server.id);
26
- });
27
- console.log(`Found ${servers.length} servers`);
53
+ const response: any = await this.manager.rest?.request('SET_GAME_SERVERS', servers);
54
+ const updatedServers = (Array.isArray(response?.data) ? response.data : []) as CMSServerAPIStruct[];
55
+
56
+ if (updatedServers.length > 0) {
57
+ this.cache.clear();
58
+ updatedServers.forEach((server) => {
59
+ const serverStruct = {
60
+ id: server.id,
61
+ config: server
62
+ };
63
+ this._add(serverStruct, true, server.id);
64
+ });
65
+ }
66
+
67
+ resolve({ success: true, data: updatedServers as globalTypes.CMSSetGameServerStruct[] });
28
68
  } catch (err) {
29
- throw new Error(String(err));
69
+ if (err instanceof APIError) {
70
+ resolve({ success: false, reason: err.response });
71
+ } else {
72
+ reject(err);
73
+ }
30
74
  }
31
- })();
75
+ });
32
76
  }
33
- }
77
+ }
@@ -0,0 +1,187 @@
1
+ import { Instance } from '../instance/Instance';
2
+ import { APIError, DefaultRadioRestOptions, REST } from '../libs/rest/src';
3
+ import { BaseManager } from './BaseManager';
4
+ import * as globalTypes from '../constants';
5
+ import type { Mutable } from '../constants';
6
+
7
+ /**
8
+ * Manages all Sonoran Radio API interactions.
9
+ */
10
+ export class RadioManager extends BaseManager {
11
+ public readonly ready: boolean = false;
12
+ public readonly failReason: unknown = null;
13
+ public rest: REST | undefined;
14
+
15
+ constructor(instance: Instance) {
16
+ super(instance);
17
+
18
+ this.rest = new REST(instance, this, globalTypes.productEnums.RADIO, DefaultRadioRestOptions);
19
+ void this.buildManager(instance);
20
+ }
21
+
22
+ protected async buildManager(instance: Instance) {
23
+ const mutableThis = this as Mutable<RadioManager>;
24
+ try {
25
+ mutableThis.ready = true;
26
+ instance.isRadioSuccessful = true;
27
+ instance.emit('RADIO_SETUP_SUCCESSFUL');
28
+ } catch (err) {
29
+ mutableThis.failReason = err;
30
+ instance.emit('RADIO_SETUP_UNSUCCESSFUL', err);
31
+ throw err;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Retrieves the configured community channel groups and channels.
37
+ */
38
+ public async getCommunityChannels(): Promise<globalTypes.RadioGetCommunityChannelsPromiseResult> {
39
+ return new Promise(async (resolve, reject) => {
40
+ try {
41
+ const response: any = await this.rest?.request('RADIO_GET_COMMUNITY_CHANNELS');
42
+ resolve({ success: true, data: response });
43
+ } catch (err) {
44
+ if (err instanceof APIError) {
45
+ resolve({ success: false, reason: err.response });
46
+ } else {
47
+ reject(err);
48
+ }
49
+ }
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Retrieves all connected users for the community.
55
+ */
56
+ public async getConnectedUsers(): Promise<globalTypes.RadioGetConnectedUsersPromiseResult> {
57
+ return new Promise(async (resolve, reject) => {
58
+ try {
59
+ const response: any = await this.rest?.request('RADIO_GET_CONNECTED_USERS');
60
+ resolve({ success: true, data: response });
61
+ } catch (err) {
62
+ if (err instanceof APIError) {
63
+ resolve({ success: false, reason: err.response });
64
+ } else {
65
+ reject(err);
66
+ }
67
+ }
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Retrieves a specific connected user by room id and identity.
73
+ * @param {number} roomId Multi-server room id.
74
+ * @param {string} identity User account UUID.
75
+ */
76
+ public async getConnectedUser(roomId: number, identity: string): Promise<globalTypes.RadioGetConnectedUserPromiseResult> {
77
+ return new Promise(async (resolve, reject) => {
78
+ try {
79
+ const response: any = await this.rest?.request('RADIO_GET_CONNECTED_USER', roomId, identity);
80
+ resolve({ success: true, data: response });
81
+ } catch (err) {
82
+ if (err instanceof APIError) {
83
+ resolve({ success: false, reason: err.response });
84
+ } else {
85
+ reject(err);
86
+ }
87
+ }
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Updates a user's transmit and scanned channel configuration.
93
+ * @param {string} identity The user's UUID.
94
+ * @param {RadioSetUserChannelsOptions} options Transmit and scan channel configuration.
95
+ */
96
+ public async setUserChannels(identity: string, options: globalTypes.RadioSetUserChannelsOptions = {}): Promise<globalTypes.RadioSetUserChannelsPromiseResult> {
97
+ return new Promise(async (resolve, reject) => {
98
+ try {
99
+ const response: any = await this.rest?.request('RADIO_SET_USER_CHANNELS', identity, options);
100
+ resolve({ success: true, result: response?.result ?? response });
101
+ } catch (err) {
102
+ if (err instanceof APIError) {
103
+ resolve({ success: false, reason: err.response });
104
+ } else {
105
+ reject(err);
106
+ }
107
+ }
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Updates a user's display name.
113
+ * @param {string} accId The user's account UUID.
114
+ * @param {string} displayName The new display name.
115
+ */
116
+ public async setUserDisplayName(accId: string, displayName: string): Promise<globalTypes.RadioSetUserDisplayNamePromiseResult> {
117
+ return new Promise(async (resolve, reject) => {
118
+ try {
119
+ const response: any = await this.rest?.request('RADIO_SET_USER_DISPLAY_NAME', accId, displayName);
120
+ resolve({ success: true, result: response?.result ?? response });
121
+ } catch (err) {
122
+ if (err instanceof APIError) {
123
+ resolve({ success: false, reason: err.response });
124
+ } else {
125
+ reject(err);
126
+ }
127
+ }
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Retrieves the community subscription level determined by the calling server's IP.
133
+ */
134
+ public async getServerSubscriptionFromIp(): Promise<globalTypes.RadioGetServerSubscriptionFromIpPromiseResult> {
135
+ return new Promise(async (resolve, reject) => {
136
+ try {
137
+ const response: any = await this.rest?.request('RADIO_GET_SERVER_SUBSCRIPTION_FROM_IP');
138
+ resolve({ success: true, data: response });
139
+ } catch (err) {
140
+ if (err instanceof APIError) {
141
+ resolve({ success: false, reason: err.response });
142
+ } else {
143
+ reject(err);
144
+ }
145
+ }
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Sets the push event URL for the community.
151
+ * @param {string} pushUrl The server push URL.
152
+ */
153
+ public async setServerIp(pushUrl: string): Promise<globalTypes.RadioSetServerIpPromiseResult> {
154
+ return new Promise(async (resolve, reject) => {
155
+ try {
156
+ const response: any = await this.rest?.request('RADIO_SET_SERVER_IP', pushUrl);
157
+ resolve({ success: true, result: response?.result ?? response });
158
+ } catch (err) {
159
+ if (err instanceof APIError) {
160
+ resolve({ success: false, reason: err.response });
161
+ } else {
162
+ reject(err);
163
+ }
164
+ }
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Sets the available in-game speaker locations for tone dispatching.
170
+ * @param {RadioSpeakerLocation[]} locations Collection of speaker locations.
171
+ * @param {string} [token] Optional bearer token for authorization. Defaults to the community API key.
172
+ */
173
+ public async setInGameSpeakerLocations(locations: globalTypes.RadioSpeakerLocation[], token?: string): Promise<globalTypes.RadioSetInGameSpeakerLocationsPromiseResult> {
174
+ return new Promise(async (resolve, reject) => {
175
+ try {
176
+ const response: any = await this.rest?.request('RADIO_SET_IN_GAME_SPEAKER_LOCATIONS', locations, token);
177
+ resolve({ success: true, result: response?.result ?? response });
178
+ } catch (err) {
179
+ if (err instanceof APIError) {
180
+ resolve({ success: false, reason: err.response });
181
+ } else {
182
+ reject(err);
183
+ }
184
+ }
185
+ });
186
+ }
187
+ }