irdata

package module
v0.6.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 12, 2025 License: MIT Imports: 23 Imported by: 0

README

irdata

CI Integration Tests

A Go module for simplified access to the iRacing /data API.

Features

  • Modern Authentication: Supports iRacing's OAuth2 Password Limited Flow
  • Simplified Management: Handles token acquisition, masking, and credential encryption locally.
  • Transparent Data Fetching: Follows and dereferences iRacing's S3 links transparently.
  • Automatic Chunk Merging: If an endpoint returns chunked data, irdata fetches all chunks and merges them into a single object.
  • Caching Layer: An optional disk-based cache to minimize API calls.
  • Resiliency: Built-in support for automatic retries on server errors (5xx) and configurable handling for rate limits (429).

Installation

go get github.com/popmonkey/irdata

Quick Start

The following is a complete example of setting up the client, authenticating, and fetching data with caching enabled.

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "time"

    "github.com/popmonkey/irdata"
)

func main() {
    // Create a new client instance.
    api := irdata.Open(context.Background())
    defer api.Close()

    // Authenticate using credentials stored in a file.
    err := api.AuthWithCredsFromFile("path/to/my.key", "path/to/my.creds")
    if err != nil {
        log.Fatalf("Authentication failed: %v", err)
    }
    
    // Enable the cache
    if err := api.EnableCache(".ir-cache"); err != nil {
        log.Fatalf("Failed to enable cache: %v", err)
    }

    // Fetch data from an API endpoint (member info for these creds)
    jsonData, err := api.GetWithCache("/data/member/info", 15*time.Minute)
    if err != nil {
        log.Fatalf("Failed to get member info: %v", err)
    }

    // Unmarshal the JSON response into a struct.
    var memberInfo struct {
        DisplayName string `json:"display_name"`
        CustomerID  int    `json:"cust_id"`
    }

    if err := json.Unmarshal(jsonData, &memberInfo); err != nil {
        log.Fatalf("Failed to parse JSON: %v", err)
    }

    fmt.Printf("Successfully fetched data for %s (Customer ID: %d)\n",
        memberInfo.DisplayName, memberInfo.CustomerID)
}

Authentication

The library uses the OAuth2 Password Limited Flow. This requires you to register your "headless" client with iRacing support to obtain a Client ID and Client Secret, in addition to your standard iRacing username and password.

See: https://oauth.iracing.com/oauth2/book/password_limited_flow.html

[!IMPORTANT] Migration Note: If you used previous versions of irdata, your existing .creds files are incompatible. You must delete them and regenerate them using the new flow to include your Client ID and Secret.

You can store credentials in a file encrypted with a key file. First, generate a key file.

Create the Key File

The key must be a random string of 16, 24, or 32 bytes, base64 encoded, and stored in a file with user-only read permissions (0400).

# Example for Linux or macOS
openssl rand -base64 32 > ~/my.key && chmod 0400 ~/my.key

[!WARNING] Do not commit your key file or credentials file to version control.

The irfetch command-line utility is included with releases, or can be built from source with make. It is the easiest way to generate your credentials file.

If the credentials file does not exist, irfetch will automatically prompt you for your iRacing credentials (Username, Password, Client ID, and Client Secret), and then create the encrypted file for you.

To generate the credentials file without fetching any data, use the -a (auth and stop) flag. You must still provide a valid API endpoint like /data/doc as the final argument.

# This will prompt for credentials and save them to ~/my.creds, then exit.
irfetch -a ~/my.key ~/my.creds /data/doc

After the my.creds file is created, your application can use api.AuthWithCredsFromFile("~/my.key", "~/my.creds") to authenticate without needing any interactive prompts.

Save and Load Credentials

Use the key file to save your credentials once. The helper CredsFromTerminal will prompt you for your Username, Password, Client ID, and Client Secret.

// Define file paths for the key and encrypted credentials
keyFile := "my.key"
credsFile := "my.creds"

// First-time setup to save credentials from terminal input
var credsProvider irdata.CredsFromTerminal
err := api.AuthAndSaveProvidedCredsToFile(keyFile, credsFile, credsProvider)
if err != nil {
    log.Fatalf("Failed to save credentials: %v", err)
}

// In subsequent runs, you can authenticate directly from the file
err = api.AuthWithCredsFromFile(keyFile, credsFile)
if err != nil {
    log.Fatalf("Failed to auth from file: %v", err)
}
Auth Token Persistence (Optional)

To reduce the number of full login requests, you can persist the OAuth2 token to a file. The library will attempt to reuse or refresh this token before falling back to full credentials authentication.

The token file is encrypted using the same key file as your credentials, so this feature works in tandem with AuthWithCredsFromFile.

// Configure the persistence file
api.SetAuthTokenFile("my.token")

// Authenticate using the encrypted credentials.
// The library will check "my.token" first.
err := api.AuthWithCredsFromFile("my.key", "my.creds")
Programmatic Credentials

You can provide credentials directly in your code by implementing the CredsProvider interface.

type MyCredsProvider struct{}

func (p MyCredsProvider) GetCreds() ([]byte, []byte, []byte, []byte, error) {
    username := "[email protected]"
    password := "your_password"
    clientId := "your_client_id"
    clientSecret := "your_client_secret"
    
    return []byte(username), []byte(password), []byte(clientId), []byte(clientSecret), nil
}

var provider MyCredsProvider
err := api.AuthWithProvideCreds(provider)
if err != nil {
    log.Fatalf("Auth failed: %v", err)
}
Terminal Prompt

To prompt for credentials from the terminal interactively:

var provider irdata.CredsFromTerminal
err := api.AuthWithProvideCreds(provider)
if err != nil {
    log.Fatalf("Auth failed: %v", err)
}

API Usage

Once authenticated, use Get() or GetWithCache() to access API endpoints.

Basic Fetch

Get() retrieves data directly from the iRacing API.

// Get member info
data, err := api.Get("/data/member/info")
if err != nil {
    // handle error
}
// Unmarshal and use data...
Cached Fetch

The iRacing API has a rate limit. Using the cache is highly recommended to avoid interruptions.

First, enable the cache. This should only be done once.

err := api.EnableCache(".cache")
if err != nil {
    // handle error
}

Then use GetWithCache() which first checks the cache for the requested data. On a cache hit, it returns the cached data immediately. On a cache miss, it calls the main API, returns the result, and populates the cache with the new data for the specified time-to-live (TTL) duration.

// This call will hit the iRacing API only if the data is not in the cache
// or if the cached data is older than 15 minutes.
data, err := api.GetWithCache("/data/member/info", 15*time.Minute)

Handling Chunked Responses

Some API endpoints (e.g., /data/results/search_series) return large datasets in chunks. irdata automatically detects this, fetches all chunks, and merges the results into a new _chunk_data field in the JSON response.

For a response that originally contains a chunk_info block, irdata adds the _chunk_data array containing the merged content from all chunks.

{
  "some_other_data": "value",
  "chunk_info": {
    "num_chunks": 2,
    "base_download_url": "...",
    "chunk_file_names": ["chunk_0.json", "chunk_1.json"]
  },
  "_chunk_data": [
    { "result_id": 1 },
    { "result_id": 2 }
  ]
}

Some API endpoints return a link to data stored on S3 instead of the data itself. irdata automatically follows these links and returns the data from the S3 bucket.

If you need to know which S3 link is being followed for a given request, you can set a callback function. This is useful for debugging or logging.

// Set a callback to print any S3 link that is followed.
api.SetS3LinkCallback(func(link string) {
    fmt.Printf("Following S3 link: %s\n", link)
})

// When you make a request that returns an S3 link, the callback will be invoked.
data, err := api.Get("/data/some_endpoint_with_s3_link")

Error Handling

Rate Limit Management

irdata can handle rate limits in two ways. You can change the behavior with SetRateLimitHandler().

Return an Error (Default)

By default, Get() or GetWithCache() will return an irdata.RateLimitExceededError if the rate limit is hit. You can check for this specific error to handle it gracefully. This error includes a timestamp value which is the reset time after which iRacing will no longer rate limit you.

import "errors"
// ...

data, err := api.Get("/data/member/info")
if err != nil {
    var rateLimitErr *irdata.RateLimitExceededError
    if errors.As(err, &rateLimitErr) {
        fmt.Printf("Rate limit exceeded. Please wait until %v to retry.\n", rateLimitErr.ResetTime)
    } else {
        // Handle other errors
        log.Fatal(err)
    }
}
Wait and Continue

Alternatively, configure irdata to pause and automatically retry the request after the rate limit resets. In this mode, the call will block until it succeeds.

api.SetRateLimitHandler(irdata.RateLimitWait)

// This call will now block and wait if the rate limit is hit
// instead of returning an error.
data, err := api.Get("/data/member/info")
Automatic Retries

For server-side errors (HTTP 5xx status codes), irdata will automatically retry the request with an increasing backoff period. You can configure the number of retries. The default is 0 (no retries):

// Set the number of retries to 10
api.SetRetries(10)

Logging

irdata uses logrus for logging. By default, only errors are logged but more detailed logging can be enabled.

api.SetLogLevel(irdata.LogLevelInfo)

Development

Clone the repository:

git clone [email protected]:popmonkey/irdata.git

[!NOTE] Key files included in the repository for testing must have their permissions set to 0400.

chmod 0400 testdata/test.key

Run tests:

# Run standard tests (no API calls)
go test .

# Run integration tests against the live iRacing API
# Requires valid key and creds files created beforehand
IRDATA_TEST_KEY_FILE=/path/to/key.file \
IRDATA_TEST_CREDS_FILE=/path/to/creds.file \
go test -tags=integration -v .

For CI environments (see .github/workflows/integration.yml), you can alternatively provide the base64-encoded content of the key and credentials files using IRDATA_TEST_KEY_DATA and IRDATA_TEST_CREDS_DATA.

Documentation

Overview

Package irdata provides simplified access to the [iRacing /data API]

  • Authentication is handled internally and credentials can be saved in an encrypted credentials file protected by a secure key file.
  • The iRacing /data API returns data in the form of S3 links. This package delivers the S3 results directly handling all the redirection.
  • If an API endpoint returns chunked data, irdata will handle the chunk fetching and return a merged object (note, it could be *huge*)
  • An optional caching layer is provided to minimize direct calls to the /data endpoints themselves as those are rate limited.

[iRacing /data API] https://forums.iracing.com/discussion/15068/general-availability-of-data-api/p1

Index

Constants

View Source
const ChunkDataKey = "_chunk_data"

Variables

View Source
var TokenURL = "https://oauth.iracing.com/oauth2/token"

Functions

This section is empty.

Types

type AuthTokenT added in v0.6.3

type AuthTokenT struct {
	AccessToken  string
	RefreshToken string
	TokenExpiry  time.Time
	ClientID     string
	ClientSecret string
}

type CredsFromTerminal

type CredsFromTerminal struct{}

func (CredsFromTerminal) GetCreds

func (CredsFromTerminal) GetCreds() ([]byte, []byte, []byte, []byte, error)

CredsFromTerminal can be used with any of the SetCreds* functions and will prompt for iRacing credentials (username, password, clientID, clientSecret) from the terminal.

type CredsProvider

type CredsProvider interface {
	// GetCreds returns username, password, clientID, and clientSecret
	GetCreds() ([]byte, []byte, []byte, []byte, error)
}

type Irdata

type Irdata struct {

	// Auth Data used for Refreshing
	AccessToken  string
	RefreshToken string
	TokenExpiry  time.Time
	ClientID     string
	ClientSecret string

	// S3 Link callback
	S3LinkCallback func(link string)
	// contains filtered or unexported fields
}

func Open

func Open(ctx context.Context) *Irdata

func (*Irdata) AuthAndSaveProvidedCredsToFile added in v0.4.3

func (i *Irdata) AuthAndSaveProvidedCredsToFile(keyFilename string, authFilename string, authSource CredsProvider) error

AuthAndSaveProvidedCredsToFile calls the provided function for the credentials, verifies auth, and then saves them to authFilename using the key in keyFilename

func (*Irdata) AuthWithCredsFromFile

func (i *Irdata) AuthWithCredsFromFile(keyFilename string, authFilename string) error

AuthWithCredsFromFile loads the username and password from a file at authFilename and encrypted with the key in keyFilename.

func (*Irdata) AuthWithProvideCreds

func (i *Irdata) AuthWithProvideCreds(authSource CredsProvider) error

AuthWithProvideCreds calls the provided function for the credentials

func (*Irdata) Close

func (i *Irdata) Close()

Close Calling Close when done is important when using caching - this will compact the cache.

func (*Irdata) EnableCache

func (i *Irdata) EnableCache(cacheDir string) error

EnableCache enables on the optional caching layer which will use the directory path provided as cacheDir

func (*Irdata) Get

func (i *Irdata) Get(uri string) ([]byte, error)

Get returns the result value for the uri provided (e.g. "/data/member/info")

The value returned is a JSON byte array and a potential error.

func (*Irdata) GetWithCache

func (i *Irdata) GetWithCache(uri string, ttl time.Duration) ([]byte, error)

GetWithCache will first check the local cache for an unexpired result and will the call Get with the uri provided.

The ttl defines for how long the results should be cached.

You must call EnableCache before calling GetWithCache NOTE: If data is fetched this will return the data even if it can't be written to the cache (along with an error)

func (*Irdata) SetAuthTokenFile added in v0.6.3

func (i *Irdata) SetAuthTokenFile(filename string)

SetAuthTokenFile sets the filename where the authentication token will be persisted. The token is encrypted using the same key as the credentials file.

func (*Irdata) SetLogLevel added in v0.4.0

func (i *Irdata) SetLogLevel(logLevel LogLevel)

SetLogLevel sets the loging level using the logrus module

func (*Irdata) SetRateLimitHandler added in v0.4.6

func (i *Irdata) SetRateLimitHandler(handler RateLimitHandler)

SetRateLimitHandler sets the desired behavior for handling API rate limits. The default is RateLimitError.

func (*Irdata) SetRetries added in v0.4.6

func (i *Irdata) SetRetries(retries int)

SetRetries sets the number of times a get will be retried if a retriable error is encountered (e.g. a 5xx)

func (*Irdata) SetS3LinkCallback added in v0.6.2

func (i *Irdata) SetS3LinkCallback(callback func(link string))

SetS3LinkCallback sets a callback function to be invoked when an S3 link is identified and followed.

type LogLevel added in v0.4.0

type LogLevel int8
const (
	LogLevelFatal LogLevel = iota
	LogLevelError LogLevel = iota
	LogLevelWarn  LogLevel = iota
	LogLevelInfo  LogLevel = iota
	LogLevelDebug LogLevel = iota
)

type RateLimitExceededError added in v0.4.6

type RateLimitExceededError struct {
	ResetTime time.Time
}

RateLimitExceededError is returned when the iRacing API rate limit has been exceeded. It includes the time when the rate limit is expected to reset.

func (*RateLimitExceededError) Error added in v0.4.6

func (e *RateLimitExceededError) Error() string

type RateLimitHandler added in v0.4.6

type RateLimitHandler int

RateLimitHandler defines the behavior when a rate limit is encountered.

const (
	// RateLimitError (default) will cause Get methods to return a RateLimitExceededError.
	RateLimitError RateLimitHandler = iota
	// RateLimitWait will cause the Get method to pause and wait until the rate limit resets.
	RateLimitWait
)

type TokenResponse added in v0.6.0

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
	Scope        string `json:"scope"`
}

TokenResponse maps the JSON response from the /token endpoint

Directories

Path Synopsis
examples
irfetch command
profile command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL