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 🙏

© 2024 – Pkg Stats / Ryan Hefner

hexgate

v0.14.8

Published

API wrapper for the League of Legends LCU

Downloads

69

Readme

Hexgate

Discord Codacy Badge Release License npm (scoped)

Hexgate is a work-in-progress LCU suite. It is not endorsed by Riot Games. You can find out more about what that means here. Thank you Riot ❤️ for providing a public API for us to play with. If you have any questions, feel free to join the cuppachino discord.

Please refer to the wiki for more info.

Installation

Add it to your own project using your favorite package manager.

pnpm add hexgate
npm i hexgate
yarn add hexgate

ESM

import { ... } from "hexgate"

CJS

import hexgate = require("hexgate")
const { ... } = hexgate

Authentication

Wait for the client by passing the auth function to the poll utility.

import { auth, poll } from "hexgate"

const credentials = await poll(auth)

Opt-out of safe authentication by explicity passing an undefined certifcate.

const unsafeCredentials = await auth({ certificate: undefined })

Once you have the credentials, you can create a new Hexgate and LcuClient.

import { Hexgate as HttpsClient, LcuClient as WsClient } from "hexgate"

const httpsClient = new HttpsClient(credentials)
const websocketClient = new WsClient(credentials)

Working with multiple clients? Get get all credentials.

import { auth, createHexgate, createLcuClient, poll, zip } from 'hexgate'

const credentials = await poll(() => auth({ all: true }))

// ~ some multi-connection interface
const clients = new Set(
  zip(
    credentials.map(createHexgate),
    credentials.map(createLcuClient)
  )
)

Builder API

The simplest way of getting started is to ".build" a request function. The builder uses generics to infer the parameters and return type of the request.

import { Hexgate as HttpsClient } from 'hexgate'

const https = new HttpsClient(credentials)

// (arg: string[], init?: any) => Promise<ApiResponse<{ ... }>>
const getSummonersFromNames = https
  .build('/lol-summoner/v2/summoners/names')
  .method('post')
  .create()

const summoner = await getSummonersByName(['dubbleignite'])
console.log(summoner.data)

Websocket Events

Subscribe to LCU events through the client.

import { LcuClient as WsClient } from 'hexgate'

const ws = new WsClient(credentials)

ws.subscribe(
  'OnJsonApiEvent_lol-champ-select_v1_session',
  ({ data, eventType, uri }) => {
    // side effects
  }
)

Note: Since many endpoints will subscribe you to multiple uris, its difficult to provide meaningful type inference for the data property. Import LcuComponents type when necessary and/or open a PR to improve typings - which would be greatly appreciated! I'm just improving types as I need them.

⚡️ Connection

The Connection class further abstracts Hexgate & LcuClient and handles authentication between client shutdowns. Configuration is optional.

import { Connection } from 'hexgate'

const client = new Connection({
  // Recipe API (createRecipe or recipe)
  createRecipe({ build, unwrap }) {
    return {
      getCurrentSummoner: unwrap(
        build('/lol-summoner/v1/current-summoner').method('get').create()
      )
    }
  },
  // Propagate status to browser windows.
  onStatusChange(status) {
    emit('status', status)
  },
  // Init
  async onConnect(con) {
    con.ws.subscribe('OnJsonApiEvent_lol-champ-select_v1_session', handleChampSelect)
    const summoner = await con.recipe.getCurrentSummoner()
    con.logger.info(summoner, `Welcome, ${summoner.displayName}`)
  },
  // Automatically reconnect
  async onDisconnect(discon) {
    await sleep(4000)
    discon.connect()
  },
  // Authentication interval
  interval: 2000,
  // Bring any logger
  logger: pino({
    name: 'main' as const
  })
})

client.connect()

The Connection class supports recipes, define a recipe: Recipe or a createRecipe: RecipeApiFn method in the ConnectionConfig constructor argument.

import { Connection, createRecipe } from 'hexgate'

const recipe = createRecipe(({ build }) => ({/*...*/}))
const client = new Connection({
  recipe
})
import { Connection } from 'hexgate'

const client = new Connection({
  createRecipe({ build }) { return {/*...*/} }
})

Recipe API

createRecipe is a higher-order function for transforming a request's parameters and response. It is a useful tool for morphing the LCU's API into your own. There are several ways to use the functions provided by the callback, and we'll take a look at each one.

Intro

Step 1: Create a recipe

This is identical to the builder API, except the request function isn't built until a hexgate instance is given to the recipe. This is useful for modeling requests ahead of time for usage in other places.

import { createRecipe } from "hexgate"

/**
 * <T extends HttpsClient>(httpsClient: T) => 
 *   (arg: string[], init?: RequestInit) => 
 *     Promise<ApiResponse<{...}>>
 */
const getSummonersFromNamesRecipe = createRecipe(({ build }) =>
  build('/lol-summoner/v2/summoners/names')
    .method('post')
    .create()
)

Step 2: Once you have a recipe, you just need to pass it a Hexgate.

const getSummonersFromNames = getSummonersFromNamesRecipe(httpsClient)

const summoners = await getSummonersFromNames(['dubbleignite'])
console.table(summoners.data)

🦋 Transforming requests

Use wrap, from, to, and unwrap to design your api.

const summonersRecipe = createRecipe(({ build, wrap, from, to, unwrap }) => ({
  getSummoners: {
    /**
     * Default for reference.
     * (arg: { ids?: string; }, init?: RequestInit) => Promise<ApiResponse<{...}>>
     */
    v2SummonersDefault: build('/lol-summoner/v2/summoners')
      .method('get')
      .create(),

    /**
     * unwrap extracts the data property from an ApiResponse.
     * (arg: { ids?: string }, init?: RequestInit) => Promise<{...}>
     */
    v2SummonersAwaited: unwrap(
      build('/lol-summoner/v2/summoners').method('get').create(),
    ),

    /**
     * wrap let's us overwrite the parameters type by supplying conversion functions.
     * (summonerIds: (number | `${number}`)[], init?: RequestInit | undefined) => Promise<{...}>
     */
    fromSummonerIds: wrap(
      build('/lol-summoner/v2/summoners').method('get').create(),
    )({
      // The return type of `from` is constrained by the expected return type of the function being wrapped.
      from(summonerIds: Array<`${number}` | number>, init?) {
        return [{ ids: JSON.stringify(summonerIds) }, init];
      },
      // awaits data similarly to `unwrap`
      to,
    }),
  },
}));

⚒️ Recipe, RecipeApiFn, and CreateWithRecipe

Some features have options that accept a Recipe, the product of createRecipe, or a RecipeApiFn, the api argument expected by createRecipe. You can achieve similar functionality in your own code by extending CreateWithRecipe or implementing its overloaded constructor signature.

import type { CreateWithRecipe } from 'hexgate'

class Foo<T> extends CreateWithRecipe<T> {}
new Foo(recipe)
new Foo((recipeApi) => "your recipe" as const)

Exporting recipes

If you want to export a recipe, you might get a type error. This is because the return type of createRecipe is inferred with references to @cuppachino/openapi-fetch and node-fetch-commonjs. To fix this, install the packages as dev dependencies and apply one of the following solutions to your tsconfig.json:

Map paths (Recommended)

Use this option if you are making a library.

{
  "compilerOptions": {
    "paths": {
      "@cuppachino/openapi-fetch": ["./node_modules/@cuppachino/openapi-fetch"],
      "node-fetch-commonjs": ["./node_modules/node-fetch-commonjs"]
    }
  }
}

Add types to the global scope (apps)

This can be used in applications, but it's not recommended.

{
  "compilerOptions": {
    "types": ["@cuppachino/openapi-fetch", "node-fetch-commonjs"]
  }
}

Additional features

LcuValue

The LcuValue class implements Update and CreateWithRecipe. It's useful for caching data retrieved from the LCU.

import { Connection, LcuValue, type OperationResponses } from 'hexgate'

type LolOwnedChampionsMinimal =
  OperationResponses<'GetLolChampionsV1OwnedChampionsMinimal'>

class ChampionLookup extends LcuValue<LolOwnedChampionsMinimal> {
  constructor() {
    super(({ build, unwrap }) =>
      unwrap(
        build('/lol-champions/v1/owned-champions-minimal')
          .method('get')
          .create()
      )
    )
  }

  championById(id: string | number | undefined) {
    return this.inner?.find((c) => c.id === Number(id ?? 0))
  }
}

const champions = new ChampionLookup()

const client = new Connection({
  async onConnect(con) {
    await champions.update(con.https)
    con.logger.info(
    champions.championById(1) satisfies
        | Partial<LolOwnedChampionsMinimal>[number]
    )
  }
})

client.connect()

Development

This package uses pnpm to manage dependencies. If you don't have pnpm, it can be installed globally using npm, yarn, brew, or scoop, as well as some other options. Check out the pnpm documentation for more information.

pnpm i