npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dendotdev/grunt

v1.0.14

Published

Unofficial TypeScript client library for the Halo Infinite API

Readme

@dendotdev/grunt

Unofficial TypeScript client library for the Halo Infinite API.

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.

Installation

npm install @dendotdev/grunt

For authenticated API access, you'll also need the Xbox authentication library:

npm install @dendotdev/conch

Quick Start

import {
  HaloInfiniteClient,
  MatchType,
  LifecycleMode,
  isSuccess,
} from '@dendotdev/grunt';

// Create a client with your Spartan token
const client = new HaloInfiniteClient({
  spartanToken: 'your-spartan-token',
  xuid: 'xuid', // Your Xbox User ID
});

// Get match history
const history = await client.stats.getMatchHistory(
  'xuid',
  0,    // start index
  25,   // count (max 25)
  MatchType.All
);

if (isSuccess(history)) {
  console.log(`Found ${history.result.resultCount} matches`);
  for (const match of history.result.results ?? []) {
    console.log(`Match: ${match.matchId}`);
  }
}

Authentication

To use authenticated endpoints, you need a Spartan token. The complete authentication flow is:

  1. Authenticate with Xbox Live using OAuth to get an access token
  2. Exchange the access token for an Xbox Live user token
  3. Exchange the user token for an XSTS token (using the Halo Waypoint relying party)
  4. Exchange the XSTS token for a Spartan token using HaloAuthenticationClient

The Xbox authentication steps (1-3) are handled by @dendotdev/conch. Here's a complete example:

import { XboxAuthenticationClient } from '@dendotdev/conch';
import { HaloAuthenticationClient, HaloInfiniteClient, isSuccess } from '@dendotdev/grunt';

// Step 1: Set up Xbox authentication
const xboxClient = new XboxAuthenticationClient();

// Generate the OAuth URL for the user to authorize
const clientId = 'your-azure-ad-client-id';
const redirectUrl = 'https://localhost:3000/callback';
const authUrl = xboxClient.generateAuthUrl(clientId, redirectUrl);

// User visits authUrl and authorizes your app, then gets redirected with a code
// ... handle the OAuth redirect and extract the authorization code ...

// Step 2: Exchange the authorization code for OAuth tokens
const oauthToken = await xboxClient.requestOAuthToken(clientId, authorizationCode, redirectUrl);

if (!oauthToken?.access_token) {
  throw new Error('Failed to get OAuth token');
}

// Step 3: Get Xbox Live user token
const userToken = await xboxClient.requestUserToken(oauthToken.access_token);

if (!userToken?.Token) {
  throw new Error('Failed to get user token');
}

// Step 4: Get XSTS token with Halo Waypoint relying party
const relyingParty = HaloAuthenticationClient.getRelyingParty();
const xstsToken = await xboxClient.requestXstsToken(userToken.Token, relyingParty);

if (!xstsToken?.Token) {
  throw new Error('Failed to get XSTS token');
}

// Step 5: Exchange XSTS token for Spartan token
const haloAuthClient = new HaloAuthenticationClient();
const spartanToken = await haloAuthClient.getSpartanToken(xstsToken.Token);

if (!spartanToken?.token) {
  throw new Error('Failed to get Spartan token');
}

// Step 6: Create the Halo Infinite API client
const xuid = xstsToken.DisplayClaims?.xui?.[0]?.xid;
const client = new HaloInfiniteClient({
  spartanToken: spartanToken.token,
  xuid: xuid,
});

// Now you can make authenticated API calls
const history = await client.stats.getMatchHistory(xuid, 0, 25);

if (isSuccess(history)) {
  console.log(`Found ${history.result.resultCount} matches`);
}

OAuth Setup

To use this authentication flow, you'll need to register an application in Azure AD:

  1. Go to the Azure Portal and navigate to Azure Active Directory
  2. Register a new application with a redirect URI
  3. Note your Application (client) ID - this is your clientId

For more details on Xbox authentication, see the @dendotdev/conch documentation.

API Overview

HaloInfiniteClient

The main client for Halo Infinite APIs, with 12 specialized modules:

| Module | Description | |--------|-------------| | stats | Match history, service records, match stats | | skill | CSR (Competitive Skill Rank) queries | | economy | Inventory, stores, customization, currency | | gameCms | Item definitions, challenges, medals, career ranks | | ugc | User-generated content authoring | | ugcDiscovery | Search and browse user content | | academy | Bot customization, drills | | lobby | QoS servers, lobby presence | | settings | Clearance levels, feature flags | | configuration | API endpoint discovery | | banProcessor | Ban status queries | | textModeration | Text moderation keys |

WaypointClient

Client for Halo Waypoint APIs:

| Module | Description | |--------|-------------| | profile | User profiles and settings | | redemption | Code redemption | | content | News articles | | comms | Notifications |

Usage Examples

Get Player Service Record

const record = await client.stats.getPlayerServiceRecordByXuid(
  'xuid',
  LifecycleMode.Matchmade
);

if (isSuccess(record)) {
  const stats = record.result.stats?.coreStats;
  console.log(`K/D: ${stats?.kills}/${stats?.deaths}`);
}

Get Match Details

const match = await client.stats.getMatchStats('match-guid-here');

if (isSuccess(match)) {
  console.log(`Map: ${match.result.matchInfo?.mapVariant?.publicName}`);
  console.log(`Players: ${match.result.players?.length}`);
}

Get Player CSR

const csr = await client.skill.getPlaylistCsr(
  'playlist-guid',
  ['xuid']
);

if (isSuccess(csr)) {
  const playerCsr = csr.result.value?.[0];
  console.log(`CSR: ${playerCsr?.csr?.value} (${playerCsr?.csr?.tier})`);
}

Get Player Inventory

const inventory = await client.economy.getInventoryItems('xuid');

if (isSuccess(inventory)) {
  console.log(`Items owned: ${inventory.result.items?.length}`);
}

Search UGC Maps

import { AssetKind } from '@dendotdev/grunt';

const maps = await client.ugcDiscovery.search({
  assetKinds: [AssetKind.Map],
  term: 'blood gulch',
  count: 10,
});

if (isSuccess(maps)) {
  for (const map of maps.result.results ?? []) {
    console.log(`${map.publicName} by ${map.admin}`);
  }
}

Get News Articles (No Auth Required)

import { WaypointClient, isSuccess } from '@dendotdev/grunt';

const client = new WaypointClient(); // No auth needed

const articles = await client.content.getArticles(1, 10);

if (isSuccess(articles)) {
  for (const article of articles.result.articles ?? []) {
    console.log(article.title);
  }
}

Result Handling

All API methods return HaloApiResult<T> which contains:

  • result: The response data (or null on failure)
  • response: Raw response info (status code, headers, etc.)

Use the helper functions to check results:

import {
  isSuccess,      // 2xx status with data
  isNotModified,  // 304 (cached response valid)
  isClientError,  // 4xx errors
  isServerError,  // 5xx errors
} from '@dendotdev/grunt';

const result = await client.stats.getMatchStats('match-id');

if (isSuccess(result)) {
  // result.result is guaranteed non-null here
  console.log(result.result.matchId);
} else if (isClientError(result)) {
  console.error(`Client error: ${result.response.code}`);
} else if (isServerError(result)) {
  console.error(`Server error: ${result.response.code}`);
}

Configuration Options

HaloInfiniteClient Options

const client = new HaloInfiniteClient({
  // Required
  spartanToken: 'your-spartan-token',

  // Optional
  xuid: 'xuid',        // Your Xbox User ID
  clearanceToken: 'flight-id',     // For flighted/preview content
  includeRawResponses: true,       // Include full request/response in results
  userAgent: 'MyApp/1.0',          // Custom User-Agent header
  cacheTtlMs: 3600000,             // Cache TTL (default: 60 minutes)
  maxRetries: 3,                   // Retry attempts (default: 3)
});

Building from Source

Prerequisites

  • Node.js 18.0.0 or higher
  • npm

Install Dependencies

npm install

Build

npm run build

This creates the dist/ folder with:

  • index.js - CommonJS build
  • index.mjs - ES Module build
  • index.d.ts - TypeScript declarations

Development

# Watch mode (rebuild on changes)
npm run dev

# Type check without emitting
npm run typecheck

# Run tests
npm run test

Features

  • Type-safe: Full TypeScript support with comprehensive type definitions
  • Caching: Built-in ETag-based caching with configurable TTL
  • Retry Logic: Automatic retry with exponential backoff for transient failures
  • Lazy Loading: Modules are initialized on first access to minimize memory usage
  • Minimal Dependencies: Only one runtime dependency (lru-cache)
  • Universal: Works in Node.js and modern browsers (uses native fetch)

API Reference

For detailed API documentation, refer to the TypeScript type definitions included with the package, or explore the source code in the src/ directory.

The API mirrors the .NET Grunt library structure, so its documentation can also serve as a reference.

Disclaimer

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.

License

MIT License - see LICENSE for details.

Credits

  • Original Grunt project by Den Delimarsky
  • TypeScript implementation maintains API compatibility with the .NET version