@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Den Delimarsky
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,359 @@
1
+ # @dendotdev/grunt
2
+
3
+ Unofficial TypeScript client library for the Halo Infinite API.
4
+
5
+ This is the TypeScript implementation of the Grunt library, providing type-safe access to Halo Infinite and Halo Waypoint APIs. For the .NET version, see the [dotnet folder](../dotnet/).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @dendotdev/grunt
11
+ ```
12
+
13
+ For authenticated API access, you'll also need the Xbox authentication library:
14
+
15
+ ```bash
16
+ npm install @dendotdev/conch
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```typescript
22
+ import {
23
+ HaloInfiniteClient,
24
+ MatchType,
25
+ LifecycleMode,
26
+ isSuccess,
27
+ } from '@dendotdev/grunt';
28
+
29
+ // Create a client with your Spartan token
30
+ const client = new HaloInfiniteClient({
31
+ spartanToken: 'your-spartan-token',
32
+ xuid: '2533274855333605', // Your Xbox User ID
33
+ });
34
+
35
+ // Get match history
36
+ const history = await client.stats.getMatchHistory(
37
+ '2533274855333605',
38
+ 0, // start index
39
+ 25, // count (max 25)
40
+ MatchType.All
41
+ );
42
+
43
+ if (isSuccess(history)) {
44
+ console.log(`Found ${history.result.resultCount} matches`);
45
+ for (const match of history.result.results ?? []) {
46
+ console.log(`Match: ${match.matchId}`);
47
+ }
48
+ }
49
+ ```
50
+
51
+ ## Authentication
52
+
53
+ To use authenticated endpoints, you need a Spartan token. The complete authentication flow is:
54
+
55
+ 1. Authenticate with Xbox Live using OAuth to get an access token
56
+ 2. Exchange the access token for an Xbox Live user token
57
+ 3. Exchange the user token for an XSTS token (using the Halo Waypoint relying party)
58
+ 4. Exchange the XSTS token for a Spartan token using `HaloAuthenticationClient`
59
+
60
+ The Xbox authentication steps (1-3) are handled by [`@dendotdev/conch`](https://github.com/dend/conch). Here's a complete example:
61
+
62
+ ```typescript
63
+ import { XboxAuthenticationClient } from '@dendotdev/conch';
64
+ import { HaloAuthenticationClient, HaloInfiniteClient, isSuccess } from '@dendotdev/grunt';
65
+
66
+ // Step 1: Set up Xbox authentication
67
+ const xboxClient = new XboxAuthenticationClient();
68
+
69
+ // Generate the OAuth URL for the user to authorize
70
+ const clientId = 'your-azure-ad-client-id';
71
+ const redirectUrl = 'https://localhost:3000/callback';
72
+ const authUrl = xboxClient.generateAuthUrl(clientId, redirectUrl);
73
+
74
+ // User visits authUrl and authorizes your app, then gets redirected with a code
75
+ // ... handle the OAuth redirect and extract the authorization code ...
76
+
77
+ // Step 2: Exchange the authorization code for OAuth tokens
78
+ const oauthToken = await xboxClient.requestOAuthToken(clientId, authorizationCode, redirectUrl);
79
+
80
+ if (!oauthToken?.access_token) {
81
+ throw new Error('Failed to get OAuth token');
82
+ }
83
+
84
+ // Step 3: Get Xbox Live user token
85
+ const userToken = await xboxClient.requestUserToken(oauthToken.access_token);
86
+
87
+ if (!userToken?.Token) {
88
+ throw new Error('Failed to get user token');
89
+ }
90
+
91
+ // Step 4: Get XSTS token with Halo Waypoint relying party
92
+ const relyingParty = HaloAuthenticationClient.getRelyingParty();
93
+ const xstsToken = await xboxClient.requestXstsToken(userToken.Token, relyingParty);
94
+
95
+ if (!xstsToken?.Token) {
96
+ throw new Error('Failed to get XSTS token');
97
+ }
98
+
99
+ // Step 5: Exchange XSTS token for Spartan token
100
+ const haloAuthClient = new HaloAuthenticationClient();
101
+ const spartanToken = await haloAuthClient.getSpartanToken(xstsToken.Token);
102
+
103
+ if (!spartanToken?.token) {
104
+ throw new Error('Failed to get Spartan token');
105
+ }
106
+
107
+ // Step 6: Create the Halo Infinite API client
108
+ const xuid = xstsToken.DisplayClaims?.xui?.[0]?.xid;
109
+ const client = new HaloInfiniteClient({
110
+ spartanToken: spartanToken.token,
111
+ xuid: xuid,
112
+ });
113
+
114
+ // Now you can make authenticated API calls
115
+ const history = await client.stats.getMatchHistory(xuid, 0, 25);
116
+
117
+ if (isSuccess(history)) {
118
+ console.log(`Found ${history.result.resultCount} matches`);
119
+ }
120
+ ```
121
+
122
+ ### OAuth Setup
123
+
124
+ To use this authentication flow, you'll need to register an application in Azure AD:
125
+
126
+ 1. Go to the [Azure Portal](https://portal.azure.com) and navigate to Azure Active Directory
127
+ 2. Register a new application with a redirect URI
128
+ 3. Note your Application (client) ID - this is your `clientId`
129
+
130
+ For more details on Xbox authentication, see the [@dendotdev/conch documentation](https://github.com/dend/conch).
131
+
132
+ ## API Overview
133
+
134
+ ### HaloInfiniteClient
135
+
136
+ The main client for Halo Infinite APIs, with 12 specialized modules:
137
+
138
+ | Module | Description |
139
+ |--------|-------------|
140
+ | `stats` | Match history, service records, match stats |
141
+ | `skill` | CSR (Competitive Skill Rank) queries |
142
+ | `economy` | Inventory, stores, customization, currency |
143
+ | `gameCms` | Item definitions, challenges, medals, career ranks |
144
+ | `ugc` | User-generated content authoring |
145
+ | `ugcDiscovery` | Search and browse user content |
146
+ | `academy` | Bot customization, drills |
147
+ | `lobby` | QoS servers, lobby presence |
148
+ | `settings` | Clearance levels, feature flags |
149
+ | `configuration` | API endpoint discovery |
150
+ | `banProcessor` | Ban status queries |
151
+ | `textModeration` | Text moderation keys |
152
+
153
+ ### WaypointClient
154
+
155
+ Client for Halo Waypoint APIs:
156
+
157
+ | Module | Description |
158
+ |--------|-------------|
159
+ | `profile` | User profiles and settings |
160
+ | `redemption` | Code redemption |
161
+ | `content` | News articles |
162
+ | `comms` | Notifications |
163
+
164
+ ## Usage Examples
165
+
166
+ ### Get Player Service Record
167
+
168
+ ```typescript
169
+ const record = await client.stats.getPlayerServiceRecordByXuid(
170
+ '2533274855333605',
171
+ LifecycleMode.Matchmade
172
+ );
173
+
174
+ if (isSuccess(record)) {
175
+ const stats = record.result.stats?.coreStats;
176
+ console.log(`K/D: ${stats?.kills}/${stats?.deaths}`);
177
+ }
178
+ ```
179
+
180
+ ### Get Match Details
181
+
182
+ ```typescript
183
+ const match = await client.stats.getMatchStats('match-guid-here');
184
+
185
+ if (isSuccess(match)) {
186
+ console.log(`Map: ${match.result.matchInfo?.mapVariant?.publicName}`);
187
+ console.log(`Players: ${match.result.players?.length}`);
188
+ }
189
+ ```
190
+
191
+ ### Get Player CSR
192
+
193
+ ```typescript
194
+ const csr = await client.skill.getPlaylistCsr(
195
+ 'playlist-guid',
196
+ ['2533274855333605']
197
+ );
198
+
199
+ if (isSuccess(csr)) {
200
+ const playerCsr = csr.result.value?.[0];
201
+ console.log(`CSR: ${playerCsr?.csr?.value} (${playerCsr?.csr?.tier})`);
202
+ }
203
+ ```
204
+
205
+ ### Get Player Inventory
206
+
207
+ ```typescript
208
+ const inventory = await client.economy.getInventoryItems('2533274855333605');
209
+
210
+ if (isSuccess(inventory)) {
211
+ console.log(`Items owned: ${inventory.result.items?.length}`);
212
+ }
213
+ ```
214
+
215
+ ### Search UGC Maps
216
+
217
+ ```typescript
218
+ import { AssetKind } from '@dendotdev/grunt';
219
+
220
+ const maps = await client.ugcDiscovery.search({
221
+ assetKinds: [AssetKind.Map],
222
+ term: 'blood gulch',
223
+ count: 10,
224
+ });
225
+
226
+ if (isSuccess(maps)) {
227
+ for (const map of maps.result.results ?? []) {
228
+ console.log(`${map.publicName} by ${map.admin}`);
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### Get News Articles (No Auth Required)
234
+
235
+ ```typescript
236
+ import { WaypointClient, isSuccess } from '@dendotdev/grunt';
237
+
238
+ const client = new WaypointClient(); // No auth needed
239
+
240
+ const articles = await client.content.getArticles(1, 10);
241
+
242
+ if (isSuccess(articles)) {
243
+ for (const article of articles.result.articles ?? []) {
244
+ console.log(article.title);
245
+ }
246
+ }
247
+ ```
248
+
249
+ ## Result Handling
250
+
251
+ All API methods return `HaloApiResult<T>` which contains:
252
+ - `result`: The response data (or `null` on failure)
253
+ - `response`: Raw response info (status code, headers, etc.)
254
+
255
+ Use the helper functions to check results:
256
+
257
+ ```typescript
258
+ import {
259
+ isSuccess, // 2xx status with data
260
+ isNotModified, // 304 (cached response valid)
261
+ isClientError, // 4xx errors
262
+ isServerError, // 5xx errors
263
+ } from '@dendotdev/grunt';
264
+
265
+ const result = await client.stats.getMatchStats('match-id');
266
+
267
+ if (isSuccess(result)) {
268
+ // result.result is guaranteed non-null here
269
+ console.log(result.result.matchId);
270
+ } else if (isClientError(result)) {
271
+ console.error(`Client error: ${result.response.code}`);
272
+ } else if (isServerError(result)) {
273
+ console.error(`Server error: ${result.response.code}`);
274
+ }
275
+ ```
276
+
277
+ ## Configuration Options
278
+
279
+ ### HaloInfiniteClient Options
280
+
281
+ ```typescript
282
+ const client = new HaloInfiniteClient({
283
+ // Required
284
+ spartanToken: 'your-spartan-token',
285
+
286
+ // Optional
287
+ xuid: '2533274855333605', // Your Xbox User ID
288
+ clearanceToken: 'flight-id', // For flighted/preview content
289
+ includeRawResponses: true, // Include full request/response in results
290
+ userAgent: 'MyApp/1.0', // Custom User-Agent header
291
+ cacheTtlMs: 3600000, // Cache TTL (default: 60 minutes)
292
+ maxRetries: 3, // Retry attempts (default: 3)
293
+ });
294
+ ```
295
+
296
+ ## Building from Source
297
+
298
+ ### Prerequisites
299
+
300
+ - Node.js 18.0.0 or higher
301
+ - npm
302
+
303
+ ### Install Dependencies
304
+
305
+ ```bash
306
+ npm install
307
+ ```
308
+
309
+ ### Build
310
+
311
+ ```bash
312
+ npm run build
313
+ ```
314
+
315
+ This creates the `dist/` folder with:
316
+ - `index.js` - CommonJS build
317
+ - `index.mjs` - ES Module build
318
+ - `index.d.ts` - TypeScript declarations
319
+
320
+ ### Development
321
+
322
+ ```bash
323
+ # Watch mode (rebuild on changes)
324
+ npm run dev
325
+
326
+ # Type check without emitting
327
+ npm run typecheck
328
+
329
+ # Run tests
330
+ npm run test
331
+ ```
332
+
333
+ ## Features
334
+
335
+ - **Type-safe**: Full TypeScript support with comprehensive type definitions
336
+ - **Caching**: Built-in ETag-based caching with configurable TTL
337
+ - **Retry Logic**: Automatic retry with exponential backoff for transient failures
338
+ - **Lazy Loading**: Modules are initialized on first access to minimize memory usage
339
+ - **Minimal Dependencies**: Only one runtime dependency (`lru-cache`)
340
+ - **Universal**: Works in Node.js and modern browsers (uses native `fetch`)
341
+
342
+ ## API Reference
343
+
344
+ For detailed API documentation, refer to the TypeScript type definitions included with the package, or explore the source code in the `src/` directory.
345
+
346
+ The API mirrors the [.NET Grunt library](../dotnet/) structure, so its documentation can also serve as a reference.
347
+
348
+ ## Disclaimer
349
+
350
+ This is an unofficial library and is not affiliated with Microsoft, 343 Industries, or Xbox Game Studios. Use at your own risk. The Halo Infinite API is not officially documented and may change without notice.
351
+
352
+ ## License
353
+
354
+ MIT License - see [LICENSE](LICENSE) for details.
355
+
356
+ ## Credits
357
+
358
+ - Original Grunt project by [Den Delimarsky](https://den.dev)
359
+ - TypeScript implementation maintains API compatibility with the .NET version