hexgate
v0.14.8
Published
API wrapper for the League of Legends LCU
Downloads
56
Maintainers
Readme
Hexgate
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