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