Appearance
JavaScript
JavaScript applications can interact with the local Riot Client session either directly via in-process library imports (@valoranchi/riot-client) or by spawning the riotclient CLI as a child process.
Setup
Install the library in your Node.js project:
bash
npm install @valoranchi/riot-clientIf you plan to use the command-line interface directly or spawn it via child_process, install it globally:
bash
npm install -g @valoranchi/riot-clientRead
You can read player information and owned items either using the library directly or through the CLI.
Library (ESM)
Read the current player's profile and collection, printing the first owned skin for every weapon:
js
import { RiotClient } from "@valoranchi/riot-client";
const client = new RiotClient({ language: "en-US" });
try {
const player = await client.account.whoami();
console.log(`Signed in as: ${player.gameName}#${player.tagLine} (${player.region})`);
const collection = await client.account.ownedItems();
console.log(`Collection generated at: ${collection.generatedAt}`);
for (const weapon of collection.weapons) {
const firstSkin = weapon.skins[0];
if (firstSkin) {
console.log(`- ${weapon.name}: ${firstSkin.name}`);
}
}
} finally {
await client.close();
}Library (CommonJS)
In CommonJS projects, load the library via dynamic import() or invoke the CLI:
js
/* eslint-disable @typescript-eslint/no-require-imports */
const { execFile } = require("node:child_process");
const { promisify } = require("node:util");
const execFileAsync = promisify(execFile);
async function main() {
// Option A: Dynamic import of the ESM client library
const { RiotClient } = await import("@valoranchi/riot-client");
const client = new RiotClient({ language: "en-US" });
try {
const player = await client.account.whoami();
console.log(`[Library] Player: ${player.gameName}#${player.tagLine}`);
const collection = await client.account.ownedItems();
for (const weapon of collection.weapons) {
const firstSkin = weapon.skins[0];
if (firstSkin) {
console.log(`- ${weapon.name}: ${firstSkin.name}`);
}
}
} finally {
await client.close();
}
// Option B: Spawning the riotclient CLI from CommonJS
try {
const { stdout } = await execFileAsync("riotclient", ["whoami"]);
const cliPlayer = JSON.parse(stdout);
console.log(`[CLI] Player: ${cliPlayer.gameName}#${cliPlayer.tagLine}`);
} catch (err) {
// riotclient exits non-zero if client not running or failed
if (err && err.code === "ENOENT") {
console.error("riotclient CLI not found in PATH");
}
}
}
main().catch(console.error);Spawning the CLI with child_process
If you prefer to invoke the CLI binary from Node.js, spawn riotclient:
js
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const { stdout: whoamiOut } = await execFileAsync("riotclient", ["whoami"]);
const player = JSON.parse(whoamiOut);
console.log(`Player: ${player.gameName}#${player.tagLine} (${player.region})`);
const { stdout: collectionOut } = await execFileAsync("riotclient", [
"owned-items",
"--language",
"en-US",
]);
const collection = JSON.parse(collectionOut);
for (const weapon of collection.weapons) {
const firstSkin = weapon.skins[0];
if (firstSkin) {
console.log(`- ${weapon.name}: ${firstSkin.name}`);
}
}Write with Validation
Every mutation is validated before any HTTP call reaches Riot's servers.
Library Usage
The library provides validateEquip for dry runs and equip to apply mutations. In case of validation errors, a ValidationError is thrown with an error reason:
js
import { RiotClient, ValidationError } from "@valoranchi/riot-client";
const client = new RiotClient();
try {
const cardUuid = "0819fbcd-4bd4-c379-5384-52803440f2b2";
// 1. Dry run: validate locally without sending a mutation to Riot
console.log("Validating card equip (dry run)...");
const validatedBody = await client.account.validateEquip({ card: cardUuid });
console.log("Validation passed. Prepared payload card ID:", validatedBody.Identity.PlayerCardID);
// 2. Apply the mutation
console.log("Applying card equip...");
const updatedLoadout = await client.account.equip({ card: cardUuid });
console.log("Card equipped successfully for:", updatedLoadout.player.gameName);
} catch (error) {
if (error instanceof ValidationError) {
console.error(`Validation failed [${error.code}]: reason=${error.reason}`);
console.error("Message:", error.message);
if (error.details) {
console.error("Details:", error.details);
}
} else {
console.error("Unexpected error:", error);
}
} finally {
await client.close();
}CLI Usage via child_process
When using the CLI, omit --yes to perform a dry run. Add --yes to apply the update. If validation fails, the process exits with code 6 and details are written to stderr:
js
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const cardUuid = "0819fbcd-4bd4-c379-5384-52803440f2b2";
// 1. Dry run (default)
const { stdout: dryRunOut } = await execFileAsync("riotclient", ["equip", "--card", cardUuid]);
console.log("Dry run payload prepared:", JSON.parse(dryRunOut).Identity.PlayerCardID);
// 2. Execute write with --yes
try {
const { stdout } = await execFileAsync("riotclient", ["equip", "--card", cardUuid, "--yes"]);
console.log("Write executed successfully:", JSON.parse(stdout));
} catch (error) {
if (error.code === 6 || error.status === 6) {
const errPayload = JSON.parse(error.stderr);
console.error(`Validation failed with reason: ${errPayload.error.reason}`);
console.error("Message:", errPayload.error.message);
} else {
throw error;
}
}Events
Subscribe to real-time WebSocket events from the local client.
Library Emitter
Connect to the live WebSocket stream and listen for friends and messages:
js
import { RiotClient } from "@valoranchi/riot-client";
const client = new RiotClient();
const events = client.events();
events.on("connected", () => {
console.log("Connected to local Riot Client WebSocket");
});
events.on("disconnected", () => {
console.log("Disconnected from local Riot Client WebSocket");
});
events.on("friend:presence", ({ friend, change }) => {
console.log(
`Friend ${friend.gameName}#${friend.tagLine} is now ${change} (${friend.presence.state})`,
);
});
events.on("message", (msg) => {
console.log(`[${msg.from.gameName}]: ${msg.body}`);
});
console.log("Listening for real-time events. Press Ctrl+C to stop.");
process.on("SIGINT", async () => {
console.log("\nClosing connection...");
await client.close();
process.exit(0);
});Streaming CLI Output (watch)
Spawn riotclient watch and consume events line-by-line using readline:
js
import { spawn } from "node:child_process";
import readline from "node:readline";
const child = spawn("riotclient", ["watch", "--only", "friend:presence,message"]);
const rl = readline.createInterface({ input: child.stdout });
rl.on("line", (line) => {
if (!line.trim()) return;
const event = JSON.parse(line);
if (event.event === "friend:presence") {
const { friend, change } = event.data;
console.log(`Presence update: ${friend.gameName} is now ${change} (${friend.presence.state})`);
} else if (event.event === "message") {
const msg = event.data;
console.log(`[${msg.from.gameName}]: ${msg.body}`);
}
});
process.on("SIGINT", () => {
child.kill();
process.exit(0);
});Types
Although JavaScript is dynamically typed, you can use JSDoc annotations or TypeScript definitions exported by the package for full autocompletion in editors such as VS Code.
Alternatively, you can generate JavaScript definitions and JSDoc typedefs from the JSON schemas using quicktype:
bash
# Generate JavaScript models with JSDoc typing
npx quicktype schema/OwnedItems.json --src-lang schema -l javascript -o OwnedItems.jsUse the generated classes to parse CLI output:
js
import { toOwnedItems } from "./OwnedItems.js";
const { stdout } = await execFileAsync("riotclient", ["owned-items"]);
const collection = toOwnedItems(stdout);
console.log(`Loaded collection for ${collection.player.gameName}`);