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