@dendotdev/grunt 1.0.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/dist/index.mjs ADDED
@@ -0,0 +1,2873 @@
1
+ import { LRUCache } from 'lru-cache';
2
+
3
+ // src/core/cache/cache-manager.ts
4
+ var DEFAULT_TTL_MS = 60 * 60 * 1e3;
5
+ var DEFAULT_MAX_SIZE = 1e3;
6
+ var CacheManager = class {
7
+ cache;
8
+ /**
9
+ * Creates a new cache manager.
10
+ *
11
+ * @param ttlMs - Time-to-live for cached entries in milliseconds
12
+ * @param maxSize - Maximum number of entries to cache
13
+ */
14
+ constructor(ttlMs = DEFAULT_TTL_MS, maxSize = DEFAULT_MAX_SIZE) {
15
+ this.cache = new LRUCache({
16
+ max: maxSize,
17
+ ttl: ttlMs,
18
+ // Calculate size based on content length for memory management
19
+ sizeCalculation: (value) => {
20
+ return value.content.length + (value.etag?.length ?? 0);
21
+ },
22
+ // 50MB max memory for cache
23
+ maxSize: 50 * 1024 * 1024
24
+ });
25
+ }
26
+ /**
27
+ * Get a cached response if it exists and hasn't expired.
28
+ *
29
+ * @param key - Cache key (typically the request URL)
30
+ * @returns Cached response or null if not found/expired
31
+ */
32
+ get(key) {
33
+ return this.cache.get(key) ?? null;
34
+ }
35
+ /**
36
+ * Store a response in the cache.
37
+ *
38
+ * @param key - Cache key (typically the request URL)
39
+ * @param response - Response to cache
40
+ */
41
+ set(key, response) {
42
+ this.cache.set(key, response);
43
+ }
44
+ /**
45
+ * Check if a key exists in the cache without updating its recency.
46
+ *
47
+ * @param key - Cache key to check
48
+ * @returns true if the key exists and hasn't expired
49
+ */
50
+ has(key) {
51
+ return this.cache.has(key);
52
+ }
53
+ /**
54
+ * Remove a specific entry from the cache.
55
+ *
56
+ * @param key - Cache key to remove
57
+ * @returns true if an entry was removed
58
+ */
59
+ delete(key) {
60
+ return this.cache.delete(key);
61
+ }
62
+ /**
63
+ * Clear all cached entries.
64
+ */
65
+ clear() {
66
+ this.cache.clear();
67
+ }
68
+ /**
69
+ * Get the current number of cached entries.
70
+ */
71
+ get size() {
72
+ return this.cache.size;
73
+ }
74
+ /**
75
+ * Manually trigger cleanup of expired entries.
76
+ * This is called automatically by the LRU cache, but can be
77
+ * invoked manually if needed.
78
+ */
79
+ prune() {
80
+ this.cache.purgeStale();
81
+ }
82
+ };
83
+
84
+ // src/core/http/retry-policy.ts
85
+ var DEFAULT_RETRY_OPTIONS = {
86
+ maxRetries: 3,
87
+ retryDelays: [200, 500, 1e3]
88
+ };
89
+ var TRANSIENT_STATUS_CODES = /* @__PURE__ */ new Set([
90
+ 408,
91
+ // Request Timeout
92
+ 429,
93
+ // Too Many Requests
94
+ 500,
95
+ // Internal Server Error
96
+ 502,
97
+ // Bad Gateway
98
+ 503,
99
+ // Service Unavailable
100
+ 504
101
+ // Gateway Timeout
102
+ ]);
103
+ var RetryPolicy = class {
104
+ options;
105
+ /**
106
+ * Creates a new retry policy.
107
+ *
108
+ * @param options - Configuration options
109
+ */
110
+ constructor(options = {}) {
111
+ this.options = {
112
+ maxRetries: options.maxRetries ?? DEFAULT_RETRY_OPTIONS.maxRetries,
113
+ retryDelays: options.retryDelays ?? DEFAULT_RETRY_OPTIONS.retryDelays
114
+ };
115
+ }
116
+ /**
117
+ * Execute a function with retry logic.
118
+ *
119
+ * The function is called immediately. If it fails with a retryable error,
120
+ * it will be retried up to maxRetries times with delays between attempts.
121
+ *
122
+ * @param fn - Async function that returns a Response
123
+ * @returns The successful Response
124
+ * @throws The last error if all retries are exhausted
125
+ */
126
+ async execute(fn) {
127
+ let lastError = null;
128
+ for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {
129
+ try {
130
+ const response = await fn();
131
+ if (this.isTransientError(response)) {
132
+ lastError = new Error(
133
+ `HTTP ${response.status}: ${response.statusText}`
134
+ );
135
+ if (attempt < this.options.maxRetries) {
136
+ await this.delay(attempt);
137
+ continue;
138
+ }
139
+ }
140
+ return response;
141
+ } catch (error) {
142
+ lastError = error instanceof Error ? error : new Error(String(error));
143
+ if (!this.isRetryableError(error) || attempt >= this.options.maxRetries) {
144
+ throw lastError;
145
+ }
146
+ await this.delay(attempt);
147
+ }
148
+ }
149
+ throw lastError ?? new Error("Retry exhausted");
150
+ }
151
+ /**
152
+ * Check if a response indicates a transient server error.
153
+ */
154
+ isTransientError(response) {
155
+ return TRANSIENT_STATUS_CODES.has(response.status);
156
+ }
157
+ /**
158
+ * Check if an error is worth retrying.
159
+ * Network errors (TypeError from fetch) are retryable.
160
+ */
161
+ isRetryableError(error) {
162
+ if (error instanceof TypeError) {
163
+ return true;
164
+ }
165
+ return false;
166
+ }
167
+ /**
168
+ * Wait for the appropriate delay before the next retry.
169
+ */
170
+ delay(attempt) {
171
+ const delays = this.options.retryDelays;
172
+ const delayMs = delays[attempt] ?? delays[delays.length - 1] ?? 1e3;
173
+ return new Promise((resolve) => setTimeout(resolve, delayMs));
174
+ }
175
+ };
176
+
177
+ // src/models/common/api-content-type.ts
178
+ var ApiContentType = {
179
+ /** Standard JSON content type */
180
+ Json: "json",
181
+ /** Bond compact binary format for binary data */
182
+ BondCompactBinary: "bond"
183
+ };
184
+ function getContentTypeHeader(contentType) {
185
+ switch (contentType) {
186
+ case ApiContentType.Json:
187
+ return "application/json";
188
+ case ApiContentType.BondCompactBinary:
189
+ return "application/x-bond-compact-binary";
190
+ default:
191
+ return "application/json";
192
+ }
193
+ }
194
+
195
+ // src/utils/constants.ts
196
+ var USER_AGENTS = {
197
+ /**
198
+ * User-Agent for Halo PC client requests.
199
+ */
200
+ HALO_PC: "SHIVA-2043073184/6.10025.12948.0 (release; PC)",
201
+ /**
202
+ * User-Agent for Halo Waypoint app requests.
203
+ */
204
+ HALO_WAYPOINT: "HaloWaypoint/2021112313511900 CFNetwork/1327.0.4 Darwin/21.2.0",
205
+ /**
206
+ * Standard web browser User-Agent.
207
+ */
208
+ WEB: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
209
+ };
210
+ var DEFAULT_AUTH_SCOPES = [
211
+ "Xboxlive.signin",
212
+ "Xboxlive.offline_access"
213
+ ];
214
+ var HEADERS = {
215
+ /** Spartan token authentication header */
216
+ SPARTAN_AUTH: "x-343-authorization-spartan",
217
+ /** Clearance/flight token header */
218
+ CLEARANCE: "343-clearance",
219
+ /** Standard Content-Type header */
220
+ CONTENT_TYPE: "Content-Type",
221
+ /** Standard Accept header */
222
+ ACCEPT: "Accept",
223
+ /** Standard User-Agent header */
224
+ USER_AGENT: "User-Agent",
225
+ /** ETag header for caching */
226
+ ETAG: "ETag",
227
+ /** If-None-Match header for conditional requests */
228
+ IF_NONE_MATCH: "If-None-Match"
229
+ };
230
+ var DEFAULT_TIMEOUT_MS = 3e4;
231
+ var DEFAULT_CACHE_TTL_MS = 60 * 60 * 1e3;
232
+ var DEFAULT_MAX_RETRIES = 3;
233
+
234
+ // src/clients/base/client-base.ts
235
+ var ClientBase = class {
236
+ /**
237
+ * Fetch function used for HTTP requests.
238
+ * Can be overridden for testing or custom environments.
239
+ */
240
+ fetchFn;
241
+ /**
242
+ * Cache manager for ETag-based response caching.
243
+ */
244
+ cache;
245
+ /**
246
+ * Retry policy for handling transient failures.
247
+ */
248
+ retryPolicy;
249
+ /**
250
+ * Spartan token for API authentication.
251
+ * Obtained through Xbox Live XSTS token exchange.
252
+ */
253
+ spartanToken = "";
254
+ /**
255
+ * Xbox User ID in numeric format.
256
+ * Used for player-specific API requests.
257
+ */
258
+ xuid = "";
259
+ /**
260
+ * Clearance/flight token for accessing flighted content.
261
+ */
262
+ clearanceToken = "";
263
+ /**
264
+ * Whether to include raw request/response data in results.
265
+ * Useful for debugging but adds overhead.
266
+ */
267
+ includeRawResponses = false;
268
+ /**
269
+ * Custom User-Agent header value.
270
+ */
271
+ userAgent = "";
272
+ /**
273
+ * Creates a new ClientBase instance.
274
+ *
275
+ * @param options - Configuration options
276
+ */
277
+ constructor(options) {
278
+ this.fetchFn = options?.fetchFn ?? globalThis.fetch.bind(globalThis);
279
+ this.cache = new CacheManager(options?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS);
280
+ this.retryPolicy = new RetryPolicy({
281
+ maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES
282
+ });
283
+ }
284
+ /**
285
+ * Execute an API request with caching, retry, and response handling.
286
+ *
287
+ * This is the core method that all module methods call. It handles:
288
+ * - Building the request with appropriate headers
289
+ * - ETag-based caching and 304 Not Modified responses
290
+ * - Retry logic for transient failures
291
+ * - Response deserialization
292
+ * - Raw response capture (when enabled)
293
+ *
294
+ * @template T - Expected response data type
295
+ * @param endpoint - Full URL for the request
296
+ * @param method - HTTP method to use
297
+ * @param options - Request configuration options
298
+ * @returns Promise resolving to the API result
299
+ */
300
+ async executeRequest(endpoint, method, options = {}) {
301
+ const result = {
302
+ result: null,
303
+ response: { code: 0 }
304
+ };
305
+ const headers = this.buildHeaders(options);
306
+ const requestInit = {
307
+ method,
308
+ headers
309
+ };
310
+ if (options.body) {
311
+ requestInit.body = options.body;
312
+ }
313
+ if (this.includeRawResponses) {
314
+ result.response.requestUrl = endpoint;
315
+ result.response.requestMethod = method;
316
+ result.response.requestHeaders = Object.fromEntries(headers.entries());
317
+ if (typeof options.body === "string") {
318
+ result.response.requestBody = options.body;
319
+ }
320
+ }
321
+ try {
322
+ const cacheKey = method === "GET" ? endpoint : null;
323
+ const cached = cacheKey ? this.cache.get(cacheKey) : null;
324
+ if (cached?.etag) {
325
+ headers.set(HEADERS.IF_NONE_MATCH, cached.etag);
326
+ }
327
+ const response = await this.retryPolicy.execute(async () => {
328
+ return this.fetchFn(endpoint, requestInit);
329
+ });
330
+ result.response.code = response.status;
331
+ if (this.includeRawResponses) {
332
+ result.response.responseHeaders = Object.fromEntries(
333
+ response.headers.entries()
334
+ );
335
+ }
336
+ if (response.status === 304 && cached) {
337
+ result.result = this.deserializeResponse(cached.content);
338
+ result.response.message = "304 Not Modified - using cached response";
339
+ return result;
340
+ }
341
+ const bodyBuffer = await response.arrayBuffer();
342
+ const bodyBytes = new Uint8Array(bodyBuffer);
343
+ if (cacheKey && response.ok) {
344
+ const etag = response.headers.get(HEADERS.ETAG) ?? void 0;
345
+ this.cache.set(cacheKey, { etag, content: bodyBytes });
346
+ }
347
+ const bodyText = new TextDecoder().decode(bodyBytes);
348
+ result.response.message = bodyText;
349
+ if (response.ok || options.enforceSuccess !== false) {
350
+ result.result = this.deserializeResponse(bodyBytes);
351
+ }
352
+ } catch (error) {
353
+ result.response.code = 0;
354
+ result.response.message = error instanceof Error ? error.message : String(error);
355
+ }
356
+ return result;
357
+ }
358
+ /**
359
+ * Build request headers based on options.
360
+ */
361
+ buildHeaders(options) {
362
+ const headers = new Headers();
363
+ headers.set(HEADERS.ACCEPT, "application/json");
364
+ if (options.body !== void 0) {
365
+ const contentType = getContentTypeHeader(
366
+ options.contentType ?? ApiContentType.Json
367
+ );
368
+ headers.set(HEADERS.CONTENT_TYPE, contentType);
369
+ }
370
+ if (options.useSpartanToken !== false && this.spartanToken) {
371
+ headers.set(HEADERS.SPARTAN_AUTH, this.spartanToken);
372
+ }
373
+ if (options.useClearance && this.clearanceToken) {
374
+ headers.set(HEADERS.CLEARANCE, this.clearanceToken);
375
+ }
376
+ if (this.userAgent) {
377
+ headers.set(HEADERS.USER_AGENT, this.userAgent);
378
+ }
379
+ if (options.customHeaders) {
380
+ for (const [key, value] of Object.entries(options.customHeaders)) {
381
+ headers.set(key, value);
382
+ }
383
+ }
384
+ return headers;
385
+ }
386
+ /**
387
+ * Deserialize response bytes to the expected type.
388
+ */
389
+ deserializeResponse(data) {
390
+ if (data.length === 0) {
391
+ return null;
392
+ }
393
+ const text = new TextDecoder().decode(data);
394
+ if (text === "true") {
395
+ return true;
396
+ }
397
+ if (text === "false") {
398
+ return false;
399
+ }
400
+ try {
401
+ return JSON.parse(text);
402
+ } catch {
403
+ return text;
404
+ }
405
+ }
406
+ /**
407
+ * Clear the response cache.
408
+ * Useful when you know data has changed.
409
+ */
410
+ clearCache() {
411
+ this.cache.clear();
412
+ }
413
+ };
414
+
415
+ // src/endpoints/halo-core-endpoints.ts
416
+ var HALO_CORE_ENDPOINTS = {
417
+ /**
418
+ * Base service domain for all Halo Waypoint services.
419
+ */
420
+ SERVICE_DOMAIN: "svc.halowaypoint.com",
421
+ // ─────────────────────────────────────────────────────────────────
422
+ // Service Origins (prepended to SERVICE_DOMAIN)
423
+ // ─────────────────────────────────────────────────────────────────
424
+ /** Game CMS for static content (challenges, items, etc.) */
425
+ GAME_CMS_ORIGIN: "gamecms-hacs",
426
+ /** Economy service for stores, inventory, customization */
427
+ ECONOMY_ORIGIN: "economy",
428
+ /** UGC authoring service for creating/editing user content */
429
+ AUTHORING_ORIGIN: "authoring-infiniteugc",
430
+ /** UGC discovery service for searching user content */
431
+ DISCOVERY_ORIGIN: "discovery-infiniteugc",
432
+ /** Lobby service for multiplayer lobbies and presence */
433
+ LOBBY_ORIGIN: "lobby-hi",
434
+ /** Settings and configuration service */
435
+ SETTINGS_ORIGIN: "settings",
436
+ /** Skill and CSR rating service */
437
+ SKILL_ORIGIN: "skill",
438
+ /** Ban processor service */
439
+ BAN_PROCESSOR_ORIGIN: "banprocessor",
440
+ /** Stats service for match history and service records */
441
+ STATS_ORIGIN: "halostats",
442
+ /** Text moderation service */
443
+ TEXT_ORIGIN: "text",
444
+ /** Content service for articles and news */
445
+ CONTENT_ORIGIN: "content-hacs",
446
+ // ─────────────────────────────────────────────────────────────────
447
+ // Authentication Endpoints
448
+ // ─────────────────────────────────────────────────────────────────
449
+ /**
450
+ * Endpoint for obtaining a Spartan token from an XSTS token.
451
+ */
452
+ SPARTAN_TOKEN_ENDPOINT: "https://settings.svc.halowaypoint.com/spartan-token",
453
+ /**
454
+ * Endpoint for discovering available Halo Infinite API endpoints.
455
+ * Returns configuration for all available services.
456
+ */
457
+ HALO_INFINITE_SETTINGS: "https://settings.svc.halowaypoint.com/settings/hipc/e2a0a7c6-6efe-42af-9283-c2ab73250c48",
458
+ // ─────────────────────────────────────────────────────────────────
459
+ // Xbox Live / XSTS Configuration
460
+ // ─────────────────────────────────────────────────────────────────
461
+ /**
462
+ * Relying party URL for XSTS token exchange.
463
+ * Use this when requesting an XSTS token for Halo Waypoint.
464
+ */
465
+ HALO_WAYPOINT_XSTS_RELYING_PARTY: "https://prod.xsts.halowaypoint.com/",
466
+ // ─────────────────────────────────────────────────────────────────
467
+ // Blob Storage
468
+ // ─────────────────────────────────────────────────────────────────
469
+ /**
470
+ * Base URL for UGC blob storage (maps, game variants, etc.)
471
+ */
472
+ BLOBS_ORIGIN: "blobs-infiniteugc"
473
+ };
474
+ var WAYPOINT_ENDPOINTS = {
475
+ /**
476
+ * Base domain for Halo Waypoint web services.
477
+ */
478
+ WEB_DOMAIN: "www.halowaypoint.com",
479
+ /**
480
+ * API subdomain for Waypoint services.
481
+ */
482
+ API_DOMAIN: "api.halowaypoint.com",
483
+ /**
484
+ * Profile API origin.
485
+ */
486
+ PROFILE_ORIGIN: "profile",
487
+ /**
488
+ * Redemption API origin for code redemption.
489
+ */
490
+ REDEMPTION_ORIGIN: "redemption"
491
+ };
492
+ function buildServiceUrl(origin, path) {
493
+ return `https://${origin}.${HALO_CORE_ENDPOINTS.SERVICE_DOMAIN}${path}`;
494
+ }
495
+
496
+ // src/modules/base/module-base.ts
497
+ var ModuleBase = class {
498
+ /**
499
+ * Reference to the parent client for making HTTP requests.
500
+ */
501
+ client;
502
+ /**
503
+ * Service origin for this module (e.g., 'halostats', 'economy').
504
+ */
505
+ origin;
506
+ /**
507
+ * Creates a new module instance.
508
+ *
509
+ * @param client - Parent client instance
510
+ * @param origin - Service origin for URL building
511
+ */
512
+ constructor(client, origin) {
513
+ this.client = client;
514
+ this.origin = origin;
515
+ }
516
+ /**
517
+ * Build a full URL from a relative path using this module's origin.
518
+ *
519
+ * @param path - API path starting with /
520
+ * @returns Full HTTPS URL
521
+ */
522
+ buildUrl(path) {
523
+ return buildServiceUrl(this.origin, path);
524
+ }
525
+ /**
526
+ * Execute a GET request to this module's service.
527
+ *
528
+ * @template T - Expected response type
529
+ * @param path - API path (e.g., '/hi/players/xuid(...)/matches')
530
+ * @param options - Request options
531
+ * @returns Promise with the API result
532
+ */
533
+ get(path, options = {}) {
534
+ return this.client.executeRequest(this.buildUrl(path), "GET", {
535
+ useSpartanToken: options.useSpartanToken ?? true,
536
+ useClearance: options.useClearance ?? false,
537
+ customHeaders: options.customHeaders
538
+ });
539
+ }
540
+ /**
541
+ * Execute a GET request to an absolute URL.
542
+ *
543
+ * Used when the URL doesn't follow the standard origin pattern
544
+ * (e.g., blob storage URLs).
545
+ *
546
+ * @template T - Expected response type
547
+ * @param fullUrl - Complete URL to request
548
+ * @param options - Request options
549
+ * @returns Promise with the API result
550
+ */
551
+ getFullUrl(fullUrl, options = {}) {
552
+ return this.client.executeRequest(fullUrl, "GET", {
553
+ useSpartanToken: options.useSpartanToken ?? true,
554
+ useClearance: options.useClearance ?? false,
555
+ customHeaders: options.customHeaders,
556
+ enforceSuccess: options.enforceSuccess ?? true
557
+ });
558
+ }
559
+ /**
560
+ * Execute a POST request to this module's service.
561
+ *
562
+ * @template T - Expected response type
563
+ * @param path - API path
564
+ * @param body - Optional request body as string
565
+ * @param options - Request options
566
+ * @returns Promise with the API result
567
+ */
568
+ post(path, body, options = {}) {
569
+ return this.client.executeRequest(this.buildUrl(path), "POST", {
570
+ useSpartanToken: options.useSpartanToken ?? true,
571
+ useClearance: options.useClearance ?? false,
572
+ body,
573
+ customHeaders: options.customHeaders
574
+ });
575
+ }
576
+ /**
577
+ * Execute a POST request with a JSON body.
578
+ *
579
+ * @template T - Expected response type
580
+ * @template TBody - Request body type
581
+ * @param path - API path
582
+ * @param body - Request body (will be serialized to JSON)
583
+ * @param options - Request options
584
+ * @returns Promise with the API result
585
+ */
586
+ postJson(path, body, options = {}) {
587
+ return this.client.executeRequest(this.buildUrl(path), "POST", {
588
+ useSpartanToken: options.useSpartanToken ?? true,
589
+ useClearance: options.useClearance ?? false,
590
+ body: JSON.stringify(body),
591
+ contentType: options.contentType ?? ApiContentType.Json,
592
+ customHeaders: options.customHeaders
593
+ });
594
+ }
595
+ /**
596
+ * Execute a PUT request with a JSON body.
597
+ *
598
+ * @template T - Expected response type
599
+ * @template TBody - Request body type
600
+ * @param path - API path
601
+ * @param body - Request body (will be serialized to JSON)
602
+ * @param options - Request options
603
+ * @returns Promise with the API result
604
+ */
605
+ putJson(path, body, options = {}) {
606
+ return this.client.executeRequest(this.buildUrl(path), "PUT", {
607
+ useSpartanToken: options.useSpartanToken ?? true,
608
+ useClearance: options.useClearance ?? false,
609
+ body: JSON.stringify(body),
610
+ customHeaders: options.customHeaders
611
+ });
612
+ }
613
+ /**
614
+ * Execute a PATCH request with a JSON body.
615
+ *
616
+ * @template T - Expected response type
617
+ * @template TBody - Request body type
618
+ * @param path - API path
619
+ * @param body - Request body (will be serialized to JSON)
620
+ * @param options - Request options
621
+ * @returns Promise with the API result
622
+ */
623
+ patchJson(path, body, options = {}) {
624
+ return this.client.executeRequest(this.buildUrl(path), "PATCH", {
625
+ useSpartanToken: options.useSpartanToken ?? true,
626
+ useClearance: options.useClearance ?? false,
627
+ body: JSON.stringify(body),
628
+ customHeaders: options.customHeaders
629
+ });
630
+ }
631
+ /**
632
+ * Execute a DELETE request.
633
+ *
634
+ * @template T - Expected response type
635
+ * @param path - API path
636
+ * @param options - Request options
637
+ * @returns Promise with the API result
638
+ */
639
+ delete(path, options = {}) {
640
+ return this.client.executeRequest(this.buildUrl(path), "DELETE", {
641
+ useSpartanToken: options.useSpartanToken ?? true,
642
+ useClearance: options.useClearance ?? false,
643
+ customHeaders: options.customHeaders
644
+ });
645
+ }
646
+ /**
647
+ * Validate that a parameter is not null or undefined.
648
+ *
649
+ * @param value - Value to check
650
+ * @param paramName - Parameter name for error message
651
+ * @throws Error if value is null or undefined
652
+ */
653
+ assertNotNull(value, paramName) {
654
+ if (value == null) {
655
+ throw new Error(`${paramName} cannot be null or undefined`);
656
+ }
657
+ }
658
+ /**
659
+ * Validate that a number is within a range.
660
+ *
661
+ * @param value - Value to check
662
+ * @param min - Minimum allowed value (inclusive)
663
+ * @param max - Maximum allowed value (inclusive)
664
+ * @param paramName - Parameter name for error message
665
+ * @throws RangeError if value is out of range
666
+ */
667
+ assertRange(value, min, max, paramName) {
668
+ if (value < min || value > max) {
669
+ throw new RangeError(`${paramName} must be between ${min} and ${max}`);
670
+ }
671
+ }
672
+ /**
673
+ * Validate that a string is not empty.
674
+ *
675
+ * @param value - Value to check
676
+ * @param paramName - Parameter name for error message
677
+ * @throws Error if value is empty
678
+ */
679
+ assertNotEmpty(value, paramName) {
680
+ if (!value || value.trim().length === 0) {
681
+ throw new Error(`${paramName} cannot be empty`);
682
+ }
683
+ }
684
+ };
685
+
686
+ // src/modules/halo-infinite/academy.module.ts
687
+ var AcademyModule = class extends ModuleBase {
688
+ constructor(client) {
689
+ super(client, HALO_CORE_ENDPOINTS.GAME_CMS_ORIGIN);
690
+ }
691
+ /**
692
+ * Get bot customization data.
693
+ *
694
+ * @param flightId - Flight/clearance ID
695
+ * @returns Bot customization options
696
+ */
697
+ getBotCustomization(flightId) {
698
+ const flightParam = flightId ? `?flight=${flightId}` : "";
699
+ return this.get(`/hi/academy/botcustomization${flightParam}`, {
700
+ useClearance: !!flightId
701
+ });
702
+ }
703
+ /**
704
+ * Get academy content manifest.
705
+ *
706
+ * @returns Academy client manifest
707
+ */
708
+ getContent() {
709
+ return this.get("/hi/academy/content");
710
+ }
711
+ /**
712
+ * Get test academy content (for flighted builds).
713
+ *
714
+ * @param clearanceId - Clearance identifier
715
+ * @returns Test academy manifest
716
+ */
717
+ getContentTest(clearanceId) {
718
+ this.assertNotEmpty(clearanceId, "clearanceId");
719
+ return this.get(`/hi/academy/content/test?clearanceId=${clearanceId}`, {
720
+ useClearance: true
721
+ });
722
+ }
723
+ /**
724
+ * Get star/scoring definitions for academy drills.
725
+ *
726
+ * @returns Star definitions
727
+ */
728
+ getStarDefinitions() {
729
+ return this.get("/hi/Progression/file/academy/stars");
730
+ }
731
+ };
732
+
733
+ // src/modules/halo-infinite/ban-processor.module.ts
734
+ var BanProcessorModule = class extends ModuleBase {
735
+ constructor(client) {
736
+ super(client, HALO_CORE_ENDPOINTS.BAN_PROCESSOR_ORIGIN);
737
+ }
738
+ /**
739
+ * Get ban summary for a list of players.
740
+ *
741
+ * @param targetList - List of player XUIDs to check
742
+ * @returns Ban summary results
743
+ */
744
+ banSummary(targetList) {
745
+ if (!targetList.length) {
746
+ throw new Error("targetList cannot be empty");
747
+ }
748
+ const playersQuery = targetList.map((id) => `targetXuids=${id}`).join("&");
749
+ return this.get(`/hi/bans/summary?${playersQuery}`);
750
+ }
751
+ };
752
+
753
+ // src/modules/halo-infinite/configuration.module.ts
754
+ var ConfigurationModule = class extends ModuleBase {
755
+ constructor(client) {
756
+ super(client, HALO_CORE_ENDPOINTS.SETTINGS_ORIGIN);
757
+ }
758
+ /**
759
+ * Get the API settings/configuration container.
760
+ *
761
+ * Returns the list of all available API endpoints and their configurations.
762
+ *
763
+ * @returns API configuration
764
+ */
765
+ getApiSettingsContainer() {
766
+ return this.getFullUrl(HALO_CORE_ENDPOINTS.HALO_INFINITE_SETTINGS, {
767
+ useSpartanToken: false
768
+ });
769
+ }
770
+ };
771
+
772
+ // src/modules/halo-infinite/economy.module.ts
773
+ var EconomyModule = class extends ModuleBase {
774
+ constructor(client) {
775
+ super(client, HALO_CORE_ENDPOINTS.ECONOMY_ORIGIN);
776
+ }
777
+ // ─────────────────────────────────────────────────────────────────
778
+ // Inventory & Currency
779
+ // ─────────────────────────────────────────────────────────────────
780
+ /**
781
+ * Get all inventory items for a player.
782
+ *
783
+ * @param player - Player's numeric XUID
784
+ * @returns Player inventory
785
+ */
786
+ getInventoryItems(player) {
787
+ this.assertNotEmpty(player, "player");
788
+ return this.get(`/hi/players/xuid(${player})/inventory`);
789
+ }
790
+ /**
791
+ * Get virtual currency balances for a player.
792
+ *
793
+ * @param player - Player's numeric XUID
794
+ * @returns Currency balances
795
+ */
796
+ getVirtualCurrencyBalances(player) {
797
+ this.assertNotEmpty(player, "player");
798
+ return this.get(`/hi/players/xuid(${player})/currencies`);
799
+ }
800
+ /**
801
+ * Post a currency transaction.
802
+ *
803
+ * @param player - Player's numeric XUID
804
+ * @param currencyId - Currency identifier
805
+ * @returns Transaction result
806
+ */
807
+ postCurrencyTransaction(player, currencyId) {
808
+ this.assertNotEmpty(player, "player");
809
+ this.assertNotEmpty(currencyId, "currencyId");
810
+ return this.post(
811
+ `/hi/players/xuid(${player})/currencies/${currencyId}/transactions`
812
+ );
813
+ }
814
+ // ─────────────────────────────────────────────────────────────────
815
+ // Customization
816
+ // ─────────────────────────────────────────────────────────────────
817
+ /**
818
+ * Get full player customization data.
819
+ *
820
+ * @param player - Player's numeric XUID
821
+ * @param viewType - View type (e.g., 'public', 'private')
822
+ * @returns Complete customization data
823
+ */
824
+ getPlayerCustomization(player, viewType = "public") {
825
+ this.assertNotEmpty(player, "player");
826
+ return this.get(
827
+ `/hi/players/xuid(${player})/customization?view=${viewType}`
828
+ );
829
+ }
830
+ /**
831
+ * Get all armor cores for a player.
832
+ *
833
+ * @param player - Player's numeric XUID
834
+ * @returns Armor core collection
835
+ */
836
+ armorCoresCustomization(player) {
837
+ this.assertNotEmpty(player, "player");
838
+ return this.get(`/hi/players/xuid(${player})/customization/armors`);
839
+ }
840
+ /**
841
+ * Get a specific armor core for a player.
842
+ *
843
+ * @param player - Player's numeric XUID
844
+ * @param coreId - Core identifier
845
+ * @returns Armor core details
846
+ */
847
+ armorCoreCustomization(player, coreId) {
848
+ this.assertNotEmpty(player, "player");
849
+ this.assertNotEmpty(coreId, "coreId");
850
+ return this.get(`/hi/players/xuid(${player})/customization/armors/${coreId}`);
851
+ }
852
+ /**
853
+ * Get all weapon cores for a player.
854
+ *
855
+ * @param player - Player's numeric XUID
856
+ * @returns Weapon core collection
857
+ */
858
+ weaponCoresCustomization(player) {
859
+ this.assertNotEmpty(player, "player");
860
+ return this.get(`/hi/players/xuid(${player})/customization/weapons`);
861
+ }
862
+ /**
863
+ * Get a specific weapon core for a player.
864
+ *
865
+ * @param player - Player's numeric XUID
866
+ * @param coreId - Core identifier
867
+ * @returns Weapon core details
868
+ */
869
+ weaponCoreCustomization(player, coreId) {
870
+ this.assertNotEmpty(player, "player");
871
+ this.assertNotEmpty(coreId, "coreId");
872
+ return this.get(`/hi/players/xuid(${player})/customization/weapons/${coreId}`);
873
+ }
874
+ /**
875
+ * Get all vehicle cores for a player.
876
+ *
877
+ * @param player - Player's numeric XUID
878
+ * @returns Vehicle core collection
879
+ */
880
+ vehicleCoresCustomization(player) {
881
+ this.assertNotEmpty(player, "player");
882
+ return this.get(`/hi/players/xuid(${player})/customization/vehicles`);
883
+ }
884
+ /**
885
+ * Get a specific vehicle core for a player.
886
+ *
887
+ * @param player - Player's numeric XUID
888
+ * @param coreId - Core identifier
889
+ * @returns Vehicle core details
890
+ */
891
+ vehicleCoreCustomization(player, coreId) {
892
+ this.assertNotEmpty(player, "player");
893
+ this.assertNotEmpty(coreId, "coreId");
894
+ return this.get(`/hi/players/xuid(${player})/customization/vehicles/${coreId}`);
895
+ }
896
+ /**
897
+ * Get all AI cores for a player.
898
+ *
899
+ * @param player - Player's numeric XUID
900
+ * @returns AI core container
901
+ */
902
+ aiCoresCustomization(player) {
903
+ this.assertNotEmpty(player, "player");
904
+ return this.get(`/hi/players/xuid(${player})/customization/ais`);
905
+ }
906
+ /**
907
+ * Get a specific AI core for a player.
908
+ *
909
+ * @param player - Player's numeric XUID
910
+ * @param coreId - Core identifier
911
+ * @returns AI core details
912
+ */
913
+ aiCoreCustomization(player, coreId) {
914
+ this.assertNotEmpty(player, "player");
915
+ this.assertNotEmpty(coreId, "coreId");
916
+ return this.get(`/hi/players/xuid(${player})/customization/ais/${coreId}`);
917
+ }
918
+ /**
919
+ * Get Spartan body customization for a player.
920
+ *
921
+ * @param player - Player's numeric XUID
922
+ * @returns Spartan body configuration
923
+ */
924
+ spartanBodyCustomization(player) {
925
+ this.assertNotEmpty(player, "player");
926
+ return this.get(`/hi/players/xuid(${player})/customization/spartanbody`);
927
+ }
928
+ /**
929
+ * Get appearance customization for a player.
930
+ *
931
+ * @param player - Player's numeric XUID
932
+ * @returns Appearance configuration
933
+ */
934
+ playerAppearanceCustomization(player) {
935
+ this.assertNotEmpty(player, "player");
936
+ return this.get(
937
+ `/hi/players/xuid(${player})/customization/appearance`
938
+ );
939
+ }
940
+ // ─────────────────────────────────────────────────────────────────
941
+ // Stores
942
+ // ─────────────────────────────────────────────────────────────────
943
+ /**
944
+ * Get the main store offerings.
945
+ *
946
+ * @param player - Player's numeric XUID
947
+ * @returns Store items
948
+ */
949
+ getMainStore(player) {
950
+ this.assertNotEmpty(player, "player");
951
+ return this.get(`/hi/players/xuid(${player})/stores/main`);
952
+ }
953
+ /**
954
+ * Get the HCS (esports) store offerings.
955
+ *
956
+ * @param player - Player's numeric XUID
957
+ * @returns Store items
958
+ */
959
+ getHcsStore(player) {
960
+ this.assertNotEmpty(player, "player");
961
+ return this.get(`/hi/players/xuid(${player})/stores/hcs`);
962
+ }
963
+ /**
964
+ * Get the boosts store offerings.
965
+ *
966
+ * @param player - Player's numeric XUID
967
+ * @returns Store items
968
+ */
969
+ getBoostsStore(player) {
970
+ this.assertNotEmpty(player, "player");
971
+ return this.get(`/hi/players/xuid(${player})/stores/boosts`);
972
+ }
973
+ /**
974
+ * Get the soft currency (Spartan Points) store.
975
+ *
976
+ * @param player - Player's numeric XUID
977
+ * @returns Store items
978
+ */
979
+ getSoftCurrencyStore(player) {
980
+ this.assertNotEmpty(player, "player");
981
+ return this.get(`/hi/players/xuid(${player})/stores/softcurrency`);
982
+ }
983
+ /**
984
+ * Get the customization store offerings.
985
+ *
986
+ * @param player - Player's numeric XUID
987
+ * @returns Store items
988
+ */
989
+ getCustomizationStore(player) {
990
+ this.assertNotEmpty(player, "player");
991
+ return this.get(`/hi/players/xuid(${player})/stores/customization`);
992
+ }
993
+ /**
994
+ * Get the operations store offerings.
995
+ *
996
+ * @param player - Player's numeric XUID
997
+ * @returns Store items
998
+ */
999
+ getOperationsStore(player) {
1000
+ this.assertNotEmpty(player, "player");
1001
+ return this.get(`/hi/players/xuid(${player})/stores/operations`);
1002
+ }
1003
+ /**
1004
+ * Get scheduled storefront offerings.
1005
+ *
1006
+ * @param player - Player's numeric XUID
1007
+ * @param storeId - Store identifier
1008
+ * @returns Scheduled store items
1009
+ */
1010
+ getScheduledStorefrontOfferings(player, storeId) {
1011
+ this.assertNotEmpty(player, "player");
1012
+ this.assertNotEmpty(storeId, "storeId");
1013
+ return this.get(
1014
+ `/hi/players/xuid(${player})/stores/${storeId}/scheduled`
1015
+ );
1016
+ }
1017
+ // ─────────────────────────────────────────────────────────────────
1018
+ // Boosts & Rewards
1019
+ // ─────────────────────────────────────────────────────────────────
1020
+ /**
1021
+ * Get active boosts for a player.
1022
+ *
1023
+ * @param player - Player's numeric XUID
1024
+ * @returns Active boosts container
1025
+ */
1026
+ getActiveBoosts(player) {
1027
+ this.assertNotEmpty(player, "player");
1028
+ return this.get(`/hi/players/xuid(${player})/boosts`);
1029
+ }
1030
+ /**
1031
+ * Get awarded rewards for a player.
1032
+ *
1033
+ * @param player - Player's numeric XUID
1034
+ * @param rewardId - Reward identifier
1035
+ * @returns Reward snapshot
1036
+ */
1037
+ getAwardedRewards(player, rewardId) {
1038
+ this.assertNotEmpty(player, "player");
1039
+ this.assertNotEmpty(rewardId, "rewardId");
1040
+ return this.get(
1041
+ `/hi/players/xuid(${player})/rewards/${rewardId}`
1042
+ );
1043
+ }
1044
+ /**
1045
+ * Get giveaway rewards for a player.
1046
+ *
1047
+ * @param player - Player's numeric XUID
1048
+ * @returns Available giveaways
1049
+ */
1050
+ getGiveawayRewards(player) {
1051
+ this.assertNotEmpty(player, "player");
1052
+ return this.get(`/hi/players/xuid(${player})/giveaways`);
1053
+ }
1054
+ // ─────────────────────────────────────────────────────────────────
1055
+ // Reward Tracks / Operations
1056
+ // ─────────────────────────────────────────────────────────────────
1057
+ /**
1058
+ * Get reward track progress for a player.
1059
+ *
1060
+ * @param player - Player's numeric XUID
1061
+ * @param rewardTrackType - Type of reward track
1062
+ * @param trackId - Track identifier
1063
+ * @returns Reward track details
1064
+ */
1065
+ getRewardTrack(player, rewardTrackType, trackId) {
1066
+ this.assertNotEmpty(player, "player");
1067
+ this.assertNotEmpty(rewardTrackType, "rewardTrackType");
1068
+ this.assertNotEmpty(trackId, "trackId");
1069
+ return this.get(
1070
+ `/hi/players/xuid(${player})/rewardtracks/${rewardTrackType}/${trackId}`
1071
+ );
1072
+ }
1073
+ /**
1074
+ * Get operation progress for a player.
1075
+ *
1076
+ * @param player - Player's numeric XUID
1077
+ * @returns Operation reward track snapshot
1078
+ */
1079
+ getPlayerOperations(player) {
1080
+ this.assertNotEmpty(player, "player");
1081
+ return this.get(
1082
+ `/hi/players/xuid(${player})/operations`,
1083
+ { useClearance: true }
1084
+ );
1085
+ }
1086
+ /**
1087
+ * Get career rank for players.
1088
+ *
1089
+ * @param playerIds - List of player XUIDs
1090
+ * @param careerPathId - Career path identifier
1091
+ * @returns Career rank results
1092
+ */
1093
+ getPlayerCareerRank(playerIds, careerPathId) {
1094
+ if (!playerIds.length) {
1095
+ throw new Error("playerIds cannot be empty");
1096
+ }
1097
+ this.assertNotEmpty(careerPathId, "careerPathId");
1098
+ const playersQuery = playerIds.map((id) => `players=xuid(${id})`).join("&");
1099
+ return this.get(
1100
+ `/hi/careerranks/${careerPathId}?${playersQuery}`
1101
+ );
1102
+ }
1103
+ };
1104
+
1105
+ // src/modules/halo-infinite/game-cms.module.ts
1106
+ var GameCmsModule = class extends ModuleBase {
1107
+ constructor(client) {
1108
+ super(client, HALO_CORE_ENDPOINTS.GAME_CMS_ORIGIN);
1109
+ }
1110
+ // ─────────────────────────────────────────────────────────────────
1111
+ // Items & Inventory
1112
+ // ─────────────────────────────────────────────────────────────────
1113
+ /**
1114
+ * Get an item definition by path.
1115
+ *
1116
+ * @param itemPath - Path to the item
1117
+ * @param flightId - Flight ID for clearance
1118
+ * @returns Item definition
1119
+ */
1120
+ getItem(itemPath, flightId) {
1121
+ this.assertNotEmpty(itemPath, "itemPath");
1122
+ const flightParam = flightId ? `?flight=${flightId}` : "";
1123
+ return this.get(`/hi/Progression/file/${itemPath}${flightParam}`, {
1124
+ useClearance: !!flightId
1125
+ });
1126
+ }
1127
+ /**
1128
+ * Get the customization catalog/inventory definitions.
1129
+ *
1130
+ * @param flightId - Optional flight ID for flighted content
1131
+ * @returns Inventory definition
1132
+ */
1133
+ getCustomizationCatalog(flightId) {
1134
+ const flightParam = flightId ? `?flight=${flightId}` : "";
1135
+ return this.get(`/hi/Progression/file/inventory/catalog${flightParam}`, {
1136
+ useClearance: !!flightId
1137
+ });
1138
+ }
1139
+ /**
1140
+ * Get a store offering definition.
1141
+ *
1142
+ * @param offeringPath - Path to the offering
1143
+ * @returns Store offering
1144
+ */
1145
+ getStoreOffering(offeringPath) {
1146
+ this.assertNotEmpty(offeringPath, "offeringPath");
1147
+ return this.get(`/hi/Progression/file/${offeringPath}`);
1148
+ }
1149
+ /**
1150
+ * Get a currency definition.
1151
+ *
1152
+ * @param currencyPath - Path to the currency
1153
+ * @param flightId - Optional flight ID
1154
+ * @returns Currency definition
1155
+ */
1156
+ getCurrency(currencyPath, flightId) {
1157
+ this.assertNotEmpty(currencyPath, "currencyPath");
1158
+ const flightParam = flightId ? `?flight=${flightId}` : "";
1159
+ return this.get(`/hi/Progression/file/${currencyPath}${flightParam}`);
1160
+ }
1161
+ // ─────────────────────────────────────────────────────────────────
1162
+ // Challenges & Events
1163
+ // ─────────────────────────────────────────────────────────────────
1164
+ /**
1165
+ * Get a challenge definition.
1166
+ *
1167
+ * @param challengePath - Path to the challenge
1168
+ * @param flightId - Optional flight ID
1169
+ * @returns Challenge definition
1170
+ */
1171
+ getChallenge(challengePath, flightId) {
1172
+ this.assertNotEmpty(challengePath, "challengePath");
1173
+ const flightParam = flightId ? `?flight=${flightId}` : "";
1174
+ return this.get(`/hi/Progression/file/${challengePath}${flightParam}`, {
1175
+ useClearance: !!flightId
1176
+ });
1177
+ }
1178
+ /**
1179
+ * Get a challenge deck definition.
1180
+ *
1181
+ * @param challengeDeckPath - Path to the challenge deck
1182
+ * @param flightId - Optional flight ID
1183
+ * @returns Challenge deck definition
1184
+ */
1185
+ getChallengeDeck(challengeDeckPath, flightId) {
1186
+ this.assertNotEmpty(challengeDeckPath, "challengeDeckPath");
1187
+ const flightParam = flightId ? `?flight=${flightId}` : "";
1188
+ return this.get(
1189
+ `/hi/Progression/file/${challengeDeckPath}${flightParam}`,
1190
+ { useClearance: !!flightId }
1191
+ );
1192
+ }
1193
+ /**
1194
+ * Get an event/reward track definition.
1195
+ *
1196
+ * @param eventPath - Path to the event
1197
+ * @param flightId - Optional flight ID
1198
+ * @returns Reward track metadata
1199
+ */
1200
+ getEvent(eventPath, flightId) {
1201
+ this.assertNotEmpty(eventPath, "eventPath");
1202
+ const flightParam = flightId ? `?flight=${flightId}` : "";
1203
+ return this.get(`/hi/Progression/file/${eventPath}${flightParam}`, {
1204
+ useClearance: !!flightId
1205
+ });
1206
+ }
1207
+ // ─────────────────────────────────────────────────────────────────
1208
+ // Career & Seasons
1209
+ // ─────────────────────────────────────────────────────────────────
1210
+ /**
1211
+ * Get career rank definitions.
1212
+ *
1213
+ * @param careerPathId - Career path identifier
1214
+ * @returns Career track container
1215
+ */
1216
+ getCareerRanks(careerPathId) {
1217
+ this.assertNotEmpty(careerPathId, "careerPathId");
1218
+ return this.get(`/hi/Progression/file/careerranks/${careerPathId}`);
1219
+ }
1220
+ /**
1221
+ * Get the season calendar.
1222
+ *
1223
+ * @returns Season calendar
1224
+ */
1225
+ getSeasonCalendar() {
1226
+ return this.get("/hi/Progression/file/calendars/seasons");
1227
+ }
1228
+ /**
1229
+ * Get the CSR/ranked season calendar.
1230
+ *
1231
+ * @returns CSR season calendar
1232
+ */
1233
+ getCsrCalendar() {
1234
+ return this.get("/hi/Progression/file/calendars/csrseasons");
1235
+ }
1236
+ /**
1237
+ * Get a season reward track definition.
1238
+ *
1239
+ * @param seasonPath - Path to the season
1240
+ * @param flightId - Optional flight ID
1241
+ * @returns Season reward track
1242
+ */
1243
+ getSeasonRewardTrack(seasonPath, flightId) {
1244
+ this.assertNotEmpty(seasonPath, "seasonPath");
1245
+ const flightParam = flightId ? `?flight=${flightId}` : "";
1246
+ return this.get(`/hi/Progression/file/${seasonPath}${flightParam}`, {
1247
+ useClearance: !!flightId
1248
+ });
1249
+ }
1250
+ // ─────────────────────────────────────────────────────────────────
1251
+ // Medals & Metadata
1252
+ // ─────────────────────────────────────────────────────────────────
1253
+ /**
1254
+ * Get medal metadata and definitions.
1255
+ *
1256
+ * @returns Medal metadata
1257
+ */
1258
+ getMedalMetadata() {
1259
+ return this.get("/hi/Progression/file/medals/metadata");
1260
+ }
1261
+ /**
1262
+ * Get general game metadata.
1263
+ *
1264
+ * @param flightId - Optional flight ID
1265
+ * @returns Metadata
1266
+ */
1267
+ getMetadata(flightId) {
1268
+ const flightParam = flightId ? `?flight=${flightId}` : "";
1269
+ return this.get(`/hi/Progression/file/metadata${flightParam}`, {
1270
+ useClearance: !!flightId
1271
+ });
1272
+ }
1273
+ // ─────────────────────────────────────────────────────────────────
1274
+ // News & Guides
1275
+ // ─────────────────────────────────────────────────────────────────
1276
+ /**
1277
+ * Get news articles.
1278
+ *
1279
+ * @param filePath - Path to news file
1280
+ * @returns News collection
1281
+ */
1282
+ getNews(filePath) {
1283
+ this.assertNotEmpty(filePath, "filePath");
1284
+ return this.get(`/hi/news/${filePath}`);
1285
+ }
1286
+ /**
1287
+ * Get academy star definitions.
1288
+ *
1289
+ * @returns Star definitions
1290
+ */
1291
+ getAcademyStarDefinitions() {
1292
+ return this.get("/hi/Progression/file/academy/stars");
1293
+ }
1294
+ // ─────────────────────────────────────────────────────────────────
1295
+ // Raw Files & Images
1296
+ // ─────────────────────────────────────────────────────────────────
1297
+ /**
1298
+ * Get an image file from the CMS.
1299
+ *
1300
+ * @param filePath - Path to the image
1301
+ * @returns Image data as bytes
1302
+ */
1303
+ getImage(filePath) {
1304
+ this.assertNotEmpty(filePath, "filePath");
1305
+ return this.get(`/hi/images/file/${filePath}`);
1306
+ }
1307
+ /**
1308
+ * Get a generic file from the CMS.
1309
+ *
1310
+ * @param filePath - Path to the file
1311
+ * @returns File data as bytes
1312
+ */
1313
+ getGenericFile(filePath) {
1314
+ this.assertNotEmpty(filePath, "filePath");
1315
+ return this.get(`/hi/Progression/file/${filePath}`);
1316
+ }
1317
+ /**
1318
+ * Get a raw progression file with custom type.
1319
+ *
1320
+ * @template T - Expected return type
1321
+ * @param filePath - Path to the file
1322
+ * @returns Typed file contents
1323
+ */
1324
+ getProgressionFile(filePath) {
1325
+ this.assertNotEmpty(filePath, "filePath");
1326
+ return this.get(`/hi/Progression/file/${filePath}`);
1327
+ }
1328
+ };
1329
+
1330
+ // src/modules/halo-infinite/lobby.module.ts
1331
+ var LobbyModule = class extends ModuleBase {
1332
+ constructor(client) {
1333
+ super(client, HALO_CORE_ENDPOINTS.LOBBY_ORIGIN);
1334
+ }
1335
+ /**
1336
+ * Get available QoS (Quality of Service) servers.
1337
+ *
1338
+ * @returns List of servers
1339
+ */
1340
+ getQosServers() {
1341
+ return this.get("/hi/qosservers");
1342
+ }
1343
+ /**
1344
+ * Check presence for players in lobbies.
1345
+ *
1346
+ * @param presenceRequest - Presence request container
1347
+ * @returns Presence results
1348
+ */
1349
+ presence(presenceRequest) {
1350
+ return this.postJson(
1351
+ "/hi/presence",
1352
+ presenceRequest
1353
+ );
1354
+ }
1355
+ /**
1356
+ * Get a third-party join handle for a lobby.
1357
+ *
1358
+ * @param lobbyId - Lobby identifier
1359
+ * @param player - Player XUID
1360
+ * @param handleAudience - Handle audience
1361
+ * @param handlePlatform - Handle platform
1362
+ * @returns Join handle
1363
+ */
1364
+ getThirdPartyJoinHandle(lobbyId, player, handleAudience, handlePlatform) {
1365
+ this.assertNotEmpty(lobbyId, "lobbyId");
1366
+ this.assertNotEmpty(player, "player");
1367
+ return this.get(
1368
+ `/hi/lobbies/${lobbyId}/players/xuid(${player})/joinhandle?handleAudience=${handleAudience}&handlePlatform=${handlePlatform}`
1369
+ );
1370
+ }
1371
+ /**
1372
+ * Join a lobby.
1373
+ *
1374
+ * @param lobbyId - Lobby identifier
1375
+ * @param player - Player XUID
1376
+ * @param auth - Auth string
1377
+ * @param lobbyBootstrapPayload - Bootstrap payload
1378
+ * @returns Join response
1379
+ */
1380
+ joinLobby(lobbyId, player, auth, lobbyBootstrapPayload) {
1381
+ this.assertNotEmpty(lobbyId, "lobbyId");
1382
+ this.assertNotEmpty(player, "player");
1383
+ return this.client.executeRequest(
1384
+ this.buildUrl(`/hi/lobbies/${lobbyId}/players/xuid(${player})?auth=${auth}`),
1385
+ "POST",
1386
+ {
1387
+ body: lobbyBootstrapPayload,
1388
+ contentType: "bond"
1389
+ }
1390
+ );
1391
+ }
1392
+ };
1393
+
1394
+ // src/modules/halo-infinite/settings.module.ts
1395
+ var SettingsModule = class extends ModuleBase {
1396
+ constructor(client) {
1397
+ super(client, HALO_CORE_ENDPOINTS.SETTINGS_ORIGIN);
1398
+ }
1399
+ /**
1400
+ * Get the clearance level for the current player.
1401
+ *
1402
+ * Returns feature flags and flight IDs the player has access to.
1403
+ *
1404
+ * @returns Flighted feature flags
1405
+ */
1406
+ getClearanceLevel() {
1407
+ return this.get("/hi/clearance", {
1408
+ useSpartanToken: true
1409
+ });
1410
+ }
1411
+ };
1412
+
1413
+ // src/modules/halo-infinite/skill.module.ts
1414
+ var SkillModule = class extends ModuleBase {
1415
+ constructor(client) {
1416
+ super(client, HALO_CORE_ENDPOINTS.SKILL_ORIGIN);
1417
+ }
1418
+ /**
1419
+ * Get skill/CSR changes for players in a specific match.
1420
+ *
1421
+ * @param matchId - Match ID in GUID format
1422
+ * @param playerIds - List of player XUIDs to query
1423
+ * @returns Skill info for each player
1424
+ */
1425
+ getMatchPlayerResult(matchId, playerIds) {
1426
+ this.assertNotEmpty(matchId, "matchId");
1427
+ if (!playerIds.length) {
1428
+ throw new Error("playerIds cannot be empty");
1429
+ }
1430
+ const playersQuery = playerIds.map((id) => `players=xuid(${id})`).join("&");
1431
+ return this.get(
1432
+ `/hi/matches/${matchId}/skill?${playersQuery}`
1433
+ );
1434
+ }
1435
+ /**
1436
+ * Get current CSR for players in a specific playlist.
1437
+ *
1438
+ * @param playlistId - Playlist ID in GUID format
1439
+ * @param playerIds - List of player XUIDs to query
1440
+ * @param seasonId - Optional season ID for season-specific CSR
1441
+ * @returns CSR results for each player
1442
+ */
1443
+ getPlaylistCsr(playlistId, playerIds, seasonId) {
1444
+ this.assertNotEmpty(playlistId, "playlistId");
1445
+ if (!playerIds.length) {
1446
+ throw new Error("playerIds cannot be empty");
1447
+ }
1448
+ const playersQuery = playerIds.map((id) => `players=xuid(${id})`).join("&");
1449
+ const seasonParam = seasonId ? `&seasonId=${seasonId}` : "";
1450
+ return this.get(
1451
+ `/hi/playlist/${playlistId}/csrs?${playersQuery}${seasonParam}`
1452
+ );
1453
+ }
1454
+ };
1455
+
1456
+ // src/modules/halo-infinite/stats.module.ts
1457
+ var StatsModule = class extends ModuleBase {
1458
+ constructor(client) {
1459
+ super(client, HALO_CORE_ENDPOINTS.STATS_ORIGIN);
1460
+ }
1461
+ /**
1462
+ * Get challenge decks available for a player.
1463
+ *
1464
+ * @param player - Player's numeric XUID
1465
+ * @returns Challenge decks response
1466
+ */
1467
+ getChallengeDecks(player) {
1468
+ this.assertNotEmpty(player, "player");
1469
+ return this.get(`/hi/players/xuid(${player})/decks`);
1470
+ }
1471
+ /**
1472
+ * Get match count summary for a player.
1473
+ *
1474
+ * @param player - Player's numeric XUID
1475
+ * @returns Match count breakdown by type
1476
+ */
1477
+ getMatchCount(player) {
1478
+ this.assertNotEmpty(player, "player");
1479
+ return this.get(`/hi/players/xuid(${player})/matches/count`);
1480
+ }
1481
+ /**
1482
+ * Get match history for a player.
1483
+ *
1484
+ * @param player - Player's numeric XUID
1485
+ * @param start - Starting index for pagination (0-based)
1486
+ * @param count - Number of matches to return (max 25)
1487
+ * @param type - Type of matches to query
1488
+ * @returns Paginated match history
1489
+ */
1490
+ getMatchHistory(player, start, count, type) {
1491
+ this.assertNotEmpty(player, "player");
1492
+ this.assertRange(count, 1, 25, "count");
1493
+ this.assertRange(start, 0, Number.MAX_SAFE_INTEGER, "start");
1494
+ return this.get(
1495
+ `/hi/players/xuid(${player})/matches?start=${start}&count=${count}&type=${type}`
1496
+ );
1497
+ }
1498
+ /**
1499
+ * Get detailed statistics for a specific match.
1500
+ *
1501
+ * @param matchId - Match ID in GUID format
1502
+ * @returns Complete match statistics
1503
+ */
1504
+ getMatchStats(matchId) {
1505
+ this.assertNotEmpty(matchId, "matchId");
1506
+ return this.get(`/hi/matches/${matchId}/stats`);
1507
+ }
1508
+ /**
1509
+ * Get challenge progression for a player in a specific match.
1510
+ *
1511
+ * @param player - Player's numeric XUID
1512
+ * @param matchId - Match ID in GUID format
1513
+ * @returns Match progression details
1514
+ */
1515
+ getPlayerMatchProgression(player, matchId) {
1516
+ this.assertNotEmpty(player, "player");
1517
+ this.assertNotEmpty(matchId, "matchId");
1518
+ return this.get(
1519
+ `/hi/players/xuid(${player})/matches/${matchId}/progression`
1520
+ );
1521
+ }
1522
+ /**
1523
+ * Get match privacy settings for a player.
1524
+ *
1525
+ * @param player - Player's numeric XUID
1526
+ * @returns Privacy settings
1527
+ */
1528
+ getMatchPrivacy(player) {
1529
+ this.assertNotEmpty(player, "player");
1530
+ return this.get(`/hi/players/xuid(${player})/matches-privacy`);
1531
+ }
1532
+ /**
1533
+ * Get service record for a player by XUID.
1534
+ *
1535
+ * The service record contains aggregate career statistics.
1536
+ *
1537
+ * @param xuid - Player's numeric XUID
1538
+ * @param mode - Lifecycle mode (matchmade, custom, local)
1539
+ * @param seasonId - Optional season ID for season-specific stats
1540
+ * @returns Player service record
1541
+ */
1542
+ getPlayerServiceRecordByXuid(xuid, mode, seasonId) {
1543
+ this.assertNotEmpty(xuid, "xuid");
1544
+ const seasonParam = seasonId ? `?seasonId=${seasonId}` : "";
1545
+ return this.get(
1546
+ `/hi/players/xuid(${xuid})/${mode}/servicerecord${seasonParam}`
1547
+ );
1548
+ }
1549
+ /**
1550
+ * Get service record for a player by gamertag.
1551
+ *
1552
+ * @param gamertag - Player's gamertag
1553
+ * @param mode - Lifecycle mode (matchmade, custom, local)
1554
+ * @param seasonId - Optional season ID for season-specific stats
1555
+ * @returns Player service record
1556
+ */
1557
+ getPlayerServiceRecordByGamertag(gamertag, mode, seasonId) {
1558
+ this.assertNotEmpty(gamertag, "gamertag");
1559
+ const encodedGamertag = encodeURIComponent(gamertag);
1560
+ const seasonParam = seasonId ? `?seasonId=${seasonId}` : "";
1561
+ return this.get(
1562
+ `/hi/players/${encodedGamertag}/${mode}/servicerecord${seasonParam}`
1563
+ );
1564
+ }
1565
+ /**
1566
+ * Get daily custom game XP for a player.
1567
+ *
1568
+ * @param player - Player's numeric XUID
1569
+ * @returns Daily custom experience info
1570
+ */
1571
+ getPlayerDailyCustomExperience(player) {
1572
+ this.assertNotEmpty(player, "player");
1573
+ return this.get(
1574
+ `/hi/players/xuid(${player})/customexperience`
1575
+ );
1576
+ }
1577
+ };
1578
+
1579
+ // src/modules/halo-infinite/text-moderation.module.ts
1580
+ var TextModerationModule = class extends ModuleBase {
1581
+ constructor(client) {
1582
+ super(client, HALO_CORE_ENDPOINTS.TEXT_ORIGIN);
1583
+ }
1584
+ /**
1585
+ * Get a moderation key for text validation.
1586
+ *
1587
+ * @returns Moderation key
1588
+ */
1589
+ getModerationKey() {
1590
+ return this.get("/hi/moderation/key");
1591
+ }
1592
+ };
1593
+
1594
+ // src/modules/halo-infinite/ugc.module.ts
1595
+ var UgcModule = class extends ModuleBase {
1596
+ constructor(client) {
1597
+ super(client, HALO_CORE_ENDPOINTS.AUTHORING_ORIGIN);
1598
+ }
1599
+ // ─────────────────────────────────────────────────────────────────
1600
+ // Asset Retrieval
1601
+ // ─────────────────────────────────────────────────────────────────
1602
+ /**
1603
+ * Get an asset by ID.
1604
+ *
1605
+ * @param title - Game title (e.g., 'hi' for Halo Infinite)
1606
+ * @param assetType - Type of asset
1607
+ * @param assetId - Asset GUID
1608
+ * @returns Asset details
1609
+ */
1610
+ getAsset(title, assetType, assetId) {
1611
+ this.assertNotEmpty(title, "title");
1612
+ this.assertNotEmpty(assetId, "assetId");
1613
+ return this.get(`/${title}/${assetType}/${assetId}`);
1614
+ }
1615
+ /**
1616
+ * Get the latest version of an asset.
1617
+ *
1618
+ * @param title - Game title
1619
+ * @param assetType - Type of asset
1620
+ * @param assetId - Asset GUID
1621
+ * @returns Latest asset version
1622
+ */
1623
+ getLatestAssetVersion(title, assetType, assetId) {
1624
+ this.assertNotEmpty(title, "title");
1625
+ this.assertNotEmpty(assetId, "assetId");
1626
+ return this.get(`/${title}/${assetType}/${assetId}/versions/latest`);
1627
+ }
1628
+ /**
1629
+ * Get a specific version of an asset.
1630
+ *
1631
+ * @param title - Game title
1632
+ * @param assetType - Type of asset
1633
+ * @param assetId - Asset GUID
1634
+ * @param versionId - Version GUID
1635
+ * @returns Asset version
1636
+ */
1637
+ getSpecificAssetVersion(title, assetType, assetId, versionId) {
1638
+ this.assertNotEmpty(title, "title");
1639
+ this.assertNotEmpty(assetId, "assetId");
1640
+ this.assertNotEmpty(versionId, "versionId");
1641
+ return this.get(
1642
+ `/${title}/${assetType}/${assetId}/versions/${versionId}`
1643
+ );
1644
+ }
1645
+ /**
1646
+ * Get the published version of an asset.
1647
+ *
1648
+ * @param title - Game title
1649
+ * @param assetType - Type of asset
1650
+ * @param assetId - Asset GUID
1651
+ * @returns Published asset version
1652
+ */
1653
+ getPublishedVersion(title, assetType, assetId) {
1654
+ this.assertNotEmpty(title, "title");
1655
+ this.assertNotEmpty(assetId, "assetId");
1656
+ return this.get(`/${title}/${assetType}/${assetId}/versions/published`);
1657
+ }
1658
+ /**
1659
+ * List all versions of an asset.
1660
+ *
1661
+ * @param title - Game title
1662
+ * @param assetType - Type of asset
1663
+ * @param assetId - Asset GUID
1664
+ * @returns All asset versions
1665
+ */
1666
+ listAllVersions(title, assetType, assetId) {
1667
+ this.assertNotEmpty(title, "title");
1668
+ this.assertNotEmpty(assetId, "assetId");
1669
+ return this.get(`/${title}/${assetType}/${assetId}/versions`);
1670
+ }
1671
+ /**
1672
+ * List assets created by a player.
1673
+ *
1674
+ * @param title - Game title
1675
+ * @param player - Player XUID
1676
+ * @param assetType - Type of asset
1677
+ * @param start - Starting offset
1678
+ * @param count - Number of results
1679
+ * @returns Player's assets
1680
+ */
1681
+ listPlayerAssets(title, player, assetType, start = 0, count = 25) {
1682
+ this.assertNotEmpty(title, "title");
1683
+ this.assertNotEmpty(player, "player");
1684
+ return this.get(
1685
+ `/${title}/players/xuid(${player})/${assetType}?start=${start}&count=${count}`
1686
+ );
1687
+ }
1688
+ // ─────────────────────────────────────────────────────────────────
1689
+ // Favorites
1690
+ // ─────────────────────────────────────────────────────────────────
1691
+ /**
1692
+ * Favorite an asset.
1693
+ *
1694
+ * @param player - Player XUID
1695
+ * @param assetType - Type of asset
1696
+ * @param assetId - Asset GUID
1697
+ * @returns Favorite result
1698
+ */
1699
+ favoriteAnAsset(player, assetType, assetId) {
1700
+ this.assertNotEmpty(player, "player");
1701
+ this.assertNotEmpty(assetId, "assetId");
1702
+ return this.postJson(
1703
+ `/hi/players/xuid(${player})/favorites`,
1704
+ { assetId, assetKind: assetType }
1705
+ );
1706
+ }
1707
+ /**
1708
+ * Check if a player has bookmarked an asset.
1709
+ *
1710
+ * @param title - Game title
1711
+ * @param player - Player XUID
1712
+ * @param assetType - Type of asset
1713
+ * @param assetId - Asset GUID
1714
+ * @returns Favorite status
1715
+ */
1716
+ checkAssetPlayerBookmark(title, player, assetType, assetId) {
1717
+ this.assertNotEmpty(title, "title");
1718
+ this.assertNotEmpty(player, "player");
1719
+ this.assertNotEmpty(assetId, "assetId");
1720
+ return this.get(
1721
+ `/${title}/players/xuid(${player})/favorites/${assetType}/${assetId}`
1722
+ );
1723
+ }
1724
+ /**
1725
+ * List player's favorite assets of a specific type.
1726
+ *
1727
+ * @param player - Player XUID
1728
+ * @param assetType - Type of asset
1729
+ * @returns Favorites container
1730
+ */
1731
+ listPlayerFavorites(player, assetType) {
1732
+ this.assertNotEmpty(player, "player");
1733
+ return this.get(
1734
+ `/hi/players/xuid(${player})/favorites/${assetType}`
1735
+ );
1736
+ }
1737
+ /**
1738
+ * List all of a player's favorite assets.
1739
+ *
1740
+ * @param player - Player XUID
1741
+ * @returns All favorites
1742
+ */
1743
+ listPlayerFavoritesAgnostic(player) {
1744
+ this.assertNotEmpty(player, "player");
1745
+ return this.get(`/hi/players/xuid(${player})/favorites`);
1746
+ }
1747
+ // ─────────────────────────────────────────────────────────────────
1748
+ // Ratings & Reports
1749
+ // ─────────────────────────────────────────────────────────────────
1750
+ /**
1751
+ * Get a player's rating for an asset.
1752
+ *
1753
+ * @param player - Player XUID
1754
+ * @param assetType - Type of asset
1755
+ * @param assetId - Asset GUID
1756
+ * @returns Rating info
1757
+ */
1758
+ getAssetRatings(player, assetType, assetId) {
1759
+ this.assertNotEmpty(player, "player");
1760
+ this.assertNotEmpty(assetId, "assetId");
1761
+ return this.get(
1762
+ `/hi/players/xuid(${player})/ratings/${assetType}/${assetId}`
1763
+ );
1764
+ }
1765
+ /**
1766
+ * Rate an asset.
1767
+ *
1768
+ * @param player - Player XUID
1769
+ * @param assetType - Type of asset
1770
+ * @param assetId - Asset GUID
1771
+ * @param rating - Rating to submit
1772
+ * @returns Updated rating
1773
+ */
1774
+ rateAnAsset(player, assetType, assetId, rating) {
1775
+ this.assertNotEmpty(player, "player");
1776
+ this.assertNotEmpty(assetId, "assetId");
1777
+ return this.putJson(
1778
+ `/hi/players/xuid(${player})/ratings/${assetType}/${assetId}`,
1779
+ rating
1780
+ );
1781
+ }
1782
+ /**
1783
+ * Report an asset for moderation.
1784
+ *
1785
+ * @param player - Player XUID
1786
+ * @param assetType - Type of asset
1787
+ * @param assetId - Asset GUID
1788
+ * @param report - Report details
1789
+ * @returns Report result
1790
+ */
1791
+ reportAnAsset(player, assetType, assetId, report) {
1792
+ this.assertNotEmpty(player, "player");
1793
+ this.assertNotEmpty(assetId, "assetId");
1794
+ return this.postJson(
1795
+ `/hi/players/xuid(${player})/reports/${assetType}/${assetId}`,
1796
+ report
1797
+ );
1798
+ }
1799
+ // ─────────────────────────────────────────────────────────────────
1800
+ // Asset Management
1801
+ // ─────────────────────────────────────────────────────────────────
1802
+ /**
1803
+ * Delete an asset and all its versions.
1804
+ *
1805
+ * @param title - Game title
1806
+ * @param assetType - Type of asset
1807
+ * @param assetId - Asset GUID
1808
+ * @returns Success status
1809
+ */
1810
+ deleteAllVersions(title, assetType, assetId) {
1811
+ this.assertNotEmpty(title, "title");
1812
+ this.assertNotEmpty(assetId, "assetId");
1813
+ return this.delete(`/${title}/${assetType}/${assetId}`);
1814
+ }
1815
+ /**
1816
+ * Delete a specific version of an asset.
1817
+ *
1818
+ * @param title - Game title
1819
+ * @param assetType - Type of asset
1820
+ * @param assetId - Asset GUID
1821
+ * @param versionId - Version GUID
1822
+ * @returns Success status
1823
+ */
1824
+ deleteVersion(title, assetType, assetId, versionId) {
1825
+ this.assertNotEmpty(title, "title");
1826
+ this.assertNotEmpty(assetId, "assetId");
1827
+ this.assertNotEmpty(versionId, "versionId");
1828
+ return this.delete(`/${title}/${assetType}/${assetId}/versions/${versionId}`);
1829
+ }
1830
+ /**
1831
+ * Publish an asset version.
1832
+ *
1833
+ * @param assetType - Type of asset
1834
+ * @param assetId - Asset GUID
1835
+ * @param versionId - Version GUID
1836
+ * @param clearanceId - Clearance ID
1837
+ * @returns Success status
1838
+ */
1839
+ publishAssetVersion(assetType, assetId, versionId, clearanceId) {
1840
+ this.assertNotEmpty(assetId, "assetId");
1841
+ this.assertNotEmpty(versionId, "versionId");
1842
+ return this.post(
1843
+ `/hi/${assetType}/${assetId}/versions/${versionId}/publish?clearanceId=${clearanceId}`
1844
+ );
1845
+ }
1846
+ /**
1847
+ * Unpublish an asset.
1848
+ *
1849
+ * @param assetType - Type of asset
1850
+ * @param assetId - Asset GUID
1851
+ * @returns Success status
1852
+ */
1853
+ unpublishAsset(assetType, assetId) {
1854
+ this.assertNotEmpty(assetId, "assetId");
1855
+ return this.post(`/hi/${assetType}/${assetId}/unpublish`);
1856
+ }
1857
+ // ─────────────────────────────────────────────────────────────────
1858
+ // Permissions
1859
+ // ─────────────────────────────────────────────────────────────────
1860
+ /**
1861
+ * Grant or revoke permissions for an asset.
1862
+ *
1863
+ * @param title - Game title
1864
+ * @param assetType - Type of asset
1865
+ * @param assetId - Asset GUID
1866
+ * @param player - Player XUID to grant/revoke
1867
+ * @param permission - Permission details
1868
+ * @returns Updated permission
1869
+ */
1870
+ grantOrRevokePermissions(title, assetType, assetId, player, permission) {
1871
+ this.assertNotEmpty(title, "title");
1872
+ this.assertNotEmpty(assetId, "assetId");
1873
+ this.assertNotEmpty(player, "player");
1874
+ return this.putJson(
1875
+ `/${title}/${assetType}/${assetId}/permissions/xuid(${player})`,
1876
+ permission
1877
+ );
1878
+ }
1879
+ // ─────────────────────────────────────────────────────────────────
1880
+ // Sessions
1881
+ // ─────────────────────────────────────────────────────────────────
1882
+ /**
1883
+ * Start an authoring session for an asset.
1884
+ *
1885
+ * @param title - Game title
1886
+ * @param assetType - Type of asset
1887
+ * @param assetId - Asset GUID
1888
+ * @param includeContainerSas - Include container SAS URL
1889
+ * @returns Session details
1890
+ */
1891
+ startSession(title, assetType, assetId, includeContainerSas = false) {
1892
+ this.assertNotEmpty(title, "title");
1893
+ this.assertNotEmpty(assetId, "assetId");
1894
+ return this.post(
1895
+ `/${title}/${assetType}/${assetId}/sessions?includeContainerSas=${includeContainerSas}`
1896
+ );
1897
+ }
1898
+ /**
1899
+ * Extend an authoring session.
1900
+ *
1901
+ * @param title - Game title
1902
+ * @param assetType - Type of asset
1903
+ * @param assetId - Asset GUID
1904
+ * @param includeContainerSas - Include container SAS URL
1905
+ * @returns Extended session
1906
+ */
1907
+ extendSession(title, assetType, assetId, includeContainerSas = false) {
1908
+ this.assertNotEmpty(title, "title");
1909
+ this.assertNotEmpty(assetId, "assetId");
1910
+ return this.putJson(
1911
+ `/${title}/${assetType}/${assetId}/sessions?includeContainerSas=${includeContainerSas}`,
1912
+ {}
1913
+ );
1914
+ }
1915
+ /**
1916
+ * End an authoring session.
1917
+ *
1918
+ * @param title - Game title
1919
+ * @param assetType - Type of asset
1920
+ * @param assetId - Asset GUID
1921
+ * @returns Success status
1922
+ */
1923
+ endSession(title, assetType, assetId) {
1924
+ this.assertNotEmpty(title, "title");
1925
+ this.assertNotEmpty(assetId, "assetId");
1926
+ return this.delete(`/${title}/${assetType}/${assetId}/sessions`);
1927
+ }
1928
+ /**
1929
+ * Create a new asset version.
1930
+ *
1931
+ * @param title - Game title
1932
+ * @param assetType - Type of asset
1933
+ * @param assetId - Asset GUID
1934
+ * @param starter - Source asset to clone from
1935
+ * @returns New asset version
1936
+ */
1937
+ createAssetVersion(title, assetType, assetId, starter) {
1938
+ this.assertNotEmpty(title, "title");
1939
+ this.assertNotEmpty(assetId, "assetId");
1940
+ return this.postJson(
1941
+ `/${title}/${assetType}/${assetId}/versions`,
1942
+ starter
1943
+ );
1944
+ }
1945
+ /**
1946
+ * Patch an asset version.
1947
+ *
1948
+ * @param title - Game title
1949
+ * @param assetType - Type of asset
1950
+ * @param assetId - Asset GUID
1951
+ * @param versionId - Version GUID
1952
+ * @param patchedAsset - Updated asset data
1953
+ * @returns Updated asset version
1954
+ */
1955
+ patchAssetVersion(title, assetType, assetId, versionId, patchedAsset) {
1956
+ this.assertNotEmpty(title, "title");
1957
+ this.assertNotEmpty(assetId, "assetId");
1958
+ this.assertNotEmpty(versionId, "versionId");
1959
+ return this.patchJson(
1960
+ `/${title}/${assetType}/${assetId}/versions/${versionId}`,
1961
+ patchedAsset
1962
+ );
1963
+ }
1964
+ // ─────────────────────────────────────────────────────────────────
1965
+ // Blob Storage
1966
+ // ─────────────────────────────────────────────────────────────────
1967
+ /**
1968
+ * Get a blob from UGC storage.
1969
+ *
1970
+ * @param blobPath - Path to the blob
1971
+ * @returns Blob data as bytes
1972
+ */
1973
+ getBlob(blobPath) {
1974
+ this.assertNotEmpty(blobPath, "blobPath");
1975
+ const blobUrl = `https://${HALO_CORE_ENDPOINTS.BLOBS_ORIGIN}.${HALO_CORE_ENDPOINTS.SERVICE_DOMAIN}${blobPath}`;
1976
+ return this.getFullUrl(blobUrl, { useSpartanToken: false });
1977
+ }
1978
+ };
1979
+
1980
+ // src/modules/halo-infinite/ugc-discovery.module.ts
1981
+ var UgcDiscoveryModule = class extends ModuleBase {
1982
+ constructor(client) {
1983
+ super(client, HALO_CORE_ENDPOINTS.DISCOVERY_ORIGIN);
1984
+ }
1985
+ /**
1986
+ * Search for user-generated content.
1987
+ *
1988
+ * @param params - Search parameters
1989
+ * @returns Search results
1990
+ */
1991
+ search(params) {
1992
+ const queryParts = [];
1993
+ if (params.term) {
1994
+ queryParts.push(`term=${encodeURIComponent(params.term)}`);
1995
+ }
1996
+ if (params.assetKinds?.length) {
1997
+ params.assetKinds.forEach((kind) => {
1998
+ queryParts.push(`assetKind=${kind}`);
1999
+ });
2000
+ }
2001
+ if (params.tags?.length) {
2002
+ params.tags.forEach((tag) => {
2003
+ queryParts.push(`tags=${encodeURIComponent(tag)}`);
2004
+ });
2005
+ }
2006
+ if (params.author) {
2007
+ queryParts.push(`author=xuid(${params.author})`);
2008
+ }
2009
+ if (params.sort) {
2010
+ queryParts.push(`sort=${params.sort}`);
2011
+ }
2012
+ if (params.order) {
2013
+ queryParts.push(`order=${params.order}`);
2014
+ }
2015
+ if (params.count !== void 0) {
2016
+ queryParts.push(`count=${params.count}`);
2017
+ }
2018
+ if (params.start !== void 0) {
2019
+ queryParts.push(`start=${params.start}`);
2020
+ }
2021
+ const queryString = queryParts.length > 0 ? `?${queryParts.join("&")}` : "";
2022
+ return this.get(`/hi/search${queryString}`);
2023
+ }
2024
+ /**
2025
+ * Get featured content of a specific type.
2026
+ *
2027
+ * @param assetKind - Type of asset
2028
+ * @returns Featured assets
2029
+ */
2030
+ getFeatured(assetKind) {
2031
+ return this.get(`/hi/featured/${assetKind}`);
2032
+ }
2033
+ /**
2034
+ * Get popular content of a specific type.
2035
+ *
2036
+ * @param assetKind - Type of asset
2037
+ * @param start - Starting offset
2038
+ * @param count - Number of results
2039
+ * @returns Popular assets
2040
+ */
2041
+ getPopular(assetKind, start = 0, count = 25) {
2042
+ return this.get(
2043
+ `/hi/popular/${assetKind}?start=${start}&count=${count}`
2044
+ );
2045
+ }
2046
+ /**
2047
+ * Get recent content of a specific type.
2048
+ *
2049
+ * @param assetKind - Type of asset
2050
+ * @param start - Starting offset
2051
+ * @param count - Number of results
2052
+ * @returns Recent assets
2053
+ */
2054
+ getRecent(assetKind, start = 0, count = 25) {
2055
+ return this.get(
2056
+ `/hi/recent/${assetKind}?start=${start}&count=${count}`
2057
+ );
2058
+ }
2059
+ /**
2060
+ * Get recommended content for a player.
2061
+ *
2062
+ * @param player - Player XUID
2063
+ * @param assetKind - Type of asset
2064
+ * @param count - Number of results
2065
+ * @returns Recommended assets
2066
+ */
2067
+ getRecommended(player, assetKind, count = 10) {
2068
+ this.assertNotEmpty(player, "player");
2069
+ return this.get(
2070
+ `/hi/players/xuid(${player})/recommendations/${assetKind}?count=${count}`
2071
+ );
2072
+ }
2073
+ /**
2074
+ * Browse content by tag.
2075
+ *
2076
+ * @param assetKind - Type of asset
2077
+ * @param tag - Tag to filter by
2078
+ * @param start - Starting offset
2079
+ * @param count - Number of results
2080
+ * @returns Tagged assets
2081
+ */
2082
+ browseByTag(assetKind, tag, start = 0, count = 25) {
2083
+ this.assertNotEmpty(tag, "tag");
2084
+ return this.get(
2085
+ `/hi/tags/${encodeURIComponent(tag)}/${assetKind}?start=${start}&count=${count}`
2086
+ );
2087
+ }
2088
+ /**
2089
+ * Get asset details for discovery purposes.
2090
+ *
2091
+ * @param assetKind - Type of asset
2092
+ * @param assetId - Asset GUID
2093
+ * @returns Asset details
2094
+ */
2095
+ getAssetDetails(assetKind, assetId) {
2096
+ this.assertNotEmpty(assetId, "assetId");
2097
+ return this.get(`/hi/${assetKind}/${assetId}`);
2098
+ }
2099
+ /**
2100
+ * Get film asset for a match.
2101
+ *
2102
+ * @param matchId - Match GUID
2103
+ * @returns Film asset if available
2104
+ */
2105
+ getFilmByMatchId(matchId) {
2106
+ this.assertNotEmpty(matchId, "matchId");
2107
+ return this.get(`/hi/films/matches/${matchId}`);
2108
+ }
2109
+ };
2110
+
2111
+ // src/clients/halo-infinite-client.ts
2112
+ var HaloInfiniteClient = class extends ClientBase {
2113
+ // Lazy-loaded module instances
2114
+ _academy;
2115
+ _banProcessor;
2116
+ _configuration;
2117
+ _economy;
2118
+ _gameCms;
2119
+ _lobby;
2120
+ _settings;
2121
+ _skill;
2122
+ _stats;
2123
+ _textModeration;
2124
+ _ugc;
2125
+ _ugcDiscovery;
2126
+ /**
2127
+ * Creates a new HaloInfiniteClient instance.
2128
+ *
2129
+ * @param options - Client configuration options
2130
+ *
2131
+ * @example
2132
+ * ```typescript
2133
+ * const client = new HaloInfiniteClient({
2134
+ * spartanToken: 'your-spartan-token',
2135
+ * xuid: '2533274855333605',
2136
+ * includeRawResponses: true, // Enable for debugging
2137
+ * });
2138
+ * ```
2139
+ */
2140
+ constructor(options) {
2141
+ super({
2142
+ fetchFn: options.fetchFn,
2143
+ cacheTtlMs: options.cacheTtlMs,
2144
+ maxRetries: options.maxRetries
2145
+ });
2146
+ this.spartanToken = options.spartanToken;
2147
+ this.xuid = options.xuid ?? "";
2148
+ this.clearanceToken = options.clearanceToken ?? "";
2149
+ this.includeRawResponses = options.includeRawResponses ?? false;
2150
+ this.userAgent = options.userAgent ?? "";
2151
+ }
2152
+ /**
2153
+ * Academy module for bot customization and drill-related APIs.
2154
+ *
2155
+ * Provides access to:
2156
+ * - Bot customization options
2157
+ * - Academy content manifest
2158
+ * - Star/scoring definitions for drills
2159
+ */
2160
+ get academy() {
2161
+ return this._academy ??= new AcademyModule(this);
2162
+ }
2163
+ /**
2164
+ * Ban Processor module for ban-related APIs.
2165
+ *
2166
+ * Provides access to:
2167
+ * - Ban summary queries for players
2168
+ */
2169
+ get banProcessor() {
2170
+ return this._banProcessor ??= new BanProcessorModule(this);
2171
+ }
2172
+ /**
2173
+ * Configuration module for endpoint discovery APIs.
2174
+ *
2175
+ * Provides access to:
2176
+ * - API settings and endpoint configuration
2177
+ */
2178
+ get configuration() {
2179
+ return this._configuration ??= new ConfigurationModule(this);
2180
+ }
2181
+ /**
2182
+ * Economy module for player customization, stores, and inventory APIs.
2183
+ *
2184
+ * Provides access to:
2185
+ * - Player inventory and currency balances
2186
+ * - Customization (armor, weapons, vehicles, AI)
2187
+ * - In-game stores and offerings
2188
+ * - Active boosts and rewards
2189
+ * - Operation/battle pass progress
2190
+ */
2191
+ get economy() {
2192
+ return this._economy ??= new EconomyModule(this);
2193
+ }
2194
+ /**
2195
+ * Game CMS module for static content and definitions.
2196
+ *
2197
+ * Provides access to:
2198
+ * - Item definitions
2199
+ * - Challenge definitions
2200
+ * - Season and career metadata
2201
+ * - Medal information
2202
+ * - News and guides
2203
+ */
2204
+ get gameCms() {
2205
+ return this._gameCms ??= new GameCmsModule(this);
2206
+ }
2207
+ /**
2208
+ * Lobby module for multiplayer lobby and presence APIs.
2209
+ *
2210
+ * Provides access to:
2211
+ * - QoS servers
2212
+ * - Player presence in lobbies
2213
+ * - Join handles and lobby joining
2214
+ */
2215
+ get lobby() {
2216
+ return this._lobby ??= new LobbyModule(this);
2217
+ }
2218
+ /**
2219
+ * Settings module for clearance and flight configuration APIs.
2220
+ *
2221
+ * Provides access to:
2222
+ * - Clearance levels and feature flags
2223
+ */
2224
+ get settings() {
2225
+ return this._settings ??= new SettingsModule(this);
2226
+ }
2227
+ /**
2228
+ * Skill module for CSR (Competitive Skill Rank) APIs.
2229
+ *
2230
+ * Provides access to:
2231
+ * - Match skill results (CSR changes after a match)
2232
+ * - Playlist CSR for players
2233
+ */
2234
+ get skill() {
2235
+ return this._skill ??= new SkillModule(this);
2236
+ }
2237
+ /**
2238
+ * Stats module for match history and service record APIs.
2239
+ *
2240
+ * Provides access to:
2241
+ * - Match history for players
2242
+ * - Individual match statistics
2243
+ * - Player service records (career stats)
2244
+ * - Challenge decks and progression
2245
+ */
2246
+ get stats() {
2247
+ return this._stats ??= new StatsModule(this);
2248
+ }
2249
+ /**
2250
+ * Text Moderation module for moderation-related APIs.
2251
+ *
2252
+ * Provides access to:
2253
+ * - Moderation keys for text validation
2254
+ */
2255
+ get textModeration() {
2256
+ return this._textModeration ??= new TextModerationModule(this);
2257
+ }
2258
+ /**
2259
+ * UGC (User Generated Content) module for authoring operations.
2260
+ *
2261
+ * Provides access to:
2262
+ * - Creating, editing, and deleting user content
2263
+ * - Managing asset permissions
2264
+ * - Rating and favoriting assets
2265
+ * - Publishing and unpublishing assets
2266
+ */
2267
+ get ugc() {
2268
+ return this._ugc ??= new UgcModule(this);
2269
+ }
2270
+ /**
2271
+ * UGC Discovery module for searching and browsing user content.
2272
+ *
2273
+ * Provides access to:
2274
+ * - Searching for maps, game variants, and other content
2275
+ * - Browsing featured and popular content
2276
+ * - Getting recommended content
2277
+ */
2278
+ get ugcDiscovery() {
2279
+ return this._ugcDiscovery ??= new UgcDiscoveryModule(this);
2280
+ }
2281
+ };
2282
+
2283
+ // src/modules/base/waypoint-module-base.ts
2284
+ var WaypointModuleBase = class {
2285
+ /**
2286
+ * Reference to the parent client for making HTTP requests.
2287
+ */
2288
+ client;
2289
+ /**
2290
+ * Creates a new Waypoint module instance.
2291
+ *
2292
+ * @param client - Parent client instance
2293
+ */
2294
+ constructor(client) {
2295
+ this.client = client;
2296
+ }
2297
+ /**
2298
+ * Build a Waypoint API URL.
2299
+ *
2300
+ * @param path - API path starting with /
2301
+ * @returns Full HTTPS URL
2302
+ */
2303
+ buildUrl(path) {
2304
+ return `https://${WAYPOINT_ENDPOINTS.API_DOMAIN}${path}`;
2305
+ }
2306
+ /**
2307
+ * Build a Waypoint web URL.
2308
+ *
2309
+ * @param path - Path starting with /
2310
+ * @returns Full HTTPS URL
2311
+ */
2312
+ buildWebUrl(path) {
2313
+ return `https://${WAYPOINT_ENDPOINTS.WEB_DOMAIN}${path}`;
2314
+ }
2315
+ /**
2316
+ * Execute a GET request to the Waypoint API.
2317
+ */
2318
+ get(path, options = {}) {
2319
+ return this.client.executeRequest(this.buildUrl(path), "GET", {
2320
+ useSpartanToken: options.useSpartanToken ?? true,
2321
+ customHeaders: options.customHeaders
2322
+ });
2323
+ }
2324
+ /**
2325
+ * Execute a POST request to the Waypoint API.
2326
+ */
2327
+ post(path, body, options = {}) {
2328
+ return this.client.executeRequest(this.buildUrl(path), "POST", {
2329
+ useSpartanToken: options.useSpartanToken ?? true,
2330
+ body,
2331
+ customHeaders: options.customHeaders
2332
+ });
2333
+ }
2334
+ /**
2335
+ * Execute a POST request with a JSON body.
2336
+ */
2337
+ postJson(path, body, options = {}) {
2338
+ return this.client.executeRequest(this.buildUrl(path), "POST", {
2339
+ useSpartanToken: options.useSpartanToken ?? true,
2340
+ body: JSON.stringify(body),
2341
+ customHeaders: options.customHeaders
2342
+ });
2343
+ }
2344
+ /**
2345
+ * Execute a PUT request with a JSON body.
2346
+ */
2347
+ putJson(path, body, options = {}) {
2348
+ return this.client.executeRequest(this.buildUrl(path), "PUT", {
2349
+ useSpartanToken: options.useSpartanToken ?? true,
2350
+ body: JSON.stringify(body),
2351
+ customHeaders: options.customHeaders
2352
+ });
2353
+ }
2354
+ /**
2355
+ * Validate that a parameter is not null or undefined.
2356
+ */
2357
+ assertNotNull(value, paramName) {
2358
+ if (value == null) {
2359
+ throw new Error(`${paramName} cannot be null or undefined`);
2360
+ }
2361
+ }
2362
+ /**
2363
+ * Validate that a string is not empty.
2364
+ */
2365
+ assertNotEmpty(value, paramName) {
2366
+ if (!value || value.trim().length === 0) {
2367
+ throw new Error(`${paramName} cannot be empty`);
2368
+ }
2369
+ }
2370
+ };
2371
+
2372
+ // src/modules/waypoint/profile.module.ts
2373
+ var ProfileModule = class extends WaypointModuleBase {
2374
+ constructor(client) {
2375
+ super(client);
2376
+ }
2377
+ /**
2378
+ * Get the current user's settings.
2379
+ *
2380
+ * @returns User settings
2381
+ */
2382
+ getUserSettings() {
2383
+ return this.get("/hi/users/me/settings");
2384
+ }
2385
+ /**
2386
+ * Get the current user's profile.
2387
+ *
2388
+ * @returns User profile
2389
+ */
2390
+ getMyProfile() {
2391
+ return this.get("/hi/users/me");
2392
+ }
2393
+ /**
2394
+ * Get a user's profile by XUID or gamertag.
2395
+ *
2396
+ * @param userId - XUID or gamertag
2397
+ * @param isXuid - Whether userId is an XUID (true) or gamertag (false)
2398
+ * @returns User profile
2399
+ */
2400
+ getUserProfile(userId, isXuid = false) {
2401
+ this.assertNotEmpty(userId, "userId");
2402
+ const identifier = isXuid ? `xuid(${userId})` : encodeURIComponent(userId);
2403
+ return this.get(`/hi/users/${identifier}`);
2404
+ }
2405
+ /**
2406
+ * Get service awards for the current user.
2407
+ *
2408
+ * @returns Service award snapshot
2409
+ */
2410
+ getServiceAwards() {
2411
+ return this.get("/hi/users/me/serviceawards");
2412
+ }
2413
+ /**
2414
+ * Update featured service awards for the current user.
2415
+ *
2416
+ * @param awards - Awards to feature
2417
+ * @returns Updated awards
2418
+ */
2419
+ putFeaturedServiceAwards(awards) {
2420
+ return this.putJson(
2421
+ "/hi/users/me/serviceawards/featured",
2422
+ awards
2423
+ );
2424
+ }
2425
+ };
2426
+
2427
+ // src/modules/waypoint/redemption.module.ts
2428
+ var RedemptionModule = class extends WaypointModuleBase {
2429
+ constructor(client) {
2430
+ super(client);
2431
+ }
2432
+ /**
2433
+ * Redeem a promotional code.
2434
+ *
2435
+ * @param code - The code to redeem
2436
+ * @returns Redemption result
2437
+ */
2438
+ redeemCode(code) {
2439
+ this.assertNotEmpty(code, "code");
2440
+ return this.postJson(
2441
+ "/hi/redemption/code",
2442
+ { code }
2443
+ );
2444
+ }
2445
+ };
2446
+
2447
+ // src/modules/waypoint/content.module.ts
2448
+ var ContentModule = class extends WaypointModuleBase {
2449
+ constructor(client) {
2450
+ super(client);
2451
+ }
2452
+ /**
2453
+ * Get articles from Halo Waypoint.
2454
+ *
2455
+ * @param page - Page number (1-based)
2456
+ * @param perPage - Articles per page
2457
+ * @param category - Optional category filter
2458
+ * @returns Articles response
2459
+ */
2460
+ getArticles(page = 1, perPage = 10, category) {
2461
+ const categoryParam = category !== void 0 ? `&category=${category}` : "";
2462
+ return this.get(
2463
+ `/hi/articles?page=${page}&per_page=${perPage}${categoryParam}`,
2464
+ { useSpartanToken: false }
2465
+ );
2466
+ }
2467
+ /**
2468
+ * Get a specific article by slug.
2469
+ *
2470
+ * @param slug - Article URL slug
2471
+ * @returns Article details
2472
+ */
2473
+ getArticle(slug) {
2474
+ this.assertNotEmpty(slug, "slug");
2475
+ return this.get(`/hi/articles/${encodeURIComponent(slug)}`, {
2476
+ useSpartanToken: false
2477
+ });
2478
+ }
2479
+ /**
2480
+ * Get article categories.
2481
+ *
2482
+ * @returns List of categories
2483
+ */
2484
+ getCategories() {
2485
+ return this.get("/hi/articles/categories", {
2486
+ useSpartanToken: false
2487
+ });
2488
+ }
2489
+ };
2490
+
2491
+ // src/modules/waypoint/comms.module.ts
2492
+ var CommsModule = class extends WaypointModuleBase {
2493
+ constructor(client) {
2494
+ super(client);
2495
+ }
2496
+ /**
2497
+ * Get notifications for the current user.
2498
+ *
2499
+ * @returns Notifications response
2500
+ */
2501
+ getNotifications() {
2502
+ return this.get("/hi/users/me/notifications");
2503
+ }
2504
+ /**
2505
+ * Mark notifications as read.
2506
+ *
2507
+ * @param notificationIds - IDs of notifications to mark as read
2508
+ * @returns Result of the operation
2509
+ */
2510
+ markNotificationsAsRead(notificationIds) {
2511
+ if (!notificationIds.length) {
2512
+ throw new Error("notificationIds cannot be empty");
2513
+ }
2514
+ return this.postJson(
2515
+ "/hi/users/me/notifications/read",
2516
+ { notificationIds }
2517
+ );
2518
+ }
2519
+ /**
2520
+ * Delete a notification.
2521
+ *
2522
+ * @param notificationId - ID of notification to delete
2523
+ * @returns Success status
2524
+ */
2525
+ deleteNotification(notificationId) {
2526
+ this.assertNotEmpty(notificationId, "notificationId");
2527
+ return this.client.executeRequest(
2528
+ this.buildUrl(`/hi/users/me/notifications/${notificationId}`),
2529
+ "DELETE",
2530
+ { useSpartanToken: true }
2531
+ );
2532
+ }
2533
+ };
2534
+
2535
+ // src/clients/waypoint-client.ts
2536
+ var WaypointClient = class extends ClientBase {
2537
+ // Lazy-loaded module instances
2538
+ _profile;
2539
+ _redemption;
2540
+ _content;
2541
+ _comms;
2542
+ /**
2543
+ * Creates a new WaypointClient instance.
2544
+ *
2545
+ * Some endpoints (like news articles) don't require authentication,
2546
+ * so spartanToken is optional.
2547
+ *
2548
+ * @param options - Client configuration options (optional)
2549
+ *
2550
+ * @example
2551
+ * ```typescript
2552
+ * // With authentication
2553
+ * const authClient = new WaypointClient({
2554
+ * spartanToken: 'your-spartan-token',
2555
+ * });
2556
+ *
2557
+ * // Without authentication (for public endpoints)
2558
+ * const publicClient = new WaypointClient();
2559
+ * ```
2560
+ */
2561
+ constructor(options = {}) {
2562
+ super({
2563
+ fetchFn: options.fetchFn,
2564
+ cacheTtlMs: options.cacheTtlMs,
2565
+ maxRetries: options.maxRetries
2566
+ });
2567
+ this.spartanToken = options.spartanToken ?? "";
2568
+ this.xuid = options.xuid ?? "";
2569
+ this.clearanceToken = options.clearanceToken ?? "";
2570
+ this.userAgent = options.userAgent ?? "";
2571
+ }
2572
+ /**
2573
+ * Profile module for user profile and settings APIs.
2574
+ *
2575
+ * Provides access to:
2576
+ * - User profiles (self and others)
2577
+ * - User settings
2578
+ * - Service awards
2579
+ */
2580
+ get profile() {
2581
+ return this._profile ??= new ProfileModule(this);
2582
+ }
2583
+ /**
2584
+ * Redemption module for code redemption APIs.
2585
+ *
2586
+ * Provides access to:
2587
+ * - Promotional code redemption
2588
+ */
2589
+ get redemption() {
2590
+ return this._redemption ??= new RedemptionModule(this);
2591
+ }
2592
+ /**
2593
+ * Content module for articles and news APIs.
2594
+ *
2595
+ * Provides access to:
2596
+ * - News articles
2597
+ * - Article categories
2598
+ *
2599
+ * Note: Most content endpoints don't require authentication.
2600
+ */
2601
+ get content() {
2602
+ return this._content ??= new ContentModule(this);
2603
+ }
2604
+ /**
2605
+ * Comms module for notifications and communications APIs.
2606
+ *
2607
+ * Provides access to:
2608
+ * - User notifications
2609
+ * - Marking notifications as read
2610
+ */
2611
+ get comms() {
2612
+ return this._comms ??= new CommsModule(this);
2613
+ }
2614
+ };
2615
+
2616
+ // src/auth/halo-auth-client.ts
2617
+ var HaloAuthenticationClient = class {
2618
+ fetchFn;
2619
+ /**
2620
+ * Creates a new HaloAuthenticationClient instance.
2621
+ *
2622
+ * @param fetchFn - Custom fetch implementation (optional)
2623
+ */
2624
+ constructor(fetchFn) {
2625
+ this.fetchFn = fetchFn ?? globalThis.fetch.bind(globalThis);
2626
+ }
2627
+ /**
2628
+ * Exchange an XSTS token for a Halo Spartan token.
2629
+ *
2630
+ * The XSTS token must be obtained through Xbox Live authentication
2631
+ * using the Halo Waypoint relying party.
2632
+ *
2633
+ * @param xstsToken - Xbox Live XSTS token
2634
+ * @param version - Spartan token version (4 for Halo Infinite, 3 for Halo 5)
2635
+ * @returns Spartan token or null if exchange failed
2636
+ *
2637
+ * @example
2638
+ * ```typescript
2639
+ * // Version 4 is for Halo Infinite (default)
2640
+ * const spartanToken = await authClient.getSpartanToken(xstsToken);
2641
+ *
2642
+ * // Version 3 is for Halo 5
2643
+ * const halo5Token = await authClient.getSpartanToken(xstsToken, 3);
2644
+ * ```
2645
+ */
2646
+ async getSpartanToken(xstsToken, version = 4) {
2647
+ if (!xstsToken) {
2648
+ throw new Error("xstsToken is required");
2649
+ }
2650
+ const tokenProof = {
2651
+ token: xstsToken,
2652
+ tokenType: "Xbox_XSTSv3"
2653
+ };
2654
+ const requestBody = {
2655
+ audience: "urn:343:s3:services",
2656
+ minVersion: version.toString(),
2657
+ proof: [tokenProof]
2658
+ };
2659
+ try {
2660
+ const response = await this.fetchFn(HALO_CORE_ENDPOINTS.SPARTAN_TOKEN_ENDPOINT, {
2661
+ method: "POST",
2662
+ headers: {
2663
+ [HEADERS.CONTENT_TYPE]: "application/json",
2664
+ [HEADERS.ACCEPT]: "application/json"
2665
+ },
2666
+ body: JSON.stringify(requestBody)
2667
+ });
2668
+ if (!response.ok) {
2669
+ console.error(
2670
+ `Failed to get Spartan token: ${response.status} ${response.statusText}`
2671
+ );
2672
+ return null;
2673
+ }
2674
+ const data = await response.json();
2675
+ return {
2676
+ token: data.SpartanToken ?? data.spartanToken ?? data.token,
2677
+ expiresUtc: data.ExpiresUtc ?? data.expiresUtc,
2678
+ tokenDuration: data.TokenDuration ?? data.tokenDuration
2679
+ };
2680
+ } catch (error) {
2681
+ console.error("Error getting Spartan token:", error);
2682
+ return null;
2683
+ }
2684
+ }
2685
+ /**
2686
+ * Check if a Spartan token is expired or about to expire.
2687
+ *
2688
+ * @param token - The Spartan token to check
2689
+ * @param bufferMinutes - Minutes before expiration to consider "about to expire"
2690
+ * @returns true if the token is expired or will expire within the buffer time
2691
+ */
2692
+ isTokenExpired(token, bufferMinutes = 5) {
2693
+ if (!token.expiresUtc) {
2694
+ return true;
2695
+ }
2696
+ const expiresAt = new Date(token.expiresUtc);
2697
+ const now = /* @__PURE__ */ new Date();
2698
+ const bufferMs = bufferMinutes * 60 * 1e3;
2699
+ return now.getTime() + bufferMs >= expiresAt.getTime();
2700
+ }
2701
+ /**
2702
+ * Get the Halo Waypoint XSTS relying party URL.
2703
+ *
2704
+ * Use this when requesting an XSTS token from Xbox Live.
2705
+ *
2706
+ * @returns The relying party URL
2707
+ */
2708
+ static getRelyingParty() {
2709
+ return HALO_CORE_ENDPOINTS.HALO_WAYPOINT_XSTS_RELYING_PARTY;
2710
+ }
2711
+ };
2712
+
2713
+ // src/models/common/api-result.ts
2714
+ function isSuccess(result) {
2715
+ return result.result !== null && result.response.code >= 200 && result.response.code < 300;
2716
+ }
2717
+ function isNotModified(result) {
2718
+ return result.response.code === 304;
2719
+ }
2720
+ function isClientError(result) {
2721
+ return result.response.code >= 400 && result.response.code < 500;
2722
+ }
2723
+ function isServerError(result) {
2724
+ return result.response.code >= 500;
2725
+ }
2726
+
2727
+ // src/models/halo-infinite/enums/match-type.ts
2728
+ var MatchType = {
2729
+ /** Return all match types */
2730
+ All: "all",
2731
+ /** Return only matchmaking matches */
2732
+ Matchmaking: "matchmaking",
2733
+ /** Return only custom games */
2734
+ Custom: "custom",
2735
+ /** Return only local/offline matches */
2736
+ Local: "local"
2737
+ };
2738
+
2739
+ // src/models/halo-infinite/enums/lifecycle-mode.ts
2740
+ var LifecycleMode = {
2741
+ /** Matchmade multiplayer games */
2742
+ Matchmade: "matchmade",
2743
+ /** Custom games */
2744
+ Custom: "custom",
2745
+ /** Local/offline games */
2746
+ Local: "local"
2747
+ };
2748
+
2749
+ // src/models/halo-infinite/enums/asset-kind.ts
2750
+ var AssetKind = {
2751
+ /** Film/theater recording */
2752
+ Film: "Film",
2753
+ /** Custom map/forge creation */
2754
+ Map: "Map",
2755
+ /** Prefabricated object collection */
2756
+ Prefab: "Prefab",
2757
+ /** User-created game variant */
2758
+ UgcGameVariant: "UgcGameVariant",
2759
+ /** Map-mode pairing */
2760
+ MapModePair: "MapModePair",
2761
+ /** Playlist definition */
2762
+ Playlist: "Playlist",
2763
+ /** Engine game variant */
2764
+ EngineGameVariant: "EngineGameVariant",
2765
+ /** Project (forge project) */
2766
+ Project: "Project"
2767
+ };
2768
+
2769
+ // src/models/halo-infinite/enums/player-type.ts
2770
+ var PlayerType = {
2771
+ /** Human player */
2772
+ Human: "Human",
2773
+ /** Bot/AI player */
2774
+ Bot: "Bot"
2775
+ };
2776
+
2777
+ // src/models/halo-infinite/enums/outcome.ts
2778
+ var Outcome = {
2779
+ /** Won the match */
2780
+ Win: "Win",
2781
+ /** Lost the match */
2782
+ Loss: "Loss",
2783
+ /** Match ended in a tie */
2784
+ Tie: "Tie",
2785
+ /** Did not finish the match */
2786
+ DidNotFinish: "DidNotFinish"
2787
+ };
2788
+
2789
+ // src/models/halo-infinite/enums/result-order.ts
2790
+ var ResultOrder = {
2791
+ /** Sort in ascending order */
2792
+ Ascending: "asc",
2793
+ /** Sort in descending order */
2794
+ Descending: "desc"
2795
+ };
2796
+
2797
+ // src/errors/halo-api-error.ts
2798
+ var HaloApiError = class _HaloApiError extends Error {
2799
+ /**
2800
+ * HTTP status code from the response.
2801
+ */
2802
+ statusCode;
2803
+ /**
2804
+ * Full raw response details.
2805
+ */
2806
+ response;
2807
+ /**
2808
+ * Creates a new HaloApiError.
2809
+ *
2810
+ * @param message - Error message
2811
+ * @param statusCode - HTTP status code
2812
+ * @param response - Full raw response
2813
+ */
2814
+ constructor(message, statusCode, response) {
2815
+ super(message);
2816
+ this.name = "HaloApiError";
2817
+ this.statusCode = statusCode;
2818
+ this.response = response;
2819
+ if (Error.captureStackTrace) {
2820
+ Error.captureStackTrace(this, _HaloApiError);
2821
+ }
2822
+ }
2823
+ /**
2824
+ * Creates a HaloApiError from a raw response.
2825
+ *
2826
+ * @param response - The raw response to convert to an error
2827
+ * @returns A new HaloApiError instance
2828
+ */
2829
+ static fromResponse(response) {
2830
+ const message = response.message ?? `HTTP Error ${response.code}`;
2831
+ return new _HaloApiError(message, response.code, response);
2832
+ }
2833
+ /**
2834
+ * Check if this is a client error (4xx).
2835
+ */
2836
+ get isClientError() {
2837
+ return this.statusCode >= 400 && this.statusCode < 500;
2838
+ }
2839
+ /**
2840
+ * Check if this is a server error (5xx).
2841
+ */
2842
+ get isServerError() {
2843
+ return this.statusCode >= 500;
2844
+ }
2845
+ /**
2846
+ * Check if this is a "not found" error (404).
2847
+ */
2848
+ get isNotFound() {
2849
+ return this.statusCode === 404;
2850
+ }
2851
+ /**
2852
+ * Check if this is an "unauthorized" error (401).
2853
+ */
2854
+ get isUnauthorized() {
2855
+ return this.statusCode === 401;
2856
+ }
2857
+ /**
2858
+ * Check if this is a "forbidden" error (403).
2859
+ */
2860
+ get isForbidden() {
2861
+ return this.statusCode === 403;
2862
+ }
2863
+ /**
2864
+ * Check if this is a "rate limited" error (429).
2865
+ */
2866
+ get isRateLimited() {
2867
+ return this.statusCode === 429;
2868
+ }
2869
+ };
2870
+
2871
+ export { ApiContentType, AssetKind, DEFAULT_AUTH_SCOPES, DEFAULT_CACHE_TTL_MS, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, HALO_CORE_ENDPOINTS, HEADERS, HaloApiError, HaloAuthenticationClient, HaloInfiniteClient, LifecycleMode, MatchType, Outcome, PlayerType, ResultOrder, USER_AGENTS, WAYPOINT_ENDPOINTS, WaypointClient, buildServiceUrl, getContentTypeHeader, isClientError, isNotModified, isServerError, isSuccess };
2872
+ //# sourceMappingURL=index.mjs.map
2873
+ //# sourceMappingURL=index.mjs.map